blob: b5c65d26207046adb50ee31655bdbc1fa90bc028 [file] [log] [blame]
Neels Hofmeyr2d292742022-06-07 23:58:05 +02001module UPF_Tests {
2
3/* Integration Tests for OsmoUPF
4 * (C) 2022 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
5 * All rights reserved.
6 *
7 * Released under the terms of GNU General Public License, Version 2 or
8 * (at your option) any later version.
9 *
10 * SPDX-License-Identifier: GPL-2.0-or-later
11 *
12 * This test suite acts as a PFCP Control Plane Function to test OsmoUPF.
13 */
14
15import from Misc_Helpers all;
16import from General_Types all;
17import from Osmocom_Types all;
18import from IPL4asp_Types all;
19import from Native_Functions all;
20import from TCCConversion_Functions all;
21
22import from Osmocom_CTRL_Functions all;
23import from Osmocom_CTRL_Types all;
24import from Osmocom_CTRL_Adapter all;
25
26import from StatsD_Types all;
27import from StatsD_CodecPort all;
28import from StatsD_CodecPort_CtrlFunct all;
29import from StatsD_Checker all;
30
31import from Osmocom_VTY_Functions all;
32import from TELNETasp_PortType all;
33
34import from CPF_ConnectionHandler all;
35
36import from PFCP_Types all;
37import from PFCP_Emulation all;
38import from PFCP_Templates all;
39
40modulepar {
41 /* IP address at which the UPF can be reached */
42 charstring mp_pfcp_ip_upf := "127.0.0.1";
43 charstring mp_pfcp_ip_local := "127.0.0.2";
44
45 /* When testing with gtp mockup, actions will not show. */
46 boolean mp_verify_gtp_actions := false;
47}
48
49type component test_CT extends CTRL_Adapter_CT {
50 port PFCPEM_PT PFCP;
51
52 port TELNETasp_PT UPFVTY;
53
54 /* global test case guard timer (actual timeout value is set in f_init()) */
55 timer T_guard := 15.0;
56}
57
58/* global altstep for global guard timer; */
59altstep as_Tguard() runs on test_CT {
60 [] T_guard.timeout {
61 setverdict(fail, "Timeout of T_guard");
62 mtc.stop;
63 }
64}
65
66friend function f_logp(TELNETasp_PT pt, charstring log_msg)
67{
68 // log on TTCN3 log output
69 log(log_msg);
70 // log in stderr log
71 f_vty_transceive(pt, "logp lglobal notice TTCN3 f_logp(): " & log_msg);
72}
73
74private function f_str_split(charstring str, charstring delim := "\n") return ro_charstring
75{
76 var integer pos := 0;
77 var ro_charstring parts := {};
78 var integer delim_pos;
79 var integer end := lengthof(str);
80 while (pos < end) {
81 delim_pos := f_strstr(str, delim, pos);
82 if (delim_pos < 0) {
83 delim_pos := end;
84 }
85 parts := parts & { substr(str, pos, delim_pos - pos) };
86 pos := delim_pos + 1;
87 }
88 return parts;
89}
90
91private function f_get_name_val(out charstring val, charstring str, charstring name, charstring sep := ":", charstring delim := " ") return boolean {
92 var charstring labl := name & sep;
93 var integer namepos := f_strstr(str, labl);
94 if (namepos < 0) {
95 return false;
96 }
97 var integer valpos := namepos + lengthof(labl);
98 var integer valend := f_strstr(str, delim, valpos);
99 if (valend < 0) {
100 valend := lengthof(str);
101 }
102 val := substr(str, valpos, valend - valpos);
103 return true;
104}
105
106private function f_get_name_val_oct8(out OCT8 val, charstring str, charstring name) return boolean {
107 var charstring token;
108 if (not f_get_name_val(token, str, name, ":0x")) {
109 return false;
110 }
111 if (lengthof(token) > 16) {
112 log("token too long: ", name, " in ", str);
113 return false;
114 }
115 var charstring padded := substr("0000000000000000", 0, 16 - lengthof(token)) & token;
116 val := str2oct(padded);
117 return true;
118}
119
120private function f_get_name_val_oct4(out OCT4 val, charstring str, charstring name) return boolean {
121 var charstring token;
122 if (not f_get_name_val(token, str, name, ":0x")) {
123 return false;
124 }
125 if (lengthof(token) > 8) {
126 log("token too long: ", name, " in ", str);
127 return false;
128 }
129 var charstring padded := substr("00000000", 0, 8 - lengthof(token)) & token;
130 val := str2oct(padded);
131 return true;
132}
133
134private function f_get_name_val_int(out integer val, charstring str, charstring name) return boolean {
135 var charstring token;
136 if (not f_get_name_val(token, str, name)) {
137 return false;
138 }
139 val := str2int(token);
140 return true;
141}
142
143private function f_get_name_val_2int(out integer val1, out integer val2, charstring str, charstring name, charstring delim := ",") return boolean {
144 var charstring token;
145 if (not f_get_name_val(token, str, name)) {
146 return false;
147 }
148 var ro_charstring nrl := f_str_split(token, delim);
149 if (lengthof(nrl) != 2) {
150 return false;
151 }
152 val1 := str2int(nrl[0]);
153 val2 := str2int(nrl[1]);
154 return true;
155}
156
157/* A PFCP session as seen by the system under test, osmo-upf. up_seid is what osmo-upf sees as its local SEID
158 * ("SEID-l"). cp_seid is this tester's side's SEID, which osmo-upf sees as the remote SEID. */
159type record PFCP_session {
160 OCT8 up_seid,
161 OCT8 cp_seid,
162 GTP_Action gtp
163}
164
165type record GTP_Action {
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100166 /* kind = ("tunend"|"tunmap") */
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200167 charstring kind,
168 charstring gtp_access_ip,
169 OCT4 teid_access_r,
170 OCT4 teid_access_l,
171 charstring core_ip,
172 charstring pfcp_peer,
173 OCT8 seid_l
174};
175
176type record of GTP_Action GTP_Action_List;
177
178private function f_parse_gtp_action(out GTP_Action ret, charstring str) return boolean {
179 var GTP_Action a;
180 if (not f_get_name_val(a.kind, str, "GTP")) {
181 return false;
182 }
183 if (not f_get_name_val(a.gtp_access_ip, str, "GTP-access")) {
184 return false;
185 }
186 if (not f_get_name_val_oct4(a.teid_access_r, str, "TEID-r")) {
187 return false;
188 }
189 if (not f_get_name_val_oct4(a.teid_access_l, str, "TEID-l")) {
190 return false;
191 }
192 if (not f_get_name_val(a.pfcp_peer, str, "PFCP-peer")) {
193 return false;
194 }
195 if (not f_get_name_val_oct8(a.seid_l, str, "SEID-l")) {
196 return false;
197 }
198 if (not f_get_name_val(a.core_ip, str, "IP-core")) {
199 return false;
200 }
201 ret := a;
202 return true;
203}
204
205private function f_vty_get_gtp_actions(TELNETasp_PT vty_pt) return GTP_Action_List {
206 var charstring gtp_str := f_vty_transceive_ret(vty_pt, "show gtp");
207 var ro_charstring lines := f_str_split(gtp_str, "\n");
208 var GTP_Action_List gtps := {};
209 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
210 var charstring line := lines[i];
211 var GTP_Action a;
212 if (not f_parse_gtp_action(a, line)) {
213 continue;
214 }
215 gtps := gtps & { a };
216 }
217 log("GTP-actions: ", gtps);
218 return gtps;
219}
220
221private function f_find_gtp_action(GTP_Action_List actions, template GTP_Action find) return boolean {
222 for (var integer i := 0; i < lengthof(actions); i := i + 1) {
223 if (match(actions[i], find)) {
224 return true;
225 }
226 }
227 return false;
228}
229
230private function f_expect_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
231 if (f_find_gtp_action(actions, expect)) {
232 log("VTY confirms: GTP action active: ", expect);
233 setverdict(pass);
234 return;
235 }
236 log("Expected to find ", expect, " in ", actions);
237 setverdict(fail, "on VTY, a GTP action failed to show as active");
238 mtc.stop;
239}
240
241private function f_expect_no_gtp_action(GTP_Action_List actions, template GTP_Action expect) {
242 if (f_find_gtp_action(actions, expect)) {
243 log("Expected to *not* find ", expect, " in ", actions);
244 setverdict(fail, "a GTP action failed to show as inactive");
245 mtc.stop;
246 }
247 log("VTY confirms: GTP action inactive: ", expect);
248 setverdict(pass);
249 return;
250}
251
252private function f_vty_expect_gtp_action(TELNETasp_PT vty_pt, template GTP_Action expect) {
253 if (not mp_verify_gtp_actions) {
254 /* In GTP mockup mode, GTP actions don't show on VTY. Cannot verify. */
255 setverdict(pass);
256 return;
257 }
258 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
259 f_expect_gtp_action(actions, expect);
260}
261
262private function f_vty_expect_no_gtp_actions(TELNETasp_PT vty_pt) {
263 var GTP_Action_List actions := f_vty_get_gtp_actions(vty_pt);
264 if (lengthof(actions) > 0) {
265 setverdict(fail, "VTY says that there are still active GTP actions");
266 mtc.stop;
267 }
268 setverdict(pass);
269}
270
271type record PFCP_Session_Status {
272 charstring peer,
273 OCT8 seid_r,
274 OCT8 seid_l,
275 charstring state,
276 integer pdr_active_count,
277 integer pdr_count,
278 integer far_active_count,
279 integer far_count,
280 integer gtp_active_count
281};
282
283template PFCP_Session_Status PFCP_session_active := {
284 peer := ?,
285 seid_r := ?,
286 seid_l := ?,
287 state := "ESTABLISHED",
288 pdr_active_count := (1..99999),
289 pdr_count := (1..99999),
290 far_active_count := (1..99999),
291 far_count := (1..99999),
292 gtp_active_count := (1..99999)
293};
294
295template PFCP_Session_Status PFCP_session_inactive := {
296 peer := ?,
297 seid_r := ?,
298 seid_l := ?,
299 state := "ESTABLISHED",
300 pdr_active_count := 0,
301 pdr_count := (1..99999),
302 far_active_count := 0,
303 far_count := (1..99999),
304 gtp_active_count := 0
305};
306
307type record of PFCP_Session_Status PFCP_Session_Status_List;
308
309private function f_parse_session_status(out PFCP_Session_Status ret, charstring str) return boolean {
310 var PFCP_Session_Status st;
311 if (not f_get_name_val(st.peer, str, "peer")) {
312 return false;
313 }
314 if (not f_get_name_val_oct8(st.seid_l, str, "SEID-l")) {
315 return false;
316 }
317 f_get_name_val_oct8(st.seid_r, str, "SEID-r");
318 f_get_name_val(st.state, str, "state");
319
320 /* parse 'PDR-active:1/2' */
321 if (not f_get_name_val_2int(st.pdr_active_count, st.pdr_count, str, "PDR-active", "/")) {
322 return false;
323 }
324 /* parse 'FAR-active:1/2' */
325 if (not f_get_name_val_2int(st.far_active_count, st.far_count, str, "FAR-active", "/")) {
326 return false;
327 }
328
329 f_get_name_val_int(st.gtp_active_count, str, "GTP-active");
330 ret := st;
331 return true;
332}
333
334private function f_vty_get_sessions(TELNETasp_PT vty_pt) return PFCP_Session_Status_List {
335 var charstring sessions_str := f_vty_transceive_ret(vty_pt, "show session");
336 var ro_charstring lines := f_str_split(sessions_str, "\n");
337 var PFCP_Session_Status_List sessions := {};
338 for (var integer i := 0; i < lengthof(lines); i := i + 1) {
339 var charstring line := lines[i];
340 var PFCP_Session_Status st;
341 if (not f_parse_session_status(st, line)) {
342 continue;
343 }
344 sessions := sessions & { st };
345 }
346 log("Sessions: ", sessions);
347 return sessions;
348}
349
350private function f_vty_get_session_status(TELNETasp_PT vty_pt, PFCP_session s, out PFCP_Session_Status ret) return boolean {
351 var PFCP_Session_Status_List sessions := f_vty_get_sessions(vty_pt);
352 return f_get_session_status(sessions, s, ret);
353}
354
355private function f_get_session_status(PFCP_Session_Status_List sessions, PFCP_session s, out PFCP_Session_Status ret)
356return boolean {
357 var PFCP_Session_Status_List matches := {};
358 for (var integer i := 0; i < lengthof(sessions); i := i + 1) {
359 var PFCP_Session_Status st := sessions[i];
360 if (st.seid_l != s.up_seid) {
361 continue;
362 }
363 if (st.seid_r != s.cp_seid) {
364 continue;
365 }
366 matches := matches & { st };
367 }
368 if (lengthof(matches) < 1) {
369 log("no session with SEID-l = ", s.up_seid);
370 return false;
371 }
372 if (lengthof(matches) > 1) {
373 log("multiple sessions have ", s, ": ", matches);
374 return false;
375 }
376 ret := matches[0];
377 return true;
378}
379
380private function f_vty_expect_session_status(TELNETasp_PT vty_pt, PFCP_session s, template PFCP_Session_Status expect_st) {
381 var PFCP_Session_Status st;
382 if (not f_vty_get_session_status(vty_pt, s, st)) {
383 log("Session ", s, " not found in VTY session list");
384 setverdict(fail, "Session not found in VTY list");
385 mtc.stop;
386 }
387 log("Session ", s, " status: ", st);
388 if (not match(st, expect_st)) {
389 log("ERROR: Session ", st, " does not match ", expect_st);
390 setverdict(fail, "VTY shows unexpected state of PFCP session");
391 mtc.stop;
392 }
393
394 setverdict(pass);
395}
396
397private function f_vty_expect_session_active(TELNETasp_PT vty_pt, PFCP_session s)
398{
399 f_vty_expect_session_status(vty_pt, s, PFCP_session_active);
400 f_vty_expect_gtp_action(vty_pt, s.gtp);
401 setverdict(pass);
402}
403
404private function f_vty_expect_no_active_sessions(TELNETasp_PT vty_pt) {
405 var PFCP_Session_Status_List stl := f_vty_get_sessions(vty_pt);
406 var integer active := 0;
407 for (var integer i := 0; i < lengthof(stl); i := i + 1) {
408 if (match(stl[i], PFCP_session_active)) {
409 log("Active session: ", stl[i]);
410 active := active + 1;
411 }
412 }
413 if (active > 0) {
414 setverdict(fail, "There are still active sessions");
415 mtc.stop;
416 }
417 setverdict(pass);
418}
419
420function f_init_vty(charstring id := "foo") runs on test_CT {
421 if (UPFVTY.checkstate("Mapped")) {
422 /* skip initialization if already executed once */
423 return;
424 }
425 map(self:UPFVTY, system:UPFVTY);
426 f_vty_set_prompts(UPFVTY);
427 f_vty_transceive(UPFVTY, "enable");
428}
429
430/* global initialization function */
431function f_init(float guard_timeout := 30.0) runs on test_CT {
432 var integer bssap_idx;
433
434 T_guard.start(guard_timeout);
435 activate(as_Tguard());
436
437 f_init_vty("VirtCPF");
438}
439
440friend function f_shutdown_helper() runs on test_CT {
441 all component.stop;
442 setverdict(pass);
443 mtc.stop;
444}
445
446private function f_gen_test_hdlr_pars() runs on test_CT return TestHdlrParams {
447 var TestHdlrParams pars := valueof(t_def_TestHdlrPars);
448 pars.remote_upf_addr := mp_pfcp_ip_upf;
449 pars.local_addr := mp_pfcp_ip_local;
450 pars.local_node_id := valueof(ts_PFCP_Node_ID_ipv4(f_inet_addr(mp_pfcp_ip_local)));
451 return pars;
452}
453
454type function void_fn(charstring id) runs on CPF_ConnHdlr;
455
456function f_start_handler_create(TestHdlrParams pars)
457runs on test_CT return CPF_ConnHdlr {
458 var charstring id := testcasename();
459 var CPF_ConnHdlr vc_conn;
460 vc_conn := CPF_ConnHdlr.create(id);
461 return vc_conn;
462}
463
464function f_start_handler_run(CPF_ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars)
465runs on test_CT return CPF_ConnHdlr {
466 var charstring id := testcasename();
467 /* Emit a marker to appear in the SUT's own logging output */
468 f_logp(UPFVTY, id & "() start");
469 vc_conn.start(f_handler_init(fn, id, pars));
470 return vc_conn;
471}
472
473function f_start_handler(void_fn fn, template (omit) TestHdlrParams pars_tmpl := omit)
474runs on test_CT return CPF_ConnHdlr {
475 var TestHdlrParams pars;
476 if (isvalue(pars_tmpl)) {
477 pars := valueof(pars_tmpl);
478 } else {
479 pars := valueof(f_gen_test_hdlr_pars());
480 }
481 return f_start_handler_run(f_start_handler_create(pars), fn, pars);
482}
483
484/* first function inside ConnHdlr component; sets g_pars + starts function */
485private function f_handler_init(void_fn fn, charstring id, TestHdlrParams pars)
486runs on CPF_ConnHdlr {
487 f_CPF_ConnHdlr_init(id, pars);
488 fn.apply(id);
489}
490
491/* Run a PFCP Association procedure */
492private function f_assoc_setup() runs on CPF_ConnHdlr {
493 PFCP.send(ts_PFCP_Assoc_Setup_Req(g_pars.local_node_id, g_recovery_timestamp));
494 PFCP.receive(tr_PFCP_Assoc_Setup_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
495}
496
497/* Release a PFCP Association */
498private function f_assoc_release() runs on CPF_ConnHdlr {
499 PFCP.send(ts_PFCP_Assoc_Release_Req(g_pars.local_node_id));
500 PFCP.receive(tr_PFCP_Assoc_Release_Resp(cause := tr_PFCP_Cause(REQUEST_ACCEPTED)));
501}
502
503type record PFCP_Ruleset {
504 Create_PDR_list pdr,
505 Create_FAR_list far
506};
507
Neels Hofmeyr4a9015f2022-11-26 03:11:22 +0100508/* Add to r a rule set that does GTP decapsulation (half of encapsulation/decapsulation):
509 * Receive GTP on src_iface = ACCESS by a local F-TEID to be chosen by osmo-upf.
510 * Dispatch GTP payload as plain IP on dest_iface = CORE. */
511private function f_ruleset_add_GTP_decaps(inout PFCP_Ruleset r) {
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200512 var integer pdr_id := lengthof(r.pdr) + 1;
513 var integer far_id := lengthof(r.far) + 1;
514
515 r.pdr := r.pdr & {
516 valueof(
517 ts_PFCP_Create_PDR(
518 pdr_id,
519 ts_PFCP_PDI(
520 ACCESS,
Neels Hofmeyr4a9015f2022-11-26 03:11:22 +0100521 local_F_TEID := ts_PFCP_F_TEID_choose_v4()),
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200522 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
523 far_id
524 )
525 )
526 };
527 r.far := r.far & {
528 valueof(
529 ts_PFCP_Create_FAR(
530 far_id,
531 ts_PFCP_Apply_Action_FORW(),
532 valueof(ts_PFCP_Forwarding_Parameters(CORE))
533 )
534 )
535 };
536}
537
538/* Add to r a rule set that does GTP encapsulation (half of encapsulation/decapsulation) */
539private function f_ruleset_add_GTP_encaps(inout PFCP_Ruleset r,
540 charstring ue_addr_v4 := "192.168.23.42",
541 OCT4 remote_teid,
Neels Hofmeyr1e311462023-01-11 01:19:12 +0100542 OCT4 gtp_dest_addr_v4) {
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200543
544 var integer pdr_id := lengthof(r.pdr) + 1;
545 var integer far_id := lengthof(r.far) + 1;
546
547 r.pdr := r.pdr & {
548 valueof(
549 ts_PFCP_Create_PDR(
550 pdr_id,
551 ts_PFCP_PDI(
552 CORE,
Neels Hofmeyr1e311462023-01-11 01:19:12 +0100553 ue_addr_v4 := ts_PFCP_UE_IP_Address_v4(f_inet_addr(ue_addr_v4), is_destination := true)
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200554 ),
555 far_id := far_id
556 )
557 )
558 };
559 r.far := r.far & {
560 valueof(
561 ts_PFCP_Create_FAR(
562 far_id,
563 ts_PFCP_Apply_Action_FORW(),
564 valueof(ts_PFCP_Forwarding_Parameters(
565 ACCESS,
566 ts_PFCP_Outer_Header_Creation_GTP_ipv4(
567 remote_teid,
568 gtp_dest_addr_v4)
569 ))
570 )
571 )
572 };
573}
574
575/* Return two PDR+FAR rulesets that involve a src=CP-Function. Such rulesets are emitted by certain third party CPF, and
576 * osmo-upf should ACK the creation but ignore the rules (no-op). This function models rulesets seen in the field, so we
577 * can confirm that osmo-upf ACKs and ignores. */
578private function f_ruleset_noop() return PFCP_Ruleset
579{
580 var PFCP_Ruleset r := { {}, {} };
581 var integer pdr_id := lengthof(r.pdr) + 1;
582 var integer far_id := lengthof(r.far) + 1;
583
584 r.pdr := r.pdr & {
585 valueof(
586 ts_PFCP_Create_PDR(
587 pdr_id,
588 ts_PFCP_PDI(
589 CP_FUNCTION,
590 local_F_TEID := ts_PFCP_F_TEID_choose_v4('17'O)),
591 ts_PFCP_Outer_Header_Removal(GTP_U_UDP_IPV4),
592 far_id
593 )
594 )
595 };
596 r.far := r.far & {
597 valueof(
598 ts_PFCP_Create_FAR(
599 far_id,
600 ts_PFCP_Apply_Action_FORW(),
601 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
602 )
603 )
604 };
605
606 /* And another one (sic) */
607 pdr_id := lengthof(r.pdr) + 1;
608 far_id := lengthof(r.far) + 1;
609
610 r.pdr := r.pdr & {
611 valueof(
612 ts_PFCP_Create_PDR(
613 pdr_id,
614 ts_PFCP_PDI(
615 CP_FUNCTION,
616 local_F_TEID := ts_PFCP_F_TEID_choose_v4('2a'O)),
617 far_id := far_id
618 )
619 )
620 };
621 r.far := r.far & {
622 valueof(
623 ts_PFCP_Create_FAR(
624 far_id,
625 ts_PFCP_Apply_Action_FORW(),
626 valueof(ts_PFCP_Forwarding_Parameters(ACCESS))
627 )
628 )
629 };
630 return r;
631}
632
633/* Return a rule set that does GTP encapsulation/decapsulation */
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100634private function f_ruleset_tunend(GTP_Action gtp) return PFCP_Ruleset
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200635{
636 var PFCP_Ruleset rules := { {}, {} };
Neels Hofmeyr4a9015f2022-11-26 03:11:22 +0100637 f_ruleset_add_GTP_decaps(rules);
Neels Hofmeyr1e311462023-01-11 01:19:12 +0100638 f_ruleset_add_GTP_encaps(rules, gtp.core_ip, gtp.teid_access_r, f_inet_addr(gtp.gtp_access_ip));
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200639 return rules;
640}
641
642/* Run a PFCP Session Establishment procedure */
643private function f_session_est(inout PFCP_session s, PFCP_Ruleset rules) runs on CPF_ConnHdlr {
644
Neels Hofmeyr1e311462023-01-11 01:19:12 +0100645 PFCP.send(ts_PFCP_Session_Est_Req(ts_PFCP_Node_ID_ipv4(f_inet_addr(g_pars.local_addr)),
646 ts_PFCP_F_SEID_ipv4(f_inet_addr(g_pars.local_addr), s.cp_seid),
647 rules.pdr, rules.far));
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200648
649 var PDU_PFCP pfcp;
650 PFCP.receive(tr_PFCP_Session_Est_Resp(s.cp_seid)) -> value pfcp;
651 s.up_seid := pfcp.message_body.pfcp_session_establishment_response.UP_F_SEID.seid;
652 s.gtp.seid_l := s.up_seid;
653 log("established PFCP session: ", s);
654}
655
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100656private function f_create_PFCP_session_tunend() runs on CPF_ConnHdlr return PFCP_session
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200657{
658 var PFCP_session s := {
659 up_seid := -,
660 cp_seid := f_next_seid(),
661 gtp := {
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100662 kind := "tunend",
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200663 gtp_access_ip := "127.0.0.2",
664 teid_access_r := f_next_remote_teid(),
665 teid_access_l := f_next_local_teid(),
666 core_ip := f_next_ue_addr(),
667 pfcp_peer := g_pars.local_addr,
668 seid_l := '0000000000000000'O
669 }
670 };
671 return s;
672}
673
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100674/* Do a PFCP Session Establishment with default values (see f_create_PFCP_session_tunend()) */
675private function f_session_est_tunend() runs on CPF_ConnHdlr return PFCP_session
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200676{
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100677 var PFCP_session s := f_create_PFCP_session_tunend();
Neels Hofmeyr366cdc72022-11-21 16:28:00 +0100678 f_session_est(s, f_ruleset_tunend(s.gtp));
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200679 return s;
680}
681
682private function f_session_del(PFCP_session s) runs on CPF_ConnHdlr {
683 PFCP.send(ts_PFCP_Session_Del_Req(s.up_seid));
684 PFCP.receive(tr_PFCP_Session_Del_Resp(s.cp_seid));
685}
686
687private function f_tc_assoc(charstring id) runs on CPF_ConnHdlr {
688 f_assoc_setup();
689 f_assoc_release();
690 setverdict(pass);
691}
692
693/* Verify that the CPF can send a Node-ID of the IPv4 type */
694testcase TC_assoc_node_id_v4() runs on test_CT {
695 var CPF_ConnHdlr vc_conn;
696
697 f_init(guard_timeout := 5.0);
698 vc_conn := f_start_handler(refers(f_tc_assoc));
699 vc_conn.done;
700 f_shutdown_helper();
701}
702
703/* Verify that the CPF can send a Node-ID of the FQDN type */
704testcase TC_assoc_node_id_fqdn() runs on test_CT {
705 var CPF_ConnHdlr vc_conn;
706 var TestHdlrParams pars := f_gen_test_hdlr_pars();
707
708 pars.local_node_id := valueof(ts_PFCP_Node_ID_fqdn("\7example\3com"));
709
710 f_init(guard_timeout := 5.0);
711 vc_conn := f_start_handler(refers(f_tc_assoc), pars);
712 vc_conn.done;
713 f_shutdown_helper();
714}
715
716/* Verify PFCP Session Establishment and Deletion */
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100717private function f_tc_session_est_tunend(charstring id) runs on CPF_ConnHdlr {
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200718 f_assoc_setup();
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100719 var PFCP_session s := f_session_est_tunend();
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200720 f_sleep(1.0);
721 f_vty_expect_session_active(UPFVTY, s);
722 f_session_del(s);
723 f_vty_expect_no_active_sessions(UPFVTY);
724 f_vty_expect_no_gtp_actions(UPFVTY);
725 f_assoc_release();
726 setverdict(pass);
727}
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100728testcase TC_session_est_tunend() runs on test_CT {
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200729 var CPF_ConnHdlr vc_conn;
730
731 f_init(guard_timeout := 15.0);
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100732 vc_conn := f_start_handler(refers(f_tc_session_est_tunend));
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200733 vc_conn.done;
734 f_shutdown_helper();
735}
736
737/* Verify that releasing a PFCP Association also releases all its sessions and GTP actions. */
738private function f_tc_session_term_by_assoc_rel(charstring id) runs on CPF_ConnHdlr {
739 f_assoc_setup();
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100740 var PFCP_session s := f_session_est_tunend();
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200741 f_sleep(1.0);
742 f_vty_expect_session_active(UPFVTY, s);
743 f_assoc_release();
744 f_vty_expect_no_active_sessions(UPFVTY);
745 f_vty_expect_no_gtp_actions(UPFVTY);
746 setverdict(pass);
747}
748testcase TC_session_term_by_assoc_rel() runs on test_CT {
749 var CPF_ConnHdlr vc_conn;
750
751 f_init(guard_timeout := 15.0);
752 vc_conn := f_start_handler(refers(f_tc_session_term_by_assoc_rel));
753 vc_conn.done;
754 f_shutdown_helper();
755}
756
757/* Verify that PFCP Sessions with a src-interface other than ACCESS or CORE are ACKed by osmo-upf but have no effect. */
758private function f_tc_session_est_noop(charstring id) runs on CPF_ConnHdlr {
759 f_assoc_setup();
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100760 var PFCP_session s := f_create_PFCP_session_tunend();
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200761 f_session_est(s, f_ruleset_noop());
762
763 f_sleep(1.0);
764 f_vty_expect_session_status(UPFVTY, s, PFCP_session_inactive);
765
766 f_session_del(s);
767 f_vty_expect_no_active_sessions(UPFVTY);
768 f_vty_expect_no_gtp_actions(UPFVTY);
769 f_assoc_release();
770 setverdict(pass);
771}
772testcase TC_session_est_noop() runs on test_CT {
773 var CPF_ConnHdlr vc_conn;
774
775 f_init(guard_timeout := 15.0);
776 vc_conn := f_start_handler(refers(f_tc_session_est_noop));
777 vc_conn.done;
778 f_shutdown_helper();
779}
780
781control {
782 execute( TC_assoc_node_id_v4() );
783 execute( TC_assoc_node_id_fqdn() );
Neels Hofmeyrb1dc8262022-11-26 01:08:53 +0100784 execute( TC_session_est_tunend() );
Neels Hofmeyr2d292742022-06-07 23:58:05 +0200785 execute( TC_session_term_by_assoc_rel() );
786 execute( TC_session_est_noop() );
787}
788
789}