From f79dd5dc88ff86c3394d25eed37432c32d80f6da Mon Sep 17 00:00:00 2001 From: Tomas Abrahamsson Date: Tue, 8 May 2012 21:30:07 +0200 Subject: inet_drv.c: Set sockaddr lengths in inet_set_[f]address Set appropriate values for the sockaddr length fields---sai.sin_len and sai6.sin6_len---if those fields exist; rely on the NO_SA_LEN configure check to see if they exist. The length field exists on at least FreeBSD, and there it needs to have a proper value for instance in the call to sctp_bindx, or else that will fail with EINVAL. --- erts/emulator/drivers/common/inet_drv.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 76a9b55179..6c02674414 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -3706,6 +3706,9 @@ static char* inet_set_address(int family, inet_address* dst, if ((family == AF_INET) && (*len >= 2+4)) { sys_memzero((char*)dst, sizeof(struct sockaddr_in)); port = get_int16(src); +#ifndef NO_SA_LEN + dst->sai.sin_len = sizeof(struct sockaddr_in); +#endif dst->sai.sin_family = family; dst->sai.sin_port = sock_htons(port); sys_memcpy(&dst->sai.sin_addr, src+2, 4); @@ -3716,6 +3719,9 @@ static char* inet_set_address(int family, inet_address* dst, else if ((family == AF_INET6) && (*len >= 2+16)) { sys_memzero((char*)dst, sizeof(struct sockaddr_in6)); port = get_int16(src); +#ifndef NO_SA_LEN + dst->sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif dst->sai6.sin6_family = family; dst->sai6.sin6_port = sock_htons(port); dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */ @@ -3770,6 +3776,9 @@ static char *inet_set_faddress(int family, inet_address* dst, return NULL; } sys_memzero((char*)dst, sizeof(struct sockaddr_in)); +#ifndef NO_SA_LEN + dst->sai.sin_len = sizeof(struct sockaddr_in6); +#endif dst->sai.sin_family = family; dst->sai.sin_port = sock_htons(port); dst->sai.sin_addr.s_addr = addr.s_addr; @@ -3789,6 +3798,9 @@ static char *inet_set_faddress(int family, inet_address* dst, return NULL; } sys_memzero((char*)dst, sizeof(struct sockaddr_in6)); +#ifndef NO_SA_LEN + dst->sai6.sin6_len = sizeof(struct sockaddr_in6); +#endif dst->sai6.sin6_family = family; dst->sai6.sin6_port = sock_htons(port); dst->sai6.sin6_flowinfo = 0; /* XXX this may be set as well ?? */ -- cgit v1.2.3 From 9d3bb79a1bec07706de46a67a001269dbbada293 Mon Sep 17 00:00:00 2001 From: Tomas Abrahamsson Date: Mon, 7 May 2012 00:26:37 +0200 Subject: Fix SCTP multihoming Setting several ip addresses for an SCTP socket worked only for IPv4 on Linux. For IPv6 and for other for instance Solaris and FreeBSD, it failed with badarg for both IPv4 and IPv6. For the first address specified to gen_sctp:open, bind is now called, while for any following addresses, sctp_bindx is called, repeatedly, with one address at a time. Previously, sctp_bindx was called for all addresses in one go, with the addresses in reverse order, and bind was not called at all if more than one address was specified. Both Solaris and FreeBSD requires bind to have been called before calling sctp_bindx, and FreeBSD additionally allows at most one address at a time in the call to sctp_bindx. For some versions of Linux, for instance SuSE 10, the port can be 0 only for the call to bind but not for subsequent calls to sctp_bindx, so replace with the port number assigned by the operating system. --- erts/emulator/drivers/common/inet_drv.c | 31 +++++------ lib/kernel/src/inet.erl | 42 ++++++++++++--- lib/kernel/test/gen_sctp_SUITE.erl | 91 ++++++++++++++++++++++++++++++++- 3 files changed, 138 insertions(+), 26 deletions(-) diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 6c02674414..9f84329dd8 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -10148,12 +10148,13 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, case SCTP_REQ_BINDX: { /* Multi-homing bind for SCTP: */ - /* Construct the list of addresses we bind to. The curr limit is - 256 addrs. Buff structure: Flags(1), ListItem,...: + /* Add additional addresses by calling sctp_bindx with one address + at a time, since this is what some OSes promise will work. + Buff structure: Flags(1), ListItem,...: */ - struct sockaddr addrs[256]; + inet_address addr; char* curr; - int add_flag, n, rflag; + int add_flag, rflag; if (!IS_SCTP(desc)) return ctl_xerror(EXBADPORT, rbuf, rsize); @@ -10162,27 +10163,23 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, add_flag = get_int8(curr); curr++; - for(n=0; n < 256 && curr < buf+len; n++) + /* Make the real flags: */ + rflag = add_flag ? SCTP_BINDX_ADD_ADDR : SCTP_BINDX_REM_ADDR; + + while (curr < buf+len) { /* List item format: Port(2), IP(4|16) -- compatible with "inet_set_address": */ - inet_address tmp; ErlDrvSizeT alen = buf + len - curr; - curr = inet_set_address(desc->sfamily, &tmp, curr, &alen); + curr = inet_set_address(desc->sfamily, &addr, curr, &alen); if (curr == NULL) return ctl_error(EINVAL, rbuf, rsize); - /* Now: we need to squeeze "tmp" into the size of "sockaddr", - which is smaller than "tmp" for IPv6 (extra IN6 info will - be cut off): */ - memcpy(addrs + n, &tmp, sizeof(struct sockaddr)); + /* Invoke the call: */ + if (p_sctp_bindx(desc->s, (struct sockaddr *)&addr, 1, + rflag) < 0) + return ctl_error(sock_errno(), rbuf, rsize); } - /* Make the real flags: */ - rflag = add_flag ? SCTP_BINDX_ADD_ADDR : SCTP_BINDX_REM_ADDR; - - /* Invoke the call: */ - if (p_sctp_bindx(desc->s, addrs, n, rflag) < 0) - return ctl_error(sock_errno(), rbuf, rsize); desc->state = INET_STATE_BOUND; diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 0bb5444dbb..1a03424f88 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -763,8 +763,12 @@ sctp_opt([Opt|Opts], Mod, R, As) -> {Name,Val} -> sctp_opt (Opts, Mod, R, As, Name, Val); _ -> {error,badarg} end; -sctp_opt([], _Mod, R, _SockOpts) -> - {ok, R}. +sctp_opt([], _Mod, #sctp_opts{ifaddr=IfAddr}=R, _SockOpts) -> + if is_list(IfAddr) -> + {ok, R#sctp_opts{ifaddr=lists:reverse(IfAddr)}}; + true -> + {ok, R} + end. sctp_opt(Opts, Mod, R, As, Name, Val) -> case add_opt(Name, Val, R#sctp_opts.opts, As) of @@ -1015,11 +1019,7 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 -> case prim_inet:setopts(S, Opts) of ok -> case if is_list(Addr) -> - prim_inet:bind(S, add, - [case A of - {_,_} -> A; - _ -> {A,Port} - end || A <- Addr]); + bindx(S, Addr, Port); true -> prim_inet:bind(S, Addr, Port) end of @@ -1040,6 +1040,34 @@ open(Fd, Addr, Port, Opts, Protocol, Family, Type, Module) when Fd < 0 -> open(Fd, _Addr, _Port, Opts, Protocol, Family, Type, Module) -> fdopen(Fd, Opts, Protocol, Family, Type, Module). +bindx(S, [Addr], Port0) -> + {IP, Port} = set_bindx_port(Addr, Port0), + prim_inet:bind(S, IP, Port); +bindx(S, Addrs, Port0) -> + [{IP, Port} | Rest] = [set_bindx_port(Addr, Port0) || Addr <- Addrs], + case prim_inet:bind(S, IP, Port) of + {ok, AssignedPort} when Port =:= 0 -> + %% On newer Linux kernels, Solaris and FreeBSD, calling + %% bindx with port 0 is ok, but on SuSE 10, it results in einval + Rest2 = [change_bindx_0_port(Addr, AssignedPort) || Addr <- Rest], + prim_inet:bind(S, add, Rest2); + {ok, _} -> + prim_inet:bind(S, add, Rest); + Error -> + Error + end. + +set_bindx_port({_IP, _Port}=Addr, _OtherPort) -> + Addr; +set_bindx_port(IP, Port) -> + {IP, Port}. + +change_bindx_0_port({IP, 0}, AssignedPort) -> + {IP, AssignedPort}; +change_bindx_0_port({_IP, _Port}=Addr, _AssignedPort) -> + Addr. + + -spec fdopen(Fd :: non_neg_integer(), Opts :: [socket_setopt()], Protocol :: socket_protocol(), diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index 8f490b6643..209b95a99f 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -31,14 +31,16 @@ [basic/1, api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1, xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1, - basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1]). + basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1, + open_multihoming_ipv4_socket/1, open_multihoming_ipv6_socket/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [basic, api_open_close, api_listen, api_connect_init, api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6, - basic_stream, xfer_stream_min, peeloff, buffers]. + basic_stream, xfer_stream_min, peeloff, buffers, + open_multihoming_ipv4_socket, open_multihoming_ipv6_socket]. groups() -> []. @@ -1105,6 +1107,91 @@ mk_data(N, Bytes, Bin) when N < Bytes -> mk_data(_, _, Bin) -> Bin. + + +open_multihoming_ipv4_socket(doc) -> + "Test opening a multihoming ipv4 socket"; +open_multihoming_ipv4_socket(suite) -> + []; +open_multihoming_ipv4_socket(Config) when is_list(Config) -> + ?line IfAddrs = ok(inet:getifaddrs()), + ?line + case filter_addrs_by_family(IfAddrs, inet) of + [Addr1, Addr2 | _] -> + ?line io:format("using ipv4 addresses ~p and ~p~n", + [Addr1, Addr2]), + ?line S = ok(gen_sctp:open(0, [{ip,Addr1},{ip,Addr2},inet])), + ?line ok = gen_sctp:listen(S, true), + ?line setup_connection(S, Addr1, inet), + ?line ok = gen_sctp:close(S); + X -> + {skip, f("Need 2 IPv4 addresses, found only ~p", [X])} + end. + +open_multihoming_ipv6_socket(doc) -> + "Test opening a multihoming ipv6 socket"; +open_multihoming_ipv6_socket(suite) -> + []; +open_multihoming_ipv6_socket(Config) when is_list(Config) -> + ?line IfAddrs = ok(inet:getifaddrs()), + ?line + case inet:getaddr(localhost, inet6) of + {error,eafnosupport} -> + {skip, "No IPv6 support"}; + {ok, _} -> + ?line + case filter_addrs_by_family(IfAddrs, inet6) of + [Addr1, Addr2 | _] -> + ?line io:format("using ipv6 addresses ~p and ~p~n", + [Addr1, Addr2]), + ?line S = ok(gen_sctp:open( + 0, [{ip,Addr1},{ip,Addr2}, inet6])), + ?line ok = gen_sctp:listen(S, true), + ?line setup_connection(S, Addr1, inet6), + ?line ok = gen_sctp:close(S); + X -> + {skip, f("Need 2 IPv6 addresses, found ~p", [X])} + end + end. + +filter_addrs_by_family(IfAddrs, Family) -> + lists:flatten([[Addr || {addr, Addr} <- Info, + is_good_addr(Addr, Family)] + || {_IfName, Info} <- IfAddrs]). + +is_good_addr(Addr, inet) when tuple_size(Addr) =:= 4 -> + true; +is_good_addr({0,0,0,0,0,16#ffff,_,_}, inet6) -> + false; %% ipv4 mapped +is_good_addr({16#fe80,_,_,_,_,_,_,_}, inet6) -> + false; %% link-local +is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 -> + true; +is_good_addr(_Addr, _Family) -> + false. + +f(F, A) -> + lists:flatten(io_lib:format(F, A)). + +setup_connection(S1, Addr, IpFamily) -> + ?line P1 = ok(inet:port(S1)), + ?line S2 = ok(gen_sctp:open(0, [IpFamily])), + ?line P2 = ok(inet:port(S2)), + ?line #sctp_assoc_change{state=comm_up} = + ok(gen_sctp:connect(S2, Addr, P1, [])), + ?line case ok(gen_sctp:recv(S1)) of + {Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> + ok; + {Addr,P2,_,#sctp_paddr_change{state=addr_confirmed, + addr={Addr,P2}}} -> + ?line case ok(gen_sctp:recv(S1)) of + {Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> + ok + end + end, + ?line ok = gen_sctp:close(S2). + + %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% socket gen_server ultra light -- cgit v1.2.3 From 2e3852b6942d4bf4eab909b501b7085d5ccd0e68 Mon Sep 17 00:00:00 2001 From: Tomas Abrahamsson Date: Tue, 29 May 2012 21:46:32 +0200 Subject: Add checks for in6addr_any and in6addr_loopback These variables are normally declared by , but for instance not on Windows 7, SDK 7.1. Work around that by using IN6ADDR_ANY_INIT and IN6ADDR_LOOPBACK_INIT if present, fallback to using :: and ::1. --- erts/configure.in | 58 +++++++++++++++++++++++++++++++++ erts/emulator/drivers/common/inet_drv.c | 21 ++++++++++++ 2 files changed, 79 insertions(+) diff --git a/erts/configure.in b/erts/configure.in index cb1b00b8b1..e9736ab6fe 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1949,6 +1949,64 @@ AC_CHECK_FUNCS([openpty]) AC_CHECK_HEADERS(net/if_dl.h ifaddrs.h netpacket/packet.h) AC_CHECK_FUNCS([getifaddrs]) +dnl Checks for variables in6addr_any and in6addr_loopback, +dnl +dnl They normally declared by netinet/in.h, according to POSIX, +dnl but not on Windows 7 (Windows SDK 7.1). I would have liked +dnl to just write AC_CHECK_DECL([in6addr_any], ...) but if doing so, +dnl the configure check fails erroneously on Linux with the error +dnl "cannot convert to a pointer type", on a line looking like +dnl "char *p = (char *) in6addr_any;", so work around that +dnl with some more code. +AC_CACHE_CHECK( + [whether in6addr_any is declared], + [erts_cv_have_in6addr_any], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ + #include + #include + #include + ]], + [[printf("%d", in6addr_any.s6_addr[16]);]] + )], + [erts_cv_have_in6addr_any=yes], + [erts_cv_have_in6addr_any=no] + )] +) + +case "$erts_cv_have_in6addr_any" in + yes) + AC_DEFINE([HAVE_IN6ADDR_ANY], [1], + [Define to 1 if you have the variable in6addr_any declared.]) +esac + +AC_CACHE_CHECK( + [whether in6addr_loopback is declared], + [erts_cv_have_in6addr_loopback], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[ + #include + #include + #include + ]], + [[printf("%d", in6addr_loopback.s6_addr[16]);]] + )], + [erts_cv_have_in6addr_loopback=yes], + [erts_cv_have_in6addr_loopback=no] + )] +) + +case "$erts_cv_have_in6addr_loopback" in + yes) + AC_DEFINE([HAVE_IN6ADDR_LOOPBACK], [1], + [Define to 1 if you have the variable in6addr_loopback declared.]) +esac + +AC_CHECK_DECLS([IN6ADDR_ANY_INIT, IN6ADDR_LOOPBACK_INIT], [], [], + [#include ]) + dnl ---------------------------------------------------------------------- dnl Checks for features/quirks in the system that affects Erlang. dnl ---------------------------------------------------------------------- diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 9f84329dd8..7f3b3323f5 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -1228,6 +1228,27 @@ struct erl_drv_entry inet_driver_entry = NULL, }; +#if HAVE_IN6 +# if ! defined(HAVE_IN6ADDR_ANY) || ! HAVE_IN6ADDR_ANY +# if HAVE_DECL_IN6ADDR_ANY_INIT +static const struct in6_addr in6addr_any = { { IN6ADDR_ANY_INIT } }; +# else +static const struct in6_addr in6addr_any = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } }; +# endif /* HAVE_IN6ADDR_ANY_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_ANY */ + +# if ! defined(HAVE_IN6ADDR_LOOPBACK) || ! HAVE_IN6ADDR_LOOPBACK +# if HAVE_DECL_IN6ADDR_LOOPBACK_INIT +static const struct in6_addr in6addr_loopback = + { { IN6ADDR_LOOPBACK_INIT } }; +# else +static const struct in6_addr in6addr_loopback = + { { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 } } }; +# endif /* HAVE_IN6ADDR_LOOPBACk_INIT */ +# endif /* ! HAVE_DECL_IN6ADDR_LOOPBACK */ +#endif /* HAVE_IN6 */ + /* XXX: is this a driver interface function ??? */ void erl_exit(int n, char*, ...); -- cgit v1.2.3 From 87570500f821c4aaeafa18705b3a4a479f5d9baa Mon Sep 17 00:00:00 2001 From: Tomas Abrahamsson Date: Thu, 10 May 2012 18:52:18 +0200 Subject: Allow mixed IPv4 and IPv6 addresses to sctp_bindx Also allow mixed address families to bind, since the first address on a multihomed sctp socket must be bound with bind, while the rest are to be bound using sctp_bindx. At least Linux supports adding address of mixing families. Make inet_set_faddress function available also when HAVE_SCTP is not defined, since we use it to find an address for bind to be able to mix ipv4 and ipv6 addresses. --- erts/emulator/drivers/common/inet_drv.c | 11 +- erts/preloaded/ebin/prim_inet.beam | Bin 70100 -> 69952 bytes erts/preloaded/src/prim_inet.erl | 6 +- lib/kernel/test/gen_sctp_SUITE.erl | 201 ++++++++++++++++++++++++-------- 4 files changed, 162 insertions(+), 56 deletions(-) diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 7f3b3323f5..8f4fff0f40 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -3753,7 +3753,7 @@ static char* inet_set_address(int family, inet_address* dst, #endif return NULL; } -#ifdef HAVE_SCTP + /* ** Set an inaddr structure, address family comes from source data, ** or from argument if source data specifies constant address. @@ -3839,7 +3839,7 @@ static char *inet_set_faddress(int family, inet_address* dst, } return inet_set_address(family, dst, src, len); } -#endif /* HAVE_SCTP */ + /* Get a inaddr structure ** src = inaddr structure @@ -7804,7 +7804,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf, if (desc->state != INET_STATE_OPEN) return ctl_xerror(EXBADPORT, rbuf, rsize); - if (inet_set_address(desc->sfamily, &local, buf, &len) == NULL) + if (inet_set_faddress(desc->sfamily, &local, buf, &len) == NULL) return ctl_error(EINVAL, rbuf, rsize); if (IS_SOCKET_ERROR(sock_bind(desc->s,(struct sockaddr*) &local, len))) @@ -10189,10 +10189,9 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf, while (curr < buf+len) { - /* List item format: Port(2), IP(4|16) -- compatible with - "inet_set_address": */ + /* List item format: see "inet_set_faddress": */ ErlDrvSizeT alen = buf + len - curr; - curr = inet_set_address(desc->sfamily, &addr, curr, &alen); + curr = inet_set_faddress(desc->sfamily, &addr, curr, &alen); if (curr == NULL) return ctl_error(EINVAL, rbuf, rsize); diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam index b2f3ab6c5b..ad49f5e892 100644 Binary files a/erts/preloaded/ebin/prim_inet.beam and b/erts/preloaded/ebin/prim_inet.beam differ diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 846ae97ed2..91fcd3ac82 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -184,7 +184,7 @@ close_pend_loop(S, N) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% bind(S,IP,Port) when is_port(S), is_integer(Port), Port >= 0, Port =< 65535 -> - case ctl_cmd(S,?INET_REQ_BIND,[?int16(Port),ip_to_bytes(IP)]) of + case ctl_cmd(S,?INET_REQ_BIND,enc_value(set, addr, {IP,Port})) of {ok, [P1,P0]} -> {ok, ?u16(P1, P0)}; {error,_}=Error -> Error end; @@ -206,10 +206,10 @@ bindx(S, AddFlag, Addrs) -> case getprotocol(S) of sctp -> %% Really multi-homed "bindx". Stringified args: - %% [AddFlag, (Port, IP)+]: + %% [AddFlag, (AddrBytes see enc_value_2(addr,X))+]: Args = [?int8(AddFlag)| - [[?int16(Port)|ip_to_bytes(IP)] || + [enc_value(set, addr, {IP,Port}) || {IP, Port} <- Addrs]], case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of {ok,_} -> {ok, S}; diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index 209b95a99f..f4bf6e719e 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -32,7 +32,10 @@ api_open_close/1,api_listen/1,api_connect_init/1,api_opts/1, xfer_min/1,xfer_active/1,def_sndrcvinfo/1,implicit_inet6/1, basic_stream/1, xfer_stream_min/1, peeloff/1, buffers/1, - open_multihoming_ipv4_socket/1, open_multihoming_ipv6_socket/1]). + open_multihoming_ipv4_socket/1, + open_unihoming_ipv6_socket/1, + open_multihoming_ipv6_socket/1, + open_multihoming_ipv4_and_ipv6_socket/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -40,7 +43,10 @@ all() -> [basic, api_open_close, api_listen, api_connect_init, api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6, basic_stream, xfer_stream_min, peeloff, buffers, - open_multihoming_ipv4_socket, open_multihoming_ipv6_socket]. + open_multihoming_ipv4_socket, + open_unihoming_ipv6_socket, + open_multihoming_ipv6_socket, + open_multihoming_ipv4_and_ipv6_socket]. groups() -> []. @@ -1114,45 +1120,119 @@ open_multihoming_ipv4_socket(doc) -> open_multihoming_ipv4_socket(suite) -> []; open_multihoming_ipv4_socket(Config) when is_list(Config) -> - ?line IfAddrs = ok(inet:getifaddrs()), - ?line - case filter_addrs_by_family(IfAddrs, inet) of - [Addr1, Addr2 | _] -> - ?line io:format("using ipv4 addresses ~p and ~p~n", - [Addr1, Addr2]), - ?line S = ok(gen_sctp:open(0, [{ip,Addr1},{ip,Addr2},inet])), - ?line ok = gen_sctp:listen(S, true), - ?line setup_connection(S, Addr1, inet), - ?line ok = gen_sctp:close(S); - X -> - {skip, f("Need 2 IPv4 addresses, found only ~p", [X])} - end. + ?line case get_addrs_by_family(inet, 2) of + {ok, [Addr1, Addr2]} -> + ?line do_open_and_connect([Addr1, Addr2], Addr1); + {error, Reason} -> + {skip, Reason} + end. + +open_unihoming_ipv6_socket(doc) -> + %% This test is mostly aimed to indicate + %% whether host has a non-working ipv6 setup + "Test opening a unihoming (non-multihoming) ipv6 socket"; +open_unihoming_ipv6_socket(suite) -> + []; +open_unihoming_ipv6_socket(Config) when is_list(Config) -> + ?line case get_addrs_by_family(inet6, 1) of + {ok, [Addr]} -> + ?line do_open_and_connect([Addr], Addr); + {error, Reason} -> + {skip, Reason} + end. + open_multihoming_ipv6_socket(doc) -> "Test opening a multihoming ipv6 socket"; open_multihoming_ipv6_socket(suite) -> []; open_multihoming_ipv6_socket(Config) when is_list(Config) -> - ?line IfAddrs = ok(inet:getifaddrs()), + ?line case get_addrs_by_family(inet6, 2) of + {ok, [Addr1, Addr2]} -> + ?line do_open_and_connect([Addr1, Addr2], Addr1); + {error, Reason} -> + {skip, Reason} + end. + +open_multihoming_ipv4_and_ipv6_socket(doc) -> + "Test opening a multihoming ipv6 socket with ipv4 and ipv6 addresses"; +open_multihoming_ipv4_and_ipv6_socket(suite) -> + []; +open_multihoming_ipv4_and_ipv6_socket(Config) when is_list(Config) -> + ?line case get_addrs_by_family(inet_and_inet6, 2) of + {ok, [[InetAddr1, InetAddr2], [Inet6Addr1, Inet6Addr2]]} -> + %% Connect to the first address to test bind + ?line do_open_and_connect([InetAddr1, Inet6Addr1, InetAddr2], + InetAddr1), + ?line do_open_and_connect([Inet6Addr1, InetAddr1], + Inet6Addr1), + + %% Connect an address, not the first, + %% to test sctp_bindx + ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1], + Inet6Addr2), + ?line do_open_and_connect([Inet6Addr1, Inet6Addr2, InetAddr1], + InetAddr1); + {error, Reason} -> + {skip, Reason} + end. + + +get_addrs_by_family(Family, NumAddrs) -> + case os:type() of + {unix,linux} -> + get_addrs_by_family_aux(Family, NumAddrs); + {unix,freebsd} -> + get_addrs_by_family_aux(Family, NumAddrs); + {unix,sunos} -> + case get_addrs_by_family_aux(Family, NumAddrs) of + {ok, [InetAddrs, Inet6Addrs]} when Family =:= inet_and_inet6 -> + %% Man page for sctp_bindx on Solaris says: "If sock is an + %% Internet Protocol Version 6 (IPv6) socket, addrs should + %% be an array of sockaddr_in6 structures containing IPv6 + %% or IPv4-mapped IPv6 addresses." + {ok, [ipv4_map_addrs(InetAddrs), Inet6Addrs]}; + {ok, Addrs} -> + {ok, Addrs}; + {error, Reason} -> + {error, Reason} + end; + Os -> + Reason = if Family =:= inet_and_inet6 -> + f("Mixing ipv4 and ipv6 addresses for multihoming " + " has not been verified on ~p", [Os]); + true -> + f("Multihoming for ~p has not been verified on ~p", + [Family, Os]) + end, + {error, Reason} + end. + +get_addrs_by_family_aux(Family, NumAddrs) when Family =:= inet; + Family =:= inet6 -> ?line - case inet:getaddr(localhost, inet6) of + case inet:getaddr(localhost, Family) of {error,eafnosupport} -> - {skip, "No IPv6 support"}; + {skip, f("No support for ~p", Family)}; {ok, _} -> - ?line - case filter_addrs_by_family(IfAddrs, inet6) of - [Addr1, Addr2 | _] -> - ?line io:format("using ipv6 addresses ~p and ~p~n", - [Addr1, Addr2]), - ?line S = ok(gen_sctp:open( - 0, [{ip,Addr1},{ip,Addr2}, inet6])), - ?line ok = gen_sctp:listen(S, true), - ?line setup_connection(S, Addr1, inet6), - ?line ok = gen_sctp:close(S); - X -> - {skip, f("Need 2 IPv6 addresses, found ~p", [X])} - end - end. + ?line IfAddrs = ok(inet:getifaddrs()), + ?line case filter_addrs_by_family(IfAddrs, Family) of + Addrs when length(Addrs) >= NumAddrs -> + {ok, lists:sublist(Addrs, NumAddrs)}; + [] -> + {error, f("Need ~p ~p address(es) found none~n", + [NumAddrs, Family])}; + Addrs -> + {error, + f("Need ~p ~p address(es) found only ~p: ~p~n", + [NumAddrs, Family, length(Addrs), Addrs])} + end + end; +get_addrs_by_family_aux(inet_and_inet6, NumAddrs) -> + ?line catch {ok, [case get_addrs_by_family_aux(Family, NumAddrs) of + {ok, Addrs} -> Addrs; + {error, Reason} -> throw({error, Reason}) + end || Family <- [inet, inet6]]}. filter_addrs_by_family(IfAddrs, Family) -> lists:flatten([[Addr || {addr, Addr} <- Info, @@ -1170,27 +1250,54 @@ is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 -> is_good_addr(_Addr, _Family) -> false. +ipv4_map_addrs(InetAddrs) -> + [begin + <> = <>, + <> = <>, + {0, 0, 0, 0, 0, 16#ffff, AB, CD} + end || {A,B,C,D} <- InetAddrs]. + f(F, A) -> lists:flatten(io_lib:format(F, A)). -setup_connection(S1, Addr, IpFamily) -> +do_open_and_connect(ServerAddresses, AddressToConnectTo) -> + ?line ServerFamily = get_family_by_addrs(ServerAddresses), + ?line io:format("Serving ~p addresses: ~p~n", + [ServerFamily, ServerAddresses]), + ?line S1 = ok(gen_sctp:open(0, [{ip,Addr} || Addr <- ServerAddresses] ++ + [ServerFamily])), + ?line ok = gen_sctp:listen(S1, true), ?line P1 = ok(inet:port(S1)), - ?line S2 = ok(gen_sctp:open(0, [IpFamily])), - ?line P2 = ok(inet:port(S2)), + ?line ClientFamily = get_family_by_addr(AddressToConnectTo), + ?line io:format("Connecting to ~p ~p~n", + [ClientFamily, AddressToConnectTo]), + ?line S2 = ok(gen_sctp:open(0, [ClientFamily])), + %% Verify client can connect ?line #sctp_assoc_change{state=comm_up} = - ok(gen_sctp:connect(S2, Addr, P1, [])), - ?line case ok(gen_sctp:recv(S1)) of - {Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> - ok; - {Addr,P2,_,#sctp_paddr_change{state=addr_confirmed, - addr={Addr,P2}}} -> - ?line case ok(gen_sctp:recv(S1)) of - {Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> - ok - end - end, - ?line ok = gen_sctp:close(S2). + ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])), + %% verify server side also receives comm_up from client + ?line recv_comm_up_eventually(S1), + ?line ok = gen_sctp:close(S2), + ?line ok = gen_sctp:close(S1). + +%% If at least one of the addresses is an ipv6 address, return inet6, else inet. +get_family_by_addrs(Addresses) -> + ?line case lists:usort([get_family_by_addr(Addr) || Addr <- Addresses]) of + [inet, inet6] -> inet6; + [inet] -> inet; + [inet6] -> inet6 + end. +get_family_by_addr(Addr) when tuple_size(Addr) =:= 4 -> inet; +get_family_by_addr(Addr) when tuple_size(Addr) =:= 8 -> inet6. + +recv_comm_up_eventually(S) -> + ?line case ok(gen_sctp:recv(S)) of + {_Addr, _Port, _Info, #sctp_assoc_change{state=comm_up}} -> + ok; + {_Addr, _Port, _Info, _OtherSctpMsg} -> + ?line recv_comm_up_eventually(S) + end. %%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% socket gen_server ultra light -- cgit v1.2.3