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

Reply via email to