diff options
author | Raimo Niskanen <raimo@erlang.org> | 2021-09-02 15:15:17 +0200 |
---|---|---|
committer | Raimo Niskanen <raimo@erlang.org> | 2021-09-02 15:15:17 +0200 |
commit | 10e1c57eb0b632a60a33b23280fb8dfded2272d7 (patch) | |
tree | fa9f16bf393c2091c3bd7bbc75e86aa823b38dbc | |
parent | ae76c26635ec3af622b86072e79ca85d7a65daed (diff) | |
parent | b8f6206c198fdc4a4084cbcbdc1432fd5a5293a2 (diff) | |
download | erlang-10e1c57eb0b632a60a33b23280fb8dfded2272d7.tar.gz |
Merge branch 'raimo/work-around-linux-dgram-reconnect/GH-5092/OTP-17559' into maint
* raimo/work-around-linux-dgram-reconnect/GH-5092/OTP-17559:
Test API errors
Validate sockaddr tighter
Work around Linux DGRAM reconnect misbehaviour
Implement setting native socket addresses
Work around Linux DGRAM reconnect misbehaviour
Test udp reconnect
-rw-r--r-- | erts/emulator/drivers/common/inet_drv.c | 29 | ||||
-rw-r--r-- | erts/emulator/nifs/common/prim_socket_nif.c | 6 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_util.c | 134 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_util.h | 8 | ||||
-rw-r--r-- | erts/preloaded/ebin/prim_socket.beam | bin | 31544 -> 32444 bytes | |||
-rw-r--r-- | erts/preloaded/src/prim_socket.erl | 44 | ||||
-rw-r--r-- | lib/kernel/doc/src/socket.xml | 6 | ||||
-rw-r--r-- | lib/kernel/src/gen_udp_socket.erl | 7 | ||||
-rw-r--r-- | lib/kernel/src/socket.erl | 47 | ||||
-rw-r--r-- | lib/kernel/test/gen_udp_SUITE.erl | 67 | ||||
-rw-r--r-- | lib/kernel/test/socket_SUITE.erl | 101 |
11 files changed, 370 insertions, 79 deletions
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 6c722dd87f..b69906b3e1 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 1997-2020. All Rights Reserved. + * Copyright Ericsson AB 1997-2021. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1660,7 +1660,9 @@ static void *realloc_wrapper(void *current, ErlDrvSizeT size){ #endif # define ANC_BUFF_SIZE INET_DEF_BUFFER/2 /* XXX: not very good... */ + #ifdef HAVE_UDP + static int load_address(ErlDrvTermData* spec, int i, char* buf) { int n; @@ -1722,7 +1724,11 @@ static int load_address(ErlDrvTermData* spec, int i, char* buf) } return i; } -#endif + +static struct sockaddr disassoc_sa; +static size_t disassoc_sa_size; + +#endif /* #ifdef HAVE_UDP */ #ifdef HAVE_SCTP @@ -12345,6 +12351,12 @@ static udp_descriptor* sctp_inet_copy(udp_descriptor* desc, SOCKET s, int* err) #ifdef HAVE_UDP static int packet_inet_init() { + sys_memzero((char *)&disassoc_sa, sizeof(disassoc_sa)); +#ifdef AF_UNSPEC + disassoc_sa.sa_family = AF_UNSPEC; +#endif /* #ifdef AF_UNSPEC */ + disassoc_sa_size = disassoc_sa.sa_data - (char *)&disassoc_sa; + return 0; } @@ -12599,8 +12611,7 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, #endif /* UDP */ if (len == 0) { - /* What does it mean??? NULL sockaddr??? */ - sock_connect(desc->s, (struct sockaddr*) NULL, 0); + sock_connect(desc->s, &disassoc_sa, disassoc_sa_size); desc->state &= ~INET_F_ACTIVE; enq_async(desc, tbuf, INET_REQ_CONNECT); async_ok (desc); @@ -12616,10 +12627,18 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, (desc->sfamily, &desc->remote, &buf, &len)) != NULL) return ctl_xerror(xerror, rbuf, rsize); +#ifdef __linux__ + /* Workaround for Linux's misbehaviour to not + * change the source address when connecting + * a datagram socket to a new destination + */ + sock_connect(desc->s, &disassoc_sa, disassoc_sa_size); +#endif /* #ifdef __linux__ */ + code = sock_connect(desc->s, (struct sockaddr*) &desc->remote, len); if (IS_SOCKET_ERROR(code)) { - sock_connect(desc->s, (struct sockaddr*) NULL, 0); + sock_connect(desc->s, &disassoc_sa, disassoc_sa_size); desc->state &= ~INET_F_ACTIVE; return ctl_error(sock_errno(), rbuf, rsize); } diff --git a/erts/emulator/nifs/common/prim_socket_nif.c b/erts/emulator/nifs/common/prim_socket_nif.c index 2c0359f528..51c601d340 100644 --- a/erts/emulator/nifs/common/prim_socket_nif.c +++ b/erts/emulator/nifs/common/prim_socket_nif.c @@ -5013,7 +5013,7 @@ ERL_NIF_TERM nif_open(ErlNifEnv* env, "\r\n eopts: %T" "\r\n", argv[0], argv[1], argv[2], eopts) ); - if (! esock_decode_domain(env, edomain, &domain)) { + if (esock_decode_domain(env, edomain, &domain) == 0) { SGDBG( ("SOCKET", "nif_open -> invalid domain: %d\r\n", edomain) ); return esock_make_invalid(env, esock_atom_domain); @@ -5229,7 +5229,7 @@ BOOLEAN_T esock_open2_get_domain(ErlNifEnv* env, esock_atom_domain, &edomain)) return FALSE; - if (! esock_decode_domain(env, edomain, domain)) + if (esock_decode_domain(env, edomain, domain) == 0) return FALSE; return TRUE; @@ -10032,7 +10032,7 @@ ERL_NIF_TERM esock_setopt_addrform(ErlNifEnv* env, "\r\n eVal: %T" "\r\n", eVal) ); - if (! esock_decode_domain(env, eVal, &domain)) + if (esock_decode_domain(env, eVal, &domain) == 0) return esock_make_invalid(env, atom_value); SSDBG( descP, ("SOCKET", diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index d03a593238..d01eb7da26 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -65,17 +65,24 @@ extern char* erl_errno_id(int error); /* THIS IS JUST TEMPORARY??? */ #if (defined(HAVE_LOCALTIME_R) && defined(HAVE_STRFTIME)) #define ESOCK_USE_PRETTY_TIMESTAMP 1 #endif - + + +static +BOOLEAN_T esock_decode_sockaddr_native(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + ESockAddress* sockAddrP, + int family, + SOCKLEN_T* addrLen); static void esock_encode_packet_addr_tuple(ErlNifEnv* env, unsigned char len, unsigned char* addr, ERL_NIF_TERM* eAddr); -static void esock_encode_sockaddr_unknown(ErlNifEnv* env, - struct sockaddr* sa, - SOCKLEN_T len, - ERL_NIF_TERM* eSockAddr); +static void esock_encode_sockaddr_native(ErlNifEnv* env, + struct sockaddr* sa, + SOCKLEN_T len, + ERL_NIF_TERM* eSockAddr); static void esock_encode_sockaddr_broken(ErlNifEnv* env, struct sockaddr* sa, @@ -284,9 +291,10 @@ extern BOOLEAN_T esock_decode_sockaddr(ErlNifEnv* env, ERL_NIF_TERM eSockAddr, ESockAddress* sockAddrP, - SOCKLEN_T* addrLen) + SOCKLEN_T* addrLenP) { ERL_NIF_TERM efam; + int decode; int fam; UDBG( ("SUTIL", "esock_decode_sockaddr -> entry\r\n") ); @@ -300,25 +308,30 @@ BOOLEAN_T esock_decode_sockaddr(ErlNifEnv* env, UDBG( ("SUTIL", "esock_decode_sockaddr -> try decode domain (%T)\r\n", efam) ); - if (! esock_decode_domain(env, efam, &fam)) + decode = esock_decode_domain(env, efam, &fam); + if (0 >= decode) { + if (0 > decode) + return esock_decode_sockaddr_native(env, eSockAddr, sockAddrP, + fam, addrLenP); return FALSE; + } UDBG( ("SUTIL", "esock_decode_sockaddr -> fam: %d\r\n", fam) ); switch (fam) { case AF_INET: return esock_decode_sockaddr_in(env, eSockAddr, - &sockAddrP->in4, addrLen); + &sockAddrP->in4, addrLenP); #if defined(HAVE_IN6) && defined(AF_INET6) case AF_INET6: return esock_decode_sockaddr_in6(env, eSockAddr, - &sockAddrP->in6, addrLen); + &sockAddrP->in6, addrLenP); #endif #ifdef HAS_AF_LOCAL case AF_LOCAL: return esock_decode_sockaddr_un(env, eSockAddr, - &sockAddrP->un, addrLen); + &sockAddrP->un, addrLenP); #endif default: @@ -386,7 +399,7 @@ void esock_encode_sockaddr(ErlNifEnv* env, #endif default: - esock_encode_sockaddr_unknown(env, &sockAddrP->sa, addrLen, eSockAddr); + esock_encode_sockaddr_native(env, &sockAddrP->sa, addrLen, eSockAddr); break; } } @@ -498,8 +511,8 @@ void esock_encode_sockaddr_in(ErlNifEnv* env, "\r\n addrLen: %d" "\r\n addr size: %d" "\r\n", addrLen, sizeof(struct sockaddr_in)) ); - esock_encode_sockaddr_unknown(env, (struct sockaddr *)sockAddrP, - addrLen, eSockAddr); + esock_encode_sockaddr_native(env, (struct sockaddr *)sockAddrP, + addrLen, eSockAddr); } } @@ -638,8 +651,8 @@ void esock_encode_sockaddr_in6(ErlNifEnv* env, eFlowInfo, eScopeId, eSockAddr); } else { - esock_encode_sockaddr_unknown(env, (struct sockaddr *)sockAddrP, - addrLen, eSockAddr); + esock_encode_sockaddr_native(env, (struct sockaddr *)sockAddrP, + addrLen, eSockAddr); } } #endif @@ -766,8 +779,8 @@ void esock_encode_sockaddr_un(ErlNifEnv* env, make_sockaddr_un(env, ePath, eSockAddr); } } else { - esock_encode_sockaddr_unknown(env, (struct sockaddr *)sockAddrP, - addrLen, eSockAddr); + esock_encode_sockaddr_native(env, (struct sockaddr *)sockAddrP, + addrLen, eSockAddr); } } #endif @@ -823,8 +836,8 @@ void esock_encode_sockaddr_ll(ErlNifEnv* env, eSockAddr); } else { - esock_encode_sockaddr_unknown(env, (struct sockaddr *)sockAddrP, - addrLen, eSockAddr); + esock_encode_sockaddr_native(env, (struct sockaddr *)sockAddrP, + addrLen, eSockAddr); } } #endif @@ -1131,15 +1144,21 @@ BOOLEAN_T esock_decode_timeval(ErlNifEnv* env, * * Decode the Erlang form of the 'domain' type, that is: * + * Return 1: * inet => AF_INET * inet6 => AF_INET6 * local => AF_LOCAL * + * Return -1: + * Int => Int + * + * Otherwise return 0. + * */ extern -BOOLEAN_T esock_decode_domain(ErlNifEnv* env, - ERL_NIF_TERM eDomain, - int* domain) +int esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain) { if (COMPARE(esock_atom_inet, eDomain) == 0) { *domain = AF_INET; @@ -1155,15 +1174,17 @@ BOOLEAN_T esock_decode_domain(ErlNifEnv* env, #endif } else { - int d = 0; + int d; - if (GET_INT(env, eDomain, &d)) + d = 0; + if (GET_INT(env, eDomain, &d)) { *domain = d; - else - return FALSE; + return -1; + } + return 0; } - return TRUE; + return 1; } @@ -1470,15 +1491,66 @@ void esock_encode_packet_addr_tuple(ErlNifEnv* env, +/* +++ esock_decode_sockaddr_native +++ + * + * Decode a general sockaddr of unknown domain, within Erlang + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): + * + * addr :: binary() + * + * The erlang module ensures that this value exist, so there + * is no need for any elaborate error handling here. + */ + +static +BOOLEAN_T esock_decode_sockaddr_native(ErlNifEnv* env, + ERL_NIF_TERM eSockAddr, + ESockAddress* sockAddrP, + int family, + SOCKLEN_T* addrLen) +{ + ErlNifBinary bin; + ERL_NIF_TERM eAddr; + SOCKLEN_T len; + + /* *** Extract (e) Addr (a binary) from map *** */ + if (! GET_MAP_VAL(env, eSockAddr, esock_atom_addr, &eAddr)) + return FALSE; + + /* Get the address */ + if (! GET_BIN(env, eAddr, &bin)) + return FALSE; + + len = sizeof(*sockAddrP) - + (CHARP(sockAddrP->sa.sa_data) - CHARP(sockAddrP)); // Max addr size + if ((size_t)len < bin.size) + return FALSE; + + sys_memzero((char*) sockAddrP, sizeof(*sockAddrP)); + sockAddrP->sa.sa_family = (sa_family_t) family; + sys_memcpy(sockAddrP->sa.sa_data, bin.data, bin.size); + len = (sockAddrP->sa.sa_data - CHARP(sockAddrP)) + bin.size; +#ifndef NO_SA_LEN + sockAddrP->sa.sa_len = len; +#endif + *addrLen = len; + + return TRUE; +} + + + /* Encode as #{family := integer(), addr := binary()} * assuming at least the ->family field can be accessed * and hence at least 0 bytes of address */ static -void esock_encode_sockaddr_unknown(ErlNifEnv* env, - struct sockaddr* addr, - SOCKLEN_T len, - ERL_NIF_TERM* eSockAddr) +void esock_encode_sockaddr_native(ErlNifEnv* env, + struct sockaddr* addr, + SOCKLEN_T len, + ERL_NIF_TERM* eSockAddr) { size_t size; ERL_NIF_TERM eFamily, eData; diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 8d35d3c06d..61b237383c 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2018-2020. All Rights Reserved. + * Copyright Ericsson AB 2018-2021. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,9 +152,9 @@ void esock_encode_domain(ErlNifEnv* env, int domain, ERL_NIF_TERM* eDomain); extern -BOOLEAN_T esock_decode_domain(ErlNifEnv* env, - ERL_NIF_TERM eDomain, - int* domain); +int esock_decode_domain(ErlNifEnv* env, + ERL_NIF_TERM eDomain, + int* domain); extern BOOLEAN_T esock_decode_type(ErlNifEnv* env, diff --git a/erts/preloaded/ebin/prim_socket.beam b/erts/preloaded/ebin/prim_socket.beam Binary files differindex e73b8f067d..91e17b603c 100644 --- a/erts/preloaded/ebin/prim_socket.beam +++ b/erts/preloaded/ebin/prim_socket.beam diff --git a/erts/preloaded/src/prim_socket.erl b/erts/preloaded/src/prim_socket.erl index 5b72841a32..272f6ae032 100644 --- a/erts/preloaded/src/prim_socket.erl +++ b/erts/preloaded/src/prim_socket.erl @@ -58,11 +58,15 @@ %% Defaults %% --define(ESOCK_SOCKADDR_IN4_DEFAULTS, - (#{port => 0, addr => any})). +-define(ESOCK_SOCKADDR_IN_DEFAULTS, + (#{family => inet, port => 0, addr => any})). -define(ESOCK_SOCKADDR_IN6_DEFAULTS, - (#{port => 0, addr => any, + (#{family => inet6, port => 0, addr => any, flowinfo => 0, scope_id => 0})). +-define(ESOCK_SOCKADDR_LOCAL_DEFAULTS, + (#{family => local, path => <<"">>})). +-define(ESOCK_SOCKADDR_NATIVE_DEFAULTS, + (#{family => 0, addr => <<>>})). %% =========================================================================== %% @@ -818,29 +822,50 @@ enc_protocol(Proto) -> enc_sockaddr(#{family := inet} = SockAddr) -> - maps:merge(?ESOCK_SOCKADDR_IN4_DEFAULTS, SockAddr); + merge_sockaddr(?ESOCK_SOCKADDR_IN_DEFAULTS, SockAddr); enc_sockaddr(#{family := inet6} = SockAddr) -> - maps:merge(?ESOCK_SOCKADDR_IN6_DEFAULTS, SockAddr); + merge_sockaddr(?ESOCK_SOCKADDR_IN6_DEFAULTS, SockAddr); enc_sockaddr(#{family := local, path := Path} = SockAddr) -> if is_list(Path), 0 =< length(Path), length(Path) =< 255 -> BinPath = enc_path(Path), enc_sockaddr(SockAddr#{path => BinPath}); is_binary(Path), 0 =< byte_size(Path), byte_size(Path) =< 255 -> - SockAddr; + merge_sockaddr(?ESOCK_SOCKADDR_LOCAL_DEFAULTS, SockAddr); true -> %% Neater than an if clause - erlang:error({invalid, {sockaddr, path, SockAddr}}) + throw({invalid, {sockaddr, path, SockAddr}}) end; enc_sockaddr(#{family := local} = SockAddr) -> %% Neater than a function clause - erlang:error({invalid, {sockaddr, path, SockAddr}}); + throw({invalid, {sockaddr, path, SockAddr}}); +enc_sockaddr(#{family := Native} = SockAddr) when is_integer(Native) -> + merge_sockaddr(?ESOCK_SOCKADDR_NATIVE_DEFAULTS, SockAddr); enc_sockaddr(#{family := _} = SockAddr) -> SockAddr; +enc_sockaddr(#{} = SockAddr) -> + throw({invalid, {sockaddr, family, SockAddr}}); enc_sockaddr(SockAddr) -> %% Neater than a function clause - erlang:error({invalid, {sockaddr, map_or_family, SockAddr}}). + erlang:error({invalid, {sockaddr, SockAddr}}). +merge_sockaddr(Default, SockAddr) -> + case + maps:fold( + fun (Key, _, Acc) -> + if + is_map_key(Key, Default) -> + Acc; + true -> + [Key | Acc] + end + end, [], SockAddr) + of + [] -> + maps:merge(Default, SockAddr); + InvalidKeys -> + throw({invalid, {sockaddr, {keys,InvalidKeys}, SockAddr}}) + end. %% File names has to be encoded according to %% the native file encoding @@ -892,6 +917,7 @@ enc_msg(#{} = M) -> end, M); enc_msg(M) -> + %% Neater than a function clause erlang:error({invalid, {msg, M}}). enc_cmsgs(Cmsgs, Protocols) -> diff --git a/lib/kernel/doc/src/socket.xml b/lib/kernel/doc/src/socket.xml index 548580381c..c9a6866f3e 100644 --- a/lib/kernel/doc/src/socket.xml +++ b/lib/kernel/doc/src/socket.xml @@ -250,6 +250,10 @@ <desc></desc> </datatype> <datatype> + <name name="sockaddr_recv"/> + <desc></desc> + </datatype> + <datatype> <name name="sockaddr_in"/> <desc></desc> </datatype> @@ -288,7 +292,7 @@ <desc></desc> </datatype> <datatype> - <name name="sockaddr_recv"/> + <name name="sockaddr_native"/> <desc></desc> </datatype> <datatype> diff --git a/lib/kernel/src/gen_udp_socket.erl b/lib/kernel/src/gen_udp_socket.erl index 998ec8e194..a53054441b 100644 --- a/lib/kernel/src/gen_udp_socket.erl +++ b/lib/kernel/src/gen_udp_socket.erl @@ -115,6 +115,13 @@ close_server(Server) -> %% -- connect ---------------------------------------------------------------- connect(?MODULE_socket(_Server, Socket), Address, Port) -> + case os:type() of + {unix,linux} -> + _ = socket:connect(Socket, #{family => 0}), + ok; + _ -> + ok + end, socket:connect(Socket, dest2sockaddr({Address, Port})). diff --git a/lib/kernel/src/socket.erl b/lib/kernel/src/socket.erl index ef2e868e2c..6dbd13742c 100644 --- a/lib/kernel/src/socket.erl +++ b/lib/kernel/src/socket.erl @@ -102,6 +102,7 @@ sockaddr_in6/0, sockaddr_un/0, sockaddr_ll/0, + sockaddr_native/0, msg_flag/0, @@ -307,6 +308,10 @@ max := 0..16#ffffffff, min := 0..16#ffffffff}. +-type packet_type() :: host | broadcast | multicast | otherhost | + outgoing | loopback | user | kernel | fastroute | + non_neg_integer(). + -type sockaddr_un() :: #{family := local, path := binary() | string()}. -type sockaddr_in() :: #{family := inet, @@ -324,18 +329,15 @@ pkttype := packet_type(), hatype := non_neg_integer(), addr := binary()}. --type packet_type() :: host | broadcast | multicast | otherhost | - outgoing | loopback | user | kernel | fastroute | - non_neg_integer(). --type sockaddr() :: sockaddr_in() | +-type sockaddr_native() :: #{family := integer(), addr := binary()}. +-type sockaddr() :: sockaddr_in() | sockaddr_in6() | sockaddr_un() | - sockaddr_ll(). + sockaddr_ll() | + sockaddr_native(). -type sockaddr_recv() :: - sockaddr() | - #{family := integer(), addr := binary()} | - binary(). + sockaddr() | binary(). %% (otp) - This option is internal to our (OTP) implementation. %% socket - The socket layer (SOL_SOCKET). @@ -1347,13 +1349,18 @@ open(FD) -> Socket :: socket(), Reason :: posix() | 'protocol'. -open(FD, Opts) when is_integer(FD), is_map(Opts) -> - case prim_socket:open(FD, Opts) of - {ok, SockRef} -> - Socket = ?socket(SockRef), - {ok, Socket}; - {error, _} = ERROR -> - ERROR +open(FD, Opts) when is_map(Opts) -> + if + is_integer(FD) -> + case prim_socket:open(FD, Opts) of + {ok, SockRef} -> + Socket = ?socket(SockRef), + {ok, Socket}; + {error, _} = ERROR -> + ERROR + end; + true -> + erlang:error(badarg, [FD, Opts]) end; open(Domain, Type) -> open(Domain, Type, 0). @@ -1413,11 +1420,8 @@ open(Domain, Type, Protocol, Opts) -> Addr :: sockaddr() | 'any' | 'broadcast' | 'loopback', Reason :: posix() | 'closed' | invalid(). -bind(?socket(SockRef) = Socket, Addr) when is_reference(SockRef) -> +bind(?socket(SockRef), Addr) when is_reference(SockRef) -> if - is_map(Addr) -> - prim_socket:bind(SockRef, Addr); - %% Addr =:= any; Addr =:= broadcast; Addr =:= loopback -> @@ -1432,9 +1436,10 @@ bind(?socket(SockRef) = Socket, Addr) when is_reference(SockRef) -> {error, _} = ERROR -> ERROR end; - %% + is_atom(Addr) -> + {error, {invalid, {sockaddr, Addr}}}; true -> - erlang:error(badarg, [Socket, Addr]) + prim_socket:bind(SockRef, Addr) end; bind(Socket, Addr) -> erlang:error(badarg, [Socket, Addr]). diff --git a/lib/kernel/test/gen_udp_SUITE.erl b/lib/kernel/test/gen_udp_SUITE.erl index 0b12e6ad18..cc726e8ace 100644 --- a/lib/kernel/test/gen_udp_SUITE.erl +++ b/lib/kernel/test/gen_udp_SUITE.erl @@ -39,7 +39,7 @@ send_to_closed/1, active_n/1, buffer_size/1, binary_passive_recv/1, max_buffer_size/1, bad_address/1, read_packets/1, recv_poll_after_active_once/1, - open_fd/1, connect/1, implicit_inet6/1, + open_fd/1, connect/1, reconnect/1, implicit_inet6/1, recvtos/1, recvtosttl/1, recvttl/1, recvtclass/1, sendtos/1, sendtosttl/1, sendttl/1, sendtclass/1, local_basic/1, local_unbound/1, @@ -111,6 +111,7 @@ all_cases() -> recv_poll_after_active_once, open_fd, connect, + reconnect, implicit_inet6, active_n, recvtos, recvtosttl, recvttl, recvtclass, @@ -1674,6 +1675,64 @@ do_connect(Config) when is_list(Config) -> ok. + +reconnect(Config) when is_list(Config) -> + ?TC_TRY(?FUNCTION_NAME, fun () -> do_reconnect(Config) end). + +do_reconnect(Config) -> + LoopAddr = {127,0,0,1}, + XtrnAddr = {8,8,8,8}, + DestPort = 53, + {S, Port} = open_port_0(Config, []), + ?P("Socket: ~w", [S]), + %% Connect to a loopback destination + ok = gen_udp:connect(S, LoopAddr, DestPort), + {ok, {LoopAddr,DestPort}} = inet:peername(S), + {ok, {LocalAddr,Port}} = inet:sockname(S), + ?P("Socket addr: ~w", [LocalAddr]), + %% Reconnect to external destination + ok = gen_udp:connect(S, XtrnAddr, DestPort), + {ok, {XtrnAddr,DestPort}} = inet:peername(S), + {ok, {RoutableAddr,Port}} = inet:sockname(S), + %% We should have a non-loopback address here + true = RoutableAddr =/= LocalAddr, + %% Reconnect to loopback + ok = gen_udp:connect(S, LoopAddr, DestPort), + {ok, {LoopAddr,DestPort}} = inet:peername(S), + {ok, {LocalAddr,Port}} = inet:sockname(S), + ok = inet:close(S). + +%% For Linux to behave predictably we need to bind +%% to a specific port; when we bind to port 0 +%% and get an ephemeral port - it apparently can change +%% when we reconnect to a different destination. +%% +%% I consider this a workaround for a Linux bug, +%% ironically in a test case that tests +%% a workaround for another Linux bug (related)... +%% +open_port_0(Config, Opts) -> +open_port_0(Config, 0, Opts, 10). +%% +open_port_0(Config, Port, Opts, N) -> + case ?OPEN(Config, Port, Opts) of + {ok, S} -> + if + Port =:= 0 -> + {ok, Port_1} = inet:port(S), + ok = gen_udp:close(S), + %% Speculate that we can open a socket with that port + open_port_0(Config, Port_1, Opts, N); + true -> + ?P("Socket port: ~w", [Port]), + {S, Port} + end; + {error, eaddrinuse} when Port =/= 0 -> + open_port_0(Config, 0, Opts, N - 1); + {error, _} = Error -> + Error + end. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% implicit_inet6(Config) when is_list(Config) -> @@ -1698,7 +1757,7 @@ implicit_inet6(Config, Host, Addr) -> Active = {active,false}, Loopback = {0,0,0,0,0,0,0,1}, ?P("try 1 with explit inet6 on loopback"), - S1 = case ?OPEN(Config, 0, [inet6, Active, {ip, Loopback}]) of + S1 = case open_port_0(Config, [inet6, Active, {ip, Loopback}]) of {ok, Sock1} -> Sock1; {error, eaddrnotavail = Reason1} -> @@ -1711,7 +1770,7 @@ implicit_inet6(Config, Host, Addr) -> %% Localaddr = ok(get_localaddr()), ?P("try 2 on local addr (~p)", [Localaddr]), - S2 = case ?OPEN(Config, 0, [{ip, Localaddr}, Active]) of + S2 = case open_port_0(Config, [{ip, Localaddr}, Active]) of {ok, Sock2} -> Sock2; {error, eaddrnotavail = Reason2} -> @@ -1721,7 +1780,7 @@ implicit_inet6(Config, Host, Addr) -> ok = gen_udp:close(S2), %% ?P("try 3 on addr ~p (~p)", [Addr, Host]), - S3 = case ?OPEN(Config, 0, [{ifaddr, Addr}, Active]) of + S3 = case open_port_0(Config, [{ifaddr, Addr}, Active]) of {ok, Sock3} -> Sock3; {error, eaddrnotavail = Reason3} -> diff --git a/lib/kernel/test/socket_SUITE.erl b/lib/kernel/test/socket_SUITE.erl index 8bb1711efb..5cecd308f3 100644 --- a/lib/kernel/test/socket_SUITE.erl +++ b/lib/kernel/test/socket_SUITE.erl @@ -71,6 +71,8 @@ %% *** API Misc *** api_m_info/1, api_m_debug/1, + api_m_error_open/1, + api_m_error_bind/1, %% *** API Basic *** api_b_open_and_info_udp4/1, @@ -849,7 +851,9 @@ api_cases() -> api_misc_cases() -> [ api_m_info, - api_m_debug + api_m_debug, + api_m_error_open, + api_m_error_bind ]. api_basic_cases() -> @@ -2239,6 +2243,101 @@ api_m_debug() -> i("ok"), ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% Some tests for API misuses + +-define( + EXCEPTION(Code), + exception(fun () -> begin Code end end)). + +exception(Fun) -> + try Fun() of + Result -> + error({unexpected_return, Result}) + catch + Class : Reason -> + {Class, Reason} + end. + + +api_m_error_open(Config) when is_list(Config) -> + %% + %% open/1 + {error, badarg} = + ?EXCEPTION( + socket:open(should_be_fd) ), + %% + %% open/2 + {error, badarg} = + ?EXCEPTION( + socket:open(should_be_fd, #{}) ), + %% + %% A non-atom, non-integer protocol causes an exception + %% in prim_socket, whilst a non-atom, non-integer type + %% or domain causes an error return tuple from the NIF + %% code. This is a bit inconsistent. Should we change + %% that, if so - how, and consolidate in tests? + %% + {error,{invalid,{domain,should_be_domain}}} = + socket:open(should_be_domain, dgram), + {error,{invalid,{type,should_be_type}}} = + socket:open(inet, should_be_type), + %% + %% open/3 + {error,{invalid,{domain,should_be_domain}}} = + socket:open(should_be_domain, dgram, #{}), + {error,{invalid,{type,should_be_type}}} = + socket:open(inet, should_be_type, #{}), + {error,{invalid,{protocol,should_be_protocol}}} = + socket:open(inet, dgram, should_be_protocol), + %% + %% open/4 + {error,{invalid,{domain,should_be_domain}}} = + socket:open(should_be_domain, dgram, default, #{}), + {error,{invalid,{type,should_be_type}}} = + socket:open(inet, should_be_type, default, #{}), + {error,{invalid,{protocol,should_be_protocol}}} = + socket:open(inet, dgram, should_be_protocol, #{}), + {error, badarg} = + ?EXCEPTION( + socket:open(inet, dgram, default, should_be_options) ). + + +api_m_error_bind(Config) when is_list(Config) -> + {ok, S} = socket:open(inet, dgram), + try + %% + %% bind/2 + {error, badarg} = + ?EXCEPTION( + socket:bind(should_be_socket, any) ), + {error, badarg} = + ?EXCEPTION( + socket:bind(make_ref(), any) ), + %% + %% A non-map, non-atom Addr causes an {invalid,_} + %% exception. Should that instead be an error + %% return? + %% + {error,{invalid,{sockaddr,should_be_sockaddr}}} = + socket:bind(S, should_be_sockaddr), + EmptyMap = #{}, + {error,{invalid,{sockaddr,family,EmptyMap}}} = + socket:bind(S, EmptyMap), + InvalidKey = #{family => inet, invalid_key => []}, + {error,{invalid,{sockaddr,{keys,[invalid_key]},InvalidKey}}} = + socket:bind(S, InvalidKey), + InvalidFamily = #{family => invalid_family}, + {error,{invalid,{sockaddr,InvalidFamily}}} = + socket:bind(S, InvalidFamily) + after + _ = socket:close(S) + end, + ok. + + +%% XXX Lots of missing error tests here, for all other API functions... %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |