From 4b1e1e148a5cc9c19127b61aaa3e15eeeaea6cb2 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 10:06:16 +0200 Subject: [socket-nif] Socket option 'SO_DOMAIN' not avalable on all platforms Internally in the socket module we accessed domain, type and protocol for an open socket. But as it turns out, domain (family) is not actually available as a socket option on all platforms. FreeBSD in this case. So, since we store these values in the socket descriptor anyway, we switch to use these values for our internal use instead. OTP-14831 --- erts/doc/src/socket_usage.xml | 2 +- erts/emulator/nifs/common/socket_nif.c | 146 ++++++++++++++++++++++++++++++++- erts/preloaded/ebin/socket.beam | Bin 66584 -> 66596 bytes erts/preloaded/src/socket.erl | 13 +-- 4 files changed, 152 insertions(+), 9 deletions(-) diff --git a/erts/doc/src/socket_usage.xml b/erts/doc/src/socket_usage.xml index 23d0f319f1..1ea29097c3 100644 --- a/erts/doc/src/socket_usage.xml +++ b/erts/doc/src/socket_usage.xml @@ -146,7 +146,7 @@ domain() no yes - none + Not on FreeBSD (for instance) dontroute diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index 876bed3135..b821fd6575 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -319,8 +319,8 @@ static void (*esock_sctp_freepaddrs)(struct sockaddr *addrs) = NULL; /* Debug stuff... */ -#define SOCKET_NIF_DEBUG_DEFAULT FALSE -#define SOCKET_DEBUG_DEFAULT FALSE +#define SOCKET_GLOBAL_DEBUG_DEFAULT FALSE +#define SOCKET_DEBUG_DEFAULT FALSE /* Counters and stuff (Don't know where to sent this stuff anyway) */ #define SOCKET_NIF_IOW_DEFAULT FALSE @@ -504,6 +504,9 @@ typedef union { #define SOCKET_OPT_OTP_RCVBUF 4 #define SOCKET_OPT_OTP_RCVCTRLBUF 6 #define SOCKET_OPT_OTP_SNDCTRLBUF 7 +#define SOCKET_OPT_OTP_DOMAIN 0xFF01 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_TYPE 0xFF02 // INTERNAL AND ONLY GET +#define SOCKET_OPT_OTP_PROTOCOL 0xFF03 // INTERNAL AND ONLY GET #define SOCKET_OPT_SOCK_ACCEPTCONN 1 #define SOCKET_OPT_SOCK_BINDTODEVICE 3 @@ -1510,6 +1513,12 @@ static ERL_NIF_TERM ngetopt_otp_rcvctrlbuf(ErlNifEnv* env, SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP); +static ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP); static ERL_NIF_TERM ngetopt_native(ErlNifEnv* env, SocketDescriptor* descP, int level, @@ -8279,6 +8288,19 @@ ERL_NIF_TERM ngetopt_otp(ErlNifEnv* env, result = ngetopt_otp_sndctrlbuf(env, descP); break; + /* *** INTERNAL *** */ + case SOCKET_OPT_OTP_DOMAIN: + result = ngetopt_otp_domain(env, descP); + break; + + case SOCKET_OPT_OTP_TYPE: + result = ngetopt_otp_type(env, descP); + break; + + case SOCKET_OPT_OTP_PROTOCOL: + result = ngetopt_otp_protocol(env, descP); + break; + default: result = esock_make_error(env, esock_atom_einval); break; @@ -8366,6 +8388,124 @@ ERL_NIF_TERM ngetopt_otp_sndctrlbuf(ErlNifEnv* env, } +/* ngetopt_otp_domain - Handle the OTP (level) domain options. + */ +static +ERL_NIF_TERM ngetopt_otp_domain(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->domain; + + switch (val) { + case AF_INET: + result = esock_make_ok2(env, esock_atom_inet); + break; + +#if defined(HAVE_IN6) && defined(AF_INET6) + case AF_INET6: + result = esock_make_ok2(env, esock_atom_inet6); + break; +#endif + +#if defined(HAVE_SYS_UN_H) + case AF_UNIX: + result = esock_make_ok2(env, esock_atom_local); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, + esock_atom_unknown, + MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_type - Handle the OTP (level) type options. + */ +static +ERL_NIF_TERM ngetopt_otp_type(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->type; + + switch (val) { + case SOCK_STREAM: + result = esock_make_ok2(env, esock_atom_stream); + break; + + case SOCK_DGRAM: + result = esock_make_ok2(env, esock_atom_dgram); + break; + +#ifdef HAVE_SCTP + case SOCK_SEQPACKET: + result = esock_make_ok2(env, esock_atom_seqpacket); + break; +#endif + case SOCK_RAW: + result = esock_make_ok2(env, esock_atom_raw); + break; + + case SOCK_RDM: + result = esock_make_ok2(env, esock_atom_rdm); + break; + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + +/* ngetopt_otp_protocol - Handle the OTP (level) protocol options. + */ +static +ERL_NIF_TERM ngetopt_otp_protocol(ErlNifEnv* env, + SocketDescriptor* descP) +{ + ERL_NIF_TERM result; + int val = descP->protocol; + + switch (val) { + case IPPROTO_IP: + result = esock_make_ok2(env, esock_atom_ip); + break; + + case IPPROTO_TCP: + result = esock_make_ok2(env, esock_atom_tcp); + break; + + case IPPROTO_UDP: + result = esock_make_ok2(env, esock_atom_udp); + break; + +#if defined(HAVE_SCTP) + case IPPROTO_SCTP: + result = esock_make_ok2(env, esock_atom_sctp); + break; +#endif + + default: + result = esock_make_error(env, + MKT2(env, esock_atom_unknown, MKI(env, val))); + break; + } + + return result; +} + + + /* The option has *not* been encoded. Instead it has been provided * in "native mode" (option is provided as is). In this case it will have the * format: {NativeOpt :: integer(), ValueSize :: non_neg_integer()} @@ -15279,7 +15419,7 @@ BOOLEAN_T extract_debug(ErlNifEnv* env, */ ERL_NIF_TERM debug = MKA(env, "debug"); - return esock_extract_bool_from_map(env, map, debug, SOCKET_NIF_DEBUG_DEFAULT); + return esock_extract_bool_from_map(env, map, debug, SOCKET_GLOBAL_DEBUG_DEFAULT); } static diff --git a/erts/preloaded/ebin/socket.beam b/erts/preloaded/ebin/socket.beam index a0550990e3..9c7bcf89b5 100644 Binary files a/erts/preloaded/ebin/socket.beam and b/erts/preloaded/ebin/socket.beam differ diff --git a/erts/preloaded/src/socket.erl b/erts/preloaded/src/socket.erl index 8093bad885..c388fc2849 100644 --- a/erts/preloaded/src/socket.erl +++ b/erts/preloaded/src/socket.erl @@ -652,6 +652,9 @@ %%-define(SOCKET_OPT_OTP_SNDBUF, 5). -define(SOCKET_OPT_OTP_RCVCTRLBUF, 6). -define(SOCKET_OPT_OTP_SNDCTRLBUF, 7). +-define(SOCKET_OPT_OTP_DOMAIN, 16#FF01). % INTERNAL +-define(SOCKET_OPT_OTP_TYPE, 16#FF02). % INTERNAL +-define(SOCKET_OPT_OTP_PROTOCOL, 16#FF03). % INTERNAL %% *** SOCKET (socket) options -define(SOCKET_OPT_SOCK_ACCEPTCONN, 1). @@ -2159,7 +2162,7 @@ getopt(#socket{ref = SockRef}, Level, Key) -> which_domain(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_DOMAIN) of + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_DOMAIN) of {ok, Domain} -> Domain; {error, _} = ERROR -> @@ -2173,7 +2176,7 @@ which_domain(SockRef) -> which_type(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_TYPE) of + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_TYPE) of {ok, Type} -> Type; {error, _} = ERROR -> @@ -2186,9 +2189,9 @@ which_type(SockRef) -> which_protocol(SockRef) -> case nif_getopt(SockRef, true, - ?SOCKET_OPT_LEVEL_SOCKET, ?SOCKET_OPT_SOCK_PROTOCOL) of - {ok, Type} -> - Type; + ?SOCKET_OPT_LEVEL_OTP, ?SOCKET_OPT_OTP_PROTOCOL) of + {ok, Proto} -> + Proto; {error, _} = ERROR -> throw(ERROR) end. -- cgit v1.2.3 From 6b43b24610329f9b1201c36cc4dfcb346ed0317c Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 12:01:35 +0200 Subject: [socket-nif] Fixed decode of send and recv flags Add explicitly handling the default case, when not flags are provided (value of zero). OTP-14831 --- erts/emulator/nifs/common/socket_nif.c | 53 ++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index b821fd6575..4fd10e1ae6 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -3052,26 +3052,36 @@ static ERL_NIF_TERM nconnect(ErlNifEnv* env, SocketDescriptor* descP) { - int code; + int code, save_errno = 0; /* Verify that we are where in the proper state */ - if (!IS_OPEN(descP)) + if (!IS_OPEN(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> not open\r\n") ); return esock_make_error(env, atom_exbadstate); + } - if (IS_CONNECTED(descP)) + if (IS_CONNECTED(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> already connected\r\n") ); return esock_make_error(env, atom_eisconn); + } - if (IS_CONNECTING(descP)) + if (IS_CONNECTING(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> already connecting\r\n") ); return esock_make_error(env, esock_atom_einval); - + } + code = sock_connect(descP->sock, (struct sockaddr*) &descP->remote, descP->addrLen); + save_errno = sock_errno(); + + SSDBG( descP, ("SOCKET", "nif_sendto -> connect result: %d, %d\r\n", + code, save_errno) ); if (IS_SOCKET_ERROR(code) && - ((sock_errno() == ERRNO_BLOCK) || /* Winsock2 */ - (sock_errno() == EINPROGRESS))) { /* Unix & OSE!! */ + ((save_errno == ERRNO_BLOCK) || /* Winsock2 */ + (save_errno == EINPROGRESS))) { /* Unix & OSE!! */ ERL_NIF_TERM ref = MKREF(env); descP->state = SOCKET_STATE_CONNECTING; SELECT(env, @@ -3086,7 +3096,7 @@ ERL_NIF_TERM nconnect(ErlNifEnv* env, */ return esock_atom_ok; } else { - return esock_make_error_errno(env, sock_errno()); + return esock_make_error_errno(env, save_errno); } } @@ -3800,16 +3810,22 @@ ERL_NIF_TERM nif_sendto(ErlNifEnv* env, descP->sock, argv[0], sendRef, sndData.size, eSockAddr, eflags) ); /* THIS TEST IS NOT CORRECT!!! */ - if (!IS_OPEN(descP)) + if (!IS_OPEN(descP)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> not open (%u)\r\n", descP->state) ); return esock_make_error(env, esock_atom_einval); + } - if (!esendflags2sendflags(eflags, &flags)) + if (!esendflags2sendflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sendflags decode failed\r\n") ); return esock_make_error(env, esock_atom_einval); + } if ((xres = esock_decode_sockaddr(env, eSockAddr, &remoteAddr, - &remoteAddrLen)) != NULL) + &remoteAddrLen)) != NULL) { + SSDBG( descP, ("SOCKET", "nif_sendto -> sockaddr decode: %s\r\n", xres) ); return esock_make_error_str(env, xres); + } MLOCK(descP->writeMtx); @@ -4345,8 +4361,10 @@ ERL_NIF_TERM nif_recvfrom(ErlNifEnv* env, /* if (IS_OPEN(descP)) */ /* return esock_make_error(env, atom_enotconn); */ - if (!erecvflags2recvflags(eflags, &flags)) + if (!erecvflags2recvflags(eflags, &flags)) { + SSDBG( descP, ("SOCKET", "nif_recvfrom -> recvflags decode failed\r\n") ); return enif_make_badarg(env); + } MLOCK(descP->readMtx); @@ -14198,6 +14216,12 @@ BOOLEAN_T esendflags2sendflags(unsigned int eflags, int* flags) unsigned int ef; int tmp = 0; + /* First, check if we have any flags at all */ + if (eflags == 0) { + *flags = 0; + return TRUE; + } + for (ef = SOCKET_SEND_FLAG_LOW; ef <= SOCKET_SEND_FLAG_HIGH; ef++) { switch (ef) { @@ -14269,6 +14293,11 @@ BOOLEAN_T erecvflags2recvflags(unsigned int eflags, int* flags) "\r\n eflags: %d" "\r\n", eflags) ); + if (eflags == 0) { + *flags = 0; + return TRUE; + } + for (ef = SOCKET_RECV_FLAG_LOW; ef <= SOCKET_RECV_FLAG_HIGH; ef++) { SGDBG( ("SOCKET", "erecvflags2recvflags -> iteration" -- cgit v1.2.3 From e698436942c7aaf4f2872c19df2555275be169d1 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Thu, 4 Oct 2018 12:03:35 +0200 Subject: [socket-nif|test] Updated test case for connect timeout The connect timeout test case uses the listen backlog argument to test connect timeout (its set to 1). The idea is that when the server backlog is full, it should not reply. The client attempting to connect will then get a timeout. But as it turns out, this behaviour is not "set in stone". On FreeBSD 11.2, the result is instead a 'econnreset'. It should also be noted that this local connections. That is, both end of the connection is on the same machine. So, test case has been updated to also accept a econnreset (even though that is not what it is testing). OTP-14831 --- lib/kernel/test/socket_SUITE.erl | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index d4cd144168..f3a7d84e81 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -203,7 +203,15 @@ api_b_open_and_close(Domain, Type, Proto) -> {error, Reason} -> ?FAIL({open, Reason}) end, - {ok, Domain} = socket:getopt(Socket, socket, domain), + %% Domain is not available on all platforms: + case socket:getopt(Socket, socket, domain) of + {ok, Domain} -> + ok; + {error, einval} -> + ok; + Else -> + ?FAIL({getopt, domain, Else}) + end, {ok, Type} = socket:getopt(Socket, socket, type), {ok, Proto} = socket:getopt(Socket, socket, protocol), Self = self(), @@ -558,6 +566,11 @@ api_to_connect_tcp6(_Config) when is_list(_Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% We use the backlog (listen) argument to test this. +%% Note that the behaviour of the TCP "server side" can vary when +%% a client connect to a "busy" server (full backlog). +%% For instance, on FreeBSD (11.2) the reponse when the backlog is full +%% is a econreset. api_to_connect_tcp(Domain) -> process_flag(trap_exit, true), @@ -621,6 +634,9 @@ api_to_connect_tcp_await_timeout([Sock|Socks], ServerSA, ID) -> {error, timeout} -> p("expected timeout (~w)", [ID]), ok; + {error, econnreset = Reason} -> + p("failed connecting: ~p - giving up", [Reason]), + ok; {error, Reason} -> p("failed connecting: ~p", [Reason]), ?FAIL({recv, Reason}); -- cgit v1.2.3