diff options
46 files changed, 4221 insertions, 579 deletions
diff --git a/doc/developer/zebra.rst b/doc/developer/zebra.rst index 6a73803d01..d51cbc9a14 100644 --- a/doc/developer/zebra.rst +++ b/doc/developer/zebra.rst @@ -382,3 +382,75 @@ Zebra Protocol Commands +------------------------------------+-------+ | ZEBRA_OPAQUE_UNREGISTER | 109 | +------------------------------------+-------+ +| ZEBRA_NEIGH_DISCOVER | 110 | ++------------------------------------+-------+ + +Dataplane batching +================== + +Dataplane batching is an optimization feature that reduces the processing +time involved in the user space to kernel space transition for every message we +want to send. + +Design +----------- + +With our dataplane abstraction, we create a queue of dataplane context objects +for the messages we want to send to the kernel. In a separate pthread, we +loop over this queue and send the context objects to the appropriate +dataplane. A batching enhancement tightly integrates with the dataplane +context objects so they are able to be batch sent to dataplanes that support +it. + +There is one main change in the dataplane code. It does not call +kernel-dependent functions one-by-one, but instead it hands a list of work down +to the kernel level for processing. + +Netlink +^^^^^^^ + +At the moment, this is the only dataplane that allows for batch sending +messages to it. + +When messages must be sent to the kernel, they are consecutively added +to the batch represented by the `struct nl_batch`. Context objects are firstly +encoded to their binary representation. All the encoding functions use the same +interface: take a context object, a buffer and a size of the buffer as an +argument. It is important that they should handle a situation in which a message +wouldn't fit in the buffer and return a proper error. To achieve a zero-copy +(in the user space only) messages are encoded to the same buffer which will +be passed to the kernel. Hence, we can theoretically hit the boundary of the +buffer. + +Messages stored in the batch are sent if one of the conditions occurs: + +- When an encoding function returns the buffer overflow error. The context + object that caused this error is re-added to the new, empty batch. + +- When the size of the batch hits certain limit. + +- When the namespace of a currently being processed context object is + different from all the previous ones. They have to be sent through + distinct sockets, so the messages cannot share the same buffer. + +- After the last message from the list is processed. + +As mentioned earlier, there is a special threshold which is smaller than +the size of the underlying buffer. It prevents the overflow error and thus +eliminates the case, in which a message is encoded twice. + +The buffer used in the batching is global, since allocating that big amount of +memory every time wouldn't be most effective. However, its size can be changed +dynamically, using hidden vtysh command: +``zebra kernel netlink batch-tx-buf (1-1048576) (1-1048576)``. This feature is +only used in tests and shouldn't be utilized in any other place. + +For every failed message in the batch, the kernel responds with an error +message. Error messages are kept in the same order as they were sent, so parsing the +response is straightforward. We use the two pointer technique to match +requests with responses and then set appropriate status of dataplane context +objects. There is also a global receive buffer and it is assumed that whatever +the kernel sends it will fit in this buffer. The payload of netlink error messages +consists of a error code and the original netlink message of the request, so +the batch response won't be bigger than the batch request increased by +some space for the headers. diff --git a/doc/user/sharp.rst b/doc/user/sharp.rst index 76bdc48dc0..57ef141c7e 100644 --- a/doc/user/sharp.rst +++ b/doc/user/sharp.rst @@ -78,7 +78,8 @@ keyword. At present, no sharp commands will be preserved in the config. The nexthop or import choice chooses the type of nexthop we are asking zebra to watch for us. This choice affects zebra's decision on what matches. Connected tells zebra whether or not that we want the route - matched against to be a static or connected route. The no form of + matched against to be a static or connected route for the nexthop keyword, + for the import keyword connected means exact match. The no form of the command obviously turns this watching off. .. index:: sharp data nexthop @@ -137,3 +138,9 @@ keyword. At present, no sharp commands will be preserved in the config. Remove a test zapi client session that was created with the specified session id. + +.. index:: sharp neigh discover +.. clicmd:: sharp neigh discover [vrf NAME] <A.B.C.D|X:X::X:X> IFNAME + + Send an ARP/NDP request to trigger the addition of a neighbor in the ARP + table. @@ -451,7 +451,8 @@ static const struct zebra_desc_table command_types[] = { DESC_ENTRY(ZEBRA_CLIENT_CAPABILITIES), DESC_ENTRY(ZEBRA_OPAQUE_MESSAGE), DESC_ENTRY(ZEBRA_OPAQUE_REGISTER), - DESC_ENTRY(ZEBRA_OPAQUE_UNREGISTER)}; + DESC_ENTRY(ZEBRA_OPAQUE_UNREGISTER), + DESC_ENTRY(ZEBRA_NEIGH_DISCOVER)}; #undef DESC_ENTRY static const struct zebra_desc_table unknown = {0, "unknown", '?'}; diff --git a/lib/thread.c b/lib/thread.c index 1df4eee25c..19e4827283 100644 --- a/lib/thread.c +++ b/lib/thread.c @@ -726,17 +726,16 @@ static void thread_free(struct thread_master *master, struct thread *thread) static int fd_poll(struct thread_master *m, struct pollfd *pfds, nfds_t pfdsize, nfds_t count, const struct timeval *timer_wait) { - /* If timer_wait is null here, that means poll() should block - * indefinitely, - * unless the thread_master has overridden it by setting + /* + * If timer_wait is null here, that means poll() should block + * indefinitely, unless the thread_master has overridden it by setting * ->selectpoll_timeout. + * * If the value is positive, it specifies the maximum number of - * milliseconds - * to wait. If the timeout is -1, it specifies that we should never wait - * and - * always return immediately even if no event is detected. If the value - * is - * zero, the behavior is default. */ + * milliseconds to wait. If the timeout is -1, it specifies that + * we should never wait and always return immediately even if no + * event is detected. If the value is zero, the behavior is default. + */ int timeout = -1; /* number of file descriptors with events */ @@ -860,7 +859,7 @@ funcname_thread_add_timer_timeval(struct thread_master *m, frr_with_mutex(&m->mtx) { if (t_ptr && *t_ptr) - // thread is already scheduled; don't reschedule + /* thread is already scheduled; don't reschedule */ return NULL; thread = thread_get(m, type, func, arg, debugargpass); @@ -940,7 +939,7 @@ struct thread *funcname_thread_add_event(struct thread_master *m, frr_with_mutex(&m->mtx) { if (t_ptr && *t_ptr) - // thread is already scheduled; don't reschedule + /* thread is already scheduled; don't reschedule */ break; thread = thread_get(m, THREAD_EVENT, func, arg, debugargpass); @@ -1047,11 +1046,12 @@ static void do_thread_cancel(struct thread_master *master) struct cancel_req *cr; struct listnode *ln; for (ALL_LIST_ELEMENTS_RO(master->cancel_req, ln, cr)) { - /* If this is an event object cancellation, linear search - * through event - * list deleting any events which have the specified argument. - * We also - * need to check every thread in the ready queue. */ + /* + * If this is an event object cancellation, linear search + * through event list deleting any events which have the + * specified argument. We also need to check every thread + * in the ready queue. + */ if (cr->eventobj) { struct thread *t; @@ -1075,11 +1075,12 @@ static void do_thread_cancel(struct thread_master *master) continue; } - /* The pointer varies depending on whether the cancellation - * request was - * made asynchronously or not. If it was, we need to check - * whether the - * thread even exists anymore before cancelling it. */ + /* + * The pointer varies depending on whether the cancellation + * request was made asynchronously or not. If it was, we + * need to check whether the thread even exists anymore + * before cancelling it. + */ thread = (cr->thread) ? cr->thread : *cr->threadref; if (!thread) @@ -1304,18 +1305,21 @@ static void thread_process_io(struct thread_master *m, unsigned int num) ready++; - /* Unless someone has called thread_cancel from another pthread, - * the only - * thing that could have changed in m->handler.pfds while we - * were - * asleep is the .events field in a given pollfd. Barring - * thread_cancel() - * that value should be a superset of the values we have in our - * copy, so - * there's no need to update it. Similarily, barring deletion, - * the fd - * should still be a valid index into the master's pfds. */ - if (pfds[i].revents & (POLLIN | POLLHUP)) { + /* + * Unless someone has called thread_cancel from another + * pthread, the only thing that could have changed in + * m->handler.pfds while we were asleep is the .events + * field in a given pollfd. Barring thread_cancel() that + * value should be a superset of the values we have in our + * copy, so there's no need to update it. Similarily, + * barring deletion, the fd should still be a valid index + * into the master's pfds. + * + * We are including POLLERR here to do a READ event + * this is because the read should fail and the + * read function should handle it appropriately + */ + if (pfds[i].revents & (POLLIN | POLLHUP | POLLERR)) { thread_process_io_helper(m, m->read[pfds[i].fd], POLLIN, pfds[i].revents, i); } @@ -1427,11 +1431,10 @@ struct thread *thread_fetch(struct thread_master *m, struct thread *fetch) * * - If there are events pending, set the poll() timeout to zero * - If there are no events pending, but there are timers - * pending, set the - * timeout to the smallest remaining time on any timer + * pending, set the timeout to the smallest remaining time on + * any timer. * - If there are neither timers nor events pending, but there - * are file - * descriptors pending, block indefinitely in poll() + * are file descriptors pending, block indefinitely in poll() * - If nothing is pending, it's time for the application to die * * In every case except the last, we need to hit poll() at least diff --git a/lib/yang_wrappers.c b/lib/yang_wrappers.c index 4f743096ee..4c658c1bfb 100644 --- a/lib/yang_wrappers.c +++ b/lib/yang_wrappers.c @@ -792,6 +792,29 @@ struct yang_data *yang_data_new_empty(const char *xpath) return yang_data_new(xpath, NULL); } +bool yang_dnode_get_empty(const struct lyd_node *dnode, const char *xpath_fmt, + ...) +{ + va_list ap; + char xpath[XPATH_MAXLEN]; + const struct lyd_node_leaf_list *dleaf; + + assert(dnode); + + va_start(ap, xpath_fmt); + vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap); + va_end(ap); + + dnode = yang_dnode_get(dnode, xpath); + if (dnode) { + dleaf = (const struct lyd_node_leaf_list *)dnode; + if (dleaf->value_type == LY_TYPE_EMPTY) + return true; + } + + return false; +} + /* * Derived type: IP prefix. */ diff --git a/lib/yang_wrappers.h b/lib/yang_wrappers.h index 335ff319d5..d781dfb1e4 100644 --- a/lib/yang_wrappers.h +++ b/lib/yang_wrappers.h @@ -120,6 +120,8 @@ extern void yang_get_default_string_buf(char *buf, size_t size, /* empty */ extern struct yang_data *yang_data_new_empty(const char *xpath); +extern bool yang_dnode_get_empty(const struct lyd_node *dnode, + const char *xpath_fmt, ...); /* ip prefix */ extern void yang_str2prefix(const char *value, union prefixptr prefix); diff --git a/lib/zclient.c b/lib/zclient.c index 6449fe15b9..808aa18bbe 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -3953,3 +3953,23 @@ int32_t zapi_capabilities_decode(struct stream *s, struct zapi_cap *api) stream_failure: return 0; } + +int zclient_send_neigh_discovery_req(struct zclient *zclient, + const struct interface *ifp, + const struct prefix *p) +{ + struct stream *s; + + s = zclient->obuf; + stream_reset(s); + + zclient_create_header(s, ZEBRA_NEIGH_DISCOVER, ifp->vrf_id); + stream_putl(s, ifp->ifindex); + + stream_putc(s, p->family); + stream_putc(s, p->prefixlen); + stream_put(s, &p->u.prefix, prefix_blen(p)); + + stream_putw_at(s, 0, stream_get_endp(s)); + return zclient_send_message(zclient); +} diff --git a/lib/zclient.h b/lib/zclient.h index bcdae85823..dab384d5ec 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -214,6 +214,7 @@ typedef enum { ZEBRA_OPAQUE_MESSAGE, ZEBRA_OPAQUE_REGISTER, ZEBRA_OPAQUE_UNREGISTER, + ZEBRA_NEIGH_DISCOVER, } zebra_message_types_t; enum zebra_error_types { @@ -956,6 +957,10 @@ enum zapi_opaque_registry { */ extern int zclient_send_hello(struct zclient *client); +extern int zclient_send_neigh_discovery_req(struct zclient *zclient, + const struct interface *ifp, + const struct prefix *p); + #ifdef __cplusplus } #endif diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c index b0dd5c6fc2..49cd42d030 100644 --- a/ospfd/ospf_packet.c +++ b/ospfd/ospf_packet.c @@ -53,6 +53,7 @@ #include "ospfd/ospf_flood.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_errors.h" +#include "ospfd/ospf_zebra.h" /* * OSPF Fragmentation / fragmented writes @@ -4319,21 +4320,10 @@ void ospf_ls_ack_send_delayed(struct ospf_interface *oi) * punt-to-CPU set on them. This may overload the CPU control path that * can be avoided if the MAC was known apriori. */ -#define OSPF_PING_NBR_STR_MAX (BUFSIZ) void ospf_proactively_arp(struct ospf_neighbor *nbr) { - char ping_nbr[OSPF_PING_NBR_STR_MAX]; - int ret; - if (!nbr) return; - snprintf(ping_nbr, sizeof(ping_nbr), - "ping -c 1 -I %s %s > /dev/null 2>&1 &", nbr->oi->ifp->name, - inet_ntoa(nbr->address.u.prefix4)); - - ret = system(ping_nbr); - if (IS_DEBUG_OSPF_EVENT) - zlog_debug("Executed %s %s", ping_nbr, - ((ret == 0) ? "successfully" : "but failed")); + ospf_zebra_send_arp(nbr->oi->ifp, &nbr->address); } diff --git a/ospfd/ospf_sr.c b/ospfd/ospf_sr.c index 8110bc2d02..f2330d6bdd 100644 --- a/ospfd/ospf_sr.c +++ b/ospfd/ospf_sr.c @@ -1048,32 +1048,25 @@ void ospf_sr_ri_lsa_update(struct ospf_lsa *lsa) } /* update LSA ID */ srn->instance = ntohl(lsah->id.s_addr); - /* Copy SRGB */ - srn->srgb.range_size = srgb.range_size; - srn->srgb.lower_bound = srgb.lower_bound; - /* Set Algorithm */ - if (algo != NULL) { - int i; - for (i = 0; i < ntohs(algo->header.length); i++) - srn->algo[i] = algo->value[0]; - for (; i < ALGORITHM_COUNT; i++) - srn->algo[i] = SR_ALGORITHM_UNSET; - } else { - srn->algo[0] = SR_ALGORITHM_SPF; - } - /* set MSD */ - srn->msd = msd; - return; } - /* Check if SRGB has changed */ - if ((srn->srgb.range_size == srgb.range_size) - && (srn->srgb.lower_bound == srgb.lower_bound)) - return; + /* Set Algorithm */ + if (algo != NULL) { + int i; + for (i = 0; i < ntohs(algo->header.length); i++) + srn->algo[i] = algo->value[0]; + for (; i < ALGORITHM_COUNT; i++) + srn->algo[i] = SR_ALGORITHM_UNSET; + } else { + srn->algo[0] = SR_ALGORITHM_SPF; + } - /* Update SRGB ... */ + srn->msd = msd; + + /* Copy SRGB */ srn->srgb.range_size = srgb.range_size; srn->srgb.lower_bound = srgb.lower_bound; + /* ... and NHLFE if it is a neighbor SR node */ if (srn->neighbor == OspfSR.self) hash_iterate(OspfSR.neighbors, update_out_nhlfe, srn); diff --git a/ospfd/ospf_zebra.c b/ospfd/ospf_zebra.c index 84bdb9ec5b..2b8769a4a8 100644 --- a/ospfd/ospf_zebra.c +++ b/ospfd/ospf_zebra.c @@ -1741,3 +1741,8 @@ void ospf_zebra_init(struct thread_master *master, unsigned short instance) prefix_list_add_hook(ospf_prefix_list_update); prefix_list_delete_hook(ospf_prefix_list_update); } + +void ospf_zebra_send_arp(const struct interface *ifp, const struct prefix *p) +{ + zclient_send_neigh_discovery_req(zclient, ifp, p); +} diff --git a/ospfd/ospf_zebra.h b/ospfd/ospf_zebra.h index 80abf62369..6a79f39fa4 100644 --- a/ospfd/ospf_zebra.h +++ b/ospfd/ospf_zebra.h @@ -41,6 +41,7 @@ struct ospf_distance { }; /* Prototypes */ +struct ospf_route; extern void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *, struct ospf_route *); extern void ospf_zebra_delete(struct ospf *ospf, struct prefix_ipv4 *, @@ -98,4 +99,7 @@ bool ospf_external_default_routemap_apply_walk( int ospf_external_info_apply_default_routemap(struct ospf *ospf, struct external_info *ei, struct external_info *default_ei); + +extern void ospf_zebra_send_arp(const struct interface *ifp, + const struct prefix *p); #endif /* _ZEBRA_OSPF_ZEBRA_H */ diff --git a/sharpd/sharp_vty.c b/sharpd/sharp_vty.c index 6a120c8eff..d390ea8192 100644 --- a/sharpd/sharp_vty.c +++ b/sharpd/sharp_vty.c @@ -131,8 +131,8 @@ DEFPY(sharp_nht_data_dump, sharp_nht_data_dump_cmd, "sharp data nexthop", "Sharp routing Protocol\n" - "Nexthop information\n" - "Data Dump\n") + "Data about what is going on\n" + "Nexthop information\n") { sharp_nh_tracker_dump(vty); @@ -649,6 +649,51 @@ DEFPY (send_opaque_reg, return CMD_SUCCESS; } +DEFPY (neigh_discover, + neigh_discover_cmd, + "sharp neigh discover [vrf NAME$vrf_name] <A.B.C.D$dst4|X:X::X:X$dst6> IFNAME$ifname", + SHARP_STR + "Discover neighbours\n" + "Send an ARP/NDP request\n" + VRF_CMD_HELP_STR + "v4 Destination address\n" + "v6 Destination address\n" + "Interface name\n") +{ + struct vrf *vrf; + struct interface *ifp; + struct prefix prefix; + + memset(&prefix, 0, sizeof(prefix)); + + if (dst4.s_addr != 0) { + prefix.family = AF_INET; + prefix.prefixlen = 32; + prefix.u.prefix4 = dst4; + } else { + prefix.family = AF_INET6; + prefix.prefixlen = 128; + prefix.u.prefix6 = dst6; + } + + vrf = vrf_lookup_by_name(vrf_name ? vrf_name : VRF_DEFAULT_NAME); + if (!vrf) { + vty_out(vty, "The vrf NAME specified: %s does not exist\n", + vrf_name ? vrf_name : VRF_DEFAULT_NAME); + return CMD_WARNING; + } + + ifp = if_lookup_by_name_vrf(ifname, vrf); + if (ifp == NULL) { + vty_out(vty, "%% Can't find interface %s\n", ifname); + return CMD_WARNING; + } + + sharp_zebra_send_arp(ifp, &prefix); + + return CMD_SUCCESS; +} + void sharp_vty_init(void) { install_element(ENABLE_NODE, &install_routes_data_dump_cmd); @@ -666,6 +711,7 @@ void sharp_vty_init(void) install_element(ENABLE_NODE, &send_opaque_cmd); install_element(ENABLE_NODE, &send_opaque_unicast_cmd); install_element(ENABLE_NODE, &send_opaque_reg_cmd); + install_element(ENABLE_NODE, &neigh_discover_cmd); install_element(VIEW_NODE, &show_debugging_sharpd_cmd); diff --git a/sharpd/sharp_zebra.c b/sharpd/sharp_zebra.c index e0f16d71f5..08f5a07b7e 100644 --- a/sharpd/sharp_zebra.c +++ b/sharpd/sharp_zebra.c @@ -663,6 +663,11 @@ void sharp_opaque_reg_send(bool is_reg, uint32_t proto, uint32_t instance, } +void sharp_zebra_send_arp(const struct interface *ifp, const struct prefix *p) +{ + zclient_send_neigh_discovery_req(zclient, ifp, p); +} + void sharp_zebra_init(void) { struct zclient_options opt = {.receive_notify = true}; diff --git a/sharpd/sharp_zebra.h b/sharpd/sharp_zebra.h index e40585aa6a..0a44fa694f 100644 --- a/sharpd/sharp_zebra.h +++ b/sharpd/sharp_zebra.h @@ -58,4 +58,7 @@ void sharp_opaque_send(uint32_t type, uint32_t proto, uint32_t instance, void sharp_opaque_reg_send(bool is_reg, uint32_t proto, uint32_t instance, uint32_t session_id, uint32_t type); +extern void sharp_zebra_send_arp(const struct interface *ifp, + const struct prefix *p); + #endif diff --git a/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py b/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py index 600d640a70..a1662dc411 100755 --- a/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py +++ b/tests/topotests/ldp-vpls-topo1/test_ldp_vpls_topo1.py @@ -154,7 +154,7 @@ def teardown_module(mod): tgen.stop_topology() -def router_compare_json_output(rname, command, reference): +def router_compare_json_output(rname, command, reference, count=80, wait=1): "Compare router JSON output" logger.info('Comparing router "%s" "%s" output', rname, command) @@ -163,9 +163,9 @@ def router_compare_json_output(rname, command, reference): filename = "{}/{}/{}".format(CWD, rname, reference) expected = json.loads(open(filename).read()) - # Run test function until we get an result. Wait at most 80 seconds. + # Run test function until we get an result. test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) - _, diff = topotest.run_and_expect(test_func, None, count=160, wait=0.5) + _, diff = topotest.run_and_expect(test_func, None, count, wait) assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) assert diff is None, assertmsg @@ -279,10 +279,12 @@ def test_ldp_pseudowires_after_link_down(): tgen.gears["r1"].peer_link_enable("r1-eth1", False) topotest.sleep(5, "Waiting for the network to reconverge") - # check if the pseudowire is still up (using an alternate path for nexthop resolution) + # check if the pseudowire is still up (using an alternate path + # for nexthop resolution). Give some extra wait time. for rname in ["r1", "r2", "r3"]: router_compare_json_output( - rname, "show l2vpn atom vc json", "show_l2vpn_vc.ref" + rname, "show l2vpn atom vc json", "show_l2vpn_vc.ref", + count=160, wait=1 ) diff --git a/tests/topotests/zebra_netlink/__init__.py b/tests/topotests/zebra_netlink/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/zebra_netlink/__init__.py diff --git a/tests/topotests/zebra_netlink/r1/sharpd.conf b/tests/topotests/zebra_netlink/r1/sharpd.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/zebra_netlink/r1/sharpd.conf diff --git a/tests/topotests/zebra_netlink/r1/v4_route.json b/tests/topotests/zebra_netlink/r1/v4_route.json new file mode 100644 index 0000000000..61e9bb240b --- /dev/null +++ b/tests/topotests/zebra_netlink/r1/v4_route.json @@ -0,0 +1,2802 @@ +{ + "2.1.3.7\/32":[ + { + "prefix":"2.1.3.7\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.8\/32":[ + { + "prefix":"2.1.3.8\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.9\/32":[ + { + "prefix":"2.1.3.9\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.10\/32":[ + { + "prefix":"2.1.3.10\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.11\/32":[ + { + "prefix":"2.1.3.11\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.12\/32":[ + { + "prefix":"2.1.3.12\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.13\/32":[ + { + "prefix":"2.1.3.13\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.14\/32":[ + { + "prefix":"2.1.3.14\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.15\/32":[ + { + "prefix":"2.1.3.15\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.16\/32":[ + { + "prefix":"2.1.3.16\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.17\/32":[ + { + "prefix":"2.1.3.17\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.18\/32":[ + { + "prefix":"2.1.3.18\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.19\/32":[ + { + "prefix":"2.1.3.19\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.20\/32":[ + { + "prefix":"2.1.3.20\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.21\/32":[ + { + "prefix":"2.1.3.21\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.22\/32":[ + { + "prefix":"2.1.3.22\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.23\/32":[ + { + "prefix":"2.1.3.23\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.24\/32":[ + { + "prefix":"2.1.3.24\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.25\/32":[ + { + "prefix":"2.1.3.25\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.26\/32":[ + { + "prefix":"2.1.3.26\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.27\/32":[ + { + "prefix":"2.1.3.27\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.28\/32":[ + { + "prefix":"2.1.3.28\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.29\/32":[ + { + "prefix":"2.1.3.29\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.30\/32":[ + { + "prefix":"2.1.3.30\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.31\/32":[ + { + "prefix":"2.1.3.31\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.32\/32":[ + { + "prefix":"2.1.3.32\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.33\/32":[ + { + "prefix":"2.1.3.33\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.34\/32":[ + { + "prefix":"2.1.3.34\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.35\/32":[ + { + "prefix":"2.1.3.35\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.36\/32":[ + { + "prefix":"2.1.3.36\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.37\/32":[ + { + "prefix":"2.1.3.37\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.38\/32":[ + { + "prefix":"2.1.3.38\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.39\/32":[ + { + "prefix":"2.1.3.39\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.40\/32":[ + { + "prefix":"2.1.3.40\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.41\/32":[ + { + "prefix":"2.1.3.41\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.42\/32":[ + { + "prefix":"2.1.3.42\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.43\/32":[ + { + "prefix":"2.1.3.43\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.44\/32":[ + { + "prefix":"2.1.3.44\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.45\/32":[ + { + "prefix":"2.1.3.45\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.46\/32":[ + { + "prefix":"2.1.3.46\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.47\/32":[ + { + "prefix":"2.1.3.47\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.48\/32":[ + { + "prefix":"2.1.3.48\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.49\/32":[ + { + "prefix":"2.1.3.49\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.50\/32":[ + { + "prefix":"2.1.3.50\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.51\/32":[ + { + "prefix":"2.1.3.51\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.52\/32":[ + { + "prefix":"2.1.3.52\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.53\/32":[ + { + "prefix":"2.1.3.53\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.54\/32":[ + { + "prefix":"2.1.3.54\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.55\/32":[ + { + "prefix":"2.1.3.55\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.56\/32":[ + { + "prefix":"2.1.3.56\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.57\/32":[ + { + "prefix":"2.1.3.57\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.58\/32":[ + { + "prefix":"2.1.3.58\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.59\/32":[ + { + "prefix":"2.1.3.59\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.60\/32":[ + { + "prefix":"2.1.3.60\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.61\/32":[ + { + "prefix":"2.1.3.61\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.62\/32":[ + { + "prefix":"2.1.3.62\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.63\/32":[ + { + "prefix":"2.1.3.63\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.64\/32":[ + { + "prefix":"2.1.3.64\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.65\/32":[ + { + "prefix":"2.1.3.65\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.66\/32":[ + { + "prefix":"2.1.3.66\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.67\/32":[ + { + "prefix":"2.1.3.67\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.68\/32":[ + { + "prefix":"2.1.3.68\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.69\/32":[ + { + "prefix":"2.1.3.69\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.70\/32":[ + { + "prefix":"2.1.3.70\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.71\/32":[ + { + "prefix":"2.1.3.71\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.72\/32":[ + { + "prefix":"2.1.3.72\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.73\/32":[ + { + "prefix":"2.1.3.73\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.74\/32":[ + { + "prefix":"2.1.3.74\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.75\/32":[ + { + "prefix":"2.1.3.75\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.76\/32":[ + { + "prefix":"2.1.3.76\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.77\/32":[ + { + "prefix":"2.1.3.77\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.78\/32":[ + { + "prefix":"2.1.3.78\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.79\/32":[ + { + "prefix":"2.1.3.79\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.80\/32":[ + { + "prefix":"2.1.3.80\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.81\/32":[ + { + "prefix":"2.1.3.81\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.82\/32":[ + { + "prefix":"2.1.3.82\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.83\/32":[ + { + "prefix":"2.1.3.83\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.84\/32":[ + { + "prefix":"2.1.3.84\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.85\/32":[ + { + "prefix":"2.1.3.85\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.86\/32":[ + { + "prefix":"2.1.3.86\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.87\/32":[ + { + "prefix":"2.1.3.87\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.88\/32":[ + { + "prefix":"2.1.3.88\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.89\/32":[ + { + "prefix":"2.1.3.89\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.90\/32":[ + { + "prefix":"2.1.3.90\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.91\/32":[ + { + "prefix":"2.1.3.91\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.92\/32":[ + { + "prefix":"2.1.3.92\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.93\/32":[ + { + "prefix":"2.1.3.93\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.94\/32":[ + { + "prefix":"2.1.3.94\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.95\/32":[ + { + "prefix":"2.1.3.95\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.96\/32":[ + { + "prefix":"2.1.3.96\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.97\/32":[ + { + "prefix":"2.1.3.97\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.98\/32":[ + { + "prefix":"2.1.3.98\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.99\/32":[ + { + "prefix":"2.1.3.99\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.100\/32":[ + { + "prefix":"2.1.3.100\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.101\/32":[ + { + "prefix":"2.1.3.101\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.102\/32":[ + { + "prefix":"2.1.3.102\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.103\/32":[ + { + "prefix":"2.1.3.103\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.104\/32":[ + { + "prefix":"2.1.3.104\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.105\/32":[ + { + "prefix":"2.1.3.105\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ], + "2.1.3.106\/32":[ + { + "prefix":"2.1.3.106\/32", + "protocol":"sharp", + "selected":true, + "destSelected":true, + "distance":150, + "metric":0, + "installed":true, + "table":254, + "internalStatus":16, + "internalFlags":9, + "internalNextHopNum":1, + "internalNextHopActiveNum":1, + "nexthops":[ + { + "flags":3, + "fib":true, + "ip":"192.168.1.1", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"r1-eth0", + "active":true, + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/zebra_netlink/r1/zebra.conf b/tests/topotests/zebra_netlink/r1/zebra.conf new file mode 100644 index 0000000000..786be19ad4 --- /dev/null +++ b/tests/topotests/zebra_netlink/r1/zebra.conf @@ -0,0 +1,2 @@ +int r1-eth0 + ip address 192.168.1.1/24
\ No newline at end of file diff --git a/tests/topotests/zebra_netlink/test_zebra_netlink.py b/tests/topotests/zebra_netlink/test_zebra_netlink.py new file mode 100755 index 0000000000..7b692c75ab --- /dev/null +++ b/tests/topotests/zebra_netlink/test_zebra_netlink.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +# +# test_zebra_netlink.py +# +# Copyright (c) 2020 by +# +# 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. +# + +""" +test_zebra_netlink.py: Test some basic interactions with kernel using Netlink + +""" + +import os +import re +import sys +import pytest +import json +import platform +from functools import partial + +# Save the Current Working Directory to find configuration files. +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 shutdown_bringup_interface + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +##################################################### +## +## Network Topology Definition +## +##################################################### + + +class ZebraTopo(Topo): + "Test topology builder" + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + tgen.add_router("r1") + + # Create a empty network for router 1 + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + + +##################################################### +## +## Tests starting +## +##################################################### + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(ZebraTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.iteritems(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + + router.load_config( + TopoRouter.RD_SHARP, os.path.join(CWD, "{}/sharpd.conf".format(rname)) + ) + + # Initialize all routers. + tgen.start_router() + + +def teardown_module(_mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def test_zebra_netlink_batching(): + "Test the situation where dataplane fills netlink send buffer entirely." + logger.info( + "Test the situation where dataplane fills netlink send buffer entirely." + ) + tgen = get_topogen() + if tgen.routers_have_failure(): + ptyest.skip("skipped because of preview test failure") + r1 = tgen.gears["r1"] + + # Reduce the size of the buffer to hit the limit. + r1.vtysh_cmd("conf t\nzebra kernel netlink batch-tx-buf 256 256") + + r1.vtysh_cmd("sharp install routes 2.1.3.7 nexthop 192.168.1.1 100") + json_file = "{}/r1/v4_route.json".format(CWD) + expected = json.loads(open(json_file).read()) + test_func = partial(topotest.router_json_cmp, r1, "show ip route json", expected,) + _, result = topotest.run_and_expect(test_func, None, count=2, wait=0.5) + assertmsg = '"r1" JSON output mismatches' + assert result is None, assertmsg + + r1.vtysh_cmd("sharp remove routes 2.1.3.7 100") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tools/etc/frr/support_bundle_commands.conf b/tools/etc/frr/support_bundle_commands.conf index 8845df5fc7..11f88e7101 100644 --- a/tools/etc/frr/support_bundle_commands.conf +++ b/tools/etc/frr/support_bundle_commands.conf @@ -38,8 +38,6 @@ PROC_NAME:zebra CMD_LIST_START show zebra show zebra client summary -show ip zebra route dump json -show ipv6 zebra route dump json show ip nht vrf all show route-map show memory diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c index 8dccbac3ae..eaab932228 100644 --- a/tools/gen_northbound_callbacks.c +++ b/tools/gen_northbound_callbacks.c @@ -194,7 +194,7 @@ static void generate_callback(const struct nb_callback_info *ncinfo, case NB_OP_MODIFY: case NB_OP_DESTROY: case NB_OP_MOVE: - printf("\tswitch (event) {\n" + printf("\tswitch (args->event) {\n" "\tcase NB_EV_VALIDATE:\n" "\tcase NB_EV_PREPARE:\n" "\tcase NB_EV_ABORT:\n" diff --git a/vrrpd/vrrp_northbound.c b/vrrpd/vrrp_northbound.c index e9cd714a95..f814963fe5 100644 --- a/vrrpd/vrrp_northbound.c +++ b/vrrpd/vrrp_northbound.c @@ -40,12 +40,31 @@ static int lib_interface_vrrp_vrrp_group_create(struct nb_cb_create_args *args) uint8_t version = 3; struct vrrp_vrouter *vr; - if (args->event != NB_EV_APPLY) + vrid = yang_dnode_get_uint8(args->dnode, "./virtual-router-id"); + version = yang_dnode_get_enum(args->dnode, "./version"); + + switch (args->event) { + case NB_EV_VALIDATE: + ifp = nb_running_get_entry(args->dnode, NULL, false); + if (ifp) { + vr = vrrp_lookup(ifp, vrid); + if (vr && vr->autoconf) { + snprintf( + args->errmsg, args->errmsg_len, + "Virtual Router with ID %d already exists on interface '%s'; created by VRRP autoconfiguration", + vrid, ifp->name); + return NB_ERR_VALIDATION; + } + } + return NB_OK; + case NB_EV_PREPARE: + case NB_EV_ABORT: return NB_OK; + case NB_EV_APPLY: + break; + } ifp = nb_running_get_entry(args->dnode, NULL, true); - vrid = yang_dnode_get_uint8(args->dnode, "./virtual-router-id"); - version = yang_dnode_get_enum(args->dnode, "./version"); vr = vrrp_vrouter_create(ifp, vrid, version); nb_running_set_entry(args->dnode, vr); diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c index 81f77d4f9b..5cd3e69299 100644 --- a/zebra/if_netlink.c +++ b/zebra/if_netlink.c @@ -987,7 +987,8 @@ int kernel_interface_set_master(struct interface *master, } /* Interface address modification. */ -static int netlink_address_ctx(const struct zebra_dplane_ctx *ctx) +static ssize_t netlink_address_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) { int bytelen; const struct prefix *p; @@ -997,64 +998,72 @@ static int netlink_address_ctx(const struct zebra_dplane_ctx *ctx) struct { struct nlmsghdr n; struct ifaddrmsg ifa; - char buf[NL_PKT_BUF_SIZE]; - } req; + char buf[0]; + } *req = buf; + + if (buflen < sizeof(*req)) + return 0; p = dplane_ctx_get_intf_addr(ctx); - memset(&req, 0, sizeof(req) - NL_PKT_BUF_SIZE); + memset(req, 0, sizeof(*req)); bytelen = (p->family == AF_INET ? 4 : 16); - req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); - req.n.nlmsg_flags = NLM_F_REQUEST; + req->n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); + req->n.nlmsg_flags = NLM_F_REQUEST; if (dplane_ctx_get_op(ctx) == DPLANE_OP_ADDR_INSTALL) cmd = RTM_NEWADDR; else cmd = RTM_DELADDR; - req.n.nlmsg_type = cmd; - req.ifa.ifa_family = p->family; + req->n.nlmsg_type = cmd; + req->ifa.ifa_family = p->family; - req.ifa.ifa_index = dplane_ctx_get_ifindex(ctx); + req->ifa.ifa_index = dplane_ctx_get_ifindex(ctx); - nl_attr_put(&req.n, sizeof(req), IFA_LOCAL, &p->u.prefix, bytelen); + if (!nl_attr_put(&req->n, buflen, IFA_LOCAL, &p->u.prefix, bytelen)) + return 0; if (p->family == AF_INET) { if (dplane_ctx_intf_is_connected(ctx)) { p = dplane_ctx_get_intf_dest(ctx); - nl_attr_put(&req.n, sizeof(req), IFA_ADDRESS, - &p->u.prefix, bytelen); + if (!nl_attr_put(&req->n, buflen, IFA_ADDRESS, + &p->u.prefix, bytelen)) + return 0; } else if (cmd == RTM_NEWADDR) { struct in_addr broad = { .s_addr = ipv4_broadcast_addr(p->u.prefix4.s_addr, p->prefixlen) }; - nl_attr_put(&req.n, sizeof(req), IFA_BROADCAST, &broad, - bytelen); + if (!nl_attr_put(&req->n, buflen, IFA_BROADCAST, &broad, + bytelen)) + return 0; } } /* p is now either address or destination/bcast addr */ - req.ifa.ifa_prefixlen = p->prefixlen; + req->ifa.ifa_prefixlen = p->prefixlen; if (dplane_ctx_intf_is_secondary(ctx)) - SET_FLAG(req.ifa.ifa_flags, IFA_F_SECONDARY); + SET_FLAG(req->ifa.ifa_flags, IFA_F_SECONDARY); if (dplane_ctx_intf_has_label(ctx)) { label = dplane_ctx_get_intf_label(ctx); - nl_attr_put(&req.n, sizeof(req), IFA_LABEL, label, - strlen(label) + 1); + if (!nl_attr_put(&req->n, buflen, IFA_LABEL, label, + strlen(label) + 1)) + return 0; } - return netlink_talk_info(netlink_talk_filter, &req.n, - dplane_ctx_get_ns(ctx), 0); + return NLMSG_ALIGN(req->n.nlmsg_len); } -enum zebra_dplane_result kernel_address_update_ctx(struct zebra_dplane_ctx *ctx) +enum netlink_msg_status +netlink_put_address_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) { - return (netlink_address_ctx(ctx) == 0 ? - ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); + return netlink_batch_add_msg(bth, ctx, netlink_address_msg_encoder, + false); } int netlink_interface_addr(struct nlmsghdr *h, ns_id_t ns_id, int startup) diff --git a/zebra/if_netlink.h b/zebra/if_netlink.h index 29fd2aca35..0bbba81ca6 100644 --- a/zebra/if_netlink.h +++ b/zebra/if_netlink.h @@ -32,6 +32,10 @@ extern int netlink_interface_addr(struct nlmsghdr *h, ns_id_t ns_id, extern int netlink_link_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); extern int interface_lookup_netlink(struct zebra_ns *zns); +extern enum netlink_msg_status +netlink_put_address_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx); + /* * Set protodown status of interface. * diff --git a/zebra/kernel_netlink.c b/zebra/kernel_netlink.c index a4d22c12a4..d0c1bc812d 100644 --- a/zebra/kernel_netlink.c +++ b/zebra/kernel_netlink.c @@ -84,6 +84,27 @@ #define RTPROT_MROUTED 17 #endif +#define NL_DEFAULT_BATCH_BUFSIZE (16 * NL_PKT_BUF_SIZE) + +/* + * We limit the batch's size to a number smaller than the length of the + * underlying buffer since the last message that wouldn't fit the batch would go + * over the upper boundary and then it would have to be encoded again into a new + * buffer. If the difference between the limit and the length of the buffer is + * big enough (bigger than the biggest Netlink message) then this situation + * won't occur. + */ +#define NL_DEFAULT_BATCH_SEND_THRESHOLD (15 * NL_PKT_BUF_SIZE) + +/* + * For every request sent to the kernel that has failed we get an error message, + * which contains a standard netlink message header and the payload consisting + * of an error code and the original netlink mesage. So the receiving buffer + * must be at least as big as the transmitting buffer increased by some space + * for headers. + */ +#define NL_BATCH_RX_BUFSIZE (NL_DEFAULT_BATCH_BUFSIZE + NL_PKT_BUF_SIZE) + static const struct message nlmsg_str[] = {{RTM_NEWROUTE, "RTM_NEWROUTE"}, {RTM_DELROUTE, "RTM_DELROUTE"}, {RTM_GETROUTE, "RTM_GETROUTE"}, @@ -151,6 +172,62 @@ extern uint32_t nl_rcvbufsize; extern struct zebra_privs_t zserv_privs; +DEFINE_MTYPE_STATIC(ZEBRA, NL_BUF, "Zebra Netlink buffers") + +size_t nl_batch_tx_bufsize; +char *nl_batch_tx_buf; + +char nl_batch_rx_buf[NL_BATCH_RX_BUFSIZE]; + +_Atomic uint32_t nl_batch_bufsize = NL_DEFAULT_BATCH_BUFSIZE; +_Atomic uint32_t nl_batch_send_threshold = NL_DEFAULT_BATCH_SEND_THRESHOLD; + +struct nl_batch { + void *buf; + size_t bufsiz; + size_t limit; + + void *buf_head; + size_t curlen; + size_t msgcnt; + + const struct zebra_dplane_info *zns; + + struct dplane_ctx_q ctx_list; + + /* + * Pointer to the queue of completed contexts outbound back + * towards the dataplane module. + */ + struct dplane_ctx_q *ctx_out_q; +}; + +int netlink_config_write_helper(struct vty *vty) +{ + uint32_t size = + atomic_load_explicit(&nl_batch_bufsize, memory_order_relaxed); + uint32_t threshold = atomic_load_explicit(&nl_batch_send_threshold, + memory_order_relaxed); + + if (size != NL_DEFAULT_BATCH_BUFSIZE + || threshold != NL_DEFAULT_BATCH_SEND_THRESHOLD) + vty_out(vty, "zebra kernel netlink batch-tx-buf %u %u\n", size, + threshold); + + return 0; +} + +void netlink_set_batch_buffer_size(uint32_t size, uint32_t threshold, bool set) +{ + if (!set) { + size = NL_DEFAULT_BATCH_BUFSIZE; + threshold = NL_DEFAULT_BATCH_SEND_THRESHOLD; + } + + atomic_store_explicit(&nl_batch_bufsize, size, memory_order_relaxed); + atomic_store_explicit(&nl_batch_send_threshold, threshold, + memory_order_relaxed); +} int netlink_talk_filter(struct nlmsghdr *h, ns_id_t ns_id, int startup) { @@ -1008,9 +1085,10 @@ int netlink_parse_info(int (*filter)(struct nlmsghdr *, ns_id_t, int), * startup -> Are we reading in under startup conditions * This is passed through eventually to filter. */ -int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), - struct nlmsghdr *n, - const struct zebra_dplane_info *dp_info, int startup) +static int +netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), + struct nlmsghdr *n, const struct zebra_dplane_info *dp_info, + int startup) { const struct nlsock *nl; @@ -1080,6 +1158,328 @@ int netlink_request(struct nlsock *nl, void *req) return 0; } +static int nl_batch_read_resp(struct nl_batch *bth) +{ + struct nlmsghdr *h; + struct sockaddr_nl snl; + struct msghdr msg; + int status, seq; + const struct nlsock *nl; + struct zebra_dplane_ctx *ctx; + bool ignore_msg; + + nl = &(bth->zns->nls); + + msg.msg_name = (void *)&snl; + msg.msg_namelen = sizeof(snl); + + status = netlink_recv_msg(nl, msg, nl_batch_rx_buf, + sizeof(nl_batch_rx_buf)); + if (status == -1 || status == 0) + return status; + + for (h = (struct nlmsghdr *)nl_batch_rx_buf; + (status >= 0 && NLMSG_OK(h, (unsigned int)status)); + h = NLMSG_NEXT(h, status)) { + ignore_msg = false; + seq = h->nlmsg_seq; + /* + * Find the corresponding context object. Received responses are + * in the same order as requests we sent, so we can simply + * iterate over the context list and match responses with + * requests at same time. + */ + while (true) { + ctx = dplane_ctx_dequeue(&(bth->ctx_list)); + if (ctx == NULL) + break; + + dplane_ctx_enqueue_tail(bth->ctx_out_q, ctx); + + /* We have found corresponding context object. */ + if (dplane_ctx_get_ns(ctx)->nls.seq == seq) + break; + + /* + * 'update' context objects take two consecutive + * sequence numbers. + */ + if (dplane_ctx_is_update(ctx) + && dplane_ctx_get_ns(ctx)->nls.seq + 1 == seq) { + /* + * This is the situation where we get a response + * to a message that should be ignored. + */ + ignore_msg = true; + break; + } + } + + if (ignore_msg) + continue; + + /* + * We received a message with the sequence number that isn't + * associated with any dplane context object. + */ + if (ctx == NULL) { + zlog_debug( + "%s: skipping unassociated response, seq number %d NS %u", + __func__, h->nlmsg_seq, bth->zns->ns_id); + continue; + } + + if (h->nlmsg_type == NLMSG_ERROR) { + int err = netlink_parse_error(nl, h, bth->zns, 0); + + if (err == -1) + dplane_ctx_set_status( + ctx, ZEBRA_DPLANE_REQUEST_FAILURE); + + zlog_debug("%s: netlink error message seq=%d ", + __func__, h->nlmsg_seq); + continue; + } + + /* + * If we get here then we did not receive neither the ack nor + * the error and instead received some other message in an + * unexpected way. + */ + zlog_debug("%s: ignoring message type 0x%04x(%s) NS %u", + __func__, h->nlmsg_type, + nl_msg_type_to_str(h->nlmsg_type), bth->zns->ns_id); + } + + return 0; +} + +static void nl_batch_reset(struct nl_batch *bth) +{ + bth->buf_head = bth->buf; + bth->curlen = 0; + bth->msgcnt = 0; + bth->zns = NULL; + + TAILQ_INIT(&(bth->ctx_list)); +} + +static void nl_batch_init(struct nl_batch *bth, struct dplane_ctx_q *ctx_out_q) +{ + /* + * If the size of the buffer has changed, free and then allocate a new + * one. + */ + size_t bufsize = + atomic_load_explicit(&nl_batch_bufsize, memory_order_relaxed); + if (bufsize != nl_batch_tx_bufsize) { + if (nl_batch_tx_buf) + XFREE(MTYPE_NL_BUF, nl_batch_tx_buf); + + nl_batch_tx_buf = XCALLOC(MTYPE_NL_BUF, bufsize); + nl_batch_tx_bufsize = bufsize; + } + + bth->buf = nl_batch_tx_buf; + bth->bufsiz = bufsize; + bth->limit = atomic_load_explicit(&nl_batch_send_threshold, + memory_order_relaxed); + + bth->ctx_out_q = ctx_out_q; + + nl_batch_reset(bth); +} + +static void nl_batch_send(struct nl_batch *bth) +{ + struct zebra_dplane_ctx *ctx; + bool err = false; + + if (bth->curlen != 0 && bth->zns != NULL) { + if (IS_ZEBRA_DEBUG_KERNEL) + zlog_debug("%s: %s, batch size=%zu, msg cnt=%zu", + __func__, bth->zns->nls.name, bth->curlen, + bth->msgcnt); + + if (netlink_send_msg(&(bth->zns->nls), bth->buf, bth->curlen) + == -1) + err = true; + + if (!err) { + if (nl_batch_read_resp(bth) == -1) + err = true; + } + } + + /* Move remaining contexts to the outbound queue. */ + while (true) { + ctx = dplane_ctx_dequeue(&(bth->ctx_list)); + if (ctx == NULL) + break; + + if (err) + dplane_ctx_set_status(ctx, + ZEBRA_DPLANE_REQUEST_FAILURE); + + dplane_ctx_enqueue_tail(bth->ctx_out_q, ctx); + } + + nl_batch_reset(bth); +} + +enum netlink_msg_status netlink_batch_add_msg( + struct nl_batch *bth, struct zebra_dplane_ctx *ctx, + ssize_t (*msg_encoder)(struct zebra_dplane_ctx *, void *, size_t), + bool ignore_res) +{ + int seq; + ssize_t size; + struct nlmsghdr *msgh; + + size = (*msg_encoder)(ctx, bth->buf_head, bth->bufsiz - bth->curlen); + + /* + * If there was an error while encoding the message (other than buffer + * overflow) then return an error. + */ + if (size < 0) + return FRR_NETLINK_ERROR; + + /* + * If the message doesn't fit entirely in the buffer then send the batch + * and retry. + */ + if (size == 0) { + nl_batch_send(bth); + size = (*msg_encoder)(ctx, bth->buf_head, + bth->bufsiz - bth->curlen); + /* + * If the message doesn't fit in the empty buffer then just + * return an error. + */ + if (size <= 0) + return FRR_NETLINK_ERROR; + } + + seq = dplane_ctx_get_ns(ctx)->nls.seq; + if (ignore_res) + seq++; + + msgh = (struct nlmsghdr *)bth->buf_head; + msgh->nlmsg_seq = seq; + msgh->nlmsg_pid = dplane_ctx_get_ns(ctx)->nls.snl.nl_pid; + + bth->zns = dplane_ctx_get_ns(ctx); + bth->buf_head = ((char *)bth->buf_head) + size; + bth->curlen += size; + bth->msgcnt++; + + return FRR_NETLINK_QUEUED; +} + +static enum netlink_msg_status nl_put_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) +{ + if (dplane_ctx_is_skip_kernel(ctx)) + return FRR_NETLINK_SUCCESS; + + switch (dplane_ctx_get_op(ctx)) { + + case DPLANE_OP_ROUTE_INSTALL: + case DPLANE_OP_ROUTE_UPDATE: + case DPLANE_OP_ROUTE_DELETE: + return netlink_put_route_update_msg(bth, ctx); + + case DPLANE_OP_NH_INSTALL: + case DPLANE_OP_NH_UPDATE: + case DPLANE_OP_NH_DELETE: + return netlink_put_nexthop_update_msg(bth, ctx); + + case DPLANE_OP_LSP_INSTALL: + case DPLANE_OP_LSP_UPDATE: + case DPLANE_OP_LSP_DELETE: + return netlink_put_lsp_update_msg(bth, ctx); + + case DPLANE_OP_PW_INSTALL: + case DPLANE_OP_PW_UNINSTALL: + return netlink_put_pw_update_msg(bth, ctx); + + case DPLANE_OP_ADDR_INSTALL: + case DPLANE_OP_ADDR_UNINSTALL: + return netlink_put_address_update_msg(bth, ctx); + + case DPLANE_OP_MAC_INSTALL: + case DPLANE_OP_MAC_DELETE: + return netlink_put_mac_update_msg(bth, ctx); + + case DPLANE_OP_NEIGH_INSTALL: + case DPLANE_OP_NEIGH_UPDATE: + case DPLANE_OP_NEIGH_DELETE: + case DPLANE_OP_VTEP_ADD: + case DPLANE_OP_VTEP_DELETE: + case DPLANE_OP_NEIGH_DISCOVER: + return netlink_put_neigh_update_msg(bth, ctx); + + case DPLANE_OP_RULE_ADD: + case DPLANE_OP_RULE_DELETE: + case DPLANE_OP_RULE_UPDATE: + return netlink_put_rule_update_msg(bth, ctx); + + case DPLANE_OP_SYS_ROUTE_ADD: + case DPLANE_OP_SYS_ROUTE_DELETE: + case DPLANE_OP_ROUTE_NOTIFY: + case DPLANE_OP_LSP_NOTIFY: + return FRR_NETLINK_SUCCESS; + + case DPLANE_OP_NONE: + return FRR_NETLINK_ERROR; + } + + return FRR_NETLINK_ERROR; +} + +void kernel_update_multi(struct dplane_ctx_q *ctx_list) +{ + struct nl_batch batch; + struct zebra_dplane_ctx *ctx; + struct dplane_ctx_q handled_list; + enum netlink_msg_status res; + + TAILQ_INIT(&handled_list); + nl_batch_init(&batch, &handled_list); + + while (true) { + ctx = dplane_ctx_dequeue(ctx_list); + if (ctx == NULL) + break; + + if (batch.zns != NULL + && batch.zns->ns_id != dplane_ctx_get_ns(ctx)->ns_id) + nl_batch_send(&batch); + + /* + * Assume all messages will succeed and then mark only the ones + * that failed. + */ + dplane_ctx_set_status(ctx, ZEBRA_DPLANE_REQUEST_SUCCESS); + + res = nl_put_msg(&batch, ctx); + + dplane_ctx_enqueue_tail(&(batch.ctx_list), ctx); + if (res == FRR_NETLINK_ERROR) + dplane_ctx_set_status(ctx, + ZEBRA_DPLANE_REQUEST_FAILURE); + + if (batch.curlen > batch.limit) + nl_batch_send(&batch); + } + + nl_batch_send(&batch); + + TAILQ_INIT(ctx_list); + dplane_ctx_list_append(ctx_list, &handled_list); +} + /* Exported interface function. This function simply calls netlink_socket (). */ void kernel_init(struct zebra_ns *zns) diff --git a/zebra/kernel_netlink.h b/zebra/kernel_netlink.h index bd8159faf3..c02e16480b 100644 --- a/zebra/kernel_netlink.h +++ b/zebra/kernel_netlink.h @@ -98,13 +98,49 @@ extern int netlink_talk_filter(struct nlmsghdr *h, ns_id_t ns, int startup); extern int netlink_talk(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), struct nlmsghdr *n, struct nlsock *nl, struct zebra_ns *zns, int startup); -/* Version with 'info' struct only */ -int netlink_talk_info(int (*filter)(struct nlmsghdr *, ns_id_t, int startup), - struct nlmsghdr *n, - const struct zebra_dplane_info *dp_info, int startup); - extern int netlink_request(struct nlsock *nl, void *req); +enum netlink_msg_status { + FRR_NETLINK_SUCCESS, + FRR_NETLINK_ERROR, + FRR_NETLINK_QUEUED, +}; + +struct nl_batch; + +/* + * netlink_batch_add_msg - add message to the netlink batch using dplane + * context object. + * + * @ctx: Dataplane context + * @msg_encoder: A function that encodes dplane context object into + * netlink message. Should take dplane context object, + * pointer to a buffer and buffer's length as parameters + * and should return -1 on error, 0 on buffer overflow or + * size of the encoded message. + * @ignore_res: Whether the result of this message should be ignored. + * This should be used in some 'update' cases where we + * need to send two messages for one context object. + * + * Return: Status of the message. + */ +extern enum netlink_msg_status netlink_batch_add_msg( + struct nl_batch *bth, struct zebra_dplane_ctx *ctx, + ssize_t (*msg_encoder)(struct zebra_dplane_ctx *, void *, size_t), + bool ignore_res); + +/* + * Vty/cli apis + */ +extern int netlink_config_write_helper(struct vty *vty); + +/* + * Configure size of the batch buffer and sending threshold. If 'unset', reset + * to default value. + */ +extern void netlink_set_batch_buffer_size(uint32_t size, uint32_t threshold, + bool set); + #endif /* HAVE_NETLINK */ #ifdef __cplusplus diff --git a/zebra/kernel_socket.c b/zebra/kernel_socket.c index 873d221149..4c29b999f0 100644 --- a/zebra/kernel_socket.c +++ b/zebra/kernel_socket.c @@ -1464,4 +1464,99 @@ void kernel_terminate(struct zebra_ns *zns, bool complete) return; } +void kernel_update_multi(struct dplane_ctx_q *ctx_list) +{ + struct zebra_dplane_ctx *ctx; + struct dplane_ctx_q handled_list; + enum zebra_dplane_result res; + + TAILQ_INIT(&handled_list); + + while (true) { + ctx = dplane_ctx_dequeue(ctx_list); + if (ctx == NULL) + break; + + /* + * A previous provider plugin may have asked to skip the + * kernel update. + */ + if (dplane_ctx_is_skip_kernel(ctx)) { + res = ZEBRA_DPLANE_REQUEST_SUCCESS; + goto skip_one; + } + + switch (dplane_ctx_get_op(ctx)) { + + case DPLANE_OP_ROUTE_INSTALL: + case DPLANE_OP_ROUTE_UPDATE: + case DPLANE_OP_ROUTE_DELETE: + res = kernel_route_update(ctx); + break; + + case DPLANE_OP_NH_INSTALL: + case DPLANE_OP_NH_UPDATE: + case DPLANE_OP_NH_DELETE: + res = kernel_nexthop_update(ctx); + break; + + case DPLANE_OP_LSP_INSTALL: + case DPLANE_OP_LSP_UPDATE: + case DPLANE_OP_LSP_DELETE: + res = kernel_lsp_update(ctx); + break; + + case DPLANE_OP_PW_INSTALL: + case DPLANE_OP_PW_UNINSTALL: + res = kernel_pw_update(ctx); + break; + + case DPLANE_OP_ADDR_INSTALL: + case DPLANE_OP_ADDR_UNINSTALL: + res = kernel_address_update_ctx(ctx); + break; + + case DPLANE_OP_MAC_INSTALL: + case DPLANE_OP_MAC_DELETE: + res = kernel_mac_update_ctx(ctx); + break; + + case DPLANE_OP_NEIGH_INSTALL: + case DPLANE_OP_NEIGH_UPDATE: + case DPLANE_OP_NEIGH_DELETE: + case DPLANE_OP_VTEP_ADD: + case DPLANE_OP_VTEP_DELETE: + case DPLANE_OP_NEIGH_DISCOVER: + res = kernel_neigh_update_ctx(ctx); + break; + + case DPLANE_OP_RULE_ADD: + case DPLANE_OP_RULE_DELETE: + case DPLANE_OP_RULE_UPDATE: + res = kernel_pbr_rule_update(ctx); + break; + + /* 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; + + default: + res = ZEBRA_DPLANE_REQUEST_FAILURE; + break; + } + + skip_one: + dplane_ctx_set_status(ctx, res); + + dplane_ctx_enqueue_tail(&handled_list, ctx); + } + + TAILQ_INIT(ctx_list); + dplane_ctx_list_append(ctx_list, &handled_list); +} + #endif /* !HAVE_NETLINK */ diff --git a/zebra/rt.h b/zebra/rt.h index 143e16b3ea..48f1df2868 100644 --- a/zebra/rt.h +++ b/zebra/rt.h @@ -40,7 +40,7 @@ extern "C" { #define RSYSTEM_ROUTE(type) \ ((RKERNEL_ROUTE(type)) || (type) == ZEBRA_ROUTE_CONNECT) - +#ifndef HAVE_NETLINK /* * Update or delete a route, nexthop, LSP, pseudowire, or vxlan MAC from the * kernel, using info from a dataplane context. @@ -63,6 +63,11 @@ enum zebra_dplane_result kernel_mac_update_ctx(struct zebra_dplane_ctx *ctx); enum zebra_dplane_result kernel_neigh_update_ctx(struct zebra_dplane_ctx *ctx); +extern enum zebra_dplane_result +kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx); + +#endif /* !HAVE_NETLINK */ + extern int kernel_neigh_update(int cmd, int ifindex, uint32_t addr, char *lla, int llalen, ns_id_t ns_id); extern int kernel_interface_set_master(struct interface *master, @@ -97,6 +102,11 @@ extern int kernel_upd_mac_nhg(uint32_t nhg_id, uint32_t nh_cnt, struct nh_grp *nh_ids); extern int kernel_del_mac_nhg(uint32_t nhg_id); +/* + * Message batching interface. + */ +extern void kernel_update_multi(struct dplane_ctx_q *ctx_list); + #ifdef __cplusplus } #endif diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c index ea15de6bdb..4a6839d3b1 100644 --- a/zebra/rt_netlink.c +++ b/zebra/rt_netlink.c @@ -147,6 +147,8 @@ static uint8_t neigh_flags_to_netlink(uint8_t dplane_flags) flags |= NTF_EXT_LEARNED; if (dplane_flags & DPLANE_NTF_ROUTER) flags |= NTF_ROUTER; + if (dplane_flags & DPLANE_NTF_USE) + flags |= NTF_USE; return flags; } @@ -166,6 +168,8 @@ static uint16_t neigh_state_to_netlink(uint16_t dplane_state) state |= NUD_NOARP; if (dplane_state & DPLANE_NUD_PROBE) state |= NUD_PROBE; + if (dplane_state & DPLANE_NUD_INCOMPLETE) + state |= NUD_INCOMPLETE; return state; } @@ -2230,19 +2234,11 @@ nexthop_done: return NLMSG_ALIGN(req->n.nlmsg_len); } -/** - * kernel_nexthop_update() - Update/delete a nexthop from the kernel - * - * @ctx: Dataplane context - * - * Return: Dataplane result flag - */ -enum zebra_dplane_result kernel_nexthop_update(struct zebra_dplane_ctx *ctx) +static ssize_t netlink_nexthop_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) { enum dplane_op_e op; int cmd = 0; - int ret = 0; - char buf[NL_PKT_BUF_SIZE]; op = dplane_ctx_get_op(ctx); if (op == DPLANE_OP_NH_INSTALL || op == DPLANE_OP_NH_UPDATE) @@ -2253,33 +2249,43 @@ enum zebra_dplane_result kernel_nexthop_update(struct zebra_dplane_ctx *ctx) flog_err(EC_ZEBRA_NHG_FIB_UPDATE, "Context received for kernel nexthop update with incorrect OP code (%u)", op); - return ZEBRA_DPLANE_REQUEST_FAILURE; + return -1; } + return netlink_nexthop_msg_encode(cmd, ctx, buf, buflen); +} + +enum netlink_msg_status +netlink_put_nexthop_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) +{ /* Nothing to do if the kernel doesn't support nexthop objects */ if (!kernel_nexthops_supported()) - return ZEBRA_DPLANE_REQUEST_SUCCESS; + return FRR_NETLINK_SUCCESS; - if (netlink_nexthop_msg_encode(cmd, ctx, buf, sizeof(buf)) > 0) - ret = netlink_talk_info(netlink_talk_filter, (void *)&buf, - dplane_ctx_get_ns(ctx), 0); - else - ret = 0; + return netlink_batch_add_msg(bth, ctx, netlink_nexthop_msg_encoder, + false); +} - return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS - : ZEBRA_DPLANE_REQUEST_FAILURE); +static ssize_t netlink_newroute_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) +{ + return netlink_route_multipath_msg_encode(RTM_NEWROUTE, ctx, buf, + buflen, false, false); } -/* - * Update or delete a prefix from the kernel, - * using info from a dataplane context. - */ -enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) +static ssize_t netlink_delroute_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) { - int cmd, ret; + return netlink_route_multipath_msg_encode(RTM_DELROUTE, ctx, buf, + buflen, false, false); +} + +enum netlink_msg_status +netlink_put_route_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx) +{ + int cmd; const struct prefix *p = dplane_ctx_get_dest(ctx); - struct nexthop *nexthop; - uint8_t nl_pkt[NL_PKT_BUF_SIZE]; if (dplane_ctx_get_op(ctx) == DPLANE_OP_ROUTE_DELETE) { cmd = RTM_DELROUTE; @@ -2289,7 +2295,6 @@ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) if (p->family == AF_INET || v6_rr_semantics) { /* Single 'replace' operation */ - cmd = RTM_NEWROUTE; /* * With route replace semantics in place @@ -2299,17 +2304,11 @@ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) * route should cause us to withdraw from * the kernel the old non-system route */ - if (RSYSTEM_ROUTE(dplane_ctx_get_type(ctx)) && - !RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) { - if (netlink_route_multipath_msg_encode( - RTM_DELROUTE, ctx, nl_pkt, - sizeof(nl_pkt), false, false) - > 0) - netlink_talk_info( - netlink_talk_filter, - (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); - } + if (RSYSTEM_ROUTE(dplane_ctx_get_type(ctx)) + && !RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) + netlink_batch_add_msg( + bth, ctx, netlink_delroute_msg_encoder, + true); } else { /* * So v6 route replace semantics are not in @@ -2323,51 +2322,24 @@ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) * of the route delete. If that happens yeah we're * screwed. */ - if (!RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) { - if (netlink_route_multipath_msg_encode( - RTM_DELROUTE, ctx, nl_pkt, - sizeof(nl_pkt), false, false) - > 0) - netlink_talk_info( - netlink_talk_filter, - (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); - } - cmd = RTM_NEWROUTE; + if (!RSYSTEM_ROUTE(dplane_ctx_get_old_type(ctx))) + netlink_batch_add_msg( + bth, ctx, netlink_delroute_msg_encoder, + true); } - } else { - return ZEBRA_DPLANE_REQUEST_FAILURE; - } - - if (!RSYSTEM_ROUTE(dplane_ctx_get_type(ctx))) { - if (netlink_route_multipath_msg_encode( - cmd, ctx, nl_pkt, sizeof(nl_pkt), false, false) - > 0) - ret = netlink_talk_info(netlink_talk_filter, - (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); - else - ret = -1; - + cmd = RTM_NEWROUTE; } else - ret = 0; - if ((cmd == RTM_NEWROUTE) && (ret == 0)) { - /* Update installed nexthops to signal which have been - * installed. - */ - for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) - continue; + return FRR_NETLINK_ERROR; - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); - } - } - } + if (RSYSTEM_ROUTE(dplane_ctx_get_type(ctx))) + return FRR_NETLINK_SUCCESS; - return (ret == 0 ? - ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); + return netlink_batch_add_msg(bth, ctx, + cmd == RTM_NEWROUTE + ? netlink_newroute_msg_encoder + : netlink_delroute_msg_encoder, + false); } /** @@ -2783,23 +2755,16 @@ static ssize_t netlink_neigh_update_msg_encode( * Add remote VTEP to the flood list for this VxLAN interface (VNI). This * is done by adding an FDB entry with a MAC of 00:00:00:00:00:00. */ -static int netlink_vxlan_flood_update_ctx(const struct zebra_dplane_ctx *ctx, - int cmd) +static ssize_t +netlink_vxlan_flood_update_ctx(const struct zebra_dplane_ctx *ctx, int cmd, + void *buf, size_t buflen) { struct ethaddr dst_mac = {.octet = {0}}; - uint8_t nl_pkt[NL_PKT_BUF_SIZE]; - - if (netlink_neigh_update_msg_encode( - ctx, cmd, &dst_mac, dplane_ctx_neigh_get_ipaddr(ctx), false, - PF_BRIDGE, 0, NTF_SELF, (NUD_NOARP | NUD_PERMANENT), - 0 /*nhg*/, false /*nfy*/, 0 /*nfy_flags*/, - nl_pkt, sizeof(nl_pkt)) - <= 0) - return -1; - return netlink_talk_info(netlink_talk_filter, - (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); + return netlink_neigh_update_msg_encode( + ctx, cmd, &dst_mac, dplane_ctx_neigh_get_ipaddr(ctx), false, + PF_BRIDGE, 0, NTF_SELF, (NUD_NOARP | NUD_PERMANENT), 0 /*nhg*/, + false /*nfy*/, 0 /*nfy_flags*/, buf, buflen); } #ifndef NDA_RTA @@ -3148,9 +3113,8 @@ int netlink_macfdb_read_specific_mac(struct zebra_ns *zns, /* * Netlink-specific handler for MAC updates using dataplane context object. */ -ssize_t -netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx, uint8_t *data, - size_t datalen) +ssize_t netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx, void *data, + size_t datalen) { struct ipaddr vtep_ip; vlanid_t vid; @@ -3626,15 +3590,14 @@ int netlink_neigh_change(struct nlmsghdr *h, ns_id_t ns_id) /* * Utility neighbor-update function, using info from dplane context. */ -static int netlink_neigh_update_ctx(const struct zebra_dplane_ctx *ctx, - int cmd) +static ssize_t netlink_neigh_update_ctx(const struct zebra_dplane_ctx *ctx, + int cmd, void *buf, size_t buflen) { const struct ipaddr *ip; const struct ethaddr *mac; uint8_t flags; uint16_t state; uint8_t family; - uint8_t nl_pkt[NL_PKT_BUF_SIZE]; ip = dplane_ctx_neigh_get_ipaddr(ctx); mac = dplane_ctx_neigh_get_mac(ctx); @@ -3659,60 +3622,56 @@ static int netlink_neigh_update_ctx(const struct zebra_dplane_ctx *ctx, flags, state); } - if (netlink_neigh_update_msg_encode(ctx, cmd, mac, ip, true, family, - RTN_UNICAST, flags, state, - 0 /*nhg*/, false /*nfy*/, 0 /*nfy_flags*/, - nl_pkt, sizeof(nl_pkt)) - <= 0) - return -1; - - return netlink_talk_info(netlink_talk_filter, (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); -} - -/* - * Update MAC, using dataplane context object. - */ -enum zebra_dplane_result kernel_mac_update_ctx(struct zebra_dplane_ctx *ctx) -{ - uint8_t nl_pkt[NL_PKT_BUF_SIZE]; - ssize_t rv; - - rv = netlink_macfdb_update_ctx(ctx, nl_pkt, sizeof(nl_pkt)); - if (rv <= 0) - return ZEBRA_DPLANE_REQUEST_FAILURE; - - rv = netlink_talk_info(netlink_talk_filter, (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); - - return rv == 0 ? - ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE; + return netlink_neigh_update_msg_encode( + ctx, cmd, mac, ip, true, family, RTN_UNICAST, flags, state, + 0 /*nhg*/, false /*nfy*/, 0 /*nfy_flags*/, buf, buflen); } -enum zebra_dplane_result kernel_neigh_update_ctx(struct zebra_dplane_ctx *ctx) +static ssize_t netlink_neigh_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) { - int ret = -1; + ssize_t ret; switch (dplane_ctx_get_op(ctx)) { case DPLANE_OP_NEIGH_INSTALL: case DPLANE_OP_NEIGH_UPDATE: - ret = netlink_neigh_update_ctx(ctx, RTM_NEWNEIGH); + case DPLANE_OP_NEIGH_DISCOVER: + ret = netlink_neigh_update_ctx(ctx, RTM_NEWNEIGH, buf, buflen); break; case DPLANE_OP_NEIGH_DELETE: - ret = netlink_neigh_update_ctx(ctx, RTM_DELNEIGH); + ret = netlink_neigh_update_ctx(ctx, RTM_DELNEIGH, buf, buflen); break; case DPLANE_OP_VTEP_ADD: - ret = netlink_vxlan_flood_update_ctx(ctx, RTM_NEWNEIGH); + ret = netlink_vxlan_flood_update_ctx(ctx, RTM_NEWNEIGH, buf, + buflen); break; case DPLANE_OP_VTEP_DELETE: - ret = netlink_vxlan_flood_update_ctx(ctx, RTM_DELNEIGH); + ret = netlink_vxlan_flood_update_ctx(ctx, RTM_DELNEIGH, buf, + buflen); break; default: - break; + ret = -1; } - return (ret == 0 ? - ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); + return ret; +} + +/* + * Update MAC, using dataplane context object. + */ + +enum netlink_msg_status netlink_put_mac_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) +{ + return netlink_batch_add_msg(bth, ctx, netlink_macfdb_update_ctx, + false); +} + +enum netlink_msg_status +netlink_put_neigh_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx) +{ + return netlink_batch_add_msg(bth, ctx, netlink_neigh_msg_encoder, + false); } /* diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h index 05c0d76427..e1bb844785 100644 --- a/zebra/rt_netlink.h +++ b/zebra/rt_netlink.h @@ -74,7 +74,7 @@ extern ssize_t netlink_route_multipath_msg_encode(int cmd, uint8_t *data, size_t datalen, bool fpm, bool force_nhg); extern ssize_t netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx, - uint8_t *data, size_t datalen); + void *data, size_t datalen); extern int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); extern int netlink_route_read(struct zebra_ns *zns); @@ -101,6 +101,23 @@ extern int netlink_neigh_read_specific_ip(struct ipaddr *ip, struct interface *vlan_if); extern vrf_id_t vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id); +struct nl_batch; +extern enum netlink_msg_status +netlink_put_route_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx); +extern enum netlink_msg_status +netlink_put_nexthop_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx); +extern enum netlink_msg_status +netlink_put_mac_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx); +extern enum netlink_msg_status +netlink_put_neigh_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx); +extern enum netlink_msg_status +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); + #ifdef __cplusplus } #endif diff --git a/zebra/rt_socket.c b/zebra/rt_socket.c index 0271dc7f41..a2e15cbd2b 100644 --- a/zebra/rt_socket.c +++ b/zebra/rt_socket.c @@ -358,20 +358,6 @@ enum zebra_dplane_result kernel_route_update(struct zebra_dplane_ctx *ctx) } } /* Elevated privs */ - if (RSYSTEM_ROUTE(type) - && dplane_ctx_get_op(ctx) != DPLANE_OP_ROUTE_DELETE) { - struct nexthop *nexthop; - - for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), nexthop)) { - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_RECURSIVE)) - continue; - - if (CHECK_FLAG(nexthop->flags, NEXTHOP_FLAG_ACTIVE)) { - SET_FLAG(nexthop->flags, NEXTHOP_FLAG_FIB); - } - } - } - return res; } diff --git a/zebra/rule_netlink.c b/zebra/rule_netlink.c index 648e9eabe1..3a3baab4ca 100644 --- a/zebra/rule_netlink.c +++ b/zebra/rule_netlink.c @@ -78,6 +78,8 @@ netlink_rule_msg_encode(int cmd, const struct zebra_dplane_ctx *ctx, char buf1[PREFIX_STRLEN]; char buf2[PREFIX_STRLEN]; + if (buflen < sizeof(*req)) + return 0; memset(req, 0, sizeof(*req)); family = PREFIX_FAMILY(src_ip); bytelen = (family == AF_INET ? 4 : 16); @@ -148,53 +150,55 @@ netlink_rule_msg_encode(int cmd, const struct zebra_dplane_ctx *ctx, return NLMSG_ALIGN(req->n.nlmsg_len); } -/* Install or uninstall specified rule for a specific interface. - * Form netlink message and ship it. - */ -static int netlink_rule_update_internal( - int cmd, const struct zebra_dplane_ctx *ctx, uint32_t filter_bm, - uint32_t priority, uint32_t table, const struct prefix *src_ip, - const struct prefix *dst_ip, uint32_t fwmark, uint8_t dsfield) +static ssize_t netlink_rule_msg_encoder(struct zebra_dplane_ctx *ctx, void *buf, + size_t buflen) { - char buf[NL_PKT_BUF_SIZE]; + int cmd = RTM_NEWRULE; + + if (dplane_ctx_get_op(ctx) == DPLANE_OP_RULE_DELETE) + cmd = RTM_DELRULE; - netlink_rule_msg_encode(cmd, ctx, filter_bm, priority, table, src_ip, - dst_ip, fwmark, dsfield, buf, sizeof(buf)); - return netlink_talk_info(netlink_talk_filter, (void *)&buf, - dplane_ctx_get_ns(ctx), 0); + return netlink_rule_msg_encode( + cmd, ctx, dplane_ctx_rule_get_filter_bm(ctx), + dplane_ctx_rule_get_priority(ctx), + dplane_ctx_rule_get_table(ctx), dplane_ctx_rule_get_src_ip(ctx), + dplane_ctx_rule_get_dst_ip(ctx), + dplane_ctx_rule_get_fwmark(ctx), + dplane_ctx_rule_get_dsfield(ctx), buf, buflen); +} + +static ssize_t netlink_oldrule_msg_encoder(struct zebra_dplane_ctx *ctx, + void *buf, size_t buflen) +{ + return netlink_rule_msg_encode( + RTM_DELRULE, ctx, dplane_ctx_rule_get_old_filter_bm(ctx), + dplane_ctx_rule_get_old_priority(ctx), + dplane_ctx_rule_get_old_table(ctx), + dplane_ctx_rule_get_old_src_ip(ctx), + dplane_ctx_rule_get_old_dst_ip(ctx), + dplane_ctx_rule_get_old_fwmark(ctx), + dplane_ctx_rule_get_old_dsfield(ctx), buf, buflen); } + /* Public functions */ -/* - * Add, update or delete a rule from the - * kernel, using info from a dataplane context. - */ -enum zebra_dplane_result kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx) +enum netlink_msg_status +netlink_put_rule_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx) { enum dplane_op_e op; - int cmd; - int ret; + enum netlink_msg_status ret; op = dplane_ctx_get_op(ctx); - if (op == DPLANE_OP_RULE_ADD || op == DPLANE_OP_RULE_UPDATE) - cmd = RTM_NEWRULE; - else if (op == DPLANE_OP_RULE_DELETE) - cmd = RTM_DELRULE; - else { + if (!(op == DPLANE_OP_RULE_ADD || op == DPLANE_OP_RULE_UPDATE + || op == DPLANE_OP_RULE_DELETE)) { flog_err( EC_ZEBRA_PBR_RULE_UPDATE, "Context received for kernel rule update with incorrect OP code (%u)", op); - return ZEBRA_DPLANE_REQUEST_FAILURE; + return FRR_NETLINK_ERROR; } - ret = netlink_rule_update_internal( - cmd, ctx, dplane_ctx_rule_get_filter_bm(ctx), - dplane_ctx_rule_get_priority(ctx), - dplane_ctx_rule_get_table(ctx), dplane_ctx_rule_get_src_ip(ctx), - dplane_ctx_rule_get_dst_ip(ctx), - dplane_ctx_rule_get_fwmark(ctx), - dplane_ctx_rule_get_dsfield(ctx)); + ret = netlink_batch_add_msg(bth, ctx, netlink_rule_msg_encoder, false); /** * Delete the old one. @@ -202,19 +206,10 @@ enum zebra_dplane_result kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx) * Don't care about this result right? */ if (op == DPLANE_OP_RULE_UPDATE) - netlink_rule_update_internal( - RTM_DELRULE, ctx, - dplane_ctx_rule_get_old_filter_bm(ctx), - dplane_ctx_rule_get_old_priority(ctx), - dplane_ctx_rule_get_old_table(ctx), - dplane_ctx_rule_get_old_src_ip(ctx), - dplane_ctx_rule_get_old_dst_ip(ctx), - dplane_ctx_rule_get_old_fwmark(ctx), - dplane_ctx_rule_get_old_dsfield(ctx)); - - - return (ret == 0 ? ZEBRA_DPLANE_REQUEST_SUCCESS - : ZEBRA_DPLANE_REQUEST_FAILURE); + netlink_batch_add_msg(bth, ctx, netlink_oldrule_msg_encoder, + true); + + return ret; } /* diff --git a/zebra/rule_netlink.h b/zebra/rule_netlink.h index 8c4741dc06..cf4d978e78 100644 --- a/zebra/rule_netlink.h +++ b/zebra/rule_netlink.h @@ -40,6 +40,9 @@ extern int netlink_rule_change(struct nlmsghdr *h, ns_id_t ns_id, int startup); */ extern int netlink_rules_read(struct zebra_ns *zns); +extern enum netlink_msg_status +netlink_put_rule_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx); + #ifdef __cplusplus } #endif diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 6dd197f010..fbed99dc59 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -1078,7 +1078,8 @@ static void zread_rnh_register(ZAPI_HANDLER_ARGS) s = msg; - client->nh_reg_time = monotime(NULL); + if (!client->nh_reg_time) + client->nh_reg_time = monotime(NULL); while (l < hdr->length) { STREAM_GETC(s, flags); @@ -2961,6 +2962,41 @@ stream_failure: return; } +static inline void zread_neigh_discover(ZAPI_HANDLER_ARGS) +{ + struct stream *s; + ifindex_t ifindex; + struct interface *ifp; + struct prefix p; + struct ipaddr ip; + + s = msg; + + STREAM_GETL(s, ifindex); + + ifp = if_lookup_by_index_per_ns(zvrf->zns, ifindex); + if (!ifp) { + zlog_debug("Failed to lookup ifindex: %u", ifindex); + return; + } + + STREAM_GETC(s, p.family); + STREAM_GETC(s, p.prefixlen); + STREAM_GET(&p.u.prefix, s, prefix_blen(&p)); + + if (p.family == AF_INET) + SET_IPADDR_V4(&ip); + else + SET_IPADDR_V6(&ip); + + memcpy(&ip.ip.addr, &p.u.prefix, prefix_blen(&p)); + + dplane_neigh_discover(ifp, &ip); + +stream_failure: + return; +} + static void zsend_error_msg(struct zserv *client, enum zebra_error_types error, struct zmsghdr *bad_hdr) { @@ -3064,8 +3100,8 @@ void (*const zserv_handlers[])(ZAPI_HANDLER_ARGS) = { [ZEBRA_MLAG_CLIENT_REGISTER] = zebra_mlag_client_register, [ZEBRA_MLAG_CLIENT_UNREGISTER] = zebra_mlag_client_unregister, [ZEBRA_MLAG_FORWARD_MSG] = zebra_mlag_forward_client_msg, - [ZEBRA_CLIENT_CAPABILITIES] = zread_client_capabilities -}; + [ZEBRA_CLIENT_CAPABILITIES] = zread_client_capabilities, + [ZEBRA_NEIGH_DISCOVER] = zread_neigh_discover}; #if defined(HANDLE_ZAPI_FUZZING) extern struct zebra_privs_t zserv_privs; diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index 5dcf76db15..8ab7a7f5bc 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -185,7 +185,7 @@ struct dplane_mac_info { }; /* - * EVPN neighbor info for the dataplane + * Neighbor info for the dataplane */ struct dplane_neigh_info { struct ipaddr ip_addr; @@ -607,6 +607,7 @@ static void dplane_ctx_free_internal(struct zebra_dplane_ctx *ctx) case DPLANE_OP_RULE_ADD: case DPLANE_OP_RULE_DELETE: case DPLANE_OP_RULE_UPDATE: + case DPLANE_OP_NEIGH_DISCOVER: case DPLANE_OP_NONE: break; } @@ -839,6 +840,10 @@ const char *dplane_op2str(enum dplane_op_e op) case DPLANE_OP_RULE_UPDATE: ret = "RULE_UPDATE"; break; + + case DPLANE_OP_NEIGH_DISCOVER: + ret = "NEIGH_DISCOVER"; + break; } return ret; @@ -858,8 +863,6 @@ const char *dplane_res2str(enum zebra_dplane_result res) case ZEBRA_DPLANE_REQUEST_SUCCESS: ret = "SUCCESS"; break; - case ZEBRA_DPLANE_REQUEST_PENDING: - ret = "PENDING"; } return ret; @@ -1979,6 +1982,7 @@ int dplane_ctx_nexthop_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, * it probably won't require two messages */ dplane_ctx_ns_init(ctx, zns, (op == DPLANE_OP_NH_UPDATE)); + ctx->zd_is_update = (op == DPLANE_OP_NH_UPDATE); ret = AOK; @@ -2001,6 +2005,7 @@ int dplane_ctx_lsp_init(struct zebra_dplane_ctx *ctx, enum dplane_op_e op, /* Capture namespace info */ dplane_ctx_ns_init(ctx, zebra_ns_lookup(NS_DEFAULT), (op == DPLANE_OP_LSP_UPDATE)); + ctx->zd_is_update = (op == DPLANE_OP_LSP_UPDATE); memset(&ctx->u.lsp, 0, sizeof(ctx->u.lsp)); @@ -2222,6 +2227,7 @@ static int dplane_ctx_rule_init(struct zebra_dplane_ctx *ctx, dplane_ctx_ns_init(ctx, zebra_ns_lookup(NS_DEFAULT), op == DPLANE_OP_RULE_UPDATE); + ctx->zd_is_update = (op == DPLANE_OP_RULE_UPDATE); ctx->zd_vrf_id = new_rule->vrf_id; memcpy(ctx->zd_ifname, new_rule->ifname, sizeof(new_rule->ifname)); @@ -3222,8 +3228,19 @@ enum zebra_dplane_result dplane_vtep_delete(const struct interface *ifp, return result; } +enum zebra_dplane_result dplane_neigh_discover(const struct interface *ifp, + const struct ipaddr *ip) +{ + enum zebra_dplane_result result; + + result = neigh_update_internal(DPLANE_OP_NEIGH_DISCOVER, ifp, NULL, ip, + DPLANE_NTF_USE, DPLANE_NUD_INCOMPLETE, 0); + + return result; +} + /* - * Common helper api for evpn neighbor updates + * Common helper api for neighbor updates */ static enum zebra_dplane_result neigh_update_internal(enum dplane_op_e op, @@ -3242,9 +3259,8 @@ neigh_update_internal(enum dplane_op_e op, char buf1[ETHER_ADDR_STRLEN], buf2[PREFIX_STRLEN]; zlog_debug("init neigh ctx %s: ifp %s, mac %s, ip %s", - dplane_op2str(op), + dplane_op2str(op), ifp->name, prefix_mac2str(mac, buf1, sizeof(buf1)), - ifp->name, ipaddr2str(ip, buf2, sizeof(buf2))); } @@ -3445,7 +3461,9 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed) out_max = atomic_load_explicit(&prov->dp_out_max, memory_order_relaxed); - vty_out(vty, "%s (%u): in: %"PRIu64", q_max: %"PRIu64", out: %"PRIu64", q_max: %"PRIu64"\n", + vty_out(vty, + "%s (%u): in: %" PRIu64 ", q_max: %" PRIu64 + ", out: %" PRIu64 ", q_max: %" PRIu64 "\n", prov->dp_name, prov->dp_id, in, in_max, out, out_max); DPLANE_LOCK(); @@ -3717,149 +3735,100 @@ void dplane_provider_enqueue_to_zebra(struct zebra_dplane_ctx *ctx) * Kernel dataplane provider */ -/* - * Handler for kernel LSP updates - */ -static enum zebra_dplane_result -kernel_dplane_lsp_update(struct zebra_dplane_ctx *ctx) +static void kernel_dplane_log_detail(struct zebra_dplane_ctx *ctx) { - return kernel_lsp_update(ctx); -} - -/* - * Handler for kernel pseudowire updates - */ -static enum zebra_dplane_result -kernel_dplane_pw_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) - zlog_debug("Dplane pw %s: op %s af %d loc: %u rem: %u", - dplane_ctx_get_ifname(ctx), - dplane_op2str(ctx->zd_op), - dplane_ctx_get_pw_af(ctx), - dplane_ctx_get_pw_local_label(ctx), - dplane_ctx_get_pw_remote_label(ctx)); + char buf[PREFIX_STRLEN]; - return kernel_pw_update(ctx); -} - -/* - * Handler for kernel route updates - */ -static enum zebra_dplane_result -kernel_dplane_route_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) { - char dest_str[PREFIX_STRLEN]; + switch (dplane_ctx_get_op(ctx)) { - prefix2str(dplane_ctx_get_dest(ctx), - dest_str, sizeof(dest_str)); + case DPLANE_OP_ROUTE_INSTALL: + case DPLANE_OP_ROUTE_UPDATE: + case DPLANE_OP_ROUTE_DELETE: + prefix2str(dplane_ctx_get_dest(ctx), buf, sizeof(buf)); zlog_debug("%u:%s Dplane route update ctx %p op %s", - dplane_ctx_get_vrf(ctx), dest_str, - ctx, dplane_op2str(dplane_ctx_get_op(ctx))); - } - - return kernel_route_update(ctx); -} - -/* - * Handler for kernel-facing interface address updates - */ -static enum zebra_dplane_result -kernel_dplane_address_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) { - char dest_str[PREFIX_STRLEN]; - - prefix2str(dplane_ctx_get_intf_addr(ctx), dest_str, - sizeof(dest_str)); - - zlog_debug("Dplane intf %s, idx %u, addr %s", - dplane_op2str(dplane_ctx_get_op(ctx)), - dplane_ctx_get_ifindex(ctx), dest_str); - } - - return kernel_address_update_ctx(ctx); -} + dplane_ctx_get_vrf(ctx), buf, ctx, + dplane_op2str(dplane_ctx_get_op(ctx))); + break; -/** - * kernel_dplane_nexthop_update() - Handler for kernel nexthop updates - * - * @ctx: Dataplane context - * - * Return: Dataplane result flag - */ -static enum zebra_dplane_result -kernel_dplane_nexthop_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) { + case DPLANE_OP_NH_INSTALL: + case DPLANE_OP_NH_UPDATE: + case DPLANE_OP_NH_DELETE: zlog_debug("ID (%u) Dplane nexthop update ctx %p op %s", dplane_ctx_get_nhe_id(ctx), ctx, dplane_op2str(dplane_ctx_get_op(ctx))); - } + break; - return kernel_nexthop_update(ctx); -} + case DPLANE_OP_LSP_INSTALL: + case DPLANE_OP_LSP_UPDATE: + case DPLANE_OP_LSP_DELETE: + break; -/* - * Handler for kernel-facing EVPN MAC address updates - */ -static enum zebra_dplane_result -kernel_dplane_mac_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) { - char buf[ETHER_ADDR_STRLEN]; + case DPLANE_OP_PW_INSTALL: + case DPLANE_OP_PW_UNINSTALL: + zlog_debug("Dplane pw %s: op %s af %d loc: %u rem: %u", + dplane_ctx_get_ifname(ctx), + dplane_op2str(ctx->zd_op), dplane_ctx_get_pw_af(ctx), + dplane_ctx_get_pw_local_label(ctx), + dplane_ctx_get_pw_remote_label(ctx)); + break; + case DPLANE_OP_ADDR_INSTALL: + case DPLANE_OP_ADDR_UNINSTALL: + prefix2str(dplane_ctx_get_intf_addr(ctx), buf, sizeof(buf)); + + zlog_debug("Dplane intf %s, idx %u, addr %s", + dplane_op2str(dplane_ctx_get_op(ctx)), + dplane_ctx_get_ifindex(ctx), buf); + break; + + case DPLANE_OP_MAC_INSTALL: + case DPLANE_OP_MAC_DELETE: prefix_mac2str(dplane_ctx_mac_get_addr(ctx), buf, sizeof(buf)); zlog_debug("Dplane %s, mac %s, ifindex %u", dplane_op2str(dplane_ctx_get_op(ctx)), buf, dplane_ctx_get_ifindex(ctx)); - } - - return kernel_mac_update_ctx(ctx); -} - -/* - * Handler for kernel-facing EVPN neighbor updates - */ -static enum zebra_dplane_result -kernel_dplane_neigh_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) { - char buf[PREFIX_STRLEN]; + break; + case DPLANE_OP_NEIGH_INSTALL: + case DPLANE_OP_NEIGH_UPDATE: + case DPLANE_OP_NEIGH_DELETE: + case DPLANE_OP_VTEP_ADD: + case DPLANE_OP_VTEP_DELETE: + case DPLANE_OP_NEIGH_DISCOVER: ipaddr2str(dplane_ctx_neigh_get_ipaddr(ctx), buf, sizeof(buf)); zlog_debug("Dplane %s, ip %s, ifindex %u", dplane_op2str(dplane_ctx_get_op(ctx)), buf, dplane_ctx_get_ifindex(ctx)); - } - - return kernel_neigh_update_ctx(ctx); -} + break; -/* - * Handler for kernel PBR rule updates - */ -static enum zebra_dplane_result -kernel_dplane_rule_update(struct zebra_dplane_ctx *ctx) -{ - if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) + case DPLANE_OP_RULE_ADD: + case DPLANE_OP_RULE_DELETE: + case DPLANE_OP_RULE_UPDATE: zlog_debug("Dplane rule update op %s, if %s(%u), ctx %p", dplane_op2str(dplane_ctx_get_op(ctx)), dplane_ctx_get_ifname(ctx), dplane_ctx_get_ifindex(ctx), ctx); + break; - return kernel_pbr_rule_update(ctx); + case DPLANE_OP_SYS_ROUTE_ADD: + case DPLANE_OP_SYS_ROUTE_DELETE: + case DPLANE_OP_ROUTE_NOTIFY: + case DPLANE_OP_LSP_NOTIFY: + + case DPLANE_OP_NONE: + break; + } } -static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx, - enum zebra_dplane_result res) +static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx) { + enum zebra_dplane_result res = dplane_ctx_get_status(ctx); + switch (dplane_ctx_get_op(ctx)) { case DPLANE_OP_ROUTE_INSTALL: @@ -3868,6 +3837,27 @@ static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx, if (res != ZEBRA_DPLANE_REQUEST_SUCCESS) atomic_fetch_add_explicit(&zdplane_info.dg_route_errors, 1, memory_order_relaxed); + + if ((dplane_ctx_get_op(ctx) != DPLANE_OP_ROUTE_DELETE) + && (res == ZEBRA_DPLANE_REQUEST_SUCCESS)) { + struct nexthop *nexthop; + + /* Update installed nexthops to signal which have been + * installed. + */ + for (ALL_NEXTHOPS_PTR(dplane_ctx_get_ng(ctx), + nexthop)) { + if (CHECK_FLAG(nexthop->flags, + NEXTHOP_FLAG_RECURSIVE)) + continue; + + if (CHECK_FLAG(nexthop->flags, + NEXTHOP_FLAG_ACTIVE)) { + SET_FLAG(nexthop->flags, + NEXTHOP_FLAG_FIB); + } + } + } break; case DPLANE_OP_NH_INSTALL: @@ -3914,6 +3904,7 @@ static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx, case DPLANE_OP_NEIGH_DELETE: case DPLANE_OP_VTEP_ADD: case DPLANE_OP_VTEP_DELETE: + case DPLANE_OP_NEIGH_DISCOVER: if (res != ZEBRA_DPLANE_REQUEST_SUCCESS) atomic_fetch_add_explicit(&zdplane_info.dg_neigh_errors, 1, memory_order_relaxed); @@ -3932,11 +3923,14 @@ static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx, case DPLANE_OP_SYS_ROUTE_DELETE: case DPLANE_OP_ROUTE_NOTIFY: case DPLANE_OP_LSP_NOTIFY: + break; + case DPLANE_OP_NONE: + if (res != ZEBRA_DPLANE_REQUEST_SUCCESS) + atomic_fetch_add_explicit(&zdplane_info.dg_other_errors, + 1, memory_order_relaxed); break; } - - dplane_ctx_set_status(ctx, res); } /* @@ -3944,7 +3938,6 @@ static void kernel_dplane_handle_result(struct zebra_dplane_ctx *ctx, */ static int kernel_dplane_process_func(struct zebra_dplane_provider *prov) { - enum zebra_dplane_result res; struct zebra_dplane_ctx *ctx, *tctx; struct dplane_ctx_q work_list; int counter, limit; @@ -3958,97 +3951,21 @@ static int kernel_dplane_process_func(struct zebra_dplane_provider *prov) dplane_provider_get_name(prov)); for (counter = 0; counter < limit; counter++) { - ctx = dplane_provider_dequeue_in_ctx(prov); if (ctx == NULL) break; - /* A previous provider plugin may have asked to skip the - * kernel update. - */ - if (dplane_ctx_is_skip_kernel(ctx)) { - res = ZEBRA_DPLANE_REQUEST_SUCCESS; - goto skip_one; - } - - /* Dispatch to appropriate kernel-facing apis */ - switch (dplane_ctx_get_op(ctx)) { - - case DPLANE_OP_ROUTE_INSTALL: - case DPLANE_OP_ROUTE_UPDATE: - case DPLANE_OP_ROUTE_DELETE: - res = kernel_dplane_route_update(ctx); - break; - - case DPLANE_OP_NH_INSTALL: - case DPLANE_OP_NH_UPDATE: - case DPLANE_OP_NH_DELETE: - res = kernel_dplane_nexthop_update(ctx); - break; - - case DPLANE_OP_LSP_INSTALL: - case DPLANE_OP_LSP_UPDATE: - case DPLANE_OP_LSP_DELETE: - res = kernel_dplane_lsp_update(ctx); - break; - - case DPLANE_OP_PW_INSTALL: - case DPLANE_OP_PW_UNINSTALL: - res = kernel_dplane_pw_update(ctx); - break; - - case DPLANE_OP_ADDR_INSTALL: - case DPLANE_OP_ADDR_UNINSTALL: - res = kernel_dplane_address_update(ctx); - break; - - case DPLANE_OP_MAC_INSTALL: - case DPLANE_OP_MAC_DELETE: - res = kernel_dplane_mac_update(ctx); - break; - - case DPLANE_OP_NEIGH_INSTALL: - case DPLANE_OP_NEIGH_UPDATE: - case DPLANE_OP_NEIGH_DELETE: - case DPLANE_OP_VTEP_ADD: - case DPLANE_OP_VTEP_DELETE: - res = kernel_dplane_neigh_update(ctx); - break; - - case DPLANE_OP_RULE_ADD: - case DPLANE_OP_RULE_DELETE: - case DPLANE_OP_RULE_UPDATE: - res = kernel_dplane_rule_update(ctx); - break; - - /* 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; - - default: - atomic_fetch_add_explicit( - &zdplane_info.dg_other_errors, 1, - memory_order_relaxed); - - res = ZEBRA_DPLANE_REQUEST_FAILURE; - break; - } - - skip_one: - /* If the request isn't pending, we can handle the result right - * away. - */ - if (res != ZEBRA_DPLANE_REQUEST_PENDING) - kernel_dplane_handle_result(ctx, res); + if (IS_ZEBRA_DEBUG_DPLANE_DETAIL) + kernel_dplane_log_detail(ctx); TAILQ_INSERT_TAIL(&work_list, ctx, zd_q_entries); } + kernel_update_multi(&work_list); + TAILQ_FOREACH_SAFE (ctx, &work_list, zd_q_entries, tctx) { + kernel_dplane_handle_result(ctx); + TAILQ_REMOVE(&work_list, ctx, zd_q_entries); dplane_provider_enqueue_out_ctx(prov, ctx); } @@ -4093,7 +4010,6 @@ static int test_dplane_process_func(struct zebra_dplane_provider *prov) limit = dplane_provider_get_work_limit(prov); for (counter = 0; counter < limit; counter++) { - ctx = dplane_provider_dequeue_in_ctx(prov); if (ctx == NULL) break; diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h index 32032ed77d..c68a617f38 100644 --- a/zebra/zebra_dplane.h +++ b/zebra/zebra_dplane.h @@ -90,7 +90,6 @@ enum zebra_dplane_result { ZEBRA_DPLANE_REQUEST_QUEUED, ZEBRA_DPLANE_REQUEST_SUCCESS, ZEBRA_DPLANE_REQUEST_FAILURE, - ZEBRA_DPLANE_REQUEST_PENDING, }; /* @@ -150,6 +149,9 @@ enum dplane_op_e { DPLANE_OP_RULE_ADD, DPLANE_OP_RULE_DELETE, DPLANE_OP_RULE_UPDATE, + + /* Link layer address discovery */ + DPLANE_OP_NEIGH_DISCOVER, }; /* @@ -161,12 +163,14 @@ enum dplane_op_e { /* Neighbor cache flags */ #define DPLANE_NTF_EXT_LEARNED 0x01 #define DPLANE_NTF_ROUTER 0x02 +#define DPLANE_NTF_USE 0x04 /* Neighbor cache states */ #define DPLANE_NUD_REACHABLE 0x01 #define DPLANE_NUD_STALE 0x02 #define DPLANE_NUD_NOARP 0x04 #define DPLANE_NUD_PROBE 0x08 +#define DPLANE_NUD_INCOMPLETE 0x10 /* MAC update flags - dplane_mac_info.update_flags */ #define DPLANE_MAC_REMOTE (1 << 0) @@ -573,6 +577,12 @@ enum zebra_dplane_result dplane_vtep_delete(const struct interface *ifp, const struct in_addr *ip, vni_t vni); +/* + * Enqueue a neighbour discovery request for the dataplane. + */ +enum zebra_dplane_result dplane_neigh_discover(const struct interface *ifp, + const struct ipaddr *ip); + /* Forward ref of zebra_pbr_rule */ struct zebra_pbr_rule; diff --git a/zebra/zebra_mpls.c b/zebra/zebra_mpls.c index a30cea6a1e..03b8c8de1f 100644 --- a/zebra/zebra_mpls.c +++ b/zebra/zebra_mpls.c @@ -956,9 +956,6 @@ static wq_item_status lsp_process(struct work_queue *wq, void *data) case ZEBRA_DPLANE_REQUEST_SUCCESS: zvrf->lsp_installs++; break; - /* Should never happen */ - case ZEBRA_DPLANE_REQUEST_PENDING: - break; } } } else { @@ -984,10 +981,6 @@ static wq_item_status lsp_process(struct work_queue *wq, void *data) case ZEBRA_DPLANE_REQUEST_SUCCESS: zvrf->lsp_removals++; break; - - /* Should never happen */ - case ZEBRA_DPLANE_REQUEST_PENDING: - break; } } else if (CHECK_FLAG(lsp->flags, LSP_FLAG_CHANGED)) { zebra_nhlfe_t *nhlfe; @@ -1031,10 +1024,6 @@ static wq_item_status lsp_process(struct work_queue *wq, void *data) case ZEBRA_DPLANE_REQUEST_SUCCESS: zvrf->lsp_installs++; break; - - /* Should never happen */ - case ZEBRA_DPLANE_REQUEST_PENDING: - break; } } } diff --git a/zebra/zebra_mpls_netlink.c b/zebra/zebra_mpls_netlink.c index c95a021639..3b2279c66c 100644 --- a/zebra/zebra_mpls_netlink.c +++ b/zebra/zebra_mpls_netlink.c @@ -28,13 +28,9 @@ #include "zebra/zebra_mpls.h" #include "zebra/kernel_netlink.h" -/* - * LSP forwarding update using dataplane context information. - */ -enum zebra_dplane_result kernel_lsp_update(struct zebra_dplane_ctx *ctx) +static ssize_t netlink_lsp_msg_encoder(struct zebra_dplane_ctx *ctx, void *buf, + size_t buflen) { - uint8_t nl_pkt[NL_PKT_BUF_SIZE]; - ssize_t ret = -1; int cmd; /* Call to netlink layer based on type of update */ @@ -48,26 +44,21 @@ enum zebra_dplane_result kernel_lsp_update(struct zebra_dplane_ctx *ctx) if (IS_ZEBRA_DEBUG_KERNEL || IS_ZEBRA_DEBUG_MPLS) zlog_debug("LSP in-label %u: update fails, no best NHLFE", dplane_ctx_get_in_label(ctx)); - goto done; + return -1; } cmd = RTM_NEWROUTE; } else /* Invalid op? */ - goto done; - - ret = netlink_mpls_multipath_msg_encode(cmd, ctx, nl_pkt, - sizeof(nl_pkt)); - if (ret <= 0) - return ZEBRA_DPLANE_REQUEST_FAILURE; - - ret = netlink_talk_info(netlink_talk_filter, (struct nlmsghdr *)nl_pkt, - dplane_ctx_get_ns(ctx), 0); + return -1; -done: + return netlink_mpls_multipath_msg_encode(cmd, ctx, buf, buflen); +} - return (ret == 0 ? - ZEBRA_DPLANE_REQUEST_SUCCESS : ZEBRA_DPLANE_REQUEST_FAILURE); +enum netlink_msg_status netlink_put_lsp_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) +{ + return netlink_batch_add_msg(bth, ctx, netlink_lsp_msg_encoder, false); } /* @@ -75,9 +66,10 @@ done: * but note that the default has been to report 'success' for pw updates * on unsupported platforms. */ -enum zebra_dplane_result kernel_pw_update(struct zebra_dplane_ctx *ctx) +enum netlink_msg_status netlink_put_pw_update_msg(struct nl_batch *bth, + struct zebra_dplane_ctx *ctx) { - return ZEBRA_DPLANE_REQUEST_SUCCESS; + return FRR_NETLINK_SUCCESS; } int mpls_kernel_init(void) diff --git a/zebra/zebra_nb_config.c b/zebra/zebra_nb_config.c index e4501273bd..b4ed910b4d 100644 --- a/zebra/zebra_nb_config.c +++ b/zebra/zebra_nb_config.c @@ -1204,12 +1204,14 @@ int lib_vrf_zebra_l3vni_id_modify(struct nb_cb_modify_args *args) switch (args->event) { case NB_EV_PREPARE: case NB_EV_ABORT: - break; + return NB_OK; case NB_EV_VALIDATE: zvrf_evpn = zebra_vrf_get_evpn(); - if (!zvrf_evpn) + if (!zvrf_evpn) { + snprintf(args->errmsg, args->errmsg_len, + "evpn vrf is not present."); return NB_ERR_VALIDATION; - + } vni = yang_dnode_get_uint32(args->dnode, NULL); /* Get vrf info from parent node, reject configuration * if zebra vrf already mapped to different vni id. @@ -1217,10 +1219,16 @@ int lib_vrf_zebra_l3vni_id_modify(struct nb_cb_modify_args *args) pn_dnode = yang_dnode_get_parent(args->dnode, "vrf"); if (pn_dnode) { vrfname = yang_dnode_get_string(pn_dnode, "./name"); - vrf = vrf_lookup_by_name(vrfname); - zvrf = zebra_vrf_lookup_by_id(vrf->vrf_id); + zvrf = zebra_vrf_lookup_by_name(vrfname); + if (!zvrf) { + snprintf(args->errmsg, args->errmsg_len, + "zebra vrf info not found for vrf:%s.", + vrfname); + return NB_ERR_VALIDATION; + } if (zvrf->l3vni && zvrf->l3vni != vni) { - zlog_debug( + snprintf( + args->errmsg, args->errmsg_len, "vni %u cannot be configured as vni %u is already configured under the vrf", vni, zvrf->l3vni); return NB_ERR_VALIDATION; @@ -1230,10 +1238,8 @@ int lib_vrf_zebra_l3vni_id_modify(struct nb_cb_modify_args *args) /* Check if this VNI is already present in the system */ zl3vni = zl3vni_lookup(vni); if (zl3vni) { - if (IS_ZEBRA_DEBUG_VXLAN) - zlog_debug( - "VNI %u is already configured as L3-VNI", - vni); + snprintf(args->errmsg, args->errmsg_len, + "VNI %u is already configured as L3-VNI", vni); return NB_ERR_VALIDATION; } @@ -1241,7 +1247,7 @@ int lib_vrf_zebra_l3vni_id_modify(struct nb_cb_modify_args *args) case NB_EV_APPLY: vrf = nb_running_get_entry(args->dnode, NULL, true); - zvrf = zebra_vrf_lookup_by_id(vrf->vrf_id); + zvrf = zebra_vrf_lookup_by_name(vrf->name); vni = yang_dnode_get_uint32(args->dnode, NULL); /* Note: This covers lib_vrf_zebra_prefix_only_modify() config * along with l3vni config @@ -1252,7 +1258,8 @@ int lib_vrf_zebra_l3vni_id_modify(struct nb_cb_modify_args *args) pfx_only ? 1 : 0, 1) != 0) { if (IS_ZEBRA_DEBUG_VXLAN) - zlog_debug( + snprintf( + args->errmsg, args->errmsg_len, "vrf vni %u mapping failed with error: %s", vni, err); return NB_ERR; @@ -1279,17 +1286,21 @@ int lib_vrf_zebra_l3vni_id_destroy(struct nb_cb_destroy_args *args) case NB_EV_PREPARE: case NB_EV_ABORT: case NB_EV_VALIDATE: - break; + return NB_OK; case NB_EV_APPLY: vrf = nb_running_get_entry(args->dnode, NULL, true); - zvrf = zebra_vrf_lookup_by_id(vrf->vrf_id); + zvrf = zebra_vrf_lookup_by_name(vrf->name); vni = yang_dnode_get_uint32(args->dnode, NULL); if (!zl3vni_lookup(vni)) return NB_OK; - if (zvrf->l3vni != vni) + if (zvrf->l3vni != vni) { + snprintf(args->errmsg, args->errmsg_len, + "vrf %s has different vni %u mapped", + vrf->name, zvrf->l3vni); return NB_ERR; + } if (is_l3vni_for_prefix_routes_only(zvrf->l3vni)) filter = 1; diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c index 1c5b843539..b8faaa43fd 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c @@ -2663,6 +2663,7 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx) case DPLANE_OP_RULE_ADD: case DPLANE_OP_RULE_DELETE: case DPLANE_OP_RULE_UPDATE: + case DPLANE_OP_NEIGH_DISCOVER: case DPLANE_OP_NONE: break; } diff --git a/zebra/zebra_pbr.h b/zebra/zebra_pbr.h index dd2d7a190d..888d2fcfa0 100644 --- a/zebra/zebra_pbr.h +++ b/zebra/zebra_pbr.h @@ -172,13 +172,6 @@ void zebra_pbr_add_iptable(struct zebra_pbr_iptable *iptable); void zebra_pbr_del_iptable(struct zebra_pbr_iptable *iptable); /* - * Add, update or delete a rule from the - * kernel, using info from a dataplane context. - */ -extern enum zebra_dplane_result -kernel_pbr_rule_update(struct zebra_dplane_ctx *ctx); - -/* * Get to know existing PBR rules in the kernel - typically called at startup. */ extern void kernel_read_pbr_rules(struct zebra_ns *zns); diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index afae449e7c..d1d56f2cdb 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -497,9 +497,6 @@ void rib_install_kernel(struct route_node *rn, struct route_entry *re, if (zvrf) zvrf->installs++; break; - /* Should never happen */ - case ZEBRA_DPLANE_REQUEST_PENDING: - break; } return; @@ -544,9 +541,6 @@ void rib_uninstall_kernel(struct route_node *rn, struct route_entry *re) if (zvrf) zvrf->removals++; break; - /* Should never happen */ - case ZEBRA_DPLANE_REQUEST_PENDING: - break; } return; @@ -3770,6 +3764,7 @@ static int rib_process_dplane_results(struct thread *thread) case DPLANE_OP_NEIGH_DELETE: case DPLANE_OP_VTEP_ADD: case DPLANE_OP_VTEP_DELETE: + case DPLANE_OP_NEIGH_DISCOVER: case DPLANE_OP_NONE: /* Don't expect this: just return the struct? */ dplane_ctx_fini(&ctx); diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index fbe6ce70a0..b6d0b26125 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -57,6 +57,7 @@ #include "zebra/interface.h" #include "northbound_cli.h" #include "zebra/zebra_nb.h" +#include "zebra/kernel_netlink.h" extern int allow_delete; @@ -3397,6 +3398,11 @@ static int config_write_protocol(struct vty *vty) if (!zebra_nhg_kernel_nexthops_enabled()) vty_out(vty, "no zebra nexthop kernel enable\n"); +#ifdef HAVE_NETLINK + /* Include netlink info */ + netlink_config_write_helper(vty); +#endif /* HAVE_NETLINK */ + return 1; } @@ -3719,6 +3725,44 @@ DEFUN_HIDDEN (show_frr, return CMD_SUCCESS; } +#ifdef HAVE_NETLINK +DEFUN_HIDDEN(zebra_kernel_netlink_batch_tx_buf, + zebra_kernel_netlink_batch_tx_buf_cmd, + "zebra kernel netlink batch-tx-buf (1-1048576) (1-1048576)", + ZEBRA_STR + "Zebra kernel interface\n" + "Set Netlink parameters\n" + "Set batch buffer size and send threshold\n" + "Size of the buffer\n" + "Send threshold\n") +{ + uint32_t bufsize = 0, threshold = 0; + + bufsize = strtoul(argv[4]->arg, NULL, 10); + threshold = strtoul(argv[5]->arg, NULL, 10); + + netlink_set_batch_buffer_size(bufsize, threshold, true); + + return CMD_SUCCESS; +} + +DEFUN_HIDDEN(no_zebra_kernel_netlink_batch_tx_buf, + no_zebra_kernel_netlink_batch_tx_buf_cmd, + "no zebra kernel netlink batch-tx-buf [(0-1048576)] [(0-1048576)]", + NO_STR ZEBRA_STR + "Zebra kernel interface\n" + "Set Netlink parameters\n" + "Set batch buffer size and send threshold\n" + "Size of the buffer\n" + "Send threshold\n") +{ + netlink_set_batch_buffer_size(0, 0, false); + + return CMD_SUCCESS; +} + +#endif /* HAVE_NETLINK */ + /* IP node for static routes. */ static int zebra_ip_config(struct vty *vty); static struct cmd_node ip_node = { @@ -3856,5 +3900,10 @@ void zebra_vty_init(void) install_element(CONFIG_NODE, &zebra_dplane_queue_limit_cmd); install_element(CONFIG_NODE, &no_zebra_dplane_queue_limit_cmd); +#ifdef HAVE_NETLINK + install_element(CONFIG_NODE, &zebra_kernel_netlink_batch_tx_buf_cmd); + install_element(CONFIG_NODE, &no_zebra_kernel_netlink_batch_tx_buf_cmd); +#endif /* HAVE_NETLINK */ + install_element(VIEW_NODE, &zebra_show_routing_tables_summary_cmd); } diff --git a/zebra/zebra_vxlan.c b/zebra/zebra_vxlan.c index ff09b48dcf..ee167fa5a3 100644 --- a/zebra/zebra_vxlan.c +++ b/zebra/zebra_vxlan.c @@ -9811,6 +9811,9 @@ int zebra_vxlan_local_mac_add_update(struct interface *ifp, bool inform_dataplane = false; bool new_static = false; + if (ifp == NULL) + return -1; + /* We are interested in MACs only on ports or (port, VLAN) that * map to a VNI. */ |
