Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proxy protocol v2 implementation #18

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
OSERL 3.2.3 needs common_lib 3.3.0 or higher.
OSERL 3.3.0 needs common_lib 3.3.0 or higher.

Documentation available in man format:

Expand All @@ -12,4 +12,4 @@ erl -man doc/man/gen_esme.3

Or any other module.

There are also two examples under doc/examples
There are also two examples under doc/examples
4 changes: 3 additions & 1 deletion ebin/oserl.app
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
{application, oserl, [
{description, "Open SMPP Erlang Library"},
{vsn, "3.2.4"},
{vsn, "3.3.0"},
{modules, [
gen_esme_session,
gen_esme,
gen_mc_session,
gen_mc,
proxy_protocol,
time,
smpp_base,
smpp_base_syntax,
smpp_disk_log_hlr,
Expand Down
28 changes: 28 additions & 0 deletions ebin/oserl.app.src
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{application, oserl, [
{description, "Open SMPP Erlang Library"},
{vsn, "3.3.0"},
{modules, [
gen_esme_session,
gen_esme,
gen_mc_session,
gen_mc,
proxy_protocol,
time,
smpp_base,
smpp_base_syntax,
smpp_disk_log_hlr,
smpp_error,
smpp_log_mgr,
smpp_operation,
smpp_param_syntax,
smpp_pdu_syntax,
smpp_ref_num,
smpp_req_tab,
smpp_session,
smpp_sm,
smpp_tty_log_hlr
]},
{registered, []},
{applications, [kernel, stdlib, common_lib]},
{env, []}
]}.
1 change: 0 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{lib_dirs, ["deps"]}.

{erl_opts, [
warnings_as_errors,
debug_info,
{parse_transform, lager_transform}
]}.
Expand Down
12 changes: 8 additions & 4 deletions src/gen_mc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
%%% RECORDS
-record(session, {pid, ref, consumer, rps}).

-record(st, {mod, mod_st, sessions = [], listener, log, timers, lsock}).
-record(st, {mod, mod_st, sessions = [], listener, log, timers, lsock, proxy_ip_list}).

%%%-----------------------------------------------------------------------------
%%% BEHAVIOUR EXPORTS
Expand Down Expand Up @@ -279,14 +279,16 @@ init({Mod, Args, Opts}) ->
{ok, LSock} ->
{ok, Log} = smpp_log_mgr:start_link(),
Timers = proplists:get_value(timers, Opts, ?DEFAULT_TIMERS_SMPP),
SessionOpts = [{log, Log}, {lsock, LSock}, {timers, Timers}],
ProxyIpList = proplists:get_value(proxy_ip_list, Opts, []),
SessionOpts = [{log, Log}, {lsock, LSock}, {timers, Timers}, {proxy_ip_list, ProxyIpList}],
% Start a listening session
{ok, Pid} = gen_mc_session:start_link(?MODULE, SessionOpts),
St = #st{mod = Mod,
listener = Pid,
log = Log,
timers = Timers,
lsock = LSock},
lsock = LSock,
proxy_ip_list = ProxyIpList},
pack((St#st.mod):init(Args), St);
{error, Reason} ->
{stop, Reason}
Expand Down Expand Up @@ -350,7 +352,7 @@ handle_call({rps_max, Pid}, _From, St) ->
handle_call({{handle_unbind, Pdu}, Pid}, From, St) ->
pack((St#st.mod):handle_unbind(Pid, Pdu, From, St#st.mod_st), St);
handle_call({{handle_accept, Addr}, Pid}, From, #st{listener = Pid} = St) ->
Opts = [{lsock, St#st.lsock}, {log, St#st.log}, {timers, St#st.timers}],
Opts = [{lsock, St#st.lsock}, {log, St#st.log}, {timers, St#st.timers}, {proxy_ip_list, St#st.proxy_ip_list}],
lager:debug("handle_call handle_accept, creating new session with opts ~p", [Opts]),
{ok, Listener} = gen_mc_session:start_link(?MODULE, Opts),
NewSt = St#st{listener = Listener},
Expand Down Expand Up @@ -564,6 +566,8 @@ split_options([{port, _} = H | T], Mc, Srv) ->
split_options(T, [H | Mc], Srv);
split_options([{timers, _} = H | T], Mc, Srv) ->
split_options(T, [H | Mc], Srv);
split_options([{proxy_ip_list, _} = H | T], Mc, Srv) ->
split_options(T, [H | Mc], Srv);
split_options([H | T], Mc, Srv) ->
split_options(T, Mc, [H | Srv]).

Expand Down
13 changes: 8 additions & 5 deletions src/gen_mc_session.erl
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@
session_init_timer,
enquire_link_timer,
inactivity_timer,
enquire_link_resp_timer}).
enquire_link_resp_timer,
proxy_ip_list = []}).

%%%-----------------------------------------------------------------------------
%%% BEHAVIOUR EXPORTS
Expand Down Expand Up @@ -161,12 +162,13 @@ outbind(FsmRef, Params) ->
init([Mod, Mc, Opts]) ->
_Ref = erlang:monitor(process, Mc),
Timers = proplists:get_value(timers, Opts, ?DEFAULT_TIMERS_SMPP),
ProxyIpList= proplists:get_value(proxy_ip_list, Opts, false),
Log = proplists:get_value(log, Opts),
case proplists:get_value(lsock, Opts) of
undefined ->
init_open(Mod, Mc, proplists:get_value(sock, Opts), Timers, Log);
LSock ->
init_listen(Mod, Mc, LSock, Timers, Log)
init_listen(Mod, Mc, LSock, Timers, Log, ProxyIpList)
end.


Expand All @@ -187,16 +189,17 @@ init_open(Mod, Mc, Sock, Tmr, Log) ->
start_timer(Tmr, enquire_link_timer)}}.


init_listen(Mod, Mc, LSock, Tmr, Log) ->
init_listen(Mod, Mc, LSock, Tmr, Log, ProxyIpList) ->
Self = self(),
Pid = spawn_link(smpp_session, wait_accept, [Self, LSock, Log]),
Pid = spawn_link(smpp_session, wait_accept, [Self, LSock, Log, ProxyIpList]),
{ok, listen, #st{mc = Mc,
mod = Mod,
log = Log,
sock_ctrl = Pid,
req_tab = smpp_req_tab:new(),
op_tab = smpp_req_tab:new(),
timers = Tmr}}.
timers = Tmr,
proxy_ip_list = ProxyIpList}}.


terminate(_Reason, _Stn, Std) ->
Expand Down
141 changes: 141 additions & 0 deletions src/proxy_protocol.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
-module(proxy_protocol).

-include("proxy_protocol.hrl").

-export([accept/1]).

-record(proxy_opts, { inet_version :: ipv4|ipv6,
source_address :: inet:ip_address(),
dest_address :: inet:ip_address(),
source_port :: inet:port_number(),
dest_port :: inet:port_number(),
connection_info = []}).
-opaque proxy_opts() :: #proxy_opts{}.

-define(WAITING_TIMEOUT, 1000).

-export_type([proxy_opts/0]).
%%%-----------------------------------------------------------------------------
%%% EXPORTS
%%%-----------------------------------------------------------------------------
accept(Sock) ->
inet:setopts(Sock, [{active, once}, {packet, line}]),
receive
{_, CSocket, <<"\r\n">>} ->
ok = inet:setopts(Sock, [{packet, raw}]),
{ok, ProxyHeader} = gen_tcp:recv(CSocket, 14, 1000),
case parse_proxy_protocol_v2(<<"\r\n", ProxyHeader/binary>>) of
{proxy, ipv4, _Protocol, Length} ->
{ok, ProxyAddr} = gen_tcp:recv(CSocket, Length, 1000),
case ProxyAddr of
<<SA1:8, SA2:8, SA3:8, SA4:8,
DA1:8, DA2:8, DA3:8, DA4:8,
SourcePort:16, DestPort:16, Rest/binary>> ->
SourceAddress = {SA1, SA2, SA3, SA4},
DestAddress = {DA1, DA2, DA3, DA4},
ConnectionInfo = parse_tlv(Rest),
{ok, #proxy_opts{inet_version = ipv4,
source_address = SourceAddress,
dest_address = DestAddress,
source_port = SourcePort,
dest_port = DestPort,
connection_info = ConnectionInfo}};
_ ->
gen_tcp:close(Sock),
lager:error("Not proxy protocol"),
{error, not_proxy_protocol}
end;
_Unsupported ->
gen_tcp:close(Sock),
{error, not_supported_v2}
end;
{_, _CSocket, Data} ->
lager:notice("data: ~p", [Data])
after
?WAITING_TIMEOUT ->
gen_tcp:close(Sock),
lager:error("Proxy protocol header expected but not received"),
{error, timeout}
end.

%%%-----------------------------------------------------------------------------
%%% INTERNAL FUNCTIONS
%%%-----------------------------------------------------------------------------
parse_proxy_protocol_v2(<<?HEADER, (?VSN):4, 0:4, X:4, Y:4, Len:16>>) ->
{local, family(X), protocol(Y), Len};
parse_proxy_protocol_v2(<<?HEADER, (?VSN):4, 1:4, X:4, Y:4, Len:16>>) ->
{proxy, family(X), protocol(Y), Len};
parse_proxy_protocol_v2(_) ->
not_proxy_protocol.

parse_tlv(Rest) ->
parse_tlv(Rest, []).

parse_tlv(<<>>, Result) ->
Result;
parse_tlv(<<Type:8, Len:16, Value:Len/binary, Rest/binary>>, Result) ->
case pp2_type(Type) of
ssl ->
parse_tlv(Rest, pp2_value(Type, Value) ++ Result);
TypeName ->
parse_tlv(Rest, [{TypeName, Value} | Result])
end;
parse_tlv(_, _) ->
{error, parse_tlv}.

pp2_type(?PP2_TYPE_ALPN) ->
negotiated_protocol;
pp2_type(?PP2_TYPE_AUTHORITY) ->
authority;
pp2_type(?PP2_TYPE_SSL) ->
ssl;
pp2_type(?PP2_SUBTYPE_SSL_VERSION) ->
protocol;
pp2_type(?PP2_SUBTYPE_SSL_CN) ->
sni_hostname;
pp2_type(?PP2_TYPE_NETNS) ->
netns;
pp2_type(_) ->
invalid_pp2_type.

pp2_value(?PP2_TYPE_SSL, <<Client:1/binary, _:32, Rest/binary>>) ->
case pp2_client(Client) of % validates bitfield format, but ignores data
invalid_client ->
invalid;
_ ->
%% Fetches TLV values attached, regardless of if the client
%% specified SSL. If this is a problem, then we should fix,
%% but in any case the blame appears to be on the sender
%% who is giving us broken headers.
parse_tlv(Rest)
end;
pp2_value(_, Value) ->
Value.

pp2_client(<<0:5, % UNASSIGNED
_ClientCert:1, % PP2_CLIENT_CERT_SESS
_ClientCert:1, % PP2_CLIENT_CERT_CONN
_ClientSSL:1>>) ->
client_ssl;
pp2_client(_) ->
invalid_client.

family(?AF_UNSPEC) ->
af_unspec;
family(?AF_INET) ->
ipv4;
family(?AF_INET6) ->
ipv6;
family(?AF_UNIX) ->
af_unix;
family(_) ->
{error, invalid_address_family}.

protocol(?UNSPEC) ->
unspec;
protocol(?STREAM) ->
stream;
protocol(?DGRAM) ->
dgram;
protocol(_) ->
{error, invalid_protocol}.
27 changes: 27 additions & 0 deletions src/proxy_protocol.hrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
%%% proxy2 defines
-define(HEADER, "\r\n\r\n\0\r\nQUIT\n").
-define(VSN, 16#02).

%% Protocol types
-define(AF_UNSPEC, 16#00).
-define(AF_INET, 16#01).
-define(AF_INET6, 16#02).
-define(AF_UNIX, 16#03).

%% Transfer types
-define(UNSPEC, 16#00).
-define(STREAM, 16#01).
-define(DGRAM, 16#02).

%% TLV types for additional headers
-define(PP2_TYPE_ALPN, 16#01).
-define(PP2_TYPE_AUTHORITY, 16#02).
-define(PP2_TYPE_SSL, 16#20).
-define(PP2_SUBTYPE_SSL_VERSION, 16#21).
-define(PP2_SUBTYPE_SSL_CN, 16#22).
-define(PP2_TYPE_NETNS, 16#30).

%% SSL Client fields
-define(PP2_CLIENT_SSL, 16#01).
-define(PP2_CLIENT_CERT_CONN, 16#02).
-define(PP2_CLIENT_CERT_SESS, 16#04).
Loading