From fe0c4e8cb741f2ade595cb19373439a71699b74b Mon Sep 17 00:00:00 2001 From: Andreas Schultz Date: Tue, 14 Sep 2021 11:22:37 +0200 Subject: [PATCH] rework context teardown on inactivity to include a proper cause Rework the context inactivity handling to generate a delete GTP message with a related cause (PDP address inactivity timer expire / PDN connection inactivity timer expire). --- apps/ergw_core/src/ggsn_gn.erl | 13 ++++- apps/ergw_core/src/gtp_context.erl | 6 +-- apps/ergw_core/src/pgw_s5s8.erl | 9 +++- apps/ergw_core/src/saegw_s11.erl | 9 +++- apps/ergw_core/test/ggsn_SUITE.erl | 70 ++++++++++++++++++++++--- apps/ergw_core/test/pgw_SUITE.erl | 39 +++++++++++--- apps/ergw_core/test/saegw_s11_SUITE.erl | 39 +++++++++++--- 7 files changed, 158 insertions(+), 27 deletions(-) diff --git a/apps/ergw_core/src/ggsn_gn.erl b/apps/ergw_core/src/ggsn_gn.erl index 72bf9dc5..62b371d8 100644 --- a/apps/ergw_core/src/ggsn_gn.erl +++ b/apps/ergw_core/src/ggsn_gn.erl @@ -852,12 +852,21 @@ send_context_alive_request(#{left_tunnel := Tunnel, context := RequestIEs = gtp_v1_c:build_recovery(Type, Tunnel, false, RequestIEs0), send_request(Tunnel, ?T3, ?N3, Type, RequestIEs, alive_check). +map_term_cause(TermCause) + when TermCause =:= cp_inactivity_timeout; + TermCause =:= up_inactivity_timeout -> + pdp_address_inactivity_timer_expires; +map_term_cause(_TermCause) -> + reactivation_requested. + delete_context(From, TermCause, connected, #{left_tunnel := Tunnel, context := #context{default_bearer_id = NSAPI}} = Data) -> Type = delete_pdp_context_request, - RequestIEs0 = [#nsapi{nsapi = NSAPI}, - #teardown_ind{value = 1}], + RequestIEs0 = + [#cause{value = map_term_cause(TermCause)}, + #nsapi{nsapi = NSAPI}, + #teardown_ind{value = 1}], RequestIEs = gtp_v1_c:build_recovery(Type, Tunnel, false, RequestIEs0), send_request(Tunnel, ?T3, ?N3, Type, RequestIEs, {From, TermCause}), {next_state, shutdown_initiated, Data}; diff --git a/apps/ergw_core/src/gtp_context.erl b/apps/ergw_core/src/gtp_context.erl index d100ac59..fdfc9570 100644 --- a/apps/ergw_core/src/gtp_context.erl +++ b/apps/ergw_core/src/gtp_context.erl @@ -418,9 +418,9 @@ handle_event({call, From}, handle_event({call, From}, {sx, #pfcp{type = session_report_request, ie = #{report_type := #report_type{upir = 1}}}}, - State, #{pfcp := PCtx} = Data0) -> - Data = close_context(both, up_inactivity_timeout, State, Data0), - {next_state, shutdown, Data, [{reply, From, {ok, PCtx}}]}; + State, #{pfcp := PCtx} = Data) -> + gen_statem:reply(From, {ok, PCtx}), + delete_context(undefined, up_inactivity_timeout, State, Data); %% Usage Report handle_event({call, From}, diff --git a/apps/ergw_core/src/pgw_s5s8.erl b/apps/ergw_core/src/pgw_s5s8.erl index 978f7eaa..50884118 100644 --- a/apps/ergw_core/src/pgw_s5s8.erl +++ b/apps/ergw_core/src/pgw_s5s8.erl @@ -918,11 +918,18 @@ send_request(Tunnel, T3, N3, Type, RequestIEs, ReqInfo) -> send_request(Tunnel, Src, DstIP, DstPort, T3, N3, Msg, ReqInfo) -> gtp_context:send_request(Tunnel, Src, DstIP, DstPort, T3, N3, Msg, ReqInfo). +map_term_cause(TermCause) + when TermCause =:= cp_inactivity_timeout; + TermCause =:= up_inactivity_timeout -> + pdn_connection_inactivity_timer_expires; +map_term_cause(_TermCause) -> + reactivation_requested. + delete_context(From, TermCause, connected, #{left_tunnel := Tunnel, context := #context{default_bearer_id = EBI}} = Data) -> Type = delete_bearer_request, - RequestIEs0 = [#v2_cause{v2_cause = reactivation_requested}, + RequestIEs0 = [#v2_cause{v2_cause = map_term_cause(TermCause)}, #v2_eps_bearer_id{eps_bearer_id = EBI}], RequestIEs = gtp_v2_c:build_recovery(Type, Tunnel, false, RequestIEs0), send_request(Tunnel, ?T3, ?N3, Type, RequestIEs, {From, TermCause}), diff --git a/apps/ergw_core/src/saegw_s11.erl b/apps/ergw_core/src/saegw_s11.erl index 13f2a175..487ec944 100644 --- a/apps/ergw_core/src/saegw_s11.erl +++ b/apps/ergw_core/src/saegw_s11.erl @@ -743,10 +743,17 @@ send_request(Tunnel, T3, N3, Type, RequestIEs, ReqInfo) -> send_request(Tunnel, Src, DstIP, DstPort, T3, N3, Msg, ReqInfo) -> gtp_context:send_request(Tunnel, Src, DstIP, DstPort, T3, N3, Msg, ReqInfo). +map_term_cause(TermCause) + when TermCause =:= cp_inactivity_timeout; + TermCause =:= up_inactivity_timeout -> + pdn_connection_inactivity_timer_expires; +map_term_cause(_TermCause) -> + reactivation_requested. + delete_context(From, TermCause, connected, #{left_tunnel := Tunnel} = Data) -> Type = delete_bearer_request, EBI = 5, - RequestIEs0 = [#v2_cause{v2_cause = reactivation_requested}, + RequestIEs0 = [#v2_cause{v2_cause = map_term_cause(TermCause)}, #v2_eps_bearer_id{eps_bearer_id = EBI}], RequestIEs = gtp_v2_c:build_recovery(Type, Tunnel, false, RequestIEs0), send_request(Tunnel, ?T3, ?N3, Type, RequestIEs, {From, TermCause}), diff --git a/apps/ergw_core/test/ggsn_SUITE.erl b/apps/ergw_core/test/ggsn_SUITE.erl index b4ac39b9..28b3e0e3 100644 --- a/apps/ergw_core/test/ggsn_SUITE.erl +++ b/apps/ergw_core/test/ggsn_SUITE.erl @@ -509,7 +509,8 @@ common() -> gx_invalid_charging_rulebase, gx_invalid_charging_rule, gx_rar_gy_interaction, - gtp_idle_timeout, + gtp_idle_timeout_gtp_session_loss, + gtp_idle_timeout_pfcp_session_loss, up_inactivity_timer, pfcp_sx_association_metric]. @@ -658,7 +659,9 @@ init_per_testcase(gx_invalid_charging_rule, Config) -> ergw_test_lib:load_aaa_answer_config([{{gx, 'CCR-Initial'}, 'Initial-Gx-Fail-2'}]), Config; %% gtp 'inactivity_timeout' reduced to 300ms for test purposes -init_per_testcase(gtp_idle_timeout, Config) -> +init_per_testcase(TestCase, Config) + when TestCase =:= gtp_idle_timeout_gtp_session_loss; + TestCase =:= gtp_idle_timeout_pfcp_session_loss -> ergw_test_lib:set_apn_key('inactivity_timeout', 300), ok = meck:expect(ergw_gtp_c_socket, send_request, fun(Socket, Src, DstIP, DstPort, _T3, _N3, @@ -724,7 +727,9 @@ end_per_testcase(create_pdp_context_overload, Config) -> jobs:modify_regulator(rate, create, {rate,create,1}, [{limit,100}]), end_per_testcase(Config); %% gtp 'inactivity_timeout' reset to default 28800000ms ~8 hrs -end_per_testcase(gtp_idle_timeout, Config) -> +end_per_testcase(TestCase, Config) + when TestCase =:= gtp_idle_timeout_gtp_session_loss; + TestCase =:= gtp_idle_timeout_pfcp_session_loss -> ergw_test_lib:set_apn_key('inactivity_timeout', 28800000), ok = meck:delete(ergw_gtp_c_socket, send_request, 8), end_per_testcase(Config); @@ -2682,9 +2687,9 @@ gx_invalid_charging_rule(Config) -> ok. %%-------------------------------------------------------------------- -gtp_idle_timeout() -> +gtp_idle_timeout_gtp_session_loss() -> [{doc, "Checks if the gtp idle timeout is triggered"}]. -gtp_idle_timeout(Config) -> +gtp_idle_timeout_gtp_session_loss(Config) -> Cntl = whereis(gtpc_client_server), {GtpC, _, _} = create_pdp_context(Config), %% The meck wait timeout (400 ms) has to be more than then the inactivity_timeout @@ -2704,6 +2709,49 @@ gtp_idle_timeout(Config) -> Resp2 = make_response(Req2, invalid_teid, GtpC), send_pdu(Cntl, GtpC, Resp2), + %% make sure we are not getting a Delete PDP Context Request + ?equal(timeout, recv_pdu(GtpC, undefined, 200, fun(Why) -> Why end)), + + delete_pdp_context(not_found, GtpC), + + ?equal([], outstanding_requests()), + ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), + wait4contexts(?TIMEOUT), + + meck_validate(Config), + ok. + +%%-------------------------------------------------------------------- +gtp_idle_timeout_pfcp_session_loss() -> + [{doc, "Checks if the gtp idle timeout is triggered"}]. +gtp_idle_timeout_pfcp_session_loss(Config) -> + Cntl = whereis(gtpc_client_server), + + {GtpC, _, _} = create_pdp_context(Config), + %% The meck wait timeout (400 ms) has to be more than then the inactivity_timeout + ok = meck:wait(gtp_context, handle_event, + [{timeout, context_idle}, '_', '_', '_'], 400), + + %% Timeout triggers a update_pdp_context_request towards the SGSN + Req1 = recv_pdu(Cntl, 5000), + ?match(#gtp{type = update_pdp_context_request}, Req1), + Resp1 = make_response(Req1, simple, GtpC), + send_pdu(Cntl, GtpC, Resp1), + + %% kill the UP session + ergw_test_sx_up:reset('pgw-u01'), + + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_pdp_context_request, + ie = #{{cause,0} := #cause{value = pdp_address_inactivity_timer_expires}}}, + Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_pdp_context(not_found, GtpC), + ?equal([], outstanding_requests()), ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), wait4contexts(?TIMEOUT), @@ -2715,6 +2763,7 @@ gtp_idle_timeout(Config) -> up_inactivity_timer() -> [{doc, "Test expiry of the User Plane Inactivity Timer"}]. up_inactivity_timer(Config) -> + Cntl = whereis(gtpc_client_server), CtxKey = #context_key{socket = 'irx', id = {imsi, ?'IMSI', 5}}, Interim = rand:uniform(1800) + 1800, AAAReply = #{'Idle-Timeout' => 1800, 'Acct-Interim-Interval' => Interim}, @@ -2732,7 +2781,7 @@ up_inactivity_timer(Config) -> meck:passthrough([Session, SessionOpts, Procedure, Opts]) end), - create_pdp_context(Config), + {GtpC, _, _} = create_pdp_context(Config), {_Handler, Server} = gtp_context_reg:lookup(CtxKey), true = is_pid(Server), @@ -2747,6 +2796,15 @@ up_inactivity_timer(Config) -> ergw_test_sx_up:up_inactivity_timer_expiry('pgw-u01', PCtx), + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_pdp_context_request}, Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_pdp_context(not_found, GtpC), + ?equal([], outstanding_requests()), ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), wait4contexts(?TIMEOUT), diff --git a/apps/ergw_core/test/pgw_SUITE.erl b/apps/ergw_core/test/pgw_SUITE.erl index 5e1987e3..a03be780 100644 --- a/apps/ergw_core/test/pgw_SUITE.erl +++ b/apps/ergw_core/test/pgw_SUITE.erl @@ -745,7 +745,7 @@ common() -> gx_invalid_charging_rule, gx_rar_gy_interaction, tdf_app_id, - gtp_idle_timeout, + gtp_idle_timeout_pfcp_session_loss, up_inactivity_timer]. sx_fail() -> @@ -1006,7 +1006,7 @@ init_per_testcase(tdf_app_id, Config) -> ergw_test_lib:load_aaa_answer_config([{{gx, 'CCR-Initial'}, 'Initial-Gx-TDF-App'}]), Config; %% gtp inactivity_timeout reduced to 300ms for test purposes -init_per_testcase(gtp_idle_timeout, Config) -> +init_per_testcase(gtp_idle_timeout_pfcp_session_loss, Config) -> ergw_test_lib:set_apn_key(inactivity_timeout, 300), setup_per_testcase(Config), Config; @@ -1111,7 +1111,7 @@ end_per_testcase(TestCase, Config) ergw_test_sx_up:nat_port_blocks('pgw-u01', example, []), end_per_testcase(Config); %% gtp inactivity_timeout reset to default 28800000ms ~8 hrs -end_per_testcase(gtp_idle_timeout, Config) -> +end_per_testcase(gtp_idle_timeout_pfcp_session_loss, Config) -> ergw_test_lib:set_apn_key(inactivity_timeout, 28800000), end_per_testcase(Config); end_per_testcase(_, Config) -> @@ -5643,15 +5643,30 @@ gx_invalid_charging_rule(Config) -> ok. %%-------------------------------------------------------------------- -gtp_idle_timeout() -> +gtp_idle_timeout_pfcp_session_loss() -> [{doc, "Checks if the gtp idle timeout is triggered"}]. -gtp_idle_timeout(Config) -> +gtp_idle_timeout_pfcp_session_loss(Config) -> + Cntl = whereis(gtpc_client_server), + {GtpC, _, _} = create_session(Config), %% The meck wait timeout (400 ms) has to be more than then the Idle-Timeout ok = meck:wait(gtp_context, handle_event, [{timeout, context_idle}, '_', '_', '_'], 400), - delete_session(GtpC), + %% kill the UP session + ergw_test_sx_up:reset('pgw-u01'), + + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_bearer_request, + ie = #{{v2_cause,0} := + #v2_cause{v2_cause = pdn_connection_inactivity_timer_expires}}}, + Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_session(not_found, GtpC), ?equal([], outstanding_requests()), @@ -5665,6 +5680,7 @@ gtp_idle_timeout(Config) -> up_inactivity_timer() -> [{doc, "Test expiry of the User Plane Inactivity Timer"}]. up_inactivity_timer(Config) -> + Cntl = whereis(gtpc_client_server), CtxKey = #context_key{socket = 'irx-socket', id = {imsi, ?'IMSI', 5}}, Interim = rand:uniform(1800) + 1800, AAAReply = #{'Idle-Timeout' => 1800, 'Acct-Interim-Interval' => Interim}, @@ -5682,7 +5698,7 @@ up_inactivity_timer(Config) -> meck:passthrough([Session, SessionOpts, Procedure, Opts]) end), - create_session(Config), + {GtpC, _, _} = create_session(Config), {_Handler, Server} = gtp_context_reg:lookup(CtxKey), true = is_pid(Server), @@ -5697,6 +5713,15 @@ up_inactivity_timer(Config) -> ergw_test_sx_up:up_inactivity_timer_expiry('pgw-u01', PCtx), + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_bearer_request}, Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_session(not_found, GtpC), + ?equal([], outstanding_requests()), ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT), diff --git a/apps/ergw_core/test/saegw_s11_SUITE.erl b/apps/ergw_core/test/saegw_s11_SUITE.erl index 823a1aec..64e922aa 100644 --- a/apps/ergw_core/test/saegw_s11_SUITE.erl +++ b/apps/ergw_core/test/saegw_s11_SUITE.erl @@ -478,7 +478,7 @@ common() -> gx_invalid_charging_rulebase, gx_invalid_charging_rule, gx_rar_gy_interaction, - gtp_idle_timeout, + gtp_idle_timeout_pfcp_session_loss, up_inactivity_timer]. groups() -> @@ -602,7 +602,7 @@ init_per_testcase(gx_invalid_charging_rule, Config) -> ergw_test_lib:load_aaa_answer_config([{{gx, 'CCR-Initial'}, 'Initial-Gx-Fail-2'}]), Config; %% gtp inactivity_timeout reduced to 300ms for test purposes -init_per_testcase(gtp_idle_timeout, Config) -> +init_per_testcase(gtp_idle_timeout_pfcp_session_loss, Config) -> ergw_test_lib:set_apn_key(inactivity_timeout, 300), setup_per_testcase(Config), Config; @@ -652,7 +652,7 @@ end_per_testcase(create_session_overload, Config) -> end_per_testcase(Config), Config; %% gtp inactivity_timeout reset to default 28800000ms ~8 hrs -end_per_testcase(gtp_idle_timeout, Config) -> +end_per_testcase(gtp_idle_timeout_pfcp_session_loss, Config) -> ergw_test_lib:set_apn_key(inactivity_timeout, 28800000), end_per_testcase(Config), Config; @@ -2162,15 +2162,30 @@ gx_invalid_charging_rule(Config) -> ok. %%-------------------------------------------------------------------- -gtp_idle_timeout() -> +gtp_idle_timeout_pfcp_session_loss() -> [{doc, "Checks if the gtp idle timeout is triggered"}]. -gtp_idle_timeout(Config) -> +gtp_idle_timeout_pfcp_session_loss(Config) -> + Cntl = whereis(gtpc_client_server), + {GtpC, _, _} = create_session(Config), %% The meck wait timeout (400 ms) has to be more than then the Idle-Timeout ok = meck:wait(gtp_context, handle_event, [{timeout, context_idle}, '_', '_', '_'], 400), - delete_session(GtpC), + %% kill the UP session + ergw_test_sx_up:reset('pgw-u01'), + + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_bearer_request, + ie = #{{v2_cause,0} := + #v2_cause{v2_cause = pdn_connection_inactivity_timer_expires}}}, + Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_session(not_found, GtpC), ?equal([], outstanding_requests()), @@ -2184,6 +2199,7 @@ gtp_idle_timeout(Config) -> up_inactivity_timer() -> [{doc, "Test expiry of the User Plane Inactivity Timer"}]. up_inactivity_timer(Config) -> + Cntl = whereis(gtpc_client_server), CtxKey = #context_key{socket = 'irx', id = {imsi, ?'IMSI', 5}}, Interim = rand:uniform(1800) + 1800, AAAReply = #{'Idle-Timeout' => 1800, 'Acct-Interim-Interval' => Interim}, @@ -2201,7 +2217,7 @@ up_inactivity_timer(Config) -> meck:passthrough([Session, SessionOpts, Procedure, Opts]) end), - create_session(Config), + {GtpC, _, _} = create_session(Config), {_Handler, Server} = gtp_context_reg:lookup(CtxKey), true = is_pid(Server), @@ -2216,6 +2232,15 @@ up_inactivity_timer(Config) -> ergw_test_sx_up:up_inactivity_timer_expiry('pgw-u01', PCtx), + %% wait for session cleanup + Req = recv_pdu(Cntl, 5000), + ?match(#gtp{type = delete_bearer_request}, Req), + Resp = make_response(Req, simple, GtpC), + send_pdu(Cntl, GtpC, Resp), + + ct:sleep(100), + delete_session(not_found, GtpC), + ?equal([], outstanding_requests()), ok = meck:wait(?HUT, terminate, '_', ?TIMEOUT),