diff options
Diffstat (limited to 'lib/srcdest_table.c')
-rw-r--r-- | lib/srcdest_table.c | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/lib/srcdest_table.c b/lib/srcdest_table.c new file mode 100644 index 0000000..3247a03 --- /dev/null +++ b/lib/srcdest_table.c @@ -0,0 +1,321 @@ +// 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 <zebra.h> + +#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; +} |