Просмотр исходного кода

[LibOS,Pal] Introduce PAL_CREATE_DUALSTACK for IPV6_V6ONLY emulation

Previously, Graphene didn't have emulation of setsockopt(IPV6_V6ONLY).
This flag signals that application doesn't want to create dual-stack
socket (i.e., it wants to bind both IPv4 and IPv6 connections on the
same port).

In reality, different applications require different behavior: e.g.,
Redis sets IPV6_V6ONLY whereas Apache with SSL/TLS unsets it, and
always emulating as set or unset leads to failure of one of these
apps. This commit introduces emulation of IPV6_V6ONLY via a generic
PAL_CREATE_DUALSTACK flag passed to DkStreamOpen(.., create, ..).

Notice that it is impossible to emulate IPV6_V6ONLY as other flags
such as TCP_NODELAY because IPV6_V6ONLY flag makes sense only before
bind, whereas Graphene historically applies other flags only after
bind() syscall. Therefore we introduced PAL_CREATE_DUALSTACK.
Dmitrii Kuvaiskii 4 лет назад
Родитель
Сommit
4ad98502fe

+ 43 - 3
LibOS/shim/src/sys/shim_socket.c

@@ -417,6 +417,20 @@ static int create_socket_uri(struct shim_handle* hdl) {
     return -EPROTONOSUPPORT;
 }
 
+/* hdl->lock must be held */
+static bool __socket_is_ipv6_v6only(struct shim_handle* hdl) {
+    struct shim_sock_option* o = hdl->info.sock.pending_options;
+    while (o) {
+        if (o->level == IPPROTO_IPV6 && o->optname == IPV6_V6ONLY) {
+            int* intval = (int*)o->optval;
+            return *intval ? 1 : 0;
+        }
+        o = o->next;
+    }
+    return false;
+}
+
+
 int shim_do_bind(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
     if (!addr || test_user_memory(addr, addrlen, false))
         return -EFAULT;
@@ -480,7 +494,13 @@ int shim_do_bind(int sockfd, struct sockaddr* addr, socklen_t addrlen) {
     if ((ret = create_socket_uri(hdl)) < 0)
         goto out;
 
-    PAL_HANDLE pal_hdl = DkStreamOpen(qstrgetstr(&hdl->uri), 0, 0, 0, hdl->flags & O_NONBLOCK);
+    int create_flags = PAL_CREATE_DUALSTACK;
+    if (__socket_is_ipv6_v6only(hdl)) {
+        /* application requests IPV6_V6ONLY, this socket is not dual-stack */
+        create_flags &= ~PAL_CREATE_DUALSTACK;
+    }
+
+    PAL_HANDLE pal_hdl = DkStreamOpen(qstrgetstr(&hdl->uri), 0, 0, create_flags, hdl->flags & O_NONBLOCK);
 
     if (!pal_hdl) {
         ret = (PAL_NATIVE_ERRNO == PAL_ERROR_STREAMEXIST) ? -EADDRINUSE : -PAL_ERRNO;
@@ -1626,7 +1646,7 @@ static bool __update_attr(PAL_STREAM_ATTR* attr, int level, int optname, char* o
 
 static int __do_setsockopt(struct shim_handle* hdl, int level, int optname, char* optval,
                            PAL_STREAM_ATTR* attr) {
-    if (level != SOL_SOCKET && level != SOL_TCP)
+    if (level != SOL_SOCKET && level != SOL_TCP && level != IPPROTO_IPV6)
         return -ENOPROTOOPT;
 
     if (level == SOL_SOCKET) {
@@ -1650,6 +1670,9 @@ static int __do_setsockopt(struct shim_handle* hdl, int level, int optname, char
         }
     }
 
+    if (level == IPPROTO_IPV6 && optname != IPV6_V6ONLY)
+        return -ENOPROTOOPT;
+
     if (level == SOL_TCP && optname != TCP_CORK && optname != TCP_NODELAY)
         return -ENOPROTOOPT;
 
@@ -1772,7 +1795,7 @@ int shim_do_getsockopt(int fd, int level, int optname, char* optval, int* optlen
 
     int* intval = (int*)optval;
 
-    if (level != SOL_SOCKET && level != SOL_TCP)
+    if (level != SOL_SOCKET && level != SOL_TCP && level != IPPROTO_IPV6)
         goto unknown;
 
     if (level == SOL_SOCKET) {
@@ -1824,6 +1847,15 @@ int shim_do_getsockopt(int fd, int level, int optname, char* optval, int* optlen
         }
     }
 
+    if (level == IPPROTO_IPV6) {
+        switch (optname) {
+            case IPV6_V6ONLY:
+                break;
+            default:
+                goto unknown;
+        }
+    }
+
     /* at this point, we need to query PAL to get current attributes of hdl */
     PAL_STREAM_ATTR attr;
 
@@ -1885,6 +1917,14 @@ int shim_do_getsockopt(int fd, int level, int optname, char* optval, int* optlen
         }
     }
 
+    if (level == IPPROTO_IPV6) {
+        switch (optname) {
+            case IPV6_V6ONLY:
+                *intval = __socket_is_ipv6_v6only(hdl) ? 1 : 0;
+                break;
+        }
+    }
+
     ret = 0;
 
 out:

+ 88 - 0
LibOS/shim/test/regression/tcp_ipv6_v6only.c

@@ -0,0 +1,88 @@
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* use the same loopback address and port for both IPV6 and IPV4 */
+#define SRV_IPV6 "::1/128"
+#define SRV_IPV4 "127.0.0.1"
+#define PORT 11112
+
+int main(int argc, char** argv) {
+    int socket_ipv4;
+    int socket_ipv6;
+    int ret;
+
+    if ((socket_ipv6 = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
+        perror("socket(ipv6)");
+        return 1;
+    }
+
+    if ((socket_ipv4 = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+        perror("socket(ipv4)");
+        return 1;
+    }
+
+    int enable = 1;
+    if (setsockopt(socket_ipv6, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+        perror("setsockopt(ipv6, SO_REUSEADDR = 1)");
+        return 1;
+    }
+    if (setsockopt(socket_ipv4, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
+        perror("setsockopt(ipv4, SO_REUSEADDR = 1)");
+        return 1;
+    }
+
+    /* this forces IPV6 to listen for both IPV4 and IPV6 connections; this in turn makes binding
+     * another (IPV4) socket on the same port meaningless and results in -EADDRINUSE */
+    int disable = 0;
+    if (setsockopt(socket_ipv6, IPPROTO_IPV6, IPV6_V6ONLY, &disable, sizeof(disable)) < 0) {
+        perror("setsockopt(IPV6_V6ONLY = 0)");
+        return 1;
+    }
+
+    struct sockaddr_in6 address_ipv6;
+    memset(&address_ipv6, 0, sizeof(address_ipv6));
+    address_ipv6.sin6_family = AF_INET6;
+    address_ipv6.sin6_port   = htons(PORT);
+
+    if (inet_pton(AF_INET6, SRV_IPV6, &address_ipv6.sin6_addr) < 0) {
+        perror("inet_pton(ipv6)");
+        return 1;
+    }
+
+    if (bind(socket_ipv6, (struct sockaddr*)&address_ipv6, sizeof(address_ipv6)) < 0) {
+        perror("bind(ipv6)");
+        return 1;
+    }
+
+    /* we must start listening on IPV6 socket to make it active and kick in Linux rules for bind() */
+    if (listen(socket_ipv6, 3) < 0) {
+        perror("listen(ipv6)");
+        return 1;
+    }
+
+    struct sockaddr_in address_ipv4;
+    memset(&address_ipv4, 0, sizeof(address_ipv4));
+    address_ipv4.sin_family      = AF_INET;
+    address_ipv4.sin_port        = htons(PORT);       /* note the same port! */
+
+    if (inet_pton(AF_INET, SRV_IPV4, &address_ipv4.sin_addr) < 0) {
+        perror("inet_pton(ipv4)");
+        return 1;
+    }
+
+    ret = bind(socket_ipv4, (struct sockaddr*)&address_ipv4, sizeof(address_ipv4));
+    if (ret != -1 || errno != EADDRINUSE) {
+        fprintf(stderr, "bind(ipv4) was successful even though there is no IPV6_V6ONLY on same port\n");
+        return 1;
+    }
+
+    puts("test completed successfully");
+    return 0;
+}

+ 4 - 0
LibOS/shim/test/regression/test_libos.py

@@ -475,3 +475,7 @@ class TC_80_Socket(RegressionTestCase):
         self.assertIn('[client] checking how many bytes are left unread: 0', stdout)
         self.assertIn('[client] done', stdout)
         self.assertIn('[server] done', stdout)
+
+    def test_310_socket_tcp_ipv6_v6only(self):
+        stdout, _ = self.run_binary(['tcp_ipv6_v6only'], timeout=50)
+        self.assertIn('test completed successfully', stdout)

+ 3 - 3
Pal/src/host/Linux-SGX/db_pipes.c

@@ -65,8 +65,8 @@ static int pipe_listen(PAL_HANDLE* handle, PAL_NUM pipeid, int options) {
 
     unsigned int addrlen = sizeof(struct sockaddr_un);
     struct sockopt sock_options;
-    ret = ocall_listen(AF_UNIX, pipe_type(options), 0, (struct sockaddr*)&addr, &addrlen,
-                       &sock_options);
+    ret = ocall_listen(AF_UNIX, pipe_type(options), 0, /*ipv6_v6only=*/0,
+                       (struct sockaddr*)&addr, &addrlen, &sock_options);
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));
 
@@ -111,7 +111,7 @@ static int pipe_connect(PAL_HANDLE* handle, PAL_NUM pipeid, int options) {
         return ret;
 
     struct sockopt sock_options;
-    ret = ocall_connect(AF_UNIX, pipe_type(options), 0, (void*)&addr,
+    ret = ocall_connect(AF_UNIX, pipe_type(options), 0, /*ipv6_v6only=*/0, (void*)&addr,
                         sizeof(struct sockaddr_un), NULL, NULL, &sock_options);
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));

+ 18 - 15
Pal/src/host/Linux-SGX/db_sockets.c

@@ -315,7 +315,7 @@ static inline int sock_type(int type, int options) {
 }
 
 /* listen on a tcp socket */
-static int tcp_listen(PAL_HANDLE* handle, char* uri, int options) {
+static int tcp_listen(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer;
     struct sockaddr* bind_addr = &buffer;
     unsigned int bind_addrlen;
@@ -336,8 +336,9 @@ static int tcp_listen(PAL_HANDLE* handle, char* uri, int options) {
     memset(&sock_options, 0, sizeof(sock_options));
     sock_options.reuseaddr = 1; /* sockets are always set as reusable in Graphene */
 
-    ret = ocall_listen(bind_addr->sa_family, sock_type(SOCK_STREAM, options), 0, bind_addr,
-                       &bind_addrlen, &sock_options);
+    int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+    ret = ocall_listen(bind_addr->sa_family, sock_type(SOCK_STREAM, options), 0, ipv6_v6only,
+                       bind_addr, &bind_addrlen, &sock_options);
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));
 
@@ -416,8 +417,8 @@ static int tcp_connect(PAL_HANDLE* handle, char* uri, int options) {
     memset(&sock_options, 0, sizeof(sock_options));
     sock_options.reuseaddr = 1; /* sockets are always set as reusable in Graphene */
 
-    ret = ocall_connect(dest_addr->sa_family, sock_type(SOCK_STREAM, options), 0, dest_addr,
-                        dest_addrlen, bind_addr, &bind_addrlen, &sock_options);
+    ret = ocall_connect(dest_addr->sa_family, sock_type(SOCK_STREAM, options), 0, /*ipv6_v6only=*/0,
+                        dest_addr, dest_addrlen, bind_addr, &bind_addrlen, &sock_options);
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));
 
@@ -448,7 +449,7 @@ static int tcp_open(PAL_HANDLE* handle, const char* type, const char* uri, int a
     memcpy(uri_buf, uri, uri_len);
 
     if (!strcmp_static(type, URI_TYPE_TCP_SRV))
-        return tcp_listen(handle, uri_buf, options);
+        return tcp_listen(handle, uri_buf, create, options);
 
     if (!strcmp_static(type, URI_TYPE_TCP))
         return tcp_connect(handle, uri_buf, options);
@@ -503,7 +504,7 @@ static int64_t tcp_write(PAL_HANDLE handle, uint64_t offset, uint64_t len, const
 }
 
 /* used by 'open' operation of tcp stream for bound socket */
-static int udp_bind(PAL_HANDLE* handle, char* uri, int options) {
+static int udp_bind(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer;
     struct sockaddr* bind_addr = &buffer;
     unsigned int bind_addrlen;
@@ -527,8 +528,9 @@ static int udp_bind(PAL_HANDLE* handle, char* uri, int options) {
     memset(&sock_options, 0, sizeof(sock_options));
     sock_options.reuseaddr = 1; /* sockets are always set as reusable in Graphene */
 
-    ret = ocall_listen(bind_addr->sa_family, sock_type(SOCK_DGRAM, options), 0, bind_addr,
-                       &bind_addrlen, &sock_options);
+    int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+    ret = ocall_listen(bind_addr->sa_family, sock_type(SOCK_DGRAM, options), 0, ipv6_v6only,
+                       bind_addr, &bind_addrlen, &sock_options);
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));
 
@@ -544,7 +546,7 @@ static int udp_bind(PAL_HANDLE* handle, char* uri, int options) {
 }
 
 /* used by 'open' operation of tcp stream for connected socket */
-static int udp_connect(PAL_HANDLE* handle, char* uri, int options) {
+static int udp_connect(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer[2];
     struct sockaddr* bind_addr = buffer;
     struct sockaddr* dest_addr = buffer + 1;
@@ -566,9 +568,10 @@ static int udp_connect(PAL_HANDLE* handle, char* uri, int options) {
     memset(&sock_options, 0, sizeof(sock_options));
     sock_options.reuseaddr = 1; /* sockets are always set as reusable in Graphene */
 
-    ret = ocall_connect(dest_addr ? dest_addr->sa_family : AF_INET,
-                        sock_type(SOCK_DGRAM, options), 0, dest_addr, dest_addrlen, bind_addr,
-                        &bind_addrlen, &sock_options);
+    int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+    ret = ocall_connect(dest_addr ? dest_addr->sa_family : AF_INET, sock_type(SOCK_DGRAM, options),
+                        0, ipv6_v6only, dest_addr, dest_addrlen, bind_addr, &bind_addrlen,
+                        &sock_options);
 
     if (IS_ERR(ret))
         return unix_to_pal_error(ERRNO(ret));
@@ -599,10 +602,10 @@ static int udp_open(PAL_HANDLE* hdl, const char* type, const char* uri, int acce
     memcpy(buf, uri, len + 1);
 
     if (!strcmp_static(type, URI_TYPE_UDP_SRV))
-        return udp_bind(hdl, buf, options);
+        return udp_bind(hdl, buf, create, options);
 
     if (!strcmp_static(type, URI_TYPE_UDP))
-        return udp_connect(hdl, buf, options);
+        return udp_connect(hdl, buf, create, options);
 
     return -PAL_ERROR_NOTSUPPORT;
 }

+ 10 - 12
Pal/src/host/Linux-SGX/enclave_ocalls.c

@@ -570,14 +570,12 @@ int ocall_socketpair (int domain, int type, int protocol,
     return retval;
 }
 
-int ocall_listen (int domain, int type, int protocol,
-                  struct sockaddr * addr, unsigned int * addrlen,
-                  struct sockopt * sockopt)
-{
+int ocall_listen(int domain, int type, int protocol, int ipv6_v6only,
+                 struct sockaddr* addr, unsigned int* addrlen, struct sockopt* sockopt) {
     int retval = 0;
     unsigned int copied;
     unsigned int len = addrlen ? *addrlen : 0;
-    ms_ocall_listen_t * ms;
+    ms_ocall_listen_t* ms;
 
     ms = sgx_alloc_on_ustack(sizeof(*ms));
     if (!ms) {
@@ -588,6 +586,7 @@ int ocall_listen (int domain, int type, int protocol,
     ms->ms_domain = domain;
     ms->ms_type = type;
     ms->ms_protocol = protocol;
+    ms->ms_ipv6_v6only = ipv6_v6only;
     ms->ms_addrlen = len;
     ms->ms_addr = (addr && len) ? sgx_copy_to_ustack(addr, len) : NULL;
 
@@ -661,16 +660,14 @@ int ocall_accept (int sockfd, struct sockaddr * addr,
     return retval;
 }
 
-int ocall_connect (int domain, int type, int protocol,
-                   const struct sockaddr * addr,
-                   unsigned int addrlen,
-                   struct sockaddr * bind_addr,
-                   unsigned int * bind_addrlen, struct sockopt * sockopt)
-{
+int ocall_connect(int domain, int type, int protocol, int ipv6_v6only,
+                  const struct sockaddr* addr, unsigned int addrlen,
+                  struct sockaddr* bind_addr, unsigned int* bind_addrlen,
+                  struct sockopt* sockopt) {
     int retval = 0;
     unsigned int copied;
     unsigned int bind_len = bind_addrlen ? *bind_addrlen : 0;
-    ms_ocall_connect_t * ms;
+    ms_ocall_connect_t* ms;
 
     ms = sgx_alloc_on_ustack(sizeof(*ms));
     if (!ms) {
@@ -681,6 +678,7 @@ int ocall_connect (int domain, int type, int protocol,
     ms->ms_domain = domain;
     ms->ms_type = type;
     ms->ms_protocol = protocol;
+    ms->ms_ipv6_v6only = ipv6_v6only;
     ms->ms_addrlen = addrlen;
     ms->ms_bind_addrlen = bind_len;
     ms->ms_addr = addr ? sgx_copy_to_ustack(addr, addrlen) : NULL;

+ 6 - 7
Pal/src/host/Linux-SGX/enclave_ocalls.h

@@ -45,17 +45,16 @@ int ocall_mkdir (const char *pathname, unsigned short mode);
 
 int ocall_getdents (int fd, struct linux_dirent64 *dirp, unsigned int size);
 
-int ocall_listen (int domain, int type, int protocol,
-                  struct sockaddr * addr, unsigned int * addrlen,
-                  struct sockopt * opt);
+int ocall_listen(int domain, int type, int protocol, int ipv6_v6only,
+                 struct sockaddr* addr, unsigned int* addrlen, struct sockopt* sockopt);
 
 int ocall_accept (int sockfd, struct sockaddr * addr,
                   unsigned int * addrlen, struct sockopt * opt);
 
-int ocall_connect (int domain, int type, int protocol,
-                   const struct sockaddr * addr, unsigned int addrlen,
-                   struct sockaddr * connaddr,
-                   unsigned int * connaddrlen, struct sockopt * opt);
+int ocall_connect(int domain, int type, int protocol, int ipv6_v6only,
+                  const struct sockaddr* addr, unsigned int addrlen,
+                  struct sockaddr* bind_addr, unsigned int* bind_addrlen,
+                  struct sockopt* sockopt);
 
 int ocall_recv (int sockfd, void * buf, unsigned int count,
                 struct sockaddr * addr, unsigned int * addrlenptr,

+ 11 - 5
Pal/src/host/Linux-SGX/ocall_types.h

@@ -172,8 +172,11 @@ typedef struct {
 } ms_ocall_socketpair_t;
 
 typedef struct {
-    int ms_domain, ms_type, ms_protocol;
-    const struct sockaddr * ms_addr;
+    int ms_domain;
+    int ms_type;
+    int ms_protocol;
+    int ms_ipv6_v6only;
+    const struct sockaddr* ms_addr;
     unsigned int ms_addrlen;
     struct sockopt ms_sockopt;
 } ms_ocall_listen_t;
@@ -186,10 +189,13 @@ typedef struct {
 } ms_ocall_accept_t;
 
 typedef struct {
-    int ms_domain, ms_type, ms_protocol;
-    const struct sockaddr * ms_addr;
+    int ms_domain;
+    int ms_type;
+    int ms_protocol;
+    int ms_ipv6_v6only;
+    const struct sockaddr* ms_addr;
     unsigned int ms_addrlen;
-    struct sockaddr * ms_bind_addr;
+    struct sockaddr* ms_bind_addr;
     unsigned int ms_bind_addrlen;
     struct sockopt ms_sockopt;
 } ms_ocall_connect_t;

+ 1 - 0
Pal/src/host/Linux-SGX/pal_linux_error.h

@@ -22,6 +22,7 @@ static inline __attribute__((unused)) int unix_to_pal_error(int unix_errno) {
         case EFAULT:
             return -PAL_ERROR_BADADDR;
         case EEXIST:
+        case EADDRINUSE:
             return -PAL_ERROR_STREAMEXIST;
         case ENOTDIR:
             return -PAL_ERROR_STREAMISFILE;

+ 19 - 6
Pal/src/host/Linux-SGX/sgx_enclave.c

@@ -14,10 +14,6 @@
 #include <math.h>
 #include <asm/errno.h>
 
-#ifndef SOL_IPV6
-# define SOL_IPV6 41
-#endif
-
 #define ODEBUG(code, ms) do {} while (0)
 
 static int sgx_ocall_exit(void* pms)
@@ -293,8 +289,17 @@ static int sgx_ocall_listen(void * pms)
 
     /* must set the socket to be reuseable */
     int reuseaddr = 1;
-    INLINE_SYSCALL(setsockopt, 5, fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
-                   sizeof(int));
+    ret = INLINE_SYSCALL(setsockopt, 5, fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+    if (IS_ERR(ret))
+        goto err_fd;
+
+    if (ms->ms_domain == AF_INET6) {
+        /* IPV6_V6ONLY socket option can only be set before first bind */
+        ret = INLINE_SYSCALL(setsockopt, 5, fd, IPPROTO_IPV6, IPV6_V6ONLY, &ms->ms_ipv6_v6only,
+                             sizeof(ms->ms_ipv6_v6only));
+        if (IS_ERR(ret))
+            goto err_fd;
+    }
 
     ret = INLINE_SYSCALL(bind, 3, fd, ms->ms_addr, ms->ms_addrlen);
     if (IS_ERR(ret))
@@ -367,6 +372,14 @@ static int sgx_ocall_connect(void * pms)
     fd = ret;
 
     if (ms->ms_bind_addr && ms->ms_bind_addr->sa_family) {
+        if (ms->ms_domain == AF_INET6) {
+            /* IPV6_V6ONLY socket option can only be set before first bind */
+            ret = INLINE_SYSCALL(setsockopt, 5, fd, IPPROTO_IPV6, IPV6_V6ONLY, &ms->ms_ipv6_v6only,
+                                 sizeof(ms->ms_ipv6_v6only));
+            if (IS_ERR(ret))
+                goto err_fd;
+        }
+
         ret = INLINE_SYSCALL(bind, 3, fd, ms->ms_bind_addr,
                              ms->ms_bind_addrlen);
         if (IS_ERR(ret))

+ 34 - 11
Pal/src/host/Linux/db_sockets.c

@@ -54,10 +54,6 @@ typedef __kernel_pid_t pid_t;
 #define TCP_CORK 3
 #endif
 
-#ifndef SOL_IPV6
-#define SOL_IPV6 41
-#endif
-
 /* 96 bytes is the minimal size of buffer to store a IPv4/IPv6
    address */
 #define PAL_SOCKADDR_SIZE 96
@@ -324,7 +320,7 @@ static bool check_any_addr(struct sockaddr* addr) {
 }
 
 /* listen on a tcp socket */
-static int tcp_listen(PAL_HANDLE* handle, char* uri, int options) {
+static int tcp_listen(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer;
     struct sockaddr* bind_addr = &buffer;
     size_t bind_addrlen;
@@ -350,7 +346,17 @@ static int tcp_listen(PAL_HANDLE* handle, char* uri, int options) {
 
     /* must set the socket to be reuseable */
     int reuseaddr = 1;
-    INLINE_SYSCALL(setsockopt, 5, fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int));
+    ret = INLINE_SYSCALL(setsockopt, 5, fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr));
+    if (IS_ERR(ret))
+        return -PAL_ERROR_INVAL;
+
+    if (bind_addr->sa_family == AF_INET6) {
+        /* IPV6_V6ONLY socket option can only be set before first bind */
+        int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+        ret = INLINE_SYSCALL(setsockopt, 5, fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_v6only, sizeof(ipv6_v6only));
+        if (IS_ERR(ret))
+            return -PAL_ERROR_INVAL;
+    }
 
     ret = INLINE_SYSCALL(bind, 3, fd, bind_addr, bind_addrlen);
 
@@ -528,7 +534,7 @@ static int tcp_open(PAL_HANDLE* handle, const char* type, const char* uri, int a
     memcpy(uri_buf, uri, uri_len);
 
     if (!strcmp_static(type, URI_TYPE_TCP_SRV))
-        return tcp_listen(handle, uri_buf, options);
+        return tcp_listen(handle, uri_buf, create, options);
 
     if (!strcmp_static(type, URI_TYPE_TCP))
         return tcp_connect(handle, uri_buf, options);
@@ -601,7 +607,7 @@ static int64_t tcp_write(PAL_HANDLE handle, uint64_t offset, size_t len, const v
 }
 
 /* used by 'open' operation of tcp stream for bound socket */
-static int udp_bind(PAL_HANDLE* handle, char* uri, int options) {
+static int udp_bind(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer;
     struct sockaddr* bind_addr = &buffer;
     size_t bind_addrlen;
@@ -625,6 +631,14 @@ static int udp_bind(PAL_HANDLE* handle, char* uri, int options) {
     if (IS_ERR(fd))
         return -PAL_ERROR_DENIED;
 
+    /* IPV6_V6ONLY socket option can only be set before first bind */
+    if (bind_addr->sa_family == AF_INET6) {
+        int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+        ret = INLINE_SYSCALL(setsockopt, 5, fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_v6only, sizeof(ipv6_v6only));
+        if (IS_ERR(ret))
+            return -PAL_ERROR_INVAL;
+    }
+
     ret = INLINE_SYSCALL(bind, 3, fd, bind_addr, bind_addrlen);
 
     if (IS_ERR(ret)) {
@@ -656,7 +670,7 @@ failed:
 }
 
 /* used by 'open' operation of tcp stream for connected socket */
-static int udp_connect(PAL_HANDLE* handle, char* uri, int options) {
+static int udp_connect(PAL_HANDLE* handle, char* uri, int create, int options) {
     struct sockaddr buffer[2];
     struct sockaddr* bind_addr = buffer;
     struct sockaddr* dest_addr = buffer + 1;
@@ -680,6 +694,15 @@ static int udp_connect(PAL_HANDLE* handle, char* uri, int options) {
         return -PAL_ERROR_DENIED;
 
     if (bind_addr) {
+        if (bind_addr->sa_family == AF_INET6) {
+            /* IPV6_V6ONLY socket option can only be set before first bind */
+            int ipv6_v6only = create & PAL_CREATE_DUALSTACK ? 0 : 1;
+            ret = INLINE_SYSCALL(setsockopt, 5, fd, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6_v6only,
+                                 sizeof(ipv6_v6only));
+            if (IS_ERR(ret))
+                return -PAL_ERROR_INVAL;
+        }
+
         ret = INLINE_SYSCALL(bind, 3, fd, bind_addr, bind_addrlen);
 
         if (IS_ERR(ret)) {
@@ -727,10 +750,10 @@ static int udp_open(PAL_HANDLE* hdl, const char* type, const char* uri, int acce
     memcpy(buf, uri, len + 1);
 
     if (!strcmp_static(type, URI_TYPE_UDP_SRV))
-        return udp_bind(hdl, buf, options);
+        return udp_bind(hdl, buf, create, options);
 
     if (!strcmp_static(type, URI_TYPE_UDP))
-        return udp_connect(hdl, buf, options);
+        return udp_connect(hdl, buf, create, options);
 
     return -PAL_ERROR_NOTSUPPORT;
 }

+ 1 - 0
Pal/src/host/Linux/pal_linux_error.h

@@ -22,6 +22,7 @@ static inline __attribute__((unused)) int unix_to_pal_error(int unix_errno) {
         case EFAULT:
             return -PAL_ERROR_BADADDR;
         case EEXIST:
+        case EADDRINUSE:
             return -PAL_ERROR_STREAMEXIST;
         case ENOTDIR:
             return -PAL_ERROR_STREAMISFILE;

+ 4 - 5
Pal/src/pal.h

@@ -426,11 +426,10 @@ DkProcessExit (PAL_NUM exitCode);
 #define PAL_SHARE_MASK      0777
 
 /* Stream Create Flags */
-#define PAL_CREATE_TRY        0100       /* 0100 Create file if file not
-                                           exist (O_CREAT) */
-#define PAL_CREATE_ALWAYS     0200       /* 0300 Create file and fail if file
-                                           already exist (O_CREAT|O_EXCL) */
-#define PAL_CREATE_MASK       0300
+#define PAL_CREATE_TRY        0100       /* Create file if file not exist (O_CREAT) */
+#define PAL_CREATE_ALWAYS     0200       /* Create file and fail if file already exist (O_CREAT|O_EXCL) */
+#define PAL_CREATE_DUALSTACK  0400       /* Create dual-stack socket (opposite of IPV6_V6ONLY) */
+#define PAL_CREATE_MASK       0700
 
 /* Stream Option Flags */
 #define PAL_OPTION_NONBLOCK     04000