Skip to content

Commit

Permalink
[PFCP] Reduce DNS spam for FQDN nodes (open5gs#3431) (open5gs#3664)
Browse files Browse the repository at this point in the history
Each received PFCP message triggered ogs_pfcp_node_find(), causing a DNS
resolution if node_id was FQDN. Under heavy traffic, this could lead to
excessive DNS queries.

- Implement a 300-second refresh interval to avoid repeated DNS lookups.
- Store last_dns_refresh in each node to defer new queries until needed.
- Treat config-based nodes with no Node ID as UNKNOWN, matching them by IP
  alone until ogs_pfcp_node_merge() updates their ID.
- Validate IPv4, IPv6, or FQDN types in ogs_pfcp_node_merge() and reject
  invalid IDs.
- Provide inline code comments for clarity and maintainability.
  • Loading branch information
acetcom committed Jan 18, 2025
1 parent ba6a84d commit 9c370ff
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 91 deletions.
26 changes: 26 additions & 0 deletions lib/core/ogs-sockaddr.c
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,32 @@ bool ogs_sockaddr_is_equal_addr(const void *p, const void *q)
return ogs_sockaddr_compare(a, b, false);
}

bool ogs_sockaddr_check_any_match(
ogs_sockaddr_t *base,
ogs_sockaddr_t *list, const ogs_sockaddr_t *single, bool compare_port)
{
ogs_sockaddr_t *p = NULL;

while (list) {
p = base;
while (p) {
if (ogs_sockaddr_compare(p, list, compare_port) == true)
return true;
p = p->next;
}
list = list->next;
}
if (single) {
p = base;
while (p) {
if (ogs_sockaddr_compare(p, single, compare_port) == true)
return true;
p = p->next;
}
}
return false;
}

static int parse_network(ogs_ipsubnet_t *ipsub, const char *network)
{
/* legacy syntax for ip addrs: a.b.c. ==> a.b.c.0/24 for example */
Expand Down
4 changes: 4 additions & 0 deletions lib/core/ogs-sockaddr.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ socklen_t ogs_sockaddr_len(const void *sa);
bool ogs_sockaddr_is_equal(const void *p, const void *q);
bool ogs_sockaddr_is_equal_addr(const void *p, const void *q);

bool ogs_sockaddr_check_any_match(
ogs_sockaddr_t *base,
ogs_sockaddr_t *list, const ogs_sockaddr_t *single, bool compare_port);

int ogs_ipsubnet(ogs_ipsubnet_t *ipsub,
const char *ipstr, const char *mask_or_numbits);

Expand Down
235 changes: 148 additions & 87 deletions lib/pfcp/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -864,42 +864,12 @@ int ogs_pfcp_context_parse_config(const char *local, const char *remote)
return OGS_OK;
}

/*--------------------------------------------------------------------------
* check_any_match():
* If any node in "list" or the single "single" matches base, return 1
*--------------------------------------------------------------------------
*/
static bool check_any_match(ogs_sockaddr_t *base,
ogs_sockaddr_t *list,
const ogs_sockaddr_t *single)
{
ogs_sockaddr_t *p = NULL;

while (list) {
p = base;
while (p) {
if (ogs_sockaddr_is_equal_addr(p, list))
return true;
p = p->next;
}
list = list->next;
}
if (single) {
p = base;
while (p) {
if (ogs_sockaddr_is_equal_addr(p, single))
return true;
p = p->next;
}
}
return false;
}

/*--------------------------------------------------------------------------
* ogs_pfcp_node_new():
* Create node with config_addr, copy config_addr into node->addr_list
*--------------------------------------------------------------------------
*/
/******************************************************************************
* ogs_pfcp_node_new()
* - Initialize node_id.type to OGS_PFCP_NODE_ID_UNKNOWN
* - So the node can later be updated with a real Node ID via
* ogs_pfcp_node_merge() once we learn it from PFCP messages.
******************************************************************************/
ogs_pfcp_node_t *ogs_pfcp_node_new(ogs_sockaddr_t *config_addr)
{
int rv;
Expand All @@ -926,6 +896,12 @@ ogs_pfcp_node_t *ogs_pfcp_node_new(ogs_sockaddr_t *config_addr)
}
}

/*
* Initialize node->node_id to UNKNOWN, meaning "no Node ID assigned yet".
*/
memset(&node->node_id, 0, sizeof(node->node_id));
node->node_id.type = OGS_PFCP_NODE_ID_UNKNOWN;

ogs_list_init(&node->local_list);
ogs_list_init(&node->remote_list);

Expand All @@ -948,12 +924,11 @@ void ogs_pfcp_node_free(ogs_pfcp_node_t *node)
ogs_pool_free(&ogs_pfcp_node_pool, node);
}

/*--------------------------------------------------------------------------
* ogs_pfcp_node_add():
* Create a new node (with config_addr=NULL), set node_id/from,
* then merge => finally add to list
*--------------------------------------------------------------------------
*/
/******************************************************************************
* ogs_pfcp_node_add()
* - Create a new PFCP node, then call ogs_pfcp_node_merge() to handle
* IPv4/IPv6 or FQDN logic.
******************************************************************************/
ogs_pfcp_node_t *ogs_pfcp_node_add(ogs_list_t *list,
ogs_pfcp_node_id_t *node_id, ogs_sockaddr_t *from)
{
Expand All @@ -962,6 +937,18 @@ ogs_pfcp_node_t *ogs_pfcp_node_add(ogs_list_t *list,
ogs_assert(list);
ogs_assert(node_id && from);

/*
* We only handle IPv4, IPv6, and FQDN types here. If the incoming
* node_id has any other type, we treat it as invalid. This ensures
* we do not merge a node with an unsupported PFCP Node ID.
*/
if (node_id->type != OGS_PFCP_NODE_ID_IPV4 &&
node_id->type != OGS_PFCP_NODE_ID_IPV6 &&
node_id->type != OGS_PFCP_NODE_ID_FQDN) {
ogs_error("Invalid PFCP Node Type = %d", node_id->type);
return NULL;
}

/* Create node with no config_addr initially */
node = ogs_pfcp_node_new(NULL);
if (!node) {
Expand All @@ -971,10 +958,11 @@ ogs_pfcp_node_t *ogs_pfcp_node_add(ogs_list_t *list,
return NULL;
}

/* Store node_id and from_addr */
/* Set node->node_id, reset last_dns_refresh. */
memcpy(&node->node_id, node_id, sizeof(node->node_id));
node->last_dns_refresh = 0;

/* Merge them => fill node->addr_list if conditions are met */
/* Merge addresses => fill node->addr_list if conditions are met */
if (ogs_pfcp_node_merge(node, node_id, from) != OGS_OK) {
ogs_error("ogs_pfcp_node_merge() failed node_id [%s] from [%s]",
ogs_pfcp_node_id_to_string_static(node_id),
Expand All @@ -988,75 +976,137 @@ ogs_pfcp_node_t *ogs_pfcp_node_add(ogs_list_t *list,
return node;
}

/*--------------------------------------------------------------------------
* ogs_pfcp_node_find():
* Find a node in the list whose "addr_list" matches node_id or from_addr
*--------------------------------------------------------------------------
*/
/******************************************************************************
* ogs_pfcp_node_find()
* - No DNS logic here. Merely finds a node by node_id (if provided) and
* checks if 'from' address is in node->addr_list.
******************************************************************************/
ogs_pfcp_node_t *ogs_pfcp_node_find(ogs_list_t *list,
ogs_pfcp_node_id_t *node_id, ogs_sockaddr_t *from)
{
ogs_pfcp_node_t *cur;
ogs_sockaddr_t *nid_list = NULL;
int found = 0;

ogs_assert(list);
ogs_assert(node_id || from);

if (node_id)
nid_list = ogs_pfcp_node_id_to_addrinfo(node_id);

ogs_list_for_each(list, cur) {
if (check_any_match(cur->addr_list, nid_list, from)) {
found = 1;
break;
/*
* If the node currently has a known Node ID (not UNKNOWN)
* and the caller provided a node_id to match, then compare them.
* If they do not match, skip this node. This allows config-based nodes
* (with an UNKNOWN node_id) to be found by IP address alone,
* while nodes with a definite ID must match the incoming node_id.
*/
if (cur->node_id.type != OGS_PFCP_NODE_ID_UNKNOWN && node_id) {
if (!ogs_pfcp_node_id_compare(&cur->node_id, node_id))
continue;
}
}
if (nid_list)
ogs_freeaddrinfo(nid_list);
if (!from)
return cur;

if (found)
return cur;
/* Check if 'from' is in cur->addr_list. */
if (ogs_sockaddr_check_any_match(cur->addr_list, NULL,
from, /* compare_port= */ true)) {
return cur;
}
}

/* No match found. */
return NULL;
}

/*--------------------------------------------------------------------------
/******************************************************************************
* ogs_pfcp_node_merge():
* Merge logic: addr_list + node_id + from_addr
*--------------------------------------------------------------------------
* - If node->addr_list is empty, copy node->config_addr into addr_list first
* - Convert node_id to addresses, then check from_addr
* - If addr_list is empty => merge everything
* - If addr_list not empty => merge only if there's a matching IP
*/
* - If node_id changes to FQDN, we check last_dns_refresh.
* => If 0, do an immediate DNS resolution (first time).
* => If >= 300 seconds passed, do a periodic refresh.
* => Otherwise, skip.
* - If node_id changes to IPv4/IPv6, convert IP addresses immediately.
* - Merge the 'from' address into addr_list if provided.
******************************************************************************/
int ogs_pfcp_node_merge(ogs_pfcp_node_t *node,
ogs_pfcp_node_id_t *node_id, ogs_sockaddr_t *from)
{
ogs_sockaddr_t *nid_list = NULL;
ogs_sockaddr_t single;
ogs_sockaddr_t *tmp_list = NULL;

ogs_assert(node);
ogs_assert(node_id || from);

/* Convert node_id to a list of addresses */
if (node_id) {
nid_list = ogs_pfcp_node_id_to_addrinfo(node_id);
if (!nid_list) {
ogs_error("ogs_pfcp_node_id_to_addrinfo() failed [%d]",
node_id->type);
/*
* We only handle IPv4, IPv6, and FQDN types here. If the incoming
* node_id has any other type, we treat it as invalid. This ensures
* we do not merge a node with an unsupported PFCP Node ID.
*/
if (node_id->type != OGS_PFCP_NODE_ID_IPV4 &&
node_id->type != OGS_PFCP_NODE_ID_IPV6 &&
node_id->type != OGS_PFCP_NODE_ID_FQDN) {
ogs_error("Invalid PFCP Node Type = %d", node_id->type);
return OGS_ERROR;
}

ogs_merge_addrinfo(&node->addr_list, nid_list);
ogs_freeaddrinfo(nid_list);
/* Check if node_id is different from node->node_id. */
if (!ogs_pfcp_node_id_compare(&node->node_id, node_id)) {
/* Update the node's ID and reset the refresh timestamp. */
memcpy(&node->node_id, node_id, sizeof(node->node_id));
node->last_dns_refresh = 0;
}

/* If FQDN, do a DNS lookup (immediate or periodic). */
if (node->node_id.type == OGS_PFCP_NODE_ID_FQDN) {
/*
* We perform a DNS resolution if 'last_dns_refresh' is zero, which means
* this FQDN node has never been resolved yet (first-time resolution), or if
* at least 300 seconds have passed since the last refresh. Without checking
* '== 0', a newly created FQDN node might skip resolution if 'now' is less
* than the 300-second threshold.
*/
ogs_time_t now = ogs_time_now();

/* For 300-second refresh interval in microseconds. */
#define OGS_PFCP_NODE_DNS_REFRESH_INTERVAL \
((ogs_time_t)(300) * OGS_USEC_PER_SEC)
if (node->last_dns_refresh == 0 ||
(now - node->last_dns_refresh) >=
OGS_PFCP_NODE_DNS_REFRESH_INTERVAL) {

tmp_list = ogs_pfcp_node_id_to_addrinfo(&node->node_id);
if (!tmp_list) {
ogs_error("DNS resolution failed for FQDN [%s]",
node->node_id.fqdn);
return OGS_ERROR;
}

ogs_freeaddrinfo(node->addr_list);
node->addr_list = tmp_list;
node->last_dns_refresh = now;
tmp_list = NULL;
}
}
/* If IPv4/IPv6, convert immediately. */
else if (node->node_id.type == OGS_PFCP_NODE_ID_IPV4 ||
node->node_id.type == OGS_PFCP_NODE_ID_IPV6) {
tmp_list = ogs_pfcp_node_id_to_addrinfo(&node->node_id);
if (!tmp_list) {
ogs_error("Failed to convert node ID to address info");
return OGS_ERROR;
}
ogs_merge_addrinfo(&node->addr_list, tmp_list);
ogs_freeaddrinfo(tmp_list);
tmp_list = NULL;
}
else {
/* Not IPv4, IPv6, or FQDN => invalid node type. */
ogs_error("Invalid Node ID type [%d]", node->node_id.type);
return OGS_ERROR;
}
}

/* "from" as single item */
/* Merge 'from' into addr_list if provided. */
if (from) {
memcpy(&single, from, sizeof(single));
single.next = NULL;

ogs_merge_addrinfo(&node->addr_list, &single);
}

Expand All @@ -1082,22 +1132,33 @@ void ogs_pfcp_node_remove_all(ogs_list_t *list)
ogs_pfcp_node_remove(list, node);
}

/* Function to compare two node IDs */
int ogs_pfcp_node_id_compare(
/******************************************************************************
* Compare two node IDs for equality. Returns true if they match, else false.
******************************************************************************/
bool ogs_pfcp_node_id_compare(
const ogs_pfcp_node_id_t *id1, const ogs_pfcp_node_id_t *id2)
{
if (id1->type != id2->type) {
if (id1->type != id2->type)
return false; /* Types do not match */
}

switch (id1->type) {
case OGS_PFCP_NODE_ID_IPV4:
return (id1->addr == id2->addr);
if (id1->addr != id2->addr) return false;
return true;

case OGS_PFCP_NODE_ID_IPV6:
return (memcmp(id1->addr6, id2->addr6, OGS_IPV6_LEN) == 0);
if (memcmp(id1->addr6, id2->addr6, OGS_IPV6_LEN) != 0)
return false;
return true;

case OGS_PFCP_NODE_ID_FQDN:
return (strcmp(id1->fqdn, id2->fqdn) == 0);
if (strcmp(id1->fqdn, id2->fqdn) != 0)
return false;
return true;

default:
ogs_error("Unexpected Node Type [%d]", id1->type);
ogs_abort();
return false; /* Unknown types do not match */
}
}
Expand Down
Loading

0 comments on commit 9c370ff

Please sign in to comment.