socket(3)

๐Ÿ“ฆ
version 29.0.6 ยท 4 versions
, in ubuntu stonking

๐ŸŒธ socket(3) โ€” Erlang Module Definition ๐ŸŒธ


๐Ÿ’– NAME ๐Ÿ’–

socket โ€” Socket interface. โœจ

๐ŸŒˆ DESCRIPTION ๐ŸŒˆ

okay so bestie, listen up. This module is literally your whole API for network sockets. It's giving you functions to create them, delete them, manipulate them, and obviously send + receive data on them. ๐Ÿซถ

The whole vibe? It wants to be as "close as possible" to the OS-level socket interface. The only significant additions (the way thatโ€ฆ) are that some functions (e.g. recv/3) have a time-out argument, and that recv/* for a stream socket iterates until the requested amount of data has been received. periodt. ๐Ÿ’…

๐Ÿ“ฌ Note: Asynchronous Calls ๐Ÿ“ฌ

Some functions feature asynchronous calls. This is achieved by setting the Timeout argument to nowait or to a Handle :: reference/0. See the respective function's type specification. This module has two different implementations of asynchronous calls. One on the Unix family of operating systems: select โ€” based on the standard socket interface's select(3)/poll(3) calls. And one on Windows: completion โ€” based on asynchronous I/O Completion Ports. The difference shows in the return values and message formats because they have slightly different semantics. ๐Ÿ˜ญ it's giving subtle chaos, but we move.

The completion and select Return Values

For instance, the call recv(Socket, 0, nowait), when there is no data available for reading, will, depending on the operating system, return one of:

  • {completion, CompletionInfo}
  • {select, SelectInfo} Where CompletionInfo is {completion_info, _, CompletionHandle} and SelectInfo is {select_info, _, SelectHandle}.

Both the CompletionHandle and the SelectHandle are of type reference/0. When the operation can continue, a completion message containing the CompletionHandle or a select message containing the SelectHandle is sent to the calling process. On select systems, recv/2,3,4 may also return:

  • {select, {SelectInfo, Data} This may happen for sockets of type stream when Length > 1 since the OS may split a data stream at any point and deliver just the first part of the requested data. For the next recv/2,3,4 call; the Length to receive will probably have to be adjusted due to the already delivered data in this return value. On select systems, when the {otp, select_read} option is true, the asynchronous recv/3,4, recvfrom/3,4, and recvmsg/3,4,5 functions may also return:
  • {select_read, {SelectInfo, Data} This indicates that the receive operation was completed; all requested data has been delivered, and that the calling process will get a select message when there is data available for the next receive operation.

The completion and select Messages

The completion message has the format:

  • {'$socket', Socket, completion, {CompletionHandle, CompletionStatus}}

The select message has the format:

  • {'$socket', Socket, select, SelectHandle}

When a completion message is received (which contains the result of the operation), it means that the operation has been completed and CompletionStatus is the return value for the operation, which is what the function that initiated the operation could have returned, with the nowait argument, except for the completion and select return values. When a select message is received, it only means that the operation may now continue, by retrying the operation (which may return a new {select, _} value). Some operations are retried by repeating the same function call, and some have a dedicated function variant to be used for the retry. See the respective function's documentation. ๐Ÿ”ฅ

Operation Queuing on select Systems

On select systems, all other processes are locked out until the current process has completed the operation as in a continuation call has returned a value indicating success or failure (not a select or select_read return). Other processes are queued and get a select return which makes them wait for their turn. Note that receiving data from parallel processes is only suitable for some protocols. For a stream socket it is in general a recipe for disaster. ๐Ÿ’€

Cancelling an operation

An operation that is in progress (not completed) may be canceled using cancel/2 both on completion and select systems. Cancelling an operation ensures that there is no completion, select, nor abort message in the inbox after the cancel/2 call. slay. โœจ

Using a Handle

If creating a reference/0 with make_ref() and using that as the Timeout | Handle argument, the same Handle will then be the SelectHandle in the returned select_info/0 and the received select message, or be the CompletionHandle in the returned completion_info/0 and the received completion message. The compiler may then optimize a following receive statement to only scan the messages that arrive after the reference/0 is created. If the message queue is large this is a big optimization. It is not possible to have more than one operation in progress with the same reference/0. ๐Ÿซก

Repeating an Operation on a select Systems

Onselect systems, if a call would be repeated before the select message has been received it replaces the operation in progress:

                        {select, {select_info, Handle}} = socket:accept(LSock, nowait),
                        {ok, Socket} = socket:accept(LSock, 1000),
                        :

Above, Handle is no longer valid once the second accept/2, call has been made (the first call is automatically canceled). After the second accept/2 call returns, the accept operation has completed. Note that there is a race here; there is no way to know if the call is repeated before the select message is sent since it may have been sent just before the repeated call. So now there might be a select message containing Handle in the inbox. ๐Ÿ˜ตโ€๐Ÿ’ซ iconic behavior honestly.

The abort Message

Another message the user must be prepared for (when using nowait | Handle) is the abort message:

  • {'$socket', Socket, abort, Info} This message indicates that the operation in progress has been aborted. For instance, if the socket has been closed by another process; Info will be {Handle, closed}.

โš ๏ธ NOTE: Support for IPv6 has been implemented but not fully tested.

This module was introduced in OTP 22.0, as experimental code.

  • In OTP 22.1, the nowait argument was added for many functions, and the cancel/2 and info/1 functions were also added.
  • In OTP 22.3, the number_of/0 function was added.
  • In OTP 23.0, the functions is_supported/1,2 and the open/1,2 functions with an FD argument were added.
  • In OTP 23.1, the use_registry/1 function was added.
  • In OTP 24.0, the select_handle/0 argument was added for many functions, the cancel/1, cancel_monitor/1, getopt/3 with tuple options, getopt_native/3, info/0, monitor/1, open/3 with an option list, many variants of the recv/*, recvfrom/*, recvmsg/*, send/*, sendmsg/*, the sendto/* functions, the sendfile/* functions, and the setopt/3, setopt_native/3 functions, were added.
  • In OTP 24.1, the i/* functions were added.
  • In OTP 24.2, the ioctl/* functions were added.
  • In OTP 26.0, the completion_handle/0 argument was added for many functions, and the cancel/2 function with completion_info/0 argument was added. That is: support for Windows asynchronous I/O Completion Ports was added. The Unix-ish flavored (select handle) API features could be considered no longer experimental.
  • In OTP 27.0, the Windows flavored (completion handle) API features could be considered no longer experimental.
  • In OTP 29.0, (experimental) complete support for SCTP was added (functionally feature compatible with inet). Not (yet) supported for FreeBSD.

๐ŸŒŸ Examples ๐ŸŒŸ

              client(SAddr, SPort) ->
                 {ok, Sock} = socket:open(inet, stream, tcp),
                 ok = socket:connect(Sock, #{family => inet,
                                             addr   => SAddr,
                                             port   => SPort}),
                 Msg = <<"hello">>,
                 ok = socket:send(Sock, Msg),
                 ok = socket:shutdown(Sock, write),
                 {ok, Msg} = socket:recv(Sock),
                 ok = socket:close(Sock).

              server(Addr, Port) ->
                 {ok, LSock} = socket:open(inet, stream, tcp),
                 ok = socket:bind(LSock, #{family => inet,
                                           port   => Port,
                                           addr   => Addr}),
                 ok = socket:listen(LSock),
                 {ok, Sock} = socket:accept(LSock),
                 {ok, Msg} = socket:recv(Sock),
                 ok = socket:send(Sock, Msg),
                 ok = socket:close(Sock),
                 ok = socket:close(LSock).

๐ŸŽ€ DATA TYPES ๐ŸŽ€

cmsg() :: cmsg_recv() | cmsg_send().

Control messages (ancillary messages). ๐Ÿ’Œ

cmsg_recv()

#{level := socket,
  type := timestamp,
  data := binary(),
  value => timeval()} |
#{level := socket,
  type := timestampns,
  data := binary(),
  value => timespec()} |
#{level := socket, type := rights, data := binary()} |
#{level := socket, type := credentials, data := binary()} |
#{level := ip,
  type := tos,
  data := binary(),
  value => ip_tos()} |
#{level := ip,
  type := recvtos,
  data := binary(),
  value := ip_tos()} |
#{level := ip,
  type := ttl,
  data := binary(),
  value => integer()} |
#{level := ip,
  type := recvttl,
  data := binary(),
  value := integer()} |
#{level := ip,
  type := pktinfo,
  data := binary(),
  value => ip_pktinfo()} |
#{level := ip,
  type := origdstaddr,
  data := binary(),
  value => sockaddr_recv()} |
#{level := ip,
  type := recverr,
  data := binary(),
  value => extended_err()} |
#{level := ipv6,
  type := hoplimit,
  data := binary(),
  value => integer()} |
#{level := ipv6,
  type := pktinfo,
  data := binary(),
  value => ipv6_pktinfo()} |
#{level := ipv6,
  type := recverr,
  data := binary(),
  value => extended_err()} |
#{level := ipv6,
  type := tclass,
  data := binary(),
  value => integer()}.

These are the โœจcontrol messagesโœจ โ€” aka ancillary messages โ€” that come back to you from recvmsg/1,2,3,5. ๐Ÿ’Œ

Every control message has a data field holding the raw message data as a native (binary) value. And if this socket library managed to decode that data for you, it also throws in a value field with the nice parsed version. Bestie, that's the glamour. ๐Ÿ’…


cmsg_send()

#{level := sctp, type := sndrcv, value := sctp_snd_rcv_info()}
|
#{level := socket,
  type := timestamp,
  data => native_value(),
  value => timeval()} |
#{level := socket, type := rights, data => native_value()} |
#{level := socket,
  type := credentials,
  data => native_value()} |
#{level := ip,
  type := tos,
  data => native_value(),
  value => iptos_value()} |
#{level := ip,
  type := ttl,
  data => native_value(),
  value => integer()} |
#{level := ip,
  type := hoplimit,
  data => native_value(),
  value => integer()} |
#{level := ipv6,
  type := tclass,
  data => native_value(),
  value => integer()}.

The control messages (ancillary messages, if you wanna be formal ๐ŸŽ€) that sendmsg/2,3,4 will actually accept.

Depending on the message type, a control message can carry a value field with a symbolic value, or a data field with a native value โ€” and that native value has to be binary compatible with whatever the platform's header files define. No vibes-based guessing here bestie, it's giving byte-exact. ๐Ÿ”ฅ


completion_handle()

reference().

The completion operation handle. ๐Ÿซถ

It's a reference/0 that uniquely identifies the (completion) operation, and you'll find it inside the returned completion_info/0. Like a little name tag for your async work. ๐ŸŽ€


completion_info()

{completion_info,
 CompletionTag :: completion_tag(),
 CompletionHandle :: completion_handle()}.

The completion operation info. ๐Ÿฆ‹

This is what you get handed back by an operation that needs you to wait for a completion message. That message carries the CompletionHandle plus the result of the operation โ€” the CompletionStatus. So like, go do your thing, come back, I'll have the โœจresultsโœจ waiting. ๐Ÿ’–


completion_tag()

accept | connect |
recv | recvfrom | recvmsg | recvmmsg | send | sendv | sendto |
sendmsg | sendmmsg | sendfile.

The completion operation tag. ๐Ÿท๏ธ

It's a tag that tells you what the ongoing (completion) operation is โ€” basically the function name โ€” and it lives inside the returned completion_info/0. So you always know which operation you're waiting on. Iconic, honestly. ๐Ÿ‘‘


domain()

inet | inet6 | local | unspec.

Protocol domain, aka the address family. ๐ŸŒˆ

It's a lowercase atom/0 standing in for a protocol domain on the platform named AF_* (or PF_*). For example inet corresponds to AF_INET. ๐Ÿงš

  • is_supported(ipv6) tells you if the IPv6 protocol โ€” the protocol domain inet6 โ€” is supported.
  • is_supported(local) tells you if the protocol domain local is supported.
  • supports/0 reports both of those values, plus a whole bunch more, all in one single call. Efficient queen. ๐Ÿ’…

ee_origin()

none | local | icmp | icmp6.

eei()

#{info :=
      econnreset | econnaborted | netname_deleted |
      too_many_cmds |
      atom(),
  raw_info := term()}.

Extended Error Information. ๐Ÿ˜ญ

A term carrying extra (error) information โ€” but only if the socket NIF has been configured to actually produce it. No config, no tea. โ˜•


extended_err()

#{error := posix(),
  origin := icmp,
  type := dest_unreach,
  code := icmp_dest_unreach() | 0..255,
  info := 0..4294967295,
  data := 0..4294967295,
  offender := sockaddr_recv()} |
#{error := posix(),
  origin := icmp,
  type := time_exceeded | 0..255,
  code := 0..255,
  info := 0..4294967295,
  data := 0..4294967295,
  offender := sockaddr_recv()} |
#{error := posix(),
  origin := icmp6,
  type := dest_unreach,
  code := icmpv6_dest_unreach() | 0..255,
  info := 0..4294967295,
  data := 0..4294967295,
  offender := sockaddr_recv()} |
#{error := posix(),
  origin := icmp6,
  type := pkt_toobig | time_exceeded | 0..255,
  code := 0..255,
  info := 0..4294967295,
  data := 0..4294967295,
  offender := sockaddr_recv()} |
#{error := posix(),
  origin := ee_origin() | 0..255,
  type := 0..255,
  code := 0..255,
  info := 0..4294967295,
  data := 0..4294967295,
  offender := sockaddr_recv()}.

hatype()

netrom | eether | ether | ax25 | pronet | chaos | ieee802 |
arcnet | appletlk | dlci | atm | metricom | ieee1394 | eui64 |
infiniband | tunnel | tunnel6 | loopback | localtlk | none |
void |
non_neg_integer().

icmp_dest_unreach()

net_unreach | host_unreach | port_unreach | frag_needed |
net_unknown | host_unknown.

icmpv6_dest_unreach()

noroute | adm_prohibited | not_neighbour | addr_unreach |
port_unreach | policy_fail | reject_route.

in6_addr()

{0..65535,
 0..65535,
 0..65535,
 0..65535,
 0..65535,
 0..65535,
 0..65535,
 0..65535}.

in6_flow_info()

0..1048575.

in6_scope_id()

0..4294967295.

in_addr()

{0..255, 0..255, 0..255, 0..255}.

info()

#{counters := #{atom() := non_neg_integer()},
  iov_max := non_neg_integer(),
  use_registry := boolean(),
  io_backend := #{name := atom()},
  load_nif_result := undefined | ok | {error, term()}}.

Platform dependent information items. ๐ŸŒ

The value of iov_max is the value of the IOV_MAX constant in the system headers, which is the largest allowed I/O vector. See also sendmsg/4 regarding the iov key of msg_send/0. The smallest allowed IOV_MAX value according to POSIX is 16, but check your platform documentation to be sure โ€” fr fr, don't assume. ๐Ÿง

About the use_registry key: see use_registry/1 and the otp_socket_option/0 with the same name. ๐Ÿ‘ฏ


info_keys()

[domain | type | protocol | fd | owner | local_address |
 remote_address | recv | sent | state].

The information element designators for the i/1 and i/2 functions. ๐Ÿ’…


interface_type()

other | hdh1822 | x25ddh | x25 | ether | ppp | loop | ipv4 |
ipv6 | '6to4' | gif | faith | stf | bridge | cellular |
non_neg_integer().

The interface type โ€” of the datalink, bestie. ๐Ÿ”Œ

We only translate a few of these values into atoms; the rest just get left as (unsigned) integer values. Not everything gets the cute nickname treatment, periodt. ๐Ÿ’…


invalid()

{invalid, What :: term()}.

ioctl_device_flag()

up | broadcast | debug | loopback | pointopoint | notrailers |
knowsepoch | running | noarp | promisc | allmulti | master |
oactive | slave | simplex | link0 | link1 | link2 |
multicast | portsel | automedia | cantconfig | ppromisc |
dynamic | monitor | staticarp | dying | renaming | nogroup |
lower_up | dormant | echo.

ioctl_device_map()

#{mem_start := non_neg_integer(),
  mem_end := non_neg_integer(),
  base_addr := non_neg_integer(),
  irq := non_neg_integer(),
  dma := non_neg_integer(),
  port := non_neg_integer()}.

ip_mreq()

#{multiaddr := in_addr(), interface := in_addr()}.

C: struct ip_mreq

This corresponds to the C struct ip_mreq โ€” it's the one for managing multicast groups. ๐ŸŽ‰ Nobody puts baby in a corner and nobody renames this struct.


ip_mreq_source()

#{multiaddr := in_addr(),
  interface := in_addr(),
  sourceaddr := in_addr()}.

C: struct ip_mreq_source

Corresponds to the C struct ip_mreq_source, also for managing multicast groups. It's the sequel and it ate. ๐Ÿ’…


ip_msfilter()

#{multiaddr := in_addr(),
  interface := in_addr(),
  mode := include | exclude,
  slist := [in_addr()]}.

C: struct ip_msfilter

Corresponds to the C struct ip_msfilter โ€” this one's for managing multicast source filtering (RFC 3376). Very demure, very standards-compliant. ๐ŸŽ€


ip_pktinfo()

#{ifindex := non_neg_integer(),
  spec_dst := in_addr(),
  addr := in_addr()}.

C: struct ip_pktinfo


ip_pmtudisc()

want | dont | do | probe.

C: IP_PMTUDISC_* values โ†’ the lowercase atom/0 values that line up one-to-one with the C library constants IP_PMTUDISC_*. And ngl bestie, some constant(s) may be unsupported by the platform โ€” so vibe check your OS before you get too attached ๐Ÿ’…


ip_tos()

ip_tos() ::
          #{native := iptos_native(),
            tos := iptos_tos(),
            dscp := iptos_dscp() | non_neg_integer()}.

C: IPTOS_* values.

Okay so here's the situation, and it's giving layered ๐Ÿง… โ€” there are literally two different representations of TOS: one from RFC 1349 (the "classic TOS") and one from RFC 2474 (DSCP). Which means we have three different value representations for tos:

  • native โ€” the raw, unencoded value of the TOS octet, no thoughts head empty
  • tos โ€” the classic one
  • dscp โ€” the DSCP one

When you're sending or setting (the ip tos option), you're the main character and you get to choose between the three representations โœจ. But when you're reading, the value comes back as a map holding all three representations โ€” because socket genuinely does not know which one you were expecting ๐Ÿ˜ญ. So it's fully on you, bestie, to pick the one you want.

And one more thing: an integer dscp value is a DSCP field value that is not known from the IANA registry (go look at iptos_dscp/0).


iptos_dscp()

iptos_dscp() ::
          cs0 | le | cs1 | af11 | af12 | af13 | cs2 | af21 | af22 |
          af23 | cs3 | af31 | af32 | af33 | cs4 | af41 | af42 | af43 |
          cs5 | voice_admit | nqb | ef | cs6 | cs7.

These symbolic DSCP values are straight-up per IANA's Differentiated Services Field Codepoints registry, which lives at:

https://www.iana.org/assignments/dscp-registry/dscp-registry.xhtml

Iconic registry behavior honestly ๐Ÿ‘‘


iptos_native()

iptos_native() :: non_neg_integer().

iptos_tos()

iptos_tos() ::
          #{precedence := iptos_tos_prec() | non_neg_integer(),
            tos := iptos_tos_value() | non_neg_integer()}.

Lowercase atom/0 values that correspond to the C library constants IPTOS_*. The atoms are named like the C library names, but โ€” and this is the plot twist โ€” to avoid platform dependency, the set of names and values follows RFC 1349, not the C library header files. So don't go assuming your headers are the blueprint ๐Ÿ™ˆ

An integer value here is a field value that isn't named in the RFC.


iptos_tos_prec()

iptos_tos_prec() ::
          netcontrol | internetcontrol | critical_ecp | flashoverride |
          flash | immediate | priority | routine.

iptos_tos_value()

iptos_tos_value() ::
          default | lowdelay | throughput | reliability | mincost.

iptos_value()

iptos_value() :: iptos_tos() | iptos_dscp() | iptos_native().

ipv6_hops()

ipv6_hops() :: default | 0..255.

IPv6 hop limit value ๐Ÿ‡

The value default is only valid to set, and it gets translated to the C value -1, which means the route default.


ipv6_mreq()

ipv6_mreq() ::
          #{multiaddr := in6_addr(), interface := non_neg_integer()}.

C: struct ipv6_mreq

This one corresponds to the C struct ipv6_mreq, and it's for managing multicast groups ๐ŸŽ‰. Also see RFC 2553 if you want the lore.


ipv6_pktinfo()

ipv6_pktinfo() :: #{addr := in6_addr(), ifindex := integer()}.

C: struct in6_pktinfo


ipv6_pmtudisc()

ipv6_pmtudisc() :: want | dont | do | probe.

C: IPV6_PMTUDISC_* values

Lowercase atom/0 values corresponding to the C library constants IPV6_PMTUDISC_*. And once again โ€” some constant(s) may be unsupported by the platform, so don't be shocked if your OS ghosts one of them ๐Ÿ’…


level()

level() :: socket | protocol().

Protocol level.

It's a lowercase atom/0 OS protocol level, meaning: socket, or a protocol/0 name.

socket is the SOL_SOCKET protocol level up in the OS header files, and its options are named SO_*.


linger()

linger() :: #{onoff := boolean(), linger := non_neg_integer()}.

C: struct linger

Corresponds to the C struct linger, used for managing the socket option {socket, linger}.


msg()

msg() :: msg_send() | msg_recv().

C: struct msghdr


msg_data_recv()

msg_data_recv() ::
          #{addr => sockaddr_recv(),
            iov := erlang:iovec(),
            ctrl :=
                [cmsg_recv() |
                 #{level := level() | integer(),
                   type := integer(),
                   data := binary()}],
            flags := [msg_flag() | integer()]}.

The message returned by recvmsg/1,2,3,5 ๐Ÿ“ฌ

Corresponds to a C struct msghdr โ€” so peep your platform documentation for recvmsg(2).

  • addr Optional peer address, used on unconnected sockets. Corresponds to the msg_name and msg_namelen fields of a struct msghdr. If NULL, the map key is simply not present (it ghosts you, respectfully).
  • iov The data as a list of binaries. This is the msg_iov and msg_iovlen fields of a struct msghdr.
  • ctrl A possibly empty list of control messages (CMSG). Corresponds to the msg_control and msg_controllen fields of a struct msghdr.
  • flags Message flags. Corresponds to the msg_flags field of a struct msghdr. Unknown flags, if any, are returned in one integer/0, sitting last in the containing list.

msg_flag()

msg_flag() ::
          cmsg_cloexec | confirm | ctrunc | dontroute | eor | errqueue |
          more | oob | peek | trunc.

Platform dependent message flags.

These translate to/from the message flag constants on your platform ๐ŸŒ. They're lowercase while the constants are uppercase with a MSG_ prefix โ€” so for example, oob translates to MSG_OOB.

Some flags are only used for sending, some only for receiving, some show up in received control messages, and some do several of those jobs. And not all flags are supported on all platforms. So check: your platform's documentation, supports(msg_flags), and is_supported(msg_flags, MsgFlag) ๐Ÿ”


msg_notification_recv()

msg_notification_recv() ::
          #{addr => sockaddr_recv(),
            notification := sctp_notification(),
            ctrl :=
                [#{level := level() | integer(),
                   type := integer(),
                   data := binary()}],
            flags := [notification | [msg_flag() | integer()]]}.

Notifications can be received on a SCTP socket (that's type = seqpacket and protocol = sctp) ๐Ÿšจ


msg_recv()

msg_recv() :: msg_data_recv() | msg_notification_recv().

The message returned by recvmsg/1,2,3,5.


msg_send()

msg_send() ::
          #{addr => sockaddr(),
            iov := erlang:iovec(),
            ctrl =>
                [cmsg_send() |
                 #{level := level() | integer(),
                   type := integer(),
                   data := binary()}]}.

The message sent by sendmsg/2,3,4 ๐Ÿ“ค

Corresponds to a C struct msghdr โ€” see your platform documentation for sendmsg(2).

  • addr Optional peer address, used on unconnected sockets. Corresponds to the msg_name and msg_namelen fields of a struct msghdr. If not used, they are set to NULL, 0.
  • iov Mandatory data as a list of binaries. The msg_iov and msg_iovlen fields of a struct msghdr.
  • ctrl Optional list of control messages (CMSG). Corresponds to the msg_control and msg_controllen fields of a struct msghdr. If not used, they are set to NULL, 0.

And heads up: the msg_flags field of the struct msghdr is set to 0.


native_value()

native_value() :: integer() | boolean() | binary().

otp_socket_option()

otp_socket_option() ::
          debug | iow | controlling_process | rcvbuf | rcvctrlbuf |
          sndctrlbuf | select_read | meta | use_registry | fd | domain.

Protocol level otp socket option.

These are the socket options for the otp pseudo protocol level, aka the {otp, Name} options โœจ

This protocol level is Erlang/OTP's own socket implementation layer โ€” meaning it sits above all OS protocol levels. Very main character of it.

  • debug boolean/0 โ€” Activate debug logging.
  • iow boolean/0 โ€” Inform On Wrap of statistics counters.
  • controlling_process pid/0 โ€” The socket "owner". Only the current controlling process can set this option. No stealing, bestie ๐Ÿ™…
  • rcvbuf BufSize :: (default | integer()>0) | {N :: integer()>0, BufSize :: (default | integer()>0)} โ€” Receive buffer size.

The value default is only valid to set.

N specifies the number of read attempts to do in a tight loop before assuming no more data is pending.

This is the allocation size for the receive buffer used when calling the OS protocol stack's receive API, when no specific size (size 0) is requested. When the receive function returns, the receive buffer is reallocated to the actually received size. Whether the data is copied or shrunk in place is up to the allocator, and can to some extent be configured in the Erlang VM.

The similar socket option {socket,rcvbuf} is a related option for the OS' protocol stack that on Unix corresponds to SOL_SOCKET,SO_RCVBUF.

  • rcvctrlbuf BufSize :: (default | integer()>0) โ€” Allocation size for the ancillary data buffer used when calling the OS protocol stack's receive API.

The value default is only valid to set.

  • sndctrlbuf BufSize :: (default | integer()>0) โ€” Allocation size for the ancillary data buffer used when calling the OS protocol stack's sendmsg API.

The value default is only valid to set.

It is the user's responsibility to set a buffer size that has room for the encoded ancillary data in the message to send ๐Ÿซก

See sendmsg and also the ctrl field of the msg_send/0 type.

  • select_read boolean/0 โ€” On select implementations (see Asynchronous Calls), automatically activate select after a completed read.

Instead of {ok, Data} the receive operation returns {select_read, {SelectInfo, Data}}, and the calling process can wait for a select message containing SelectInfo when there is data available again.

Setting this option locks out other processes from receiving any data, since the current process continues its operation โ€” so it effectively disables receive operation queuing. Proceed with caution ๐Ÿ’€
  • fd integer/0 โ€” Only valid to get. The OS protocol levels' socket descriptor. Functions open/1,2 can be used to create a socket according to this module from an existing OS socket descriptor.
  • use_registry boolean/0 โ€” Only valid to get. The value is set when the socket is created with open/2 or open/4.

Options not described here are intentionally undocumented and for Erlang/OTP internal use only ๐Ÿ‘€


packet_type()

packet_type() ::
          host | broadcast | multicast | otherhost | outgoing |
          loopback | user | kernel | fastroute |
          non_neg_integer().

port_number()

port_number() :: 0..65535.

posix()

posix() :: inet:posix().

Posix error codes ๐Ÿ’ข

okay so bestie, we're still deep in DATA TYPES land and it's giving encyclopedia but make it cute ๐Ÿ“šโœจ buckle up:

Local alias for inet:posix/0, a set of atom/0s.


protocol() :: atom(). โ†’ this one is the Protocol name. ๐Ÿ’…

A lowercase atom/0 representing an OS protocol name. You'd use it for example in socket_option/0 in control messages.

And ngl, here's what they're actually called in the OS header files ๐Ÿ‘‡

  • ip โ€” IPPROTO_IP a.k.a SOL_IP with options named IP_*.
  • ipv6 โ€” IPPROTO_IPV6 a.k.a SOL_IPV6 with options named IPV6_*.
  • tcp โ€” IPPROTO_TCP with options named TCP_*.
  • mptcp โ€” IPPROTO_MPTCP with a few options named MPTCP_*. Most TCP_* options apply which is the purpose of Multi-Path TCP.
  • udp โ€” IPPROTO_UDP with options named UDP_*.
  • sctp โ€” IPPROTO_SCTP with options named SCTP_*.

There are so many other possible protocols out there, but the ones above are the ones this socket library actually implements socket options and/or control messages for. periodt.

All protocols known to the OS get enumerated when the Erlang VM is started, through the C library call getprotoent(). Go peep the OS man page for protocols(5). Those in the list above are valid if supported by the platform, even if they aren't enumerated. ๐Ÿ‘‘

The calls is_supported(ipv6), is_supported(sctp) and is_supported(mptcp) can be used to find out if the protocols ipv6, sctp and mptcp are supported on the platform as in; appropriate header file and libraries exist. ๐Ÿซถ

The call is_supported(protocols, Protocol) can only be used to find out if the platform knows the protocol number for a named Protocol.

See open/2,3,4.


sctp_adapt_event() โ€” a peer has sent a Adaptation Layer Indication parameter. ๐Ÿฆ‹

sctp_adapt_event() ::
          #{type := adaptation_event,
            flags := uint16(),
            adaption_ind := uint32(),
            assoc_id := sctp_assoc_id()}.

C: struct sctp_adaptation_event


sctp_assoc_change() โ€” an SCTP association has either begun or ended. iconic milestone moment tbh ๐Ÿ’ซ

sctp_assoc_change() ::
          #{type := assoc_change,
            flags := integer(),
            state := sctp_assoc_change_state(),
            error := sctp_operation_error(),
            outbound_streams := integer(),
            inbound_streams := integer(),
            assoc_id := sctp_assoc_id()}.

C: struct sctp_assoc_change


sctp_assoc_change_state() ๐Ÿ˜Œ

sctp_assoc_change_state() ::
          comm_up | comm_lost | restart | shutdown_comp |
          cant_str_assoc |
          integer().

sctp_assoc_id() :: integer().

C: sctp_assoc_t


sctp_assoc_reset_event() ๐ŸŽ€

sctp_assoc_reset_event() ::
          #{type := assoc_reset,
            flags := [denied | failed],
            assoc_id := sctp_assoc_id(),
            local_tsn := uint32(),
            remote_tsn := uint32()}.

C: struct sctp_assoc_reset_event


sctp_assoc_stats() โ€” the receipts, the statistics, the whole vibe check ๐Ÿ“Š

sctp_assoc_stats() ::
          #{assoc_id := sctp_assoc_id(),
            max_rto_addr := sockaddr(),
            max_rto := uint64(),
            in_sacks := uint64(),
            out_sacks := uint64(),
            in_packets := uint64(),
            out_packets := uint64(),
            rtx_chunks := uint64(),
            out_of_seq_tsns := uint64(),
            in_dup_chunks := uint64(),
            gap_ack_recv := uint64(),
            in_unordered_chunks := uint64(),
            out_unordered_chunks := uint64(),
            in_ordered_chunks := uint64(),
            out_ordered_chunks := uint64(),
            in_ctrl_chunks := uint64(),
            out_ctrl_chunks := uint64()}.

C: struct sctp_assoc_stats.


sctp_assocparams() ๐ŸŒธ

sctp_assocparams() ::
          #{assoc_id := sctp_assoc_id(),
            asocmaxrxt := uint16(),
            number_peer_destinations := uint16(),
            peer_rwnd := uint32(),
            local_rwnd := uint32(),
            cookie_life := uint32()}.

C: struct sctp_assocparams


sctp_authkey() โ€” when a receiver is using authentication, info about new keys and errors are provided in this notification. lowkey spy-movie energy ๐Ÿ”

sctp_authkey() ::
          #{type := authkey,
            flags := uint16(),
            keynumber := uint16(),
            altkeynumber := uint16(),
            indication := uint32(),
            assoc_id := sctp_assoc_id()}.

C: struct sctp_authkey_event


sctp_event_subscribe() โ€” the subscription list, aka which notifications you're signing up for ๐Ÿ‘€

sctp_event_subscribe() ::
          #{data_io := boolean(),
            association := boolean(),
            address := boolean(),
            send_failure := boolean(),
            peer_error := boolean(),
            shutdown := boolean(),
            partial_delivery := boolean(),
            adaptation_layer => boolean(),
            sender_dry => boolean(),
            stream_reset => boolean(),
            assoc_reset => boolean(),
            stream_change => boolean()}.

C: struct sctp_event_subscribe.

bestie fr fr: Not all fields are implemented on all platforms; unimplemented fields are ignored, but implemented fields are mandatory. Note that the '_event' suffixes have been stripped from the C struct field names, for convenience.

sctp_initmsg() โ€” the opening pitch of the association ๐ŸŽฌ

sctp_initmsg() ::
          #{num_ostreams := uint16(),
            max_instreams := uint16(),
            max_attempts := uint16(),
            max_init_timeo := uint16()}.

C: struct sctp_initmsg.


sctp_notification() โ€” ALL the possible notification types โœจ

sctp_notification() ::
          sctp_assoc_change() |
          sctp_paddr_change() |
          sctp_send_failed() |
          sctp_remote_error() |
          sctp_shutdown_event() |
          sctp_adapt_event() |
          sctp_pdapi_event() |
          sctp_authkey() |
          sctp_sender_dry() |
          sctp_stream_reset_event() |
          sctp_assoc_reset_event() |
          sctp_stream_change_event() |
          sctp_send_failed_event() |
          sctp_notification_generic().

All of them has at least two fields: 'type' and 'flags' ('flags' are not allways used). (yes, we know, it says "has" and "allways", we don't make the rules ๐Ÿ’€)

C: union sctp_notification


sctp_notification_generic() โ€” the fallback bestie ๐Ÿซถ

sctp_notification_generic() ::
          #{type := uint16(), flags := uint16()}.

C: union sctp_notification

This is intended as a fallback type for any notification we have not yet implemented.


sctp_operation_error() โ€” the error menu ๐Ÿ˜ญ

sctp_operation_error() ::
          unknown | bad_sid | missing_parm | stale_cookie |
          no_resources | bad_addr | unrec_chunk | bad_mandparm |
          unrec_parm | no_usr_data | cookie_shut | restart_new_addrs |
          user_abort | delete_lastaddr | resource_shortage |
          delete_srcaddr | auth_err |
          pos_integer().

These error codes are (currently) defined in RFC 4960, and named as Operation Errors.


sctp_paddr_change() โ€” a destination address on a multi-homed peer has encountered a change. plot twist ๐ŸŒ€

sctp_paddr_change() ::
          #{type := peer_addr_change,
            flags := pos_integer(),
            addr := socket:sockaddr(),
            state := sctp_peer_addr_change_state(),
            error := pos_integer(),
            assoc_id := sctp_assoc_id()}.

C: struct sctp_paddr_change


sctp_pap_flag() โ€” the flag buffet ๐Ÿ“

sctp_pap_flag() ::
          enable_heartbeats | disable_heartbeats |
          send_heartbeat_immediately | enable_pmtu_discovery |
          disable_pmtu_discovery | enable_sack | disable_sack |
          set_heartbeat_delay_to_zero | ipv6_flowlabel | dscp.

C: enum sctp_spp_flags.


sctp_pap_flags() :: integer() | [sctp_pap_flag()].

There are three pairs of flags that cannot be both be set (maybe obviously) at the same time:

  • enable_heartbeats and disable_heartbeats
  • enable_pmtu_discovery and disable_pmtu_discovery
  • enable_sack and disable_sack

sctp_pdapi_event() โ€” a receiver is engaged in a partial delivery. it's giving "loadingโ€ฆ 47%" ๐Ÿ˜ญ

sctp_pdapi_event() ::
          #{type := partial_delivery_event,
            flags := uint16(),
            indication := uint16(),
            assoc_id := sctp_assoc_id(),
            stream => uint32(),
            seq => uint32()}.

C: struct sctp_pdapi_event

heads up bestie: Note that not all fields are available on all platforms. The stream and/or seq fields may not be present.

sctp_peer_addr_change_state() ๐Ÿงš

sctp_peer_addr_change_state() ::
          addr_available | addr_unreachable | addr_removed |
          addr_added | addr_made_prim | addr_confirmed |
          addr_potentially_failed |
          integer().

sctp_peer_address_info() ๐Ÿ“

sctp_peer_address_info() ::
          #{assoc_id := sctp_assoc_id(),
            address := sockaddr(),
            state := sctp_peer_address_state(),
            cwnd := uint32(),
            srtt := uint32(),
            rto := uint32(),
            mtu := uint32()}.

C: struct sctp_paddrinfo.


sctp_peer_address_parameters() ๐ŸŽš๏ธ

sctp_peer_address_parameters() ::
          #{assoc_id := sctp_assoc_id(),
            addr := sockaddr(),
            heatbeat_interval := uint32(),
            path_max_rxt := uint16(),
            path_mtu => uint32(),
            sack_delay => uint32(),
            flags => sctp_pap_flags(),
            ipv6_flowlabel => uint32(),
            dscp => uint8()}.

C: struct sctp_paddrparams.

(and yes, heatbeat_interval โ€” that spelling is not a typo we invented, that's literally the field, do not "fix" it ๐Ÿ’€)


sctp_peer_address_state() ๐Ÿฆ‹

sctp_peer_address_state() ::
          inactive | potentially_failed | active | unconfirmed |
unknown.

C: enum sctp_spinfo_state.


sctp_remote_error() โ€” a remote peer may send an operational error message to its peer. ๐Ÿ’Œ but make it a complaint

sctp_remote_error() ::
          #{type := remote_error,
            flags := uint16(),
            error := sctp_operation_error(),
            assoc_id := sctp_assoc_id(),
            remote_causes := [integer()]}.

C: struct sctp_remote_error


sctp_rtoinfo() โฑ๏ธ

sctp_rtoinfo() ::
          #{assoc_id := sctp_assoc_id(),
            initial := uint32(),
            max := uint32(),
            min := uint32()}.

C: struct sctp_rtoinfo.


sctp_send_failed() โ€” SCTP cannot deliver a message. the tragedy ๐Ÿ˜ญ

sctp_send_failed() ::
          #{type := send_failed,
            flags := uint16(),
            error := uint32(),
            info := sctp_snd_rcv_info(),
            assoc_id := sctp_assoc_id(),
            data := binary()}.

C: struct sctp_send_failed

Deprecated. ๐Ÿ’€ it's over bestie

sctp_send_failed_event() โ€” SCTP cannot deliver a message (the glow-up version) โœจ

sctp_send_failed_event() ::
          #{type := send_failed_event,
            flags := [data_unsent | data_sent],
            error := uint32(),
            info := sctp_snd_info(),
            assoc_id := sctp_assoc_id(),
            data := binary()}.

C: struct sctp_send_failed_event

sctp_sender_dry() ๐Ÿซ™

sctp_sender_dry() ::
      #{type := sender_dry,
        flags := uint16(),
        assoc_id := sctp_assoc_id()}.

Bestie, this is the SCTP stack going "the queue is EMPTY, I have no more user data to send or retransmit, we're so done". Nothing left. Periodt.

C: struct sctp_sender_dry_event

sctp_set_adaptation_layer_ind() ๐ŸŽš๏ธ

sctp_set_adaptation_layer_ind() :: #{ind := uint32()}.

C: struct sctp_setadaptation.

sctp_set_peer_primary_address() ๐Ÿ‘‘

sctp_set_peer_primary_address() ::
      #{assoc_id := sctp_assoc_id(), addr := sockaddr()}.

C: struct sctp_setpeerprim.

sctp_set_primary_address() ๐ŸŒŸ

sctp_set_primary_address() ::
      #{assoc_id := sctp_assoc_id(), addr := sockaddr()}.

C: struct sctp_prim.

sctp_setadaption() ๐ŸŽ›๏ธ

sctp_setadaption() :: #{adaption_ind := uint32()}.

C: struct sctp_setadaption

sctp_shutdown_event() ๐Ÿ’ค

sctp_shutdown_event() ::
      #{type := shutdown_event,
        flags := uint16(),
        assoc_id := sctp_assoc_id()}.

Some peer out there sent a SHUTDOWN. They said bye fr.

C: struct sctp_shutdown_event

sctp_snd_info() ๐Ÿ“จ

sctp_snd_info() ::
      #{sid := uint16(),
        flags := uint16(),
        ppid := uint16(),
        context := uint32(),
        assic_id := sctp_assoc_id()}.

C: struct sctp_sndinfo

Heads up: yes, that key is spelled assic_id in here and that's exactly how it's written โ€” not me being sloppy, ok? ๐Ÿ’…

sctp_snd_rcv_info() ๐Ÿ”

sctp_snd_rcv_info() ::
      #{stream := uint16(),
        ssn => uint16(),
        flags => sctp_snd_rcv_info_flags(),
        ppid => uint32(),
        context => uint32(),
        time_to_live => uint32(),
        tsn => uint32(),
        cum_tsn => uint32(),
        assoc_id := sctp_assoc_id()}.

This one is giving dual-threat: it's the SCTP options for sendmsg(), and the SCTP header information about a received message that comes through recvmsg().

When you're sending, the only fields you actually need to assign are stream and assoc_id. When you're receiving, every value gets assigned for you. Slay, no notes.

C: struct sctp_sndrcvinfo

sctp_snd_rcv_info_flags() ๐Ÿšฉ

sctp_snd_rcv_info_flags() :: [unordered | addr_over | abort | eof].

sctp_status() ๐Ÿ“Š

sctp_status() ::
      #{assoc_id := sctp_assoc_id(),
        state := sctp_peer_address_state(),
        rwnd := uint32(),
        unacked_data := uint16(),
        pending_data := uint16(),
        in_streams := uint16(),
        out_streams := uint16(),
        fragmentation_point := uint32(),
        primary := sctp_peer_address_info()}.

C: struct sctp_status.

sctp_stream_change_event() ๐Ÿฆ‹

sctp_stream_change_event() ::
      #{type := stream_change,
        flags := [denied | failed],
        assoc_id := sctp_assoc_id(),
        inbound_streams := uint16(),
        outbound_streams := uint16()}.

C: struct sctp_stream_change_event

sctp_stream_reset_event() ๐Ÿ”„

sctp_stream_reset_event() ::
      #{type := stream_reset,
        flags := [incoming_ssn | outgoing_ssn | denied | failed],
        assoc_id := sctp_assoc_id(),
        stream_list := [uint16()]}.

C: struct sctp_stream_reset_event

select_handle() ๐ŸŽŸ๏ธ

select_handle() :: reference().

Select operation handle.

It's a reference/0 that uniquely identifies the (select) operation, and it lives inside the returned select_info/0.

select_info() ๐Ÿงพ

select_info() ::
      {select_info,
       SelectTag :: select_tag(),
       SelectHandle :: select_handle()}.

Select operation info.

This gets returned by an operation that needs the caller to wait for a select message containing the SelectHandle.

And on select systems, if the option {otp, select_read} is set, what you get back is {select_read, {select_info(), _}} instead of {ok, _} โ€” which is the signal that a new asynchronous receive operation has been initiated and the caller should wait for a select message containing the SelectHandle. Ik it's a mouthful but that's the whole bit ๐Ÿ’–

select_tag() ๐Ÿท๏ธ

select_tag() ::
      accept | connect |
      recv | recvfrom | recvmsg | recvmmsg | send | sendv | sendto |
      sendmsg | sendmmsg | sendfile |
      {recv | recvfrom | recvmsg | recvmmsg | send | sendv |
       sendto | sendmsg | sendmmsg | sendfile,
       ContData :: term()}.

Select operation tag.

It's the tag that describes the (select) operation โ€” basically the function name โ€” and it's contained in the returned select_info/0.

sockaddr() ๐Ÿ 

sockaddr() ::
      sockaddr_in() |
      sockaddr_in6() |
      sockaddr_un() |
      sockaddr_ll() |
      sockaddr_dl() |
      sockaddr_unspec() |
      sockaddr_native().

sockaddr_dl() ๐Ÿ”—

sockaddr_dl() ::
      #{family := link,
        index := non_neg_integer(),
        type := interface_type(),
        nlen := non_neg_integer(),
        alen := non_neg_integer(),
        slen := non_neg_integer(),
        data := binary()}.

C: struct sockaddr_dl

Link level address (PF_LINK) on BSD:s. Very old-school network interface energy.

sockaddr_in() 4๏ธโƒฃ

sockaddr_in() ::
      #{family := inet,
        port := port_number(),
        addr := any | broadcast | loopback | in_addr()}.

C: struct sockaddr_in

Domain inet (IPv4) address. The classic ๐Ÿ’…

sockaddr_in6() 6๏ธโƒฃ

sockaddr_in6() ::
      #{family := inet6,
        port := port_number(),
        addr := any | loopback | in6_addr(),
        flowinfo := in6_flow_info(),
        scope_id := in6_scope_id()}.

C: struct sockaddr_in6

Domain inet6 (IPv6) address.

sockaddr_ll() ๐Ÿ“ฆ

sockaddr_ll() ::
      #{family := packet,
        protocol := non_neg_integer(),
        ifindex := integer(),
        pkttype := packet_type(),
        hatype := hatype(),
        addr := binary()}.

C: struct sockaddr_ll

Domain packet, type raw (link level) address.

sockaddr_native() ๐Ÿช†

sockaddr_native() :: #{family := integer(), addr := binary()}.

C: struct sockaddr

In C this is a struct sockaddr where the integer value of sa_family sits in the map/0 key family, and the content of sa_data sits in the map/0 key addr.

sockaddr_recv() ๐Ÿ“ฅ

sockaddr_recv() :: sockaddr() | binary().

sockaddr_un() ๐Ÿ”Œ

sockaddr_un() :: #{family := local, path := binary() | string()}.

C: struct sockaddr_un.

A Unix Domain socket address, a.k.a. local address (AF_LOCAL).

The path element will always be a binary when returned from this module. But when you supply it to an API function in this module it may be a string/0, and that will get encoded into a binary according to the native file name encoding on the platform.

A terminating zero character is appended before the address path is given to the OS, and that terminating zero gets stripped back off before the address path is handed to the caller. Symmetry, we love her ๐ŸŒธ

Linux's non-portable abstract socket address extension is handled by not doing any terminating zero processing in either direction, if the first byte of the address is zero.

sockaddr_unspec() โ“

sockaddr_unspec() :: #{family := unspec, addr := binary()}.

C: struct sockaddr of AF_UNSPEC

In C, a struct sockaddr with sa_family = AF_UNSPEC and the content of sa_data in the map/0 key addr.

socket() ๐Ÿ•ณ๏ธ

socket() :: {'$socket', socket_handle()}.

A socket, according to this module.

Created and returned by open/1,2,3,4 and accept/1,2.

socket_counters() ๐Ÿ”ข

socket_counters() ::
      #{read_byte := non_neg_integer(),
        read_fails := non_neg_integer(),
        read_pkg := non_neg_integer(),
        read_pkg_max := non_neg_integer(),
        read_tries := non_neg_integer(),
        read_waits := non_neg_integer(),
        write_byte := non_neg_integer(),
        write_fails := non_neg_integer(),
        write_pkg := non_neg_integer(),
        write_pkg_max := non_neg_integer(),
        write_tries := non_neg_integer(),
        write_waits := non_neg_integer(),
        sendfile => non_neg_integer(),
        sendfile_byte => non_neg_integer(),
        sendfile_fails => non_neg_integer(),
        sendfile_max => non_neg_integer(),
        sendfile_pkg => non_neg_integer(),
        sendfile_pkg_max => non_neg_integer(),
        sendfile_tries => non_neg_integer(),
        sendfile_waits => non_neg_integer(),
        acc_success := non_neg_integer(),
        acc_fails := non_neg_integer(),
        acc_tries := non_neg_integer(),
        acc_waits := non_neg_integer()}.

A map/0 of Name := Counter associations. All the receipts, literally.

-opaque socket_handle() ๐Ÿ”’

Opaque socket handle unique for the socket. Don't peek under the hood, bestie โ€” it's opaque for a reason ๐Ÿ™ˆ

socket_info() ๐Ÿ“‹

socket_info() ::
      #{domain := domain() | integer(),
        type := type() | integer(),
        protocol := protocol() | integer(),
        owner := pid(),
        ctype := normal | fromfd | {fromfd, integer()},
        counters := socket_counters(),
        num_readers := non_neg_integer(),
        num_writers := non_neg_integer(),
        num_acceptors := non_neg_integer(),
        writable := boolean(),
        readable := boolean(),
        rstates := [atom()],
        wstates := [atom()]}.
socket_option() ::
          {Level :: socket,
           Opt ::
               acceptconn | acceptfilter | bindtodevice | broadcast |
               bsp_state | busy_poll | debug | domain | dontroute |
               error | exclusiveaddruse | keepalive | linger | mark |
               maxdg | max_msg_size | oobinline | passcred | peek_off |
               peercred | priority | protocol | rcvbuf | rcvbufforce |
               rcvlowat | rcvtimeo | reuseaddr | reuseport | rxq_ovfl |
               setfib | sndbuf | sndbufforce | sndlowat | sndtimeo |
               timestamp | timestampns | type} |
          {Level :: ip,
           Opt ::
               add_membership | add_source_membership | block_source |
               dontfrag | drop_membership | drop_source_membership |
               freebind | hdrincl | minttl | msfilter | mtu |
               mtu_discover | multicast_all | multicast_if |
               multicast_loop | multicast_ttl | nodefrag | options |
               pktinfo | recvdstaddr | recverr | recvif | recvopts |
               recvorigdstaddr | recvtos | recvttl | retopts |
               router_alert | sndsrcaddr | tos | transparent | ttl |
               unblock_source} |
          {Level :: ipv6,
           Opt ::
               addrform | add_membership | authhdr | auth_level |
               checksum | drop_membership | dstopts | esp_trans_level |
               esp_network_level | faith | flowinfo | hopopts |
               ipcomp_level | join_group | leave_group | mtu |
               mtu_discover | multicast_hops | multicast_if |
               multicast_loop | portrange | pktoptions | recverr |
               recvhoplimit | hoplimit | recvpktinfo | pktinfo |
               recvtclass | router_alert | rthdr | tclass |
               unicast_hops | use_min_mtu | v6only} |
          {Level :: tcp,
           Opt ::
               congestion | cork | info | keepcnt | keepidle |
               keepintvl | maxseg | md5sig | nodelay | noopt | nopush |
               syncnt | user_timeout} |
          {Level :: udp, Opt :: cork} |
          {Level :: sctp,
           Opt ::
               adaption_layer | associnfo | auth_active_key |
               auth_asconf | auth_chunk | auth_key | auth_delete_key |
               autoclose | context | default_send_param |
               delayed_ack_time | disable_fragments | hmac_ident |
               events | explicit_eor | fragment_interleave |
               get_peer_addr_info | initmsg | i_want_mapped_v4_addr |
               local_auth_chunks | maxseg | maxburst | nodelay |
               partial_delivery_point | peer_addr_params |
               peer_auth_chunks | primary_addr | reset_streams |
               rtoinfo | set_peer_primary_addr | status |
               use_ext_recvinfo}.

Socket option. ๐Ÿ’– It's giving "one atom pair to rule them all".

Socket options of the form {Level, Opt} where the OS protocol Level = level/0 and Opt is a socket option on that protocol level. ๐ŸŽ€

The OS name for an options is, except where otherwise noted, the Opt atom, in capitals, with prefix according to level/0. iykyk โœจ

๐Ÿšจ NOTE: The IPv6 option pktoptions is a special (<span>barf</span>) case. It is intended for backward compatibility usage only. Do not use this option. Like, bestie, I am begging you.
๐Ÿ“– NOTE: See the OS documentation for every socket option. We are not the OS docs, we are your hype squad. ๐Ÿซถ

An option below that has the value type boolean/0 will translate the value false to a C int with value 0, and the value true to !!0 (not (not false)). No cap. ๐Ÿ”ฅ

An option with value type integer/0 will be translated to a C int that may have a restricted range, for example byte: 0..255. See the OS documentation. ๐ŸŒธ

The calls supports(options), supports(options, Level) and is_supported(options, {Level, Opt}) can be used to find out which socket options that are supported by the platform. Literally your vibe check for options. ๐Ÿ‘‘


โœจ Options for protocol level socket โœจ

  • {socket, acceptconn} โ€” Value = boolean()
  • {socket, bindtodevice} โ€” Value = string()
  • {socket, broadcast} โ€” Value = boolean()
  • {socket, debug} โ€” Value = integer()
  • {socket, domain} โ€” Value = domain/0

Only valid to get.

The socket's protocol domain. Does not work on for instance <span>FreeBSD</span>. ๐Ÿ˜ญ

  • {socket, dontroute} โ€” Value = boolean()
  • {socket, keepalive} โ€” Value = boolean()
  • {socket, linger} โ€” Value = abort | linger/0

The value abort is shorthand for #{onoff => true, linger => 0}, and only valid to set. It's giving "we're leaving immediately". ๐Ÿ’…

  • {socket, oobinline} โ€” Value = boolean()
  • {socket, passcred} โ€” Value = boolean()
  • {socket, peek_off} โ€” Value = integer()

Currently disabled due to a possible infinite loop when calling recv/1-4 with peek in Flags. Bestie. Do not. ๐Ÿ’€

  • {socket, priority} โ€” Value = integer()
  • {socket, protocol} โ€” Value = protocol/0

Only valid to get.

The socket's protocol. Does not work on for instance <span>Darwin</span>. ๐Ÿ™ˆ

  • {socket, rcvbuf} โ€” Value = integer()
  • {socket, rcvlowat} โ€” Value = integer()
  • {socket, rcvtimeo} โ€” Value = timeval/0

This option is unsupported per default; OTP has to be explicitly built with the --enable-esock-rcvsndtimeo configure option for this to be available. ๐Ÿ”ง

Since our implementation uses non-blocking sockets, it is <span>unknown</span> if and how this option works, or even if it may cause malfunction. Therefore, we do not recommend setting this option. ๐Ÿ˜ฌ

Instead, use the Timeout argument to, for instance, the recv/3 function. Periodt. โœจ

  • {socket, reuseaddr} โ€” Value = boolean()
  • {socket, reuseport} โ€” Value = boolean()
  • {socket, sndbuf} โ€” Value = integer()
  • {socket, sndlowat} โ€” Value = integer()
  • {socket, sndtimeo} โ€” Value = timeval/0

This option is unsupported per default; OTP has to be explicitly built with the --enable-esock-rcvsndtimeo configure option for this to be available. ๐Ÿ”ง

Since our implementation uses non-blocking sockets, it is <span>unknown</span> if and how this option works, or even if it may cause malfunction. Therefore, we do not recommend setting this option. ๐Ÿ˜ฌ

Instead, use the Timeout argument to, for instance, the send/3 function. Periodt. โœจ

  • {socket, timestamp} โ€” Value = boolean()

Enable or disable the SO_TIMESTAMP socket option. When enabled, the socket will receive timestamps in control messages for received packets. โฐ

  • {socket, timestampns} โ€” Value = boolean()

Enable or disable the SO_TIMESTAMPNS socket option. When enabled, the socket will receive nanosecond-precision timestamps in control messages for received packets. Nanoseconds, bestie. We're fancy now. ๐Ÿฆ‹

  • {socket, type} โ€” Value = type/0

Only valid to get.

The socket's type.


๐ŸŒˆ Options for protocol level ip ๐ŸŒˆ

  • {ip, add_membership} โ€” Value = ip_mreq/0

Only valid to set.

  • {ip, add_source_membership} โ€” Value = ip_mreq_source/0

Only valid to set.

  • {ip, block_source} โ€” Value = ip_mreq_source/0

Only valid to set.

  • {ip, drop_membership} โ€” Value = ip_mreq/0

Only valid to set.

  • {ip, drop_source_membership} โ€” Value = ip_mreq_source/0

Only valid to set.

  • {ip, freebind} โ€” Value = boolean()
  • {ip, hdrincl} โ€” Value = boolean()
  • {ip, minttl} โ€” Value = integer()
  • {ip, msfilter} โ€” Value = null | ip_msfilter/0

Only valid to set.

The value null passes a NULL pointer and size 0 to the C library call. ๐Ÿ’…

  • {ip, mtu} โ€” Value = integer()

Only valid to get.

  • {ip, mtu_discover} โ€” Value = ip_pmtudisc() | integer()

An integer/0 value according to the platform's header files.

  • {ip, multicast_all} โ€” Value = boolean()
  • {ip, multicast_if} โ€” Value = any | in_addr/0
  • {ip, multicast_loop} โ€” Value = boolean()
  • {ip, multicast_ttl} โ€” Value = integer()
  • {ip, nodefrag} โ€” Value = boolean()
  • {ip, pktinfo} โ€” Value = boolean()
  • {ip, recvdstaddr} โ€” Value = boolean()
  • {ip, recverr} โ€” Value = boolean()

Enable extended reliable error message passing. ๐Ÿ“จ

โš ๏ธ Warning! When this option is enabled, error messages may arrive on the socket's error queue, which should be read using the message flag errqueue, and using recvmsg/1,2,3,4,5 to get all error information in the message's ctrl field as a control message #{level := ip, type := recverr}. ๐Ÿ˜ณ

A working strategy should be to first poll the error queue using recvmsg/2,3,4 with Timeout =:= 0 and Flags containing errqueue (ignore the return value {error, timeout}) before reading the actual data to ensure that the error queue gets cleared. And read the data using one of the nowait | select_handle() recv functions: recv/3,4, recvfrom/3,4 or recvmsg/3,4,5. Otherwise you might accidentally cause a busy loop in and out of select for the socket. ๐Ÿ’€ Trust. Write this down.

  • {ip, recvif} โ€” Value = boolean()
  • {ip, recvopts} โ€” Value = boolean()
  • {ip, recvorigdstaddr} โ€” Value = boolean()
  • {ip, recvtos} โ€” Value = boolean()
  • {ip, recvttl} โ€” Value = boolean()
  • {ip, retopts} โ€” Value = boolean()
  • {ip, router_alert} โ€” Value = integer()
  • {ip, sendsrcaddr} โ€” Value = boolean()
  • {ip, tos} โ€” Value = iptos_value() | ip_tos()

When sending/setting the value is according to iptos_value/0. When reading/getting the value is according to ip_tos/0. Two moods, one option. ๐ŸŒ—

  • {ip, transparent} โ€” Value = boolean()
  • {ip, ttl} โ€” Value = integer()
  • {ip, unblock_source} โ€” Value = ip_mreq_source/0

Only valid to set.


Options for protocol level ipv6: โœจ

  • {ipv6, addrform} โ€” Value = domain/0 As far as we know the only valid value is inet and it is only allowed for an IPv6 socket that is connected and bound to an IPv4-mapped IPv6 address.
  • {ipv6, add_membership} โ€” Value = ipv6_mreq/0 Only valid to set. ๐Ÿซถ
  • {ipv6, authhdr} โ€” Value = boolean()
  • {ipv6, drop_membership} โ€” Value = ipv6_mreq/0 Only valid to set.
  • {ipv6, dstopts} โ€” Value = boolean()
  • {ipv6, flowinfo} โ€” Value = boolean()
  • {ipv6, hoplimit} โ€” Value = boolean()
  • {ipv6, hopopts} โ€” Value = boolean()
  • {ipv6, mtu} โ€” Value = integer()
  • {ipv6, mtu_discover} โ€” Value = ipv6_pmtudisc() | integer() An integer/0 value is according to the platform's header files.
  • {ipv6, multicast_hops} โ€” Value = ipv6_hops/0
  • {ipv6, multicast_if} โ€” Value = integer()
  • {ipv6, multicast_loop} โ€” Value = boolean()
  • {ipv6, recverr} โ€” Value = boolean()
โš ๏ธ Warning! See the socket option {ip, recverr} regarding the socket's error queue. The same warning applies for this option. like, respect the error queue bestie, it's not a vibe.
  • {ipv6, recvhoplimit} โ€” Value = boolean()
  • {ipv6, recvpktinfo} โ€” Value = boolean()
  • {ipv6, recvtclass} โ€” Value = boolean()
  • {ipv6, router_alert} โ€” Value = integer()
  • {ipv6, rthdr} โ€” Value = boolean()
  • {ipv6, tclass} โ€” Value = boolean()
  • {ipv6, unicast_hops} โ€” Value = ipv6_hops/0
  • {ipv6, v6only} โ€” Value = boolean()

Options for protocol level sctp. See also RFC 6458. ๐Ÿ“œ

  • {sctp, associnfo} โ€” Value = sctp_assocparams/0
  • {sctp, autoclose} โ€” Value = integer()
  • {sctp, disable_fragments} โ€” Value = boolean()
  • {sctp, events} โ€” Value = sctp_event_subscribe/0 Only valid to set.
  • {sctp, initmsg} โ€” Value = sctp_initmsg/0
  • {sctp, maxseg} โ€” Value = integer()
  • {sctp, nodelay} โ€” Value = boolean()
  • {sctp, rtoinfo} โ€” Value = sctp_rtoinfo/0
  • {sctp, get_peer_addr_info} โ€” Value = sctp_peer_address_info/0 Only valid for get. Also, requires an OptValue (containing sctp_assoc_id/0 and sockaddr/0) specifying the peer. See getopt/3 for more info.
  • {sctp, status} โ€” Value = sctp_status/0 Only valid for get. Also, requires an OptValue (containing sctp_assoc_id/0) specifying the association. See getopt/3 for more info.

Options for protocol level tcp: ๐Ÿงฆ

  • {tcp, congestion} โ€” Value = string()
  • {tcp, cork} โ€” Value = boolean()
  • {tcp, keepcnt} โ€” Value = integer()
  • {tcp, keepidle} โ€” Value = integer()
  • {tcp, keepintvl} โ€” Value = integer()
  • {tcp, maxseg} โ€” Value = integer()
  • {tcp, nodelay} โ€” Value = boolean()
  • {tcp, nopush} โ€” Value = boolean()
  • {tcp, user_timeout} โ€” Value = non_neg_integer()

Options for protocol level udp:

  • {udp, cork} โ€” Value = boolean()

  • timespec() โ€” #{sec := integer(), nsec := integer()}. C: struct timespec Corresponds to the C struct timespec. The field sec holds seconds, and nsec nanoseconds. โฑ๏ธ
  • timeval() โ€” #{sec := integer(), usec := integer()}. C: struct timeval Corresponds to the C struct timeval. The field sec holds seconds, and usec microseconds.
  • type() โ€” stream | dgram | raw | rdm | seqpacket. Protocol type. A lowercase atom/0 representing a protocol type on the platform named SOCK_*. For example stream corresponds to SOCK_STREAM.
  • uint16() โ€” 0..65535.
  • uint32() โ€” 0..4294967295.
  • uint64() โ€” 0..18446744073709551615.
  • uint8() โ€” 0..255.

๐Ÿซถ FUNCTIONS ๐Ÿซถ

accept

accept(ListenSocket) -> Result
    when
        Result :: {ok, Socket} | {error, Reason},
        ListenSocket :: socket(),
        Socket :: socket(),
        Reason :: dynamic().

Equivalent to accept(ListenSocket, infinity). literally the same thing bestie.

accept(ListenSocket, Timeout :: infinity) ->
        {ok, Socket} | {error, Reason}
        when
            ListenSocket :: socket(),
            Socket :: socket(),
            Reason ::
                posix() |
                closed |
                invalid() |
                {create_accept_socket, posix()} |
                {add_socket, posix()} |
                {update_accept_context, posix()};
    (ListenSocket, Timeout :: non_neg_integer()) ->
        {ok, Socket} | {error, Reason}
        when
            ListenSocket :: socket(),
            Socket :: socket(),
            Reason ::
                posix() |
                closed |
                invalid() |
                timeout |
                {create_accept_socket, posix()} |
                {add_socket, posix()} |
                {update_accept_context, posix()};
    (ListenSocket,
     nowait | (Handle :: select_handle() | completion_handle()))
    ->
        {ok, Socket} |
        {select, SelectInfo} |
        {completion, CompletionInfo} |
        {error, Reason}
        when
            ListenSocket :: socket(),
            Socket :: socket(),
            SelectInfo :: select_info(),
            CompletionInfo :: completion_info(),
            Reason ::
                posix() |
                closed |
                invalid() |
                {create_accept_socket, posix()} |
                {add_accept_socket, posix()} |
                {update_accept_context, posix()}.

Accept a connection on a listening socket. the moment we've all been waiting for ๐Ÿ’…

ListenSocket has to be of a connection oriented type (types stream or seqpacket, see open/1), and set to listen (see listen/1).

If the Timeout argument is infinity; accepts the first pending incoming connection for the listen socket or wait for one to arrive, and return the new connection socket.

If the Timeout argument is a time-out value (non_neg_integer/0); returns {error, timeout} if no connection has arrived after Timeout milliseconds.

If the Handle argument is nowait (since OTP 22.1), starts an asynchronous call if the operation couldn't be completed immediately.

If the Handle argument is a select_handle/0, (since OTP 24.0), or on Windows, the equivalent completion_handle/0 (since OTP 26.0), starts an asynchronous call like for nowait.

The possible values for CompletionStatus in the completion message are:

  • {ok, NewSocket} โ€” Success; A connection has been accepted. ๐ŸŽ‰
  • {error, Reason} โ€” An error occured and no connection was established. ๐Ÿ’€

See the note Asynchronous Calls at the start of this module reference manual page.


bind

bind(Socket, Addr) -> ok | {error, Reason}
    when
        Socket :: socket(),
        Addr :: sockaddr() | any | broadcast | loopback,
        Reason :: posix() | closed | invalid().

Bind a name to a socket. name drop but for sockets ๐ŸŽ€

When a socket is created (with open), it has no address assigned to it. bind assigns the address specified by the Addr argument.

The rules used for name binding vary between domains.

If you bind a socket to an address in for example the inet or inet6 address families, with an ephemeral port number (0), and want to know which port that was chosen, you can find out using something like:

{ok, #{port := Port}} =socket:sockname(Socket)
bind(Socket, Addrs, Action) -> ok | {error, Reason}
    when
        Socket :: socket(),
        Addrs ::
            [sockaddr_in()] | [sockaddr_in() |
            sockaddr_in6()],
        Action :: add | remove,
        Reason :: posix() | closed.

Bind a list of socket addreses to a socket.

When a socket is created (with open), it has no address assigned to it. This bind assigns the address specified by the Addr argument.

Calling this function is only valid if the socket is type = seqpacket and protocol = sctp.

If the domain is inet, then all addresses must be IPv4. If the domain is inet6, the addresses can be either IPv4 or IPv6.


cancel

cancel(Socket, SelectInfo | CompletionInfo) ->
        ok | {error, Reason}
        when
            Socket :: socket(),
            SelectInfo :: select_info(),
            CompletionInfo :: completion_info(),
            Reason :: closed | invalid().

Cancel an asynchronous call in progress. nvm bestie, we're over. ๐Ÿ™ˆ

Call this function to cancel an asynchronous call in progress, that is; it returned a value containing a completion_info/0 or select_info/0.

See the note Asynchronous Calls at the start of this module reference manual page.

If another process tries an operation of the same basic type (accept/1 | send/2 | recv/2) it will be enqueued and notified through a select or completion message when the current operation and all enqueued before it has been completed. If the current operation is canceled by this function it is treated as a completed operation; the process first in queue is notified.

If SelectInfo | CompletionInfo does not match an operation in progress for the calling process, this function returns {error, {invalid, SelectInfo | CompletionInfo}}.


cancel_monitor

cancel_monitor(MRef :: reference()) -> boolean().

Cancel a socket monitor. ๐Ÿฆ‹

๐Ÿ’… demonitor โ€” the MRef cleanup bit

If MRef is a reference that the calling process got from calling monitor/1, that monitor gets yeeted (removed). And if there's, like, no such monitor for the calling process (or MRef isn't even a monitor reference at all), then honestly nothing happens. No drama. No error. It's giving "we move on."

The returned value is one of these two besties:

  • true โ€” the monitor was found and removed. And in this case, no 'DOWN' message for this monitor has been delivered, and it will not be delivered. It's gone-gone. โœจ
  • false โ€” the monitor was not found, so it couldn't be removed. This might be because the monitor already triggered and there's a 'DOWN' message from it just chilling in the caller's message queue already. ๐Ÿ™ˆ

๐Ÿ”’ close/1 โ€” closing a socket

close(Socket) -> ok | {error, Reason}
               when
                   Socket :: socket(),
                   Reason :: posix() | closed | timeout.

Close a socket. That's the whole vibe. ๐Ÿซถ

NOTE: bestie, listen. For Protocol = tcp (peep open/3), even though TCP guarantees that when the other side sees the stream close, all the data we sent before closing has been delivered โ€” there is no way for us to know that the other side actually got all the data and the stream close. All kinds of network and OS issues may simply obliterate that. To get such a guarantee we need to implement an in-band acknowledge protocol on the connection, or we can use the shutdown function to signal that no more data will be sent and then wait for the other end to close the socket. Then we will see our read side getting a socket close. In this way we implement a small acknowledge protocol using shutdown/2. The other side cannot know that we ever saw the socket close, but in a client/server scenario that is often not relevant. ๐Ÿ˜Œ

๐ŸŒˆ connect/1 โ€” finishing what connect/3 started

connect(Socket :: socket()) -> ok | {error, Reason}
                 when Reason :: posix() | closed | invalid().

Finalize a connect/3 operation. Yes, it's the grand finale of the async connect saga. ๐Ÿ’…

See the note Asynchronous Calls at the start of this module reference manual page. (iykyk)

On select systems this function finalizes a connection setup on a socket, after receiving a select message {'$socket', Socket, select, SelectHandle}, and returns whether the connection setup was successful or not.

Instead of calling this function, for backwards compatibility, it is allowed to call connect/2,3 again, but that incurs more overhead since the connect address and time-out argument are processed in vain. Like, why do all that work for nothing, bestie. ๐Ÿ˜ญ

The call that completes the connect operation, the second call, cannot return a select return value. Periodt.


๐Ÿฆ‹ connect/2 โ€” the chill shorthand

connect(Socket :: socket(),
              SockAddr :: sockaddr() | [SockAddr :: term()]) ->
                 ok | {error, Reason :: dynamic()}.

Equivalent to connect(Socket, SockAddr, infinity) or connect(Socket, SockAddrs, undefined, infinity).

That's it, that's the function. Short queen. ๐Ÿ‘‘


๐Ÿ”ฅ connect/3 โ€” the big one (four clauses, we love a multi-faceted icon)

connect(Socket, SockAddrs, TimeoutOrHandle) ->
                 {ok, AssocId} | {error, Reason}
                 when
                     Socket :: socket(),
                     SockAddrs :: [sockaddr_in()] | [sockaddr_in6()],
                     TimeoutOrHandle ::
                         infinity | Timeout | nowait | Handle,
                     Timeout :: non_neg_integer(),
                     Handle :: select_handle(),
                     AssocId :: sctp_assoc_id(),
                     Reason :: posix() | closed | invalid() | already;
             (Socket, SockAddr, Timeout :: infinity) ->
                 ok | {error, Reason}
                 when
                     Socket :: socket(),
                     SockAddr :: sockaddr(),
                     Reason ::
                         posix() |
                         closed |
                         invalid() |
                         already | not_bound |
                         {add_socket, posix()} |
                         {update_connect_context, posix()};
             (Socket, SockAddr, Timeout :: non_neg_integer()) ->
                 ok | {error, Reason}
                 when
                     Socket :: socket(),
                     SockAddr :: sockaddr(),
                     Reason ::
                         posix() |
                         closed |
                         invalid() |
                         already | not_bound | timeout |
                         {add_socket, posix()} |
                         {update_connect_context, posix()};
             (Socket, SockAddr, nowait | Handle) ->
                 ok |
                 {select, SelectInfo} |
                 {completion, CompletionInfo} |
                 {error, Reason}
                 when
                     Socket :: socket(),
                     SockAddr :: sockaddr(),
                     Handle :: select_handle() | completion_handle(),
                     SelectInfo :: select_info(),
                     CompletionInfo :: completion_info(),
                     Reason ::
                         posix() |
                         closed |
                         invalid() |
                         already | not_bound |
                         {add_socket, posix()} |
                         {update_connect_context, posix()}.

Connect the socket to the given address(s). ๐ŸŒธ

This function connects the socket to the address(s) specified by the SockAddr|SockAddrs argument.

If a connection attempt is already in progress (by another process), {error, already} is returned. Like, hello, someone's already on it. ๐Ÿ™ˆ

NOTE: On Windows the socket has to be bound.

If the time-out argument (argument 3) is infinity, it is up to the OS implementation to decide when the connection attempt failed and then what to return; probably {error, etimedout}. The OS time-out may be very long. Literally could be forever, ngl.

If the time-out argument (argument 3) is a time-out value (non_neg_integer/0), it returns {error, timeout} if the connection hasn't been established within Timeout milliseconds.

NOTE: And this one is important, so listen. When this call has returned {error, timeout}, the connection state of the socket is uncertain, since the platform's network stack may complete the connection at any time, up to some platform specific time-out. Repeating a connection attempt towards the same address would be okay, but towards a different address could end up with a connection to either address. The safe play is to close the socket and start over. Also note that this applies to cancelling a nowait connect call described below. ๐Ÿ’€

If the time-out argument (argument 2) is nowait (since OTP 22.1), start an asynchronous call if the operation couldn't be completed immediately.

If the time-out argument (argument 2) is a Handle :: select_handle/0, (since OTP 24.0), or on Windows, the equivalent Handle :: completion_handle/0 (since OTP 26.0), start an asynchronous call like for nowait.

See the note Asynchronous Calls at the start of this module reference manual page.

After receiving a select message, call connect/1 to complete the operation. โ˜‘๏ธ

If cancelling the operation with cancel/2, see the note above about connection time-out.

The possible values for CompletionStatus in the completion message are:

  • ok โ€” Complete success; A connection has been established. ๐ŸŽ‰
  • {error, Reason} โ€” An error occured and no connection was established. ๐Ÿ˜ญ
NOTE: Note that calling with a list of socket addresses only works for SCTP sockets (type = seqpacket). And that the family of all addresses in the list is either IPv4 (inet) or IPv6 (inet6).

โœจ getopt/2 โ€” getting those socket option values

getopt(socket(),
             SocketOption :: {Level :: otp, Opt :: otp_socket_option()})
->
                {ok, Value :: term()} | {error, invalid() | closed};
            (socket(), SocketOption :: socket_option()) ->
                {ok, Value :: term()} |
                {error, posix() | invalid() | closed}.

Get the value of a socket option. ๐ŸŽ€

Gets the value of an OS protocol level socket option, or from the otp pseudo protocol level, which is this module's implementation level above the OS protocol levels.

See the type otp_socket_option() for a description of the otp protocol level.

See the type socket_option/0 for which OS protocol level options that this implementation knows about, how they are related to OS option names, and if there are known peculiarities with any of them.

What options that are valid depends on the OS, and on the kind of socket (domain/0, type/0 and protocol/0). See the type socket_option() and the socket options chapter in the User's Guide for more info. ๐Ÿ“š

NOTE: Not all options are valid, nor possible to get, on all platforms. That is, even if this socket implementation support an option, it doesn't mean that the underlying OS does. So like, don't come for us if your platform doesn't vibe with it. ๐Ÿ’…

๐Ÿ’œ getopt/3 โ€” the getopt that needs extra input (a little high maintenance, but iconic)

getopt(Socket :: socket(),
             SocketOption :: {sctp, get_peer_addr_info},
             OptValue ::
                 #{assoc_id := sctp_assoc_id(), addr := sockaddr()}) ->
                {ok, Value :: sctp_peer_address_info()} |
                {error, posix() | invalid() | closed};
            (Socket :: socket(),
             SocketOption :: {sctp, status},
             OptValue :: #{assoc_id := sctp_assoc_id()}) ->
                {ok, Value :: sctp_status()} |
                {error, posix() | invalid() | closed};
            (Socket :: socket(), Level :: level(), Opt :: term()) ->
                {ok, Value :: term()} |
                {error, posix() | invalid() | closed}.

Get a socket option, with a specifier (extra input) OptValue. ๐Ÿซถ

This function is used when the option takes an input 'value' argument. We only support this for a limited set of options: {sctp, get_peer_addr_info} and {sctp, status}. Exclusive little club, tbh. ๐Ÿ‘‘

Some uses of this function is for backwards compatibility reasons.

For instance, getopt(Socket, Level, Opt) is equivalent to getopt(Socket, {Level, Opt}), or as a special case if Opt = {NativeOpt :: integer/0, ValueSpec} equivalent to getopt_native(Socket, {Level, NativeOpt}, ValueSpec).

Use getopt/2 or getopt_native/3 instead to handle the option level and name as a single term, and to make the difference between known options and native options clear. โญ


getopt_native/3 โ€” get a socket option that's giving "native mode" ๐Ÿ’…

getopt_native(socket(),
                    SocketOption ::
                        socket_option() |
                        {Level :: level() | (NativeLevel :: integer()),
                         NativeOpt :: integer()},
                    ValueType :: integer) ->
                       {ok, Value :: integer()} |
                       {error, posix() | invalid() | closed};
               (socket(),
                    SocketOption ::
                        socket_option() |
                        {Level :: level() | (NativeLevel :: integer()),
                         NativeOpt :: integer()},
                    ValueType :: boolean) ->
                       {ok, Value :: boolean()} |
                       {error, posix() | invalid() | closed};
               (socket(),
                    SocketOption ::
                        socket_option() |
                        {Level :: level() | (NativeLevel :: integer()),
                         NativeOpt :: integer()},
                    ValueSize :: non_neg_integer()) ->
                       {ok, Value :: binary()} |
                       {error, posix() | invalid() | closed};
               (socket(),
                    SocketOption ::
                        socket_option() |
                        {Level :: level() | (NativeLevel :: integer()),
                         NativeOpt :: integer()},
                    ValueSpec :: binary()) ->
                       {ok, Value :: binary()} |
                       {error, posix() | invalid() | closed}.

okay so this one is literally for getting a socket option that our implementation might not even know about, or that has a type that doesn't vibe with our implementation โ€” that's the whole point of "native mode" โœจ

You can hand it the socket option as a plain socket_option() tuple, with a Level you actually know (level()) plus an integer NativeOpt, OR as both an integer NativeLevel and an integer NativeOpt.

The way it decodes the option value? You gotta tell it. Either with ValueType, by giving the ValueSize for a binary/0 that'll hold the fetched option value, or by handing over a binary/0 ValueSpec that gets copied into a buffer so the getsockopt() call can write the value in there, and that comes back to you as a brand new binary/0 ๐ŸŽ€

  • If ValueType is integer, a C type (int) gets fetched.
  • If it's boolean, a C type (int) gets fetched and converted into a boolean/0 using the C implementation's own idea of what's true and what's false. not us deciding, the C code deciding, iykyk.

Whether an option is even valid depends on both the platform and what kind of socket it is (domain/0, type/0 and protocol/0). So no, not universal, bestie.

The integer values for NativeLevel and NativeOpt, plus how the Value is encoded โ€” you have to deduce all that from the header files for the running system. yes, the headers. sorry. ๐Ÿ™ˆ


i/0, i/1, i/2 โ€” the socket info tables ๐Ÿซถ

i() -> ok.

i(InfoKeys :: info_keys()) -> ok;
       (Domain :: inet | inet6 | local) -> ok;
       (Proto :: sctp | tcp | udp) -> ok;
       (Type :: dgram | seqpacket | stream) -> ok.

i(Domain :: inet | inet6 | local, InfoKeys) -> ok
           when InfoKeys :: info_keys();
       (Proto :: sctp | tcp | udp, InfoKeys) -> ok
           when InfoKeys :: info_keys();
       (Type :: dgram | seqpacket | stream, InfoKeys) -> ok
           when InfoKeys :: info_keys().

i() โ†’ ok.

Prints info to the erlang shell in table format for every socket. Obsessed, honestly. The info it prints for each socket is whatever the default set of info_keys/0 says (so, all the keys). And the sockets it prints are all sockets created by this socket module's implementation. only ours, no sneaking outside ones in.

**i(InfoKeys :: info_keys()) / i(Domain :: inet | inet6 | local) / i(Proto :: sctp | tcp | udp) / i(Type :: dgram | seqpacket | stream) โ†’ ok.

Also prints info to the erlang shell in table format for all sockets ๐Ÿ’ซ

  • If the argument is a list of info_keys/0, it prints the specified info for all sockets. See i/0.
  • Otherwise it's the same as i/2 with that same first argument and the default information (peep i/0).

i/2:

i(Domain :: inet | inet6 | local, InfoKeys) -> ok
           when InfoKeys :: info_keys();
       (Proto :: sctp | tcp | udp, InfoKeys) -> ok
           when InfoKeys :: info_keys();
       (Type :: dgram | seqpacket | stream, InfoKeys) -> ok
           when InfoKeys :: info_keys().

Prints info to the erlang shell in table format for a selection of sockets. The InfoKeys argument decides which info gets printed for each socket ๐Ÿ‘‘

  • First arg is a Domain โ†’ prints info for all sockets of that specific domain/0.
  • First arg is a Proto โ†’ prints info for all sockets of that specific protocol/0.
  • First arg is a Type โ†’ prints info for all sockets of that specific type/0.

info/0 โ†’ info()

Gets miscellaneous information about this whole socket library. Returns a map where each information item is a key-value pair.

NOTE: In order to ensure data integrity, mutexes are taken when needed. So, don't call this function often. like, it's not a fidget toy, bestie, it's locking stuff. ๐Ÿ’€

info/1 โ†’ socket_info() when Socket :: socket()

info(Socket) -> socket_info() when Socket :: socket().

Gets miscellaneous info about one single socket. Returns a map where each information item is a key-value pair reflecting the "current" state of the socket (think counter values, that kinda thing).

NOTE: In order to ensure data integrity, mutexes are taken when needed. So, don't call this function often. same warning, same energy, the locks are real ๐Ÿ”’

ioctl/2 โ€” the get-request era ๐ŸŒธ

ioctl(Socket, GetRequest :: gifconf) ->
                 {ok, IFConf :: [#{name := string, addr := sockaddr()}]} |
                 {error, Reason}
                 when Socket :: socket(), Reason :: posix() | closed;
             (Socket, GetRequest :: nread | nwrite | nspace) ->
                 {ok, NumBytes :: non_neg_integer()} | {error, Reason}
                 when Socket :: socket(), Reason :: posix() | closed;
             (Socket, GetRequest :: atmark) ->
                 {ok, Available :: boolean()} | {error, Reason}
                 when Socket :: socket(), Reason :: posix() | closed;
             (Socket, GetRequest :: tcp_info) ->
                 {ok, Info :: map()} | {error, Reason}
                 when Socket :: socket(), Reason :: posix() | closed.

Set socket (device) parameters. Well โ€” this one retrieves a specific parameter, chosen by the GetRequest argument ๐Ÿ’…

  • gifconf โ€” Get a list of interface (transport layer) addresses. Result: a list of map/0s, one for each interface, with its name and address. so cute, a whole lil list of interfaces.
  • nread โ€” Get the number of bytes immediately available for reading (since OTP 26.1). Result: the number of bytes, integer/0.
  • nwrite โ€” Get the number of bytes in the send queue (since OTP 26.1). Result: the number of bytes, integer/0.
  • nspace โ€” Get the free space in the send queue (since OTP 26.1). Result: the number of bytes, integer/0.
  • atmark โ€” Test if there is OOB (out-of-bound) data waiting to be read (since OTP 26.1). Result: a boolean/0.
  • tcp_info โ€” Get miscellaneous TCP related information for a connected socket (since OTP 26.1). Result: a map/0 with information items as key-value pairs.
NOTE: Not all requests are supported by all platforms. To see if a ioctl request is supported on the current platform:
Request = nread,
true = socket:is_supported(ioctl_requests, Request),
:

ioctl/3 โ€” get or set, the plot thickens ๐ŸŒˆ

ioctl(Socket, GetRequest, NameOrIndex) ->
                 {ok, Result} | {error, Reason}
                 when
                     Socket :: socket(),
                     GetRequest ::
                         gifname | gifindex | gifaddr | gifdstaddr |
                         gifbrdaddr | gifnetmask | gifhwaddr | genaddr |
                         gifmtu | giftxqlen | gifflags | tcp_info,
                     NameOrIndex :: string() | integer(),
                     Result :: dynamic(),
                     Reason :: posix() | closed;
             (Socket, SetRequest, Value) -> ok | {error, Reason}
                 when
                     Socket :: socket(),
                     SetRequest :: rcvall,
                     Value :: off | on | iplevel,
                     Reason :: posix() | closed;
             (Socket, SetRequest, Value) -> ok | {error, Reason}
                 when
                     Socket :: socket(),
                     SetRequest :: rcvall_igmpmcast | rcvall_mcast,
                     Value :: off | on,
                     Reason :: posix() | closed.

Get or set socket (device) parameters. This function retrieves a specific parameter according to one of the GetRequest arguments below. The third argument is the (lookup) key that identifies the interface โ€” for most requests that's the name of the interface as a string/0. Also, peep the note above, it still applies ๐Ÿซถ

  • gifname โ€” Get the name of the interface with the specified index (integer/0). Result: the name of the interface, string/0.
  • gifindex โ€” Get the index of the interface with the specified name. Result: the interface index, integer/0.
  • gifaddr โ€” Get the address of the interface with the specified name. Result: the address of the interface, sockaddr/0.
  • gifdstaddr โ€” Get the destination address of the point-to-point interface with the specified name. Result: the destination address of the interface, sockaddr/0.
  • gifbrdaddr โ€” Get the broadcast address of the interface with the specified name. Result: broadcast address of the interface, sockaddr/0.
  • gifnetmask โ€” Get the network mask of the interface with the specified name. Result: the network mask of the interface, sockaddr/0.
  • gifhwaddr | genaddr โ€” Get the hardware address for the interface with the specified name. Result: the hardware address of the interface, sockaddr/0 | binary/0. The family field contains the 'ARPHRD' device type (or an integer).
  • gifmtu โ€” Get the MTU (Maximum Transfer Unit) for the interface with the specified name. Result: MTU of the interface, integer/0.
  • giftxqlen โ€” Get the transmit queue length of the interface with the specified name. Result: transmit queue length of the interface, integer/0.
  • gifflags โ€” Get the active flag word of the interface with the specified name. Result: the active flag word of the interface, is a list of ioctl_device_flag/0 | integer( ).

With the following SetRequest argument, this function sets the Value for the request parameter ๐Ÿซถ (since OTP 26.1).

  • rcvall โ€” enables (or disables) a socket so it can receive all IPv4 or IPv6 packets passing through a network interface ๐Ÿ‘€

Your Socket has to be one of:

  • An IPv4 socket โ€” created with the address domain inet, socket type raw and protocol ip.
  • An IPv6 socket โ€” created with the address domain inet6, socket type raw and protocol ipv6.

The socket must also be bound to an (explicit) local IPv4 or IPv6 interface โ€” any isn't allowed, bestie, not even a little.

Setting this IOCTL requires elevated privileges ๐Ÿ‘‘โœจ

With the following SetRequest arguments, this function sets the Value for the request parameter (since OTP 26.1). Yes, plural. We got options ๐Ÿ’…

  • rcvall_igmpmcall โ€” enables (or disables) a socket to receive IGMP multicast IP traffic, without receiving any other IP traffic ๐ŸŒˆ

The socket has to be created with the address domain inet, socket type raw and protocol igmp.

The socket must also be bound to an (explicit) local interface โ€” any isn't allowed.

The receive buffer must be sufficiently large. Like, dont skimp on it ๐Ÿฅค

Setting this IOCTL requires elevated privileges ๐Ÿ‘‘
  • rcvall_mcall โ€” enables (or disables) a socket to receive all multicast IP traffic (as in; all IP packets destined for IP addresses in the range 224.0.0.0 to 239.255.255.255) ๐Ÿ“ก

The socket has to be created with the address domain inet, socket type raw and protocol udp.

The socket must also be bound to an (explicit) local interface (any isn't allowed), And bound to port 0.

The receive buffer must be sufficiently large. Fr fr ๐Ÿซ™

Setting this IOCTL requires elevated privileges ๐Ÿ‘‘

๐ŸŽ€ ioctl/4 โ€” the setter version

ioctl(Socket, SetRequest, Name, Value) -> ok | {error, Reason}
               when
                   Socket :: socket(),
                   SetRequest ::
                       sifflags | sifaddr | sifdstaddr | sifbrdaddr |
                       sifnetmask | sifhwaddr | sifmtu | siftxqlen,
                   Name :: string(),
                   Value :: dynamic(),
                   Reason :: posix() | closed.

Set socket (device) parameters โœจ

This function sets one specific parameter, according to the SetRequest argument. The Name argument is the name of the interface, and the Value argument is the value to set.

These operations require elevated privileges ๐Ÿ‘‘ so like, act accordingly.
  • sifflags โ€” set the the active flag word, #{Flag => boolean()}, of the interface with the specified name. (Yes, "the the". The original said it, I'm not fixing it, it's iconic ๐Ÿ˜ญ)

Each flag you wanna change should be added to the value map/0, with the value true if the Flag should be set and false if the flag should be cleared. Binary behaviour, love that for it ๐Ÿ’…

  • sifaddr โ€” set the address, sockaddr/0, of the interface with the specified name ๐Ÿ 
  • sifdstaddr โ€” set the destination address, sockaddr/0, of a point-to-point interface with the specified name ๐Ÿ“ฎ
  • sifbrdaddr โ€” set the broadcast address, sockaddr/0, of the interface with the specified name ๐Ÿ“ข
  • sifnetmask โ€” set the network mask, sockaddr/0, of the interface with the specified name ๐ŸŽญ
  • sifhwaddr โ€” set the hardware address, sockaddr/0, of the interface with the specified name ๐Ÿ”ง
  • sifmtu โ€” set the MTU (Maximum Transfer Unit), integer/0, for the interface with the specified name ๐Ÿ“
  • siftxqlen โ€” set the transmit queue length, integer/0, of the interface with the specified name ๐Ÿšฆ

๐ŸŒธ is_supported/1

is_supported(Key1 :: term()) -> boolean().

Check if a socket feature is supported ๐Ÿ”

Returns true if supports/0 has a {Key1, true} tuple or a {Key1, list()} tuple in its returned list, otherwise false (also for unknown keys โ€” yeah, unknown means false, no surprises).

Example:

true = socket:is_supported(local),

๐ŸŒธ is_supported/2

is_supported(Key1 :: term(), Key2 :: term()) -> boolean().

Check if a socket feature is supported ๐Ÿ”

Returns true if supports(Key1) has a {Key2, true} tuple in its returned list, otherwise false (also for unknown keys).

Example:

true = socket:is_supported(msg_flags, errqueue),

๐Ÿฆ‹ listen/1

listen(Socket :: socket()) ->
                ok | {error, Reason :: posix() | closed}.

Make a socket listen for connections ๐Ÿ‘‚

Equivalent to listen(Socket, Backlog) with a default value for Backlog (currently 5).


๐Ÿฆ‹ listen/2

listen(Socket, IsServer) -> ok | {error, Reason}
                when
                    Socket :: socket(),
                    IsServer :: boolean(),
                    Reason :: posix() | closed;
            (Socket, Backlog) -> ok | {error, Reason}
                when
                    Socket :: socket(),
                    Backlog :: pos_integer(),
                    Reason :: posix() | closed.

Make a socket listen for connections ๐Ÿ‘‚โœจ

The IsServer clauses are intended to be used for SCTP sockets. The Backlog argument states the length of the queue for incoming not-yet-accepted connections. Exactly how that number is interpreted is up to the OS' protocol stack, but the resulting effective queue length will most probably be perceived as at least that long. So like, it's at least that long. Probably. The OS decides ๐Ÿซ 

NOTE: On Windows the socket has to be bound. Windows stays needing extra steps, no cap ๐ŸชŸ

๐Ÿ’– monitor/1

monitor(Socket :: socket()) -> MonitorRef :: reference().

Start a socket monitor ๐Ÿ•ต๏ธโ™€๏ธ

If the Socket doesn't exist, or when later the monitor is triggered, a 'DOWN' message is sent to the process that called monitor/1 with the following pattern:

{'DOWN', MonitorRef, socket, Socket, Info}

Info is the termination reason of the socket or nosock if Socket did not exist when the monitor was started.

Making several calls to socket:monitor/1 for the same Socket is not an error; each call creates an independent monitor instance. So spam away, each one is its own little guy ๐Ÿ’•


๐Ÿ“ number_of/0

number_of() -> non_neg_integer().

Return the number of active sockets ๐Ÿ”ข


๐Ÿ‘‘ open/1

open(FD :: integer()) -> dynamic().

Equivalent to open(FD, #{}).


๐Ÿ‘‘ open/2

open(FD, Opts) -> {ok, Socket} | {error, Reason}
                     when
                         FD :: integer(),
                         Opts ::
                             #{domain => domain() | integer(),
                               type => type() | integer(),
                               protocol => default | protocol() | integer(),
                               dup => boolean(),
                               debug => boolean(),
                               use_registry => boolean()},
                         Socket :: socket(),
                         Reason :: posix() | domain | type | protocol;
                 (Domain, Type) -> {ok, Socket} | {error, Reason}
                     when
                         Domain :: domain(),
                         Type :: type() | integer(),
                         Socket :: socket(),
                         Reason :: posix() | protocol.

Create a socket โœจ

With arguments Domain and Type

Equivalent to open(Domain, Type, default, #{}).

With arguments FD and Opts (since OTP 23.0)

Creates an endpoint for communication (socket) based on an already existing file descriptor that must be a socket. This function attempts to retrieve the file descriptor's domain, type and protocol from the system. This is however not possible on all platforms; in that case they should be specified in Opts. Rude of the platform honestly ๐Ÿ˜ค

The Opts argument can provide extra information:

  • domain โ€” the file descriptor's communication domain. See also open/2,3,4.
  • type โ€” the file descriptor's socket type. See also open/2,3,4.
  • protocol โ€” the file descriptor's protocol. The atom default is equivalent to the integer protocol number 0, which means the default protocol for a given domain and type.

If the protocol can not be retrieved from the platform for the socket, and protocol is not specified, the default protocol is used, which may or may not be correct. Gamble responsibly ๐ŸŽฒ

See also open/2,3,4.

  • dup โ€” if false, don't duplicate the provided file descriptor.

Defaults to true; do duplicate the file descriptor.

  • debug โ€” if true, enable socket debug logging.

Defaults to false; don't enable socket debug logging.

  • use_registry โ€” enable or disable use of the socket registry for this socket. This overrides the global setting.

Defaults to the global setting, see use_registry/1.

NOTE: This function should be used with care! On some platforms it is necessary to provide domain, type and protocol since they cannot be retrieved from the platform. On some platforms it is not easy to get hold of a file descriptor to use in this function. Care, bestie. Genuinely. ๐Ÿ’€

๐Ÿ‘‘ open/3

open(Domain, Type, Opts | Protocol) ->
                     {ok, Socket} | {error, Reason}
                     when
                         Domain :: domain() | integer(),
                         Type :: type() | integer(),
                         Opts :: map(),
                         Protocol :: default | protocol() | integer(),
                         Socket :: socket(),
                         Reason :: posix() | protocol.

Create a socket โœจ

With arguments Domain, Type and Protocol

Equivalent to open(Domain, Type, Protocol, #{}).

With arguments Domain, Type and Opts (since OTP 24.0)

Equivalent to open(Domain, Type, default, #{}).


๐Ÿ‘‘ open/4

open(Domain, Type, Protocol, Opts) ->
                     {ok, Socket} | {error, Reason}
                     when
                         Domain :: domain() | integer(),
                         Type :: type() | integer(),
                         Protocol :: default | protocol() | integer(),
                         Opts ::
                             #{netns => string(),
                               debug => boolean(),
                               use_registry => boolean()},
                         Socket :: socket(),
                         Reason :: posix() | protocol.

Create a socket โœจ the final form.

Creates an endpoint for communication (socket).

Domain and Type may be integer/0s, as defined in the platform's header files. The same goes for Protocol as defined in the platform's services(5) database. See also the OS man page for the library call socket(2). Iykyk ๐Ÿ“š

NOTE: for some Domain + Type combos, the platform's got a default protocol you can grab with Protocol = default โ€” and depending on the platform it might <b>allow</b> you to pick that default, or a specific protocol, or it might <b>literally require</b> it. Examples, bestie:
socket:open(inet, stream, tcp)

It's super common that for inet + stream you're allowed to name-drop tcp even though that's usually already the default. ๐Ÿ’…

socket:open(local, dgram)

And for domain local it's common that you're mandatory-required to <b>not</b> pick a protocol โ€” like, just select the default, that's the whole assignment. ๐Ÿซถ


The Opts argument is for all the "other" stuff that doesn't fit anywhere else. Here's what's supported, babes:

  • netns : string() โ€” sets the network namespace during the open call. Linux only, no exceptions. ๐Ÿง
  • debug : boolean() โ€” turns debug logging on or off.

Defaults to false.

  • use_registry : boolean() โ€” turns the socket registry on or off for this socket. This overrides the global value, so it's giving main character energy.

Defaults to the global value, check out use_registry/1.


๐Ÿ“ peeloff/2 and peeloff/3

peeloff(Socket, AssocId) ->
                    {ok, NewSock} |
                    {ok, NewSock, InheritErrs} |
                    {error, Reason}
                    when
                        Socket :: socket(),
                        AssocId :: sctp_assoc_id(),
                        NewSock :: socket(),
                        InheritErrs :: [{SockOpt, get | set, Reason}],
                        SockOpt :: socket_option(),
                        Reason :: posix() | closed.

Branch off an association into a separate socket, like a little glow-up ๐Ÿฆ‹

Equivalent to peeloff(Socket, AssocId, []).

peeloff(Socket, AssocId, InheritOpts) ->
                    {ok, NewSock} |
                    {ok, NewSock, InheritErrs} |
                    {error, Reason}
                    when
                        Socket :: socket(),
                        AssocId :: sctp_assoc_id(),
                        InheritOpts :: [socket_option()],
                        NewSock :: socket(),
                        InheritErrs :: [{SockOpt, get | set, Reason}],
                        SockOpt :: socket_option(),
                        Reason :: posix() | closed.

Branch off an association into a separate socket. โœจ

You're creating a brand new one-to-one socket out of an existing one-to-many socket. Those InheritOpts you listed get inherited by the new socket โ€” get from the old one, then set on the new one. Basically passing down the family heirlooms ๐Ÿ’–

If the peeloff itself works but some (or, like, all) of the InheritOpts didn't make it over to the new socket, you get {'ok', NewSock, InheritErrs} back. Then it's <b>on you</b> to decide if that's acceptable. No pressure bestie ๐Ÿ˜ญ


๐ŸŒธ peername/1

peername(Socket :: socket()) -> {ok, SockAddr} | {error, Reason}
                    when
                        SockAddr :: sockaddr_recv(),
                        Reason :: posix() | closed.

Returns the remote address of a socket โ€” as in, the address of the connected peer, the far end of the socket. The pen pal, if you will ๐Ÿ’Œ


๐ŸŒˆ peernames/2

peernames(Socket :: socket(), AssocId :: sctp_assoc_id()) ->
                         {ok, [SockAddr]} | {error, Reason}
                         when
                             SockAddr :: sockaddr_recv(),
                             Reason :: posix() | closed.

Returns the remote (peer) address(es) of an association on a socket. Get you a socket that has multiple peers ๐Ÿ‘‘

If the socket is IPv4, then <b>all</b> the returned addresses will be IPv4. If the socket is IPv6, the returned addresses can be a whole mix of IPv4 and IPv6. Chaos, but make it valid. ๐Ÿฆ‹

For a one-to-many socket, AssocId says which association. For a one-to-one socket, AssocId gets ignored, respectfully. ๐Ÿ™ˆ

And the behaviour if AssocId is 0 (zero) for one-to-many sockets? Undefined. So don't. ๐Ÿ’€


๐ŸŽ€ recv/1 and recv/2

recv(Socket :: socket()) -> dynamic().

Equivalent to recv(Socket, 0, [], infinity).

recv(Socket :: socket(), Flags :: [msg_flag() | integer()]) ->
                dynamic();
            (Socket :: socket(), Length :: non_neg_integer()) ->
dynamic().

Receive data on a connected socket. ๐Ÿ“ฅ

With the Length argument, it's equivalent to recv(Socket, Length, [], infinity).

With the Flags argument, it's equivalent to recv(Socket, 0, Flags, infinity) (since OTP 24.0, so this one's got a little history).


๐Ÿ’œ recv/3

recv(Socket, Flags, TimeoutOrHandle) -> dynamic()
                when
                    Socket :: socket(),
                    Flags :: [msg_flag() | integer()],
                    TimeoutOrHandle ::
                        timeout() |
                        nowait |
                        select_handle() |
                        completion_handle();
            (Socket :: socket(),
             Length :: non_neg_integer(),
             Flags :: [msg_flag() | integer()]) ->
                dynamic();
            (Socket :: socket(),
             Length :: non_neg_integer(),
             TimeoutOrHandle) ->
                dynamic()
                when
                    TimeoutOrHandle ::
                        timeout() |
                        nowait |
                        select_handle() |
                        completion_handle().

Receive data on a connected socket. ๐Ÿ’ซ

With Length and Flags, it's equivalent to recv(Socket, Length, Flags, infinity).

With Length and TimeoutOrHandle, it's equivalent to recv(Socket, Length, [], TimeoutOrHandle). Heads up: TimeoutOrHandle :: nowait has been allowed since OTP 22.1, and TimeoutOrHandle :: Handle has been allowed since OTP 24.0. ๐Ÿ“…

With Flags and TimeoutOrHandle, it's equivalent to recv(Socket, 0, Flags, TimeoutOrHandle) (since OTP 24.0).


๐Ÿ”ฅ recv/4

recv(Socket, Length, Flags, Timeout :: infinity) ->
                {ok, Data} | {error, Reason} | {error, {Reason, Data}}
                when
                    Socket :: socket(),
                    Length :: non_neg_integer(),
                    Flags :: [msg_flag() | integer()],
                    Data :: binary(),
                    Reason :: posix() | closed | invalid();
            (Socket, Length, Flags, Timeout :: non_neg_integer()) ->
                {ok, Data} | {error, Reason} | {error, {Reason, Data}}
                when
                    Socket :: socket(),
                    Length :: non_neg_integer(),
                    Flags :: [msg_flag() | integer()],
                    Data :: binary(),
                    Reason :: posix() | closed | invalid() | timeout;
            (Socket, Length, Flags, nowait | Handle) ->
                {ok, Data} |
                {select, SelectInfo} |
                {select, {SelectInfo, Data}} |
                {select_read, {SelectInfo, Data}} |
                {completion, CompletionInfo} |
                {error, Reason}
                when
                    Socket :: socket(),
                    Length :: non_neg_integer(),
                    Flags :: [msg_flag() | integer()],
                    Handle :: select_handle() | completion_handle(),
                    Data :: binary(),
                    SelectInfo :: select_info(),
                    CompletionInfo :: completion_info(),
                    Reason :: posix() | closed | invalid().

Receive data on a connected socket. This is the big one, so buckle up bestie ๐Ÿซถ

The Length argument specifies the size of the receive buffer. Packet oriented sockets will truncate the packet if the size is too small โ€” no mercy. โœ‚๏ธ

If Length == 0, a default buffer size is used, which you can set with socket:setopt(Socket, {otp,recvbuf}, BufSz).

For a socket of type stream, when a Timeout argument is used, the operation iterates until Length bytes have been received, or it times out. If Length == 0, all readily available data is returned.

On a select system, when the default receive buffer size option {otp,recvbuf} has the special value {N,BufSize}, then N limits how many BufSize buffers may be received in a tight loop before the receive operation returns. And the option value {1,BufSize} is equivalent to just specifying a size value BufSize. So cute and equivalent ๐ŸŽ€

The message Flags may be symbolic msg_flag/0s and/or integer/0s as in the platform's appropriate header files. The values of all symbolic flags and integers are or:ed together โ€” a whole group chat of flags, combined. ๐Ÿ’–

When there's a socket error this function returns {error, Reason}, or if some data arrived before the error, {error, {Reason, Data}} โ€” and ngl that second one can <b>only</b> happen for a socket of type stream.

If the Timeout argument is infinity, it waits for the data to arrive. For a socket of type stream this call won't return until all requested data can be delivered, or if "all available" was requested when the first data chunk arrives, or if the OS reports an error for the operation. It's giving devoted. ๐Ÿ™

If the Timeout argument is a time-out value (non_neg_integer/0), it returns {error, timeout} if no data has arrived after Timeout milliseconds, or {error, {timeout, Data}} if some but not enough data has been received on a socket of type stream with Length > 0. It can also return directly with {ok, Data} (type dgram).

Timeout = 0 only polls the OS receive call and doesn't engage the Asynchronous Calls mechanisms. If no data is immediately available, {error, timeout} is returned. On a socket of type stream, {error, {timeout, Data}} is returned if there's an insufficient amount of data immediately available.

If the Handle argument is nowait (since OTP 22.1), it starts an asynchronous call if the operation couldn't be completed immediately.

If the Handle argument is a select_handle/0 (since OTP 24.0), or on Windows, the equivalent completion_handle/0 (since OTP 26.0), it starts an asynchronous call like for nowait. ๐ŸชŸ

See the note Asynchronous Calls at the start of this module reference manual page โ€” go look, it's important, iykyk.

The possible values for CompletionStatus in the completion message are:

  • {ok, Data} โ€” Complete success; all requested data was read. Ate. ๐Ÿ‘‘
  • {more, Data} โ€” Partial success; some, but not all, data was read.
  • {error, Reason} โ€” An error occured and no data was read. ๐Ÿ˜ญ
lowkey select systems are doing the most, so here's the tea ๐Ÿต
  • On select systems, for a socket of type stream, if Length > 0 and there is some but not enough data available, this function will return {select, {SelectInfo, Data}} with partial Data. A repeated call to complete the operation may need an updated Length argument.
  • On select systems, if the option {otp, select_read} is set, {select_read, {SelectInfo, Data}} is returned instead of {ok, Data} and a new asynchronous receive operation has been initiated, which can be seen as an automatic nowait call whenever a receive operation is completed.

โœจ recvfrom โœจ

๐Ÿ’Œ recvfrom/1

recvfrom(Socket :: socket()) -> dynamic().

fr fr it's just recvfrom(Socket, 0, [], infinity). same vibe, fewer words ๐Ÿ’…

๐Ÿ’Œ recvfrom/2

recvfrom(Socket :: socket(), Flags :: list()) -> dynamic();
             (Socket :: socket(), BufSz :: non_neg_integer()) ->
                 dynamic().

Receive a message on a socket. bestie, this is the one that actually cooks ๐Ÿณ

  • With argument BufSz; equivalent to recvfrom(Socket, BufSz, [], infinity).
  • With argument Flags; equivalent to recvfrom(Socket, 0, Flags, infinity) (since OTP 24.0).

๐Ÿ’Œ recvfrom/3

recvfrom(Socket :: socket(),
              Flags :: [msg_flag() | integer()],
              TimeoutOrHandle :: dynamic()) ->
                 dynamic();
             (Socket :: socket(),
              BufSz :: non_neg_integer(),
              Flags :: [msg_flag() | integer()]) ->
                 dynamic();
             (Socket :: socket(),
              BufSz :: non_neg_integer(),
              TimeoutOrHandle) ->
                 dynamic()
                 when
                     TimeoutOrHandle ::
                         timeout() |
                         nowait |
                         select_handle() |
                         completion_handle().

Receive a message on a socket.

  • With arguments BufSz and Flags; equivalent to recvfrom(Socket, BufSz, Flags, infinity).
  • With arguments BufSz and TimeoutOrHandle; equivalent to recvfrom(Socket, BufSz, [], TimeoutOrHandle).
  • With arguments Flags and TimeoutOrHandle; equivalent to recvfrom(Socket, 0, Flags, TimeoutOrHandle)
  • TimeoutOrHandle :: 'nowait' has been allowed since OTP 22.1.
  • TimeoutOrHandle :: Handle has been allowed since OTP 24.0.

๐Ÿ’Œ recvfrom/4

recvfrom(Socket, BufSz, Flags, Timeout :: infinity) ->
                 {ok, {Source, Data}} | {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Source :: sockaddr_recv(),
                     Data :: binary(),
                     Reason :: posix() | closed | invalid();
             (Socket, BufSz, Flags, Timeout :: non_neg_integer()) ->
                 {ok, {Source, Data}} | {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Source :: sockaddr_recv(),
                     Data :: binary(),
                     Reason :: posix() | closed | invalid() | timeout;
             (Socket, BufSz, Flags, nowait | Handle) ->
                 {ok, {Source, Data}} |
                 {select, SelectInfo} |
                 {select_read, {SelectInfo, {Source, Data}}} |
                 {completion, CompletionInfo} |
                 {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Handle :: select_handle() | completion_handle(),
                     Source :: sockaddr_recv(),
                     Data :: binary(),
                     SelectInfo :: select_info(),
                     CompletionInfo :: completion_info(),
                     Reason :: posix() | closed | invalid().

Receive a message on a socket. ๐Ÿซถ

This function is intended primarily for sockets that are not connection oriented such as type dgram or seqpacket where messages may arrive from different source addresses.

Argument BufSz specifies the number of bytes for the receive buffer. If the buffer size is too small, the message will be truncated. not ideal but okay ๐Ÿ’€

If BufSz is 0, a default buffer size is used, which can be set by socket:setopt(Socket, {otp,recvbuf}, BufSz).

If there is no known appropriate buffer size, it may be possible to use the receive message flag peek. When this flag is used, the message is not "consumed" from the underlying buffers, so another recvfrom/1,2,3,4 call is needed, possibly with an adjusted buffer size. spy on it, don't eat it ๐Ÿ‘€

The message Flags may be symbolic msg_flag()s and/or integer()s, as in the platform's appropriate header files. The values of all symbolic flags and integers are or:ed together.

If the Timeout argument is infinity; waits for a message to arrive, or for a socket error. main character energy, patience is a virtue ๐Ÿง˜

If the Timeout argument is a time-out value (non_neg_integer()); returns {error, timeout} if no message has arrived after Timeout milliseconds.

Timeout = 0 only polls the OS receive call and doesn't engage the Asynchronous Calls mechanisms. If no message is immediately available {error, timeout} is returned. no waiting, no patience, no crumbs ๐Ÿ˜ค

If the Handle argument is nowait (since OTP 22.1), starts an asynchronous call if the operation couldn't be completed immediately.

If the 'Handle' argument is a select_handle() (since OTP 24.0), or on Windows, the equivalent completion_handle() (since OTP 26.0), starts an asynchronous call like for nowait.

See the note Asynchronous Calls at the start of this module reference manual page.

The possible values for CompletionStatus in the completion message are:

  • {ok, {Source, Data}} โ€” Success. ate and left no crumbs ๐Ÿ’…
  • {error, Reason} โ€” An error occured and no data was read. oop ๐Ÿ™ˆ

๐ŸŒˆ recvmmsg/6

recvmmsg(Socket, VLen, BufSz, CtrlSz, Flags, Timeout :: infinity) ->
                 {ok, Msgs} | {error, Reason}
                 when
                     Socket :: socket(),
                     VLen :: non_neg_integer(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Msgs :: [msg_recv()],
                     Reason :: posix() | closed | invalid();
             (Socket, VLen, BufSz, CtrlSz, Flags,
              Timeout :: non_neg_integer()) ->
                 {ok, Msgs} | {error, Reason}
                 when
                     Socket :: socket(),
                     VLen :: non_neg_integer(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Msgs :: [msg_recv()],
                     Reason :: posix() | closed | invalid() | timeout;
             (Socket, VLen, BufSz, CtrlSz, Flags, nowait | Handle) ->
                 {ok, Msgs} |
                 {select, SelectInfo} |
                 {select_read, {SelectInfo, Msgs}} |
                 {completion, CompletionInfo} |
                 {error, Reason}
                 when
                     Socket :: socket(),
                     VLen :: non_neg_integer(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Handle :: select_handle() | completion_handle(),
                     Flags :: [msg_flag() | integer()],
                     Msgs :: [msg_recv()],
                     SelectInfo :: select_info(),
                     CompletionInfo :: completion_info(),
                     Reason :: posix() | closed | invalid().

Receive multiple messages on a socket. iconic behavior honestly โœจ

This function is equivalent to calling recvmsg/5 multiple times, but uses the platform's recvmmsg syscall for better performance when receiving multiple datagrams.

NOTE: This function is only available on Linux and BSD systems (not macOS/Darwin or Windows). On unsupported platforms, it will return {error, notsup}. sorry babe, not on your platform ๐Ÿ’”

Arguments:

  • VLen โ€” Maximum number of messages to receive (must be >= 1).
  • BufSz โ€” Buffer size for each message (0 uses default).
  • CtrlSz โ€” Control message buffer size for each message (0 uses default).
  • Flags โ€” Receive flags (same as recvmsg/5).
  • Timeout โ€” Timeout or nowait or infinity.

Returns a list of message maps, one per received message. The list length indicates how many messages were actually received. length = the receipts ๐Ÿ“‹


๐Ÿฆ‹ recvmsg ๐Ÿฆ‹

โญ recvmsg/1

recvmsg(Socket :: socket()) -> dynamic().

Equivalent to recvmsg(Socket, 0, 0, [], infinity). short and sweet, periodt ๐Ÿ’–

โญ recvmsg/2

recvmsg(Socket :: socket(), Flags :: list()) -> dynamic();
            (Socket :: socket(), TimeoutOrHandle) -> dynamic()
                when
                    TimeoutOrHandle :: timeout() | nowait | Handle,
                    Handle :: select_handle() | completion_handle().

Receive a message on a socket.

  • With argument Flags; equivalent to recvmsg(Socket, 0, 0, Flags, infinity).
  • With argument TimeoutOrHandle; equivalent to recvmsg(Socket, 0, 0, [], TimeoutOrHandle).
  • TimeoutOrHandle :: nowait has been allowed since OTP 22.1.
  • TimeoutOrHandle :: Handle has been allowed since OTP 24.0.

โญ recvmsg/3

recvmsg(Socket :: dynamic(),
             Flags :: list(),
             TimeoutOrHandle :: dynamic()) ->
                dynamic();
            (Socket :: dynamic(),
             BufSz :: integer(),
             CtrlSz :: integer()) ->
                dynamic().

Receive a message on a socket.

  • With arguments Flags; equivalent to recvmsg(Socket, 0, 0, Flags, infinity).
  • With argument TimeoutOrHandle; equivalent to recvmsg(Socket, 0, 0, [], TimeoutOrHandle).
  • TimeoutOrHandle :: nowait has been allowed since OTP 22.1.
  • TimeoutOrHandle :: Handle has been allowed since OTP 24.0.

โญ recvmsg/4

recvmsg(Socket :: socket(),
             BufSz :: non_neg_integer(),
             CtrlSz :: non_neg_integer(),
             TimeoutOrHandle :: dynamic()) ->
                dynamic().

โœจ recvmsg โ€” the whole spec bestie, read it slowly ๐Ÿ’…

recvmsg(Socket, BufSz, CtrlSz, Flags, Timeout :: infinity) ->
                 {ok, Msg} | {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Msg :: msg_recv(),
                     Reason :: posix() | closed | invalid();
            (Socket, BufSz, CtrlSz, Flags,
             Timeout :: non_neg_integer()) ->
                 {ok, Msg} | {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Flags :: [msg_flag() | integer()],
                     Msg :: msg_recv(),
                     Reason :: posix() | closed | invalid() | timeout;
            (Socket, BufSz, CtrlSz, Flags, nowait | Handle) ->
                 {ok, Msg} |
                 {select, SelectInfo} |
                 {select_read, {SelectInfo, Msg}} |
                 {completion, CompletionInfo} |
                 {error, Reason}
                 when
                     Socket :: socket(),
                     BufSz :: non_neg_integer(),
                     CtrlSz :: non_neg_integer(),
                     Handle :: select_handle() | completion_handle(),
                     Flags :: [msg_flag() | integer()],
                     Msg :: msg_recv(),
                     SelectInfo :: select_info(),
                     CompletionInfo :: completion_info(),
                     Reason :: posix() | closed | invalid().

Okay so this one receives a message on a socket โ€” and not just any lil message, it grabs the data message, the control messages, and the source address all in one go. It's giving main character energy tbh, it does everything. ๐Ÿ‘‘

The arguments BufSz and CtrlSz are the number of bytes for the receive buffer and the control message buffer respectively. And listen โ€” if the buffer size(s) is(are) too small, the message and/or the control message list gets truncated. ๐Ÿ˜ญ No cap. Size your buffers, bestie.

If BufSz is 0, a default buffer size gets used, and you can set that with socket:setopt(Socket, {otp,recvbuf}, BufSz). Same exact deal for CtrlSz and socket:setopt(Socket, {otp,recvctrlbuf}, CtrlSz). ๐ŸŽ€

If there's no known appropriate buffer size, it may be possible to use the receive message flag peek. When you use that flag, the message is not "consumed" from the underlying buffers โ€” so you need another recvfrom/1,2,3,4 call, possibly with an adjusted buffer size. Peek first, commit later, iykyk. ๐Ÿ™ˆ

The message Flags may be symbolic msg_flag/0s and/or integer/0s, exactly like in the platform's appropriate header files. And the values of all symbolic flags and integers are or:ed together. โœจ

Now the Timeout tea:

  • If Timeout is infinity โ€” it waits for the message to arrive, or for a socket error. Sitting pretty. ๐Ÿฆ‹
  • If Timeout is a time-out value (non_neg_integer/0) โ€” it returns {error, timeout} if no message has arrived after Timeout milliseconds. โฐ
  • Timeout = 0 only polls the OS receive call and doesn't engage the Asynchronous Calls mechanisms. If no message is immediately available, {error, timeout} comes right back at you.

If the Handle argument is nowait (since OTP 22.1), it starts an asynchronous call if the operation couldn't be completed immediately. ๐Ÿƒโ€โ™€๏ธ

If the Handle argument is a select_handle/0 (since OTP 24.0), or on Windows the equivalent completion_handle/0 (since OTP 26.0), it starts an asynchronous call just like for nowait. ๐Ÿ’ซ

See the note Asynchronous Calls at the start of this module reference manual page. Don't skip it, it's literally right there. ๐Ÿ“Œ

The possible values for CompletionStatus in the completion message are:

  • {ok, Msg} โ€” success. slay. โœจ
  • {error, Reason} โ€” an error occured and no data was read. sad but honest. ๐Ÿ’€

โœจ rest_iov/2 โ€” the math girlie of the module ๐Ÿงฎ

rest_iov(Written, IOV) -> RestIOV
                  when
                      Written :: non_neg_integer(),
                      IOV :: erlang:iovec(),
                      RestIOV :: erlang:iovec().

Calculate the rest I/O vector after a partially successful sendv (when CompletionStatus was {ok, Written}). That's it, that's the whole job. She's precise, she's focused, she's lowkey iconic. ๐ŸŽ€


โœจ send/2 โ€” baby's first send ๐Ÿ’Œ

send(Socket, Data) -> Result
              when
                  Socket :: socket(),
                  Data :: iodata(),
                  Result ::
                      ok |
                      {ok, RestData :: binary()} |
                      {select, SelectInfo :: dynamic()} |
                      {completion, CompletionInfo :: dynamic()} |
                      {error, Reason :: dynamic()}.

Equivalent to send(Socket, Data, [], infinity). Default vibes, no thoughts, head empty. ๐Ÿซถ


โœจ send/3 โ€” three flavors, pick your fighter ๐Ÿ“

send(Socket :: term(), Data :: term(), Cont :: tuple()) -> _;
          (Socket :: term(), Data :: term(), Flags :: list()) -> _;
          (Socket :: term(), Data :: term(), Timeout :: timeout()) -> _.

Send data on a connected socket. ๐Ÿ”ฅ

  • With argument Timeout โ€” equivalent to send(Socket, Data, [], Timeout).
  • With argument Flags โ€” equivalent to send(Socket, Data, Flags, infinity).
  • With argument Cont โ€” equivalent to send(Socket, Data, Cont, infinity) (since OTP 24.0).

โœจ send/4 โ€” the big one ๐Ÿ’…

send(Socket, Data, Flags | Cont, Timeout :: infinity) ->
                  ok |
                  {ok, RestData} |
                  {error, Reason} |
                  {error, {Reason, RestData}}
                  when
                      Socket :: socket(),
                      Data :: iodata(),
                      Flags :: [msg_flag() | integer()],
                      Cont :: select_info(),
                      RestData :: binary(),
                      Reason ::
                          posix() |
                          closed |
                          invalid() |
                          netname_deleted | too_many_cmds |
                          eei();
             (Socket, Data, Flags | Cont, Timeout :: non_neg_integer()) ->
                  ok |
                  {ok, RestData} |
                  {error, Reason | timeout} |
                  {error, {Reason | timeout, RestData}}
                  when
                      Socket :: socket(),
                      Data :: iodata(),
                      Flags :: [msg_flag() | integer()],
                      Cont :: select_info(),
                      RestData :: binary(),
                      Reason ::
                          posix() |
                          closed |
                          invalid() |
                          netname_deleted | too_many_cmds |
                          eei();
             (Socket, Data, Flags | Cont, nowait | Handle) ->
                  ok |
                  {ok, RestData} |
                  {select, SelectInfo} |
                  {select, {SelectInfo, RestData}} |
                  {completion, CompletionInfo} |
                  {error, Reason}
                  when
                      Socket :: socket(),
                      Data :: iodata(),
                      Flags :: [msg_flag() | integer()],
                      Cont :: select_info(),
                      Handle :: select_handle() | completion_handle(),
                      RestData :: binary(),
                      SelectInfo :: select_info(),
                      CompletionInfo :: completion_info(),
                      Reason ::
                          posix() |
                          closed |
                          invalid() |
                          netname_deleted | too_many_cmds |
                          eei().

Send data on a connected socket. (Yes, again. She contains multitudes. ๐Ÿ‘‘)

The message Flags may be symbolic msg_flag/0s and/or integer/0s as in the platform's appropriate header files. The values of all symbolic flags and integers are or:ed together. Just like recvmsg, consistency queen. ๐ŸŒธ

The Data, if it is not a binary/0, is copied into one before calling the platform network API, because a single buffer is required. And a returned RestData is a sub binary of it. So like, heads up, there's a copy happening. ๐Ÿซ 

The return value indicates the result from the platform's network layer:

  • ok โ€” all data was accepted by the OS for delivery. Periodt. โœจ
  • {ok, RestData} โ€” some but not all data was accepted, but no error was reported (a partially successful send). RestData is the tail of Data that wasn't accepted.
    • This cannot happen for a socket of type stream, where such a partially successful send is retried until the data is either accepted for delivery or there is an error.
    • For a socket of type dgram this should probably also not happen, since a message that cannot be passed atomically should render an error.
    • It is nevertheless possible for the platform's network layer to return this โ€” surely more possible for a socket of type seqpacket.
  • {error, Reason} โ€” an error has been reported and no data was accepted for delivery. Reason :: posix/0 is what the platform's network layer reported. closed means that this socket library was informed that the socket was closed, and invalid/0 means that this socket library found an argument to be invalid. ๐Ÿšซ
  • {error, {Reason, RestData}} โ€” an error was reported but before that some data was accepted for delivery. RestData is the tail of Data that wasn't accepted. See {error, Reason} above. This can only happen for a socket of type stream, when a partially successful send is retried until there is an error. ๐Ÿ˜ญ

Timeout behavior, because this part matters fr fr:

  • If the Timeout argument is infinity โ€” wait for the OS to complete the send operation (take responsibility for the data), or return an error. ๐Ÿง˜โ€โ™€๏ธ
  • If the Timeout argument is a time-out value (non_neg_integer/0) โ€” return {error, timeout} if no data has been sent within Timeout millisecond, or {error, {timeout, RestData}} if some data was sent (accepted by the OS for delivery). RestData is the tail of the data that hasn't been sent. โณ

(Yes, the original says "millisecond" singular. We respect the source. ๐Ÿ’œ)

If the Handle argument is nowait (since OTP 22.1), it starts an asynchronous call if the operation couldn't be completed immediately. ๐Ÿƒโ€โ™€๏ธ

If the Handle argument is a select_handle/0 (since OTP 24.0), or on Windows the equivalent completion_handle/0 (since OTP 26.0), it starts an asynchronous call like for nowait. ๐Ÿ’ซ

See the note Asynchronous Calls at the start of this module reference manual page. It's the same note, bestie, go read it. ๐Ÿ“Œ

The possible values for CompletionStatus in the completion message are:

so, back to what send hands back to you ๐Ÿ‘€

  • ok โ€” Complete success; the data was written in its entirety. That's it, that's the whole thing, we love a clean win โœจ
  • {ok, Written} โ€” Partial success; some but not all data was written, but no error was reported. Written is the number of bytes that were written.
  • {error, Reason} โ€” An error occured and no data was sent.

And if you call the function with a Cont argument โ€” that's the SelectInfo from the previous send/3,4 call โ€” the send gets continued with preprocessed send parameters sitting in the SelectInfo. Lowkey the reason you'd do this: it means you don't have to re-validate and re-encode message flags on every single call, only the first one. Efficiency queen behavior ๐Ÿ’…


โœจ sendfile/2 โœจ

sendfile(Socket, FileHandle | Continuation) -> dynamic()
                  when
                      Socket :: socket(),
                      FileHandle :: file:fd(),
                      Continuation :: select_info().

Send a file on a socket. ๐ŸŽ€

Equivalent to sendfile(Socket, FileHandle_or_Continuation, 0, 0, infinity).

โœจ sendfile/3 โœจ

sendfile(Socket, FileHandle | Continuation, Timeout | Handle) ->
                  dynamic()
                  when
                      Socket :: socket(),
                      FileHandle :: file:fd(),
                      Continuation :: select_info(),
                      Timeout :: infinity | non_neg_integer(),
                      Handle :: nowait | select_handle().

Send a file on a socket. ๐Ÿฆ‹

Equivalent to sendfile(Socket, FileHandle_or_Continuation, 0, 0, Timeout_or_Handle).

โœจ sendfile/4 โœจ

sendfile(Socket, FileHandle | Continuation, Offset, Count) ->
                  dynamic()
                  when
                      Socket :: socket(),
                      FileHandle :: file:fd(),
                      Continuation :: select_info(),
                      Offset :: integer(),
                      Count :: non_neg_integer().

Send a file on a socket. ๐ŸŒธ

Equivalent to sendfile(Socket, FileHandle_or_Continuation, Offset, Count, infinity).

โœจ sendfile/5 โœจ

sendfile(Socket,
                  FileHandle | Continuation,
                  Offset, Count,
                  Timeout :: infinity) ->
                     {ok, BytesSent} |
                     {error, Reason} |
                     {error, {Reason, BytesSent}}
                     when
                         Socket :: socket(),
                         FileHandle :: file:fd(),
                         Continuation :: select_info(),
                         Offset :: integer(),
                         Count :: non_neg_integer(),
                         BytesSent :: non_neg_integer(),
                         Reason :: posix() | closed | invalid();
                 (Socket,
                  FileHandle | Continuation,
                  Offset, Count,
                  Timeout :: non_neg_integer()) ->
                     {ok, BytesSent} |
                     {error, Reason} |
                     {error, {Reason, BytesSent}}
                     when
                         Socket :: socket(),
                         FileHandle :: file:fd(),
                         Continuation :: select_info(),
                         Offset :: integer(),
                         Count :: non_neg_integer(),
                         BytesSent :: non_neg_integer(),
                         Reason :: posix() | closed | invalid() | timeout;
                 (Socket,
                  FileHandle | Continuation,
                  Offset, Count,
                  nowait | (SelectHandle :: select_handle())) ->
                     {ok, BytesSent} |
                     {select, SelectInfo} |
                     {select, {SelectInfo, BytesSent}} |
                     {error, Reason}
                     when
                         Socket :: socket(),
                         FileHandle :: file:fd(),
                         Continuation :: select_info(),
                         Offset :: integer(),
                         Count :: non_neg_integer(),
                         BytesSent :: non_neg_integer(),
                         SelectInfo :: select_info(),
                         Reason :: posix() | closed | invalid().

Send a file on a socket. ๐Ÿ“โœจ

NOTE: This function unsupported on Windows. (not supported bestie, don't come for me)

The FileHandle argument must refer to an open raw file as described in file:open/2.

The Offset argument is the file offset to start reading from. The default offset is 0.

The Count argument is the number of bytes to transfer from FileHandle to Socket. If Count = 0 (the default) the transfer stops at the end of file.

The return value indicates the result from the platform's network layer:

  • {ok, BytesSent} โ€” The transfer completed successfully after BytesSent bytes of data. ๐ŸŽ‰
  • {error, Reason} โ€” An error has been reported and no data was transferred. Reason :: posix/0 is what the platform's network layer reported. closed means that this socket library was informed that the socket was closed, and invalid/0 means that this socket library found an argument to be invalid. ๐Ÿ’€
  • {error, {Reason, BytesSent}} โ€” An error has been reported, but before that some data was transferred. See {error, Reason} and {ok, BytesSent} above.

If the Timeout argument is infinity; wait for the OS to complete the send operation (take responsibility for the data), or return an error.

If the Timeout argument is a time-out value (non_neg_integer/0); return {error, timeout} if no data has been sent within Timeout millisecond, or {error, {timeout, BytesSent}} if some but not all data was sent (accepted by the OS for delivery).

If the Handle argument is nowait, starts an asynchronous call if the operation couldn't be completed immediately.

If the Handle argument is a select_handle/0, starts an asynchronous call like for nowait.

See the note Asynchronous Calls at the start of this module reference manual page.

After receiving a select message; call sendfile/2,3,4,5 with SelectInfo as the Continuation argument, to complete the operation.

If the function is called with a Continuation argument, that is; the SelectInfo from the previous sendfile/5 call; the transfer is continued with preprocessed parameters in the SelectInfo.

The Offset and maybe Count arguments will probably need to be updated between continuation calls. ๐Ÿ‘‘ (yes you, bestie, gotta keep track of that yourself)

โœจ sendmmsg/4 โœจ

sendmmsg(Socket, Msgs, Flags, Timeout :: infinity) ->
                  ok | {ok, Rest} | {error, Reason}
                  when
                      Socket :: socket(),
                      Msgs :: [msg_send()],
                      Flags :: [msg_flag() | integer()],
                      Rest :: [erlang:iovec()],
                      Reason :: posix() | closed | invalid();
              (Socket, Msgs, Flags, Timeout :: non_neg_integer()) ->
                  ok | {ok, Rest} | {error, Reason | timeout}
                  when
                      Socket :: socket(),
                      Msgs :: [msg_send()],
                      Flags :: [msg_flag() | integer()],
                      Rest :: [erlang:iovec()],
                      Reason :: posix() | closed | invalid();
              (Socket, Msgs, Flags, nowait | Handle) ->
                  ok |
                  {ok, Rest} |
                  {select_write, {SelectInfo, SentCount}} |
                  {select, SelectInfo} |
                  {completion, CompletionInfo} |
                  {error, Reason}
                  when
                      Socket :: socket(),
                      Msgs :: [msg_send()],
                      Flags :: [msg_flag() | integer()],
                      Handle :: select_handle() | completion_handle(),
                      Rest :: [erlang:iovec()],
                      SentCount :: non_neg_integer(),
                      SelectInfo :: select_info(),
                      CompletionInfo :: completion_info(),
                      Reason :: posix() | closed | invalid().

Send multiple messages on a socket. ๐Ÿ“จ๐Ÿ“จ๐Ÿ“จ

This function is equivalent to calling sendmsg/4 multiple times, but uses the platform's sendmmsg syscall for better performance when sending multiple datagrams. It's giving "why do one when you can do many".

NOTE: This function is only available on Linux and BSD systems (not macOS/Darwin or Windows). On unsupported platforms, it will return {error, notsup}. ๐Ÿ™ˆ

On success, returns either:

  • ok โ€“ when all messages were sent in full (or there were zero messages).
  • {ok, Rest} โ€“ when one or more messages had a partial write. Rest is a list with one element per message that was not fully sent, in message order. Each element is the remaining data for that message in the same form as sendmsg/4's rest data (erlang:iovec/0), so you can retry with sendmsg for each.

On error returns {error, Reason}.

โœจ sendmsg/2,3,4 โœจ

sendmsg(Socket, Msg) -> Result
                 when
                     Socket :: socket(),
                     Msg :: msg_send() | erlang:iovec(),
                     Result :: dynamic().

Equivalent to sendmsg(Socket, Msg, [], infinity).

sendmsg(Socket :: socket(), Msg :: msg_send(), Flags :: list()) ->
                 dynamic();
             (Socket :: socket(),
              Data :: msg_send() | erlang:iovec(),
              Cont :: select_info()) ->
                 dynamic();
             (Socket :: socket(),
              Msg :: msg_send(),
              Timeout :: infinity) ->
                 dynamic().

Send data and control messages on a socket. ๐Ÿซถ

  • With arguments Msg and Timeout; equivalent to sendmsg(Socket, Msg, [], Timeout).
  • With arguments Msg and Flags; equivalent to sendmsg(Socket, Msg, Flags, infinity).
  • With arguments Data and Cont; equivalent to sendmsg(Socket, Data, Cont, infinity) since OTP 24.0.

โœจ sendmsg/3,4 โ€” send data AND control messages, all at once ๐Ÿ’…

sendmsg(Socket, Msg, Flags, Timeout :: infinity) ->
                        ok |
                        {ok, RestData} |
                        {error, Reason} |
                        {error, {Reason, RestData}}
                        when
                            Socket :: socket(),
                            Msg :: msg_send(),
                            Flags :: [msg_flag() | integer()],
                            RestData :: erlang:iovec(),
                            Reason :: posix() | closed | invalid();
                    (Socket, Msg, Flags, Timeout :: non_neg_integer()) ->
                        ok |
                        {ok, RestData} |
                        {error, Reason | timeout} |
                        {error, {Reason | timeout, RestData}}
                        when
                            Socket :: socket(),
                            Msg :: msg_send(),
                            Flags :: [msg_flag() | integer()],
                            RestData :: erlang:iovec(),
                            Reason :: posix() | closed | invalid();
                    (Socket, Msg, Flags, nowait | Handle) ->
                        ok |
                        {ok, RestData} |
                        {select, SelectInfo} |
                        {select, {SelectInfo, RestData}} |
                        {completion, CompletionInfo} |
                        {error, Reason} |
                        {error, {Reason, RestData}}
                        when
                            Socket :: socket(),
                            Msg :: msg_send(),
                            Flags :: [msg_flag() | integer()],
                            Handle :: select_handle() | completion_handle(),
                            RestData :: erlang:iovec(),
                            SelectInfo :: select_info(),
                            CompletionInfo :: completion_info(),
                            Reason :: posix() | closed | invalid();
                    (Socket, Data, Cont, Timeout :: infinity) ->
                        ok |
                        {ok, RestData} |
                        {error, Reason} |
                        {error, {Reason, RestData}}
                        when
                            Socket :: socket(),
                            Data :: msg_send() | erlang:iovec(),
                            Cont :: select_info(),
                            RestData :: erlang:iovec(),
                            Reason :: posix() | closed | invalid();
                    (Socket, Data, Cont, Timeout :: non_neg_integer()) ->
                        ok |
                        {ok, RestData} |
                        {error, Reason | timeout} |
                        {error, {Reason | timeout, RestData}}
                        when
                            Socket :: socket(),
                            Data :: msg_send() | erlang:iovec(),
                            Cont :: select_info(),
                            RestData :: erlang:iovec(),
                            Reason :: posix() | closed | invalid();
                    (Socket, Data, Cont, nowait | Handle) ->
                        ok |
                        {ok, RestData} |
                        {select, SelectInfo} |
                        {select, {SelectInfo, RestData}} |
                        {completion, CompletionInfo} |
                        {error, Reason} |
                        {error, {Reason, RestData}}
                        when
                            Socket :: socket(),
                            Data :: msg_send() | erlang:iovec(),
                            Cont :: select_info(),
                            Handle :: select_handle(),
                            RestData :: erlang:iovec(),
                            SelectInfo :: select_info(),
                            CompletionInfo :: completion_info(),
                            Reason :: posix() | closed | invalid().

okay so bestie, sendmsg is your girl when you wanna send data and control messages on a socket all in one go. โœจ

The argument Msg is a map that carries the data you're sending under the key iov, and that's an erlang:iovec/0 โ€” which is literally just a list of binary/0. ๐Ÿซถ It may also carry the destination address under the key addr, and heads up, that key is mandatory if the socket isn't connected. If the socket is connected, it's bestie-approved best practice to NOT include an addr key, because the platform may treat that as an error (or just ignore it, which is also lowkey not the vibe). And under the key ctrl there may be a whole list of protocol- and platform-dependent control messages (a.k.a. ancillary data, a.k.a. control information) to send. Fancy, right? ๐ŸŽ€

The message data gets handed to the platform's network layer as an I/O vector with no copying of the content. Efficiency queen behavior, no cap ๐Ÿ’…. But if the number of elements in the I/O vector is larger than what's allowed on the platform (you can peek at this number in the iov_max field from info/0), then: on a socket of type stream the send gets iterated over all the elements, but for other socket types the call just fails. ๐Ÿ’€

See send/4 for the whole rundown of the Flags argument and the return values.

NOTE: On Windows, this function can only be used with datagram and raw sockets.

If the Timeout argument is infinity โ€” you wait for the OS to complete the send operation (aka it takes responsibility for the data), or you get an error back.

If the Timeout argument is a time-out value (non_neg_integer/0) โ€” you'll get {error, timeout} if no data has been sent within Timeout millisecond, or {error, {timeout, RestData}} if some data was sent (accepted by the OS for delivery). And RestData is the tail of the data that hasn't been sent. ๐Ÿ˜ญ

If the Handle argument is nowait (since OTP 22.1), it starts an asynchronous call if the operation couldn't be completed immediately.

If the Handle argument is a select_handle/0 (since OTP 24.0), or on Windows, the equivalent completion_handle/0 (since OTP 26.0), it starts an asynchronous call just like for nowait.

See the note Asynchronous Calls at the start of this module reference manual page. ๐Ÿ‘€

The possible values for CompletionStatus in the completion message are:

  • ok โ€” complete success ๐ŸŽ‰ the data was written in its entirety.
  • {ok, Written} โ€” partial success ๐ŸŒธ some but not all data was written, but no error was reported. Written is the number of bytes that was written.
  • {error, Reason} โ€” an error occured and no data was sent. ๐Ÿ™ˆ

After receiving a select message, call sendmsg/3,4 with SelectInfo as the Cont argument, and the operation gets completed. Periodt.

With the arguments Data and Cont, you continue the send operation. Cont should be the SelectInfo returned from the previous sendmsg/2,3,4 call.

Data can be a Msg map/0 where only the key iov is used, or an erlang:iovec/0.


โœจ sendto/2 โ€” send data, and tell it where to go ๐Ÿ’Œ

sendto(Socket :: socket(), Data :: iodata(), Cont | Dest) ->
                       Result
                       when
                           Cont :: select_info(),
                           Dest :: sockaddr(),
                           Result ::
                               ok |
                               {ok, RestData} |
                               {error, Reason} |
                               {error, {Reason, RestData}},
                           RestData :: binary(),
                           Reason :: posix() | closed | invalid().

Send data on a socket.

With argument Dest โ€” it's equivalent to sendto(Socket, Data, Dest, [], infinity).

With argument Cont โ€” it's equivalent to sendto(Socket, Data, Cont, infinity), since OTP 24.0. ๐Ÿฆ‹


โœจ sendto/3,4 โ€” the flexible one ๐Ÿงš

sendto(Socket :: socket(),
                    Data :: iodata(),
                    Dest :: sockaddr(),
                    Flags :: [msg_flag() | integer()]) ->
                       dynamic();
                   (Socket :: socket(),
                    Data :: iodata(),
                    Cont :: select_info(),
                    Timeout :: timeout() | nowait | (Handle ::
       select_handle())) ->
                       dynamic();
                   (Socket :: socket(),
                    Data :: iodata(),
                    Dest :: sockaddr(),
                    Timeout ::
                        timeout() |
                        nowait |
                        (Handle :: select_handle() | completion_handle())) ->
                       dynamic().

Send data on a socket.

With arguments Dest and TimeoutOrHandle โ€” it's equivalent to sendto(Socket, Data, Dest, [], TimeoutOrHandle).

With arguments Dest and Flags โ€” it's equivalent to sendto(Socket, Data, Dest, Flags, infinity).

With arguments Cont and TimeoutOrHandle โ€” Cont must be the SelectInfo from the previous sendto/3,4,5 call, and the send continues with preprocessed send parameters tucked inside the SelectInfo. Using this argument variant avoids, for example, having to validate and encode message flags in every call but the first. (Since OTP 24.0) ๐Ÿ”ฅ

See the last argument (argument 5) of sendto/5 for an explanation of TimeoutOrHandle.


โœจ sendto/5 โ€” the full send-it-all (so far) โญ

sendto(Socket, Data, Dest, Flags, Timeout :: infinity) ->
                       ok |
                       {ok, RestData} |
                       {error, Reason} |
                       {error, {Reason, RestData}}
                       when
                           Socket :: socket(),
                           Data :: iodata(),
                           Dest :: sockaddr(),
                           Flags :: [msg_flag() | integer()],
                           RestData :: binary(),
                           Reason :: posix() | closed | invalid();
                   (Socket, Data, Dest, Flags, Timeout :: non_neg_integer()) ->
                       ok |
                       {ok, RestData} |
                       {error, Reason | timeout} |
                       {error, {Reason | timeout, RestData}}
                       when
                           Socket :: socket(),
                           Data :: iodata(),
                           Dest :: sockaddr(),
                           Flags :: [msg_flag() | integer()],
                           RestData :: binary(),
                           Reason :: posix() | closed | invalid();
                   (Socket, Data, Dest, Flags, nowait | Handle) ->
                       ok |
                       {ok, RestData} |
                       {select, SelectInfo} |
                       {select, {SelectInfo, RestData}} |
                       {completion, CompletionInfo} |
                       {error, Reason}
                       when
                           Socket :: socket(),
                           Data :: iodata(),
                           Dest :: sockaddr(),
                           Flags :: [msg_flag() | integer()],
                           Handle :: select_handle() | completion_handle(),
                           RestData :: binary(),
                           SelectInfo :: select_info(),
                           CompletionInfo :: completion_info(),
                           Reason :: posix() | closed | invalid().

Send data on a socket.

The To argument is where the data's going โ€” the destination address, bestie. And lowkey important: even on a connected socket you still gotta pass this argument, and it gets handed to the OS call anyway, which might just ignore the address or straight up throw an error at you. ๐Ÿ™ˆ

For the Flags and Data arguments, plus the return values, go check out send/4 โ€” it's all there.

So about Timeout:

  • If it's infinity โ€” you wait for the OS to finish the send operation (like, actually take responsibility for the data), or you get an error back. No in-between. ๐Ÿ’…
  • If it's a time-out value (non_neg_integer/0) โ€” you get {error, timeout} if nothing got sent within Timeout milliseconds, or {error, {timeout, RestData}} if some data made it out (accepted by the OS for delivery). And RestData? That's the tail of the data that hasn't been sent. The leftovers. ๐Ÿ“

Now, if Handle is nowait (shoutout OTP 22.1), it kicks off an asynchronous call when the operation just can't wrap up immediately.

And if Handle is a select_handle/0 (since OTP 24.0), or on Windows, the equivalent completion_handle/0 (since OTP 26.0), it starts an async call just like nowait does. Same vibe, different handle. โœจ

See the note Asynchronous Calls at the start of this module reference manual page. It's giving essential reading, fr fr.

Okay so the possible values for CompletionStatus in the completion message are:

  • ok โ€” complete success, the data was written in its entirety. Perfect, no notes. ๐Ÿ‘‘
  • {ok, Written} โ€” partial success; some but not all data was written, but no error got reported. Written is the number of bytes that was written.
  • {error, Reason} โ€” an error occured and no data was sent. ๐Ÿ’€

After you get a select message, call sendto/3,4 with SelectInfo as the Cont argument to finish the whole operation off. Slay.

sendv(Socket, IOV, Timeout :: infinity) ->
                ok |
                {ok, RestIOV} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    RestIOV :: erlang:iovec(),
                    Reason :: posix() | closed | invalid();
            (Socket, IOV, Timeout :: non_neg_integer()) ->
                ok |
                {ok, RestIOV} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    RestIOV :: erlang:iovec(),
                    Reason :: posix() | closed | invalid() | timeout;
            (Socket, IOV, nowait | Handle) ->
                ok |
                {ok, RestIOV} |
                {select, SelectInfo} |
                {select, {SelectInfo, RestIOV}} |
                {completion, CompletionInfo} |
                {completion, {CompletionInfo, RestIOV}} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    Handle :: select_handle() | completion_handle(),
                    RestIOV :: erlang:iovec(),
                    SelectInfo :: select_info(),
                    CompletionInfo :: completion_info(),
                    Reason :: posix() | closed | invalid();
            (Socket, IOV, Cont) ->
                ok |
                {ok, RestIOV} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    Cont :: select_info(),
                    RestIOV :: erlang:iovec(),
                    Reason :: posix() | closed | invalid().

Send erlang:iovec/0 data on a connected socket. Let's gooo. ๐Ÿฆ‹

Wanna know how the IOV data gets handled on its way down to the platform's network layer? Check sendmsg/4. ๐Ÿ‘€

The return value tells you what the platform's network layer actually did:

  • ok โ€” all data has been accepted by the OS for delivery. Full send, no cap. ๐Ÿ”ฅ
  • {ok, RestIOV} โ€” some but not all data was accepted, but no error was reported (partially successful send). RestIOV is the tail of IOV that wasn't accepted.
  • {error, Reason} โ€” an error has been reported and no data was accepted for delivery. Reason :: posix/0 is what the platform's network layer reported. closed means this socket library was informed that the socket was closed, and invalid/0 means this socket library found an argument to be invalid.
  • {error, {Reason, RestIOV}} โ€” this one's the awkward middle child: an error was reported, but before that some data was accepted for delivery. RestIOV is the tail of IOV that wasn't accepted. See {error, Reason} above for the deets.

Now the Timeout tea again:

  • infinity โ€” wait for the OS to complete the send operation (take responsibility for the data), or return an error.
  • a time-out value (non_neg_integer/0) โ€” return {error, timeout} if no data has been sent within Timeout millisecond, or {error, {timeout, RestIOV}} if some data was sent (accepted by the OS for delivery). RestIOV is the tail of the data that hasn't been sent.

If Handle is nowait, it starts an asynchronous call if the operation couldn't be completed immediately.

If Handle is a select_handle/0, or on Windows, the equivalent completion_handle/0, it starts an asynchronous call like for nowait.

See the note Asynchronous Calls at the start of this module reference manual page. ๐Ÿค

The possible values for CompletionStatus in the completion message are:

  • ok โ€” complete success; the I/O vector was written in its entirety. Iconic. โœจ
  • {ok, Written} โ€” partial success; some but not all data was written, but no error was reported. Written is the number of bytes that was written. And plot twist: rest_iov(Written, IOV) can be used to calculate the rest I/O vector (from the original IOV). So helpful.
  • {error, Reason} โ€” an error occured and no data was sent. ๐Ÿ˜ญ

With the argument Cont, it's equivalent to sendv(Socket, IOV, Cont, infinity).

sendv(Socket, IOV, Cont, Timeout :: infinity) ->
                ok |
                {ok, RestIOV} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    Cont :: select_info(),
                    RestIOV :: erlang:iovec(),
                    Reason :: posix() | closed | invalid();
            (Socket, IOV, Cont, Timeout :: non_neg_integer()) ->
                ok |
                {ok, RestIOV} |
                {error, Reason} |
                {error, {Reason | RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    Cont :: select_info(),
                    RestIOV :: erlang:iovec(),
                    Reason :: posix() | closed | invalid() | timeout;
            (Socket, IOV, Cont, nowait | SelectHandle) ->
                ok |
                {ok, RestIOV} |
                {select, SelectInfo} |
                {select, {SelectInfo, RestIOV}} |
                {error, Reason} |
                {error, {Reason, RestIOV}}
                when
                    Socket :: socket(),
                    IOV :: erlang:iovec(),
                    Cont :: select_info(),
                    SelectHandle :: select_handle(),
                    RestIOV :: erlang:iovec(),
                    SelectInfo :: select_info(),
                    Reason :: posix() | closed | invalid().

Send data on a connected socket, continuation. The "we're so back" of sends. ๐ŸŽ€

Continues sending data on a connected socket. Cont is the SelectInfo returned from the previous sendv/2,3 call, and IOV should be the rest data that wasn't sent. Pick up right where you left off, bestie.

See asynchronous calls about continuing unfinished calls.

See sendv/3 about the return values. Same rules apply, no need to re-read the whole thing.

setopt(Socket, SocketOption, Value) ->
                 ok | {error, invalid() | closed}
                 when
                     Socket :: socket(),
                     SocketOption ::
                         {Level :: otp, Opt :: otp_socket_option()},
                     Value :: dynamic();
             (Socket, SocketOption, Value) ->
                 ok | {error, posix() | invalid() | closed}
                 when
                     Socket :: socket(),
                     SocketOption :: socket_option(),
                     Value :: dynamic().

Set a socket option. Tune it, customize it, make it yours. ๐Ÿ’…

Set an OS protocol level option, or an otp pseudo protocol level option. That otp level is this module's implementation level sitting above the OS protocol levels, fyi.

See the type otp_socket_option() for a description of the otp protocol level.

See the type socket_option/0 for which OS protocol level options this implementation knows about, how they're related to OS option names, and if there are any known peculiarities with any of them.

What options are valid depends on the OS, and on the kind of socket (domain/0, type/0 and protocol/0). See the type socket_option() and the socket options chapter in the User's Guide for more info. ๐Ÿ“–

NOTE: Not all options are valid, nor possible to set, on all platforms. That is, even if this socket implementation support an option; it doesn't mean that the underlying OS does. So don't come for me if your OS says no. ๐Ÿ™ˆ
setopt(socket(), Level :: term(), Opt :: term(), Value :: term()) ->
                 _.

Set a socket option (backwards compatibility function). It's giving legacy, but make it cute. ๐Ÿงš

Equivalent to setopt(Socket, {Level, Opt}, Value), or as a special case if Opt = NativeOpt :: integer/0 and Value = binary/0 equivalent to setopt_native(Socket, {Level, NativeOpt}, ValueSpec).

Use setopt/3 or setopt_native/3 instead to handle the option level and name as a single term, and to make the difference between known options and native options clear. Do future-you a favour, bestie. ๐ŸŒธ

๐ŸŒธ setopt_native/3 โ€” the "idk what this option even is" button ๐Ÿ’…

setopt_native(Socket, Option, Value) ->
                       ok | {error, posix() | invalid() | closed}
                       when
                           Socket :: socket(),
                           Option ::
                               socket_option() |
                               {Level, NativeOpt} |
                               {NativeLevel, NativeOpt},
                           Value :: native_value(),
                           Level :: level(),
                           NativeLevel :: integer(),
                           NativeOpt :: integer().

okay so bestie, this one sets a "native" socket option โ€” aka an option that our implementation literally doesn't know about, or that has a type our implementation can't vibe with. Native mode, periodt. ๐Ÿงš

Here's how Value gets interpreted:

  • if Value is an integer/0 it gets used as a C type (int)
  • if Value is a boolean/0 it gets used as a C type (int), with the C implementation's values for false or true
  • if Value is a binary/0 its content and size get used as the option value

You can name the option three different ways, pick your fighter:

  • an ordinary socket_option/0 tuple
  • a symbolic Level as {Level :: level/0, NativeOpt :: integer/0}
  • or integers for both, as {NativeLevel :: integer/0, NativeOpt :: integer/0}

Whether an option is even valid depends on the platform and on what kind of socket it is (domain/0, type/0 and protocol/0) โ€” so like, it's giving very "depends on the day" energy. ๐ŸŒˆ

lowkey important: the integer values for NativeLevel and NativeOpt, plus the Value encoding, have to be deduced from the header files for the running system. Not me guessing it for you, go read the headers ๐Ÿ’€

๐ŸŽ€ shutdown/2

shutdown(Socket, How) -> ok | {error, Reason}
                   when
                       Socket :: socket(),
                       How :: read | write | read_write,
                       Reason :: posix() | closed.

Shuts down all of โ€” or part of โ€” a full-duplex connection. Pick your lane with read, write, or read_write. โœ‚๏ธ


โœจ sockname/1

sockname(Socket :: socket()) -> {ok, SockAddr} | {error, Reason}
                   when
                       SockAddr :: sockaddr_recv(),
                       Reason :: posix() | closed.

Gets the socket's address. ๐Ÿ’Œ

It returns the address the socket is currently bound to. And heads up: if the bind address had the wildcard port 0, the address you get back here contains the ephemeral port selected by the OS. Surprise port reveal, iconic. ๐Ÿ‘‘


๐Ÿฆ‹ socknames/2

socknames(Socket :: socket(), AssocId :: sctp_assoc_id()) ->
                        {ok, [SockAddr]} | {error, Reason}
                        when
                            SockAddr :: sockaddr_recv(),
                            Reason :: posix() | closed.

Gets the socket's address โ€” plural era. ๐Ÿ’–

It returns all the locally bound addresses the socket is currently bound to.

  • If the socket is IPv4, then all returned addresess will be IPv4.
  • If the socket is IPv6, then the returned addresess can be a mix of IPv4 and IPv6.

For a one-to-many socket, AssocId specifies the association. For a one-to-one socket, AssocId is ignored. ๐Ÿซถ

If AssocId is 0 (zero), then the returned addresses are without any particular association.


๐ŸŒธ supports/0

supports() ->
                   [{Key1 :: term(),
                     boolean() |
                     [{Key2 :: term(),
                       boolean() | [{Key3 :: term(), boolean()}]}]}].

Retrieves info about what socket features the module and the platform support. A whole vibe check on your system. โœ…

Returns a list of, in no particular order, {Key1, supports(Key1)} tuples for every Key1 described in supports/1, plus {Key, boolean()} tuples for each of these keys:

  • sctp โ€” SCTP support
  • ipv6 โ€” IPv6 support
  • local โ€” Unix Domain sockets support ( AF_UNIX | AF_LOCAL)
  • netns โ€” Network Namespaces support (Linux, setns(2))
  • sendfile โ€” Sendfile support ( sendfile(2))

๐Ÿ’… supports/1

supports(Key1 :: term()) ->
                   [{Key2 :: term(),
                     boolean() | [{Key3 :: term(), boolean()}]}].

Retrieves info about what socket features the module and the platform support โ€” part two, the sequel. ๐ŸŒŸ

  • If Key1 = msg_flags it returns a list of {Flag, boolean()} tuples for every Flag in msg_flag/0, with the boolean/0 indicating if the flag is supported on this platform.
  • If Key1 = protocols it returns a list of {Name, boolean()} tuples for every Name in protocol/0, with the boolean/0 indicating if the protocol is supported on this platform.
  • If Key1 = options it returns a list of {SocketOption, boolean()} tuples for every SocketOption in socket_option/0, with the boolean/0 indicating if the socket option is supported on this platform.

There is no particular order of any of the returned lists. Don't come for the ordering, it doesn't exist. ๐Ÿ™ˆ

For other values of Key1 it returns []. And note that in future versions of this module or on different platforms, there might be more supported keys. Foreshadowing. ๐Ÿ‘€


๐ŸŒˆ supports/2

supports(Key1 :: term(), Key2 :: term()) ->
                   [{Key3 :: term(), boolean()}].

Retrieves info about what socket features the module and the platform support โ€” the threequel. ๐ŸŽฌ

If Key1 = options, for a Key2 in level/0 it returns a list of {Opt, boolean()} tuples for all known socket options Opt on that Level = Key2, with the boolean/0 indicating if the socket option is supported on this platform. See setopt/3 and getopt/2.

There is no particular order of any of the returned lists.

For other values of Key1 or Key2 it returns []. Note that in future versions of this module or on different platforms, there might be more supported keys.


๐ŸŽ€ use_registry/1

use_registry(D :: boolean()) -> ok.

Sets the global use_registry option default value. ๐ŸŒ

Globally changes whether the socket registry is to be used or not. But ngl, it's still possible to override this explicitly when creating an individual socket โ€” see open/2,3,4 for more info (the Opts :: map/0). Your socket, your rules. ๐Ÿ’–


๐Ÿ‘‘ which_sockets/0

which_sockets() -> [socket()].

Returns a list of all known sockets.

Equivalent to which_sockets(fun (_) -> true end). Yes bestie, everything passes the vibe check. โœจ


๐Ÿซถ which_sockets/1

which_sockets(FilterRule) -> [socket()]
                        when
                            FilterRule ::
                                inet | inet6 | local | stream | dgram |
                                seqpacket | sctp | tcp | udp |
                                pid() |
                                fun((socket_info()) -> boolean()).

Returns a filtered list of known sockets. Curated feed energy. ๐Ÿฆ‹

There are several predefined FilterRules and one general one:

  • inet | inet6 โ€” only the sockets with matching domain/0 are returned.
  • stream | dgram | seqpacket โ€” only the sockets with matching type/0 are returned.
  • sctp | tcp | udp โ€” only the sockets with matching protocol/0 are returned.
  • pid/0 โ€” only the sockets with matching Controlling Process are returned. See the OTP socket option controlling_process.
  • fun((socket_info()) -> boolean()) โ€” the general filter rule. A fun that takes the socket info and returns a boolean/0 indicating if the socket should be returned or not.

and that's a wrap on the whole page, bestie!! ๐ŸŽ‰๐Ÿ’– no cap, you just read the entire socket(3) man page. the source on this one is Ericsson AB, from kernel 11.0.3, and it's filed under socket(3). go forth and socket responsibly, mwah ๐Ÿ’…โœจ๐ŸŒธ

๐Ÿ“ฆ where this page comes from