Skip to content

Instantly share code, notes, and snippets.

@2garryn
Created November 22, 2014 23:57
Show Gist options
  • Save 2garryn/828754779457a526ba2e to your computer and use it in GitHub Desktop.
Save 2garryn/828754779457a526ba2e to your computer and use it in GitHub Desktop.
-module(nasoc_conn_handler2).
-export([]).
-record(state, {cli_socket :: inet:socket(),
ext_socket :: inet:socket(),
cli_ip_port :: {ip_address(), ip_port()},
ext_ip_port :: {ip_address(), ip_port()},
target_ip_port :: {ip_address(), ip_port()},
parent :: reference()).
start(CliSocket) ->
Self = self(),
Pid = spawn(fun() -> init_loop(Self, CliSocket) end),
Pid ! set_ctrl,
Pid.
init_loop(ParentPid, CliSocket) ->
Ref = erlang:monitor(process, ParentPid),
State = #state{ parent = Ref, cli_socket = CliSocket },
Fsm = connected,
loop(Fsm, State).
loop(Fsm, State) ->
receive
set_ctrl ->
set_active(State#state.cli_socket, once),
loop(State, Fsm);
Message ->
{NewFsm, NewState, Socket, Reply} =
process(Message, Fsm, State),
calc_do(Fsm, NewFsm, Socket, Reply, NewState)
end.
calc_do(connected, await_cmd, Socket, Reply, State = #state{cli_socket = Socket}) ->
case gen_tcp:send(Socket, Reply) of
ok ->
set_active(Socket, once),
loop(await_cmd, State);
{error, Reason} ->
{error, Reason}
end;
calc_do(await_cmd, await_msg, Socket, Reply, State = #state{cli_socket = Socket}) ->
case gen_tcp:send(Socket, Reply) of
ok ->
set_active(Socket, true),
loop(await_msg, State);
{error, Reason} ->
close(State#state.ext_socket),
close(Socket),
{error, Reason}
end;
calc_do(await_msg, await_msg, Socket, Reply, State) ->
case gen_tcp:send(Socket, Reply) of
ok ->
loop(await_msg, State);
{error, Reason} ->
close(State#state.ext_socket),
close(State#state.cli_socket),
{error, Reason}
end;
calc_do(OldState, close, Socket, undefined, State) ->
close(State#state.ext_socket),
close(State#state.cli_socket).
close(undefined) -> ok;
close(Socket) -> catch gen_tcp:close(Socket).
set_active(Socket, Active) ->
inet:setopts(Socket,[{active, Active}]),
process({tcp, Socket, Data}, Fsm, State = #state{cli_socket = Socket}) ->
client_msg(Fsm, Data, State);
process({tcp, Socket, Data}, Fsm, State = #state{ext_socket = Socket}) ->
target_msg(Fsm, Data, State);
process({tcp_closed, Socket}, Fsm, State = #state{cli_socket = Socket}) ->
client_close(Fsm, State);
process({tcp_closed, Socket}, Fsm, State = #state{ext_socket = Socket}) ->
target_close(Fsm, State);
process({tcp_error, Socket, Reason}, Fsm, State = #state{cli_socket = Socket}) ->
client_error(Fsm, Reason, State);
process({tcp_error, Socket, Reason}, Fsm, State = #state{ext_socket = Socket}) ->
target_error(Fsm, Reason, State).
client_msg(connected, <<?PROTO_VER5:8, MethNumber:8, ReqMethods/binary>>, State) ->
case is_method_supported(?NO_AUTH, MethNumber, ReqMethods) of
true -> {await_cmd, State, State#state.cli_socket, <<?V5, ?NO_AUTH>>};
false -> {close, State, State#state.cli_socket, <<?V5, ?NO_ACPT_METHODS>>}
end;
client_msg(await_cmd, <<?PROTO_VER5:8, ?CMD_CONNECT:8, ?RSV:8, AType:8, AddrPort/binary>>, State) ->
case parse_atype(AType, AddrPort) of
{ok, Addr, Port} ->
connect_to_target(Addr, Port, AType, State);
{error, BinReply, Reason} ->
Binary = <<?PROTO_VER5, BinReply, ?RSV, AType, AddrPort/binary>>,
{close, State, State#state.cli_socket, Binary}
end;
client_msg(await_cmd, <<?PROTO_VER5:8, UnsupCmd:8, Tail/binary>>, State) ->
Binary = <<?PROTO_VER5, ?CMD_NOT_SUPPORTED, Tail/binary>>,
{close, State, State#state.cli_socket, Binary};
client_msg(await_msg, Binary, State) ->
{await_msg, State, State#state.ext_socket, Binary}.
target_msg(await_msg, Binary, State) ->
{await_msg, State, State#state.cli_socket, Binary}.
client_close(Fsm, State) ->
{close, State, State#state.ext_socket, undefined}.
target_close(Fsm, State) ->
{close, State, State#state.cli_socket, undefined}.
client_error(Fsm, Reason, State) ->
{close, State, State#state.cli_socket, undefined}.
target_error(Fsm, Reason, State) ->
{close, State, State#state.ext_socket, undefined}.
is_method_supported(Method, MethNumber, ReqMethods)
when MethNumber == byte_size(ReqMethods) ->
MethodsTrunc = erlang:binary_part(ReqMethods, 0, MethNumber),
lists:member(Method, binary_to_list(MethodsTrunc));
is_method_supported(_Method, _MethNumber, _ReqMethods) ->
false.
parse_atype(?ATYPE_IPV4, <<Ip1, Ip2, Ip3, Ip4, Port:16, _/binary>>) ->
{ok, {Ip1, Ip2, Ip3, Ip4}, Port};
parse_atype(?ATYTE_DOMAIN, <<DLength:8, DomainPort/binary>>) ->
<<Domain:DLength/binary, Port:16, _/binary>> = DomainPort,
{ok, binary_to_list(Domain), Port};
parse_atype(AType, Bin) ->
{error, ?ATYPE_NOT_SUPPORTED, {not_supported, AType, Bin}}.
connect_to_target(Addr, Port, AType, State) ->
#state{ext_ip_port = {ExtIp, _ExtPort}} = State,
Result = gen_tcp:connect(Addr, Port, [{ip, ExtIp}, {active, true}, binary]),
process_connect(Result, Addr, Port, AType, State).
process_connect({ok, ExtSocket}, Addr, Port, AType, State) ->
{ok, {ExtIp, ExtPort}} = inet:sockname(ExtSocket)
NewState = State#state{ext_socket = ExtSocket,
ext_ip_port = {ExtIp, ExtPort}},
BinAddress = address_to_binary(?ATYPE_IPV4, ExtIp),
BinaryReply = <<?PROTO_VER5, ?SUCCESS, ?RSV, AType, BinAddress/binary, ExtPort:16>>,
{await_msg, NewState, State#state.cli_socket, BinaryReply};
process_connect({error, Error}, Addr, Port, AType, State) ->
BinError = error_to_bin(Error),
BinAddress = address_to_binary(AType, Addr),
BinaryReply = <<?PROTO_VER5, BinErrir, ?RSV, AType, BinAddress/binary, Port:16>>,
{close, State, State#state.cli_socket, BinaryReply}.
error_to_bin(enetunreach) -> ?NETWORK_UNREACH;
error_to_bin(ehostunreach) -> ?HOST_UNREACH;
error_to_bin(econnrefused) -> ?CONN_REFUSED;
error_to_bin(Error) -> ?ANY_OTHER_ERROR.
address_to_binary(?ATYPE_IPV4, {Ip1, Ip2, Ip3, Ip4}) ->
<<Ip1, Ip2, Ip3, Ip4>>;
address_to_binary(?ATYPE_DOMAIN, Domain) ->
Length = length(Domain),
Binary = list_to_binary(Domain),
<<Length, Binary/binary>>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment