aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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