Skip to main content

src/vpn_udp.erl

%%%-------------------------------------------------------------------
%% @doc Minimal UDP worker.
%%%-------------------------------------------------------------------
-module(vpn_udp).

-behaviour(gen_server).

-export([start_link/1, start_link/2, stop/1, send/4]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).

start_link(Port) ->
    start_link(Port, undefined).

start_link(Port, OwnerPid) ->
    gen_server:start_link(?MODULE, {Port, OwnerPid}, []).

stop(Pid) ->
    gen_server:stop(Pid).

send(Pid, Host, Port, Packet) when is_binary(Packet) ->
    gen_server:call(Pid, {send, Host, Port, Packet}).

init({Port, OwnerPid}) ->
    case gen_udp:open(Port, [binary, {active, true}]) of
        {ok, Socket} ->
            {ok, #{socket => Socket, port => Port, owner => OwnerPid}};
        {error, Reason} ->
            {stop, Reason}
    end.

handle_call({send, Host, Port, Packet}, _From, State = #{socket := Socket}) ->
    Reply = gen_udp:send(Socket, Host, Port, Packet),
    {reply, Reply, State};
handle_call(_Request, _From, State) ->
    {reply, {error, not_implemented}, State}.

handle_cast(_Request, State) ->
    {noreply, State}.

handle_info({udp, Socket, Ip, Port, Packet}, State = #{socket := Socket}) ->
    logger:info("vpn_udp received packet from ~p:~p: ~p bytes",
                [Ip, Port, byte_size(Packet)]),
    maybe_send_packet(Ip, Port, Packet, State),
    {noreply, State};
handle_info({udp, _OtherSocket, _Ip, _Port, _Packet}, State) ->
    {noreply, State};
handle_info(_Message, State) ->
    {noreply, State}.

terminate(_Reason, #{socket := Socket}) ->
    gen_udp:close(Socket),
    ok;
terminate(_Reason, _State) ->
    ok.

maybe_send_packet(_Ip, _Port, _Packet, #{owner := undefined}) ->
    ok;
maybe_send_packet(Ip, Port, Packet, #{owner := OwnerPid}) ->
    OwnerPid ! {vpn_udp_packet, self(), Ip, Port, Packet},
    ok.