aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomas Abrahamsson <[email protected]>2012-05-07 00:26:37 +0200
committerTomas Abrahamsson <[email protected]>2012-08-16 01:40:57 +0200
commit9d3bb79a1bec07706de46a67a001269dbbada293 (patch)
tree8887406c5ce88ae4b44d77478327d0c91689d147
parentf79dd5dc88ff86c3394d25eed37432c32d80f6da (diff)
downloadotp-9d3bb79a1bec07706de46a67a001269dbbada293.tar.gz
otp-9d3bb79a1bec07706de46a67a001269dbbada293.tar.bz2
otp-9d3bb79a1bec07706de46a67a001269dbbada293.zip
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.
-rw-r--r--erts/emulator/drivers/common/inet_drv.c31
-rw-r--r--lib/kernel/src/inet.erl42
-rw-r--r--lib/kernel/test/gen_sctp_SUITE.erl91
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