ssh_tcp.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 322 行

ERL
322
字号
%% ``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 via the world wide web 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.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %%     $Id$%%%%% Description: SSH port forwarding-module(ssh_tcp).-behaviour(gen_server).%%--------------------------------------------------------------------%% Include files%%---------------------------------------------------------------------include("ssh.hrl").%%--------------------------------------------------------------------%% External exports-export([start/1, start_link/1]).-export([start/2, start_link/2]).-export([start/3, start_link/3]).-export([forward/5, backward/5]).%% gen_server callbacks-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).-record(state,	{	  cm,     % connection manager	  opts    % options	 }).-define(DEFAULT_TIMEOUT, 5000).%%====================================================================%% External functions%%====================================================================%%--------------------------------------------------------------------%% Function: start_link/0%% Description: Starts the server%%--------------------------------------------------------------------start_link(CM) ->    gen_server:start_link(?MODULE, [CM], []).start_link(Host, Opts) ->    gen_server:start_link(?MODULE, [Host,22,Opts], []).    start_link(Host, Port, Opts) ->    gen_server:start_link(?MODULE, [Host,Port,Opts], []).start(CM) ->    gen_server:start(?MODULE, [CM], []).start(Host, Opts) ->    gen_server:start(?MODULE, [Host, 22, Opts], []).    start(Host, Port, Opts) ->    gen_server:start(?MODULE, [Host, Port, Opts], []).forward(Pid, LocalIP, LocalPort, RemoteIP, RemotePort) ->    gen_server:call(Pid, {forward, 			  LocalIP, LocalPort,			  RemoteIP, RemotePort}).backward(Pid, LocalIP, LocalPort, RemoteIP, RemotePort) ->    gen_server:call(Pid, {backward,			  LocalIP, LocalPort,			  RemoteIP, RemotePort}).%%====================================================================%% Server functions%%====================================================================%%--------------------------------------------------------------------%% Function: init/1%% Description: Initiates the server%% Returns: {ok, State}          |%%          {ok, State, Timeout} |%%          ignore               |%%          {stop, Reason}%%--------------------------------------------------------------------init([CM]) ->    case ssh_cm:attach(CM, ?DEFAULT_TIMEOUT) of	{ok,CMPid} ->	    {ok, #state{cm = CMPid, opts = []}};	Error ->	    {stop, Error }    end;init([Host, Port, Opts]) ->    case ssh_cm:connect(Host, Port, Opts) of	{ok, CM} ->	    {ok, #state {cm = CM, opts = Opts}};	Error ->	    {stop, Error}    end.%%--------------------------------------------------------------------%% Function: handle_call/3%% Description: Handling call messages%% Returns: {reply, Reply, State}          |%%          {reply, Reply, State, Timeout} |%%          {noreply, State}               |%%          {noreply, State, Timeout}      |%%          {stop, Reason, Reply, State}   | (terminate/2 is called)%%          {stop, Reason, State}            (terminate/2 is called)%%--------------------------------------------------------------------handle_call({forward, LocalIP, LocalPort, RemoteIP, RemotePort},_From,State) ->    LIP = ip_address(LocalIP),    _RIP = ip_address(RemoteIP),    Me = self(),    Prog = fun(S) ->		   ?dbg(true, "accepted\n", ""),		   gen_tcp:controlling_process(S, Me),		   gen_server:cast(Me,{forward, S, RemoteIP, RemotePort})	   end,    case ssh_tcp_wrap:spawn_server(LocalPort,				   [{ifaddr, LIP},{mode, binary},				    {packet, 0},{active, false}],Prog) of	{ok,_Server,ListenPort} ->	    {reply, {ok,ListenPort}, State};	Error ->	    {reply, Error, State}    end;handle_call({backward, LocalIP, LocalPort, RemoteIP, RemotePort},_From,State) ->    case ssh_cm:tcpip_forward(State#state.cm, RemoteIP, RemotePort) of	ok ->	    put({ipmap,{RemoteIP,RemotePort}}, {LocalIP,LocalPort}),	    {reply, ok, State};	Error ->	    {reply, Error, State}    end;handle_call(_Request, _From, State) ->    {reply, {error,bad_call}, State}.%%--------------------------------------------------------------------%% Function: handle_cast/2%% Description: Handling cast messages%% Returns: {noreply, State}          |%%          {noreply, State, Timeout} |%%          {stop, Reason, State}            (terminate/2 is called)%%--------------------------------------------------------------------handle_cast({forward, S, RemoteIP, RemotePort}, State) ->    case inet:peername(S) of	{ok,{OrigIP, OrigPort}} ->	    ?dbg(true, "peer ~p ~p remote ~p ~p\n", 		 [OrigIP, OrigPort, RemoteIP, RemotePort]),	    #state{opts = Opts, cm = CM} = State,	    TMO = proplists:get_value(connect_timeout,				      Opts, ?DEFAULT_TIMEOUT),	    case ssh_cm:direct_tcpip(CM,				     RemoteIP, RemotePort,				     OrigIP, OrigPort, TMO) of		{ok, Channel} ->		    ?dbg(true, "got channel ~p\n", [Channel]),		    ssh_cm:set_user_ack(CM, Channel, true, TMO),		    put({channel,S}, Channel),		    put({socket,Channel}, S),		    inet:setopts(S, [{active, once}]),		    {noreply, State};		{error, _Error} ->		    ?dbg(true, "forward: error ~p\n", [_Error]),		    gen_tcp:close(S),		    {noreply, State}	    end;	_Error ->	    gen_tcp:close(S),	    {noreply, State}    end;    handle_cast(_Msg, State) ->    {noreply, State}.%%--------------------------------------------------------------------%% Function: handle_info/2%% Description: Handling all non call/cast messages%% Returns: {noreply, State}          |%%          {noreply, State, Timeout} |%%          {stop, Reason, State}            (terminate/2 is called)%%--------------------------------------------------------------------handle_info({tcp, S, Data}, State) ->    case get({channel,S}) of	undefined ->	    {noreply, State};	    	Channel ->	    ?dbg(true, "sending ~p -> ~p\n", [S, Channel]),	    %% send and wait for ack	    ssh_cm:send_ack(State#state.cm, Channel, Data),	    inet:setopts(S, [{active, once}]),	    {noreply, State}    end;handle_info({tcp_closed, S}, State) ->    ?dbg(true, "tcp: closed: ~p\n", [S]),    case get({channel,S}) of	undefined -> 	    {noreply, State};	Channel ->	    ssh_cm:send_eof(State#state.cm, Channel),	    {noreply, State}    end;handle_info({ssh_cm, CM, {data, Channel, Type, Data}}, State) ->    if Type == 0 ->	    ?dbg(true, "ssh_cm: data: ~p\n", [Channel]),	    case get({socket,Channel}) of		undefined ->{noreply, State};		S ->		    ?dbg(true, "sending ~p -> ~p\n", [Channel,S]),		    gen_tcp:send(S, Data),		    ssh_cm:adjust_window(CM, Channel, size(Data)),		    {noreply, State}	    end;       true  ->	    ?dbg(true, "STDERR: ~s\n", [binary_to_list(Data)]),	    ssh_cm:adjust_window(CM, Channel, size(Data)),	    {noreply, State}    end;handle_info({ssh_cm, _CM, {closed, Channel}}, State) ->    ?dbg(true, "ssh_cm: closed: ~p\n", [Channel]),    case get({socket, Channel}) of	undefined -> {noreply, State};	S ->	    erase({socket,Channel}),	    erase({channel,S}),	    gen_tcp:close(S),	    {noreply, State}    end;handle_info({ssh_cm, _CM, {eof, Channel}}, State) ->    ?dbg(true, "ssh_cm: eof: ~p\n", [Channel]),    case get({socket,Channel}) of	undefined -> {noreply, State};	S ->	    gen_tcp:shutdown(S, write),	    {noreply, State}    end;handle_info({open, Channel, {forwarded_tcpip,			     RemoteAddr, RemotePort,			     _OrigIp, _OrigPort}},	    #state{opts = Opts, cm = CM} = State) ->    TMO = proplists:get_value(connect_timeout,			      Opts, ?DEFAULT_TIMEOUT),    NoDelay = proplists:get_value(tcp_nodelay, Opts, false),    case get({ipmap,{RemoteAddr,RemotePort}}) of	undefined ->	    ssh_cm:close(CM, Channel),	    {noreply, State};	{LocalIP, LocalPort} ->	    case gen_tcp:connect(LocalIP, LocalPort, [{active,once},						      {mode,binary},						      {packet,0},						      {nodelay,NoDelay},						      {connect_timeout, TMO}]) of		{ok, S} ->		    %% We want ack on send!		    ssh_cm:set_user_ack(CM, Channel, true, TMO),		    %% FIXME: set fake peer and port?		    put({channel, S}, Channel),		    put({socket,Channel}, S),		    {noreply, State};		_Error ->		    ssh_cm:close(CM, Channel),		    {noreply, State}	    end    end;handle_info(_Info, State) ->    {noreply, State}.%%--------------------------------------------------------------------%% Function: terminate/2%% Description: Shutdown the server%% Returns: any (ignored by gen_server)%%--------------------------------------------------------------------terminate(_Reason, _State) ->    ok.%%--------------------------------------------------------------------%% Func: code_change/3%% Purpose: Convert process state when code is changed%% Returns: {ok, NewState}%%--------------------------------------------------------------------code_change(_OldVsn, State, _Extra) ->    {ok, State}.%%--------------------------------------------------------------------%%% Internal functions %%--------------------------------------------------------------------%% Try to convert to ip4/ip6 address tuple	    ip_address(Addr) when tuple(Addr) ->    Addr;ip_address(local) -> local;ip_address(any)   -> any;ip_address(Addr) when list(Addr) ->    case inet_parse:address(Addr) of	{error, _} -> Addr;	{ok,A} -> A    end;ip_address(A) -> A.    

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?