%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.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.erlang.org/EPL1_0.txt
%% 
%% 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.
%% 
%% The Original Code is Erlang-4.7.3, December, 1998.
%% 
%% The Initial Developer of the Original Code is Ericsson Telecom
%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
%% Telecom AB. All Rights Reserved.
%% 
%% Contributor(s): ______________________________________.''
%%
%%% File    : inet_tcp_dist.erl
%%% Author  : Magnus Fr|berg <magnus@erix.ericsson.se>
%%% Purpose : Handles the connection setup phase with other
%%%           Erlang nodes.
%%% Created : 18 Jun 1997 by Magnus Fr|berg <magnus@erix.ericsson.se>
%%% Updated : 29 Oct 1998 added challenge/md5 authentication 
%%%           by Tony Rogvall <tony@erix.ericsson.se>
%%%
-module(inet_tcp_dist).
-copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
-vsn('$Revision: /main/release/free/3').
-author('magnus@erix.ericsson.se').

-export([listen/0, accept/1, accept_connection/5,
	 setup/4, close/1, reg/2, select/1]).

%% internal exports

-export([accept_loop/2,do_accept/6,do_setup/5,setup_timer/2]).

-import(error_logger,[error_msg/2]).

-include("net_address.hrl").
-include("erl_epmd.hrl").

%% uncomment this if tracing of handshake is wanted
%% -define(dist_trace, true). 

-define(shutdown(Data), shutdown(?LINE, Data)).
-define(shutdown_pid(Pid, Data), shutdown_pid(?LINE, Pid, Data)).

-ifdef(dist_trace).
-define(trace(Fmt,Args), io:format(Fmt, Args)).
-else.
-define(trace(Fmt,Args), ok).
-endif.

-define(to_port(Socket, Data),
	case inet_tcp:send(Socket, Data) of
	    {error, closed} ->
		self() ! {tcp_closed, Socket},
	        {error, closed};
	    R ->
	        R
        end).


-define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]).

-define(int32(X), 
	[((X) bsr 24) band 16#ff, ((X) bsr 16) band 16#ff,
	 ((X) bsr 8) band 16#ff, (X) band 16#ff]).

-define(i16(X1,X0),
        (?u16(X1,X0) - 
         (if (X1) > 127 -> 16#10000; true -> 0 end))).

-define(u16(X1,X0),
        (((X1) bsl 8) bor (X0))).

-define(u32(X3,X2,X1,X0),
        (((X3) bsl 24) bor ((X2) bsl 16) bor ((X1) bsl 8) bor (X0))).

-define(VERSION, 2).

-record(tick, {read = 0,
	       write = 0,
	       tick = 0,
	       ticked = 0
	       }).

%% ------------------------------------------------------------
%%  Select this protocol based on node name
%%  select(Node) => Bool
%% ------------------------------------------------------------

select(Node) ->
    case split_node(atom_to_list(Node), $@, []) of
	[_, Host] ->
	    case inet:getaddr(Host,inet) of
		{ok,_} -> true;
		_ -> false
	    end;
	_ -> false
    end.

%% ------------------------------------------------------------
%% Register the node with epmd
%% ------------------------------------------------------------

reg(Name, Address) ->
    {_,Port} = Address#net_address.address,
    erl_epmd:register_node(Name, Port, ?VERSION).

%% ------------------------------------------------------------
%% Create the listen socket, i.e. the port that this erlang
%% node is accessible through.
%% ------------------------------------------------------------

listen() ->
    case inet_tcp:listen(0, [{active, false}, {packet,2}]) of
	{ok, Socket} ->
	    TcpAddress = get_tcp_address(Socket),
	    {ok, {Socket, TcpAddress}};
	Error ->
	    Error
    end.

%% ------------------------------------------------------------
%% Accepts new connection attempts from other Erlang nodes.
%% ------------------------------------------------------------

accept(Listen) ->
    spawn_link(?MODULE, accept_loop, [self(), Listen]).

accept_loop(Kernel, Listen) ->
    process_flag(priority, max),
    case inet_tcp:accept(Listen) of
	{ok, Socket} ->
	    Kernel ! {accept,self(),Socket,inet,tcp},
	    controller(Kernel, Socket),
	    accept_loop(Kernel, Listen);
	Error ->
	    exit(Error)
    end.

controller(Kernel, Socket) ->
    receive
	{Kernel, controller, Pid} ->
	    flush_controller(Pid, Socket),
	    inet_tcp:controlling_process(Socket, Pid),
	    flush_controller(Pid, Socket),
	    Pid ! {self(), controller};
	{Kernel, unsupported_protocol} ->
	    exit(unsupported_protocol)
    end.

flush_controller(Pid, Socket) ->
    receive
	{tcp, Socket, Data} ->
	    Pid ! {tcp, Socket, Data},
	    flush_controller(Pid, Socket);
	{tcp_closed, Socket} ->
	    Pid ! {tcp_closed, Socket},
	    flush_controller(Pid, Socket)
    after 0 ->
	    ok
    end.

%% ------------------------------------------------------------
%% Accepts a new connection attempt from another Erlang node.
%% Performs the handshake with the other side.
%% ------------------------------------------------------------

accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
    spawn_link(?MODULE, do_accept,
	       [self(), AcceptPid, Socket, MyNode,
		Allowed, SetupTime]).

do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) ->
    process_flag(priority, max),
    receive
	{AcceptPid, controller} ->
	    Timer = start_timer(SetupTime),
	    case check_ip(Socket) of
		true ->
		    shakehand(Kernel, Socket, MyNode, Allowed, Timer);
		{false,IP} ->
		    error_msg("** Connection attempt from "
			      "disallowed IP ~w ** ~n", [IP]),
		    ?shutdown(no_node)
	    end
    end.

shakehand(Kernel, Socket, MyNode, Allowed, Timer) ->
    {Type,Node,?VERSION} = recv_name(Socket),
    {MyCookie,HisCookie} = net_kernel:get_cookie(Node),
    ChallengeA = net_kernel:gen_challenge(),
    challenge_send(Socket, MyNode, ChallengeA, ?VERSION),
    reset_timer(Timer),
    ChallengeB = challenge_recv_reply(Socket, Node, ChallengeA, MyCookie),
    challenge_send_ack(Socket, net_kernel:gen_digest(ChallengeB, HisCookie)),
    Address = get_remote_id(Socket, Node),
    mark_pending(Kernel, Node, Type, Socket, Address,
		 MyNode, Allowed, Type,
		 ?VERSION, Timer).

%%
%% No nodedown will be sent if we fail before this process has
%% succeeded to mark the node as pending !
%%
mark_pending(Kernel, Node, Type, Socket, Address,
	     MyNode, Allowed, Type, Version, Timer) ->
    case lists:member(Node, Allowed) of
	false when Allowed /= [] ->
	    error_msg("** Connection attempt from "
		      "disallowed node ~w ** ~n", [Node]),
	    ?shutdown(Node); 
	_ ->
	    mark_pending(Kernel, Node, Type, Socket, Address,
			 MyNode, Type, Version, Timer)
    end.

mark_pending(Kernel, Node, Type, Socket, Address,
	     MyNode, Type, Version, Timer) ->
    case do_mark_pending(Kernel,Node,Address,Type) of
	ok ->
	    reset_timer(Timer),
	    connection(Kernel, Node, Socket, Address, MyNode,
		       Type, Version, Timer);
	{pending, SetupPid} ->
	    simultan_cnct(Kernel, Socket, Address, SetupPid,
			  Node, MyNode, Type, Version, Timer);
	up ->
	    %% If our socket already is closed this was a
	    %% simultaneous attempt where the accept process
	    %% at the other side killed the setup process !!
	    case is_closed(Socket) of
		true ->
		    ?shutdown(Node);
		_ ->
		    %% This can happen if the other node goes down,
		    %% and goes up again and contact us before we have
		    %% detected that the socket was closed.  Force the
		    %% cleanup here.
		    disconnect_old(Node),
		    %% Try again !
		    mark_pending(Kernel, Node, Type, Socket, Address,
				 MyNode, Type, Version, Timer)
	    end
    end.

%%
%% We are sure that the other side has not told us anything as
%% we have not told our name yet ! Thus, the inet_tcp:recv call
%% just tells us if the socket is closed.
%% Have to do this stuff as the socket is in passive mode, i.e.
%% the inet_drv.c has no READ select on it !!
%%
is_closed(Socket) ->
    %% Uack!  What if the other end is slow and doesn't close the socket
    %% within 100 ms?  Unfortunatley, this one needs a protocol change -
    %% We need an explicit message to wait for instead of rely on the
    %% timeout.
    case inet_tcp:recv(Socket, 0, 100) of
	{error, _} -> true;
	_ -> false
    end.

disconnect_old(Node) ->
    erlang:monitor_node(Node, true),
    net_kernel:disconnect(Node),
    receive
	{nodedown, Node} -> ok
    end.

do_mark_pending(Kernel,Node,Address,Type) ->
    Kernel ! {self(), {accept_pending,Node,Address,Type}},
    receive
	{Kernel, {accept_pending, Ret}} ->
	    Ret
    end.

simultan_cnct(_, _, _, _, Node, MyNode, _, _, _) when MyNode > Node ->
    ?shutdown(Node);
simultan_cnct(Kernel, Socket, Address, SetupPid, Node,
	      MyNode, Type, Version, Timer) ->
    case mark_new_pending(Kernel, Node) of
	ok ->
	    ?shutdown_pid(SetupPid, Node),
	    reset_timer(Timer),
	    connection(Kernel, Node, Socket, Address,
		       MyNode, Type, Version, Timer);
	bad_request ->
	    %% Can not occur !!
	    error_msg("net_connect: ** Simultaneous connect failed for ~p~n",
		      [Node]),
	    ?shutdown(Node)
    end.

mark_new_pending(Kernel, Node) ->
    Kernel ! {self(), {remark_pending, Node}},
    receive
	{Kernel, {remark_pending, Resp}} ->
	    Resp
    end.

%% ------------------------------------------------------------
%% Get remote information about a Socket.
%% ------------------------------------------------------------
get_remote_id(Socket, Node) ->
    {ok, Address} = inet:peername(Socket),
    [_, Host] = split_node(atom_to_list(Node), $@, []),
    #net_address {
		  address = Address,
		  host = Host,
		  protocol = tcp,
		  family = inet }.

%% ------------------------------------------------------------
%% Setup a new connection to another Erlang node.
%% Performs the handshake with the other side.
%% ------------------------------------------------------------

setup(Node, MyNode, LongOrShortNames,SetupTime) ->
    spawn_link(?MODULE, do_setup, [self(),
				   Node,
				   MyNode,
				   LongOrShortNames,
				   SetupTime]).

do_setup(Kernel, Node, MyNode, LongOrShortNames,SetupTime) ->
    process_flag(priority, max),
    [Name, Address] = splitnode(Node, LongOrShortNames),
    case inet:getaddr(Address, inet) of
	{ok, Ip} ->
	    Timer = start_timer(SetupTime),
	    case erl_epmd:port_please(Name, Ip, ?VERSION) of
		{port, TcpPort, ?TCP_PROTO, ?VERSION} ->
		    reset_timer(Timer),
		    case inet_tcp:connect(Ip, TcpPort, 
					  [{active, false},{packet,2}]) of
			{ok, Socket} ->
			    handshake(Kernel, Node, Socket,
				      #net_address {
						    address = {Ip,TcpPort},
						    host = Address,
						    protocol = tcp,
						    family = inet},
				      MyNode,
				      ?VERSION,
				      Timer);
			_ ->
			    %% Other Node may have closed since port_please !
			    ?shutdown(Node)
		    end;
		_ ->
		    ?shutdown(Node)
	    end;
	Other ->
	    ?shutdown(Node)
    end.

%%
%% This will tell the net_kernel about the nodedown as it
%% recognizes the exit signal.
%% Terminate with reason shutdown so inet processes want
%% generate crash reports.
%% The termination of this process does also imply that the Socket
%% is closed in a controlled way by inet_drv.
%%
shutdown(Line, Data) ->
    flush_down(),
    exit(shutdown).
% Use this line to debug connection.  Set net_kernel verbose = 1 as well.
%    exit({shutdown, ?MODULE, Line, Data, erlang:now()}).

shutdown_pid(Line, Pid, Data) ->
    exit(Pid, shutdown).
% Use this line to debug connection.  Set net_kernel verbose = 1 as well.
%    exit(Pid, {shutdown, ?MODULE, Line, Data, erlang:now()}).

flush_down() ->
    receive
	{From, get_status} ->
	    From ! {self(), get_status, error},
	    flush_down()
    after 0 ->
	    ok
    end.

%% If Node is illegal terminate the connection setup!!
splitnode(Node, LongOrShortNames) ->
    case split_node(atom_to_list(Node), $@, []) of
	[Name|Tail] when Tail /= [] ->
	    Host = lists:append(Tail),
	    case split_node(Host, $., []) of
		[_] when LongOrShortNames == longnames ->
		    error_msg("** System running to use fully qualified "
			      "hostnames **~n"
			      "** Hostname ~s is illegal **~n",
			      [Host]),
		    ?shutdown(Node);
		L when length(L) > 1, LongOrShortNames == shortnames ->
		    error_msg("** System NOT running to use fully qualified "
			      "hostnames **~n"
			      "** Hostname ~s is illegal **~n",
			      [Host]),
		    ?shutdown(Node);
		_ ->
		    [Name, Host]
	    end;
	[_] ->
	    error_msg("** Nodename ~p illegal, no '@' character **~n",
		      [Node]),
	    ?shutdown(Node);
	_ ->
	    error_msg("** Nodename ~p illegal **~n", [Node]),
	    ?shutdown(Node)
    end.

split_node([Chr|T], Chr, Ack) -> [lists:reverse(Ack)|split_node(T, Chr, [])];
split_node([H|T], Chr, Ack)   -> split_node(T, Chr, [H|Ack]);
split_node([], _, Ack)        -> [lists:reverse(Ack)].

handshake(Kernel,Node,Socket,TcpAddress, MyNode,Version,Timer) ->
    name_send(Socket, MyNode, Version),
    {Type, NodeA, VersionA, ChallengeA} = challenge_recv(Socket),
    if Node =/= NodeA -> ?shutdown(no_node);
       Version =/= VersionA -> ?shutdown(no_node);
       true -> true
    end,
    MyChallenge = net_kernel:gen_challenge(),
    {MyCookie,HisCookie} = net_kernel:get_cookie(Node),
    challenge_send_reply(Socket,MyChallenge,
			 net_kernel:gen_digest(ChallengeA,HisCookie)),
    reset_timer(Timer),
    challenge_recv_ack(Socket, Node, MyChallenge, MyCookie),
    connection(Kernel, Node, Socket, TcpAddress,
	       MyNode, Type, Version, Timer).


%% --------------------------------------------------------------
%% The connection has been established.
%% --------------------------------------------------------------

connection(Kernel, Node, Socket, TcpAddress,
	   MyNode, Type, Version, Timer) ->
    cancel_timer(Timer),
    case do_setnode(Node,Socket,Type,Version) of
	error ->
	    ?shutdown(Node);
	ok ->
	    case inet:setopts(Socket, [{active, true},
				       {packet, 4},
				       {nodelay, true}]) of
		ok -> 
		    mark_nodeup(Kernel,Node,TcpAddress,Type),
		    con_loop(Kernel, Node, Socket, TcpAddress,
			     MyNode, Type, #tick{});
		_ ->
		    ?shutdown(Node)
	    end
    end.

do_setnode(Node, Socket, Type, Version) ->
    case inet:getll(Socket) of
	{ok,Port} ->
	    ?trace("setnode(~p ~p ~p)~n", [Node, Port, {Type, Version}]),
	    erlang:setnode(Node, Port, {Type, Version}),
	    ok;
	_ ->
	    error
    end.


mark_nodeup(Kernel,Node,Address,Type) ->
    Kernel ! {self(), {nodeup,Node,Address,Type}},
    receive
	{Kernel, inserted} ->
	    ok;
	{Kernel, bad_request} ->
	    error_msg("Uuugh, we were not allowed to send {nodeup, ~p} !!~n",
		      [Node]),
	    ?shutdown(Node)
    end.

con_loop(Kernel, Node, Socket, TcpAddress, MyNode, Type, Tick) ->
    receive
	{tcp_closed, Socket} ->
	    ?shutdown(Node);
	{Kernel, disconnect} ->
	    ?shutdown(Node);
	{Kernel, tick} ->
	    case send_tick(Socket, Tick, Type) of
		{ok, NewTick} ->
		    con_loop(Kernel,Node,Socket,TcpAddress,
			     MyNode,Type,NewTick);
		{error, not_responding} ->
		    error_msg("** Node ~p not responding **~n"
			      "** Removing (timedout) connection **~n",
			      [Node]),
		    ?shutdown(Node);
		Other ->
		    ?shutdown(Node)
	    end;
	{From, get_status} ->
	    case getstat(Socket) of
		{ok, Read, Write, _} ->
		    From ! {self(), get_status, {ok, Read, Write}},
		    con_loop(Kernel, Node, Socket, TcpAddress,
			     MyNode, Type, Tick);
		_ ->
		    ?shutdown(Node)
	    end
    end.

%% ------------------------------------------------------------
%% Fetch local information about a Socket.
%% ------------------------------------------------------------
get_tcp_address(Socket) ->
    {ok, Address} = inet:sockname(Socket),
    {ok, Host} = inet:gethostname(),
    #net_address {
		  address = Address,
		  host = Host,
		  protocol = tcp,
		  family = inet
		 }.

%% ------------------------------------------------------------
%% Misc. functions.
%% ------------------------------------------------------------

%%
%% do_challang is a direct response on an accept
%%
name_send(Socket, Node, Version) ->
    ?trace("name_send: node=~w, version=~w\n",
	   [Node,Version]),
    {ok, {{Ip1,Ip2,Ip3,Ip4}, _}} = inet:sockname(Socket),
    ?to_port(Socket, [$m,?int16(Version),
		      Ip1,Ip2,Ip3,Ip4,atom_to_list(Node)]).

challenge_send(Socket, Node, Challenge, Version) ->
    ?trace("send: challenge=~w version=~w\n",
	   [Challenge,Version]),
    {ok, {{Ip1,Ip2,Ip3,Ip4}, _}} = inet:sockname(Socket),
    ?to_port(Socket, [$m,?int16(Version),Ip1,Ip2,Ip3,Ip4,
		      ?int32(Challenge), atom_to_list(Node)]).

challenge_send_reply(Socket, Challenge, Digest) ->
    ?trace("send_reply: challenge=~w digest=~s\n",
	   [Challenge,md5:format(Digest)]),
    ?to_port(Socket, [$r,?int32(Challenge),Digest]).

challenge_send_ack(Socket, Digest) ->
    ?trace("send_ack: digest=~s\n", [md5:format(Digest)]),
    ?to_port(Socket, [$a,Digest]).


recv_name(Socket) ->
    case inet_tcp:recv(Socket, 0) of
	{ok,[T,V1,V0,Ip1,Ip2,Ip3,Ip4 | Ns]} ->
	    Type = if T == $m -> normal;
		      T == $h -> hidden;
		      true -> ?shutdown(no_node)
		   end,
	    Node = list_to_atom(Ns),
	    Version = ?u16(V1,V0),
	    ?trace("recv_name: node=~w, version=~w\n",
		   [Node, Version]),
	    {Type,Node,Version};
	_ ->
	    ?shutdown(no_node)	    
    end.
    
%% wait for challenge after connect
challenge_recv(Socket) ->
    case inet_tcp:recv(Socket, 0) of
	{ok,[T,V1,V0,Ip1,Ip2,Ip3,Ip4,CA3,CA2,CA1,CA0 | Ns]} ->
	    Type = if T == $m -> normal;
		      T == $h -> hidden;
		      true -> ?shutdown(no_node)
		   end,
	    Node =list_to_atom(Ns),
	    Version = ?u16(V1,V0),
	    Challenge = ?u32(CA3,CA2,CA1,CA0),
	    ?trace("recv: node=~w, challenge=~w version=~w\n",
		   [Node, Challenge,Version]),
	    {Type,Node,Version,Challenge};
	_ ->
	    ?shutdown(no_node)	    
    end.


%%
%% wait for challenge response after challenge_send
%%
challenge_recv_reply(Socket, NodeB, ChallengeA, Cookie) ->
    case inet_tcp:recv(Socket, 0) of
	{ok,[$r,CB3,CB2,CB1,CB0 | SumB]} when length(SumB) == 16 ->
	    SumA = net_kernel:gen_digest(ChallengeA, Cookie),
	    ChallengeB = ?u32(CB3,CB2,CB1,CB0),
	    ?trace("recv_reply: challenge=~w digest=~s\n",
		   [ChallengeB,md5:format(SumB)]),
	    ?trace("sum = ~s\n", [md5:format(SumA)]),
	    if SumB == SumA ->
		    ChallengeB;
	       true ->
		    error_msg("** Connection attempt from "
			      "disallowed node ~w ** ~n", [NodeB]),
		    ?shutdown(NodeB)
	    end;
	_ ->
	    ?shutdown(no_node)
    end.

challenge_recv_ack(Socket, NodeB, ChallengeB, CookieA) ->
    case inet_tcp:recv(Socket, 0) of
	{ok,[$a | SumB]} when length(SumB) == 16 ->
	    SumA = net_kernel:gen_digest(ChallengeB, CookieA),
	    ?trace("recv_ack: digest=~s\n", [md5:format(SumB)]),
	    ?trace("sum = ~s\n", [md5:format(SumA)]),
	    if SumB == SumA ->
		    ok;
	       true ->
		    error_msg("** Connection attempt to "
			      "disallowed node ~w ** ~n", [NodeB]),
		    ?shutdown(NodeB)
	    end;
	_ ->
	    ?shutdown(NodeB)
    end.
    
%%
%% Send a TICK to the other side.
%%
%% This will happen every 15 seconds (by default) 
%% The idea here is that every 15 secs, we write a little 
%% something on the connection if we haven't written anything for 
%% the last 15 secs.
%% This will ensure that nodes that are not responding due to 
%% hardware errors (Or being suspended by means of ^Z) will 
%% be considered to be down. If we do not want to have this  
%% we must start the net_kernel (in erlang) without its 
%% ticker process, In that case this code will never run 

%% And then every 60 seconds we also check the connection and 
%% close it if we havn't received anything on it for the 
%% last 60 secs. If ticked == tick we havn't received anything 
%% on the connection the last 60 secs. 

%% The detection time interval is thus, by default, 45s < DT < 75s 

%% A HIDDEN node is always (if not a pending write) ticked if 
%% we haven't read anything as a hidden node only ticks when it receives 
%% a TICK !! 
	
send_tick(Socket, Tick, Type) ->
    #tick{tick = T0,
	  read = Read,
	  write = Write,
	  ticked = Ticked} = Tick,
    T = T0 + 1,
    T1 = T rem 4,
    case getstat(Socket) of
	{ok, Read, _, _} when  Ticked == T ->
	    {error, not_responding};
	{ok, Read, W, Pend} when Type == hidden ->
	    send_tick(Socket, Pend),
	    {ok, Tick#tick{write = W + 1,
			   tick = T1}};
	{ok, Read, Write, Pend} ->
	    send_tick(Socket, Pend),
	    {ok, Tick#tick{write = Write + 1,
			   tick = T1}};
	{ok, R, Write, Pend} ->
	    send_tick(Socket, Pend),
	    {ok, Tick#tick{write = Write + 1,
			   read = R,
			   tick = T1,
			   ticked = T}};
	{ok, Read, W, _} ->
	    {ok, Tick#tick{write = W,
			   tick = T1}};
	{ok, R, W, _} ->
	    {ok, Tick#tick{write = W,
			   read = R,
			   tick = T1,
			   ticked = T}};
	Error ->
	    Error
    end.

send_tick(Socket, 0) ->
    ?to_port(Socket, []);
send_tick(_, Pend) ->
    %% Dont send tick if pending write.
    ok.

getstat(Socket) ->
    case inet:getstat(Socket, [recv_cnt, send_cnt, send_pend]) of
	{ok, Stat} ->
	    split_stat(Stat,0,0,0);
	Error ->
	    Error
    end.

split_stat([{recv_cnt, R}|Stat], _, W, P) ->
    split_stat(Stat, R, W, P);
split_stat([{send_cnt, W}|Stat], R, _, P) ->
    split_stat(Stat, R, W, P);
split_stat([{send_pend, P}|Stat], R, W, _) ->
    split_stat(Stat, R, W, P);
split_stat([], R, W, P) ->
    {ok, R, W, P}.

%%
%% Close a socket.
%%
close(Socket) ->
    inet_tcp:close(Socket).

%% ------------------------------------------------------------
%% Connection setup timeout timer.
%% After Timeout milliseconds this process terminates
%% which implies that the owning setup/accept process terminates.
%% The timer is reset before every network operation during the
%% connection setup !
%% ------------------------------------------------------------

start_timer(Timeout) ->
    spawn_link(?MODULE, setup_timer, [self(), Timeout]).

setup_timer(Pid, Timeout) ->
    receive
	{Pid, reset} ->
	    setup_timer(Pid, Timeout)
    after Timeout ->
	    ?shutdown(timer)
    end.

reset_timer(Timer) ->
    Timer ! {self(), reset}.

cancel_timer(Timer) ->
    unlink(Timer),
    exit(Timer, shutdown).

%% ------------------------------------------------------------
%% Do only accept new connection attempts from nodes at our
%% own LAN, if the check_ip environment parameter is true.
%% ------------------------------------------------------------
check_ip(Socket) ->
    case application:get_env(check_ip) of
	{ok, true} ->
	    case get_ifs(Socket) of
		{ok, IFs, IP} ->
		    check_ip(IFs, IP);
		_ ->
		    ?shutdown(no_node)
	    end;
	_ ->
	    true
    end.

get_ifs(Socket) ->
    case inet:peername(Socket) of
	{ok, {IP, _}} ->
	    case inet:getif(Socket) of
		{ok, IFs} -> {ok, IFs, IP};
		Error     -> Error
	    end;
	Error ->
	    Error
    end.

check_ip([{OwnIP, Netmask, _}|IFs], PeerIP) ->
    case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of
	{M, M} -> true;
	_      -> check_ip(IFs, PeerIP)
    end;
check_ip([], PeerIP) ->
    {false, PeerIP}.
    
mask({M1,M2,M3,M4}, {IP1,IP2,IP3,IP4}) ->
    {M1 band IP1,
     M2 band IP2,
     M3 band IP3,
     M4 band IP4}.
