net_kernel(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

✨ NAME ✨

net_kernel β€” the Erlang networking kernel, bestie πŸ’– (it's a module in the Erlang manual, page net_kernel(3))


🫢 DESCRIPTION 🫢

okay so. The net kernel is a whole entire system process, and it's registered under the name net_kernel. It must be up and running for distributed Erlang to work at all β€” no net kernel, no distribution, periodt. What it does is implement parts of the BIFs spawn/4 and spawn_link/4, and it also handles monitoring of the network. She's the main character of the distribution story, fr fr πŸ‘‘

An Erlang node gets started using the command-line flag -name or -sname:

$ erl -sname foobar

You can also just call net_kernel:start(foobar, #{}) straight from the normal Erlang shell prompt, like the iconique thing that it is:

1> net_kernel:start(foobar, #{name_domain => shortnames}).
{ok,<0.64.0>}
(foobar@gringotts)2>

If the node is started with the command-line flag -sname, the node name is foobar@Host, where Host is the short name of the host (not the fully qualified domain name). If it's started with the flag -name, the node name is foobar@Host, where Host is the fully qualified domain name. For more info, see erl.

Normally, connections just establish themselves automatically whenever another node gets referenced. If you wanna turn that off you can set the Kernel configuration parameter dist_auto_connect to never, see kernel(6). In that case, connections have to be established explicitly by calling connect_node/1.

Which nodes are allowed to talk to each other is handled by the magic cookie system, see the section Distributed Erlang in the Erlang Reference Manual. πŸͺ✨

⚠️ WARNING: Starting a distributed node without also specifying -proto_dist inet_tls will expose the node to attacks that may give the attacker complete access to the node and, in extension, the cluster πŸ˜­πŸ’€ This is NOT the vibe. When using un-secure distributed nodes, make sure that the network is configured to keep potential attackers out. See the Using SSL for Erlang Distribution User's Guide for details on how to set up a secure distributed node. Lock it down bestie πŸ”’

πŸŽ€ DATA TYPES πŸŽ€

connection_state() :: check_pending | pending | up | up_pending.

connection_type() :: normal | hidden.

πŸ’… FUNCTIONS πŸ’…

allow(Nodes) -> ok | error | ignored when Nodes :: [node()]

Permits access to the specified set of nodes. She's the bouncer at the club πŸšͺ

Before the very first call to allow/1, any node with the correct cookie can connect. But the moment allow/1 gets called, a list of allowed nodes is established. Any access attempts made from (or to) nodes that are not in that list will be rejected. Sorry babe, not on the list πŸ’…

Subsequent calls to allow/1 will add the specified nodes to the list of allowed nodes. It is not possible to remove nodes from the list. Once you're in, you're in β€” no takebacks πŸ™ˆ

Disallowing an already connected node will not cause it to be disconnected. It will, however, prevent any future reconnection attempts. So they can stay for the party but they're not getting back in later βœ‹

Passing Nodes as an empty list has never any affect at all. Like literally zero. Nothing happens.

Returns error if any element in Nodes is not an atom, and ignored if the local node is not alive.


allowed() -> {ok, Nodes} | ignored when Nodes :: [node()]

Returns a list of nodes that are explicitly allowed to connect to the node by calling allow/1. If an empty list comes back, that means any node using the same cookie will be able to connect. It's giving open door policy πŸ¦‹


connect_node(Node) -> boolean() | ignored when Node :: node()

Establishes a connection to Node. Manifesting that link ✨

Returns true if a connection was established, or was already established, or if Node is the local node itself. Returns false if the connection attempt failed, and ignored if the local node is not alive.


get_net_ticktime() -> Res

when
    Res ::
        NetTicktime |
        {ongoing_change_to, NetTicktime} |
        ignored,
    NetTicktime :: pos_integer().

Returns the currently used net tick time, in seconds ⏱️

For more info, see the net_ticktime Kernel parameter.

Defined return values (Res):

  • NetTicktime β€” net_ticktime is NetTicktime seconds. Simple as that.
  • {ongoing_change_to, NetTicktime} β€” net_kernel is currently changing net_ticktime to NetTicktime seconds. Mid-transition, don't rush her 🚧
  • ignored β€” the local node is not alive.

get_state() -> #{started => no | static | dynamic, name => atom(), name_type => static | dynamic, name_domain => shortnames | longnames}

get_state() ->
                    #{started => no | static | dynamic,
                      name => atom(),
                      name_type => static | dynamic,
                      name_domain => shortnames | longnames}.

Get the current state of the distribution for the local node. A little vibe check on the whole setup πŸ“‹

Returns a map with (at least) the following key-value pairs:

  • started => Started β€” valid values for Started:
    • no β€” the distribution is not started. In this state none of the other keys below are present in the map. Empty map energy.
    • static β€” the distribution was started with the command line arguments -name or -sname.
    • dynamic β€” the distribution was started with net_kernel:start/1 and can be stopped with net_kernel:stop/0.
  • name => Name β€” the name of the node. Same as returned by erlang:node/0, except when name_type is dynamic, in which case Name may be undefined (instead of nonode@nohost).
  • name_type => NameType β€” valid values for NameType:
    • static β€” the node has a static node name set by the node itself.
    • dynamic β€” the distribution was started in dynamic node name mode, and will get its node name assigned from the first node it connects to. If key name has value undefined, that has not happened yet. She's still waiting on her name to arrive πŸ¦‹
  • name_domain => NameDomain β€” valid values for NameDomain:
    • shortnames β€” the distribution was started to use node names with a short host portion (not fully qualified).
    • longnames β€” the distribution was started to use node names with a long fully qualified host portion.

getopts(Node, Options) -> {ok, OptionValues} | {error, Reason} | ignored

when
    Node :: node(),
    Options :: [inet:socket_getopt()],
    OptionValues :: [inet:socket_setopt()],
    Reason :: inet:posix() | noconnection.

Get one or more options for the distribution socket connected to Node πŸ”Œ

If Node is a connected node, the return value is the same as from inet:getopts(Sock, Options) where Sock is the distribution socket for Node.

Returns ignored if the local node is not alive, or {error, noconnection} if Node is not connected.


monitor_nodes(Flag, Options) -> ok | Error

when
    Flag :: boolean(),
    Options :: OptionsList | OptionsMap,
    OptionsList :: [ListOption],
    ListOption ::
        connection_id |
        {node_type, NodeType} |
        nodedown_reason,
    OptionsMap ::
        #{connection_id => boolean(),
          node_type => NodeType,
          nodedown_reason => boolean()},
    NodeType :: visible | hidden | all,
    Error :: error | {error, term()}.

The calling process subscribes or unsubscribes to node status change messages πŸ‘€ A nodeup message is delivered to all subscribing processes when a new node is connected, and a nodedown message is delivered when a node is disconnected. It's giving notifications turned ON πŸ””

If Flag is true, a new subscription is started. If Flag is false, all previous subscriptions started with the same Options are stopped. Two option lists are considered the same if they contain the same set of options.

Delivery guarantees of nodeup/nodedown messages β€” these are lowkey sacred, do not skip:

  • nodeup messages are delivered before delivery of any signals from the remote node through the newly established connection.
  • nodedown messages are delivered after all the signals from the remote node over the connection have been delivered.
  • nodeup messages are delivered after the corresponding node appears in results from erlang:nodes().
  • nodedown messages are delivered after the corresponding node has disappeared in results from erlang:nodes().
  • As of OTP 23.0, a nodedown message for a connection being taken down will be delivered before a nodeup message due to a new connection to the same node. Prior to OTP 23.0, this was not guaranteed to be the case. (old behaviour walked so new behaviour could run πŸƒβ™€οΈ)

The format of the node status change messages depends on Options. If Options is the empty list, or if net_kernel:monitor_nodes/1 is called, the format is as follows:

{nodeup, Node} | {nodedown, Node}
  Node = node()

When Options is the empty map or empty list, the caller will only subscribe for status change messages for visible nodes. That is, only nodes that appear in the result of erlang:nodes/0.

If Options equals anything other than the empty list, the format of the status change messages is as follows:

{nodeup, Node, Info} | {nodedown, Node, Info}
  Node = node()
  Info = #{Tag => Val} | [{Tag, Val}]

Info is either a map or a list of 2-tuples. Its content depends on Options. If Options is a map, Info will also be a map. If Options is a list, Info will also be a list. Matching energy, always πŸ’–

When Options is a map, currently the following associations are allowed:

  • connection_id => boolean() β€” if the value of the association equals true, a connection_id => ConnectionId association will be included in the Info map, where ConnectionId is the connection identifier of the connection coming up or going down. For more info about this connection identifier, see the documentation of erlang:nodes/2.
  • node_type => NodeType β€” valid values for NodeType:
    • visible β€” subscribe to node status change messages for visible nodes only. The association node_type => visible will be included in the Info map.
    • hidden β€” subscribe to node status change messages for hidden nodes only. The association node_type => hidden will be included in the Info map.
  • all β€” bestie, you want everything. Subscribe to node status change messages for visible and hidden nodes. The association node_type => visible | hidden will be included in the Info map. πŸ‘‘

If no node_type => NodeType association is included in the Options map, the caller will subscribe for status change messages for visible nodes only, but no node_type => visible association will be included in the Info map. (Yes, that's the sneaky default. Visible-only, and it doesn't even tell you. πŸ™ˆ)

  • nodedown_reason => boolean() β€” set this association's value to true and a nodedown_reason => Reason association will be included in the Info map for nodedown messages. You deserve the lore, not just the headline. πŸ’…

Reason can, depending on which distribution module or process that is used, be any term, but for the standard TCP distribution module it is one of the following:

  • connection_setup_failed β€” the connection setup failed (after nodeup messages were sent).
  • no_network β€” no network is available.
  • net_kernel_terminated β€” the net_kernel process terminated.
  • shutdown β€” unspecified connection shutdown.
  • connection_closed β€” the connection was closed.
  • disconnect β€” the connection was disconnected (forced from the current node).
  • net_tick_timeout β€” net tick time-out.
  • send_net_tick_failed β€” failed to send net tick over the connection.
  • get_status_failed β€” status information retrieval from the Port holding the connection failed.

When Options is a list, currently ListOption can be one of the following:

  • connection_id β€” a {connection_id, ConnectionId} tuple will be included in Info where ConnectionId is the connection identifier of the connection coming up or going down. For more info about this connection identifier see the documentation of erlang:nodes/2. It's giving receipts, but make them official ones. 🧾
  • {node_type, NodeType} β€” valid values for NodeType:
    • visible β€” subscribe to node status change messages for visible nodes only. The tuple {node_type, visible} will be included in the Info list.
    • hidden β€” subscribe to node status change messages for hidden nodes only. The tuple {node_type, hidden} will be included in the Info list.
    • all β€” subscribe to node status change messages for both visible and hidden nodes. The tuple {node_type, visible | hidden} will be included in the Info list.

If no {node_type, NodeType} option has been given. The caller will subscribe for status change messages for visible nodes only, but no {node_type, visible} tuple will be included in the Info list.

  • nodedown_reason β€” the tuple {nodedown_reason, Reason} will be included in the Info list for nodedown messages. See the documentation of the nodedown_reason => boolean() association above for information about possible Reason values. Yes, same energy, list edition. πŸŽ€

Example:

(a@localhost)1> net_kernel:monitor_nodes(true, #{connection_id=>true, node_type=>all, nodedown_reason=>true}).
ok
(a@localhost)2> flush().
Shell got {nodeup,b@localhost,
                  #{connection_id => 3067552,node_type => visible}}
Shell got {nodeup,c@localhost,
                  #{connection_id => 13892107,node_type => hidden}}
Shell got {nodedown,b@localhost,
                    #{connection_id => 3067552,node_type => visible,
                      nodedown_reason => connection_closed}}
Shell got {nodedown,c@localhost,
                    #{connection_id => 13892107,node_type => hidden,
                      nodedown_reason => net_tick_timeout}}
Shell got {nodeup,b@localhost,
                  #{connection_id => 3067553,node_type => visible}}
ok
(a@localhost)3>

✨ set_net_ticktime(NetTicktime, TransitionPeriod) -> Res ✨

set_net_ticktime(NetTicktime, TransitionPeriod) -> Res
                          when
                              NetTicktime :: pos_integer(),
                              TransitionPeriod :: non_neg_integer(),
                              Res ::
                                  unchanged | change_initiated |
                                  {ongoing_change_to, NewNetTicktime},
                              NewNetTicktime :: pos_integer().

Sets net_ticktime (see kernel(6)) to NetTicktime seconds. TransitionPeriod defaults to 60. Slay, we're doing a whole coordinated vibe shift. 🌈

Some definitions, bestie, write these down:

  • Minimum transition traffic interval (MTTI) β€” minimum(NetTicktime, PreviousNetTicktime)*1000 div 4 milliseconds.
  • Transition period β€” the time of the least number of consecutive MTTIs to cover TransitionPeriod seconds following the call to set_net_ticktime/2 (that is, ((TransitionPeriod*1000 - 1) div MTTI + 1)*MTTI milliseconds).

If NetTicktime < PreviousNetTicktime, the net_ticktime change is done at the end of the transition period; otherwise at the beginning. During the transition period, net_kernel ensures that there is outgoing traffic on all connections at least every MTTI millisecond. It's literally keeping the convo alive on every single line. 🫢

NOTE: The net_ticktime changes must be initiated on all nodes in the network (with the same NetTicktime) before the end of any transition period on any node; otherwise connections can erroneously be disconnected. No cap, this one bites. Do it everywhere or do it nowhere. πŸ’€

Returns one of the following:

  • unchanged β€” net_ticktime already has the value of NetTicktime and is left unchanged. Nothing to do, iconic behaviour honestly.
  • change_initiated β€” net_kernel initiated the change of net_ticktime to NetTicktime seconds.
  • {ongoing_change_to, NewNetTicktime} β€” the request is ignored because net_kernel is busy changing net_ticktime to NewNetTicktime seconds. It's in its main character era, please hold. ⏳

✨ setopts(Node, Options) -> ok | {error, Reason} | ignored ✨

setopts(Node, Options) -> ok | {error, Reason} | ignored
                 when
                     Node :: node() | new,
                     Options :: [inet:socket_setopt()],
                     Reason :: inet:posix() | noconnection.

Set one or more options for distribution sockets. Argument Node can be either one node name or the atom new to affect the distribution sockets of all future connected nodes. Yes, new means "and also all my future friendships." πŸ¦‹

The return value is the same as from inet:setopts/2 or {error, noconnection} if Node is not a connected node or new.

If Node is new the Options will then also be added to kernel configuration parameters inet_dist_listen_options and inet_dist_connect_options. Double-dipping, but make it configuration. πŸ’…

Returns ignored if the local node is not alive. Ghost mode = request denied. πŸ‘»


✨ start(Options) -> {ok, pid()} | {error, Reason} ✨

start(Options) -> {ok, pid()} | {error, Reason}
               when
                   Options :: [Name | NameDomain | TickTime, ...],
                   Name :: atom(),
                   NameDomain :: shortnames | longnames,
                   TickTime :: pos_integer(),
                   Reason :: {already_started, pid()} | term().
Deprecated: Use start/2 instead β€” she's retired but we still tell her story. πŸ•°οΈ

Turns a non-distributed node into a distributed node by starting net_kernel and other necessary processes. Glow-up time. ✨

Options list can only be exactly one of the following lists (order is important, so don't shuffle it like a playlist):

  • [Name] β€” the same as net_kernel:start([Name, longnames, 15000]).
  • [Name, NameDomain] β€” the same as net_kernel:start([Name, NameDomain, 15000]).
  • [Name, NameDomain, TickTime] β€” the same as net_kernel:start(Name, #{name_domain => NameDomain, net_ticktime => ((TickTime*4-1) div 1000) + 1, net_tickintensity => 4}). Note that TickTime is not the same as net tick time expressed in milliseconds. TickTime is the time between ticks when net tick intensity equals 4.

✨ start(Name, Options) -> {ok, pid()} | {error, Reason} ✨

start(Name, Options) -> {ok, pid()} | {error, Reason}
               when
                   Options ::
                       #{name_domain => NameDomain,
                         net_ticktime => NetTickTime,
                         net_tickintensity => NetTickIntensity,
                         dist_listen => boolean(),
                         hidden => boolean()},
                   Name :: atom(),
                   NameDomain :: shortnames | longnames,
                   NetTickTime :: pos_integer(),
                   NetTickIntensity :: 4..1000,
                   Reason :: {already_started, pid()} | term().

Turns a non-distributed node into a distributed node by starting net_kernel and other necessary processes. The non-deprecated, thriving version. πŸ’–

If Name is set to undefined the distribution will be started to request a dynamic node name from the first node it connects to. See Dynamic Node Name. Setting Name to undefined implies options dist_listen => false and hidden => true. Mystery era, but make it intentional. 🎭

Currently supported options:

  • name_domain => NameDomain β€” determines the host name part of the node name. If NameDomain equals longnames, fully qualified domain names will be used which also is the default. If NameDomain equals shortnames, only the short name of the host will be used. Full legal name or cute nickname, your call. πŸŽ€
  • net_ticktime => NetTickTime β€” net tick time to use in seconds. Defaults to the value of the net_ticktime kernel(6) parameter. For more information about net tick time, see the kernel parameter. However, note that if the value of the kernel parameter is invalid, it will silently be replaced by a valid value, but if an invalid NetTickTime value is passed as option value to this function, the call will fail. So the kernel parameter gets a free pass and you don't. Unfair? Maybe. Real? Absolutely. 😭
  • net_tickintensity => NetTickIntensity β€” net tick intensity to use. Defaults to the value of the net_tickintensity kernel(6) parameter. For more information about net tick intensity, see the kernel parameter. However, note that if the value of the kernel parameter is invalid, it will silently be replaced by a valid value, but if an invalid NetTickIntensity value is passed as option value to this function, the call will fail. Same plot twist as above, different option. πŸ™ˆ
  • dist_listen => boolean() β€” turn listening for incoming connections on or off. Defaults to whatever the -dist_listen erl command line argument says. Heads up bestie: dist_listen => false implies hidden => true, no takebacks πŸ’…

And if undefined got passed as Name? Then the dist_listen option gets overridden with dist_listen => false. Periodt. πŸ™ˆ

  • hidden => boolean() β€” turn hidden node on/off. Defaults to true if the -hidden erl command line argument has been passed; otherwise it's false.

⚠️ overridden alert ⚠️ if undefined was passed as Name, or if the option dist_listen equals false, then the hidden option gets overridden with hidden => true. The vibes override your flags, sorry not sorry πŸŽ€


πŸ’« stop() πŸ’«

stop() -> ok | {error, Reason}
              when Reason :: not_allowed | not_found.

Okay so this one's a whole glow-down on purpose β€” it turns a distributed node into a non-distributed node. πŸ¦‹

For every other node out there in the network, this is literally the same as your node going down. Not "kind of similar", not "close enough" β€” the same. Nobody else can tell the difference. πŸ’€

It is only possible when the net kernel was started using start/2. Try it any other way and you get {error, not_allowed} returned to you. Rude, but fair.

And if the local node isn't alive? You get {error, not_found}. Can't stop what isn't running, bestie 😭


That's the whole page, babes. Go forth and distribute responsibly πŸ‘‘

This one came to you straight from Ericsson AB, rockin' kernel 11.0.3 β€” the net_kernel(3) era, and yes she ate πŸ’–βœ¨

πŸ“¦ where this page comes from