diff options
22 files changed, 1015 insertions, 251 deletions
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 12b68f2607..0270695c2f 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -1505,7 +1505,8 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */ } else { if (!CHECK_FLAG(from_bgp->af_flags[afi][SAFI_UNICAST], BGP_CONFIG_VRF_TO_VRF_EXPORT)) { - if (afi == AFI_IP) { + if (afi == AFI_IP && + !BGP_ATTR_NEXTHOP_AFI_IP6(path_vrf->attr)) { /* * For ipv4, copy to multiprotocol * nexthop field diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c index 9c91a30804..2d70aa94d3 100644 --- a/bgpd/bgp_snmp_bgp4v2.c +++ b/bgpd/bgp_snmp_bgp4v2.c @@ -440,11 +440,11 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, sockunion_init(&su); if (exact) { - if (*length - v->namelen != BGP_NLRI_ENTRY_OFFSET) + if (*length - namelen != BGP_NLRI_ENTRY_OFFSET) return NULL; /* Set OID offset for prefix */ - offset = name + v->namelen; + offset = name + namelen; if (family == AF_INET) oid2in_addr(offset, afi_len, &addr->u.prefix4); else @@ -477,8 +477,8 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, return NULL; } - offset = name + v->namelen; - offsetlen = *length - v->namelen; + offset = name + namelen; + offsetlen = *length - namelen; len = offsetlen; if (offsetlen == 0) { @@ -560,9 +560,9 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, if (min) { const struct prefix *rn_p = bgp_dest_get_prefix(dest); - *length = v->namelen + BGP_NLRI_ENTRY_OFFSET; + *length = namelen + BGP_NLRI_ENTRY_OFFSET; - offset = name + v->namelen; + offset = name + namelen; if (family == AF_INET) oid_copy_in_addr(offset, &rn_p->u.prefix4); diff --git a/doc/accords/cli-colors b/doc/accords/cli-colors new file mode 100644 index 0000000000..04bdfc7fae --- /dev/null +++ b/doc/accords/cli-colors @@ -0,0 +1,44 @@ +Adding colors to FRR CLI output +=============================== + + +There were multiple approaches/attempts to get colored output for the CLI into +FRR, most recently End of 2022 in PR #12497. After some discussion, some items +crystallized out: + +First, generally speaking, colors (or other rich output formatting) must be +used sparingly. In particular, e.g. "every IP address" is not something to +color. The output formatting needs to have an actual purpose to improve UX, +not turn it into a christmas tree. + +In the long run, the CLI will hopefully become a YANG frontend. In that case, +the CLI frontend component is a great place to apply all kinds of output/UI/UX +features. However, this is a long way off. + +That said, an implementation in the current vtysh+daemon ecosystem is not out +of the question, especially if the use of colors/formatting is limited to +important places (which is desirable anyway - see general statement above.) +We don't want to litter formatting all over every single vty_out call. + +A color option on a per-command/DEFUN level (i.e. the way `[json]` is done) was +rejected. The decision to color output must take information from vtysh's +environment into account, notably the TERM environment variable, the NO_COLOR +environment variable, and whether stdout is a terminal or not. An explicit +`--color` switch (or `terminal color` vtysh command, or other similar things) +is needed too. To be clear, the switch must not be on individual commands, it +needs to be on the vtysh session level. + +Lastly, the output pager needs to work with this. + + +Suggested implementation +------------------------ + +(not part of the consensus / accord, only to record discussion) + +As far as discussion went, the most promising approach to actually implement +this is to put some type of unconditional formatting tag into daemon's vty_out +calls. This would be some escape-like sequence - an actual ANSI color code +itself is not particularly readable or pretty, though that would work as well. +vtysh would then, while passing through the output from the daemons, replace or +remove these tags according to terminal/user settings. diff --git a/doc/developer/fpm.rst b/doc/developer/fpm.rst index 9849869133..56d33671d2 100644 --- a/doc/developer/fpm.rst +++ b/doc/developer/fpm.rst @@ -101,3 +101,19 @@ Data ^^^^ The netlink or protobuf message payload. + + +Route Status Notification from ASIC +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The dplane_fpm_nl has the ability to read route netlink messages +from the underlying fpm implementation that can tell zebra +whether or not the route has been Offloaded/Failed or Trapped. +The end developer must send the data up the same socket that has +been created to listen for FPM messages from Zebra. The data sent +must have a Frame Header with Version set to 1, Message Type set to 1 +and an appropriate message Length. The message data must contain +a RTM_NEWROUTE netlink message that sends the prefix and nexthops +associated with the route. Finally rtm_flags must contain +RTM_F_OFFLOAD, RTM_F_TRAP and or RTM_F_OFFLOAD_FAILED to signify +what has happened to the route in the ASIC. diff --git a/doc/user/zebra.rst b/doc/user/zebra.rst index 230a4f43b0..3608f828e8 100644 --- a/doc/user/zebra.rst +++ b/doc/user/zebra.rst @@ -1342,7 +1342,7 @@ zebra Terminal Mode Commands total number of route nodes in the table. Which will be higher than the actual number of routes that are held. -.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] +.. clicmd:: show nexthop-group rib [ID] [vrf NAME] [singleton [ip|ip6]] [type] [json] Display nexthop groups created by zebra. The [vrf NAME] option is only meaningful if you have started zebra with the --vrfwnetns diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf new file mode 100644 index 0000000000..66493f0fea --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/bgpd.conf @@ -0,0 +1,23 @@ +frr defaults traditional +! +hostname ce1 +password zebra +! +log stdout notifications +log monitor notifications +log commands +! +router bgp 65002 + bgp router-id 192.0.2.2 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor eth0 interface + neighbor eth0 remote-as external + neighbor eth0 timers connect 1 + ! + address-family ipv4 unicast + neighbor eth0 activate + redistribute connected + exit-address-family + ! +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf new file mode 100644 index 0000000000..a163295844 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/ce1/zebra.conf @@ -0,0 +1,13 @@ +log file zebra.log +! +hostname ce1 +! +interface lo + ip address 172.16.0.1/32 +! +interface eth0 + ipv6 nd ra-interval 1 + no ipv6 nd suppress-ra +! +line vty +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf new file mode 100644 index 0000000000..c5c99270e7 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/bgpd.conf @@ -0,0 +1,41 @@ +frr defaults traditional +! +hostname pe1 +password zebra +! +log stdout notifications +log monitor notifications +log commands +! +router bgp 65001 + bgp router-id 192.0.2.1 + ! +! +router bgp 65001 vrf vrf10 + bgp router-id 192.0.2.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor eth0 interface + neighbor eth0 remote-as external + neighbor eth0 timers connect 1 + ! + address-family ipv4 unicast + neighbor eth0 activate + rd vpn export 65001:10 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! +router bgp 65001 vrf vrf20 + bgp router-id 192.0.2.1 + ! + address-family ipv4 unicast + rd vpn export 65001:20 + rt vpn both 0:10 + import vpn + export vpn + exit-address-family + ! +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json new file mode 100644 index 0000000000..9516016fc2 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/default_ipv4_vpn.json @@ -0,0 +1,32 @@ +{ + "vrfName": "default", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "routeDistinguishers": { + "65001:10": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv6", + "used": true + } + ] + } + ] + } + } + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json new file mode 100644 index 0000000000..768bffbe9d --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf10_ipv4_unicast.json @@ -0,0 +1,32 @@ +{ + "vrfName": "vrf10", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "nexthops": [ + { + "hostname": "ce1", + "afi": "ipv6", + "scope": "global", + "used": true + }, + { + "hostname": "ce1", + "afi": "ipv6", + "scope": "link-local" + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json new file mode 100644 index 0000000000..1e93715270 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/results/vrf20_ipv4_unicast.json @@ -0,0 +1,34 @@ +{ + "vrfName": "vrf20", + "routerId": "192.0.2.1", + "localAS": 65001, + "routes": { + "172.16.0.1/32": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "prefix": "172.16.0.1", + "prefixLen": 32, + "network": "172.16.0.1\/32", + "path": "65002", + "origin": "incomplete", + "announceNexthopSelf": true, + "nhVrfName": "vrf10", + "nexthops": [ + { + "hostname": "pe1", + "afi": "ipv6", + "scope": "global", + "used": true + }, + { + "hostname": "pe1", + "afi": "ipv6", + "scope": "link-local" + } + ] + } + ] + } +} diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf new file mode 100644 index 0000000000..d40041ab3c --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/pe1/zebra.conf @@ -0,0 +1,10 @@ +log file zebra.log +! +hostname pe1 +! +interface eth0 vrf vrf10 + ipv6 nd ra-interval 1 + no ipv6 nd suppress-ra +! +line vty +! diff --git a/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py b/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py new file mode 100755 index 0000000000..dd27ad3ed1 --- /dev/null +++ b/tests/topotests/bgp_vrf_leaking_5549_routes/test_bgp_vrf_leaking.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +# Copyright (c) 2022, LINE Corporation +# Authored by Ryoga Saito <ryoga.saito@linecorp.com> +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +import os +import re +import sys +import json +import functools +import pytest + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger +from lib.common_config import required_linux_kernel_version + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + tgen.add_router("pe1") + tgen.add_router("ce1") + + tgen.add_link(tgen.gears["pe1"], tgen.gears["ce1"], "eth0", "eth0") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + for rname, router in tgen.routers().items(): + router.load_config(TopoRouter.RD_ZEBRA, + os.path.join(CWD, '{}/zebra.conf'.format(rname))) + router.load_config(TopoRouter.RD_BGP, + os.path.join(CWD, '{}/bgpd.conf'.format(rname))) + + tgen.gears["pe1"].run("ip link add vrf10 type vrf table 10") + tgen.gears["pe1"].run("ip link set vrf10 up") + tgen.gears["pe1"].run("ip link add vrf20 type vrf table 20") + tgen.gears["pe1"].run("ip link set vrf20 up") + tgen.gears["pe1"].run("ip link set eth0 master vrf10") + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def open_json_file(path): + try: + with open(path, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(path) + + +def check_vrf10_rib(output): + expected = open_json_file("%s/pe1/results/vrf10_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_default_vpn_rib(output): + expected = open_json_file("%s/pe1/results/default_ipv4_vpn.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check_vrf20_rib(output): + expected = open_json_file("%s/pe1/results/vrf20_ipv4_unicast.json" % CWD) + actual = json.loads(output) + return topotest.json_cmp(actual, expected) + + +def check(name, command, checker): + tgen = get_topogen() + router = tgen.gears[name] + + def _check(): + try: + return checker(router.vtysh_cmd(command)) + except: + return False + + logger.info('[+] check {} "{}"'.format(name, command)) + _, result = topotest.run_and_expect(_check, None, count=10, wait=0.5) + assert result is None, "Failed" + + +def test_rib(): + check("pe1", "show bgp vrf vrf10 ipv4 unicast json", check_vrf10_rib) + check("pe1", "show bgp ipv4 vpn json", check_default_vpn_rib) + check("pe1", "show bgp vrf vrf20 ipv4 unicast json", check_vrf20_rib) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c index 6c95be29df..337113988e 100644 --- a/zebra/dplane_fpm_nl.c +++ b/zebra/dplane_fpm_nl.c @@ -51,6 +51,7 @@ #include "zebra/kernel_netlink.h" #include "zebra/rt_netlink.h" #include "zebra/debug.h" +#include "fpm/fpm.h" #define SOUTHBOUND_DEFAULT_ADDR INADDR_LOOPBACK #define SOUTHBOUND_DEFAULT_PORT 2620 @@ -462,18 +463,17 @@ static void fpm_reconnect(struct fpm_nl_ctx *fnc) static void fpm_read(struct thread *t) { struct fpm_nl_ctx *fnc = THREAD_ARG(t); + fpm_msg_hdr_t fpm; ssize_t rv; + char buf[65535]; + struct nlmsghdr *hdr; + struct zebra_dplane_ctx *ctx; + size_t available_bytes; + size_t hdr_available_bytes; /* Let's ignore the input at the moment. */ rv = stream_read_try(fnc->ibuf, fnc->socket, STREAM_WRITEABLE(fnc->ibuf)); - /* We've got an interruption. */ - if (rv == -2) { - /* Schedule next read. */ - thread_add_read(fnc->fthread->master, fpm_read, fnc, - fnc->socket, &fnc->t_read); - return; - } if (rv == 0) { atomic_fetch_add_explicit(&fnc->counters.connection_closes, 1, memory_order_relaxed); @@ -492,14 +492,131 @@ static void fpm_read(struct thread *t) FPM_RECONNECT(fnc); return; } - stream_reset(fnc->ibuf); + + /* Schedule the next read */ + thread_add_read(fnc->fthread->master, fpm_read, fnc, fnc->socket, + &fnc->t_read); + + /* We've got an interruption. */ + if (rv == -2) + return; + /* Account all bytes read. */ atomic_fetch_add_explicit(&fnc->counters.bytes_read, rv, memory_order_relaxed); - thread_add_read(fnc->fthread->master, fpm_read, fnc, fnc->socket, - &fnc->t_read); + available_bytes = STREAM_READABLE(fnc->ibuf); + while (available_bytes) { + if (available_bytes < (ssize_t)FPM_MSG_HDR_LEN) { + stream_pulldown(fnc->ibuf); + return; + } + + fpm.version = stream_getc(fnc->ibuf); + fpm.msg_type = stream_getc(fnc->ibuf); + fpm.msg_len = stream_getw(fnc->ibuf); + + if (fpm.version != FPM_PROTO_VERSION && + fpm.msg_type != FPM_MSG_TYPE_NETLINK) { + stream_reset(fnc->ibuf); + zlog_warn( + "%s: Received version/msg_type %u/%u, expected 1/1", + __func__, fpm.version, fpm.msg_type); + + FPM_RECONNECT(fnc); + return; + } + + /* + * If the passed in length doesn't even fill in the header + * something is wrong and reset. + */ + if (fpm.msg_len < FPM_MSG_HDR_LEN) { + zlog_warn( + "%s: Received message length: %u that does not even fill the FPM header", + __func__, fpm.msg_len); + FPM_RECONNECT(fnc); + return; + } + + /* + * If we have not received the whole payload, reset the stream + * back to the beginning of the header and move it to the + * top. + */ + if (fpm.msg_len > available_bytes) { + stream_rewind_getp(fnc->ibuf, FPM_MSG_HDR_LEN); + stream_pulldown(fnc->ibuf); + return; + } + + available_bytes -= FPM_MSG_HDR_LEN; + + /* + * Place the data from the stream into a buffer + */ + hdr = (struct nlmsghdr *)buf; + stream_get(buf, fnc->ibuf, fpm.msg_len - FPM_MSG_HDR_LEN); + hdr_available_bytes = fpm.msg_len - FPM_MSG_HDR_LEN; + available_bytes -= hdr_available_bytes; + + /* Sanity check: must be at least header size. */ + if (hdr->nlmsg_len < sizeof(*hdr)) { + zlog_warn( + "%s: [seq=%u] invalid message length %u (< %zu)", + __func__, hdr->nlmsg_seq, hdr->nlmsg_len, + sizeof(*hdr)); + continue; + } + if (hdr->nlmsg_len > fpm.msg_len) { + zlog_warn( + "%s: Received a inner header length of %u that is greater than the fpm total length of %u", + __func__, hdr->nlmsg_len, fpm.msg_len); + FPM_RECONNECT(fnc); + } + /* Not enough bytes available. */ + if (hdr->nlmsg_len > hdr_available_bytes) { + zlog_warn( + "%s: [seq=%u] invalid message length %u (> %zu)", + __func__, hdr->nlmsg_seq, hdr->nlmsg_len, + available_bytes); + continue; + } + + if (!(hdr->nlmsg_flags & NLM_F_REQUEST)) { + if (IS_ZEBRA_DEBUG_FPM) + zlog_debug( + "%s: [seq=%u] not a request, skipping", + __func__, hdr->nlmsg_seq); + + /* + * This request is a bust, go to the next one + */ + continue; + } + + switch (hdr->nlmsg_type) { + case RTM_NEWROUTE: + ctx = dplane_ctx_alloc(); + dplane_ctx_set_op(ctx, DPLANE_OP_ROUTE_NOTIFY); + if (netlink_route_change_read_unicast_internal( + hdr, 0, false, ctx) != 1) { + dplane_ctx_fini(&ctx); + stream_pulldown(fnc->ibuf); + return; + } + break; + default: + if (IS_ZEBRA_DEBUG_FPM) + zlog_debug( + "%s: Received message type %u which is not currently handled", + __func__, hdr->nlmsg_type); + break; + } + } + + stream_reset(fnc->ibuf); } static void fpm_write(struct thread *t) diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c index 2396dfe4d6..96ec90e549 100644 --- a/zebra/rt_netlink.c +++ b/zebra/rt_netlink.c @@ -692,8 +692,9 @@ static uint8_t parse_multipath_nexthops_unicast(ns_id_t ns_id, } /* Looking up routing table by netlink interface. */ -static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, - int startup) +int netlink_route_change_read_unicast_internal(struct nlmsghdr *h, + ns_id_t ns_id, int startup, + struct zebra_dplane_ctx *ctx) { int len; struct rtmsg *rtm; @@ -768,9 +769,8 @@ static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, selfroute = is_selfroute(rtm->rtm_protocol); - if (!startup && selfroute - && h->nlmsg_type == RTM_NEWROUTE - && !zrouter.asic_offloaded) { + if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE && + !zrouter.asic_offloaded && !ctx) { if (IS_ZEBRA_DEBUG_KERNEL) zlog_debug("Route type: %d Received that we think we have originated, ignoring", rtm->rtm_protocol); @@ -988,8 +988,8 @@ static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, } } if (nhe_id || ng) - rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p, re, ng, - startup); + dplane_rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p, + re, ng, startup, ctx); else { /* * I really don't see how this is possible @@ -1004,6 +1004,13 @@ static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, XFREE(MTYPE_RE, re); } } else { + if (ctx) { + zlog_err( + "%s: %pFX RTM_DELROUTE received but received a context as well", + __func__, &p); + return 0; + } + if (nhe_id) { rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags, &p, &src_p, NULL, nhe_id, table, metric, @@ -1028,7 +1035,14 @@ static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, } } - return 0; + return 1; +} + +static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id, + int startup) +{ + return netlink_route_change_read_unicast_internal(h, ns_id, startup, + NULL); } static struct mcast_route_data *mroute = NULL; diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h index b1af4b20e1..fd2b79a2bf 100644 --- a/zebra/rt_netlink.h +++ b/zebra/rt_netlink.h @@ -122,6 +122,10 @@ netlink_put_lsp_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx); extern enum netlink_msg_status netlink_put_pw_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx); +int netlink_route_change_read_unicast_internal(struct nlmsghdr *h, + ns_id_t ns_id, int startup, + struct zebra_dplane_ctx *ctx); + #ifdef NETLINK_DEBUG const char *nlmsg_type2str(uint16_t type); const char *af_type2str(int type); diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index c189408b57..84dae7f2d6 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -135,6 +135,8 @@ struct dplane_route_info { uint32_t zd_mtu; uint32_t zd_nexthop_mtu; + uint32_t zd_flags; + /* Nexthop hash entry info */ struct dplane_nexthop_info nhe; @@ -1430,6 +1432,20 @@ uint16_t dplane_ctx_get_old_instance(const struct zebra_dplane_ctx *ctx) return ctx->u.rinfo.zd_old_instance; } +uint32_t dplane_ctx_get_flags(const struct zebra_dplane_ctx *ctx) +{ + DPLANE_CTX_VALID(ctx); + + return ctx->u.rinfo.zd_flags; +} + +void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags) +{ + DPLANE_CTX_VALID(ctx); + + ctx->u.rinfo.zd_flags = flags; +} + uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx) { DPLANE_CTX_VALID(ctx); @@ -2766,25 +2782,16 @@ static int dplane_ctx_ns_init(struct zebra_dplane_ctx *ctx, return AOK; } -/* - * Initialize a context block for a route update from zebra data structs. - */ -int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, - struct route_node *rn, struct route_entry *re) +int dplane_ctx_route_init_basic(struct zebra_dplane_ctx *ctx, + enum dplane_op_e op, struct route_entry *re, + const struct prefix *p, + const struct prefix_ipv6 *src_p, afi_t afi, + safi_t safi) { int ret = EINVAL; - const struct route_table *table = NULL; - const struct rib_table_info *info; - const struct prefix *p, *src_p; - struct zebra_ns *zns; - struct zebra_vrf *zvrf; - struct nexthop *nexthop; - struct zebra_l3vni *zl3vni; - const struct interface *ifp; - struct dplane_intf_extra *if_extra; - if (!ctx || !rn || !re) - goto done; + if (!ctx || !re) + return ret; TAILQ_INIT(&ctx->u.rinfo.intf_extra_q); @@ -2794,9 +2801,6 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, ctx->u.rinfo.zd_type = re->type; ctx->u.rinfo.zd_old_type = re->type; - /* Prefixes: dest, and optional source */ - srcdest_rnode_prefixes(rn, &p, &src_p); - prefix_copy(&(ctx->u.rinfo.zd_dest), p); if (src_p) @@ -2806,6 +2810,7 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, ctx->zd_table_id = re->table; + ctx->u.rinfo.zd_flags = re->flags; ctx->u.rinfo.zd_metric = re->metric; ctx->u.rinfo.zd_old_metric = re->metric; ctx->zd_vrf_id = re->vrf_id; @@ -2816,11 +2821,46 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, ctx->u.rinfo.zd_old_tag = re->tag; ctx->u.rinfo.zd_distance = re->distance; + ctx->u.rinfo.zd_afi = afi; + ctx->u.rinfo.zd_safi = safi; + + return AOK; +} + +/* + * Initialize a context block for a route update from zebra data structs. + */ +int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, + struct route_node *rn, struct route_entry *re) +{ + int ret = EINVAL; + const struct route_table *table = NULL; + const struct rib_table_info *info; + const struct prefix *p; + const struct prefix_ipv6 *src_p; + struct zebra_ns *zns; + struct zebra_vrf *zvrf; + struct nexthop *nexthop; + struct zebra_l3vni *zl3vni; + const struct interface *ifp; + struct dplane_intf_extra *if_extra; + + if (!ctx || !rn || !re) + return ret; + + /* + * Let's grab the data from the route_node + * so that we can call a helper function + */ + + /* Prefixes: dest, and optional source */ + srcdest_rnode_prefixes(rn, &p, (const struct prefix **)&src_p); table = srcdest_rnode_table(rn); info = table->info; - ctx->u.rinfo.zd_afi = info->afi; - ctx->u.rinfo.zd_safi = info->safi; + if (dplane_ctx_route_init_basic(ctx, op, re, p, src_p, info->afi, + info->safi) != AOK) + return ret; /* Copy nexthops; recursive info is included too */ copy_nexthops(&(ctx->u.rinfo.zd_ng.nexthop), @@ -2875,8 +2915,7 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, /* Don't need some info when capturing a system notification */ if (op == DPLANE_OP_SYS_ROUTE_ADD || op == DPLANE_OP_SYS_ROUTE_DELETE) { - ret = AOK; - goto done; + return AOK; } /* Extract ns info - can't use pointers to 'core' structs */ @@ -2897,14 +2936,12 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, * If its a delete we only use the prefix anyway, so this only * matters for INSTALL/UPDATE. */ - if (zebra_nhg_kernel_nexthops_enabled() - && (((op == DPLANE_OP_ROUTE_INSTALL) - || (op == DPLANE_OP_ROUTE_UPDATE)) - && !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED) - && !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_QUEUED))) { - ret = ENOENT; - goto done; - } + if (zebra_nhg_kernel_nexthops_enabled() && + (((op == DPLANE_OP_ROUTE_INSTALL) || + (op == DPLANE_OP_ROUTE_UPDATE)) && + !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED) && + !CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_QUEUED))) + return ENOENT; re->nhe_installed_id = nhe->id; } @@ -2916,10 +2953,7 @@ int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, re->dplane_sequence = zebra_router_get_next_sequence(); ctx->zd_seq = re->dplane_sequence; - ret = AOK; - -done: - return ret; + return AOK; } static int dplane_ctx_tc_qdisc_init(struct zebra_dplane_ctx *ctx, @@ -3031,7 +3065,7 @@ int dplane_ctx_nexthop_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, int ret = EINVAL; if (!ctx || !nhe) - goto done; + return ret; ctx->zd_op = op; ctx->zd_status = ZEBRA_DPLANE_REQUEST_SUCCESS; @@ -3066,7 +3100,6 @@ int dplane_ctx_nexthop_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, ret = AOK; -done: return ret; } @@ -3088,7 +3121,7 @@ int dplane_ctx_intf_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, bool set_pdown, unset_pdown; if (!ctx || !ifp) - goto done; + return ret; ctx->zd_op = op; ctx->zd_status = ZEBRA_DPLANE_REQUEST_SUCCESS; @@ -3133,7 +3166,6 @@ int dplane_ctx_intf_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, ret = AOK; -done: return ret; } @@ -3161,10 +3193,8 @@ int dplane_ctx_lsp_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, /* This may be called to create/init a dplane context, not necessarily * to copy an lsp object. */ - if (lsp == NULL) { - ret = AOK; - goto done; - } + if (lsp == NULL) + return ret; if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) zlog_debug("init dplane ctx %s: in-label %u ecmp# %d", @@ -3207,7 +3237,7 @@ int dplane_ctx_lsp_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, } if (ret != AOK) - goto done; + return ret; /* Capture backup nhlfes/nexthops */ frr_each(nhlfe_list, &lsp->backup_nhlfe_list, nhlfe) { @@ -3228,11 +3258,6 @@ int dplane_ctx_lsp_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, new_nhlfe->nexthop->flags = nhlfe->nexthop->flags; } - /* On error the ctx will be cleaned-up, so we don't need to - * deal with any allocated nhlfe or nexthop structs here. - */ -done: - return ret; } @@ -3293,11 +3318,11 @@ static int dplane_ctx_pw_init(struct zebra_dplane_ctx *ctx, afi = (pw->af == AF_INET) ? AFI_IP : AFI_IP6; table = zebra_vrf_table(afi, SAFI_UNICAST, pw->vrf_id); if (table == NULL) - goto done; + return ret; rn = route_node_match(table, &p); if (rn == NULL) - goto done; + return ret; re = NULL; RNODE_FOREACH_RE(rn, re) { @@ -3365,10 +3390,7 @@ static int dplane_ctx_pw_init(struct zebra_dplane_ctx *ctx, } route_unlock_node(rn); - ret = AOK; - -done: - return ret; + return AOK; } /** @@ -3943,12 +3965,11 @@ enum zebra_dplane_result dplane_route_add(struct route_node *rn, enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; if (rn == NULL || re == NULL) - goto done; + return ret; ret = dplane_route_update_internal(rn, re, NULL, DPLANE_OP_ROUTE_INSTALL); -done: return ret; } @@ -3962,11 +3983,11 @@ enum zebra_dplane_result dplane_route_update(struct route_node *rn, enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; if (rn == NULL || re == NULL) - goto done; + return ret; ret = dplane_route_update_internal(rn, re, old_re, DPLANE_OP_ROUTE_UPDATE); -done: + return ret; } @@ -3979,12 +4000,11 @@ enum zebra_dplane_result dplane_route_delete(struct route_node *rn, enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; if (rn == NULL || re == NULL) - goto done; + return ret; ret = dplane_route_update_internal(rn, re, NULL, DPLANE_OP_ROUTE_DELETE); -done: return ret; } @@ -3997,18 +4017,16 @@ enum zebra_dplane_result dplane_sys_route_add(struct route_node *rn, enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; /* Ignore this event unless a provider plugin has requested it. */ - if (!zdplane_info.dg_sys_route_notifs) { - ret = ZEBRA_DPLANE_REQUEST_SUCCESS; - goto done; - } + if (!zdplane_info.dg_sys_route_notifs) + return ZEBRA_DPLANE_REQUEST_SUCCESS; + if (rn == NULL || re == NULL) - goto done; + return ret; ret = dplane_route_update_internal(rn, re, NULL, DPLANE_OP_SYS_ROUTE_ADD); -done: return ret; } @@ -4021,18 +4039,15 @@ enum zebra_dplane_result dplane_sys_route_del(struct route_node *rn, enum zebra_dplane_result ret = ZEBRA_DPLANE_REQUEST_FAILURE; /* Ignore this event unless a provider plugin has requested it. */ - if (!zdplane_info.dg_sys_route_notifs) { - ret = ZEBRA_DPLANE_REQUEST_SUCCESS; - goto done; - } + if (!zdplane_info.dg_sys_route_notifs) + return ZEBRA_DPLANE_REQUEST_SUCCESS; if (rn == NULL || re == NULL) - goto done; + return ret; ret = dplane_route_update_internal(rn, re, NULL, DPLANE_OP_SYS_ROUTE_DELETE); -done: return ret; } @@ -6287,6 +6302,20 @@ kernel_dplane_process_ipset_entry(struct zebra_dplane_provider *prov, dplane_provider_enqueue_out_ctx(prov, ctx); } +void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p, + struct prefix_ipv6 *src_p, struct route_entry *re, + struct nexthop_group *ng, int startup, + struct zebra_dplane_ctx *ctx) +{ + if (!ctx) + rib_add_multipath(afi, safi, p, src_p, re, ng, startup); + else { + dplane_ctx_route_init_basic(ctx, dplane_ctx_get_op(ctx), re, p, + src_p, afi, safi); + dplane_provider_enqueue_to_zebra(ctx); + } +} + /* * Kernel provider callback */ @@ -6463,7 +6492,7 @@ int dplane_clean_ctx_queue(bool (*context_cb)(struct zebra_dplane_ctx *ctx, TAILQ_INIT(&work_list); if (context_cb == NULL) - goto done; + return AOK; /* Walk the pending context queue under the dplane lock. */ DPLANE_LOCK(); @@ -6487,9 +6516,7 @@ int dplane_clean_ctx_queue(bool (*context_cb)(struct zebra_dplane_ctx *ctx, dplane_ctx_fini(&ctx); } -done: - - return 0; + return AOK; } /* Indicates zebra shutdown/exit is in progress. Some operations may be @@ -6553,10 +6580,8 @@ static bool dplane_work_pending(void) } DPLANE_UNLOCK(); - if (ctx != NULL) { - ret = true; - goto done; - } + if (ctx != NULL) + return true; while (prov) { @@ -6579,7 +6604,6 @@ static bool dplane_work_pending(void) if (ctx != NULL) ret = true; -done: return ret; } diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h index b9fd176de7..51f6f3d897 100644 --- a/zebra/zebra_dplane.h +++ b/zebra/zebra_dplane.h @@ -380,6 +380,8 @@ 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_flags(const struct zebra_dplane_ctx *ctx); +void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags); 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); @@ -908,6 +910,12 @@ dplane_pbr_ipset_entry_delete(struct zebra_pbr_ipset_entry *ipset); int dplane_ctx_route_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, struct route_node *rn, struct route_entry *re); +int dplane_ctx_route_init_basic(struct zebra_dplane_ctx *ctx, + enum dplane_op_e op, struct route_entry *re, + const struct prefix *p, + const struct prefix_ipv6 *src_p, afi_t afi, + safi_t safi); + /* Encode next hop information into data plane context. */ int dplane_ctx_nexthop_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, struct nhg_hash_entry *nhe); @@ -1073,6 +1081,16 @@ void zebra_dplane_pre_finish(void); void zebra_dplane_finish(void); void zebra_dplane_shutdown(void); +/* + * decision point for sending a routing update through the old + * straight to zebra master pthread or through the dplane to + * the master pthread for handling + */ +void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p, + struct prefix_ipv6 *src_p, struct route_entry *re, + struct nexthop_group *ng, int startup, + struct zebra_dplane_ctx *ctx); + #ifdef __cplusplus } #endif diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index 656588bb82..b86780276b 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -1523,8 +1523,7 @@ static bool rib_route_match_ctx(const struct route_entry *re, } done: - - return (result); + return result; } static void zebra_rib_fixup_system(struct route_node *rn) @@ -2261,10 +2260,8 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx) } /* Ensure we clear the QUEUED flag */ - if (!zrouter.asic_offloaded) { - UNSET_FLAG(re->status, ROUTE_ENTRY_QUEUED); - UNSET_FLAG(re->status, ROUTE_ENTRY_ROUTE_REPLACING); - } + UNSET_FLAG(re->status, ROUTE_ENTRY_QUEUED); + UNSET_FLAG(re->status, ROUTE_ENTRY_ROUTE_REPLACING); /* Is this a notification that ... matters? We mostly care about * the route that is currently selected for installation; we may also @@ -2307,6 +2304,19 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx) dplane_ctx_get_type(ctx))); } goto done; + } else { + uint32_t flags = dplane_ctx_get_flags(ctx); + + if (CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOADED)) { + UNSET_FLAG(re->flags, ZEBRA_FLAG_OFFLOAD_FAILED); + SET_FLAG(re->flags, ZEBRA_FLAG_OFFLOADED); + } + if (CHECK_FLAG(flags, ZEBRA_FLAG_OFFLOAD_FAILED)) { + UNSET_FLAG(re->flags, ZEBRA_FLAG_OFFLOADED); + SET_FLAG(re->flags, ZEBRA_FLAG_OFFLOAD_FAILED); + } + if (CHECK_FLAG(flags, ZEBRA_FLAG_TRAPPED)) + SET_FLAG(re->flags, ZEBRA_FLAG_TRAPPED); } /* We'll want to determine whether the installation status of the @@ -2340,55 +2350,70 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx) /* Various fib transitions: changed nexthops; from installed to * not-installed; or not-installed to installed. */ - if (start_count > 0 && end_count > 0) { - if (debug_p) - zlog_debug( - "%s(%u:%u):%pRN applied nexthop changes from dplane notification", - VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx), - dplane_ctx_get_table(ctx), rn); + if (zrouter.asic_notification_nexthop_control) { + if (start_count > 0 && end_count > 0) { + if (debug_p) + zlog_debug( + "%s(%u:%u):%pRN applied nexthop changes from dplane notification", + VRF_LOGNAME(vrf), + dplane_ctx_get_vrf(ctx), + dplane_ctx_get_table(ctx), rn); - /* Changed nexthops - update kernel/others */ - dplane_route_notif_update(rn, re, - DPLANE_OP_ROUTE_UPDATE, ctx); + /* 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( - "%s(%u:%u):%pRN installed transition from dplane notification", - VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx), - dplane_ctx_get_table(ctx), rn); + } else if (start_count == 0 && end_count > 0) { + if (debug_p) + zlog_debug( + "%s(%u:%u):%pRN installed transition from dplane notification", + VRF_LOGNAME(vrf), + dplane_ctx_get_vrf(ctx), + dplane_ctx_get_table(ctx), rn); - /* We expect this to be the selected route, so we want - * to tell others about this transition. - */ - SET_FLAG(re->status, ROUTE_ENTRY_INSTALLED); + /* We expect this to be the selected route, so we want + * to tell others about this transition. + */ + SET_FLAG(re->status, ROUTE_ENTRY_INSTALLED); - /* Changed nexthops - update kernel/others */ - dplane_route_notif_update(rn, re, DPLANE_OP_ROUTE_UPDATE, ctx); + /* Changed nexthops - update kernel/others */ + dplane_route_notif_update(rn, re, + DPLANE_OP_ROUTE_UPDATE, ctx); - /* Redistribute, lsp, and nht update */ - redistribute_update(rn, re, NULL); + /* Redistribute, lsp, and nht update */ + redistribute_update(rn, re, NULL); - } else if (start_count > 0 && end_count == 0) { - if (debug_p) - zlog_debug( - "%s(%u:%u):%pRN un-installed transition from dplane notification", - VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx), - dplane_ctx_get_table(ctx), rn); + } else if (start_count > 0 && end_count == 0) { + if (debug_p) + zlog_debug( + "%s(%u:%u):%pRN un-installed transition from dplane notification", + VRF_LOGNAME(vrf), + dplane_ctx_get_vrf(ctx), + dplane_ctx_get_table(ctx), rn); - /* 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); + /* 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); + /* Changed nexthops - update kernel/others */ + dplane_route_notif_update(rn, re, + DPLANE_OP_ROUTE_DELETE, ctx); - /* Redistribute, lsp, and nht update */ - redistribute_delete(rn, re, NULL); + /* Redistribute, lsp, and nht update */ + redistribute_delete(rn, re, NULL); + } + } + + if (!zebra_router_notify_on_ack()) { + if (CHECK_FLAG(re->flags, ZEBRA_FLAG_OFFLOADED)) + zsend_route_notify_owner_ctx(ctx, ZAPI_ROUTE_INSTALLED); + if (CHECK_FLAG(re->flags, ZEBRA_FLAG_OFFLOAD_FAILED)) + zsend_route_notify_owner_ctx(ctx, + ZAPI_ROUTE_FAIL_INSTALL); } /* Make any changes visible for lsp and nexthop-tracking processing */ diff --git a/zebra/zebra_router.c b/zebra/zebra_router.c index b8923ef57d..a9a7b66ce7 100644 --- a/zebra/zebra_router.c +++ b/zebra/zebra_router.c @@ -330,6 +330,17 @@ void zebra_router_init(bool asic_offload, bool notify_on_ack) zrouter.asic_offloaded = asic_offload; zrouter.notify_on_ack = notify_on_ack; + /* + * If you start using asic_notification_nexthop_control + * come talk to the FRR community about what you are doing + * We would like to know. + */ +#if CONFDATE > 20251231 + CPP_NOTICE( + "Remove zrouter.asic_notification_nexthop_control as that it's not being maintained or used"); +#endif + zrouter.asic_notification_nexthop_control = false; + #ifdef HAVE_SCRIPTING zebra_script_init(); #endif diff --git a/zebra/zebra_router.h b/zebra/zebra_router.h index 069437ef47..e0ef86f082 100644 --- a/zebra/zebra_router.h +++ b/zebra/zebra_router.h @@ -224,6 +224,14 @@ struct zebra_router { bool asic_offloaded; bool notify_on_ack; + /* + * If the asic is notifying us about successful nexthop + * allocation/control. Some developers have made their + * asic take control of how many nexthops/ecmp they can + * have and will report what is successfull or not + */ + bool asic_notification_nexthop_control; + bool supports_nhgs; bool all_mc_forwardingv4, default_mc_forwardingv4; diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index 91a0c1dd31..8ed8abe304 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -630,8 +630,7 @@ static void show_route_nexthop_helper(struct vty *vty, case NEXTHOP_TYPE_IFINDEX: vty_out(vty, " is directly connected, %s", - ifindex2ifname(nexthop->ifindex, - nexthop->vrf_id)); + ifindex2ifname(nexthop->ifindex, nexthop->vrf_id)); break; case NEXTHOP_TYPE_BLACKHOLE: vty_out(vty, " unreachable"); @@ -703,8 +702,10 @@ static void show_route_nexthop_helper(struct vty *vty, seg6local_context2str(buf, sizeof(buf), &nexthop->nh_srv6->seg6local_ctx, nexthop->nh_srv6->seg6local_action); - vty_out(vty, ", seg6local %s %s", seg6local_action2str( - nexthop->nh_srv6->seg6local_action), buf); + vty_out(vty, ", seg6local %s %s", + seg6local_action2str( + nexthop->nh_srv6->seg6local_action), + buf); inet_ntop(AF_INET6, &nexthop->nh_srv6->seg6_segs, buf, sizeof(buf)); @@ -722,6 +723,7 @@ static void show_route_nexthop_helper(struct vty *vty, } } + /* * Render a nexthop into a json object; the caller allocates and owns * the json object memory. @@ -806,9 +808,8 @@ static void show_nexthop_json_helper(json_object *json_nexthop, json_nexthop, "reject"); break; case BLACKHOLE_ADMINPROHIB: - json_object_boolean_true_add( - json_nexthop, - "admin-prohibited"); + json_object_boolean_true_add(json_nexthop, + "adminProhibited"); break; case BLACKHOLE_NULL: json_object_boolean_true_add( @@ -827,7 +828,7 @@ static void show_nexthop_json_helper(json_object *json_nexthop, if (nexthop->rparent) json_object_boolean_true_add(json_nexthop, "resolver"); - if (nexthop->vrf_id != re->vrf_id) + if ((re == NULL || (nexthop->vrf_id != re->vrf_id))) json_object_string_add(json_nexthop, "vrf", vrf_id_to_name(nexthop->vrf_id)); @@ -840,8 +841,7 @@ static void show_nexthop_json_helper(json_object *json_nexthop, "active"); if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ONLINK)) - json_object_boolean_true_add(json_nexthop, - "onLink"); + json_object_boolean_true_add(json_nexthop, "onLink"); if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_LINKDOWN)) json_object_boolean_true_add(json_nexthop, "linkDown"); @@ -1479,125 +1479,264 @@ DEFUN (ip_nht_default_route, return CMD_SUCCESS; } -static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe) +static void show_nexthop_group_out(struct vty *vty, struct nhg_hash_entry *nhe, + json_object *json_nhe_hdr) { struct nexthop *nexthop = NULL; struct nhg_connected *rb_node_dep = NULL; struct nexthop_group *backup_nhg; char up_str[MONOTIME_STRLEN]; char time_left[MONOTIME_STRLEN]; + json_object *json_dependants = NULL; + json_object *json_depends = NULL; + json_object *json_nexthop_array = NULL; + json_object *json_nexthops = NULL; + json_object *json = NULL; + json_object *json_backup_nexthop_array = NULL; + json_object *json_backup_nexthops = NULL; + uptime2str(nhe->uptime, up_str, sizeof(up_str)); - vty_out(vty, "ID: %u (%s)\n", nhe->id, zebra_route_string(nhe->type)); - vty_out(vty, " RefCnt: %u", nhe->refcnt); - if (thread_is_scheduled(nhe->timer)) - vty_out(vty, " Time to Deletion: %s", - thread_timer_to_hhmmss(time_left, sizeof(time_left), - nhe->timer)); - vty_out(vty, "\n"); + if (json_nhe_hdr) + json = json_object_new_object(); + + if (json) { + json_object_string_add(json, "type", + zebra_route_string(nhe->type)); + json_object_int_add(json, "refCount", nhe->refcnt); + if (thread_is_scheduled(nhe->timer)) + json_object_string_add( + json, "timeToDeletion", + thread_timer_to_hhmmss(time_left, + sizeof(time_left), + nhe->timer)); + json_object_string_add(json, "uptime", up_str); + json_object_string_add(json, "vrf", + vrf_id_to_name(nhe->vrf_id)); - vty_out(vty, " Uptime: %s\n", up_str); - vty_out(vty, " VRF: %s\n", vrf_id_to_name(nhe->vrf_id)); + } else { + vty_out(vty, "ID: %u (%s)\n", nhe->id, + zebra_route_string(nhe->type)); + vty_out(vty, " RefCnt: %u", nhe->refcnt); + if (thread_is_scheduled(nhe->timer)) + vty_out(vty, " Time to Deletion: %s", + thread_timer_to_hhmmss(time_left, + sizeof(time_left), + nhe->timer)); + vty_out(vty, "\n"); + vty_out(vty, " Uptime: %s\n", up_str); + vty_out(vty, " VRF: %s\n", vrf_id_to_name(nhe->vrf_id)); + } if (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_VALID)) { - vty_out(vty, " Valid"); - if (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED)) - vty_out(vty, ", Installed"); - vty_out(vty, "\n"); + if (json) + json_object_boolean_true_add(json, "valid"); + else + vty_out(vty, " Valid"); + + if (CHECK_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED)) { + if (json) + json_object_boolean_true_add(json, "installed"); + else + vty_out(vty, ", Installed"); + } + if (!json) + vty_out(vty, "\n"); + } + if (nhe->ifp) { + if (json) + json_object_int_add(json, "interfaceIndex", + nhe->ifp->ifindex); + else + vty_out(vty, " Interface Index: %d\n", + nhe->ifp->ifindex); } - if (nhe->ifp) - vty_out(vty, " Interface Index: %d\n", nhe->ifp->ifindex); if (!zebra_nhg_depends_is_empty(nhe)) { - vty_out(vty, " Depends:"); + if (json) + json_depends = json_object_new_array(); + else + vty_out(vty, " Depends:"); frr_each(nhg_connected_tree, &nhe->nhg_depends, rb_node_dep) { - vty_out(vty, " (%u)", rb_node_dep->nhe->id); + if (json_depends) + json_object_array_add( + json_depends, + json_object_new_int( + rb_node_dep->nhe->id)); + else + vty_out(vty, " (%u)", rb_node_dep->nhe->id); } - vty_out(vty, "\n"); + if (!json_depends) + vty_out(vty, "\n"); + else + json_object_object_add(json, "depends", json_depends); } /* Output nexthops */ - for (ALL_NEXTHOPS(nhe->nhg, nexthop)) { - if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) - vty_out(vty, " "); - else - /* Make recursive nexthops a bit more clear */ - vty_out(vty, " "); + if (json) + json_nexthop_array = json_object_new_array(); - show_route_nexthop_helper(vty, NULL, nexthop); + + for (ALL_NEXTHOPS(nhe->nhg, nexthop)) { + if (json_nexthop_array) { + json_nexthops = json_object_new_object(); + show_nexthop_json_helper(json_nexthops, nexthop, NULL); + } else { + if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) + vty_out(vty, " "); + else + /* Make recursive nexthops a bit more clear */ + vty_out(vty, " "); + show_route_nexthop_helper(vty, NULL, nexthop); + } if (nhe->backup_info == NULL || nhe->backup_info->nhe == NULL) { if (CHECK_FLAG(nexthop->flags, - NEXTHOP_FLAG_HAS_BACKUP)) - vty_out(vty, " [backup %d]", - nexthop->backup_idx[0]); + NEXTHOP_FLAG_HAS_BACKUP)) { + if (json) + json_object_int_add( + json_nexthops, "backup", + nexthop->backup_idx[0]); + else + vty_out(vty, " [backup %d]", + nexthop->backup_idx[0]); + } + + if (!json) + vty_out(vty, "\n"); + else + json_object_array_add(json_nexthop_array, + json_nexthops); - vty_out(vty, "\n"); continue; } - /* TODO -- print more useful backup info */ - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_HAS_BACKUP)) { - int i; - - vty_out(vty, "[backup"); - for (i = 0; i < nexthop->backup_num; i++) - vty_out(vty, " %d", nexthop->backup_idx[i]); - - vty_out(vty, "]"); + if (!json) { + /* TODO -- print more useful backup info */ + if (CHECK_FLAG(nexthop->flags, + NEXTHOP_FLAG_HAS_BACKUP)) { + int i; + + vty_out(vty, "[backup"); + for (i = 0; i < nexthop->backup_num; i++) + vty_out(vty, " %d", + nexthop->backup_idx[i]); + vty_out(vty, "]"); + } + vty_out(vty, "\n"); + } else { + json_object_array_add(json_nexthop_array, + json_nexthops); } - - vty_out(vty, "\n"); } + if (json) + json_object_object_add(json, "nexthops", json_nexthop_array); + /* Output backup nexthops (if any) */ backup_nhg = zebra_nhg_get_backup_nhg(nhe); if (backup_nhg) { - vty_out(vty, " Backups:\n"); + if (json) + json_backup_nexthop_array = json_object_new_array(); + else + vty_out(vty, " Backups:\n"); for (ALL_NEXTHOPS_PTR(backup_nhg, nexthop)) { - if (!CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) - vty_out(vty, " "); - else - /* Make recursive nexthops a bit more clear */ - vty_out(vty, " "); + if (json_backup_nexthop_array) { + json_backup_nexthops = json_object_new_object(); + show_nexthop_json_helper(json_backup_nexthops, + nexthop, NULL); + json_object_array_add(json_backup_nexthop_array, + json_backup_nexthops); + } else { - show_route_nexthop_helper(vty, NULL, nexthop); - vty_out(vty, "\n"); + if (!CHECK_FLAG(nexthop->flags, + NEXTHOP_FLAG_RECURSIVE)) + vty_out(vty, " "); + else + /* Make recursive nexthops a bit more + * clear + */ + vty_out(vty, " "); + show_route_nexthop_helper(vty, NULL, nexthop); + vty_out(vty, "\n"); + } } + + if (json) + json_object_object_add(json, "backupNexthops", + json_backup_nexthop_array); } if (!zebra_nhg_dependents_is_empty(nhe)) { - vty_out(vty, " Dependents:"); + if (json) + json_dependants = json_object_new_array(); + else + vty_out(vty, " Dependents:"); frr_each(nhg_connected_tree, &nhe->nhg_dependents, rb_node_dep) { - vty_out(vty, " (%u)", rb_node_dep->nhe->id); + if (json) + json_object_array_add( + json_dependants, + json_object_new_int( + rb_node_dep->nhe->id)); + else + vty_out(vty, " (%u)", rb_node_dep->nhe->id); } - vty_out(vty, "\n"); + if (json) + json_object_object_add(json, "dependents", + json_dependants); + else + vty_out(vty, "\n"); } - if (nhe->nhg.nhgr.buckets) - vty_out(vty, - " Buckets: %u Idle Timer: %u Unbalanced Timer: %u Unbalanced time: %" PRIu64 "\n", - nhe->nhg.nhgr.buckets, nhe->nhg.nhgr.idle_timer, - nhe->nhg.nhgr.unbalanced_timer, - nhe->nhg.nhgr.unbalanced_time); + if (nhe->nhg.nhgr.buckets) { + if (json) { + json_object_int_add(json, "buckets", + nhe->nhg.nhgr.buckets); + json_object_int_add(json, "idleTimer", + nhe->nhg.nhgr.idle_timer); + json_object_int_add(json, "unbalancedTimer", + nhe->nhg.nhgr.unbalanced_timer); + json_object_int_add(json, "unbalancedTime", + nhe->nhg.nhgr.unbalanced_time); + } else { + vty_out(vty, + " Buckets: %u Idle Timer: %u Unbalanced Timer: %u Unbalanced time: %" PRIu64 + "\n", + nhe->nhg.nhgr.buckets, nhe->nhg.nhgr.idle_timer, + nhe->nhg.nhgr.unbalanced_timer, + nhe->nhg.nhgr.unbalanced_time); + } + } + + if (json_nhe_hdr) + json_object_object_addf(json_nhe_hdr, json, "%u", nhe->id); } -static int show_nexthop_group_id_cmd_helper(struct vty *vty, uint32_t id) +static int show_nexthop_group_id_cmd_helper(struct vty *vty, uint32_t id, + json_object *json) { struct nhg_hash_entry *nhe = NULL; nhe = zebra_nhg_lookup_id(id); if (nhe) - show_nexthop_group_out(vty, nhe); + show_nexthop_group_out(vty, nhe, json); else { - vty_out(vty, "Nexthop Group ID: %u does not exist\n", id); + if (json) + vty_json(vty, json); + else + vty_out(vty, "Nexthop Group ID: %u does not exist\n", + id); return CMD_WARNING; } + + if (json) + vty_json(vty, json); + return CMD_SUCCESS; } @@ -1608,6 +1747,7 @@ struct nhe_show_context { vrf_id_t vrf_id; afi_t afi; int type; + json_object *json; }; static int nhe_show_walker(struct hash_bucket *bucket, void *arg) @@ -1626,7 +1766,7 @@ static int nhe_show_walker(struct hash_bucket *bucket, void *arg) if (ctx->type && nhe->type != ctx->type) goto done; - show_nexthop_group_out(ctx->vty, nhe); + show_nexthop_group_out(ctx->vty, nhe, ctx->json); done: return HASHWALK_CONTINUE; @@ -1634,7 +1774,7 @@ done: static void show_nexthop_group_cmd_helper(struct vty *vty, struct zebra_vrf *zvrf, afi_t afi, - int type) + int type, json_object *json) { struct nhe_show_context ctx; @@ -1642,6 +1782,7 @@ static void show_nexthop_group_cmd_helper(struct vty *vty, ctx.afi = afi; ctx.vrf_id = zvrf->vrf->vrf_id; ctx.type = type; + ctx.json = json; hash_walk(zrouter.nhgs_id, nhe_show_walker, &ctx); } @@ -1659,7 +1800,7 @@ static void if_nexthop_group_dump_vty(struct vty *vty, struct interface *ifp) frr_each(nhg_connected_tree, &zebra_if->nhg_dependents, rb_node_dep) { vty_out(vty, " "); - show_nexthop_group_out(vty, rb_node_dep->nhe); + show_nexthop_group_out(vty, rb_node_dep->nhe, NULL); } } } @@ -1698,29 +1839,36 @@ DEFPY (show_interface_nexthop_group, return CMD_SUCCESS; } -DEFPY (show_nexthop_group, - show_nexthop_group_cmd, - "show nexthop-group rib <(0-4294967295)$id|[singleton <ip$v4|ipv6$v6>] [<kernel|zebra|bgp|sharp>$type_str] [vrf <NAME$vrf_name|all$vrf_all>]>", - SHOW_STR - "Show Nexthop Groups\n" - "RIB information\n" - "Nexthop Group ID\n" - "Show Singleton Nexthop-Groups\n" - IP_STR - IP6_STR - "Kernel (not installed via the zebra RIB)\n" - "Zebra (implicitly created by zebra)\n" - "Border Gateway Protocol (BGP)\n" - "Super Happy Advanced Routing Protocol (SHARP)\n" - VRF_FULL_CMD_HELP_STR) +DEFPY(show_nexthop_group, + show_nexthop_group_cmd, + "show nexthop-group rib <(0-4294967295)$id|[singleton <ip$v4|ipv6$v6>] [<kernel|zebra|bgp|sharp>$type_str] [vrf <NAME$vrf_name|all$vrf_all>]> [json]", + SHOW_STR + "Show Nexthop Groups\n" + "RIB information\n" + "Nexthop Group ID\n" + "Show Singleton Nexthop-Groups\n" + IP_STR + IP6_STR + "Kernel (not installed via the zebra RIB)\n" + "Zebra (implicitly created by zebra)\n" + "Border Gateway Protocol (BGP)\n" + "Super Happy Advanced Routing Protocol (SHARP)\n" + VRF_FULL_CMD_HELP_STR + JSON_STR) { struct zebra_vrf *zvrf = NULL; afi_t afi = AFI_UNSPEC; int type = 0; + bool uj = use_json(argc, argv); + json_object *json = NULL; + json_object *json_vrf = NULL; + + if (uj) + json = json_object_new_object(); if (id) - return show_nexthop_group_id_cmd_helper(vty, id); + return show_nexthop_group_id_cmd_helper(vty, id, json); if (v4) afi = AFI_IP; @@ -1736,8 +1884,11 @@ DEFPY (show_nexthop_group, } if (!vrf_is_backend_netns() && (vrf_name || vrf_all)) { - vty_out(vty, - "VRF subcommand does not make any sense in l3mdev based vrf's\n"); + if (uj) + vty_json(vty, json); + else + vty_out(vty, + "VRF subcommand does not make any sense in l3mdev based vrf's\n"); return CMD_WARNING; } @@ -1750,11 +1901,21 @@ DEFPY (show_nexthop_group, zvrf = vrf->info; if (!zvrf) continue; + if (uj) + json_vrf = json_object_new_object(); + else + vty_out(vty, "VRF: %s\n", vrf->name); - vty_out(vty, "VRF: %s\n", vrf->name); - show_nexthop_group_cmd_helper(vty, zvrf, afi, type); + show_nexthop_group_cmd_helper(vty, zvrf, afi, type, + json_vrf); + if (uj) + json_object_object_add(json, vrf->name, + json_vrf); } + if (uj) + vty_json(vty, json); + return CMD_SUCCESS; } @@ -1764,12 +1925,18 @@ DEFPY (show_nexthop_group, zvrf = zebra_vrf_lookup_by_name(VRF_DEFAULT_NAME); if (!zvrf) { - vty_out(vty, "%% VRF '%s' specified does not exist\n", - vrf_name); + if (uj) + vty_json(vty, json); + else + vty_out(vty, "%% VRF '%s' specified does not exist\n", + vrf_name); return CMD_WARNING; } - show_nexthop_group_cmd_helper(vty, zvrf, afi, type); + show_nexthop_group_cmd_helper(vty, zvrf, afi, type, json); + + if (uj) + vty_json(vty, json); return CMD_SUCCESS; } @@ -4073,6 +4240,15 @@ DEFUN (show_zebra, ttable_add_row(table, "ASIC offload|%s", zrouter.asic_offloaded ? "Used" : "Unavailable"); + /* + * Do not display this unless someone is actually using it + * + * Why this distinction? I think this is effectively dead code + * and should not be exposed. Maybe someone proves me wrong. + */ + if (zrouter.asic_notification_nexthop_control) + ttable_add_row(table, "ASIC offload and nexthop control|Used"); + ttable_add_row(table, "RA|%s", rtadv_compiled_in() ? "Compiled in" : "Not Compiled in"); ttable_add_row(table, "RFC 5549|%s", |
