From 827bb85bafd96ec3037c849ea42878e4f581d22f Mon Sep 17 00:00:00 2001 From: Simon Cornish Date: Mon, 21 Dec 2009 22:48:42 -0800 Subject: Implement a non-blocking SCTP connect This patch adds a new set of functions - gen_sctp:connect_init/* that initiate an SCTP connection without blocking for the result. The result is delivered asynchronously as an sctp_assoc_change event. The new functions have the same API as documented for gen_sctp:connect/* with the following exceptions: * Timeout is only used to supervise resolving Addr (the peer address) * The possible return values are ok | {error, posix()} The caller application is responsible for receiving the #sctp_assoc_change{} event and correctly determining the connect it originated from (for example, by examining the remote host and/or port). The application should have at least {active, once} or use gen_sctp:recv to retrieve the connect result. The implementation of gen_sctp:connect suffers from a number of shortcomings which the user may avoid by using gen_sctp:connect_init and adding code to receive the connect result. First, irrespective of the Timeout value given to gen_sctp:connect, the OS attempts and retries the SCTP INIT according to various kernel parameters. If the Timeout value is shorter than the entire attempt then the application will still receive an sctp_assoc_change event after the {error, timeout} is returned from the initial call. This could be somewhat confusing (either to the application or the designer!) especially if the status is comm_up. Subsequent calls to connect before the OS has finished this process return {error, ealready} which may also be counter-intuitive. Second, there is a race-condition (documented in comments in inet_sctp.erl) that can cause the wrong sctp_assoc_change record to be returned to an application calling gen_sctp:connect. The race seriously affects connection attempts when using one-to-many sockets. --- lib/kernel/doc/src/gen_sctp.xml | 113 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) (limited to 'lib/kernel/doc/src') diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml index de41178a17..40efba2bb7 100644 --- a/lib/kernel/doc/src/gen_sctp.xml +++ b/lib/kernel/doc/src/gen_sctp.xml @@ -170,10 +170,19 @@

Establishes a new association for the socket Socket, with the peer (SCTP server socket) given by Addr and Port. The Timeout, - is expressed in milliseconds.

-

A socket can be associated with multiple peers. - + is expressed in milliseconds. A socket can be associated with multiple peers.

+

WARNING:Using a value of Timeout less than + the maximum time taken by the OS to establish an association (around 4.5 minutes + if the default values from RFC 4960 are used) can result in + inconsistent or incorrect return values. This is especially + relevant for associations sharing the same Socket + (i.e. source address and port) since the controlling process + blocks until connect/* returns. + connect_init/* + provides an alternative not subject to this limitation.

+ +

The result of connect/* is an #sctp_assoc_change{} event which contains, in particular, the new Association ID:

@@ -232,6 +241,45 @@ + + connect_init(Socket, Addr, Port, Opts) -> ok | {error, posix()} + Same as connect_init(Socket, Addr, Port, Opts, infinity). + +

Same as connect_init(Socket, Addr, Port, Opts, infinity).

+
+
+ + connect_init(Socket, Addr, Port, [Opt], Timeout) -> ok | {error, posix()} + Initiate a new association for the socket Socket, with a peer (SCTP server socket) + + Socket = sctp_socket() + Addr = ip_address() | Host + Port = port_number() + Opt = sctp_option() + Timeout = timeout() + Host = atom() | string() + + +

Initiates a new association for the socket Socket, + with the peer (SCTP server socket) given by + Addr and Port.

+

The fundamental difference between this API + and connect/* is that the return value is that of the + underlying OS connect(2) system call. If ok is returned + then the result of the association establishement is received + by the calling process as + an + #sctp_assoc_change{} + event. The calling process must be prepared to receive this, or + poll for it using recv/* depending on the value of the + active option.

+

The parameters are as described + in connect/*, with the + exception of the Timeout value.

+

The timer associated with Timeout only supervises + IP resolution of Addr

+
+
controlling_process(sctp_socket(), pid()) -> ok Assign a new controlling process pid to the socket @@ -1058,6 +1106,65 @@ gen_sctp:close(S).

+ +

A very simple Erlang SCTP Client which uses the + connect_init API.

+
+-module(ex3).
+
+-export([client/4]).
+-include_lib("kernel/include/inet.hrl").
+-include_lib("kernel/include/inet_sctp.hrl").
+
+client(Peer1, Port1, Peer2, Port2)
+  when is_tuple(Peer1), is_integer(Port1), is_tuple(Peer2), is_integer(Port2) ->
+    {ok,S}     = gen_sctp:open(),
+    SctpInitMsgOpt = {sctp_initmsg,#sctp_initmsg{num_ostreams=5}},
+    ActiveOpt = {active, true},
+    Opts = [SctpInitMsgOpt, ActiveOpt],
+    ok = gen_sctp:connect(S, Peer1, Port1, Opts),
+    ok = gen_sctp:connect(S, Peer2, Port2, Opts),
+    io:format("Connections initiated~n", []),
+    client_loop(S, Peer1, Port1, undefined, Peer2, Port2, undefined).
+
+client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2, AssocId2) ->
+    receive
+        {sctp, S, Peer1, Port1, {_Anc, SAC}}
+          when is_record(SAC, sctp_assoc_change), AssocId1 == undefined ->
+            io:format("Association 1 connect result: ~p. AssocId: ~p~n",
+                      [SAC#sctp_assoc_change.state,
+                       SAC#sctp_assoc_change.assoc_id]),
+            client_loop(S, Peer1, Port1, SAC#sctp_assoc_change.assoc_id,
+                        Peer2, Port2, AssocId2);
+
+        {sctp, S, Peer2, Port2, {_Anc, SAC}}
+          when is_record(SAC, sctp_assoc_change), AssocId2 == undefined ->
+            io:format("Association 2 connect result: ~p. AssocId: ~p~n",
+                      [SAC#sctp_assoc_change.state, SAC#sctp_assoc_change.assoc_id]),
+            client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2,
+                        SAC#sctp_assoc_change.assoc_id);
+
+        {sctp, S, Peer1, Port1, Data} ->
+            io:format("Association 1: received ~p~n", [Data]),
+            client_loop(S, Peer1, Port1, AssocId1,
+                        Peer2, Port2, AssocId2);
+
+        {sctp, S, Peer2, Port2, Data} ->
+            io:format("Association 2: received ~p~n", [Data]),
+            client_loop(S, Peer1, Port1, AssocId1,
+                        Peer2, Port2, AssocId2);
+
+        Other ->
+            io:format("Other ~p~n", [Other]),
+            client_loop(S, Peer1, Port1, AssocId1,
+                        Peer2, Port2, AssocId2)
+
+    after 5000 ->
+            ok
+    end.
+
+

+
-- cgit v1.2.3