/home/runner/work/eturnal/eturnal/_build/test/cover/ct/eturnal.html

1 %%% eturnal STUN/TURN server.
2 %%%
3 %%% Copyright (c) 2020-2022 Holger Weiss <holger@zedat.fu-berlin.de>.
4 %%% Copyright (c) 2020-2022 ProcessOne, SARL.
5 %%% All rights reserved.
6 %%%
7 %%% Licensed under the Apache License, Version 2.0 (the "License");
8 %%% you may not use this file except in compliance with the License.
9 %%% You may obtain a copy of the License at
10 %%%
11 %%% http://www.apache.org/licenses/LICENSE-2.0
12 %%%
13 %%% Unless required by applicable law or agreed to in writing, software
14 %%% distributed under the License is distributed on an "AS IS" BASIS,
15 %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 %%% See the License for the specific language governing permissions and
17 %%% limitations under the License.
18
19 -module(eturnal).
20 -behaviour(gen_server).
21 -export([start/0,
22 stop/0]).
23 -export([start_link/0]).
24 -export([init/1,
25 handle_call/3,
26 handle_cast/2,
27 handle_info/2,
28 terminate/2,
29 code_change/3]).
30 -export([init_config/0,
31 config_is_loaded/0,
32 run_hook/2,
33 get_password/2,
34 get_opt/1,
35 create_self_signed/1,
36 reload/3,
37 abort/1]).
38 -export_type([transport/0]).
39
40 -ifdef(EUNIT).
41 -include_lib("eunit/include/eunit.hrl").
42 -endif.
43 -include_lib("kernel/include/logger.hrl").
44 -define(PEM_FILE_NAME, "cert.pem").
45
46 -record(eturnal_state,
47 {listeners :: listeners(),
48 modules :: modules()}).
49
50 -type transport() :: udp | tcp | tls | auto.
51 -type listeners() :: [{inet:ip_address(), inet:port_number(), transport()}].
52 -type modules() :: [module()].
53 -type option() :: atom().
54 -type value() :: term().
55 -type config_changes() :: {[{option(), value()}],
56 [{option(), value()}],
57 [option()]}.
58 -type state() :: #eturnal_state{}.
59
60 %% API: non-release startup and shutdown (used by test suite).
61
62 -spec start() -> ok | {error, term()}.
63 start() ->
64 1 case application:ensure_all_started(eturnal) of
65 {ok, _Started} ->
66 1 ok;
67 {error, _Reason} = Err ->
68
:-(
Err
69 end.
70
71 -spec stop() -> ok | {error, term()}.
72 stop() ->
73 1 application:stop(eturnal).
74
75 %% API: supervisor callback.
76
77 -spec start_link() -> {ok, pid()} | ignore | {error, term()}.
78 start_link() ->
79 1 gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
80
81 %% API: gen_server callbacks.
82
83 -spec init(any()) -> {ok, state()}.
84 init(_Opts) ->
85 1 process_flag(trap_exit, true),
86 1 ok = eturnal_module:init(),
87 1 ok = log_relay_addresses(),
88 1 ok = log_control_listener(),
89 1 try
90 1 ok = ensure_run_dir(),
91 1 ok = check_turn_config(),
92 1 ok = check_proxy_config(),
93 1 _R = check_pem_file()
94 catch exit:Reason1 ->
95
:-(
abort(Reason1)
96 end,
97 1 try {start_modules(), start_listeners()} of
98 {Modules, Listeners} ->
99 1 ?LOG_DEBUG("Started ~B modules", [length(Modules)]),
100 1 ?LOG_DEBUG("Started ~B listeners", [length(Listeners)]),
101 1 {ok, #eturnal_state{listeners = Listeners, modules = Modules}}
102 catch exit:Reason2 ->
103
:-(
abort(Reason2)
104 end.
105
106 -spec handle_call(reload | get_info | get_version | get_loglevel |
107 {set_loglevel, eturnal_logger:level()} |
108 {get_password, binary()} | term(),
109 {pid(), term()}, state())
110 -> {reply, ok | {ok, term()} | {error, term()}, state()}.
111 handle_call(reload, _From, State) ->
112 2 case reload(State) of
113 {ok, State1} ->
114 2 {reply, ok, State1};
115 {error, _Reason} = Err ->
116
:-(
{reply, Err, State}
117 end;
118 handle_call(get_info, _From, State) ->
119 1 Info = eturnal_misc:info(),
120 1 {reply, {ok, Info}, State};
121 handle_call(get_version, _From, State) ->
122 1 Version = eturnal_misc:version(),
123 1 {reply, {ok, Version}, State};
124 handle_call(get_loglevel, _From, State) ->
125 2 Level = eturnal_logger:get_level(),
126 2 {reply, {ok, Level}, State};
127 handle_call({set_loglevel, Level}, _From, State) ->
128 1 try
129 1 ok = eturnal_logger:set_level(Level),
130 1 {reply, ok, State}
131 catch error:{badmatch, {error, _Reason} = Err} ->
132
:-(
{reply, Err, State}
133 end;
134 handle_call({get_password, Username}, _From, State) ->
135 11 case get_opt(secret) of
136 [Secret | _Secrets] ->
137 11 Password = derive_password(Username, [Secret]),
138 11 {reply, {ok, Password}, State};
139 undefined ->
140
:-(
{reply, {error, no_secret}, State}
141 end;
142 handle_call(Request, From, State) ->
143
:-(
?LOG_ERROR("Got unexpected request from ~p: ~p", [From, Request]),
144
:-(
{reply, {error, badarg}, State}.
145
146 -spec handle_cast(reload |
147 {config_change, config_changes(),
148 fun(() -> ok), fun(() -> ok)} | term(), state())
149 -> {noreply, state()}.
150 handle_cast(reload, State) ->
151
:-(
case reload(State) of
152 {ok, State1} ->
153
:-(
{noreply, State1};
154 {error, _Reason} ->
155
:-(
{noreply, State}
156 end;
157 handle_cast({config_change, Changes, BeginFun, EndFun}, State) ->
158
:-(
ok = BeginFun(),
159
:-(
State1 = apply_config_changes(State, Changes),
160
:-(
ok = EndFun(),
161
:-(
{noreply, State1};
162 handle_cast(Msg, State) ->
163
:-(
?LOG_ERROR("Got unexpected message: ~p", [Msg]),
164
:-(
{noreply, State}.
165
166 -spec handle_info(term(), state()) -> {noreply, state()}.
167 handle_info(Info, State) ->
168
:-(
?LOG_ERROR("Got unexpected info: ~p", [Info]),
169
:-(
{noreply, State}.
170
171 -spec terminate(normal | shutdown | {shutdown, term()} | term(), state()) -> ok.
172 terminate(Reason, State) ->
173 1 ?LOG_DEBUG("Terminating ~s (~p)", [?MODULE, Reason]),
174 1 try stop_listeners(State)
175 catch exit:Reason1 ->
176
:-(
?LOG_ERROR(format_error(Reason1))
177 end,
178 1 try stop_modules(State)
179 catch exit:Reason2 ->
180
:-(
?LOG_ERROR(format_error(Reason2))
181 end,
182 1 try clean_run_dir()
183 catch exit:Reason3 ->
184
:-(
?LOG_ERROR(format_error(Reason3))
185 end,
186 1 _ = eturnal_module:terminate(),
187 1 ok.
188
189 -spec code_change({down, term()} | term(), state(), term()) -> {ok, state()}.
190 code_change(_OldVsn, State, _Extra) ->
191
:-(
?LOG_NOTICE("Upgraded to eturnal ~s, reapplying configuration",
192
:-(
[eturnal_misc:version()]),
193
:-(
ok = reload_config(),
194
:-(
{ok, State}.
195
196 %% API: (re)load configuration.
197
198 -spec init_config() -> ok.
199 init_config() -> % Just to cope with an empty configuration file.
200 1 case config_is_loaded() of
201 true ->
202 1 ?LOG_DEBUG("Configuration has been loaded successfully"),
203 1 ok;
204 false ->
205
:-(
?LOG_DEBUG("Empty configuration, using defaults"),
206
:-(
ok = conf:load([{eturnal, []}])
207 end.
208
209 -spec config_is_loaded() -> boolean().
210 config_is_loaded() ->
211 1 try eturnal:get_opt(realm) of
212 Realm when is_binary(Realm) ->
213 1 true
214 catch error:{badmatch, undefined} ->
215
:-(
false
216 end.
217
218 %% API: stun callbacks.
219
220 -spec run_hook(eturnal_module:event(), eturnal_module:info()) -> ok.
221 run_hook(Event, Info) ->
222 7 eturnal_module:handle_event(Event, Info).
223
224 -spec get_password(binary(), binary())
225 -> binary() | [binary()] | {expired, binary() | [binary()]}.
226 get_password(Username, _Realm) ->
227 2 [Expiration | _Suffix] = binary:split(Username, <<$:>>),
228 2 try binary_to_integer(Expiration) of
229 ExpireTime ->
230 2 case erlang:system_time(second) of
231 Now when Now < ExpireTime ->
232 2 ?LOG_DEBUG("Looking up password for: ~ts", [Username]),
233 2 derive_password(Username, get_opt(secret));
234 Now when Now >= ExpireTime ->
235
:-(
case get_opt(strict_expiry) of
236 true ->
237
:-(
?LOG_INFO("Credentials expired: ~ts", [Username]),
238
:-(
<<>>;
239 false ->
240
:-(
?LOG_DEBUG("Credentials expired: ~ts", [Username]),
241
:-(
{expired,
242 derive_password(Username, get_opt(secret))}
243 end
244 end
245 catch _:badarg ->
246
:-(
?LOG_INFO("Non-numeric expiration field: ~ts", [Username]),
247
:-(
<<>>
248 end.
249
250 %% API: retrieve option value.
251
252 -spec get_opt(option()) -> value().
253 get_opt(Opt) ->
254 110 {ok, Val} = application:get_env(eturnal, Opt),
255 110 Val.
256
257 %% API: create self-signed certificate.
258
259 -spec create_self_signed(file:filename_all()) -> ok.
260 create_self_signed(File) ->
261 2 try
262 2 PEM = eturnal_cert:create(get_opt(realm)),
263 2 ok = touch(File),
264 2 ok = file:write_file(File, PEM, [raw])
265 catch error:{_, {error, Reason}} ->
266
:-(
exit({pem_failure, File, Reason})
267 end.
268
269 %% API: reload service.
270
271 -spec reload(config_changes(), fun(() -> ok), fun(() -> ok)) -> ok.
272 reload(ConfigChanges, BeginFun, EndFun) ->
273
:-(
Msg = {config_change, ConfigChanges, BeginFun, EndFun},
274
:-(
ok = gen_server:cast(?MODULE, Msg).
275
276 %% API: abnormal termination.
277
278 -spec abort(term()) -> no_return().
279 abort(Reason) ->
280
:-(
case application:get_env(eturnal, on_fail, halt) of
281 exit ->
282
:-(
?LOG_CRITICAL("Stopping: ~s", [format_error(Reason)]),
283
:-(
exit(Reason);
284 _Halt ->
285
:-(
?LOG_CRITICAL("Aborting: ~s", [format_error(Reason)]),
286
:-(
eturnal_logger:flush(),
287
:-(
halt(1)
288 end.
289
290 %% Internal functions: reload configuration.
291
292 -spec reload_config() -> ok.
293 reload_config() ->
294
:-(
ok = gen_server:cast(?MODULE, reload).
295
296 %% Internal functions: authentication.
297
298 -spec derive_password(binary(), [binary()]) -> binary() | [binary()].
299 -ifdef(old_crypto).
300 derive_password(Username, [Secret]) ->
301 base64:encode(crypto:hmac(sha, Secret, Username));
302 derive_password(Username, Secrets) when is_list(Secrets) ->
303 [derive_password(Username, [Secret]) || Secret <- Secrets].
304 -else.
305 derive_password(Username, [Secret]) ->
306 13 base64:encode(crypto:mac(hmac, sha, Secret, Username));
307 derive_password(Username, Secrets) when is_list(Secrets) ->
308
:-(
[derive_password(Username, [Secret]) || Secret <- Secrets].
309 -endif.
310
311 %% Internal functions: log relay address(es) and distribution listener port.
312
313 -spec log_relay_addresses() -> ok.
314 log_relay_addresses() ->
315 1 Min = get_opt(relay_min_port),
316 1 Max = get_opt(relay_max_port),
317 1 case get_opt(relay_ipv4_addr) of
318 {_, _, _, _} = Addr4 ->
319 1 ?LOG_INFO("Relay IPv4 address: ~s (port range: ~B-~B)",
320
:-(
[inet:ntoa(Addr4), Min, Max]);
321 undefined ->
322
:-(
?LOG_INFO("Relay IPv4 address not configured")
323 end,
324 1 case get_opt(relay_ipv6_addr) of
325 {_, _, _, _, _, _, _, _} = Addr6 ->
326
:-(
?LOG_INFO("Relay IPv6 address: ~s (port range: ~B-~B)",
327
:-(
[inet:ntoa(Addr6), Min, Max]);
328 undefined ->
329 1 ?LOG_INFO("Relay IPv6 address not configured")
330 end.
331
332 -spec log_control_listener() -> ok.
333 -dialyzer({[no_fail_call, no_match], log_control_listener/0}). % OTP 21/22.
334 log_control_listener() ->
335 1 [Name, Host] = string:split(atom_to_list(node()), "@"),
336 % The 'catch' calms Dialyzer on OTP 21 (even though we don't match 'EXIT').
337 1 case catch erl_epmd:port_please(Name, Host, timer:seconds(10)) of
338 {port, Port, Version} ->
339
:-(
?LOG_INFO("Listening on ~s:~B (tcp) (Erlang protocol version ~B)",
340
:-(
[Host, Port, Version]);
341 {error, Reason} ->
342
:-(
?LOG_INFO("Cannot determine control query port: ~p", [Reason]);
343 Reason when is_atom(Reason) ->
344 1 ?LOG_INFO("Cannot determine control query port: ~s", [Reason])
345 end.
346
347 %% Internal functions: module startup/shutdown.
348
349 -spec start_modules() -> modules().
350 start_modules() ->
351 3 lists:map(
352 fun({Mod, _Opts}) ->
353 9 case eturnal_module:start(Mod) of
354 ok ->
355 9 ?LOG_INFO("Started ~s", [Mod]),
356 9 Mod;
357 {error, Reason} ->
358
:-(
exit({module_failure, start, Mod, Reason})
359 end
360 end, maps:to_list(get_opt(modules))).
361
362 -spec stop_modules(state()) -> ok.
363 stop_modules(#eturnal_state{modules = Modules}) ->
364 3 lists:foreach(
365 fun(Mod) ->
366 9 case eturnal_module:stop(Mod) of
367 ok ->
368 9 ?LOG_INFO("Stopped ~s", [Mod]);
369 {error, Reason} ->
370
:-(
exit({module_failure, stop, Mod, Reason})
371 end
372 end, Modules).
373
374 %% Internal functions: listener startup/shutdown.
375
376 -spec start_listeners() -> listeners().
377 start_listeners() ->
378 1 Opts = lists:filtermap(
379 fun({InKey, OutKey}) ->
380 11 opt_filter({OutKey, get_opt(InKey)})
381 end, opt_map()) ++ [{auth_fun, fun ?MODULE:get_password/2},
382 {hook_fun, fun ?MODULE:run_hook/2}],
383 1 lists:map(
384 fun({IP, Port, Transport, ProxyProtocol, EnableTURN}) ->
385 4 Opts1 = tls_opts(Transport) ++ Opts,
386 4 Opts2 = turn_opts(EnableTURN) ++ Opts1,
387 4 Opts3 = proxy_opts(ProxyProtocol) ++ Opts2,
388 4 ?LOG_DEBUG("Starting listener ~s (~s) with options:~n~p",
389 [eturnal_misc:addr_to_str(IP, Port), Transport,
390
:-(
Opts3]),
391 4 InfoArgs = [eturnal_misc:addr_to_str(IP, Port), Transport,
392 describe_listener(EnableTURN)],
393 4 case stun_listener:add_listener(IP, Port, Transport, Opts3) of
394 ok ->
395 4 ?LOG_INFO("Listening on ~s (~s) (~s)", InfoArgs);
396 {error, already_started} ->
397
:-(
?LOG_INFO("Already listening on ~s (~s) (~s)", InfoArgs);
398 {error, Reason} ->
399
:-(
exit({listener_failure, start, IP, Port, Transport,
400 Reason})
401 end,
402 4 {IP, Port, Transport}
403 end, get_opt(listen)).
404
405 -spec stop_listeners(state()) -> ok.
406 stop_listeners(#eturnal_state{listeners = Listeners}) ->
407 1 lists:foreach(
408 fun({IP, Port, Transport}) ->
409 4 case stun_listener:del_listener(IP, Port, Transport) of
410 ok ->
411 4 ?LOG_INFO("Stopped listening on ~s (~s)",
412 [eturnal_misc:addr_to_str(IP, Port),
413
:-(
Transport]);
414 {error, Reason} ->
415
:-(
exit({listener_failure, stop, IP, Port, Transport,
416 Reason})
417 end
418 end, Listeners).
419
420 -spec describe_listener(boolean()) -> binary().
421 describe_listener(_EnableTURN = true) ->
422 1 <<"STUN/TURN">>;
423 describe_listener(_EnableTURN = false) ->
424 3 <<"STUN only">>.
425
426 -spec opt_map() -> [{atom(), atom()}].
427 opt_map() ->
428 1 [{relay_ipv4_addr, turn_ipv4_address},
429 {relay_ipv6_addr, turn_ipv6_address},
430 {relay_min_port, turn_min_port},
431 {relay_max_port, turn_max_port},
432 {max_allocations, turn_max_allocations},
433 {max_permissions, turn_max_permissions},
434 {max_bps, shaper},
435 {blacklist, turn_blacklist},
436 {whitelist, turn_whitelist},
437 {realm, auth_realm},
438 {software_name, server_name}].
439
440 -spec opt_filter(Opt) -> {true, Opt} | false when Opt :: {option(), value()}.
441 opt_filter({turn_ipv6_address, undefined}) ->
442 1 false; % The 'stun' application currently wouldn't accept 'undefined'.
443 opt_filter(Opt) ->
444 10 {true, Opt}.
445
446 -spec turn_opts(boolean()) -> proplists:proplist().
447 turn_opts(EnableTURN) ->
448 4 case {EnableTURN, got_secret(), got_relay_addr()} of
449 {true, true, true} ->
450 1 [{use_turn, true},
451 {auth_type, user}];
452 {_, _, _} ->
453 3 [{use_turn, false},
454 {auth_type, anonymous}]
455 end.
456
457 -spec proxy_opts(boolean()) -> proplists:proplist().
458 proxy_opts(true = _ProxyProtocol) ->
459
:-(
[proxy_protocol];
460 proxy_opts(false = _ProxyProtocol) ->
461 4 [].
462
463 -spec tls_opts(transport()) -> proplists:proplist().
464 -ifdef(old_inet_backend).
465 tls_opts(tls) ->
466 [{tls, true} | extra_tls_opts()];
467 tls_opts(auto) ->
468 exit({otp_too_old, transport, auto, 23});
469 tls_opts(_) ->
470 [].
471 -else.
472 tls_opts(tls) ->
473 1 [{tls, true} | extra_tls_opts()];
474 tls_opts(auto) ->
475 1 [{tls, optional} | extra_tls_opts()];
476 tls_opts(_) ->
477 2 [].
478 -endif.
479
480 -spec extra_tls_opts() -> proplists:proplist().
481 extra_tls_opts() ->
482 2 Opts = [{certfile, get_pem_file_path()},
483 {ciphers, get_opt(tls_ciphers)},
484 {protocol_options, get_opt(tls_options)}],
485 2 case get_opt(tls_dh_file) of
486 Path when is_binary(Path) ->
487
:-(
[{dhfile, Path} | Opts];
488 none ->
489 2 Opts
490 end.
491
492 %% Internal functions: configuration parsing.
493
494 -spec tls_enabled() -> boolean().
495 tls_enabled() ->
496 3 lists:any(fun({_IP, _Port, Transport, _ProxyProtocol, _EnableTURN}) ->
497 9 (Transport =:= tls) or (Transport =:= auto)
498 end, get_opt(listen)).
499
500 -spec turn_enabled() -> boolean().
501 turn_enabled() ->
502 1 lists:any(fun({_IP, _Port, _Transport, _ProxyProtocol, EnableTURN}) ->
503 1 EnableTURN =:= true
504 end, get_opt(listen)).
505
506 -spec got_secret() -> boolean().
507 got_secret() ->
508 4 case get_opt(secret) of
509 Secrets when is_list(Secrets) ->
510 4 lists:all(fun(Secret) ->
511 4 is_binary(Secret) and (byte_size(Secret) > 0)
512 end, Secrets);
513 Secret when is_binary(Secret), byte_size(Secret) > 0 ->
514
:-(
true;
515 undefined ->
516
:-(
false
517 end.
518
519 -spec got_relay_addr() -> boolean().
520 got_relay_addr() ->
521 5 case get_opt(relay_ipv4_addr) of
522 {_, _, _, _} ->
523 5 true;
524 undefined ->
525
:-(
false
526 end.
527
528 -spec check_turn_config() -> ok.
529 check_turn_config() ->
530 1 case turn_enabled() of
531 true ->
532 1 case {got_relay_addr(),
533 get_opt(relay_min_port),
534 get_opt(relay_max_port)} of
535 {_GotAddr, Min, Max} when Max =< Min ->
536
:-(
exit(turn_config_failure);
537 {false, _Min, _Max} ->
538
:-(
?LOG_WARNING("Specify a 'relay_ipv4_addr' to enable TURN");
539 {true, _Min, _Max} ->
540 1 ?LOG_DEBUG("TURN configuration seems fine")
541 end;
542 false ->
543
:-(
?LOG_DEBUG("TURN is disabled")
544 end.
545
546 -spec check_proxy_config() -> ok.
547 check_proxy_config() ->
548 1 case lists:any(
549 fun({_IP, _Port, Transport, ProxyProtocol, _EnableTURN}) ->
550 4 (Transport =:= udp) and (ProxyProtocol =:= true)
551 end, get_opt(listen)) of
552 true ->
553
:-(
exit(proxy_config_failure);
554 false ->
555 1 ok
556 end.
557
558 %% Internal functions: configuration reload.
559
560 -spec reload(state()) -> {ok, state()} | {error, term()}.
561 reload(State) ->
562 2 case conf:reload_file() of
563 ok ->
564 2 try check_pem_file() of
565 ok ->
566 1 ok = fast_tls:clear_cache(),
567 1 ?LOG_INFO("Using new TLS certificate");
568 unchanged ->
569 1 ?LOG_DEBUG("TLS certificate unchanged")
570 catch exit:Reason1 ->
571
:-(
?LOG_ERROR(format_error(Reason1))
572 end,
573 2 try {stop_modules(State), start_modules()} of
574 {ok, Modules} ->
575 2 ?LOG_DEBUG("Restarted modules"),
576 2 {ok, State#eturnal_state{modules = Modules}}
577 catch exit:Reason2 ->
578
:-(
?LOG_ERROR(format_error(Reason2)),
579
:-(
{ok, State}
580 end;
581 {error, Reason} = Err ->
582
:-(
?LOG_ERROR("Cannot reload configuration: ~ts",
583
:-(
[conf:format_error(Reason)]),
584
:-(
Err
585 end.
586
587 -spec apply_config_changes(state(), config_changes()) -> state().
588 apply_config_changes(State, {Changed, New, Removed} = ConfigChanges) ->
589
:-(
if length(Changed) > 0 ->
590
:-(
?LOG_DEBUG("Changed options: ~p", [Changed]);
591 length(Changed) =:= 0 ->
592
:-(
?LOG_DEBUG("No changed options")
593 end,
594
:-(
if length(Removed) > 0 ->
595
:-(
?LOG_DEBUG("Removed options: ~p", [Removed]);
596 length(Removed) =:= 0 ->
597
:-(
?LOG_DEBUG("No removed options")
598 end,
599
:-(
if length(New) > 0 ->
600
:-(
?LOG_DEBUG("New options: ~p", [New]);
601 length(New) =:= 0 ->
602
:-(
?LOG_DEBUG("No new options")
603 end,
604
:-(
try apply_logging_config_changes(ConfigChanges)
605 catch exit:Reason1 ->
606
:-(
?LOG_ERROR(format_error(Reason1))
607 end,
608
:-(
try apply_run_dir_config_changes(ConfigChanges)
609 catch exit:Reason2 ->
610
:-(
?LOG_ERROR(format_error(Reason2))
611 end,
612
:-(
try apply_relay_config_changes(ConfigChanges)
613 catch exit:Reason3 ->
614
:-(
?LOG_ERROR(format_error(Reason3))
615 end,
616
:-(
try apply_listener_config_changes(ConfigChanges, State)
617 catch exit:Reason4 ->
618
:-(
?LOG_ERROR(format_error(Reason4)),
619
:-(
State
620 end.
621
622 -spec apply_logging_config_changes(config_changes()) -> ok.
623 apply_logging_config_changes(ConfigChanges) ->
624
:-(
case logging_config_changed(ConfigChanges) of
625 true ->
626
:-(
?LOG_INFO("Using new logging configuration"),
627
:-(
ok = eturnal_logger:reconfigure();
628 false ->
629
:-(
?LOG_DEBUG("Logging configuration unchanged")
630 end.
631
632 -spec apply_run_dir_config_changes(config_changes()) -> ok.
633 apply_run_dir_config_changes(ConfigChanges) ->
634
:-(
case run_dir_config_changed(ConfigChanges) of
635 true ->
636
:-(
?LOG_INFO("Using new run directory configuration"),
637
:-(
ok = ensure_run_dir(),
638
:-(
case check_pem_file() of
639 ok ->
640
:-(
ok = fast_tls:clear_cache();
641 unchanged ->
642
:-(
ok
643 end;
644 false ->
645
:-(
?LOG_DEBUG("Run directory configuration unchanged")
646 end.
647
648 -spec apply_relay_config_changes(config_changes()) -> ok.
649 apply_relay_config_changes(ConfigChanges) ->
650
:-(
case relay_config_changed(ConfigChanges) of
651 true ->
652
:-(
?LOG_INFO("Using new TURN relay configuration"),
653
:-(
ok = log_relay_addresses();
654 false ->
655
:-(
?LOG_DEBUG("TURN relay configuration unchanged")
656 end.
657
658 -spec apply_listener_config_changes(config_changes(), state()) -> state().
659 apply_listener_config_changes(ConfigChanges, State) ->
660
:-(
case listener_config_changed(ConfigChanges) of
661 true ->
662
:-(
?LOG_INFO("Using new listener configuration"),
663
:-(
ok = check_turn_config(),
664
:-(
ok = check_proxy_config(),
665
:-(
ok = stop_listeners(State),
666
:-(
ok = timer:sleep(500),
667
:-(
Listeners = start_listeners(),
668
:-(
State#eturnal_state{listeners = Listeners};
669 false ->
670
:-(
?LOG_DEBUG("Listener configuration unchanged"),
671
:-(
State
672 end.
673
674 -spec logging_config_changed(config_changes()) -> boolean().
675 logging_config_changed({Changed, New, Removed}) ->
676
:-(
ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
677
:-(
LoggingKeys = [log_dir,
678 log_level,
679 log_rotate_size,
680 log_rotate_count],
681
:-(
lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, LoggingKeys).
682
683 -spec run_dir_config_changed(config_changes()) -> boolean().
684 run_dir_config_changed({Changed, New, Removed}) ->
685
:-(
ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
686
:-(
RunDirKeys = [run_dir],
687
:-(
lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, RunDirKeys).
688
689 -spec relay_config_changed(config_changes()) -> boolean().
690 relay_config_changed({Changed, New, Removed}) ->
691
:-(
ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
692
:-(
RelayKeys = [relay_ipv4_addr,
693 relay_ipv6_addr,
694 relay_min_port,
695 relay_max_port],
696
:-(
lists:any(fun(Key) -> lists:member(Key, ModifiedKeys) end, RelayKeys).
697
698 -spec listener_config_changed(config_changes()) -> boolean().
699 listener_config_changed({Changed, New, Removed} = ConfigChanges) ->
700
:-(
case relay_config_changed(ConfigChanges) or
701 run_dir_config_changed(ConfigChanges) of
702 true ->
703
:-(
true;
704 false ->
705
:-(
ModifiedKeys = proplists:get_keys(Changed ++ New ++ Removed),
706
:-(
ListenerKeys = [listen,
707 max_allocations,
708 max_permissions,
709 max_bps,
710 blacklist,
711 whitelist,
712 realm,
713 software_name,
714 tls_options,
715 tls_ciphers,
716 tls_dh_file],
717
:-(
lists:any(fun(Key) ->
718
:-(
lists:member(Key, ModifiedKeys)
719 end, ListenerKeys)
720 end.
721
722 %% Internal functions: PEM file handling.
723
724 -spec get_pem_file_path() -> file:filename_all().
725 get_pem_file_path() ->
726 6 filename:join(get_opt(run_dir), <<?PEM_FILE_NAME>>).
727
728 -spec check_pem_file() -> ok | unchanged.
729 check_pem_file() ->
730 3 case tls_enabled() of
731 true ->
732 3 OutFile = get_pem_file_path(),
733 3 case {get_opt(tls_crt_file), filelib:last_modified(OutFile)} of
734 {none, OutTime} when OutTime =/= 0 ->
735 1 ?LOG_DEBUG("Keeping PEM file (~ts)", [OutFile]),
736 1 unchanged;
737 {none, OutTime} when OutTime =:= 0 ->
738 2 ?LOG_WARNING("TLS enabled without 'tls_crt_file', creating "
739
:-(
"self-signed certificate"),
740 2 ok = create_self_signed(OutFile);
741 {CrtFile, OutTime} ->
742
:-(
case filelib:last_modified(CrtFile) of
743 CrtTime when CrtTime =< OutTime ->
744
:-(
?LOG_DEBUG("Keeping PEM file (~ts)", [OutFile]),
745
:-(
unchanged;
746 CrtTime when CrtTime =/= 0 -> % Assert to be true.
747
:-(
?LOG_DEBUG("Updating PEM file (~ts)", [OutFile]),
748
:-(
ok = import_pem_file(CrtFile, OutFile)
749 end
750 end;
751 false ->
752
:-(
?LOG_DEBUG("TLS not enabled, ignoring certificate configuration"),
753
:-(
unchanged
754 end.
755
756 -spec import_pem_file(binary(), file:filename_all()) -> ok.
757 import_pem_file(CrtFile, OutFile) ->
758
:-(
try
759
:-(
ok = touch(OutFile),
760
:-(
case get_opt(tls_key_file) of
761 KeyFile when is_binary(KeyFile) ->
762
:-(
ok = copy_file(KeyFile, OutFile, write);
763 none ->
764
:-(
?LOG_INFO("No 'tls_key_file' specified, assuming key in ~ts",
765
:-(
[CrtFile])
766 end,
767
:-(
ok = copy_file(CrtFile, OutFile, append)
768 catch error:{_, {error, Reason}} ->
769
:-(
exit({pem_failure, OutFile, Reason})
770 end.
771
772 -spec copy_file(file:name_all(), file:name_all(), write | append) -> ok.
773 copy_file(Src, Dst, Mode) ->
774
:-(
SrcMode = [read, binary, raw],
775
:-(
DstMode = [Mode, binary, raw],
776
:-(
{ok, _} = file:copy({Src, SrcMode}, {Dst, DstMode}),
777
:-(
?LOG_DEBUG("Copied ~ts into ~ts", [Src, Dst]).
778
779 -spec touch(file:filename_all()) -> ok.
780 touch(File) ->
781 2 {ok, Fd} = file:open(File, [append, binary, raw]),
782 2 ok = file:close(Fd),
783 2 ok = file:change_mode(File, 8#00600).
784
785 %% Internal functions: run directory.
786
787 -spec ensure_run_dir() -> ok.
788 ensure_run_dir() ->
789 1 RunDir = get_opt(run_dir),
790 1 case filelib:ensure_dir(filename:join(RunDir, <<"file">>)) of
791 ok ->
792 1 ?LOG_DEBUG("Using run directory ~ts", [RunDir]);
793 {error, Reason} ->
794
:-(
exit({run_dir_failure, create, RunDir, Reason})
795 end.
796
797 -spec clean_run_dir() -> ok.
798 clean_run_dir() ->
799 1 PEMFile = get_pem_file_path(),
800 1 case filelib:is_regular(PEMFile) of
801 true ->
802 1 case file:delete(PEMFile) of
803 ok ->
804 1 ?LOG_DEBUG("Removed ~ts", [PEMFile]);
805 {error, Reason} ->
806
:-(
exit({run_dir_failure, clean, PEMFile, Reason})
807 end;
808 false ->
809
:-(
?LOG_DEBUG("PEM file doesn't exist: ~ts", [PEMFile])
810 end.
811
812 %% Internal functions: error message formatting.
813
814 -spec format_error(atom() | tuple()) -> binary().
815 format_error({module_failure, Action, Mod, Reason}) ->
816
:-(
format("Failed to ~s ~s: ~p", [Action, Mod, Reason]);
817 format_error({dependency_failure, Mod, Dep}) ->
818
:-(
format("Dependency ~s is missing; install it below ~s, or point ERL_LIBS "
819 "to it, or disable ~s", [Dep, code:lib_dir(), Mod]);
820 format_error({listener_failure, Action, IP, Port, Transport, Reason}) ->
821
:-(
format("Cannot ~s listening on ~s (~s): ~s",
822 [Action, eturnal_misc:addr_to_str(IP, Port), Transport,
823 inet:format_error(Reason)]);
824 format_error({run_dir_failure, Action, RunDir, Reason}) ->
825
:-(
format("Cannot ~s run directory ~ts: ~ts",
826 [Action, RunDir, file:format_error(Reason)]);
827 format_error({pem_failure, File, Reason}) ->
828
:-(
format("Cannot create PEM file ~ts: ~ts",
829 [File, file:format_error(Reason)]);
830 format_error({otp_too_old, Key, Value, Vsn}) ->
831
:-(
format("Setting '~s: ~s' requires Erlang/OTP ~B or later",
832 [Key, Value, Vsn]);
833 format_error(proxy_config_failure) ->
834
:-(
<<"The 'proxy_protocol' ist not supported for 'udp'">>;
835 format_error(turn_config_failure) ->
836
:-(
<<"The 'relay_max_port' must be larger than the 'relay_min_port'">>;
837 format_error(_Unknown) ->
838
:-(
<<"Unknown error">>.
839
840 -spec format(io:format(), [term()]) -> binary().
841 format(Fmt, Data) ->
842
:-(
case unicode:characters_to_binary(io_lib:format(Fmt, Data)) of
843 Bin when is_binary(Bin) ->
844
:-(
Bin;
845 {_, _, _} = Err ->
846
:-(
erlang:error(Err)
847 end.
848
849 %% EUnit tests.
850
851 -ifdef(EUNIT).
852 config_change_test_() ->
853
:-(
[?_assert(logging_config_changed({[{log_level, info}], [], []})),
854
:-(
?_assert(run_dir_config_changed({[{run_dir, <<"run">>}], [], []})),
855
:-(
?_assert(relay_config_changed({[{relay_min_port, 50000}], [], []})),
856
:-(
?_assert(listener_config_changed({[{max_bps, 42}], [], []})),
857
:-(
?_assertNot(logging_config_changed({[{strict_expiry, false}], [], []})),
858
:-(
?_assertNot(run_dir_config_changed({[{strict_expiry, false}], [], []})),
859
:-(
?_assertNot(relay_config_changed({[{strict_expiry, false}], [], []})),
860
:-(
?_assertNot(listener_config_changed({[{strict_expiry, false}], [], []}))].
861 -endif.
Line Hits Source