diff options
Diffstat (limited to 'lib/megaco/src/tcp/megaco_tcp.erl')
-rw-r--r-- | lib/megaco/src/tcp/megaco_tcp.erl | 692 |
1 files changed, 692 insertions, 0 deletions
diff --git a/lib/megaco/src/tcp/megaco_tcp.erl b/lib/megaco/src/tcp/megaco_tcp.erl new file mode 100644 index 0000000000..45fd35eabc --- /dev/null +++ b/lib/megaco/src/tcp/megaco_tcp.erl @@ -0,0 +1,692 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1999-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%----------------------------------------------------------------- +%% +%% Purpose: +%% Interface the TPKT (TCP/IP) transport module for Megaco/H.248 +%% +%%----------------------------------------------------------------- +-module(megaco_tcp). + +-behaviour(gen_server). + + +%%----------------------------------------------------------------- +%% Include files +%%----------------------------------------------------------------- +-include_lib("megaco/include/megaco.hrl"). +-include_lib("megaco/src/tcp/megaco_tcp.hrl"). +-include_lib("megaco/src/app/megaco_internal.hrl"). + + +-define(d1(F, A), ?d("~p " ++ F, [self()|A])). +-define(d2(F), ?d1(F, [])). + + +%%----------------------------------------------------------------- +%% External exports +%%----------------------------------------------------------------- +-export([ + start_transport/0, %% Start TPKT transport service + stop_transport/1, %% Stop TPKT transport service + listen/2, %% Starts a new listener socket + connect/2, %% Used on client side to connect server + socket/1, %% Returns the inet socket + send_message/2, %% Used to send data on connection + block/1, %% Used to block the socket for incomming + %% messages + unblock/1, %% Used to unblock the node + close/1, %% Used on both sides to close connection + + upgrade_receive_handle/2 + ]). + +%% Statistics exports +-export([ + get_stats/0, get_stats/1, get_stats/2, + reset_stats/0, reset_stats/1 + ]). + + +%%----------------------------------------------------------------- +%% Internal exports +%%----------------------------------------------------------------- +-export([ + start_link/1, %% Start TCP/IP net server + init/1, %% + terminate/2, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3, + start_connection/2 + ]). + + +%%----------------------------------------------------------------- +%% Server state record +%%----------------------------------------------------------------- +-record(state, {supervisor_pid, linkdb}). + + +%%----------------------------------------------------------------- +%% External interface functions +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: get_stats/0, get_stats/1, get_stats/2 +%% Description: Retreive statistics (counters) for TCP +%%----------------------------------------------------------------- +get_stats() -> + megaco_stats:get_stats(megaco_tcp_stats). + +get_stats(Socket) -> + megaco_stats:get_stats(megaco_tcp_stats, Socket). + +get_stats(Socket, Counter) -> + megaco_stats:get_stats(megaco_tcp_stats, Socket, Counter). + + +%%----------------------------------------------------------------- +%% Func: reset_stats/0, reaet_stats/1 +%% Description: Reset statistics (counters) for TCP +%%----------------------------------------------------------------- +reset_stats() -> + megaco_stats:reset_stats(megaco_tcp_stats). + +reset_stats(Socket) -> + megaco_stats:reset_stats(megaco_tcp_stats, Socket). + + +%%----------------------------------------------------------------- +%% Func: start_transport/0 +%% Description: Starts the TPKT transport service +%%----------------------------------------------------------------- +start_transport() -> + ?d2("start_transport -> entry"), + (catch megaco_stats:init(megaco_tcp_stats)), + megaco_tcp_sup:start_link(). + + +%%----------------------------------------------------------------- +%% Func: stop_transport/1, 2 +%% Description: Stop the TPKT transport service +%%----------------------------------------------------------------- +stop_transport(Pid) -> + (catch unlink(Pid)), + stop_transport(Pid, shutdown). + +stop_transport(Pid, Reason) -> + ?d1("stop_transport -> entry with" + "~n Pid: ~p" + "~n Reason: ~p", [Pid, Reason]), + exit(Pid, Reason). + + +%%----------------------------------------------------------------- +%% Func: listen/2 +%% Description: Starts new TPKT listener sockets +%%----------------------------------------------------------------- +listen(SupPid, Parameters) -> + ?d1("listen -> entry with" + "~n SupPid: ~p" + "~n Parameters: ~p", [SupPid, Parameters]), + ProcList = supervisor:which_children(SupPid), + case lists:keysearch(megaco_tcp, 1, ProcList) of + {value, {_Name, Pid, _Type, _Modules}} -> + ?d1("listen -> found listener: " + "~n Pid: ~p", [Pid]), + call(Pid, {add_listener, Parameters}); + false -> + {error, no_tcp_server} + end. + + +%%----------------------------------------------------------------- +%% Func: connect +%% Description: Function is used when opening an TCP socket +%% at the MG side when trying to connect an MGC +%%----------------------------------------------------------------- +connect(SupPid, Parameters) -> + ?d1("connect -> entry with" + "~n SupPid: ~p" + "~n Parameters: ~p", [SupPid, Parameters]), + Mand = [host, port, receive_handle], + case parse_options(Parameters, #megaco_tcp{}, Mand) of + {ok, Rec} -> + + ?d1("connect -> options parsed: " + "~n Rec: ~p", [Rec]), + + #megaco_tcp{host = Host, + port = Port, + options = Options} = Rec, + + IpOpt = [binary, {packet, tpkt}, {active, once} | Options], + + %%------------------------------------------------------ + %% Connect the other side + case (catch gen_tcp:connect(Host, Port, IpOpt)) of + {ok, Socket} -> + ?d1("connect -> connected: " + "~n Socket: ~p", [Socket]), + %%---------------------------------------------- + %% Socket up start a new control process + Rec2 = Rec#megaco_tcp{socket = Socket}, + case start_connection(SupPid, Rec2) of + {ok, Pid} -> + ?d1("connect -> connection started: " + "~n Pid: ~p", [Pid]), + gen_tcp:controlling_process(Socket, Pid), + ?d2("connect -> control transferred"), + {ok, Socket, Pid}; + {error, Reason} -> + ?d1("connect -> failed starting connection: " + "~n Reason: ~p", [Reason]), + {error, Reason} + end; + + {error, Reason} -> + ?d1("connect -> failed connecting: " + "~n Reason: ~p", [Reason]), + Error = {error, {gen_tcp_connect, Reason}}, + ?tcp_debug(Rec, "tcp connect failed", [Error]), + Error; + + {'EXIT', _Reason} = Exit -> + ?d1("connect -> connect exited: " + "~n Exit: ~p", [Exit]), + Error = {error, {gen_tcp_connect, Exit}}, + ?tcp_debug(Rec, "tcp connect failed", [Error]), + Error + + end; + + {error, _Reason} = Error -> + ?d1("connect -> failed parsing options: " + "~n Error: ~p", [Error]), + ?tcp_debug(#megaco_tcp{}, "tcp connect failed", + [Error, {options, Parameters}]), + Error + end. + + +%%----------------------------------------------------------------- +%% Func: send_message +%% Description: Function is used for sending data on the TCP socket +%%----------------------------------------------------------------- +send_message(Socket, Data) -> + ?d1("send_message -> entry with" + "~n Socket: ~p" + "~n size(Data): ~p", [Socket, sz(Data)]), + {Size, NewData} = add_tpkt_header(Data), + Res = gen_tcp:send(Socket, NewData), + case Res of + ok -> + incNumOutMessages(Socket), + incNumOutOctets(Socket, Size); + _ -> + ok + end, + Res. + +-ifdef(megaco_debug). +sz(Bin) when is_binary(Bin) -> + size(Bin); +sz(List) when is_list(List) -> + length(List). +-endif. + + +%%----------------------------------------------------------------- +%% Func: block +%% Description: Function is used for blocking incomming messages +%% on the TCP socket +%%----------------------------------------------------------------- +block(Socket) -> + ?tcp_debug({socket, Socket}, "tcp block", []), + inet:setopts(Socket, [{active, false}]). + + +%%----------------------------------------------------------------- +%% Func: unblock +%% Description: Function is used for blocking incomming messages +%% on the TCP socket +%%----------------------------------------------------------------- +unblock(Socket) -> + ?tcp_debug({socket, Socket}, "tcp unblock", []), + inet:setopts(Socket, [{active, once}]). + + +%%----------------------------------------------------------------- +%% Func: close +%% Description: Function is used for closing the TCP socket +%%----------------------------------------------------------------- +close(Socket) -> + ?tcp_debug({socket, Socket}, "tcp close", []), + gen_tcp:close(Socket). + + +%%----------------------------------------------------------------- +%% Func: socket +%% Description: Returns the inet socket +%%----------------------------------------------------------------- +socket(Socket) -> + Socket. + +upgrade_receive_handle(Pid, NewHandle) + when is_pid(Pid) andalso is_record(NewHandle, megaco_receive_handle) -> + megaco_tcp_connection:upgrade_receive_handle(Pid, NewHandle). + + +%%----------------------------------------------------------------- +%% Internal Interface functions +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: start_link/1 +%% Description: Starts the net server +%%----------------------------------------------------------------- +start_link(Args) -> + gen_server:start_link(?MODULE, Args, []). + + +%%----------------------------------------------------------------- +%% Func: start_connection +%% Description: Function is used for starting up a connection +%% process +%%----------------------------------------------------------------- +start_connection(SupPid, #megaco_tcp{socket = Socket} = TcpRec) -> + ?d1("start_connection -> entry with" + "~n SupPid: ~p" + "~n Socket: ~p", [SupPid, Socket]), + + case connection_sup(SupPid) of + {ok, ConnSupPid} -> + ?d1("start_connection -> found connection supervisor: " + "~n ConnSupPid: ~p", [ConnSupPid]), + ?tcp_debug(TcpRec, "tcp connect", []), + case create_connection(ConnSupPid, TcpRec) of + {ok, Pid} -> + ?d1("start_connection -> started: " + "~n Pid: ~p", [Pid]), + ?tcp_debug(TcpRec, "connect handler started", [Pid]), + create_snmp_counters(Socket), + {ok, Pid}; + {error, Reason} -> + ?d1("start_connection -> failed starting: " + "~n Reason: ~p", [Reason]), + Error = {error, {controlling_process_not_started, Reason}}, + ?tcp_debug(TcpRec, "tcp connect failed", [Error]), + Error + end; + {error, _Reason} -> + ?d2("start_connection -> could not find connection supervisor"), + Error = {error, no_connection_supervisor}, + ?tcp_debug(TcpRec, "tcp connect failed", [Error]), + Error + end. + +connection_sup(Pid) -> + megaco_tcp_sup:which_connection_sup(Pid). + +create_connection(Pid, Rec) -> + megaco_tcp_connection_sup:start_child(Pid, Rec). + +create_snmp_counters(Socket) -> + Counters = [medGwyGatewayNumInMessages, + medGwyGatewayNumInOctets, + medGwyGatewayNumOutMessages, + medGwyGatewayNumOutOctets, + medGwyGatewayNumErrors], + create_snmp_counters(Socket, Counters). + +create_snmp_counters(_Socket, []) -> + ok; +create_snmp_counters(Socket, [Counter|Counters]) -> + Key = {Socket, Counter}, + ets:insert(megaco_tcp_stats, {Key, 0}), + create_snmp_counters(Socket, Counters). + + +%%----------------------------------------------------------------- +%% Server functions +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: init/1 +%% Description: Init funcion for the supervisor +%%----------------------------------------------------------------- +init({SupPid, _}) -> + process_flag(trap_exit, true), + {ok, #state{supervisor_pid = SupPid}}. + +%%----------------------------------------------------------------- +%% Func: terminate/1 +%% Description: Termination function for the generic server +%%----------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + + +%%----------------------------------------------------------------- +%% Internal Functions +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: start_tcp_listener/2 +%% Description: Function which parses the list of transport layers +%% to start +%%----------------------------------------------------------------- +start_tcp_listener(P, State) -> + ?d1("start_tcp_listener -> entry with" + "~n P: ~p", [P]), + case setup(State#state.supervisor_pid, P) of + {ok, Pid, Data} -> + ?d1("start_tcp_listener -> setup ok" + "~n Pid: ~p" + "~n Data: ~p", [Pid, Data]), + link(Pid), + {reply, ok, + State#state{linkdb=[{Pid, Data} | State#state.linkdb]}}; + {error, Reason} -> + ?d1("start_tcp_listener -> setup failed" + "~n Reason: ~p", [Reason]), + {reply, {error, {could_not_start_listener, Reason}}, State} + end. + + +%%----------------------------------------------------------------- +%% Func: handle_call/3 +%% Description: Handling call messages (really just garbage) +%%----------------------------------------------------------------- +handle_call({add_listener, Parameters}, _From, State) -> + ?d1("handle_call(add_listener) -> entry with" + "~n Parameters: ~p", [Parameters]), + start_tcp_listener(Parameters, State); +handle_call(Req, From, State) -> + warning_msg("received unexpected request from ~p: " + "~n~w", [From, Req]), + {noreply, State}. + + +%%------------------------------------------------------------ +%% Func: handle_cast/2 +%% Description: Handling cast messages (really just garbage) +%%------------------------------------------------------------ +handle_cast(Msg, State) -> + warning_msg("received unexpected message: " + "~n~w", [Msg]), + {noreply, State}. + + +%%----------------------------------------------------------------- +%% Func: handle_info/2 +%% Description: Handling non call/cast messages, eg exit messages +%%----------------------------------------------------------------- +handle_info({'EXIT', Pid, Reason}, State) when is_pid(Pid) -> + %% Accept process died + NewState = resetup(Pid, Reason, State), + {noreply, NewState}; +handle_info(Info, State) -> + warning_msg("received unexpected info: " + "~n~w", [Info]), + {noreply, State}. + + +%%----------------------------------------------------------------- +%% Func: code_change/3 +%% Descrition: Handles code change messages during upgrade. +%%----------------------------------------------------------------- +code_change(_Vsn, State, _Extra) -> + {ok, State}. + + +%%----------------------------------------------------------------- +%% Internal functions +%%----------------------------------------------------------------- +%%----------------------------------------------------------------- +%% Func: setup/2 +%% Description: Function is used when setting up an TCP listen +%% socket in the MGC +%%----------------------------------------------------------------- +setup(SupPid, Options) -> + ?d1("setup -> entry with" + "~n SupPid: ~p" + "~n Options: ~p", [SupPid, Options]), + Mand = [port, receive_handle], + case parse_options(Options, #megaco_tcp{}, Mand) of + {ok, TcpRec} -> + + ?d1("setup -> options parsed" + "~n TcpRec: ~p", [TcpRec]), + + %%------------------------------------------------------ + %% Setup the listen socket + IpOpts = [binary, {packet, tpkt}, {active, once}, + {reuseaddr, true} | TcpRec#megaco_tcp.options], + case catch gen_tcp:listen(TcpRec#megaco_tcp.port, IpOpts) of + {ok, Listen} -> + + ?d1("setup -> listen ok" + "~n Listen: ~p", [Listen]), + + %%----------------------------------------------- + %% Startup the accept process that will wait for + %% connect attempts + case start_accept(SupPid, TcpRec, Listen) of + {ok, Pid} -> + + ?d1("setup -> accept process started" + "~n Pid: ~p", [Pid]), + + ?tcp_debug(TcpRec, "tcp listen setup", []), + {ok, Pid, {TcpRec, Listen}}; + {error, _Reason} = Error -> + ?d1("setup -> failed starting accept process" + "~n Error: ~p", [Error]), + ?tcp_debug(TcpRec, "tcp listen setup failed", + [Error]), + Error + end; + {error, Reason} -> + ?d1("setup -> listen failed" + "~n Reason: ~p", [Reason]), + Error = {error, {gen_tcp_listen, Reason}}, + ?tcp_debug(TcpRec, "tcp listen setup failed", [Error]), + Error; + {'EXIT', _Reason} = Exit -> + ?d1("setup -> listen exited" + "~n Exit: ~p", [Exit]), + Error = {error, {gen_tcp_listen, Exit}}, + ?tcp_debug(TcpRec, "tcp listen setup failed", [Error]), + Error + end; + {error, _Reason} = Error -> + ?d1("setup -> failed parsing options" + "~n Error: ~p", [Error]), + ?tcp_debug(#megaco_tcp{}, "tcp listen setup failed", + [Error, {options, Options}]), + Error + end. + + +%%----------------------------------------------------------------- +%% Func: resetup +%% Description: Function is used when restarting the accept process +%% if it died for some reason. +%%----------------------------------------------------------------- + +resetup(Pid, Reason, State) -> + ?d1("resetup -> entry with" + "~n Pid: ~p" + "~n Reason: ~p", [Pid, Reason]), + case lists:keysearch(Pid, 1, State#state.linkdb) of + {value, {Pid, {TcpRec, Listener}}} -> + ?d1("resetup -> found accept process: " + "~n TcpRec: ~p" + "~n Listener: ~p", [TcpRec, Listener]), + ?tcp_debug(TcpRec, "tcp listen resetup", [{error, Reason}]), + unlink(Pid), + warning_msg("received unexpected 'EXIT' signal " + "from accept process ~p: " + "~n~w", [Pid, Reason]), + case start_accept(State#state.supervisor_pid, TcpRec, Listener) of + {ok, NewPid} -> + ?d1("resetup -> start new accept process ok: " + "~n NewPid: ~p", [NewPid]), + link(NewPid), + NewList = lists:keyreplace(Pid, 1, State#state.linkdb, + {NewPid, {TcpRec, Listener}}), + State#state{linkdb = NewList}; + {error, Reason} -> + ?d1("resetup -> failed starting new accept process: " + "~n :Reason ~p", [Reason]), + ?tcp_debug(TcpRec, + "tcp listen resetup failed", [{error, Reason}]), + State + end; + false -> + warning_msg("received unexpected 'EXIT' signal from ~p: " + "~n~w", [Pid, Reason]), + State + end. + + +%%----------------------------------------------------------------- +%% Func: start_accept +%% Description: Function is used for starting up an TCP accept +%% process +%%----------------------------------------------------------------- +start_accept(SupPid, TcpRec, Listen) -> + ?d1("start_accept -> entry with" + "~n SupPid: ~p" + "~n TcpRec: ~p" + "~n Reason: ~p", [SupPid, TcpRec, Listen]), + case accept_sup(SupPid) of + {ok, AcceptSupPid} -> + ?d1("start_accept -> found accept supervisor" + "~n AcceptSupPid: ~p", [AcceptSupPid]), + case create_acceptor(AcceptSupPid, TcpRec, SupPid, Listen) of + {ok, Pid} -> + ?d1("start_accept -> accept process started" + "~n Pid: ~p", [Pid]), + {ok, Pid}; + {error, Reason} -> + ?d1("start_accept -> failed starting accept process: " + "~n Reason: ~p", [Reason]), + {error, {accept_not_started, Reason}} + end; + {error, Reason} -> + ?d1("start_accept -> could not find acceept supervisor: " + "~n Reason: ~p", [Reason]), + {error, {no_tcp_accept_sup, Reason}} + end. + +accept_sup(Pid) -> + megaco_tcp_sup:which_accept_sup(Pid). + +create_acceptor(Pid, Rec, TopSup, Listen) -> + megaco_tcp_accept_sup:start_child(Pid, Rec, TopSup, Listen). + + +%%----------------------------------------------------------------- +%% Func: add_tpkt_header +%% Description: Function is used to add the TPKT header +%%----------------------------------------------------------------- +add_tpkt_header(Data) when is_binary(Data) -> + L = size(Data) + 4, + {L, [3, 0, ((L) bsr 8) band 16#ff, (L) band 16#ff ,Data]}; +add_tpkt_header(IOList) when is_list(IOList) -> + Binary = list_to_binary(IOList), + L = size(Binary) + 4, + {L, [3, 0, ((L) bsr 8) band 16#ff, (L) band 16#ff , Binary]}. + +%%----------------------------------------------------------------- +%% Func: parse_options +%% Description: Function that parses the options sent to the TCP +%% module. +%%----------------------------------------------------------------- +parse_options([{Tag, Val} | T], TcpRec, Mand) -> + ?d1("parse_options -> entry with" + "~n Tag: ~p" + "~n Val: ~p", [Tag, Val]), + Mand2 = Mand -- [Tag], + case Tag of + port -> + parse_options(T, TcpRec#megaco_tcp{port = Val}, Mand2); + host -> + parse_options(T, TcpRec#megaco_tcp{host = Val}, Mand2); + tcp_options when is_list(Val) -> + parse_options(T, TcpRec#megaco_tcp{options = Val}, Mand2); + receive_handle -> + parse_options(T, TcpRec#megaco_tcp{receive_handle = Val}, Mand2); + module when is_atom(Val) -> + parse_options(T, TcpRec#megaco_tcp{module = Val}, Mand2); + serialize when (Val =:= true) orelse (Val =:= false) -> + parse_options(T, TcpRec#megaco_tcp{serialize = Val}, Mand2); + Bad -> + ?d1("parse_options -> bad option: " + "~n Tag: ~p", [Tag]), + {error, {bad_option, Bad}} + end; +parse_options([], TcpRec, []) -> + ?d2("parse_options -> done"), + {ok, TcpRec}; +parse_options([], _TcpRec, Mand) -> + ?d1("parse_options -> entry with" + "~n Mand: ~p", [Mand]), + {error, {missing_options, Mand}}; +parse_options(BadList, _TcpRec, _Mand) -> + ?d1("parse_options -> entry with" + "~n BadList: ~p", [BadList]), + {error, {bad_option_list, BadList}}. + + +%%----------------------------------------------------------------- +%% Func: incNumOutMessages/1, incNumOutOctets/2, incNumErrors/1 +%% Description: SNMP counter increment functions +%% +%%----------------------------------------------------------------- +incNumOutMessages(Socket) -> + incCounter({Socket, medGwyGatewayNumOutMessages}, 1). + +incNumOutOctets(Socket, NumOctets) -> + incCounter({Socket, medGwyGatewayNumOutOctets}, NumOctets). + +incCounter(Key, Inc) -> + ets:update_counter(megaco_tcp_stats, Key, Inc). + +% incNumErrors(Socket) -> +% ets:update_counter(megaco_tcp_stats, +% {Socket, medGwyGatewayNumErrors}, 1). + + +%%----------------------------------------------------------------- + + +warning_msg(F, A) -> + ?megaco_warning("TCP server: " ++ F, A). + +%% error_msg(F, A) -> +%% ?megaco_error("TCP server: " ++ F, A). + + +call(Pid, Req) -> + gen_server:call(Pid, Req, infinity). |