s2b: Implement GTPv2C DeleteBearerReq

Sessions are now stored/kept upon CreateSession time until deleted
through DeleteBearerReq.

Related: OS#6046
Change-Id: I1e5af1ead17385d2e494f4c90ffe6455aee850da
This commit is contained in:
Pau Espin 2023-10-24 14:49:42 +02:00
parent 757cca01d8
commit d6600aae20
1 changed files with 124 additions and 26 deletions

View File

@ -70,7 +70,9 @@
rport :: non_neg_integer(),
restart_counter :: 0..255,
seq_no :: 0..16#ffffffff,
sess_list %% TODO: fill it, list of gtp_session
next_local_control_tei = 1 :: 0..16#ffffffff,
next_local_data_tei = 1 :: 0..16#ffffffff,
sessions = sets:new()
}).
-record(gtp_bearer, {
@ -134,36 +136,45 @@ 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),
handle_call({gtpc_create_session_req, {Imsi}}, _From, State0) ->
{Sess0, State1} = find_or_new_gtp_session(Imsi, State0),
Req = gen_create_session_request(Sess0, State1),
%TODO: increment State.seq_no.
tx_gtp(Req, State),
tx_gtp(Req, State1),
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}
lager:info("s2b: Rx from IP ~p port ~p ~p~n", [IP, InPortNo, Resp]),
Sess1 = update_gtp_session_from_create_session_response(Resp, Sess0),
lager:info("s2b: Updated Session after create_session_response: ~p~n", [Sess1]),
State2 = update_gtp_session(Sess0, Sess1, State1),
{reply, {ok, Resp}, State2}
catch Any ->
logger:error("Error sending message to receiver, ERROR: ~p~n", [Any]),
{reply, {error, decode_failure}, State}
lager:error("Error sending message to receiver, ERROR: ~p~n", [Any]),
{reply, {error, decode_failure}, State1}
end
after 5000 ->
logger:error("Timeout waiting for CreateSessionResponse for ~p~n", [Req]),
{reply, timeout, State}
lager:error("Timeout waiting for CreateSessionResponse for ~p~n", [Req]),
{reply, timeout, State1}
end.
%% @callback gen_server
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Req, State) ->
handle_cast(Req, State) ->
lager:info("S2b handle_cast: ~p ~n", [Req]),
{noreply, State}.
%% @callback gen_server
handle_info(_Info, State) ->
handle_info({udp, _Socket, IP, InPortNo, RxMsg}, State) ->
lager:info("S2b: Rx from IP ~p port ~p: ~p~n", [IP, InPortNo, RxMsg]),
Req = gtp_packet:decode(RxMsg),
lager:info("S2b: Rx from IP ~p port ~p: ~p~n", [IP, InPortNo, Req]),
rx_gtp(Req, State);
handle_info(Info, State) ->
lager:info("S2b handle_info: ~p ~n", [Info]),
{noreply, State}.
%% @callback gen_server
@ -185,6 +196,76 @@ terminate(_Reason, _State) ->
%% Internal Function Definitions
%% ------------------------------------------------------------------
new_gtp_session(Imsi, State) ->
% TODO: find non-used local TEI inside State
Bearer = #gtp_bearer{
ebi = 5,
local_data_tei = State#gtp_state.next_local_data_tei
},
Sess = #gtp_session{imsi = Imsi,
apn = ?APN,
local_control_tei = State#gtp_state.next_local_control_tei,
bearer = Bearer
},
NewSt = State#gtp_state{next_local_control_tei = State#gtp_state.next_local_control_tei + 1,
next_local_data_tei = State#gtp_state.next_local_data_tei + 1,
sessions = sets:add_element(Sess, State#gtp_state.sessions)},
{Sess, NewSt}.
% returns Sess if found, undefined it not
find_gtp_session_by_imsi(Imsi, State) ->
sets:fold(
fun(SessIt = #gtp_session{imsi = Imsi}, _AccIn) -> SessIt;
(_, AccIn) -> AccIn
end,
undefined,
State#gtp_state.sessions).
find_or_new_gtp_session(Imsi, State) ->
Sess = find_gtp_session_by_imsi(Imsi, State),
case Sess of
#gtp_session{imsi = Imsi} ->
{Sess, State};
undefined ->
new_gtp_session(Imsi, State)
end.
update_gtp_session(OldSess, NewSess, State) ->
SetRemoved = sets:del_element(OldSess, State#gtp_state.sessions),
SetUpdated = sets:add_element(NewSess, SetRemoved),
State#gtp_state{sessions = SetUpdated}.
delete_gtp_session(Sess, State) ->
SetRemoved = sets:del_element(Sess, State#gtp_state.sessions),
State#gtp_state{sessions = SetRemoved}.
update_gtp_session_from_create_session_response_ie(none, Sess) ->
Sess;
update_gtp_session_from_create_session_response_ie({_,
#v2_fully_qualified_tunnel_endpoint_identifier{
interface_type = _Interface,
key = TEI, ipv4 = _IP4, ipv6 = _IP6},
Next}, Sess) ->
update_gtp_session_from_create_session_response_ie(maps:next(Next), Sess#gtp_session{remote_control_tei = TEI});
update_gtp_session_from_create_session_response_ie({_, _, Next},
Sess) ->
update_gtp_session_from_create_session_response_ie(maps:next(Next), Sess).
update_gtp_session_from_create_session_response_ies(#gtp{ie = IEs}, Sess) ->
update_gtp_session_from_create_session_response_ie(maps:next(maps:iterator(IEs)), Sess).
update_gtp_session_from_create_session_response(Resp = #gtp{version = v2, type = create_session_response}, Sess) ->
update_gtp_session_from_create_session_response_ies(#gtp{ie = Resp#gtp.ie}, Sess).
% returns Sess if found, undefined it not
find_gtp_session_by_local_teic(LocalControlTei, State) ->
sets:fold(
fun(SessIt = #gtp_session{local_control_tei = LocalControlTei}, _AccIn) -> SessIt;
(_, AccIn) -> AccIn
end,
undefined,
State#gtp_state.sessions).
%% connect/2
connect(Name, {Socket, RemoteAddr, RemotePort}) ->
lager:info("~s connecting to IP ~s port ~p~n", [Name, RemoteAddr, RemotePort]),
@ -193,23 +274,27 @@ connect(Name, {Socket, RemoteAddr, RemotePort}) ->
connect(Address) ->
connect(?SVC_NAME, Address).
rx_gtp(Req = #gtp{version = v2, type = delete_bearer_request}, State) ->
Sess = find_gtp_session_by_local_teic(Req#gtp.tei, State),
case Sess of
undefined ->
lager:error("Rx unknown TEI ~p: ~p~n", [Req#gtp.tei, Req]),
{noreply, State};
Sess ->
Resp = gen_delete_bearer_response(Req, Sess, request_accepted, State),
tx_gtp(Resp, State),
State1 = delete_gtp_session(Sess, State),
{noreply, State1}
end;
rx_gtp(Req, State) ->
lager:error("S2b: UNIMPLEMENTED Rx: ~p~n", [Req]),
{noreply, State}.
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,
@ -249,4 +334,17 @@ gen_create_session_request(#gtp_session{imsi = Imsi,
],
#gtp{version = v2, type = create_session_request, tei = 0, seq_no = SeqNo, ie = IEs}.
gen_delete_bearer_response(Req = #gtp{version = v2, type = delete_bearer_request},
Sess = #gtp_session{remote_control_tei = RemoteCtlTEI},
GtpCause,
#gtp_state{restart_counter = RCnt}) ->
IEs = [#v2_recovery{restart_counter = RCnt},
#v2_cause{v2_cause = GtpCause}
],
#gtp{version = v2,
type = delete_bearer_response,
tei = RemoteCtlTEI,
seq_no = Req#gtp.seq_no,
ie = IEs}.