From e293ad1b08b2f937555a102e6f3b4336574773c8 Mon Sep 17 00:00:00 2001
From: Serge Aleynikov <saleyn@gmail.com>
Date: Wed, 30 Dec 2015 13:29:34 -0500
Subject: Assign externally open fd to gen_tcp (UDS support)

When a AF_LOCAL file descriptor is created externally (e.g. Unix
Domain Socket) and passed to `gen_tcp:listen(0, [{fd, FD}])`, the
implementation incorrectly assigned the address family to be equal
to `inet`, which in the inet_drv driver translated to AF_INET instead
of AF_LOCAL (or AF_UNIX), and an `einval` error code was returned.
This patch fixes this problem such that the file descriptors of the
`local` address family are supported in the inet:fdopen/5,
gen_tcp:connect/3, gen_tcp:listen/2, gen_udp:open/2 calls
---
 erts/configure.in                       |   2 +-
 erts/emulator/drivers/common/inet_drv.c | 161 +++++++++++++++++++++++++++++---
 erts/preloaded/src/prim_inet.erl        |  17 +++-
 3 files changed, 162 insertions(+), 18 deletions(-)

(limited to 'erts')

diff --git a/erts/configure.in b/erts/configure.in
index 4fb725ff00..68ead3285b 100644
--- a/erts/configure.in
+++ b/erts/configure.in
@@ -2238,7 +2238,7 @@ fi
 dnl Need by run_erl.
 AC_CHECK_FUNCS([openpty])
 
-AC_CHECK_HEADERS(net/if_dl.h ifaddrs.h netpacket/packet.h)
+AC_CHECK_HEADERS(net/if_dl.h ifaddrs.h netpacket/packet.h sys/un.h)
 AC_CHECK_FUNCS([getifaddrs])
 
 dnl Checks for variables in6addr_any and in6addr_loopback,
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c
index 89011d89ad..bd4543f831 100644
--- a/erts/emulator/drivers/common/inet_drv.c
+++ b/erts/emulator/drivers/common/inet_drv.c
@@ -58,6 +58,9 @@
 #ifdef HAVE_NETPACKET_PACKET_H
 #include <netpacket/packet.h>
 #endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
 
 /* All platforms fail on malloc errors. */
 #define FATAL_MALLOC
@@ -747,6 +750,7 @@ static int my_strncasecmp(const char *s1, const char *s2, size_t n)
 #define INET_AF_INET6       2
 #define INET_AF_ANY         3 /* INADDR_ANY or IN6ADDR_ANY_INIT */
 #define INET_AF_LOOPBACK    4 /* INADDR_LOOPBACK or IN6ADDR_LOOPBACK_INIT */
+#define INET_AF_LOCAL       5
 
 /* open and INET_REQ_GETTYPE enumeration */
 #define INET_TYPE_STREAM    1
@@ -1032,19 +1036,29 @@ typedef union {
 #ifdef HAVE_IN6
     struct sockaddr_in6 sai6;
 #endif
+#ifdef HAVE_SYS_UN_H
+    struct sockaddr_un sal;
+#endif
 } inet_address;
 
 
 /* for AF_INET & AF_INET6 */
 #define inet_address_port(x) ((x)->sai.sin_port)
 
+#ifdef HAVE_SYS_UN_H
+#define localaddrlen(family, data) \
+    ((family == AF_LOCAL) ? *(unsigned char*)(data) : 0)
+#else
+    0
+#endif
+
 #if defined(HAVE_IN6) && defined(AF_INET6)
-#define addrlen(family) \
+#define addrlen(family, data) \
    ((family == AF_INET) ? sizeof(struct in_addr) : \
-    ((family == AF_INET6) ? sizeof(struct in6_addr) : 0))
+    ((family == AF_INET6) ? sizeof(struct in6_addr) : localaddrlen(family, data)))
 #else
-#define addrlen(family) \
-   ((family == AF_INET) ? sizeof(struct in_addr) : 0)
+#define addrlen(family, data) \
+   ((family == AF_INET) ? sizeof(struct in_addr) : localaddrlen(family, data))
 #endif
 
 typedef struct _multi_timer_data {
@@ -1727,6 +1741,12 @@ static int load_ip_address(ErlDrvTermData* spec, int i, int family, char* buf)
 	spec[i++] = ERL_DRV_TUPLE;
 	spec[i++] = 8;
     }
+#endif
+#ifdef HAVE_SYS_UN_H
+    else if (family == AF_LOCAL) {
+	int len = *(unsigned char*)buf++;
+	i = LOAD_STRING(spec, i, buf, len);
+    }
 #endif
     else {
 	spec[i++] = ERL_DRV_TUPLE;
@@ -3573,10 +3593,11 @@ static int tcp_error_message(tcp_descriptor* desc, int err)
 #ifdef HAVE_UDP
 /* 
 ** active mode message:
-**        {udp,  S, IP, Port, [H1,...Hsz | Data]} or
-**	  {sctp, S, IP, Port, {[AncilData],  Event_or_Data}}
+**    {udp,  S, IP, Port, [H1,...Hsz | Data]} or
+**    {sctp, S, IP, Port, {[AncilData],  Event_or_Data}}
 ** where
 ** 	  [H1,...,HSz] are msg headers (without IP/Port, UDP only),
+**    [AddrLen, H2,...,HSz] are msg headers for UDP AF_LOCAL only
 **	  Data  : List() | Binary()
 */
 static int packet_binary_message
@@ -3586,6 +3607,7 @@ static int packet_binary_message
     ErlDrvTermData spec [PACKET_ERL_DRV_TERM_DATA_LEN];
     int i = 0;
     int alen;
+    char* data = bin->orig_bytes+offs;
 
     DEBUGF(("packet_binary_message(%ld): len = %d\r\n",
 	   (long)desc->port, len));
@@ -3596,10 +3618,15 @@ static int packet_binary_message
 #   endif
     i = LOAD_PORT(spec, i, desc->dport);   		      /* S	  */
     
-    alen = addrlen(desc->sfamily);
-    i = load_ip_address(spec, i, desc->sfamily, bin->orig_bytes+offs+3);
-    i = load_ip_port(spec, i, bin->orig_bytes+offs+1);	      /* IP, Port */
+    alen = addrlen(desc->sfamily, data+3);
+    i = load_ip_address(spec, i, desc->sfamily, data+3);
+    i = load_ip_port(spec, i, data+1);	      		      /* IP, Port */
     
+#   ifdef HAVE_SYS_UN_H
+    /* AF_LOCAL addresses have a prefix byte containing address length */
+    if (desc->sfamily == AF_LOCAL)
+	alen++;
+#   endif
     offs += (alen + 3);
     len  -= (alen + 3);
 
@@ -4151,6 +4178,16 @@ static char* inet_set_address(int family, inet_address* dst,
 	*len = sizeof(struct sockaddr_in6); 
 	return src + 2+16;
     }
+#endif
+#ifdef HAVE_SYS_UN_H
+    else if ((family == AF_LOCAL) && (*len >= 3+sizeof(struct sockaddr_un))) {
+	int n = *((unsigned char*)src+2);
+	dst->sal.sun_family  = family;
+	sys_memcpy(dst->sal.sun_path, src+3, n);
+	dst->sal.sun_path[n-1] = '\0';
+	*len = n;
+	return src + 3 + n;
+    }
 #endif
     return NULL;
 }
@@ -4160,7 +4197,7 @@ static char* inet_set_address(int family, inet_address* dst,
 ** or from argument if source data specifies constant address.
 ** 
 ** src = [TAG,P1,P0]           when TAG = INET_AF_ANY  | INET_AF_LOOPBACK
-** src = [TAG,P1,P0,X1,X2,...] when TAG = INET_AF_INET | INET_AF_INET6
+** src = [TAG,P1,P0,X1,X2,...] when TAG = INET_AF_INET | INET_AF_INET6 | INET_AF_LOCAL
 */
 static char *inet_set_faddress(int family, inet_address* dst,
 			       char *src, ErlDrvSizeT* len) {
@@ -4177,6 +4214,21 @@ static char *inet_set_faddress(int family, inet_address* dst,
     case INET_AF_INET6:
 	family = AF_INET6;
 	break;
+#   endif
+#   ifdef HAVE_SYS_UN_H
+    case INET_AF_LOCAL: {
+	int n;
+	if (*len || *len < 3) return NULL;
+	family = AF_LOCAL;
+	/* Next two bytes are the length of the local path (< 256) */
+	src++;
+	n = *(unsigned char*)src++;
+	if (n+3 > *len) return NULL;
+	dst->sal.sun_family = family;
+	sys_memcpy(dst->sal.sun_path, src, n);
+	*len = n;
+	break;
+    }
 #   endif
     case INET_AF_ANY:
     case INET_AF_LOOPBACK: {
@@ -4241,7 +4293,6 @@ static char *inet_set_faddress(int family, inet_address* dst,
     return inet_set_address(family, dst, src, len);
 }
 
-
 /* Get a inaddr structure
 ** src = inaddr structure
 ** *len is the lenght of structure
@@ -4273,10 +4324,55 @@ static int inet_get_address(char* dst, inet_address* src, unsigned int* len)
 	*len = 3 + sizeof(struct in6_addr);
 	return 0;
     }
+#endif
+#ifdef HAVE_SYS_UN_H
+    else if ((family == AF_LOCAL) && *len > 0) {
+	int n = *len - 4;
+	dst[0] = INET_AF_LOCAL;
+	put_int16(0, dst+1);
+	if (n == 0 || n >= sizeof(src->sal.sun_path)) {
+	    *(dst+3) = 0;
+	    *len = 3+1;
+        } else {
+	    *(dst+3) = n;
+	    sys_memcpy(dst+4, src->sal.sun_path, n);
+	    *len = 3+1+n;
+	}
+	return 0;
+    }
 #endif
     return -1;
 }
 
+static int inet_family_get_address(inet_descriptor* desc, char* dst, inet_address* src, unsigned int* len)
+{
+#ifdef HAVE_SYS_UN_H
+    if (desc->sfamily == AF_LOCAL) {
+	int n = *len - 4;
+	dst[0] = INET_AF_LOCAL;
+	put_int16(0, dst+1);
+	if (n <= 0 || n >= sizeof(src->sal.sun_path)) {
+	    if (desc->name_ptr) {
+		char* p = desc->name_ptr->sal.sun_path;
+		n = strlen(p);
+                *(dst+3) = n;
+                sys_memcpy(dst+4, p, n);
+		*len = 3+1+n;
+	    } else {
+		*(dst+3) = 0;
+		*len = 3+1;
+	    }
+        } else {
+	    *(dst+3) = n;
+	    sys_memcpy(dst+4, src->sal.sun_path, n);
+	    *len = 3+1+n;
+	}
+	return 0;
+    }
+#endif
+    return inet_get_address(dst, src, len);
+}
+
 /* Same as the above, but take family from the address structure,
 ** and advance the address pointer to the next address
 ** according to the size of the current,
@@ -4306,6 +4402,19 @@ static int inet_address_to_erlang(char *dst, inet_address **src) {
 	}
 	(*src) = (inet_address *) (&(*src)->sai6 + 1);
 	return 1 + 2 + 16;
+#endif
+#ifdef HAVE_SYS_UN_H
+    case AF_LOCAL: {
+	int n = strlen((*src)->sal.sun_path);
+	if (dst) {
+	    dst[0] = INET_AF_LOCAL;
+	    put_int16(0, dst+1);
+	    *(dst+3) = n;
+            sys_memcpy(dst+1+2+1, (*src)->sal.sun_path, n);
+	}
+	(*src) = (inet_address *) (&(*src)->sal + 1);
+	return 1+2+1+n;
+    }
 #endif
     default:
 	return -1;
@@ -4402,7 +4511,8 @@ static void desc_close_read(inet_descriptor* desc)
 static int erl_inet_close(inet_descriptor* desc)
 {
     free_subscribers(&desc->empty_out_q_subs);
-    if ((desc->prebound == 0) && (desc->state & INET_F_OPEN)) {
+    if ((desc->prebound == 0 || desc->sfamily == AF_LOCAL) &&
+        (desc->state & INET_F_OPEN)) {
 	desc_close(desc);
 	desc->state = INET_STATE_CLOSED;
     } else if (desc->prebound && (desc->s != INVALID_SOCKET)) {
@@ -4582,6 +4692,13 @@ static ErlDrvSSizeT inet_ctl_fdopen(inet_descriptor* desc, int domain, int type,
             return ctl_error(sock_errno(), rbuf, rsize);
         if (name.sa.sa_family != domain)
             return ctl_error(EINVAL, rbuf, rsize);
+#ifdef HAVE_SYS_UN_H
+        if (domain == AF_LOCAL) {
+            sys_memcpy(&desc->name_addr, &name, sizeof(desc->name_addr));
+            if (desc->name_ptr == NULL)
+               desc->name_ptr = &desc->name_addr;
+        }
+#endif
     }
 #ifdef __OSE__        
     /* for fdopen duplicating the sd will allow to uniquely identify
@@ -8570,6 +8687,11 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
         else if (desc->sfamily == AF_INET6) {
 	    put_int32(INET_AF_INET6, &tbuf[0]);
 	}
+#endif
+#ifdef HAVE_SYS_UN_H
+	else if (desc->sfamily == AF_LOCAL) {
+	    put_int32(INET_AF_LOCAL, &tbuf[0]);
+	}
 #endif
 	else
 	    return ctl_error(EINVAL, rbuf, rsize);
@@ -9255,6 +9377,11 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
 	case INET_AF_INET6:
 	    return ctl_xerror("eafnosupport", rbuf, rsize);
 	    break;
+#endif
+#ifdef HAVE_SYS_UN_H
+	case INET_AF_LOCAL:
+	    domain = AF_LOCAL;
+	    break;
 #endif
 	default:
 	    return ctl_error(EINVAL, rbuf, rsize);
@@ -9281,6 +9408,11 @@ static ErlDrvSSizeT tcp_inet_ctl(ErlDrvData e, unsigned int cmd,
 	case INET_AF_INET6:
 	    return ctl_xerror("eafnosupport", rbuf, rsize);
 	    break;
+#endif
+#ifdef HAVE_SYS_UN_H
+	case INET_AF_LOCAL:
+	    domain = AF_LOCAL;
+	    break;
 #endif
 	default:
 	    return ctl_error(EINVAL, rbuf, rsize);
@@ -11343,6 +11475,9 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,
 	case INET_AF_INET6:
 	    return ctl_xerror("eafnosupport", rbuf, rsize);
 	    break;
+#endif
+#ifdef HAVE_SYS_UN_H
+	case INET_AF_LOCAL: af = AF_LOCAL; break;
 #endif
 	default:
 	    return ctl_error(EINVAL, rbuf, rsize);
@@ -11955,7 +12090,7 @@ static int packet_inet_input(udp_descriptor* udesc, HANDLE event)
 
 	    inet_input_count(desc, n);
 	    udesc->i_ptr += n;
-	    inet_get_address(abuf, &other, &len);
+	    inet_family_get_address(desc, abuf, &other, &len);
 	    /* Copy formatted address to the buffer allocated; "len" is the
 	       actual length which must be <= than the original reserved.
 	       This means that the addr + data in the buffer are contiguous,
diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl
index bd74831bb7..4659448221 100644
--- a/erts/preloaded/src/prim_inet.erl
+++ b/erts/preloaded/src/prim_inet.erl
@@ -70,6 +70,8 @@ open(Protocol, Family, Type) ->
 open(Protocol, Family, Type, Opts) ->
     open(Protocol, Family, Type, Opts, ?INET_REQ_OPEN, []).
 
+%% FDOPEN(tcp|udp|sctp, inet|inet6|local, stream|dgram|seqpacket, integer())
+
 fdopen(Protocol, Family, Type, Fd) when is_integer(Fd) ->
     fdopen(Protocol, Family, Type, Fd, true).
 
@@ -104,8 +106,9 @@ open(Protocol, Family, Type, Opts, Req, Data) ->
 	error:system_limit -> {error, system_limit}
     end.
 
-enc_family(inet) -> ?INET_AF_INET;
-enc_family(inet6) -> ?INET_AF_INET6.
+enc_family(inet)  -> ?INET_AF_INET;
+enc_family(inet6) -> ?INET_AF_INET6;
+enc_family(local) -> ?INET_AF_LOCAL.
 
 enc_type(stream) -> ?INET_TYPE_STREAM;
 enc_type(dgram) -> ?INET_TYPE_DGRAM;
@@ -1619,6 +1622,8 @@ enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 4 ->
     [?INET_AF_INET,?int16(Port)|ip4_to_bytes(IP)];
 enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 8 ->
     [?INET_AF_INET6,?int16(Port)|ip6_to_bytes(IP)];
+enc_value_2(addr, {File,0}) when is_list(File) ->
+    [?INET_AF_LOCAL,0,0,length(File)|File];
 enc_value_2(ether, [_,_,_,_,_,_]=Xs) -> Xs;
 enc_value_2(sockaddr, any) ->
     [?INET_AF_ANY];
@@ -1628,6 +1633,8 @@ enc_value_2(sockaddr, IP) when tuple_size(IP) =:= 4 ->
     [?INET_AF_INET|ip4_to_bytes(IP)];
 enc_value_2(sockaddr, IP) when tuple_size(IP) =:= 8 ->
     [?INET_AF_INET6|ip6_to_bytes(IP)];
+enc_value_2(sockaddr, File) when is_list(File) ->
+    [?INET_AF_LOCAL,0,0,length(File)|File];
 enc_value_2(linkaddr, Linkaddr) ->
     [?int16(length(Linkaddr)),Linkaddr];
 enc_value_2(sctp_assoc_id, Val) -> ?int32(Val);
@@ -2265,8 +2272,10 @@ get_addrs([F,P1,P0|Addr]) ->
     {IP,Addrs} = get_ip(F, Addr),
     [{IP,?u16(P1, P0)}|get_addrs(Addrs)].
 
-get_ip(?INET_AF_INET, Addr)  -> get_ip4(Addr);
-get_ip(?INET_AF_INET6, Addr) -> get_ip6(Addr).
+get_ip(?INET_AF_INET,  Addr) -> get_ip4(Addr);
+get_ip(?INET_AF_INET6, Addr) -> get_ip6(Addr);
+get_ip(?INET_AF_LOCAL, [0])  -> {[], []};
+get_ip(?INET_AF_LOCAL, [N | Addr]) -> lists:split(N, Addr).
 
 get_ip4([A,B,C,D | T]) -> {{A,B,C,D},T}.
 
-- 
cgit v1.2.3