%%%------------------------------------------------------------------- %%% File : turn.erl %%% Author : Evgeniy Khramtsov %%% Description : Handles TURN allocations, see RFC5766 %%% Created : 23 Aug 2009 by Evgeniy Khramtsov %%% %%% %%% Copyright (C) 2002-2023 ProcessOne, SARL. 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. %%% %%%------------------------------------------------------------------- -module(turn). -define(GEN_FSM, p1_fsm). -behaviour(?GEN_FSM). %% API -export([start_link/1, start/1, stop/1, route/2]). %% gen_fsm callbacks -export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). %% gen_fsm states -export([wait_for_allocate/2, active/2]). -include("stun.hrl"). -include("stun_logger.hrl"). -define(UDP_READ_PACKETS, 100). -define(UDP_ACTIVE, 500). -define(UDP_RECBUF, 1024 * 1024). % 1 MiB -define(MAX_LIFETIME, 3600000). %% 1 hour -define(DEFAULT_LIFETIME, 600000). %% 10 minutes -define(PERMISSION_LIFETIME, 300000). %% 5 minutes -define(CHANNEL_LIFETIME, 600000). %% 10 minutes -define(INITIAL_BLACKLIST, [ %% Could be used to circumvent blocking of loopback addresses: {{0, 0, 0, 0}, 8}, {{0, 0, 0, 0, 0, 0, 0, 0}, 128}, %% RFC 6156, 9.1: "a TURN relay MUST NOT accept Teredo or 6to4 addresses". {{8193, 0, 0, 0, 0, 0, 0, 0}, 32}, % 2001::/32 (Teredo). {{8194, 0, 0, 0, 0, 0, 0, 0}, 16}]). % 2002::/16 (6to4). -type addr() :: {inet:ip_address(), inet:port_number()}. -type subnet() :: {inet:ip4_address(), 0..32} | {inet:ip6_address(), 0..128}. -type accesslist() :: [subnet()]. -export_type([accesslist/0]). -record(state, {sock_mod = gen_udp :: gen_udp | gen_tcp | fast_tls, sock :: inet:socket() | fast_tls:tls_socket() | undefined, addr = {{0,0,0,0}, 0} :: addr(), owner = self() :: pid(), username = <<"">> :: binary(), realm = <<"">> :: binary(), key = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary()}, server_name = <<"">> :: binary(), peers = #{} :: map(), channels = #{} :: map(), permissions = #{} :: map(), max_permissions :: non_neg_integer() | atom() | undefined, relay_ipv4_ip = {127,0,0,1} :: inet:ip4_address(), relay_ipv6_ip :: inet:ip6_address() | undefined, min_port = 49152 :: non_neg_integer(), max_port = 65535 :: non_neg_integer(), relay_addr :: addr() | undefined, relay_sock :: inet:socket() | undefined, last_trid :: non_neg_integer() | undefined, last_pkt = <<>> :: binary(), seq = 1 :: non_neg_integer(), life_timer :: reference() | undefined, blacklist_clients = [] :: accesslist(), whitelist_clients = [] :: accesslist(), blacklist_peers = [] :: accesslist(), whitelist_peers = [] :: accesslist(), hook_fun :: function() | undefined, session_id :: binary(), rcvd_bytes = 0 :: non_neg_integer(), rcvd_pkts = 0 :: non_neg_integer(), sent_bytes = 0 :: non_neg_integer(), sent_pkts = 0 :: non_neg_integer(), start_timestamp = get_timestamp() :: integer()}). %%==================================================================== %% API %%==================================================================== start_link(Opts) -> ?GEN_FSM:start_link(?MODULE, [Opts], []). start(Opts) -> supervisor:start_child(turn_tmp_sup, [Opts]). stop(Pid) -> ?GEN_FSM:send_all_state_event(Pid, stop). route(Pid, Msg) -> ?GEN_FSM:send_event(Pid, Msg). %%==================================================================== %% gen_fsm callbacks %%==================================================================== init([Opts]) -> process_flag(trap_exit, true), ID = proplists:get_value(session_id, Opts), Owner = proplists:get_value(owner, Opts), Username = proplists:get_value(username, Opts), Realm = proplists:get_value(realm, Opts), AddrPort = proplists:get_value(addr, Opts), SockMod = proplists:get_value(sock_mod, Opts), HookFun = proplists:get_value(hook_fun, Opts), BlacklistClients = proplists:get_value(blacklist_clients, Opts), WhitelistClients = proplists:get_value(whitelist_clients, Opts), BlacklistPeers = proplists:get_value(blacklist_peers, Opts), WhitelistPeers = proplists:get_value(whitelist_peers, Opts), State = #state{sock_mod = SockMod, sock = proplists:get_value(sock, Opts), key = proplists:get_value(key, Opts), relay_ipv4_ip = proplists:get_value(relay_ipv4_ip, Opts), relay_ipv6_ip = proplists:get_value(relay_ipv6_ip, Opts), min_port = proplists:get_value(min_port, Opts), max_port = proplists:get_value(max_port, Opts), max_permissions = proplists:get_value(max_permissions, Opts), server_name = proplists:get_value(server_name, Opts), username = Username, realm = Realm, addr = AddrPort, session_id = ID, owner = Owner, hook_fun = HookFun, blacklist_clients = BlacklistClients ++ ?INITIAL_BLACKLIST, whitelist_clients = WhitelistClients, blacklist_peers = BlacklistPeers ++ ?INITIAL_BLACKLIST, whitelist_peers = WhitelistPeers}, stun_logger:set_metadata(turn, SockMod, ID, AddrPort, Username), MaxAllocs = proplists:get_value(max_allocs, Opts), if is_pid(Owner) -> erlang:monitor(process, Owner); true -> ok end, TRef = erlang:start_timer(?DEFAULT_LIFETIME, self(), stop), case turn_sm:add_allocation(AddrPort, Username, Realm, MaxAllocs, self()) of ok -> run_hook(turn_session_start, State), {ok, wait_for_allocate, State#state{life_timer = TRef}} %% %% turn_sm:add_allocation/5 currently doesn't return errors. %% %% {error, Reason} -> %% {stop, Reason} end. wait_for_allocate(#stun{class = request, method = ?STUN_METHOD_ALLOCATE} = Msg, State) -> Family = case Msg#stun.'REQUESTED-ADDRESS-FAMILY' of ipv4 -> inet; ipv6 -> inet6; undefined -> inet; unknown -> unknown end, IsBlacklisted = is_blacklisted_client(State), Resp = prepare_response(State, Msg), if Msg#stun.'REQUESTED-TRANSPORT' == undefined -> ?LOG_NOTICE("Rejecting allocation request: no transport requested"), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(400)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; Msg#stun.'REQUESTED-TRANSPORT' == unknown -> ?LOG_NOTICE("Rejecting allocation request: unsupported transport"), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(442)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; Msg#stun.'DONT-FRAGMENT' == true -> ?LOG_NOTICE("Rejecting allocation request: dont-fragment not " "supported"), R = Resp#stun{class = error, 'UNKNOWN-ATTRIBUTES' = [?STUN_ATTR_DONT_FRAGMENT], 'ERROR-CODE' = stun_codec:error(420)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; Family == unknown -> ?LOG_NOTICE("Rejecting allocation request: unknown address family"), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(440)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; Family == inet6, State#state.relay_ipv6_ip == undefined -> ?LOG_NOTICE("Rejecting allocation request: IPv6 not supported"), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(440)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; IsBlacklisted -> ?LOG_NOTICE("Rejecting allocation request: Client address is " "blacklisted"), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(403)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)}; true -> RelayIP = case Family of inet -> State#state.relay_ipv4_ip; inet6 -> State#state.relay_ipv6_ip end, case allocate_addr(Family, RelayIP, {State#state.min_port, State#state.max_port}) of {ok, RelayPort, RelaySock} -> Lifetime = time_left(State#state.life_timer), AddrPort = stun:unmap_v4_addr(State#state.addr), RelayAddr = {RelayIP, RelayPort}, stun_logger:add_metadata( #{stun_relay => stun_logger:encode_addr(RelayAddr)}), ?LOG_NOTICE("Creating TURN allocation " "(lifetime: ~B seconds)", [Lifetime]), R = Resp#stun{class = response, 'XOR-RELAYED-ADDRESS' = RelayAddr, 'LIFETIME' = Lifetime, 'XOR-MAPPED-ADDRESS' = AddrPort}, NewState = send(State, R), {next_state, active, NewState#state{relay_sock = RelaySock, relay_addr = RelayAddr}}; Err -> ?LOG_ERROR("Cannot allocate TURN relay: ~s", [format_error(Err)]), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(508)}, run_hook(protocol_error, State, R), {stop, normal, send(State, R)} end end; wait_for_allocate(Event, State) -> ?LOG_ERROR("Unexpected event in 'wait_for_allocate': ~p", [Event]), {next_state, wait_for_allocate, State}. active(#stun{trid = TrID}, #state{last_trid = TrID} = State) -> send(State, State#state.last_pkt), {next_state, active, State}; active(#stun{class = request, method = ?STUN_METHOD_ALLOCATE} = Msg, State) -> ?LOG_NOTICE("Rejecting allocation request: Relay already allocated"), Resp = prepare_response(State, Msg), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(437)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; active(#stun{class = request, 'REQUESTED-ADDRESS-FAMILY' = ipv4, method = ?STUN_METHOD_REFRESH} = Msg, #state{relay_addr = {{_, _, _, _, _, _, _, _}, _}} = State) -> ?LOG_NOTICE("Rejecting refresh request: IPv4 requested for IPv6 peer"), Resp = prepare_response(State, Msg), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(443)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; active(#stun{class = request, 'REQUESTED-ADDRESS-FAMILY' = ipv6, method = ?STUN_METHOD_REFRESH} = Msg, #state{relay_addr = {{_, _, _, _}, _}} = State) -> ?LOG_NOTICE("Rejecting refresh request: IPv6 requested for IPv4 peer"), Resp = prepare_response(State, Msg), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(443)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; active(#stun{class = request, method = ?STUN_METHOD_REFRESH} = Msg, State) -> Resp = prepare_response(State, Msg), case Msg#stun.'LIFETIME' of 0 -> ?LOG_INFO("Client requested closing the TURN session"), R = Resp#stun{class = response, 'LIFETIME' = 0}, {stop, normal, send(State, R)}; LifeTime -> cancel_timer(State#state.life_timer), MSecs = if LifeTime == undefined -> ?DEFAULT_LIFETIME; true -> lists:min([LifeTime*1000, ?MAX_LIFETIME]) end, ?LOG_NOTICE("Refreshing TURN allocation (lifetime: ~B seconds)", [MSecs div 1000]), TRef = erlang:start_timer(MSecs, self(), stop), R = Resp#stun{class = response, 'LIFETIME' = (MSecs div 1000)}, {next_state, active, send(State#state{life_timer = TRef}, R)} end; active(#stun{class = request, 'XOR-PEER-ADDRESS' = XorPeerAddrs, method = ?STUN_METHOD_CREATE_PERMISSION} = Msg, State) -> {Addrs, _Ports} = lists:unzip(XorPeerAddrs), Resp = prepare_response(State, Msg), case update_permissions(State, Addrs) of {ok, NewState} -> R = Resp#stun{class = response}, {next_state, active, send(NewState, R)}; {error, Code} -> Err = {_, Txt} = stun_codec:error(Code), ?LOG_NOTICE("Rejecting permission creation request: ~s", [Txt]), R = Resp#stun{class = error, 'ERROR-CODE' = Err}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)} end; active(#stun{class = indication, method = ?STUN_METHOD_SEND, 'XOR-PEER-ADDRESS' = [{Addr, Port}], 'DATA' = Data}, State) when is_binary(Data) -> State1 = case maps:find(Addr, State#state.permissions) of {ok, _} -> gen_udp:send(State#state.relay_sock, Addr, Port, Data), count_sent(State, Data); error -> State end, {next_state, active, State1}; active(#stun{class = request, 'CHANNEL-NUMBER' = Channel, 'XOR-PEER-ADDRESS' = [{Addr, _Port} = Peer], method = ?STUN_METHOD_CHANNEL_BIND} = Msg, State) when is_integer(Channel), Channel >= 16#4000, Channel =< 16#7ffe -> Resp = prepare_response(State, Msg), case {maps:find(Channel, State#state.channels), maps:find(Peer, State#state.peers)} of {_, {ok, OldChannel}} when Channel /= OldChannel -> ?LOG_NOTICE("Rejecting channel binding request: Peer already bound " "to a different channel (~.16B)", [OldChannel]), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(400)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; {{ok, {OldPeer, _}}, _} when Peer /= OldPeer -> ?LOG_NOTICE("Rejecting channel binding request: Channel already " "bound to a different peer (~s)", [stun_logger:encode_addr(OldPeer)]), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(400)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; {FindResult, _} -> case update_permissions(State, [Addr]) of {ok, NewState0} -> _Op = case FindResult of {ok, {_, OldTRef}} -> cancel_timer(OldTRef), maybe_log(<<"Refreshing">>); _ -> maybe_log(<<"Binding">>) end, TRef = erlang:start_timer(?CHANNEL_LIFETIME, self(), {channel_timeout, Channel}), Peers = maps:put(Peer, Channel, State#state.peers), Chans = maps:put(Channel, {Peer, TRef}, State#state.channels), NewState = NewState0#state{peers = Peers, channels = Chans}, ?LOG_INFO("~s TURN channel ~.16B for peer ~s", [_Op, Channel, stun_logger:encode_addr(Peer)]), R = Resp#stun{class = response}, {next_state, active, send(NewState, R)}; {error, Code} -> Err = {_, Txt} = stun_codec:error(Code), ?LOG_NOTICE("Rejecting channel binding request: ~s", [Txt]), R = Resp#stun{class = error, 'ERROR-CODE' = Err}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)} end end; active(#stun{class = request, method = ?STUN_METHOD_CHANNEL_BIND} = Msg, State) -> ?LOG_NOTICE("Rejecting channel binding request: Missing channel number " "and/or peer address"), Resp = prepare_response(State, Msg), R = Resp#stun{class = error, 'ERROR-CODE' = stun_codec:error(400)}, run_hook(protocol_error, State, R), {next_state, active, send(State, R)}; active(#turn{channel = Channel, data = Data}, State) -> case maps:find(Channel, State#state.channels) of {ok, {{Addr, Port}, _}} -> gen_udp:send(State#state.relay_sock, Addr, Port, Data), State1 = count_sent(State, Data), {next_state, active, State1}; error -> {next_state, active, State} end; active(Event, State) -> ?LOG_ERROR("Unexpected event in 'active': ~p", [Event]), {next_state, active, State}. handle_event(stop, _StateName, State) -> {stop, normal, State}; handle_event(Event, StateName, State) -> ?LOG_ERROR("Unexpected event in '~s': ~p", [StateName, Event]), {next_state, StateName, State}. handle_sync_event(_Event, _From, StateName, State) -> {reply, {error, badarg}, StateName, State}. handle_info({udp, _Sock, Addr, Port, Data}, StateName, State) -> Peer = {Addr, Port}, case {maps:find(Addr, State#state.permissions), maps:find(Peer, State#state.peers)} of {{ok, _}, {ok, Channel}} -> TurnMsg = #turn{channel = Channel, data = Data}, State1 = count_rcvd(State, Data), {next_state, StateName, send(State1, TurnMsg)}; {{ok, _}, error} -> Seq = State#state.seq, Ind = #stun{class = indication, method = ?STUN_METHOD_DATA, trid = Seq, 'XOR-PEER-ADDRESS' = [Peer], 'DATA' = Data}, State1 = count_rcvd(State, Data), {next_state, StateName, send(State1#state{seq = Seq+1}, Ind)}; {error, _} -> {next_state, StateName, State} end; handle_info({udp_passive, Sock}, StateName, State) -> inet:setopts(Sock, [{active, ?UDP_ACTIVE}]), {next_state, StateName, State}; handle_info({timeout, _Tref, stop}, _StateName, State) -> {stop, normal, State}; handle_info({timeout, _Tref, {permission_timeout, Addr}}, StateName, State) -> ?LOG_INFO("TURN permission for ~s timed out", [stun_logger:encode_addr(Addr)]), case maps:find(Addr, State#state.permissions) of {ok, _} -> Perms = maps:remove(Addr, State#state.permissions), {next_state, StateName, State#state{permissions = Perms}}; error -> {next_state, StateName, State} end; handle_info({timeout, _Tref, {channel_timeout, Channel}}, StateName, State) -> case maps:find(Channel, State#state.channels) of {ok, {Peer, _}} -> ?LOG_INFO("TURN channel ~.16B for peer ~s timed out", [Channel, stun_logger:encode_addr(Peer)]), Chans = maps:remove(Channel, State#state.channels), Peers = maps:remove(Peer, State#state.peers), {next_state, StateName, State#state{channels = Chans, peers = Peers}}; error -> {next_state, StateName, State} end; handle_info({'DOWN', _Ref, _, _, _}, _StateName, State) -> {stop, normal, State}; handle_info(Info, StateName, State) -> ?LOG_ERROR("Unexpected info in '~s': ~p", [StateName, Info]), {next_state, StateName, State}. terminate(_Reason, _StateName, State) -> AddrPort = State#state.addr, Username = State#state.username, Realm = State#state.realm, RcvdBytes = State#state.rcvd_bytes, RcvdPkts = State#state.rcvd_pkts, SentBytes = State#state.sent_bytes, SentPkts = State#state.sent_pkts, case State#state.relay_addr of undefined -> ok; _RAddrPort -> ?LOG_INFO("Deleting TURN allocation") end, if is_pid(State#state.owner) -> stun:stop(State#state.owner); true -> ok end, ?LOG_NOTICE("Relayed ~B KiB (in ~B B / ~B packets, out ~B B / ~B packets), " "duration: ~B seconds", [round((RcvdBytes + SentBytes) / 1024), RcvdBytes, RcvdPkts, SentBytes, SentPkts, get_duration(State, second)]), run_hook(turn_session_stop, State), turn_sm:del_allocation(AddrPort, Username, Realm). code_change(_OldVsn, StateName, State, _Extra) -> {ok, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- update_permissions(_State, []) -> {error, 400}; update_permissions(#state{permissions = Perms, max_permissions = Max}, Addrs) when map_size(Perms) + length(Addrs) > Max -> {error, 508}; update_permissions(#state{relay_addr = {IP, _}} = State, Addrs) -> case {families_match(IP, Addrs), is_blacklisted_peer(State, Addrs)} of {true, false} -> Perms = lists:foldl( fun(Addr, Acc) -> _Op = case maps:find(Addr, Acc) of {ok, OldTRef} -> cancel_timer(OldTRef), maybe_log(<<"Refreshing">>); error -> maybe_log(<<"Creating">>) end, TRef = erlang:start_timer( ?PERMISSION_LIFETIME, self(), {permission_timeout, Addr}), ?LOG_INFO("~s TURN permission for ~s", [_Op, stun_logger:encode_addr(Addr)]), maps:put(Addr, TRef, Acc) end, State#state.permissions, Addrs), {ok, State#state{permissions = Perms}}; {false, _} -> {error, 443}; {_, true} -> {error, 403} end. send(State, Pkt) when is_binary(Pkt) -> SockMod = State#state.sock_mod, Sock = State#state.sock, if SockMod == gen_udp -> {Addr, Port} = State#state.addr, gen_udp:send(Sock, Addr, Port, Pkt); true -> case SockMod:send(Sock, Pkt) of ok -> ok; _ -> ?LOG_INFO("Cannot respond to client: Connection closed"), exit(normal) end end; send(State, Msg) -> ?LOG_DEBUG(#{verbatim => {"Sending:~n~s", [stun_codec:pp(Msg)]}}), Key = State#state.key, case Msg of #stun{class = indication} -> send(State, stun_codec:encode(Msg)), State; #stun{class = response} -> Pkt = stun_codec:encode(Msg, Key), send(State, Pkt), State#state{last_trid = Msg#stun.trid, last_pkt = Pkt}; _ -> send(State, stun_codec:encode(Msg, Key)), State end. time_left(TRef) -> erlang:read_timer(TRef) div 1000. %% Simple port randomization algorithm from %% draft-ietf-tsvwg-port-randomization-04 allocate_addr(Family, Addr, {Min, Max}) -> Count = Max - Min + 1, Next = Min + stun:rand_uniform(Count) - 1, allocate_addr(Family, Addr, Min, Max, Next, Count). allocate_addr(_Family, _Addr, _Min, _Max, _Next, 0) -> {error, eaddrinuse}; allocate_addr(Family, Addr, Min, Max, Next, Count) -> case gen_udp:open(Next, [binary, Family, {ip, Addr}, {active, ?UDP_ACTIVE}, {recbuf, ?UDP_RECBUF}, {read_packets, ?UDP_READ_PACKETS}]) of {ok, Sock} -> case inet:sockname(Sock) of {ok, {_, Port}} -> {ok, Port, Sock}; Err -> Err end; {error, eaddrinuse} -> if Next == Max -> allocate_addr(Family, Addr, Min, Max, Min, Count-1); true -> allocate_addr(Family, Addr, Min, Max, Next+1, Count-1) end; {error, eaddrnotavail} when is_tuple(Addr) -> allocate_addr(Family, any, Min, Max, Next, Count); Err -> Err end. families_match(RelayAddr, Addrs) -> lists:all(fun(Addr) -> family_matches(RelayAddr, Addr) end, Addrs). family_matches({_, _, _, _}, {_, _, _, _}) -> true; family_matches({_, _, _, _, _, _, _, _}, {_, _, _, _, _, _, _, _}) -> true; family_matches(_Addr1, _Addr2) -> false. is_blacklisted_client(#state{addr = {IP, _Port}, blacklist_clients = Blacklist, whitelist_clients = Whitelist}) -> is_blacklisted(Blacklist, Whitelist, [IP]). is_blacklisted_peer(#state{blacklist_peers = Blacklist, whitelist_peers = Whitelist}, IPs) -> is_blacklisted(Blacklist, Whitelist, IPs). is_blacklisted(Blacklist, Whitelist, IPs) -> lists:any( fun(IP) -> lists:any( fun({Net, Mask}) -> match_subnet(IP, Net, Mask) end, Blacklist) end, IPs) andalso not lists:any( fun(IP) -> lists:any( fun({Net, Mask}) -> match_subnet(IP, Net, Mask) end, Whitelist) end, IPs). match_subnet({_, _, _, _} = IP, {_, _, _, _} = Net, Mask) -> IPInt = ip_to_integer(IP), NetInt = ip_to_integer(Net), M = bnot (1 bsl (32 - Mask) - 1), IPInt band M =:= NetInt band M; match_subnet({_, _, _, _, _, _, _, _} = IP, {_, _, _, _, _, _, _, _} = Net, Mask) -> IPInt = ip_to_integer(IP), NetInt = ip_to_integer(Net), M = bnot (1 bsl (128 - Mask) - 1), IPInt band M =:= NetInt band M; match_subnet({_, _, _, _} = IP, {0, 0, 0, 0, 0, 16#FFFF, _, _} = Net, Mask) -> IPInt = ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}) + ip_to_integer(IP), NetInt = ip_to_integer(Net), M = bnot (1 bsl (128 - Mask) - 1), IPInt band M =:= NetInt band M; match_subnet({0, 0, 0, 0, 0, 16#FFFF, _, _} = IP, {_, _, _, _} = Net, Mask) -> IPInt = ip_to_integer(IP) - ip_to_integer({0, 0, 0, 0, 0, 16#FFFF, 0, 0}), NetInt = ip_to_integer(Net), M = bnot (1 bsl (32 - Mask) - 1), IPInt band M =:= NetInt band M; match_subnet(_, _, _) -> false. ip_to_integer({IP1, IP2, IP3, IP4}) -> IP1 bsl 8 bor IP2 bsl 8 bor IP3 bsl 8 bor IP4; ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}) -> IP1 bsl 16 bor IP2 bsl 16 bor IP3 bsl 16 bor IP4 bsl 16 bor IP5 bsl 16 bor IP6 bsl 16 bor IP7 bsl 16 bor IP8. format_error({error, Reason}) -> case inet:format_error(Reason) of "unknown POSIX error" -> Reason; Res -> Res end. cancel_timer(undefined) -> ok; cancel_timer(TRef) -> case erlang:cancel_timer(TRef) of false -> receive {timeout, TRef, _} -> ok after 0 -> ok end; _ -> ok end. get_timestamp() -> erlang:monotonic_time(). get_duration(State, Unit) -> erlang:convert_time_unit(get_duration(State), native, Unit). get_duration(#state{start_timestamp = Start}) -> get_timestamp() - Start. prepare_response(State, Msg) -> #stun{method = Msg#stun.method, magic = Msg#stun.magic, trid = Msg#stun.trid, 'SOFTWARE' = State#state.server_name}. count_sent(#state{sent_bytes = SentSize, sent_pkts = SentPkts} = State, Data) -> State#state{sent_bytes = SentSize + byte_size(Data), sent_pkts = SentPkts + 1}. count_rcvd(#state{rcvd_bytes = RcvdSize, rcvd_pkts = RcvdPkts} = State, Data) -> State#state{rcvd_bytes = RcvdSize + byte_size(Data), rcvd_pkts = RcvdPkts + 1}. run_hook(HookName, State) -> run_hook(HookName, State, #stun{}). run_hook(HookName, #state{session_id = ID, username = User, realm = Realm, addr = Client, sock_mod = SockMod, hook_fun = HookFun} = State, #stun{'ERROR-CODE' = Reason}) when is_function(HookFun) -> Info1 = #{id => ID, user => User, realm => Realm, client => Client, transport => stun_logger:encode_transport(SockMod), reason => Reason}, Info2 = case {HookName, State} of {turn_session_start, _State} -> Info1; {turn_session_stop, #state{sent_bytes = SentBytes, sent_pkts = SentPkts, rcvd_bytes = RcvdBytes, rcvd_pkts = RcvdPkts}} -> Info1#{sent_bytes => SentBytes, sent_pkts => SentPkts, rcvd_bytes => RcvdBytes, rcvd_pkts => RcvdPkts, duration => get_duration(State)}; {protocol_error, _State} -> Info1 end, ?LOG_DEBUG("Running '~s' hook", [HookName]), try HookFun(HookName, Info2) catch _:Err -> ?LOG_ERROR("Hook '~s' failed: ~p", [HookName, Err]) end; run_hook(HookName, _State, _Msg) -> ?LOG_DEBUG("No callback function specified for '~s' hook", [HookName]), ok. -ifdef(USE_OLD_LOGGER). -ifdef(debug). maybe_log(Term) -> Term. -else. maybe_log(_Term) -> ok. -endif. -else. maybe_log(Term) -> Term. -endif.