Packages
esip
1.0.28
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_socket.erl
%%%----------------------------------------------------------------------
%%% File : esip_socket.erl
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%% Purpose :
%%% Created : 6 Jan 2011 by Evgeniy Khramtsov <ekhramtsov@process-one.net>
%%%
%%%
%%% Copyright (C) 2002-2019 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_socket).
-define(GEN_SERVER, p1_server).
-behaviour(?GEN_SERVER).
%% API
-export([start_link/0, start_link/1, start_link/2, start/0, start/2,
connect/1, connect/2, send/2, socket_type/0, udp_recv/5, udp_init/2,
start_pool/0, get_pool_size/0, tcp_init/2, sockname/1, close/1]).
%% 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("stun.hrl").
-define(TCP_SEND_TIMEOUT, 15000).
-define(CONNECT_TIMEOUT, 20000).
-type addr() :: {inet:ip_address(), inet:port_number()}.
-record(state, {type = udp :: udp | tcp | tls,
addr :: addr(),
peer :: addr(),
sock :: port(),
buf = <<>> :: binary(),
max_size :: non_neg_integer(),
msg :: #sip{},
wait_size :: non_neg_integer(),
certfile :: iodata()}).
%%%===================================================================
%%% API
%%%===================================================================
%% @doc Start TCP client to connect
start_link() ->
?GEN_SERVER:start_link(?MODULE, [], [{max_queue, 5000}]).
%% @doc Start UDP worker
start_link(I) ->
?GEN_SERVER:start_link({local, get_proc(I)}, ?MODULE, [],
[{max_queue, 5000}]).
%% @doc Start TCP acceptor
start_link(Sock, Opts) ->
?GEN_SERVER:start_link(?MODULE, [Sock, Opts], [{max_queue, 5000}]).
%% @doc Start TCP client to connect, the callback
start() ->
supervisor:start_child(esip_tcp_sup, []).
socket_type() ->
raw.
start({gen_tcp, Sock}, Opts) ->
supervisor:start_child(esip_tcp_sup, [Sock, Opts]).
%% @doc TCP connect
connect(Addrs) ->
connect(Addrs, []).
%% @doc first is UDP connect, second is TCP
connect([AddrPort|_Addrs], #sip_socket{} = Sock) ->
{ok, Sock#sip_socket{peer = AddrPort}};
connect(Addrs, Opts) when is_list(Opts) ->
case start() of
{ok, Pid} ->
?GEN_SERVER:call(Pid, {connect, Addrs, Opts}, 60000);
Err ->
Err
end.
send(#sip_socket{pid = Pid} = SIPSocket, Data) when node(Pid) /= node() ->
Msg = {send, SIPSocket, Data},
case erlang:send(Pid, Msg, [noconnect, nosuspend]) of
nosuspend ->
{error, closed};
noconnect ->
{error, closed};
_ ->
ok
end;
send(#sip_socket{type = tls, sock = Sock}, Data) ->
fast_tls:send(Sock, Data);
send(#sip_socket{type = tcp, sock = Sock}, Data) ->
gen_tcp:send(Sock, Data);
send(#sip_socket{type = udp, sock = Sock, peer = {Addr, Port}}, Data) ->
NewAddr = case Addr of
{A, B, C, D} ->
case inet:sockname(Sock) of
{ok, {{_, _, _, _, _, _, _, _}, _}} ->
{0, 0, 0, 0, 0, 16#ffff,
(A bsl 8) bor B, (C bsl 8) bor D};
_ ->
Addr
end;
{0, 0, 0, 0, 0, 16#ffff, X, Y} ->
case inet:sockname(Sock) of
{ok, {{_, _, _, _}, _}} ->
<<A, B, C, D>> = <<X:16, Y:16>>,
{A, B, C, D};
_ ->
Addr
end;
_ ->
Addr
end,
gen_udp:send(Sock, NewAddr, Port, Data).
close(#sip_socket{pid = Pid} = SIPSocket) when node(Pid) /= node() ->
case erlang:send(Pid, {close, SIPSocket}, [noconnect, nosuspend]) of
nosuspend -> {error, closed};
noconnect -> {error, closed};
_ -> ok
end;
close(#sip_socket{type = tls, sock = Sock}) ->
fast_tls:close(Sock);
close(#sip_socket{type = tcp, sock = Sock}) ->
gen_tcp:close(Sock);
close(#sip_socket{type = udp, sock = Sock}) ->
gen_udp:close(Sock).
tcp_init(ListenSock, Opts) ->
{ok, {IP, Port}} = inet:sockname(ListenSock),
ViaHost = get_via_host(IP),
Transport = case proplists:get_bool(tls, Opts) of
false -> tcp;
true -> tls
end,
esip_transport:register_route(Transport, ViaHost, Port),
Opts.
udp_init(Sock, Opts) ->
{ok, {IP, Port}} = inet:sockname(Sock),
ViaHost = get_via_host(IP),
esip_transport:register_route(udp, ViaHost, Port),
lists:foreach(
fun(I) ->
Pid = get_proc(I),
Pid ! {init, Sock, self()}
end, lists:seq(1, get_pool_size())),
Opts.
udp_recv(Sock, Addr, Port, Data, Opts) ->
Pid = get_proc_by_hash({Addr, Port}),
Pid ! {udp, Sock, Addr, Port, Data},
Opts.
start_pool() ->
try
lists:foreach(
fun(I) ->
Spec = {get_proc(I),
{?MODULE, start_link, [I]},
permanent, brutal_kill, worker,
[?MODULE]},
{ok, _} = supervisor:start_child(esip_udp_sup, Spec)
end, lists:seq(1, get_pool_size()))
catch error:{badmatch, {error, _} = Err} ->
?ERROR_MSG("failed to start UDP pool: ~p", [Err]),
Err
end.
sockname(#sip_socket{type = tls, sock = Sock}) ->
fast_tls:sockname(Sock);
sockname(#sip_socket{sock = Sock}) ->
inet:sockname(Sock).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([]) ->
MaxSize = esip:get_config_value(max_msg_size),
{ok, #state{max_size = MaxSize}};
init([Sock, Opts]) ->
case inet:peername(Sock) of
{ok, PeerAddr} ->
case inet:sockname(Sock) of
{ok, MyAddr} ->
Transport = get_transport(Opts),
CertFile = get_certfile(Opts),
case maybe_starttls(Sock, Transport, CertFile, undefined,
{PeerAddr, MyAddr}, server) of
{ok, NewSock} ->
inet:setopts(Sock, [{active, once}]),
MaxSize = esip:get_config_value(max_msg_size),
{ok, #state{max_size = MaxSize,
sock = NewSock,
peer = PeerAddr,
addr = MyAddr,
certfile = CertFile,
type = Transport}};
{error, Err} ->
{stop, Err}
end;
{error, _Err} ->
{stop, normal}
end;
{error, _Err} ->
{stop, normal}
end.
handle_call({connect, Addrs, Opts}, _From, State) ->
Type = get_transport(Opts),
CertFile = get_certfile(Opts),
SNI = get_sni(Opts),
case do_connect(Addrs, ?CONNECT_TIMEOUT div (length(Addrs) + 1)) of
{ok, MyAddr, Peer, Sock} ->
case maybe_starttls(Sock, Type, CertFile, SNI,
{MyAddr, Peer}, client) of
{ok, NewSock} ->
inet:setopts(Sock, [{active, once}]),
NewState = State#state{type = Type, sock = NewSock,
certfile = CertFile,
addr = MyAddr, peer = Peer},
SIPSock = make_sip_socket(NewState),
esip_transport:register_socket(Peer, Type, SIPSock, CertFile),
{reply, {ok, SIPSock}, NewState};
{error, _} = Err ->
{stop, normal, Err, State}
end;
{error, _} = Err ->
{stop, normal, Err, State}
end;
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({udp, UDPSock, IP, Port, Data}, State) ->
SIPSock = #sip_socket{type = udp, sock = UDPSock,
addr = State#state.addr,
pid = self(),
peer = {IP, Port}},
esip:callback(data_in, [Data, SIPSock]),
datagram_transport_recv(SIPSock, Data),
{noreply, State};
handle_info({init, UDPSock, Owner}, State) ->
{ok, MyAddr} = inet:sockname(UDPSock),
SIPSock = #sip_socket{type = udp, sock = UDPSock,
addr = MyAddr, pid = Owner},
esip_transport:register_udp_listener(SIPSock),
{noreply, State#state{addr = MyAddr}};
handle_info({tcp, Sock, Data}, #state{type = tcp} = State) ->
inet:setopts(Sock, [{active, once}]),
esip:callback(data_in, [Data, make_sip_socket(State)]),
process_data(State, Data);
handle_info({tcp, _Sock, TLSData}, #state{type = tls} = State) ->
fast_tls:setopts(State#state.sock, [{active, once}]),
case fast_tls:recv_data(State#state.sock, TLSData) of
{ok, Data} ->
esip:callback(data_in, [Data, make_sip_socket(State)]),
process_data(State, Data);
_Err ->
{stop, normal, State}
end;
handle_info({send, SIPSocket, Data}, State) ->
send(SIPSocket, Data),
{noreply, State};
handle_info({close, SIPSocket}, State) ->
close(SIPSocket),
{noreply, State};
handle_info({tcp_closed, _Sock}, State) ->
{stop, normal, State};
handle_info({tcp_error, _Sock, _Reason}, State) ->
{stop, normal, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{peer = Peer, type = Type,
certfile = CertFile} = State) ->
SIPSock = make_sip_socket(State),
esip_transport:unregister_socket(Peer, Type, SIPSock, CertFile).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do_connect([{Addr, Port}|Addrs], ConnectTimeout) ->
case gen_tcp:connect(Addr, Port, connect_opts(), ConnectTimeout) of
{ok, Sock} ->
case inet:sockname(Sock) of
{error, _} = Err ->
fail_or_proceed(Err, Addrs, ConnectTimeout);
{ok, MyAddr} ->
{ok, MyAddr, {Addr, Port}, Sock}
end;
Err ->
fail_or_proceed(Err, Addrs, ConnectTimeout)
end.
fail_or_proceed(Err, [], _ConnectTimeout) ->
Err;
fail_or_proceed(_Err, Addrs, ConnectTimeout) ->
do_connect(Addrs, ConnectTimeout).
make_sip_socket(#state{type = Type, addr = MyAddr, peer = Peer, sock = Sock}) ->
#sip_socket{type = Type, addr = MyAddr,
sock = Sock, peer = Peer, pid = self()}.
process_data(#state{buf = Buf, max_size = MaxSize,
msg = undefined} = State, Data) ->
case process_crlf(<<Buf/binary, Data/binary>>, State) of
{ok, NewBuf} ->
case catch esip_codec:decode(NewBuf, stream) of
{ok, Msg, Tail} ->
case esip:get_hdr('content-length', Msg#sip.hdrs) of
N when is_integer(N), N >= 0, N =< MaxSize ->
process_data(State#state{buf = <<>>,
msg = Msg,
wait_size = N}, Tail);
_ ->
{stop, normal, State}
end;
more when size(NewBuf) < MaxSize ->
{noreply, State#state{buf = NewBuf}};
_ ->
{stop, normal, State}
end;
{error, _} ->
{stop, normal, State}
end;
process_data(#state{buf = Buf, max_size = MaxSize,
msg = Msg, wait_size = WaitSize} = State, Data) ->
NewBuf = <<Buf/binary, Data/binary>>,
case NewBuf of
<<Body:WaitSize/binary, Tail/binary>> ->
NewState = State#state{buf = Tail, msg = undefined},
stream_transport_recv(NewState, Msg#sip{body = Body}),
process_data(NewState, <<>>);
_ when size(NewBuf) < MaxSize ->
{noreply, State#state{buf = NewBuf}};
_ ->
{stop, normal, State}
end.
stream_transport_recv(State, Msg) ->
SIPSock = make_sip_socket(State),
case catch esip_transport:recv(SIPSock, Msg) of
{'EXIT', Reason} ->
?ERROR_MSG("transport layer failed:~n"
"** Packet: ~p~n** Reason: ~p",
[Msg, Reason]);
_ ->
ok
end.
process_crlf(<<"\r\n\r\n", Data/binary>>, State) ->
SIPSock = make_sip_socket(State),
SendRes = case esip:callback(message_in, [ping, SIPSock]) of
drop ->
ok;
pang ->
{error, closed};
_ ->
DataOut = <<"\r\n">>,
esip:callback(data_out, [DataOut, SIPSock]),
send(SIPSock, DataOut)
end,
case SendRes of
ok ->
process_crlf(Data, State);
{error, Why} ->
{error, Why}
end;
process_crlf(Data, _State) ->
{ok, Data}.
datagram_transport_recv(SIPSock, <<_:32, ?STUN_MAGIC:32, _/binary>> = Data) ->
case stun_codec:decode(Data, datagram) of
{ok, Msg} ->
case esip:callback(message_in, [ping, SIPSock]) of
drop ->
ok;
pang ->
Resp = prepare_stun_response(Msg),
ErrMsg = Resp#stun{class = error,
'ERROR-CODE' = {400, <<"Flow Failed">>}},
send_stun_response(SIPSock, ErrMsg);
_ ->
Resp = prepare_stun_response(Msg),
send_stun_response(SIPSock,
Resp#stun{'XOR-MAPPED-ADDRESS' =
SIPSock#sip_socket.peer})
end;
_ ->
ok
end;
datagram_transport_recv(SIPSock, Data) ->
case catch esip_codec:decode(Data) of
{ok, #sip{hdrs = Hdrs, body = Body} = Msg} ->
case esip:get_hdr('content-length', Hdrs) of
N when is_integer(N), N >= 0 ->
case Body of
<<_:N/binary>> ->
do_transport_recv(SIPSock, Msg);
<<Body1:N/binary, _/binary>> ->
do_transport_recv(SIPSock, Msg#sip{body = Body1});
_ ->
ok
end;
_ ->
do_transport_recv(SIPSock, Msg)
end;
Err ->
Err
end.
do_transport_recv(SIPSock, Msg) ->
case catch esip_transport:recv(SIPSock, Msg) of
{'EXIT', Reason} ->
?ERROR_MSG("transport layer failed:~n"
"** Packet: ~p~n** Reason: ~p",
[Msg, Reason]);
_ ->
ok
end.
connect_opts() ->
[binary,
{active, false},
{packet, 0},
{send_timeout, ?TCP_SEND_TIMEOUT},
{send_timeout_close, true}].
get_pool_size() ->
100.
get_proc_by_hash(Source) ->
N = erlang:phash2(Source, get_pool_size()) + 1,
get_proc(N).
get_proc(N) ->
list_to_atom("esip_udp_" ++ integer_to_list(N)).
get_via_host(IP) ->
case lists:sum(tuple_to_list(IP)) of
0 ->
undefined;
_ ->
iolist_to_binary(inet_parse:ntoa(IP))
end.
get_transport(Opts) ->
case proplists:get_bool(tls, Opts) of
false -> tcp;
true -> tls
end.
get_certfile(Opts) ->
case catch iolist_to_binary(proplists:get_value(certfile, Opts)) of
Filename when is_binary(Filename), Filename /= <<"">> ->
Filename;
_ ->
undefined
end.
get_sni(Opts) ->
proplists:get_value(sni, Opts).
maybe_starttls(_Sock, tls, undefined, _SNI, FromTo, server) ->
{{FromIP, FromPort}, {ToIP, ToPort}} = FromTo,
?ERROR_MSG("failed to start TLS connection ~s:~p -> ~s:~p: "
"option 'certfile' is not set",
[inet_parse:ntoa(FromIP), FromPort,
inet_parse:ntoa(ToIP), ToPort]),
{error, eprotonosupport};
maybe_starttls(Sock, tls, CertFile, SNI, _FromTo, Role) ->
Opts1 = case Role of
client -> [connect];
server -> []
end,
Opts2 = case CertFile of
undefined -> Opts1;
_ -> [{certfile, CertFile}|Opts1]
end,
Opts3 = case SNI of
undefined -> Opts2;
_ -> [{sni, SNI}|Opts2]
end,
case fast_tls:tcp_to_tls(Sock, Opts3) of
{ok, NewSock} when Role == client ->
case fast_tls:recv_data(NewSock, <<"">>) of
{ok, <<"">>} ->
{ok, NewSock};
{error, _} = Err ->
Err
end;
{ok, NewSock} when Role == server ->
{ok, NewSock};
{error, _} = Err ->
Err
end;
maybe_starttls(Sock, _Transport, _CertFile, _SNI, _FromTo, _Role) ->
{ok, Sock}.
prepare_stun_response(Msg) ->
Software = esip:get_config_value(software),
#stun{method = Msg#stun.method,
magic = Msg#stun.magic,
trid = Msg#stun.trid,
'SOFTWARE' = Software}.
send_stun_response(SIPSock, Msg) ->
DataOut = stun_codec:encode(Msg),
esip:callback(data_out, [DataOut, SIPSock]),
send(SIPSock, DataOut).