-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Expand file tree
/
Copy pathnet_kernel.erl
More file actions
3126 lines (2750 loc) · 109 KB
/
net_kernel.erl
File metadata and controls
3126 lines (2750 loc) · 109 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
%%
%% %CopyrightBegin%
%%
%% SPDX-License-Identifier: Apache-2.0
%%
%% Copyright Ericsson AB 1996-2025. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(net_kernel).
-moduledoc """
Erlang networking kernel.
The net kernel is a system process, registered as `net_kernel`, which must be
operational for distributed Erlang to work. The purpose of this process is to
implement parts of the BIFs [`spawn/4`](`spawn/4`) and
[`spawn_link/4`](`spawn_link/4`), and to provide monitoring of the network.
An Erlang node is started using command-line flag `-name` or `-sname`:
```text
$ erl -sname foobar
```
It is also possible to call [`net_kernel:start(foobar, #{})`](`start/2`)
directly from the normal Erlang shell prompt:
```erlang
1> net_kernel:start(foobar, #{name_domain => shortnames}).
{ok,<0.64.0>}
(foobar@gringotts)2>
```
If the node is started with 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 started with flag `-name`, the node name is
`foobar@Host`, where `Host` is the fully qualified domain name. For more
information, see [`erl`](`e:erts:erl_cmd.md`).
Normally, connections are established automatically when another node is
referenced. This functionality can be disabled by setting Kernel configuration
parameter `dist_auto_connect` to `never`, see [`kernel(6)`](kernel_app.md). In
this case, connections must be established explicitly by calling
`connect_node/1`.
Which nodes that are allowed to communicate with each other is handled by the
magic cookie system, see section [Distributed Erlang](`e:system:distributed.md`)
in the Erlang Reference Manual.
> #### Warning {: .warning }
>
> Starting a distributed node without also specifying
> [`-proto_dist inet_tls`](`e:erts:erl_cmd.md#proto_dist`) will expose the node
> to attacks that may give the attacker complete access to the node and in
> extension the cluster. 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](`e:ssl:ssl_distribution.md`) User's Guide
> for details on how to setup a secure distributed node.
""".
-compile(nowarn_deprecated_catch).
-behaviour(gen_server).
-define(nodedown(N, State), verbose({?MODULE, ?LINE, nodedown, N}, 1, State)).
%%-define(nodeup(N, State), verbose({?MODULE, ?LINE, nodeup, N}, 1, State)).
%%-define(dist_debug, true).
-ifdef(dist_debug).
-define(debug(Term), erlang:display(Term)).
-else.
-define(debug(Term), ok).
-endif.
-ifdef(dist_debug).
-define(connect_failure(Node,Term),
io:format("Net Kernel 2: Failed connection to node ~p, reason ~p~n",
[Node,Term])).
-else.
-define(connect_failure(Node,Term),noop).
-endif.
%% Default ticktime change transition period in seconds
-define(DEFAULT_TRANSITION_PERIOD, 60).
%-define(TCKR_DBG, 1).
-ifdef(TCKR_DBG).
-define(tckr_dbg(X), erlang:display({?LINE, X})).
-else.
-define(tckr_dbg(X), ok).
-endif.
%% Documented API functions.
-export([allow/1, allowed/0,
connect_node/1,
monitor_nodes/1,
monitor_nodes/2,
setopts/2,
getopts/2,
start/2,
start/1,
stop/0]).
%% Exports for internal use.
-export([start_link/1,
kernel_apply/3,
longnames/0,
nodename/0,
protocol_childspecs/0,
epmd_module/0,
get_state/0]).
-export([disconnect/1, async_disconnect/1, passive_cnct/1]).
-export([hidden_connect_node/1]).
-export([set_net_ticktime/1, set_net_ticktime/2, get_net_ticktime/0]).
-export([node_info/1, node_info/2, nodes_info/0,
connecttime/0,
i/0, i/1, verbose/1]).
-export([publish_on_node/1]).
%% Internal exports for spawning processes.
-export([do_spawn/3,
spawn_func/6,
ticker/2,
ticker_loop/2,
aux_ticker/4]).
-export([init/1,handle_call/3,handle_cast/2,handle_info/2,
terminate/2,code_change/3]).
-export([passive_connect_monitor/2]).
-import(error_logger,[error_msg/2]).
-type node_name_type() :: static | dynamic.
-record(state, {
node, %% The node name including hostname
type, %% long or short names
tick, %% tick information
connecttime, %% the connection setuptime.
connections, %% table of connections
conn_owners = #{}, %% Map of connection owner pids,
dist_ctrlrs = #{}, %% Map of dist controllers (local ports or pids),
pend_owners = #{}, %% Map of potential owners
listen, %% list of #listen
allowed, %% list of allowed nodes in a restricted system
verbose = 0, %% level of verboseness
dyn_name_pool = #{}, %% Reusable remote node names: #{Host => [{Name,Creation}]}
supervisor, %% Our supervisor (net_sup | net_sup_dynamic | {restart,Restarter})
req_map = #{} %% Map for outstanding async requests
}).
-record(listen, {
listen, %% listen socket
accept, %% accepting pid
address, %% #net_address
module %% proto module
}).
-define(LISTEN_ID, #listen.listen).
-define(ACCEPT_ID, #listen.accept).
-type connection_state() :: check_pending | pending | up | up_pending.
-type connection_type() :: normal | hidden.
-include("net_address.hrl").
-include_lib("kernel/include/logger.hrl").
%% Relaxed typing to allow ets:select without Dialyzer complains.
-record(connection, {
node :: node() | '_', %% remote node name
conn_id, %% Connection identity
state :: connection_state() | '_',
owner :: pid() | '_', %% owner pid
ctrlr, %% Controller port or pid
pending_owner :: pid() | '_' | undefined, %% possible new owner
address = #net_address{} :: #net_address{} | '_',
waiting = [], %% queued processes
type :: connection_type() | '_',
remote_name_type :: node_name_type() | '_',
creation :: integer() | '_' | undefined, %% only set if remote_name_type == dynamic
named_me = false :: boolean() | '_' %% did peer gave me my node name?
}).
-record(barred_connection, {
node %% remote node name
}).
-record(tick,
{ticker :: pid(), %% ticker
time :: pos_integer(), %% net tick time (ms)
intensity :: 4..1000 %% ticks until timeout
}).
-record(tick_change,
{ticker :: pid(), %% ticker
time :: pos_integer(), %% net tick time (ms)
intensity :: 4..1000, %% ticks until timeout
how :: 'longer' | 'shorter' %% What type of change
}).
%% Default connection setup timeout in milliseconds.
%% This timeout is set for every distributed action during
%% the connection setup.
-define(SETUPTIME, 7000).
%%% BIF
-export([dflag_unicode_io/1]).
-doc false.
-spec dflag_unicode_io(pid()) -> boolean().
dflag_unicode_io(_) ->
erlang:nif_error(undef).
%%% End of BIF
%% Interface functions
-doc false.
kernel_apply(M,F,A) -> request({apply,M,F,A}).
-doc """
Permits access to the specified set of nodes.
Before the first call to [`allow/1`](`allow/1`), any node with the correct
cookie can be connected. When [`allow/1`](`allow/1`) is called, a list of
allowed nodes is established. Any access attempts made from (or to) nodes not in
that list will be rejected.
Subsequent calls to [`allow/1`](`allow/1`) will add the specified nodes to the
list of allowed nodes. It is not possible to remove nodes from the list.
Disallowing an already connected node will not cause it to be disconnected. It
will, however, prevent any future reconnection attempts.
Passing `Nodes` as an empty list has never any affect at all.
Returns `error` if any element in `Nodes` is not an atom, and `ignored` if the
local node is not alive.
""".
-spec allow(Nodes) -> ok | error | ignored when
Nodes :: [node()].
allow(Nodes) -> request({allow, Nodes}).
-doc """
Returns a list of nodes that are explicitly allowed to connect to the node by calling
[`allow/1`](`allow/1`). If empty list is returned, it means that any node using the
same cookie will be able to connect.
""".
-doc(#{since => <<"OTP 28.0">>}).
-spec allowed() -> {ok, Nodes} | ignored when
Nodes :: [node()].
allowed() -> request(allowed).
-doc false.
longnames() -> request(longnames).
-doc false.
nodename() -> request(nodename).
-doc """
Get the current state of the distribution for the local node.
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.
- **`static`** - The distribution was started with command line arguments
[`-name`](`e:erts:erl_cmd.md#name`) or
[`-sname`](`e:erts:erl_cmd.md#sname`).
- **`dynamic`** - The distribution was started with
[`net_kernel:start/1`](`start/1`) and can be stopped with
[`net_kernel:stop/0`](`start/1`).
- **`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](`e:system:distributed.md#dyn_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.
- **`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.
""".
-doc(#{since => <<"OTP 25.0">>}).
-spec get_state() -> #{started => no | static | dynamic,
name => atom(),
name_type => static | dynamic,
name_domain => shortnames | longnames}.
get_state() ->
case whereis(net_kernel) of
undefined ->
case retry_request_maybe(get_state) of
ignored ->
#{started => no};
Reply ->
Reply
end;
_ ->
request(get_state)
end.
-doc """
Turns a distributed node into a non-distributed node.
For other nodes in the network, this is the same as the node going down.
Only possible when the net kernel was started using `start/2`, otherwise
`{error, not_allowed}` is returned. Returns `{error, not_found}` if the local
node is not alive.
""".
-spec stop() -> ok | {error, Reason} when
Reason :: not_allowed | not_found.
stop() ->
erl_distribution:stop().
-type node_info() ::
{address, #net_address{}} |
{type, connection_type()} |
{in, non_neg_integer()} |
{out, non_neg_integer()} |
{owner, pid()} |
{state, connection_state()}.
-doc false.
-spec node_info(node()) -> {ok, [node_info()]} | {error, bad_node}.
node_info(Node) ->
get_node_info(Node).
-doc false.
-spec node_info(node(), address) -> {ok, Address} | {error, bad_node} when Address :: #net_address{};
(node(), type) -> {ok, Type} | {error, bad_node} when Type :: connection_type();
(node(), in | out) -> {ok, Bytes} | {error, bad_node} when Bytes :: non_neg_integer();
(node(), owner) -> {ok, Owner} | {error, bad_node} when Owner :: pid();
(node(), state) -> {ok, State} | {error, bad_node} when State :: connection_state().
%(node(), term()) -> {error, invalid_key} | {error, bad_node}.
node_info(Node, Key) ->
get_node_info(Node, Key).
-doc false.
-spec nodes_info() -> {ok, [{node(), [node_info()]}]}.
nodes_info() ->
get_nodes_info().
-doc false.
i() -> print_info().
-doc false.
i(Node) -> print_info(Node).
-doc false.
verbose(Level) when is_integer(Level) ->
request({verbose, Level}).
-doc """
Sets `net_ticktime` (see [`kernel(6)`](kernel_app.md)) to `NetTicktime` seconds.
`TransitionPeriod` defaults to `60`.
Some definitions:
- **Minimum transition traffic interval (`MTTI`)** -
`minimum(NetTicktime, PreviousNetTicktime)*1000 div 4` milliseconds.
- **Transition period** - The time of the least number of consecutive `MTTI`s to
cover `TransitionPeriod` seconds following the call to
[`set_net_ticktime/2`](`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.
> #### Note {: .info }
>
> 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.
Returns one of the following:
- **`unchanged`** - `net_ticktime` already has the value of `NetTicktime` and is
left unchanged.
- **`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.
""".
-spec 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().
set_net_ticktime(T, TP) when is_integer(T), T > 0, is_integer(TP), TP >= 0 ->
ticktime_res(request({new_ticktime, T*1000, TP*1000})).
-doc(#{equiv => set_net_ticktime(NetTicktime, ?DEFAULT_TRANSITION_PERIOD)}).
-spec set_net_ticktime(NetTicktime) -> Res when
NetTicktime :: pos_integer(),
Res :: unchanged
| change_initiated
| {ongoing_change_to, NewNetTicktime},
NewNetTicktime :: pos_integer().
set_net_ticktime(T) when is_integer(T) ->
set_net_ticktime(T, ?DEFAULT_TRANSITION_PERIOD).
-doc """
Returns currently used net tick time in seconds.
For more information see the [`net_ticktime`](kernel_app.md#net_ticktime)
`Kernel` parameter.
Defined return values (`Res`):
- **`NetTicktime`** - `net_ticktime` is `NetTicktime` seconds.
- **`{ongoing_change_to, NetTicktime}`** - `net_kernel` is currently changing
`net_ticktime` to `NetTicktime` seconds.
- **`ignored`** - The local node is not alive.
""".
-spec get_net_ticktime() -> Res when
Res :: NetTicktime | {ongoing_change_to, NetTicktime} | ignored,
NetTicktime :: pos_integer().
get_net_ticktime() ->
ticktime_res(request(ticktime)).
%% The monitor_nodes() feature has been moved into the emulator.
%% The feature is reached via (intentionally) undocumented process
%% flags (we may want to move it elsewhere later). In order to easily
%% be backward compatible, errors are created here when process_flag()
%% fails.
-doc(#{equiv => monitor_nodes(Flag, [])}).
-spec monitor_nodes(Flag) -> ok | Error when
Flag :: boolean(),
Error :: error | {error, term()}.
monitor_nodes(Flag) ->
case catch process_flag(monitor_nodes, Flag) of
N when is_integer(N) -> ok;
_ -> mk_monitor_nodes_error(Flag, [])
end.
-doc """
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.
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:
- `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.
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:
```erlang
{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:
```erlang
{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.
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](`m:erlang#connection_id`).
- **`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`** - Subscribe to node status change messages for both 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.
- **`nodedown_reason => boolean()`** - If the value of the association equals
`true`, a `nodedown_reason => Reason` association will be included in the
`Info` map for `nodedown` messages.
[](){: #nodedown_reasons } `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](`m:erlang#connection_id`).
- **`{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()`](`m:net_kernel#nodedown_reasons`) association
above for information about possible `Reason` values.
Example:
```erlang
(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>
```
""".
-spec 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()}.
monitor_nodes(Flag, Opts) ->
try
MapOpts = if is_map(Opts) ->
error = maps:find(list, Opts),
Opts;
is_list(Opts) ->
lists:foldl(fun (nodedown_reason, Acc) ->
Acc#{nodedown_reason => true};
(connection_id, Acc) ->
Acc#{connection_id => true};
({node_type, Val}, Acc) ->
case maps:find(node_type, Acc) of
error -> ok;
{ok, Val} -> ok
end,
Acc#{node_type => Val}
end,
#{list => true},
Opts)
end,
true = is_integer(process_flag({monitor_nodes, MapOpts}, Flag)),
ok
catch
_:_ ->
mk_monitor_nodes_error(Flag, Opts)
end.
%% ...
ticktime_res({A, I}) when is_atom(A), is_integer(I) -> {A, I div 1000};
ticktime_res(I) when is_integer(I) -> I div 1000;
ticktime_res(A) when is_atom(A) -> A.
%% Called though BIF's
%%% Long timeout if blocked (== barred), only affects nodes with
%%% {dist_auto_connect, once} set.
-doc false.
passive_cnct(Node) ->
case request({passive_cnct, Node}) of
ignored -> false;
Other -> Other
end.
-doc false.
disconnect(Node) -> request({disconnect, Node}).
-doc false.
async_disconnect(Node) ->
gen_server:cast(net_kernel, {async_disconnect, Node}).
%% Should this node publish itself on Node?
-doc false.
publish_on_node(Node) when is_atom(Node) ->
global_group:publish(persistent_term:get({?MODULE, publish_type},
hidden),
Node).
-doc """
Establishes a connection to `Node`.
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.
""".
-spec connect_node(Node) -> boolean() | ignored when
Node :: node().
%% explicit connects
connect_node(Node) when is_atom(Node) ->
request({connect, normal, Node}).
-doc false.
hidden_connect_node(Node) when is_atom(Node) ->
request({connect, hidden, Node}).
-doc false.
passive_connect_monitor(From, Node) ->
ok = monitor_nodes(true,[{node_type,all}]),
Reply = case lists:member(Node,nodes([connected])) of
true ->
true;
_ ->
receive
{nodeup,Node,_} ->
true
after connecttime() ->
false
end
end,
ok = monitor_nodes(false,[{node_type,all}]),
{Pid, Tag} = From,
erlang:send(Pid, {Tag, Reply}).
%% If the net_kernel isn't running we ignore all requests to the
%% kernel, thus basically accepting them :-)
request(Req) ->
case whereis(net_kernel) of
P when is_pid(P) ->
try
gen_server:call(net_kernel,Req,infinity)
catch
exit:{Reason,_} when Reason =:= noproc;
Reason =:= shutdown;
Reason =:= killed ->
retry_request_maybe(Req)
end;
_ ->
retry_request_maybe(Req)
end.
retry_request_maybe(Req) ->
case erts_internal:dynamic_node_name() of
true ->
%% net_kernel must be restarting due to lost connection
%% toward the node that named us.
%% We want reconnection attempts to succeed so we wait and retry.
receive after 100 -> ok end,
request(Req);
false ->
ignored
end.
%% This function is used to dynamically start the
%% distribution.
-doc """
Turns a non-distributed node into a distributed node by starting `net_kernel`
and other necessary processes.
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](`e:system:distributed.md#dyn_node_name`). Setting `Name` to
`undefined` implies options `dist_listen => false` and `hidden => true`.
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.
- **`net_ticktime => NetTickTime`** - _Net tick time_ to use in seconds.
Defaults to the value of the [`net_ticktime`](kernel_app.md#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.
- **`net_tickintensity => NetTickIntensity`** - _Net tick intensity_ to use.
Defaults to the value of the
[`net_tickintensity`](kernel_app.md#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.
- **`dist_listen => boolean()`** - Enable or disable listening for incoming
connections. Defaults to the value of the
[`-dist_listen`](`e:erts:erl_cmd.md#dist_listen`) `erl` command line argument.
Note that `dist_listen => false` implies `hidden => true`.
If `undefined` has been passed as `Name`, the `dist_listen` option will be
overridden with `dist_listen => false`.
- **`hidden => boolean()`** - Enable or disable hidden node. Defaults to `true`
if the [`-hidden`](`e:erts:erl_cmd.md#hidden`) `erl` command line argument has
been passed; otherwise `false`.
If `undefined` has been passed as `Name`, or the option `dist_listen` equals
`false`, the `hidden` option will be overridden with `hidden => true`.
""".
-doc(#{since => <<"OTP 24.3">>}).
-spec 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().
start(Name, Options) when is_atom(Name), is_map(Options) ->
try
maps:foreach(fun (name_domain, Val) when Val == shortnames;
Val == longnames ->
ok;
(net_ticktime, Val) when is_integer(Val),
Val > 0 ->
ok;
(net_tickintensity, Val) when is_integer(Val),
4 =< Val,
Val =< 1000 ->
ok;
(dist_listen, Val) when is_boolean(Val) ->
ok;
(hidden, Val) when is_boolean(Val) ->
ok;
(Opt, Val) ->
error({invalid_option, Opt, Val})
end, Options)
catch error:Reason ->
error(Reason, [Name, Options])
end,
erl_distribution:start(Options#{name => Name});
start(Name, Options) when is_map(Options) ->
error(invalid_name, [Name, Options]);
start(Name, Options) ->
error(invalid_options, [Name, Options]).
-doc """
Turns a non-distributed node into a distributed node by starting `net_kernel`
and other necessary processes.
`Options` list can only be exactly one of the following lists (order is
imporant):
- **`[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})`](`start/2`).
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`.
""".
-doc(#{ deprecated => ~"Use start/2 instead" }).
-spec start(Options) -> {ok, pid()} | {error, Reason} when
Options :: nonempty_list(Name | NameDomain | TickTime),
Name :: atom(),
NameDomain :: shortnames | longnames,
TickTime :: pos_integer(),
Reason :: {already_started, pid()} | term().
start([Name]) when is_atom(Name) ->
start([Name, longnames, 15000]);
start([Name, NameDomain]) when is_atom(Name),
is_atom(NameDomain) ->
start([Name, NameDomain, 15000]);
start([Name, NameDomain, TickTime]) when is_atom(Name),
is_atom(NameDomain),
is_integer(TickTime),
TickTime > 0 ->
%% NetTickTime is in seconds. TickTime is time in milliseconds
%% between ticks when net tick intensity is 4. We round upwards...
NetTickTime = ((TickTime*4-1) div 1000)+1,
start(Name, #{name_domain => NameDomain,
net_ticktime => NetTickTime,
net_tickintensity => 4}).
%% This is the main startup routine for net_kernel (only for internal
%% use) by the Kernel application.
-doc false.
start_link(StartOpts) ->
case gen_server:start_link({local, net_kernel}, ?MODULE,
make_init_opts(StartOpts), []) of
{ok, Pid} ->
{ok, Pid};
{error, {already_started, Pid}} ->
{ok, Pid};
_Error ->
exit(nodistribution)
end.
make_init_opts(Opts) ->
%% Net tick time given in seconds, but kept in milliseconds...
NTT1 = case maps:find(net_ticktime, Opts) of
{ok, NTT0} ->
NTT0*1000;
error ->
case application:get_env(kernel, net_ticktime) of
{ok, NTT0} when is_integer(NTT0), NTT0 < 1 ->
1000;
{ok, NTT0} when is_integer(NTT0) ->
NTT0*1000;
_ ->
60000
end
end,
NTI = case maps:find(net_tickintensity, Opts) of
{ok, NTI0} ->
NTI0;
error ->
case application:get_env(kernel, net_tickintensity) of
{ok, NTI0} when is_integer(NTI0), NTI0 < 4 ->
4;
{ok, NTI0} when is_integer(NTI0), NTI0 > 1000 ->
1000;
{ok, NTI0} when is_integer(NTI0) ->
NTI0;
_ ->
4
end
end,
%% Net tick time needs to be a multiple of net tick intensity;
%% round net tick time upwards if not...
NTT = if NTT1 rem NTI =:= 0 -> NTT1;
true -> ((NTT1 div NTI) + 1) * NTI
end,
ND = case maps:find(name_domain, Opts) of
{ok, ND0} ->
ND0;
error ->
longnames
end,
DL = case split_node(maps:get(name, Opts)) of
{"undefined", _} ->
%% dynamic node name implies dist_listen=false
false;
_ ->
case maps:find(dist_listen, Opts) of
error ->
dist_listen_argument();
{ok, false} ->
false;
_ ->
true
end
end,
H = case DL of
false ->
%% dist_listen=false implies hidden=true
true;
true ->
case maps:find(hidden, Opts) of
error ->
hidden_argument();
{ok, true} ->
true;
_ ->
false
end
end,
Opts#{net_ticktime => NTT,
net_tickintensity => NTI,
name_domain => ND,
dist_listen => DL,
hidden => H}.