├── .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 [](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.
--------------------------------------------------------------------------------