Skip to content

Commit

Permalink
Allow mixed IPv4 and IPv6 addresses to sctp_bindx
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tomas-abrahamsson committed Aug 15, 2012
1 parent 2e3852b commit 8757050
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 56 deletions.
11 changes: 5 additions & 6 deletions erts/emulator/drivers/common/inet_drv.c
Expand Up @@ -3753,7 +3753,7 @@ static char* inet_set_address(int family, inet_address* dst,
#endif #endif
return NULL; return NULL;
} }
#ifdef HAVE_SCTP
/* /*
** Set an inaddr structure, address family comes from source data, ** Set an inaddr structure, address family comes from source data,
** or from argument if source data specifies constant address. ** or from argument if source data specifies constant address.
Expand Down Expand Up @@ -3839,7 +3839,7 @@ static char *inet_set_faddress(int family, inet_address* dst,
} }
return inet_set_address(family, dst, src, len); return inet_set_address(family, dst, src, len);
} }
#endif /* HAVE_SCTP */


/* Get a inaddr structure /* Get a inaddr structure
** src = inaddr structure ** src = inaddr structure
Expand Down Expand Up @@ -7804,7 +7804,7 @@ static ErlDrvSSizeT inet_ctl(inet_descriptor* desc, int cmd, char* buf,
if (desc->state != INET_STATE_OPEN) if (desc->state != INET_STATE_OPEN)
return ctl_xerror(EXBADPORT, rbuf, rsize); 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); return ctl_error(EINVAL, rbuf, rsize);


if (IS_SOCKET_ERROR(sock_bind(desc->s,(struct sockaddr*) &local, len))) if (IS_SOCKET_ERROR(sock_bind(desc->s,(struct sockaddr*) &local, len)))
Expand Down Expand Up @@ -10189,10 +10189,9 @@ static ErlDrvSSizeT packet_inet_ctl(ErlDrvData e, unsigned int cmd, char* buf,


while (curr < buf+len) while (curr < buf+len)
{ {
/* List item format: Port(2), IP(4|16) -- compatible with /* List item format: see "inet_set_faddress": */
"inet_set_address": */
ErlDrvSizeT alen = buf + len - curr; 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) if (curr == NULL)
return ctl_error(EINVAL, rbuf, rsize); return ctl_error(EINVAL, rbuf, rsize);


Expand Down
Binary file modified erts/preloaded/ebin/prim_inet.beam
Binary file not shown.
6 changes: 3 additions & 3 deletions erts/preloaded/src/prim_inet.erl
Expand Up @@ -184,7 +184,7 @@ close_pend_loop(S, N) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


bind(S,IP,Port) when is_port(S), is_integer(Port), Port >= 0, Port =< 65535 -> 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)}; {ok, [P1,P0]} -> {ok, ?u16(P1, P0)};
{error,_}=Error -> Error {error,_}=Error -> Error
end; end;
Expand All @@ -206,10 +206,10 @@ bindx(S, AddFlag, Addrs) ->
case getprotocol(S) of case getprotocol(S) of
sctp -> sctp ->
%% Really multi-homed "bindx". Stringified args: %% Really multi-homed "bindx". Stringified args:
%% [AddFlag, (Port, IP)+]: %% [AddFlag, (AddrBytes see enc_value_2(addr,X))+]:
Args = Args =
[?int8(AddFlag)| [?int8(AddFlag)|
[[?int16(Port)|ip_to_bytes(IP)] || [enc_value(set, addr, {IP,Port}) ||
{IP, Port} <- Addrs]], {IP, Port} <- Addrs]],
case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of
{ok,_} -> {ok, S}; {ok,_} -> {ok, S};
Expand Down
201 changes: 154 additions & 47 deletions lib/kernel/test/gen_sctp_SUITE.erl
Expand Up @@ -32,15 +32,21 @@
api_open_close/1,api_listen/1,api_connect_init/1,api_opts/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, 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]). 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]}]. suite() -> [{ct_hooks,[ts_install_cth]}].


all() -> all() ->
[basic, api_open_close, api_listen, api_connect_init, [basic, api_open_close, api_listen, api_connect_init,
api_opts, xfer_min, xfer_active, def_sndrcvinfo, implicit_inet6, 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]. open_multihoming_ipv4_socket,
open_unihoming_ipv6_socket,
open_multihoming_ipv6_socket,
open_multihoming_ipv4_and_ipv6_socket].


groups() -> groups() ->
[]. [].
Expand Down Expand Up @@ -1114,45 +1120,119 @@ open_multihoming_ipv4_socket(doc) ->
open_multihoming_ipv4_socket(suite) -> open_multihoming_ipv4_socket(suite) ->
[]; [];
open_multihoming_ipv4_socket(Config) when is_list(Config) -> open_multihoming_ipv4_socket(Config) when is_list(Config) ->
?line IfAddrs = ok(inet:getifaddrs()), ?line case get_addrs_by_family(inet, 2) of
?line {ok, [Addr1, Addr2]} ->
case filter_addrs_by_family(IfAddrs, inet) of ?line do_open_and_connect([Addr1, Addr2], Addr1);
[Addr1, Addr2 | _] -> {error, Reason} ->
?line io:format("using ipv4 addresses ~p and ~p~n", {skip, Reason}
[Addr1, Addr2]), end.
?line S = ok(gen_sctp:open(0, [{ip,Addr1},{ip,Addr2},inet])),
?line ok = gen_sctp:listen(S, true), open_unihoming_ipv6_socket(doc) ->
?line setup_connection(S, Addr1, inet), %% This test is mostly aimed to indicate
?line ok = gen_sctp:close(S); %% whether host has a non-working ipv6 setup
X -> "Test opening a unihoming (non-multihoming) ipv6 socket";
{skip, f("Need 2 IPv4 addresses, found only ~p", [X])} open_unihoming_ipv6_socket(suite) ->
end. [];
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) -> open_multihoming_ipv6_socket(doc) ->
"Test opening a multihoming ipv6 socket"; "Test opening a multihoming ipv6 socket";
open_multihoming_ipv6_socket(suite) -> open_multihoming_ipv6_socket(suite) ->
[]; [];
open_multihoming_ipv6_socket(Config) when is_list(Config) -> 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 ?line
case inet:getaddr(localhost, inet6) of case inet:getaddr(localhost, Family) of
{error,eafnosupport} -> {error,eafnosupport} ->
{skip, "No IPv6 support"}; {skip, f("No support for ~p", Family)};
{ok, _} -> {ok, _} ->
?line ?line IfAddrs = ok(inet:getifaddrs()),
case filter_addrs_by_family(IfAddrs, inet6) of ?line case filter_addrs_by_family(IfAddrs, Family) of
[Addr1, Addr2 | _] -> Addrs when length(Addrs) >= NumAddrs ->
?line io:format("using ipv6 addresses ~p and ~p~n", {ok, lists:sublist(Addrs, NumAddrs)};
[Addr1, Addr2]), [] ->
?line S = ok(gen_sctp:open( {error, f("Need ~p ~p address(es) found none~n",
0, [{ip,Addr1},{ip,Addr2}, inet6])), [NumAddrs, Family])};
?line ok = gen_sctp:listen(S, true), Addrs ->
?line setup_connection(S, Addr1, inet6), {error,
?line ok = gen_sctp:close(S); f("Need ~p ~p address(es) found only ~p: ~p~n",
X -> [NumAddrs, Family, length(Addrs), Addrs])}
{skip, f("Need 2 IPv6 addresses, found ~p", [X])} end
end 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) -> filter_addrs_by_family(IfAddrs, Family) ->
lists:flatten([[Addr || {addr, Addr} <- Info, lists:flatten([[Addr || {addr, Addr} <- Info,
Expand All @@ -1170,27 +1250,54 @@ is_good_addr(Addr, inet6) when tuple_size(Addr) =:= 8 ->
is_good_addr(_Addr, _Family) -> is_good_addr(_Addr, _Family) ->
false. 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) -> f(F, A) ->
lists:flatten(io_lib:format(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 P1 = ok(inet:port(S1)),
?line S2 = ok(gen_sctp:open(0, [IpFamily])), ?line ClientFamily = get_family_by_addr(AddressToConnectTo),
?line P2 = ok(inet:port(S2)), ?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} = ?line #sctp_assoc_change{state=comm_up} =
ok(gen_sctp:connect(S2, Addr, P1, [])), ok(gen_sctp:connect(S2, AddressToConnectTo, P1, [])),
?line case ok(gen_sctp:recv(S1)) of %% verify server side also receives comm_up from client
{Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> ?line recv_comm_up_eventually(S1),
ok; ?line ok = gen_sctp:close(S2),
{Addr,P2,_,#sctp_paddr_change{state=addr_confirmed, ?line ok = gen_sctp:close(S1).
addr={Addr,P2}}} ->
?line case ok(gen_sctp:recv(S1)) of %% If at least one of the addresses is an ipv6 address, return inet6, else inet.
{Addr,P2,_,#sctp_assoc_change{state=comm_up}} -> get_family_by_addrs(Addresses) ->
ok ?line case lists:usort([get_family_by_addr(Addr) || Addr <- Addresses]) of
end [inet, inet6] -> inet6;
end, [inet] -> inet;
?line ok = gen_sctp:close(S2). [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 %%% socket gen_server ultra light
Expand Down

0 comments on commit 8757050

Please sign in to comment.