-module(nlUdpIns).
|
|
|
|
-export([
|
|
server/4
|
|
, count_peers/1
|
|
, stop/1
|
|
]).
|
|
|
|
-export([
|
|
init/1
|
|
, handleMsg/2
|
|
, terminate/2
|
|
]).
|
|
|
|
-record(state, {proto, sock, port, peers, mfa}).
|
|
|
|
-define(ERROR_MSG(Format, Args),
|
|
error_logger:error_msg("[~s]: " ++ Format, [?MODULE | Args])).
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% API
|
|
%%--------------------------------------------------------------------
|
|
|
|
-spec(server(atom(), esockd:listen_on(), [gen_udp:option()], mfa())
|
|
-> {ok, pid()} | {error, term()}).
|
|
server(Proto, Port, Opts, MFA) when is_integer(Port) ->
|
|
gen_server:start_link(?MODULE, [Proto, Port, Opts, MFA], []);
|
|
server(Proto, {Host, Port}, Opts, MFA) when is_integer(Port) ->
|
|
IfAddr = case proplists:get_value(ip, Opts) of
|
|
undefined -> proplists:get_value(ifaddr, Opts);
|
|
Addr -> Addr
|
|
end,
|
|
(IfAddr == undefined) orelse (IfAddr = Host),
|
|
gen_server:start_link(?MODULE, [Proto, Port, merge_addr(Host, Opts), MFA], []).
|
|
|
|
merge_addr(Addr, Opts) ->
|
|
lists:keystore(ip, 1, Opts, {ip, Addr}).
|
|
|
|
-spec(count_peers(pid()) -> integer()).
|
|
count_peers(Pid) ->
|
|
gen_server:call(Pid, count_peers).
|
|
|
|
-spec(stop(pid()) -> ok).
|
|
stop(Pid) ->
|
|
gen_server:stop(Pid, normal, infinity).
|
|
|
|
%%--------------------------------------------------------------------
|
|
%% gen_server callbacks
|
|
%%--------------------------------------------------------------------
|
|
|
|
init([Proto, Port, Opts, MFA]) ->
|
|
process_flag(trap_exit, true),
|
|
case gen_udp:open(Port, esockd_util:merge_opts([binary, {reuseaddr, true}], Opts)) of
|
|
{ok, Sock} ->
|
|
%% Trigger the udp_passive event
|
|
inet:setopts(Sock, [{active, 1}]),
|
|
%% error_logger:info_msg("~s opened on udp ~p~n", [Proto, Port]),
|
|
{ok, #state{proto = Proto, sock = Sock, port = Port, peers = #{}, mfa = MFA}};
|
|
{error, Reason} ->
|
|
{stop, Reason}
|
|
end.
|
|
|
|
handleMsg(count_peers, _From, State = #state{peers = Peers}) ->
|
|
{reply, maps:size(Peers) div 2, State, hibernate};
|
|
|
|
handleMsg(Req, _From, State) ->
|
|
?ERROR_MSG("unexpected call: ~p", [Req]),
|
|
{reply, ignored, State}.
|
|
|
|
|
|
handleMsg({udp, Sock, IP, InPortNo, Packet},
|
|
State = #state{sock = Sock, peers = Peers, mfa = {M, F, Args}}) ->
|
|
Peer = {IP, InPortNo},
|
|
case maps:find(Peer, Peers) of
|
|
{ok, Pid} ->
|
|
Pid ! {datagram, self(), Packet},
|
|
{noreply, State};
|
|
error ->
|
|
try erlang:apply(M, F, [{udp, self(), Sock}, Peer | Args]) of
|
|
{ok, Pid} ->
|
|
_Ref = erlang:monitor(process, Pid),
|
|
Pid ! {datagram, self(), Packet},
|
|
{noreply, store_peer(Peer, Pid, State)};
|
|
{error, Reason} ->
|
|
?ERROR_MSG("Error returned. udp channel: ~s, reason: ~p",
|
|
[esockd_net:format(Peer), Reason]),
|
|
{noreply, State}
|
|
catch
|
|
_Error:Reason ->
|
|
?ERROR_MSG("Failed to start udp channel: ~s, reason: ~p",
|
|
[esockd_net:format(Peer), Reason]),
|
|
{noreply, State}
|
|
end
|
|
end;
|
|
|
|
handleMsg({udp_passive, Sock}, State) ->
|
|
%% TODO: rate limit here?
|
|
inet:setopts(Sock, [{active, 100}]),
|
|
{noreply, State, hibernate};
|
|
|
|
handleMsg({'DOWN', _MRef, process, DownPid, _Reason}, State = #state{peers = Peers}) ->
|
|
case maps:find(DownPid, Peers) of
|
|
{ok, Peer} ->
|
|
{noreply, erase_peer(Peer, DownPid, State)};
|
|
error -> {noreply, State}
|
|
end;
|
|
|
|
handleMsg({datagram, Peer = {IP, Port}, Packet}, State = #state{sock = Sock}) ->
|
|
case gen_udp:send(Sock, IP, Port, Packet) of
|
|
ok -> ok;
|
|
{error, Reason} ->
|
|
?ERROR_MSG("Dropped packet to: ~s, reason: ~s", [esockd_net:format(Peer), Reason])
|
|
end,
|
|
{noreply, State};
|
|
|
|
handleMsg(Info, State) ->
|
|
?ERROR_MSG("unexpected info: ~p", [Info]),
|
|
{noreply, State}.
|
|
|
|
terminate(_Reason, #state{sock = Sock}) ->
|
|
gen_udp:close(Sock).
|
|
|
|
|
|
store_peer(Peer, Pid, State = #state{peers = Peers}) ->
|
|
State#state{peers = maps:put(Pid, Peer, maps:put(Peer, Pid, Peers))}.
|
|
|
|
erase_peer(Peer, Pid, State = #state{peers = Peers}) ->
|
|
State#state{peers = maps:remove(Peer, maps:remove(Pid, Peers))}.
|
|
|