]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - utils/nfsd/nfssvc.c
Remove the AI_ADDRCONFIG hint flag to getaddrinfo() when it's
[nfs-utils.git] / utils / nfsd / nfssvc.c
index 0a7546a3c6f53544f65b65a564dc16a4f7308577..b8028bbed3bca6e76d88d44506a6a495545ec5b6 100644 (file)
@@ -10,7 +10,9 @@
 #include <config.h>
 #endif
 
+#include <sys/types.h>
 #include <sys/socket.h>
+#include <netdb.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <unistd.h>
 #include "nfslib.h"
 #include "xlog.h"
 
+/*
+ * IPv6 support for nfsd was finished before some of the other daemons (mountd
+ * and statd in particular). That could be a problem in the future if someone
+ * were to boot a kernel that supports IPv6 serving with an older nfs-utils. For
+ * now, hardcode the IPv6 switch into the off position until the other daemons
+ * are functional.
+ */
+#undef IPV6_SUPPORTED
+
 #define NFSD_PORTS_FILE     "/proc/fs/nfsd/portlist"
 #define NFSD_VERS_FILE    "/proc/fs/nfsd/versions"
 #define NFSD_THREAD_FILE  "/proc/fs/nfsd/threads"
 
-static void
-nfssvc_setfds(int port, unsigned int ctlbits, char *haddr)
+/*
+ * declaring a common static scratch buffer here keeps us from having to
+ * continually thrash the stack. The value of 128 bytes here is really just a
+ * SWAG and can be increased if necessary. It ought to be enough for the
+ * routines below however.
+ */
+char buf[128];
+
+/*
+ * Are there already sockets configured? If not, then it is safe to try to
+ * open some and pass them through.
+ *
+ * Note: If the user explicitly asked for 'udp', then we should probably check
+ * if that is open, and should open it if not. However we don't yet. All
+ * sockets have to be opened when the first daemon is started.
+ */
+int
+nfssvc_inuse(void)
 {
-       int fd, n, on=1;
-       char buf[BUFSIZ];
-       int udpfd = -1, tcpfd = -1;
-       struct sockaddr_in sin;
+       int fd, n;
 
        fd = open(NFSD_PORTS_FILE, O_RDONLY);
+
+       /* problem opening file, assume that nothing is configured */
        if (fd < 0)
-               return;
-       n = read(fd, buf, BUFSIZ);
+               return 0;
+
+       n = read(fd, buf, sizeof(buf));
        close(fd);
-       if (n != 0)
-               return;
-       /* there are no ports currently open, so it is safe to
-        * try to open some and pass them through.
-        * Note: If the user explicitly asked for 'udp', then
-        * we should probably check if that is open, and should
-        * open it if not.  However we don't yet.  All sockets
-        * have to be opened when the first daemon is started.
-        */
+
+       xlog(D_GENERAL, "knfsd is currently %s", (n > 0) ? "up" : "down");
+
+       return (n > 0);
+}
+
+static int
+nfssvc_setfds(const struct addrinfo *hints, const char *node, const char *port)
+{
+       int fd, on = 1, fac = L_ERROR;
+       int sockfd = -1, rc = 0;
+       struct addrinfo *addrhead = NULL, *addr;
+       char *proto, *family;
+
+       /*
+        * if file can't be opened, then assume that it's not available and
+        * that the caller should just fall back to the old nfsctl interface
+        */
        fd = open(NFSD_PORTS_FILE, O_WRONLY);
        if (fd < 0)
-               return;
-       sin.sin_family = AF_INET;
-       sin.sin_port   = htons(port);
-       sin.sin_addr.s_addr =  inet_addr(haddr);
-
-       if (NFSCTL_UDPISSET(ctlbits)) {
-               udpfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
-               if (udpfd < 0) {
-                       xlog(L_ERROR, "unable to create UDP socket: "
-                               "errno %d (%m)", errno);
-                       exit(1);
-               }
-               if (bind(udpfd, (struct  sockaddr  *)&sin, sizeof(sin)) < 0){
-                       xlog(L_ERROR, "unable to bind UDP socket: "
-                               "errno %d (%m)", errno);
-                       exit(1);
-               }
+               return 0;
+
+       switch(hints->ai_family) {
+       case AF_INET:
+               family = "inet";
+               break;
+#ifdef IPV6_SUPPORTED
+       case AF_INET6:
+               family = "inet6";
+               break;
+#endif /* IPV6_SUPPORTED */
+       default:
+               xlog(L_ERROR, "Unknown address family specified: %d\n",
+                               hints->ai_family);
+               rc = EAFNOSUPPORT;
+               goto error;
        }
 
-       if (NFSCTL_TCPISSET(ctlbits)) {
-               tcpfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-               if (tcpfd < 0) {
-                       xlog(L_ERROR, "unable to create TCP socket: "
-                               "errno %d (%m)", errno);
-                       exit(1);
+       rc = getaddrinfo(node, port, hints, &addrhead);
+       if (rc == EAI_NONAME && !strcmp(port, "nfs")) {
+               snprintf(buf, sizeof(buf), "%d", NFS_PORT);
+               rc = getaddrinfo(node, buf, hints, &addrhead);
+       }
+
+       if (rc != 0) {
+               xlog(L_ERROR, "unable to resolve %s:%s to %s address: "
+                               "%s", node ? node : "ANYADDR", port, family,
+                               rc == EAI_SYSTEM ? strerror(errno) :
+                                       gai_strerror(rc));
+               goto error;
+       }
+
+       addr = addrhead;
+       while(addr) {
+               /* skip non-TCP / non-UDP sockets */
+               switch(addr->ai_protocol) {
+               case IPPROTO_UDP:
+                       proto = "UDP";
+                       break;
+               case IPPROTO_TCP:
+                       proto = "TCP";
+                       break;
+               default:
+                       addr = addr->ai_next;
+                       continue;
                }
-               if (setsockopt(tcpfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
-                       xlog(L_ERROR, "unable to set SO_REUSEADDR: "
-                               "errno %d (%m)", errno);
-                       exit(1);
+
+               xlog(D_GENERAL, "Creating %s %s socket.", family, proto);
+
+               /* open socket and prepare to hand it off to kernel */
+               sockfd = socket(addr->ai_family, addr->ai_socktype,
+                               addr->ai_protocol);
+               if (sockfd < 0) {
+                       xlog(L_ERROR, "unable to create %s %s socket: "
+                               "errno %d (%m)", family, proto, errno);
+                       rc = errno;
+                       goto error;
                }
-               if (bind(tcpfd, (struct  sockaddr  *)&sin, sizeof(sin)) < 0){
-                       xlog(L_ERROR, "unable to bind TCP socket: "
-                               "errno %d (%m)", errno);
-                       exit(1);
+#ifdef IPV6_SUPPORTED
+               if (addr->ai_family == AF_INET6 &&
+                   setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on))) {
+                       xlog(L_ERROR, "unable to set IPV6_V6ONLY: "
+                               "errno %d (%m)\n", errno);
+                       rc = errno;
+                       goto error;
                }
-               if (listen(tcpfd, 64) < 0){
+#endif /* IPV6_SUPPORTED */
+               if (addr->ai_protocol == IPPROTO_TCP &&
+                   setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
+                       xlog(L_ERROR, "unable to set SO_REUSEADDR on %s "
+                               "socket: errno %d (%m)", family, errno);
+                       rc = errno;
+                       goto error;
+               }
+               if (bind(sockfd, addr->ai_addr, addr->ai_addrlen)) {
+                       xlog(L_ERROR, "unable to bind %s %s socket: "
+                               "errno %d (%m)", family, proto, errno);
+                       rc = errno;
+                       goto error;
+               }
+               if (addr->ai_protocol == IPPROTO_TCP && listen(sockfd, 64)) {
                        xlog(L_ERROR, "unable to create listening socket: "
                                "errno %d (%m)", errno);
-                       exit(1);
-               }
-       }
-       if (udpfd >= 0) {
-               snprintf(buf, BUFSIZ,"%d\n", udpfd); 
-               if (write(fd, buf, strlen(buf)) != strlen(buf)) {
-                       xlog(L_ERROR, 
-                              "writing fds to kernel failed: errno %d (%m)", 
-                              errno);
+                       rc = errno;
+                       goto error;
                }
-               close(fd);
-               fd = -1;
-       }
-       if (tcpfd >= 0) {
+
                if (fd < 0)
                        fd = open(NFSD_PORTS_FILE, O_WRONLY);
-               snprintf(buf, BUFSIZ,"%d\n", tcpfd); 
+
+               if (fd < 0) {
+                       xlog(L_ERROR, "couldn't open ports file: errno "
+                                     "%d (%m)", errno);
+                       goto error;
+               }
+
+               snprintf(buf, sizeof(buf), "%d\n", sockfd); 
                if (write(fd, buf, strlen(buf)) != strlen(buf)) {
-                       xlog(L_ERROR, 
-                              "writing fds to kernel failed: errno %d (%m)", 
-                              errno);
+                       /*
+                        * this error may be common on older kernels that don't
+                        * support IPv6, so turn into a debug message.
+                        */
+                       if (errno == EAFNOSUPPORT)
+                               fac = D_ALL;
+                       xlog(fac, "writing fd to kernel failed: errno %d (%m)",
+                                 errno);
+                       rc = errno;
+                       goto error;
                }
+               close(fd);
+               close(sockfd);
+               sockfd = fd = -1;
+               addr = addr->ai_next;
        }
-       close(fd);
+error:
+       if (fd >= 0)
+               close(fd);
+       if (sockfd >= 0)
+               close(sockfd);
+       if (addrhead)
+               freeaddrinfo(addrhead);
+       return rc;
+}
 
-       return;
+int
+nfssvc_set_sockets(const int family, const unsigned int protobits,
+                  const char *host, const char *port)
+{
+       struct addrinfo hints = { .ai_flags = AI_PASSIVE };
+
+       hints.ai_family = family;
+
+       if (!NFSCTL_ANYPROTO(protobits))
+               return EPROTOTYPE;
+       else if (!NFSCTL_UDPISSET(protobits))
+               hints.ai_protocol = IPPROTO_TCP;
+       else if (!NFSCTL_TCPISSET(protobits))
+               hints.ai_protocol = IPPROTO_UDP;
+
+       return nfssvc_setfds(&hints, host, port);
 }
-static void
-nfssvc_versbits(unsigned int ctlbits, int minorvers4)
+
+void
+nfssvc_setvers(unsigned int ctlbits, int minorvers4)
 {
        int fd, n, off;
-       char buf[BUFSIZ], *ptr;
+       char *ptr;
 
        ptr = buf;
        off = 0;
@@ -128,17 +240,17 @@ nfssvc_versbits(unsigned int ctlbits, int minorvers4)
 
        for (n = NFSD_MINVERS; n <= NFSD_MAXVERS; n++) {
                if (NFSCTL_VERISSET(ctlbits, n))
-                   off += snprintf(ptr+off, BUFSIZ - off, "+%d ", n);
+                   off += snprintf(ptr+off, sizeof(buf) - off, "+%d ", n);
                else
-                   off += snprintf(ptr+off, BUFSIZ - off, "-%d ", n);
+                   off += snprintf(ptr+off, sizeof(buf) - off, "-%d ", n);
        }
        n = minorvers4 >= 0 ? minorvers4 : -minorvers4;
        if (n >= NFSD_MINMINORVERS4 && n <= NFSD_MAXMINORVERS4)
-                   off += snprintf(ptr+off, BUFSIZ - off, "%c4.%d",
+                   off += snprintf(ptr+off, sizeof(buf) - off, "%c4.%d",
                                    minorvers4 > 0 ? '+' : '-',
                                    n);
        xlog(D_GENERAL, "Writing version string to kernel: %s", buf);
-       snprintf(ptr+off, BUFSIZ - off, "\n");
+       snprintf(ptr+off, sizeof(buf) - off, "\n");
        if (write(fd, buf, strlen(buf)) != strlen(buf))
                xlog(L_ERROR, "Setting version failed: errno %d (%m)", errno);
 
@@ -146,31 +258,23 @@ nfssvc_versbits(unsigned int ctlbits, int minorvers4)
 
        return;
 }
+
 int
-nfssvc(int port, int nrservs, unsigned int versbits, int minorvers4,
-       unsigned protobits, char *haddr)
+nfssvc_threads(unsigned short port, const int nrservs)
 {
        struct nfsctl_arg       arg;
+       struct servent *ent;
+       ssize_t n;
        int fd;
 
-       /* Note: must set versions before fds so that
-        * the ports get registered with portmap against correct
-        * versions
-        */
-       nfssvc_versbits(versbits, minorvers4);
-       nfssvc_setfds(port, protobits, haddr);
-
        fd = open(NFSD_THREAD_FILE, O_WRONLY);
        if (fd < 0)
                fd = open("/proc/fs/nfs/threads", O_WRONLY);
        if (fd >= 0) {
                /* 2.5+ kernel with nfsd filesystem mounted.
-                * Just write the number in.
-                * Cannot handle port number yet, but does anyone care?
+                * Just write the number of threads.
                 */
-               char buf[20];
-               int n;
-               snprintf(buf, 20,"%d\n", nrservs);
+               snprintf(buf, sizeof(buf), "%d\n", nrservs);
                n = write(fd, buf, strlen(buf));
                close(fd);
                if (n != strlen(buf))
@@ -179,6 +283,14 @@ nfssvc(int port, int nrservs, unsigned int versbits, int minorvers4,
                        return 0;
        }
 
+       if (!port) {
+               ent = getservbyname("nfs", "udp");
+               if (ent != NULL)
+                       port = ntohs(ent->s_port);
+               else
+                       port = NFS_PORT;
+       }
+
        arg.ca_version = NFSCTL_VERSION;
        arg.ca_svc.svc_nthreads = nrservs;
        arg.ca_svc.svc_port = port;