/* Test the SGSN routing ares */ /* * (C) 2024 by sysmocom s.f.m.c. GmbH * All Rights Reserved * Author: Alexander Couzens * * 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 Affero 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 . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void *tall_sgsn_ctx; struct sgsn_instance *sgsn; struct paging_exp { uint16_t nsei; uint16_t bvci; /* paged when we send one paging request */ bool paged; /* valid when this entry contains valid data */ bool valid; }; struct paging_exp g_paging[4]; static void cleanup_test(void) { TALLOC_FREE(sgsn); } /* Create RA, free RA */ static void test_routing_area_create(void) { struct sgsn_ra *ra; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 23 }, .rac = 42 }; printf("Testing Routing Area create/free\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); ra = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); sgsn_ra_free(ra); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); /* Cleanup */ cleanup_test(); } static void test_routing_area_free_empty(void) { struct sgsn_ra *ra; struct sgsn_ra_cell *cell_a; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; uint16_t cell_id = 9999; uint16_t nsei = 2, bvci = 3; printf("Testing Routing Area create/free\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); ra = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci); OSMO_ASSERT(cell_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); OSMO_ASSERT(llist_count(&ra->cells_alive_list) == 1); sgsn_ra_free(ra); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); ra = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); cell_a = sgsn_ra_cell_alloc_geran(ra, cell_id, nsei, bvci); OSMO_ASSERT(cell_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); OSMO_ASSERT(llist_count(&ra->cells_alive_list) == 1); sgsn_ra_free(ra); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); cleanup_test(); } /* Create RA, use different find functiosn, free RA */ static void test_routing_area_find(void) { struct sgsn_ra *ra_a, *ra_b; struct sgsn_ra_cell *cell_a, *cell_b; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; uint16_t cell_id = 9999, cell_id_not_found = 44; struct osmo_cell_global_id_ps cgi_ps = { .rai = rai, .cell_identity = cell_id, }; struct osmo_cell_global_id cgi = { .lai = rai.lac, .cell_identity = cell_id }; uint16_t nsei = 2, bvci = 3; printf("Testing Routing Area find\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); ra_a = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); ra_b = sgsn_ra_get_ra(&rai); OSMO_ASSERT(ra_a == ra_b); cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci); OSMO_ASSERT(cell_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); cell_b = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_b == cell_a); cell_b = sgsn_ra_geran_get_cell_by_ra(ra_a, cgi.cell_identity); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_b == cell_a); cell_b = sgsn_ra_geran_get_cell_by_cgi(&cgi); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_b == cell_a); cell_b = sgsn_ra_geran_get_cell_by_lai(&cgi.lai, cgi.cell_identity); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_b == cell_a); sgsn_ra_free(ra_a); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); /* try to search for a cell id which isn't present */ cgi.cell_identity = cell_id_not_found; cgi_ps.cell_identity = cell_id_not_found; ra_a = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); cell_a = sgsn_ra_cell_alloc_geran(ra_a, cell_id, nsei, bvci); OSMO_ASSERT(cell_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); cell_b = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(!cell_b); cell_b = sgsn_ra_geran_get_cell_by_ra(ra_a, cgi_ps.cell_identity); OSMO_ASSERT(!cell_b); cell_b = sgsn_ra_geran_get_cell_by_cgi(&cgi); OSMO_ASSERT(!cell_b); cell_b = sgsn_ra_geran_get_cell_by_lai(&cgi.lai, cgi.cell_identity); OSMO_ASSERT(!cell_b); /* try to find for a different RAC */ cgi_ps.rai.rac = 45; rai.rac = 46; cell_b = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(!cell_b); ra_b = sgsn_ra_get_ra(&rai); OSMO_ASSERT(!ra_b); /* try to find for different LAC */ cgi.lai.lac = 46; cell_b = sgsn_ra_geran_get_cell_by_cgi(&cgi); OSMO_ASSERT(!cell_b); sgsn_ra_free(ra_a); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); cleanup_test(); } static void test_routing_area_reset_ind(void) { struct sgsn_ra *ra_a; struct sgsn_ra_cell *cell_a, *cell_b; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; uint16_t cell_id = 9999; struct osmo_cell_global_id_ps cgi_ps = { .rai = rai, .cell_identity = cell_id, }; struct osmo_cell_global_id cgi = { .lai = rai.lac, .cell_identity = cell_id }; uint16_t nsei = 2, bvci = 3; int rc; printf("Testing Routing Area BSSGP BVC RESET IND\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); ra_a = sgsn_ra_find_or_create(&rai, RA_TYPE_GERAN_Gb); OSMO_ASSERT(ra_a); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); OSMO_ASSERT(llist_count(&ra_a->cells_alive_list) == 0); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); OSMO_ASSERT(llist_count(&ra_a->cells_alive_list) == 1); cell_a = sgsn_ra_geran_get_cell_by_cgi(&cgi); OSMO_ASSERT(cell_a); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); cell_b = sgsn_ra_geran_get_cell_by_cgi(&cgi); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_a == cell_b); sgsn_ra_free(ra_a); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); ra_a = sgsn_ra_get_ra(&cgi_ps.rai); sgsn_ra_free(ra_a); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); cleanup_test(); } void test_routing_area_nsei_free(void) { struct sgsn_ra *ra_a; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; uint16_t cell_id = 9999; struct osmo_cell_global_id_ps cgi_ps = { .rai = rai, .cell_identity = cell_id, }; uint16_t nsei = 2, bvci = 3; int rc; printf("Testing Routing Area nsei failure\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); ra_a = sgsn_ra_get_ra(&cgi_ps.rai); OSMO_ASSERT(llist_count(&ra_a->cells_alive_list) == 1); rc = sgsn_ra_geran_nsei_failure_ind(nsei); OSMO_ASSERT(rc == 0); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); rc = sgsn_ra_geran_nsei_failure_ind(nsei); OSMO_ASSERT(rc == -ENOENT); OSMO_ASSERT(llist_empty(&sgsn->routing_area->ra_list)); cleanup_test(); } /* BSSGP Paging RA */ int bssgp_tx_paging(uint16_t nsei, uint16_t _bvci, struct bssgp_paging_info *pinfo) { bool found = false; OSMO_ASSERT(pinfo); fprintf(stderr, "Tx paging for nsei %05u / bvci %05u\n", nsei, pinfo->bvci); /* match against list of expect pagings */ for (int i = 0; i < ARRAY_SIZE(g_paging); i++) { struct paging_exp *exp = &g_paging[i]; if (exp->paged || !exp->valid) continue; if (exp->nsei == nsei && exp->bvci == pinfo->bvci) { exp->paged = true; found = true; break; } } OSMO_ASSERT(found); return 0; } static void check_paging(void) { for (int i = 0; i < ARRAY_SIZE(g_paging); i++) { struct paging_exp *exp = &g_paging[i]; if (!exp->valid) continue; OSMO_ASSERT(exp->paged) } } void test_routing_area_paging(void) { struct sgsn_mm_ctx *mmctx; struct osmo_routing_area_id rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; uint16_t cell_id = 9999; struct osmo_cell_global_id_ps cgi_ps = { .rai = rai, .cell_identity = cell_id, }; uint16_t nsei = 2, bvci = 3; int rc; printf("Testing Routing Area paging\n"); sgsn = sgsn_instance_alloc(tall_sgsn_ctx); memset(g_paging, 0, sizeof(g_paging)); g_paging[0].bvci = bvci; g_paging[0].nsei = nsei; g_paging[0].valid = true; g_paging[0].paged = false; rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); cgi_ps.cell_identity++; rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci+1, &cgi_ps); OSMO_ASSERT(rc == 0); g_paging[1].bvci = bvci+1; g_paging[1].nsei = nsei; g_paging[1].valid = true; g_paging[1].paged = false; mmctx = sgsn_mm_ctx_alloc_gb(0xc0001234, &rai); sgsn_ra_geran_page_ra(&rai, mmctx); check_paging(); sgsn_mm_ctx_cleanup_free(mmctx); cleanup_test(); } /* check if a GERAN cell got removed when sending a Reset Ind on Sig BVCI */ void test_routing_area_geran_geran_sig_reset(void) { int rc; /* GERAN */ struct osmo_routing_area_id geran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_cell_global_id_ps cgi_ps = { .rai = geran_rai, .cell_identity = 9998, }; uint16_t nsei = 2, bvci = 5; struct sgsn_ra_cell *cell; sgsn = sgsn_instance_alloc(tall_sgsn_ctx); printf("Testing Routing Area GERAN BVCI Signalling Reset Ind\n"); printf(" Registering GERAN RA/cell via BVCI 5/BVC Reset Ind\n"); sgsn_ra_geran_bvc_sign_reset_ind(nsei); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); printf(" Checking cell on BVCI 5\n"); cell = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(cell); OSMO_ASSERT(cell->ran_type == RA_TYPE_GERAN_Gb); OSMO_ASSERT(cell->u.geran.bvci == bvci); printf(" Ensure only 1 RA is present\n"); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); printf(" Ensure only 1 cell is present\n"); OSMO_ASSERT(llist_count(&cell->ra->cells_alive_list) == 1); printf(" Drop all cells via BVC Reset Ind on Signalling BVCI\n"); sgsn_ra_geran_bvc_sign_reset_ind(nsei); printf(" Ensure only 0 RAs are present\n"); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 0); cleanup_test(); } /* check if a GERAN cell X can changed it's BVCI by BVC Reset Ind */ void test_routing_area_geran_geran_bvci_change(void) { int rc; /* GERAN */ struct osmo_routing_area_id geran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_cell_global_id_ps cgi_ps = { .rai = geran_rai, .cell_identity = 9998, }; uint16_t nsei = 2, bvci_a = 3, bvci_b = 4; struct sgsn_ra_cell *cell_a, *cell_b; sgsn = sgsn_instance_alloc(tall_sgsn_ctx); printf("Testing Routing Area GERAN to GERAN (BVCI change)\n"); printf(" Registering GERAN RA/cell via BVCI A/BVC Reset Ind\n"); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci_a, &cgi_ps); OSMO_ASSERT(rc == 0); printf(" Checking cell on BVCI A\n"); cell_a = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(cell_a); OSMO_ASSERT(cell_a->ran_type == RA_TYPE_GERAN_Gb); OSMO_ASSERT(cell_a->u.geran.bvci == bvci_a); printf(" Ensure only 1 RA is present\n"); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); printf(" Ensure only 1 cell is present\n"); OSMO_ASSERT(llist_count(&cell_a->ra->cells_alive_list) == 1); printf(" Registering GERAN RA/cell via BVCI B/BVC Reset Ind\n"); rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci_b, &cgi_ps); OSMO_ASSERT(rc == 0); printf(" Checking cell on BVCI B\n"); cell_b = sgsn_ra_geran_get_cell_by_cgi_ps(&cgi_ps); OSMO_ASSERT(cell_b); OSMO_ASSERT(cell_b->ran_type == RA_TYPE_GERAN_Gb); OSMO_ASSERT(cell_b->u.geran.bvci == bvci_b); printf(" Ensure only 1 RA is present\n"); OSMO_ASSERT(llist_count(&sgsn->routing_area->ra_list) == 1); printf(" Ensure only 1 cell is present\n"); OSMO_ASSERT(llist_count(&cell_b->ra->cells_alive_list) == 1); cleanup_test(); } /* check if UTRAN RA gets rejected, if a GERAN RA/cell with the same LAC is already registered * The SGSN does not support the same LAC/RA for GERAN and UTRAN at the same time. */ void test_routing_area_mv_utran_geran_reject(void) { int rc; /* GERAN */ struct osmo_routing_area_id geran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_cell_global_id_ps cgi_ps = { .rai = geran_rai, .cell_identity = 9998, }; uint16_t nsei = 2, bvci = 3; /* UTRAN */ struct osmo_routing_area_id utran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_rnc_id rnc_id = { .plmn = utran_rai.lac.plmn, .rnc_id = 2222 }; sgsn = sgsn_instance_alloc(tall_sgsn_ctx); /* Registering UTRAN RA */ rc = sgsn_ra_utran_register(&utran_rai, &rnc_id); OSMO_ASSERT(rc == 0); /* Registering GERAN RA/cell via BVC Reset Ind (should fail) */ rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc != 0); cleanup_test(); } /* check if a UTRAN RA with the same LAC as an already register GERAN RA gets rejected */ void test_routing_area_mv_geran_utran_reject(void) { int rc; /* GERAN */ struct osmo_routing_area_id geran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_cell_global_id_ps cgi_ps = { .rai = geran_rai, .cell_identity = 9998, }; uint16_t nsei = 2, bvci = 3; /* UTRAN */ struct osmo_routing_area_id utran_rai = { .lac = { .plmn = { .mcc = 262, .mnc = 42, .mnc_3_digits = false }, .lac = 24 }, .rac = 43 }; struct osmo_rnc_id rnc_id = { .plmn = utran_rai.lac.plmn, .rnc_id = 2222 }; sgsn = sgsn_instance_alloc(tall_sgsn_ctx); /* Registering GERAN RA/cell via BVC Reset Ind */ rc = sgsn_ra_geran_bvc_cell_reset_ind(nsei, bvci, &cgi_ps); OSMO_ASSERT(rc == 0); /* Registering UTRAN RA (should fail) */ rc = sgsn_ra_utran_register(&utran_rai, &rnc_id); OSMO_ASSERT(rc != 0); cleanup_test(); } static struct log_info_cat gprs_categories[] = { [DMM] = { .name = "DMM", .description = "Layer3 Mobility Management (MM)", .color = "\033[1;33m", .enabled = 1, .loglevel = LOGL_DEBUG, }, [DPAG] = { .name = "DPAG", .description = "Paging Subsystem", .color = "\033[1;38m", .enabled = 1, .loglevel = LOGL_NOTICE, }, [DREF] = { .name = "DREF", .description = "Reference Counting", .enabled = 0, .loglevel = LOGL_NOTICE, }, [DGPRS] = { .name = "DGPRS", .description = "GPRS Packet Service", .enabled = 1, .loglevel = LOGL_DEBUG, }, [DLLC] = { .name = "DLLC", .description = "GPRS Logical Link Control Protocol (LLC)", .enabled = 1, .loglevel = LOGL_DEBUG, }, [DRA] = { .name = "DRA", .description = "Routing Area", .enabled = 1, .loglevel = LOGL_DEBUG, }, }; static struct log_info info = { .cat = gprs_categories, .num_cat = ARRAY_SIZE(gprs_categories), }; static struct vty_app_info vty_info = { .name = "testSGSN", }; int main(int argc, char **argv) { void *osmo_sgsn_ctx; void *msgb_ctx; osmo_sgsn_ctx = talloc_named_const(NULL, 0, "osmo_sgsn"); osmo_init_logging2(osmo_sgsn_ctx, &info); tall_sgsn_ctx = talloc_named_const(osmo_sgsn_ctx, 0, "sgsn"); msgb_ctx = msgb_talloc_ctx_init(osmo_sgsn_ctx, 0); vty_init(&vty_info); test_routing_area_create(); test_routing_area_find(); test_routing_area_free_empty(); test_routing_area_reset_ind(); test_routing_area_nsei_free(); test_routing_area_paging(); test_routing_area_geran_geran_sig_reset(); test_routing_area_geran_geran_bvci_change(); #ifdef BUILD_IU test_routing_area_mv_geran_utran_reject(); test_routing_area_mv_utran_geran_reject(); #endif printf("Done\n"); talloc_report_full(osmo_sgsn_ctx, stderr); OSMO_ASSERT(talloc_total_blocks(msgb_ctx) == 1); OSMO_ASSERT(talloc_total_blocks(tall_sgsn_ctx) == 1); return 0; } /* stubs */ struct osmo_prim_hdr; int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx) { abort(); }