gen_sctp(3)
π gen_sctp β the Erlang module for SCTP sockets π
(that's gen_sctp(3) in the Erlang Module Definition manual, bestie π«Ά)
β¨ NAME β¨
gen_sctp β the interface to SCTP sockets, periodt.
πΈ DESCRIPTION πΈ
okay so. This module hands you functions for talking over SCTP sockets. The whole thing assumes your OS kernel supports SCTP (RFC 2960) through the user-level Sockets API Extensions. Like, that's the whole vibe, no cap.
During development, this implementation was tested on:
- Linux Fedora Core 5.0 (kernel
2.6.15-2054or later is needed, fr fr) - Solaris 10, 11
During the OTP adaptation it got tested on:
- SUSE Linux Enterprise Server 10 (x86_64) kernel
2.6.16.27-0.6-smp, withlksctp-tools-1.0.6 - briefly on Solaris 10
- SUSE Linux Enterprise Server 10 Service Pack 1 (x86_64) kernel
2.6.16.54-0.2.3-smpwithlksctp-tools-1.0.7 - FreeBSD 8.2
This module was written for one-to-many style sockets (type seqpacket). Then peeloff/2 showed up and brought one-to-one style sockets (type stream) with it. Your fave got an upgrade π
Record definitions for this module? You find them like this:
-include_lib("kernel/include/inet_sctp.hrl").
And listen β these record definitions use the "new" spelling 'adaptation', not the deprecated 'adaption', no matter which spelling the underlying C API uses. So don't come at me with the old spelling, bestie, it's giving deprecated energy π
π SCTP Socket Options π
The set of admissible SCTP socket options is, by construction, orthogonal to the sets of TCP, UDP, and generic inet options. Over here we only list the options that are actually allowed for SCTP sockets. No strays, no impostors.
Options can be set on the socket when calling open/1,2, and changed when calling connect/4,5 or by calling inet:setopts/2. You can get them back with inet:getopts/2. Iconic round trip π₯
{mode, list|binary}|list|binaryβ decides the type of data that comes back fromrecv/1,2or in active mode data messages.{active, false|true|once|N}- If
false(passive mode, the default), the caller has to do an explicitrecvcall to pull the available data off the socket. You gotta ask for it, it's not just gonna appear. - If
true|once|N(active modes), received data or events get sent to the owning process. Peepopen/0..2for the message format. - If
true(fully active mode) there is no flow control. NOTE: this can cause the message queue to overflow, causing for example the virtual machine to run out of memory and crash. so likeβ¦ be careful, bestie ππ - If
once, only one message is automatically placed in the message queue, and then the mode resets to passive. This gives you flow control, plus the receiver gets to listen for incoming SCTP data interleaved with other inter-process messages. Balanced. Multitasking. Main character energy β¨ - If
activeis specified as an integerNin the range-32768to32767(inclusive), that number gets added to the socket's data messages counter. If the result of the addition is negative, the counter is set to0. Once the counter reaches0β either through the delivery of messages, or by being explicitly set withinet:setopts/2β the socket mode resets to passive ({active, false}). And when a socket in{active, N}mode transitions to passive mode, the message{sctp_passive, Socket}gets sent to the controlling process, to notify it that if it wants more data messages from the socket, it must callinet:setopts/2to set the socket back into an active mode. Like a little tap on the shoulder. so polite π«Ά
- If
{tos, integer()}β sets the Type-Of-Service field on the IP datagrams that get sent, to the specified value. This effectively determines a prioritization policy for the outbound packets. The acceptable values are system-dependent. iykyk.{priority, integer()}β a protocol-independent equivalent oftosup there. Settingpriorityimplies settingtosas well. Two birds, one option π¦{dontroute, true|false}β defaults tofalse. Iftrue, the kernel does not send packets through any gateway, it only sends them to directly connected hosts. no detours, no gateways, just straight there.{reuseaddr, true|false}β defaults tofalse. Iftrue, the local binding address{IP,Port}of the socket can be reused immediately. No waiting around in stateCLOSE_WAITis performed (can be required for some types of servers). say it with me: no CLOSE_WAIT waiting, we move on π{sndbuf, integer()}β the size, in bytes, of the OS kernel send buffer for this socket. Sending errors would occur for datagrams larger thanval(sndbuf). Setting this option also adjusts the size of the driver buffer (seebufferabove). tiny datagrams only bestie.{recbuf, integer()}β the size, in bytes, of the OS kernel receive buffer for this socket. Sending errors would occur for datagrams larger thanval(recbuf). Setting this option also adjusts the size of the driver buffer (seebufferabove).{non_block_send, boolean()}β a send call that would otherwise block (hang) will instead immediately return with e.g.{error, eagain}if this option has been set totrue. Defaults tofalse. no more hanging around waiting, we're booked and busy β¨{sctp_module, module()}β overrides which callback module is used. Defaults toinet_sctpfor IPv4 andinet6_sctpfor IPv6. you can swap the whole callback module, that's wild π₯{sctp_rtoinfo, #sctp_rtoinfo{}}
#sctp_rtoinfo{
assoc_id = assoc_id(),
initial = integer(),
max = integer(),
min = integer()
}
Determines retransmission time-out parameters, in milliseconds, for the association(s) specified by assoc_id.
assoc_id = 0 (default) indicates the whole endpoint. See RFC 2960 and Sockets API Extensions for SCTP for the exact semantics of the field values. (yes, the exact semantics live over there, go read them, I'm not paraphrasing the RFC for you π)
{sctp_associnfo, #sctp_assocparams{}}
#sctp_assocparams{
assoc_id = assoc_id(),
asocmaxrxt = integer(),
number_peer_destinations = integer(),
peer_rwnd = integer(),
local_rwnd = integer(),
cookie_life = integer()
}
Determines association parameters for the association(s) specified by assoc_id.
assoc_id = 0 (default) indicates the whole endpoint. See Sockets API Extensions for SCTP for the discussion of their semantics. Rarely used. like, rarely rarely.
{sctp_initmsg, #sctp_initmsg{}}
#sctp_initmsg{
num_ostreams = integer(),
max_instreams = integer(),
max_attempts = integer(),
max_init_timeo = integer()
}
Determines the default parameters that this socket tries to negotiate with its peer while establishing an association with it. Is to be set after open/* but before the first connect/*. #sctp_initmsg{} can also be used as ancillary data with the first call of send/* to a new peer (when a new association is created). timing matters bestie β°
num_ostreamsβ Number of outbound streamsmax_instreamsβ Maximum number of inbound streamsmax_attemptsβ Maximum retransmissions while establishing an associationmax_init_timeoβ Time-out, in milliseconds, for establishing an association
{sctp_autoclose, integer() >= 0}β determines the time, in seconds, after which an idle association is automatically closed.0means that the association is never automatically closed. never ever, no cap.{sctp_nodelay, true|false}β turns off (true) / on (false) the Nagle algorithm for merging small packets into larger ones. This improves throughput at the expense of latency. pick your struggle π«{sctp_disable_fragments, true|false}β iftrue, induces an error on an attempt to send a message larger than the current PMTU size (which would require fragmentation/reassembling). Notice that message fragmentation does not affect the logical atomicity of its delivery; this option is provided for performance reasons only. so don't panic, your atomicity is safe π«Ά{sctp_i_want_mapped_v4_addr, true|false}β turns on|off automatic mapping of IPv4 addresses into IPv6 ones (if the socket address family isAF_INET6).{sctp_maxseg, integer()}β determines the maximum chunk size if message fragmentation is used. If0, the chunk size is limited by the Path MTU only.{sctp_primary_addr, #sctp_prim{}}
#sctp_prim{
assoc_id = assoc_id(),
addr = {IP, Port}
}
IP = ip_address()
Port = port_number()
For the association specified by assoc_id, {IP,Port} must be one of the peer addresses. This option determines that the specified address is treated by the local SCTP stack as the primary address of the peer. choosing favorites, but make it protocol-legal π
{sctp_set_peer_primary_addr, #sctp_setpeerprim{}}
#sctp_setpeerprim{
assoc_id = assoc_id(),
addr = {IP, Port}
}
IP = ip_address()
Port = port_number()
When set, informs the peer to use {IP, Port} as the primary address of the local endpoint for the association specified by assoc_id. telling them who's the main character π
{sctp_adaptation_layer, #sctp_setadaptation{}}
#sctp_setadaptation{
adaptation_ind = integer()
}
When set, requests that the local endpoint uses the value specified by adaptation_ind as the Adaptation Indication parameter for establishing new associations. For details, see RFC 2960 and Sockets API Extensions for SCTP.
{sctp_peer_addr_params, #sctp_paddrparams{}}
#sctp_paddrparams{
assoc_id = assoc_id(),
address = {IP, Port},
hbinterval = integer(),
pathmaxrxt = integer(),
pathmtu = integer(),
sackdelay = integer(),
flags = list()
}
IP = ip_address()
Port = port_number()
Determines various per-address parameters for the association specified by assoc_id and the peer address address (the SCTP protocol supports multi-homing, so more than one address can correspond to a specified association). multi-homing said "I contain multitudes" π
hbintervalβ Heartbeat interval, in millisecondspathmaxrxtβ Maximum number of retransmissions before this address is considered unreachable (and an alternative address is selected)pathmtuβ Fixed Path MTU, if automatic discovery is disabled (seeflagsbelow)sackdelayβ Delay, in milliseconds, for SAC messages (if the delay is enabled, seeflagsbelow)flagsβ The following flags are available:hb_enableβ Enables heartbeat πhb_disableβ Disables heartbeathb_demandβ Initiates heartbeat immediately
pmtud_enableβ switches automatic Path MTU discovery ON β¨pmtud_disableβ switches automatic Path MTU discovery OFFsackdelay_enableβ enables SAC delaysackdelay_disableβ disables SAC delay
{sctp_default_send_param, #sctp_sndrcvinfo{}}
#sctp_sndrcvinfo{
stream = integer(),
ssn = integer(),
flags = list(),
ppid = integer(),
context = integer(),
timetolive = integer(),
tsn = integer(),
cumtsn = integer(),
assoc_id = assoc_id()
}
okay so #sctp_sndrcvinfo{} is doing double duty, bestie β it shows up as this socket option and as ancillary data while you're sending or receiving SCTP messages. When you set it as an option, it hands out default values for all the send calls that come after it, on the association that assoc_id points to π«Ά
assoc_id = 0 (that's the default) means the whole endpoint, no cap.
These are the fields the sender usually has to fill in:
sinfo_stream : The stream number (0-based! don't forget) inside the association you want the messages to travel through π
sinfo_flags : These are the flags it recognises:
unorderedβ send the message unorderedaddr_overβ whatever address you passed tosendoverwrites the primary peer addressabortβ aborts the current association, and no, it does not flush any unsent data πeofβ gracefully shuts down the current association, and this one does flush unsent data πΈ
The other fields are rarely used, fr fr. If you want the complete lore, go read RFC 2960 and Sockets API Extensions for SCTP.
{sctp_events, #sctp_event_subscribe{}}
#sctp_event_subscribe{
data_io_event = true | false,
association_event = true | false,
address_event = true | false,
send_failure_event = true | false,
peer_error_event = true | false,
shutdown_event = true | false,
partial_delivery_event = true | false,
adaptation_layer_event = true | false
}
This one decides which SCTP Events you get to receive (through recv/*) alongside the data. The one exception? data_io_event β because that one doesn't control events at all, it toggles whether you receive #sctp_sndrcvinfo{} ancillary data π
By default, every flag is enabled except adaptation_layer_event. Plot twist though: sctp_data_io_event and association_event are used by the driver itself and never exported up to the user level. Iconic behaviour, honestly.
{sctp_delayed_ack_time, #sctp_assoc_value{}}
#sctp_assoc_value{
assoc_id = assoc_id(),
assoc_value = integer()
}
Rarely used, ngl. It sets the ACK time β that's assoc_value, in milliseconds π β for the association you name, or for the whole endpoint if assoc_value = 0 (which is the default).
{sctp_status, #sctp_status{}}
#sctp_status{
assoc_id = assoc_id(),
state = atom(),
rwnd = integer(),
unackdata = integer(),
penddata = integer(),
instrms = integer(),
outstrms = integer(),
fragmentation_point = integer(),
primary = #sctp_paddrinfo{}
}
Heads up: this option is read-only π It tells you the status of the SCTP association named by assoc_id. Here's every possible value of state (the names are mostly self-explanatory, iykyk):
sctp_state_emptyβ the default. Means no other state is currently active.sctp_state_closedsctp_state_cookie_waitsctp_state_cookie_echoedsctp_state_establishedsctp_state_shutdown_pendingsctp_state_shutdown_sentsctp_state_shutdown_receivedsctp_state_shutdown_ack_sent
And the semantics of the other fields, bestie:
sstat_rwnd : Current receiver window size of the association
sstat_unackdata : Number of unacked data chunks
sstat_penddata : Number of data chunks pending receipt
sstat_instrms : Number of inbound streams
sstat_outstrms : Number of outbound streams
sstat_fragmentation_point : Message size at which SCTP fragmentation occurs
sstat_primary : Information on the current primary peer address (the format of #sctp_paddrinfo{} is right below, don't worry π)
{sctp_get_peer_addr_info, #sctp_paddrinfo{}}
#sctp_paddrinfo{
assoc_id = assoc_id(),
address = {IP, Port},
state = inactive | active | unconfirmed,
cwnd = integer(),
srtt = integer(),
rto = integer(),
mtu = integer()
}
IP = ip_address()
Port = port_number()
Also read-only, so don't try to write to it π This one gives you the parameters specific to the peer address you named in address, inside the association named by assoc_id. The address field must be set by the caller; all the other fields get filled in on return for you. And if assoc_id = 0 (the default), the address gets automatically translated into the matching association ID β convenient! β¨
This option is rarely used. For the semantics of all the fields, see RFC 2960 and Sockets API Extensions for SCTP.
π SCTP Examples π
Example of an Erlang SCTP server that receives SCTP messages and prints them on the standard output:
-module(sctp_server).
-export([server/0,server/1,server/2]).
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
server() ->
server(any, 2006).
server([Host,Port]) when is_list(Host), is_list(Port) ->
{ok, #hostent{h_addr_list = [IP|_]}} = inet:gethostbyname(Host),
io:format("~w -> ~w~n", [Host, IP]),
server([IP, list_to_integer(Port)]).
server(IP, Port) when is_tuple(IP) orelse IP == any orelse IP == loopback,
is_integer(Port) ->
{ok,S} = gen_sctp:open(Port, [{recbuf,65536}, {ip,IP}]),
io:format("Listening on ~w:~w. ~w~n", [IP,Port,S]),
ok = gen_sctp:listen(S, true),
server_loop(S).
server_loop(S) ->
case gen_sctp:recv(S) of
{error, Error} ->
io:format("SCTP RECV ERROR: ~p~n", [Error]);
Data ->
io:format("Received: ~p~n", [Data])
end,
server_loop(S).
Example of an Erlang SCTP client interacting with the above server. Note that in this example the client creates an association with the server with 5 outbound streams. Therefore, sending of "Test 0" over stream 0 succeeds, but sending of "Test 5" over stream 5 fails π The client then aborts the association, which results in that the corresponding event is received on the server side.
-module(sctp_client).
-export([client/0, client/1, client/2]).
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
client() ->
client([localhost]).
client([Host]) ->
client(Host, 2006);
client([Host, Port]) when is_list(Host), is_list(Port) ->
client(Host,list_to_integer(Port)),
init:stop().
client(Host, Port) when is_integer(Port) ->
{ok,S} = gen_sctp:open(),
{ok,Assoc} = gen_sctp:connect
(S, Host, Port, [{sctp_initmsg,#sctp_initmsg{num_ostreams=5}}]),
io:format("Connection Successful, Assoc=~p~n", [Assoc]),
io:write(gen_sctp:send(S, Assoc, 0, <<"Test 0">>)),
io:nl(),
timer:sleep(10000),
io:write(gen_sctp:send(S, Assoc, 5, <<"Test 5">>)),
io:nl(),
timer:sleep(10000),
io:write(gen_sctp:abort(S, Assoc)),
io:nl(),
timer:sleep(1000),
gen_sctp:close(S).
A simple Erlang SCTP client that 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);
{sctp_error, S, _Peer, _Port, {_Anc, Error}} ->
io:format("SCTP error: ~p~n", [Error]),
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.
and that's the whole thing bestie, the after 5000 -> ok is the little time-out nap so the loop doesn't die waiting forever π€
π« SEE ALSO π«
gen_tcpgen_udpinet- RFC 2960 (Stream Control Transmission Protocol)
- Sockets API Extensions for SCTP
like, if you already know your way around gen_tcp and gen_udp you're honestly 80% of the way there π«Ά
πΈ DATA TYPES πΈ
assoc_id() :: term().
The Association ID, bestie. It's an opaque term that gets handed to you in stuff like #sctp_paddr_change{}, and it's what tells you which association on an SCTP socket we're even talking about. Opaque means: don't peek inside, don't try to pattern-match it, just accept it. π
The one exception: the special value 0, which means something along the lines of "the whole endpoint" or "all future associations". That one's allowed, so use it whenever you need to talk about everything at once. β¨
elementary_option() ::
{elementary_option() ::
{active, true | false | once | -32768..32767} |
{buffer, non_neg_integer()} |
{non_block_send, boolean()} |
{debug, boolean()} |
{dontroute, boolean()} |
{exclusiveaddruse, boolean()} |
{high_msgq_watermark, pos_integer()} |
{linger, {boolean(), non_neg_integer()}} |
{low_msgq_watermark, pos_integer()} |
{mode, list | binary} |
list | binary |
{priority, non_neg_integer()} |
{recbuf, non_neg_integer()} |
{reuseaddr, boolean()} |
{reuseport, boolean()} |
{reuseport_lb, boolean()} |
{ipv6_v6only, boolean()} |
{sndbuf, non_neg_integer()} |
{sctp_autoclose, non_neg_integer()} |
{sctp_disable_fragments, boolean()} |
{sctp_i_want_mapped_v4_addr, boolean()} |
{sctp_maxseg, non_neg_integer()} |
{sctp_nodelay, boolean()} |
{tos, non_neg_integer()} |
{tclass, non_neg_integer()} |
{ttl, non_neg_integer()} |
{recvtos, boolean()} |
{recvtclass, boolean()} |
{recvttl, boolean()}.
no cap, that's the whole lineup of the plain-Jane options π
elementary_option_name() ::
{elementary_option_name() ::
active | buffer | non_block_send | debug | dontroute |
exclusiveaddruse | high_msgq_watermark | linger |
low_msgq_watermark | mode | priority | recbuf | reuseaddr |
reuseport | reuseport_lb | ipv6_v6only | sctp_autoclose |
sctp_disable_fragments | sctp_i_want_mapped_v4_addr |
sctp_maxseg | sctp_nodelay | sndbuf | tos | tclass | ttl |
recvtos | recvtclass | recvttl.
the same crew, but just the names, for when you only need to know what you're asking for π
option() :: elementary_option() | record_option().
SCTP Socket Option name and value β this is what you set. π
option_name() :: elementary_option_name() | record_option() | ro_option().
SCTP Socket Option name only β this is what you get with a get-style call. π
option_value() :: elementary_option() | record_option() | ro_option().
SCTP Socket Option name and value β literally what you get back. it's giving "you get what you get" π
record_option() ::
{record_option() ::
{sctp_adaptation_layer, #sctp_setadaptation{}} |
{sctp_associnfo, #sctp_assocparams{}} |
{sctp_default_send_param, #sctp_sndrcvinfo{}} |
{sctp_delayed_ack_time, #sctp_assoc_value{}} |
{sctp_events, #sctp_event_subscribe{}} |
{sctp_initmsg, #sctp_initmsg{}} |
{sctp_peer_addr_params, #sctp_paddrparams{}} |
{sctp_primary_addr, #sctp_prim{}} |
{sctp_rtoinfo, #sctp_rtoinfo{}} |
{sctp_set_peer_primary_addr, #sctp_setpeerprim{}}.
these are the β¨fancyβ¨ options β the ones that carry a whole record instead of a bare value. main character options only. π
ro_option() ::
{ro_option() ::
{sctp_get_peer_addr_info, #sctp_paddrinfo{}} |
{sctp_status, #sctp_status{}}.
"ro" = read-only, so these are the ones you can only look at, never set. admire from afar π¦
sctp_socket() :: port().
The socket identifier that open/* gives you back. that's your little handle on the whole world of this socket π
π οΈ FUNCTIONS π οΈ
abort(Socket, Assoc) -> ok | {error, inet:posix()}
when
Socket :: sctp_socket(),
Assoc :: #sctp_assoc_change{}.
Abort an association. π
This one abnormally terminates the association you name in Assoc, and it does not flush any unsent data first β so yeah, anything still in the pipe just goes poof, no cap.
The good news: the socket itself stays open π«Ά. Any other associations opened on that same socket are still perfectly valid, and the socket can absolutely be used to make brand-new associations afterward. so you're not starting over from zero, bestie.
close(Socket) -> ok | {error, inet:posix()}
when Socket :: sctp_socket().
Close an SCTP socket. πͺ
Closes the socket and every single association living on it. The unsent data gets flushed, same as with eof/2.
Here's the tea though: whether close/1 blocks depends on the value of the linger socket option. If it's false, or if the linger time-out runs out, the call just returns and whatever unsent data is left gets flushed in the background. so like, it's fine, but it's asynchronous-fine, iykyk.
connect(Socket, SockAddr, Opts, Timeout)
connect(Socket, SockAddr, Opts, Timeout) ->
{ok, #sctp_assoc_change{state :: comm_up}} |
{error, #sctp_assoc_change{state :: cant_assoc}} |
{error, inet:posix()}
when
Socket :: sctp_socket(),
SockAddr ::
socket:sockaddr_in() | socket:sockaddr_in6(),
Opts :: [Opt :: option()],
Timeout :: timeout();
(Socket, Addr, Port, Opts) ->
{ok, #sctp_assoc_change{state :: comm_up}} |
{error, #sctp_assoc_change{state :: cant_assoc}} |
{error, inet:posix()}
when
Socket :: sctp_socket(),
Addr :: inet:ip_address() | inet:hostname(),
Port :: inet:port_number(),
Opts :: [Opt :: option()].
Establish an association with a peer. π€
With the Addr and Port arguments, this is equivalent to:
connect(Socket, Addr, Port, Opts, infinity)
With the SockAddr and Opts arguments (since OTP 24.3), it's equivalent to connect(Socket, Addr, Port, Opts, Timeout) where Addr and Port get extracted from SockAddr. cute little shortcut β¨
connect(Socket, Addr, Port, Opts, Timeout)
connect(Socket, Addr, Port, Opts, Timeout) ->
{ok, #sctp_assoc_change{state :: comm_up}} |
{error, #sctp_assoc_change{state :: cant_assoc}} |
{error, inet:posix()}
when
Socket :: sctp_socket(),
Addr :: inet:ip_address() | inet:hostname(),
Port :: inet:port_number(),
Opts :: [Opt :: option()],
Timeout :: timeout().
Establish an association with a peer. π€
This sets up a brand-new association for socket Socket, with the peer (that's the SCTP server socket) specified by Addr and Port. Timeout is in milliseconds.
A socket can be associated with multiple peers β multi-tasking icon behavior π. Oh, and the socket has to be of type seqpacket. no exceptions, bestie.
β οΈ WARNING: if you pass aTimeoutvalue that's less than the maximum time the OS takes to establish an association β which is around 4.5 minutes if the default values from RFC 4960 are in play β you can get inconsistent or incorrect return values. π This hits especially hard for associations that share the sameSocket(meaning same source address and port), because the controlling process blocks untilconnect/*returns. If that's your situation,connect_init/*gives you an alternative without this limitation. π«Ά
#sctp_assoc_change{}
The result of connect/* is an #sctp_assoc_change{} event that hands you, in particular, the brand-new Association ID (l, yes bestie, that's literally what the page says):
#sctp_assoc_change{
state = atom(),
error = integer(),
outbound_streams = integer(),
inbound_streams = integer(),
assoc_id = assoc_id()
}
You can set how many outbound and inbound streams the association gets by handing an sctp_initmsg option to connect, like this:
connect(Socket, Ip, Port>,
[{sctp_initmsg,#sctp_initmsg{num_ostreams=OutStreams,
max_instreams=MaxInStreams}}])
Every option in Opt gets set on the socket before the association is even attempted. And here's the cute part: if an option record has undefined field values, that record is first read from the socket to fill those in. So in effect, your Opt records only need to define the field values you actually want to change before connecting. lowkey genius β¨
The outbound_streams and inbound_streams you get back are the number of streams on the socket. They can differ from the values you asked for (OutStreams and MaxInStreams respectively) if the peer insists on lower values. peer said "be humble" π
state can be:
comm_upβ the association is successfully established. This meansconnectcompleted successfully. πcant_assocβ the association cannot be established (that's aconnect/*failure). π
Other states don't normally show up in the output from connect/*. Instead, they can show up inside the #sctp_assoc_change{} events you receive in place of data from recv/* calls or socket messages. All of them mean you've lost the association because of various error conditions, and they're listed here just for the sake of completeness:
comm_lostrestartshutdown_comp
The error field can give you more detailed diagnostics β its value can be converted into a string using error_string/1. π
connect_init(Socket, SockAddr, Opts, Timeout)
connect_init(Socket, SockAddr, Opts, Timeout) ->
ok | {error, inet:posix()}
when
Socket :: sctp_socket(),
SockAddr ::
socket:sockaddr_in() |
socket:sockaddr_in6(),
Opts :: [option()],
Timeout :: timeout();
(Socket, Addr, Port, Opts) ->
ok | {error, inet:posix()}
when
Socket :: sctp_socket(),
Addr :: inet:ip_address() | inet:hostname(),
Port :: inet:port_number(),
Opts :: [option()].
Start establishing an association with a peer. π
With the Addr and Port arguments, this is equivalent to:
connect_init(Socket, Addr, Port, Opts, infinity)
π¦ connect_init/4 π¦
okay so with args SockAddr and Opts (since OTP 24.3), this is literally just the same thing as calling connect_init(Socket, Addr, Port, Opts, Timeout) where Addr and Port are extracted from SockAddr. that's it bestie π
β¨ connect_init/5 β¨
connect_init(Socket, Addr, Port, Opts, Timeout) ->
ok | {error, inet:posix()}
when
Socket :: sctp_socket(),
Addr :: inet:ip_address() | inet:hostname(),
Port :: inet:port_number(),
Opts :: [option()],
Timeout :: timeout().
Start establishing an association with a peer.
So this kicks off a brand new association for socket Socket, with the peer (that's your SCTP server socket) specified by Addr and Port. π«Ά
The fundamental difference between this API and connect/* is that the return value is the return value of the underlying OS connect(2) system call. So if you get ok back, that means the operation has been successfully initiated β not that it's done. The final result of the association establishment gets sent to the socket owner (the controlling process) as an #sctp_assoc_change{} event. The socket owner must be prepared to receive this, so the recv/* call has to be polled, depending on the value of the active option. π€
The parameters are as described for connect/*, except for the Timeout value β because for this function, the time-out only applies to the name resolving of Addr when it's an inet:hostname/0. iykyk π
π connectx_init/3 π
connectx_init(Socket, SockAddrs, Opts) ->
{ok, assoc_id()} | {error, inet:posix()}
when
Socket :: sctp_socket(),
SockAddrs ::
[{inet:ip_address(), inet:port_number()}
|
inet:family_address() |
socket:sockaddr_in() |
socket:sockaddr_in6()],
Opts :: [option()].
Start establishing an association with a peer (multiple addresses).
Basically connectx_init/5 but using socket addresses, and no Timeout. Since the addresses don't need lookup and the connect is non-blocking, this call returns immediately. no waiting around, periodt β
The value of each socket address port must be the same or zero. At least one socket address must have a non-zero port
π connectx_init/5 π
connectx_init(Socket, Addrs, Port, Opts, Timeout) ->
{ok, assoc_id()} | {error, inet:posix()}
when
Socket :: sctp_socket(),
Addrs ::
[inet:ip_address() | inet:hostname()],
Port :: inet:port_number() | atom(),
Opts :: [option()],
Timeout :: timeout().
Start establishing an association with a peer (multiple addresses).
Initiates a new association for socket Socket, with the peer (SCTP server socket) specified by Addrs and Port. πΈ
This API is similar to connect_init/*, except the underlying OS sctp_connectx(3) system call is used β and that one accepts multiple destination addresses. multi-homing said "let me cook" π₯
If it's successful, the association ID is returned, and that ID will be received in a subsequent #sctp_assoc_change{} event.
The parameters are as described in connect_init/5
NOTE: this API allows the OS to use all Addrs when establishing an association, but it does not guarantee it will. So if the connection fails, you may want to rotate the order of the addresses for a subsequent call. bestie, that's your hint that order matters sometimes π
π controlling_process/2 π
controlling_process(Socket, Pid) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
Pid :: pid(),
Reason ::
closed | not_owner | badarg |
inet:posix().
Change the controlling process (owner) of a socket.
Assigns a new controlling process Pid to Socket. See gen_udp:controlling_process/2. new main character just dropped π
π¦ eof/2 π¦
eof(Socket, Assoc) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
Assoc :: #sctp_assoc_change{},
Reason :: term().
Terminate an association gracefully.
Gracefully terminates the association specified by Assoc, flushing all unsent data. π The socket itself stays open. Other associations opened on this socket are still valid. And the socket can be used in new associations. it's giving "break up but stay friends" β¨
π error_string/1 π
error_string(ErrorNumber) -> ok | string() | unknown_error
when ErrorNumber :: integer().
Translate an error number into a string or atom.
Translates an SCTP error number from, like, #sctp_remote_error{} or #sctp_send_failed{} into an explanatory string. Or into one of the atoms ok for no error, or unknown_error for an unrecognized integer. very demure, very mindful translation service π«Ά
πΈ listen/2 πΈ
listen(Socket, IsServer) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
IsServer :: boolean(),
Reason :: term();
(Socket, Backlog) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
Backlog :: integer(),
Reason :: term().
Make an SCTP socket listen to incoming associations.
The socket will listen on the IP address(es) and port number it is bound to. π‘
For type seqpacket sockets (which are the default, bestie), the argument IsServer must be a boolean/0. And in contrast to stream sockets, there is no listening queue length. If IsServer is true, the socket accepts new associations β that is, it becomes an SCTP server socket. π
For type stream sockets, the argument Backlog sets the backlog queue length, exactly like it does for TCP.
π open/1 π
open(Port) -> {ok, Socket} | {error, inet:posix()}
when Port :: inet:port_number(), Socket :: sctp_socket();
(Opts) -> {ok, Socket} | {error, inet:posix()}
when
Opts :: [Opt],
Opt ::
{ifaddr, IP | SockAddr} |
{ip, IP} |
{port, Port} |
inet:address_family() |
{type, SockType} |
{netns, file:filename_all()} |
{bind_to_device, binary()} |
option(),
IP :: inet:ip_address() | any | loopback,
SockAddr ::
socket:sockaddr_in() | socket:sockaddr_in6(),
Port :: inet:port_number(),
SockType :: seqpacket | stream,
Socket :: sctp_socket().
Create an SCTP socket.
With the argument Port, this is equivalent to open([{port, Port}].
Creates an SCTP socket and binds it to the local addresses specified by all {ip,IP} (or synonymously {ifaddr,IP}) options β this feature is called SCTP multi-homing. π β¨ The default IP and Port are any and 0, meaning bind to all local addresses on any free port.
It's also possible to use {ifaddr, SockAddr}, and in that case it takes precedence over the ip and port options. Those options can however be used to update the address and port of ifaddr (if they occur after ifaddr in the options list) β although this is not recommended. don't do it bestie, it's giving chaos π
Other options:
inet6β Sets up the socket for IPv6.inetβ Sets up the socket for IPv4. This is the default.
A default set of socket options is used. In particular, the socket is opened in binary and passive mode, with SockType seqpacket, and with reasonably large kernel and driver buffers. π§
When the socket is in passive mode, data can be received through the recv/1,2 calls.
When the socket is in active mode, received data is delivered to the controlling process as messages:
{sctp, Socket, FromIP, FromPort, {AncData, Data}}
Error-related events β like #sctp_send_failed{}, #sctp_pdapi_event{}, and #sctp_remote_error{} β are also delivered to the controlling process as messages, but they use a different tuple tag:
{sctp_error, Socket, FromIP, FromPort, {AncData, Data}}
See recv/1,2 for a description of the message fields. π
NOTE: this message format unfortunately differs slightly from thegen_udpmessage format with ancillary data, and from therecv/1,2return tuple format. yeah it's a little inconsistent, we know π
π peeloff/2 π
peeloff(Socket, Assoc) -> {ok, NewSocket} | {error, Reason}
when
Socket :: sctp_socket(),
Assoc :: #sctp_assoc_change{} | assoc_id(),
NewSocket :: sctp_socket(),
Reason :: term().
Branch off an association into a new socket of type stream.
The existing association Assoc in the socket Socket (which has to be of type seqpacket; one-to-many style) is branched off into a new socket NewSocket of type stream (one-to-one style). it's giving glow up, one association got its own little apartment π¦
The existing association argument Assoc can be a #sctp_assoc_change{} record as returned from, for example, recv/*, connect/*, or from a listening socket in active mode. It can also be just the field assoc_id integer/0 from such a record. π
recv(Socket, Timeout) ->
{ok, {FromIP, FromPort, AncData, Data}} | {error, Reason}
when
Socket :: sctp_socket(),
Timeout :: timeout(),
FromIP :: inet:ip_address(),
FromPort :: inet:port_number(),
AncData ::
[#sctp_sndrcvinfo{} | inet:ancillary_data()],
Data ::
binary() |
string() |
#sctp_sndrcvinfo{} |
#sctp_assoc_change{} |
#sctp_paddr_change{} |
#sctp_adaptation_event{},
Reason ::
inet:posix() |
#sctp_send_failed{} |
#sctp_paddr_change{} |
#sctp_pdapi_event{} |
#sctp_remote_error{} |
#sctp_shutdown_event{}.
Receiving a Data message, bestie. π
Okay so this grabs a Data message from any association of the socket. If the receive times out you get {error,timeout} back β and the default time-out is infinity, so by default it will simply wait forever for you like a loyal bestie. π FromIP and FromPort tell you the address of whoever sent it. π
AncData is a list of ancillary data items that came along with the main Data. This list can be empty, or it can hold a single #sctp_sndrcvinfo{} record β that happens if receiving ancillary data is enabled (peep the option sctp_events). And fr fr it's enabled by default, because ancillary data is such an easy slay way to figure out which association and which stream the message arrived on. β¨ (There's an alternative: pull the association ID out of FromIP and FromPort using the socket option sctp_get_peer_addr_info β but that still doesn't give you the stream number, so likeβ¦ why would you. π
)
AncData may also contain ancillary data from the socket options recvtos, recvtclass or recvttl, if the platform supports that for this socket. π«Ά
The Data you receive can β depending on the socket mode β be a binary/0 or a list/0 of bytes (integers in the range 0 through 255), or it can be an SCTP event. π
β¨ Possible SCTP events β¨
#sctp_sndrcvinfo{}#sctp_assoc_change{}- ```erlang #sctp_paddr_change{ addr = {ip_address(),port()}, state = atom(), error = integer(), assoc_id = assoc_id() }
<pink>This one says: hey, the status of the peer's IP address changed.</pink> The address in question is `addr`, inside association `assoc_id`. The possible values of `state` are mostly self-explanatory, and they include:
- `addr_unreachable`
- `addr_available`
- `addr_removed`
- `addr_added`
- `addr_made_prim`
- `addr_confirmed`
If something errored (say, `addr_unreachable`), the `error` field serves up extra diagnostics. π And in those cases the `#sctp_paddr_change{}` event gets automatically converted into an error term that `recv` hands back to you. You can turn that `error` field value into a string with `error_string/1`. π
- ```erlang
#sctp_send_failed{
flags = true | false,
error = integer(),
info = #sctp_sndrcvinfo{},
assoc_id = assoc_id()
data = binary()
}
The sender can receive this event if a send operation fails. π
flags : A Boolean specifying if the data has been transmitted over the wire.
error : Provides extended diagnostics, use error_string/1.
info : The original #sctp_sndrcvinfo{} record that was used in the failed send/*.
data : The whole original data chunk that was attempted to be sent.
In the current implementation of the Erlang/SCTP binding, this event is internally converted into an error term returned by recv/*. Lowkey convenient, ngl.
- ```erlang #sctp_adaptation_event{ adaptation_ind = integer(), assoc_id = assoc_id() }
Delivered when a peer sends an adaptation layer indication parameter (configured through the option `sctp_adaptation_layer`). Note that with the current implementation of the Erlang/SCTP binding, this event is <yellow>disabled by default</yellow>. π
- ```erlang
#sctp_pdapi_event{
indication = sctp_partial_delivery_aborted,
assoc_id = assoc_id()
}
A partial delivery failure. π΅ In the current implementation of the Erlang/SCTP binding, this event is internally converted into an error term returned by recv/*.
send(Socket, SndRcvInfo, Data) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
SndRcvInfo :: #sctp_sndrcvinfo{},
Data :: binary() | iolist(),
Reason :: term().
Send a Data message β the full-featured version. β¨
This sends the Data message with every single sending parameter coming from a #sctp_sndrcvinfo{} record. That means you get to specify the PPID (which gets passed to the remote end) and the context (which goes to the local SCTP layer), and you can use those for stuff like identifying errors. π΅οΈββοΈ That said, this level of fine-grained user control is <highkey>rarely required</highkey>. The function send/4 is plenty for most applications. π
NOTE: Send is normally blocking, but if the socket optionnon_block_sendis set totrue, the function will return with e.g.{error, eagain}in the case when the function would otherwise block. It is then up to the user to try again later. β°
send(Socket, Assoc, Stream, Data) -> ok | {error, Reason}
when
Socket :: sctp_socket(),
Assoc :: #sctp_assoc_change{} | assoc_id(),
Stream :: integer(),
Data :: binary() | iolist(),
Reason :: term().
Send a data message. π¦
Sends a Data message on the association Assoc and Stream.
Assoc can be given as a #sctp_assoc_change{} record from an association establishment, or as the assoc_id/0 integer/0 field value. Either way bestie, we ride. π«Ά
NOTE: Send is normally blocking, but if the socket optionnon_block_sendis set totrue, the function will return with e.g.{error, eagain}in the case when the function would otherwise block. It is then up to the user to try again later. β°
and that's a wrap on gen_sctp!! π genuinely one of the most iconic little corners of the Erlang kernel, brought to you by Ericsson AB (the OGs), shipping as part of kernel 11.0.3, documented as gen_sctp(3). go forth and slay your SCTP sockets, bestie β mwah πβ¨π
π¦ where this page comes from
- ubuntu stonking: erlang-doc 1:29.0.6+dfsg-1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms