pespin has submitted this change. ( https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/34804?usp=email )
Change subject: Handle GSUP EPDG_Tunnel through GTPv2C CreateSession Req+Resp ...................................................................... Handle GSUP EPDG_Tunnel through GTPv2C CreateSession Req+Resp Initial GTPv2C infrastructure to send GTPv2C CreateSession Request upon receival of GSUP EPDG_Tunnel Request, and answer with EPDG_Tunnel Resp/err when creating the session fails. Related: OS#6046 Change-Id: I6f00b7fce2d5fcdc484bfd45629b9141f16bc579 --- M config/sys.config M rebar.config M rebar.lock A src/epdg_gtpc_s2b.erl M src/gsup_server.erl M src/osmo_epdg.app.src M src/osmo_epdg_sup.erl 7 files changed, 320 insertions(+), 7 deletions(-) Approvals: lynxis lazus: Looks good to me, approved Jenkins Builder: Verified diff --git a/config/sys.config b/config/sys.config index 8d751b9..1b89e86 100755 --- a/config/sys.config +++ b/config/sys.config @@ -13,7 +13,13 @@ {vendor_id, 0}, {origin_host, "epdg.localdomain"}, {origin_realm, "localdomain"}, - {context_id, "epdg@localdomain"}]}, + {context_id, "epdg@localdomain"}, + % GTPv2C Connection parameters + {gtpc_local_ip, "127.0.0.2"}, + {gtpc_local_port, 2123}, + {gtpc_remote_ip, "127.0.0.1"}, + {gtpc_remote_port, 2123} + ]}, %% =========================================== %% SASL config %% =========================================== diff --git a/rebar.config b/rebar.config index 03e0e7c..d834c32 100644 --- a/rebar.config +++ b/rebar.config @@ -4,6 +4,7 @@ {deps, [ {lager, {git, "https://github.com/erlang-lager/lager", {tag, "3.9.2"}}}, + {gtplib, "3.2.0"}, {osmo_ss7, {git, "https://gitea.osmocom.org/erlang/osmo_ss7", {ref, "9f294d3612f998860004820d1d85b4264721577b"}}}, {osmo_gsup, {git, "https://gitea.osmocom.org/erlang/osmo_gsup", {ref, "07672d8ab1608aa9c9e50ca035521876558fcd42"}}} ]}. diff --git a/rebar.lock b/rebar.lock index 4aeafea..8f00ea6 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,16 +1,18 @@ {"1.2.0", -[{<<"epcap">>, +[{<<"cut">>,{pkg,<<"cut">>,<<"1.0.3">>},1}, + {<<"epcap">>, {git,"https://github.com/msantos/epcap", {ref,"d5c03caf608c1369e68cfed0a606d3eb82ddfd21"}}, 1}, {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, + {<<"gtplib">>,{pkg,<<"gtplib">>,<<"3.2.0">>},0}, {<<"lager">>, {git,"https://github.com/erlang-lager/lager", {ref,"459a3b2cdd9eadd29e5a7ce5c43932f5ccd6eb88"}}, 0}, {<<"osmo_gsup">>, {git,"https://gitea.osmocom.org/erlang/osmo_gsup", - {ref,"07672d8ab1608aa9c9e50ca035521876558fcd42"}}, + {ref,"e23f118e6e8f7ee1247db34e4cb79e4cecdb0947"}}, 0}, {<<"osmo_ss7">>, {git,"https://gitea.osmocom.org/erlang/osmo_ss7", @@ -23,10 +25,17 @@ {<<"pkt">>, {git,"https://github.com/msantos/pkt", {ref,"67a4a14f596fded5ad5f2d8f94318faa8ad2c288"}}, - 1}]}. + 1}, + {<<"ppplib">>,{pkg,<<"ppplib">>,<<"1.0.0">>},1}]}. [ {pkg_hash,[ - {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}]}, + {<<"cut">>, <<"1577F2F3BC0F2BF3B97903B7426F8A3D79523687B6A444D0F59A095EF69A0E81">>}, + {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, + {<<"gtplib">>, <<"41E8E14BE21DD6E08B2CBB9D708BCF8FDD47CA49D7FFA480219CAB29F5AE2760">>}, + {<<"ppplib">>, <<"F9EC2690532BAF590277A305A2276FCFAD0285557E1055552F8A2FCAF1BF081A">>}]}, {pkg_hash_ext,[ - {<<"goldrush">>, <<"99CB4128CFFCB3227581E5D4D803D5413FA643F4EB96523F77D9E6937D994CEB">>}]} + {<<"cut">>, <<"1A4A25DB2B7C5565FD28B314A4EEB898B1ED3CAFFA1AB09149345FB5731ED04B">>}, + {<<"goldrush">>, <<"99CB4128CFFCB3227581E5D4D803D5413FA643F4EB96523F77D9E6937D994CEB">>}, + {<<"gtplib">>, <<"264626E6993C17B00CA3C083B9BF23D16B88BEEDDDE35F62C954832C21B57AEF">>}, + {<<"ppplib">>, <<"32440D630F55DD29F849847DD8F15F69175FDDC210AA88517AC8AD2854CD6FA1">>}]} ]. diff --git a/src/epdg_gtpc_s2b.erl b/src/epdg_gtpc_s2b.erl new file mode 100644 index 0000000..82985a5 --- /dev/null +++ b/src/epdg_gtpc_s2b.erl @@ -0,0 +1,252 @@ +% S2b: GTPv2C towards PGW +% +% 3GPP TS 29.274 +% +% (C) 2023 by sysmocom - s.f.m.c. GmbH <i...@sysmocom.de> +% Author: Pau Espin Pedrol <pes...@sysmocom.de> +% +% All Rights Reserved +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU Affero General Public License as +% published by the Free Software Foundation; either version 3 of the +% License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU Affero General Public License +% along with this program. If not, see <http://www.gnu.org/licenses/>. +% +% Additional Permission under GNU AGPL version 3 section 7: +% +% If you modify this Program, or any covered work, by linking or +% combining it with runtime libraries of Erlang/OTP as released by +% Ericsson on http://www.erlang.org (or a modified version of these +% libraries), containing parts covered by the terms of the Erlang Public +% License (http://www.erlang.org/EPLICENSE), the licensors of this +% Program grant you additional permission to convey the resulting work +% without the need to license the runtime libraries of Erlang/OTP under +% the GNU Affero General Public License. Corresponding Source for a +% non-source form of such a combination shall include the source code +% for the parts of the runtime libraries of Erlang/OTP used as well as +% that of the covered work. + + +-module(epdg_gtpc_s2b). +-author('Pau Espin Pedrol <pes...@sysmocom.de>'). + +-behaviour(gen_server). + +-include_lib("gtplib/include/gtp_packet.hrl"). + +%% API Function Exports +-export([start_link/5]). +-export([terminate/2]). +%% gen_server Function Exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). +-export([code_change/3]). +-export([create_session_req/1]). + +%% Application Definitions +-define(SERVER, ?MODULE). +-define(SVC_NAME, ?MODULE). +-define(APP_ALIAS, ?MODULE). +-define(CALLBACK_MOD, epdg_gtpc_s2b_cb). +-define(ENV_APP_NAME, osmo_epdg). + +%% TODO: make APN configurable? get it from HSS? +-define(APN, <<"internet">>). + +-record(gtp_state, { + socket, + laddr_str, + laddr :: inet:ip_address(), + lport :: non_neg_integer(), + raddr_str, + raddr :: inet:ip_address(), + rport :: non_neg_integer(), + restart_counter :: 0..255, + seq_no :: 0..16#ffffffff, + sess_list %% TODO: fill it, list of gtp_session +}). + +-record(gtp_bearer, { + ebi :: non_neg_integer(), + local_data_tei = 0 :: non_neg_integer(), + remote_data_tei = 0 :: non_neg_integer() +}). + +-record(gtp_session, { + imsi :: binary(), + apn :: binary(), + ue_ip :: inet:ip_address(), + local_control_tei = 0 :: non_neg_integer(), + remote_control_tei = 0 :: non_neg_integer(), + bearer :: gtp_bearer %% FIXME: only one bearer for now +}). + +start_link(LocalAddr, LocalPort, RemoteAddr, RemotePort, Options) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [LocalAddr, LocalPort, RemoteAddr, RemotePort, Options], []). + +peer_down(API, SvcName, {PeerRef, _} = Peer) -> + % fixme: why do we still have ets here? + (catch ets:delete(?MODULE, {API, PeerRef})), + gen_server:cast(?SERVER, {peer_down, SvcName, Peer}), + ok. + +init(State) -> + lager:info("epdg_gtpc_s2b: init(): ~p", [State]), + [LocalAddr | [LocalPort | [RemoteAddr | [RemotePort | _]]]] = State, + lager:info("epdg_gtpc_s2b: Binding to IP ~s port ~p~n", [LocalAddr, LocalPort]), + {ok, LocalAddrInet} = inet_parse:address(LocalAddr), + {ok, RemoteAddrInet} = inet_parse:address(RemoteAddr), + Opts = [ + binary, + {ip, LocalAddrInet}, + {active, true}, + {reuseaddr, true} + ], + Ret = gen_udp:open(LocalPort, Opts), + case Ret of + {ok, Socket} -> + lager:info("epdg_gtpc_s2b: Socket is ~p~n", [Socket]), + ok = connect({Socket, RemoteAddr, RemotePort}), + St = #gtp_state{ + socket = Socket, + laddr_str = LocalAddr, + laddr = LocalAddrInet, + lport = LocalPort, + raddr_str = RemoteAddr, + raddr = RemoteAddrInet, + rport = RemotePort, + restart_counter = 0, + seq_no = 0 + }, + {ok, St}; + {error, Reason} -> + lager:error("GTPv2C UDP socket open error: ~w~n", [Reason]) + end. + +create_session_req(Imsi) -> + gen_server:call(?SERVER, + {gtpc_create_session_req, {Imsi}}). + +handle_call({gtpc_create_session_req, {Imsi}}, _From, State) -> + Sess = new_gtp_session(Imsi, State), + Req = gen_create_session_request(Sess, State), + %TODO: increment State.seq_no. + tx_gtp(Req, State), + lager:debug("Waiting for CreateSessionResponse~n", []), + receive + {udp, _Socket, IP, InPortNo, RxMsg} -> + try + Resp = gtp_packet:decode(RxMsg), + logger:info("s2b: Rx from IP ~p port ~n ~p~n", [IP, InPortNo, Resp]), + %% TODO: store Sess in State. + {reply, {ok, Resp}, State} + catch Any -> + logger:error("Error sending message to receiver, ERROR: ~p~n", [Any]), + {reply, {error, decode_failure}, State} + end + after 5000 -> + logger:error("Timeout waiting for CreateSessionResponse for ~p~n", [Req]), + {reply, timeout, State} + end. + +%% @callback gen_server +handle_cast(stop, State) -> + {stop, normal, State}; +handle_cast(_Req, State) -> + {noreply, State}. + +%% @callback gen_server +handle_info(_Info, State) -> + {noreply, State}. + +%% @callback gen_server +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%% @callback gen_server +terminate(normal, State) -> + udp_gen:close(State#gtp_state.socket), + ok; +terminate(shutdown, _State) -> + ok; +terminate({shutdown, _Reason}, _State) -> + ok; +terminate(_Reason, _State) -> + ok. + +%% ------------------------------------------------------------------ +%% Internal Function Definitions +%% ------------------------------------------------------------------ + +%% connect/2 +connect(Name, {Socket, RemoteAddr, RemotePort}) -> + lager:info("~s connecting to IP ~s port ~p~n", [Name, RemoteAddr, RemotePort]), + gen_udp:connect(Socket, RemoteAddr, RemotePort). + +connect(Address) -> + connect(?SVC_NAME, Address). + +tx_gtp(Req, State) -> + lager:info("s2b: Tx ~p~n", [Req]), + Msg = gtp_packet:encode(Req), + gen_udp:send(State#gtp_state.socket, State#gtp_state.raddr, State#gtp_state.rport, Msg). + +new_gtp_session(Imsi, _State) -> + % TODO: find non-used local TEI inside State + Bearer = #gtp_bearer{ + ebi = 5, + local_data_tei = 1 + }, + #gtp_session{imsi = Imsi, + apn = ?APN, + local_control_tei = 0, + bearer = Bearer + }. + +%% 7.2.1 Create Session Request +gen_create_session_request(#gtp_session{imsi = Imsi, + apn = Apn, + local_control_tei = LocalCtlTEI, + bearer = Bearer}, + #gtp_state{laddr = LocalAddr, + restart_counter = RCnt, + seq_no = SeqNo}) -> + BearersIE = [#v2_bearer_level_quality_of_service{ + pci = 1, pl = 10, pvi = 0, label = 8, + maximum_bit_rate_for_uplink = 0, + maximum_bit_rate_for_downlink = 0, + guaranteed_bit_rate_for_uplink = 0, + guaranteed_bit_rate_for_downlink = 0 + }, + #v2_eps_bearer_id{eps_bearer_id = Bearer#gtp_bearer.ebi}, + #v2_fully_qualified_tunnel_endpoint_identifier{ + instance = 0, + interface_type = 31, %% "S2b-U ePDG GTP-U" + key = Bearer#gtp_bearer.local_data_tei, + ipv4 = LocalAddr + } + ], + IEs = [#v2_recovery{restart_counter = RCnt}, + #v2_international_mobile_subscriber_identity{imsi = Imsi}, + #v2_rat_type{rat_type = 3}, %% 3 = WLAN + #v2_fully_qualified_tunnel_endpoint_identifier{ + instance = Bearer#gtp_bearer.ebi, + interface_type = 30, %% "S2b ePDG GTP-C" + key = LocalCtlTEI, + ipv4 = LocalAddr + }, + #v2_access_point_name{instance = 0, apn = [Apn]}, + #v2_selection_mode{mode = 0}, + #v2_pdn_address_allocation{type = ipv4, address = <<0,0,0,0>>}, + #v2_bearer_context{group = BearersIE} + ], + #gtp{version = v2, type = create_session_request, tei = 0, seq_no = SeqNo, ie = IEs}. + + diff --git a/src/gsup_server.erl b/src/gsup_server.erl index 923d020..14d2364 100644 --- a/src/gsup_server.erl +++ b/src/gsup_server.erl @@ -177,6 +177,23 @@ % epdg tunnel request / trigger the establishment to the PGW and prepares everything for the user traffic to flow % When sending a epdg_tunnel_response everything must be ready for the UE traffic handle_info({ipa, Socket, ?IPAC_PROTO_EXT_GSUP, GsupMsgRx = #{message_type := epdg_tunnel_request, imsi := Imsi}}, S) -> + lager:info("GSUP: Rx ~p~n", [GsupMsgRx]), + Result = epdg_gtpc_s2b:create_session_req(Imsi), + case Result of + {ok, _} -> + Resp = #{message_type => epdg_tunnel_result, + imsi => Imsi, + message_class => 5 + }; + {error, _} -> + Resp = #{message_type => epdg_tunnel_error, + imsi => Imsi, + message_class => 5, + cause => 16#11 % FIXME: Use proper defines as cause code and use Network failure + } + end, + lager:info("GSUP: Tx ~p~n", [Resp]), + ipa_proto:send(Socket, ?IPAC_PROTO_EXT_GSUP, Resp), {noreply, S}; handle_info(Info, S) -> diff --git a/src/osmo_epdg.app.src b/src/osmo_epdg.app.src index 483d339..ec60bd2 100644 --- a/src/osmo_epdg.app.src +++ b/src/osmo_epdg.app.src @@ -8,6 +8,7 @@ kernel, stdlib, lager, + gtplib, diameter, osmo_gsup, osmo_ss7 diff --git a/src/osmo_epdg_sup.erl b/src/osmo_epdg_sup.erl index 457d993..c679b39 100644 --- a/src/osmo_epdg_sup.erl +++ b/src/osmo_epdg_sup.erl @@ -8,6 +8,10 @@ -define(ENV_APP_NAME, osmo_epdg). -define(ENV_DEFAULT_GSUP_LOCAL_IP, "0.0.0.0"). -define(ENV_DEFAULT_GSUP_LOCAL_PORT, 4222). +-define(ENV_DEFAULT_GTPC_LOCAL_IP, "127.0.0.2"). +-define(ENV_DEFAULT_GTPC_LOCAL_PORT, 2123). +-define(ENV_DEFAULT_GTPC_REMOTE_IP, "127.0.0.1"). +-define(ENV_DEFAULT_GTPC_REMOTE_PORT, 2123). start_link() -> supervisor:start_link({local, ?SERVER}, ?MODULE, []). @@ -15,11 +19,20 @@ init([]) -> GsupLocalIp = application:get_env(?ENV_APP_NAME, gsup_local_ip, ?ENV_DEFAULT_GSUP_LOCAL_IP), GsupLocalPort = application:get_env(?ENV_APP_NAME, gsup_local_port, ?ENV_DEFAULT_GSUP_LOCAL_PORT), + GtpcLocalIp = application:get_env(?ENV_APP_NAME, gtpc_local_ip, ?ENV_DEFAULT_GTPC_LOCAL_IP), + GtpcLocalPort = application:get_env(?ENV_APP_NAME, gtpc_local_port, ?ENV_DEFAULT_GTPC_LOCAL_PORT), + GtpcRemoteIp = application:get_env(?ENV_APP_NAME, gtpc_remote_ip, ?ENV_DEFAULT_GTPC_REMOTE_IP), + GtpcRemotePort = application:get_env(?ENV_APP_NAME, gtpc_remote_port, ?ENV_DEFAULT_GTPC_REMOTE_PORT), DiaServer = {epdg_diameter_swx, {epdg_diameter_swx,start_link,[]}, permanent, 5000, worker, [epdg_diameter_swx_cb]}, + GtpcServer = {epdg_gtpc_s2b, {epdg_gtpc_s2b,start_link, [GtpcLocalIp, GtpcLocalPort, GtpcRemoteIp, GtpcRemotePort, []]}, + permanent, + 5000, + worker, + [epdg_gtpc_s2b]}, GsupServer = {gsup_server, {gsup_server, start_link, [GsupLocalIp, GsupLocalPort, []]}, permanent, 5000, @@ -30,4 +43,4 @@ 5000, worker, [auth_handler]}, - {ok, { {one_for_all, 5, 10}, [DiaServer, GsupServer, AuthHandler]} }. + {ok, { {one_for_all, 5, 10}, [DiaServer, GtpcServer, GsupServer, AuthHandler]} }. -- To view, visit https://gerrit.osmocom.org/c/erlang/osmo-epdg/+/34804?usp=email To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: erlang/osmo-epdg Gerrit-Branch: master Gerrit-Change-Id: I6f00b7fce2d5fcdc484bfd45629b9141f16bc579 Gerrit-Change-Number: 34804 Gerrit-PatchSet: 4 Gerrit-Owner: pespin <pes...@sysmocom.de> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: fixeria <vyanits...@sysmocom.de> Gerrit-Reviewer: laforge <lafo...@osmocom.org> Gerrit-Reviewer: lynxis lazus <lyn...@fe80.eu> Gerrit-Reviewer: pespin <pes...@sysmocom.de> Gerrit-MessageType: merged