tsocket/bsd: set IPV6_V6ONLY on AF_INET6 sockets
[abartlet/samba.git/.git] / lib / tsocket / tsocket_bsd.c
index 054bb3de3a7cca0ad28bcb87bf236bba0e3e3c56..13680ec0c54793d71cc8bbb0d777f3f84155a9db 100644 (file)
@@ -3,7 +3,7 @@
 
    Copyright (C) Stefan Metzmacher 2009
 
-     ** NOTE! The following LGPL license applies to the tevent
+     ** NOTE! The following LGPL license applies to the tsocket
      ** library. This does NOT imply that all of Samba is released
      ** under the LGPL
 
@@ -201,34 +201,40 @@ struct tsocket_address_bsd {
        } u;
 };
 
-static int _tsocket_address_bsd_from_sockaddr(TALLOC_CTX *mem_ctx,
-                                             struct sockaddr *sa,
-                                             socklen_t sa_len,
-                                             struct tsocket_address **_addr,
-                                             const char *location)
+int _tsocket_address_bsd_from_sockaddr(TALLOC_CTX *mem_ctx,
+                                      struct sockaddr *sa,
+                                      size_t sa_socklen,
+                                      struct tsocket_address **_addr,
+                                      const char *location)
 {
        struct tsocket_address *addr;
        struct tsocket_address_bsd *bsda;
 
+       if (sa_socklen < sizeof(sa->sa_family)) {
+               errno = EINVAL;
+               return -1;
+       }
+
        switch (sa->sa_family) {
        case AF_UNIX:
-               if (sa_len < sizeof(struct sockaddr_un)) {
-                       errno = EINVAL;
-                       return -1;
+               if (sa_socklen > sizeof(struct sockaddr_un)) {
+                       sa_socklen = sizeof(struct sockaddr_un);
                }
                break;
        case AF_INET:
-               if (sa_len < sizeof(struct sockaddr_in)) {
+               if (sa_socklen < sizeof(struct sockaddr_in)) {
                        errno = EINVAL;
                        return -1;
                }
+               sa_socklen = sizeof(struct sockaddr_in);
                break;
 #ifdef HAVE_IPV6
        case AF_INET6:
-               if (sa_len < sizeof(struct sockaddr_in6)) {
+               if (sa_socklen < sizeof(struct sockaddr_in6)) {
                        errno = EINVAL;
                        return -1;
                }
+               sa_socklen = sizeof(struct sockaddr_in6);
                break;
 #endif
        default:
@@ -236,7 +242,7 @@ static int _tsocket_address_bsd_from_sockaddr(TALLOC_CTX *mem_ctx,
                return -1;
        }
 
-       if (sa_len > sizeof(struct sockaddr_storage)) {
+       if (sa_socklen > sizeof(struct sockaddr_storage)) {
                errno = EINVAL;
                return -1;
        }
@@ -253,12 +259,56 @@ static int _tsocket_address_bsd_from_sockaddr(TALLOC_CTX *mem_ctx,
 
        ZERO_STRUCTP(bsda);
 
-       memcpy(&bsda->u.ss, sa, sa_len);
+       memcpy(&bsda->u.ss, sa, sa_socklen);
 
        *_addr = addr;
        return 0;
 }
 
+ssize_t tsocket_address_bsd_sockaddr(const struct tsocket_address *addr,
+                                    struct sockaddr *sa,
+                                    size_t sa_socklen)
+{
+       struct tsocket_address_bsd *bsda = talloc_get_type(addr->private_data,
+                                          struct tsocket_address_bsd);
+       ssize_t rlen = 0;
+
+       if (!bsda) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       switch (bsda->u.sa.sa_family) {
+       case AF_UNIX:
+               rlen = sizeof(struct sockaddr_un);
+               break;
+       case AF_INET:
+               rlen = sizeof(struct sockaddr_in);
+               break;
+#ifdef HAVE_IPV6
+       case AF_INET6:
+               rlen = sizeof(struct sockaddr_in6);
+               break;
+#endif
+       default:
+               errno = EAFNOSUPPORT;
+               return -1;
+       }
+
+       if (sa_socklen < rlen) {
+               errno = EINVAL;
+               return -1;
+       }
+
+       if (sa_socklen > sizeof(struct sockaddr_storage)) {
+               memset(sa, 0, sa_socklen);
+               sa_socklen = sizeof(struct sockaddr_storage);
+       }
+
+       memcpy(sa, &bsda->u.ss, sa_socklen);
+       return rlen;
+}
+
 int _tsocket_address_inet_from_strings(TALLOC_CTX *mem_ctx,
                                       const char *fam,
                                       const char *addr,
@@ -444,9 +494,14 @@ int _tsocket_address_unix_from_path(TALLOC_CTX *mem_ctx,
                path = "";
        }
 
+       if (strlen(path) > sizeof(un.sun_path)-1) {
+               errno = ENAMETOOLONG;
+               return -1;
+       }
+
        ZERO_STRUCT(un);
        un.sun_family = AF_UNIX;
-       strncpy(un.sun_path, path, sizeof(un.sun_path));
+       strncpy(un.sun_path, path, sizeof(un.sun_path)-1);
 
        ret = _tsocket_address_bsd_from_sockaddr(mem_ctx,
                                                 (struct sockaddr *)p,
@@ -612,12 +667,15 @@ static int tdgram_bsd_set_readable_handler(struct tdgram_bsd *bsds,
                TALLOC_FREE(bsds->fde);
        }
 
-       if (bsds->fde == NULL) {
+       if (tevent_fd_get_flags(bsds->fde) == 0) {
+               TALLOC_FREE(bsds->fde);
+
                bsds->fde = tevent_add_fd(ev, bsds,
                                          bsds->fd, TEVENT_FD_READ,
                                          tdgram_bsd_fde_handler,
                                          bsds);
                if (!bsds->fde) {
+                       errno = ENOMEM;
                        return -1;
                }
 
@@ -663,12 +721,15 @@ static int tdgram_bsd_set_writeable_handler(struct tdgram_bsd *bsds,
                TALLOC_FREE(bsds->fde);
        }
 
-       if (bsds->fde == NULL) {
+       if (tevent_fd_get_flags(bsds->fde) == 0) {
+               TALLOC_FREE(bsds->fde);
+
                bsds->fde = tevent_add_fd(ev, bsds,
                                          bsds->fd, TEVENT_FD_WRITE,
                                          tdgram_bsd_fde_handler,
                                          bsds);
                if (!bsds->fde) {
+                       errno = ENOMEM;
                        return -1;
                }
 
@@ -767,7 +828,7 @@ static void tdgram_bsd_recvfrom_handler(void *private_data)
        struct tsocket_address_bsd *bsda;
        ssize_t ret;
        struct sockaddr *sa = NULL;
-       socklen_t sa_len = 0;
+       socklen_t sa_socklen = 0;
        int err;
        bool retry;
 
@@ -803,16 +864,16 @@ static void tdgram_bsd_recvfrom_handler(void *private_data)
        ZERO_STRUCTP(bsda);
 
        sa = &bsda->u.sa;
-       sa_len = sizeof(bsda->u.ss);
+       sa_socklen = sizeof(bsda->u.ss);
        /*
         * for unix sockets we can't use the size of sockaddr_storage
         * we would get EINVAL
         */
        if (bsda->u.sa.sa_family == AF_UNIX) {
-               sa_len = sizeof(bsda->u.un);
+               sa_socklen = sizeof(bsda->u.un);
        }
 
-       ret = recvfrom(bsds->fd, state->buf, state->len, 0, sa, &sa_len);
+       ret = recvfrom(bsds->fd, state->buf, state->len, 0, sa, &sa_socklen);
        err = tsocket_bsd_error_from_errno(ret, errno, &retry);
        if (retry) {
                /* retry later */
@@ -940,7 +1001,7 @@ static void tdgram_bsd_sendto_handler(void *private_data)
        struct tdgram_context *dgram = state->dgram;
        struct tdgram_bsd *bsds = tdgram_context_data(dgram, struct tdgram_bsd);
        struct sockaddr *sa = NULL;
-       socklen_t sa_len = 0;
+       socklen_t sa_socklen = 0;
        ssize_t ret;
        int err;
        bool retry;
@@ -951,17 +1012,17 @@ static void tdgram_bsd_sendto_handler(void *private_data)
                        struct tsocket_address_bsd);
 
                sa = &bsda->u.sa;
-               sa_len = sizeof(bsda->u.ss);
+               sa_socklen = sizeof(bsda->u.ss);
                /*
                 * for unix sockets we can't use the size of sockaddr_storage
                 * we would get EINVAL
                 */
                if (bsda->u.sa.sa_family == AF_UNIX) {
-                       sa_len = sizeof(bsda->u.un);
+                       sa_socklen = sizeof(bsda->u.un);
                }
        }
 
-       ret = sendto(bsds->fd, state->buf, state->len, 0, sa, sa_len);
+       ret = sendto(bsds->fd, state->buf, state->len, 0, sa, sa_socklen);
        err = tsocket_bsd_error_from_errno(ret, errno, &retry);
        if (retry) {
                /* retry later */
@@ -1081,7 +1142,10 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
        int ret;
        bool do_bind = false;
        bool do_reuseaddr = false;
-       socklen_t sa_len = sizeof(lbsda->u.ss);
+       bool do_ipv6only = false;
+       bool is_inet = false;
+       int sa_fam = lbsda->u.sa.sa_family;
+       socklen_t sa_socklen = sizeof(lbsda->u.ss);
 
        if (remote) {
                rbsda = talloc_get_type_abort(remote->private_data,
@@ -1102,16 +1166,18 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
                 * for unix sockets we can't use the size of sockaddr_storage
                 * we would get EINVAL
                 */
-               sa_len = sizeof(lbsda->u.un);
+               sa_socklen = sizeof(lbsda->u.un);
                break;
        case AF_INET:
                if (lbsda->u.in.sin_port != 0) {
                        do_reuseaddr = true;
                        do_bind = true;
                }
-               if (lbsda->u.in.sin_addr.s_addr == INADDR_ANY) {
+               if (lbsda->u.in.sin_addr.s_addr != INADDR_ANY) {
                        do_bind = true;
                }
+               is_inet = true;
+               sa_socklen = sizeof(rbsda->u.in);
                break;
 #ifdef HAVE_IPV6
        case AF_INET6:
@@ -1124,6 +1190,9 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
                           sizeof(in6addr_any)) != 0) {
                        do_bind = true;
                }
+               is_inet = true;
+               sa_socklen = sizeof(rbsda->u.in6);
+               do_ipv6only = true;
                break;
 #endif
        default:
@@ -1131,7 +1200,23 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
                return -1;
        }
 
-       fd = socket(lbsda->u.sa.sa_family, SOCK_DGRAM, 0);
+       if (!do_bind && is_inet && rbsda) {
+               sa_fam = rbsda->u.sa.sa_family;
+               switch (sa_fam) {
+               case AF_INET:
+                       sa_socklen = sizeof(rbsda->u.in);
+                       do_ipv6only = false;
+                       break;
+#ifdef HAVE_IPV6
+               case AF_INET6:
+                       sa_socklen = sizeof(rbsda->u.in6);
+                       do_ipv6only = true;
+                       break;
+#endif
+               }
+       }
+
+       fd = socket(sa_fam, SOCK_DGRAM, 0);
        if (fd < 0) {
                return fd;
        }
@@ -1156,6 +1241,21 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
        bsds->fd = fd;
        talloc_set_destructor(bsds, tdgram_bsd_destructor);
 
+#ifdef HAVE_IPV6
+       if (do_ipv6only) {
+               int val = 1;
+
+               ret = setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                (const void *)&val, sizeof(val));
+               if (ret == -1) {
+                       int saved_errno = errno;
+                       talloc_free(dgram);
+                       errno = saved_errno;
+                       return ret;
+               }
+       }
+#endif
+
        if (broadcast) {
                int val = 1;
 
@@ -1183,7 +1283,7 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
        }
 
        if (do_bind) {
-               ret = bind(fd, &lbsda->u.sa, sa_len);
+               ret = bind(fd, &lbsda->u.sa, sa_socklen);
                if (ret == -1) {
                        int saved_errno = errno;
                        talloc_free(dgram);
@@ -1193,7 +1293,13 @@ static int tdgram_bsd_dgram_socket(const struct tsocket_address *local,
        }
 
        if (rbsda) {
-               ret = connect(fd, &rbsda->u.sa, sa_len);
+               if (rbsda->u.sa.sa_family != sa_fam) {
+                       talloc_free(dgram);
+                       errno = EINVAL;
+                       return -1;
+               }
+
+               ret = connect(fd, &rbsda->u.sa, sa_socklen);
                if (ret == -1) {
                        int saved_errno = errno;
                        talloc_free(dgram);
@@ -1286,6 +1392,10 @@ static void tstream_bsd_fde_handler(struct tevent_context *ev,
        }
        if (flags & TEVENT_FD_READ) {
                if (!bsds->readable_handler) {
+                       if (bsds->writeable_handler) {
+                               bsds->writeable_handler(bsds->writeable_private);
+                               return;
+                       }
                        TEVENT_FD_NOT_READABLE(bsds->fde);
                        return;
                }
@@ -1323,12 +1433,15 @@ static int tstream_bsd_set_readable_handler(struct tstream_bsd *bsds,
                TALLOC_FREE(bsds->fde);
        }
 
-       if (bsds->fde == NULL) {
+       if (tevent_fd_get_flags(bsds->fde) == 0) {
+               TALLOC_FREE(bsds->fde);
+
                bsds->fde = tevent_add_fd(ev, bsds,
                                          bsds->fd, TEVENT_FD_READ,
                                          tstream_bsd_fde_handler,
                                          bsds);
                if (!bsds->fde) {
+                       errno = ENOMEM;
                        return -1;
                }
 
@@ -1374,19 +1487,25 @@ static int tstream_bsd_set_writeable_handler(struct tstream_bsd *bsds,
                TALLOC_FREE(bsds->fde);
        }
 
-       if (bsds->fde == NULL) {
+       if (tevent_fd_get_flags(bsds->fde) == 0) {
+               TALLOC_FREE(bsds->fde);
+
                bsds->fde = tevent_add_fd(ev, bsds,
-                                         bsds->fd, TEVENT_FD_WRITE,
+                                         bsds->fd,
+                                         TEVENT_FD_READ | TEVENT_FD_WRITE,
                                          tstream_bsd_fde_handler,
                                          bsds);
                if (!bsds->fde) {
+                       errno = ENOMEM;
                        return -1;
                }
 
                /* cache the event context we're running on */
                bsds->event_ptr = ev;
        } else if (!bsds->writeable_handler) {
-               TEVENT_FD_WRITEABLE(bsds->fde);
+               uint16_t flags = tevent_fd_get_flags(bsds->fde);
+               flags |= TEVENT_FD_READ | TEVENT_FD_WRITE;
+               tevent_fd_set_flags(bsds->fde, flags);
        }
 
        bsds->writeable_handler = handler;
@@ -1534,6 +1653,19 @@ static void tstream_bsd_readv_handler(void *private_data)
                state->count -= 1;
        }
 
+       /*
+        * there're maybe some empty vectors at the end
+        * which we need to skip, otherwise we would get
+        * ret == 0 from the readv() call and return EPIPE
+        */
+       while (state->count > 0) {
+               if (state->vector[0].iov_len > 0) {
+                       break;
+               }
+               state->vector += 1;
+               state->count -= 1;
+       }
+
        if (state->count > 0) {
                /* we have more to read */
                return;
@@ -1681,6 +1813,19 @@ static void tstream_bsd_writev_handler(void *private_data)
                state->count -= 1;
        }
 
+       /*
+        * there're maybe some empty vectors at the end
+        * which we need to skip, otherwise we would get
+        * ret == 0 from the writev() call and return EPIPE
+        */
+       while (state->count > 0) {
+               if (state->vector[0].iov_len > 0) {
+                       break;
+               }
+               state->vector += 1;
+               state->count -= 1;
+       }
+
        if (state->count > 0) {
                /* we have more to read */
                return;
@@ -1844,7 +1989,10 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
        bool retry;
        bool do_bind = false;
        bool do_reuseaddr = false;
-       socklen_t sa_len = sizeof(rbsda->u.ss);
+       bool do_ipv6only = false;
+       bool is_inet = false;
+       int sa_fam = lbsda->u.sa.sa_family;
+       socklen_t sa_socklen = sizeof(rbsda->u.ss);
 
        req = tevent_req_create(mem_ctx, &state,
                                struct tstream_bsd_connect_state);
@@ -1872,16 +2020,18 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
                 * for unix sockets we can't use the size of sockaddr_storage
                 * we would get EINVAL
                 */
-               sa_len = sizeof(rbsda->u.un);
+               sa_socklen = sizeof(rbsda->u.un);
                break;
        case AF_INET:
                if (lbsda->u.in.sin_port != 0) {
                        do_reuseaddr = true;
                        do_bind = true;
                }
-               if (lbsda->u.in.sin_addr.s_addr == INADDR_ANY) {
+               if (lbsda->u.in.sin_addr.s_addr != INADDR_ANY) {
                        do_bind = true;
                }
+               is_inet = true;
+               sa_socklen = sizeof(rbsda->u.in);
                break;
 #ifdef HAVE_IPV6
        case AF_INET6:
@@ -1894,6 +2044,9 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
                           sizeof(in6addr_any)) != 0) {
                        do_bind = true;
                }
+               is_inet = true;
+               sa_socklen = sizeof(rbsda->u.in6);
+               do_ipv6only = true;
                break;
 #endif
        default:
@@ -1901,7 +2054,23 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
                goto post;
        }
 
-       state->fd = socket(lbsda->u.sa.sa_family, SOCK_STREAM, 0);
+       if (!do_bind && is_inet) {
+               sa_fam = rbsda->u.sa.sa_family;
+               switch (sa_fam) {
+               case AF_INET:
+                       sa_socklen = sizeof(rbsda->u.in);
+                       do_ipv6only = false;
+                       break;
+#ifdef HAVE_IPV6
+               case AF_INET6:
+                       sa_socklen = sizeof(rbsda->u.in6);
+                       do_ipv6only = true;
+                       break;
+#endif
+               }
+       }
+
+       state->fd = socket(sa_fam, SOCK_STREAM, 0);
        if (state->fd == -1) {
                tevent_req_error(req, errno);
                goto post;
@@ -1913,6 +2082,19 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
                goto post;
        }
 
+#ifdef HAVE_IPV6
+       if (do_ipv6only) {
+               int val = 1;
+
+               ret = setsockopt(state->fd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                (const void *)&val, sizeof(val));
+               if (ret == -1) {
+                       tevent_req_error(req, errno);
+                       goto post;
+               }
+       }
+#endif
+
        if (do_reuseaddr) {
                int val = 1;
 
@@ -1925,14 +2107,19 @@ static struct tevent_req * tstream_bsd_connect_send(TALLOC_CTX *mem_ctx,
        }
 
        if (do_bind) {
-               ret = bind(state->fd, &lbsda->u.sa, sizeof(lbsda->u.ss));
+               ret = bind(state->fd, &lbsda->u.sa, sa_socklen);
                if (ret == -1) {
                        tevent_req_error(req, errno);
                        goto post;
                }
        }
 
-       ret = connect(state->fd, &rbsda->u.sa, sa_len);
+       if (rbsda->u.sa.sa_family != sa_fam) {
+               tevent_req_error(req, EINVAL);
+               goto post;
+       }
+
+       ret = connect(state->fd, &rbsda->u.sa, sa_socklen);
        err = tsocket_bsd_error_from_errno(ret, errno, &retry);
        if (retry) {
                /* retry later */