summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaimo Niskanen <raimo@erlang.org>2021-09-02 15:15:17 +0200
committerRaimo Niskanen <raimo@erlang.org>2021-09-02 15:15:17 +0200
commit10e1c57eb0b632a60a33b23280fb8dfded2272d7 (patch)
treefa9f16bf393c2091c3bd7bbc75e86aa823b38dbc
parentae76c26635ec3af622b86072e79ca85d7a65daed (diff)
parentb8f6206c198fdc4a4084cbcbdc1432fd5a5293a2 (diff)
downloaderlang-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.c29
-rw-r--r--erts/emulator/nifs/common/prim_socket_nif.c6
-rw-r--r--erts/emulator/nifs/common/socket_util.c134
-rw-r--r--erts/emulator/nifs/common/socket_util.h8
-rw-r--r--erts/preloaded/ebin/prim_socket.beambin31544 -> 32444 bytes
-rw-r--r--erts/preloaded/src/prim_socket.erl44
-rw-r--r--lib/kernel/doc/src/socket.xml6
-rw-r--r--lib/kernel/src/gen_udp_socket.erl7
-rw-r--r--lib/kernel/src/socket.erl47
-rw-r--r--lib/kernel/test/gen_udp_SUITE.erl67
-rw-r--r--lib/kernel/test/socket_SUITE.erl101
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
index e73b8f067d..91e17b603c 100644
--- a/erts/preloaded/ebin/prim_socket.beam
+++ b/erts/preloaded/ebin/prim_socket.beam
Binary files differ
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...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%