From b31dc21bbdae0ddeb096bf53d79af804cca4a4ea Mon Sep 17 00:00:00 2001
From: "Wesley W. Terpstra" <w.terpstra@gsi.de>
Date: Thu, 8 Mar 2012 16:31:06 +0000
Subject: [PATCH] Implement dual-stack using separate IPv4/6 sockets. IPv6
 should now work under Windows.

---
 api/transport/posix-ip.c  | 18 ++++++++
 api/transport/posix-tcp.c | 49 ++++++++++++++-------
 api/transport/posix-tcp.h |  5 ++-
 api/transport/posix-udp.c | 91 +++++++++++++++++++++++++++------------
 api/transport/posix-udp.h |  3 +-
 5 files changed, 122 insertions(+), 44 deletions(-)

diff --git a/api/transport/posix-ip.c b/api/transport/posix-ip.c
index 89a3e18..1593a15 100644
--- a/api/transport/posix-ip.c
+++ b/api/transport/posix-ip.c
@@ -36,6 +36,7 @@
 #define EB_DEFAULT_PORT_STR "60368" /* 0xEBD0 */
 
 void eb_posix_ip_close(eb_posix_sock_t sock) {
+  if (sock == -1) return;
 #ifdef __WIN32
   closesocket(sock);
 #else
@@ -47,6 +48,7 @@ eb_posix_sock_t eb_posix_ip_open(int family, int type, const char* port) {
   struct addrinfo hints, *match, *i;
   eb_posix_sock_t sock;
   int protocol;
+  int optval;
   
   switch (type) {
   case SOCK_DGRAM:  protocol = IPPROTO_UDP; break;
@@ -68,6 +70,15 @@ eb_posix_sock_t eb_posix_ip_open(int family, int type, const char* port) {
   for (i = match; i; i = i->ai_next) {
     sock = socket(i->ai_family, i->ai_socktype, i->ai_protocol);
     if (sock == -1) continue;
+
+#ifndef EB_DISABLE_IPV6    
+    /* We bind IPv6 sockets to only IPv6... for compatability with Windows */
+    if (i->ai_family == PF_INET6) {
+      optval = 1;
+      setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&optval, sizeof(optval));
+    }
+#endif
+    
     if (bind(sock, i->ai_addr, i->ai_addrlen) == 0) break;
     eb_posix_ip_close(sock);
   }
@@ -75,6 +86,12 @@ eb_posix_sock_t eb_posix_ip_open(int family, int type, const char* port) {
   freeaddrinfo(match);
   if (!i) return -1;
   
+  /* Etherbone can broadcast over UDP */
+  if (sock != -1 && protocol == IPPROTO_UDP) {
+    optval = 1;
+    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&optval, sizeof(optval));
+  }
+  
   return sock;
 }
 
@@ -129,6 +146,7 @@ socklen_t eb_posix_ip_resolve(const char* prefix, const char* address, int famil
 }
 
 void eb_posix_ip_force_non_blocking(eb_posix_sock_t sock, unsigned long on) {
+  if (sock == -1) return;
 #if defined(__WIN32)
   ioctlsocket(sock, FIONBIO, &on);
 #else
diff --git a/api/transport/posix-tcp.c b/api/transport/posix-tcp.c
index 4043c94..075e22f 100644
--- a/api/transport/posix-tcp.c
+++ b/api/transport/posix-tcp.c
@@ -36,20 +36,29 @@
 
 eb_status_t eb_posix_tcp_open(struct eb_transport* transportp, const char* port) {
   struct eb_posix_tcp_transport* transport;
-  eb_posix_sock_t sock;
-  
-  sock = eb_posix_ip_open(PF_INET6, SOCK_STREAM, port);
-  if (sock == -1) return EB_BUSY;
-  
-  if (listen(sock, 5) != 0) {
-    eb_posix_ip_close(sock);
+  eb_posix_sock_t sock4, sock6;
+  
+  sock4 = eb_posix_ip_open(PF_INET, SOCK_STREAM, port);
+#ifdef EB_DISABLE_IPV6    
+  sock6 = -1;
+#else
+  sock6 = eb_posix_ip_open(PF_INET6, SOCK_STREAM, port);
+#endif
+  if (sock4 == -1 && sock6 == -1) return EB_BUSY;
+  
+  if ((sock4 != -1 && listen(sock4, 5) != 0) ||
+      (sock6 != -1 && listen(sock6, 5) != 0)) {
+    eb_posix_ip_close(sock4);
+    eb_posix_ip_close(sock6);
     return EB_ADDRESS; 
   }
 
-  eb_posix_ip_force_non_blocking(sock, 1);
+  eb_posix_ip_force_non_blocking(sock4, 1);
+  eb_posix_ip_force_non_blocking(sock6, 1);
   
   transport = (struct eb_posix_tcp_transport*)transportp;
-  transport->port = sock;
+  transport->port4 = sock4;
+  transport->port6 = sock6;
 
   return EB_OK;
 }
@@ -58,7 +67,8 @@ void eb_posix_tcp_close(struct eb_transport* transportp) {
   struct eb_posix_tcp_transport* transport;
   
   transport = (struct eb_posix_tcp_transport*)transportp;
-  eb_posix_ip_close(transport->port);
+  eb_posix_ip_close(transport->port4);
+  eb_posix_ip_close(transport->port6);
 }
 
 eb_status_t eb_posix_tcp_connect(struct eb_transport* transportp, struct eb_link* linkp, const char* address) {
@@ -104,7 +114,8 @@ void eb_posix_tcp_fdes(struct eb_transport* transportp, struct eb_link* linkp, e
     (*cb)(data, link->socket);
   } else {
     transport = (struct eb_posix_tcp_transport*)transportp;
-    (*cb)(data, transport->port);
+    if (transport->port4 != -1) (*cb)(data, transport->port4);
+    if (transport->port6 != -1) (*cb)(data, transport->port6);
   }
 }
 
@@ -162,14 +173,22 @@ int eb_posix_tcp_accept(struct eb_transport* transportp, struct eb_link* result_
   struct eb_posix_tcp_link* result_link;
   eb_posix_sock_t sock;
   
+  sock = -1;
   transport = (struct eb_posix_tcp_transport*)transportp;
   
-  sock = accept(transport->port, 0, 0);
-  if (sock == -1) {
-    if (errno != EAGAIN) return -1;
-    return 0;
+  if (sock == -1 && transport->port4 != -1) {
+    sock = accept(transport->port4, 0, 0);
+    if (sock == -1 && errno != EAGAIN) return -1;
   }
   
+  if (sock == -1 && transport->port6 != -1) {
+    sock = accept(transport->port6, 0, 0);
+    if (sock == -1 && errno != EAGAIN) return -1;
+  }
+  
+  if (sock == -1) 
+    return 0;
+  
   if (result_linkp != 0) {
     result_link = (struct eb_posix_tcp_link*)result_linkp;
     result_link->socket = sock;
diff --git a/api/transport/posix-tcp.h b/api/transport/posix-tcp.h
index 30e84db..a0b6e39 100644
--- a/api/transport/posix-tcp.h
+++ b/api/transport/posix-tcp.h
@@ -46,7 +46,10 @@ EB_PRIVATE int eb_posix_tcp_accept(struct eb_transport*, struct eb_link* result_
 
 struct eb_posix_tcp_transport {
   /* Contents must fit in 9 bytes */
-  eb_posix_sock_t port;
+  eb_posix_sock_t port4; /* IPv4 */
+#ifndef EB_DISABLE_IPV6
+  eb_posix_sock_t port6; /* IPv6 */
+#endif
 };
 
 struct eb_posix_tcp_link {
diff --git a/api/transport/posix-udp.c b/api/transport/posix-udp.c
index 5fa7a37..e0de162 100644
--- a/api/transport/posix-udp.c
+++ b/api/transport/posix-udp.c
@@ -45,21 +45,22 @@
 
 eb_status_t eb_posix_udp_open(struct eb_transport* transportp, const char* port) {
   struct eb_posix_udp_transport* transport;
-  eb_posix_sock_t sock;
-  int optval;
+  eb_posix_sock_t sock4, sock6;
   
-  sock = eb_posix_ip_open(PF_INET6, SOCK_DGRAM, port);
-  if (sock == -1) return EB_BUSY;
-  
-  /* Etherbone can broadcast */
-  optval = 1;
-  if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&optval, sizeof(optval)) != 0) {
-    eb_posix_ip_close(sock);
-    return EB_FAIL;
-  }
+  sock4 = eb_posix_ip_open(PF_INET, SOCK_DGRAM, port);
+#ifdef EB_DISABLE_IPV6    
+  sock6 = -1;
+#else
+  sock6 = eb_posix_ip_open(PF_INET6, SOCK_DGRAM, port);
+#endif
+
+  /* Failure if we can't get either protocol */
+  if (sock4 == -1 && sock6 == -1) 
+    return EB_BUSY;
   
   transport = (struct eb_posix_udp_transport*)transportp;
-  transport->socket = sock;
+  transport->socket4 = sock4;
+  transport->socket6 = sock6;
   
   return EB_OK;
 }
@@ -68,22 +69,34 @@ void eb_posix_udp_close(struct eb_transport* transportp) {
   struct eb_posix_udp_transport* transport;
   
   transport = (struct eb_posix_udp_transport*)transportp;
-  eb_posix_ip_close(transport->socket);
+  eb_posix_ip_close(transport->socket4);
+  eb_posix_ip_close(transport->socket6);
 }
 
 eb_status_t eb_posix_udp_connect(struct eb_transport* transportp, struct eb_link* linkp, const char* address) {
+  struct eb_posix_udp_transport* transport;
   struct eb_posix_udp_link* link;
   struct sockaddr_storage sa;
   socklen_t len;
   
   len = -1;
-  if (len == -1) len = eb_posix_ip_resolve("udp6/", address, PF_INET6, SOCK_DGRAM, &sa);
   if (len == -1) len = eb_posix_ip_resolve("udp4/", address, PF_INET,  SOCK_DGRAM, &sa);
+#ifndef EB_DISABLE_IPV6
+  if (len == -1) len = eb_posix_ip_resolve("udp6/", address, PF_INET6, SOCK_DGRAM, &sa);
   if (len == -1) len = eb_posix_ip_resolve("udp/",  address, PF_INET6, SOCK_DGRAM, &sa);
+#endif
   if (len == -1) len = eb_posix_ip_resolve("udp/",  address, PF_INET,  SOCK_DGRAM, &sa);
   if (len == -1) return EB_ADDRESS;
   
+  transport = (struct eb_posix_udp_transport*)transportp;
   link = (struct eb_posix_udp_link*)linkp;
+
+  /* Do we have support for the socket? */
+  if (sa.ss_family == PF_INET  && transport->socket4 == -1) return EB_FAIL;
+#ifndef EB_DISABLE_IPV6
+  if (sa.ss_family == PF_INET6 && transport->socket6 == -1) return EB_FAIL;
+#endif
+
   link->sa = (struct sockaddr_storage*)malloc(sizeof(struct sockaddr_storage));
   link->sa_len = len;
   
@@ -104,7 +117,10 @@ void eb_posix_udp_fdes(struct eb_transport* transportp, struct eb_link* linkp, e
   
   transport = (struct eb_posix_udp_transport*)transportp;
   if (linkp == 0) {
-    (*cb)(data, transport->socket);
+    if (transport->socket4 != -1) (*cb)(data, transport->socket4);
+#ifndef EB_DISABLE_IPV6
+    if (transport->socket6 != -1) (*cb)(data, transport->socket6);
+#endif
   } else {
     /* no per-link socket */
   }
@@ -123,14 +139,24 @@ int eb_posix_udp_poll(struct eb_transport* transportp, struct eb_link* linkp, ui
   transport = (struct eb_posix_udp_transport*)transportp;
   
   /* Set non-blocking */
-  eb_posix_ip_non_blocking(transport->socket, 1);
+  eb_posix_ip_non_blocking(transport->socket4, 1);
+  eb_posix_ip_non_blocking(transport->socket6, 1);
+  
+  if (transport->socket4 != -1) {
+    eb_posix_udp_sa_len = sizeof(eb_posix_udp_sa);
+    result = recvfrom(transport->socket4, (char*)buf, len, MSG_DONTWAIT, (struct sockaddr*)&eb_posix_udp_sa, &eb_posix_udp_sa_len);
+    if (result == -1 && errno != EAGAIN) return -1;
+    if (result != -1) return result;
+  }
   
-  eb_posix_udp_sa_len = sizeof(eb_posix_udp_sa);
-  result = recvfrom(transport->socket, (char*)buf, len, MSG_DONTWAIT, (struct sockaddr*)&eb_posix_udp_sa, &eb_posix_udp_sa_len);
+  if (transport->socket6 != -1) {
+    eb_posix_udp_sa_len = sizeof(eb_posix_udp_sa);
+    result = recvfrom(transport->socket6, (char*)buf, len, MSG_DONTWAIT, (struct sockaddr*)&eb_posix_udp_sa, &eb_posix_udp_sa_len);
+    if (result == -1 && errno != EAGAIN) return -1;
+    if (result != -1) return result;
+  }
   
-  if (result == -1 && errno == EAGAIN) return 0;
-  if (result == 0) return -1;
-  return result;
+  return 0;
 }
 
 int eb_posix_udp_recv(struct eb_transport* transportp, struct eb_link* linkp, uint8_t* buf, int len) {
@@ -152,13 +178,24 @@ void eb_posix_udp_send(struct eb_transport* transportp, struct eb_link* linkp, c
   transport = (struct eb_posix_udp_transport*)transportp;
   link = (struct eb_posix_udp_link*)linkp;
   
-  /* Set blocking */
-  eb_posix_ip_non_blocking(transport->socket, 0);
   
-  if (link == 0)
-    sendto(transport->socket, (const char*)buf, len, 0, (struct sockaddr*)&eb_posix_udp_sa, eb_posix_udp_sa_len);
-  else
-    sendto(transport->socket, (const char*)buf, len, 0, (struct sockaddr*)link->sa, link->sa_len);
+  if (link == 0) {
+    if (eb_posix_udp_sa.ss_family == PF_INET6) {
+      eb_posix_ip_non_blocking(transport->socket6, 0);
+      sendto(transport->socket6, (const char*)buf, len, 0, (struct sockaddr*)&eb_posix_udp_sa, eb_posix_udp_sa_len);
+    } else {
+      eb_posix_ip_non_blocking(transport->socket4, 0);
+      sendto(transport->socket4, (const char*)buf, len, 0, (struct sockaddr*)&eb_posix_udp_sa, eb_posix_udp_sa_len);
+    }
+  } else {
+    if (link->sa->ss_family == PF_INET6) {
+      eb_posix_ip_non_blocking(transport->socket6, 0);
+      sendto(transport->socket6, (const char*)buf, len, 0, (struct sockaddr*)link->sa, link->sa_len);
+    } else {
+      eb_posix_ip_non_blocking(transport->socket4, 0);
+      sendto(transport->socket4, (const char*)buf, len, 0, (struct sockaddr*)link->sa, link->sa_len);
+    }
+  }
 }
 
 int eb_posix_udp_accept(struct eb_transport* transportp, struct eb_link* result_linkp) {
diff --git a/api/transport/posix-udp.h b/api/transport/posix-udp.h
index 13587e6..98184c1 100644
--- a/api/transport/posix-udp.h
+++ b/api/transport/posix-udp.h
@@ -46,7 +46,8 @@ EB_PRIVATE int eb_posix_udp_accept(struct eb_transport*, struct eb_link* result_
 
 struct eb_posix_udp_transport {
   /* Contents must fit in 9 bytes */
-  eb_posix_sock_t socket;
+  eb_posix_sock_t socket4; /* IPv4 */
+  eb_posix_sock_t socket6; /* IPv6 */
 };
 
 struct eb_posix_udp_link {
-- 
GitLab