X-Git-Url: https://git.decadent.org.uk/gitweb/?p=nfs-utils.git;a=blobdiff_plain;f=utils%2Fstatd%2Fsm-notify.c;h=928d1c56f03bff687ec90adef071d78a0313720b;hp=581234e15e02adac4f7b2216818c8f9b8355ae21;hb=86cfdf1d38da18f9f917c674daba79a4435445a7;hpb=c74532a864caea0ca126dc8e9f4914e7c8e86898 diff --git a/utils/statd/sm-notify.c b/utils/statd/sm-notify.c index 581234e..928d1c5 100644 --- a/utils/statd/sm-notify.c +++ b/utils/statd/sm-notify.c @@ -33,35 +33,30 @@ #include "nsm.h" #include "nfsrpc.h" -#define NSM_PROG 100024 -#define NSM_PROGRAM 100024 -#define NSM_VERSION 1 #define NSM_TIMEOUT 2 -#define NSM_NOTIFY 6 #define NSM_MAX_TIMEOUT 120 /* don't make this too big */ -#define MAXMSGSIZE 256 struct nsm_host { struct nsm_host * next; char * name; - struct sockaddr_storage addr; struct addrinfo *ai; time_t last_used; time_t send_next; unsigned int timeout; unsigned int retries; - unsigned int xid; + uint32_t xid; }; static char nsm_hostname[256]; static int nsm_state; +static int nsm_family = AF_INET; static int opt_debug = 0; static _Bool opt_update_state = true; static unsigned int opt_max_retry = 15 * 60; static char * opt_srcaddr = 0; static uint16_t opt_srcport = 0; -static void notify(void); +static void notify(const int sock); static int notify_host(int, struct nsm_host *); static void recv_reply(int); static void insert_host(struct nsm_host *); @@ -147,11 +142,173 @@ smn_get_host(const char *hostname, return 1; } +#ifdef IPV6_SUPPORTED +static int smn_socket(void) +{ + int sock; + + /* + * Use an AF_INET socket if IPv6 is disabled on the + * local system. + */ + sock = socket(AF_INET6, SOCK_DGRAM, 0); + if (sock == -1) { + if (errno != EAFNOSUPPORT) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + } else + nsm_family = AF_INET6; + + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); + goto out_close; + } + + /* + * TI-RPC over IPv6 (udp6/tcp6) does not handle IPv4. However, + * since sm-notify open-codes all of its RPC support, it can + * use a single socket and let the local network stack provide + * the correct mapping between address families automatically. + * This is the same thing that is done in the kernel. + */ + if (nsm_family == AF_INET6) { + const int zero = 0; + socklen_t zerolen = (socklen_t)sizeof(zero); + + if (setsockopt(sock, SOL_IPV6, IPV6_V6ONLY, + (char *)&zero, zerolen) == -1) { + xlog(L_ERROR, "setsockopt(3) on RPC socket failed: %m"); + goto out_close; + } + } + + return sock; + +out_close: + (void)close(sock); + return -1; +} +#else /* !IPV6_SUPPORTED */ +static int smn_socket(void) +{ + int sock; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + xlog(L_ERROR, "Failed to create RPC socket: %m"); + return -1; + } + + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { + xlog(L_ERROR, "fcntl(3) on RPC socket failed: %m"); + (void)close(sock); + return -1; + } + + return sock; +} +#endif /* !IPV6_SUPPORTED */ + +#ifdef HAVE_LIBTIRPC +static int +smn_bindresvport(int sock, struct sockaddr *sap) +{ + return bindresvport_sa(sock, sap); +} + +#else /* !HAVE_LIBTIRPC */ +static int +smn_bindresvport(int sock, struct sockaddr *sap) +{ + if (sap->sa_family != AF_INET) { + errno = EAFNOSUPPORT; + return -1; + } + + return bindresvport(sock, (struct sockaddr_in *)(char *)sap); +} +#endif /* !HAVE_LIBTIRPC */ + +/* + * Prepare a socket for sending RPC requests + * + * Returns a bound datagram socket file descriptor, or -1 if + * an error occurs. + */ +static int +smn_create_socket(const char *srcaddr, const uint16_t srcport) +{ + struct sockaddr_storage address; + struct sockaddr *local_addr = (struct sockaddr *)&address; + int sock, retry_cnt = 0; + +retry: + sock = smn_socket(); + if (sock == -1) + return -1; + + memset(&address, 0, sizeof(address)); + local_addr->sa_family = AF_INET; /* Default to IPv4 */ + + /* Bind source IP if provided on command line */ + if (srcaddr) { + struct addrinfo *ai = smn_lookup(srcaddr); + if (!ai) { + xlog(L_ERROR, + "Not a valid hostname or address: \"%s\"", + srcaddr); + (void)close(sock); + return -1; + } + + /* We know it's IPv4 at this point */ + memcpy(local_addr, ai->ai_addr, ai->ai_addrlen); + + freeaddrinfo(ai); + } + + /* Use source port if provided on the command line, + * otherwise use bindresvport */ + if (srcport) { + nfs_set_port(local_addr, srcport); + if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) { + xlog(L_ERROR, "Failed to bind RPC socket: %m"); + (void)close(sock); + return -1; + } + } else { + struct servent *se; + struct sockaddr_in *sin = (struct sockaddr_in *)local_addr; + + if (smn_bindresvport(sock, local_addr) == -1) { + xlog(L_ERROR, + "bindresvport on RPC socket failed: %m"); + (void)close(sock); + return -1; + } + + /* try to avoid known ports */ + se = getservbyport(sin->sin_port, "udp"); + if (se && retry_cnt < 100) { + retry_cnt++; + close(sock); + goto retry; + } + } + + return sock; +} + int main(int argc, char **argv) { - int c; - int force = 0; + int c, sock, force = 0; char * progname; progname = strrchr(argv[0], '/'); @@ -248,7 +405,14 @@ usage: fprintf(stderr, close(2); } - notify(); + sock = smn_create_socket(opt_srcaddr, opt_srcport); + if (sock == -1) + exit(1); + + if (!nsm_drop_privileges(-1)) + exit(1); + + notify(sock); if (hosts) { struct nsm_host *hp; @@ -268,68 +432,13 @@ usage: fprintf(stderr, * Notify hosts */ static void -notify(void) +notify(const int sock) { - struct sockaddr_storage address; - struct sockaddr *local_addr = (struct sockaddr *)&address; time_t failtime = 0; - int sock = -1; - int retry_cnt = 0; - - retry: - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - xlog(L_ERROR, "Failed to create RPC socket: %m"); - exit(1); - } - fcntl(sock, F_SETFL, O_NONBLOCK); - - memset(&address, 0, sizeof(address)); - local_addr->sa_family = AF_INET; /* Default to IPv4 */ - - /* Bind source IP if provided on command line */ - if (opt_srcaddr) { - struct addrinfo *ai = smn_lookup(opt_srcaddr); - if (!ai) { - xlog(L_ERROR, - "Not a valid hostname or address: \"%s\"", - opt_srcaddr); - exit(1); - } - - /* We know it's IPv4 at this point */ - memcpy(local_addr, ai->ai_addr, ai->ai_addrlen); - - freeaddrinfo(ai); - } - - /* Use source port if provided on the command line, - * otherwise use bindresvport */ - if (opt_srcport) { - nfs_set_port(local_addr, opt_srcport); - if (bind(sock, local_addr, sizeof(struct sockaddr_in)) < 0) { - xlog(L_ERROR, "Failed to bind RPC socket: %m"); - exit(1); - } - } else { - struct servent *se; - struct sockaddr_in *sin = (struct sockaddr_in *)local_addr; - (void) bindresvport(sock, sin); - /* try to avoid known ports */ - se = getservbyport(sin->sin_port, "udp"); - if (se && retry_cnt < 100) { - retry_cnt++; - close(sock); - goto retry; - } - } if (opt_max_retry) failtime = time(NULL) + opt_max_retry; - if (!nsm_drop_privileges(-1)) - exit(1); - while (hosts) { struct pollfd pfd; time_t now = time(NULL); @@ -387,17 +496,8 @@ notify(void) static int notify_host(int sock, struct nsm_host *host) { - struct sockaddr_storage address; - struct sockaddr *dest = (struct sockaddr *)&address; - socklen_t destlen = sizeof(address); - static unsigned int xid = 0; - uint32_t msgbuf[MAXMSGSIZE], *p; - unsigned int len; - - if (!xid) - xid = getpid() + time(NULL); - if (!host->xid) - host->xid = xid++; + struct sockaddr *sap; + socklen_t salen; if (host->ai == NULL) { host->ai = smn_lookup(host->name); @@ -408,12 +508,6 @@ notify_host(int sock, struct nsm_host *host) } } - memset(msgbuf, 0, sizeof(msgbuf)); - p = msgbuf; - *p++ = htonl(host->xid); - *p++ = 0; - *p++ = htonl(2); - /* If we retransmitted 4 times, reset the port to force * a new portmap lookup (in case statd was restarted). * We also rotate through multiple IP addresses at this @@ -421,10 +515,7 @@ notify_host(int sock, struct nsm_host *host) */ if (host->retries >= 4) { /* don't rotate if there is only one addrinfo */ - if (host->ai->ai_next == NULL) - memcpy(&host->addr, host->ai->ai_addr, - host->ai->ai_addrlen); - else { + if (host->ai->ai_next != NULL) { struct addrinfo *first = host->ai; struct addrinfo **next = &host->ai; @@ -437,58 +528,59 @@ notify_host(int sock, struct nsm_host *host) next = & (*next)->ai_next; /* put first entry at end */ *next = first; - memcpy(&host->addr, first->ai_addr, - first->ai_addrlen); } - nfs_set_port((struct sockaddr *)&host->addr, 0); + nfs_set_port(host->ai->ai_addr, 0); host->retries = 0; } - memcpy(dest, &host->addr, destlen); - if (nfs_get_port(dest) == 0) { - /* Build a PMAP packet */ - xlog(D_GENERAL, "Sending portmap query to %s", host->name); + sap = host->ai->ai_addr; + salen = host->ai->ai_addrlen; - nfs_set_port(dest, 111); - *p++ = htonl(100000); - *p++ = htonl(2); - *p++ = htonl(3); + if (nfs_get_port(sap) == 0) + host->xid = nsm_xmit_rpcbind(sock, sap, SM_PROG, SM_VERS); + else + host->xid = nsm_xmit_notify(sock, sap, salen, + SM_PROG, nsm_hostname, nsm_state); + + return 0; +} + +/* + * Extract the returned port number and set up the SM_NOTIFY call. + */ +static void +recv_rpcbind_reply(struct sockaddr *sap, struct nsm_host *host, XDR *xdr) +{ + uint16_t port = nsm_recv_rpcbind(sap->sa_family, xdr); - /* Auth and verf */ - *p++ = 0; *p++ = 0; - *p++ = 0; *p++ = 0; + host->send_next = time(NULL); + host->xid = 0; - *p++ = htonl(NSM_PROGRAM); - *p++ = htonl(NSM_VERSION); - *p++ = htonl(IPPROTO_UDP); - *p++ = 0; + if (port == 0) { + /* No binding for statd... */ + xlog(D_GENERAL, "No statd on host %s", host->name); + host->timeout = NSM_MAX_TIMEOUT; + host->send_next += NSM_MAX_TIMEOUT; } else { - /* Build an SM_NOTIFY packet */ - xlog(D_GENERAL, "Sending SM_NOTIFY to %s", host->name); - - *p++ = htonl(NSM_PROGRAM); - *p++ = htonl(NSM_VERSION); - *p++ = htonl(NSM_NOTIFY); - - /* Auth and verf */ - *p++ = 0; *p++ = 0; - *p++ = 0; *p++ = 0; - - /* state change */ - len = strlen(nsm_hostname); - *p++ = htonl(len); - memcpy(p, nsm_hostname, len); - p += (len + 3) >> 2; - *p++ = htonl(nsm_state); + nfs_set_port(sap, port); + if (host->timeout >= NSM_MAX_TIMEOUT / 4) + host->timeout = NSM_MAX_TIMEOUT / 4; } - len = (p - msgbuf) << 2; - if (sendto(sock, msgbuf, len, 0, dest, destlen) < 0) - xlog_warn("Sending Reboot Notification to " - "'%s' failed: errno %d (%m)", host->name, errno); - - return 0; + insert_host(host); +} + +/* + * Successful NOTIFY call. Server returns void, so nothing + * we need to do here. + */ +static void +recv_notify_reply(struct nsm_host *host) +{ + xlog(D_GENERAL, "Host %s notified successfully", host->name); + + smn_forget_host(host); } /* @@ -499,70 +591,37 @@ recv_reply(int sock) { struct nsm_host *hp; struct sockaddr *sap; - uint32_t msgbuf[MAXMSGSIZE], *p, *end; + char msgbuf[NSM_MAXMSGSIZE]; uint32_t xid; - int res; + ssize_t msglen; + XDR xdr; - res = recv(sock, msgbuf, sizeof(msgbuf), 0); - if (res < 0) + memset(msgbuf, 0 , sizeof(msgbuf)); + msglen = recv(sock, msgbuf, sizeof(msgbuf), 0); + if (msglen < 0) return; xlog(D_GENERAL, "Received packet..."); - p = msgbuf; - end = p + (res >> 2); - - xid = ntohl(*p++); - if (*p++ != htonl(1) /* must be REPLY */ - || *p++ != htonl(0) /* must be ACCEPTED */ - || *p++ != htonl(0) /* must be NULL verifier */ - || *p++ != htonl(0) - || *p++ != htonl(0)) /* must be SUCCESS */ - return; + memset(&xdr, 0, sizeof(xdr)); + xdrmem_create(&xdr, msgbuf, (unsigned int)msglen, XDR_DECODE); + xid = nsm_parse_reply(&xdr); + if (xid == 0) + goto out; /* Before we look at the data, find the host struct for this reply */ if ((hp = find_host(xid)) == NULL) - return; - sap = (struct sockaddr *)&hp->addr; - - if (nfs_get_port(sap) == 0) { - /* This was a portmap request */ - unsigned int port; - - port = ntohl(*p++); - if (p > end) - goto fail; - - hp->send_next = time(NULL); - if (port == 0) { - /* No binding for statd. Delay the next - * portmap query for max timeout */ - xlog(D_GENERAL, "No statd on %s", hp->name); - hp->timeout = NSM_MAX_TIMEOUT; - hp->send_next += NSM_MAX_TIMEOUT; - } else { - nfs_set_port(sap, port); - if (hp->timeout >= NSM_MAX_TIMEOUT / 4) - hp->timeout = NSM_MAX_TIMEOUT / 4; - } - hp->xid = 0; - } else { - /* Successful NOTIFY call. Server returns void, - * so nothing we need to do here (except - * check that we didn't read past the end of the - * packet) - */ - if (p <= end) { - xlog(D_GENERAL, "Host %s notified successfully", - hp->name); - smn_forget_host(hp); - return; - } - } + goto out; + + sap = hp->ai->ai_addr; + if (nfs_get_port(sap) == 0) + recv_rpcbind_reply(sap, hp, &xdr); + else + recv_notify_reply(hp); -fail: /* Re-insert the host */ - insert_host(hp); +out: + xdr_destroy(&xdr); } /*