| 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_misc). | 
| 20 |  | -export([my_ipv4_addr/0, | 
| 21 |  |          my_ipv6_addr/0, | 
| 22 |  |          addr_to_str/1, | 
| 23 |  |          addr_to_str/2, | 
| 24 |  |          version/0, | 
| 25 |  |          info/0]). | 
| 26 |  |  | 
| 27 |  | -ifdef(EUNIT). | 
| 28 |  | -include_lib("eunit/include/eunit.hrl"). | 
| 29 |  | -endif. | 
| 30 |  | -include_lib("kernel/include/logger.hrl"). | 
| 31 |  | -include("eturnal.hrl"). | 
| 32 |  |  | 
| 33 |  | %% API. | 
| 34 |  |  | 
| 35 |  | -spec my_ipv4_addr() -> inet:ip4_address() | undefined. | 
| 36 |  | my_ipv4_addr() -> | 
| 37 | :-( |     case my_host_addr(inet) of | 
| 38 |  |         {_, _, _, _} = Addr -> | 
| 39 | :-( |             Addr; | 
| 40 |  |         undefined -> | 
| 41 | :-( |             my_if_addr(inet) | 
| 42 |  |     end. | 
| 43 |  |  | 
| 44 |  | -spec my_ipv6_addr() -> inet:ip6_address() | undefined. | 
| 45 |  | my_ipv6_addr() -> | 
| 46 | :-( |     case my_host_addr(inet6) of | 
| 47 |  |         {_, _, _, _, _, _, _, _} = Addr -> | 
| 48 | :-( |             Addr; | 
| 49 |  |         undefined -> | 
| 50 | :-( |             my_if_addr(inet6) | 
| 51 |  |     end. | 
| 52 |  |  | 
| 53 |  | -spec addr_to_str(inet:ip_address(), inet:port_number()) -> iolist(). | 
| 54 |  | addr_to_str(Addr, Port) -> | 
| 55 | :-( |     addr_to_str({Addr, Port}). | 
| 56 |  |  | 
| 57 |  | -spec addr_to_str({inet:ip_address(), inet:port_number()} | inet:ip_address()) | 
| 58 |  |       -> iolist(). | 
| 59 |  | addr_to_str({Addr, Port}) when is_tuple(Addr) -> | 
| 60 | :-( |     [addr_to_str(Addr), [$: | integer_to_list(Port)]]; | 
| 61 |  | addr_to_str({0, 0, 0, 0, 0, 16#FFFF, D7, D8}) -> | 
| 62 | :-( |     addr_to_str({D7 bsr 8, D7 band 255, D8 bsr 8, D8 band 255}); | 
| 63 |  | addr_to_str({_, _, _, _, _, _, _, _} = Addr) -> | 
| 64 | :-( |     [$[, inet:ntoa(Addr), $]]; | 
| 65 |  | addr_to_str(Addr) -> | 
| 66 | :-( |     inet:ntoa(Addr). | 
| 67 |  |  | 
| 68 |  | -spec version() -> binary(). | 
| 69 |  | version() -> | 
| 70 | :-( |     {ok, Version} = application:get_key(vsn), | 
| 71 | :-( |     unicode:characters_to_binary(Version). | 
| 72 |  |  | 
| 73 |  | -spec info() -> eturnal_node_info(). | 
| 74 |  | info() -> | 
| 75 | :-( |     #eturnal_node_info{ | 
| 76 |  |        eturnal_vsn = version(), | 
| 77 |  |        otp_vsn = {erlang:system_info(otp_release), erlang:system_info(version)}, | 
| 78 |  |        uptime = element(1, erlang:statistics(wall_clock)), | 
| 79 |  |        num_sessions = length(supervisor:which_children(turn_tmp_sup)), | 
| 80 |  |        num_processes = erlang:system_info(process_count), | 
| 81 |  |        num_reductions = element(1, erlang:statistics(reductions)), | 
| 82 |  |        total_queue_len = erlang:statistics(total_run_queue_lengths), | 
| 83 |  |        total_memory = erlang:memory(total)}. | 
| 84 |  |  | 
| 85 |  | %% Internal functions. | 
| 86 |  |  | 
| 87 |  | -spec my_host_addr(inet) -> inet:ip4_address() | undefined; | 
| 88 |  |                   (inet6) -> inet:ip6_address() | undefined. | 
| 89 |  | my_host_addr(Family) -> | 
| 90 | :-( |     {ok, Name} = inet:gethostname(), | 
| 91 | :-( |     case inet:getaddr(Name, Family) of | 
| 92 |  |         {ok, Addr} -> | 
| 93 | :-( |             case is_public_addr(Addr) of | 
| 94 |  |                 true -> | 
| 95 | :-( |                     Addr; | 
| 96 |  |                 false -> | 
| 97 | :-( |                     undefined | 
| 98 |  |             end; | 
| 99 |  |         {error, _} -> | 
| 100 | :-( |             undefined | 
| 101 |  |     end. | 
| 102 |  |  | 
| 103 |  | -spec my_if_addr(inet) -> inet:ip4_address() | undefined; | 
| 104 |  |                 (inet6) -> inet:ip6_address() | undefined. | 
| 105 |  | my_if_addr(Family) -> | 
| 106 |  |     % | 
| 107 |  |     % net:getifaddrs/1 is more convenient, but depends on Erlang/OTP 22.3+ with | 
| 108 |  |     % 'socket' support (and therefore wouldn't work on Windows, for example): | 
| 109 |  |     % | 
| 110 |  |     % net:getifaddrs(#{family => Family, flags => [up]}). | 
| 111 |  |     % | 
| 112 | 2 |     {ok, Interfaces} = inet:getifaddrs(), | 
| 113 | 2 |     Addrs = lists:filtermap(fun({_Name, Opts}) -> | 
| 114 | 6 |                                     filter_interface(Family, Opts) | 
| 115 |  |                             end, Interfaces), | 
| 116 | 2 |     case Addrs of | 
| 117 |  |         [Addr | _] -> | 
| 118 | :-( |             Addr; | 
| 119 |  |         [] -> | 
| 120 | 2 |             undefined | 
| 121 |  |     end. | 
| 122 |  |  | 
| 123 |  | -spec filter_interface(inet, [{atom(), tuple()}]) | 
| 124 |  |       -> {true, inet:ip4_address()} | false; | 
| 125 |  |                       (inet6, [{atom(), tuple()}]) | 
| 126 |  |       -> {true, inet:ip6_address()} | false. | 
| 127 |  | filter_interface(Family, Opts) -> | 
| 128 | 6 |     Flags = lists:flatten([Fs || {flags, Fs} <- Opts]), | 
| 129 | 6 |     case lists:member(up, Flags) of | 
| 130 |  |         true -> | 
| 131 | 6 |             Addrs = [Addr || {addr, Addr} <- Opts, | 
| 132 | 10 |                              family_matches(Addr, Family), | 
| 133 | 5 |                              is_public_addr(Addr)], | 
| 134 | 6 |             case Addrs of | 
| 135 |  |                 [Addr | _] -> | 
| 136 | :-( |                     {true, Addr}; | 
| 137 |  |                 [] -> | 
| 138 | 6 |                     false | 
| 139 |  |             end; | 
| 140 |  |         false -> | 
| 141 | :-( |             false | 
| 142 |  |     end. | 
| 143 |  |  | 
| 144 |  | -spec is_public_addr(inet:ip_address()) -> boolean(). | 
| 145 |  | is_public_addr({0, 0, 0, 0}) -> | 
| 146 | :-( |     false; | 
| 147 |  | is_public_addr({127, _, _, _}) -> | 
| 148 | 1 |     false; | 
| 149 |  | is_public_addr({169, 254, _, _}) -> | 
| 150 | :-( |     false; | 
| 151 |  | is_public_addr({10, _, _, _}) -> | 
| 152 | 1 |     false; | 
| 153 |  | is_public_addr({172, D2, _, _}) when D2 >= 16, D2 =< 31 -> | 
| 154 | 2 |     false; | 
| 155 |  | is_public_addr({192, 168, _, _}) -> | 
| 156 | :-( |     false; | 
| 157 |  | is_public_addr({0, 0, 0, 0, 0, 0, 0, 0}) -> | 
| 158 | :-( |     false; | 
| 159 |  | is_public_addr({0, 0, 0, 0, 0, 0, 0, 1}) -> | 
| 160 | 1 |     false; | 
| 161 |  | is_public_addr({65152, _, _, _, _, _, _, _}) -> | 
| 162 | 1 |     false; | 
| 163 |  | is_public_addr({D1, _, _, _, _, _, _, _}) when D1 >= 64512, D1 =< 65023 -> | 
| 164 | 1 |     false; | 
| 165 |  | is_public_addr({_, _, _, _}) -> | 
| 166 | 1 |     true; | 
| 167 |  | is_public_addr({_, _, _, _, _, _, _, _}) -> | 
| 168 | 1 |     true. | 
| 169 |  |  | 
| 170 |  | -spec family_matches(inet:ip_address(), inet | inet6) -> boolean(). | 
| 171 |  | family_matches({_, _, _, _}, inet) -> | 
| 172 | 4 |     true; | 
| 173 |  | family_matches({_, _, _, _}, inet6) -> | 
| 174 | 4 |     false; | 
| 175 |  | family_matches({_, _, _, _, _, _, _, _}, inet6) -> | 
| 176 | 3 |     true; | 
| 177 |  | family_matches({_, _, _, _, _, _, _, _}, inet) -> | 
| 178 | 3 |     false. | 
| 179 |  |  | 
| 180 |  | %% EUnit tests. | 
| 181 |  |  | 
| 182 |  | -ifdef(EUNIT). | 
| 183 |  | -define(PRIVATE_IPV4ADDR, {172, 20, 0, 2}). | 
| 184 |  | -define(PUBLIC_IPV4ADDR, {203, 0, 113, 2}). | 
| 185 |  | -define(PRIVATE_IPV6ADDR, {65000, 0, 0, 0, 0, 0, 0, 2}). | 
| 186 |  | -define(PUBLIC_IPV6ADDR, {8193, 3512, 0, 0, 0, 0, 0, 2}). | 
| 187 |  |  | 
| 188 |  | addr_test_() -> | 
| 189 | 2 |     [?_assert(is_public_addr(?PUBLIC_IPV4ADDR)), | 
| 190 | 1 |      ?_assert(is_public_addr(?PUBLIC_IPV6ADDR)), | 
| 191 | 1 |      ?_assert(family_matches(?PUBLIC_IPV4ADDR, inet)), | 
| 192 | 1 |      ?_assert(family_matches(?PUBLIC_IPV6ADDR, inet6)), | 
| 193 | 1 |      ?_assertNot(is_public_addr(?PRIVATE_IPV4ADDR)), | 
| 194 | 1 |      ?_assertNot(is_public_addr(?PRIVATE_IPV6ADDR)), | 
| 195 | 1 |      ?_assertNot(family_matches(?PRIVATE_IPV4ADDR, inet6)), | 
| 196 | 1 |      ?_assertNot(family_matches(?PRIVATE_IPV6ADDR, inet)), | 
| 197 | 1 |      ?_test(my_if_addr(inet)), | 
| 198 | 1 |      ?_test(my_if_addr(inet6))]. | 
| 199 |  | -endif. |