Packages
esip
1.0.21
1.0.60
1.0.59
1.0.58
1.0.57
1.0.56
1.0.54
1.0.53
1.0.52
1.0.51
1.0.50
1.0.49
1.0.48
1.0.47
1.0.46
1.0.45
1.0.44
1.0.43
1.0.42
1.0.41
1.0.39
1.0.37
1.0.34
1.0.33
1.0.32
1.0.31
1.0.30
1.0.29
1.0.28
1.0.27
1.0.26
1.0.24
1.0.23
1.0.22
1.0.21
1.0.20
1.0.18
1.0.16
1.0.15
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
ProcessOne SIP server component in Erlang
Current section
Files
Jump to
Current section
Files
src/esip_transport.erl
%%%----------------------------------------------------------------------
%%% File : esip_transport.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose : Transport layer
%%% Created : 14 Jul 2009 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2017 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(esip_transport).
%% API
-export([recv/2, send/2, start_link/0,
register_socket/4, unregister_socket/4,
register_udp_listener/1, unregister_udp_listener/1,
connect/1, connect/2, register_route/3, unregister_route/3,
via_transport_to_atom/1]).
-behaviour(gen_server).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-include("esip.hrl").
-include("esip_lib.hrl").
-include_lib("kernel/include/inet.hrl").
-record(route, {transport, host, port}).
-record(state, {}).
%%====================================================================
%% API
%%====================================================================
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
recv(SIPSock, #sip{type = request} = Req) ->
case prepare_request(SIPSock, Req) of
#sip{} = NewReq ->
case esip:callback(message_in, [NewReq, SIPSock]) of
drop ->
ok;
#sip{type = request} = NewReq1 ->
esip_transaction:process(SIPSock, NewReq1);
#sip{type = response} = Resp ->
send(SIPSock, Resp);
_ ->
esip_transaction:process(SIPSock, NewReq)
end;
_ ->
ok
end;
recv(SIPSock, #sip{type = response} = Resp) ->
case esip:callback(message_in, [Resp, SIPSock]) of
drop ->
ok;
#sip{type = response} = NewResp ->
esip_transaction:process(SIPSock, NewResp);
_ ->
esip_transaction:process(SIPSock, Resp)
end.
send(SIPSock, Msg) ->
case esip:callback(message_out, [Msg, SIPSock]) of
drop ->
ok;
Msg1 = #sip{} ->
do_send(SIPSock, Msg1);
_ ->
do_send(SIPSock, Msg)
end.
connect(SIPMsg) ->
connect(SIPMsg, []).
connect(#sip{type = request, uri = URI, hdrs = Hdrs} = Req, Opts) ->
NewURI = case esip:callback(locate, [Req]) of
U = #uri{} ->
U;
_ ->
case esip:get_hdrs('route', Hdrs) of
[{_, RouteURI, _}|_] ->
RouteURI;
_ ->
URI
end
end,
do_connect(NewURI, Opts);
connect(#sip{type = response, hdrs = Hdrs} = Resp, Opts) ->
NewVia = case esip:callback(locate, [Resp]) of
Via = #via{} ->
Via;
_ ->
[Via|_] = esip:get_hdrs('via', Hdrs),
Via
end,
do_connect(NewVia, Opts).
do_connect(URIorVia, Opts) ->
case resolve(URIorVia) of
{ok, AddrsPorts, Transport} ->
case lookup_socket(AddrsPorts, Transport, get_certfile(Opts)) of
{ok, Sock} ->
{ok, Sock};
_ ->
case Transport of
tcp ->
esip_socket:connect(AddrsPorts, Opts);
tls ->
SNI = get_server_name(URIorVia),
esip_socket:connect(AddrsPorts,
[tls, {sni, SNI}|Opts]);
udp ->
case get_udp_listener() of
{ok, UDPSock} ->
esip_socket:connect(AddrsPorts, UDPSock);
_ ->
{error, eprotonosupport}
end;
_ ->
{error, eprotonosupport}
end
end;
Err ->
Err
end.
via_transport_to_atom(<<"TLS">>) -> tls;
via_transport_to_atom(<<"TCP">>) -> tcp;
via_transport_to_atom(<<"UDP">>) -> udp;
via_transport_to_atom(_) -> unknown.
register_socket(Addr, tls, Sock, CertFile) ->
ets:insert(esip_socket, {{Addr, tls, CertFile}, Sock});
register_socket(Addr, Transport, Sock, _CertFile) ->
ets:insert(esip_socket, {{Addr, Transport}, Sock}).
unregister_socket(Addr, tls, Sock, CertFile) ->
ets:delete_object(esip_socket, {{Addr, tls, CertFile}, Sock});
unregister_socket(Addr, Transport, Sock, _CertFile) ->
ets:delete_object(esip_socket, {{Addr, Transport}, Sock}).
lookup_socket([Addr|Rest], Transport, CertFile) ->
case lookup_socket(Addr, Transport, CertFile) of
{ok, Sock} ->
{ok, Sock};
_ ->
lookup_socket(Rest, Transport, CertFile)
end;
lookup_socket([], _, _) ->
error;
lookup_socket(Addr, tls, CertFile) ->
case ets:lookup(esip_socket, {Addr, tls, CertFile}) of
[{_, Sock}|_] ->
{ok, Sock};
_ ->
error
end;
lookup_socket(Addr, Transport, _CertFile) ->
case ets:lookup(esip_socket, {Addr, Transport}) of
[{_, Sock}|_] ->
{ok, Sock};
_ ->
error
end.
register_udp_listener(SIPSock) ->
ets:insert(esip_socket, {udp, SIPSock}).
unregister_udp_listener(SIPSock) ->
ets:delete_object(esip_socket, {udp, SIPSock}).
get_udp_listener() ->
case ets:lookup(esip_socket, udp) of
[{_, SIPSock}|_] ->
{ok, SIPSock};
_ ->
error
end.
register_route(Transport, Host, Port) ->
ets:insert(esip_route, #route{transport = Transport,
host = Host,
port = Port}).
unregister_route(Transport, Host, Port) ->
ets:delete_object(esip_route, #route{transport = Transport,
host = Host,
port = Port}).
get_all_routes() ->
ets:tab2list(esip_route).
supported_transports() ->
supported_transports(all).
supported_transports(Type) ->
lists:flatmap(
fun(#route{transport = Transport}) ->
case Transport of
tls when Type == tls ->
[Transport];
_ when Type == tls ->
[];
_ ->
[Transport]
end
end, get_all_routes()).
supported_uri_schemes() ->
case supported_transports(tls) of
[] ->
[<<"sip">>];
_ ->
[<<"sips">>, <<"sip">>]
end.
%%====================================================================
%% gen_server callbacks
%%====================================================================
init([]) ->
ets:new(esip_socket, [public, named_table, bag]),
ets:new(esip_route, [public, named_table, bag, {keypos, #route.transport}]),
{ok, #state{}}.
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%====================================================================
%% Internal functions
%%====================================================================
prepare_request(#sip_socket{peer = {Addr, Port}, type = SockType},
#sip{hdrs = Hdrs} = Request) ->
case esip:split_hdrs('via', Hdrs) of
{[#via{params = Params, host = Host} = Via|Vias], RestHdrs} ->
case is_valid_via(Via, SockType)
andalso is_valid_hdrs(RestHdrs) of
true ->
Params1 = case host_to_ip(Host) of
{ok, Addr} ->
Params;
_ ->
esip:set_param(<<"received">>,
ip_to_host(Addr),
Params)
end,
Params2 = case esip:get_param(<<"rport">>, Params1, false) of
<<>> ->
esip:set_param(<<"rport">>,
list_to_binary(
integer_to_list(Port)),
Params1);
_ ->
Params1
end,
NewVia = {'via', [Via#via{params = Params2}|Vias]},
NewRestHdrs = case esip:get_hdr('max-forwards', RestHdrs) of
undefined ->
MF = esip:get_config_value(max_forwards),
[{'max-forwards', MF}|RestHdrs];
_ ->
RestHdrs
end,
Request#sip{hdrs = [NewVia|NewRestHdrs]};
false ->
error
end;
_ ->
error
end.
is_valid_hdrs(Hdrs) ->
try
From = esip:get_hdr('from', Hdrs),
To = esip:get_hdr('to', Hdrs),
CSeq = esip:get_hdr('cseq', Hdrs),
CallID = esip:get_hdr('call-id', Hdrs),
has_from(From) and has_to(To)
and (CSeq /= undefined) and (CallID /= undefined)
catch _:_ ->
false
end.
is_valid_via(#via{transport = _Transport,
params = Params}, _SockType) ->
case esip:get_param(<<"branch">>, Params) of
<<>> ->
false;
_ ->
true
end.
has_from({_, #uri{}, _}) -> true;
has_from(_) -> false.
has_to({_, #uri{}, _}) -> true;
has_to(_) -> false.
do_send(SIPSock, Msg) ->
case catch esip_codec:encode(Msg) of
{'EXIT', _} = Err ->
?ERROR_MSG("failed to encode:~n"
"** Packet: ~p~n** Reason: ~p",
[Msg, Err]);
Data ->
esip:callback(data_out, [Data, SIPSock]),
esip_socket:send(SIPSock, Data)
end.
host_to_ip(Host) when is_binary(Host) ->
host_to_ip(binary_to_list(Host));
host_to_ip(Host) ->
case Host of
"[" ->
{error, einval};
"[" ++ Rest ->
inet_parse:ipv6_address(string:substr(Rest, 1, length(Rest)-1));
_ ->
inet_parse:address(Host)
end.
ip_to_host({0,0,0,0,0,16#ffff,X,Y}) ->
<<A, B, C, D>> = <<X:16, Y:16>>,
ip_to_host({A, B, C, D});
ip_to_host({_, _, _, _} = Addr) ->
list_to_binary(inet_parse:ntoa(Addr));
ip_to_host(IPv6Addr) ->
iolist_to_binary(
[$[, esip_codec:to_lower(list_to_binary(inet_parse:ntoa(IPv6Addr))), $]]).
srv_prefix(tls) -> "_sips._tcp.";
srv_prefix(tcp) -> "_sip._tcp.";
srv_prefix(udp) -> "_sip._udp.".
default_port(tls) -> 5061;
default_port(_) -> 5060.
resolve(#uri{scheme = Scheme} = URI) ->
case lists:member(Scheme, supported_uri_schemes()) of
true ->
SupportedTransports = case Scheme of
<<"sips">> ->
supported_transports(tls);
_ ->
supported_transports()
end,
case SupportedTransports of
[] ->
{error, unsupported_transport};
_ ->
do_resolve(URI, SupportedTransports)
end;
false ->
{error, unsupported_uri_scheme}
end;
resolve(#via{transport = ViaTransport} = Via) ->
Transport = via_transport_to_atom(ViaTransport),
case lists:member(Transport, supported_transports()) of
true ->
do_resolve(Via, Transport);
false ->
{error, unsupported_transport}
end.
do_resolve(#uri{host = Host, port = Port, params = Params}, SupportedTransports) ->
case esip:to_lower(esip:get_param(<<"transport">>, Params)) of
<<>> ->
[FallbackTransport|_] = lists:sort(fun sort_transport/2,
SupportedTransports),
case host_to_ip(Host) of
{ok, Addr} ->
select_host_port(Addr, Port, FallbackTransport);
_ when is_integer(Port) ->
select_host_port(Host, Port, FallbackTransport);
_ ->
case naptr_srv_lookup(Host, SupportedTransports) of
{ok, _, _} = Res ->
Res;
_Err ->
select_host_port(Host, Port, FallbackTransport)
end
end;
TransportBinary ->
Transport = (catch erlang:binary_to_existing_atom(
TransportBinary, utf8)),
case lists:member(Transport, SupportedTransports) of
true ->
case host_to_ip(Host) of
{ok, Addr} ->
select_host_port(Addr, Port, Transport);
_ when is_integer(Port) ->
select_host_port(Host, Port, Transport);
_ ->
case srv_lookup(Host, [Transport]) of
{ok, _, _} = Res ->
Res;
_Err ->
select_host_port(Host, Port, Transport)
end
end;
false ->
{error, unsupported_transport}
end
end;
do_resolve(#via{transport = ViaTransport, host = Host,
port = Port, params = Params}, Transport) ->
NewPort = if ViaTransport == <<"UDP">> ->
case esip:get_param(<<"rport">>, Params) of
<<>> ->
Port;
RPort ->
case esip_codec:to_integer(RPort, 1, 65535) of
{ok, PortInt} -> PortInt;
_ -> Port
end
end;
true ->
Port
end,
NewHost = case esip:get_param(<<"received">>, Params) of
<<>> -> Host;
Host1 -> Host1
end,
case host_to_ip(NewHost) of
{ok, Addr} ->
select_host_port(Addr, NewPort, Transport);
_ when is_integer(NewPort) ->
select_host_port(NewHost, NewPort, Transport);
_ ->
case srv_lookup(NewHost, [Transport]) of
{ok, _, _} = Res ->
Res;
_Err ->
select_host_port(NewHost, NewPort, Transport)
end
end.
select_host_port(Addr, Port, Transport) when is_tuple(Addr) ->
NewPort = if is_integer(Port) ->
Port;
true ->
default_port(Transport)
end,
{ok, [{Addr, NewPort}], Transport};
select_host_port(Host, Port, Transport) when is_integer(Port) ->
case a_lookup(Host) of
{error, _} = Err ->
Err;
{ok, Addrs} ->
{ok, [{Addr, Port} || Addr <- Addrs], Transport}
end;
select_host_port(Host, Port, Transport) ->
NewPort = if is_integer(Port) ->
Port;
true ->
default_port(Transport)
end,
case a_lookup(Host) of
{ok, Addrs} ->
{ok, [{Addr, NewPort} || Addr <- Addrs], Transport};
Err ->
Err
end.
naptr_srv_lookup(Host, Transports) when is_binary(Host) ->
naptr_srv_lookup(binary_to_list(Host), Transports);
naptr_srv_lookup(Host, Transports) ->
case naptr_lookup(Host) of
{error, _Err} ->
srv_lookup(Host, Transports);
SRVHosts ->
case lists:filter(
fun({_, T}) ->
lists:member(T, Transports)
end, SRVHosts) of
[{SRVHost, Transport}|_] ->
case srv_lookup(SRVHost) of
{error, _} = Err ->
Err;
AddrsPorts ->
{ok, AddrsPorts, Transport}
end;
_ ->
srv_lookup(Host, Transports)
end
end.
srv_lookup(Host, Transports) when is_binary(Host) ->
srv_lookup(binary_to_list(Host), Transports);
srv_lookup(Host, Transports) ->
lists:foldl(
fun(_Transport, {ok, _, _} = Acc) ->
Acc;
(Transport, Err) ->
SRVHost = srv_prefix(Transport) ++ Host,
case srv_lookup(SRVHost) of
{error, _} = Err ->
Err;
AddrsPorts ->
{ok, AddrsPorts, Transport}
end
end, {error, nxdomain}, Transports).
naptr_lookup(Host) when is_binary(Host) ->
naptr_lookup(binary_to_list(Host));
naptr_lookup(Host) ->
case inet_res:getbyname(Host, naptr) of
{ok, #hostent{h_addr_list = Addrs}} ->
L = lists:flatmap(
fun({_Order, _Pref, "s", Service, _Regexp, SRVHost}) ->
case Service of
"sips+d2t" -> [{SRVHost, tls}];
"sip+d2t" -> [{SRVHost, tcp}];
"sip+d2u" -> [{SRVHost, udp}];
_ -> []
end;
(_) ->
[]
end, Addrs),
lists:reverse(
lists:sort(
fun({_, T1}, {_, T2}) ->
sort_transport(T1, T2)
end, L));
Err ->
Err
end.
srv_lookup(Host) when is_binary(Host) ->
srv_lookup(binary_to_list(Host));
srv_lookup(Host) ->
case inet_res:getbyname(Host, srv) of
{ok, #hostent{h_addr_list = Rs}} ->
case lists:flatmap(
fun({_, _, Port, HostName}) ->
case a_lookup(HostName) of
{ok, Addrs} ->
[{Addr, Port} || Addr <- Addrs];
_ ->
[]
end
end, lists:keysort(1, Rs)) of
[] ->
{error, nxdomain};
Res ->
Res
end;
Err ->
Err
end.
a_lookup(Host) when is_binary(Host) ->
a_lookup(binary_to_list(Host));
a_lookup(Host) ->
case inet_res:getbyname(Host, a) of
{ok, #hostent{h_addr_list = Addrs}} ->
{ok, Addrs};
Err ->
case inet_res:getbyname(Host, aaaa) of
{ok, #hostent{h_addr_list = Addrs}} ->
{ok, Addrs};
Err ->
Err
end
end.
sort_transport(udp, udp) -> true;
sort_transport(udp, tcp) -> true;
sort_transport(udp, tls) -> true;
sort_transport(tcp, udp) -> false;
sort_transport(tcp, tcp) -> true;
sort_transport(tcp, tls) -> true;
sort_transport(tls, udp) -> false;
sort_transport(tls, tcp) -> false;
sort_transport(tls, tls) -> true.
get_certfile(Opts) ->
case catch iolist_to_binary(proplists:get_value(certfile, Opts)) of
Filename when is_binary(Filename), Filename /= <<"">> ->
Filename;
_ ->
undefined
end.
get_server_name(#uri{host = Host}) ->
esip_codec:to_lower(Host);
get_server_name(#via{host = Host}) ->
esip_codec:to_lower(Host).