// SPDX-License-Identifier: GPL-2.0-or-later /* * SRC-DEST Routing Table * * Copyright (C) 2017 by David Lamparter & Christian Franke, * Open Source Routing / NetDEF Inc. * * This file is part of FRRouting (FRR) */ #include #include "srcdest_table.h" #include "memory.h" #include "prefix.h" #include "table.h" #include "printfrr.h" DEFINE_MTYPE_STATIC(LIB, ROUTE_SRC_NODE, "Route source node"); /* ----- functions to manage rnodes _with_ srcdest table ----- */ struct srcdest_rnode { /* must be first in structure for casting to/from route_node */ ROUTE_NODE_FIELDS; struct route_table *src_table; }; static struct srcdest_rnode *srcdest_rnode_from_rnode(struct route_node *rn) { assert(rnode_is_dstnode(rn)); return (struct srcdest_rnode *)rn; } static struct route_node *srcdest_rnode_to_rnode(struct srcdest_rnode *srn) { return (struct route_node *)srn; } static struct route_node *srcdest_rnode_create(route_table_delegate_t *delegate, struct route_table *table) { struct srcdest_rnode *srn; srn = XCALLOC(MTYPE_ROUTE_NODE, sizeof(struct srcdest_rnode)); return srcdest_rnode_to_rnode(srn); } static void srcdest_rnode_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *rn) { struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); struct route_table *src_table; /* Clear route node's src_table here already, otherwise the * deletion of the last node in the src_table will trigger * another call to route_table_finish for the src_table. * * (Compare with srcdest_srcnode_destroy) */ src_table = srn->src_table; srn->src_table = NULL; route_table_finish(src_table); XFREE(MTYPE_ROUTE_NODE, rn); } route_table_delegate_t _srcdest_dstnode_delegate = { .create_node = srcdest_rnode_create, .destroy_node = srcdest_rnode_destroy}; /* ----- functions to manage rnodes _in_ srcdest table ----- */ /* node creation / deletion for srcdest source prefix nodes. * the route_node isn't actually different from the normal route_node, * but the cleanup is special to free the table (and possibly the * destination prefix's route_node) */ static struct route_node * srcdest_srcnode_create(route_table_delegate_t *delegate, struct route_table *table) { return XCALLOC(MTYPE_ROUTE_SRC_NODE, sizeof(struct route_node)); } static void srcdest_srcnode_destroy(route_table_delegate_t *delegate, struct route_table *table, struct route_node *rn) { struct srcdest_rnode *srn; XFREE(MTYPE_ROUTE_SRC_NODE, rn); srn = route_table_get_info(table); if (srn->src_table && route_table_count(srn->src_table) == 0) { /* deleting the route_table from inside destroy_node is ONLY * permitted IF table->count is 0! see lib/table.c * route_node_delete() * for details */ route_table_finish(srn->src_table); srn->src_table = NULL; /* drop the ref we're holding in srcdest_node_get(). there * might be * non-srcdest routes, so the route_node may still exist. * hence, it's * important to clear src_table above. */ route_unlock_node(srcdest_rnode_to_rnode(srn)); } } route_table_delegate_t _srcdest_srcnode_delegate = { .create_node = srcdest_srcnode_create, .destroy_node = srcdest_srcnode_destroy}; /* NB: read comments in code for refcounting before using! */ static struct route_node *srcdest_srcnode_get(struct route_node *rn, const struct prefix_ipv6 *src_p) { struct srcdest_rnode *srn; if (!src_p || src_p->prefixlen == 0) return rn; srn = srcdest_rnode_from_rnode(rn); if (!srn->src_table) { /* this won't use srcdest_rnode, we're already on the source * here */ srn->src_table = route_table_init_with_delegate( &_srcdest_srcnode_delegate); route_table_set_info(srn->src_table, srn); /* there is no route_unlock_node on the original rn here. * The reference is kept for the src_table. */ } else { /* only keep 1 reference for the src_table, makes the * refcounting * more similar to the non-srcdest case. Either way after * return from * function, the only reference held is the one on the return * value. * * We can safely drop our reference here because src_table is * holding * another reference, so this won't free rn */ route_unlock_node(rn); } return route_node_get(srn->src_table, (const struct prefix *)src_p); } static struct route_node *srcdest_srcnode_lookup( struct route_node *rn, const struct prefix_ipv6 *src_p) { struct srcdest_rnode *srn; if (!rn || !src_p || src_p->prefixlen == 0) return rn; /* We got this rn from a lookup, so its refcnt was incremented. As we * won't * return return rn from any point beyond here, we should decrement its * refcnt. */ route_unlock_node(rn); srn = srcdest_rnode_from_rnode(rn); if (!srn->src_table) return NULL; return route_node_lookup(srn->src_table, (const struct prefix *)src_p); } /* ----- exported functions ----- */ struct route_table *srcdest_table_init(void) { return route_table_init_with_delegate(&_srcdest_dstnode_delegate); } struct route_node *srcdest_route_next(struct route_node *rn) { struct route_node *next, *parent; /* For a non src-dest node, just return route_next */ if (!(rnode_is_dstnode(rn) || rnode_is_srcnode(rn))) return route_next(rn); if (rnode_is_dstnode(rn)) { /* This means the route_node is part of the top hierarchy * and refers to a destination prefix. */ struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); if (srn->src_table) next = route_top(srn->src_table); else next = NULL; if (next) { /* There is a source prefix. Return the node for it */ route_unlock_node(rn); return next; } else { /* There is no source prefix, just continue as usual */ return route_next(rn); } } /* This part handles the case of iterating source nodes. */ parent = route_lock_node(route_table_get_info(rn->table)); next = route_next(rn); if (next) { /* There is another source node, continue in the source table */ route_unlock_node(parent); return next; } else { /* The source table is complete, continue in the parent table */ return route_next(parent); } } struct route_node *srcdest_rnode_get(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p) { const struct prefix_ipv6 *dst_p = dst_pu.p6; struct route_node *rn; rn = route_node_get(table, (const struct prefix *)dst_p); return srcdest_srcnode_get(rn, src_p); } struct route_node *srcdest_rnode_lookup(struct route_table *table, union prefixconstptr dst_pu, const struct prefix_ipv6 *src_p) { const struct prefix_ipv6 *dst_p = dst_pu.p6; struct route_node *rn; struct route_node *srn; rn = route_node_lookup_maynull(table, (const struct prefix *)dst_p); srn = srcdest_srcnode_lookup(rn, src_p); if (rn != NULL && rn == srn && !rn->info) { /* Match the behavior of route_node_lookup and don't return an * empty route-node for a dest-route */ route_unlock_node(rn); return NULL; } return srn; } void srcdest_rnode_prefixes(const struct route_node *rn, const struct prefix **p, const struct prefix **src_p) { if (rnode_is_srcnode(rn)) { struct route_node *dst_rn = route_table_get_info(rn->table); if (p) *p = &dst_rn->p; if (src_p) *src_p = &rn->p; } else { if (p) *p = &rn->p; if (src_p) *src_p = NULL; } } const char *srcdest2str(const struct prefix *dst_p, const struct prefix_ipv6 *src_p, char *str, int size) { char dst_buf[PREFIX_STRLEN], src_buf[PREFIX_STRLEN]; snprintf(str, size, "%s%s%s", prefix2str(dst_p, dst_buf, sizeof(dst_buf)), (src_p && src_p->prefixlen) ? " from " : "", (src_p && src_p->prefixlen) ? prefix2str(src_p, src_buf, sizeof(src_buf)) : ""); return str; } const char *srcdest_rnode2str(const struct route_node *rn, char *str, int size) { const struct prefix *dst_p, *src_p; srcdest_rnode_prefixes(rn, &dst_p, &src_p); return srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, str, size); } printfrr_ext_autoreg_p("RN", printfrr_rn); static ssize_t printfrr_rn(struct fbuf *buf, struct printfrr_eargs *ea, const void *ptr) { const struct route_node *rn = ptr; const struct prefix *dst_p, *src_p; char cbuf[PREFIX_STRLEN * 2 + 6]; if (!rn) return bputs(buf, "(null)"); srcdest_rnode_prefixes(rn, &dst_p, &src_p); srcdest2str(dst_p, (const struct prefix_ipv6 *)src_p, cbuf, sizeof(cbuf)); return bputs(buf, cbuf); } struct route_table *srcdest_srcnode_table(struct route_node *rn) { if (rnode_is_dstnode(rn)) { struct srcdest_rnode *srn = srcdest_rnode_from_rnode(rn); return srn->src_table; } return NULL; }