Current section

Files

Jump to
esip src esip_transport.erl
Raw

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).