| /* |
| * CDDL HEADER START |
| * |
| * The contents of this file are subject to the terms of the |
| * Common Development and Distribution License (the "License"). |
| * You may not use this file except in compliance with the License. |
| * |
| * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE |
| * or http://www.opensolaris.org/os/licensing. |
| * See the License for the specific language governing permissions |
| * and limitations under the License. |
| * |
| * When distributing Covered Code, include this CDDL HEADER in each |
| * file and include the License file at usr/src/OPENSOLARIS.LICENSE. |
| * If applicable, add the following below this CDDL HEADER, with the |
| * fields enclosed by brackets "[]" replaced with your own identifying |
| * information: Portions Copyright [yyyy] [name of copyright owner] |
| * |
| * CDDL HEADER END |
| */ |
| /* |
| * Copyright 2006 Sun Microsystems, Inc. All rights reserved. |
| * Use is subject to license terms. |
| */ |
| |
| /* |
| * nfs_tbind.c, common part for nfsd and lockd. |
| */ |
| |
| #pragma ident "%Z%%M% %I% %E% SMI" |
| |
| #include <tiuser.h> |
| #include <fcntl.h> |
| #include <netconfig.h> |
| #include <stropts.h> |
| #include <errno.h> |
| #include <syslog.h> |
| #include <rpc/rpc.h> |
| #include <sys/time.h> |
| #include <sys/resource.h> |
| #include <signal.h> |
| #include <netdir.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <netinet/tcp.h> |
| #include <malloc.h> |
| #include <stdlib.h> |
| #include "nfs_tbind.h" |
| #include <nfs/nfs.h> |
| #include <nfs/nfs_acl.h> |
| #include <nfs/nfssys.h> |
| #include <nfs/nfs4.h> |
| #include <zone.h> |
| #include <sys/socket.h> |
| #include <tsol/label.h> |
| |
| /* |
| * Determine valid semantics for most applications. |
| */ |
| #define OK_TPI_TYPE(_nconf) \ |
| (_nconf->nc_semantics == NC_TPI_CLTS || \ |
| _nconf->nc_semantics == NC_TPI_COTS || \ |
| _nconf->nc_semantics == NC_TPI_COTS_ORD) |
| |
| #define BE32_TO_U32(a) \ |
| ((((ulong_t)((uchar_t *)a)[0] & 0xFF) << (ulong_t)24) | \ |
| (((ulong_t)((uchar_t *)a)[1] & 0xFF) << (ulong_t)16) | \ |
| (((ulong_t)((uchar_t *)a)[2] & 0xFF) << (ulong_t)8) | \ |
| ((ulong_t)((uchar_t *)a)[3] & 0xFF)) |
| |
| /* |
| * Number of elements to add to the poll array on each allocation. |
| */ |
| #define POLL_ARRAY_INC_SIZE 64 |
| |
| /* |
| * Number of file descriptors by which the process soft limit may be |
| * increased on each call to nofile_increase(0). |
| */ |
| #define NOFILE_INC_SIZE 64 |
| |
| struct conn_ind { |
| struct conn_ind *conn_next; |
| struct conn_ind *conn_prev; |
| struct t_call *conn_call; |
| }; |
| |
| struct conn_entry { |
| bool_t closing; |
| struct netconfig nc; |
| }; |
| |
| /* |
| * this file contains transport routines common to nfsd and lockd |
| */ |
| static int nofile_increase(int); |
| static int reuseaddr(int); |
| static int recvucred(int); |
| static int anonmlp(int); |
| static void add_to_poll_list(int, struct netconfig *); |
| static char *serv_name_to_port_name(char *); |
| static int bind_to_proto(char *, char *, struct netbuf **, |
| struct netconfig **); |
| static int bind_to_provider(char *, char *, struct netbuf **, |
| struct netconfig **); |
| static void conn_close_oldest(void); |
| static boolean_t conn_get(int, struct netconfig *, struct conn_ind **); |
| static void cots_listen_event(int, int); |
| static int discon_get(int, struct netconfig *, struct conn_ind **); |
| static int do_poll_clts_action(int, int); |
| static int do_poll_cots_action(int, int); |
| static void remove_from_poll_list(int); |
| static int set_addrmask(int, struct netconfig *, struct netbuf *); |
| static int is_listen_fd_index(int); |
| |
| static struct pollfd *poll_array; |
| static struct conn_entry *conn_polled; |
| static int num_conns; /* Current number of connections */ |
| int (*Mysvc4)(int, struct netbuf *, struct netconfig *, int, |
| struct netbuf *); |
| |
| /* |
| * Called to create and prepare a transport descriptor for in-kernel |
| * RPC service. |
| * Returns -1 on failure and a valid descriptor on success. |
| */ |
| int |
| nfslib_transport_open(struct netconfig *nconf) |
| { |
| int fd; |
| struct strioctl strioc; |
| |
| if ((nconf == (struct netconfig *)NULL) || |
| (nconf->nc_device == (char *)NULL)) { |
| syslog(LOG_ERR, "no netconfig device"); |
| return (-1); |
| } |
| |
| /* |
| * Open the transport device. |
| */ |
| fd = t_open(nconf->nc_device, O_RDWR, (struct t_info *)NULL); |
| if (fd == -1) { |
| if (t_errno == TSYSERR && errno == EMFILE && |
| (nofile_increase(0) == 0)) { |
| /* Try again with a higher NOFILE limit. */ |
| fd = t_open(nconf->nc_device, O_RDWR, |
| (struct t_info *)NULL); |
| } |
| if (fd == -1) { |
| syslog(LOG_ERR, "t_open %s failed: t_errno %d, %m", |
| nconf->nc_device, t_errno); |
| return (-1); |
| } |
| } |
| |
| /* |
| * Pop timod because the RPC module must be as close as possible |
| * to the transport. |
| */ |
| if (ioctl(fd, I_POP, 0) < 0) { |
| syslog(LOG_ERR, "I_POP of timod failed: %m"); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| /* |
| * Common code for CLTS and COTS transports |
| */ |
| if (ioctl(fd, I_PUSH, "rpcmod") < 0) { |
| syslog(LOG_ERR, "I_PUSH of rpcmod failed: %m"); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| strioc.ic_cmd = RPC_SERVER; |
| strioc.ic_dp = (char *)0; |
| strioc.ic_len = 0; |
| strioc.ic_timout = -1; |
| |
| /* Tell rpcmod to act like a server stream. */ |
| if (ioctl(fd, I_STR, &strioc) < 0) { |
| syslog(LOG_ERR, "rpcmod set-up ioctl failed: %m"); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| /* |
| * Re-push timod so that we will still be doing TLI |
| * operations on the descriptor. |
| */ |
| if (ioctl(fd, I_PUSH, "timod") < 0) { |
| syslog(LOG_ERR, "I_PUSH of timod failed: %m"); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| return (fd); |
| } |
| |
| static int |
| nofile_increase(int limit) |
| { |
| struct rlimit rl; |
| |
| if (getrlimit(RLIMIT_NOFILE, &rl) == -1) { |
| syslog(LOG_ERR, "getrlimit of NOFILE failed: %m"); |
| return (-1); |
| } |
| |
| if (limit > 0) |
| rl.rlim_cur = limit; |
| else |
| rl.rlim_cur += NOFILE_INC_SIZE; |
| |
| if (rl.rlim_cur > rl.rlim_max && |
| rl.rlim_max != RLIM_INFINITY) |
| rl.rlim_max = rl.rlim_cur; |
| |
| if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { |
| syslog(LOG_ERR, "setrlimit of NOFILE to %d failed: %m", |
| rl.rlim_cur); |
| return (-1); |
| } |
| |
| return (0); |
| } |
| |
| int |
| nfslib_bindit(struct netconfig *nconf, struct netbuf **addr, |
| struct nd_hostserv *hs, int backlog) |
| { |
| int fd; |
| struct t_bind *ntb; |
| struct t_bind tb; |
| struct nd_addrlist *addrlist; |
| struct t_optmgmt req, resp; |
| struct opthdr *opt; |
| char reqbuf[128]; |
| bool_t use_any = FALSE; |
| bool_t gzone = TRUE; |
| |
| if ((fd = nfslib_transport_open(nconf)) == -1) { |
| syslog(LOG_ERR, "cannot establish transport service over %s", |
| nconf->nc_device); |
| return (-1); |
| } |
| |
| addrlist = (struct nd_addrlist *)NULL; |
| |
| /* nfs4_callback service does not used a fieed port number */ |
| |
| if (strcmp(hs->h_serv, "nfs4_callback") == 0) { |
| tb.addr.maxlen = 0; |
| tb.addr.len = 0; |
| tb.addr.buf = 0; |
| use_any = TRUE; |
| gzone = (getzoneid() == GLOBAL_ZONEID); |
| } else if (netdir_getbyname(nconf, hs, &addrlist) != 0) { |
| |
| syslog(LOG_ERR, |
| "Cannot get address for transport %s host %s service %s", |
| nconf->nc_netid, hs->h_host, hs->h_serv); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| if (strcmp(nconf->nc_proto, "tcp") == 0) { |
| /* |
| * If we're running over TCP, then set the |
| * SO_REUSEADDR option so that we can bind |
| * to our preferred address even if previously |
| * left connections exist in FIN_WAIT states. |
| * This is somewhat bogus, but otherwise you have |
| * to wait 2 minutes to restart after killing it. |
| */ |
| if (reuseaddr(fd) == -1) { |
| syslog(LOG_WARNING, |
| "couldn't set SO_REUSEADDR option on transport"); |
| } |
| } else if (strcmp(nconf->nc_proto, "udp") == 0) { |
| /* |
| * In order to run MLP on UDP, we need to handle creds. |
| */ |
| if (recvucred(fd) == -1) { |
| syslog(LOG_WARNING, |
| "couldn't set SO_RECVUCRED option on transport"); |
| } |
| } |
| |
| /* |
| * Make non global zone nfs4_callback port MLP |
| */ |
| if (use_any && is_system_labeled() && !gzone) { |
| if (anonmlp(fd) == -1) { |
| /* |
| * failing to set this option means nfs4_callback |
| * could fail silently later. So fail it with |
| * with an error message now. |
| */ |
| syslog(LOG_ERR, |
| "couldn't set SO_ANON_MLP option on transport"); |
| (void) t_close(fd); |
| return (-1); |
| } |
| } |
| |
| if (nconf->nc_semantics == NC_TPI_CLTS) |
| tb.qlen = 0; |
| else |
| tb.qlen = backlog; |
| |
| /* LINTED pointer alignment */ |
| ntb = (struct t_bind *)t_alloc(fd, T_BIND, T_ALL); |
| if (ntb == (struct t_bind *)NULL) { |
| syslog(LOG_ERR, "t_alloc failed: t_errno %d, %m", t_errno); |
| (void) t_close(fd); |
| netdir_free((void *)addrlist, ND_ADDRLIST); |
| return (-1); |
| } |
| |
| /* |
| * XXX - what about the space tb->addr.buf points to? This should |
| * be either a memcpy() to/from the buf fields, or t_alloc(fd,T_BIND,) |
| * should't be called with T_ALL. |
| */ |
| if (addrlist) |
| tb.addr = *(addrlist->n_addrs); /* structure copy */ |
| |
| if (t_bind(fd, &tb, ntb) == -1) { |
| syslog(LOG_ERR, "t_bind failed: t_errno %d, %m", t_errno); |
| (void) t_free((char *)ntb, T_BIND); |
| netdir_free((void *)addrlist, ND_ADDRLIST); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| /* make sure we bound to the right address */ |
| if (use_any == FALSE && |
| (tb.addr.len != ntb->addr.len || |
| memcmp(tb.addr.buf, ntb->addr.buf, tb.addr.len) != 0)) { |
| syslog(LOG_ERR, "t_bind to wrong address"); |
| (void) t_free((char *)ntb, T_BIND); |
| netdir_free((void *)addrlist, ND_ADDRLIST); |
| (void) t_close(fd); |
| return (-1); |
| } |
| |
| /* |
| * Call nfs4svc_setport so that the kernel can be |
| * informed what port number the daemon is listing |
| * for incoming connection requests. |
| */ |
| |
| if ((nconf->nc_semantics == NC_TPI_COTS || |
| nconf->nc_semantics == NC_TPI_COTS_ORD) && Mysvc4 != NULL) |
| (*Mysvc4)(fd, NULL, nconf, NFS4_SETPORT, &ntb->addr); |
| |
| *addr = &ntb->addr; |
| netdir_free((void *)addrlist, ND_ADDRLIST); |
| |
| if (strcmp(nconf->nc_proto, "tcp") == 0) { |
| /* |
| * Disable the Nagle algorithm on TCP connections. |
| * Connections accepted from this listener will |
| * inherit the listener options. |
| */ |
| |
| /* LINTED pointer alignment */ |
| opt = (struct opthdr *)reqbuf; |
| opt->level = IPPROTO_TCP; |
| opt->name = TCP_NODELAY; |
| opt->len = sizeof (int); |
| |
| /* LINTED pointer alignment */ |
| *(int *)((char *)opt + sizeof (*opt)) = 1; |
| |
| req.flags = T_NEGOTIATE; |
| req.opt.len = sizeof (*opt) + opt->len; |
| req.opt.buf = (char *)opt; |
| resp.flags = 0; |
| resp.opt.buf = reqbuf; |
| resp.opt.maxlen = sizeof (reqbuf); |
| |
| if (t_optmgmt(fd, &req, &resp) < 0 || |
| resp.flags != T_SUCCESS) { |
| syslog(LOG_ERR, |
| "couldn't set NODELAY option for proto %s: t_errno = %d, %m", |
| nconf->nc_proto, t_errno); |
| } |
| } |
| |
| return (fd); |
| } |
| |
| static int |
| setopt(int fd, int level, int name, int value) |
| { |
| struct t_optmgmt req, resp; |
| struct { |
| struct opthdr opt; |
| int value; |
| } reqbuf; |
| |
| reqbuf.opt.level = level; |
| reqbuf.opt.name = name; |
| reqbuf.opt.len = sizeof (int); |
| |
| reqbuf.value = value; |
| |
| req.flags = T_NEGOTIATE; |
| req.opt.len = sizeof (reqbuf); |
| req.opt.buf = (char *)&reqbuf; |
| |
| resp.flags = 0; |
| resp.opt.buf = (char *)&reqbuf; |
| resp.opt.maxlen = sizeof (reqbuf); |
| |
| if (t_optmgmt(fd, &req, &resp) < 0 || resp.flags != T_SUCCESS) { |
| t_error("t_optmgmt"); |
| return (-1); |
| } |
| return (0); |
| } |
| |
| static int |
| reuseaddr(int fd) |
| { |
| return (setopt(fd, SOL_SOCKET, SO_REUSEADDR, 1)); |
| } |
| |
| static int |
| recvucred(int fd) |
| { |
| return (setopt(fd, SOL_SOCKET, SO_RECVUCRED, 1)); |
| } |
| |
| static int |
| anonmlp(int fd) |
| { |
| return (setopt(fd, SOL_SOCKET, SO_ANON_MLP, 1)); |
| } |
| |
| void |
| nfslib_log_tli_error(char *tli_name, int fd, struct netconfig *nconf) |
| { |
| int error; |
| |
| /* |
| * Save the error code across syslog(), just in case syslog() |
| * gets its own error and, therefore, overwrites errno. |
| */ |
| error = errno; |
| if (t_errno == TSYSERR) { |
| syslog(LOG_ERR, "%s(file descriptor %d/transport %s) %m", |
| tli_name, fd, nconf->nc_proto); |
| } else { |
| syslog(LOG_ERR, |
| "%s(file descriptor %d/transport %s) TLI error %d", |
| tli_name, fd, nconf->nc_proto, t_errno); |
| } |
| errno = error; |
| } |
| |
| /* |
| * Called to set up service over a particular transport. |
| */ |
| void |
| do_one(char *provider, NETSELDECL(proto), struct protob *protobp0, |
| int (*svc)(int, struct netbuf, struct netconfig *)) |
| { |
| register int sock; |
| struct protob *protobp; |
| struct netbuf *retaddr; |
| struct netconfig *retnconf; |
| struct netbuf addrmask; |
| int vers; |
| int err; |
| int l; |
| |
| if (provider) |
| sock = bind_to_provider(provider, protobp0->serv, &retaddr, |
| &retnconf); |
| else |
| sock = bind_to_proto(proto, protobp0->serv, &retaddr, |
| &retnconf); |
| |
| if (sock == -1) { |
| (void) syslog(LOG_ERR, |
| "Cannot establish %s service over %s: transport setup problem.", |
| protobp0->serv, provider ? provider : proto); |
| return; |
| } |
| |
| if (set_addrmask(sock, retnconf, &addrmask) < 0) { |
| (void) syslog(LOG_ERR, |
| "Cannot set address mask for %s", retnconf->nc_netid); |
| return; |
| } |
| |
| /* |
| * Register all versions of the programs in the protocol block list. |
| */ |
| l = strlen(NC_UDP); |
| for (protobp = protobp0; protobp; protobp = protobp->next) { |
| for (vers = protobp->versmin; vers <= protobp->versmax; |
| vers++) { |
| if ((protobp->program == NFS_PROGRAM || |
| protobp->program == NFS_ACL_PROGRAM) && |
| vers == NFS_V4 && |
| strncasecmp(retnconf->nc_proto, NC_UDP, l) == 0) |
| continue; |
| |
| (void) rpcb_unset(protobp->program, vers, retnconf); |
| (void) rpcb_set(protobp->program, vers, retnconf, |
| retaddr); |
| } |
| } |
| |
| if (retnconf->nc_semantics == NC_TPI_CLTS) { |
| /* Don't drop core if supporting module(s) aren't loaded. */ |
| (void) signal(SIGSYS, SIG_IGN); |
| |
| /* |
| * svc() doesn't block, it returns success or failure. |
| */ |
| |
| if (svc == NULL && Mysvc4 != NULL) |
| err = (*Mysvc4)(sock, &addrmask, retnconf, |
| NFS4_SETPORT|NFS4_KRPC_START, retaddr); |
| else |
| err = (*svc)(sock, addrmask, retnconf); |
| |
| if (err < 0) { |
| (void) syslog(LOG_ERR, |
| "Cannot establish %s service over <file desc." |
| " %d, protocol %s> : %m. Exiting", |
| protobp0->serv, sock, retnconf->nc_proto); |
| exit(1); |
| } |
| } |
| |
| /* |
| * We successfully set up the server over this transport. |
| * Add this descriptor to the one being polled on. |
| */ |
| add_to_poll_list(sock, retnconf); |
| } |
| /* |
| * Set up the NFS service over all the available transports. |
| * Returns -1 for failure, 0 for success. |
| */ |
| int |
| do_all(struct protob *protobp, |
| int (*svc)(int, struct netbuf, struct netconfig *)) |
| { |
| struct netconfig *nconf; |
| NCONF_HANDLE *nc; |
| int l; |
| |
| if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { |
| syslog(LOG_ERR, "setnetconfig failed: %m"); |
| return (-1); |
| } |
| l = strlen(NC_UDP); |
| while (nconf = getnetconfig(nc)) { |
| if ((nconf->nc_flag & NC_VISIBLE) && |
| strcmp(nconf->nc_protofmly, NC_LOOPBACK) != 0 && |
| OK_TPI_TYPE(nconf) && |
| (protobp->program != NFS4_CALLBACK || |
| strncasecmp(nconf->nc_proto, NC_UDP, l) != 0)) |
| do_one(nconf->nc_device, nconf->nc_proto, |
| protobp, svc); |
| } |
| (void) endnetconfig(nc); |
| return (0); |
| } |
| |
| /* |
| * poll on the open transport descriptors for events and errors. |
| */ |
| void |
| poll_for_action(void) |
| { |
| int nfds; |
| int i; |
| |
| /* |
| * Keep polling until all transports have been closed. When this |
| * happens, we return. |
| */ |
| while ((int)num_fds > 0) { |
| nfds = poll(poll_array, num_fds, INFTIM); |
| switch (nfds) { |
| case 0: |
| continue; |
| |
| case -1: |
| /* |
| * Some errors from poll could be |
| * due to temporary conditions, and we try to |
| * be robust in the face of them. Other |
| * errors (should never happen in theory) |
| * are fatal (eg. EINVAL, EFAULT). |
| */ |
| switch (errno) { |
| case EINTR: |
| continue; |
| |
| case EAGAIN: |
| case ENOMEM: |
| (void) sleep(10); |
| continue; |
| |
| default: |
| (void) syslog(LOG_ERR, |
| "poll failed: %m. Exiting"); |
| exit(1); |
| } |
| default: |
| break; |
| } |
| |
| /* |
| * Go through the poll list looking for events. |
| */ |
| for (i = 0; i < num_fds && nfds > 0; i++) { |
| if (poll_array[i].revents) { |
| nfds--; |
| /* |
| * We have a message, so try to read it. |
| * Record the error return in errno, |
| * so that syslog(LOG_ERR, "...%m") |
| * dumps the corresponding error string. |
| */ |
| if (conn_polled[i].nc.nc_semantics == |
| NC_TPI_CLTS) { |
| errno = do_poll_clts_action( |
| poll_array[i].fd, i); |
| } else { |
| errno = do_poll_cots_action( |
| poll_array[i].fd, i); |
| } |
| |
| if (errno == 0) |
| continue; |
| /* |
| * Most returned error codes mean that there is |
| * fatal condition which we can only deal with |
| * by closing the transport. |
| */ |
| if (errno != EAGAIN && errno != ENOMEM) { |
| (void) syslog(LOG_ERR, |
| "Error (%m) reading descriptor %d/transport %s. Closing it.", |
| poll_array[i].fd, |
| conn_polled[i].nc.nc_proto); |
| (void) t_close(poll_array[i].fd); |
| remove_from_poll_list(poll_array[i].fd); |
| |
| } else if (errno == ENOMEM) |
| (void) sleep(5); |
| } |
| } |
| } |
| |
| (void) syslog(LOG_ERR, |
| "All transports have been closed with errors. Exiting."); |
| } |
| |
| /* |
| * Allocate poll/transport array entries for this descriptor. |
| */ |
| static void |
| add_to_poll_list(int fd, struct netconfig *nconf) |
| { |
| static int poll_array_size = 0; |
| |
| /* |
| * If the arrays are full, allocate new ones. |
| */ |
| if (num_fds == poll_array_size) { |
| struct pollfd *tpa; |
| struct conn_entry *tnp; |
| |
| if (poll_array_size != 0) { |
| tpa = poll_array; |
| tnp = conn_polled; |
| } else |
| tpa = (struct pollfd *)0; |
| |
| poll_array_size += POLL_ARRAY_INC_SIZE; |
| /* |
| * Allocate new arrays. |
| */ |
| poll_array = (struct pollfd *) |
| malloc(poll_array_size * sizeof (struct pollfd) + 256); |
| conn_polled = (struct conn_entry *) |
| malloc(poll_array_size * sizeof (struct conn_entry) + 256); |
| if (poll_array == (struct pollfd *)NULL || |
| conn_polled == (struct conn_entry *)NULL) { |
| syslog(LOG_ERR, "malloc failed for poll array"); |
| exit(1); |
| } |
| |
| /* |
| * Copy the data of the old ones into new arrays, and |
| * free the old ones. |
| */ |
| if (tpa) { |
| (void) memcpy((void *)poll_array, (void *)tpa, |
| num_fds * sizeof (struct pollfd)); |
| (void) memcpy((void *)conn_polled, (void *)tnp, |
| num_fds * sizeof (struct conn_entry)); |
| free((void *)tpa); |
| free((void *)tnp); |
| } |
| } |
| |
| /* |
| * Set the descriptor and event list. All possible events are |
| * polled for. |
| */ |
| poll_array[num_fds].fd = fd; |
| poll_array[num_fds].events = POLLIN|POLLRDNORM|POLLRDBAND|POLLPRI; |
| |
| /* |
| * Copy the transport data over too. |
| */ |
| conn_polled[num_fds].nc = *nconf; |
| conn_polled[num_fds].closing = 0; |
| |
| /* |
| * Set the descriptor to non-blocking. Avoids a race |
| * between data arriving on the stream and then having it |
| * flushed before we can read it. |
| */ |
| if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { |
| (void) syslog(LOG_ERR, |
| "fcntl(file desc. %d/transport %s, F_SETFL, O_NONBLOCK): %m. Exiting", |
| num_fds, nconf->nc_proto); |
| exit(1); |
| } |
| |
| /* |
| * Count this descriptor. |
| */ |
| ++num_fds; |
| } |
| |
| static void |
| remove_from_poll_list(int fd) |
| { |
| int i; |
| int num_to_copy; |
| |
| for (i = 0; i < num_fds; i++) { |
| if (poll_array[i].fd == fd) { |
| --num_fds; |
| num_to_copy = num_fds - i; |
| (void) memcpy((void *)&poll_array[i], |
| (void *)&poll_array[i+1], |
| num_to_copy * sizeof (struct pollfd)); |
| (void) memset((void *)&poll_array[num_fds], 0, |
| sizeof (struct pollfd)); |
| (void) memcpy((void *)&conn_polled[i], |
| (void *)&conn_polled[i+1], |
| num_to_copy * sizeof (struct conn_entry)); |
| (void) memset((void *)&conn_polled[num_fds], 0, |
| sizeof (struct conn_entry)); |
| return; |
| } |
| } |
| syslog(LOG_ERR, "attempt to remove nonexistent fd from poll list"); |
| |
| } |
| |
| /* |
| * Called to read and interpret the event on a connectionless descriptor. |
| * Returns 0 if successful, or a UNIX error code if failure. |
| */ |
| static int |
| do_poll_clts_action(int fd, int conn_index) |
| { |
| int error; |
| int ret; |
| int flags; |
| struct netconfig *nconf = &conn_polled[conn_index].nc; |
| static struct t_unitdata *unitdata = NULL; |
| static struct t_uderr *uderr = NULL; |
| static int oldfd = -1; |
| struct nd_hostservlist *host = NULL; |
| struct strbuf ctl[1], data[1]; |
| /* |
| * We just need to have some space to consume the |
| * message in the event we can't use the TLI interface to do the |
| * job. |
| * |
| * We flush the message using getmsg(). For the control part |
| * we allocate enough for any TPI header plus 32 bytes for address |
| * and options. For the data part, there is nothing magic about |
| * the size of the array, but 256 bytes is probably better than |
| * 1 byte, and we don't expect any data portion anyway. |
| * |
| * If the array sizes are too small, we handle this because getmsg() |
| * (called to consume the message) will return MOREDATA|MORECTL. |
| * Thus we just call getmsg() until it's read the message. |
| */ |
| char ctlbuf[sizeof (union T_primitives) + 32]; |
| char databuf[256]; |
| |
| /* |
| * If this is the same descriptor as the last time |
| * do_poll_clts_action was called, we can save some |
| * de-allocation and allocation. |
| */ |
| if (oldfd != fd) { |
| oldfd = fd; |
| |
| if (unitdata) { |
| (void) t_free((char *)unitdata, T_UNITDATA); |
| unitdata = NULL; |
| } |
| if (uderr) { |
| (void) t_free((char *)uderr, T_UDERROR); |
| uderr = NULL; |
| } |
| } |
| |
| /* |
| * Allocate a unitdata structure for receiving the event. |
| */ |
| if (unitdata == NULL) { |
| /* LINTED pointer alignment */ |
| unitdata = (struct t_unitdata *)t_alloc(fd, T_UNITDATA, T_ALL); |
| if (unitdata == NULL) { |
| if (t_errno == TSYSERR) { |
| /* |
| * Save the error code across |
| * syslog(), just in case |
| * syslog() gets its own error |
| * and therefore overwrites errno. |
| */ |
| error = errno; |
| (void) syslog(LOG_ERR, |
| "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed: %m", |
| fd, nconf->nc_proto); |
| return (error); |
| } |
| (void) syslog(LOG_ERR, |
| "t_alloc(file descriptor %d/transport %s, T_UNITDATA) failed TLI error %d", |
| fd, nconf->nc_proto, t_errno); |
| goto flush_it; |
| } |
| } |
| |
| try_again: |
| flags = 0; |
| |
| /* |
| * The idea is we wait for T_UNITDATA_IND's. Of course, |
| * we don't get any, because rpcmod filters them out. |
| * However, we need to call t_rcvudata() to let TLI |
| * tell us we have a T_UDERROR_IND. |
| * |
| * algorithm is: |
| * t_rcvudata(), expecting TLOOK. |
| * t_look(), expecting T_UDERR. |
| * t_rcvuderr(), expecting success (0). |
| * expand destination address into ASCII, |
| * and dump it. |
| */ |
| |
| ret = t_rcvudata(fd, unitdata, &flags); |
| if (ret == 0 || t_errno == TBUFOVFLW) { |
| (void) syslog(LOG_WARNING, |
| "t_rcvudata(file descriptor %d/transport %s) got unexpected data, %d bytes", |
| fd, nconf->nc_proto, unitdata->udata.len); |
| |
| /* |
| * Even though we don't expect any data, in case we do, |
| * keep reading until there is no more. |
| */ |
| if (flags & T_MORE) |
| goto try_again; |
| |
| return (0); |
| } |
| |
| switch (t_errno) { |
| case TNODATA: |
| return (0); |
| case TSYSERR: |
| /* |
| * System errors are returned to caller. |
| * Save the error code across |
| * syslog(), just in case |
| * syslog() gets its own error |
| * and therefore overwrites errno. |
| */ |
| error = errno; |
| (void) syslog(LOG_ERR, |
| "t_rcvudata(file descriptor %d/transport %s) %m", |
| fd, nconf->nc_proto); |
| return (error); |
| case TLOOK: |
| break; |
| default: |
| (void) syslog(LOG_ERR, |
| "t_rcvudata(file descriptor %d/transport %s) TLI error %d", |
| fd, nconf->nc_proto, t_errno); |
| goto flush_it; |
| } |
| |
| ret = t_look(fd); |
| switch (ret) { |
| case 0: |
| return (0); |
| case -1: |
| /* |
| * System errors are returned to caller. |
| */ |
| if (t_errno == TSYSERR) { |
| /* |
| * Save the error code across |
| * syslog(), just in case |
| * syslog() gets its own error |
| * and therefore overwrites errno. |
| */ |
| error = errno; |
| (void) syslog(LOG_ERR, |
| "t_look(file descriptor %d/transport %s) %m", |
| fd, nconf->nc_proto); |
| return (error); |
| } |
| (void) syslog(LOG_ERR, |
| "t_look(file descriptor %d/transport %s) TLI error %d", |
| fd, nconf->nc_proto, t_errno); |
| goto flush_it; |
| case T_UDERR: |
| break; |
| default: |
| (void) syslog(LOG_WARNING, |
| "t_look(file descriptor %d/transport %s) returned %d not T_UDERR (%d)", |
| fd, nconf->nc_proto, ret, T_UDERR); |
| } |
| |
| if (uderr == NULL) { |
| /* LINTED pointer alignment */ |
| uderr = (struct t_uderr *)t_alloc(fd, T_UDERROR, T_ALL); |
| if (uderr == NULL) { |
| if (t_errno == TSYSERR) { |
| /* |
| * Save the error code across |
| * syslog(), just in case |
| * syslog() gets its own error |
| * and therefore overwrites errno. |
| */ |
| error = errno; |
| (void) syslog(LOG_ERR, |
| "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed: %m", |
| fd, nconf->nc_proto); |
| return (error); |
| } |
| (void) syslog(LOG_ERR, |
| "t_alloc(file descriptor %d/transport %s, T_UDERROR) failed TLI error: %d", |
| fd, nconf->nc_proto, t_errno); |
| goto flush_it; |
| } |
| } |
| |
| ret = t_rcvuderr(fd, uderr); |
| if (ret == 0) { |
| |
| /* |
| * Save the datagram error in errno, so that the |
| * %m argument to syslog picks up the error string. |
| */ |
| errno = uderr->error; |
| |
| /* |
| * Log the datagram error, then log the host that |
| * probably triggerred. Cannot log both in the |
| * same transaction because of packet size limitations |
| * in /dev/log. |
| */ |
| (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, |
| "NFS response over <file descriptor %d/transport %s> generated error: %m", |
| fd, nconf->nc_proto); |
| |
| /* |
| * Try to map the client's address back to a |
| * name. |
| */ |
| ret = netdir_getbyaddr(nconf, &host, &uderr->addr); |
| if (ret != -1 && host && host->h_cnt > 0 && |
| host->h_hostservs) { |
| (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, |
| "Bad NFS response was sent to client with host name: %s; service port: %s", |
| host->h_hostservs->h_host, |
| host->h_hostservs->h_serv); |
| } else { |
| int i, j; |
| char *buf; |
| char *hex = "0123456789abcdef"; |
| |
| /* |
| * Mapping failed, print the whole thing |
| * in ASCII hex. |
| */ |
| buf = (char *)malloc(uderr->addr.len * 2 + 1); |
| for (i = 0, j = 0; i < uderr->addr.len; i++, j += 2) { |
| buf[j] = hex[((uderr->addr.buf[i]) >> 4) & 0xf]; |
| buf[j+1] = hex[uderr->addr.buf[i] & 0xf]; |
| } |
| buf[j] = '\0'; |
| (void) syslog((errno == ECONNREFUSED) ? LOG_DEBUG : LOG_WARNING, |
| "Bad NFS response was sent to client with transport address: 0x%s", |
| buf); |
| free((void *)buf); |
| } |
| |
| if (ret == 0 && host != NULL) |
| netdir_free((void *)host, ND_HOSTSERVLIST); |
| return (0); |
| } |
| |
| switch (t_errno) { |
| case TNOUDERR: |
| goto flush_it; |
| case TSYSERR: |
| /* |
| * System errors are returned to caller. |
| * Save the error code across |
| * syslog(), just in case |
| * syslog() gets its own error |
| * and therefore overwrites errno. |
| */ |
| error = errno; |
| (void) syslog(LOG_ERR, |
| "t_rcvuderr(file descriptor %d/transport %s) %m", |
| fd, nconf->nc_proto); |
| return (error); |
| default: |
| (void) syslog(LOG_ERR, |
| "t_rcvuderr(file descriptor %d/transport %s) TLI error %d", |
| fd, nconf->nc_proto, t_errno); |
| goto flush_it; |
| } |
| |
| flush_it: |
| /* |
| * If we get here, then we could not cope with whatever message |
| * we attempted to read, so flush it. If we did read a message, |
| * and one isn't present, that is all right, because fd is in |
| * nonblocking mode. |
| */ |
| (void) syslog(LOG_ERR, |
| "Flushing one input message from <file descriptor %d/transport %s>", |
| fd, nconf->nc_proto); |
| |
| /* |
| * Read and discard the message. Do this this until there is |
| * no more control/data in the message or until we get an error. |
| */ |
| do { |
| ctl->maxlen = sizeof (ctlbuf); |
| ctl->buf = ctlbuf; |
| data->maxlen = sizeof (databuf); |
| data->buf = databuf; |
| flags = 0; |
| ret = getmsg(fd, ctl, data, &flags); |
| if (ret == -1) |
| return (errno); |
| } while (ret != 0); |
| |
| return (0); |
| } |
| |
| static void |
| conn_close_oldest(void) |
| { |
| int fd; |
| int i1; |
| |
| /* |
| * Find the oldest connection that is not already in the |
| * process of shutting down. |
| */ |
| for (i1 = end_listen_fds; /* no conditional expression */; i1++) { |
| if (i1 >= num_fds) |
| return; |
| if (conn_polled[i1].closing == 0) |
| break; |
| } |
| #ifdef DEBUG |
| printf("too many connections (%d), releasing oldest (%d)\n", |
| num_conns, poll_array[i1].fd); |
| #else |
| syslog(LOG_WARNING, "too many connections (%d), releasing oldest (%d)", |
| num_conns, poll_array[i1].fd); |
| #endif |
| fd = poll_array[i1].fd; |
| if (conn_polled[i1].nc.nc_semantics == NC_TPI_COTS) { |
| /* |
| * For politeness, send a T_DISCON_REQ to the transport |
| * provider. We close the stream anyway. |
| */ |
| (void) t_snddis(fd, (struct t_call *)0); |
| num_conns--; |
| remove_from_poll_list(fd); |
| (void) t_close(fd); |
| } else { |
| /* |
| * For orderly release, we do not close the stream |
| * until the T_ORDREL_IND arrives to complete |
| * the handshake. |
| */ |
| if (t_sndrel(fd) == 0) |
| conn_polled[i1].closing = 1; |
| } |
| } |
| |
| static boolean_t |
| conn_get(int fd, struct netconfig *nconf, struct conn_ind **connp) |
| { |
| struct conn_ind *conn; |
| struct conn_ind *next_conn; |
| |
| conn = (struct conn_ind *)malloc(sizeof (*conn)); |
| if (conn == NULL) { |
| syslog(LOG_ERR, "malloc for listen indication failed"); |
| return (FALSE); |
| } |
| |
| /* LINTED pointer alignment */ |
| conn->conn_call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL); |
| if (conn->conn_call == NULL) { |
| free((char *)conn); |
| nfslib_log_tli_error("t_alloc", fd, nconf); |
| return (FALSE); |
| } |
| |
| if (t_listen(fd, conn->conn_call) == -1) { |
| nfslib_log_tli_error("t_listen", fd, nconf); |
| (void) t_free((char *)conn->conn_call, T_CALL); |
| free((char *)conn); |
| return (FALSE); |
| } |
| |
| if (conn->conn_call->udata.len > 0) { |
| syslog(LOG_WARNING, |
| "rejecting inbound connection(%s) with %d bytes of connect data", |
| nconf->nc_proto, conn->conn_call->udata.len); |
| |
| conn->conn_call->udata.len = 0; |
| (void) t_snddis(fd, conn->conn_call); |
| (void) t_free((char *)conn->conn_call, T_CALL); |
| free((char *)conn); |
| return (FALSE); |
| } |
| |
| if ((next_conn = *connp) != NULL) { |
| next_conn->conn_prev->conn_next = conn; |
| conn->conn_next = next_conn; |
| conn->conn_prev = next_conn->conn_prev; |
| next_conn->conn_prev = conn; |
| } else { |
| conn->conn_next = conn; |
| conn->conn_prev = conn; |
| *connp = conn; |
| } |
| return (TRUE); |
| } |
| |
| static int |
| discon_get(int fd, struct netconfig *nconf, struct conn_ind **connp) |
| { |
| struct conn_ind *conn; |
| struct t_discon discon; |
| |
| discon.udata.buf = (char *)0; |
| discon.udata.maxlen = 0; |
| if (t_rcvdis(fd, &discon) == -1) { |
| nfslib_log_tli_error("t_rcvdis", fd, nconf); |
| return (-1); |
| } |
| |
| conn = *connp; |
| if (conn == NULL) |
| return (0); |
| |
| do { |
| if (conn->conn_call->sequence == discon.sequence) { |
| if (conn->conn_next == conn) |
| *connp = (struct conn_ind *)0; |
| else { |
| if (conn == *connp) { |
| *connp = conn->conn_next; |
| } |
| conn->conn_next->conn_prev = conn->conn_prev; |
| conn->conn_prev->conn_next = conn->conn_next; |
| } |
| free((char *)conn); |
| break; |
| } |
| conn = conn->conn_next; |
| } while (conn != *connp); |
| |
| return (0); |
| } |
| |
| static void |
| cots_listen_event(int fd, int conn_index) |
| { |
| struct t_call *call; |
| struct conn_ind *conn; |
| struct conn_ind *conn_head; |
| int event; |
| struct netconfig *nconf = &conn_polled[conn_index].nc; |
| int new_fd; |
| struct netbuf addrmask; |
| int ret = 0; |
| char *clnt; |
| char *clnt_uaddr = NULL; |
| struct nd_hostservlist *clnt_serv = NULL; |
| |
| conn_head = (struct conn_ind *)0; |
| (void) conn_get(fd, nconf, &conn_head); |
| |
| while ((conn = conn_head) != NULL) { |
| conn_head = conn->conn_next; |
| if (conn_head == conn) |
| conn_head = (struct conn_ind *)0; |
| else { |
| conn_head->conn_prev = conn->conn_prev; |
| conn->conn_prev->conn_next = conn_head; |
| } |
| call = conn->conn_call; |
| free((char *)conn); |
| |
| /* |
| * If we have already accepted the maximum number of |
| * connections allowed on the command line, then drop |
| * the oldest connection (for any protocol) before |
| * accepting the new connection. Unless explicitly |
| * set on the command line, max_conns_allowed is -1. |
| */ |
| if (max_conns_allowed != -1 && num_conns >= max_conns_allowed) |
| conn_close_oldest(); |
| |
| /* |
| * Create a new transport endpoint for the same proto as |
| * the listener. |
| */ |
| new_fd = nfslib_transport_open(nconf); |
| if (new_fd == -1) { |
| call->udata.len = 0; |
| (void) t_snddis(fd, call); |
| (void) t_free((char *)call, T_CALL); |
| syslog(LOG_ERR, "Cannot establish transport over %s", |
| nconf->nc_device); |
| continue; |
| } |
| |
| /* Bind to a generic address/port for the accepting stream. */ |
| if (t_bind(new_fd, (struct t_bind *)NULL, |
| (struct t_bind *)NULL) == -1) { |
| nfslib_log_tli_error("t_bind", new_fd, nconf); |
| call->udata.len = 0; |
| (void) t_snddis(fd, call); |
| (void) t_free((char *)call, T_CALL); |
| (void) t_close(new_fd); |
| continue; |
| } |
| |
| while (t_accept(fd, new_fd, call) == -1) { |
| if (t_errno != TLOOK) { |
| #ifdef DEBUG |
| nfslib_log_tli_error("t_accept", fd, nconf); |
| #endif |
| call->udata.len = 0; |
| (void) t_snddis(fd, call); |
| (void) t_free((char *)call, T_CALL); |
| (void) t_close(new_fd); |
| goto do_next_conn; |
| } |
| while (event = t_look(fd)) { |
| switch (event) { |
| case T_LISTEN: |
| #ifdef DEBUG |
| printf( |
| "cots_listen_event(%s): T_LISTEN during accept processing\n", nconf->nc_proto); |
| #endif |
| (void) conn_get(fd, nconf, &conn_head); |
| continue; |
| case T_DISCONNECT: |
| #ifdef DEBUG |
| printf( |
| "cots_listen_event(%s): T_DISCONNECT during accept processing\n", |
| nconf->nc_proto); |
| #endif |
| (void) discon_get(fd, nconf, |
| &conn_head); |
| continue; |
| default: |
| syslog(LOG_ERR, |
| "unexpected event 0x%x during accept processing (%s)", |
| event, nconf->nc_proto); |
| call->udata.len = 0; |
| (void) t_snddis(fd, call); |
| (void) t_free((char *)call, T_CALL); |
| (void) t_close(new_fd); |
| goto do_next_conn; |
| } |
| } |
| } |
| |
| if (set_addrmask(new_fd, nconf, &addrmask) < 0) { |
| (void) syslog(LOG_ERR, |
| "Cannot set address mask for %s", |
| nconf->nc_netid); |
| return; |
| } |
| |
| /* Tell KRPC about the new stream. */ |
| if (Mysvc4 != NULL) |
| ret = (*Mysvc4)(new_fd, &addrmask, nconf, |
| NFS4_KRPC_START, &call->addr); |
| else |
| ret = (*Mysvc)(new_fd, addrmask, nconf); |
| |
| if (ret < 0) { |
| if (errno != ENOTCONN) { |
| syslog(LOG_ERR, |
| "unable to register new connection: %m"); |
| } else { |
| /* |
| * This is the only error that could be |
| * caused by the client, so who was it? |
| */ |
| if (netdir_getbyaddr(nconf, &clnt_serv, |
| &(call->addr)) == ND_OK && |
| clnt_serv->h_cnt > 0) |
| clnt = clnt_serv->h_hostservs->h_host; |
| else |
| clnt = clnt_uaddr = taddr2uaddr(nconf, |
| &(call->addr)); |
| /* |
| * If we don't know who the client was, |
| * remain silent. |
| */ |
| if (clnt) |
| syslog(LOG_ERR, |
| "unable to register new connection: client %s has dropped connection", clnt); |
| if (clnt_serv) |
| netdir_free(clnt_serv, ND_HOSTSERVLIST); |
| if (clnt_uaddr) |
| free(clnt_uaddr); |
| } |
| free(addrmask.buf); |
| (void) t_snddis(new_fd, (struct t_call *)0); |
| (void) t_free((char *)call, T_CALL); |
| (void) t_close(new_fd); |
| goto do_next_conn; |
| } |
| |
| free(addrmask.buf); |
| (void) t_free((char *)call, T_CALL); |
| |
| /* |
| * Poll on the new descriptor so that we get disconnect |
| * and orderly release indications. |
| */ |
| num_conns++; |
| add_to_poll_list(new_fd, nconf); |
| |
| /* Reset nconf in case it has been moved. */ |
| nconf = &conn_polled[conn_index].nc; |
| do_next_conn:; |
| } |
| } |
| |
| static int |
| do_poll_cots_action(int fd, int conn_index) |
| { |
| char buf[256]; |
| int event; |
| int i1; |
| int flags; |
| struct conn_entry *connent = &conn_polled[conn_index]; |
| struct netconfig *nconf = &(connent->nc); |
| const char *errorstr; |
| |
| while (event = t_look(fd)) { |
| switch (event) { |
| case T_LISTEN: |
| #ifdef DEBUG |
| printf("do_poll_cots_action(%s,%d): T_LISTEN event\n", nconf->nc_proto, fd); |
| #endif |
| cots_listen_event(fd, conn_index); |
| break; |
| |
| case T_DATA: |
| #ifdef DEBUG |
| printf("do_poll_cots_action(%d,%s): T_DATA event\n", fd, nconf->nc_proto); |
| #endif |
| /* |
| * Receive a private notification from CONS rpcmod. |
| */ |
| i1 = t_rcv(fd, buf, sizeof (buf), &flags); |
| if (i1 == -1) { |
| syslog(LOG_ERR, "t_rcv failed"); |
| break; |
| } |
| if (i1 < sizeof (int)) |
| break; |
| i1 = BE32_TO_U32(buf); |
| if (i1 == 1 || i1 == 2) { |
| /* |
| * This connection has been idle for too long, |
| * so release it as politely as we can. If we |
| * have already initiated an orderly release |
| * and we get notified that the stream is |
| * still idle, pull the plug. This prevents |
| * hung connections from continuing to consume |
| * resources. |
| */ |
| #ifdef DEBUG |
| printf("do_poll_cots_action(%s,%d): ", nconf->nc_proto, fd); |
| printf("initiating orderly release of idle connection\n"); |
| #endif |
| if (nconf->nc_semantics == NC_TPI_COTS || |
| connent->closing != 0) { |
| (void) t_snddis(fd, (struct t_call *)0); |
| goto fdclose; |
| } |
| /* |
| * For NC_TPI_COTS_ORD, the stream is closed |
| * and removed from the poll list when the |
| * T_ORDREL is received from the provider. We |
| * don't wait for it here because it may take |
| * a while for the transport to shut down. |
| */ |
| if (t_sndrel(fd) == -1) { |
| syslog(LOG_ERR, |
| "unable to send orderly release %m"); |
| } |
| connent->closing = 1; |
| } else |
| syslog(LOG_ERR, |
| "unexpected event from CONS rpcmod %d", i1); |
| break; |
| |
| case T_ORDREL: |
| #ifdef DEBUG |
| printf("do_poll_cots_action(%s,%d): T_ORDREL event\n", nconf->nc_proto, fd); |
| #endif |
| /* Perform an orderly release. */ |
| if (t_rcvrel(fd) == 0) { |
| /* T_ORDREL on listen fd's should be ignored */ |
| if (!is_listen_fd_index(conn_index)) { |
| (void) t_sndrel(fd); |
| goto fdclose; |
| } |
| break; |
| |
| } else if (t_errno == TLOOK) { |
| break; |
| } else { |
| nfslib_log_tli_error("t_rcvrel", fd, nconf); |
| |
| /* |
| * check to make sure we do not close |
| * listen fd |
| */ |
| if (is_listen_fd_index(conn_index)) |
| break; |
| else |
| goto fdclose; |
| } |
| |
| case T_DISCONNECT: |
| #ifdef DEBUG |
| printf("do_poll_cots_action(%s,%d): T_DISCONNECT event\n", nconf->nc_proto, fd); |
| #endif |
| if (t_rcvdis(fd, (struct t_discon *)NULL) == -1) |
| nfslib_log_tli_error("t_rcvdis", fd, nconf); |
| |
| /* |
| * T_DISCONNECT on listen fd's should be ignored. |
| */ |
| if (is_listen_fd_index(conn_index)) |
| break; |
| else |
| goto fdclose; |
| |
| case T_ERROR: |
| default: |
| if (event == T_ERROR || t_errno == TSYSERR) { |
| if ((errorstr = strerror(errno)) == NULL) { |
| (void) sprintf(buf, "Unknown error num %d", |
| errno); |
| errorstr = (const char *) buf; |
| } |
| } else if (event == -1) |
| errorstr = t_strerror(t_errno); |
| else |
| errorstr = ""; |
| syslog(LOG_ERR, |
| "unexpected TLI event (0x%x) on " |
| "connection-oriented transport(%s,%d):%s", |
| event, nconf->nc_proto, fd, errorstr); |
| fdclose: |
| num_conns--; |
| remove_from_poll_list(fd); |
| (void) t_close(fd); |
| return (0); |
| } |
| } |
| |
| return (0); |
| } |
| |
| static char * |
| serv_name_to_port_name(char *name) |
| { |
| /* |
| * Map service names (used primarily in logging) to |
| * RPC port names (used by netdir_*() routines). |
| */ |
| if (strcmp(name, "NFS") == 0) { |
| return ("nfs"); |
| } else if (strcmp(name, "NLM") == 0) { |
| return ("lockd"); |
| } else if (strcmp(name, "NFS4_CALLBACK") == 0) { |
| return ("nfs4_callback"); |
| } |
| |
| return ("unrecognized"); |
| } |
| |
| static int |
| bind_to_provider(char *provider, char *serv, struct netbuf **addr, |
| struct netconfig **retnconf) |
| { |
| struct netconfig *nconf; |
| NCONF_HANDLE *nc; |
| struct nd_hostserv hs; |
| |
| hs.h_host = HOST_SELF; |
| hs.h_serv = serv_name_to_port_name(serv); |
| |
| if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { |
| syslog(LOG_ERR, "setnetconfig failed: %m"); |
| return (-1); |
| } |
| while (nconf = getnetconfig(nc)) { |
| if (OK_TPI_TYPE(nconf) && |
| strcmp(nconf->nc_device, provider) == 0) { |
| *retnconf = nconf; |
| return (nfslib_bindit(nconf, addr, &hs, |
| listen_backlog)); |
| } |
| } |
| (void) endnetconfig(nc); |
| |
| syslog(LOG_ERR, "couldn't find netconfig entry for provider %s", |
| provider); |
| return (-1); |
| } |
| |
| static int |
| bind_to_proto(NETSELDECL(proto), char *serv, struct netbuf **addr, |
| struct netconfig **retnconf) |
| { |
| struct netconfig *nconf; |
| NCONF_HANDLE *nc = NULL; |
| struct nd_hostserv hs; |
| |
| hs.h_host = HOST_SELF; |
| hs.h_serv = serv_name_to_port_name(serv); |
| |
| if ((nc = setnetconfig()) == (NCONF_HANDLE *)NULL) { |
| syslog(LOG_ERR, "setnetconfig failed: %m"); |
| return (-1); |
| } |
| while (nconf = getnetconfig(nc)) { |
| if (OK_TPI_TYPE(nconf) && NETSELEQ(nconf->nc_proto, proto)) { |
| *retnconf = nconf; |
| return (nfslib_bindit(nconf, addr, &hs, |
| listen_backlog)); |
| } |
| } |
| (void) endnetconfig(nc); |
| |
| syslog(LOG_ERR, "couldn't find netconfig entry for protocol %s", |
| proto); |
| return (-1); |
| } |
| |
| #include <netinet/in.h> |
| |
| /* |
| * Create an address mask appropriate for the transport. |
| * The mask is used to obtain the host-specific part of |
| * a network address when comparing addresses. |
| * For an internet address the host-specific part is just |
| * the 32 bit IP address and this part of the mask is set |
| * to all-ones. The port number part of the mask is zeroes. |
| */ |
| static int |
| set_addrmask(fd, nconf, mask) |
| struct netconfig *nconf; |
| struct netbuf *mask; |
| { |
| struct t_info info; |
| |
| /* |
| * Find the size of the address we need to mask. |
| */ |
| if (t_getinfo(fd, &info) < 0) { |
| t_error("t_getinfo"); |
| return (-1); |
| } |
| mask->len = mask->maxlen = info.addr; |
| if (info.addr <= 0) { |
| syslog(LOG_ERR, "set_addrmask: address size: %ld", |
| info.addr); |
| return (-1); |
| } |
| |
| mask->buf = (char *)malloc(mask->len); |
| if (mask->buf == NULL) { |
| syslog(LOG_ERR, "set_addrmask: no memory"); |
| return (-1); |
| } |
| (void) memset(mask->buf, 0, mask->len); /* reset all mask bits */ |
| |
| if (strcmp(nconf->nc_protofmly, NC_INET) == 0) { |
| /* |
| * Set the mask so that the port is ignored. |
| */ |
| /* LINTED pointer alignment */ |
| ((struct sockaddr_in *)mask->buf)->sin_addr.s_addr = |
| (ulong_t)~0; |
| /* LINTED pointer alignment */ |
| ((struct sockaddr_in *)mask->buf)->sin_family = |
| (ushort_t)~0; |
| } else if (strcmp(nconf->nc_protofmly, NC_INET6) == 0) { |
| /* LINTED pointer alignment */ |
| (void) memset(&((struct sockaddr_in6 *)mask->buf)->sin6_addr, |
| (uchar_t)~0, sizeof (struct in6_addr)); |
| /* LINTED pointer alignment */ |
| ((struct sockaddr_in6 *)mask->buf)->sin6_family = |
| (ushort_t)~0; |
| } else { |
| |
| /* |
| * Set all mask bits. |
| */ |
| (void) memset(mask->buf, 0xFF, mask->len); |
| } |
| return (0); |
| } |
| |
| /* |
| * For listen fd's index is always less than end_listen_fds. |
| * end_listen_fds is defined externally in the daemon that uses this library. |
| * It's value is equal to the number of open file descriptors after the |
| * last listen end point was opened but before any connection was accepted. |
| */ |
| static int |
| is_listen_fd_index(int index) |
| { |
| return (index < end_listen_fds); |
| } |