diff options
| -rw-r--r-- | doc/user/pim.rst | 6 | ||||
| -rw-r--r-- | isisd/isis_route.c | 8 | ||||
| -rw-r--r-- | isisd/isis_vty_fabricd.c | 2 | ||||
| -rw-r--r-- | isisd/isisd.c | 8 | ||||
| -rw-r--r-- | lib/linklist.c | 4 | ||||
| -rw-r--r-- | lib/linklist.h | 4 | ||||
| -rw-r--r-- | pimd/pim_cmd.c | 93 | ||||
| -rw-r--r-- | snapcraft/README.snap_build.md | 8 | ||||
| -rw-r--r-- | snapcraft/README.usage.md | 2 | ||||
| -rw-r--r-- | snapcraft/defaults/fabricd.conf.default | 0 | ||||
| -rw-r--r-- | snapcraft/scripts/Makefile | 1 | ||||
| -rw-r--r-- | snapcraft/scripts/bgpd-service | 2 | ||||
| -rw-r--r-- | snapcraft/scripts/fabricd-service | 13 | ||||
| -rw-r--r-- | snapcraft/snapcraft.yaml.in | 51 | ||||
| -rw-r--r-- | zebra/rib.h | 15 | ||||
| -rw-r--r-- | zebra/subdir.am | 2 | ||||
| -rw-r--r-- | zebra/zebra_dplane.c | 325 | ||||
| -rw-r--r-- | zebra/zebra_dplane.h | 65 | ||||
| -rw-r--r-- | zebra/zebra_mpls.c | 208 | ||||
| -rw-r--r-- | zebra/zebra_mpls.h | 3 | ||||
| -rw-r--r-- | zebra/zebra_nhg.c | 511 | ||||
| -rw-r--r-- | zebra/zebra_nhg.h | 29 | ||||
| -rw-r--r-- | zebra/zebra_rib.c | 1037 | ||||
| -rw-r--r-- | zebra/zebra_vty.c | 26 |
24 files changed, 1852 insertions, 571 deletions
diff --git a/doc/user/pim.rst b/doc/user/pim.rst index 414423ae7c..d05127059b 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -322,6 +322,12 @@ cause great confusion. Display information about installed into the kernel S,G mroutes and in addition display data about packet flow for the mroutes. +.. index:: show ip mroute summary +.. clicmd:: show ip mroute summary + + Display total number of S,G mroutes and number of S,G mroutes installed + into the kernel. + .. index:: show ip pim assert .. clicmd:: show ip pim assert diff --git a/isisd/isis_route.c b/isisd/isis_route.c index 82005c911e..281eaf11bc 100644 --- a/isisd/isis_route.c +++ b/isisd/isis_route.c @@ -543,7 +543,8 @@ void isis_route_verify_merge(struct isis_area *area, ISIS_ROUTE_FLAG_ZEBRA_SYNCED ); continue; - } else { + } else if (CHECK_FLAG(rinfo->flag, + ISIS_ROUTE_FLAG_ACTIVE)) { /* Clear the ZEBRA_SYNCED flag on the L1 * route when L2 wins, otherwise L1 * won't get reinstalled when it @@ -553,6 +554,11 @@ void isis_route_verify_merge(struct isis_area *area, mrinfo->flag, ISIS_ROUTE_FLAG_ZEBRA_SYNCED ); + } else if ( + CHECK_FLAG( + mrinfo->flag, + ISIS_ROUTE_FLAG_ZEBRA_SYNCED)) { + continue; } } mrnode->info = rnode->info; diff --git a/isisd/isis_vty_fabricd.c b/isisd/isis_vty_fabricd.c index 2476bd2552..431ad9712a 100644 --- a/isisd/isis_vty_fabricd.c +++ b/isisd/isis_vty_fabricd.c @@ -168,7 +168,7 @@ DEFUN (show_lsp_flooding, area->area_tag : "null"); if (lspid) { - struct isis_lsp *lsp = lsp_for_arg(head, lspid); + lsp = lsp_for_arg(head, lspid); if (lsp) lsp_print_flooding(vty, lsp); diff --git a/isisd/isisd.c b/isisd/isisd.c index 07be68d9ae..bee3b6deb5 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -272,7 +272,7 @@ int isis_area_destroy(const char *area_tag) lsp_db_fini(&area->lspdb[1]); /* invalidate and verify to delete all routes from zebra */ - isis_area_invalidate_routes(area, ISIS_LEVEL1 & ISIS_LEVEL2); + isis_area_invalidate_routes(area, area->is_type); isis_area_verify_routes(area); spftree_area_del(area); @@ -738,11 +738,7 @@ DEFUN (clear_isis_neighbor_arg, */ void print_debug(struct vty *vty, int flags, int onoff) { - char onoffs[4]; - if (onoff) - strcpy(onoffs, "on"); - else - strcpy(onoffs, "off"); + const char *onoffs = onoff ? "on" : "off"; if (flags & DEBUG_ADJ_PACKETS) vty_out(vty, diff --git a/lib/linklist.c b/lib/linklist.c index e8ba9edc12..0d1efdf3aa 100644 --- a/lib/linklist.c +++ b/lib/linklist.c @@ -246,7 +246,7 @@ void listnode_move_to_tail(struct list *l, struct listnode *n) LISTNODE_ATTACH(l, n); } -void listnode_delete(struct list *list, void *val) +void listnode_delete(struct list *list, const void *val) { struct listnode *node = listnode_lookup(list, val); @@ -307,7 +307,7 @@ void list_delete(struct list **list) *list = NULL; } -struct listnode *listnode_lookup(struct list *list, void *data) +struct listnode *listnode_lookup(struct list *list, const void *data) { struct listnode *node; diff --git a/lib/linklist.h b/lib/linklist.h index da42aa2688..d23d425d62 100644 --- a/lib/linklist.h +++ b/lib/linklist.h @@ -180,7 +180,7 @@ extern void listnode_move_to_tail(struct list *list, struct listnode *node); * data * data to insert into list */ -extern void listnode_delete(struct list *list, void *data); +extern void listnode_delete(struct list *list, const void *data); /* * Find the listnode corresponding to an element in a list. @@ -194,7 +194,7 @@ extern void listnode_delete(struct list *list, void *data); * Returns: * pointer to listnode storing the given data if found, NULL otherwise */ -extern struct listnode *listnode_lookup(struct list *list, void *data); +extern struct listnode *listnode_lookup(struct list *list, const void *data); /* * Retrieve the element at the head of a list. diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index a2357067f9..fe910591a6 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -5705,6 +5705,97 @@ DEFUN (show_ip_mroute_count_vrf_all, return CMD_SUCCESS; } +static void show_mroute_summary(struct pim_instance *pim, struct vty *vty) +{ + struct listnode *node; + struct channel_oil *c_oil; + struct static_route *s_route; + uint32_t starg_sw_mroute_cnt = 0; + uint32_t sg_sw_mroute_cnt = 0; + uint32_t starg_hw_mroute_cnt = 0; + uint32_t sg_hw_mroute_cnt = 0; + + vty_out(vty, "Mroute Type Installed/Total\n"); + + for (ALL_LIST_ELEMENTS_RO(pim->channel_oil_list, node, c_oil)) { + if (!c_oil->installed) { + if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) + starg_sw_mroute_cnt++; + else + sg_sw_mroute_cnt++; + } else { + if (c_oil->oil.mfcc_origin.s_addr == INADDR_ANY) + starg_hw_mroute_cnt++; + else + sg_hw_mroute_cnt++; + } + } + + for (ALL_LIST_ELEMENTS_RO(pim->static_routes, node, s_route)) { + if (!s_route->c_oil.installed) { + if (s_route->c_oil.oil.mfcc_origin.s_addr == INADDR_ANY) + starg_sw_mroute_cnt++; + else + sg_sw_mroute_cnt++; + } else { + if (s_route->c_oil.oil.mfcc_origin.s_addr == INADDR_ANY) + starg_hw_mroute_cnt++; + else + sg_hw_mroute_cnt++; + } + } + + vty_out(vty, "%-20s %d/%d\n", "(*, G)", starg_hw_mroute_cnt, + starg_sw_mroute_cnt + starg_hw_mroute_cnt); + vty_out(vty, "%-20s %d/%d\n", "(S, G)", sg_hw_mroute_cnt, + sg_sw_mroute_cnt + sg_hw_mroute_cnt); + vty_out(vty, "------\n"); + vty_out(vty, "%-20s %d/%d\n", "Total", + (starg_hw_mroute_cnt + sg_hw_mroute_cnt), + (starg_sw_mroute_cnt + + starg_hw_mroute_cnt + + sg_sw_mroute_cnt + + sg_hw_mroute_cnt)); +} + +DEFUN (show_ip_mroute_summary, + show_ip_mroute_summary_cmd, + "show ip mroute [vrf NAME] summary", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n") +{ + int idx = 2; + struct vrf *vrf = pim_cmd_lookup_vrf(vty, argv, argc, &idx); + + if (!vrf) + return CMD_WARNING; + + show_mroute_summary(vrf->info, vty); + return CMD_SUCCESS; +} + +DEFUN (show_ip_mroute_summary_vrf_all, + show_ip_mroute_summary_vrf_all_cmd, + "show ip mroute vrf all summary", + SHOW_STR + IP_STR + MROUTE_STR + VRF_CMD_HELP_STR + "Summary of all mroutes\n") +{ + struct vrf *vrf; + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + vty_out(vty, "VRF: %s\n", vrf->name); + show_mroute_summary(vrf->info, vty); + } + + return CMD_SUCCESS; +} + DEFUN (show_ip_rib, show_ip_rib_cmd, "show ip rib [vrf NAME] A.B.C.D", @@ -10085,6 +10176,8 @@ void pim_cmd_init(void) install_element(VIEW_NODE, &show_ip_mroute_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_mroute_count_cmd); install_element(VIEW_NODE, &show_ip_mroute_count_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_mroute_summary_cmd); + install_element(VIEW_NODE, &show_ip_mroute_summary_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_rib_cmd); install_element(VIEW_NODE, &show_ip_ssmpingd_cmd); install_element(VIEW_NODE, &show_debugging_pim_cmd); diff --git a/snapcraft/README.snap_build.md b/snapcraft/README.snap_build.md index 7c42848451..e43f63f2d9 100644 --- a/snapcraft/README.snap_build.md +++ b/snapcraft/README.snap_build.md @@ -92,6 +92,14 @@ All the commands are prefixed with frr. frr.ripngd-debug frr.ldp-debug frr.zebra-debug + frr.pimd-debug + frr.nhrpd-debug + frr.babeld-debug + frr.eigrpd-debug + frr.pbrd-debug + frr.staticd-debug + frr.bfdd-debug + frr.fabricd-debug vtysh can be accessed as frr.vtysh (Make sure you have /snap/bin in your path). If access as `vtysh` instead of `frr.vtysh` is needed, you can enable it diff --git a/snapcraft/README.usage.md b/snapcraft/README.usage.md index a7b51a5656..6a0864c8c5 100644 --- a/snapcraft/README.usage.md +++ b/snapcraft/README.usage.md @@ -66,6 +66,8 @@ depend on them). These are mainly intended to debug the Snap Starts staticd daemon in foreground - `frr.bfdd-debug`: Starts bfdd daemon in foreground +- `frr.fabricd-debug`: + Starts fabricd daemon in foreground MPLS (LDP) ---------- diff --git a/snapcraft/defaults/fabricd.conf.default b/snapcraft/defaults/fabricd.conf.default new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/snapcraft/defaults/fabricd.conf.default diff --git a/snapcraft/scripts/Makefile b/snapcraft/scripts/Makefile index e3a7708f23..7ddb7f0769 100644 --- a/snapcraft/scripts/Makefile +++ b/snapcraft/scripts/Makefile @@ -17,6 +17,7 @@ install: install -D -m 0755 pbrd-service $(DESTDIR)/bin/ install -D -m 0755 staticd-service $(DESTDIR)/bin/ install -D -m 0755 bfdd-service $(DESTDIR)/bin/ + install -D -m 0755 fabricd-service $(DESTDIR)/bin/ install -D -m 0755 set-options $(DESTDIR)/bin/ install -D -m 0755 show_version $(DESTDIR)/bin/ diff --git a/snapcraft/scripts/bgpd-service b/snapcraft/scripts/bgpd-service index 6c3a6f5959..64273d9f80 100644 --- a/snapcraft/scripts/bgpd-service +++ b/snapcraft/scripts/bgpd-service @@ -10,7 +10,7 @@ fi if ! [ -e $SNAP_DATA/rpki.conf ]; then echo "-M rpki" > $SNAP_DATA/rpki.conf fi -EXTRA_OPTIONS="`cat $SNAP_DATA/rpki.conf`" +EXTRA_OPTIONS="`$SNAP/bin/cat $SNAP_DATA/rpki.conf`" exec $SNAP/sbin/bgpd \ -f $SNAP_DATA/bgpd.conf \ --pid_file $SNAP_DATA/bgpd.pid \ diff --git a/snapcraft/scripts/fabricd-service b/snapcraft/scripts/fabricd-service new file mode 100644 index 0000000000..586f061ef0 --- /dev/null +++ b/snapcraft/scripts/fabricd-service @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e -x + +if ! [ -e $SNAP_DATA/fabricd.conf ]; then + cp $SNAP/etc/frr/fabricd.conf.default $SNAP_DATA/fabricd.conf +fi +exec $SNAP/sbin/fabricd \ + -f $SNAP_DATA/fabricd.conf \ + --pid_file $SNAP_DATA/fabricd.pid \ + --socket $SNAP_DATA/zsock \ + --vty_socket $SNAP_DATA + diff --git a/snapcraft/snapcraft.yaml.in b/snapcraft/snapcraft.yaml.in index b70d6efee2..d8071e8cfe 100644 --- a/snapcraft/snapcraft.yaml.in +++ b/snapcraft/snapcraft.yaml.in @@ -4,8 +4,8 @@ summary: FRRouting BGP/OSPFv2/OSPFv3/ISIS/RIP/RIPng/PIM/LDP/EIGRP/BFD routing da description: BGP/OSPFv2/OSPFv3/ISIS/RIP/RIPng/PIM/LDP/EIGRP/BFD routing daemon FRRouting (FRR) is free software which manages TCP/IP based routing protocols. It supports BGP4, BGP4+, OSPFv2, OSPFv3, IS-IS, RIPv1, RIPv2, - RIPng, PIM, LDP, Babel, EIGRP, PBR (Policy-based routing) and BFD as well as - the IPv6 versions of these. + RIPng, PIM, LDP, Babel, EIGRP, PBR (Policy-based routing), BFD and OpenFabric + as well as the IPv6 versions of these. FRRouting (frr) is a fork of Quagga. confinement: strict grade: devel @@ -127,6 +127,13 @@ apps: - network - network-bind - network-control + fabricd: + command: bin/fabricd-service + daemon: simple + plugs: + - network + - network-bind + - network-control set: command: bin/set-options zebra-debug: @@ -136,7 +143,7 @@ apps: - network-bind - network-control bgpd-debug: - command: sbin/bgpd -f $SNAP_DATA/bgpd.conf --pid_file $SNAP_DATA/bgpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA --moduledir $SNAP/lib/frr/modules `cat $SNAP_DATA/rpki.conf 2> /dev/null` + command: sbin/bgpd -f $SNAP_DATA/bgpd.conf --pid_file $SNAP_DATA/bgpd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA --moduledir $SNAP/lib/frr/modules plugs: - network - network-bind @@ -219,6 +226,12 @@ apps: - network - network-bind - network-control + fabricd-debug: + command: sbin/fabricd -f $SNAP_DATA/fabricd.conf --pid_file $SNAP_DATA/fabricd.pid --socket $SNAP_DATA/zsock --vty_socket $SNAP_DATA + plugs: + - network + - network-bind + - network-control parts: rtrlib: @@ -230,16 +243,33 @@ parts: stage-packages: - libssh-4 prime: - - lib/x86_64-linux-gnu/librtr.so* + - lib/librtr.so* - usr/lib/x86_64-linux-gnu/libssh.so* source: https://github.com/rtrlib/rtrlib.git source-type: git - source-tag: v0.5.0 + source-tag: v0.6.3 plugin: cmake configflags: - -DCMAKE_BUILD_TYPE=Release + libyang: + build-packages: + - cmake + - make + - gcc + - libpcre3-dev + stage-packages: + - libpcre3 + source: https://github.com/CESNET/libyang.git + source-type: git + source-tag: v0.16-r3 + plugin: cmake + configflags: + - -DCMAKE_INSTALL_PREFIX:PATH=/usr + - -DENABLE_LYD_PRIV=ON + - -DENABLE_CACHE=OFF + - -DCMAKE_BUILD_TYPE:String="Release" frr: - after: [rtrlib] + after: [rtrlib,libyang] build-packages: - gcc - autoconf @@ -272,13 +302,13 @@ parts: - iproute2 - logrotate - libcap2 - - libc6 - libtinfo5 - libreadline6 - libjson-c2 - libc-ares2 - libatm1 - libprotobuf-c1 + - libdb5.3 plugin: autotools source: ../frr-@PACKAGE_VERSION@.tar.gz configflags: @@ -322,7 +352,9 @@ parts: eigrpd.conf.default: etc/frr/eigrpd.conf.default pbrd.conf.default: etc/frr/pbrd.conf.default bfdd.conf.default: etc/frr/bfdd.conf.default + fabricd.conf.default: etc/frr/fabricd.conf.default vtysh.conf.default: etc/frr/vtysh.conf.default + staticd.conf.default: etc/frr/staticd.conf.default frr-scripts: plugin: make source: scripts @@ -344,3 +376,8 @@ parts: README.snap_build.md: doc/README.snap_build.md extra_version_info.txt: doc/extra_version_info.txt +passthrough: + layout: + /usr/lib/x86_64-linux-gnu/libyang: + bind: $SNAP/usr/lib/x86_64-linux-gnu/libyang + diff --git a/zebra/rib.h b/zebra/rib.h index 0353c9bb99..a54e164d98 100644 --- a/zebra/rib.h +++ b/zebra/rib.h @@ -87,9 +87,12 @@ struct route_entry { /* Link list. */ struct re_list_item next; - /* Nexthop structure */ + /* Nexthop structure (from RIB) */ struct nexthop_group ng; + /* Nexthop group from FIB (optional) */ + struct nexthop_group fib_ng; + /* Tag */ route_tag_t tag; @@ -529,6 +532,16 @@ static inline void rib_tables_iter_cleanup(rib_tables_iter_t *iter) DECLARE_HOOK(rib_update, (struct route_node * rn, const char *reason), (rn, reason)) +/* + * Access active nexthop-group, either RIB or FIB version + */ +static inline struct nexthop_group *rib_active_nhg(struct route_entry *re) +{ + if (re->fib_ng.nexthop) + return &(re->fib_ng); + else + return &(re->ng); +} extern void zebra_vty_init(void); diff --git a/zebra/subdir.am b/zebra/subdir.am index 1e36d020a3..25040a2717 100644 --- a/zebra/subdir.am +++ b/zebra/subdir.am @@ -75,6 +75,7 @@ zebra_zebra_SOURCES = \ zebra/zebra_mpls_null.c \ zebra/zebra_mpls_vty.c \ zebra/zebra_mroute.c \ + zebra/zebra_nhg.c \ zebra/zebra_ns.c \ zebra/zebra_pbr.c \ zebra/zebra_ptm.c \ @@ -135,6 +136,7 @@ noinst_HEADERS += \ zebra/zebra_memory.h \ zebra/zebra_mpls.h \ zebra/zebra_mroute.h \ + zebra/zebra_nhg.h \ zebra/zebra_ns.h \ zebra/zebra_pbr.h \ zebra/zebra_ptm.h \ diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index 6fc62147c8..1707d3a68b 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -172,6 +172,11 @@ struct zebra_dplane_ctx { uint32_t zd_seq; uint32_t zd_old_seq; + /* Some updates may be generated by notifications: allow the + * plugin to notice and ignore results from its own notifications. + */ + uint32_t zd_notif_provider; + /* TODO -- internal/sub-operation status? */ enum zebra_dplane_result zd_remote_status; enum zebra_dplane_result zd_kernel_status; @@ -222,6 +227,8 @@ struct zebra_dplane_provider { /* Flags */ int dp_flags; + int (*dp_start)(struct zebra_dplane_provider *prov); + int (*dp_fp)(struct zebra_dplane_provider *prov); int (*dp_fini)(struct zebra_dplane_provider *prov, bool early_p); @@ -351,7 +358,7 @@ struct thread_master *dplane_get_thread_master(void) /* * Allocate a dataplane update context */ -static struct zebra_dplane_ctx *dplane_ctx_alloc(void) +struct zebra_dplane_ctx *dplane_ctx_alloc(void) { struct zebra_dplane_ctx *p; @@ -392,6 +399,7 @@ static void dplane_ctx_free(struct zebra_dplane_ctx **pctx) case DPLANE_OP_ROUTE_DELETE: case DPLANE_OP_SYS_ROUTE_ADD: case DPLANE_OP_SYS_ROUTE_DELETE: + case DPLANE_OP_ROUTE_NOTIFY: /* Free allocated nexthops */ if ((*pctx)->u.rinfo.zd_ng.nexthop) { @@ -413,6 +421,7 @@ static void dplane_ctx_free(struct zebra_dplane_ctx **pctx) case DPLANE_OP_LSP_INSTALL: case DPLANE_OP_LSP_UPDATE: case DPLANE_OP_LSP_DELETE: + case DPLANE_OP_LSP_NOTIFY: { zebra_nhlfe_t *nhlfe, *next; @@ -543,6 +552,12 @@ bool dplane_ctx_is_skip_kernel(const struct zebra_dplane_ctx *ctx) return CHECK_FLAG(ctx->zd_flags, DPLANE_CTX_FLAG_NO_KERNEL); } +void dplane_ctx_set_op(struct zebra_dplane_ctx *ctx, enum dplane_op_e op) +{ + DPLANE_CTX_VALID(ctx); + ctx->zd_op = op; +} + enum dplane_op_e dplane_ctx_get_op(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -569,6 +584,9 @@ const char *dplane_op2str(enum dplane_op_e op) case DPLANE_OP_ROUTE_DELETE: ret = "ROUTE_DELETE"; break; + case DPLANE_OP_ROUTE_NOTIFY: + ret = "ROUTE_NOTIFY"; + break; case DPLANE_OP_LSP_INSTALL: ret = "LSP_INSTALL"; @@ -579,6 +597,9 @@ const char *dplane_op2str(enum dplane_op_e op) case DPLANE_OP_LSP_DELETE: ret = "LSP_DELETE"; break; + case DPLANE_OP_LSP_NOTIFY: + ret = "LSP_NOTIFY"; + break; case DPLANE_OP_PW_INSTALL: ret = "PW_INSTALL"; @@ -625,6 +646,14 @@ const char *dplane_res2str(enum zebra_dplane_result res) return ret; } +void dplane_ctx_set_dest(struct zebra_dplane_ctx *ctx, + const struct prefix *dest) +{ + DPLANE_CTX_VALID(ctx); + + prefix_copy(&(ctx->u.rinfo.zd_dest), dest); +} + const struct prefix *dplane_ctx_get_dest(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -632,6 +661,16 @@ const struct prefix *dplane_ctx_get_dest(const struct zebra_dplane_ctx *ctx) return &(ctx->u.rinfo.zd_dest); } +void dplane_ctx_set_src(struct zebra_dplane_ctx *ctx, const struct prefix *src) +{ + DPLANE_CTX_VALID(ctx); + + if (src) + prefix_copy(&(ctx->u.rinfo.zd_src), src); + else + memset(&(ctx->u.rinfo.zd_src), 0, sizeof(struct prefix)); +} + /* Source prefix is a little special - return NULL for "no src prefix" */ const struct prefix *dplane_ctx_get_src(const struct zebra_dplane_ctx *ctx) { @@ -666,6 +705,13 @@ uint32_t dplane_ctx_get_old_seq(const struct zebra_dplane_ctx *ctx) return ctx->zd_old_seq; } +void dplane_ctx_set_vrf(struct zebra_dplane_ctx *ctx, vrf_id_t vrf) +{ + DPLANE_CTX_VALID(ctx); + + ctx->zd_vrf_id = vrf; +} + vrf_id_t dplane_ctx_get_vrf(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -673,6 +719,35 @@ vrf_id_t dplane_ctx_get_vrf(const struct zebra_dplane_ctx *ctx) return ctx->zd_vrf_id; } +bool dplane_ctx_is_from_notif(const struct zebra_dplane_ctx *ctx) +{ + DPLANE_CTX_VALID(ctx); + + return (ctx->zd_notif_provider != 0); +} + +uint32_t dplane_ctx_get_notif_provider(const struct zebra_dplane_ctx *ctx) +{ + DPLANE_CTX_VALID(ctx); + + return ctx->zd_notif_provider; +} + +void dplane_ctx_set_notif_provider(struct zebra_dplane_ctx *ctx, + uint32_t id) +{ + DPLANE_CTX_VALID(ctx); + + ctx->zd_notif_provider = id; +} + +void dplane_ctx_set_type(struct zebra_dplane_ctx *ctx, int type) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_type = type; +} + int dplane_ctx_get_type(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -687,6 +762,13 @@ int dplane_ctx_get_old_type(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_old_type; } +void dplane_ctx_set_afi(struct zebra_dplane_ctx *ctx, afi_t afi) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_afi = afi; +} + afi_t dplane_ctx_get_afi(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -694,6 +776,13 @@ afi_t dplane_ctx_get_afi(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_afi; } +void dplane_ctx_set_safi(struct zebra_dplane_ctx *ctx, safi_t safi) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_safi = safi; +} + safi_t dplane_ctx_get_safi(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -701,6 +790,13 @@ safi_t dplane_ctx_get_safi(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_safi; } +void dplane_ctx_set_table(struct zebra_dplane_ctx *ctx, uint32_t table) +{ + DPLANE_CTX_VALID(ctx); + + ctx->zd_table_id = table; +} + uint32_t dplane_ctx_get_table(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -715,6 +811,13 @@ route_tag_t dplane_ctx_get_tag(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_tag; } +void dplane_ctx_set_tag(struct zebra_dplane_ctx *ctx, route_tag_t tag) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_tag = tag; +} + route_tag_t dplane_ctx_get_old_tag(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -729,6 +832,13 @@ uint16_t dplane_ctx_get_instance(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_instance; } +void dplane_ctx_set_instance(struct zebra_dplane_ctx *ctx, uint16_t instance) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_instance = instance; +} + uint16_t dplane_ctx_get_old_instance(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -771,6 +881,13 @@ uint8_t dplane_ctx_get_distance(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_distance; } +void dplane_ctx_set_distance(struct zebra_dplane_ctx *ctx, uint8_t distance) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_distance = distance; +} + uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -778,6 +895,17 @@ uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_old_distance; } +void dplane_ctx_set_nexthops(struct zebra_dplane_ctx *ctx, struct nexthop *nh) +{ + DPLANE_CTX_VALID(ctx); + + if (ctx->u.rinfo.zd_ng.nexthop) { + nexthops_free(ctx->u.rinfo.zd_ng.nexthop); + ctx->u.rinfo.zd_ng.nexthop = NULL; + } + copy_nexthops(&(ctx->u.rinfo.zd_ng.nexthop), nh, NULL); +} + const struct nexthop_group *dplane_ctx_get_ng( const struct zebra_dplane_ctx *ctx) { @@ -811,6 +939,13 @@ mpls_label_t dplane_ctx_get_in_label(const struct zebra_dplane_ctx *ctx) return ctx->u.lsp.ile.in_label; } +void dplane_ctx_set_in_label(struct zebra_dplane_ctx *ctx, mpls_label_t label) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.lsp.ile.in_label = label; +} + uint8_t dplane_ctx_get_addr_family(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -818,6 +953,14 @@ uint8_t dplane_ctx_get_addr_family(const struct zebra_dplane_ctx *ctx) return ctx->u.lsp.addr_family; } +void dplane_ctx_set_addr_family(struct zebra_dplane_ctx *ctx, + uint8_t family) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.lsp.addr_family = family; +} + uint32_t dplane_ctx_get_lsp_flags(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -825,6 +968,14 @@ uint32_t dplane_ctx_get_lsp_flags(const struct zebra_dplane_ctx *ctx) return ctx->u.lsp.flags; } +void dplane_ctx_set_lsp_flags(struct zebra_dplane_ctx *ctx, + uint32_t flags) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.lsp.flags = flags; +} + const zebra_nhlfe_t *dplane_ctx_get_nhlfe(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -832,6 +983,24 @@ const zebra_nhlfe_t *dplane_ctx_get_nhlfe(const struct zebra_dplane_ctx *ctx) return ctx->u.lsp.nhlfe_list; } +zebra_nhlfe_t *dplane_ctx_add_nhlfe(struct zebra_dplane_ctx *ctx, + enum lsp_types_t lsp_type, + enum nexthop_types_t nh_type, + union g_addr *gate, + ifindex_t ifindex, + mpls_label_t out_label) +{ + zebra_nhlfe_t *nhlfe; + + DPLANE_CTX_VALID(ctx); + + nhlfe = zebra_mpls_lsp_add_nhlfe(&(ctx->u.lsp), + lsp_type, nh_type, gate, + ifindex, out_label); + + return nhlfe; +} + const zebra_nhlfe_t * dplane_ctx_get_best_nhlfe(const struct zebra_dplane_ctx *ctx) { @@ -840,6 +1009,16 @@ dplane_ctx_get_best_nhlfe(const struct zebra_dplane_ctx *ctx) return ctx->u.lsp.best_nhlfe; } +const zebra_nhlfe_t * +dplane_ctx_set_best_nhlfe(struct zebra_dplane_ctx *ctx, + zebra_nhlfe_t *nhlfe) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.lsp.best_nhlfe = nhlfe; + return ctx->u.lsp.best_nhlfe; +} + uint32_t dplane_ctx_get_lsp_num_ecmp(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -1497,6 +1676,59 @@ done: } /* + * Update from an async notification, to bring other fibs up-to-date. + */ +enum zebra_dplane_result +dplane_route_notif_update(struct route_node *rn, + struct route_entry *re, + enum dplane_op_e op, + struct zebra_dplane_ctx *ctx) +{ + enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; + struct zebra_dplane_ctx *new_ctx = NULL; + struct nexthop *nexthop; + + if (rn == NULL || re == NULL) + goto done; + + new_ctx = dplane_ctx_alloc(); + if (new_ctx == NULL) + goto done; + + /* Init context with info from zebra data structs */ + dplane_ctx_route_init(new_ctx, op, rn, re); + + /* For add/update, need to adjust the nexthops so that we match + * the notification state, which may not be the route-entry/RIB + * state. + */ + if (op == DPLANE_OP_ROUTE_UPDATE || + op == DPLANE_OP_ROUTE_INSTALL) { + + nexthops_free(new_ctx->u.rinfo.zd_ng.nexthop); + new_ctx->u.rinfo.zd_ng.nexthop = NULL; + + copy_nexthops(&(new_ctx->u.rinfo.zd_ng.nexthop), + (rib_active_nhg(re))->nexthop, NULL); + + for (ALL_NEXTHOPS(new_ctx->u.rinfo.zd_ng, nexthop)) + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); + + } + + /* Capture info about the source of the notification, in 'ctx' */ + dplane_ctx_set_notif_provider(new_ctx, + dplane_ctx_get_notif_provider(ctx)); + + dplane_route_enqueue(new_ctx); + + ret = ZEBRA_DPLANE_REQUEST_QUEUED; + +done: + return ret; +} + +/* * Enqueue LSP add for the dataplane. */ enum zebra_dplane_result dplane_lsp_add(zebra_lsp_t *lsp) @@ -1529,6 +1761,50 @@ enum zebra_dplane_result dplane_lsp_delete(zebra_lsp_t *lsp) return ret; } +/* Update or un-install resulting from an async notification */ +enum zebra_dplane_result +dplane_lsp_notif_update(zebra_lsp_t *lsp, + enum dplane_op_e op, + struct zebra_dplane_ctx *notif_ctx) +{ + enum zebra_dplane_result result = ZEBRA_DPLANE_REQUEST_FAILURE; + int ret = EINVAL; + struct zebra_dplane_ctx *ctx = NULL; + + /* Obtain context block */ + ctx = dplane_ctx_alloc(); + if (ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = dplane_ctx_lsp_init(ctx, op, lsp); + if (ret != AOK) + goto done; + + /* Capture info about the source of the notification */ + dplane_ctx_set_notif_provider( + ctx, + dplane_ctx_get_notif_provider(notif_ctx)); + + ret = dplane_route_enqueue(ctx); + +done: + /* Update counter */ + atomic_fetch_add_explicit(&zdplane_info.dg_lsps_in, 1, + memory_order_relaxed); + + if (ret == AOK) + result = ZEBRA_DPLANE_REQUEST_QUEUED; + else { + atomic_fetch_add_explicit(&zdplane_info.dg_lsp_errors, 1, + memory_order_relaxed); + if (ctx) + dplane_ctx_free(&ctx); + } + return result; +} + /* * Enqueue pseudowire install for the dataplane. */ @@ -1823,6 +2099,7 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed) int dplane_provider_register(const char *name, enum dplane_provider_prio prio, int flags, + int (*start_fp)(struct zebra_dplane_provider *), int (*fp)(struct zebra_dplane_provider *), int (*fini_fp)(struct zebra_dplane_provider *, bool early), @@ -1853,6 +2130,7 @@ int dplane_provider_register(const char *name, p->dp_priority = prio; p->dp_fp = fp; + p->dp_start = start_fp; p->dp_fini = fini_fp; p->dp_data = data; @@ -2046,6 +2324,20 @@ int dplane_provider_work_ready(void) } /* + * Enqueue a context directly to zebra main. + */ +void dplane_provider_enqueue_to_zebra(struct zebra_dplane_ctx *ctx) +{ + struct dplane_ctx_q temp_list; + + /* Zebra's api takes a list, so we need to use a temporary list */ + TAILQ_INIT(&temp_list); + + TAILQ_INSERT_TAIL(&temp_list, ctx, zd_q_entries); + (zdplane_info.dg_results_cb)(&temp_list); +} + +/* * Kernel dataplane provider */ @@ -2207,9 +2499,11 @@ static int kernel_dplane_process_func(struct zebra_dplane_provider *prov) res = kernel_dplane_address_update(ctx); break; - /* Ignore system 'notifications' - the kernel already knows */ + /* Ignore 'notifications' - no-op */ case DPLANE_OP_SYS_ROUTE_ADD: case DPLANE_OP_SYS_ROUTE_DELETE: + case DPLANE_OP_ROUTE_NOTIFY: + case DPLANE_OP_LSP_NOTIFY: res = ZEBRA_DPLANE_REQUEST_SUCCESS; break; @@ -2320,7 +2614,7 @@ static void dplane_provider_init(void) ret = dplane_provider_register("Kernel", DPLANE_PRIO_KERNEL, - DPLANE_PROV_FLAGS_DEFAULT, + DPLANE_PROV_FLAGS_DEFAULT, NULL, kernel_dplane_process_func, NULL, NULL, NULL); @@ -2333,7 +2627,7 @@ static void dplane_provider_init(void) /* Optional test provider ... */ ret = dplane_provider_register("Test", DPLANE_PRIO_PRE_KERNEL, - DPLANE_PROV_FLAGS_DEFAULT, + DPLANE_PROV_FLAGS_DEFAULT, NULL, test_dplane_process_func, test_dplane_shutdown_func, NULL /* data */, NULL); @@ -2652,7 +2946,6 @@ static int dplane_thread_loop(struct thread *event) TAILQ_INIT(&error_list); - /* Call through to zebra main */ (zdplane_info.dg_results_cb)(&work_list); @@ -2717,13 +3010,14 @@ static void zebra_dplane_init_internal(void) */ void zebra_dplane_start(void) { - /* Start dataplane pthread */ - + struct zebra_dplane_provider *prov; struct frr_pthread_attr pattr = { .start = frr_pthread_attr_default.start, .stop = frr_pthread_attr_default.stop }; + /* Start dataplane pthread */ + zdplane_info.dg_pthread = frr_pthread_new(&pattr, "Zebra dplane thread", "Zebra dplane"); @@ -2735,6 +3029,23 @@ void zebra_dplane_start(void) thread_add_event(zdplane_info.dg_master, dplane_thread_loop, NULL, 0, &zdplane_info.dg_t_update); + /* Call start callbacks for registered providers */ + + DPLANE_LOCK(); + prov = TAILQ_FIRST(&zdplane_info.dg_providers_q); + DPLANE_UNLOCK(); + + while (prov) { + + if (prov->dp_start) + (prov->dp_start)(prov); + + /* Locate next provider */ + DPLANE_LOCK(); + prov = TAILQ_NEXT(prov, dp_prov_link); + DPLANE_UNLOCK(); + } + frr_pthread_run(zdplane_info.dg_pthread, NULL); } diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h index d45628fdd0..6238026bcf 100644 --- a/zebra/zebra_dplane.h +++ b/zebra/zebra_dplane.h @@ -105,11 +105,13 @@ enum dplane_op_e { DPLANE_OP_ROUTE_INSTALL, DPLANE_OP_ROUTE_UPDATE, DPLANE_OP_ROUTE_DELETE, + DPLANE_OP_ROUTE_NOTIFY, /* LSP update */ DPLANE_OP_LSP_INSTALL, DPLANE_OP_LSP_UPDATE, DPLANE_OP_LSP_DELETE, + DPLANE_OP_LSP_NOTIFY, /* Pseudowire update */ DPLANE_OP_PW_INSTALL, @@ -139,6 +141,9 @@ void dplane_enable_sys_route_notifs(void); */ TAILQ_HEAD(dplane_ctx_q, zebra_dplane_ctx); +/* Allocate a context object */ +struct zebra_dplane_ctx *dplane_ctx_alloc(void); + /* Return a dataplane results context block after use; the caller's pointer will * be cleared. */ @@ -169,9 +174,12 @@ void dplane_ctx_set_status(struct zebra_dplane_ctx *ctx, const char *dplane_res2str(enum zebra_dplane_result res); enum dplane_op_e dplane_ctx_get_op(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_op(struct zebra_dplane_ctx *ctx, enum dplane_op_e op); const char *dplane_op2str(enum dplane_op_e op); const struct prefix *dplane_ctx_get_dest(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_dest(struct zebra_dplane_ctx *ctx, + const struct prefix *dest); /* Retrieve last/current provider id */ uint32_t dplane_ctx_get_provider(const struct zebra_dplane_ctx *ctx); @@ -186,29 +194,44 @@ bool dplane_ctx_is_skip_kernel(const struct zebra_dplane_ctx *ctx); * to mean "no src prefix" */ const struct prefix *dplane_ctx_get_src(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_src(struct zebra_dplane_ctx *ctx, const struct prefix *src); bool dplane_ctx_is_update(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_seq(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_old_seq(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_vrf(struct zebra_dplane_ctx *ctx, vrf_id_t vrf); vrf_id_t dplane_ctx_get_vrf(const struct zebra_dplane_ctx *ctx); +bool dplane_ctx_is_from_notif(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_notif_provider(struct zebra_dplane_ctx *ctx, + uint32_t id); +uint32_t dplane_ctx_get_notif_provider(const struct zebra_dplane_ctx *ctx); + /* Accessors for route update information */ +void dplane_ctx_set_type(struct zebra_dplane_ctx *ctx, int type); int dplane_ctx_get_type(const struct zebra_dplane_ctx *ctx); int dplane_ctx_get_old_type(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_afi(struct zebra_dplane_ctx *ctx, afi_t afi); afi_t dplane_ctx_get_afi(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_safi(struct zebra_dplane_ctx *ctx, safi_t safi); safi_t dplane_ctx_get_safi(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_table(struct zebra_dplane_ctx *ctx, uint32_t table); uint32_t dplane_ctx_get_table(const struct zebra_dplane_ctx *ctx); route_tag_t dplane_ctx_get_tag(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_tag(struct zebra_dplane_ctx *ctx, route_tag_t tag); route_tag_t dplane_ctx_get_old_tag(const struct zebra_dplane_ctx *ctx); uint16_t dplane_ctx_get_instance(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_instance(struct zebra_dplane_ctx *ctx, uint16_t instance); uint16_t dplane_ctx_get_old_instance(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_old_metric(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx); uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx); uint8_t dplane_ctx_get_distance(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_distance(struct zebra_dplane_ctx *ctx, uint8_t distance); uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_nexthops(struct zebra_dplane_ctx *ctx, struct nexthop *nh); const struct nexthop_group *dplane_ctx_get_ng( const struct zebra_dplane_ctx *ctx); const struct nexthop_group *dplane_ctx_get_old_ng( @@ -216,11 +239,26 @@ const struct nexthop_group *dplane_ctx_get_old_ng( /* Accessors for LSP information */ mpls_label_t dplane_ctx_get_in_label(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_in_label(struct zebra_dplane_ctx *ctx, + mpls_label_t label); uint8_t dplane_ctx_get_addr_family(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_addr_family(struct zebra_dplane_ctx *ctx, + uint8_t family); uint32_t dplane_ctx_get_lsp_flags(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_lsp_flags(struct zebra_dplane_ctx *ctx, + uint32_t flags); const zebra_nhlfe_t *dplane_ctx_get_nhlfe(const struct zebra_dplane_ctx *ctx); +zebra_nhlfe_t *dplane_ctx_add_nhlfe(struct zebra_dplane_ctx *ctx, + enum lsp_types_t lsp_type, + enum nexthop_types_t nh_type, + union g_addr *gate, + ifindex_t ifindex, + mpls_label_t out_label); + const zebra_nhlfe_t *dplane_ctx_get_best_nhlfe( const struct zebra_dplane_ctx *ctx); +const zebra_nhlfe_t *dplane_ctx_set_best_nhlfe(struct zebra_dplane_ctx *ctx, + zebra_nhlfe_t *nhlfe); uint32_t dplane_ctx_get_lsp_num_ecmp(const struct zebra_dplane_ctx *ctx); /* Accessors for pseudowire information */ @@ -282,6 +320,13 @@ enum zebra_dplane_result dplane_sys_route_add(struct route_node *rn, enum zebra_dplane_result dplane_sys_route_del(struct route_node *rn, struct route_entry *re); +/* Update from an async notification, to bring other fibs up-to-date */ +enum zebra_dplane_result dplane_route_notif_update( + struct route_node *rn, + struct route_entry *re, + enum dplane_op_e op, + struct zebra_dplane_ctx *ctx); + /* * Enqueue LSP change operations for the dataplane. */ @@ -289,6 +334,11 @@ enum zebra_dplane_result dplane_lsp_add(zebra_lsp_t *lsp); enum zebra_dplane_result dplane_lsp_update(zebra_lsp_t *lsp); enum zebra_dplane_result dplane_lsp_delete(zebra_lsp_t *lsp); +/* Update or un-install resulting from an async notification */ +enum zebra_dplane_result dplane_lsp_notif_update(zebra_lsp_t *lsp, + enum dplane_op_e op, + struct zebra_dplane_ctx *ctx); + /* * Enqueue pseudowire operations for the dataplane. */ @@ -321,7 +371,6 @@ uint32_t dplane_get_in_queue_len(void); int dplane_show_helper(struct vty *vty, bool detailed); int dplane_show_provs_helper(struct vty *vty, bool detailed); - /* * Dataplane providers: modules that process or consume dataplane events. */ @@ -363,7 +412,13 @@ enum dplane_provider_prio { * then checks the provider's outbound queue for completed work. */ -/* Providers offer an entry-point for shutdown and cleanup. This is called +/* + * Providers can offer a 'start' callback; if present, the dataplane will + * call it when it is starting - when its pthread and event-scheduling + * thread_master are available. + */ + +/* Providers can offer an entry-point for shutdown and cleanup. This is called * with 'early' during shutdown, to indicate that the dataplane subsystem * is allowing work to move through the providers and finish. * When called without 'early', the provider should release @@ -372,6 +427,7 @@ enum dplane_provider_prio { int dplane_provider_register(const char *name, enum dplane_provider_prio prio, int flags, + int (*start_fp)(struct zebra_dplane_provider *), int (*fp)(struct zebra_dplane_provider *), int (*fini_fp)(struct zebra_dplane_provider *, bool early), @@ -409,10 +465,13 @@ struct zebra_dplane_ctx *dplane_provider_dequeue_in_ctx( int dplane_provider_dequeue_in_list(struct zebra_dplane_provider *prov, struct dplane_ctx_q *listp); -/* Enqueue, maintain associated counter and locking */ +/* Enqueue completed work, maintain associated counter and locking */ void dplane_provider_enqueue_out_ctx(struct zebra_dplane_provider *prov, struct zebra_dplane_ctx *ctx); +/* Enqueue a context directly to zebra main. */ +void dplane_provider_enqueue_to_zebra(struct zebra_dplane_ctx *ctx); + /* * Initialize the dataplane modules at zebra startup. This is currently called * by the rib module. Zebra registers a results callback with the dataplane. diff --git a/zebra/zebra_mpls.c b/zebra/zebra_mpls.c index 5356a7f498..48366417dd 100644 --- a/zebra/zebra_mpls.c +++ b/zebra/zebra_mpls.c @@ -1810,6 +1810,214 @@ void zebra_mpls_lsp_dplane_result(struct zebra_dplane_ctx *ctx) } /* + * Process async dplane notifications. + */ +void zebra_mpls_process_dplane_notify(struct zebra_dplane_ctx *ctx) +{ + struct zebra_vrf *zvrf; + zebra_ile_t tmp_ile; + struct hash *lsp_table; + zebra_lsp_t *lsp; + zebra_nhlfe_t *nhlfe; + const zebra_nhlfe_t *ctx_nhlfe; + struct nexthop *nexthop; + const struct nexthop *ctx_nexthop; + int start_count = 0, end_count = 0; /* Installed counts */ + bool changed_p = false; + bool is_debug = (IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_MPLS); + + if (is_debug) + zlog_debug("LSP dplane notif, in-label %u", + dplane_ctx_get_in_label(ctx)); + + /* Look for zebra LSP object */ + zvrf = vrf_info_lookup(VRF_DEFAULT); + if (zvrf == NULL) + goto done; + + lsp_table = zvrf->lsp_table; + + tmp_ile.in_label = dplane_ctx_get_in_label(ctx); + lsp = hash_lookup(lsp_table, &tmp_ile); + if (lsp == NULL) { + if (is_debug) + zlog_debug("dplane LSP notif: in-label %u not found", + dplane_ctx_get_in_label(ctx)); + goto done; + } + + /* + * The dataplane/forwarding plane is notifying zebra about the state + * of the nexthops associated with this LSP. First, we take a + * pre-scan pass to determine whether the LSP has transitioned + * from installed -> uninstalled. In that case, we need to have + * the existing state of the LSP objects available before making + * any changes. + */ + for (nhlfe = lsp->nhlfe_list; nhlfe; nhlfe = nhlfe->next) { + char buf[NEXTHOP_STRLEN]; + + nexthop = nhlfe->nexthop; + if (!nexthop) + continue; + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + start_count++; + + ctx_nexthop = NULL; + for (ctx_nhlfe = dplane_ctx_get_nhlfe(ctx); + ctx_nhlfe; ctx_nhlfe = ctx_nhlfe->next) { + + ctx_nexthop = ctx_nhlfe->nexthop; + if (!ctx_nexthop) + continue; + + if ((ctx_nexthop->type == nexthop->type) && + nexthop_same(ctx_nexthop, nexthop)) { + /* Matched */ + break; + } + } + + if (is_debug) + nexthop2str(nexthop, buf, sizeof(buf)); + + if (ctx_nhlfe && ctx_nexthop) { + if (is_debug) { + const char *tstr = ""; + + if (!CHECK_FLAG(ctx_nhlfe->flags, + NHLFE_FLAG_INSTALLED)) + tstr = "not "; + + zlog_debug("LSP dplane notif: matched nh %s (%sinstalled)", + buf, tstr); + } + + /* Test zebra nhlfe install state */ + if (CHECK_FLAG(ctx_nhlfe->flags, + NHLFE_FLAG_INSTALLED)) { + + if (!CHECK_FLAG(nhlfe->flags, + NHLFE_FLAG_INSTALLED)) + changed_p = true; + + /* Update counter */ + end_count++; + } else { + + if (CHECK_FLAG(nhlfe->flags, + NHLFE_FLAG_INSTALLED)) + changed_p = true; + } + + } else { + /* Not mentioned in lfib set -> uninstalled */ + if (CHECK_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED) || + CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE) || + CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) { + changed_p = true; + } + + if (is_debug) + zlog_debug("LSP dplane notif: no match, nh %s", + buf); + } + } + + if (is_debug) + zlog_debug("LSP dplane notif: lfib start_count %d, end_count %d%s", + start_count, end_count, + changed_p ? ", changed" : ""); + + /* + * Has the LSP become uninstalled? + */ + if (start_count > 0 && end_count == 0) { + /* Inform other lfibs */ + dplane_lsp_notif_update(lsp, DPLANE_OP_LSP_DELETE, ctx); + } + + /* + * Now we take a second pass and bring the zebra + * nexthop state into sync with the forwarding-plane state. + */ + for (nhlfe = lsp->nhlfe_list; nhlfe; nhlfe = nhlfe->next) { + char buf[NEXTHOP_STRLEN]; + + nexthop = nhlfe->nexthop; + if (!nexthop) + continue; + + ctx_nexthop = NULL; + for (ctx_nhlfe = dplane_ctx_get_nhlfe(ctx); + ctx_nhlfe; ctx_nhlfe = ctx_nhlfe->next) { + + ctx_nexthop = ctx_nhlfe->nexthop; + if (!ctx_nexthop) + continue; + + if ((ctx_nexthop->type == nexthop->type) && + nexthop_same(ctx_nexthop, nexthop)) { + /* Matched */ + break; + } + } + + if (is_debug) + nexthop2str(nexthop, buf, sizeof(buf)); + + if (ctx_nhlfe && ctx_nexthop) { + + /* Bring zebra nhlfe install state into sync */ + if (CHECK_FLAG(ctx_nhlfe->flags, + NHLFE_FLAG_INSTALLED)) { + + SET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED); + + } else { + + UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED); + } + + if (CHECK_FLAG(ctx_nhlfe->nexthop->flags, + NEXTHOP_FLAG_FIB)) { + SET_FLAG(nhlfe->nexthop->flags, + NEXTHOP_FLAG_ACTIVE); + SET_FLAG(nhlfe->nexthop->flags, + NEXTHOP_FLAG_FIB); + } else { + UNSET_FLAG(nhlfe->nexthop->flags, + NEXTHOP_FLAG_ACTIVE); + UNSET_FLAG(nhlfe->nexthop->flags, + NEXTHOP_FLAG_FIB); + } + + } else { + /* Not mentioned in lfib set -> uninstalled */ + + UNSET_FLAG(nhlfe->flags, NHLFE_FLAG_INSTALLED); + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + } + } + + if (end_count > 0) { + SET_FLAG(lsp->flags, LSP_FLAG_INSTALLED); + + if (changed_p) + dplane_lsp_notif_update(lsp, DPLANE_OP_LSP_UPDATE, ctx); + + } else { + UNSET_FLAG(lsp->flags, LSP_FLAG_INSTALLED); + clear_nhlfe_installed(lsp); + } + +done: + dplane_ctx_fini(&ctx); +} + +/* * Install dynamic LSP entry. */ int zebra_mpls_lsp_install(struct zebra_vrf *zvrf, struct route_node *rn, diff --git a/zebra/zebra_mpls.h b/zebra/zebra_mpls.h index 3a131e1aaf..d983221cb5 100644 --- a/zebra/zebra_mpls.h +++ b/zebra/zebra_mpls.h @@ -352,6 +352,9 @@ struct zebra_dplane_ctx; void zebra_mpls_lsp_dplane_result(struct zebra_dplane_ctx *ctx); +/* Process async dplane notifications. */ +void zebra_mpls_process_dplane_notify(struct zebra_dplane_ctx *ctx); + /* * Schedule all MPLS label forwarding entries for processing. * Called upon changes that may affect one or more of them such as diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c new file mode 100644 index 0000000000..f2a76d1c52 --- /dev/null +++ b/zebra/zebra_nhg.c @@ -0,0 +1,511 @@ +/* Zebra Nexthop Group Code. + * Copyright (C) 2019 Cumulus Networks, Inc. + * Donald Sharp + * Stephen Worley + * + * This file is part of FRR. + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#include <zebra.h> + +#include "lib/nexthop.h" +#include "lib/routemap.h" + +#include "zebra/connected.h" +#include "zebra/debug.h" +#include "zebra/zebra_router.h" +#include "zebra/zebra_nhg.h" +#include "zebra/zebra_rnh.h" +#include "zebra/zebra_routemap.h" +#include "zebra/rt.h" + +static void nexthop_set_resolved(afi_t afi, const struct nexthop *newhop, + struct nexthop *nexthop) +{ + struct nexthop *resolved_hop; + + resolved_hop = nexthop_new(); + SET_FLAG(resolved_hop->flags, NEXTHOP_FLAG_ACTIVE); + + resolved_hop->vrf_id = nexthop->vrf_id; + switch (newhop->type) { + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + /* If the resolving route specifies a gateway, use it */ + resolved_hop->type = newhop->type; + resolved_hop->gate.ipv4 = newhop->gate.ipv4; + + if (newhop->ifindex) { + resolved_hop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + resolved_hop->ifindex = newhop->ifindex; + } + break; + case NEXTHOP_TYPE_IPV6: + case NEXTHOP_TYPE_IPV6_IFINDEX: + resolved_hop->type = newhop->type; + resolved_hop->gate.ipv6 = newhop->gate.ipv6; + + if (newhop->ifindex) { + resolved_hop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + resolved_hop->ifindex = newhop->ifindex; + } + break; + case NEXTHOP_TYPE_IFINDEX: + /* If the resolving route is an interface route, + * it means the gateway we are looking up is connected + * to that interface. (The actual network is _not_ onlink). + * Therefore, the resolved route should have the original + * gateway as nexthop as it is directly connected. + * + * On Linux, we have to set the onlink netlink flag because + * otherwise, the kernel won't accept the route. + */ + resolved_hop->flags |= NEXTHOP_FLAG_ONLINK; + if (afi == AFI_IP) { + resolved_hop->type = NEXTHOP_TYPE_IPV4_IFINDEX; + resolved_hop->gate.ipv4 = nexthop->gate.ipv4; + } else if (afi == AFI_IP6) { + resolved_hop->type = NEXTHOP_TYPE_IPV6_IFINDEX; + resolved_hop->gate.ipv6 = nexthop->gate.ipv6; + } + resolved_hop->ifindex = newhop->ifindex; + break; + case NEXTHOP_TYPE_BLACKHOLE: + resolved_hop->type = NEXTHOP_TYPE_BLACKHOLE; + resolved_hop->bh_type = nexthop->bh_type; + break; + } + + if (newhop->flags & NEXTHOP_FLAG_ONLINK) + resolved_hop->flags |= NEXTHOP_FLAG_ONLINK; + + /* Copy labels of the resolved route */ + if (newhop->nh_label) + nexthop_add_labels(resolved_hop, newhop->nh_label_type, + newhop->nh_label->num_labels, + &newhop->nh_label->label[0]); + + resolved_hop->rparent = nexthop; + nexthop_add(&nexthop->resolved, resolved_hop); +} + +/* + * Given a nexthop we need to properly recursively resolve + * the route. As such, do a table lookup to find and match + * if at all possible. Set the nexthop->ifindex as appropriate + */ +static int nexthop_active(afi_t afi, struct route_entry *re, + struct nexthop *nexthop, struct route_node *top) +{ + struct prefix p; + struct route_table *table; + struct route_node *rn; + struct route_entry *match = NULL; + int resolved; + struct nexthop *newhop; + struct interface *ifp; + rib_dest_t *dest; + + if ((nexthop->type == NEXTHOP_TYPE_IPV4) + || nexthop->type == NEXTHOP_TYPE_IPV6) + nexthop->ifindex = 0; + + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE); + nexthops_free(nexthop->resolved); + nexthop->resolved = NULL; + re->nexthop_mtu = 0; + + /* + * If the kernel has sent us a route, then + * by golly gee whiz it's a good route. + */ + if (re->type == ZEBRA_ROUTE_KERNEL || re->type == ZEBRA_ROUTE_SYSTEM) + return 1; + + /* + * Check to see if we should trust the passed in information + * for UNNUMBERED interfaces as that we won't find the GW + * address in the routing table. + * This check should suffice to handle IPv4 or IPv6 routes + * sourced from EVPN routes which are installed with the + * next hop as the remote VTEP IP. + */ + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) { + ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); + if (!ifp) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t%s: Onlink and interface: %u[%u] does not exist", + __PRETTY_FUNCTION__, nexthop->ifindex, + nexthop->vrf_id); + return 0; + } + if (connected_is_unnumbered(ifp)) { + if (if_is_operative(ifp)) + return 1; + else { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t%s: Onlink and interface %s is not operative", + __PRETTY_FUNCTION__, ifp->name); + return 0; + } + } + if (!if_is_operative(ifp)) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t%s: Interface %s is not unnumbered", + __PRETTY_FUNCTION__, ifp->name); + return 0; + } + } + + /* Make lookup prefix. */ + memset(&p, 0, sizeof(struct prefix)); + switch (afi) { + case AFI_IP: + p.family = AF_INET; + p.prefixlen = IPV4_MAX_PREFIXLEN; + p.u.prefix4 = nexthop->gate.ipv4; + break; + case AFI_IP6: + p.family = AF_INET6; + p.prefixlen = IPV6_MAX_PREFIXLEN; + p.u.prefix6 = nexthop->gate.ipv6; + break; + default: + assert(afi != AFI_IP && afi != AFI_IP6); + break; + } + /* Lookup table. */ + table = zebra_vrf_table(afi, SAFI_UNICAST, nexthop->vrf_id); + if (!table) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("\t%s: Table not found", + __PRETTY_FUNCTION__); + return 0; + } + + rn = route_node_match(table, (struct prefix *)&p); + while (rn) { + route_unlock_node(rn); + + /* Lookup should halt if we've matched against ourselves ('top', + * if specified) - i.e., we cannot have a nexthop NH1 is + * resolved by a route NH1. The exception is if the route is a + * host route. + */ + if (top && rn == top) + if (((afi == AFI_IP) && (rn->p.prefixlen != 32)) + || ((afi == AFI_IP6) && (rn->p.prefixlen != 128))) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t%s: Matched against ourself and prefix length is not max bit length", + __PRETTY_FUNCTION__); + return 0; + } + + /* Pick up selected route. */ + /* However, do not resolve over default route unless explicitly + * allowed. */ + if (is_default_prefix(&rn->p) + && !rnh_resolve_via_default(p.family)) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t:%s: Resolved against default route", + __PRETTY_FUNCTION__); + return 0; + } + + dest = rib_dest_from_rnode(rn); + if (dest && dest->selected_fib + && !CHECK_FLAG(dest->selected_fib->status, + ROUTE_ENTRY_REMOVED) + && dest->selected_fib->type != ZEBRA_ROUTE_TABLE) + match = dest->selected_fib; + + /* If there is no selected route or matched route is EGP, go up + tree. */ + if (!match) { + do { + rn = rn->parent; + } while (rn && rn->info == NULL); + if (rn) + route_lock_node(rn); + + continue; + } + + if (match->type == ZEBRA_ROUTE_CONNECT) { + /* Directly point connected route. */ + newhop = match->ng.nexthop; + if (newhop) { + if (nexthop->type == NEXTHOP_TYPE_IPV4 + || nexthop->type == NEXTHOP_TYPE_IPV6) + nexthop->ifindex = newhop->ifindex; + } + return 1; + } else if (CHECK_FLAG(re->flags, ZEBRA_FLAG_ALLOW_RECURSION)) { + resolved = 0; + for (ALL_NEXTHOPS(match->ng, newhop)) { + if (!CHECK_FLAG(match->status, + ROUTE_ENTRY_INSTALLED)) + continue; + if (CHECK_FLAG(newhop->flags, + NEXTHOP_FLAG_RECURSIVE)) + continue; + + SET_FLAG(nexthop->flags, + NEXTHOP_FLAG_RECURSIVE); + SET_FLAG(re->status, + ROUTE_ENTRY_NEXTHOPS_CHANGED); + nexthop_set_resolved(afi, newhop, nexthop); + resolved = 1; + } + if (resolved) + re->nexthop_mtu = match->mtu; + if (!resolved && IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("\t%s: Recursion failed to find", + __PRETTY_FUNCTION__); + return resolved; + } else if (re->type == ZEBRA_ROUTE_STATIC) { + resolved = 0; + for (ALL_NEXTHOPS(match->ng, newhop)) { + if (!CHECK_FLAG(match->status, + ROUTE_ENTRY_INSTALLED)) + continue; + if (CHECK_FLAG(newhop->flags, + NEXTHOP_FLAG_RECURSIVE)) + continue; + + SET_FLAG(nexthop->flags, + NEXTHOP_FLAG_RECURSIVE); + nexthop_set_resolved(afi, newhop, nexthop); + resolved = 1; + } + if (resolved) + re->nexthop_mtu = match->mtu; + + if (!resolved && IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug( + "\t%s: Static route unable to resolve", + __PRETTY_FUNCTION__); + return resolved; + } else { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) { + zlog_debug( + "\t%s: Route Type %s has not turned on recursion", + __PRETTY_FUNCTION__, + zebra_route_string(re->type)); + if (re->type == ZEBRA_ROUTE_BGP + && !CHECK_FLAG(re->flags, ZEBRA_FLAG_IBGP)) + zlog_debug( + "\tEBGP: see \"disable-ebgp-connected-route-check\" or \"disable-connected-check\""); + } + return 0; + } + } + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("\t%s: Nexthop did not lookup in table", + __PRETTY_FUNCTION__); + return 0; +} + +/* This function verifies reachability of one given nexthop, which can be + * numbered or unnumbered, IPv4 or IPv6. The result is unconditionally stored + * in nexthop->flags field. The nexthop->ifindex will be updated + * appropriately as well. An existing route map can turn + * (otherwise active) nexthop into inactive, but not vice versa. + * + * The return value is the final value of 'ACTIVE' flag. + */ +static unsigned nexthop_active_check(struct route_node *rn, + struct route_entry *re, + struct nexthop *nexthop) +{ + struct interface *ifp; + route_map_result_t ret = RMAP_MATCH; + int family; + char buf[SRCDEST2STR_BUFFER]; + const struct prefix *p, *src_p; + struct zebra_vrf *zvrf; + + srcdest_rnode_prefixes(rn, &p, &src_p); + + if (rn->p.family == AF_INET) + family = AFI_IP; + else if (rn->p.family == AF_INET6) + family = AFI_IP6; + else + family = 0; + switch (nexthop->type) { + case NEXTHOP_TYPE_IFINDEX: + ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); + if (ifp && if_is_operative(ifp)) + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + break; + case NEXTHOP_TYPE_IPV4: + case NEXTHOP_TYPE_IPV4_IFINDEX: + family = AFI_IP; + if (nexthop_active(AFI_IP, re, nexthop, rn)) + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + break; + case NEXTHOP_TYPE_IPV6: + family = AFI_IP6; + if (nexthop_active(AFI_IP6, re, nexthop, rn)) + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + break; + case NEXTHOP_TYPE_IPV6_IFINDEX: + /* RFC 5549, v4 prefix with v6 NH */ + if (rn->p.family != AF_INET) + family = AFI_IP6; + if (IN6_IS_ADDR_LINKLOCAL(&nexthop->gate.ipv6)) { + ifp = if_lookup_by_index(nexthop->ifindex, + nexthop->vrf_id); + if (ifp && if_is_operative(ifp)) + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + } else { + if (nexthop_active(AFI_IP6, re, nexthop, rn)) + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + else + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + } + break; + case NEXTHOP_TYPE_BLACKHOLE: + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + break; + default: + break; + } + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("\t%s: Unable to find a active nexthop", + __PRETTY_FUNCTION__); + return 0; + } + + /* XXX: What exactly do those checks do? Do we support + * e.g. IPv4 routes with IPv6 nexthops or vice versa? + */ + if (RIB_SYSTEM_ROUTE(re) || (family == AFI_IP && p->family != AF_INET) + || (family == AFI_IP6 && p->family != AF_INET6)) + return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + + /* The original code didn't determine the family correctly + * e.g. for NEXTHOP_TYPE_IFINDEX. Retrieve the correct afi + * from the rib_table_info in those cases. + * Possibly it may be better to use only the rib_table_info + * in every case. + */ + if (!family) { + rib_table_info_t *info; + + info = srcdest_rnode_table_info(rn); + family = info->afi; + } + + memset(&nexthop->rmap_src.ipv6, 0, sizeof(union g_addr)); + + zvrf = zebra_vrf_lookup_by_id(nexthop->vrf_id); + if (!zvrf) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("\t%s: zvrf is NULL", __PRETTY_FUNCTION__); + return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + } + + /* It'll get set if required inside */ + ret = zebra_route_map_check(family, re->type, re->instance, p, nexthop, + zvrf, re->tag); + if (ret == RMAP_DENYMATCH) { + if (IS_ZEBRA_DEBUG_RIB) { + srcdest_rnode2str(rn, buf, sizeof(buf)); + zlog_debug( + "%u:%s: Filtering out with NH out %s due to route map", + re->vrf_id, buf, + ifindex2ifname(nexthop->ifindex, + nexthop->vrf_id)); + } + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + } + return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); +} + +/* + * Iterate over all nexthops of the given RIB entry and refresh their + * ACTIVE flag. re->nexthop_active_num is updated accordingly. If any + * nexthop is found to toggle the ACTIVE flag, the whole re structure + * is flagged with ROUTE_ENTRY_CHANGED. + * + * Return value is the new number of active nexthops. + */ +int nexthop_active_update(struct route_node *rn, struct route_entry *re) +{ + struct nexthop *nexthop; + union g_addr prev_src; + unsigned int prev_active, new_active; + ifindex_t prev_index; + + re->nexthop_active_num = 0; + UNSET_FLAG(re->status, ROUTE_ENTRY_CHANGED); + + for (nexthop = re->ng.nexthop; nexthop; nexthop = nexthop->next) { + /* No protocol daemon provides src and so we're skipping + * tracking it */ + prev_src = nexthop->rmap_src; + prev_active = CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + prev_index = nexthop->ifindex; + /* + * We need to respect the multipath_num here + * as that what we should be able to install from + * a multipath perpsective should not be a data plane + * decision point. + */ + new_active = nexthop_active_check(rn, re, nexthop); + if (new_active + && re->nexthop_active_num >= zrouter.multipath_num) { + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); + new_active = 0; + } + if (new_active) + re->nexthop_active_num++; + /* Don't allow src setting on IPv6 addr for now */ + if (prev_active != new_active || prev_index != nexthop->ifindex + || ((nexthop->type >= NEXTHOP_TYPE_IFINDEX + && nexthop->type < NEXTHOP_TYPE_IPV6) + && prev_src.ipv4.s_addr + != nexthop->rmap_src.ipv4.s_addr) + || ((nexthop->type >= NEXTHOP_TYPE_IPV6 + && nexthop->type < NEXTHOP_TYPE_BLACKHOLE) + && !(IPV6_ADDR_SAME(&prev_src.ipv6, + &nexthop->rmap_src.ipv6))) + || CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED)) { + SET_FLAG(re->status, ROUTE_ENTRY_CHANGED); + SET_FLAG(re->status, ROUTE_ENTRY_NEXTHOPS_CHANGED); + } + } + + return re->nexthop_active_num; +} + diff --git a/zebra/zebra_nhg.h b/zebra/zebra_nhg.h new file mode 100644 index 0000000000..ff2351c759 --- /dev/null +++ b/zebra/zebra_nhg.h @@ -0,0 +1,29 @@ +/* Zebra Nexthop Group header. + * Copyright (C) 2019 Cumulus Networks, Inc. + * Donald Sharp + * Stephen Worley + * + * This file is part of FRR. + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with FRR; see the file COPYING. If not, write to the Free + * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ +#ifndef __ZEBRA_NHG_H__ +#define __ZEBRA_NHG_H__ + +#include "zebra/rib.h" + +extern int nexthop_active_update(struct route_node *rn, struct route_entry *re); +#endif diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index b31b6a1250..600e820bc4 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -54,6 +54,7 @@ #include "zebra/zebra_vxlan.h" #include "zebra/zapi_msg.h" #include "zebra/zebra_dplane.h" +#include "zebra/zebra_nhg.h" /* * Event, list, and mutex for delivery of dataplane results @@ -336,298 +337,6 @@ struct nexthop *route_entry_nexthop_blackhole_add(struct route_entry *re, return nexthop; } -static void nexthop_set_resolved(afi_t afi, const struct nexthop *newhop, - struct nexthop *nexthop) -{ - struct nexthop *resolved_hop; - - resolved_hop = nexthop_new(); - SET_FLAG(resolved_hop->flags, NEXTHOP_FLAG_ACTIVE); - - resolved_hop->vrf_id = nexthop->vrf_id; - switch (newhop->type) { - case NEXTHOP_TYPE_IPV4: - case NEXTHOP_TYPE_IPV4_IFINDEX: - /* If the resolving route specifies a gateway, use it */ - resolved_hop->type = newhop->type; - resolved_hop->gate.ipv4 = newhop->gate.ipv4; - - if (newhop->ifindex) { - resolved_hop->type = NEXTHOP_TYPE_IPV4_IFINDEX; - resolved_hop->ifindex = newhop->ifindex; - } - break; - case NEXTHOP_TYPE_IPV6: - case NEXTHOP_TYPE_IPV6_IFINDEX: - resolved_hop->type = newhop->type; - resolved_hop->gate.ipv6 = newhop->gate.ipv6; - - if (newhop->ifindex) { - resolved_hop->type = NEXTHOP_TYPE_IPV6_IFINDEX; - resolved_hop->ifindex = newhop->ifindex; - } - break; - case NEXTHOP_TYPE_IFINDEX: - /* If the resolving route is an interface route, - * it means the gateway we are looking up is connected - * to that interface. (The actual network is _not_ onlink). - * Therefore, the resolved route should have the original - * gateway as nexthop as it is directly connected. - * - * On Linux, we have to set the onlink netlink flag because - * otherwise, the kernel won't accept the route. - */ - resolved_hop->flags |= NEXTHOP_FLAG_ONLINK; - if (afi == AFI_IP) { - resolved_hop->type = NEXTHOP_TYPE_IPV4_IFINDEX; - resolved_hop->gate.ipv4 = nexthop->gate.ipv4; - } else if (afi == AFI_IP6) { - resolved_hop->type = NEXTHOP_TYPE_IPV6_IFINDEX; - resolved_hop->gate.ipv6 = nexthop->gate.ipv6; - } - resolved_hop->ifindex = newhop->ifindex; - break; - case NEXTHOP_TYPE_BLACKHOLE: - resolved_hop->type = NEXTHOP_TYPE_BLACKHOLE; - resolved_hop->bh_type = nexthop->bh_type; - break; - } - - if (newhop->flags & NEXTHOP_FLAG_ONLINK) - resolved_hop->flags |= NEXTHOP_FLAG_ONLINK; - - /* Copy labels of the resolved route */ - if (newhop->nh_label) - nexthop_add_labels(resolved_hop, newhop->nh_label_type, - newhop->nh_label->num_labels, - &newhop->nh_label->label[0]); - - resolved_hop->rparent = nexthop; - nexthop_add(&nexthop->resolved, resolved_hop); -} - -/* - * Given a nexthop we need to properly recursively resolve - * the route. As such, do a table lookup to find and match - * if at all possible. Set the nexthop->ifindex as appropriate - */ -static int nexthop_active(afi_t afi, struct route_entry *re, - struct nexthop *nexthop, - struct route_node *top) -{ - struct prefix p; - struct route_table *table; - struct route_node *rn; - struct route_entry *match = NULL; - int resolved; - struct nexthop *newhop; - struct interface *ifp; - rib_dest_t *dest; - - if ((nexthop->type == NEXTHOP_TYPE_IPV4) - || nexthop->type == NEXTHOP_TYPE_IPV6) - nexthop->ifindex = 0; - - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE); - nexthops_free(nexthop->resolved); - nexthop->resolved = NULL; - re->nexthop_mtu = 0; - - /* - * If the kernel has sent us a route, then - * by golly gee whiz it's a good route. - */ - if (re->type == ZEBRA_ROUTE_KERNEL || - re->type == ZEBRA_ROUTE_SYSTEM) - return 1; - - /* - * Check to see if we should trust the passed in information - * for UNNUMBERED interfaces as that we won't find the GW - * address in the routing table. - * This check should suffice to handle IPv4 or IPv6 routes - * sourced from EVPN routes which are installed with the - * next hop as the remote VTEP IP. - */ - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) { - ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); - if (!ifp) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t%s: Onlink and interface: %u[%u] does not exist", - __PRETTY_FUNCTION__, nexthop->ifindex, - nexthop->vrf_id); - return 0; - } - if (connected_is_unnumbered(ifp)) { - if (if_is_operative(ifp)) - return 1; - else { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t%s: Onlink and interface %s is not operative", - __PRETTY_FUNCTION__, ifp->name); - return 0; - } - } - if (!if_is_operative(ifp)) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t%s: Interface %s is not unnumbered", - __PRETTY_FUNCTION__, ifp->name); - return 0; - } - } - - /* Make lookup prefix. */ - memset(&p, 0, sizeof(struct prefix)); - switch (afi) { - case AFI_IP: - p.family = AF_INET; - p.prefixlen = IPV4_MAX_PREFIXLEN; - p.u.prefix4 = nexthop->gate.ipv4; - break; - case AFI_IP6: - p.family = AF_INET6; - p.prefixlen = IPV6_MAX_PREFIXLEN; - p.u.prefix6 = nexthop->gate.ipv6; - break; - default: - assert(afi != AFI_IP && afi != AFI_IP6); - break; - } - /* Lookup table. */ - table = zebra_vrf_table(afi, SAFI_UNICAST, nexthop->vrf_id); - if (!table) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug("\t%s: Table not found", - __PRETTY_FUNCTION__); - return 0; - } - - rn = route_node_match(table, (struct prefix *)&p); - while (rn) { - route_unlock_node(rn); - - /* Lookup should halt if we've matched against ourselves ('top', - * if specified) - i.e., we cannot have a nexthop NH1 is - * resolved by a route NH1. The exception is if the route is a - * host route. - */ - if (top && rn == top) - if (((afi == AFI_IP) && (rn->p.prefixlen != 32)) - || ((afi == AFI_IP6) && (rn->p.prefixlen != 128))) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t%s: Matched against ourself and prefix length is not max bit length", - __PRETTY_FUNCTION__); - return 0; - } - - /* Pick up selected route. */ - /* However, do not resolve over default route unless explicitly - * allowed. */ - if (is_default_prefix(&rn->p) - && !rnh_resolve_via_default(p.family)) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t:%s: Resolved against default route", - __PRETTY_FUNCTION__); - return 0; - } - - dest = rib_dest_from_rnode(rn); - if (dest && dest->selected_fib - && !CHECK_FLAG(dest->selected_fib->status, - ROUTE_ENTRY_REMOVED) - && dest->selected_fib->type != ZEBRA_ROUTE_TABLE) - match = dest->selected_fib; - - /* If there is no selected route or matched route is EGP, go up - tree. */ - if (!match) { - do { - rn = rn->parent; - } while (rn && rn->info == NULL); - if (rn) - route_lock_node(rn); - - continue; - } - - if (match->type == ZEBRA_ROUTE_CONNECT) { - /* Directly point connected route. */ - newhop = match->ng.nexthop; - if (newhop) { - if (nexthop->type == NEXTHOP_TYPE_IPV4 - || nexthop->type == NEXTHOP_TYPE_IPV6) - nexthop->ifindex = newhop->ifindex; - } - return 1; - } else if (CHECK_FLAG(re->flags, ZEBRA_FLAG_ALLOW_RECURSION)) { - resolved = 0; - for (ALL_NEXTHOPS(match->ng, newhop)) { - if (!CHECK_FLAG(match->status, - ROUTE_ENTRY_INSTALLED)) - continue; - if (CHECK_FLAG(newhop->flags, - NEXTHOP_FLAG_RECURSIVE)) - continue; - - SET_FLAG(nexthop->flags, - NEXTHOP_FLAG_RECURSIVE); - SET_FLAG(re->status, - ROUTE_ENTRY_NEXTHOPS_CHANGED); - nexthop_set_resolved(afi, newhop, nexthop); - resolved = 1; - } - if (resolved) - re->nexthop_mtu = match->mtu; - if (!resolved && IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug("\t%s: Recursion failed to find", - __PRETTY_FUNCTION__); - return resolved; - } else if (re->type == ZEBRA_ROUTE_STATIC) { - resolved = 0; - for (ALL_NEXTHOPS(match->ng, newhop)) { - if (!CHECK_FLAG(match->status, - ROUTE_ENTRY_INSTALLED)) - continue; - if (CHECK_FLAG(newhop->flags, - NEXTHOP_FLAG_RECURSIVE)) - continue; - - SET_FLAG(nexthop->flags, - NEXTHOP_FLAG_RECURSIVE); - nexthop_set_resolved(afi, newhop, nexthop); - resolved = 1; - } - if (resolved) - re->nexthop_mtu = match->mtu; - - if (!resolved && IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug( - "\t%s: Static route unable to resolve", - __PRETTY_FUNCTION__); - return resolved; - } else { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) { - zlog_debug("\t%s: Route Type %s has not turned on recursion", - __PRETTY_FUNCTION__, - zebra_route_string(re->type)); - if (re->type == ZEBRA_ROUTE_BGP && - !CHECK_FLAG(re->flags, ZEBRA_FLAG_IBGP)) - zlog_debug("\tEBGP: see \"disable-ebgp-connected-route-check\" or \"disable-connected-check\""); - } - return 0; - } - } - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug("\t%s: Nexthop did not lookup in table", - __PRETTY_FUNCTION__); - return 0; -} - struct route_entry *rib_match(afi_t afi, safi_t safi, vrf_id_t vrf_id, union g_addr *addr, struct route_node **rn_out) { @@ -798,190 +507,6 @@ struct route_entry *rib_lookup_ipv4(struct prefix_ipv4 *p, vrf_id_t vrf_id) return NULL; } -/* This function verifies reachability of one given nexthop, which can be - * numbered or unnumbered, IPv4 or IPv6. The result is unconditionally stored - * in nexthop->flags field. The nexthop->ifindex will be updated - * appropriately as well. An existing route map can turn - * (otherwise active) nexthop into inactive, but not vice versa. - * - * The return value is the final value of 'ACTIVE' flag. - */ -static unsigned nexthop_active_check(struct route_node *rn, - struct route_entry *re, - struct nexthop *nexthop) -{ - struct interface *ifp; - route_map_result_t ret = RMAP_MATCH; - int family; - char buf[SRCDEST2STR_BUFFER]; - const struct prefix *p, *src_p; - struct zebra_vrf *zvrf; - - srcdest_rnode_prefixes(rn, &p, &src_p); - - if (rn->p.family == AF_INET) - family = AFI_IP; - else if (rn->p.family == AF_INET6) - family = AFI_IP6; - else - family = 0; - switch (nexthop->type) { - case NEXTHOP_TYPE_IFINDEX: - ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); - if (ifp && if_is_operative(ifp)) - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - else - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - break; - case NEXTHOP_TYPE_IPV4: - case NEXTHOP_TYPE_IPV4_IFINDEX: - family = AFI_IP; - if (nexthop_active(AFI_IP, re, nexthop, rn)) - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - else - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - break; - case NEXTHOP_TYPE_IPV6: - family = AFI_IP6; - if (nexthop_active(AFI_IP6, re, nexthop, rn)) - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - else - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - break; - case NEXTHOP_TYPE_IPV6_IFINDEX: - /* RFC 5549, v4 prefix with v6 NH */ - if (rn->p.family != AF_INET) - family = AFI_IP6; - if (IN6_IS_ADDR_LINKLOCAL(&nexthop->gate.ipv6)) { - ifp = if_lookup_by_index(nexthop->ifindex, - nexthop->vrf_id); - if (ifp && if_is_operative(ifp)) - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - else - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - } else { - if (nexthop_active(AFI_IP6, re, nexthop, rn)) - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - else - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - } - break; - case NEXTHOP_TYPE_BLACKHOLE: - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - break; - default: - break; - } - if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug("\t%s: Unable to find a active nexthop", - __PRETTY_FUNCTION__); - return 0; - } - - /* XXX: What exactly do those checks do? Do we support - * e.g. IPv4 routes with IPv6 nexthops or vice versa? - */ - if (RIB_SYSTEM_ROUTE(re) || (family == AFI_IP && p->family != AF_INET) - || (family == AFI_IP6 && p->family != AF_INET6)) - return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - - /* The original code didn't determine the family correctly - * e.g. for NEXTHOP_TYPE_IFINDEX. Retrieve the correct afi - * from the rib_table_info in those cases. - * Possibly it may be better to use only the rib_table_info - * in every case. - */ - if (!family) { - rib_table_info_t *info; - - info = srcdest_rnode_table_info(rn); - family = info->afi; - } - - memset(&nexthop->rmap_src.ipv6, 0, sizeof(union g_addr)); - - zvrf = zebra_vrf_lookup_by_id(nexthop->vrf_id); - if (!zvrf) { - if (IS_ZEBRA_DEBUG_RIB_DETAILED) - zlog_debug("\t%s: zvrf is NULL", __PRETTY_FUNCTION__); - return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - } - - /* It'll get set if required inside */ - ret = zebra_route_map_check(family, re->type, re->instance, p, - nexthop, zvrf, re->tag); - if (ret == RMAP_DENYMATCH) { - if (IS_ZEBRA_DEBUG_RIB) { - srcdest_rnode2str(rn, buf, sizeof(buf)); - zlog_debug( - "%u:%s: Filtering out with NH out %s due to route map", - re->vrf_id, buf, - ifindex2ifname(nexthop->ifindex, - nexthop->vrf_id)); - } - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - } - return CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); -} - -/* - * Iterate over all nexthops of the given RIB entry and refresh their - * ACTIVE flag. re->nexthop_active_num is updated accordingly. If any - * nexthop is found to toggle the ACTIVE flag, the whole re structure - * is flagged with ROUTE_ENTRY_CHANGED. - * - * Return value is the new number of active nexthops. - */ -static int nexthop_active_update(struct route_node *rn, struct route_entry *re) -{ - struct nexthop *nexthop; - union g_addr prev_src; - unsigned int prev_active, new_active; - ifindex_t prev_index; - - re->nexthop_active_num = 0; - UNSET_FLAG(re->status, ROUTE_ENTRY_CHANGED); - - for (nexthop = re->ng.nexthop; nexthop; nexthop = nexthop->next) { - /* No protocol daemon provides src and so we're skipping - * tracking it */ - prev_src = nexthop->rmap_src; - prev_active = CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - prev_index = nexthop->ifindex; - /* - * We need to respect the multipath_num here - * as that what we should be able to install from - * a multipath perpsective should not be a data plane - * decision point. - */ - new_active = nexthop_active_check(rn, re, nexthop); - if (new_active - && re->nexthop_active_num >= zrouter.multipath_num) { - UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE); - new_active = 0; - } - if (new_active) - re->nexthop_active_num++; - /* Don't allow src setting on IPv6 addr for now */ - if (prev_active != new_active || prev_index != nexthop->ifindex - || ((nexthop->type >= NEXTHOP_TYPE_IFINDEX - && nexthop->type < NEXTHOP_TYPE_IPV6) - && prev_src.ipv4.s_addr - != nexthop->rmap_src.ipv4.s_addr) - || ((nexthop->type >= NEXTHOP_TYPE_IPV6 - && nexthop->type < NEXTHOP_TYPE_BLACKHOLE) - && !(IPV6_ADDR_SAME(&prev_src.ipv6, - &nexthop->rmap_src.ipv6))) - || CHECK_FLAG(re->status, ROUTE_ENTRY_LABELS_CHANGED)) { - SET_FLAG(re->status, ROUTE_ENTRY_CHANGED); - SET_FLAG(re->status, ROUTE_ENTRY_NEXTHOPS_CHANGED); - } - } - - return re->nexthop_active_num; -} - /* * Is this RIB labeled-unicast? It must be of type BGP and all paths * (nexthops) must have a label. @@ -1062,8 +587,25 @@ void rib_install_kernel(struct route_node *rn, struct route_entry *re, switch (ret) { case ZEBRA_DPLANE_REQUEST_QUEUED: SET_FLAG(re->status, ROUTE_ENTRY_QUEUED); - if (old) + + if (old) { SET_FLAG(old->status, ROUTE_ENTRY_QUEUED); + + /* Free old FIB nexthop group */ + if (old->fib_ng.nexthop) { + nexthops_free(old->fib_ng.nexthop); + old->fib_ng.nexthop = NULL; + } + + if (!RIB_SYSTEM_ROUTE(old)) { + /* Clear old route's FIB flags */ + for (ALL_NEXTHOPS(old->ng, nexthop)) { + UNSET_FLAG(nexthop->flags, + NEXTHOP_FLAG_FIB); + } + } + } + if (zvrf) zvrf->installs_queued++; break; @@ -1149,6 +691,12 @@ static void rib_uninstall(struct route_node *rn, struct route_entry *re) dest->selected_fib = NULL; + /* Free FIB nexthop group, if present */ + if (re->fib_ng.nexthop) { + nexthops_free(re->fib_ng.nexthop); + re->fib_ng.nexthop = NULL; + } + for (ALL_NEXTHOPS(re->ng, nexthop)) UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); } @@ -1841,21 +1389,239 @@ static void zebra_rib_fixup_system(struct route_node *rn) } /* - * Route-update results processing after async dataplane update. + * Update a route from a dplane context. This consolidates common code + * that can be used in processing of results from FIB updates, and in + * async notification processing. + * The return is 'true' if the installed nexthops changed; 'false' otherwise. */ -static void rib_process_result(struct zebra_dplane_ctx *ctx) +static bool rib_update_re_from_ctx(struct route_entry *re, + struct route_node *rn, + struct zebra_dplane_ctx *ctx) +{ + char dest_str[PREFIX_STRLEN] = ""; + char nh_str[NEXTHOP_STRLEN]; + struct nexthop *nexthop, *ctx_nexthop; + bool matched; + const struct nexthop_group *ctxnhg; + bool is_selected = false; /* Is 're' currently the selected re? */ + bool changed_p = false; /* Change to nexthops? */ + rib_dest_t *dest; + + /* Note well: only capturing the prefix string if debug is enabled here; + * unconditional log messages will have to generate the string. + */ + if (IS_ZEBRA_DEBUG_RIB) + prefix2str(&(rn->p), dest_str, sizeof(dest_str)); + + dest = rib_dest_from_rnode(rn); + if (dest) + is_selected = (re == dest->selected_fib); + + if (IS_ZEBRA_DEBUG_RIB_DETAILED) + zlog_debug("update_from_ctx: %u:%s: %sSELECTED", + re->vrf_id, dest_str, (is_selected ? "" : "NOT ")); + + /* Update zebra's nexthop FIB flag for each nexthop that was installed. + * If the installed set differs from the set requested by the rib/owner, + * we use the fib-specific nexthop-group to record the actual FIB + * status. + */ + + /* + * First check the fib nexthop-group, if it's present. The comparison + * here is quite strict: we require that the fib sets match exactly. + */ + matched = false; + do { + if (re->fib_ng.nexthop == NULL) + break; + + matched = true; + + /* First check the route's fib nexthops */ + for (ALL_NEXTHOPS(re->fib_ng, nexthop)) { + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + continue; + + ctx_nexthop = NULL; + for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), + ctx_nexthop)) { + if (nexthop_same(ctx_nexthop, nexthop)) + break; + } + + if (ctx_nexthop == NULL) { + /* Nexthop not in the new installed set */ + if (IS_ZEBRA_DEBUG_RIB_DETAILED) { + nexthop2str(nexthop, nh_str, + sizeof(nh_str)); + zlog_debug("update_from_ctx: no match for fib nh %s", + nh_str); + } + + matched = false; + break; + } + } + + if (!matched) + break; + + /* Check the new installed set */ + ctx_nexthop = NULL; + for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), ctx_nexthop)) { + + if (CHECK_FLAG(ctx_nexthop->flags, + NEXTHOP_FLAG_RECURSIVE)) + continue; + + /* Compare with the current group's nexthops */ + nexthop = NULL; + for (ALL_NEXTHOPS(re->fib_ng, nexthop)) { + if (nexthop_same(nexthop, ctx_nexthop)) + break; + } + + if (nexthop == NULL) { + /* Nexthop not in the old installed set */ + if (IS_ZEBRA_DEBUG_RIB_DETAILED) { + nexthop2str(ctx_nexthop, nh_str, + sizeof(nh_str)); + zlog_debug("update_from_ctx: no fib match for notif nh %s", + nh_str); + } + matched = false; + break; + } + } + + } while (0); + + /* If the new FIB set matches the existing FIB set, we're done. */ + if (matched) { + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%u:%s update_from_ctx(): existing fib nhg, no change", + re->vrf_id, dest_str); + goto done; + + } else if (re->fib_ng.nexthop) { + /* + * Free stale fib list and move on to check the rib nhg. + */ + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%u:%s update_from_ctx(): replacing fib nhg", + re->vrf_id, dest_str); + nexthops_free(re->fib_ng.nexthop); + re->fib_ng.nexthop = NULL; + + /* Note that the installed nexthops have changed */ + changed_p = true; + } else { + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%u:%s update_from_ctx(): no fib nhg", + re->vrf_id, dest_str); + } + + /* + * Compare with the rib nexthop group. The comparison here is different: + * the RIB group may be a superset of the list installed in the FIB. We + * walk the RIB group, looking for the 'installable' candidate + * nexthops, and then check those against the set + * that is actually installed. + */ + matched = true; + for (ALL_NEXTHOPS(re->ng, nexthop)) { + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + continue; + + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) + continue; + + /* Check for a FIB nexthop corresponding to the RIB nexthop */ + ctx_nexthop = NULL; + for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), ctx_nexthop)) { + if (nexthop_same(ctx_nexthop, nexthop)) + break; + } + + /* If the FIB doesn't know about the nexthop, + * it's not installed + */ + if (ctx_nexthop == NULL) { + if (IS_ZEBRA_DEBUG_RIB_DETAILED) { + nexthop2str(nexthop, nh_str, sizeof(nh_str)); + zlog_debug("update_from_ctx: no notif match for rib nh %s", + nh_str); + } + matched = false; + + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + changed_p = true; + + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); + break; + } + + if (CHECK_FLAG(ctx_nexthop->flags, NEXTHOP_FLAG_FIB)) { + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + changed_p = true; + + SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); + } else { + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + changed_p = true; + + UNSET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); + } + } + + /* If all nexthops were processed, we're done */ + if (matched) { + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%u:%s update_from_ctx(): rib nhg matched, changed '%s'", + re->vrf_id, dest_str, + (changed_p ? "true" : "false")); + goto done; + } + + /* FIB nexthop set differs from the RIB set: + * create a fib-specific nexthop-group + */ + if (IS_ZEBRA_DEBUG_RIB) + zlog_debug("%u:%s update_from_ctx(): changed %s, adding new fib nhg", + re->vrf_id, dest_str, + (changed_p ? "true" : "false")); + + ctxnhg = dplane_ctx_get_ng(ctx); + + if (ctxnhg->nexthop) + copy_nexthops(&(re->fib_ng.nexthop), ctxnhg->nexthop, NULL); + else { + /* Bit of a special case when the fib has _no_ installed + * nexthops. + */ + nexthop = nexthop_new(); + nexthop->type = NEXTHOP_TYPE_IPV4; + nexthop_add(&(re->fib_ng.nexthop), nexthop); + } + +done: + return changed_p; +} + +/* + * Helper to locate a zebra route-node from a dplane context. This is used + * when processing dplane results, e.g. Note well: the route-node is returned + * with a ref held - route_unlock_node() must be called eventually. + */ +static struct route_node * +rib_find_rn_from_ctx(const struct zebra_dplane_ctx *ctx) { struct route_table *table = NULL; - struct zebra_vrf *zvrf = NULL; struct route_node *rn = NULL; - struct route_entry *re = NULL, *old_re = NULL, *rib; - bool is_update = false; - struct nexthop *nexthop, *ctx_nexthop; - char dest_str[PREFIX_STRLEN] = ""; - enum dplane_op_e op; - enum zebra_dplane_result status; const struct prefix *dest_pfx, *src_pfx; - uint32_t seq; /* Locate rn and re(s) from ctx */ @@ -1865,7 +1631,7 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) dplane_ctx_get_table(ctx)); if (table == NULL) { if (IS_ZEBRA_DEBUG_DPLANE) { - zlog_debug("Failed to process dplane results: no table for afi %d, safi %d, vrf %u", + zlog_debug("Failed to find route for ctx: no table for afi %d, safi %d, vrf %u", dplane_ctx_get_afi(ctx), dplane_ctx_get_safi(ctx), dplane_ctx_get_vrf(ctx)); @@ -1873,8 +1639,35 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) goto done; } - zvrf = vrf_info_lookup(dplane_ctx_get_vrf(ctx)); + dest_pfx = dplane_ctx_get_dest(ctx); + src_pfx = dplane_ctx_get_src(ctx); + + rn = srcdest_rnode_get(table, dest_pfx, + src_pfx ? (struct prefix_ipv6 *)src_pfx : NULL); + +done: + return rn; +} + + + +/* + * Route-update results processing after async dataplane update. + */ +static void rib_process_result(struct zebra_dplane_ctx *ctx) +{ + struct zebra_vrf *zvrf = NULL; + struct route_node *rn = NULL; + struct route_entry *re = NULL, *old_re = NULL, *rib; + bool is_update = false; + char dest_str[PREFIX_STRLEN] = ""; + enum dplane_op_e op; + enum zebra_dplane_result status; + const struct prefix *dest_pfx, *src_pfx; + uint32_t seq; + bool fib_changed = false; + zvrf = vrf_info_lookup(dplane_ctx_get_vrf(ctx)); dest_pfx = dplane_ctx_get_dest(ctx); /* Note well: only capturing the prefix string if debug is enabled here; @@ -1883,9 +1676,8 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) if (IS_ZEBRA_DEBUG_DPLANE) prefix2str(dest_pfx, dest_str, sizeof(dest_str)); - src_pfx = dplane_ctx_get_src(ctx); - rn = srcdest_rnode_get(table, dplane_ctx_get_dest(ctx), - src_pfx ? (struct prefix_ipv6 *)src_pfx : NULL); + /* Locate rn and re(s) from ctx */ + rn = rib_find_rn_from_ctx(ctx); if (rn == NULL) { if (IS_ZEBRA_DEBUG_DPLANE) { zlog_debug("Failed to process dplane results: no route for %u:%s", @@ -1979,34 +1771,25 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) UNSET_FLAG(old_re->status, ROUTE_ENTRY_INSTALLED); } - /* Update zebra nexthop FIB flag for each - * nexthop that was installed. - */ - for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), - ctx_nexthop)) { - - if (!re) - continue; - for (ALL_NEXTHOPS(re->ng, nexthop)) { - if (nexthop_same(ctx_nexthop, nexthop)) - break; + /* Update zebra route based on the results in + * the context struct. + */ + if (re) { + fib_changed = + rib_update_re_from_ctx(re, rn, ctx); + + if (!fib_changed) { + if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) + zlog_debug("%u:%s no fib change for re", + dplane_ctx_get_vrf( + ctx), + dest_str); } - if (nexthop == NULL) - continue; - - if (CHECK_FLAG(nexthop->flags, - NEXTHOP_FLAG_RECURSIVE)) - continue; - - if (CHECK_FLAG(ctx_nexthop->flags, - NEXTHOP_FLAG_FIB)) - SET_FLAG(nexthop->flags, - NEXTHOP_FLAG_FIB); - else - UNSET_FLAG(nexthop->flags, - NEXTHOP_FLAG_FIB); + /* Redistribute */ + redistribute_update(dest_pfx, src_pfx, + re, NULL); } /* @@ -2023,19 +1806,6 @@ static void rib_process_result(struct zebra_dplane_ctx *ctx) if (zvrf) zvrf->installs++; - /* Redistribute */ - /* - * TODO -- still calling the redist api using the - * route_entries, and there's a corner-case here: - * if there's no client for the 'new' route, a redist - * deleting the 'old' route will be sent. But if the - * 'old' context info was stale, 'old_re' will be - * NULL here and that delete will not be sent. - */ - if (re) - redistribute_update(dest_pfx, src_pfx, - re, old_re); - /* Notify route owner */ zsend_route_notify_owner_ctx(ctx, ZAPI_ROUTE_INSTALLED); @@ -2110,6 +1880,179 @@ done: dplane_ctx_fini(&ctx); } +/* + * Handle notification from async dataplane: the dataplane has detected + * some change to a route, and notifies zebra so that the control plane + * can reflect that change. + */ +static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx) +{ + struct route_node *rn = NULL; + struct route_entry *re = NULL; + struct nexthop *nexthop; + char dest_str[PREFIX_STRLEN] = ""; + const struct prefix *dest_pfx, *src_pfx; + rib_dest_t *dest; + bool fib_changed = false; + bool debug_p = IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_RIB; + int start_count, end_count; + dest_pfx = dplane_ctx_get_dest(ctx); + + /* Note well: only capturing the prefix string if debug is enabled here; + * unconditional log messages will have to generate the string. + */ + if (debug_p) + prefix2str(dest_pfx, dest_str, sizeof(dest_str)); + + /* Locate rn and re(s) from ctx */ + rn = rib_find_rn_from_ctx(ctx); + if (rn == NULL) { + if (debug_p) { + zlog_debug("Failed to process dplane notification: no routes for %u:%s", + dplane_ctx_get_vrf(ctx), dest_str); + } + goto done; + } + + dest = rib_dest_from_rnode(rn); + srcdest_rnode_prefixes(rn, &dest_pfx, &src_pfx); + + if (debug_p) + zlog_debug("%u:%s Processing dplane notif ctx %p", + dplane_ctx_get_vrf(ctx), dest_str, ctx); + + /* + * Take a pass through the routes, look for matches with the context + * info. + */ + RNODE_FOREACH_RE(rn, re) { + if (rib_route_match_ctx(re, ctx, false /*!update*/)) + break; + } + + /* No match? Nothing we can do */ + if (re == NULL) { + if (debug_p) + zlog_debug("%u:%s Unable to process dplane notification: no entry for type %s", + dplane_ctx_get_vrf(ctx), dest_str, + zebra_route_string( + dplane_ctx_get_type(ctx))); + + goto done; + } + + /* Is this a notification that ... matters? We only really care about + * the route that is currently selected for installation. + */ + if (re != dest->selected_fib) { + /* TODO -- don't skip processing entirely? We might like to + * at least report on the event. + */ + if (debug_p) + zlog_debug("%u:%s dplane notif, but type %s not selected_fib", + dplane_ctx_get_vrf(ctx), dest_str, + zebra_route_string( + dplane_ctx_get_type(ctx))); + goto done; + } + + /* We'll want to determine whether the installation status of the + * route has changed: we'll check the status before processing, + * and then again if there's been a change. + */ + start_count = 0; + for (ALL_NEXTHOPS_PTR(rib_active_nhg(re), nexthop)) { + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + start_count++; + } + + /* Update zebra's nexthop FIB flags based on the context struct's + * nexthops. + */ + fib_changed = rib_update_re_from_ctx(re, rn, ctx); + + if (!fib_changed) { + if (debug_p) + zlog_debug("%u:%s No change from dplane notification", + dplane_ctx_get_vrf(ctx), dest_str); + + goto done; + } + + /* + * Perform follow-up work if the actual status of the prefix + * changed. + */ + + end_count = 0; + for (ALL_NEXTHOPS_PTR(rib_active_nhg(re), nexthop)) { + if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB)) + end_count++; + } + + /* Various fib transitions: changed nexthops; from installed to + * not-installed; or not-installed to installed. + */ + if (start_count > 0 && end_count > 0) { + + /* Changed nexthops - update kernel/others */ + dplane_route_notif_update(rn, re, + DPLANE_OP_ROUTE_UPDATE, ctx); + + } else if (start_count == 0 && end_count > 0) { + if (debug_p) + zlog_debug("%u:%s installed transition from dplane notification", + dplane_ctx_get_vrf(ctx), dest_str); + + /* We expect this to be the selected route, so we want + * to tell others about this transistion. + */ + SET_FLAG(re->status, ROUTE_ENTRY_INSTALLED); + + /* Changed nexthops - update kernel/others */ + dplane_route_notif_update(rn, re, DPLANE_OP_ROUTE_INSTALL, ctx); + + /* Redistribute, lsp, and nht update */ + redistribute_update(dest_pfx, src_pfx, re, NULL); + + zebra_rib_evaluate_rn_nexthops( + rn, zebra_router_get_next_sequence()); + + zebra_rib_evaluate_mpls(rn); + + } else if (start_count > 0 && end_count == 0) { + if (debug_p) + zlog_debug("%u:%s un-installed transition from dplane notification", + dplane_ctx_get_vrf(ctx), dest_str); + + /* Transition from _something_ installed to _nothing_ + * installed. + */ + /* We expect this to be the selected route, so we want + * to tell others about this transistion. + */ + UNSET_FLAG(re->status, ROUTE_ENTRY_INSTALLED); + + /* Changed nexthops - update kernel/others */ + dplane_route_notif_update(rn, re, DPLANE_OP_ROUTE_DELETE, ctx); + + /* Redistribute, lsp, and nht update */ + redistribute_delete(dest_pfx, src_pfx, re); + + zebra_rib_evaluate_rn_nexthops( + rn, zebra_router_get_next_sequence()); + + zebra_rib_evaluate_mpls(rn); + } + +done: + if (rn) + route_unlock_node(rn); + + /* Return context to dataplane module */ + dplane_ctx_fini(&ctx); +} + /* Take a list of route_node structs and return 1, if there was a record * picked from it and processed by rib_process(). Don't process more, * than one RN record; operate only in the specified sub-queue. @@ -2133,6 +2076,7 @@ static unsigned int process_subq(struct list *subq, uint8_t qindex) if (IS_ZEBRA_DEBUG_RIB_DETAILED) { char buf[SRCDEST2STR_BUFFER]; + srcdest_rnode2str(rnode, buf, sizeof(buf)); zlog_debug("%u:%s: rn %p dequeued from sub-queue %u", zvrf ? zvrf_id(zvrf) : 0, buf, rnode, qindex); @@ -2468,6 +2412,8 @@ void rib_unlink(struct route_node *rn, struct route_entry *re) dest->selected_fib = NULL; nexthops_free(re->ng.nexthop); + nexthops_free(re->fib_ng.nexthop); + XFREE(MTYPE_RE, re); } @@ -3331,13 +3277,40 @@ static int rib_process_dplane_results(struct thread *thread) case DPLANE_OP_ROUTE_INSTALL: case DPLANE_OP_ROUTE_UPDATE: case DPLANE_OP_ROUTE_DELETE: - rib_process_result(ctx); + { + /* Bit of special case for route updates + * that were generated by async notifications: + * we don't want to continue processing these + * in the rib. + */ + if (dplane_ctx_get_notif_provider(ctx) == 0) + rib_process_result(ctx); + else + dplane_ctx_fini(&ctx); + } + break; + + case DPLANE_OP_ROUTE_NOTIFY: + rib_process_dplane_notify(ctx); break; case DPLANE_OP_LSP_INSTALL: case DPLANE_OP_LSP_UPDATE: case DPLANE_OP_LSP_DELETE: - zebra_mpls_lsp_dplane_result(ctx); + { + /* Bit of special case for LSP updates + * that were generated by async notifications: + * we don't want to continue processing these. + */ + if (dplane_ctx_get_notif_provider(ctx) == 0) + zebra_mpls_lsp_dplane_result(ctx); + else + dplane_ctx_fini(&ctx); + } + break; + + case DPLANE_OP_LSP_NOTIFY: + zebra_mpls_process_dplane_notify(ctx); break; case DPLANE_OP_PW_INSTALL: diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index ece8f40dcf..257fb168d2 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -382,7 +382,8 @@ static void vty_show_ip_route_detail(struct vty *vty, struct route_node *rn, } static void vty_show_ip_route(struct vty *vty, struct route_node *rn, - struct route_entry *re, json_object *json) + struct route_entry *re, json_object *json, + bool is_fib) { struct nexthop *nexthop; int len = 0; @@ -394,11 +395,20 @@ static void vty_show_ip_route(struct vty *vty, struct route_node *rn, time_t uptime; struct tm *tm; rib_dest_t *dest = rib_dest_from_rnode(rn); + struct nexthop_group *nhg; uptime = monotime(NULL); uptime -= re->uptime; tm = gmtime(&uptime); + /* If showing fib information, use the fib view of the + * nexthops. + */ + if (is_fib) + nhg = rib_active_nhg(re); + else + nhg = &(re->ng); + if (json) { json_route = json_object_new_object(); json_nexthops = json_object_new_array(); @@ -455,7 +465,7 @@ static void vty_show_ip_route(struct vty *vty, struct route_node *rn, json_object_string_add(json_route, "uptime", buf); - for (ALL_NEXTHOPS(re->ng, nexthop)) { + for (ALL_NEXTHOPS_PTR(nhg, nexthop)) { json_nexthop = json_object_new_object(); json_object_int_add(json_nexthop, "flags", @@ -625,8 +635,8 @@ static void vty_show_ip_route(struct vty *vty, struct route_node *rn, } /* Nexthop information. */ - for (ALL_NEXTHOPS(re->ng, nexthop)) { - if (nexthop == re->ng.nexthop) { + for (ALL_NEXTHOPS_PTR(nhg, nexthop)) { + if (nexthop == nhg->nexthop) { /* Prefix information. */ len = vty_out(vty, "%c", zebra_route_char(re->type)); if (re->instance) @@ -779,7 +789,7 @@ static void vty_show_ip_route_detail_json(struct vty *vty, */ if (use_fib && re != dest->selected_fib) continue; - vty_show_ip_route(vty, rn, re, json_prefix); + vty_show_ip_route(vty, rn, re, json_prefix, use_fib); } prefix2str(&rn->p, buf, sizeof(buf)); @@ -865,7 +875,7 @@ static void do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, } } - vty_show_ip_route(vty, rn, re, json_prefix); + vty_show_ip_route(vty, rn, re, json_prefix, use_fib); } if (json_prefix) { @@ -1552,7 +1562,7 @@ DEFUN (show_ipv6_mroute, vty_out(vty, SHOW_ROUTE_V6_HEADER); first = 0; } - vty_show_ip_route(vty, rn, re, NULL); + vty_show_ip_route(vty, rn, re, NULL, false); } return CMD_SUCCESS; } @@ -1584,7 +1594,7 @@ DEFUN (show_ipv6_mroute_vrf_all, vty_out(vty, SHOW_ROUTE_V6_HEADER); first = 0; } - vty_show_ip_route(vty, rn, re, NULL); + vty_show_ip_route(vty, rn, re, NULL, false); } } return CMD_SUCCESS; |
