[UPF] Handle framed routes

This commit is contained in:
mitmitmitm 2023-01-18 12:32:42 +01:00 committed by Sukchan Lee
parent 3e980e006f
commit 990abbab2c
7 changed files with 395 additions and 8 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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,

View File

@ -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);

View File

@ -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;