staging: usbip: Fix IPv6 support in usbipd
authorDominik Paulus <dominik.paulus@fau.de>
Fri, 13 Sep 2013 09:55:51 +0000 (11:55 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 25 Sep 2013 23:35:56 +0000 (16:35 -0700)
getaddrinfo() leaves the order of the returned addrinfo structs
unspecified. On systems with bindv6only disabled (this is the default),
PF_INET6 sockets bind to IPv4, too. Thus, IPv6 support in usbipd was
broken when getaddrinfo returned first IPv4 and then IPv6 addrinfos, as
the IPv6 bind failed with EADDRINUSE.

This patch uses seperate sockets for IPv4 and IPv6 and sets IPV6_V6ONLY
on all IPv6 sockets. Two command line arguments, -4 and -6 were added to
manually select the socket family.

Signed-off-by: Tobias Polzer <tobias.polzer@fau.de>
Signed-off-by: Dominik Paulus <dominik.paulus@fau.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/usbip/userspace/src/usbip_network.c
drivers/staging/usbip/userspace/src/usbip_network.h
drivers/staging/usbip/userspace/src/usbipd.c

index 5b3c20b..b4c37e7 100644 (file)
@@ -243,6 +243,18 @@ int usbip_net_set_keepalive(int sockfd)
        return ret;
 }
 
+int usbip_net_set_v6only(int sockfd)
+{
+       const int val = 1;
+       int ret;
+
+       ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val));
+       if (ret < 0)
+               dbg("setsockopt: IPV6_V6ONLY");
+
+       return ret;
+}
+
 /*
  * IPv6 Ready
  */
index 2d0e427..f19ae19 100644 (file)
@@ -180,6 +180,7 @@ int usbip_net_recv_op_common(int sockfd, uint16_t *code);
 int usbip_net_set_reuseaddr(int sockfd);
 int usbip_net_set_nodelay(int sockfd);
 int usbip_net_set_keepalive(int sockfd);
+int usbip_net_set_v6only(int sockfd);
 int usbip_net_tcp_connect(char *hostname, char *port);
 
 #endif /* __USBIP_NETWORK_H */
index 1c76cfd..7980f8b 100644 (file)
@@ -56,6 +56,13 @@ static const char usbip_version_string[] = PACKAGE_STRING;
 
 static const char usbipd_help_string[] =
        "usage: usbipd [options]\n"
+       "\n"
+       "       -4, --ipv4\n"
+       "               Bind to IPv4. Default is both.\n"
+       "\n"
+       "       -6, --ipv6\n"
+       "               Bind to IPv6. Default is both.\n"
+       "\n"
        "       -D, --daemon\n"
        "               Run as a daemon process.\n"
        "\n"
@@ -354,14 +361,15 @@ static void addrinfo_to_text(struct addrinfo *ai, char buf[],
        snprintf(buf, buf_size, "%s:%s", hbuf, sbuf);
 }
 
-static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[])
+static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[],
+                            int maxsockfd)
 {
        struct addrinfo *ai;
        int ret, nsockfd = 0;
        const size_t ai_buf_size = NI_MAXHOST + NI_MAXSERV + 2;
        char ai_buf[ai_buf_size];
 
-       for (ai = ai_head; ai && nsockfd < MAXSOCKFD; ai = ai->ai_next) {
+       for (ai = ai_head; ai && nsockfd < maxsockfd; ai = ai->ai_next) {
                int sock;
                addrinfo_to_text(ai, ai_buf, ai_buf_size);
                dbg("opening %s", ai_buf);
@@ -374,6 +382,9 @@ static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[])
 
                usbip_net_set_reuseaddr(sock);
                usbip_net_set_nodelay(sock);
+               /* We use seperate sockets for IPv4 and IPv6
+                * (see do_standalone_mode()) */
+               usbip_net_set_v6only(sock);
 
                if (sock >= FD_SETSIZE) {
                        err("FD_SETSIZE: %s: sock=%d, max=%d",
@@ -402,11 +413,6 @@ static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[])
                sockfdlist[nsockfd++] = sock;
        }
 
-       if (nsockfd == 0)
-               return -1;
-
-       dbg("listening on %d address%s", nsockfd, (nsockfd == 1) ? "" : "es");
-
        return nsockfd;
 }
 
@@ -473,11 +479,11 @@ static void remove_pid_file()
        }
 }
 
-static int do_standalone_mode(int daemonize)
+static int do_standalone_mode(int daemonize, int ipv4, int ipv6)
 {
        struct addrinfo *ai_head;
        int sockfdlist[MAXSOCKFD];
-       int nsockfd;
+       int nsockfd, family;
        int i, terminate;
        struct pollfd *fds;
        struct timespec timeout;
@@ -501,21 +507,36 @@ static int do_standalone_mode(int daemonize)
        set_signal();
        write_pid_file();
 
-       ai_head = do_getaddrinfo(NULL, PF_UNSPEC);
+       info("starting " PROGNAME " (%s)", usbip_version_string);
+
+       /*
+        * To suppress warnings on systems with bindv6only disabled
+        * (default), we use seperate sockets for IPv6 and IPv4 and set
+        * IPV6_V6ONLY on the IPv6 sockets.
+        */
+       if (ipv4 && ipv6)
+               family = AF_UNSPEC;
+       else if (ipv4)
+               family = AF_INET;
+       else
+               family = AF_INET6;
+
+       ai_head = do_getaddrinfo(NULL, family);
        if (!ai_head) {
                usbip_host_driver_close();
                return -1;
        }
-
-       info("starting " PROGNAME " (%s)", usbip_version_string);
-
-       nsockfd = listen_all_addrinfo(ai_head, sockfdlist);
+       nsockfd = listen_all_addrinfo(ai_head, sockfdlist,
+               sizeof(sockfdlist) / sizeof(*sockfdlist));
+       freeaddrinfo(ai_head);
        if (nsockfd <= 0) {
                err("failed to open a listening socket");
-               freeaddrinfo(ai_head);
                usbip_host_driver_close();
                return -1;
        }
+
+       dbg("listening on %d address%s", nsockfd, (nsockfd == 1) ? "" : "es");
+
        fds = calloc(nsockfd, sizeof(struct pollfd));
        for (i = 0; i < nsockfd; i++) {
                fds[i].fd = sockfdlist[i];
@@ -551,7 +572,6 @@ static int do_standalone_mode(int daemonize)
 
        info("shutting down " PROGNAME);
        free(fds);
-       freeaddrinfo(ai_head);
        usbip_host_driver_close();
 
        return 0;
@@ -560,6 +580,9 @@ static int do_standalone_mode(int daemonize)
 int main(int argc, char *argv[])
 {
        static const struct option longopts[] = {
+               { "ipv4",     no_argument,       NULL, '4' },
+               { "ipv6",     no_argument,       NULL, '6' },
+               { "daemon",   no_argument,       NULL, 'D' },
                { "daemon",   no_argument,       NULL, 'D' },
                { "debug",    no_argument,       NULL, 'd' },
                { "pid",      optional_argument, NULL, 'P' },
@@ -576,6 +599,7 @@ int main(int argc, char *argv[])
        } cmd;
 
        int daemonize = 0;
+       int ipv4 = 0, ipv6 = 0;
        int opt, rc = -1;
        pid_file = NULL;
 
@@ -587,12 +611,18 @@ int main(int argc, char *argv[])
 
        cmd = cmd_standalone_mode;
        for (;;) {
-               opt = getopt_long(argc, argv, "DdP::t:hv", longopts, NULL);
+               opt = getopt_long(argc, argv, "46DdP::t:hv", longopts, NULL);
 
                if (opt == -1)
                        break;
 
                switch (opt) {
+               case '4':
+                       ipv4 = 1;
+                       break;
+               case '6':
+                       ipv6 = 1;
+                       break;
                case 'D':
                        daemonize = 1;
                        break;
@@ -618,9 +648,12 @@ int main(int argc, char *argv[])
                }
        }
 
+       if (!ipv4 && !ipv6)
+               ipv4 = ipv6 = 1;
+
        switch (cmd) {
        case cmd_standalone_mode:
-               rc = do_standalone_mode(daemonize);
+               rc = do_standalone_mode(daemonize, ipv4, ipv6);
                remove_pid_file();
                break;
        case cmd_version: