diff --git a/README b/README index e80863c..ac00e8b 100644 --- a/README +++ b/README @@ -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: @@ -12,4 +12,4 @@ erl -man doc/man/gen_esme.3 Or any other module. -There are also two examples under doc/examples \ No newline at end of file +There are also two examples under doc/examples diff --git a/ebin/oserl.app b/ebin/oserl.app index 0223728..6cd6db0 100644 --- a/ebin/oserl.app +++ b/ebin/oserl.app @@ -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, diff --git a/ebin/oserl.app.src b/ebin/oserl.app.src new file mode 100644 index 0000000..ea1e5d8 --- /dev/null +++ b/ebin/oserl.app.src @@ -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, []} +]}. diff --git a/rebar.config b/rebar.config index 4d5096e..a6a24f0 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,6 @@ {lib_dirs, ["deps"]}. {erl_opts, [ - warnings_as_errors, debug_info, {parse_transform, lager_transform} ]}. diff --git a/src/gen_mc.erl b/src/gen_mc.erl index a0e130b..d0c8b61 100644 --- a/src/gen_mc.erl +++ b/src/gen_mc.erl @@ -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 @@ -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} @@ -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}, @@ -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]). diff --git a/src/gen_mc_session.erl b/src/gen_mc_session.erl index 65255d2..aa359ae 100644 --- a/src/gen_mc_session.erl +++ b/src/gen_mc_session.erl @@ -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 @@ -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. @@ -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) -> diff --git a/src/proxy_protocol.erl b/src/proxy_protocol.erl new file mode 100644 index 0000000..9b32129 --- /dev/null +++ b/src/proxy_protocol.erl @@ -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 + <> -> + 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(<>) -> + {local, family(X), protocol(Y), Len}; +parse_proxy_protocol_v2(<>) -> + {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(<>, 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, <>) -> + 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}. diff --git a/src/proxy_protocol.hrl b/src/proxy_protocol.hrl new file mode 100644 index 0000000..cd282c5 --- /dev/null +++ b/src/proxy_protocol.hrl @@ -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). diff --git a/src/smpp_session.erl b/src/smpp_session.erl index 1e2c07c..1a62e7d 100644 --- a/src/smpp_session.erl +++ b/src/smpp_session.erl @@ -35,7 +35,7 @@ -export([congestion/3, connect/1, listen/1, tcp_send/2, send_pdu/3]). %%% SOCKET LISTENER FUNCTIONS EXPORTS --export([wait_accept/3, wait_recv/3, recv_loop/4]). +-export([wait_accept/4, wait_accept/3, wait_recv/3, recv_loop/4]). %% TIMER EXPORTS -export([cancel_timer/1, start_timer/2]). @@ -70,7 +70,7 @@ %% instant congestion state value is calculated. Notice this value cannot be %% greater than 99. congestion(CongestionSt, WaitTime, Timestamp) -> - case (timer:now_diff(erlang:timestamp(), Timestamp) div (WaitTime + 1)) * 85 of + case (timer:now_diff(time:timestamp(), Timestamp) div (WaitTime + 1)) * 85 of Val when Val < 1 -> 0; Val when Val > 99 -> % Out of bounds @@ -82,6 +82,7 @@ congestion(CongestionSt, WaitTime, Timestamp) -> connect(Opts) -> Ip = proplists:get_value(ip, Opts), + lager:debug("smpp_session connect opts: ~p", [Opts]), case proplists:get_value(sock, Opts, undefined) of undefined -> Addr = proplists:get_value(addr, Opts), @@ -96,6 +97,7 @@ connect(Opts) -> listen(Opts) -> + lager:debug("smpp_session listen opts: ~p", [Opts]), case proplists:get_value(lsock, Opts, undefined) of undefined -> Addr = proplists:get_value(addr, Opts, default_addr()), @@ -142,14 +144,17 @@ send_pdu(Sock, Pdu, Log) -> %%% SOCKET LISTENER FUNCTIONS %%%----------------------------------------------------------------------------- wait_accept(Pid, LSock, Log) -> + wait_accept(Pid, LSock, Log, false). + +wait_accept(Pid, LSock, Log, ProxyProtocol) -> case gen_tcp:accept(LSock) of {ok, Sock} -> - case handle_accept(Pid, Sock) of + case handle_accept(Pid, Sock, ProxyProtocol) of true -> ?MODULE:recv_loop(Pid, Sock, <<>>, Log); false -> gen_tcp:close(Sock), - ?MODULE:wait_accept(Pid, LSock, Log) + ?MODULE:wait_accept(Pid, LSock, Log, ProxyProtocol) end; {error, Reason} -> gen_fsm:send_all_state_event(Pid, {listen_error, Reason}) @@ -161,12 +166,11 @@ wait_recv(Pid, Sock, Log) -> recv_loop(Pid, Sock, Buffer, Log) -> - Timestamp = erlang:monotonic_time(), + Timestamp = time:timestamp(), inet:setopts(Sock, [{active, once}]), receive {tcp, Sock, Input} -> - Diff = erlang:monotonic_time() - Timestamp, - L = erlang:convert_time_unit(Diff, native, micro_seconds), + L = timer:now_diff(time:timestamp(), Timestamp), B = handle_input(Pid, list_to_binary([Buffer, Input]), L, 1, Log), ?MODULE:recv_loop(Pid, Sock, B, Log); {tcp_closed, Sock} -> @@ -213,19 +217,27 @@ default_addr() -> {ok, Addr} = inet:getaddr(Host, inet), Addr. - -handle_accept(Pid, Sock) -> +handle_accept(Pid, Sock, ProxyIpList) -> case inet:peername(Sock) of {ok, {Addr, _Port}} -> - lager:debug("sync_send_event accept to pid:~p Addr:~p", [Pid, Addr]), - gen_fsm:sync_send_event(Pid, {accept, Sock, Addr}); + lager:debug("sync_send_event accept to pid:~p Addr:~p, proxy_ip_list: ~p", [Pid, Addr, ProxyIpList]), + case lists:member(Addr, ProxyIpList) of + true -> case proxy_protocol:accept(Sock) of + {ok, {proxy_opts, _IpVersion, SourceAddress, DestAddress, SourcePort, DestPort, ConnectionInfo}} -> + gen_fsm:sync_send_event(Pid, + {accept, Sock, {Addr, [ + SourceAddress, DestAddress, SourcePort, DestPort, ConnectionInfo + ]}}); + {error, _Reason} -> false + end; + false -> gen_fsm:sync_send_event(Pid, {accept, Sock, Addr}) + end; {error, _Reason} -> % Most probably the socket is closed false end. - handle_input(Pid, <> = Buffer, Lapse, N, Log) -> - Now = erlang:timestamp(), % PDU received. PDU handling starts now! + Now = time:timestamp(), % PDU received. PDU handling starts now! Len = CmdLen - 4, case Rest of <> -> diff --git a/src/time.erl b/src/time.erl new file mode 100644 index 0000000..352d476 --- /dev/null +++ b/src/time.erl @@ -0,0 +1,11 @@ +-module(time). + +-export([timestamp/0]). + +timestamp() -> + OtpRelease = list_to_integer(erlang:system_info(otp_release)), + case OtpRelease of + 17 -> erlang:now(); + N when N >= 18 -> erlang:timestamp(); + _ -> {error, "OTP verstion is not supported"} + end.