├── .travis.yml ├── rebar.config ├── src ├── epmdpxy.hrl ├── epmdpxy.app.src ├── epmdpxy_app.erl ├── epmdpxy_sup.erl ├── epmdpxy.erl ├── epmdpxy_conn_sup.erl ├── epmdpxy_session_sup.erl ├── epmdpxy_listener.erl ├── epmdpxy_reg.erl ├── epmdpxy_splitter.erl ├── epmdpxy_conn.erl └── epmdpxy_session.erl ├── Makefile ├── readme.md └── test └── epmdpxy_netsplit_tests.erl /.travis.yml: -------------------------------------------------------------------------------- 1 | language: erlang 2 | otp_release: 3 | - 17.3 4 | 5 | script: "make compile test" 6 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | %%-*- mode: erlang -*- 2 | {cover_enabled, true}. 3 | {erl_opts, [warnings_as_errors]}. 4 | -------------------------------------------------------------------------------- /src/epmdpxy.hrl: -------------------------------------------------------------------------------- 1 | -record(node, {pid, 2 | monitor, 3 | port_no, 4 | proxy_port_no, 5 | node_type, 6 | protocol, 7 | highest_version, 8 | lowest_version, 9 | node_name, 10 | extra}). 11 | 12 | -------------------------------------------------------------------------------- /src/epmdpxy.app.src: -------------------------------------------------------------------------------- 1 | {application, epmdpxy, 2 | [ 3 | {description, "A Proxy for the Erlang Port Mapper Deamon"}, 4 | {vsn, "0.0.1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib 9 | ]}, 10 | {mod, { epmdpxy_app, []}}, 11 | {env, [ 12 | {port, 4369} 13 | ]} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/epmdpxy_app.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | epmdpxy_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | REBAR=$(shell which rebar) 2 | 3 | # ============================================================================= 4 | # Verify that the programs we need to run are installed on this system 5 | # ============================================================================= 6 | ERL = $(shell which erl) 7 | 8 | ifeq ($(ERL),) 9 | $(error "Erlang not available on this system") 10 | endif 11 | 12 | ifeq ($(REBAR),) 13 | $(error "Rebar not available on this system") 14 | endif 15 | 16 | all: compile test 17 | 18 | compile: 19 | $(REBAR) skip_deps=true compile 20 | 21 | eunit: compile 22 | ERL_FLAGS="-epmd_port 43690" $(REBAR) skip_deps=true eunit 23 | 24 | test: compile eunit 25 | 26 | clean: 27 | - rm -rf $(CURDIR)/.eunit 28 | - rm -rf $(CURDIR)/ebin 29 | $(REBAR) skip_deps=true clean 30 | -------------------------------------------------------------------------------- /src/epmdpxy_sup.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | {ok, { {one_for_one, 5, 10}, [?CHILD(epmdpxy_listener, worker), 27 | ?CHILD(epmdpxy_conn_sup, supervisor), 28 | ?CHILD(epmdpxy_splitter, worker), 29 | ?CHILD(epmdpxy_session_sup, supervisor), 30 | ?CHILD(epmdpxy_reg, worker)]} }. 31 | 32 | -------------------------------------------------------------------------------- /src/epmdpxy.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy). 2 | -export([start/0, 3 | start/1, 4 | start/2, 5 | status/0, 6 | cut_cables/2, 7 | fix_cables/2]). 8 | 9 | start() -> 10 | EPMD_PORT = 11 | case os:getenv("ERL_EPMD_PORT") of 12 | false -> 13 | {ok, DefaultPort} = application:get_env(epmdpxy, port), 14 | DefaultPort; 15 | StrPort -> 16 | list_to_integer(StrPort) 17 | end, 18 | start(EPMD_PORT). 19 | 20 | start(EPMD_PORT) -> 21 | application:load(epmdpxy), 22 | application:set_env(epmdpxy, port, EPMD_PORT), 23 | application:ensure_all_started(epmdpxy). 24 | 25 | start(NodeName, EPMD_PORT) when is_atom(NodeName) and is_integer(EPMD_PORT) -> 26 | %% be sure that you either started the vm with erl -epmd_prot EPMD_PORT 27 | %% or you have exported the enviroment variable ERL_EPMD_PORT 28 | StrEPMD_PORT = integer_to_list(EPMD_PORT), 29 | {ok, [[StrEPMD_PORT]|_]} = init:get_argument(epmd_port), 30 | start(EPMD_PORT), 31 | NodeNameType = 32 | case re:split(atom_to_list(NodeName), "@") of 33 | [_] -> shortnames; 34 | [_,_] -> longnames 35 | end, 36 | {ok, _} = net_kernel:start([NodeName, NodeNameType]), 37 | ok. 38 | 39 | 40 | status() -> 41 | epmdpxy_session_sup:status(). 42 | 43 | cut_cables(Island1, Island2) -> 44 | epmdpxy_splitter:cut_cables(sanitize(Island1), 45 | sanitize(Island2)). 46 | 47 | fix_cables(Island1, Island2) -> 48 | epmdpxy_splitter:fix_cables(sanitize(Island1), 49 | sanitize(Island2)). 50 | 51 | sanitize(Nodes) -> 52 | sanitize(Nodes, []). 53 | 54 | sanitize([Node|Rest], Acc) when is_atom(Node) -> 55 | sanitize(Rest, [atom_to_binary(Node, latin1)|Acc]); 56 | sanitize([Node|Rest], Acc) when is_list(Node) -> 57 | sanitize(Rest, [list_to_binary(Node)|Acc]); 58 | sanitize([Node|Rest], Acc) when is_binary(Node) -> 59 | sanitize(Rest, [Node|Acc]); 60 | sanitize([], Acc) -> Acc. 61 | -------------------------------------------------------------------------------- /src/epmdpxy_conn_sup.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_conn_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0, 7 | start_conn/1]). 8 | 9 | %% Supervisor callbacks 10 | -export([init/1]). 11 | 12 | -define(CHILD(Mod, Type, Args), {Mod, {Mod, start_link, Args}, 13 | temporary, 5000, Type, [Mod]}). 14 | 15 | %%%=================================================================== 16 | %%% API functions 17 | %%%=================================================================== 18 | 19 | %%-------------------------------------------------------------------- 20 | %% @doc 21 | %% Starts the supervisor 22 | %% 23 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 24 | %% @end 25 | %%-------------------------------------------------------------------- 26 | start_link() -> 27 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 28 | 29 | start_conn(Sock) -> 30 | {ok, Pid} = supervisor:start_child(?MODULE, []), 31 | ok = gen_tcp:controlling_process(Sock, Pid), 32 | ok = epmdpxy_conn:hand_over_socket(Pid, Sock), 33 | {ok, Pid}. 34 | 35 | %%%=================================================================== 36 | %%% Supervisor callbacks 37 | %%%=================================================================== 38 | 39 | %%-------------------------------------------------------------------- 40 | %% @private 41 | %% @doc 42 | %% Whenever a supervisor is started using supervisor:start_link/[2,3], 43 | %% this function is called by the new process to find out about 44 | %% restart strategy, maximum restart frequency and child 45 | %% specifications. 46 | %% 47 | %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | 48 | %% ignore | 49 | %% {error, Reason} 50 | %% @end 51 | %%-------------------------------------------------------------------- 52 | init([]) -> 53 | {ok, {{simple_one_for_one, 0, 5}, [?CHILD(epmdpxy_conn, worker, [])]}}. 54 | 55 | %%%=================================================================== 56 | %%% Internal functions 57 | %%%=================================================================== 58 | -------------------------------------------------------------------------------- /src/epmdpxy_session_sup.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_session_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0, 7 | start_session/2, 8 | connection_created/3, 9 | connection_deleted/3, 10 | status/0]). 11 | 12 | %% Supervisor callbacks 13 | -export([init/1]). 14 | 15 | -define(CHILD(Id, Mod, Type, Args), {Id, {Mod, start_link, Args}, 16 | temporary, 5000, Type, [Mod]}). 17 | 18 | %%%=================================================================== 19 | %%% API functions 20 | %%%=================================================================== 21 | 22 | %%-------------------------------------------------------------------- 23 | %% @doc 24 | %% Starts the supervisor 25 | %% 26 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 27 | %% @end 28 | %%-------------------------------------------------------------------- 29 | start_link() -> 30 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 31 | 32 | start_session(UpstreamNode, Port) -> 33 | {ok, Pid} = supervisor:start_child(?MODULE, [UpstreamNode, Port]), 34 | ProxyPort = epmdpxy_session:accept(Pid), 35 | ProxyPort. 36 | 37 | connection_created(SessionPid, DownstreamNode, UpstreamNode) -> 38 | epmdpxy_splitter:add_cable(DownstreamNode, UpstreamNode, SessionPid). 39 | 40 | connection_deleted(SessionPid, DownstreamNode, UpstreamNode) -> 41 | epmdpxy_splitter:delete_cable(DownstreamNode, UpstreamNode, SessionPid). 42 | 43 | status() -> 44 | [epmdpxy_session:status(Pid) 45 | || {_, Pid, _, _} <- supervisor:which_children(?MODULE), is_pid(Pid)]. 46 | 47 | %%%=================================================================== 48 | %%% Supervisor callbacks 49 | %%%=================================================================== 50 | 51 | %%-------------------------------------------------------------------- 52 | %% @private 53 | %% @doc 54 | %% Whenever a supervisor is started using supervisor:start_link/[2,3], 55 | %% this function is called by the new process to find out about 56 | %% restart strategy, maximum restart frequency and child 57 | %% specifications. 58 | %% 59 | %% @spec init(Args) -> {ok, {SupFlags, [ChildSpec]}} | 60 | %% ignore | 61 | %% {error, Reason} 62 | %% @end 63 | %%-------------------------------------------------------------------- 64 | init([]) -> 65 | {ok, {{simple_one_for_one, 0, 5}, 66 | [?CHILD(epmdpxy_session, epmdpxy_session, worker, [])]}}. 67 | 68 | %%%=================================================================== 69 | %%% Internal functions 70 | %%%=================================================================== 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # EPMDPXY - A Proxy for the Erlang Port Mapper Deamon [![Build Status](https://travis-ci.org/dergraf/epmdpxy.svg)](https://travis-ci.org/dergraf/epmdpxy) 2 | 3 | EPMDPXY simulates the basic functionality of EPMD, just enough to make a local Erlang cluster work. With one exception though, instead of replying the listener port when handling PORT_PLEASE2_REQ, it spawns an internal listener and replies the new (random) port number instead. The newly spawned listener process accepts one single connection and connects to the 'real' listener port of the remote node, acting as a proxy between the two Erlang nodes. 4 | 5 | ### EPMD 6 | 7 | If a distributed Erlang node is started with the -name or -sname argument it will first try to connect to the EPMD. In case the EPMD is not running on that local machine yet it gets started as a separate OS process. The EPMD normally listens on the 4369 TCP port, this can be adjusted using the environment variable ERL_EPMD_PORT. A distributed Erlang node connects to the EPMD and registers its node name together with a random listener port, that is used for accepting connections from other nodes in the cluster (ALIVE2_REQ). If a node wants to communicate with an other cluster node it hasn't seen yet, it contacts the EPMD and asks for the listening port of the other node (PORT_PLEASE2_REQ). 8 | 9 | It's all on [Distributed Erlang](http://www.erlang.org/doc/reference_manual/distributed.html) and [Erlang Distribution Protocol](http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html) 10 | 11 | ## Starting EPMDPXY 12 | 13 | Open a new Erlang shell, without the -name or -sname arguments. 14 | 15 | ```erlang 16 | %% start the epmdpxy listening on the default EPMD port 4369. 17 | %% be sure that no other EPMD is running on this port. 18 | epmdpxy:start(). 19 | ``` 20 | 21 | If you prefer to use a different EPMD port use: 22 | 23 | ```erlang 24 | %% start the epmdpxy listening on a non standard port. 25 | epmdpxy:start(43690). 26 | ``` 27 | If you go this way, you have to set the ERL_EPMD_PORT=43690 environment variable before starting 28 | your cluster nodes. 29 | 30 | ### Simulating Network Partitions 31 | 32 | EPMDPXY currently only implements one feature, namely the simulation of network partitions in an Erlang cluster. 33 | Assuming your cluster consists of five nodes node1, node2, node3, node4, node5. Simulating a 34 | netsplit resulting in two islands [node1, node2] and [node3, node4, node5] can be done 35 | using: 36 | 37 | ```erlang 38 | epmdpxy:cut_cables([node1, node2], [node3, node4, node5]). 39 | ``` 40 | 41 | In this situation the proxy blocks all the traffic between the two islands. 42 | 43 | To fix the network partition call: 44 | 45 | ```erlang 46 | epmdpxy:fix_cables([node1, node2], [node3, node4, node5]). 47 | ``` 48 | 49 | ## Remarks 50 | 51 | - it's pretty alpha.. 52 | - we'll be using it at Erlio GmbH, so it will eventually mature 53 | - of course this is not a replacement for real hardware damage 54 | -------------------------------------------------------------------------------- /src/epmdpxy_listener.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_listener). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% gen_server callbacks 9 | -export([init/1, 10 | handle_call/3, 11 | handle_cast/2, 12 | handle_info/2, 13 | terminate/2, 14 | code_change/3]). 15 | 16 | -record(state, {listener_socket}). 17 | 18 | %%%=================================================================== 19 | %%% API 20 | %%%=================================================================== 21 | 22 | %%-------------------------------------------------------------------- 23 | %% @doc 24 | %% Starts the server 25 | %% 26 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 27 | %% @end 28 | %%-------------------------------------------------------------------- 29 | start_link() -> 30 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 31 | 32 | %%%=================================================================== 33 | %%% gen_server callbacks 34 | %%%=================================================================== 35 | 36 | %%-------------------------------------------------------------------- 37 | %% @private 38 | %% @doc 39 | %% Initializes the server 40 | %% 41 | %% @spec init(Args) -> {ok, State} | 42 | %% {ok, State, Timeout} | 43 | %% ignore | 44 | %% {stop, Reason} 45 | %% @end 46 | %%-------------------------------------------------------------------- 47 | init([]) -> 48 | {ok, ListenerPort} = application:get_env(epmdpxy, port), 49 | {ok, ListenerSocket} = gen_tcp:listen(ListenerPort, [binary, 50 | {reuseaddr, true}, 51 | {active, false}]), 52 | {ok, #state{listener_socket=ListenerSocket}, 0}. 53 | 54 | %%-------------------------------------------------------------------- 55 | %% @private 56 | %% @doc 57 | %% Handling call messages 58 | %% 59 | %% @spec handle_call(Request, From, State) -> 60 | %% {reply, Reply, State} | 61 | %% {reply, Reply, State, Timeout} | 62 | %% {noreply, State} | 63 | %% {noreply, State, Timeout} | 64 | %% {stop, Reason, Reply, State} | 65 | %% {stop, Reason, State} 66 | %% @end 67 | %%-------------------------------------------------------------------- 68 | handle_call(_Request, _From, State) -> 69 | Reply = ok, 70 | {reply, Reply, State}. 71 | 72 | %%-------------------------------------------------------------------- 73 | %% @private 74 | %% @doc 75 | %% Handling cast messages 76 | %% 77 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 78 | %% {noreply, State, Timeout} | 79 | %% {stop, Reason, State} 80 | %% @end 81 | %%-------------------------------------------------------------------- 82 | handle_cast(_Msg, State) -> 83 | {noreply, State}. 84 | 85 | %%-------------------------------------------------------------------- 86 | %% @private 87 | %% @doc 88 | %% Handling all non call/cast messages 89 | %% 90 | %% @spec handle_info(Info, State) -> {noreply, State} | 91 | %% {noreply, State, Timeout} | 92 | %% {stop, Reason, State} 93 | %% @end 94 | %%-------------------------------------------------------------------- 95 | handle_info(timeout, #state{listener_socket=ListenerSocket} = State) -> 96 | {ok, Sock} = gen_tcp:accept(ListenerSocket), 97 | {ok, _Pid} = epmdpxy_conn_sup:start_conn(Sock), 98 | {noreply, State, 0}. 99 | 100 | %%-------------------------------------------------------------------- 101 | %% @private 102 | %% @doc 103 | %% This function is called by a gen_server when it is about to 104 | %% terminate. It should be the opposite of Module:init/1 and do any 105 | %% necessary cleaning up. When it returns, the gen_server terminates 106 | %% with Reason. The return value is ignored. 107 | %% 108 | %% @spec terminate(Reason, State) -> void() 109 | %% @end 110 | %%-------------------------------------------------------------------- 111 | terminate(_Reason, _State) -> 112 | ok. 113 | 114 | %%-------------------------------------------------------------------- 115 | %% @private 116 | %% @doc 117 | %% Convert process state when code is changed 118 | %% 119 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 120 | %% @end 121 | %%-------------------------------------------------------------------- 122 | code_change(_OldVsn, State, _Extra) -> 123 | {ok, State}. 124 | 125 | %%%=================================================================== 126 | %%% Internal functions 127 | %%%=================================================================== 128 | -------------------------------------------------------------------------------- /test/epmdpxy_netsplit_tests.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_netsplit_tests). 2 | -export([start_node/0, 3 | proxy/0]). 4 | -include_lib("eunit/include/eunit.hrl"). 5 | 6 | -define(NR_OF_NODES, 5). 7 | -define(NET_TICK_TIME, 5). % 5 seconds 8 | 9 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 10 | %%% Setup Functions 11 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 12 | run_test_() -> 13 | {timeout, ?NET_TICK_TIME * 3, 14 | {setup, 15 | fun() -> 16 | epmdpxy:start(epmdpxy_test, epmd_port()), 17 | timer:sleep(1000), 18 | io:format(user, "Started EPMDPXY on node ~p with EPMD port ~p~n", [node(), epmd_port()]), 19 | net_kernel:set_net_ticktime(?NET_TICK_TIME, ?NET_TICK_TIME), 20 | Hosts = hosts(), 21 | Ns = start_slaves(Hosts), 22 | try 23 | [ok = rpc:call(Node, ?MODULE, start_node, []) 24 | || Node <- Ns], 25 | timer:sleep(?NET_TICK_TIME), 26 | Ns 27 | catch 28 | _:R -> 29 | stop_slaves(Ns), 30 | exit(R) 31 | end 32 | end, 33 | fun(Ns) -> 34 | stop_slaves(Ns) 35 | end, 36 | fun(Ns) -> 37 | {timeout, ?NET_TICK_TIME * 2, 38 | [?_test(netsplit_test(Ns))] 39 | } 40 | end 41 | }}. 42 | 43 | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | %%% Actual Tests 46 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 47 | netsplit_test(Nodes) -> 48 | ok = check_connected(Nodes, 0), 49 | io:format(user, "Nodes ~p are fully connected~n", [Nodes]), 50 | 51 | %% Create Partitions 52 | Size = ?NR_OF_NODES div 2, 53 | Island1 = lists:sublist(Nodes, Size), 54 | Island2 = Nodes -- Island1, 55 | io:format(user, "Create two partitions ~p and ~p~n", [Island1, Island2]), 56 | epmdpxy:cut_cables(Island1, Island2), 57 | io:format(user, "Sleep ~p seconds (net_ticktime)~n", [?NET_TICK_TIME]), 58 | timer:sleep(?NET_TICK_TIME * 1000), 59 | io:format(user, "Check network partitions~n", []), 60 | check_connected(Island1, 0), 61 | check_connected(Island2, 0), 62 | io:format(user, "Fix network partitions~n", []), 63 | epmdpxy:fix_cables(Island1, Island2), 64 | ok = check_connected(Nodes, 0). 65 | 66 | 67 | check_connected([N1, N2|Rest] = Nodes, I) when length(Nodes) < I -> 68 | %% ensure all nodes are connected 69 | [N2|Rest] = call_proxy(N1, erlang, nodes, []) -- [node()], 70 | check_connected([N2|Rest] ++ [N1], I + 1); 71 | check_connected(_ , _) -> ok. 72 | 73 | 74 | 75 | 76 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 77 | %%% Internal 78 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 79 | 80 | hosts() -> 81 | [list_to_atom("node"++integer_to_list(I)) 82 | || I <- lists:seq(1, ?NR_OF_NODES)]. 83 | 84 | start_node() -> 85 | net_kernel:set_net_ticktime(?NET_TICK_TIME, ?NET_TICK_TIME), 86 | ok. 87 | 88 | %% from uwiger/locks 89 | -define(PROXY, epmdpxy_test_proxy). 90 | proxy() -> 91 | register(?PROXY, self()), 92 | process_flag(trap_exit, true), 93 | proxy_loop(). 94 | 95 | proxy_loop() -> 96 | receive 97 | {From, Ref, apply, M, F, A} -> 98 | From ! {Ref, (catch apply(M,F,A))}; 99 | _ -> 100 | ok 101 | end, 102 | proxy_loop(). 103 | 104 | %proxy_multicall(Ns, M, F, A) -> 105 | % [call_proxy(N, M, F, A) || N <- Ns]. 106 | % 107 | call_proxy(N, M, F, A) -> 108 | Ref = erlang:monitor(process, {?PROXY, N}), 109 | {?PROXY, N} ! {self(), Ref, apply, M, F, A}, 110 | receive 111 | {'DOWN', Ref, _, _, Reason} -> 112 | error({proxy_died, N, Reason}); 113 | {Ref, Result} -> 114 | Result 115 | after 1000 -> 116 | error(proxy_call_timeout) 117 | end. 118 | 119 | start_slaves(Ns) -> 120 | Nodes = [start_slave(N) || N <- Ns], 121 | Nodes. 122 | 123 | start_slave(Name) -> 124 | {Pa, Pz} = paths(), 125 | Paths = "-pa ./ -pz ../ebin" ++ 126 | lists:flatten([[" -pa " ++ Path || Path <- Pa], 127 | [" -pz " ++ Path || Path <- Pz]]), 128 | {ok, Node} = ct_slave:start(host(), Name, [{erl_flags, Paths}, 129 | {monitor_master, true}]), 130 | spawn(Node, ?MODULE, proxy, []), 131 | Node. 132 | 133 | stop_slaves(Ns) -> 134 | [ok = stop_slave(N) || N <- Ns], 135 | ok. 136 | 137 | stop_slave(N) -> 138 | try erlang:monitor_node(N, true) of 139 | true -> 140 | rpc:call(N, erlang, halt, []), 141 | receive 142 | {nodedown, N} -> ok 143 | after 10000 -> 144 | erlang:error(slave_stop_timeout) 145 | end 146 | catch 147 | error:badarg -> 148 | ok 149 | end. 150 | 151 | paths() -> 152 | Path = code:get_path(), 153 | {ok, [[Root]]} = init:get_argument(root), 154 | {Pas, Rest} = lists:splitwith(fun(P) -> 155 | not lists:prefix(Root, P) 156 | end, Path), 157 | Pzs = lists:filter(fun(P) -> 158 | not lists:prefix(Root, P) 159 | end, Rest), 160 | {Pas, Pzs}. 161 | 162 | host() -> 163 | {ok, HostName} = inet:gethostname(), 164 | list_to_atom(HostName). 165 | 166 | epmd_port() -> 167 | {ok, [[StrEPMD_PORT]]} = init:get_argument(epmd_port), 168 | list_to_integer(StrEPMD_PORT). 169 | -------------------------------------------------------------------------------- /src/epmdpxy_reg.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_reg). 2 | -include("epmdpxy.hrl"). 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0, 7 | register_node/7, 8 | get_node/1, 9 | fold/2]). 10 | 11 | %% gen_server callbacks 12 | -export([init/1, 13 | handle_call/3, 14 | handle_cast/2, 15 | handle_info/2, 16 | terminate/2, 17 | code_change/3]). 18 | 19 | -record(state, {}). 20 | 21 | %%%=================================================================== 22 | %%% API 23 | %%%=================================================================== 24 | 25 | %%-------------------------------------------------------------------- 26 | %% @doc 27 | %% Starts the server 28 | %% 29 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 30 | %% @end 31 | %%-------------------------------------------------------------------- 32 | start_link() -> 33 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 34 | 35 | register_node(PortNo, NodeType, Protocol, HighestVersion, 36 | LowestVersion, NodeName, Extra) -> 37 | case get_node_(NodeName) of 38 | {error, not_found} -> 39 | Node = #node{port_no=PortNo, 40 | node_type=NodeType, 41 | protocol=Protocol, 42 | highest_version=HighestVersion, 43 | lowest_version=LowestVersion, 44 | node_name=NodeName, 45 | extra=Extra, 46 | pid=self()}, 47 | gen_server:call(?MODULE, {register_node, Node}); 48 | {ok, _} -> 49 | {error, already_registered} 50 | end. 51 | 52 | fold(FoldFun, FoldAcc) -> 53 | ets:foldl(FoldFun, FoldAcc, ?MODULE). 54 | 55 | get_node(NodeName) -> 56 | case get_node_(NodeName) of 57 | {ok, #node{port_no=Port} = Node} -> 58 | ProxyPort = epmdpxy_session_sup:start_session(NodeName, Port), 59 | {ok, Node#node{proxy_port_no=ProxyPort}}; 60 | {error, not_found} -> 61 | {error, not_found} 62 | end. 63 | 64 | get_node_(NodeName) -> 65 | gen_server:call(?MODULE, {get_node, NodeName}). 66 | 67 | %%%=================================================================== 68 | %%% gen_server callbacks 69 | %%%=================================================================== 70 | 71 | %%-------------------------------------------------------------------- 72 | %% @private 73 | %% @doc 74 | %% Initializes the server 75 | %% 76 | %% @spec init(Args) -> {ok, State} | 77 | %% {ok, State, Timeout} | 78 | %% ignore | 79 | %% {stop, Reason} 80 | %% @end 81 | %%-------------------------------------------------------------------- 82 | init([]) -> 83 | ets:new(?MODULE, [named_table, public, {keypos, 2}]), 84 | {ok, #state{}}. 85 | 86 | %%-------------------------------------------------------------------- 87 | %% @private 88 | %% @doc 89 | %% Handling call messages 90 | %% 91 | %% @spec handle_call(Request, From, State) -> 92 | %% {reply, Reply, State} | 93 | %% {reply, Reply, State, Timeout} | 94 | %% {noreply, State} | 95 | %% {noreply, State, Timeout} | 96 | %% {stop, Reason, Reply, State} | 97 | %% {stop, Reason, State} 98 | %% @end 99 | %%-------------------------------------------------------------------- 100 | handle_call({register_node, #node{pid=Pid} = Node}, _From, State) -> 101 | MRef = monitor(process, Pid), 102 | ets:insert(?MODULE, Node#node{monitor=MRef}), 103 | {reply, ok, State}; 104 | handle_call({get_node, NodeName}, _From, State) -> 105 | Reply = 106 | case ets:match_object(?MODULE, #node{node_name=NodeName, _='_'}) of 107 | [] -> {error, not_found}; 108 | [Node] ->{ok, Node} 109 | end, 110 | {reply, Reply, State}; 111 | handle_call(_Request, _From, State) -> 112 | Reply = ok, 113 | {reply, Reply, State}. 114 | 115 | %%-------------------------------------------------------------------- 116 | %% @private 117 | %% @doc 118 | %% Handling cast messages 119 | %% 120 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 121 | %% {noreply, State, Timeout} | 122 | %% {stop, Reason, State} 123 | %% @end 124 | %%-------------------------------------------------------------------- 125 | handle_cast(_Msg, State) -> 126 | {noreply, State}. 127 | 128 | %%-------------------------------------------------------------------- 129 | %% @private 130 | %% @doc 131 | %% Handling all non call/cast messages 132 | %% 133 | %% @spec handle_info(Info, State) -> {noreply, State} | 134 | %% {noreply, State, Timeout} | 135 | %% {stop, Reason, State} 136 | %% @end 137 | %%-------------------------------------------------------------------- 138 | handle_info({'DOWN', _, process, Pid, _}, State) -> 139 | ets:delete(?MODULE, Pid), 140 | {noreply, State}. 141 | 142 | %%-------------------------------------------------------------------- 143 | %% @private 144 | %% @doc 145 | %% This function is called by a gen_server when it is about to 146 | %% terminate. It should be the opposite of Module:init/1 and do any 147 | %% necessary cleaning up. When it returns, the gen_server terminates 148 | %% with Reason. The return value is ignored. 149 | %% 150 | %% @spec terminate(Reason, State) -> void() 151 | %% @end 152 | %%-------------------------------------------------------------------- 153 | terminate(_Reason, _State) -> 154 | ok. 155 | 156 | %%-------------------------------------------------------------------- 157 | %% @private 158 | %% @doc 159 | %% Convert process state when code is changed 160 | %% 161 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 162 | %% @end 163 | %%-------------------------------------------------------------------- 164 | code_change(_OldVsn, State, _Extra) -> 165 | {ok, State}. 166 | 167 | %%%=================================================================== 168 | %%% Internal functions 169 | %%%=================================================================== 170 | 171 | -------------------------------------------------------------------------------- /src/epmdpxy_splitter.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_splitter). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/0, 7 | add_cable/3, 8 | delete_cable/3, 9 | cut_cables/2, 10 | fix_cables/2]). 11 | 12 | %% gen_server callbacks 13 | -export([init/1, 14 | handle_call/3, 15 | handle_cast/2, 16 | handle_info/2, 17 | terminate/2, 18 | code_change/3]). 19 | 20 | -record(state, {}). 21 | 22 | %%%=================================================================== 23 | %%% API 24 | %%%=================================================================== 25 | 26 | %%-------------------------------------------------------------------- 27 | %% @doc 28 | %% Starts the server 29 | %% 30 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 31 | %% @end 32 | %%-------------------------------------------------------------------- 33 | start_link() -> 34 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 35 | 36 | add_cable(NodeA, NodeB, SessionPid) -> 37 | SNodeA = ensure_sname(NodeA), 38 | SNodeB = ensure_sname(NodeB), 39 | gen_server:call(?MODULE, {add_cable, SNodeA, SNodeB, SessionPid}). 40 | 41 | delete_cable(NodeA, NodeB, SessionPid) -> 42 | SNodeA = ensure_sname(NodeA), 43 | SNodeB = ensure_sname(NodeB), 44 | gen_server:call(?MODULE, {del_cable, SNodeA, SNodeB, SessionPid}). 45 | 46 | cut_cables(IslandA, IslandB) -> 47 | play_with_cables(true, IslandA, IslandB). 48 | 49 | fix_cables(IslandA, IslandB) -> 50 | play_with_cables(false, IslandA, IslandB). 51 | 52 | play_with_cables(Cut, IslandA, IslandB) -> 53 | gen_server:call(?MODULE, {play_with_cables, Cut, IslandA, IslandB}). 54 | 55 | %%%=================================================================== 56 | %%% gen_server callbacks 57 | %%%=================================================================== 58 | 59 | %%-------------------------------------------------------------------- 60 | %% @private 61 | %% @doc 62 | %% Initializes the server 63 | %% 64 | %% @spec init(Args) -> {ok, State} | 65 | %% {ok, State, Timeout} | 66 | %% ignore | 67 | %% {stop, Reason} 68 | %% @end 69 | %%-------------------------------------------------------------------- 70 | init([]) -> 71 | ets:new(epmdpxy_cables, [named_table, bag]), 72 | ets:new(epmdpxy_cuts, [named_table]), 73 | {ok, #state{}}. 74 | 75 | %%-------------------------------------------------------------------- 76 | %% @private 77 | %% @doc 78 | %% Handling call messages 79 | %% 80 | %% @spec handle_call(Request, From, State) -> 81 | %% {reply, Reply, State} | 82 | %% {reply, Reply, State, Timeout} | 83 | %% {noreply, State} | 84 | %% {noreply, State, Timeout} | 85 | %% {stop, Reason, Reply, State} | 86 | %% {stop, Reason, State} 87 | %% @end 88 | %%-------------------------------------------------------------------- 89 | handle_call({add_cable, NodeA, NodeB, SessionPid}, _From, State) -> 90 | Key = lists:sort([NodeA, NodeB]), 91 | ets:insert(epmdpxy_cables, {Key, SessionPid}), 92 | IsActive = [] == ets:lookup(epmdpxy_cuts, Key), 93 | {reply, IsActive, State}; 94 | handle_call({del_cable, NodeA, NodeB, SessionPid}, _From, State) -> 95 | Key = lists:sort([NodeA, NodeB]), 96 | ets:delete_object(epmdpxy_cables, {Key, SessionPid}), 97 | {reply, ok, State}; 98 | handle_call({play_with_cables, Cut, IslandA, IslandB}, _From, State) -> 99 | Segments = map_cables(IslandA, IslandB, []), 100 | [case Cut of 101 | true -> 102 | epmdpxy_session:cut_cable(SessionPid), 103 | ets:insert(epmdpxy_cuts, {Key, cut}); 104 | false -> 105 | epmdpxy_session:fix_cable(SessionPid), 106 | ets:delete(epmdpxy_cuts, Key) 107 | end || {Key, SessionPid} <- Segments], 108 | {reply, ok, State}. 109 | 110 | 111 | 112 | %%-------------------------------------------------------------------- 113 | %% @private 114 | %% @doc 115 | %% Handling cast messages 116 | %% 117 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 118 | %% {noreply, State, Timeout} | 119 | %% {stop, Reason, State} 120 | %% @end 121 | %%-------------------------------------------------------------------- 122 | handle_cast(_Msg, State) -> 123 | {noreply, State}. 124 | 125 | %%-------------------------------------------------------------------- 126 | %% @private 127 | %% @doc 128 | %% Handling all non call/cast messages 129 | %% 130 | %% @spec handle_info(Info, State) -> {noreply, State} | 131 | %% {noreply, State, Timeout} | 132 | %% {stop, Reason, State} 133 | %% @end 134 | %%-------------------------------------------------------------------- 135 | handle_info(_Info, State) -> 136 | {noreply, State}. 137 | 138 | %%-------------------------------------------------------------------- 139 | %% @private 140 | %% @doc 141 | %% This function is called by a gen_server when it is about to 142 | %% terminate. It should be the opposite of Module:init/1 and do any 143 | %% necessary cleaning up. When it returns, the gen_server terminates 144 | %% with Reason. The return value is ignored. 145 | %% 146 | %% @spec terminate(Reason, State) -> void() 147 | %% @end 148 | %%-------------------------------------------------------------------- 149 | terminate(_Reason, _State) -> 150 | ok. 151 | 152 | %%-------------------------------------------------------------------- 153 | %% @private 154 | %% @doc 155 | %% Convert process state when code is changed 156 | %% 157 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 158 | %% @end 159 | %%-------------------------------------------------------------------- 160 | code_change(_OldVsn, State, _Extra) -> 161 | {ok, State}. 162 | 163 | %%%=================================================================== 164 | %%% Internal functions 165 | %%%=================================================================== 166 | ensure_sname(Name) when is_binary(Name) -> 167 | [SName|_] = re:split(Name, "@"), 168 | SName; 169 | ensure_sname(undefined) -> 170 | undefined. 171 | 172 | map_cables([DownStreamNode|DownstreamNodes], UpstreamNodes, Acc) -> 173 | SDownStreamNode = ensure_sname(DownStreamNode), 174 | map_cables( 175 | DownstreamNodes, 176 | UpstreamNodes, 177 | lists:foldl( 178 | fun(N, AccAcc) -> 179 | Key = lists:sort([SDownStreamNode, ensure_sname(N)]), 180 | lists:foldl(fun({_, SessionPid}, AccAccAcc) -> 181 | [{Key, SessionPid}|AccAccAcc] 182 | end, AccAcc, ets:lookup(epmdpxy_cables, Key)) 183 | end, Acc, UpstreamNodes)); 184 | map_cables([], _, Acc) -> Acc. 185 | -------------------------------------------------------------------------------- /src/epmdpxy_conn.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_conn). 2 | -include("epmdpxy.hrl"). 3 | 4 | -behaviour(gen_server). 5 | 6 | %% API 7 | -export([start_link/0, 8 | hand_over_socket/2]). 9 | 10 | %% gen_server callbacks 11 | -export([init/1, 12 | handle_call/3, 13 | handle_cast/2, 14 | handle_info/2, 15 | terminate/2, 16 | code_change/3]). 17 | 18 | -record(state, {socket, buffer= <<>>}). 19 | 20 | -define(ALIVE2_REQ, 120). 21 | -define(ALIVE2_RESP, 121). 22 | -define(PORT_PLEASE2_REQ, 122). 23 | -define(PORT2_RESP, 119). 24 | -define(NAMES_REQ, 110). 25 | -define(DUMP_REQ, 100). 26 | 27 | -define(OK, 0). 28 | 29 | %%%=================================================================== 30 | %%% API 31 | %%%=================================================================== 32 | 33 | %%-------------------------------------------------------------------- 34 | %% @doc 35 | %% Starts the server 36 | %% 37 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 38 | %% @end 39 | %%-------------------------------------------------------------------- 40 | start_link() -> 41 | gen_server:start_link(?MODULE, [], []). 42 | 43 | hand_over_socket(Pid, Socket) -> 44 | gen_server:cast(Pid, {hand_over_socket, Socket}). 45 | 46 | %%%=================================================================== 47 | %%% gen_server callbacks 48 | %%%=================================================================== 49 | 50 | %%-------------------------------------------------------------------- 51 | %% @private 52 | %% @doc 53 | %% Initializes the server 54 | %% 55 | %% @spec init(Args) -> {ok, State} | 56 | %% {ok, State, Timeout} | 57 | %% ignore | 58 | %% {stop, Reason} 59 | %% @end 60 | %%-------------------------------------------------------------------- 61 | init([]) -> 62 | {ok, #state{}}. 63 | 64 | %%-------------------------------------------------------------------- 65 | %% @private 66 | %% @doc 67 | %% Handling call messages 68 | %% 69 | %% @spec handle_call(Request, From, State) -> 70 | %% {reply, Reply, State} | 71 | %% {reply, Reply, State, Timeout} | 72 | %% {noreply, State} | 73 | %% {noreply, State, Timeout} | 74 | %% {stop, Reason, Reply, State} | 75 | %% {stop, Reason, State} 76 | %% @end 77 | %%-------------------------------------------------------------------- 78 | handle_call(_Request, _From, State) -> 79 | Reply = ok, 80 | {reply, Reply, State}. 81 | 82 | %%-------------------------------------------------------------------- 83 | %% @private 84 | %% @doc 85 | %% Handling cast messages 86 | %% 87 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 88 | %% {noreply, State, Timeout} | 89 | %% {stop, Reason, State} 90 | %% @end 91 | %%-------------------------------------------------------------------- 92 | handle_cast({hand_over_socket, Socket}, State) -> 93 | inet:setopts(Socket, [{active, once}]), 94 | {noreply, State#state{socket=Socket}}. 95 | 96 | %%-------------------------------------------------------------------- 97 | %% @private 98 | %% @doc 99 | %% Handling all non call/cast messages 100 | %% 101 | %% @spec handle_info(Info, State) -> {noreply, State} | 102 | %% {noreply, State, Timeout} | 103 | %% {stop, Reason, State} 104 | %% @end 105 | %%-------------------------------------------------------------------- 106 | handle_info({tcp, Socket, Data}, #state{socket=Socket, buffer=Buffer} = State) -> 107 | case parse(Socket, <>) of 108 | {ok, NewBuffer} -> 109 | inet:setopts(Socket, [{active, once}]), 110 | {noreply, State#state{buffer=NewBuffer}}; 111 | stop -> 112 | ok = gen_tcp:close(Socket), 113 | {stop, normal, State} 114 | end; 115 | handle_info({tcp_closed, Socket}, #state{socket=Socket} = State) -> 116 | {stop, normal, State}; 117 | handle_info({tcp_error, Socket, Reason}, #state{socket=Socket} = State) -> 118 | {stop, Reason, State}. 119 | 120 | %%-------------------------------------------------------------------- 121 | %% @private 122 | %% @doc 123 | %% This function is called by a gen_server when it is about to 124 | %% terminate. It should be the opposite of Module:init/1 and do any 125 | %% necessary cleaning up. When it returns, the gen_server terminates 126 | %% with Reason. The return value is ignored. 127 | %% 128 | %% @spec terminate(Reason, State) -> void() 129 | %% @end 130 | %%-------------------------------------------------------------------- 131 | terminate(_Reason, _State) -> 132 | ok. 133 | 134 | %%-------------------------------------------------------------------- 135 | %% @private 136 | %% @doc 137 | %% Convert process state when code is changed 138 | %% 139 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 140 | %% @end 141 | %%-------------------------------------------------------------------- 142 | code_change(_OldVsn, State, _Extra) -> 143 | {ok, State}. 144 | 145 | %%%=================================================================== 146 | %%% Internal functions 147 | %%%=================================================================== 148 | parse(Socket, <>) when size(Rest) >= Len -> 149 | <> = Rest, 150 | case parse_req(Socket, Req) of 151 | ok -> {ok, Rest1}; 152 | stop -> stop; 153 | error -> 154 | io:format("got unknown request ~p~n", [Req]), 155 | stop 156 | end; 157 | parse(_, B) -> {ok, B}. 158 | 159 | 160 | parse_req(Socket, <>) -> 163 | <> = Rest, 164 | case epmdpxy_reg:register_node(PortNo, NodeType, Protocol, HighestVersion, 165 | LowestVersion, NodeName, Extra) of 166 | ok -> 167 | gen_tcp:send(Socket, <>), 168 | ok; 169 | {error, already_registered} -> 170 | gen_tcp:send(Socket, <>), 171 | stop 172 | end; 173 | parse_req(Socket, <>) -> 174 | case epmdpxy_reg:get_node(NodeName) of 175 | {ok, #node{node_type=NodeType, 176 | proxy_port_no=ProxyPortNo, 177 | protocol=Protocol, 178 | highest_version=HighestVersion, 179 | lowest_version=LowestVersion, 180 | node_name=NodeName, 181 | extra=Extra}} -> 182 | gen_tcp:send(Socket, <>), 191 | stop; 192 | {error, not_found} -> 193 | gen_tcp:send(Socket, <>), 194 | stop 195 | end; 196 | parse_req(Socket, <>) -> 197 | {ok, EpmdPort} = application:get_env(epmdpxy, port), 198 | Acc = [<>], 199 | Res = 200 | epmdpxy_reg:fold( 201 | fun(#node{node_name=NodeName, port_no=Port}, Buffer) -> 202 | S = io_lib:format("name ~ts at port ~p~n", [NodeName, Port]), 203 | [S|Buffer] 204 | end, Acc), 205 | gen_tcp:send(Socket, lists:reverse(Res)), 206 | stop; 207 | 208 | parse_req(_, _) -> error. 209 | -------------------------------------------------------------------------------- /src/epmdpxy_session.erl: -------------------------------------------------------------------------------- 1 | -module(epmdpxy_session). 2 | 3 | -behaviour(gen_server). 4 | 5 | %% API 6 | -export([start_link/2, 7 | accept/1, 8 | cut_cable/1, 9 | fix_cable/1, 10 | status/1]). 11 | 12 | %% gen_server callbacks 13 | -export([init/1, 14 | handle_call/3, 15 | handle_cast/2, 16 | handle_info/2, 17 | terminate/2, 18 | code_change/3]). 19 | 20 | -record(state, {status=not_ready, 21 | downstream_name, 22 | upstream_name, 23 | upstream_port, 24 | listen_socket, 25 | downstream_socket, 26 | upstream_socket, 27 | tref}). 28 | 29 | -define(CLOSE_AFTER, 120000). %% should be larger than netsplit time 30 | 31 | %%%=================================================================== 32 | %%% API 33 | %%%=================================================================== 34 | 35 | %%-------------------------------------------------------------------- 36 | %% @doc 37 | %% Starts the server 38 | %% 39 | %% @spec start_link() -> {ok, Pid} | ignore | {error, Error} 40 | %% @end 41 | %%-------------------------------------------------------------------- 42 | start_link(Name, Port) -> 43 | gen_server:start_link(?MODULE, [Name, Port], []). 44 | 45 | accept(Pid) -> 46 | gen_server:call(Pid, accept). 47 | 48 | cut_cable(Pid) -> 49 | gen_server:call(Pid, cut_cable). 50 | 51 | fix_cable(Pid) -> 52 | gen_server:call(Pid, fix_cable). 53 | 54 | status(Pid) -> 55 | gen_server:call(Pid, status). 56 | 57 | 58 | %%%=================================================================== 59 | %%% gen_server callbacks 60 | %%%=================================================================== 61 | 62 | %%-------------------------------------------------------------------- 63 | %% @private 64 | %% @doc 65 | %% Initializes the server 66 | %% 67 | %% @spec init(Args) -> {ok, State} | 68 | %% {ok, State, Timeout} | 69 | %% ignore | 70 | %% {stop, Reason} 71 | %% @end 72 | %%-------------------------------------------------------------------- 73 | init([Name, Port]) -> 74 | {ok, ListenSocket} = gen_tcp:listen(0, [binary]), 75 | {ok, #state{ 76 | upstream_name=Name, 77 | upstream_port=Port, 78 | listen_socket=ListenSocket}}. 79 | 80 | %%-------------------------------------------------------------------- 81 | %% @private 82 | %% @doc 83 | %% Handling call messages 84 | %% 85 | %% @spec handle_call(Request, From, State) -> 86 | %% {reply, Reply, State} | 87 | %% {reply, Reply, State, Timeout} | 88 | %% {noreply, State} | 89 | %% {noreply, State, Timeout} | 90 | %% {stop, Reason, Reply, State} | 91 | %% {stop, Reason, State} 92 | %% @end 93 | %%-------------------------------------------------------------------- 94 | handle_call(accept, From, #state{listen_socket=ListenSocket} = State) -> 95 | {ok, ListenPort} = inet:port(ListenSocket), 96 | gen_server:reply(From, ListenPort), 97 | {ok, Socket} = gen_tcp:accept(ListenSocket), 98 | {noreply, State#state{downstream_socket=Socket}}; 99 | handle_call(cut_cable, _From, #state{downstream_socket=S, 100 | upstream_socket=UpS} = State) -> 101 | inet:setopts(S, [{active, false}]), 102 | inet:setopts(UpS, [{active, false}]), 103 | {reply, ok, State#state{status=cut}}; 104 | handle_call(fix_cable, _From, #state{downstream_socket=S, 105 | upstream_socket=UpS} = State) -> 106 | inet:setopts(S, [{active, true}]), 107 | inet:setopts(UpS, [{active, true}]), 108 | {reply, ok, State#state{status=ready}}; 109 | handle_call(status, _From, State) -> 110 | #state{status=Status, 111 | downstream_name=DownName, 112 | upstream_name=UpName} = State, 113 | {reply, [{pid, self()}, 114 | {downstream, DownName}, 115 | {upstream, UpName}, 116 | {status, Status}], State}; 117 | handle_call(_Request, _From, State) -> 118 | Reply = ok, 119 | {reply, Reply, State}. 120 | 121 | %%-------------------------------------------------------------------- 122 | %% @private 123 | %% @doc 124 | %% Handling cast messages 125 | %% 126 | %% @spec handle_cast(Msg, State) -> {noreply, State} | 127 | %% {noreply, State, Timeout} | 128 | %% {stop, Reason, State} 129 | %% @end 130 | %%-------------------------------------------------------------------- 131 | handle_cast(_Msg, State) -> 132 | {noreply, State}. 133 | 134 | %%-------------------------------------------------------------------- 135 | %% @private 136 | %% @doc 137 | %% Handling all non call/cast messages 138 | %% 139 | %% @spec handle_info(Info, State) -> {noreply, State} | 140 | %% {noreply, State, Timeout} | 141 | %% {stop, Reason, State} 142 | %% @end 143 | %%-------------------------------------------------------------------- 144 | handle_info({tcp, DownSocket, Data}, #state{downstream_name=undefined, 145 | downstream_socket=DownSocket, 146 | upstream_name=UpName} = State) -> 147 | case parse_name(UpName, Data) of 148 | undefined -> 149 | {stop, invalid_data, State}; 150 | {DownstreamName, IsActive} -> 151 | case maybe_connect(State) of 152 | {ok, #state{upstream_socket=UpSocket} = NewState} -> 153 | inet:setopts(DownSocket, [{active, IsActive}]), 154 | inet:setopts(UpSocket, [{active, IsActive}]), 155 | gen_tcp:send(UpSocket, Data), 156 | {noreply, NewState#state{ 157 | downstream_name=DownstreamName, 158 | status=case IsActive of true -> ready; _ -> cut end, 159 | tref=erlang:send_after(?CLOSE_AFTER, self(), die) 160 | }}; 161 | {error, Reason} -> 162 | gen_tcp:close(DownSocket), 163 | {stop, Reason, State} 164 | end 165 | end; 166 | handle_info(die, State) -> 167 | {stop, normal, State}; 168 | handle_info({tcp, DownS, Data}, #state{downstream_socket=DownS, 169 | upstream_socket=UpS, 170 | tref=TRef} = State) -> 171 | erlang:cancel_timer(TRef), 172 | gen_tcp:send(UpS, Data), 173 | {noreply, State#state{tref=erlang:send_after(?CLOSE_AFTER, self(), die)}}; 174 | handle_info({tcp, UpS, Data}, #state{downstream_socket=DownS, 175 | upstream_socket=UpS, 176 | tref=TRef} = State) -> 177 | erlang:cancel_timer(TRef), 178 | gen_tcp:send(DownS, Data), 179 | {noreply, State#state{tref=erlang:send_after(?CLOSE_AFTER, self(), die)}}; 180 | handle_info({tcp_closed, _}, State) -> 181 | {stop, normal, State}; 182 | handle_info({tcp_error, _, Error}, State) -> 183 | {stop, Error, State}. 184 | 185 | 186 | 187 | %%-------------------------------------------------------------------- 188 | %% @private 189 | %% @doc 190 | %% This function is called by a gen_server when it is about to 191 | %% terminate. It should be the opposite of Module:init/1 and do any 192 | %% necessary cleaning up. When it returns, the gen_server terminates 193 | %% with Reason. The return value is ignored. 194 | %% 195 | %% @spec terminate(Reason, State) -> void() 196 | %% @end 197 | %%-------------------------------------------------------------------- 198 | terminate(_Reason, #state{upstream_name=UpstreamName, 199 | downstream_name=DownstreamName}) -> 200 | epmdpxy_session_sup:connection_deleted(self(), DownstreamName, 201 | UpstreamName), 202 | ok. 203 | 204 | %%-------------------------------------------------------------------- 205 | %% @private 206 | %% @doc 207 | %% Convert process state when code is changed 208 | %% 209 | %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState} 210 | %% @end 211 | %%-------------------------------------------------------------------- 212 | code_change(_OldVsn, State, _Extra) -> 213 | {ok, State}. 214 | 215 | %%%=================================================================== 216 | %%% Internal functions 217 | %%%=================================================================== 218 | maybe_connect(#state{upstream_port=Port, upstream_socket=undefined} = State) -> 219 | case gen_tcp:connect({127,0,0,1}, Port, [binary, {active, true}]) of 220 | {ok, Socket} -> 221 | {ok, State#state{upstream_socket=Socket}}; 222 | {error, Reason} -> 223 | {error, Reason} 224 | end; 225 | maybe_connect(State) -> 226 | {ok, State}. 227 | 228 | parse_name(UpstreamName, Data) -> 229 | case parse_name(Data) of 230 | undefined -> 231 | undefined; 232 | DownstreamName -> 233 | process_flag(trap_exit, true), 234 | Active = epmdpxy_session_sup:connection_created(self(), DownstreamName, 235 | UpstreamName), 236 | {DownstreamName, Active} 237 | end. 238 | 239 | parse_name(<<_Len:16, "n", _Version:16, _Flags:32, DownstreamName/binary>>) -> 240 | DownstreamName; 241 | parse_name(<<_Len:16, "N", _Flags:64, _Creation:32, _NLen:16, DownstreamName/binary>>) -> 242 | DownstreamName; 243 | parse_name(_) -> 244 | undefined. --------------------------------------------------------------------------------