diff options
-rw-r--r-- | erts/configure.in | 58 | ||||
-rw-r--r-- | erts/emulator/drivers/common/inet_drv.c | 73 | ||||
-rw-r--r-- | erts/preloaded/ebin/prim_inet.beam | bin | 70100 -> 69952 bytes | |||
-rw-r--r-- | erts/preloaded/src/prim_inet.erl | 6 | ||||
-rw-r--r-- | lib/kernel/src/inet.erl | 42 | ||||
-rw-r--r-- | lib/kernel/test/gen_sctp_SUITE.erl | 198 |
6 files changed, 343 insertions, 34 deletions
diff --git a/erts/configure.in b/erts/configure.in index e576bd7755..281f61f86e 100644 --- a/erts/configure.in +++ b/erts/configure.in @@ -1944,6 +1944,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 <sys/types.h> + #include <sys/socket.h> + #include <netinet/in.h> + ]], + [[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 <sys/types.h> + #include <sys/socket.h> + #include <netinet/in.h> + ]], + [[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 <netinet/in.h>]) + 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 76a9b55179..8f4fff0f40 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*, ...); @@ -3706,6 +3727,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 +3740,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 ?? */ @@ -3726,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. @@ -3770,6 +3797,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 +3819,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 ?? */ @@ -3806,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 @@ -7771,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))) @@ -10136,12 +10169,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); @@ -10150,27 +10184,22 @@ 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; + /* List item format: see "inet_set_faddress": */ ErlDrvSizeT alen = buf + len - curr; - curr = inet_set_address(desc->sfamily, &tmp, curr, &alen); + curr = inet_set_faddress(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/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex b2f3ab6c5b..ad49f5e892 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam 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/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..f4bf6e719e 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -31,14 +31,22 @@ [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_unihoming_ipv6_socket/1, + open_multihoming_ipv6_socket/1, + open_multihoming_ipv4_and_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_unihoming_ipv6_socket, + open_multihoming_ipv6_socket, + open_multihoming_ipv4_and_ipv6_socket]. groups() -> []. @@ -1105,6 +1113,192 @@ 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 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 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, Family) of + {error,eafnosupport} -> + {skip, f("No support for ~p", Family)}; + {ok, _} -> + ?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, + 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. + +ipv4_map_addrs(InetAddrs) -> + [begin + <<AB:16>> = <<A,B>>, + <<CD:16>> = <<C,D>>, + {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)). + +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 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, 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 |