diff --git a/lib/pfcp/context.c b/lib/pfcp/context.c index 92d425557..173b8da3b 100644 --- a/lib/pfcp/context.c +++ b/lib/pfcp/context.c @@ -1004,6 +1004,8 @@ void ogs_pfcp_pdr_associate_qer(ogs_pfcp_pdr_t *pdr, ogs_pfcp_qer_t *qer) void ogs_pfcp_pdr_remove(ogs_pfcp_pdr_t *pdr) { + int i; + ogs_assert(pdr); ogs_assert(pdr->sess); @@ -1033,6 +1035,24 @@ void ogs_pfcp_pdr_remove(ogs_pfcp_pdr_t *pdr) if (pdr->id_node) ogs_pool_free(&pdr->sess->pdr_id_pool, pdr->id_node); + if (pdr->ipv4_framed_routes) { + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!pdr->ipv4_framed_routes[i]) + break; + ogs_free(pdr->ipv4_framed_routes[i]); + } + ogs_free(pdr->ipv4_framed_routes); + } + + if (pdr->ipv6_framed_routes) { + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!pdr->ipv6_framed_routes[i]) + break; + ogs_free(pdr->ipv6_framed_routes[i]); + } + ogs_free(pdr->ipv6_framed_routes); + } + ogs_pool_free(&ogs_pfcp_pdr_pool, pdr); } diff --git a/lib/pfcp/context.h b/lib/pfcp/context.h index d0a9643a1..81f260d63 100644 --- a/lib/pfcp/context.h +++ b/lib/pfcp/context.h @@ -160,6 +160,9 @@ typedef struct ogs_pfcp_pdr_s { ogs_pfcp_ue_ip_addr_t ue_ip_addr; int ue_ip_addr_len; + char **ipv4_framed_routes; + char **ipv6_framed_routes; + ogs_pfcp_f_teid_t f_teid; int f_teid_len; diff --git a/lib/pfcp/handler.c b/lib/pfcp/handler.c index fe01e8881..86fd1ce4a 100644 --- a/lib/pfcp/handler.c +++ b/lib/pfcp/handler.c @@ -478,6 +478,58 @@ ogs_pfcp_pdr_t *ogs_pfcp_handle_create_pdr(ogs_pfcp_sess_t *sess, pdr->ue_ip_addr_len); } + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!pdr->ipv4_framed_routes || !pdr->ipv4_framed_routes[i]) + break; + ogs_free(pdr->ipv4_framed_routes[i]); + pdr->ipv4_framed_routes[i] = NULL; + } + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!pdr->ipv6_framed_routes || !pdr->ipv6_framed_routes[i]) + break; + ogs_free(pdr->ipv6_framed_routes[i]); + pdr->ipv6_framed_routes[i] = NULL; + } + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + char *route; + + if (!message->pdi.framed_route[i].presence) + break; + + if (!pdr->ipv4_framed_routes) { + pdr->ipv4_framed_routes = ogs_calloc( + OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI, sizeof(pdr->ipv4_framed_routes[0])); + ogs_assert(pdr->ipv4_framed_routes); + } + route = ogs_malloc(message->pdi.framed_route[i].len + 1); + ogs_assert(route); + memcpy(route, message->pdi.framed_route[i].data, + message->pdi.framed_route[i].len); + route[message->pdi.framed_route[i].len] = '\0'; + pdr->ipv4_framed_routes[i] = route; + } + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + char *route; + + if (!message->pdi.framed_ipv6_route[i].presence) + break; + + if (!pdr->ipv6_framed_routes) { + pdr->ipv6_framed_routes = ogs_calloc( + OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI, sizeof(ogs_ipsubnet_t)); + ogs_assert(pdr->ipv6_framed_routes); + } + route = ogs_malloc(message->pdi.framed_ipv6_route[i].len + 1); + ogs_assert(route); + memcpy(route, message->pdi.framed_ipv6_route[i].data, + message->pdi.framed_ipv6_route[i].len); + route[message->pdi.framed_ipv6_route[i].len] = '\0'; + pdr->ipv6_framed_routes[i] = route; + } + memset(&pdr->outer_header_removal, 0, sizeof(pdr->outer_header_removal)); pdr->outer_header_removal_len = 0; diff --git a/src/upf/context.c b/src/upf/context.c index f9ae0c93a..c907faf11 100644 --- a/src/upf/context.c +++ b/src/upf/context.c @@ -44,6 +44,7 @@ void upf_context_init(void) ogs_pfcp_self()->up_function_features.empu = 1; ogs_pfcp_self()->up_function_features.mnop = 1; ogs_pfcp_self()->up_function_features.vtime = 1; + ogs_pfcp_self()->up_function_features.frrt = 1; ogs_pfcp_self()->up_function_features_len = 4; ogs_list_init(&self.sess_list); @@ -61,6 +62,15 @@ void upf_context_init(void) context_initialized = 1; } +static void free_upf_route_trie_node(struct upf_route_trie_node *node) +{ + if (!node) + return; + free_upf_route_trie_node(node->left); + free_upf_route_trie_node(node->right); + ogs_free(node); +} + void upf_context_final(void) { ogs_assert(context_initialized == 1); @@ -76,6 +86,9 @@ void upf_context_final(void) ogs_assert(self.ipv6_hash); ogs_hash_destroy(self.ipv6_hash); + free_upf_route_trie_node(self.ipv4_framed_routes); + free_upf_route_trie_node(self.ipv6_framed_routes); + ogs_pool_final(&upf_sess_pool); context_initialized = 0; @@ -208,6 +221,9 @@ int upf_sess_remove(upf_sess_t *sess) ogs_pfcp_ue_ip_free(sess->ipv6); } + upf_sess_set_ue_ipv4_framed_routes(sess, NULL); + upf_sess_set_ue_ipv6_framed_routes(sess, NULL); + ogs_pfcp_pool_final(&sess->pfcp); ogs_pool_free(&upf_sess_pool, sess); @@ -259,16 +275,66 @@ upf_sess_t *upf_sess_find_by_upf_n4_seid(uint64_t seid) upf_sess_t *upf_sess_find_by_ipv4(uint32_t addr) { + upf_sess_t *ret; + struct upf_route_trie_node *trie = self.ipv4_framed_routes; + const int nbits = sizeof(addr) << 3; + int i; + ogs_assert(self.ipv4_hash); - return (upf_sess_t *)ogs_hash_get(self.ipv4_hash, &addr, OGS_IPV4_LEN); + + ret = ogs_hash_get(self.ipv4_hash, &addr, OGS_IPV4_LEN); + if (ret) + return ret; + + for (i = 0; i <= nbits; i++) { + int bit = nbits - i - 1; + + if (!trie) + break; + if (trie->sess) + ret = trie->sess; + if (i == nbits) + break; + + if ((1 << bit) & be32toh(addr)) + trie = trie->right; + else + trie = trie->left; + } + return ret; } upf_sess_t *upf_sess_find_by_ipv6(uint32_t *addr6) { + upf_sess_t *ret = NULL; + struct upf_route_trie_node *trie = self.ipv6_framed_routes; + int i; + const int chunk_size = sizeof(*addr6) << 3; + ogs_assert(self.ipv6_hash); ogs_assert(addr6); - return (upf_sess_t *)ogs_hash_get( + ret = ogs_hash_get( self.ipv6_hash, addr6, OGS_IPV6_DEFAULT_PREFIX_LEN >> 3); + if (ret) + return ret; + + for (i = 0; i <= OGS_IPV6_128_PREFIX_LEN; i++) { + int part = i / chunk_size; + int bit = (OGS_IPV6_128_PREFIX_LEN - i - 1) % chunk_size; + + if (!trie) + break; + if (trie->sess) + ret = trie->sess; + if (i == OGS_IPV6_128_PREFIX_LEN) + break; + + if ((1 << bit) & be32toh(addr6[part])) + trie = trie->right; + else + trie = trie->left; + } + return ret; } upf_sess_t *upf_sess_add_by_message(ogs_pfcp_message_t *message) @@ -408,6 +474,184 @@ uint8_t upf_sess_set_ue_ip(upf_sess_t *sess, return cause_value; } +/* Remove amd free framed ROUTE from TRIE. It isn't an error if the framed + route doesn't exist in TRIE. */ +static void free_framed_route_from_trie(ogs_ipsubnet_t *route) +{ + const int chunk_size = sizeof(route->sub[0]) << 3; + const int is_ipv4 = route->family == AF_INET; + const int nbits = is_ipv4 ? chunk_size : OGS_IPV6_128_PREFIX_LEN; + struct upf_route_trie_node **trie = + is_ipv4 ? &self.ipv4_framed_routes : &self.ipv6_framed_routes; + + struct upf_route_trie_node **to_free_tries[OGS_IPV6_128_PREFIX_LEN + 1]; + int free_from = 0; + int i = 0; + + for (i = 0; i <= nbits; i++) { + int part = i / chunk_size; + int bit = (nbits - i - 1) % chunk_size; + + if (!*trie) + break; + to_free_tries[i] = trie; + + if (i == nbits || + ((1 << bit) & be32toh(route->mask[part])) == 0) { + (*trie)->sess = NULL; + if ((*trie)->left || (*trie)->right) + free_from = i + 1; + i++; + break; + } + + if ((1 << bit) & be32toh(route->sub[part])) { + if ((*trie)->left || (*trie)->sess) + free_from = i + 1; + trie = &(*trie)->right; + } else { + if ((*trie)->right || (*trie)->sess) + free_from = i + 1; + trie = &(*trie)->left; + } + } + + for (i = i - 1; i >= free_from; i--) { + trie = to_free_tries[i]; + ogs_free(*trie); + *trie = NULL; + } +} + +static void add_framed_route_to_trie(ogs_ipsubnet_t *route, upf_sess_t *sess) +{ + const int chunk_size = sizeof(route->sub[0]) << 3; + const int is_ipv4 = route->family == AF_INET; + const int nbits = is_ipv4 ? chunk_size : OGS_IPV6_128_PREFIX_LEN; + struct upf_route_trie_node **trie = + is_ipv4 ? &self.ipv4_framed_routes : &self.ipv6_framed_routes; + int i = 0; + + for (i = 0; i <= nbits; i++) { + int part = i / chunk_size; + int bit = (- i - 1) % chunk_size; + + if (!*trie) + *trie = ogs_calloc(1, sizeof(**trie)); + + if (i == nbits || + ((1 << bit) & be32toh(route->mask[part])) == 0) { + (*trie)->sess = sess; + break; + } + + if ((1 << bit) & be32toh(route->sub[part])) { + trie = &(*trie)->right; + } else { + trie = &(*trie)->left; + } + } +} + +static int parse_framed_route(ogs_ipsubnet_t *subnet, const char *framed_route) +{ + char *mask = ogs_strdup(framed_route); + char *addr = strsep(&mask, "/"); + int rv; + + rv = ogs_ipsubnet(subnet, addr, mask); + ogs_free(addr); + return rv; +} + +uint8_t upf_sess_set_ue_ipv4_framed_routes(upf_sess_t *sess, + char *framed_routes[]) +{ + int i = 0, j = 0, rv; + uint8_t cause_value = OGS_PFCP_CAUSE_REQUEST_ACCEPTED; + + ogs_assert(sess); + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!sess->ipv4_framed_routes || !sess->ipv4_framed_routes[i].family) + break; + free_framed_route_from_trie(&sess->ipv4_framed_routes[i]); + memset(&sess->ipv4_framed_routes[i], 0, + sizeof(sess->ipv4_framed_routes[i])); + } + + for (i = 0, j = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!framed_routes || !framed_routes[i]) + break; + + if (sess->ipv4_framed_routes == NULL) { + sess->ipv4_framed_routes = ogs_calloc( + OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI, sizeof(ogs_ipsubnet_t)); + ogs_assert(sess->ipv4_framed_routes); + } + + rv = parse_framed_route(&sess->ipv4_framed_routes[j], framed_routes[i]); + + if (rv != OGS_OK) { + ogs_warn("Ignoring invalid framed route %s", framed_routes[i]); + memset(&sess->ipv4_framed_routes[j], 0, + sizeof(sess->ipv4_framed_routes[j])); + continue; + } + add_framed_route_to_trie(&sess->ipv4_framed_routes[j], sess); + j++; + } + if (j == 0 && sess->ipv4_framed_routes) { + ogs_free(sess->ipv4_framed_routes); + sess->ipv4_framed_routes = NULL; + } + + return cause_value; +} + +uint8_t upf_sess_set_ue_ipv6_framed_routes(upf_sess_t *sess, + char *framed_routes[]) +{ + int i = 0, j = 0, rv; + uint8_t cause_value = OGS_PFCP_CAUSE_REQUEST_ACCEPTED; + + ogs_assert(sess); + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!sess->ipv6_framed_routes || !sess->ipv6_framed_routes[i].family) + break; + free_framed_route_from_trie(&sess->ipv6_framed_routes[i]); + } + + for (i = 0, j = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + if (!framed_routes || !framed_routes[i]) + break; + + if (sess->ipv6_framed_routes == NULL) { + sess->ipv6_framed_routes = ogs_calloc( + OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI, sizeof(ogs_ipsubnet_t)); + ogs_assert(sess->ipv6_framed_routes); + } + + rv = parse_framed_route(&sess->ipv6_framed_routes[j], framed_routes[i]); + + if (rv != OGS_OK) { + ogs_warn("Ignoring invalid framed route %s", framed_routes[i]); + memset(&sess->ipv6_framed_routes[j], 0, + sizeof(sess->ipv6_framed_routes[j])); + continue; + } + add_framed_route_to_trie(&sess->ipv6_framed_routes[j], sess); + j++; + } + if (j == 0 && sess->ipv6_framed_routes) { + ogs_free(sess->ipv6_framed_routes); + sess->ipv6_framed_routes = NULL; + } + + return cause_value; +} + void upf_sess_urr_acc_add(upf_sess_t *sess, ogs_pfcp_urr_t *urr, size_t size, bool is_uplink) { upf_sess_urr_acc_t *urr_acc = &sess->urr_acc[urr->id]; diff --git a/src/upf/context.h b/src/upf/context.h index 6fa846219..49747092c 100644 --- a/src/upf/context.h +++ b/src/upf/context.h @@ -45,15 +45,26 @@ extern int __upf_log_domain; #undef OGS_LOG_DOMAIN #define OGS_LOG_DOMAIN __upf_log_domain -typedef struct upf_context_s { - ogs_hash_t *seid_hash; /* hash table (SEID) */ - ogs_hash_t *f_seid_hash; /* hash table (F-SEID) */ - ogs_hash_t *ipv4_hash; /* hash table (IPv4 Address) */ - ogs_hash_t *ipv6_hash; /* hash table (IPv6 Address) */ +struct upf_route_trie_node; - ogs_list_t sess_list; +typedef struct upf_context_s { + ogs_hash_t *seid_hash; /* hash table (SEID) */ + ogs_hash_t *f_seid_hash; /* hash table (F-SEID) */ + ogs_hash_t *ipv4_hash; /* hash table (IPv4 Address) */ + ogs_hash_t *ipv6_hash; /* hash table (IPv6 Address) */ + struct upf_route_trie_node *ipv4_framed_routes; /* IPv4 framed routes trie */ + struct upf_route_trie_node *ipv6_framed_routes; /* IPv6 framed routes trie */ + + ogs_list_t sess_list; } upf_context_t; +/* trie mapping from IP framed routes to session. */ +struct upf_route_trie_node { + struct upf_route_trie_node *left; + struct upf_route_trie_node *right; + upf_sess_t *sess; +}; + /* Accounting: */ typedef struct upf_sess_urr_acc_s { bool reporting_enabled; @@ -99,6 +110,9 @@ typedef struct upf_sess_s { ogs_pfcp_ue_ip_t *ipv4; ogs_pfcp_ue_ip_t *ipv6; + ogs_ipsubnet_t *ipv4_framed_routes; + ogs_ipsubnet_t *ipv6_framed_routes; + char *gx_sid; /* Gx Session ID */ ogs_pfcp_node_t *pfcp_node; @@ -126,6 +140,10 @@ upf_sess_t *upf_sess_find_by_ipv6(uint32_t *addr6); uint8_t upf_sess_set_ue_ip(upf_sess_t *sess, uint8_t session_type, ogs_pfcp_pdr_t *pdr); +uint8_t upf_sess_set_ue_ipv4_framed_routes(upf_sess_t *sess, + char *framed_routes[]); +uint8_t upf_sess_set_ue_ipv6_framed_routes(upf_sess_t *sess, + char *framed_routes[]); void upf_sess_urr_acc_add(upf_sess_t *sess, ogs_pfcp_urr_t *urr, size_t size, bool is_uplink); void upf_sess_urr_acc_fill_usage_report(upf_sess_t *sess, const ogs_pfcp_urr_t *urr, diff --git a/src/upf/gtp-path.c b/src/upf/gtp-path.c index 2626fa3cb..cd99cad01 100644 --- a/src/upf/gtp-path.c +++ b/src/upf/gtp-path.c @@ -61,6 +61,36 @@ static ogs_pkbuf_pool_t *packet_pool = NULL; static void upf_gtp_handle_multicast(ogs_pkbuf_t *recvbuf); +static int check_framed_routes(upf_sess_t *sess, int family, uint32_t *addr) +{ + int i = 0; + ogs_ipsubnet_t *routes = family == AF_INET ? + sess->ipv4_framed_routes : sess->ipv6_framed_routes; + + if (!routes) + return false; + + for (i = 0; i < OGS_MAX_NUM_OF_FRAMED_ROUTES_IN_PDI; i++) { + uint32_t *sub = routes[i].sub; + uint32_t *mask = routes[i].mask; + + if (!routes[i].family) + break; + + if (family == AF_INET) { + if (sub[0] == (addr[0] & mask[0])) + return true; + } else { + if (sub[0] == (addr[0] & mask[0]) && + sub[1] == (addr[1] & mask[1]) && + sub[2] == (addr[2] & mask[2]) && + sub[3] == (addr[3] & mask[3])) + return true; + } + } + return false; +} + static uint16_t _get_eth_type(uint8_t *data, uint len) { if (len > ETHER_HDR_LEN) { struct ether_header *hdr = (struct ether_header*)data; @@ -443,6 +473,8 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data) if (src_addr[0] == sess->ipv4->addr[0]) { /* Source IP address should be matched in uplink */ + } else if (check_framed_routes(sess, AF_INET, src_addr)) { + /* Or source IP address should match a framed route */ } else { ogs_error("[DROP] Source IP-%d Spoofing APN:%s SrcIf:%d DstIf:%d TEID:0x%x", ip_h->ip_v, pdr->dnn, pdr->src_if, far->dst_if, teid); @@ -519,6 +551,8 @@ static void _gtpv1_u_recv_cb(short when, ogs_socket_t fd, void *data) * If Global address * 64 bit prefix should be matched */ + } else if (check_framed_routes(sess, AF_INET6, src_addr)) { + /* Or source IP address should match a framed route */ } else { ogs_error("[DROP] Source IP-%d Spoofing APN:%s SrcIf:%d DstIf:%d TEID:0x%x", ip_h->ip_v, pdr->dnn, pdr->src_if, far->dst_if, teid); diff --git a/src/upf/n4-handler.c b/src/upf/n4-handler.c index d11f7034b..6f1f35982 100644 --- a/src/upf/n4-handler.c +++ b/src/upf/n4-handler.c @@ -138,6 +138,22 @@ void upf_n4_handle_session_establishment_request( } } + if (pdr->ipv4_framed_routes) { + cause_value = + upf_sess_set_ue_ipv4_framed_routes(sess, + pdr->ipv4_framed_routes); + if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) + goto cleanup; + } + + if (pdr->ipv6_framed_routes) { + cause_value = + upf_sess_set_ue_ipv6_framed_routes(sess, + pdr->ipv6_framed_routes); + if (cause_value != OGS_PFCP_CAUSE_REQUEST_ACCEPTED) + goto cleanup; + } + /* Setup UPF-N3-TEID & QFI Hash */ if (pdr->f_teid_len) { ogs_pfcp_object_type_e type = OGS_PFCP_OBJ_SESS_TYPE;