/home/runner/work/eturnal/eturnal/_build/test/cover/aggregate/eturnal_yaml.html

1 %%% eturnal STUN/TURN server.
2 %%%
3 %%% Copyright (c) 2020-2023 Holger Weiss <holger@zedat.fu-berlin.de>.
4 %%% Copyright (c) 2020-2023 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_yaml).
20 -behaviour(conf).
21 -export([validator/0]).
22 -import(yval, [and_then/2, any/0, beam/1, binary/0, bool/0, directory/1,
23 either/2, enum/1, file/1, int/2, ip/0, ipv4/0, ipv6/0, ip_mask/0,
24 list/1, list/2, list_or_single/1, map/3, non_empty/1,
25 non_neg_int/0, options/1, options/2, port/0, pos_int/1]).
26
27 -include_lib("kernel/include/logger.hrl").
28
29 -define(RECOMMENDED_BLACKLIST,
30 [{{127, 0, 0, 0}, 8},
31 {{10, 0, 0, 0}, 8},
32 {{100, 64, 0, 0}, 10},
33 {{169, 254, 0, 0}, 16},
34 {{172, 16, 0, 0}, 12},
35 {{192, 0, 0, 0}, 24},
36 {{192, 0, 2, 0}, 24},
37 {{192, 88, 99, 0}, 24},
38 {{192, 168, 0, 0}, 16},
39 {{198, 18, 0, 0}, 15},
40 {{198, 51, 100, 0}, 24},
41 {{203, 0, 113, 0}, 24},
42 {{224, 0, 0, 0}, 4},
43 {{240, 0, 0, 0}, 4},
44 {{0, 0, 0, 0, 0, 0, 0, 1}, 128},
45 {{100, 65435, 0, 0, 0, 0, 0, 0}, 96},
46 {{256, 0, 0, 0, 0, 0, 0, 0}, 64},
47 {{64512, 0, 0, 0, 0, 0, 0, 0}, 7},
48 {{65152, 0, 0, 0, 0, 0, 0, 0}, 10},
49 {{65280, 0, 0, 0, 0, 0, 0, 0}, 8}]).
50
51 -type listener() :: {inet:ip_address(), inet:port_number(), eturnal:transport(),
52 boolean(), boolean()}.
53 -type family() :: ipv4 | ipv6.
54 -type boundary() :: min | max.
55
56 %% API.
57
58 -spec validator() -> yval:validator().
59 validator() ->
60 3 options(
61 #{secret => list_or_single(non_empty(binary())),
62 listen => listen_validator(),
63 relay_ipv4_addr => and_then(ipv4(), fun check_relay_addr/1),
64 relay_ipv6_addr => and_then(ipv6(), fun check_relay_addr/1),
65 relay_min_port => port(),
66 relay_max_port => port(),
67 tls_crt_file => file(read),
68 tls_key_file => file(read),
69 tls_dh_file => file(read),
70 tls_options => openssl_list($|),
71 tls_ciphers => openssl_list($:),
72 max_allocations => pos_int(unlimited),
73 max_permissions => pos_int(unlimited),
74 max_bps => and_then(pos_int(unlimited),
75
:-(
fun(unlimited) -> none;
76 3 (I) -> I
77 end),
78 blacklist => blacklist_validator(),
79 whitelist => list_or_single(ip_mask()),
80 blacklist_clients => list_or_single(ip_mask()),
81 whitelist_clients => list_or_single(ip_mask()),
82 blacklist_peers => blacklist_validator(),
83 whitelist_peers => list_or_single(ip_mask()),
84 strict_expiry => bool(),
85 credentials => map(binary(), binary(), [unique, {return, map}]),
86 realm => non_empty(binary()),
87 software_name => non_empty(binary()),
88 run_dir => directory(write),
89 log_dir => either(stdout, directory(write)),
90 log_level => enum([critical, error, warning, notice, info, debug]),
91 log_rotate_size => pos_int(infinity),
92 log_rotate_count => non_neg_int(),
93 modules => module_validator()},
94 [unique,
95 {required, []},
96 {defaults,
97 #{listen => [{{0, 0, 0, 0, 0, 0, 0, 0}, 3478, udp, false, true},
98 {{0, 0, 0, 0, 0, 0, 0, 0}, 3478, tcp, false, true}],
99 relay_ipv4_addr => get_default_addr(ipv4),
100 relay_ipv6_addr => get_default_addr(ipv6),
101 relay_min_port => get_default_port(min, 49152),
102 relay_max_port => get_default_port(max, 65535),
103 tls_crt_file => none,
104 tls_key_file => none,
105 tls_dh_file => none,
106 tls_options => <<"cipher_server_preference">>,
107 tls_ciphers => <<"HIGH:!aNULL:@STRENGTH">>,
108 max_allocations => 10,
109 max_permissions => 10,
110 max_bps => none,
111 blacklist => [],
112 whitelist => [],
113 blacklist_clients => [],
114 whitelist_clients => [],
115 blacklist_peers => ?RECOMMENDED_BLACKLIST,
116 whitelist_peers => [],
117 strict_expiry => false,
118 credentials => #{},
119 realm => <<"eturnal.net">>,
120 secret => [get_default(secret, make_random_secret())],
121 software_name => <<"eturnal">>,
122 run_dir => get_default("RUNTIME_DIRECTORY", <<"run">>),
123 log_dir => get_default("LOGS_DIRECTORY", <<"log">>),
124 log_level => info,
125 log_rotate_size => infinity,
126 log_rotate_count => 10,
127 modules => #{}}}]).
128
129 %% Internal functions.
130
131 -spec blacklist_validator() -> yval:validator().
132 blacklist_validator() ->
133 6 and_then(
134 list_or_single(either(recommended, ip_mask())),
135 fun(L) ->
136 3 lists:usort(
137 lists:flatmap(
138 fun(recommended) ->
139 3 ?RECOMMENDED_BLACKLIST;
140 (Network) ->
141 6 [Network]
142 end, L))
143 end).
144
145 -spec module_validator() -> yval:validator().
146 module_validator() ->
147 3 and_then(
148 map(
149 beam([{handle_event, 2}, {options, 0}]),
150 options(#{'_' => any()}),
151 [unique]),
152 fun(L) ->
153 3 lists:foldl(
154 fun({Mod, Opts}, Acc) ->
155 9 {Validators,
156 ValidatorOpts0} = eturnal_module:options(Mod),
157 9 ValidatorOpts = [unique,
158 {return, map} | ValidatorOpts0],
159 9 Acc#{Mod => (options(Validators, ValidatorOpts))(Opts)}
160 end, #{}, L)
161 end).
162
163 -spec listen_validator() -> yval:validator().
164 listen_validator() ->
165 3 and_then(
166 list(
167 and_then(
168 options(
169 #{ip => ip(),
170 port => int(0, 65535),
171 transport => enum([tcp, udp, tls, auto]),
172 proxy_protocol => bool(),
173 enable_turn => bool()}),
174 fun(Opts) ->
175 12 DefP = fun(udp) -> 3478;
176 3 (tcp) -> 3478;
177 3 (tls) -> 5349;
178 3 (auto) -> 3478
179 end,
180 12 I = proplists:get_value(ip, Opts, {0, 0, 0, 0, 0, 0, 0, 0}),
181 12 T = proplists:get_value(transport, Opts, udp),
182 12 P = proplists:get_value(port, Opts, DefP(T)),
183 12 X = proplists:get_value(proxy_protocol, Opts, false),
184 12 E = proplists:get_value(enable_turn, Opts, true),
185 12 {I, P, T, X, E}
186 end)),
187 fun check_overlapping_listeners/1).
188
189 -spec check_overlapping_listeners([listener()]) -> [listener()].
190 check_overlapping_listeners(Listeners) ->
191 3 ok = check_overlapping_listeners(Listeners, fun(L) -> L end),
192 3 ok = check_overlapping_listeners(Listeners, fun lists:reverse/1),
193 3 Listeners.
194
195 -spec check_overlapping_listeners([listener()],
196 fun(([listener()]) -> [listener()]))
197 -> ok.
198 check_overlapping_listeners(Listeners, PrepareFun) ->
199 6 _ = lists:foldl(
200 fun({IP, Port, Transport, _ProxyProtocol, _EnableTURN} = Listener,
201 Acc) ->
202 24 Key = case Transport of
203 udp ->
204 6 {IP, Port, udp};
205 _ ->
206 18 {IP, Port, tcp}
207 end,
208 24 case lists:member(Key, Acc) of
209 true ->
210
:-(
fail({duplicated_value,
211 format_listener(Listener)});
212 false ->
213 % With dual-stack sockets, we won't detect conflicts
214 % of IPv4 addresses with "::".
215 24 AnyIP = case tuple_size(IP) of
216
:-(
8 -> {0, 0, 0, 0, 0, 0, 0, 0};
217 24 4 -> {0, 0, 0, 0}
218 end,
219 24 Key1 = {AnyIP, Port, Transport},
220 24 case lists:member(Key1, Acc) of
221 true ->
222
:-(
fail({duplicated_value,
223 format_listener(Listener)});
224 false ->
225 24 [Key | Acc]
226 end
227 end
228 end, [], PrepareFun(Listeners)),
229 6 ok.
230
231 -spec format_listener(listener()) -> binary().
232 format_listener({IP, Port, Transport, _ProxyProtocol, _EnableTURN}) ->
233
:-(
Addr = eturnal_misc:addr_to_str(IP, Port),
234
:-(
list_to_binary(io_lib:format("~s (~s)", [Addr, Transport])).
235
236 -spec check_relay_addr(inet:ip_address()) -> inet:ip_address().
237 check_relay_addr({0, 0, 0, 0} = Addr) ->
238
:-(
fail({bad_ip, inet:ntoa(Addr)});
239 check_relay_addr({_, _, _, _} = Addr) ->
240 3 Addr;
241 check_relay_addr({0, 0, 0, 0, 0, 0, 0, 0} = Addr) ->
242
:-(
fail({bad_ip, inet:ntoa(Addr)});
243 check_relay_addr({_, _, _, _, _, _, _, _} = Addr) ->
244
:-(
Addr.
245
246 -spec get_default_addr(family()) -> inet:ip_address() | undefined.
247 get_default_addr(Family) ->
248 6 {Vsn, Opt, ParseAddr, MyAddr} =
249 case Family of
250 ipv4 ->
251 3 {4, relay_ipv4_addr,
252 fun inet:parse_ipv4strict_address/1,
253 fun eturnal_misc:my_ipv4_addr/0};
254 ipv6 ->
255 3 {6, relay_ipv6_addr,
256 fun inet:parse_ipv6strict_address/1,
257 fun eturnal_misc:my_ipv6_addr/0}
258 end,
259 6 case get_default(Opt, undefined) of
260 RelayAddr when is_binary(RelayAddr) ->
261
:-(
try
262
:-(
{ok, Addr} = ParseAddr(binary_to_list(RelayAddr)),
263
:-(
check_relay_addr(Addr)
264 catch error:_ ->
265
:-(
abort("Bad ETURNAL_RELAY_IPV~B_ADDR: ~s", [Vsn, RelayAddr])
266 end;
267 undefined ->
268 6 MyAddr()
269 end.
270
271 -spec get_default_port(boundary(), inet:port_number()) -> inet:port_number().
272 get_default_port(MinMax, Default) ->
273 6 MinMaxStr = from_atom(MinMax),
274 6 Opt = to_atom(<<"relay_", MinMaxStr/binary, "_port">>),
275 6 case get_default(Opt, Default) of
276 Bin when is_binary(Bin) ->
277
:-(
try
278
:-(
Port = binary_to_integer(Bin),
279
:-(
true = (Port >= 1),
280
:-(
true = (Port =< 65535),
281
:-(
Port
282 catch error:_ ->
283
:-(
abort("Bad ETURNAL_RELAY_~s_PORT: ~s",
284 [string:uppercase(MinMaxStr), Bin])
285 end;
286 Default ->
287 6 Default
288 end.
289
290 -spec get_default(atom() | string(), Term) -> binary() | Term.
291 get_default(Opt, Default) when is_atom(Opt) ->
292 15 get_default(get_env_name(Opt), Default);
293 get_default(Var, Default) ->
294 21 case os:getenv(Var) of
295 Val when is_list(Val), length(Val) > 0 ->
296
:-(
unicode:characters_to_binary(Val);
297 _ ->
298 21 Default
299 end.
300
301 -spec get_env_name(atom()) -> string().
302 get_env_name(Opt) ->
303 15 "ETURNAL_" ++ string:uppercase(atom_to_list(Opt)).
304
305 -spec make_random_secret() -> binary().
306 -ifdef(old_rand).
307 make_random_secret() ->
308 <<(rand:uniform(1 bsl 127)):128>>.
309 -else.
310 make_random_secret() ->
311 3 rand:bytes(16).
312 -endif.
313
314 -spec openssl_list(char()) -> fun((binary() | [binary()]) -> binary()).
315 openssl_list(Sep) ->
316 6 fun(L) when is_list(L) ->
317 6 (and_then(list(binary(), [unique]), join(Sep)))(L);
318 (B) ->
319
:-(
(binary())(B)
320 end.
321
322 -spec join(char()) -> fun(([binary()]) -> binary()).
323 join(Sep) ->
324 6 fun(Opts) -> unicode:characters_to_binary(lists:join(<<Sep>>, Opts)) end.
325
326 -spec from_atom(atom()) -> binary().
327 -spec to_atom(binary()) -> atom().
328 -ifdef(old_atom_conversion). % Erlang/OTP < 23.0.
329 from_atom(A) -> atom_to_binary(A, utf8).
330 to_atom(S) -> list_to_existing_atom(binary_to_list(S)).
331 -else.
332 6 from_atom(A) -> atom_to_binary(A).
333 6 to_atom(S) -> binary_to_existing_atom(S).
334 -endif.
335
336 -spec fail({atom(), term()}) -> no_return().
337 fail(Reason) ->
338
:-(
yval:fail(yval, Reason).
339
340 -spec abort(io:format(), [term()]) -> no_return().
341 abort(Format, Data) ->
342
:-(
?LOG_CRITICAL(Format, Data),
343
:-(
eturnal_logger:flush(),
344
:-(
halt(2).
Line Hits Source