diff options
157 files changed, 14404 insertions, 595 deletions
diff --git a/bgpd/bgp_advertise.c b/bgpd/bgp_advertise.c index 6f4916b3c3..17d6592c0f 100644 --- a/bgpd/bgp_advertise.c +++ b/bgpd/bgp_advertise.c @@ -83,10 +83,25 @@ void bgp_advertise_free(struct bgp_advertise *adv) void bgp_advertise_add(struct bgp_advertise_attr *baa, struct bgp_advertise *adv) { - adv->next = baa->adv; - if (baa->adv) - baa->adv->prev = adv; - baa->adv = adv; + struct bgp_advertise *spot, *prev = NULL; + + spot = baa->adv; + + while (spot) { + prev = spot; + spot = spot->next; + } + + if (prev) { + prev->next = adv; + adv->prev = prev; + } else + adv->prev = NULL; + + adv->next = NULL; + + if (!baa->adv) + baa->adv = adv; } void bgp_advertise_delete(struct bgp_advertise_attr *baa, diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c index 2e2248cd72..fc13085437 100644 --- a/bgpd/bgp_aspath.c +++ b/bgpd/bgp_aspath.c @@ -1703,7 +1703,7 @@ struct aspath *aspath_filter_exclude_acl(struct aspath *source, if (cur_seg == source->segments) /* first segment */ source->segments = cur_seg->next; - else + else if (prev_seg) prev_seg->next = cur_seg->next; assegment_free(cur_seg); } @@ -1725,8 +1725,9 @@ struct aspath *aspath_filter_exclude_acl(struct aspath *source, else if (prev_seg) prev_seg->next = new_seg; assegment_free(cur_seg); - } - prev_seg = cur_seg; + prev_seg = new_seg; + } else + prev_seg = cur_seg; cur_seg = next_seg; } diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 7916233444..cc7afbe74f 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -40,8 +40,12 @@ #endif #include "bgp_evpn.h" #include "bgp_flowspec_private.h" +#include "bgp_linkstate_tlv.h" #include "bgp_mac.h" +DEFINE_MTYPE_STATIC(BGPD, BGP_ATTR_LS, "BGP Attribute Link-State"); +DEFINE_MTYPE_STATIC(BGPD, BGP_ATTR_LS_DATA, "BGP Attribute Link-State Data"); + /* Attribute strings for logging. */ static const struct message attr_str[] = { {BGP_ATTR_ORIGIN, "ORIGIN"}, @@ -65,6 +69,7 @@ static const struct message attr_str[] = { #ifdef ENABLE_BGP_VNC_ATTR {BGP_ATTR_VNC, "VNC"}, #endif + {BGP_ATTR_LINK_STATE, "LINK_STATE"}, {BGP_ATTR_LARGE_COMMUNITIES, "LARGE_COMMUNITY"}, {BGP_ATTR_PREFIX_SID, "PREFIX_SID"}, {BGP_ATTR_IPV6_EXT_COMMUNITIES, "IPV6_EXT_COMMUNITIES"}, @@ -199,6 +204,8 @@ static struct hash *vnc_hash = NULL; static struct hash *srv6_l3vpn_hash; static struct hash *srv6_vpn_hash; +static struct hash *link_state_hash; + struct bgp_attr_encap_subtlv *encap_tlv_dup(struct bgp_attr_encap_subtlv *orig) { struct bgp_attr_encap_subtlv *new; @@ -716,6 +723,99 @@ static void srv6_finish(void) hash_clean_and_free(&srv6_vpn_hash, (void (*)(void *))srv6_vpn_free); } +static void *link_state_hash_alloc(void *p) +{ + return p; +} + +static void link_state_free(struct bgp_attr_ls *link_state) +{ + XFREE(MTYPE_BGP_ATTR_LS_DATA, link_state->data); + XFREE(MTYPE_BGP_ATTR_LS, link_state); +} + +static struct bgp_attr_ls *link_state_intern(struct bgp_attr_ls *link_state) +{ + struct bgp_attr_ls *find; + + find = hash_get(link_state_hash, link_state, link_state_hash_alloc); + if (find != link_state) + link_state_free(link_state); + find->refcnt++; + return find; +} + +static void link_state_unintern(struct bgp_attr_ls **link_statep) +{ + struct bgp_attr_ls *link_state = *link_statep; + + if (!*link_statep) + return; + + if (link_state->refcnt) + link_state->refcnt--; + + if (link_state->refcnt == 0) { + hash_release(link_state_hash, link_state); + link_state_free(link_state); + *link_statep = NULL; + } +} + +static uint32_t link_state_hash_key_make(const void *p) +{ + const struct bgp_attr_ls *link_state = p; + uint32_t key = 0; + + key = jhash_1word(link_state->length, key); + key = jhash(link_state->data, link_state->length, key); + + return key; +} + +static bool link_state_hash_cmp(const void *p1, const void *p2) +{ + const struct bgp_attr_ls *link_state1 = p1; + const struct bgp_attr_ls *link_state2 = p2; + + if (!link_state1 && link_state2) + return false; + if (!link_state1 && link_state2) + return false; + if (!link_state1 && !link_state2) + return true; + + if (link_state1->length != link_state2->length) + return false; + + return !memcmp(link_state1->data, link_state2->data, + link_state1->length); +} + +static bool link_state_same(const struct bgp_attr_ls *h1, + const struct bgp_attr_ls *h2) +{ + if (h1 == h2) + return true; + else if (h1 == NULL || h2 == NULL) + return false; + else + return link_state_hash_cmp((const void *)h1, (const void *)h2); +} + +static void link_state_init(void) +{ + link_state_hash = + hash_create(link_state_hash_key_make, link_state_hash_cmp, + "BGP Link-State Attributes TLVs"); +} + +static void link_state_finish(void) +{ + hash_clean_and_free(&link_state_hash, + (void (*)(void *))link_state_free); +} + static unsigned int transit_hash_key_make(const void *p) { const struct transit *transit = p; @@ -805,6 +905,8 @@ unsigned int attrhash_key_make(const void *p) MIX(attr->bh_type); MIX(attr->otc); MIX(bgp_attr_get_aigp_metric(attr)); + if (attr->link_state) + MIX(link_state_hash_key_make(attr->link_state)); return key; } @@ -870,7 +972,8 @@ bool attrhash_cmp(const void *p1, const void *p2) attr1->srte_color == attr2->srte_color && attr1->nh_type == attr2->nh_type && attr1->bh_type == attr2->bh_type && - attr1->otc == attr2->otc) + attr1->otc == attr2->otc && + link_state_same(attr1->link_state, attr2->link_state)) return true; } @@ -1030,6 +1133,12 @@ struct attr *bgp_attr_intern(struct attr *attr) else attr->srv6_vpn->refcnt++; } + if (attr->link_state) { + if (!attr->link_state->refcnt) + attr->link_state = link_state_intern(attr->link_state); + else + attr->link_state->refcnt++; + } #ifdef ENABLE_BGP_VNC struct bgp_attr_encap_subtlv *vnc_subtlvs = bgp_attr_get_vnc_subtlvs(attr); @@ -1248,6 +1357,8 @@ void bgp_attr_unintern_sub(struct attr *attr) srv6_l3vpn_unintern(&attr->srv6_l3vpn); srv6_vpn_unintern(&attr->srv6_vpn); + + link_state_unintern(&attr->link_state); } /* Free bgp attribute and aspath. */ @@ -1411,6 +1522,7 @@ bgp_attr_malformed(struct bgp_attr_parser_args *args, uint8_t subcode, case BGP_ATTR_ENCAP: case BGP_ATTR_OTC: return BGP_ATTR_PARSE_WITHDRAW; + case BGP_ATTR_LINK_STATE: case BGP_ATTR_MP_REACH_NLRI: case BGP_ATTR_MP_UNREACH_NLRI: bgp_notify_send_with_data(peer->connection, @@ -1497,6 +1609,7 @@ const uint8_t attr_flags_values[] = { [BGP_ATTR_IPV6_EXT_COMMUNITIES] = BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS, [BGP_ATTR_AIGP] = BGP_ATTR_FLAG_OPTIONAL, + [BGP_ATTR_LINK_STATE] = BGP_ATTR_FLAG_OPTIONAL, }; static const size_t attr_flags_values_max = array_size(attr_flags_values) - 1; @@ -3290,6 +3403,32 @@ aigp_ignore: return bgp_attr_ignore(peer, args->type); } +/* Link-State (rfc7752) */ +static enum bgp_attr_parse_ret +bgp_attr_linkstate(struct bgp_attr_parser_args *args) +{ + struct peer *const peer = args->peer; + struct attr *const attr = args->attr; + const bgp_size_t length = args->length; + struct stream *s = peer->curr; + struct bgp_attr_ls *bgp_attr_ls; + void *bgp_attr_ls_data; + + if (STREAM_READABLE(s) == 0) + return BGP_ATTR_PARSE_PROCEED; + + attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_LINK_STATE); + + bgp_attr_ls = XCALLOC(MTYPE_BGP_ATTR_LS, sizeof(struct bgp_attr_ls)); + bgp_attr_ls->length = length; + bgp_attr_ls_data = XCALLOC(MTYPE_BGP_ATTR_LS_DATA, length); + bgp_attr_ls->data = bgp_attr_ls_data; + stream_get(bgp_attr_ls_data, s, length); + attr->link_state = link_state_intern(bgp_attr_ls); + + return BGP_ATTR_PARSE_PROCEED; +} + /* OTC attribute. */ static enum bgp_attr_parse_ret bgp_attr_otc(struct bgp_attr_parser_args *args) { @@ -3747,6 +3886,9 @@ enum bgp_attr_parse_ret bgp_attr_parse(struct peer *peer, struct attr *attr, case BGP_ATTR_AIGP: ret = bgp_attr_aigp(&attr_args); break; + case BGP_ATTR_LINK_STATE: + ret = bgp_attr_linkstate(&attr_args); + break; default: ret = bgp_attr_unknown(&attr_args); break; @@ -4003,6 +4145,8 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, switch (nh_afi) { case AFI_IP: switch (safi) { + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: @@ -4036,6 +4180,8 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, break; case AFI_IP6: switch (safi) { + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: @@ -4086,8 +4232,9 @@ size_t bgp_packet_mpattr_start(struct stream *s, struct peer *peer, afi_t afi, break; } break; + case AFI_LINKSTATE: case AFI_L2VPN: - if (safi != SAFI_FLOWSPEC) + if (nh_afi == AFI_L2VPN && safi != SAFI_FLOWSPEC) flog_err( EC_BGP_ATTR_NH_SEND_LEN, "Bad nexthop when sending to %s, AFI %u SAFI %u nhlen %d", @@ -4138,6 +4285,12 @@ void bgp_packet_mpattr_prefix(struct stream *s, afi_t afi, safi_t safi, stream_put_labeled_prefix(s, p, label, addpath_capable, addpath_tx_id); break; + case SAFI_LINKSTATE: + bgp_nlri_encode_linkstate(s, p); + break; + case SAFI_LINKSTATE_VPN: + /* not yet supported */ + break; case SAFI_FLOWSPEC: stream_putc(s, p->u.prefix_flowspec.prefixlen); stream_put(s, (const void *)p->u.prefix_flowspec.ptr, @@ -4164,6 +4317,8 @@ size_t bgp_packet_mpattr_prefix_size(afi_t afi, safi_t safi, case SAFI_MAX: assert(!"Attempting to figure size for a SAFI_UNSPEC/SAFI_MAX this is a DEV ESCAPE"); break; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNICAST: case SAFI_MULTICAST: break; @@ -4826,6 +4981,14 @@ bgp_size_t bgp_packet_attribute(struct bgp *bgp, struct peer *peer, #endif } + /* BGP Link-State */ + if (attr && attr->link_state) { + stream_putc(s, BGP_ATTR_FLAG_OPTIONAL); + stream_putc(s, BGP_ATTR_LINK_STATE); + stream_putc(s, attr->link_state->length); + stream_put(s, attr->link_state->data, attr->link_state->length); + } + /* PMSI Tunnel */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { stream_putc(s, BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_TRANS); @@ -4930,6 +5093,7 @@ void bgp_attr_init(void) transit_init(); encap_init(); srv6_init(); + link_state_init(); } void bgp_attr_finish(void) @@ -4943,6 +5107,7 @@ void bgp_attr_finish(void) transit_finish(); encap_finish(); srv6_finish(); + link_state_finish(); } /* Make attribute packet. */ diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index 961e5f1224..e637b0efbf 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -136,6 +136,13 @@ struct bgp_attr_srv6_l3vpn { uint8_t transposition_offset; }; +struct bgp_attr_ls { + unsigned long refcnt; + + uint8_t length; + void *data; +}; + /* BGP core attribute structure. */ struct attr { /* AS Path structure */ @@ -159,6 +166,8 @@ struct attr { /* Path origin attribute */ uint8_t origin; + struct bgp_attr_ls *link_state; /* BGP Link State attribute */ + /* PMSI tunnel type (RFC 6514). */ enum pta_type pmsi_tnl_type; diff --git a/bgpd/bgp_btoa.c b/bgpd/bgp_btoa.c index fc3363b098..c4e866c42e 100644 --- a/bgpd/bgp_btoa.c +++ b/bgpd/bgp_btoa.c @@ -120,6 +120,7 @@ int main(int argc, char **argv) struct in_addr dip; uint16_t viewno, seq_num; struct prefix_ipv4 p; + char tbuf[32]; s = stream_new(10000); @@ -155,7 +156,7 @@ int main(int argc, char **argv) subtype = stream_getw(s); len = stream_getl(s); - printf("TIME: %s", ctime(&now)); + printf("TIME: %s", ctime_r(&now, tbuf)); /* printf ("TYPE: %d/%d\n", type, subtype); */ @@ -176,6 +177,9 @@ int main(int argc, char **argv) case AFI_IP6: printf("/AFI_IP6\n"); break; + case AFI_LINKSTATE: + printf("/AFI_LINKSTATE\n"); + break; default: printf("/UNKNOWN %d", subtype); break; @@ -239,7 +243,8 @@ int main(int argc, char **argv) source_as = stream_getw(s); printf("FROM: %pI4 AS%d\n", &peer, source_as); - printf("ORIGINATED: %s", ctime(&originated)); + printf("ORIGINATED: %s", ctime_r(&originated, + tbuf)); attrlen = stream_getw(s); printf("ATTRLEN: %d\n", attrlen); diff --git a/bgpd/bgp_damp.c b/bgpd/bgp_damp.c index a6d0e74dc0..80425ebe7a 100644 --- a/bgpd/bgp_damp.c +++ b/bgpd/bgp_damp.c @@ -558,7 +558,7 @@ void bgp_damp_info_vty(struct vty *vty, struct bgp_path_info *path, afi_t afi, { struct bgp_damp_info *bdi; time_t t_now, t_diff; - char timebuf[BGP_UPTIME_LEN]; + char timebuf[BGP_UPTIME_LEN] = {}; int penalty; struct bgp_damp_config *bdc = &damp[afi][safi]; @@ -570,7 +570,9 @@ void bgp_damp_info_vty(struct vty *vty, struct bgp_path_info *path, afi_t afi, /* If dampening is not enabled or there is no dampening information, return immediately. */ - if (!bdc || !bdi) + if (!CHECK_FLAG(path->peer->bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING) || + !bdi) return; /* Calculate new penalty. */ @@ -624,7 +626,9 @@ const char *bgp_damp_reuse_time_vty(struct vty *vty, struct bgp_path_info *path, /* If dampening is not enabled or there is no dampening information, return immediately. */ - if (!bdc || !bdi) + if (!CHECK_FLAG(path->peer->bgp->af_flags[afi][safi], + BGP_CONFIG_DAMPENING) || + !bdi) return NULL; /* Calculate new penalty. */ diff --git a/bgpd/bgp_debug.c b/bgpd/bgp_debug.c index 782245e512..0ff8bdcbbe 100644 --- a/bgpd/bgp_debug.c +++ b/bgpd/bgp_debug.c @@ -51,6 +51,7 @@ unsigned long conf_bgp_debug_nht; unsigned long conf_bgp_debug_update_groups; unsigned long conf_bgp_debug_vpn; unsigned long conf_bgp_debug_flowspec; +unsigned long conf_bgp_debug_linkstate; unsigned long conf_bgp_debug_labelpool; unsigned long conf_bgp_debug_pbr; unsigned long conf_bgp_debug_graceful_restart; @@ -72,6 +73,7 @@ unsigned long term_bgp_debug_nht; unsigned long term_bgp_debug_update_groups; unsigned long term_bgp_debug_vpn; unsigned long term_bgp_debug_flowspec; +unsigned long term_bgp_debug_linkstate; unsigned long term_bgp_debug_labelpool; unsigned long term_bgp_debug_pbr; unsigned long term_bgp_debug_graceful_restart; diff --git a/bgpd/bgp_debug.h b/bgpd/bgp_debug.h index 118325e0a3..38beebf872 100644 --- a/bgpd/bgp_debug.h +++ b/bgpd/bgp_debug.h @@ -60,6 +60,7 @@ extern unsigned long conf_bgp_debug_nht; extern unsigned long conf_bgp_debug_update_groups; extern unsigned long conf_bgp_debug_vpn; extern unsigned long conf_bgp_debug_flowspec; +extern unsigned long conf_bgp_debug_linkstate; extern unsigned long conf_bgp_debug_labelpool; extern unsigned long conf_bgp_debug_pbr; extern unsigned long conf_bgp_debug_graceful_restart; @@ -79,6 +80,7 @@ extern unsigned long term_bgp_debug_nht; extern unsigned long term_bgp_debug_update_groups; extern unsigned long term_bgp_debug_vpn; extern unsigned long term_bgp_debug_flowspec; +extern unsigned long term_bgp_debug_linkstate; extern unsigned long term_bgp_debug_labelpool; extern unsigned long term_bgp_debug_pbr; extern unsigned long term_bgp_debug_graceful_restart; @@ -118,6 +120,7 @@ struct bgp_debug_filter { #define BGP_DEBUG_VPN_LEAK_RMAP_EVENT 0x04 #define BGP_DEBUG_VPN_LEAK_LABEL 0x08 #define BGP_DEBUG_FLOWSPEC 0x01 +#define BGP_DEBUG_LINKSTATE 0x01 #define BGP_DEBUG_LABELPOOL 0x01 #define BGP_DEBUG_PBR 0x01 #define BGP_DEBUG_PBR_ERROR 0x02 diff --git a/bgpd/bgp_errors.c b/bgpd/bgp_errors.c index cfcefed996..2f9f16f800 100644 --- a/bgpd/bgp_errors.c +++ b/bgpd/bgp_errors.c @@ -450,6 +450,12 @@ static struct log_ref ferr_bgp_err[] = { .suggestion = "Gather log files from the router and open an issue, Restart FRR" }, { + .code = EC_BGP_LINKSTATE_PACKET, + .title = "BGP Link-State packet processing error", + .description = "The BGP Link-State subsystem has detected a error in the send or receive of a packet", + .suggestion = "Gather log files from both sides of the peering relationship and open an issue" + }, + { .code = EC_BGP_DOPPELGANGER_CONFIG, .title = "BGP has detected a configuration overwrite during peer collision resolution", .description = "As part of BGP startup, the peer and ourselves can start connections to each other at the same time. During this process BGP received additional configuration, but it was only applied to one of the two nascent connections. Depending on the result of collision detection and resolution this configuration might be lost. To remedy this, after performing collision detection and resolution the peer session has been reset in order to apply the new configuration.", diff --git a/bgpd/bgp_errors.h b/bgpd/bgp_errors.h index 4567f87835..bc6b5a4a3d 100644 --- a/bgpd/bgp_errors.h +++ b/bgpd/bgp_errors.h @@ -59,6 +59,7 @@ enum bgp_log_refs { EC_BGP_EVPN_INSTANCE_MISMATCH, EC_BGP_FLOWSPEC_PACKET, EC_BGP_FLOWSPEC_INSTALLATION, + EC_BGP_LINKSTATE_PACKET, EC_BGP_ASPATH_FEWER_HOPS, EC_BGP_DEFUNCT_SNPA_LEN, EC_BGP_MISSING_ATTRIBUTE, diff --git a/bgpd/bgp_evpn.c b/bgpd/bgp_evpn.c index aa23f06762..ad101f171a 100644 --- a/bgpd/bgp_evpn.c +++ b/bgpd/bgp_evpn.c @@ -1722,8 +1722,8 @@ static void bgp_evpn_get_sync_info(struct bgp *bgp, esi_t *esi, continue; } - if (bgp_evpn_path_info_cmp(bgp, tmp_pi, - second_best_path, &paths_eq)) + if (bgp_evpn_path_info_cmp(bgp, tmp_pi, second_best_path, + &paths_eq, false)) second_best_path = tmp_pi; } diff --git a/bgpd/bgp_labelpool.c b/bgpd/bgp_labelpool.c index dc032660ce..883338610c 100644 --- a/bgpd/bgp_labelpool.c +++ b/bgpd/bgp_labelpool.c @@ -1131,7 +1131,7 @@ static void show_bgp_nexthop_label_afi(struct vty *vty, afi_t afi, ifindex2ifname(iter->nh->ifindex, iter->nh->vrf_id)); tbuf = time(NULL) - (monotime(NULL) - iter->last_update); - vty_out(vty, " Last update: %s", ctime(&tbuf)); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, buf)); if (!detail) continue; vty_out(vty, " Paths:\n"); diff --git a/bgpd/bgp_linkstate.c b/bgpd/bgp_linkstate.c new file mode 100644 index 0000000000..f76c68ca5d --- /dev/null +++ b/bgpd/bgp_linkstate.c @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State + * Copyright 2023 6WIND S.A. + */ + +#include <zebra.h> + +#include "prefix.h" +#include "lib_errors.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_linkstate.h" +#include "bgpd/bgp_linkstate_tlv.h" + +void bgp_linkstate_init(void) +{ + prefix_set_linkstate_display_hook(bgp_linkstate_nlri_prefix_display); +} diff --git a/bgpd/bgp_linkstate.h b/bgpd/bgp_linkstate.h new file mode 100644 index 0000000000..c8d4d23f16 --- /dev/null +++ b/bgpd/bgp_linkstate.h @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State header + * Copyright 2023 6WIND S.A. + */ + +#ifndef _FRR_BGP_LINKSTATE_H +#define _FRR_BGP_LINKSTATE_H + +void bgp_linkstate_init(void); +#endif /* _FRR_BGP_LINKSTATE_H */ diff --git a/bgpd/bgp_linkstate_tlv.c b/bgpd/bgp_linkstate_tlv.c new file mode 100644 index 0000000000..5538f7a761 --- /dev/null +++ b/bgpd/bgp_linkstate_tlv.c @@ -0,0 +1,1750 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State TLV Serializer/Deserializer + * Copyright 2023 6WIND S.A. + */ + +#include <zebra.h> + +#include "iso.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_route.h" +#include "bgpd/bgp_debug.h" +#include "bgpd/bgp_errors.h" +#include "bgpd/bgp_linkstate_tlv.h" + + +static bool bgp_linkstate_nlri_value_display(char *buf, size_t size, + uint8_t *pnt, uint16_t nlri_type, + uint16_t type, uint16_t length, + bool first, json_object *json); + +struct bgp_linkstate_tlv_info { + const char *descr; + uint8_t min_size; + uint16_t max_size; + uint8_t multiple; +}; + +#define UNDEF_MIN_SZ 0xFF +#define MAX_SZ 0xFFFF +#define UNDEF_MULTPL 1 + +/* clang-format off */ +struct bgp_linkstate_tlv_info bgp_linkstate_tlv_infos[BGP_LS_TLV_MAX] = { + /* NLRI TLV */ + [BGP_LS_TLV_LOCAL_NODE_DESCRIPTORS] = {"Local Node Descriptors", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_REMOTE_NODE_DESCRIPTORS] = {"Remote Node Descriptors", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_LINK_LOCAL_REMOTE_IDENTIFIERS] = {"Link Local/Remote Identifiers", 2, 2, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_INTERFACE_ADDRESS] = {"IPv4 interface address", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_NEIGHBOR_ADDRESS] = {"IPv4 neighbor address", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV6_INTERFACE_ADDRESS] = {"IPv6 interface address", 16, 16, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV6_NEIGHBOR_ADDRESS] = {"IPv6 neighbor address", 16, 16, UNDEF_MULTPL}, + [BGP_LS_TLV_OSPF_ROUTE_TYPE] = {"OSPF Route Type", 1, 1, UNDEF_MULTPL}, + [BGP_LS_TLV_IP_REACHABILITY_INFORMATION] = {"IP Reachability Information", 2, 17, UNDEF_MULTPL}, + [BGP_LS_TLV_AUTONOMOUS_SYSTEM] = {"Autonomous System", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_BGP_LS_IDENTIFIER] = {"BGP-LS Identifier", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_OSPF_AREA_ID] = {"OSPF Area-ID", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_IGP_ROUTER_ID] = {"IGP Router-ID", 4, 8, UNDEF_MULTPL}, + /* NRLI & BGP-LS Attributes */ + [BGP_LS_TLV_MULTI_TOPOLOGY_ID] = {"Multi-Topology ID", 2, MAX_SZ, 2}, + /* BGP-LS Attributes */ + [BGP_LS_TLV_NODE_MSD] = {"Node MSD", 2, MAX_SZ, 2}, + [BGP_LS_TLV_LINK_MSD] = {"Link MSD", 2, MAX_SZ, 2}, + [BGP_LS_TLV_BGP_ROUTER_ID] = {"BGP Router-ID", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_BGP_CONFEDERATION_MEMBER] = {"BGP Confederation Member", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_NODE_FLAG_BITS] = {"Node Flag Bits", 1, 1, UNDEF_MULTPL}, + [BGP_LS_TLV_OPAQUE_NODE_ATTRIBUTE] = {"Opaque Node Attribute", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_NODE_NAME] = {"Node Name", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IS_IS_AREA_IDENTIFIER] = {"IS-IS Area Identifier", 1, 13, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_ROUTER_ID_OF_LOCAL_NODE] = {"IPv4 Router-ID of Local Node", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV6_ROUTER_ID_OF_LOCAL_NODE] = {"IPv6 Router-ID of Local Node", 16, 16, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_ROUTER_ID_OF_REMOTE_NODE] = {"IPv4 Router-ID of Remote Node", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV6_ROUTER_ID_OF_REMOTE_NODE] = {"IPv6 Router-ID of Remote Node", 16, 16, UNDEF_MULTPL}, + [BGP_LS_TLV_S_BFD_DISCRIMINATORS] = {"S-BFD Discriminators", 4, MAX_SZ, 4}, + [BGP_LS_TLV_SR_CAPABILITIES] = {"SR Capabilities", 12, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_ALGORITHM] = {"SR Algorithm", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_LOCAL_BLOCK] = {"SR Local Block", 12, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRMS_PREFERENCE] = {"SRMS Preference", 1, 1, UNDEF_MULTPL}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION] = {"Flexible Algorithm Definition", 4, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_ANY_AFFINITY] = {"Flexible Algorithm Exclude Any Affinity", 4, MAX_SZ, 4}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ANY_AFFINITY] = {"Flexible Algorithm Include Any Affinity", 4, MAX_SZ, 4}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ALL_AFFINITY] = {"Flexible Algorithm Include All Affinity", 4, MAX_SZ, 4}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION_FLAGS] = {"Flexible Algorithm Definition Flags", 4, MAX_SZ, 4}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_PREFIX_METRIC] = {"Flexible Algorithm Prefix Metric", 8, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_SRLG] = {"Flexible Algorithm Exclude SRLG", 4, MAX_SZ, 4}, + [BGP_LS_TLV_ADMINISTRATIVE_GROUP] = {"Administrative group", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_MAXIMUM_LINK_BANDWIDTH] = {"Maximum link bandwidth", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_MAX_RESERVABLE_LINK_BANDWIDTH] = {"Max. reservable link bandwidth", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_UNRESERVED_BANDWIDTH] = {"Unreserved bandwidth", 32, 32, UNDEF_MULTPL}, + [BGP_LS_TLV_TE_DEFAULT_METRIC] = {"TE Default Metric", 3, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_LINK_PROTECTION_TYPE] = {"Link Protection Type", 2, 2, UNDEF_MULTPL}, + [BGP_LS_TLV_MPLS_PROTOCOL_MASK] = {"MPLS Protocol Mask", 1, 1, UNDEF_MULTPL}, + [BGP_LS_TLV_IGP_METRIC] = {"IGP Metric", 1, 3, UNDEF_MULTPL}, + [BGP_LS_TLV_SHARED_RISK_LINK_GROUP] = {"Shared Risk Link Group", 4, MAX_SZ, 4}, + [BGP_LS_TLV_OPAQUE_LINK_ATTRIBUTE] = {"Opaque Link Attribute", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_LINK_NAME] = {"Link Name", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_ADJACENCY_SID] = {"Adjacency SID", 7, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_LAN_ADJACENCY_SID] = {"LAN Adjacency SID", 11, 14, UNDEF_MULTPL}, + [BGP_LS_TLV_PEERNODE_SID] = {"PeerNode SID", 7, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_PEERADJ_SID] = {"PeerAdj SID", 7, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_PEERSET_SID] = {"PeerSet SID", 7, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_RTM_CAPABILITY] = {"RTM Capability", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_LINK_DELAY] = {"Unidirectional Link Delay", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY] = {"Min/Max Unidirectional Link Delay", 8, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_DELAY_VARIATION] = {"Unidirectional Delay Variation", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_LINK_LOSS] = {"Unidirectional Link Loss", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH] = {"Unidirectional Residual Bandwidth", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH] = {"Unidirectional Available Bandwidth", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_UNIDIRECTIONAL_UTILIZED_BANDWIDTH] = {"Unidirectional Utilized Bandwidth", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_GRACEFUL_LINK_SHUTDOWN_TLV] = {"Graceful-Link-Shutdown TLV", 0, 0, UNDEF_MULTPL}, + [BGP_LS_TLV_APPLICATION_SPECIFIC_LINK_ATTRIBUTES] = {"Application-Specific Link Attributes", 11, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IGP_FLAGS] = {"IGP Flags", 1, 1, UNDEF_MULTPL}, + [BGP_LS_TLV_IGP_ROUTE_TAG] = {"IGP Route Tag", 4, MAX_SZ, 4}, + [BGP_LS_TLV_IGP_EXTENDED_ROUTE_TAG] = {"IGP Extended Route Tag", 8, MAX_SZ, 8}, + [BGP_LS_TLV_PREFIX_METRIC] = {"Prefix Metric", 3, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_OSPF_FORWARDING_ADDRESS] = {"OSPF Forwarding Address", 4, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_OPAQUE_PREFIX_ATTRIBUTE] = {"Opaque Prefix Attribute", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_PREFIX_SID] = {"Prefix-SID", 7, 8, UNDEF_MULTPL}, + [BGP_LS_TLV_RANGE] = {"Range", 11, 12, UNDEF_MULTPL}, + [BGP_LS_TLV_SID_LABEL] = {"SID/Label", 3, 4, UNDEF_MULTPL}, + [BGP_LS_TLV_PREFIX_ATTRIBUTES_FLAGS] = {"Prefix Attributes Flags", 1, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SOURCE_ROUTER_IDENTIFIER] = {"Source Router Identifier", 4, 16, UNDEF_MULTPL}, + [BGP_LS_TLV_L2_BUNDLE_MEMBER_ATTRIBUTES] = {"L2 Bundle Member Attributes", 4, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_EXTENDED_ADMINISTRATIVE_GROUP] = {"Extended Administrative Group", 4, MAX_SZ, 4}, + [BGP_LS_TLV_SOURCE_OSPF_ROUTER_ID] = {"Source OSPF Router-ID", 4, 4, UNDEF_MULTPL}, + /* display not yet supported */ + [BGP_LS_TLV_SRV6_SID_INFORMATION_TLV] = {"SRv6 SID Information TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_TUNNEL_ID_TLV] = {"Tunnel ID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_LSP_ID_TLV] = {"LSP ID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_6_TUNNEL_HEAD_END_ADDRESS_TLV] = {"IPv4/6 Tunnel Head-end address TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IPV4_6_TUNNEL_TAIL_END_ADDRESS_TLV] = {"IPv4/6 Tunnel Tail-end address TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_POLICY_CP_DESCRIPTOR_TLV] = {"SR Policy CP Descriptor TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_MPLS_LOCAL_CROSS_CONNECT_TLV] = {"MPLS Local Cross Connect TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_MPLS_CROSS_CONNECT_INTERFACE_TLV] = {"MPLS Cross Connect Interface TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_MPLS_CROSS_CONNECT_FEC_TLV] = {"MPLS Cross Connect FEC TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_CAPABILITIES_TLV] = {"SRv6 Capabilities TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_FLEXIBLE_ALGORITHM_UNSUPPORTED] = {"Flexible Algorithm Unsupported", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_END_X_SID_TLV] = {"SRv6 End.X SID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IS_IS_SRV6_LAN_END_X_SID_TLV] = {"IS-IS SRv6 LAN End.X SID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_OSPFV3_SRV6_LAN_END_X_SID_TLV] = {"OSPFv3 SRv6 LAN End.X SID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_IS_IS_FLOOD_REFLECTION] = {"IS-IS Flood Reflection", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_LOCATOR_TLV] = {"SRv6 Locator TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_MPLS_TE_POLICY_STATE_TLV] = {"MPLS-TE Policy State TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_BSID_TLV] = {"SR BSID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_CP_STATE_TLV] = {"SR CP State TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_CP_NAME_TLV] = {"SR CP Name TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_CP_CONSTRAINTS_TLV] = {"SR CP Constraints TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_SEGMENT_LIST_TLV] = {"SR Segment List TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_SEGMENT_SUB_TLV] = {"SR Segment sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_SEGMENT_LIST_METRIC_SUB_TLV] = {"SR Segment List Metric sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_AFFINITY_CONSTRAINT_SUB_TLV] = {"SR Affinity Constraint sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_SRLG_CONSTRAINT_SUB_TLV] = {"SR SRLG Constraint sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_BANDWIDTH_CONSTRAINT_SUB_TLV] = {"SR Bandwidth Constraint sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_DISJOINT_GROUP_CONSTRAINT_SUB_TLV] = {"SR Disjoint Group Constraint sub-TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_BSID_TLV] = {"SRv6 BSID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SR_POLICY_NAME_TLV] = {"SR Policy Name TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_ENDPOINT_FUNCTION_TLV] = {"SRv6 Endpoint Function TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_BGP_PEER_NODE_SID_TLV] = {"SRv6 BGP Peer Node SID TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, + [BGP_LS_TLV_SRV6_SID_STRUCTURE_TLV] = {"SRv6 SID Structure TLV", UNDEF_MIN_SZ, MAX_SZ, UNDEF_MULTPL}, +}; +/* clang-format on */ + +/* Return the TLV length is valid for the TLV type */ +static bool bgp_ls_tlv_check_size(enum bgp_linkstate_tlv type, size_t length) +{ + if (type > BGP_LS_TLV_MAX || + bgp_linkstate_tlv_infos[type].descr == NULL) + /* TLV type is not defined. Cannot check size */ + return false; + + if (bgp_linkstate_tlv_infos[type].min_size > length) + return false; + if (bgp_linkstate_tlv_infos[type].max_size < length) + return false; + if (length % bgp_linkstate_tlv_infos[type].multiple != 0) + return false; + + return true; +} + +static uint8_t pnt_decode8(uint8_t **pnt) +{ + uint8_t data; + + data = **pnt; + *pnt += 1; + return data; +} + +static uint16_t pnt_decode16(uint8_t **pnt) +{ + uint16_t data; + + *pnt = ptr_get_be16(*pnt, &data); + + return data; +} + +static uint32_t pnt_decode24(uint8_t **pnt) +{ + uint8_t tmp1; + uint16_t tmp2; + + memcpy(&tmp1, *pnt, sizeof(uint8_t)); + memcpy(&tmp2, *pnt + sizeof(uint8_t), sizeof(uint16_t)); + + *pnt += 3; + + return (tmp1 << 16) | ntohs(tmp2); +} + +static uint32_t pnt_decode32(uint8_t **pnt) +{ + uint32_t data; + + *pnt = (uint8_t *)ptr_get_be32(*pnt, &data); + + return data; +} + +static uint64_t pnt_decode64(uint8_t **pnt) +{ + uint64_t data; + + *pnt = (uint8_t *)ptr_get_be64(*pnt, &data); + + return data; +} + +static const char *bgp_ls_print_nlri_proto(enum bgp_ls_nlri_proto proto) +{ + switch (proto) { + case BGP_LS_NLRI_PROTO_ID_IS_IS_LEVEL_1: + return "ISIS-L1"; + case BGP_LS_NLRI_PROTO_ID_IS_IS_LEVEL_2: + return "ISIS-L2"; + case BGP_LS_NLRI_PROTO_ID_OSPF: + return "OSPFv2"; + case BGP_LS_NLRI_PROTO_ID_DIRECT: + return "Direct"; + case BGP_LS_NLRI_PROTO_ID_STATIC: + return "Static"; + case BGP_LS_NLRI_PROTO_ID_OSPFv3: + return "OSPFv3"; + case BGP_LS_NLRI_PROTO_ID_UNKNOWN: + return "Unknown"; + } + return "Unknown"; +} + +int bgp_nlri_parse_linkstate(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, int withdraw) +{ + uint8_t *pnt; + uint8_t *lim; + afi_t afi; + safi_t safi; + uint16_t length = 0; + struct prefix p; + + /* Start processing the NLRI - there may be multiple in the MP_REACH */ + pnt = packet->nlri; + lim = pnt + packet->length; + afi = packet->afi; + safi = packet->safi; + + for (; pnt < lim; pnt += length) { + /* Clear prefix structure. */ + memset(&p, 0, sizeof(p)); + + /* All linkstate NLRI begin with NRLI type and length. */ + if (pnt + 4 > lim) + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + + p.u.prefix_linkstate.nlri_type = pnt_decode16(&pnt); + length = pnt_decode16(&pnt); + /* When packet overflow occur return immediately. */ + if (pnt + length > lim) { + flog_err( + EC_BGP_LINKSTATE_PACKET, + "Link-State NLRI length inconsistent (size %u seen)", + length); + return BGP_NLRI_PARSE_ERROR_PACKET_OVERFLOW; + } + p.family = AF_LINKSTATE; + + p.u.prefix_linkstate.ptr = (uintptr_t)pnt; + p.prefixlen = length; + + if (BGP_DEBUG(linkstate, LINKSTATE)) { + zlog_debug("LS Rx %s %s %pFX", + withdraw ? "Withdraw" : "Update", + afi2str(afi), &p); + } + + /* Process the route. */ + if (withdraw) + bgp_withdraw(peer, &p, 0, afi, safi, ZEBRA_ROUTE_BGP, + BGP_ROUTE_NORMAL, NULL, NULL, 0, NULL); + else + bgp_update(peer, &p, 0, attr, afi, safi, + ZEBRA_ROUTE_BGP, BGP_ROUTE_NORMAL, NULL, + NULL, 0, 0, NULL); + } + return BGP_NLRI_PARSE_OK; +} + +/* + * Encode Link-State prefix in Update (MP_REACH) + */ +void bgp_nlri_encode_linkstate(struct stream *s, const struct prefix *p) +{ + /* NLRI type */ + stream_putw(s, p->u.prefix_linkstate.nlri_type); + + /* Size */ + stream_putw(s, p->prefixlen); + + stream_put(s, (const void *)p->u.prefix_linkstate.ptr, p->prefixlen); +} + +static size_t bgp_linkstate_nlri_hexa_display(char *buf, size_t size, + uint8_t *pnt, uint16_t type, + uint16_t length, bool first, + json_object *json) +{ + json_object *json_array = NULL; + uint8_t *lim = pnt + length; + char json_buf[19]; + int i; + + if (json) { + snprintf(json_buf, sizeof(json_buf), "%u", type); + json_array = json_object_new_array(); + json_object_object_add(json, json_buf, json_array); + for (i = 0; pnt < lim; pnt++, i++) { + if (i % 8 == 0) { + if (i != 0) + json_object_array_add( + json_array, + json_object_new_string( + json_buf)); + snprintf(json_buf, sizeof(buf), "0x"); + } + snprintf(json_buf + strlen(json_buf), + sizeof(json_buf) - strlen(json_buf), "%02x", + *pnt); + } + if (strlen(json_buf) > 2) /* do not only contain 0x */ + json_object_array_add(json_array, + json_object_new_string(json_buf)); + + return size; + } + + snprintf(buf, size, "%s%u:", first ? "" : " ", type); + size -= strlen(buf); + buf += strlen(buf); + + snprintf(buf, size, "0x"); + size -= strlen(buf); + buf += strlen(buf); + + for (i = 0; pnt < lim; pnt++, i++) { + snprintf(buf, size, "%02x", *pnt); + size -= strlen(buf); + buf += strlen(buf); + } + + return size; +} + +static void bgp_linkstate_nlri_mtid_display(char *buf, size_t size, + uint8_t *pnt, uint16_t type, + uint16_t length, bool first, + json_object *json) +{ + json_object *json_array = NULL; + + if (json) { + json_array = json_object_new_array(); + json_object_object_add(json, "mtID", json_array); + for (int i = 0; i < (length / 2); i++) { + json_object_array_add( + json_array, + json_object_new_int(pnt_decode16(&pnt))); + } + return; + } + + for (int i = 0; i < (length / 2); i++) { + if (i == 0) + snprintf(buf, size, "%sMT:%hu", first ? "" : " ", + pnt_decode16(&pnt)); + else + snprintf(buf, size, ",%hu", pnt_decode16(&pnt)); + size -= strlen(buf); + buf += strlen(buf); + } +} + +static bool bgp_linkstate_nlri_node_descriptor_display( + char *buf, size_t size, uint8_t *pnt, uint16_t nlri_type, uint16_t type, + uint16_t length, bool first, json_object *json) +{ + json_object *json_node = NULL; + bool sub_first = true; + uint8_t *lim = pnt + length; + uint16_t sub_type, sub_length; + + if (json) { + json_node = json_object_new_object(); + if (type == BGP_LS_TLV_LOCAL_NODE_DESCRIPTORS) + json_object_object_add(json, "localNode", json_node); + else + json_object_object_add(json, "remoteNode", json_node); + } else { + if (type == BGP_LS_TLV_LOCAL_NODE_DESCRIPTORS) + snprintf(buf, size, "%sLocal {", first ? "" : " "); + else + snprintf(buf, size, "%sRemote {", first ? "" : " "); + size -= strlen(buf); + buf += strlen(buf); + } + + for (; pnt < lim; pnt += sub_length) { + sub_type = pnt_decode16(&pnt); + sub_length = pnt_decode16(&pnt); + + if (pnt + sub_length > lim) + /* bad length */ + return false; + + bgp_linkstate_nlri_value_display(buf, size, pnt, nlri_type, + sub_type, sub_length, + sub_first, json_node); + + if (!json) { + size -= strlen(buf); + buf += strlen(buf); + sub_first = false; + } + } + + if (!json) + snprintf(buf, size, "}"); + + return true; +} + +static bool bgp_linkstate_nlri_value_display(char *buf, size_t size, + uint8_t *pnt, uint16_t nlri_type, + uint16_t type, uint16_t length, + bool first, json_object *json) +{ + struct in_addr ipv4 = {0}; + struct in6_addr ipv6 = {0}; + uint8_t mask_length; + + if (!bgp_ls_tlv_check_size(type, length) && !json) { + bgp_linkstate_nlri_hexa_display(buf, size, pnt, type, length, + first, json); + return true; + } + + switch (type) { + case BGP_LS_TLV_LOCAL_NODE_DESCRIPTORS: + case BGP_LS_TLV_REMOTE_NODE_DESCRIPTORS: + return bgp_linkstate_nlri_node_descriptor_display( + buf, size, pnt, nlri_type, type, length, first, json); + case BGP_LS_TLV_AUTONOMOUS_SYSTEM: + if (json) + json_object_int_add(json, "as", pnt_decode32(&pnt)); + else + snprintf(buf, size, "%sAS:%u", first ? "" : " ", + pnt_decode32(&pnt)); + break; + case BGP_LS_TLV_BGP_LS_IDENTIFIER: + if (json) + json_object_int_add(json, "identifier", + pnt_decode32(&pnt)); + else + snprintf(buf, size, "%sID:%u", first ? "" : " ", + pnt_decode32(&pnt)); + break; + case BGP_LS_TLV_OSPF_AREA_ID: + if (json) + json_object_int_add(json, "area", pnt_decode32(&pnt)); + else + snprintf(buf, size, "%sArea:%u", first ? "" : " ", + pnt_decode32(&pnt)); + break; + case BGP_LS_TLV_IGP_ROUTER_ID: + switch (length) { + case BGP_LS_TLV_IGP_ROUTER_ID_ISIS_NON_PSEUDOWIRE_SIZE: + if (json) + json_object_string_addf(json, "routerID", + "%pSY", pnt); + else + snprintfrr(buf, size, "%sRtr:%pSY", + first ? "" : " ", pnt); + break; + case BGP_LS_TLV_IGP_ROUTER_ID_ISIS_PSEUDOWIRE_SIZE: + if (json) + json_object_string_addf(json, "routerID", + "%pPN", pnt); + else + snprintfrr(buf, size, "%sRtr:%pPN", + first ? "" : " ", pnt); + break; + case BGP_LS_TLV_IGP_ROUTER_ID_OSPF_NON_PSEUDOWIRE_SIZE: + if (json) + json_object_string_addf(json, "routerID", + "%pI4", + (in_addr_t *)pnt); + else + snprintfrr(buf, size, "%sRtr:%pI4", + first ? "" : " ", (in_addr_t *)pnt); + break; + case BGP_LS_TLV_IGP_ROUTER_ID_OSPF_PSEUDOWIRE_SIZE: + if (json) + json_object_string_addf(json, "routerID", + "%pI4:%pI4", + (in_addr_t *)pnt, + ((in_addr_t *)pnt + 1)); + else + snprintfrr(buf, size, "%sRtr:%pI4:%pI4", + first ? "" : " ", (in_addr_t *)pnt, + ((in_addr_t *)pnt + 1)); + break; + default: + bgp_linkstate_nlri_hexa_display(buf, size, pnt, type, + length, first, json); + } + break; + case BGP_LS_TLV_LINK_LOCAL_REMOTE_IDENTIFIERS: + if (json) + json_object_int_add(json, "localRemoteID", + pnt_decode16(&pnt)); + else + snprintf(buf, size, "%sLocal/remote:%hu", + first ? "" : " ", pnt_decode16(&pnt)); + break; + case BGP_LS_TLV_IPV4_INTERFACE_ADDRESS: + if (json) + json_object_string_addf(json, "interfaceIPv4", "%pI4", + (in_addr_t *)pnt); + else + snprintfrr(buf, size, "%sIPv4:%pI4", first ? "" : " ", + (in_addr_t *)pnt); + break; + case BGP_LS_TLV_IPV4_NEIGHBOR_ADDRESS: + if (json) + json_object_string_addf(json, "neighborIPv4", "%pI4", + (in_addr_t *)pnt); + else + snprintfrr(buf, size, "%sNeigh-IPv4:%pI4", + first ? "" : " ", (in_addr_t *)pnt); + break; + case BGP_LS_TLV_IPV6_INTERFACE_ADDRESS: + if (json) + json_object_string_addf(json, "interfaceIPv6", "%pI6", + (struct in6_addr *)pnt); + else + snprintfrr(buf, size, "%sIPv6:%pI6", first ? "" : " ", + (struct in6_addr *)pnt); + break; + case BGP_LS_TLV_IPV6_NEIGHBOR_ADDRESS: + if (json) + json_object_string_addf(json, "neighborIPv6", "%pI6", + (struct in6_addr *)pnt); + else + snprintfrr(buf, size, "%sNeigh-IPv6:%pI6", + first ? "" : " ", (struct in6_addr *)pnt); + break; + case BGP_LS_TLV_MULTI_TOPOLOGY_ID: + bgp_linkstate_nlri_mtid_display(buf, size, pnt, type, length, + first, json); + break; + case BGP_LS_TLV_OSPF_ROUTE_TYPE: + if (json) + json_object_int_add(json, "ospfRouteType", + pnt_decode8(&pnt)); + else + snprintf(buf, size, "%sOSPF-Route-Type:%u", + first ? "" : " ", pnt_decode8(&pnt)); + break; + case BGP_LS_TLV_IP_REACHABILITY_INFORMATION: + mask_length = pnt_decode8(&pnt); + if (nlri_type == BGP_LINKSTATE_PREFIX4) { + memcpy(&ipv4.s_addr, pnt, length - sizeof(mask_length)); + if (json) + json_object_string_addf(json, "ipReachability", + "%pI4/%u", &ipv4, + mask_length); + else + snprintfrr(buf, size, "%sIPv4:%pI4/%u", + first ? "" : " ", &ipv4, + mask_length); + } else if (nlri_type == BGP_LINKSTATE_PREFIX6) { + memcpy(&ipv6, pnt, length - sizeof(mask_length)); + if (json) + json_object_string_addf(json, "ipReachability", + "%pI6/%u", &ipv6, + mask_length); + else + snprintfrr(buf, size, "%sIPv6:%pI6/%u", + first ? "" : " ", &ipv6, + mask_length); + } else + bgp_linkstate_nlri_hexa_display(buf, size, pnt, type, + length, first, json); + + break; + default: + bgp_linkstate_nlri_hexa_display(buf, size, pnt, type, length, + first, json); + } + + return true; +} + +char *bgp_linkstate_nlri_prefix_display(char *buf, size_t size, + uint16_t nlri_type, uintptr_t ptr, + uint16_t len) +{ + uint8_t *pnt = (uint8_t *)ptr; + uint8_t *lim = pnt + len; + uint16_t type, length; + char *cbuf = buf, *cbuf2; + uint8_t proto; + bool ret; + bool first = true; + + proto = pnt_decode8(&pnt); + + snprintfrr(buf, size, "%s %s ID:0x%" PRIx64 " {", + bgp_linkstate_nlri_type_2str(nlri_type), + bgp_ls_print_nlri_proto(proto), pnt_decode64(&pnt)); + size -= strlen(buf); + buf += strlen(buf); + + cbuf2 = buf; + + for (; pnt < lim; pnt += length) { + type = pnt_decode16(&pnt); + length = pnt_decode16(&pnt); + + if (pnt + length > lim) { + /* bad length */ + snprintf(cbuf2, size, "Bad format}"); + return cbuf; + } + + ret = bgp_linkstate_nlri_value_display( + buf, size, pnt, nlri_type, type, length, first, NULL); + + if (!ret) { + /* bad length */ + snprintf(cbuf2, size, "Bad format}"); + return cbuf; + } + + size -= strlen(buf); + buf += strlen(buf); + first = false; + } + + snprintf(buf, size, "}"); + + return cbuf; +} + +void bgp_linkstate_nlri_prefix_json(json_object *json, uint16_t nlri_type, + uintptr_t ptr, uint16_t len) +{ + json_object *json_nlri = json_object_new_object(); + uint8_t *pnt = (uint8_t *)ptr; + uint8_t *lim = pnt + len; + uint16_t type, length; + uint8_t proto; + bool ret; + + proto = pnt_decode8(&pnt); + + json_object_object_add(json, "linkStateNLRI", json_nlri); + json_object_string_add(json_nlri, "nlriType", + bgp_linkstate_nlri_type_2str(nlri_type)); + json_object_string_add(json_nlri, "protocol", + bgp_ls_print_nlri_proto(proto)); + json_object_string_addf(json_nlri, "identifier", "0x%" PRIx64, + pnt_decode64(&pnt)); + + for (; pnt < lim; pnt += length) { + type = pnt_decode16(&pnt); + length = pnt_decode16(&pnt); + + if (pnt + length > lim) + /* bad length */ + return; + + ret = bgp_linkstate_nlri_value_display(NULL, 0, pnt, nlri_type, + type, length, false, + json_nlri); + + if (!ret) + /* bad length */ + return; + } +} + + +static uint8_t *bgp_linkstate_tlv_binary_string(char *buf, size_t buf_sz, + uint8_t *pnt, uint16_t length) +{ + uint8_t tmp; + int i, j; + + for (i = 0; i < length; i++) { + if (i == 0) + snprintf(buf, buf_sz, "0b"); + else + snprintf(buf + strlen(buf), buf_sz - strlen(buf), " "); + tmp = pnt_decode8(&pnt); + for (j = 7; j >= 0; j--) + snprintf(buf + strlen(buf), buf_sz - strlen(buf), "%d", + (tmp >> j) & 1); + } + + return pnt; +} + +/* dump bits. Their meaning is not decoded */ +static uint8_t *bgp_linkstate_tlv_binary_display(struct vty *vty, uint8_t *pnt, + uint16_t length, + json_object *json) +{ + char buf[290]; + uint8_t tmp; + int i, j; + + if (json) { + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, + length); + json_object_string_add(json, "data", buf); + return pnt; + } + + for (i = 0; i < length; i++) { + if (i == 0) + vty_out(vty, "0b"); + else + vty_out(vty, " "); + tmp = pnt_decode8(&pnt); + for (j = 7; j >= 0; j--) + vty_out(vty, "%d", (tmp >> j) & 1); + } + vty_out(vty, "\n"); + + return pnt; +} + +static void bgp_linkstate_tlv_hexa_display(struct vty *vty, uint8_t *pnt, + uint16_t length, json_object *json) +{ + uint8_t *lim = pnt + length; + char buf[290]; + int i; + + if (json) { + snprintf(buf, sizeof(buf), "0x"); + for (; pnt < lim; pnt++) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%02x", *pnt); + json_object_string_add(json, "data", buf); + + return; + } + + vty_out(vty, "0x"); + for (i = 0; pnt < lim; pnt++, i++) { + if (i != 0 && i % 8 == 0) + vty_out(vty, " "); + vty_out(vty, "%02x", *pnt); + } + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_integer_list_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + uint8_t integer_sz, + json_object *json) +{ + json_object *json_array = NULL; + int i; + + if (json) { + json_array = json_object_new_array(); + json_object_object_add(json, "data", json_array); + } + + for (i = 0; i < (length / integer_sz); i++) { + switch (integer_sz) { + case 1: + if (json) { + json_object_array_add( + json_array, + json_object_new_int(pnt_decode8(&pnt))); + break; + } + vty_out(vty, "%s%u", i == 0 ? "" : ", ", + pnt_decode8(&pnt)); + break; + case 2: + if (json) { + json_object_array_add( + json_array, + json_object_new_int( + pnt_decode16(&pnt))); + break; + } + vty_out(vty, "%s%u", i == 0 ? "" : ", ", + pnt_decode16(&pnt)); + break; + case 4: + if (json) { + json_object_array_add( + json_array, + json_object_new_int( + pnt_decode32(&pnt))); + break; + } + vty_out(vty, "%s%u", i == 0 ? "" : ", ", + pnt_decode32(&pnt)); + break; + case 8: + if (json) { + json_object_array_add( + json_array, + json_object_new_int64( + pnt_decode64(&pnt))); + break; + } + vty_out(vty, "%s%" PRIu64, i == 0 ? "" : ", ", + pnt_decode64(&pnt)); + break; + } + } + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_integer_display(struct vty *vty, uint8_t *pnt, + uint16_t length, + json_object *json) +{ + switch (length) { + case 1: + if (json) { + json_object_int_add(json, "data", pnt_decode8(&pnt)); + break; + } + vty_out(vty, "%u\n", pnt_decode8(&pnt)); + break; + case 2: + if (json) { + json_object_int_add(json, "data", pnt_decode16(&pnt)); + break; + } + vty_out(vty, "%u\n", pnt_decode16(&pnt)); + break; + case 3: + if (json) { + json_object_int_add(json, "data", pnt_decode24(&pnt)); + break; + } + vty_out(vty, "%u\n", pnt_decode24(&pnt)); + break; + case 4: + if (json) { + json_object_int_add(json, "data", pnt_decode32(&pnt)); + break; + } + vty_out(vty, "%u\n", pnt_decode32(&pnt)); + break; + case 8: + if (json) { + json_object_int_add(json, "data", pnt_decode64(&pnt)); + break; + } + vty_out(vty, "%" PRIu64 "\n", pnt_decode64(&pnt)); + break; + } +} + +static void bgp_linkstate_tlv_ipv4_6_address_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + json_object *json) +{ + if (length == IPV4_MAX_BYTELEN) { + if (json) { + json_object_string_addf(json, "data", "%pI4", + (in_addr_t *)pnt); + return; + } + vty_out(vty, "%pI4\n", (in_addr_t *)pnt); + } else if (length == IPV6_MAX_BYTELEN) { + if (json) { + json_object_string_addf(json, "data", "%pI6", + (struct in6_addr *)pnt); + return; + } + vty_out(vty, "%pI6\n", (struct in6_addr *)pnt); + } else + bgp_linkstate_tlv_hexa_display(vty, pnt, length, json); +} + +static void bgp_linkstate_tlv_name_display(struct vty *vty, uint8_t *pnt, + uint16_t length, json_object *json) +{ + char buf[length + 1]; + int i; + + buf[0] = '\0'; + for (i = 0; i < length; i++) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%c", + pnt_decode8(&pnt)); + + if (json) + json_object_string_add(json, "data", buf); + else + vty_out(vty, "%s\n", buf); +} + +static void bgp_linkstate_tlv_msd_display(struct vty *vty, uint8_t *pnt, + uint16_t length, int indent, + json_object *json) +{ + json_object *json_array = NULL; + json_object *json_data = NULL; + int i; + + if (json) { + json_array = json_object_new_array(); + json_object_object_add(json, "data", json_array); + } + + for (i = 0; i < (length / 2); i++) { + if (json) { + json_data = json_object_new_object(); + json_object_array_add(json_array, json_data); + json_object_int_add(json_data, "type", + pnt_decode8(&pnt)); + json_object_int_add(json_data, "value", + pnt_decode8(&pnt)); + continue; + } + vty_out(vty, "\n%*sType: %u Value: %u", indent, "", + pnt_decode8(&pnt), pnt_decode8(&pnt)); + } + + if (!json) + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_bandwidth_display(struct vty *vty, uint8_t *pnt, + uint16_t length, + json_object *json) +{ + union { + float r; + uint32_t d; + } float_uint32; + + float_uint32.d = pnt_decode32(&pnt); + + if (json) { + json_object_double_add(json, "data", float_uint32.r); + json_object_string_add(json, "dataUnit", "bps"); + return; + } + vty_out(vty, "%g Mbps\n", float_uint32.r / 1000 / 1000 * 8); +} + +static void bgp_linkstate_tlv_unreserved_bandwidth_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + int indent, + json_object *json) +{ + json_object *json_data = NULL; + union { + float r; + uint32_t d; + } float_uint32; + char buf[3]; + int i; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + for (i = 0; i < MAX_CLASS_TYPE; i++) { + float_uint32.d = pnt_decode32(&pnt); + snprintf(buf, sizeof(buf), "%d", i); + json_object_double_add(json_data, buf, float_uint32.r); + } + json_object_string_add(json, "dataUnit", "bps"); + return; + } + + for (i = 0; i < MAX_CLASS_TYPE; i += 2) { + float_uint32.d = pnt_decode32(&pnt); + vty_out(vty, "\n%*s[%d]: %g Mbps ", indent, "", i, + float_uint32.r / 1000 / 1000 * 8); + float_uint32.d = pnt_decode32(&pnt); + vty_out(vty, "[%d]: %g Mbps", i + 1, + float_uint32.r / 1000 / 1000 * 8); + } + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_sid_display(struct vty *vty, uint8_t *pnt, + uint16_t length, uint16_t type, + int indent, json_object *json) +{ + json_object *json_data = NULL; + char buf[11]; + uint32_t sid; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + } + + if (json) { + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, 1); + json_object_string_add(json_data, "flags", buf); + } else { + vty_out(vty, "\n%*sFlags: ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, 1, json); + } + + if (type == BGP_LS_TLV_PREFIX_SID) { + if (json) + json_object_int_add(json_data, "algorithm", + pnt_decode8(&pnt)); + else + vty_out(vty, "%*sAlgorithm: %u\n", indent, "", + pnt_decode8(&pnt)); + } else { + if (json) + json_object_int_add(json_data, "weight", + pnt_decode8(&pnt)); + else + vty_out(vty, "%*sWeight: %u\n", indent, "", + pnt_decode8(&pnt)); + } + + pnt += 2; /* ignore reserved 2 bytes */ + + if (type == BGP_LS_TLV_LAN_ADJACENCY_SID) { + vty_out(vty, "%*sNeighbor ID:", indent, ""); + if (length == 11 || length == 12) { + /* OSPF Router-ID */ + if (json) + json_object_string_addf(json_data, "neighborId", + "%pI4\n", + (in_addr_t *)pnt); + else + vty_out(vty, "%pI4\n", (in_addr_t *)pnt); + pnt += 4; + } else { + /* IS-IS System-ID */ + if (json) + json_object_string_addf(json_data, "neighborId", + "%pSY\n", + (uint8_t *)pnt); + else + vty_out(vty, "%pSY\n", (uint8_t *)pnt); + pnt += 6; + } + } + + if (length == 7 || length == 11 || length == 13) + sid = pnt_decode24(&pnt); + else + sid = pnt_decode32(&pnt); + + if (json) + json_object_int_add(json_data, "sid", sid); + else + vty_out(vty, "%*sSID: %u\n", indent, "", sid); +} + +static void bgp_linkstate_tlv_range_display(struct vty *vty, uint8_t *pnt, + uint16_t length, int indent, + json_object *json) +{ + json_object *json_data = NULL; + char buf[11]; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, 1); + json_object_string_add(json_data, "flags", buf); + } else { + vty_out(vty, "\n%*sFlags: ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, 1, json); + } + pnt++; /* ignore reserved byte */ + if (json) + json_object_int_add(json_data, "rangeSize", pnt_decode16(&pnt)); + else + vty_out(vty, "%*sRange Size: %u\n", indent, "", + pnt_decode16(&pnt)); + + /* RFC9085 2.3.5 is unclear. Just display a hexa dump */ + bgp_linkstate_tlv_hexa_display(vty, pnt, length - 4, json_data); +} + +static void bgp_linkstate_tlv_delay_display(struct vty *vty, uint8_t *pnt, + uint16_t length, uint16_t type, + json_object *json) +{ + json_object *json_data = NULL; + uint32_t tmp32; + bool anomalous; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + } + + tmp32 = pnt_decode32(&pnt); + anomalous = !!(tmp32 & TE_EXT_ANORMAL); + + if (json) + json_object_boolean_add(json_data, "anomalous", anomalous); + else if (anomalous) + vty_out(vty, "Anomalous "); + + if (json) + json_object_string_add(json, "dataUnit", "microseconds"); + + if (type == BGP_LS_TLV_UNIDIRECTIONAL_LINK_DELAY || + type == BGP_LS_TLV_UNIDIRECTIONAL_DELAY_VARIATION) { + if (json) + json_object_int_add(json_data, "delay", + tmp32 & TE_EXT_MASK); + else + vty_out(vty, "%u microseconds\n", tmp32 & TE_EXT_MASK); + } else if (type == BGP_LS_TLV_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY) { + if (json) { + json_object_int_add(json_data, "minDelay", + tmp32 & TE_EXT_MASK); + json_object_int_add(json_data, "maxDelay", + pnt_decode32(&pnt) & TE_EXT_MASK); + } else { + vty_out(vty, "%u", tmp32 & TE_EXT_MASK); + vty_out(vty, "/%u microseconds\n", + pnt_decode32(&pnt) & TE_EXT_MASK); + } + } +} + +static void bgp_linkstate_tlv_unidirectional_link_loss_display( + struct vty *vty, uint8_t *pnt, uint16_t length, json_object *json) +{ + json_object *json_data = NULL; + uint32_t tmp32; + float value; + bool anomalous; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + } + + tmp32 = pnt_decode32(&pnt); + + anomalous = !!(tmp32 & TE_EXT_ANORMAL); + value = ((float)(tmp32 & TE_EXT_MASK)) * LOSS_PRECISION; + + if (json) { + json_object_boolean_add(json_data, "anomalous", anomalous); + json_object_double_add(json_data, "lossPercent", value); + return; + } + + if (anomalous) + vty_out(vty, "Anomalous "); + vty_out(vty, "%g%%\n", value); +} + +static void bgp_linkstate_tlv_asla_display(struct vty *vty, uint8_t *pnt, + uint16_t length, int indent, + json_object *json) +{ + json_object *json_data = NULL; + char buf[290]; + uint8_t sabm_len, udabm_len; + struct bgp_attr_ls attr_ls; + uint8_t *orig_pnt = pnt; + + sabm_len = pnt_decode8(&pnt); + udabm_len = pnt_decode8(&pnt); + pnt += 2; /* ignore reserved 2 bytes */ + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, + sabm_len); + json_object_string_add(json_data, "sabmFlags", buf); + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, + udabm_len); + json_object_string_add(json_data, "udabmFlags", buf); + } else { + vty_out(vty, "\n%*sSABM Flags : ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, sabm_len, + json); + vty_out(vty, "%*sUDABM Flags: ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, udabm_len, + json); + } + + attr_ls.length = length - (pnt - orig_pnt); + attr_ls.data = pnt; + + bgp_linkstate_tlv_attribute_display(vty, &attr_ls, indent, json_data); +} + +static void bgp_linkstate_tlv_sr_range_display(struct vty *vty, uint8_t *pnt, + uint16_t length, int indent, + json_object *json) +{ + json_object *json_data = NULL; + struct bgp_attr_ls attr_ls; + uint8_t *orig_pnt = pnt; + char buf[11]; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, 1); + json_object_string_add(json_data, "flags", buf); + } else { + vty_out(vty, "\n%*sFlags: ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, 1, json); + } + pnt++; /* ignore reserved byte */ + if (json) + json_object_int_add(json_data, "range", pnt_decode24(&pnt)); + else + vty_out(vty, "%*sRange: %u\n", indent, "", pnt_decode24(&pnt)); + + attr_ls.length = length - (pnt - orig_pnt); + attr_ls.data = pnt; + + bgp_linkstate_tlv_attribute_display(vty, &attr_ls, indent, json_data); +} + +static void bgp_linkstate_tlv_sid_label_display(struct vty *vty, uint8_t *pnt, + uint16_t length, + json_object *json) +{ + json_object *json_data = NULL; + + /* RFC9085 + * If the length is set to 3, then the 20 rightmost bits + * represent a label (the total TLV size is 7), and the 4 + * leftmost bits are set to 0. If the length is set to 4, then + * the value represents a 32-bit SID (the total TLV size is 8). + */ + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + } + + if (length == 3) { + if (json) + json_object_int_add(json_data, "fromLabel", + pnt_decode24(&pnt) & 0x0FFFFF); + else + vty_out(vty, "From Label: %u\n", + pnt_decode24(&pnt) & 0x0FFFFF); + } else { + if (json) + json_object_int_add(json_data, "fromIndex", + pnt_decode32(&pnt)); + else + vty_out(vty, "From Index: %u\n", pnt_decode32(&pnt)); + } +} + +static void bgp_linkstate_tlv_flexible_algorithm_definition_display( + struct vty *vty, uint8_t *pnt, uint16_t length, int indent, + json_object *json) +{ + json_object *json_data = NULL; + struct bgp_attr_ls attr_ls; + uint8_t *orig_pnt = pnt; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + json_object_int_add(json_data, "flexAlgo", pnt_decode8(&pnt)); + json_object_int_add(json_data, "metricType", pnt_decode8(&pnt)); + json_object_int_add(json_data, "calcType", pnt_decode8(&pnt)); + json_object_int_add(json_data, "priority", pnt_decode8(&pnt)); + } else { + vty_out(vty, "\n%*sFlex-Algo: %u\n", indent, "", + pnt_decode8(&pnt)); + vty_out(vty, "%*sMetric-Type: %u\n", indent, "", + pnt_decode8(&pnt)); + vty_out(vty, "%*sCalc-Type: %u\n", indent, "", + pnt_decode8(&pnt)); + vty_out(vty, "%*sPriority: %u\n", indent, "", + pnt_decode8(&pnt)); + } + + attr_ls.length = length - (pnt - orig_pnt); + attr_ls.data = pnt; + + bgp_linkstate_tlv_attribute_display(vty, &attr_ls, indent, json_data); +} + +static void bgp_linkstate_tlv_flexible_algorithm_prefix_metric_display( + struct vty *vty, uint8_t *pnt, uint16_t length, int indent, + json_object *json) +{ + json_object *json_data = NULL; + char buf[11]; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + json_object_int_add(json_data, "flexAlgo", pnt_decode8(&pnt)); + pnt = bgp_linkstate_tlv_binary_string(buf, sizeof(buf), pnt, 1); + json_object_string_add(json_data, "flags", buf); + pnt += 2; /* ignore reserved 2 bytes */ + json_object_int_add(json_data, "metric", pnt_decode32(&pnt)); + return; + } + + vty_out(vty, "\n%*sFlex-Algo: %u\n", indent, "", pnt_decode8(&pnt)); + vty_out(vty, "%*sFlags: ", indent, ""); + pnt = bgp_linkstate_tlv_binary_display(vty, pnt, 1, json); + pnt += 2; /* ignore reserved 2 bytes */ + vty_out(vty, "%*sMetric: %u\n", indent, "", pnt_decode32(&pnt)); +} + +static void bgp_linkstate_tlv_opaque_display(struct vty *vty, uint8_t *pnt, + uint16_t length, int indent, + json_object *json) +{ + uint16_t sub_type = 0, sub_length = 0; + json_object *json_data = NULL; + json_object *json_tlv = NULL; + uint8_t *lim = pnt + length; + bool ospf_tlv_header; + char tlv_type[6]; + int i; + + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + } + + /* Opaque TLV carries original IGP TLVs + * IS-IS TLV header is 1 byte each for the TLV type and length. + * OSPF TLV header is 2 bytes each for the TLV type and length + * but the TLV type values are far from exceeding 255. + * Assume TLV header format is the OSPF one if first value is 0x00. + */ + ospf_tlv_header = (*pnt == 0); + + for (; pnt < lim; pnt += sub_length) { + if (ospf_tlv_header) { + sub_type = pnt_decode16(&pnt); + sub_length = pnt_decode16(&pnt); + } else { + sub_type = pnt_decode8(&pnt); + sub_length = pnt_decode8(&pnt); + } + + if (json) { + snprintf(tlv_type, sizeof(tlv_type), "%u", sub_type); + json_tlv = json_object_new_object(); + json_object_object_add(json_data, tlv_type, json_tlv); + + json_object_int_add(json_tlv, "type", sub_type); + json_object_int_add(json_tlv, "length", sub_length); + + if (pnt + sub_length > lim) { + json_object_string_addf( + json_tlv, "error", + "too high length received: %u", + sub_length); + break; + } + + bgp_linkstate_tlv_hexa_display(vty, pnt, sub_length, + json_tlv); + continue; + } + + vty_out(vty, "\n%*sTLV type %u: 0x", indent, "", sub_type); + + if (pnt + sub_length > lim) { + vty_out(vty, "Bad length received: %u\n", sub_length); + break; + } + + for (i = 0; i < sub_length; i++) { + if (i != 0 && i % 8 == 0) + vty_out(vty, " "); + vty_out(vty, "%02x", *pnt); + } + } + if (!json) + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_rtm_capability_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + json_object *json) +{ + json_object *json_data = NULL; + json_object *json_array = NULL; + uint8_t *lim = pnt + length; + uint8_t tmp8; + char buf[11]; + int i; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + + tmp8 = pnt_decode8(&pnt); + snprintf(buf, sizeof(buf), "0b"); + for (i = 7; i >= 5; i--) + snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), + "%d", (tmp8 >> i) & 1); + json_object_string_add(json_data, "flags", buf); + + if (length > 8) { + json_array = json_object_new_array(); + json_object_object_add(json, "values", json_array); + for (i = 0; pnt < lim; pnt++, i++) { + if (i % 8 == 0) { + if (i != 0) + json_object_array_add( + json_array, + json_object_new_string( + buf)); + snprintf(buf, sizeof(buf), "0x"); + } + if (i == 0) + snprintf(buf + strlen(buf), + sizeof(buf) - strlen(buf), + "%02x", tmp8 & 0x1F); + else + snprintf(buf + strlen(buf), + sizeof(buf) - strlen(buf), + "%02x", *pnt); + } + if (strlen(buf) > 2) /* do not only contain 0x */ + json_object_array_add( + json_array, + json_object_new_string(buf)); + } else { + snprintf(buf, sizeof(buf), "0x"); + for (i = 0; pnt < lim; pnt++, i++) { + if (i == 0) + snprintf(buf + strlen(buf), + sizeof(buf) - strlen(buf), + "%02x", tmp8 & 0x1F); + else + snprintf(buf + strlen(buf), + sizeof(buf) - strlen(buf), + "%02x", *pnt); + } + json_object_string_add(json_data, "values", buf); + } + return; + } + + tmp8 = pnt_decode8(&pnt); + + vty_out(vty, "Flags: 0b"); + for (i = 7; i >= 5; i--) + vty_out(vty, "%d", (tmp8 >> i) & 1); + vty_out(vty, " Values: 0x%02x", tmp8 & 0x1F); + for (; pnt < lim; pnt++) + vty_out(vty, "%02x", *pnt); + vty_out(vty, "\n"); +} + +static void bgp_linkstate_tlv_l2_member_attributes_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + int indent, + json_object *json) +{ + json_object *json_data = NULL; + struct bgp_attr_ls attr_ls; + uint8_t *orig_pnt = pnt; + + if (json) { + json_data = json_object_new_object(); + json_object_object_add(json, "data", json_data); + json_object_string_addf(json_data, "descriptor", "0x%02x", + pnt_decode32(&pnt)); + } else + vty_out(vty, "Descriptor: 0x%02x\n", pnt_decode32(&pnt)); + + attr_ls.length = length - (pnt - orig_pnt); + attr_ls.data = pnt; + + if (attr_ls.length == 0) + /* No Sub-TLV */ + return; + + bgp_linkstate_tlv_attribute_display(vty, &attr_ls, indent, json_data); +} + +static void bgp_linkstate_tlv_isis_area_indentifier_display(struct vty *vty, + uint8_t *pnt, + uint16_t length, + json_object *json) +{ + struct iso_address addr; + + addr.addr_len = length; + memcpy(addr.area_addr, pnt, length); + + if (json) + json_object_string_addf(json, "data", "%pIS", &addr); + else + vty_out(vty, "%pIS\n", &addr); +} + +static void +bgp_linkstate_tlv_attribute_value_display(struct vty *vty, uint8_t *pnt, + uint16_t type, uint16_t length, + int indent, json_object *json) +{ + if (!bgp_ls_tlv_check_size(type, length)) { + bgp_linkstate_tlv_hexa_display(vty, pnt, length, json); + return; + } + + switch (type) { + case BGP_LS_TLV_SRMS_PREFERENCE: + case BGP_LS_TLV_IGP_METRIC: + case BGP_LS_TLV_PREFIX_METRIC: + case BGP_LS_TLV_TE_DEFAULT_METRIC: + bgp_linkstate_tlv_integer_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_SR_ALGORITHM: + bgp_linkstate_tlv_integer_list_display(vty, pnt, length, 1, + json); + break; + case BGP_LS_TLV_MULTI_TOPOLOGY_ID: + bgp_linkstate_tlv_integer_list_display(vty, pnt, length, 2, + json); + break; + case BGP_LS_TLV_IGP_ROUTE_TAG: + case BGP_LS_TLV_SHARED_RISK_LINK_GROUP: + case BGP_LS_TLV_S_BFD_DISCRIMINATORS: + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_SRLG: + bgp_linkstate_tlv_integer_list_display(vty, pnt, length, 4, + json); + break; + case BGP_LS_TLV_IGP_EXTENDED_ROUTE_TAG: + bgp_linkstate_tlv_integer_list_display(vty, pnt, length, 8, + json); + break; + case BGP_LS_TLV_IPV4_ROUTER_ID_OF_LOCAL_NODE: + case BGP_LS_TLV_IPV6_ROUTER_ID_OF_LOCAL_NODE: + case BGP_LS_TLV_IPV4_ROUTER_ID_OF_REMOTE_NODE: + case BGP_LS_TLV_IPV6_ROUTER_ID_OF_REMOTE_NODE: + case BGP_LS_TLV_OSPF_FORWARDING_ADDRESS: + case BGP_LS_TLV_SOURCE_OSPF_ROUTER_ID: + case BGP_LS_TLV_SOURCE_ROUTER_IDENTIFIER: + bgp_linkstate_tlv_ipv4_6_address_display(vty, pnt, length, + json); + break; + case BGP_LS_TLV_NODE_NAME: + case BGP_LS_TLV_LINK_NAME: + bgp_linkstate_tlv_name_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_NODE_FLAG_BITS: + case BGP_LS_TLV_IGP_FLAGS: + case BGP_LS_TLV_PREFIX_ATTRIBUTES_FLAGS: + case BGP_LS_TLV_MPLS_PROTOCOL_MASK: + case BGP_LS_TLV_LINK_PROTECTION_TYPE: + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION_FLAGS: + bgp_linkstate_tlv_binary_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_ADMINISTRATIVE_GROUP: + case BGP_LS_TLV_EXTENDED_ADMINISTRATIVE_GROUP: + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_ANY_AFFINITY: + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ANY_AFFINITY: + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ALL_AFFINITY: + bgp_linkstate_tlv_hexa_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_OPAQUE_NODE_ATTRIBUTE: + case BGP_LS_TLV_OPAQUE_LINK_ATTRIBUTE: + case BGP_LS_TLV_OPAQUE_PREFIX_ATTRIBUTE: + bgp_linkstate_tlv_opaque_display(vty, pnt, length, indent + 2, + json); + break; + case BGP_LS_TLV_NODE_MSD: + case BGP_LS_TLV_LINK_MSD: + bgp_linkstate_tlv_msd_display(vty, pnt, length, indent + 2, + json); + break; + case BGP_LS_TLV_MAXIMUM_LINK_BANDWIDTH: + case BGP_LS_TLV_MAX_RESERVABLE_LINK_BANDWIDTH: + case BGP_LS_TLV_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH: + case BGP_LS_TLV_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH: + case BGP_LS_TLV_UNIDIRECTIONAL_UTILIZED_BANDWIDTH: + bgp_linkstate_tlv_bandwidth_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_UNRESERVED_BANDWIDTH: + bgp_linkstate_tlv_unreserved_bandwidth_display( + vty, pnt, length, indent + 2, json); + break; + case BGP_LS_TLV_IS_IS_AREA_IDENTIFIER: + bgp_linkstate_tlv_isis_area_indentifier_display(vty, pnt, + length, json); + break; + case BGP_LS_TLV_PREFIX_SID: + case BGP_LS_TLV_ADJACENCY_SID: + case BGP_LS_TLV_LAN_ADJACENCY_SID: + case BGP_LS_TLV_PEERNODE_SID: + case BGP_LS_TLV_PEERADJ_SID: + case BGP_LS_TLV_PEERSET_SID: + bgp_linkstate_tlv_sid_display(vty, pnt, length, type, + indent + 2, json); + break; + case BGP_LS_TLV_RANGE: + bgp_linkstate_tlv_range_display(vty, pnt, length, indent + 2, + json); + break; + case BGP_LS_TLV_UNIDIRECTIONAL_LINK_DELAY: + case BGP_LS_TLV_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY: + case BGP_LS_TLV_UNIDIRECTIONAL_DELAY_VARIATION: + bgp_linkstate_tlv_delay_display(vty, pnt, length, type, json); + break; + case BGP_LS_TLV_UNIDIRECTIONAL_LINK_LOSS: + bgp_linkstate_tlv_unidirectional_link_loss_display( + vty, pnt, length, json); + break; + case BGP_LS_TLV_APPLICATION_SPECIFIC_LINK_ATTRIBUTES: + bgp_linkstate_tlv_asla_display(vty, pnt, length, indent + 2, + json); + break; + case BGP_LS_TLV_SR_CAPABILITIES: + case BGP_LS_TLV_SR_LOCAL_BLOCK: + bgp_linkstate_tlv_sr_range_display(vty, pnt, length, indent + 2, + json); + break; + case BGP_LS_TLV_SID_LABEL: + bgp_linkstate_tlv_sid_label_display(vty, pnt, length, json); + break; + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION: + bgp_linkstate_tlv_flexible_algorithm_definition_display( + vty, pnt, length, indent + 2, json); + break; + case BGP_LS_TLV_FLEXIBLE_ALGORITHM_PREFIX_METRIC: + bgp_linkstate_tlv_flexible_algorithm_prefix_metric_display( + vty, pnt, length, indent + 2, json); + break; + case BGP_LS_TLV_GRACEFUL_LINK_SHUTDOWN_TLV: + if (!json) + vty_out(vty, "Enabled\n"); /* TLV must have no data */ + break; + case BGP_LS_TLV_L2_BUNDLE_MEMBER_ATTRIBUTES: + bgp_linkstate_tlv_l2_member_attributes_display( + vty, pnt, length, indent + 2, json); + break; + case BGP_LS_TLV_RTM_CAPABILITY: + bgp_linkstate_tlv_rtm_capability_display(vty, pnt, length, + json); + break; + default: + bgp_linkstate_tlv_hexa_display(vty, pnt, length, json); + } +} + +void bgp_linkstate_tlv_attribute_display(struct vty *vty, + struct bgp_attr_ls *attr_ls, + int indent, json_object *json) +{ + uint8_t *pnt = attr_ls->data; + uint8_t *lim = pnt + attr_ls->length; + uint16_t length = 0; + uint16_t type = 0; + char tlv_type[6]; + json_object *json_tlv = NULL; + + for (; pnt < lim; pnt += length) { + type = pnt_decode16(&pnt); + length = pnt_decode16(&pnt); + + if (json) { + snprintf(tlv_type, sizeof(tlv_type), "%u", type); + + json_tlv = json_object_new_object(); + json_object_object_add(json, tlv_type, json_tlv); + + if (type < BGP_LS_TLV_MAX && + bgp_linkstate_tlv_infos[type].descr != NULL) + json_object_string_add( + json_tlv, "description", + bgp_linkstate_tlv_infos[type].descr); + + json_object_int_add(json_tlv, "type", type); + json_object_int_add(json_tlv, "length", length); + + if (pnt + length > lim) { + json_object_string_addf( + json_tlv, "error", + "too high length received: %u", length); + break; + } + if (type < BGP_LS_TLV_MAX && + bgp_linkstate_tlv_infos[type].descr != NULL && + !bgp_ls_tlv_check_size(type, length)) + json_object_string_addf( + json_tlv, "error", + "unexpected length received: %u", + length); + } else { + if (type < BGP_LS_TLV_MAX && + bgp_linkstate_tlv_infos[type].descr != NULL) + vty_out(vty, "%*s%s: ", indent, "", + bgp_linkstate_tlv_infos[type].descr); + else + vty_out(vty, "%*sTLV type %u: ", indent, "", + type); + + if (pnt + length > lim) { + vty_out(vty, "Bad length received: %u\n", + length); + break; + } + } + + bgp_linkstate_tlv_attribute_value_display( + vty, pnt, type, length, indent, json_tlv); + } +} diff --git a/bgpd/bgp_linkstate_tlv.h b/bgpd/bgp_linkstate_tlv.h new file mode 100644 index 0000000000..ad3b2570d6 --- /dev/null +++ b/bgpd/bgp_linkstate_tlv.h @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State TLV Serializer/Deserializer header + * Copyright 2023 6WIND S.A. + */ + +#ifndef BGP_LINKSTATE_TLV_H +#define BGP_LINKSTATE_TLV_H + +/* RFC7752 Link-State NLRI Protocol-ID values + * +-------------+----------------------------------+ + * | Protocol-ID | NLRI information source protocol | + * +-------------+----------------------------------+ + * | 1 | IS-IS Level 1 | + * | 2 | IS-IS Level 2 | + * | 3 | OSPFv2 | + * | 4 | Direct | + * | 5 | Static configuration | + * | 6 | OSPFv3 | + * +-------------+----------------------------------+ + */ + +enum bgp_ls_nlri_proto { + BGP_LS_NLRI_PROTO_ID_UNKNOWN = 0, + BGP_LS_NLRI_PROTO_ID_IS_IS_LEVEL_1 = 1, + BGP_LS_NLRI_PROTO_ID_IS_IS_LEVEL_2 = 2, + BGP_LS_NLRI_PROTO_ID_OSPF = 3, + BGP_LS_NLRI_PROTO_ID_DIRECT = 4, + BGP_LS_NLRI_PROTO_ID_STATIC = 5, + BGP_LS_NLRI_PROTO_ID_OSPFv3 = 6, +}; + +/* + * List of BGP Link-State TLVs extracted from + * https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv + * + * Retrieved on 2023-01-03 + * + * The following bash command was used to convert the list: + * sed -e 's| (.\+)||g' tmp \ + * | awk -F'\t' '($1 ~ /^[0-9]+$/) {gsub(/(\/|-| |\.)/,"_",$2); printf + * "\tBGP_LS_TLV_"toupper($2)" = "$1", \/\* "$4" \*\/\n"}' \ + * | grep -v UNASSIGNED \ + * | sed -e 's/\[//g;s/\]//g' + * + */ + +enum bgp_linkstate_tlv { + BGP_LS_TLV_LOCAL_NODE_DESCRIPTORS = 256, /* RFC7752, Section 3.2.1.2 */ + BGP_LS_TLV_REMOTE_NODE_DESCRIPTORS = 257, /* RFC7752, Section 3.2.1.3 */ + BGP_LS_TLV_LINK_LOCAL_REMOTE_IDENTIFIERS = + 258, /* RFC5307, Section 1.1 */ + BGP_LS_TLV_IPV4_INTERFACE_ADDRESS = 259, /* RFC5305, Section 3.2 */ + BGP_LS_TLV_IPV4_NEIGHBOR_ADDRESS = 260, /* RFC5305, Section 3.3 */ + BGP_LS_TLV_IPV6_INTERFACE_ADDRESS = 261, /* RFC6119, Section 4.2 */ + BGP_LS_TLV_IPV6_NEIGHBOR_ADDRESS = 262, /* RFC6119, Section 4.3 */ + BGP_LS_TLV_MULTI_TOPOLOGY_ID = 263, /* RFC7752, Section 3.2.1.5 */ + BGP_LS_TLV_OSPF_ROUTE_TYPE = 264, /* RFC7752, Section 3.2.3 */ + BGP_LS_TLV_IP_REACHABILITY_INFORMATION = + 265, /* RFC7752, Section 3.2.3 */ + BGP_LS_TLV_NODE_MSD = 266, /* RFC8814 */ + BGP_LS_TLV_LINK_MSD = 267, /* RFC8814 */ + BGP_LS_TLV_AUTONOMOUS_SYSTEM = 512, /* RFC7752, Section 3.2.1.4 */ + BGP_LS_TLV_BGP_LS_IDENTIFIER = 513, /* RFC7752, Section 3.2.1.4 */ + BGP_LS_TLV_OSPF_AREA_ID = 514, /* RFC7752, Section 3.2.1.4 */ + BGP_LS_TLV_IGP_ROUTER_ID = 515, /* RFC7752, Section 3.2.1.4 */ + BGP_LS_TLV_BGP_ROUTER_ID = 516, /* RFC9086 */ + BGP_LS_TLV_BGP_CONFEDERATION_MEMBER = 517, /* RFC9086 */ + BGP_LS_TLV_SRV6_SID_INFORMATION_TLV = + 518, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_TUNNEL_ID_TLV = + 550, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_LSP_ID_TLV = 551, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_IPV4_6_TUNNEL_HEAD_END_ADDRESS_TLV = + 552, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_IPV4_6_TUNNEL_TAIL_END_ADDRESS_TLV = + 553, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_POLICY_CP_DESCRIPTOR_TLV = + 554, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_MPLS_LOCAL_CROSS_CONNECT_TLV = + 555, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_MPLS_CROSS_CONNECT_INTERFACE_TLV = + 556, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_MPLS_CROSS_CONNECT_FEC_TLV = + 557, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_NODE_FLAG_BITS = 1024, /* RFC7752, Section 3.3.1.1 */ + BGP_LS_TLV_OPAQUE_NODE_ATTRIBUTE = 1025, /* RFC7752, Section 3.3.1.5 */ + BGP_LS_TLV_NODE_NAME = 1026, /* RFC7752, Section 3.3.1.3 */ + BGP_LS_TLV_IS_IS_AREA_IDENTIFIER = 1027, /* RFC7752, Section 3.3.1.2 */ + BGP_LS_TLV_IPV4_ROUTER_ID_OF_LOCAL_NODE = + 1028, /* RFC5305, Section 4.3 */ + BGP_LS_TLV_IPV6_ROUTER_ID_OF_LOCAL_NODE = + 1029, /* RFC6119, Section 4.1 */ + BGP_LS_TLV_IPV4_ROUTER_ID_OF_REMOTE_NODE = + 1030, /* RFC5305, Section 4.3 */ + BGP_LS_TLV_IPV6_ROUTER_ID_OF_REMOTE_NODE = + 1031, /* RFC6119, Section 4.1 */ + BGP_LS_TLV_S_BFD_DISCRIMINATORS = 1032, /* RFC9247 */ + BGP_LS_TLV_UNASSIGNED = 1033, /* */ + BGP_LS_TLV_SR_CAPABILITIES = 1034, /* RFC9085, Section 2.1.2 */ + BGP_LS_TLV_SR_ALGORITHM = 1035, /* RFC9085, Section 2.1.3 */ + BGP_LS_TLV_SR_LOCAL_BLOCK = 1036, /* RFC9085, Section 2.1.4 */ + BGP_LS_TLV_SRMS_PREFERENCE = 1037, /* RFC9085, Section 2.1.5 */ + BGP_LS_TLV_SRV6_CAPABILITIES_TLV = + 1038, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION = 1039, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_ANY_AFFINITY = 1040, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ANY_AFFINITY = 1041, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_INCLUDE_ALL_AFFINITY = 1042, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_DEFINITION_FLAGS = 1043, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_PREFIX_METRIC = 1044, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_EXCLUDE_SRLG = 1045, /* RFC9351 */ + BGP_LS_TLV_FLEXIBLE_ALGORITHM_UNSUPPORTED = 1046, /* RFC9351 */ + BGP_LS_TLV_ADMINISTRATIVE_GROUP = 1088, /* RFC5305, Section 3.1 */ + BGP_LS_TLV_MAXIMUM_LINK_BANDWIDTH = 1089, /* RFC5305, Section 3.4 */ + BGP_LS_TLV_MAX_RESERVABLE_LINK_BANDWIDTH = + 1090, /* RFC5305, Section 3.5 */ + BGP_LS_TLV_UNRESERVED_BANDWIDTH = 1091, /* RFC5305, Section 3.6 */ + BGP_LS_TLV_TE_DEFAULT_METRIC = 1092, /* RFC7752, Section 3.3.2.3 */ + BGP_LS_TLV_LINK_PROTECTION_TYPE = 1093, /* RFC5307, Section 1.2 */ + BGP_LS_TLV_MPLS_PROTOCOL_MASK = 1094, /* RFC7752, Section 3.3.2.2 */ + BGP_LS_TLV_IGP_METRIC = 1095, /* RFC7752, Section 3.3.2.4 */ + BGP_LS_TLV_SHARED_RISK_LINK_GROUP = 1096, /* RFC7752, Section 3.3.2.5 */ + BGP_LS_TLV_OPAQUE_LINK_ATTRIBUTE = 1097, /* RFC7752, Section 3.3.2.6 */ + BGP_LS_TLV_LINK_NAME = 1098, /* RFC7752, Section 3.3.2.7 */ + BGP_LS_TLV_ADJACENCY_SID = 1099, /* RFC9085, Section 2.2.1 */ + BGP_LS_TLV_LAN_ADJACENCY_SID = 1100, /* RFC9085, Section 2.2.2 */ + BGP_LS_TLV_PEERNODE_SID = 1101, /* RFC9086 */ + BGP_LS_TLV_PEERADJ_SID = 1102, /* RFC9086 */ + BGP_LS_TLV_PEERSET_SID = 1103, /* RFC9086 */ + BGP_LS_TLV_RTM_CAPABILITY = 1105, /* RFC8169 */ + BGP_LS_TLV_SRV6_END_X_SID_TLV = + 1106, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_IS_IS_SRV6_LAN_END_X_SID_TLV = + 1107, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_OSPFV3_SRV6_LAN_END_X_SID_TLV = + 1108, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_UNIDIRECTIONAL_LINK_DELAY = 1114, /* RFC8571 */ + BGP_LS_TLV_MIN_MAX_UNIDIRECTIONAL_LINK_DELAY = 1115, /* RFC8571 */ + BGP_LS_TLV_UNIDIRECTIONAL_DELAY_VARIATION = 1116, /* RFC8571 */ + BGP_LS_TLV_UNIDIRECTIONAL_LINK_LOSS = 1117, /* RFC8571 */ + BGP_LS_TLV_UNIDIRECTIONAL_RESIDUAL_BANDWIDTH = 1118, /* RFC8571 */ + BGP_LS_TLV_UNIDIRECTIONAL_AVAILABLE_BANDWIDTH = 1119, /* RFC8571 */ + BGP_LS_TLV_UNIDIRECTIONAL_UTILIZED_BANDWIDTH = 1120, /* RFC8571 */ + BGP_LS_TLV_GRACEFUL_LINK_SHUTDOWN_TLV = 1121, /* RFC8379 */ + BGP_LS_TLV_APPLICATION_SPECIFIC_LINK_ATTRIBUTES = 1122, /* RFC9294 */ + BGP_LS_TLV_IGP_FLAGS = 1152, /* RFC7752, Section 3.3.3.1 */ + BGP_LS_TLV_IGP_ROUTE_TAG = 1153, /* RFC5130 */ + BGP_LS_TLV_IGP_EXTENDED_ROUTE_TAG = 1154, /* RFC5130 */ + BGP_LS_TLV_PREFIX_METRIC = 1155, /* RFC5305 */ + BGP_LS_TLV_OSPF_FORWARDING_ADDRESS = 1156, /* RFC2328 */ + BGP_LS_TLV_OPAQUE_PREFIX_ATTRIBUTE = + 1157, /* RFC7752, Section 3.3.3.6 */ + BGP_LS_TLV_PREFIX_SID = 1158, /* RFC9085, Section 2.3.1 */ + BGP_LS_TLV_RANGE = 1159, /* RFC9085, Section 2.3.5 */ + BGP_LS_TLV_IS_IS_FLOOD_REFLECTION = + 1160, /* draft-ietf-idr-bgp-ls-isis-flood-reflection-02 */ + BGP_LS_TLV_SID_LABEL = 1161, /* RFC9085, Section 2.1.1 */ + BGP_LS_TLV_SRV6_LOCATOR_TLV = + 1162, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_PREFIX_ATTRIBUTES_FLAGS = 1170, /* RFC9085, Section 2.3.2 */ + BGP_LS_TLV_SOURCE_ROUTER_IDENTIFIER = 1171, /* RFC9085, Section 2.3.3 */ + BGP_LS_TLV_L2_BUNDLE_MEMBER_ATTRIBUTES = + 1172, /* RFC9085, Section 2.2.3 */ + BGP_LS_TLV_EXTENDED_ADMINISTRATIVE_GROUP = 1173, /* RFC9104 */ + BGP_LS_TLV_SOURCE_OSPF_ROUTER_ID = 1174, /* RFC9085, Section 2.3.4 */ + BGP_LS_TLV_MPLS_TE_POLICY_STATE_TLV = + 1200, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_BSID_TLV = + 1201, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_CP_STATE_TLV = + 1202, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_CP_NAME_TLV = + 1203, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_CP_CONSTRAINTS_TLV = + 1204, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_SEGMENT_LIST_TLV = + 1205, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_SEGMENT_SUB_TLV = + 1206, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_SEGMENT_LIST_METRIC_SUB_TLV = + 1207, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_AFFINITY_CONSTRAINT_SUB_TLV = + 1208, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_SRLG_CONSTRAINT_SUB_TLV = + 1209, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_BANDWIDTH_CONSTRAINT_SUB_TLV = + 1210, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_DISJOINT_GROUP_CONSTRAINT_SUB_TLV = + 1211, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SRV6_BSID_TLV = + 1212, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SR_POLICY_NAME_TLV = + 1213, /* draft-ietf-idr-te-lsp-distribution-17 */ + BGP_LS_TLV_SRV6_ENDPOINT_FUNCTION_TLV = + 1250, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_SRV6_BGP_PEER_NODE_SID_TLV = + 1251, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_SRV6_SID_STRUCTURE_TLV = + 1252, /* draft-ietf-idr-bgpls-srv6-ext-08 */ + BGP_LS_TLV_MAX = 1253, /* max TLV value for table size*/ +}; + +/* RFC7752 #3.2.1.4 IGP router-ID */ +enum bgp_ls_nlri_node_descr_ig_router_id_size { + BGP_LS_TLV_IGP_ROUTER_ID_ISIS_NON_PSEUDOWIRE_SIZE = 6, + BGP_LS_TLV_IGP_ROUTER_ID_ISIS_PSEUDOWIRE_SIZE = 7, + BGP_LS_TLV_IGP_ROUTER_ID_OSPF_NON_PSEUDOWIRE_SIZE = 4, + BGP_LS_TLV_IGP_ROUTER_ID_OSPF_PSEUDOWIRE_SIZE = 8, +}; + +extern int bgp_nlri_parse_linkstate(struct peer *peer, struct attr *attr, + struct bgp_nlri *packet, int withdraw); + +extern void bgp_nlri_encode_linkstate(struct stream *s, const struct prefix *p); + +extern char *bgp_linkstate_nlri_prefix_display(char *buf, size_t size, + uint16_t nlri_type, + uintptr_t prefix, uint16_t len); +extern void bgp_linkstate_nlri_prefix_json(json_object *json, + uint16_t nlri_type, uintptr_t prefix, + uint16_t len); +extern void bgp_linkstate_tlv_attribute_display(struct vty *vty, + struct bgp_attr_ls *attr_ls, + int indent, json_object *json); + +#endif /* BGP_LINKSTATE_TLV_H */ diff --git a/bgpd/bgp_linkstate_vty.c b/bgpd/bgp_linkstate_vty.c new file mode 100644 index 0000000000..3962da9c69 --- /dev/null +++ b/bgpd/bgp_linkstate_vty.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State VTY + * Copyright 2023 6WIND S.A. + */ + +#include <zebra.h> +#include "command.h" +#include "prefix.h" +#include "lib/json.h" +#include "lib/printfrr.h" +#include "stream.h" + +#include "bgpd/bgpd.h" +#include "bgpd/bgp_linkstate_vty.h" +#include "bgpd/bgp_linkstate.h" +#include "bgpd/bgp_zebra.h" +#include "bgpd/bgp_vty.h" +#include "bgpd/bgp_debug.h" + +#include "bgpd/bgp_linkstate_vty_clippy.c" + + +DEFPY (debug_bgp_linkstate, + debug_bgp_linkstate_cmd, + "[no] debug bgp linkstate", + NO_STR + DEBUG_STR + BGP_STR + "BGP allow linkstate debugging entries\n") +{ + if (vty->node == CONFIG_NODE) { + if (no) + DEBUG_OFF(linkstate, LINKSTATE); + else + DEBUG_ON(linkstate, LINKSTATE); + } else { + if (no) + TERM_DEBUG_OFF(linkstate, LINKSTATE); + else + TERM_DEBUG_ON(linkstate, LINKSTATE); + vty_out(vty, "BGP linkstate debugging is %s\n", + no ? "off" : "on"); + } + + return CMD_SUCCESS; +} + + +void bgp_linkstate_vty_init(void) +{ + install_element(ENABLE_NODE, &debug_bgp_linkstate_cmd); + install_element(CONFIG_NODE, &debug_bgp_linkstate_cmd); +} diff --git a/bgpd/bgp_linkstate_vty.h b/bgpd/bgp_linkstate_vty.h new file mode 100644 index 0000000000..5ba96b58b4 --- /dev/null +++ b/bgpd/bgp_linkstate_vty.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* BGP Link-State VTY header + * Copyright 2023 6WIND S.A. + */ + +#ifndef _FRR_BGP_LINKSTATE_VTY_H +#define _FRR_BGP_LINKSTATE_VTY_H + +void bgp_linkstate_vty_init(void); + +#endif /* _FRR_BGP_LINKSTATE_VTY_H */ diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 9b5d568636..138b182718 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -1179,12 +1179,13 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn, /* Process change. */ bgp_aggregate_increment(to_bgp, p, bpi, afi, safi); bgp_process(to_bgp, bn, afi, safi); - bgp_dest_unlock_node(bn); if (debug) zlog_debug("%s: ->%s: %pBD Found route, changed attr", __func__, to_bgp->name_pretty, bn); + bgp_dest_unlock_node(bn); + return bpi; } @@ -3141,7 +3142,7 @@ int bgp_show_mpls_vpn(struct vty *vty, afi_t afi, struct prefix_rd *prd, return CMD_WARNING; } table = bgp->rib[afi][SAFI_MPLS_VPN]; - return bgp_show_table_rd(vty, bgp, SAFI_MPLS_VPN, table, prd, type, + return bgp_show_table_rd(vty, bgp, afi, SAFI_MPLS_VPN, table, prd, type, output_arg, show_flags); } @@ -4204,6 +4205,7 @@ static void show_bgp_mplsvpn_nh_label_bind_internal(struct vty *vty, struct bgp *bgp_path; struct bgp_table *table; time_t tbuf; + char buf[32]; vty_out(vty, "Current BGP mpls-vpn nexthop label bind cache, %s\n", bgp->name_pretty); @@ -4221,7 +4223,7 @@ static void show_bgp_mplsvpn_nh_label_bind_internal(struct vty *vty, ifindex2ifname(iter->nh->ifindex, iter->nh->vrf_id)); tbuf = time(NULL) - (monotime(NULL) - iter->last_update); - vty_out(vty, " Last update: %s", ctime(&tbuf)); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, buf)); if (!detail) continue; vty_out(vty, " Paths:\n"); diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c index 7f1a6e73e1..d12dc22330 100644 --- a/bgpd/bgp_nexthop.c +++ b/bgpd/bgp_nexthop.c @@ -972,6 +972,7 @@ static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp, { char buf[PREFIX2STR_BUFFER]; time_t tbuf; + char timebuf[32]; struct peer *peer; json_object *json_last_update = NULL; json_object *json_nexthop = NULL; @@ -1070,14 +1071,14 @@ static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp, json_last_update = json_object_new_object(); json_object_int_add(json_last_update, "epoch", tbuf); json_object_string_add(json_last_update, "string", - ctime(&tbuf)); + ctime_r(&tbuf, timebuf)); json_object_object_add(json_nexthop, "lastUpdate", json_last_update); } else { json_object_int_add(json_nexthop, "lastUpdate", tbuf); } } else { - vty_out(vty, " Last update: %s", ctime(&tbuf)); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, timebuf)); } /* show paths dependent on nexthop, if needed. */ diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c index 6ee5b5dc5c..8a202b480a 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c @@ -147,6 +147,12 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, "capabilityErrorMultiProtocolAfi", "L2VPN"); break; + case AFI_LINKSTATE: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolAfi", + "Link State"); + break; case AFI_UNSPEC: case AFI_MAX: json_object_int_add( @@ -198,6 +204,18 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, "capabilityErrorMultiProtocolSafi", "flowspec"); break; + case SAFI_LINKSTATE: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "Link State"); + break; + case SAFI_LINKSTATE_VPN: + json_object_string_add( + json_cap, + "capabilityErrorMultiProtocolSafi", + "Link State VPN"); + break; case SAFI_UNSPEC: case SAFI_MAX: json_object_int_add( @@ -219,6 +237,9 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, case AFI_L2VPN: vty_out(vty, "AFI L2VPN, "); break; + case AFI_LINKSTATE: + vty_out(vty, "AFI Link State, "); + break; case AFI_UNSPEC: case AFI_MAX: vty_out(vty, "AFI Unknown %d, ", @@ -247,6 +268,12 @@ void bgp_capability_vty_out(struct vty *vty, struct peer *peer, bool use_json, case SAFI_EVPN: vty_out(vty, "SAFI EVPN"); break; + case SAFI_LINKSTATE: + vty_out(vty, "SAFI LINK STATE"); + break; + case SAFI_LINKSTATE_VPN: + vty_out(vty, "SAFI LINK STATE VPN"); + break; case SAFI_UNSPEC: case SAFI_MAX: vty_out(vty, "SAFI Unknown %d ", @@ -1414,7 +1441,8 @@ int bgp_open_option_parse(struct peer *peer, uint16_t length, && !peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] && !peer->afc_nego[AFI_IP6][SAFI_ENCAP] && !peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] - && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) { + && !peer->afc_nego[AFI_L2VPN][SAFI_EVPN] + && !peer->afc_nego[AFI_LINKSTATE][SAFI_LINKSTATE]) { flog_err(EC_BGP_PKT_OPEN, "%s [Error] Configured AFI/SAFIs do not overlap with received MP capabilities", peer->host); diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 851432d562..d473245934 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -48,6 +48,7 @@ #include "bgpd/bgp_io.h" #include "bgpd/bgp_keepalives.h" #include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_linkstate_tlv.h" #include "bgpd/bgp_trace.h" DEFINE_HOOK(bgp_packet_dump, @@ -349,7 +350,11 @@ int bgp_nlri_parse(struct peer *peer, struct attr *attr, return bgp_nlri_parse_evpn(peer, attr, packet, mp_withdraw); case SAFI_FLOWSPEC: return bgp_nlri_parse_flowspec(peer, attr, packet, mp_withdraw); + case SAFI_LINKSTATE: + return bgp_nlri_parse_linkstate(peer, attr, packet, + mp_withdraw); } + return BGP_NLRI_PARSE_ERROR; } @@ -1896,6 +1901,8 @@ static int bgp_open_receive(struct peer_connection *connection, peer->afc[AFI_L2VPN][SAFI_EVPN]; peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] = peer->afc[AFI_IP6][SAFI_FLOWSPEC]; + peer->afc_nego[AFI_LINKSTATE][SAFI_LINKSTATE] = + peer->afc[AFI_LINKSTATE][SAFI_LINKSTATE]; } /* Verify valid local address present based on negotiated @@ -2878,6 +2885,8 @@ static void bgp_dynamic_capability_llgr(uint8_t *pnt, int action, uint8_t *data = pnt + 3; uint8_t *end = data + hdr->length; size_t len = end - data; + afi_t afi; + safi_t safi; if (action == CAPABILITY_ACTION_SET) { if (len < BGP_CAP_LLGR_MIN_PACKET_LEN) { @@ -2944,6 +2953,15 @@ static void bgp_dynamic_capability_llgr(uint8_t *pnt, int action, data += BGP_CAP_LLGR_MIN_PACKET_LEN; } } else { + FOREACH_AFI_SAFI (afi, safi) { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_LLGR_AF_RCV); + + peer->llgr[afi][safi].flags = 0; + peer->llgr[afi][safi].stale_time = + BGP_DEFAULT_LLGR_STALE_TIME; + } + UNSET_FLAG(peer->cap, PEER_CAP_LLGR_RCV); } } @@ -2957,6 +2975,8 @@ static void bgp_dynamic_capability_graceful_restart(uint8_t *pnt, int action, uint8_t *data = pnt + 3; uint8_t *end = pnt + hdr->length; size_t len = end - data; + afi_t afi; + safi_t safi; if (action == CAPABILITY_ACTION_SET) { if (len < sizeof(gr_restart_flag_time)) { @@ -3031,6 +3051,15 @@ static void bgp_dynamic_capability_graceful_restart(uint8_t *pnt, int action, data += GRACEFUL_RESTART_CAPABILITY_PER_AFI_SAFI_SIZE; } } else { + FOREACH_AFI_SAFI (afi, safi) { + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_RCV); + UNSET_FLAG(peer->af_cap[afi][safi], + PEER_CAP_RESTART_AF_PRESERVE_RCV); + } + + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_R_BIT_RCV); + UNSET_FLAG(peer->cap, PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV); UNSET_FLAG(peer->cap, PEER_CAP_RESTART_RCV); } } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index df3397af99..71b43c6756 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -73,6 +73,7 @@ #include "bgpd/bgp_flowspec.h" #include "bgpd/bgp_flowspec_util.h" #include "bgpd/bgp_pbr.h" +#include "bgpd/bgp_linkstate_tlv.h" #include "bgpd/bgp_route_clippy.c" @@ -1493,11 +1494,11 @@ int bgp_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, int bgp_evpn_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, - struct bgp_path_info *exist, int *paths_eq) + struct bgp_path_info *exist, int *paths_eq, + bool debug) { enum bgp_path_selection_reason reason; char pfx_buf[PREFIX2STR_BUFFER] = {}; - bool debug = false; if (debug) prefix2str(bgp_dest_get_prefix(new->net), pfx_buf, @@ -1581,7 +1582,7 @@ static enum filter_type bgp_input_filter(struct peer *peer, done: if (frrtrace_enabled(frr_bgp, input_filter)) { - char pfxprint[PREFIX2STR_BUFFER]; + char pfxprint[PREFIX_STRLEN_EXTENDED]; prefix2str(p, pfxprint, sizeof(pfxprint)); frrtrace(5, frr_bgp, input_filter, peer, pfxprint, afi, safi, @@ -1638,7 +1639,7 @@ static enum filter_type bgp_output_filter(struct peer *peer, } if (frrtrace_enabled(frr_bgp, output_filter)) { - char pfxprint[PREFIX2STR_BUFFER]; + char pfxprint[PREFIX_STRLEN_EXTENDED]; prefix2str(p, pfxprint, sizeof(pfxprint)); frrtrace(5, frr_bgp, output_filter, peer, pfxprint, afi, safi, @@ -2712,7 +2713,7 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, int paths_eq, do_mpath; bool debug; struct list mp_list; - char pfx_buf[PREFIX2STR_BUFFER] = {}; + char pfx_buf[PREFIX_STRLEN_EXTENDED] = {}; char path_buf[PATH_ADDPATH_STR_BUFFER]; bgp_mp_list_init(&mp_list); @@ -4167,7 +4168,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, int allowas_in = 0; if (frrtrace_enabled(frr_bgp, process_update)) { - char pfxprint[PREFIX2STR_BUFFER]; + char pfxprint[PREFIX_STRLEN_EXTENDED]; prefix2str(p, pfxprint, sizeof(pfxprint)); frrtrace(6, frr_bgp, process_update, peer, pfxprint, addpath_id, @@ -4729,8 +4730,8 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || (safi == SAFI_MPLS_VPN && pi->sub_type != BGP_ROUTE_IMPORTED))) || - (safi == SAFI_EVPN && - bgp_evpn_is_prefix_nht_supported(p))) { + (safi == SAFI_EVPN && bgp_evpn_is_prefix_nht_supported(p)) || + afi == AFI_LINKSTATE) { if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP && peer->ttl == BGP_DEFAULT_TTL && !CHECK_FLAG(peer->flags, @@ -4877,9 +4878,9 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, /* Nexthop reachability check. */ if (((afi == AFI_IP || afi == AFI_IP6) && (safi == SAFI_UNICAST || safi == SAFI_LABELED_UNICAST || - (safi == SAFI_MPLS_VPN && - new->sub_type != BGP_ROUTE_IMPORTED))) || - (safi == SAFI_EVPN && bgp_evpn_is_prefix_nht_supported(p))) { + (safi == SAFI_MPLS_VPN && new->sub_type != BGP_ROUTE_IMPORTED))) || + (safi == SAFI_EVPN && bgp_evpn_is_prefix_nht_supported(p)) || + afi == AFI_LINKSTATE) { if (safi != SAFI_EVPN && peer->sort == BGP_PEER_EBGP && peer->ttl == BGP_DEFAULT_TTL && !CHECK_FLAG(peer->flags, @@ -6597,7 +6598,7 @@ int bgp_static_set(struct vty *vty, bool negate, const char *ip_str, int ret; struct prefix p; struct bgp_static *bgp_static; - struct prefix_rd prd; + struct prefix_rd prd = {}; struct bgp_dest *pdest; struct bgp_dest *dest; struct bgp_table *table; @@ -6626,7 +6627,6 @@ int bgp_static_set(struct vty *vty, bool negate, const char *ip_str, } if (safi == SAFI_MPLS_VPN || safi == SAFI_EVPN) { - memset(&prd, 0, sizeof(prd)); ret = str2prefix_rd(rd_str, &prd); if (!ret) { vty_out(vty, "%% Malformed rd\n"); @@ -8773,7 +8773,7 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, struct vty *vty, json_object *json, bool wide) { int len = 0; - char buf[INET6_ADDRSTRLEN]; + char buf[PREFIX_STRLEN_EXTENDED]; if (p->family == AF_INET) { if (!json) { @@ -8799,6 +8799,14 @@ static void route_vty_out_route(struct bgp_dest *dest, const struct prefix *p, json ? NLRI_STRING_FORMAT_JSON_SIMPLE : NLRI_STRING_FORMAT_MIN, json); + } else if (p->family == AF_LINKSTATE) { + if (json) { + json_object_int_add(json, "version", dest->version); + bgp_linkstate_nlri_prefix_json( + json, p->u.prefix_linkstate.nlri_type, + p->u.prefix_linkstate.ptr, p->prefixlen); + } else + len = vty_out(vty, "%pFX", p); } else { if (!json) len = vty_out(vty, "%pFX", p); @@ -9825,7 +9833,7 @@ static void damp_route_vty_out(struct vty *vty, const struct prefix *p, { struct attr *attr = path->attr; int len; - char timebuf[BGP_UPTIME_LEN]; + char timebuf[BGP_UPTIME_LEN] = {}; json_object *json_path = NULL; if (use_json) @@ -9884,7 +9892,7 @@ static void flap_route_vty_out(struct vty *vty, const struct prefix *p, { struct attr *attr = path->attr; struct bgp_damp_info *bdi; - char timebuf[BGP_UPTIME_LEN]; + char timebuf[BGP_UPTIME_LEN] = {}; int len; json_object *json_path = NULL; @@ -10090,6 +10098,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, char tag_buf[30]; struct attr *attr = path->attr; time_t tbuf; + char timebuf[32]; json_object *json_bestpath = NULL; json_object *json_cluster_list = NULL; json_object *json_cluster_list_list = NULL; @@ -10103,6 +10112,7 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_object *json_peer = NULL; json_object *json_string = NULL; json_object *json_adv_to = NULL; + json_object *json_bgp_ls_attr = NULL; int first = 0; struct listnode *node, *nnode; struct peer *peer; @@ -10983,11 +10993,11 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, json_last_update = json_object_new_object(); json_object_int_add(json_last_update, "epoch", tbuf); json_object_string_add(json_last_update, "string", - ctime(&tbuf)); + ctime_r(&tbuf, timebuf)); json_object_object_add(json_path, "lastUpdate", json_last_update); } else - vty_out(vty, " Last update: %s", ctime(&tbuf)); + vty_out(vty, " Last update: %s", ctime_r(&tbuf, timebuf)); /* Line 10 display PMSI tunnel attribute, if present */ if (attr->flag & ATTR_FLAG_BIT(BGP_ATTR_PMSI_TUNNEL)) { @@ -11037,6 +11047,28 @@ void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_dest *bn, llgr_remaining); } + if (safi == SAFI_LINKSTATE) { + /* BGP Link-State NLRI */ + if (json_paths) + bgp_linkstate_nlri_prefix_json( + json_path, bn->rn->p.u.prefix_linkstate.nlri_type, + bn->rn->p.u.prefix_linkstate.ptr, bn->rn->p.prefixlen); + + /* BGP Link-State Attributes */ + if (attr->link_state) { + if (json_paths) { + json_bgp_ls_attr = json_object_new_object(); + json_object_object_add(json_path, + "linkStateAttributes", + json_bgp_ls_attr); + } else { + vty_out(vty, " BGP-LS attributes:\n"); + } + bgp_linkstate_tlv_attribute_display( + vty, attr->link_state, 4, json_bgp_ls_attr); + } + } + /* Output some debug about internal state of the dest flags */ if (json_paths) { if (CHECK_FLAG(bn->flags, BGP_NODE_PROCESS_SCHEDULED)) @@ -11085,7 +11117,7 @@ static int bgp_show_community(struct vty *vty, struct bgp *bgp, const char *comstr, int exact, afi_t afi, safi_t safi, uint16_t show_flags); -static int bgp_show_table(struct vty *vty, struct bgp *bgp, safi_t safi, +static int bgp_show_table(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_table *table, enum bgp_show_type type, void *output_arg, const char *rd, int is_last, unsigned long *output_cum, unsigned long *total_cum, @@ -11419,7 +11451,7 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, safi_t safi, vty_out(vty, ASN_FORMAT(bgp->asnotation), &bgp->as); vty_out(vty, "\n"); - if (!detail_routes) { + if (!detail_routes && safi != SAFI_LINKSTATE) { vty_out(vty, BGP_SHOW_SCODE_HEADER); vty_out(vty, BGP_SHOW_NCODE_HEADER); vty_out(vty, BGP_SHOW_OCODE_HEADER); @@ -11446,12 +11478,12 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, safi_t safi, if (type == bgp_show_type_dampend_paths || type == bgp_show_type_damp_neighbor) damp_route_vty_out(vty, dest_p, pi, display, - AFI_IP, safi, use_json, + afi, safi, use_json, json_paths); else if (type == bgp_show_type_flap_statistics || type == bgp_show_type_flap_neighbor) flap_route_vty_out(vty, dest_p, pi, display, - AFI_IP, safi, use_json, + afi, safi, use_json, json_paths); else { if (detail_routes || detail_json) { @@ -11607,7 +11639,7 @@ static int bgp_show_table(struct vty *vty, struct bgp *bgp, safi_t safi, return CMD_SUCCESS; } -int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, safi_t safi, +int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_table *table, struct prefix_rd *prd_match, enum bgp_show_type type, void *output_arg, uint16_t show_flags) @@ -11636,7 +11668,7 @@ int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, safi_t safi, memcpy(&prd, dest_p, sizeof(struct prefix_rd)); prefix_rd2str(&prd, rd, sizeof(rd), bgp->asnotation); - bgp_show_table(vty, bgp, safi, itable, type, output_arg, + bgp_show_table(vty, bgp, afi, safi, itable, type, output_arg, rd, next == NULL, &output_cum, &total_cum, &json_header_depth, show_flags, RPKI_NOT_BEING_USED); @@ -11686,7 +11718,7 @@ static int bgp_show(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, table = bgp->rib[afi][safi]; /* use MPLS and ENCAP specific shows until they are merged */ if (safi == SAFI_MPLS_VPN) { - return bgp_show_table_rd(vty, bgp, safi, table, NULL, type, + return bgp_show_table_rd(vty, bgp, afi, safi, table, NULL, type, output_arg, show_flags); } @@ -11699,7 +11731,7 @@ static int bgp_show(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, if (safi == SAFI_EVPN) return bgp_evpn_show_all_routes(vty, bgp, type, use_json, 0); - return bgp_show_table(vty, bgp, safi, table, type, output_arg, NULL, 1, + return bgp_show_table(vty, bgp, afi, safi, table, type, output_arg, NULL, 1, NULL, NULL, &json_header_depth, show_flags, rpki_target_state); } @@ -12023,7 +12055,7 @@ static void bgp_show_path_info(const struct prefix_rd *pfx_rd, || CHECK_FLAG(pi->flags, BGP_PATH_SELECTED)))) route_vty_out_detail(vty, bgp, bgp_node, bgp_dest_get_prefix(bgp_node), pi, - AFI_IP, safi, rpki_curr_state, + afi, safi, rpki_curr_state, json_paths); } @@ -12052,6 +12084,8 @@ const struct prefix_rd *bgp_rd_from_dest(const struct bgp_dest *dest, case SAFI_UNICAST: case SAFI_MULTICAST: case SAFI_LABELED_UNICAST: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_FLOWSPEC: case SAFI_MAX: return NULL; @@ -12968,6 +13002,62 @@ DEFPY(show_ip_bgp, show_ip_bgp_cmd, return CMD_SUCCESS; } +/* BGP route print out function */ +DEFPY (show_ip_bgp_link_state, show_ip_bgp_link_state_cmd, + "show [ip] bgp [<view|vrf> VIEWVRFNAME] link-state link-state\ + [all$all]\ + [version (1-4294967295)\ + |detail-routes$detail_routes\ + ] [json$uj [detail$detail_json] | wide$wide]", + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR + BGP_AF_STR + BGP_AF_MODIFIER_STR + "Display the entries for all address families\n" + "Display prefixes with matching version numbers\n" + "Version number and above\n" + "Display detailed version of all routes\n" + JSON_STR + "Display detailed version of JSON output\n" + "Increase table width for longer prefixes\n") +{ + afi_t afi = AFI_LINKSTATE; + safi_t safi = SAFI_LINKSTATE; + enum bgp_show_type sh_type = bgp_show_type_normal; + void *output_arg = NULL; + struct bgp *bgp = NULL; + int idx = 0; + uint16_t show_flags = 0; + enum rpki_states rpki_target_state = RPKI_NOT_BEING_USED; + + if (uj) { + argc--; + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON); + } + + if (detail_json) + SET_FLAG(show_flags, BGP_SHOW_OPT_JSON_DETAIL); + + if (detail_routes) + SET_FLAG(show_flags, BGP_SHOW_OPT_ROUTES_DETAIL); + + if (wide) + SET_FLAG(show_flags, BGP_SHOW_OPT_WIDE); + + bgp_vty_find_and_parse_afi_safi_bgp(vty, argv, argc, &idx, &afi, &safi, + &bgp, uj); + if (!idx) + return CMD_WARNING; + + /* Display prefixes with matching version numbers */ + if (argv_find(argv, argc, "version", &idx)) { + sh_type = bgp_show_type_prefix_version; + output_arg = argv[idx + 1]->arg; + } + + return bgp_show(vty, bgp, afi, safi, sh_type, output_arg, show_flags, + rpki_target_state); +} + DEFUN (show_ip_bgp_route, show_ip_bgp_route_cmd, "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]]<A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> [<bestpath|multipath>] [rpki <valid|invalid|notfound>] [json]", @@ -13048,13 +13138,13 @@ DEFUN (show_ip_bgp_route, DEFUN (show_ip_bgp_regexp, show_ip_bgp_regexp_cmd, - "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_CMD_STR" ["BGP_SAFI_WITH_LABEL_CMD_STR"]] regexp REGEX [json]", + "show [ip] bgp [<view|vrf> VIEWVRFNAME] ["BGP_AFI_WITH_LS_CMD_STR" ["BGP_SAFI_WITH_LABEL_LS_CMD_STR"]] regexp REGEX [json]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR - BGP_AFI_HELP_STR - BGP_SAFI_WITH_LABEL_HELP_STR + BGP_AFI_WITH_LS_HELP_STR + BGP_SAFI_WITH_LABEL_LS_HELP_STR "Display routes matching the AS path regular expression\n" "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n" JSON_STR) @@ -13301,6 +13391,8 @@ static void bgp_table_stats_walker(struct event *t) case AFI_L2VPN: space = EVPN_ROUTE_PREFIXLEN; break; + case AFI_LINKSTATE: + /* TODO */ case AFI_UNSPEC: case AFI_MAX: return; @@ -13557,6 +13649,8 @@ static int bgp_table_stats_single(struct vty *vty, struct bgp *bgp, afi_t afi, case AFI_L2VPN: bitlen = EVPN_ROUTE_PREFIXLEN; break; + case AFI_LINKSTATE: + /* TODO */ case AFI_UNSPEC: case AFI_MAX: break; @@ -14591,13 +14685,13 @@ DEFPY (show_ip_bgp_instance_neighbor_bestpath_route, DEFPY(show_ip_bgp_instance_neighbor_advertised_route, show_ip_bgp_instance_neighbor_advertised_route_cmd, - "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR " [" BGP_SAFI_WITH_LABEL_CMD_STR "]] [all$all] neighbors <A.B.C.D|X:X::X:X|WORD> <advertised-routes|received-routes|filtered-routes> [route-map RMAP_NAME$route_map] [<A.B.C.D/M|X:X::X:X/M>$prefix | detail$detail] [json$uj | wide$wide]", + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_WITH_LS_CMD_STR " [" BGP_SAFI_WITH_LABEL_LS_CMD_STR "]] [all$all] neighbors <A.B.C.D|X:X::X:X|WORD> <advertised-routes|received-routes|filtered-routes> [route-map RMAP_NAME$route_map] [<A.B.C.D/M|X:X::X:X/M>$prefix | detail$detail] [json$uj | wide$wide]", SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR - BGP_AFI_HELP_STR - BGP_SAFI_WITH_LABEL_HELP_STR + BGP_AFI_WITH_LS_HELP_STR + BGP_SAFI_WITH_LABEL_LS_HELP_STR "Display the entries for all address families\n" "Detailed information on TCP and BGP neighbor connections\n" "Neighbor to display information about\n" @@ -14651,6 +14745,12 @@ DEFPY(show_ip_bgp_instance_neighbor_advertised_route, if (!idx) return CMD_WARNING; + if (afi == AFI_LINKSTATE && prefix_str) { + vty_out(vty, + "The prefix option cannot be selected with AFI Link-State\n"); + return CMD_WARNING; + } + /* neighbors <A.B.C.D|X:X::X:X|WORD> */ argv_find(argv, argc, "neighbors", &idx); peerstr = argv[++idx]->arg; @@ -15873,6 +15973,7 @@ void bgp_route_init(void) install_element(VIEW_NODE, &show_ip_bgp_l2vpn_evpn_statistics_cmd); install_element(VIEW_NODE, &show_ip_bgp_dampening_params_cmd); install_element(VIEW_NODE, &show_ip_bgp_cmd); + install_element(VIEW_NODE, &show_ip_bgp_link_state_cmd); install_element(VIEW_NODE, &show_ip_bgp_route_cmd); install_element(VIEW_NODE, &show_ip_bgp_regexp_cmd); install_element(VIEW_NODE, &show_ip_bgp_statistics_all_cmd); diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 7470954bf7..e9f48ea647 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -887,7 +887,7 @@ extern void route_vty_out_detail(struct vty *vty, struct bgp *bgp, struct bgp_path_info *path, afi_t afi, safi_t safi, enum rpki_states, json_object *json_paths); -extern int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, safi_t safi, +extern int bgp_show_table_rd(struct vty *vty, struct bgp *bgp, afi_t afi, safi_t safi, struct bgp_table *table, struct prefix_rd *prd, enum bgp_show_type type, void *output_arg, uint16_t show_flags); @@ -896,7 +896,8 @@ extern bool bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, uint8_t type, uint8_t stype, struct attr *attr, struct bgp_dest *dest); extern int bgp_evpn_path_info_cmp(struct bgp *bgp, struct bgp_path_info *new, - struct bgp_path_info *exist, int *paths_eq); + struct bgp_path_info *exist, int *paths_eq, + bool debug); extern void bgp_aggregate_toggle_suppressed(struct bgp_aggregate *aggregate, struct bgp *bgp, const struct prefix *p, afi_t afi, diff --git a/bgpd/bgp_snmp_bgp4.c b/bgpd/bgp_snmp_bgp4.c index 0c391b621e..692e232a83 100644 --- a/bgpd/bgp_snmp_bgp4.c +++ b/bgpd/bgp_snmp_bgp4.c @@ -401,7 +401,7 @@ static struct bgp_path_info *bgp4PathAttrLookup(struct variable *v, oid name[], /* Set OID offset for prefix. */ offset = name + v->namelen; oid2in_addr(offset, IN_ADDR_SIZE, &addr->prefix); - offset += IN_ADDR_SIZE; + offset++; /* Prefix length. */ addr->prefixlen = *offset; @@ -497,7 +497,7 @@ static struct bgp_path_info *bgp4PathAttrLookup(struct variable *v, oid name[], offset = name + v->namelen; oid_copy_in_addr(offset, &rn_p->u.prefix4); - offset += IN_ADDR_SIZE; + offset++; *offset = rn_p->prefixlen; offset++; oid_copy_in_addr(offset, diff --git a/bgpd/bgp_snmp_bgp4v2.c b/bgpd/bgp_snmp_bgp4v2.c index cfafae55dc..fb6f13a6ca 100644 --- a/bgpd/bgp_snmp_bgp4v2.c +++ b/bgpd/bgp_snmp_bgp4v2.c @@ -139,7 +139,7 @@ static struct peer *bgpv2PeerTable_lookup(struct variable *v, oid name[], struct peer *peer = NULL; size_t namelen = v ? v->namelen : BGP4V2_PEER_ENTRY_OFFSET; oid *offset = name + namelen; - sa_family_t family = name[namelen - 1] == 4 ? AF_INET : AF_INET6; + sa_family_t family = name[namelen - 1] == 1 ? AF_INET : AF_INET6; int afi_len = IN_ADDR_SIZE; size_t offsetlen = *length - namelen; @@ -438,7 +438,7 @@ bgp4v2PathAttrLookup(struct variable *v, oid name[], size_t *length, unsigned int len; struct ipaddr paddr = {}; size_t namelen = v ? v->namelen : BGP4V2_NLRI_ENTRY_OFFSET; - sa_family_t family = name[namelen - 1] == 4 ? AF_INET : AF_INET6; + sa_family_t family = name[namelen - 1] == 1 ? AF_INET : AF_INET6; afi_t afi = AFI_IP; size_t afi_len = IN_ADDR_SIZE; @@ -800,616 +800,616 @@ static struct variable bgpv2_variables[] = { RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_INSTANCE, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_INSTANCE, 1, 1}}, {BGP4V2_PEER_INSTANCE, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_INSTANCE, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_INSTANCE, 1, 2}}, {BGP4V2_PEER_LOCAL_ADDR_TYPE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 1, 1}}, {BGP4V2_PEER_LOCAL_ADDR_TYPE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR_TYPE, 1, 2}}, {BGP4V2_PEER_LOCAL_ADDR, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 1, 1}}, {BGP4V2_PEER_LOCAL_ADDR, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_ADDR, 1, 2}}, {BGP4V2_PEER_REMOTE_ADDR_TYPE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 1, 1}}, {BGP4V2_PEER_REMOTE_ADDR_TYPE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR_TYPE, 1, 2}}, {BGP4V2_PEER_REMOTE_ADDR, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 1, 1}}, {BGP4V2_PEER_REMOTE_ADDR, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_ADDR, 1, 2}}, {BGP4V2_PEER_LOCAL_PORT, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 1}}, {BGP4V2_PEER_LOCAL_PORT, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_PORT, 1, 2}}, {BGP4V2_PEER_LOCAL_AS, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 1, 1}}, {BGP4V2_PEER_LOCAL_AS, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_AS, 1, 2}}, {BGP4V2_PEER_LOCAL_IDENTIFIER, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 1, 1}}, {BGP4V2_PEER_LOCAL_IDENTIFIER, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_LOCAL_IDENTIFIER, 1, 2}}, {BGP4V2_PEER_REMOTE_PORT, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 1}}, {BGP4V2_PEER_REMOTE_PORT, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_PORT, 1, 2}}, {BGP4V2_PEER_REMOTE_AS, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 1, 1}}, {BGP4V2_PEER_REMOTE_AS, ASN_UNSIGNED, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_AS, 1, 2}}, {BGP4V2_PEER_REMOTE_IDENTIFIER, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 1, 1}}, {BGP4V2_PEER_REMOTE_IDENTIFIER, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_REMOTE_IDENTIFIER, 1, 2}}, {BGP4V2_PEER_ADMIN_STATUS, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 1, 1}}, {BGP4V2_PEER_ADMIN_STATUS, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_ADMIN_STATUS, 1, 2}}, {BGP4V2_PEER_STATE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_STATE, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_STATE, 1, 1}}, {BGP4V2_PEER_STATE, ASN_INTEGER, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_STATE, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_STATE, 1, 2}}, {BGP4V2_PEER_DESCRIPTION, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 1, 4}}, + {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 1, 1}}, {BGP4V2_PEER_DESCRIPTION, ASN_OCTET_STR, RONLY, bgpv2PeerTable, 6, - {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 2, 16}}, + {1, 2, 1, BGP4V2_PEER_DESCRIPTION, 1, 2}}, /* bgp4V2PeerErrorsEntry */ {BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_RECEIVED, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_RECEIVED, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, - ASN_UNSIGNED, + ASN_TIMETICKS, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, - ASN_UNSIGNED, + ASN_TIMETICKS, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TIME, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_TEXT, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_RECEIVED_DATA, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_CODE_SENT, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_CODE_SENT, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_CODE_SENT, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, ASN_UNSIGNED, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SUBCODE_SENT, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_SENT_TIME, - ASN_UNSIGNED, + ASN_TIMETICKS, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_SENT_TIME, - ASN_UNSIGNED, + ASN_TIMETICKS, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TIME, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_SENT_TEXT, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_SENT_TEXT, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_TEXT, 1, 2}}, {BGP4V2_PEER_LAST_ERROR_SENT_DATA, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 1, 4}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 1, 1}}, {BGP4V2_PEER_LAST_ERROR_SENT_DATA, ASN_OCTET_STR, RONLY, bgpv2PeerErrorsTable, 6, - {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 2, 16}}, + {1, 3, 1, BGP4V2_PEER_LAST_ERROR_SENT_DATA, 1, 2}}, /* bgp4V2PeerEventTimesEntry */ {BGP4V2_PEER_FSM_ESTABLISHED_TIME, ASN_UNSIGNED, RONLY, bgpv2PeerEventTimesTable, 6, - {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 1, 4}}, + {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 1, 1}}, {BGP4V2_PEER_FSM_ESTABLISHED_TIME, ASN_UNSIGNED, RONLY, bgpv2PeerEventTimesTable, 6, - {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 2, 16}}, + {1, 4, 1, BGP4V2_PEER_FSM_ESTABLISHED_TIME, 1, 2}}, {BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, ASN_UNSIGNED, RONLY, bgpv2PeerEventTimesTable, 6, - {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 1, 4}}, + {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 1, 1}}, {BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, ASN_UNSIGNED, RONLY, bgpv2PeerEventTimesTable, 6, - {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 2, 16}}, + {1, 4, 1, BGP4V2_PEER_PEER_IN_UPDATES_ELAPSED_TIME, 1, 2}}, /* bgp4V2NlriTable */ {BGP4V2_NLRI_INDEX, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_INDEX, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_INDEX, 1, 1}}, {BGP4V2_NLRI_INDEX, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_INDEX, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_INDEX, 1, 2}}, {BGP4V2_NLRI_AFI, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AFI, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AFI, 1, 1}}, {BGP4V2_NLRI_AFI, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AFI, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AFI, 1, 2}}, {BGP4V2_NLRI_SAFI, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_SAFI, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_SAFI, 1, 1}}, {BGP4V2_NLRI_SAFI, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_SAFI, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_SAFI, 1, 2}}, {BGP4V2_NLRI_PREFIX_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 1, 1}}, {BGP4V2_NLRI_PREFIX_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX_TYPE, 1, 2}}, {BGP4V2_NLRI_PREFIX, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX, 1, 1}}, {BGP4V2_NLRI_PREFIX, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX, 1, 2}}, {BGP4V2_NLRI_PREFIX_LEN, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 1, 1}}, {BGP4V2_NLRI_PREFIX_LEN, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_PREFIX_LEN, 1, 2}}, {BGP4V2_NLRI_BEST, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_BEST, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_BEST, 1, 1}}, {BGP4V2_NLRI_BEST, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_BEST, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_BEST, 1, 2}}, {BGP4V2_NLRI_CALC_LOCAL_PREF, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 1, 1}}, {BGP4V2_NLRI_CALC_LOCAL_PREF, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_CALC_LOCAL_PREF, 1, 2}}, {BGP4V2_NLRI_ORIGIN, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_ORIGIN, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_ORIGIN, 1, 1}}, {BGP4V2_NLRI_ORIGIN, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_ORIGIN, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_ORIGIN, 1, 2}}, {BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 1, 1}}, {BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR_TYPE, 1, 2}}, {BGP4V2_NLRI_NEXT_HOP_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 1, 1}}, {BGP4V2_NLRI_NEXT_HOP_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_NEXT_HOP_ADDR, 1, 2}}, {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 1, 1}}, {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR_TYPE, 1, 2}}, {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 1, 1}}, {BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_LINK_LOCAL_NEXT_HOP_ADDR, 1, 2}}, {BGP4V2_NLRI_LOCAL_PREF_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 1, 1}}, {BGP4V2_NLRI_LOCAL_PREF_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF_PRESENT, 1, 2}}, {BGP4V2_NLRI_LOCAL_PREF, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 1, 1}}, {BGP4V2_NLRI_LOCAL_PREF, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_LOCAL_PREF, 1, 2}}, {BGP4V2_NLRI_MED_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 1, 1}}, {BGP4V2_NLRI_MED_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_MED_PRESENT, 1, 2}}, {BGP4V2_NLRI_MED, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_MED, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_MED, 1, 1}}, {BGP4V2_NLRI_MED, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_MED, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_MED, 1, 2}}, {BGP4V2_NLRI_ATOMIC_AGGREGATE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 1, 1}}, {BGP4V2_NLRI_ATOMIC_AGGREGATE, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_ATOMIC_AGGREGATE, 1, 2}}, {BGP4V2_NLRI_AGGREGATOR_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 1, 1}}, {BGP4V2_NLRI_AGGREGATOR_PRESENT, ASN_INTEGER, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_PRESENT, 1, 2}}, {BGP4V2_NLRI_AGGREGATOR_AS, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 1, 1}}, {BGP4V2_NLRI_AGGREGATOR_AS, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_AS, 1, 2}}, {BGP4V2_NLRI_AGGREGATOR_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 1, 1}}, {BGP4V2_NLRI_AGGREGATOR_ADDR, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AGGREGATOR_ADDR, 1, 2}}, {BGP4V2_NLRI_AS_PATH_CALC_LENGTH, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 1, 1}}, {BGP4V2_NLRI_AS_PATH_CALC_LENGTH, ASN_UNSIGNED, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_CALC_LENGTH, 1, 2}}, {BGP4V2_NLRI_AS_PATH_STRING, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 1, 1}}, {BGP4V2_NLRI_AS_PATH_STRING, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH_STRING, 1, 2}}, {BGP4V2_NLRI_AS_PATH, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH, 1, 1}}, {BGP4V2_NLRI_AS_PATH, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_AS_PATH, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_AS_PATH, 1, 2}}, {BGP4V2_NLRI_PATH_ATTR_UNKNOWN, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 1, 4}}, + {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 1, 1}}, {BGP4V2_NLRI_PATH_ATTR_UNKNOWN, ASN_OCTET_STR, RONLY, bgp4v2PathAttrTable, 6, - {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 2, 16}}, + {1, 9, 1, BGP4V2_NLRI_PATH_ATTR_UNKNOWN, 1, 2}}, }; int bgp_snmp_bgp4v2_init(struct event_loop *tm) diff --git a/bgpd/bgp_snmp_bgp4v2.h b/bgpd/bgp_snmp_bgp4v2.h index 7cdad586bc..6587a825c5 100644 --- a/bgpd/bgp_snmp_bgp4v2.h +++ b/bgpd/bgp_snmp_bgp4v2.h @@ -14,6 +14,7 @@ /* bgp4V2PeerEntry: * offset 1.3.6.1.3.5.1.1.2.1.x.(1|2).(4|16) = 13 + * offset 1.3.6.1.4.1.7336.3.2.1.1.2.1.x.1.(1|2) = 16 */ #define BGP4V2_PEER_ENTRY_OFFSET 13 #define BGP4V2_PEER_INSTANCE 1 @@ -49,6 +50,7 @@ /* bgp4V2NlriEntry * offset 1.3.6.1.3.5.1.1.9.1.x.(1|2).(4|16) = 13 + * offset 1.3.6.1.4.1.7336.3.2.1.1.9.1.x.1.(1|2) = 16 */ #define BGP4V2_NLRI_ENTRY_OFFSET 13 #define BGP4V2_NLRI_INDEX 1 diff --git a/bgpd/bgp_table.c b/bgpd/bgp_table.c index 8465ada996..e01bf39113 100644 --- a/bgpd/bgp_table.c +++ b/bgpd/bgp_table.c @@ -63,7 +63,7 @@ struct bgp_dest *bgp_dest_lock_node(struct bgp_dest *dest) const char *bgp_dest_get_prefix_str(struct bgp_dest *dest) { const struct prefix *p = NULL; - static char str[PREFIX_STRLEN] = {0}; + static char str[PREFIX_STRLEN_EXTENDED] = {0}; p = bgp_dest_get_prefix(dest); if (p) @@ -117,6 +117,9 @@ static void bgp_node_destroy(route_table_delegate_t *delegate, node->info = NULL; } + if (family2afi(node->p.family) == AFI_LINKSTATE) + prefix_linkstate_ptr_free(&node->p); + XFREE(MTYPE_ROUTE_NODE, node); } diff --git a/bgpd/bgp_updgrp.c b/bgpd/bgp_updgrp.c index e47ea8aa8a..a2006c3508 100644 --- a/bgpd/bgp_updgrp.c +++ b/bgpd/bgp_updgrp.c @@ -689,6 +689,7 @@ static int update_group_show_walkcb(struct update_group *updgrp, void *arg) json_object *json_peers = NULL; json_object *json_pkt_info = NULL; time_t epoch_tbuf, tbuf; + char timebuf[32]; if (!ctx) return CMD_SUCCESS; @@ -724,7 +725,7 @@ static int update_group_show_walkcb(struct update_group *updgrp, void *arg) json_time = json_object_new_object(); json_object_int_add(json_time, "epoch", epoch_tbuf); json_object_string_add(json_time, "epochString", - ctime(&epoch_tbuf)); + ctime_r(&epoch_tbuf, timebuf)); json_object_object_add(json_updgrp, "groupCreateTime", json_time); json_object_string_add(json_updgrp, "afi", @@ -733,7 +734,8 @@ static int update_group_show_walkcb(struct update_group *updgrp, void *arg) safi2str(updgrp->safi)); } else { vty_out(vty, "Update-group %" PRIu64 ":\n", updgrp->id); - vty_out(vty, " Created: %s", timestamp_string(updgrp->uptime)); + vty_out(vty, " Created: %s", + timestamp_string(updgrp->uptime, timebuf)); } filter = &updgrp->conf->filter[updgrp->afi][updgrp->safi]; @@ -794,7 +796,7 @@ static int update_group_show_walkcb(struct update_group *updgrp, void *arg) json_object_int_add(json_subgrp_time, "epoch", epoch_tbuf); json_object_string_add(json_subgrp_time, "epochString", - ctime(&epoch_tbuf)); + ctime_r(&epoch_tbuf, timebuf)); json_object_object_add(json_subgrp, "groupCreateTime", json_subgrp_time); } else { @@ -802,7 +804,7 @@ static int update_group_show_walkcb(struct update_group *updgrp, void *arg) vty_out(vty, " Update-subgroup %" PRIu64 ":\n", subgrp->id); vty_out(vty, " Created: %s", - timestamp_string(subgrp->uptime)); + timestamp_string(subgrp->uptime, timebuf)); } if (subgrp->split_from.update_group_id diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index bbe74ef5a7..08c14df1fe 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -168,6 +168,8 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV4_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV4_NODE; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: @@ -188,6 +190,8 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) return BGP_VPNV6_NODE; case SAFI_FLOWSPEC: return BGP_FLOWSPECV6_NODE; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNSPEC: case SAFI_ENCAP: case SAFI_EVPN: @@ -198,6 +202,23 @@ static enum node_type bgp_node_type(afi_t afi, safi_t safi) break; case AFI_L2VPN: return BGP_EVPN_NODE; + case AFI_LINKSTATE: + switch (safi) { + case SAFI_LINKSTATE: + return BGP_LS_NODE; + case SAFI_LINKSTATE_VPN: /* Not yet supported */ + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_MPLS_VPN: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_ENCAP: + case SAFI_EVPN: + case SAFI_MAX: + return BGP_IPV4_NODE; + } + break; case AFI_UNSPEC: case AFI_MAX: // We should never be here but to clarify the switch statement.. @@ -239,6 +260,11 @@ static const char *get_afi_safi_vty_str(afi_t afi, safi_t safi) } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "L2VPN EVPN"; + } else if (afi == AFI_LINKSTATE) { + if (safi == SAFI_LINKSTATE) + return "Link State"; + if (safi == SAFI_LINKSTATE_VPN) + return "Link State VPN"; } return "Unknown"; @@ -281,6 +307,11 @@ static const char *get_afi_safi_json_str(afi_t afi, safi_t safi) } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) return "l2VpnEvpn"; + } else if (afi == AFI_LINKSTATE) { + if (safi == SAFI_LINKSTATE) + return "linkState"; + if (safi == SAFI_LINKSTATE_VPN) + return "linkStateVPN"; } return "Unknown"; @@ -371,6 +402,9 @@ afi_t bgp_node_afi(struct vty *vty) case BGP_EVPN_NODE: afi = AFI_L2VPN; break; + case BGP_LS_NODE: + afi = AFI_LINKSTATE; + break; default: afi = AFI_IP; break; @@ -403,6 +437,9 @@ safi_t bgp_node_safi(struct vty *vty) case BGP_FLOWSPECV6_NODE: safi = SAFI_FLOWSPEC; break; + case BGP_LS_NODE: + safi = SAFI_LINKSTATE; + break; default: safi = SAFI_UNICAST; break; @@ -428,6 +465,8 @@ afi_t bgp_vty_afi_from_str(const char *afi_str) afi = AFI_IP6; else if (strmatch(afi_str, "l2vpn")) afi = AFI_L2VPN; + else if (strmatch(afi_str, "link-state")) + afi = AFI_LINKSTATE; return afi; } @@ -447,6 +486,10 @@ int argv_find_and_parse_afi(struct cmd_token **argv, int argc, int *index, ret = 1; if (afi) *afi = AFI_L2VPN; + } else if (argv_find(argv, argc, "link-state", index)) { + ret = 1; + if (afi) + *afi = AFI_LINKSTATE; } return ret; } @@ -467,6 +510,10 @@ safi_t bgp_vty_safi_from_str(const char *safi_str) safi = SAFI_LABELED_UNICAST; else if (strmatch(safi_str, "flowspec")) safi = SAFI_FLOWSPEC; + else if (strmatch(safi_str, "link-state")) + safi = SAFI_LINKSTATE; + else if (strmatch(safi_str, "link-state-vpn")) + safi = SAFI_LINKSTATE_VPN; return safi; } @@ -498,6 +545,10 @@ int argv_find_and_parse_safi(struct cmd_token **argv, int argc, int *index, ret = 1; if (safi) *safi = SAFI_FLOWSPEC; + } else if (argv_find(argv, argc, "link-state", index)) { + ret = 1; + if (safi) + *safi = SAFI_LINKSTATE; } return ret; } @@ -534,6 +585,8 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_FLOWSPEC: return "ipv4-flowspec"; case SAFI_UNSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_EVPN: case SAFI_MAX: return "unknown-afi/safi"; @@ -553,6 +606,8 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) return "ipv6-labeled-unicast"; case SAFI_FLOWSPEC: return "ipv6-flowspec"; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_UNSPEC: case SAFI_EVPN: case SAFI_MAX: @@ -568,9 +623,30 @@ static const char *get_bgp_default_af_flag(afi_t afi, safi_t safi) case SAFI_MPLS_VPN: case SAFI_ENCAP: case SAFI_LABELED_UNICAST: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: + case SAFI_MAX: + return "unknown-afi/safi"; + } + break; + case AFI_LINKSTATE: + switch (safi) { + case SAFI_EVPN: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: case SAFI_UNSPEC: case SAFI_MAX: + case SAFI_LINKSTATE: + return "link-state"; + case SAFI_LINKSTATE_VPN: + return "link-state-vpn"; + default: return "unknown-afi/safi"; } break; @@ -4136,6 +4212,7 @@ DEFPY(bgp_default_afi_safi, bgp_default_afi_safi_cmd, "ipv6-vpn|" "ipv6-labeled-unicast|" "ipv6-flowspec|" + "link-state|" "l2vpn-evpn>$afi_safi", NO_STR BGP_STR @@ -4150,6 +4227,7 @@ DEFPY(bgp_default_afi_safi, bgp_default_afi_safi_cmd, "Activate ipv6-vpn for a peer by default\n" "Activate ipv6-labeled-unicast for a peer by default\n" "Activate ipv6-flowspec for a peer by default\n" + "Activate link-state for a peer by default\n" "Activate l2vpn-evpn for a peer by default\n") { VTY_DECLVAR_CONTEXT(bgp, bgp); @@ -4159,9 +4237,14 @@ DEFPY(bgp_default_afi_safi, bgp_default_afi_safi_cmd, strlcpy(afi_safi_str, afi_safi, sizeof(afi_safi_str)); char *afi_str = strtok_r(afi_safi_str, "-", &afi_safi_str_tok); char *safi_str = strtok_r(NULL, "-", &afi_safi_str_tok); - afi_t afi = bgp_vty_afi_from_str(afi_str); + afi_t afi; safi_t safi; + if (strmatch(afi_safi, "link-state")) + afi = bgp_vty_afi_from_str("link-state"); + else + afi = bgp_vty_afi_from_str(afi_str); + /* * Impossible situation but making coverity happy */ @@ -4169,6 +4252,8 @@ DEFPY(bgp_default_afi_safi, bgp_default_afi_safi_cmd, if (strmatch(safi_str, "labeled")) safi = bgp_vty_safi_from_str("labeled-unicast"); + else if (strmatch(afi_safi, "link-state")) + safi = bgp_vty_safi_from_str("link-state"); else safi = bgp_vty_safi_from_str(safi_str); @@ -10278,6 +10363,15 @@ DEFUN_NOSH (address_family_evpn, return CMD_SUCCESS; } +DEFUN_NOSH(address_family_linkstate, address_family_linkstate_cmd, + "address-family link-state link-state", + "Enter Address Family command mode\n" BGP_AF_STR BGP_AF_MODIFIER_STR) +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + vty->node = BGP_LS_NODE; + return CMD_SUCCESS; +} + DEFUN_NOSH (bgp_segment_routing_srv6, bgp_segment_routing_srv6_cmd, "segment-routing srv6", @@ -10418,7 +10512,8 @@ DEFUN_NOSH (exit_address_family, || vty->node == BGP_IPV6L_NODE || vty->node == BGP_VPNV6_NODE || vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE - || vty->node == BGP_FLOWSPECV6_NODE) + || vty->node == BGP_FLOWSPECV6_NODE + || vty->node == BGP_LS_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } @@ -12292,11 +12387,11 @@ int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi, /* `show [ip] bgp summary' commands. */ DEFPY(show_ip_bgp_summary, show_ip_bgp_summary_cmd, - "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_CMD_STR - " [" BGP_SAFI_WITH_LABEL_CMD_STR + "show [ip] bgp [<view|vrf> VIEWVRFNAME] [" BGP_AFI_WITH_LS_CMD_STR + " [" BGP_SAFI_WITH_LABEL_LS_CMD_STR "]] [all$all] summary [established|failed] [<neighbor <A.B.C.D|X:X::X:X|WORD>|remote-as <ASNUM|internal|external>>] [terse] [wide] [json$uj]", - SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_HELP_STR - BGP_SAFI_WITH_LABEL_HELP_STR + SHOW_STR IP_STR BGP_STR BGP_INSTANCE_HELP_STR BGP_AFI_WITH_LS_HELP_STR + BGP_SAFI_WITH_LABEL_LS_HELP_STR "Display the entries for all address families\n" "Summary of BGP neighbor status\n" "Show only sessions in Established state\n" @@ -18365,6 +18460,11 @@ static void bgp_config_write_family(struct vty *vty, struct bgp *bgp, afi_t afi, } else if (afi == AFI_L2VPN) { if (safi == SAFI_EVPN) vty_frame(vty, "l2vpn evpn"); + } else if (afi == AFI_LINKSTATE) { + if (safi == SAFI_LINKSTATE) + vty_frame(vty, "link-state link-state"); + else if (safi == SAFI_LINKSTATE_VPN) + vty_frame(vty, "link-state link-state-vpn"); } vty_frame(vty, "\n"); @@ -18908,6 +19008,11 @@ int bgp_config_write(struct vty *vty) /* EVPN configuration. */ bgp_config_write_family(vty, bgp, AFI_L2VPN, SAFI_EVPN); + bgp_config_write_family(vty, bgp, AFI_LINKSTATE, + SAFI_LINKSTATE); + bgp_config_write_family(vty, bgp, AFI_LINKSTATE, + SAFI_LINKSTATE_VPN); + hook_call(bgp_inst_config_write, bgp, vty); #ifdef ENABLE_BGP_VNC @@ -19032,6 +19137,13 @@ static struct cmd_node bgp_srv6_node = { .prompt = "%s(config-router-srv6)# ", }; +static struct cmd_node bgp_ls_node = { + .name = "bgp link-state", + .node = BGP_LS_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af-ls)# ", +}; + static void community_list_vty(void); static void bgp_ac_peergroup(vector comps, struct cmd_token *token) @@ -19346,6 +19458,7 @@ void bgp_vty_init(void) install_node(&bgp_flowspecv4_node); install_node(&bgp_flowspecv6_node); install_node(&bgp_srv6_node); + install_node(&bgp_ls_node); /* Install default VTY commands to new nodes. */ install_default(BGP_NODE); @@ -19362,6 +19475,7 @@ void bgp_vty_init(void) install_default(BGP_EVPN_NODE); install_default(BGP_EVPN_VNI_NODE); install_default(BGP_SRV6_NODE); + install_default(BGP_LS_NODE); /* "global bgp inq-limit command */ install_element(CONFIG_NODE, &bgp_inq_limit_cmd); @@ -19493,6 +19607,13 @@ void bgp_vty_init(void) install_element(BGP_IPV6L_NODE, &bgp_maxpaths_ibgp_cluster_cmd); install_element(BGP_IPV6L_NODE, &no_bgp_maxpaths_ibgp_cmd); + install_element(BGP_LS_NODE, &bgp_maxpaths_cmd); + install_element(BGP_LS_NODE, &no_bgp_maxpaths_cmd); + install_element(BGP_LS_NODE, &bgp_maxpaths_ibgp_cmd); + install_element(BGP_LS_NODE, &no_bgp_maxpaths_ibgp_cmd); + install_element(BGP_LS_NODE, &bgp_maxpaths_ibgp_cluster_cmd); + + /* "timers bgp" commands. */ install_element(BGP_NODE, &bgp_timers_cmd); install_element(BGP_NODE, &no_bgp_timers_cmd); @@ -19715,6 +19836,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &neighbor_activate_cmd); + install_element(BGP_LS_NODE, &neighbor_activate_cmd); /* "no neighbor activate" commands. */ install_element(BGP_NODE, &no_neighbor_activate_hidden_cmd); @@ -19729,6 +19851,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_activate_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_activate_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_activate_cmd); + install_element(BGP_LS_NODE, &no_neighbor_activate_cmd); /* "neighbor peer-group" set commands. */ install_element(BGP_NODE, &neighbor_set_peer_group_cmd); @@ -19743,6 +19866,8 @@ void bgp_vty_init(void) &neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_set_peer_group_hidden_cmd); + install_element(BGP_LS_NODE, &neighbor_set_peer_group_hidden_cmd); + /* "no neighbor peer-group unset" commands. */ install_element(BGP_NODE, &no_neighbor_set_peer_group_cmd); @@ -19757,6 +19882,7 @@ void bgp_vty_init(void) &no_neighbor_set_peer_group_hidden_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_set_peer_group_hidden_cmd); + install_element(BGP_LS_NODE, &no_neighbor_set_peer_group_hidden_cmd); /* "neighbor softreconfiguration inbound" commands.*/ install_element(BGP_NODE, &neighbor_soft_reconfiguration_hidden_cmd); @@ -19787,6 +19913,8 @@ void bgp_vty_init(void) &no_neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &neighbor_soft_reconfiguration_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_soft_reconfiguration_cmd); + install_element(BGP_LS_NODE, &neighbor_soft_reconfiguration_cmd); + install_element(BGP_LS_NODE, &no_neighbor_soft_reconfiguration_cmd); /* "neighbor attribute-unchanged" commands. */ install_element(BGP_NODE, &neighbor_attr_unchanged_hidden_cmd); @@ -19807,9 +19935,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_VPNV6_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_attr_unchanged_cmd); - install_element(BGP_EVPN_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_attr_unchanged_cmd); + install_element(BGP_LS_NODE, &neighbor_attr_unchanged_cmd); + install_element(BGP_LS_NODE, &no_neighbor_attr_unchanged_cmd); install_element(BGP_FLOWSPECV4_NODE, &neighbor_attr_unchanged_cmd); install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_attr_unchanged_cmd); @@ -19842,6 +19971,8 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_cmd); + install_element(BGP_LS_NODE, &neighbor_nexthop_self_cmd); + install_element(BGP_LS_NODE, &no_neighbor_nexthop_self_cmd); /* "neighbor next-hop-self force" commands. */ install_element(BGP_NODE, &neighbor_nexthop_self_force_hidden_cmd); @@ -19890,6 +20021,8 @@ void bgp_vty_init(void) &no_neighbor_nexthop_self_all_hidden_cmd); install_element(BGP_EVPN_NODE, &neighbor_nexthop_self_force_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_nexthop_self_force_cmd); + install_element(BGP_LS_NODE, &neighbor_nexthop_self_force_cmd); + install_element(BGP_LS_NODE, &no_neighbor_nexthop_self_force_cmd); /* "neighbor as-override" commands. */ install_element(BGP_NODE, &neighbor_as_override_hidden_cmd); @@ -20022,6 +20155,18 @@ void bgp_vty_init(void) &neighbor_remove_private_as_all_replace_as_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_LS_NODE, &neighbor_remove_private_as_cmd); + install_element(BGP_LS_NODE, &no_neighbor_remove_private_as_cmd); + install_element(BGP_LS_NODE, &neighbor_remove_private_as_all_cmd); + install_element(BGP_LS_NODE, &no_neighbor_remove_private_as_all_cmd); + install_element(BGP_LS_NODE, + &neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_LS_NODE, + &no_neighbor_remove_private_as_replace_as_cmd); + install_element(BGP_LS_NODE, + &neighbor_remove_private_as_all_replace_as_cmd); + install_element(BGP_LS_NODE, + &no_neighbor_remove_private_as_all_replace_as_cmd); /* "neighbor send-community" commands.*/ install_element(BGP_NODE, &neighbor_send_community_hidden_cmd); @@ -20060,6 +20205,10 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &neighbor_send_community_type_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_send_community_type_cmd); + install_element(BGP_LS_NODE, &neighbor_send_community_cmd); + install_element(BGP_LS_NODE, &neighbor_send_community_type_cmd); + install_element(BGP_LS_NODE, &no_neighbor_send_community_cmd); + install_element(BGP_LS_NODE, &no_neighbor_send_community_type_cmd); /* "neighbor route-reflector" commands.*/ install_element(BGP_NODE, &neighbor_route_reflector_client_hidden_cmd); @@ -20097,6 +20246,8 @@ void bgp_vty_init(void) &no_neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_reflector_client_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_reflector_client_cmd); + install_element(BGP_LS_NODE, &neighbor_route_reflector_client_cmd); + install_element(BGP_LS_NODE, &no_neighbor_route_reflector_client_cmd); /* "neighbor route-server" commands.*/ install_element(BGP_NODE, &neighbor_route_server_client_hidden_cmd); @@ -20125,6 +20276,8 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV6_NODE, &neighbor_route_server_client_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_server_client_cmd); + install_element(BGP_LS_NODE, &neighbor_route_server_client_cmd); + install_element(BGP_LS_NODE, &no_neighbor_route_server_client_cmd); /* "neighbor disable-addpath-rx" commands. */ install_element(BGP_IPV4_NODE, &neighbor_disable_addpath_rx_cmd); @@ -20286,6 +20439,8 @@ void bgp_vty_init(void) install_element(BGP_IPV6M_NODE, &no_neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6L_NODE, &neighbor_capability_orf_prefix_cmd); install_element(BGP_IPV6L_NODE, &no_neighbor_capability_orf_prefix_cmd); + install_element(BGP_LS_NODE, &neighbor_capability_orf_prefix_cmd); + install_element(BGP_LS_NODE, &no_neighbor_capability_orf_prefix_cmd); /* "neighbor capability dynamic" commands.*/ install_element(BGP_NODE, &neighbor_capability_dynamic_cmd); @@ -20422,6 +20577,8 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &neighbor_distribute_list_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_distribute_list_cmd); + install_element(BGP_LS_NODE, &neighbor_distribute_list_cmd); + install_element(BGP_LS_NODE, &no_neighbor_distribute_list_cmd); /* "neighbor prefix-list" commands. */ install_element(BGP_NODE, &neighbor_prefix_list_hidden_cmd); @@ -20446,6 +20603,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_prefix_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_prefix_list_cmd); + install_element(BGP_LS_NODE, &neighbor_prefix_list_cmd); /* "neighbor filter-list" commands. */ install_element(BGP_NODE, &neighbor_filter_list_hidden_cmd); @@ -20470,6 +20628,8 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &no_neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &neighbor_filter_list_cmd); install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_filter_list_cmd); + install_element(BGP_LS_NODE, &neighbor_filter_list_cmd); + install_element(BGP_LS_NODE, &no_neighbor_filter_list_cmd); /* "neighbor route-map" commands. */ install_element(BGP_NODE, &neighbor_route_map_hidden_cmd); @@ -20496,6 +20656,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV6_NODE, &no_neighbor_route_map_cmd); install_element(BGP_EVPN_NODE, &neighbor_route_map_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_route_map_cmd); + install_element(BGP_LS_NODE, &neighbor_route_map_cmd); /* "neighbor unsuppress-map" commands. */ install_element(BGP_NODE, &neighbor_unsuppress_map_hidden_cmd); @@ -20516,6 +20677,8 @@ void bgp_vty_init(void) install_element(BGP_VPNV4_NODE, &no_neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &neighbor_unsuppress_map_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_unsuppress_map_cmd); + install_element(BGP_LS_NODE, &neighbor_unsuppress_map_cmd); + install_element(BGP_LS_NODE, &no_neighbor_unsuppress_map_cmd); /* "neighbor advertise-map" commands. */ install_element(BGP_NODE, &bgp_condadv_period_cmd); @@ -20635,6 +20798,15 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &neighbor_maximum_prefix_threshold_restart_cmd); install_element(BGP_VPNV6_NODE, &no_neighbor_maximum_prefix_cmd); + install_element(BGP_LS_NODE, &neighbor_maximum_prefix_cmd); + install_element(BGP_LS_NODE, &neighbor_maximum_prefix_threshold_cmd); + install_element(BGP_LS_NODE, &neighbor_maximum_prefix_warning_cmd); + install_element(BGP_LS_NODE, + &neighbor_maximum_prefix_threshold_warning_cmd); + install_element(BGP_LS_NODE, &neighbor_maximum_prefix_restart_cmd); + install_element(BGP_LS_NODE, + &neighbor_maximum_prefix_threshold_restart_cmd); + install_element(BGP_LS_NODE, &no_neighbor_maximum_prefix_cmd); /* "neighbor allowas-in" */ install_element(BGP_NODE, &neighbor_allowas_in_hidden_cmd); @@ -20657,6 +20829,8 @@ void bgp_vty_init(void) install_element(BGP_VPNV6_NODE, &no_neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &neighbor_allowas_in_cmd); install_element(BGP_EVPN_NODE, &no_neighbor_allowas_in_cmd); + install_element(BGP_LS_NODE, &neighbor_allowas_in_cmd); + install_element(BGP_LS_NODE, &no_neighbor_allowas_in_cmd); /* neighbor accept-own */ install_element(BGP_VPNV4_NODE, &neighbor_accept_own_cmd); @@ -20692,6 +20866,8 @@ void bgp_vty_init(void) install_element(BGP_NODE, &address_family_evpn_cmd); + install_element(BGP_NODE, &address_family_linkstate_cmd); + /* "exit-address-family" command. */ install_element(BGP_IPV4_NODE, &exit_address_family_cmd); install_element(BGP_IPV4M_NODE, &exit_address_family_cmd); @@ -20704,6 +20880,7 @@ void bgp_vty_init(void) install_element(BGP_FLOWSPECV4_NODE, &exit_address_family_cmd); install_element(BGP_FLOWSPECV6_NODE, &exit_address_family_cmd); install_element(BGP_EVPN_NODE, &exit_address_family_cmd); + install_element(BGP_LS_NODE, &exit_address_family_cmd); /* BGP retain all route-target */ install_element(BGP_VPNV4_NODE, &bgp_retain_route_target_cmd); diff --git a/bgpd/bgp_vty.h b/bgpd/bgp_vty.h index a105b6de3f..955752f85a 100644 --- a/bgpd/bgp_vty.h +++ b/bgpd/bgp_vty.h @@ -17,6 +17,8 @@ struct bgp; #define BGP_AF_MODIFIER_STR "Address Family modifier\n" #define BGP_AFI_CMD_STR "<ipv4|ipv6>" #define BGP_AFI_HELP_STR BGP_AF_STR BGP_AF_STR +#define BGP_AFI_WITH_LS_CMD_STR "<ipv4|ipv6|link-state>" +#define BGP_AFI_WITH_LS_HELP_STR BGP_AF_STR BGP_AF_STR BGP_AF_STR #define BGP_SAFI_CMD_STR "<unicast|multicast|vpn>" #define BGP_SAFI_HELP_STR \ BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR @@ -28,6 +30,12 @@ struct bgp; BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR \ BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR +#define BGP_SAFI_WITH_LABEL_LS_CMD_STR \ + "<unicast|multicast|vpn|labeled-unicast|flowspec|link-state>" +#define BGP_SAFI_WITH_LABEL_LS_HELP_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR \ + BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR BGP_AF_MODIFIER_STR + #define BGP_SELF_ORIG_CMD_STR "self-originate" #define BGP_SELF_ORIG_HELP_STR "Display only self-originated routes\n" diff --git a/bgpd/bgp_zebra.c b/bgpd/bgp_zebra.c index 3d993e12c0..741542f9ba 100644 --- a/bgpd/bgp_zebra.c +++ b/bgpd/bgp_zebra.c @@ -1322,6 +1322,10 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, uint32_t bos = 0; uint32_t exp = 0; + if (afi == AFI_LINKSTATE) + /* nothing to install */ + return; + /* * BGP is installing this route and bgp has been configured * to suppress announcements until the route has been installed @@ -1557,17 +1561,17 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, api_nh->weight = nh_weight; if (((mpinfo->attr->srv6_l3vpn && - !sid_zero(&mpinfo->attr->srv6_l3vpn->sid)) || + !sid_zero_ipv6(&mpinfo->attr->srv6_l3vpn->sid)) || (mpinfo->attr->srv6_vpn && - !sid_zero(&mpinfo->attr->srv6_vpn->sid))) && + !sid_zero_ipv6(&mpinfo->attr->srv6_vpn->sid))) && !is_evpn && bgp_is_valid_label(&labels[0])) { struct in6_addr *sid_tmp = mpinfo->attr->srv6_l3vpn ? (&mpinfo->attr->srv6_l3vpn->sid) : (&mpinfo->attr->srv6_vpn->sid); - memcpy(&api_nh->seg6_segs, sid_tmp, - sizeof(api_nh->seg6_segs)); + memcpy(&api_nh->seg6_segs[0], sid_tmp, + sizeof(api_nh->seg6_segs[0])); if (mpinfo->attr->srv6_l3vpn && mpinfo->attr->srv6_l3vpn->transposition_len != 0) { @@ -1581,13 +1585,14 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, continue; } - transpose_sid(&api_nh->seg6_segs, nh_label, + transpose_sid(&api_nh->seg6_segs[0], nh_label, mpinfo->attr->srv6_l3vpn ->transposition_offset, mpinfo->attr->srv6_l3vpn ->transposition_len); } + api_nh->seg_num = 1; SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); } @@ -1704,7 +1709,7 @@ void bgp_zebra_announce(struct bgp_dest *dest, const struct prefix *p, if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6) && !CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_EVPN)) { - inet_ntop(AF_INET6, &api_nh->seg6_segs, + inet_ntop(AF_INET6, &api_nh->seg6_segs[0], sid_buf, sizeof(sid_buf)); snprintf(segs_buf, sizeof(segs_buf), "segs %s", sid_buf); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 585863954c..dbbc1fa822 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -71,6 +71,8 @@ #include "bgpd/bgp_io.h" #include "bgpd/bgp_ecommunity.h" #include "bgpd/bgp_flowspec.h" +#include "bgpd/bgp_linkstate.h" +#include "bgpd/bgp_linkstate_vty.h" #include "bgpd/bgp_labelpool.h" #include "bgpd/bgp_pbr.h" #include "bgpd/bgp_addpath.h" @@ -2040,6 +2042,10 @@ void peer_as_change(struct peer *peer, as_t as, int as_specified, PEER_FLAG_REFLECTOR_CLIENT); UNSET_FLAG(peer->af_flags[AFI_L2VPN][SAFI_EVPN], PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_LINKSTATE][SAFI_LINKSTATE], + PEER_FLAG_REFLECTOR_CLIENT); + UNSET_FLAG(peer->af_flags[AFI_LINKSTATE][SAFI_LINKSTATE_VPN], + PEER_FLAG_REFLECTOR_CLIENT); } } @@ -4384,7 +4390,9 @@ bool peer_active(struct peer *peer) || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] - || peer->afc[AFI_L2VPN][SAFI_EVPN]) + || peer->afc[AFI_L2VPN][SAFI_EVPN] + || peer->afc[AFI_LINKSTATE][SAFI_LINKSTATE] + || peer->afc[AFI_LINKSTATE][SAFI_LINKSTATE_VPN]) return true; return false; } @@ -4404,7 +4412,9 @@ bool peer_active_nego(struct peer *peer) || peer->afc_nego[AFI_IP6][SAFI_MPLS_VPN] || peer->afc_nego[AFI_IP6][SAFI_ENCAP] || peer->afc_nego[AFI_IP6][SAFI_FLOWSPEC] - || peer->afc_nego[AFI_L2VPN][SAFI_EVPN]) + || peer->afc_nego[AFI_L2VPN][SAFI_EVPN] + || peer->afc_nego[AFI_LINKSTATE][SAFI_LINKSTATE] + || peer->afc_nego[AFI_LINKSTATE][SAFI_LINKSTATE_VPN]) return true; return false; } @@ -8384,6 +8394,8 @@ void bgp_init(unsigned short instance) #endif bgp_ethernetvpn_init(); bgp_flowspec_vty_init(); + bgp_linkstate_init(); + bgp_linkstate_vty_init(); /* Access list initialize. */ access_list_init(); diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index c3e55b711e..8f78e33f65 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -81,6 +81,8 @@ enum bgp_af_index { BGP_AF_IPV6_LBL_UNICAST, BGP_AF_IPV4_FLOWSPEC, BGP_AF_IPV6_FLOWSPEC, + BGP_AF_LINKSTATE, + BGP_AF_LINKSTATE_VPN, BGP_AF_MAX }; @@ -1924,6 +1926,7 @@ struct bgp_nlri { #define BGP_ATTR_ENCAP 23 #define BGP_ATTR_IPV6_EXT_COMMUNITIES 25 #define BGP_ATTR_AIGP 26 +#define BGP_ATTR_LINK_STATE 29 #define BGP_ATTR_LARGE_COMMUNITIES 32 #define BGP_ATTR_OTC 35 #define BGP_ATTR_PREFIX_SID 40 @@ -2504,6 +2507,8 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV4_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV4_FLOWSPEC; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_EVPN: case SAFI_UNSPEC: case SAFI_MAX: @@ -2524,6 +2529,8 @@ static inline int afindex(afi_t afi, safi_t safi) return BGP_AF_IPV6_ENCAP; case SAFI_FLOWSPEC: return BGP_AF_IPV6_FLOWSPEC; + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_EVPN: case SAFI_UNSPEC: case SAFI_MAX: @@ -2541,6 +2548,26 @@ static inline int afindex(afi_t afi, safi_t safi) case SAFI_ENCAP: case SAFI_FLOWSPEC: case SAFI_UNSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: + case SAFI_MAX: + return BGP_AF_MAX; + } + break; + case AFI_LINKSTATE: + switch (safi) { + case SAFI_LINKSTATE: + return BGP_AF_LINKSTATE; + case SAFI_LINKSTATE_VPN: + return BGP_AF_LINKSTATE_VPN; + case SAFI_EVPN: + case SAFI_UNICAST: + case SAFI_MULTICAST: + case SAFI_LABELED_UNICAST: + case SAFI_MPLS_VPN: + case SAFI_ENCAP: + case SAFI_FLOWSPEC: + case SAFI_UNSPEC: case SAFI_MAX: return BGP_AF_MAX; } @@ -2570,7 +2597,9 @@ static inline int peer_afi_active_nego(const struct peer *peer, afi_t afi) || peer->afc_nego[afi][SAFI_MPLS_VPN] || peer->afc_nego[afi][SAFI_ENCAP] || peer->afc_nego[afi][SAFI_FLOWSPEC] - || peer->afc_nego[afi][SAFI_EVPN]) + || peer->afc_nego[afi][SAFI_EVPN] + || peer->afc_nego[afi][SAFI_LINKSTATE] + || peer->afc_nego[afi][SAFI_LINKSTATE_VPN]) return 1; return 0; } @@ -2590,16 +2619,19 @@ static inline int peer_group_af_configured(struct peer_group *group) || peer->afc[AFI_IP6][SAFI_MPLS_VPN] || peer->afc[AFI_IP6][SAFI_ENCAP] || peer->afc[AFI_IP6][SAFI_FLOWSPEC] - || peer->afc[AFI_L2VPN][SAFI_EVPN]) + || peer->afc[AFI_L2VPN][SAFI_EVPN] + || peer->afc[AFI_LINKSTATE][SAFI_LINKSTATE] + || peer->afc[AFI_LINKSTATE][SAFI_LINKSTATE_VPN]) return 1; return 0; } -static inline char *timestamp_string(time_t ts) +static inline char *timestamp_string(time_t ts, char *timebuf) { time_t tbuf; + tbuf = time(NULL) - (monotime(NULL) - ts); - return ctime(&tbuf); + return ctime_r(&tbuf, timebuf); } static inline bool peer_established(struct peer_connection *connection) diff --git a/bgpd/rfapi/rfapi_import.c b/bgpd/rfapi/rfapi_import.c index a93e186f8d..126ba225cb 100644 --- a/bgpd/rfapi/rfapi_import.c +++ b/bgpd/rfapi/rfapi_import.c @@ -248,6 +248,8 @@ void rfapiCheckRefcount(struct agg_node *rn, safi_t safi, int lockoffset) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_MAX: assert(!"Passed in safi should be impossible"); } @@ -2972,6 +2974,7 @@ static void rfapiBgpInfoFilteredImportEncap( case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); return; @@ -3420,6 +3423,7 @@ void rfapiBgpInfoFilteredImportVPN( rt = import_table->imported_vpn[afi]; break; + case AFI_LINKSTATE: case AFI_UNSPEC: case AFI_MAX: flog_err(EC_LIB_DEVELOPMENT, "%s: bad afi %d", __func__, afi); @@ -3819,6 +3823,8 @@ rfapiBgpInfoFilteredImportFunction(safi_t safi) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_MAX: /* not expected */ flog_err(EC_LIB_DEVELOPMENT, "%s: bad safi %d", __func__, safi); @@ -4063,6 +4069,8 @@ static void rfapiProcessPeerDownRt(struct peer *peer, case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_MAX: /* Suppress uninitialized variable warning */ rt = NULL; diff --git a/bgpd/rfapi/rfapi_monitor.c b/bgpd/rfapi/rfapi_monitor.c index 3fe957ba4d..766b5d5360 100644 --- a/bgpd/rfapi/rfapi_monitor.c +++ b/bgpd/rfapi/rfapi_monitor.c @@ -237,6 +237,8 @@ void rfapiMonitorExtraFlush(safi_t safi, struct agg_node *rn) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_MAX: assert(0); } @@ -305,6 +307,8 @@ void rfapiMonitorExtraPrune(safi_t safi, struct agg_node *rn) case SAFI_EVPN: case SAFI_LABELED_UNICAST: case SAFI_FLOWSPEC: + case SAFI_LINKSTATE: + case SAFI_LINKSTATE_VPN: case SAFI_MAX: assert(0); } diff --git a/bgpd/subdir.am b/bgpd/subdir.am index c2dd207a49..8b6b5ae743 100644 --- a/bgpd/subdir.am +++ b/bgpd/subdir.am @@ -51,6 +51,9 @@ bgpd_libbgp_a_SOURCES = \ bgpd/bgp_label.c \ bgpd/bgp_labelpool.c \ bgpd/bgp_lcommunity.c \ + bgpd/bgp_linkstate.c \ + bgpd/bgp_linkstate_tlv.c \ + bgpd/bgp_linkstate_vty.c \ bgpd/bgp_mac.c \ bgpd/bgp_memory.c \ bgpd/bgp_mpath.c \ @@ -133,6 +136,9 @@ noinst_HEADERS += \ bgpd/bgp_label.h \ bgpd/bgp_labelpool.h \ bgpd/bgp_lcommunity.h \ + bgpd/bgp_linkstate.h \ + bgpd/bgp_linkstate_tlv.h \ + bgpd/bgp_linkstate_vty.h \ bgpd/bgp_mac.h \ bgpd/bgp_memory.h \ bgpd/bgp_mpath.h \ @@ -209,6 +215,7 @@ clippy_scan += \ bgpd/bgp_debug.c \ bgpd/bgp_evpn_vty.c \ bgpd/bgp_labelpool.c \ + bgpd/bgp_linkstate_vty.c \ bgpd/bgp_route.c \ bgpd/bgp_routemap.c \ bgpd/bgp_rpki.c \ diff --git a/doc/developer/index.rst b/doc/developer/index.rst index 5da7bc4168..c2123f1ad2 100644 --- a/doc/developer/index.rst +++ b/doc/developer/index.rst @@ -22,3 +22,4 @@ FRRouting Developer's Guide path pceplib link-state + northbound/northbound diff --git a/doc/developer/northbound/_sidebar.rst b/doc/developer/northbound/_sidebar.rst new file mode 100644 index 0000000000..f2bca0bc05 --- /dev/null +++ b/doc/developer/northbound/_sidebar.rst @@ -0,0 +1,15 @@ +Northbound: +~~~~~~~~~~~ + +- [[Architecture]] +- [[Transactional CLI]] +- [[Retrofitting Configuration Commands]] +- [[Operational data, RPCs and Notifications]] +- [[Plugins - ConfD]] +- [[Plugins: Sysrepo]] +- [[Plugins - Writing Your Own]] +- [[YANG module translator]] +- [[Advanced topics]] +- [[YANG tools]] +- [[Demos]] +- [[Links]] diff --git a/doc/developer/northbound/advanced-topics.rst b/doc/developer/northbound/advanced-topics.rst new file mode 100644 index 0000000000..bee29a95a9 --- /dev/null +++ b/doc/developer/northbound/advanced-topics.rst @@ -0,0 +1,294 @@ +Auto-generated CLI commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to have less code to maintain, it should be possible to write a +tool that auto-generates CLI commands based on the FRR YANG models. As a +matter of fact, there are already a number of NETCONF-based CLIs that do +exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__, +ConfD’s CLI). + +The problem however is that there isn’t an exact one-to-one mapping +between the existing CLI commands and the corresponding YANG nodes from +the native models. As an example, ripd’s +``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` command +changes three YANG leaves at the same time. In order to auto-generate +CLI commands and retain their original form, it’s necessary to add +annotations in the YANG modules to specify how the commands should look +like. Without YANG annotations, the CLI auto-generator will generate a +command for each YANG leaf, (leaf-)list and presence-container. The +ripd’s ``timers basic`` command, for instance, would become three +different commands, which would be undesirable. + + This Tail-f’s® + `document <http://info.tail-f.com/hubfs/Whitepapers/Tail-f_ConfD-CLI__Cfg_Mode_App_Note_Rev%20C.pdf>`__ + shows how to customize ConfD auto-generated CLI commands using YANG + annotations. + +The good news is that *libyang* allows users to create plugins to +implement their own YANG extensions, which can be used to implement CLI +annotations. If done properly, a CLI generator can save FRR developers +from writing and maintaining hundreds if not thousands of DEFPYs! + +CLI on a separate program +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flexible design of the northbound architecture opens the door to +move the CLI to a separate program in the long-term future. Some +advantages of doing so would be: \* Treat the CLI as just another +northbound client, instead of having CLI commands embedded in the +binaries of all FRR daemons. \* Improved robustness: bugs in CLI +commands (e.g. null-pointer dereferences) or in the CLI code itself +wouldn’t affect the FRR daemons. \* Foster innovation by allowing other +CLI programs to be implemented, possibly using higher level programming +languages. + +The problem, however, is that the northbound retrofitting process will +convert only the CLI configuration commands and EXEC commands in a first +moment. Retrofitting the “show” commands is a completely different story +and shouldn’t happen anytime soon. This should hinder progress towards +moving the CLI to a separate program. + +Proposed feature: confirmed commits +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Confirmed commits allow the user to request an automatic rollback to the +previous configuration if the commit operation is not confirmed within a +number of minutes. This is particularly useful when the user is +accessing the CLI through the network (e.g. using SSH) and any +configuration change might cause an unexpected loss of connectivity +between the user and the router (e.g. misconfiguration of a routing +protocol). By using a confirmed commit, the user can rest assured the +connectivity will be restored after the given timeout expires, avoiding +the need to access the router physically to fix the problem. + +Example of how this feature could be provided in the CLI: +``commit confirmed [minutes <1-60>]``. The ability to do confirmed +commits should also be exposed in the northbound API so that the +northbound plugins can also take advantage of it (in the case of the +Sysrepo and ConfD plugins, confirmed commits are implemented externally +in the *netopeer2-server* and *confd* daemons, respectively). + +Proposed feature: enable/disable configuration commands/sections +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the ``lyd_node`` data structure from *libyang* can hold private +data, it should be possible to mark configuration commands or sections +as active or inactive. This would allow CLI users to leverage this +feature to disable parts of the running configuration without actually +removing the associated commands, and then re-enable the disabled +configuration commands or sections later when necessary. Example: + +:: + + ripd(config)# show configuration running + Configuration: + [snip] + ! + router rip + default-metric 2 + distance 80 + network eth0 + network eth1 + ! + end + ripd(config)# disable router rip + ripd(config)# commit + % Configuration committed successfully (Transaction ID #7). + + ripd(config)# show configuration running + Configuration: + [snip] + ! + !router rip + !default-metric 2 + !distance 80 + !network eth0 + !network eth1 + ! + end + ripd(config)# enable router rip + ripd(config)# commit + % Configuration committed successfully (Transaction ID #8). + + ripd(config)# show configuration running + [snip] + frr defaults traditional + ! + router rip + default-metric 2 + distance 80 + network eth0 + network eth1 + ! + end + +This capability could be useful in a number of occasions, like disabling +configuration commands that are no longer necessary (e.g. ACLs) but that +might be necessary at a later point in the future. Other example is +allowing users to disable a configuration section for testing purposes, +and then re-enable it easily without needing to copy and paste any +command. + +Configuration reloads +~~~~~~~~~~~~~~~~~~~~~ + +Given the limitations of the previous northbound architecture, the FRR +daemons didn’t have the ability to reload their configuration files by +themselves. The SIGHUP handler of most daemons would only re-read the +configuration file and merge it into the running configuration. In most +cases, however, what is desired is to replace the running configuration +by the updated configuration file. The *frr-reload.py* script was +written to work around this problem and it does it well to a certain +extent. The problem with the *frr-reload.py* script is that it’s full of +special cases here and there, which makes it fragile and unreliable. +Maintaining the script is also an additional burden for FRR developers, +few of whom are familiar with its code or know when it needs to be +updated to account for a new feature. + +In the new northbound architecture, reloading the configuration file can +be easily implemented using a configuration transaction. Once the FRR +northbound retrofitting process is complete, all daemons should have the +ability to reload their configuration files upon receiving the SIGHUP +signal, or when the ``configuration load [...] replace`` command is +used. Once that point is reached, the *frr-reload.py* script will no +longer be necessary and should be removed from the FRR repository. + +Configuration changes coming from the kernel +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This +`post <http://discuss.tail-f.com/t/who-should-not-set-configuration-once-a-system-is-up-and-running/111>`__ +from the Tail-f’s® forum describes the problem of letting systems +configure themselves behind the users back. Here are some selected +snippets from it: > Traditionally, northbound interface users are the +ones in charge of providing configuration data for systems. > > In some +systems, we see a deviation from this traditional practice; allowing +systems to configure “themselves” behind the scenes (or behind the users +back). > > While there might be a business case for such a practice, +this kind of configuration remains “dangerous” from northbound users +perspective and makes systems hard to predict and even harder to debug. +(…) > > With the advent of transactional Network configuration, this +practice can not work anymore. The fact that systems are given the right +to change configuration is a key here in breaking transactional +configuration in a Network. + +FRR is immune to some of the problems described in the aforementioned +post. Management clients can configure interfaces that don’t yet exist, +and once an interface is deleted from the kernel, its configuration is +retained in FRR. + +There are however some cases where information learned from the kernel +(e.g. using netlink) can affect the running configuration of all FRR +daemons. Examples: interface rename events, VRF rename events, interface +being moved to a different VRF, etc. In these cases, since these events +can’t be ignored, the best we can do is to send YANG notifications to +the management clients to inform about the configuration changes. The +management clients should then be prepared to handle such notifications +and react accordingly. + +Interfaces and VRFs +~~~~~~~~~~~~~~~~~~~ + +As of now zebra doesn’t have the ability to create VRFs or virtual +interfaces in the kernel. The ``vrf`` and ``interface`` commands only +create pre-provisioned VRFs and interfaces that are only activated when +the corresponding information is learned from the kernel. When +configuring FRR using an external management client, like a NETCONF +client, it might be desirable to actually create functional VRFs and +virtual interfaces (e.g. VLAN subinterfaces, bridges, etc) that are +installed in the kernel using OS-specific APIs (e.g. netlink, routing +socket, etc). Work needs to be done in this area to make this possible. + +Shared configuration objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One of the existing problems in FRR is that it’s hard to ensure that all +daemons are in sync with respect to the shared configuration objects +(e.g. interfaces, VRFs, route-maps, ACLs, etc). When a route-map is +configured using *vtysh*, the same command is sent to all relevant +daemons (the daemons that implement route-maps), which ensures +synchronization among them. The problem is when a daemon starts after +the route-maps are created. In this case this daemon wouldn’t be aware +of the previously configured route-maps (unlike the other daemons), +which can lead to a lot of confusion and unexpected problems. + +With the new northbound architecture, configuration objects can be +manipulated using higher level abstractions, which opens more +possibilities to solve this decades-long problem. As an example, one +solution would be to make the FRR daemons fetch the shared configuration +objects from zebra using the ZAPI interface during initialization. The +shared configuration objects could be requested using a list of XPaths +expressions in the ``ZEBRA_HELLO`` message, which zebra would respond by +sending the shared configuration objects encoded in the JSON format. +This solution however doesn’t address the case where zebra starts or +restarts after the other FRR daemons. Other solution would be to store +the shared configuration objects in the northbound SQL database and make +all daemons fetch these objects from there. So far no work has been made +on this area as more investigation needs to be done. + +vtysh support +~~~~~~~~~~~~~ + +As explained in the [[Transactional CLI]] page, all commands introduced +by the transactional CLI are not yet available in *vtysh*. This needs to +be addressed in the short term future. Some challenges for doing that +work include: \* How to display configurations (running, candidates and +rollbacks) in a more clever way? The implementation of the +``show running-config`` command in *vtysh* is not something that should +be followed as an example. A better idea would be to fetch the desired +configuration from all daemons (encoded in JSON for example), merge them +all into a single ``lyd_node`` variable and then display the combined +configurations from this variable (the configuration merges would +transparently take care of combining the shared configuration objects). +In order to be able to manipulate the JSON configurations, *vtysh* will +need to load the YANG modules from all daemons at startup (this might +have a minimal impact on startup time). The only issue with this +approach is that the ``cli_show()`` callbacks from all daemons are +embedded in their binaries and thus not accessible externally. It might +be necessary to compile these callbacks on a separate shared library so +that they are accessible to *vtysh* too. Other than that, displaying the +combined configurations in the JSON/XML formats should be +straightforward. \* With the current design, transaction IDs are +per-daemon and not global across all FRR daemons. This means that the +same transaction ID can represent different transactions on different +daemons. Given this observation, how to implement the +``rollback configuration`` command in *vtysh*? The easy solution would +be to add a ``daemon WORD`` argument to specify the context of the +rollback, but per-daemon rollbacks would certainly be confusing and +convoluted to end users. A better idea would be to attack the root of +the problem: change configuration transactions to be global instead of +being per-daemon. This involves a bigger change in the northbound +architecture, and would have implications on how transactions are stored +in the SQL database (daemon-specific and shared configuration objects +would need to have their own tables or columns). \* Loading +configuration files in the JSON or XML formats will be tricky, as +*vtysh* will need to know which sections of the configuration should be +sent to which daemons. *vtysh* will either need to fetch the YANG +modules implemented by all daemons at runtime or obtain this information +at compile-time somehow. + +Detecting type mismatches at compile-time +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As described in the [[Retrofitting Configuration Commands]] page, the +northbound configuration callbacks detect type mismatches at runtime +when fetching data from the the ``dnode`` parameter (which represents +the configuration node being created, modified, deleted or moved). When +a type mismatch is detected, the program aborts and displays a backtrace +showing where the problem happened. It would be desirable to detect such +type mismatches at compile-time, the earlier the problems are detected +the sooner they are fixed. + +One possible solution to this problem would be to auto-generate C +structures from the YANG models and provide a function that converts a +libyang’s ``lyd_node`` variable to a C structure containing the same +information. The northbound callbacks could then fetch configuration +data from this C structure, which would naturally lead to type +mismatches being detected at compile time. One of the challenges of +doing this would be the handling of YANG lists and leaf-lists. It would +be necessary to use dynamic data structures like hashes or rb-trees to +hold all elements of the lists and leaf-lists, and the process of +converting a ``lyd_node`` to an auto-generated C-structure could be +expensive. At this point it’s unclear if it’s worth adding more +complexity in the northbound architecture to solve this specific +problem. diff --git a/doc/developer/northbound/architecture.rst b/doc/developer/northbound/architecture.rst new file mode 100644 index 0000000000..f551ce9e2f --- /dev/null +++ b/doc/developer/northbound/architecture.rst @@ -0,0 +1,283 @@ +Introduction +------------ + +The goal of the new northbound API is to provide a better interface to +configure and monitor FRR programatically. The current design based on +CLI commands is no longer adequate in a world where computer networks +are becoming increasingly bigger, more diverse and more complex. Network +scripting using *expect* and screen scraping techniques is too primitive +and unreliable to be used in large-scale networks. What is proposed is +to modernize FRR to turn it into an API-first routing stack, and +reposition the CLI on top of this API. The most important change, +however, is not the API that will be provided to external users. In +fact, multiple APIs will be supported and users will have the ability to +write custom management APIs if necessary. The biggest change is the +introduction of a model-driven management architecture based on the +`YANG <https://tools.ietf.org/html/rfc7950>`__ modeling language. +Instead of writing code tied to any particular user interface +(e.g. DEFUNs), YANG allows us to write API-agnostic code (in the form of +callbacks) that can be used by any management interface. As an example, +it shouldn’t matter if a set of configuration changes is coming from a +`NETCONF <https://tools.ietf.org/html/rfc6241>`__ session or from a CLI +terminal, the same callbacks should be called to process the +configuration changes regardless of where they came from. This +model-driven design ensures feature parity across all management +interfaces supported by FRR. + +Quoting RFC 7950: > YANG is a language originally designed to model data +for the NETCONF protocol. A YANG module defines hierarchies of data that +can be used for NETCONF-based operations, including configuration, state +data, RPCs, and notifications. This allows a complete description of all +data sent between a NETCONF client and server. Although out of scope for +this specification, YANG can also be used with protocols other than +NETCONF. + +While the YANG and NETCONF specifications are tightly coupled with one +another, both are independent to a certain extent and are evolving +separately. Examples of other management protocols that use YANG include +`RESTCONF <https://tools.ietf.org/html/rfc8040>`__, +`gNMI <https://github.com/openconfig/reference/tree/master/rpc/gnmi>`__ +and +`CoAP <https://www.ietf.org/archive/id/draft-vanderstok-core-comi-11.txt>`__. + +In addition to being management-protocol independent, some other +advantages of using YANG in FRR are listed below: \* Have a formal +contract between FRR and application developers (management clients). A +management client that has access to the FRR YANG models knows about all +existing configuration options available for use. This information can +be used to auto-generate user-friendly interfaces like Web-UIs, custom +CLIs and even code bindings for several different programming languages. +Using `PyangBind <https://github.com/robshakir/pyangbind>`__, for +example, it’s possible to generate Python class hierarchies from YANG +models and use these classes to instantiate objects that mirror the +structure of the YANG modules and can be serialized/deserialized using +different encoding formats. \* Support different encoding formats for +instance data. Currently only JSON and XML are supported, but +`GPB <https://developers.google.com/protocol-buffers/>`__ and +`CBOR <http://cbor.io/>`__ are other viable options in the long term. +Additional encoding formats can be implemented in the *libyang* library +for optimal performance, or externally by translating data to/from one +of the supported formats (with a performance penalty). \* Have a formal +mechanism to introduce backward-incompatible changes based on `semantic +versioning <http://www.openconfig.net/docs/semver/>`__ (not part of the +YANG standard, which allows backward-compatible module updates only). \* +Provide seamless support to the industry-standard NETCONF/RESTCONF +protocols as alternative management APIs. If FRR configuration/state +data is modeled using YANG, supporting YANG-based protocols like NETCONF +and RESTCONF is much easier. + +As important as shifting to a model-driven management paradigm, the new +northbound architecture also introduces the concept of configuration +transactions. Configuration transactions allow management clients to +commit multiple configuration changes at the same time and rest assured +that either all changes will be applied or none will (all-or-nothing). +Configuration transactions are implemented as pseudo-atomic operations +and facilitate automation by removing the burden of error recovery from +the management side. Another property of configuration transactions is +that the configuration changes are always processed in a pre-defined +order to ensure consistency. Configuration transactions that encompass +multiple network devices are called network-wide transactions and are +also supported by the new northbound architecture. When FRR is built +using the ``--enable-config-rollbacks`` option, all committed +transactions are recorded in the FRR rollback log, which can reside +either in memory (volatile) or on persistent storage. + + Network-wide Transactions is the most important leap in network + management technology since SNMP. The error recovery and sequencing + tasks are removed from the manager side. This is usually more than + half the cost in a mature system; more than the entire cost of the + managed devices. + `[source] <https://www.nanog.org/sites/default/files/tuesday_tutorial_moberg_netconf_35.pdf>`__. + +Figures 1 and 2 below illustrate the old and new northbound architecture +of FRR, respectively. As it can be seen, in the old architecture the CLI +was the only interface used to configure and monitor FRR (the SNMP +plugin was’t taken into account given the small number of implemented +MIBs). This means that the only way to automate FRR was by writing +scripts that send CLI commands and parse the text output (which usually +doesn’t have any structure) using screen scraping and regular +expressions. + ++-----------------------------------------+ +| |space-1.jpg| | ++=========================================+ +| *Figure 1: old northbound architecture* | ++-----------------------------------------+ + +The new northbound architectures, on the other hand, features a +multitude of different management APIs, all of them connected to the +northbound layer of the FRR daemons. By default, only the CLI interface +is compiled built-in in the FRR daemons. The other management interfaces +are provided as optional plugins and need to be loaded during the daemon +initialization (e.g. *zebra -M confd*). This design makes it possible to +integrate FRR with different NETCONF solutions without introducing +vendor lock-in. The [[Plugins - Writing Your Own]] page explains how to +write custom northbound plugins that can be tailored to all needs +(e.g. support custom transport protocols, different data encoding +formats, fine-grained access control, etc). + ++-----------------------------------------+ +| |space-1.jpg| | ++=========================================+ +| *Figure 2: new northbound architecture* | ++-----------------------------------------+ + +Figure 3 shows the internal view of the FRR northbound architecture. In +this image we can see that northbound layer is an abstract entity +positioned between the northbound callbacks and the northbound clients. +The northbound layer is responsible to process the requests coming from +the northbound clients and call the appropriate callbacks to satisfy +these requests. The northbound plugins communicate with the northbound +layer through a public API, which allow users to write third-party +plugins that can be maintained separately. The northbound plugins, in +turn, have their own APIs to communicate with external management +clients. + ++---------------------------------------------------------+ +| |space-1.jpg| | ++=========================================================+ +| *Figure 3: new northbound architecture - internal view* | ++---------------------------------------------------------+ + +Initially the CLI (and all of its commands) will be maintained inside +the FRR daemons. In the long term, however, the goal is to move the CLI +to a separate program just like any other management client. The +[[Advanced Topics]] page describes the motivations and challenges of +doing that. Last but not least, the *libyang* block inside the +northbound layer is the engine that makes everything possible. The +*libyang* library will be described in more detail in the following +sections. + +YANG models +----------- + +The main decision to be made when using YANG is which models to +implement. There’s a general consensus that using standard models is +preferable over using custom (native) models. The reasoning is that +applications based on standard models can be reused for all network +appliances that support those models, whereas the same doesn’t apply for +applications written based on custom models. + +That said, there are multiple standards bodies publishing YANG models +and unfortunately not all of them are converging (or at least not yet). +In the context of FRR, which is a routing stack, the two sets of YANG +models that would make sense to implement are the ones from IETF and +from the OpenConfig working group. The question that arises is: which +one of them should we commit to? Or should we try to support both +somehow, at the cost of extra development efforts? + +Another problem, from an implementation point of view, is that it’s +challenging to adapt the existing code base to match standard models. A +more reasonable solution, at least in a first moment, would be to use +YANG deviations and augmentations to do the opposite: adapt the standard +models to the existing code. In practice however this is not as simple +as it seems. There are cases where the differences are too substantial +to be worked around without restructuring the code by changing its data +structures and their relationships. As an example, the *ietf-rip* model +places per-interface RIP configuration parameters inside the +*control-plane-protocol* list (which is augmented by *ietf-rip*). This +means that it’s impossible to configure RIP interface parameters without +first configuring a RIP routing instance. The *ripd* daemon on the other +hand allows the operator to configure RIP interface parameters even if +``router rip`` is not configured. If we were to implement the *ietf-rip* +module natively, we’d need to change ripd’s CLI commands (and the +associated code) to reflect the new configuration hierarchy. + +Taking into account that FRR has a huge code base and that the +northbound retrofitting process per-se will cause a lot of impact, it +was decided to take a conservative approach and write custom YANG models +for FRR modeled after the existing CLI commands. Having YANG models that +closely mirror the CLI commands will allow the FRR developers to +retrofit the code base much more easily, without introducing +backward-incompatible changes in the CLI and reducing the likelihood of +introducing bugs. The [[Retrofitting Configuration Commands]] page +explains in detail how to convert configuration commands to the new +northbound model. + +Even though having native YANG models is not the ideal solution, it will +be already a big step forward for FRR to migrate to a model-driven +management architecture, with support for configuration transactions and +multiple management interfaces, including NETCONF and RESTCONF (through +the northbound plugins). + +The new northbound also features an experimental YANG module translator +that will allow users to translate to and from standard YANG models by +using translation tables. The [[YANG module translator]] page describes +this mechanism in more detail. At this point it’s unclear what can be +achieved through module translation and if that can be considered as a +definitive solution to support standard models or not. + +Northbound Architecture +----------------------- + ++-----------------------------------------------+ +| |space-1.jpg| | ++===============================================+ +| *Figure 4: libyang’s lys_node data structure* | ++-----------------------------------------------+ + ++-----------------------------------------------+ +| |space-1.jpg| | ++===============================================+ +| *Figure 5: libyang’s lyd_node data structure* | ++-----------------------------------------------+ + ++---------------------------------------------+ +| |space-1.jpg| | ++=============================================+ +| *Figure 6: libyang’s ly_ctx data structure* | ++---------------------------------------------+ + ++----------------------------------------+ +| |space-1.jpg| | ++========================================+ +| *Figure 7: configuration transactions* | ++----------------------------------------+ + +Testing +------- + +The new northbound adds the libyang library as a new mandatory +dependency for FRR. To obtain and install this library, follow the steps +below: + +:: + + $ git clone https://github.com/CESNET/libyang + $ cd libyang + $ git checkout devel + $ mkdir build ; cd build + $ cmake -DENABLE_LYD_PRIV=ON .. + $ make + $ sudo make install + +.. + + NOTE: first make sure to install the libyang + `requirements <https://github.com/CESNET/libyang#build-requirements>`__. + +FRR needs libyang from version 0.16.7 or newer, which is maintained in +the ``devel`` branch. libyang 0.15.x is maintained in the ``master`` +branch and doesn’t contain one small feature used by FRR (the +``LY_CTX_DISABLE_SEARCHDIR_CWD`` flag). FRR also makes use of the +libyang’s ``ENABLE_LYD_PRIV`` feature, which is disabled by default and +needs to be enabled at compile time. + +It’s advisable (but not required) to install sqlite3 and build FRR with +``--enable-config-rollbacks`` in order to have access to the +configuration rollback feature. + +To test the northbound, the suggested method is to use the +[[Transactional CLI]] with the *ripd* daemon and play with the new +commands. The ``debug northbound`` command can be used to see which +northbound callbacks are called in response to the ``commit`` command. +For reference, the [[Demos]] page shows a small demonstration of the +transactional CLI in action and what it’s capable of. + +.. |space-1.jpg| image:: https://s22.postimg.cc/se52j8awh/arch-before.png +.. |space-1.jpg| image:: https://s22.postimg.cc/fziaiwboh/arch-after.png +.. |space-1.jpg| image:: https://s22.postimg.cc/qmc3ocmep/nb-layer.png +.. |space-1.jpg| image:: https://s22.postimg.cc/z4ljsodht/lys_node.png +.. |space-1.jpg| image:: https://s22.postimg.cc/6eynw1h7l/lyd_node.png +.. |space-1.jpg| image:: https://s22.postimg.cc/5cohdhiyp/ly_ctx.png +.. |space-1.jpg| image:: https://s22.postimg.cc/8waf3bgjl/transactions.png diff --git a/doc/developer/northbound/demos.rst b/doc/developer/northbound/demos.rst new file mode 100644 index 0000000000..21ab43a49b --- /dev/null +++ b/doc/developer/northbound/demos.rst @@ -0,0 +1,25 @@ +Transactional CLI +----------------- + +This short demo shows some of the capabilities of the new transactional +CLI: |asciicast| + +ConfD + NETCONF + Cisco YDK +--------------------------- + +This is a very simple demo of *ripd* being configured by a python +script. The script uses NETCONF to communicate with *ripd*, which has +the ConfD plugin loaded. The most interesting part, however, is the fact +that the python script is not using handcrafted XML payloads to +configure *ripd*. Instead, the script is using python bindings generated +using Cisco’s YANG Development Kit (YDK). + +- Script used in the demo: + https://gist.github.com/rwestphal/defa9bd1ccf216ab082d4711ae402f95 + +|asciicast| + +.. |asciicast| image:: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1.png + :target: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1 +.. |asciicast| image:: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv.png + :target: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv diff --git a/doc/developer/northbound/links.rst b/doc/developer/northbound/links.rst new file mode 100644 index 0000000000..e80374c57b --- /dev/null +++ b/doc/developer/northbound/links.rst @@ -0,0 +1,233 @@ +RFCs +~~~~ + +- `RFC 7950 - The YANG 1.1 Data Modeling + Language <https://tools.ietf.org/html/rfc7950>`__ +- `RFC 7951 - JSON Encoding of Data Modeled with + YANG <https://tools.ietf.org/html/rfc7951>`__ +- `RFC 8342 - Network Management Datastore Architecture + (NMDA) <https://tools.ietf.org/html/rfc8342>`__ +- `RFC 6087 - Guidelines for Authors and Reviewers of YANG Data Model + Documents <https://tools.ietf.org/html/rfc6087>`__ +- `RFC 8340 - YANG Tree + Diagrams <https://tools.ietf.org/html/rfc8340>`__ +- `RFC 6991 - Common YANG Data + Types <https://tools.ietf.org/html/rfc6991>`__ +- `RFC 6241 - Network Configuration Protocol + (NETCONF) <https://tools.ietf.org/html/rfc6241>`__ +- `RFC 8040 - RESTCONF + Protocol <https://tools.ietf.org/html/rfc8040>`__ + +YANG models +~~~~~~~~~~~ + +- Collection of several YANG models, including models from standards + organizations such as the IETF and vendor specific models: + https://github.com/YangModels/yang +- OpenConfig: https://github.com/openconfig/public + +Presentations +~~~~~~~~~~~~~ + +- FRR Advanced Northbound API (May 2018) + + - Slides: + https://www.dropbox.com/s/zhybthruwocbqaw/netdef-frr-northbound.pdf?dl=1 + +- Ok, We Got Data Models, Now What? + + - Video: https://www.youtube.com/watch?v=2oqkiZ83vAA + - Slides: + https://www.nanog.org/sites/default/files/20161017_Alvarez_Ok_We_Got_v1.pdf + +- Data Model-Driven Management: Latest Industry and Tool Developments + + - Video: https://www.youtube.com/watch?v=n_oKGJ_jgYQ + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG72/1559/20180219_Claise_Data_Modeling-Driven_Management__v1.pdf + +- Network Automation And Programmability: Reality Versus The Vendor + Hype When Considering Legacy And NFV Networks + + - Video: https://www.youtube.com/watch?v=N5wbYncUS9o + - Slides: + https://www.nanog.org/sites/default/files/1_Moore_Network_Automation_And_Programmability.pdf + +- Lightning Talk: The API is the new CLI? + + - Video: https://www.youtube.com/watch?v=ngi0erGNi58 + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG72/1638/20180221_Grundemann_Lightning_Talk_The_v1.pdf + +- Lightning Talk: OpenConfig - progress toward vendor-neutral network + management + + - Video: https://www.youtube.com/watch?v=10rSUbeMmT4 + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG71/1535/20171004_Shaikh_Lightning_Talk_Openconfig_v1.pdf + +- Getting started with OpenConfig + + - Video: https://www.youtube.com/watch?v=L7trUNK8NJI + - Slides: + https://pc.nanog.org/static/published/meetings/NANOG71/1456/20171003_Alvarez_Getting_Started_With_v1.pdf + +- Why NETCONF and YANG + + - Video: https://www.youtube.com/watch?v=mp4h8aSTba8 + +- NETCONF and YANG Concepts + + - Video: https://www.youtube.com/watch?v=UwYYvT7DBvg + +- NETCONF Tutorial + + - Video: https://www.youtube.com/watch?v=N4vov1mI14U + +Whitepapers +~~~~~~~~~~~ + +- Automating Network and Service Configuration Using NETCONF and YANG: + http://www.tail-f.com/wordpress/wp-content/uploads/2013/02/Tail-f-Presentation-Netconf-Yang.pdf +- Creating the Programmable Network: The Business Case for NETCONF/YANG + in Network Devices: + http://www.tail-f.com/wordpress/wp-content/uploads/2013/10/HR-Tail-f-NETCONF-WP-10-08-13.pdf +- NETCONF/YANG: What’s Holding Back Adoption & How to Accelerate It: + https://www.oneaccess-net.com/images/public/wp_heavy_reading.pdf +- Achieving Automation with YANG Modeling Technologies: + https://www.cisco.com/c/dam/en/us/products/collateral/cloud-systems-management/network-services-orchestrator/idc-achieving-automation-wp.pdf + +Blog posts and podcasts +~~~~~~~~~~~~~~~~~~~~~~~ + +- OpenConfig and IETF YANG Models: Can they converge? - + http://rob.sh/post/215/ +- OpenConfig: Standardized Models For Networking - + https://packetpushers.net/openconfig-standardized-models-networking/ +- (Podcast) OpenConfig: From Basics to Implementations - + https://blog.ipspace.net/2017/02/openconfig-from-basics-to.html +- (Podcast) How Did NETCONF Start on Software Gone Wild - + https://blog.ipspace.net/2017/12/how-did-netconf-start-on-software-gone.html +- YANG Data Models in the Industry: Current State of Affairs (March + 2018) - + https://www.claise.be/2018/03/yang-data-models-in-the-industry-current-state-of-affairs-march-2018/ +- Why Data Model-driven Telemetry is the only useful Telemetry? - + https://www.claise.be/2018/02/why-data-model-driven-telemetry-is-the-only-useful-telemetry/ +- NETCONF versus RESTCONF: Capabilitity Comparisons for Data + Model-driven Management - + https://www.claise.be/2017/10/netconf-versus-restconf-capabilitity-comparisons-for-data-model-driven-management-2/ +- An Introduction to NETCONF/YANG - + https://www.fir3net.com/Networking/Protocols/an-introduction-to-netconf-yang.html +- Network Automation and the Rise of NETCONF - + https://medium.com/@k.okasha/network-automation-and-the-rise-of-netconf-e96cc33fe28 +- YANG and the Road to a Model Driven Network - + https://medium.com/@k.okasha/yang-and-road-to-a-model-driven-network-e9e52d47148d + +Software +~~~~~~~~ + +libyang +^^^^^^^ + + libyang is a YANG data modelling language parser and toolkit written + (and providing API) in C. + +- GitHub page: https://github.com/CESNET/libyang +- Documentaion: https://netopeer.liberouter.org/doc/libyang/master/ + +pyang +^^^^^ + + pyang is a YANG validator, transformator and code generator, written + in python. It can be used to validate YANG modules for correctness, + to transform YANG modules into other formats, and to generate code + from the modules. + +- GitHub page: https://github.com/mbj4668/pyang +- Documentaion: https://github.com/mbj4668/pyang/wiki/Documentation + +ncclient +^^^^^^^^ + + ncclient is a Python library that facilitates client-side scripting + and application development around the NETCONF protocol. + +- GitHub page: https://github.com/ncclient/ncclient +- Documentaion: https://ncclient.readthedocs.io/en/latest/ + +YDK +^^^ + + ydk-gen is a developer tool that can generate API’s that are modeled + in YANG. Currently, it generates language binding for Python, Go and + C++ with planned support for other language bindings in the future. + +- GitHub pages: + + - Generator: https://github.com/CiscoDevNet/ydk-gen + - Python: https://github.com/CiscoDevNet/ydk-py + + - Python samples: https://github.com/CiscoDevNet/ydk-py-samples + + - Go: https://github.com/CiscoDevNet/ydk-go + - C++: https://github.com/CiscoDevNet/ydk-cpp + +- Documentation: + + - Python: http://ydk.cisco.com/py/docs/ + - Go: http://ydk.cisco.com/go/docs/ + - C++: http://ydk.cisco.com/cpp/docs/ + +- (Blog post) Simplifying Network Programmability with Model-Driven + APIs: + https://blogs.cisco.com/sp/simplifying-network-programmability-with-model-driven-apis +- (Video introduction) Infrastructure as a Code Using YANG, OpenConfig + and YDK: https://www.youtube.com/watch?v=G1b6vJW1R5w + +pyangbind +^^^^^^^^^ + + A plugin for pyang that creates Python bindings for a YANG model. + +- GitHub page: https://github.com/robshakir/pyangbind +- Documentation: http://pynms.io/pyangbind/ + +ConfD +^^^^^ + +- Official webpage (for ConfD Basic): + http://www.tail-f.com/confd-basic/ +- Training Videos: http://www.tail-f.com/confd-training-videos/ +- Forum: http://discuss.tail-f.com/ + +Sysrepo +^^^^^^^ + + Sysrepo is an YANG-based configuration and operational state data + store for Unix/Linux applications. + +- GitHub page: https://github.com/sysrepo/sysrepo +- Official webpage: http://www.sysrepo.org/ +- Documentation: http://www.sysrepo.org/static/doc/html/ + +Netopeer2 +^^^^^^^^^ + + Netopeer2 is a set of tools implementing network configuration tools + based on the NETCONF Protocol. This is the second generation of the + toolset, originally available as the Netopeer project. Netopeer2 is + based on the new generation of the NETCONF and YANG libraries - + libyang and libnetconf2. The Netopeer server uses sysrepo as a + NETCONF datastore implementation. + +- GitHub page: https://github.com/CESNET/Netopeer2 + +Clixon +^^^^^^ + + Clixon is an automatic configuration manager where you generate + interactive CLI, NETCONF, RESTCONF and embedded databases with + transaction support from a YANG specification. + +- GitHub page: https://github.com/clicon/clixon +- Project page: http://www.clicon.org/ diff --git a/doc/developer/northbound/northbound.rst b/doc/developer/northbound/northbound.rst new file mode 100644 index 0000000000..c5e30f16ca --- /dev/null +++ b/doc/developer/northbound/northbound.rst @@ -0,0 +1,21 @@ +.. _northbound: + +************** +Northbound API +************** + +.. toctree:: + :maxdepth: 2 + + advanced-topics + architecture + demos + links + operational-data-rpcs-and-notifications + plugins-sysrepo + ppr-basic-test-topology + ppr-mpls-basic-test-topology + retrofitting-configuration-commands + transactional-cli + yang-module-translator + yang-tools diff --git a/doc/developer/northbound/operational-data-rpcs-and-notifications.rst b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst new file mode 100644 index 0000000000..554bc17c80 --- /dev/null +++ b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst @@ -0,0 +1,565 @@ +Operational data +~~~~~~~~~~~~~~~~ + +Writing API-agnostic code for YANG-modeled operational data is +challenging. ConfD and Sysrepo, for instance, have completely different +APIs to fetch operational data. So how can we write API-agnostic +callbacks that can be used by both the ConfD and Sysrepo plugins, and +any other northbound client that might be written in the future? + +As an additional requirement, the callbacks must be designed in a way +that makes in-place XPath filtering possible. As an example, a +management client might want to retrieve only a subset of a large YANG +list (e.g. a BGP table), and for optimal performance it should be +possible to filter out the unwanted elements locally in the managed +devices instead of returning all elements and performing the filtering +on the management application. + +To meet all these requirements, the four callbacks below were introduced +in the northbound architecture: + +.. code:: c + + /* + * Operational data callback. + * + * The callback function should return the value of a specific leaf or + * inform if a typeless value (presence containers or leafs of type + * empty) exists or not. + * + * xpath + * YANG data path of the data we want to get + * + * list_entry + * pointer to list entry + * + * Returns: + * pointer to newly created yang_data structure, or NULL to indicate + * the absence of data + */ + struct yang_data *(*get_elem)(const char *xpath, void *list_entry); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return the next entry in the list. The + * 'list_entry' parameter will be NULL on the first invocation. + * + * list_entry + * pointer to a list entry + * + * Returns: + * pointer to the next entry in the list, or NULL to signal that the + * end of the list was reached + */ + void *(*get_next)(void *list_entry); + + /* + * Operational data callback for YANG lists. + * + * The callback function should fill the 'keys' parameter based on the + * given list_entry. + * + * list_entry + * pointer to a list entry + * + * keys + * structure to be filled based on the attributes of the provided + * list entry + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + int (*get_keys)(void *list_entry, struct yang_list_keys *keys); + + /* + * Operational data callback for YANG lists. + * + * The callback function should return a list entry based on the list + * keys given as a parameter. + * + * keys + * structure containing the keys of the list entry + * + * Returns: + * a pointer to the list entry if found, or NULL if not found + */ + void *(*lookup_entry)(struct yang_list_keys *keys); + +These callbacks were designed to provide maximum flexibility, and borrow +a lot of ideas from the ConfD API. Each callback does one and only one +task, they are indivisible primitives that can be combined in several +different ways to iterate over operational data. The extra flexibility +certainly has a performance cost, but it’s the price to pay if we want +to expose FRR operational data using several different management +interfaces (e.g. NETCONF via either ConfD or Sysrepo+Netopeer2). In the +future it might be possible to introduce optional callbacks that do +things like returning multiple objects at once. They would provide +enhanced performance when iterating over large lists, but their use +would be limited by the northbound plugins that can be integrated with +them. + + NOTE: using the northbound callbacks as a base, the ConfD plugin can + provide up to 100 objects between each round trip between FRR and the + *confd* daemon. Preliminary tests showed FRR taking ~7 seconds + (asynchronously, without blocking the main pthread) to return a RIP + table containing 100k routes to a NETCONF client connected to *confd* + (JSON was used as the encoding format). Work needs to be done to find + the bottlenecks and optimize this operation. + +The [[Plugins - Writing Your Own]] page explains how the northbound +plugins can fetch operational data using the aforementioned northbound +callbacks, and how in-place XPath filtering can be implemented. + +Example +^^^^^^^ + +Now let’s move to an example to show how these callbacks are implemented +in practice. The following YANG container is part of the *ietf-rip* +module and contains operational data about RIP neighbors: + +.. code:: yang + + container neighbors { + description + "Neighbor information."; + list neighbor { + key "address"; + description + "A RIP neighbor."; + leaf address { + type inet:ipv4-address; + description + "IP address that a RIP neighbor is using as its + source address."; + } + leaf last-update { + type yang:date-and-time; + description + "The time when the most recent RIP update was + received from this neighbor."; + } + leaf bad-packets-rcvd { + type yang:counter32; + description + "The number of RIP invalid packets received from + this neighbor which were subsequently discarded + for any reason (e.g. a version 0 packet, or an + unknown command type)."; + } + leaf bad-routes-rcvd { + type yang:counter32; + description + "The number of routes received from this neighbor, + in valid RIP packets, which were ignored for any + reason (e.g. unknown address family, or invalid + metric)."; + } + } + } + +We know that this is operational data because the ``neighbors`` +container is within the ``state`` container, which has the +``config false;`` property (which is applied recursively). + +As expected, the ``gen_northbound_callbacks`` tool also generates +skeleton callbacks for nodes that represent operational data: + +.. code:: c + + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor", + .cbs.get_next = ripd_state_neighbors_neighbor_get_next, + .cbs.get_keys = ripd_state_neighbors_neighbor_get_keys, + .cbs.lookup_entry = ripd_state_neighbors_neighbor_lookup_entry, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/address", + .cbs.get_elem = ripd_state_neighbors_neighbor_address_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/last-update", + .cbs.get_elem = ripd_state_neighbors_neighbor_last_update_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd", + .cbs.get_elem = ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd", + .cbs.get_elem = ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem, + }, + +The ``/frr-ripd:ripd/state/neighbors/neighbor`` list within the +``neighbors`` container has three different callbacks that need to be +implemented. Let’s start with the first one, the ``get_next`` callback: + +.. code:: c + + static void *ripd_state_neighbors_neighbor_get_next(void *list_entry) + { + struct listnode *node; + + if (list_entry == NULL) + node = listhead(peer_list); + else + node = listnextnode((struct listnode *)list_entry); + + return node; + } + +Given a list entry, the job of this callback is to find the next element +from the list. When the ``list_entry`` parameter is NULL, then the first +element of the list should be returned. + +*ripd* uses the ``rip_peer`` structure to represent RIP neighbors, and +the ``peer_list`` global variable (linked list) is used to store all RIP +neighbors. + +In order to be able to iterate over the list of RIP neighbors, the +callback returns a ``listnode`` variable instead of a ``rip_peer`` +variable. The ``listnextnode`` macro can then be used to find the next +element from the linked list. + +Now the second callback, ``get_keys``: + +.. code:: c + + static int ripd_state_neighbors_neighbor_get_keys(void *list_entry, + struct yang_list_keys *keys) + { + struct listnode *node = list_entry; + struct rip_peer *peer = listgetdata(node); + + keys->num = 1; + (void)inet_ntop(AF_INET, &peer->addr, keys->key[0].value, + sizeof(keys->key[0].value)); + + return NB_OK; + } + +This one is easy. First, we obtain the RIP neighbor from the +``listnode`` structure. Then, we fill the ``keys`` parameter according +to the attributes of the RIP neighbor. In this case, the ``neighbor`` +YANG list has only one key: the neighbor IP address. We then use the +``inet_ntop()`` function to transform this binary IP address into a +string (the lingua franca of the FRR northbound). + +The last callback for the ``neighbor`` YANG list is the ``lookup_entry`` +callback: + +.. code:: c + + static void * + ripd_state_neighbors_neighbor_lookup_entry(struct yang_list_keys *keys) + { + struct in_addr address; + + yang_str2ipv4(keys->key[0].value, &address); + + return rip_peer_lookup(&address); + } + +This callback is the counterpart of the ``get_keys`` callback: given an +array of list keys, the associated list entry should be returned. The +``yang_str2ipv4()`` function is used to convert the list key (an IP +address) from a string to an ``in_addr`` structure. Then the +``rip_peer_lookup()`` function is used to find the list entry. + +Finally, each YANG leaf inside the ``neighbor`` list has its associated +``get_elem`` callback: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/address + */ + static struct yang_data * + ripd_state_neighbors_neighbor_address_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_ipv4(xpath, &peer->addr); + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/last-update + */ + static struct yang_data * + ripd_state_neighbors_neighbor_last_update_get_elem(const char *xpath, + void *list_entry) + { + /* TODO: yang:date-and-time is tricky */ + return NULL; + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-packets-rcvd + */ + static struct yang_data * + ripd_state_neighbors_neighbor_bad_packets_rcvd_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_uint32(xpath, peer->recv_badpackets); + } + + /* + * XPath: /frr-ripd:ripd/state/neighbors/neighbor/bad-routes-rcvd + */ + static struct yang_data * + ripd_state_neighbors_neighbor_bad_routes_rcvd_get_elem(const char *xpath, + void *list_entry) + { + struct rip_peer *peer = list_entry; + + return yang_data_new_uint32(xpath, peer->recv_badroutes); + } + +These callbacks receive the list entry as parameter and return the +corresponding data using the ``yang_data_new_*()`` wrapper functions. +Not much to explain here. + +Iterating over operational data without blocking the main pthread +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +One of the problems we have in FRR is that some “show” commands in the +CLI can take too long, potentially long enough to the point of +triggering some protocol timeouts and bringing sessions down. + +To avoid this kind of problem, northbound clients are encouraged to do +one of the following: \* Create a separate pthread for handling requests +to fetch operational data. \* Iterate over YANG lists and leaf-lists +asynchronously, returning a maximum number of elements per time instead +of returning all elements in one shot. + +In order to handle both cases correctly, the ``get_next`` callbacks need +to use locks to prevent the YANG lists from being modified while they +are being iterated over. If that is not done, the list entry returned by +this callback can become a dangling pointer when used in another +callback. + +Currently the ConfD and Sysrepo plugins run only in the main pthread. +The plan in the short-term is to introduce a separate pthread only for +handling operational data, and use the main pthread only for handling +configuration changes, RPCs and notifications. + +RPCs and Actions +~~~~~~~~~~~~~~~~ + +The FRR northbound supports YANG RPCs and Actions through the ``rpc()`` +callback, which is documented as follows in the *lib/northbound.h* file: + +.. code:: c + + /* + * RPC and action callback. + * + * Both 'input' and 'output' are lists of 'yang_data' structures. The + * callback should fetch all the input parameters from the 'input' list, + * and add output parameters to the 'output' list if necessary. + * + * xpath + * xpath of the YANG RPC or action + * + * input + * read-only list of input parameters + * + * output + * list of output parameters to be populated by the callback + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + int (*rpc)(const char *xpath, const struct list *input, + struct list *output); + +Note that the same callback is used for both RPCs and actions, which are +essentially the same thing. In the case of YANG actions, the ``xpath`` +parameter can be consulted to find the data node associated to the +operation. + +As part of the northbound retrofitting process, it’s suggested to model +some EXEC-level commands using YANG so that their functionality is +exposed to other management interfaces other than the CLI. As an +example, if the ``clear bgp`` command is modeled using a YANG RPC, and a +corresponding ``rpc`` callback is written, then it should be possible to +clear BGP neighbors using NETCONF and RESTCONF with that RPC (the ConfD +and Sysrepo plugins have full support for YANG RPCs and actions). + +Here’s an example of a very simple RPC modeled using YANG: + +.. code:: yang + + rpc clear-rip-route { + description + "Clears RIP routes from the IP routing table and routes + redistributed into the RIP protocol."; + } + +This RPC doesn’t have any input or output parameters. Below we can see +the implementation of the corresponding ``rpc`` callback, whose skeleton +was automatically generated by the ``gen_northbound_callbacks`` tool: + +.. code:: c + + /* + * XPath: /frr-ripd:clear-rip-route + */ + static int clear_rip_route_rpc(const char *xpath, const struct list *input, + struct list *output) + { + struct route_node *rp; + struct rip_info *rinfo; + struct list *list; + struct listnode *listnode; + + /* Clear received RIP routes */ + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + if (list == NULL) + continue; + + for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { + if (!rip_route_rte(rinfo)) + continue; + + if (CHECK_FLAG(rinfo->flags, RIP_RTF_FIB)) + rip_zebra_ipv4_delete(rp); + break; + } + + if (rinfo) { + RIP_TIMER_OFF(rinfo->t_timeout); + RIP_TIMER_OFF(rinfo->t_garbage_collect); + listnode_delete(list, rinfo); + rip_info_free(rinfo); + } + + if (list_isempty(list)) { + list_delete_and_null(&list); + rp->info = NULL; + route_unlock_node(rp); + } + } + + return NB_OK; + } + +If the ``clear-rip-route`` RPC had any input parameters, they would be +available in the ``input`` list given as a parameter to the callback. +Similarly, the ``output`` list can be used to append output parameters +generated by the RPC, if any are defined in the YANG model. + +The northbound clients (CLI and northbound plugins) have the +responsibility to create and delete the ``input`` and ``output`` lists. +However, in the cases where the RPC or action doesn’t have any input or +output parameters, the northbound client can pass NULL pointers to the +``rpc`` callback to avoid creating linked lists unnecessarily. We can +see this happening in the example below: + +.. code:: c + + /* + * XPath: /frr-ripd:clear-rip-route + */ + DEFPY (clear_ip_rip, + clear_ip_rip_cmd, + "clear ip rip", + CLEAR_STR + IP_STR + "Clear IP RIP database\n") + { + return nb_cli_rpc("/frr-ripd:clear-rip-route", NULL, NULL); + } + +``nb_cli_rpc()`` is a helper function that merely finds the appropriate +``rpc`` callback based on the XPath provided in the first argument, and +map the northbound error code from the ``rpc`` callback to a vty error +code (e.g. ``CMD_SUCCESS``, ``CMD_WARNING``). The second and third +arguments provided to the function refer to the ``input`` and ``output`` +lists. In this case, both arguments are set to NULL since the YANG RPC +in question doesn’t have any input/output parameters. + +Notifications +~~~~~~~~~~~~~ + +YANG notifations are sent using the ``nb_notification_send()`` function, +documented in the *lib/northbound.h* file as follows: + +.. code:: c + + /* + * Send a YANG notification. This is a no-op unless the 'nb_notification_send' + * hook was registered by a northbound plugin. + * + * xpath + * xpath of the YANG notification + * + * arguments + * linked list containing the arguments that should be sent. This list is + * deleted after being used. + * + * Returns: + * NB_OK on success, NB_ERR otherwise + */ + extern int nb_notification_send(const char *xpath, struct list *arguments); + +The northbound doesn’t use callbacks for notifications because +notifications are generated locally and sent to the northbound clients. +This way, whenever a notification needs to be sent, it’s possible to +call the appropriate function directly instead of finding a callback +based on the XPath of the YANG notification. + +As an example, the *ietf-rip* module contains the following +notification: + +.. code:: yang + + notification authentication-failure { + description + "This notification is sent when the system + receives a PDU with the wrong authentication + information."; + leaf interface-name { + type string; + description + "Describes the name of the RIP interface."; + } + } + +The following convenience function was implemented in *ripd* to send +*authentication-failure* YANG notifications: + +.. code:: c + + /* + * XPath: /frr-ripd:authentication-failure + */ + void ripd_notif_send_auth_failure(const char *ifname) + { + const char *xpath = "/frr-ripd:authentication-failure"; + struct list *arguments; + char xpath_arg[XPATH_MAXLEN]; + struct yang_data *data; + + arguments = yang_data_list_new(); + + snprintf(xpath_arg, sizeof(xpath_arg), "%s/interface-name", xpath); + data = yang_data_new_string(xpath_arg, ifname); + listnode_add(arguments, data); + + nb_notification_send(xpath, arguments); + } + +Now sending the *authentication-failure* YANG notification should be as +simple as calling the above function and provide the appropriate +interface name. The notification will be processed by all northbound +plugins that subscribed a callback to the ``nb_notification_send`` hook. +The ConfD and Sysrepo plugins, for instance, use this hook to relay the +notifications to the *confd*/*sysrepod* daemons, which can generate +NETCONF notifications to subscribed clients. When no northbound plugin +is loaded, ``nb_notification_send()`` doesn’t do anything and the +notifications are ignored. diff --git a/doc/developer/northbound/plugins-sysrepo.rst b/doc/developer/northbound/plugins-sysrepo.rst new file mode 100644 index 0000000000..186c3a0177 --- /dev/null +++ b/doc/developer/northbound/plugins-sysrepo.rst @@ -0,0 +1,137 @@ +Installation +------------ + +Required dependencies +^^^^^^^^^^^^^^^^^^^^^ + +:: + + # apt-get install git cmake build-essential bison flex libpcre3-dev libev-dev \ + libavl-dev libprotobuf-c-dev protobuf-c-compiler libcmocka0 \ + libcmocka-dev doxygen libssl-dev libssl-dev libssh-dev + +libyang +^^^^^^^ + +:: + + # apt-get install libyang0.16 libyang-dev + +Sysrepo +^^^^^^^ + +:: + + $ git clone https://github.com/sysrepo/sysrepo.git + $ cd sysrepo/ + $ mkdir build; cd build + $ cmake -DCMAKE_BUILD_TYPE=Release -DGEN_LANGUAGE_BINDINGS=OFF .. && make + # make install + +libnetconf2 +^^^^^^^^^^^ + +:: + + $ git clone https://github.com/CESNET/libnetconf2.git + $ cd libnetconf2/ + $ mkdir build; cd build + $ cmake .. && make + # make install + +netopeer2 +^^^^^^^^^ + +:: + + $ git clone https://github.com/CESNET/Netopeer2.git + $ cd Netopeer2 + $ cd server + $ mkdir build; cd build + $ cmake .. && make + # make install + +**Note:** If ``make install`` fails as it can’t find +``libsysrepo.so.0.7``, then run ``ldconfig`` and try again as it might +not have updated the lib search path + +FRR +^^^ + +Build and install FRR using the ``--enable-sysrepo`` configure-time +option. + +Initialization +-------------- + +Install the FRR YANG modules in the Sysrepo datastore: + +:: + + # sysrepoctl --install /usr/local/share/yang/ietf-interfaces@2018-01-09.yang + # sysrepoctl --install /usr/local/share/yang/frr-vrf.yang + # sysrepoctl --install /usr/local/share/yang/frr-interface.yang + # sysrepoctl --install /usr/local/share/yang/frr-route-types.yang + # sysrepoctl --install /usr/local/share/yang/frr-filter.yang + # sysrepoctl --install /usr/local/share/yang/frr-route-map.yang + # sysrepoctl --install /usr/local/share/yang/frr-isisd.yang + # sysrepoctl --install /usr/local/share/yang/frr-ripd.yang + # sysrepoctl --install /usr/local/share/yang/frr-ripngd.yang + # sysrepoctl -c frr-vrf --owner frr --group frr + # sysrepoctl -c frr-interface --owner frr --group frr + # sysrepoctl -c frr-route-types --owner frr --group frr + # sysrepoctl -c frr-filter --owner frr --group frr + # sysrepoctl -c frr-route-map --owner frr --group frr + # sysrepoctl -c frr-isisd --owner frr --group frr + # sysrepoctl -c frr-ripd --owner frr --group frr + # sysrepoctl -c frr-ripngd --owner frr --group frr + +Start netopeer2-server: + +:: + + # netopeer2-server -d & + +Start the FRR daemons with the sysrepo module: + +:: + + # isisd -M sysrepo --log=stdout + +Managing the configuration +-------------------------- + +The following NETCONF scripts can be used to show and edit the FRR +configuration: +https://github.com/rzalamena/ietf-hackathon-brazil-201907/tree/master/netconf-scripts + +Example: + +:: + + # ./netconf-edit.py 127.0.0.1 + # ./netconf-get-config.py 127.0.0.1 + <?xml version="1.0" encoding="UTF-8"?><data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0"><isis xmlns="http://frrouting.org/yang/isisd"><instance><area-tag>testnet</area-tag><is-type>level-1</is-type></instance></isis></data> + +.. + + NOTE: the ncclient library needs to be installed first: + ``apt install -y python3-ncclient`` + +The *sysrepocfg* tool can also be used to show/edit the FRR +configuration. Example: + +:: + + # sysrepocfg --format=json --import=frr-isisd.json --datastore=running frr-isisd + # sysrepocfg --format=json --export --datastore=running frr-isisd + { + "frr-isisd:isis": { + "instance": [ + { + "area-tag": "testnet", + "is-type": "level-1" + } + ] + } + } diff --git a/doc/developer/northbound/ppr-basic-test-topology.rst b/doc/developer/northbound/ppr-basic-test-topology.rst new file mode 100644 index 0000000000..582c76c059 --- /dev/null +++ b/doc/developer/northbound/ppr-basic-test-topology.rst @@ -0,0 +1,1632 @@ +Table of Contents +~~~~~~~~~~~~~~~~~ + +- `Software <#software>`__ +- `Topology <#topology>`__ +- `Configuration <#configuration>`__ + + - `CLI <#configuration-cli>`__ + - `YANG <#configuration-yang>`__ + +- `Verification - Control Plane <#verification-cplane>`__ +- `Verification - Forwarding Plane <#verification-fplane>`__ + +Software +~~~~~~~~ + +The FRR PPR implementation for IS-IS is available here: +https://github.com/opensourcerouting/frr/tree/isisd-ppr + +Topology +~~~~~~~~ + +In this topology we have an IS-IS network consisting of 12 routers. CE1 +and CE2 are the consumer edges, connected to R11 and R14, respectively. +Three hosts are connected to the CEs using only static routes. + +Router R11 advertises 6 PPR TLVs, which corresponds to three +bi-directional GRE tunnels: \* **6000:1::1 <-> 6000:2::1:** {R11 - R21 - +R22 - R23 - R14} (IPv6 Node Addresses only) \* **6000:1::2 <-> +6000:2::2:** {R11 - R21 - R32 - R41 - R33 - R23 - R14} (IPv6 Node and +Interface Addresses) \* **6000:1::3 <-> 6000:2::3:** {R11 - R21 - R99 - +R23 - R14} (misconfigured path) + +PBR rules are configured on R11 and R14 to route the traffic between +Host 1 and Host 3 using the first PPR tunnel. Traffic between Host 2 and +Host 3 uses the regular IS-IS shortest path. + +Additional information: \* Addresses in the 4000::/16 range refer to +interface addresses, where the last hextet corresponds to the node ID. +\* Addresses in the 5000::/16 range refer to loopback addresses, where +the last hextet corresponds to the node ID. \* Addresses in the +6000::/16 range refer to PPR-ID addresses. + +:: + + +-------+ +-------+ +-------+ + | | | | | | + | HOST1 | | HOST2 | | HOST3 | + | | | | | | + +---+---+ +---+---+ +---+---+ + | | | + |fd00:10:1::/64 | | + +-----+ +------+ fd00:20:1::/64| + | |fd00:10:2::/64 | + | | | + +-+--+--+ +---+---+ + | | | | + | CE1 | | CE2 | + | | | | + +---+---+ +---+---+ + | | + | | + |fd00:10:0::/64 fd00:20:0::/64| + | | + | | + +---+---+ +-------+ +-------+ +---+---+ + | |4000:101::/64| |4000:102::/64| |4000:103::/64| | + | R11 +-------------+ R12 +-------------+ R13 +-------------+ R14 | + | | | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | | | | | | + |4000:104::/64 | |4000:106::/64 | |4000:108::/64 | + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + | |4000:105::/64 | |4000:107::/64 | |4000:109::/64 + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | |4000:110::/64| |4000:111::/64| | + | R21 +-------------+ R22 +-------------+ R23 | + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | | | | | | + | |4000:113::/64 | |4000:115::/64 | |4000:117::/64 + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + |4000:112::/64 | |4000:114::/64 | |4000:116::/64 | + | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | |4000:118::/64| |4000:119::/64| |4000:120::/64| | + | R31 +-------------+ R32 +-------------+ R33 +-------------+ R34 | + | | | | | | | | + +-------+ +---+---+ +---+---+ +-------+ + | | + |4000:121::/64 | + +----------+----------+ + | + | + +---+---+ + | | + | R41 | + | | + +-------+ + +Configuration +~~~~~~~~~~~~~ + +PPR TLV processing needs to be enabled on all IS-IS routers using the +``ppr on`` command. The advertisements of all PPR TLVs is done by router +R11. + +CLI configuration +^^^^^^^^^^^^^^^^^ + +.. code:: yaml + + --- + + routers: + + host1: + links: + eth-ce1: + peer: [ce1, eth-host1] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:1::1/64 + ! + ipv6 route ::/0 fd00:10:1::100 + + host2: + links: + eth-ce1: + peer: [ce1, eth-host2] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:2::1/64 + ! + ipv6 route ::/0 fd00:10:2::100 + + host3: + links: + eth-ce2: + peer: [ce2, eth-host3] + frr: + zebra: + staticd: + config: | + interface eth-ce2 + ipv6 address fd00:20:1::1/64 + ! + ipv6 route ::/0 fd00:20:1::100 + + ce1: + links: + eth-host1: + peer: [host1, eth-ce1] + eth-host2: + peer: [host2, eth-ce1] + eth-rt11: + peer: [rt11, eth-ce1] + frr: + zebra: + staticd: + config: | + interface eth-host1 + ipv6 address fd00:10:1::100/64 + ! + interface eth-host2 + ipv6 address fd00:10:2::100/64 + ! + interface eth-rt11 + ipv6 address fd00:10:0::100/64 + ! + ipv6 route ::/0 fd00:10:0::11 + + ce2: + links: + eth-host3: + peer: [host3, eth-ce2] + eth-rt14: + peer: [rt14, eth-ce2] + frr: + zebra: + staticd: + config: | + interface eth-host3 + ipv6 address fd00:20:1::100/64 + ! + interface eth-rt14 + ipv6 address fd00:20:0::100/64 + ! + ipv6 route ::/0 fd00:20:0::14 + + rt11: + links: + lo-ppr: + eth-ce1: + peer: [ce1, eth-rt11] + eth-rt12: + peer: [rt12, eth-rt11] + eth-rt21: + peer: [rt21, eth-rt11] + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:1::1/128 + ipv6 address 6000:1::2/128 + ipv6 address 6000:1::3/128 + ! + interface lo + ipv6 address 5000::11/128 + ipv6 router isis 1 + ! + interface eth-ce1 + ipv6 address fd00:10:0::11/64 + ! + interface eth-rt12 + ipv6 address 4000:101::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:104::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:10::/32 fd00:10:0::100 + ! + ppr group VOIP + ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + ppr group INTERFACE_PDES + ppr ipv6 6000:1::2/128 prefix 5000::11/128 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::33/128 + pde ipv6-interface 4000:121::41/64 + pde ipv6-node 5000::32/128 + pde ipv6-interface 4000:113::21/64 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::2/128 prefix 5000::14/128 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::32/128 + pde ipv6-interface 4000:121::41/64 + pde ipv6-node 5000::33/128 + pde ipv6-interface 4000:116::23/64 + pde ipv6-node 5000::14/128 + ! + ! + ppr group BROKEN + ppr ipv6 6000:1::3/128 prefix 5000::11/128 metric 1500 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + ! non-existing node!!! + pde ipv6-node 5000::99/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::3/128 prefix 5000::14/128 metric 1500 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + ! non-existing node!!! + pde ipv6-node 5000::99/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + router isis 1 + net 49.0000.0000.0000.0011.00 + is-type level-1 + topology ipv6-unicast + ppr on + ppr advertise VOIP + ppr advertise INTERFACE_PDES + ppr advertise BROKEN + ! + + rt12: + links: + eth-rt11: + peer: [rt11, eth-rt12] + eth-rt13: + peer: [rt13, eth-rt12] + eth-rt21: + peer: [rt21, eth-rt12] + eth-rt22: + peer: [rt22, eth-rt12] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::12/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:101::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:102::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:105::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:106::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0012.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt13: + links: + eth-rt12: + peer: [rt12, eth-rt13] + eth-rt14: + peer: [rt14, eth-rt13] + eth-rt22: + peer: [rt22, eth-rt13] + eth-rt23: + peer: [rt23, eth-rt13] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::13/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:102::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:103::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:107::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:108::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0013.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt14: + links: + lo-ppr: + eth-ce2: + peer: [ce2, eth-rt14] + eth-rt13: + peer: [rt13, eth-rt14] + eth-rt23: + peer: [rt23, eth-rt14] + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:2::1/128 + ipv6 address 6000:2::2/128 + ipv6 address 6000:2::3/128 + ! + interface lo + ipv6 address 5000::14/128 + ipv6 router isis 1 + ! + interface eth-ce2 + ipv6 address fd00:20:0::14/64 + ! + interface eth-rt13 + ipv6 address 4000:103::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:109::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:20::/32 fd00:20:0::100 + ! + router isis 1 + net 49.0000.0000.0000.0014.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt21: + links: + eth-rt11: + peer: [rt11, eth-rt21] + eth-rt12: + peer: [rt12, eth-rt21] + eth-rt22: + peer: [rt22, eth-rt21] + eth-rt31: + peer: [rt31, eth-rt21] + eth-rt32: + peer: [rt32, eth-rt21] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::21/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:104::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt12 + ipv6 address 4000:105::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:110::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:112::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:113::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0021.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt22: + links: + eth-rt12: + peer: [rt12, eth-rt22] + eth-rt13: + peer: [rt13, eth-rt22] + eth-rt21: + peer: [rt21, eth-rt22] + eth-rt23: + peer: [rt23, eth-rt22] + eth-rt32: + peer: [rt32, eth-rt22] + eth-rt33: + peer: [rt33, eth-rt22] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::22/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:106::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:107::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:110::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:111::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:114::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:115::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0022.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt23: + links: + eth-rt13: + peer: [rt13, eth-rt23] + eth-rt14: + peer: [rt14, eth-rt23] + eth-rt22: + peer: [rt22, eth-rt23] + eth-rt33: + peer: [rt33, eth-rt23] + eth-rt34: + peer: [rt34, eth-rt23] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::23/128 + ipv6 router isis 1 + ! + interface eth-rt13 + ipv6 address 4000:108::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:109::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:111::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:116::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:117::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0023.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt31: + links: + eth-rt21: + peer: [rt21, eth-rt31] + eth-rt32: + peer: [rt32, eth-rt31] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::31/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:112::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:118::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0031.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt32: + links: + eth-rt21: + peer: [rt21, eth-rt32] + eth-rt22: + peer: [rt22, eth-rt32] + eth-rt31: + peer: [rt31, eth-rt32] + eth-rt33: + peer: [rt33, eth-rt32] + eth-sw1: + peer: [sw1, eth-rt32] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::32/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:113::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:114::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:118::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:119::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::32/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0032.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt33: + links: + eth-rt22: + peer: [rt22, eth-rt33] + eth-rt23: + peer: [rt23, eth-rt33] + eth-rt32: + peer: [rt32, eth-rt33] + eth-rt34: + peer: [rt34, eth-rt33] + eth-sw1: + peer: [sw1, eth-rt33] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::33/128 + ipv6 router isis 1 + ! + interface eth-rt22 + ipv6 address 4000:115::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:116::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:119::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:120::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::33/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0033.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt34: + links: + eth-rt23: + peer: [rt23, eth-rt34] + eth-rt33: + peer: [rt33, eth-rt34] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::34/128 + ipv6 router isis 1 + ! + interface eth-rt23 + ipv6 address 4000:117::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:120::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0034.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + rt41: + links: + eth-sw1: + peer: [sw1, eth-rt41] + frr: + zebra: + isisd: + config: | + interface lo + ipv6 address 5000::41/128 + ipv6 router isis 1 + ! + interface eth-sw1 + ipv6 address 4000:121::41/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0041.00 + is-type level-1 + topology ipv6-unicast + ppr on + ! + + switches: + sw1: + links: + eth-rt32: + peer: [rt32, eth-sw1] + eth-rt33: + peer: [rt33, eth-sw1] + eth-rt41: + peer: [rt41, eth-sw1] + + frr: + base-config: | + hostname %(node) + password 1 + log file %(logdir)/%(node).log + log commands + ! + debug zebra rib + debug isis ppr + debug isis events + debug isis route-events + debug isis spf-events + debug isis lsp-gen + ! + +YANG +^^^^ + +PPR can also be configured using NETCONF, RESTCONF and gRPC based on the +following YANG models: \* +`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__ +\* +`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__ + +As an example, here’s R11 configuration in the XML format: + +.. code:: xml + + <lib xmlns="http://frrouting.org/yang/interface"> + <interface> + <name>lo-ppr</name> + <vrf>default</vrf> + </interface> + <interface> + <name>lo</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + </isis> + </interface> + <interface> + <name>eth-ce1</name> + <vrf>default</vrf> + </interface> + <interface> + <name>eth-rt12</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + <interface> + <name>eth-rt21</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + </lib> + <ppr xmlns="http://frrouting.org/yang/ppr"> + <group> + <name>VOIP</name> + <ipv6> + <ppr-id>6000:1::1/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::1/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + </group> + <group> + <name>INTERFACE_PDES</name> + <ipv6> + <ppr-id>6000:1::2/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::33/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:121::41/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::32/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:113::21/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </ipv6> + <ipv6> + <ppr-id>6000:2::2/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::32/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:121::41/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::33/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>4000:116::23/64</pde-id> + <pde-id-type>ipv6-interface</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </ipv6> + </group> + <group> + <name>BROKEN</name> + <ipv6> + <ppr-id>6000:1::3/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::99/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>1500</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::3/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::99/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>1500</ppr-metric> + </attributes> + </ipv6> + </group> + </ppr> + <isis xmlns="http://frrouting.org/yang/isisd"> + <instance> + <area-tag>1</area-tag> + <area-address>49.0000.0000.0000.0011.00</area-address> + <multi-topology> + <ipv6-unicast> + </ipv6-unicast> + </multi-topology> + <ppr> + <enable>true</enable> + <ppr-advertise> + <name>VOIP</name> + </ppr-advertise> + <ppr-advertise> + <name>INTERFACE_PDES</name> + </ppr-advertise> + <ppr-advertise> + <name>BROKEN</name> + </ppr-advertise> + </ppr> + </instance> + </isis> + +Verification - Control Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers: + +:: + + # show isis database detail 0000.0000.0011 + Area 1: + IS-IS Level-1 link-state database: + LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL + debian.00-00 1233 0x00000009 0x7bd4 683 0/0/0 + Protocols Supported: IPv4, IPv6 + Area Address: 49.0000 + MT Router Info: ipv4-unicast + MT Router Info: ipv6-unicast + Hostname: debian + MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast + MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::3/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 1500 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::3/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::99/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 1500 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::2/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:113::21 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::2/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::32/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:121::41 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::33/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 4000:116::23 (IPv6 Interface Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::1/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::1/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + +The PPR TLVs can also be seen using a modified version of Wireshark as +seen below: + +.. figure:: https://user-images.githubusercontent.com/931662/61582441-9551e500-ab01-11e9-8f6f-400ee3fba927.png + :alt: s2 + + s2 + +Using the ``show isis ppr`` command, verify that all routers installed +the PPR-IDs for the paths they are part of. Example: + +Router RT11 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Tail-End - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Tail-End - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Tail-End - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Head-End Up 00:45:41 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Head-End Up 00:45:41 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Head-End Up 00:45:41 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:2::1/128 [115/50] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + I>* 6000:2::2/128 [115/0] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + I>* 6000:2::3/128 [115/1500] via fe80::c2a:54ff:fe39:bff7, eth-rt21, 00:01:33 + +Router RT12 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT13 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT14 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Head-End Up 00:45:45 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Head-End Up 00:45:45 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Head-End Up 00:45:45 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Tail-End - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Tail-End - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Tail-End - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + I>* 6000:1::2/128 [115/0] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + I>* 6000:1::3/128 [115/1500] via fe80::58ea:78ff:fe00:92c1, eth-rt23, 00:01:36 + +Router RT21 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:46 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:46 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Mid-Point Up 00:45:46 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:46 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:46 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Mid-Point Down - + + # show isis ppr id ipv6 6000:2::3/128 detail + Area 1: + PPR-ID: 6000:2::3/128 (Native IPv6) + PPR-Prefix: 5000::14/128 + PDEs: + 5000::11/128 (IPv6 Node Address) + 5000::21/128 (IPv6 Node Address) [LOCAL] + 5000::99/128 (IPv6 Node Address) [NEXT] + 5000::23/128 (IPv6 Node Address) + 5000::14/128 (IPv6 Node Address) + Attributes: + Metric: 1500 + Position: Mid-Point + Originator: 0000.0000.0011 + Level: L1 + Algorithm: 1 + MT-ID: ipv4-unicast + Status: Down: PDE is unreachable + Last change: 00:00:37 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:1::2/128 [115/0] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:1::3/128 [115/1500] via fe80::142e:79ff:feeb:cffc, eth-rt11, 00:01:38 + I>* 6000:2::1/128 [115/50] via fe80::c88e:7fff:fe5f:a08d, eth-rt22, 00:01:38 + I>* 6000:2::2/128 [115/0] via fe80::8b2:9eff:fe98:f66a, eth-rt32, 00:01:38 + +Router RT22 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:47 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:47 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::2cb5:edff:fe60:29b1, eth-rt21, 00:01:38 + I>* 6000:2::1/128 [115/50] via fe80::e8d9:63ff:fea3:177b, eth-rt23, 00:01:38 + +Router RT23 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:45:49 + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:49 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Mid-Point Down - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:45:49 + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:49 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Mid-Point Up 00:45:49 + + # show isis ppr id ipv6 6000:1::3/128 detail + Area 1: + PPR-ID: 6000:1::3/128 (Native IPv6) + PPR-Prefix: 5000::11/128 + PDEs: + 5000::14/128 (IPv6 Node Address) + 5000::23/128 (IPv6 Node Address) [LOCAL] + 5000::99/128 (IPv6 Node Address) [NEXT] + 5000::21/128 (IPv6 Node Address) + 5000::11/128 (IPv6 Node Address) + Attributes: + Metric: 1500 + Position: Mid-Point + Originator: 0000.0000.0011 + Level: L1 + Algorithm: 1 + MT-ID: ipv4-unicast + Status: Down: PDE is unreachable + Last change: 00:02:50 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::d09f:1bff:fe31:e9c9, eth-rt22, 00:01:40 + I>* 6000:1::2/128 [115/0] via fe80::c0c3:b3ff:fe9f:b5d3, eth-rt33, 00:01:40 + I>* 6000:2::1/128 [115/50] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + I>* 6000:2::2/128 [115/0] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + I>* 6000:2::3/128 [115/1500] via fe80::f40a:66ff:fefc:5c32, eth-rt14, 00:01:40 + +Router RT31 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT32 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:51 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:51 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via 4000:113::21, eth-rt21, 00:01:42 + I>* 6000:2::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:42 + +Router RT33 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:52 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:52 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via 4000:121::41, eth-sw1, 00:01:43 + I>* 6000:2::2/128 [115/0] via 4000:116::23, eth-rt23, 00:01:43 + +Router RT34 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Off-Path - - + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Off-Path - - + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT41 +''''''''''' + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:1::2/128 (Native IPv6) 5000::11/128 0 Mid-Point Up 00:45:55 + 1 L1 6000:1::3/128 (Native IPv6) 5000::11/128 1500 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + 1 L1 6000:2::2/128 (Native IPv6) 5000::14/128 0 Mid-Point Up 00:45:55 + 1 L1 6000:2::3/128 (Native IPv6) 5000::14/128 1500 Off-Path - - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::2/128 [115/0] via fe80::b4b9:60ff:feee:3c73, eth-sw1, 00:01:46 + I>* 6000:2::2/128 [115/0] via fe80::bc2a:d9ff:fe65:97f2, eth-sw1, 00:01:46 + +As it can be seen by the output of ``show isis ppr id ipv6 ... detail``, +routers R21 and R23 couldn’t install the third PPR path because of an +unreachable PDE (configuration error). + +Verification - Forwarding Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +On Router R11, use the ``traceroute`` tool to ensure that the PPR paths +were installed correctly in the network: + +:: + + root@rt11:~# traceroute 6000:2::1 + traceroute to 6000:2::1 (6000:2::1), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 0.612 ms 0.221 ms 0.241 ms + 2 4000:110::22 (4000:110::22) 0.257 ms 0.113 ms 0.105 ms + 3 4000:111::23 (4000:111::23) 0.257 ms 0.151 ms 0.098 ms + 4 6000:2::1 (6000:2::1) 0.346 ms 0.139 ms 0.100 ms + root@rt11:~# + root@rt11:~# traceroute 6000:2::2 + traceroute to 6000:2::2 (6000:2::2), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 4.383 ms 4.148 ms 0.044 ms + 2 4000:113::32 (4000:113::32) 0.272 ms 0.065 ms 0.064 ms + 3 4000:121::41 (4000:121::41) 0.263 ms 0.101 ms 0.086 ms + 4 4000:115::33 (4000:115::33) 0.351 ms 4000:119::33 (4000:119::33) 0.249 ms 4000:115::33 (4000:115::33) 0.153 ms + 5 4000:111::23 (4000:111::23) 0.232 ms 0.293 ms 0.131 ms + 6 6000:2::2 (6000:2::2) 0.184 ms 0.212 ms 0.140 ms + root@rt11:~# + root@rt11:~# traceroute 6000:2::3 + traceroute to 6000:2::3 (6000:2::3), 30 hops max, 80 byte packets + 1 4000:104::21 (4000:104::21) 1.537 ms !N 1.347 ms !N 1.075 ms !N + +The failure on the third traceroute is expected since the 6000:2::3 +PPR-ID is misconfigured. + +Now ping Host 3 from Host 1 and use tcpdump or wireshark to verify that +the ICMP packets are being tunneled using GRE and following the {R11 - +R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and +R21: + +.. figure:: https://user-images.githubusercontent.com/931662/61582398-d4cc0180-ab00-11e9-83a8-d219f98010b9.png + :alt: s1 + + s1 + +Using ``traceroute`` it’s also possible to see that the ICMP packets are +being tunneled through the IS-IS network: + +:: + + root@host1:~# traceroute fd00:20:1::1 -s fd00:10:1::1 + traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets + 1 fd00:10:1::100 (fd00:10:1::100) 0.354 ms 0.092 ms 0.031 ms + 2 fd00:10::11 (fd00:10::11) 0.125 ms 0.022 ms 0.026 ms + 3 * * * + 4 * * * + 5 fd00:20:1::1 (fd00:20:1::1) 0.235 ms 0.106 ms 0.091 ms diff --git a/doc/developer/northbound/ppr-mpls-basic-test-topology.rst b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst new file mode 100644 index 0000000000..cedb795da9 --- /dev/null +++ b/doc/developer/northbound/ppr-mpls-basic-test-topology.rst @@ -0,0 +1,1991 @@ +Table of Contents +~~~~~~~~~~~~~~~~~ + +- `Software <#software>`__ +- `Topology <#topology>`__ +- `Configuration <#configuration>`__ + + - `CLI <#configuration-cli>`__ + - `YANG <#configuration-yang>`__ + +- `Verification - Control Plane <#verification-cplane>`__ +- `Verification - Forwarding Plane <#verification-fplane>`__ + +Software +~~~~~~~~ + +The FRR PPR implementation for IS-IS is available here: +https://github.com/opensourcerouting/frr/tree/isisd-ppr-sr + +Topology +~~~~~~~~ + +In this topology we have an IS-IS network consisting of 12 routers. CE1 +and CE2 are the consumer edges, connected to R11 and R14, respectively. +Three hosts are connected to the CEs using only static routes. + +Router R11 advertises 6 PPR TLVs: \* **IPv6 prefixes 6000:1::1/128 and +6000:2::1/128:** {R11 - R21 - R22 - R23 - R14} (IPv6 Node Addresses). \* +**MPLS SR Prefix-SIDs 500 and 501:** {R11 - R21 - R22 - R23 - R14} (SR +Prefix-SIDs). \* **MPLS SR Prefix-SIDs 502 and 503:** {R11 - R21 - R31 - +R32 - R41 - R33 - R34 - R23 - R14} (SR Prefix-SIDs) + +PBR rules are configured on R11 and R14 to route the traffic between +Host 1 and Host 3 using the first PPR tunnel, whereas all other traffic +between CE1 and CE2 uses the second PPR tunnel. + +Additional information: \* Addresses in the 4000::/16 range refer to +interface addresses, where the last hextet corresponds to the node ID. +\* Addresses in the 5000::/16 range refer to loopback addresses, where +the last hextet corresponds to the node ID. \* Addresses in the +6000::/16 range refer to PPR-ID addresses. + +:: + + +-------+ +-------+ +-------+ + | | | | | | + | HOST1 | | HOST2 | | HOST3 | + | | | | | | + +---+---+ +---+---+ +---+---+ + | | | + |fd00:10:1::/64 | | + +-----+ +------+ fd00:20:1::/64| + | |fd00:10:2::/64 | + | | | + +-+--+--+ +---+---+ + | | | | + | CE1 | | CE2 | + | | | | + +---+---+ +---+---+ + | | + | | + |fd00:10:0::/64 fd00:20:0::/64| + | | + | | + +---+---+ +-------+ +-------+ +---+---+ + | |4000:101::/64| |4000:102::/64| |4000:103::/64| | + | R11 +-------------+ R12 +-------------+ R13 +-------------+ R14 | + | | | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | | | | | | + |4000:104::/64 | |4000:106::/64 | |4000:108::/64 | + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + | |4000:105::/64 | |4000:107::/64 | |4000:109::/64 + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | |4000:110::/64| |4000:111::/64| | + | R21 +-------------+ R22 +-------------+ R23 | + | | | | | | + +--+-+--+ +--+-+--+ +--+-+--+ + | | | | | | + | |4000:113::/64 | |4000:115::/64 | |4000:117::/64 + +---------+ +--------+ +--------+ +--------+ +--------+ +---------+ + |4000:112::/64 | |4000:114::/64 | |4000:116::/64 | + | | | | | | + +---+---+ +--+-+--+ +--+-+--+ +---+---+ + | |4000:118::/64| |4000:119::/64| |4000:120::/64| | + | R31 +-------------+ R32 +-------------+ R33 +-------------+ R34 | + | | | | | | | | + +-------+ +---+---+ +---+---+ +-------+ + | | + |4000:121::/64 | + +----------+----------+ + | + | + +---+---+ + | | + | R41 | + | | + +-------+ + +Configuration +~~~~~~~~~~~~~ + +PPR TLV processing needs to be enabled on all IS-IS routers using the +``ppr on`` command. The advertisements of all PPR TLVs is done by router +R11. + +CLI configuration +^^^^^^^^^^^^^^^^^ + +.. code:: yaml + + --- + + routers: + + host1: + links: + eth-ce1: + peer: [ce1, eth-host1] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:1::1/64 + ! + ipv6 route ::/0 fd00:10:1::100 + + host2: + links: + eth-ce1: + peer: [ce1, eth-host2] + frr: + zebra: + staticd: + config: | + interface eth-ce1 + ipv6 address fd00:10:2::1/64 + ! + ipv6 route ::/0 fd00:10:2::100 + + host3: + links: + eth-ce2: + peer: [ce2, eth-host3] + frr: + zebra: + staticd: + config: | + interface eth-ce2 + ipv6 address fd00:20:1::1/64 + ! + ipv6 route ::/0 fd00:20:1::100 + + ce1: + links: + eth-host1: + peer: [host1, eth-ce1] + eth-host2: + peer: [host2, eth-ce1] + eth-rt11: + peer: [rt11, eth-ce1] + frr: + zebra: + staticd: + config: | + interface eth-host1 + ipv6 address fd00:10:1::100/64 + ! + interface eth-host2 + ipv6 address fd00:10:2::100/64 + ! + interface eth-rt11 + ipv6 address fd00:10:0::100/64 + ! + ipv6 route ::/0 fd00:10:0::11 label 16501 + + ce2: + links: + eth-host3: + peer: [host3, eth-ce2] + eth-rt14: + peer: [rt14, eth-ce2] + frr: + zebra: + staticd: + config: | + interface eth-host3 + ipv6 address fd00:20:1::100/64 + ! + interface eth-rt14 + ipv6 address fd00:20:0::100/64 + ! + ipv6 route ::/0 fd00:20:0::14 label 16500 + + rt11: + links: + lo: + mpls: yes + lo-ppr: + eth-ce1: + peer: [ce1, eth-rt11] + mpls: yes + eth-rt12: + peer: [rt12, eth-rt11] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt11] + mpls: yes + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:2::1 local 6000:1::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:10:1::/64 to fd00:20:1::/64 iif eth-ce1 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:1::1/128 + ! + interface lo + ip address 10.0.0.11/32 + ipv6 address 5000::11/128 + ipv6 router isis 1 + ! + interface eth-ce1 + ipv6 address fd00:10:0::11/64 + ! + interface eth-rt12 + ipv6 address 4000:101::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:104::11/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:10::/32 fd00:10:0::100 + ! + ppr group PPR_IPV6 + ppr ipv6 6000:1::1/128 prefix 5000::11/128 metric 50 + pde ipv6-node 5000::14/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::11/128 + ! + ppr ipv6 6000:2::1/128 prefix 5000::14/128 metric 50 + pde ipv6-node 5000::11/128 + pde ipv6-node 5000::21/128 + pde ipv6-node 5000::22/128 + pde ipv6-node 5000::23/128 + pde ipv6-node 5000::14/128 + ! + ! + ppr group PPR_MPLS_1 + ppr mpls 500 prefix 5000::11/128 + pde prefix-sid 14 + pde prefix-sid 23 + pde prefix-sid 22 + pde prefix-sid 21 + pde prefix-sid 11 + ! + ppr mpls 501 prefix 5000::14/128 + pde prefix-sid 11 + pde prefix-sid 21 + pde prefix-sid 22 + pde prefix-sid 23 + pde prefix-sid 14 + ! + ! + ppr group PPR_MPLS_2 + ppr mpls 502 prefix 5000::11/128 + pde prefix-sid 14 + pde prefix-sid 23 + pde prefix-sid 34 + pde prefix-sid 33 + pde prefix-sid 41 + pde prefix-sid 32 + pde prefix-sid 31 + pde prefix-sid 21 + pde prefix-sid 11 + ! + ppr mpls 503 prefix 5000::14/128 + pde prefix-sid 11 + pde prefix-sid 21 + pde prefix-sid 31 + pde prefix-sid 32 + pde prefix-sid 41 + pde prefix-sid 33 + pde prefix-sid 34 + pde prefix-sid 23 + pde prefix-sid 14 + ! + ! + router isis 1 + net 49.0000.0000.0000.0011.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::11/128 index 11 no-php-flag + ppr on + ppr advertise PPR_IPV6 + ppr advertise PPR_MPLS_1 + ppr advertise PPR_MPLS_2 + ! + + rt12: + links: + lo: + mpls: yes + eth-rt11: + peer: [rt11, eth-rt12] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt12] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt12] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt12] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.12/32 + ipv6 address 5000::12/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:101::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:102::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:105::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:106::12/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0012.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::12/128 index 12 no-php-flag + ppr on + ! + + rt13: + links: + lo: + mpls: yes + eth-rt12: + peer: [rt12, eth-rt13] + mpls: yes + eth-rt14: + peer: [rt14, eth-rt13] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt13] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt13] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.13/32 + ipv6 address 5000::13/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:102::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:103::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:107::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:108::13/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0013.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::13/128 index 13 no-php-flag + ppr on + ! + + rt14: + links: + lo: + mpls: yes + lo-ppr: + eth-ce2: + peer: [ce2, eth-rt14] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt14] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt14] + mpls: yes + shell: | + # GRE tunnel for preferred packets (PPR) + ip -6 tunnel add tun-ppr mode ip6gre remote 6000:1::1 local 6000:2::1 ttl 64 + ip link set dev tun-ppr up + # PBR rules + ip -6 rule add from fd00:20:1::/64 to fd00:10:1::/64 iif eth-ce2 lookup 10000 + ip -6 route add default dev tun-ppr table 10000 + frr: + zebra: + staticd: + isisd: + config: | + interface lo-ppr + ipv6 address 6000:2::1/128 + ! + interface lo + ip address 10.0.0.14/32 + ipv6 address 5000::14/128 + ipv6 router isis 1 + ! + interface eth-ce2 + ipv6 address fd00:20:0::14/64 + ! + interface eth-rt13 + ipv6 address 4000:103::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:109::14/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + ipv6 route fd00:20::/32 fd00:20:0::100 + ! + router isis 1 + net 49.0000.0000.0000.0014.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::14/128 index 14 no-php-flag + ppr on + ! + + rt21: + links: + lo: + mpls: yes + eth-rt11: + peer: [rt11, eth-rt21] + mpls: yes + eth-rt12: + peer: [rt12, eth-rt21] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt21] + mpls: yes + eth-rt31: + peer: [rt31, eth-rt21] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt21] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.21/32 + ipv6 address 5000::21/128 + ipv6 router isis 1 + ! + interface eth-rt11 + ipv6 address 4000:104::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt12 + ipv6 address 4000:105::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:110::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:112::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:113::21/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0021.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::21/128 index 21 no-php-flag + ppr on + ! + + rt22: + links: + lo: + mpls: yes + eth-rt12: + peer: [rt12, eth-rt22] + mpls: yes + eth-rt13: + peer: [rt13, eth-rt22] + mpls: yes + eth-rt21: + peer: [rt21, eth-rt22] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt22] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt22] + mpls: yes + eth-rt33: + mpls: yes + peer: [rt33, eth-rt22] + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.22/32 + ipv6 address 5000::22/128 + ipv6 router isis 1 + ! + interface eth-rt12 + ipv6 address 4000:106::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt13 + ipv6 address 4000:107::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt21 + ipv6 address 4000:110::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:111::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:114::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:115::22/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0022.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::22/128 index 22 no-php-flag + ppr on + ! + + rt23: + links: + lo: + mpls: yes + eth-rt13: + peer: [rt13, eth-rt23] + mpls: yes + eth-rt14: + peer: [rt14, eth-rt23] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt23] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt23] + mpls: yes + eth-rt34: + peer: [rt34, eth-rt23] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.23/32 + ipv6 address 5000::23/128 + ipv6 router isis 1 + ! + interface eth-rt13 + ipv6 address 4000:108::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt14 + ipv6 address 4000:109::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:111::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:116::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:117::23/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0023.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing global-block 20000 27999 + segment-routing prefix 5000::23/128 index 23 no-php-flag + ppr on + ! + + rt31: + links: + lo: + mpls: yes + eth-rt21: + peer: [rt21, eth-rt31] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt31] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.31/32 + ipv6 address 5000::31/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:112::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:118::31/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0031.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::31/128 index 31 no-php-flag + ppr on + ! + + rt32: + links: + lo: + mpls: yes + eth-rt21: + peer: [rt21, eth-rt32] + mpls: yes + eth-rt22: + peer: [rt22, eth-rt32] + mpls: yes + eth-rt31: + peer: [rt31, eth-rt32] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt32] + mpls: yes + eth-sw1: + peer: [sw1, eth-rt32] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.32/32 + ipv6 address 5000::32/128 + ipv6 router isis 1 + ! + interface eth-rt21 + ipv6 address 4000:113::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt22 + ipv6 address 4000:114::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt31 + ipv6 address 4000:118::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:119::32/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::32/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0032.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::32/128 index 32 no-php-flag + ppr on + ! + + rt33: + links: + lo: + mpls: yes + eth-rt22: + peer: [rt22, eth-rt33] + mpls: yes + eth-rt23: + peer: [rt23, eth-rt33] + mpls: yes + eth-rt32: + peer: [rt32, eth-rt33] + mpls: yes + eth-rt34: + peer: [rt34, eth-rt33] + mpls: yes + eth-sw1: + peer: [sw1, eth-rt33] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.33/32 + ipv6 address 5000::33/128 + ipv6 router isis 1 + ! + interface eth-rt22 + ipv6 address 4000:115::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt23 + ipv6 address 4000:116::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt32 + ipv6 address 4000:119::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt34 + ipv6 address 4000:120::33/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-sw1 + ipv6 address 4000:121::33/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0033.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::33/128 index 33 no-php-flag + ppr on + ! + + rt34: + links: + lo: + mpls: yes + eth-rt23: + peer: [rt23, eth-rt34] + mpls: yes + eth-rt33: + peer: [rt33, eth-rt34] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.34/32 + ipv6 address 5000::34/128 + ipv6 router isis 1 + ! + interface eth-rt23 + ipv6 address 4000:117::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + interface eth-rt33 + ipv6 address 4000:120::34/64 + ipv6 router isis 1 + isis network point-to-point + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0034.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::34/128 index 34 no-php-flag + ppr on + ! + + rt41: + links: + lo: + mpls: yes + eth-sw1: + peer: [sw1, eth-rt41] + mpls: yes + frr: + zebra: + isisd: + config: | + interface lo + ip address 10.0.0.41/32 + ipv6 address 5000::41/128 + ipv6 router isis 1 + ! + interface eth-sw1 + ipv6 address 4000:121::41/64 + ipv6 router isis 1 + isis hello-multiplier 3 + ! + router isis 1 + net 49.0000.0000.0000.0041.00 + is-type level-1 + topology ipv6-unicast + segment-routing on + segment-routing prefix 5000::41/128 index 41 no-php-flag + ppr on + ! + + switches: + sw1: + links: + eth-rt32: + peer: [rt32, eth-sw1] + eth-rt33: + peer: [rt33, eth-sw1] + eth-rt41: + peer: [rt41, eth-sw1] + + frr: + #valgrind: yes + base-config: | + hostname %(node) + password 1 + log file %(logdir)/%(node).log + log commands + ! + debug zebra rib + debug isis sr-events + debug isis ppr + debug isis events + debug isis route-events + debug isis spf-events + debug isis lsp-gen + ! + +.. + + NOTE: it’s of fundamental importance to enable MPLS processing on the + loopback interfaces, otherwise the tail-end routers of the PPR-MPLS + tunnels will drop the labeled packets they receive. + +YANG +^^^^ + +PPR can also be configured using NETCONF, RESTCONF and gRPC based on the +following YANG models: \* +`frr-ppr.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-ppr.yang>`__ +\* +`frr-isisd.yang <https://github.com/opensourcerouting/frr/blob/isisd-ppr/yang/frr-isisd.yang>`__ + +As an example, here’s R11 configuration in the XML format: + +.. code:: xml + + <lib xmlns="http://frrouting.org/yang/interface"> + <interface> + <name>lo-ppr</name> + <vrf>default</vrf> + </interface> + <interface> + <name>lo</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + </isis> + </interface> + <interface> + <name>eth-ce1</name> + <vrf>default</vrf> + </interface> + <interface> + <name>eth-rt12</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + <interface> + <name>eth-rt21</name> + <vrf>default</vrf> + <isis xmlns="http://frrouting.org/yang/isisd"> + <area-tag>1</area-tag> + <ipv6-routing>true</ipv6-routing> + <hello> + <multiplier> + <level-1>3</level-1> + <level-2>3</level-2> + </multiplier> + </hello> + <network-type>point-to-point</network-type> + </isis> + </interface> + </lib> + <ppr xmlns="http://frrouting.org/yang/ppr"> + <group> + <name>PPR_IPV6</name> + <ipv6> + <ppr-id>6000:1::1/128</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + <ipv6> + <ppr-id>6000:2::1/128</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>5000::11/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::21/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::22/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::23/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>5000::14/128</pde-id> + <pde-id-type>ipv6-node</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <attributes> + <ppr-metric>50</ppr-metric> + </attributes> + </ipv6> + </group> + <group> + <name>PPR_MPLS_1</name> + <mpls> + <ppr-id>500</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>22</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + <mpls> + <ppr-id>501</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>22</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + </group> + <group> + <name>PPR_MPLS_2</name> + <mpls> + <ppr-id>502</ppr-id> + <ppr-prefix>5000::11/128</ppr-prefix> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>34</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>33</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>41</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>32</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>31</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + <mpls> + <ppr-id>503</ppr-id> + <ppr-prefix>5000::14/128</ppr-prefix> + <ppr-pde> + <pde-id>11</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>21</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>31</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>32</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>41</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>33</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>34</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>23</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + <ppr-pde> + <pde-id>14</pde-id> + <pde-id-type>prefix-sid</pde-id-type> + <pde-type>topological</pde-type> + </ppr-pde> + </mpls> + </group> + </ppr> + <isis xmlns="http://frrouting.org/yang/isisd"> + <instance> + <area-tag>1</area-tag> + <area-address>49.0000.0000.0000.0011.00</area-address> + <multi-topology> + <ipv6-unicast> + </ipv6-unicast> + </multi-topology> + <segment-routing> + <enabled>true</enabled> + <prefix-sid-map> + <prefix-sid> + <prefix>5000::11/128</prefix> + <sid-value>11</sid-value> + <last-hop-behavior>no-php</last-hop-behavior> + </prefix-sid> + </prefix-sid-map> + </segment-routing> + <ppr> + <enable>true</enable> + <ppr-advertise> + <name>PPR_IPV6</name> + </ppr-advertise> + <ppr-advertise> + <name>PPR_MPLS_1</name> + </ppr-advertise> + <ppr-advertise> + <name>PPR_MPLS_2</name> + </ppr-advertise> + </ppr> + </instance> + </isis> + +Verification - Control Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Verify that R11 has flooded the PPR TLVs correctly to all IS-IS routers: + +:: + + # show isis database detail 0000.0000.0011 + Area 1: + IS-IS Level-1 link-state database: + LSP ID PduLen SeqNumber Chksum Holdtime ATT/P/OL + debian.00-00 * 980 0x00000003 0x3b69 894 0/0/0 + Protocols Supported: IPv4, IPv6 + Area Address: 49.0000 + MT Router Info: ipv4-unicast + MT Router Info: ipv6-unicast + Hostname: debian + TE Router ID: 10.0.0.11 + Router Capability: 10.0.0.11 , D:0, S:0 + Segment Routing: I:1 V:1, SRGB Base: 16000 Range: 8000 + Algorithm: 0: SPF 0: Strict SPF + MT Reachability: 0000.0000.0012.00 (Metric: 10) ipv6-unicast + Adjacency-SID: 16, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0 + MT Reachability: 0000.0000.0021.00 (Metric: 10) ipv6-unicast + Adjacency-SID: 17, Weight: 0, Flags: F:1 B:0, V:1, L:1, S:0, P:0 + IPv4 Interface Address: 10.0.0.11 + Extended IP Reachability: 10.0.0.11/32 (Metric: 10) + MT IPv6 Reachability: 5000::11/128 (Metric: 10) ipv6-unicast + Subtlvs: + SR Prefix-SID Index: 11, Algorithm: 0, Flags: NO-PHP + MT IPv6 Reachability: 4000:101::/64 (Metric: 10) ipv6-unicast + MT IPv6 Reachability: 4000:104::/64 (Metric: 10) ipv6-unicast + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 6000:1::1/128 (Native IPv6) + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 6000:2::1/128 (Native IPv6) + PDE: 5000::11/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::21/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::22/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::23/128 (IPv6 Node Address), L:0 N:0 E:0 + PDE: 5000::14/128 (IPv6 Node Address), L:0 N:1 E:0 + Metric: 50 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 500 (MPLS) + PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 501 (MPLS) + PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 22 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::11/128 + ID: 502 (MPLS) + PDE: 14 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 11 (SR-MPLS Prefix SID), L:0 N:1 E:0 + PPR: Fragment ID: 0, MT-ID: ipv4-unicast, Algorithm: SPF, F:0 D:0 A:0 U:1 + PPR Prefix: 5000::14/128 + ID: 503 (MPLS) + PDE: 11 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 21 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 31 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 32 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 41 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 33 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 34 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 23 (SR-MPLS Prefix SID), L:0 N:0 E:0 + PDE: 14 (SR-MPLS Prefix SID), L:0 N:1 E:0 + +Using the ``show isis ppr`` command, verify that all routers installed +the PPR-IDs for the paths they are part of. Example: + +Router RT11 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Tail-End Up 00:00:42 + 1 L1 501 (MPLS) 5000::14/128 0 Head-End Up 00:00:41 + 1 L1 502 (MPLS) 5000::11/128 0 Tail-End Up 00:00:42 + 1 L1 503 (MPLS) 5000::14/128 0 Head-End Up 00:00:41 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Tail-End - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Head-End Up 00:00:41 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 implicit-null + 17 SR (IS-IS) fe80::345f:dfff:fea4:913d implicit-null + 16011 SR (IS-IS) lo - + 16012 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16012 + 16013 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16013 + 16014 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16014 + 16021 SR (IS-IS) fe80::345f:dfff:fea4:913d 16021 + 16022 SR (IS-IS) fe80::345f:dfff:fea4:913d 16022 + 16022 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16022 + 16023 SR (IS-IS) fe80::345f:dfff:fea4:913d 16023 + 16023 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16023 + 16031 SR (IS-IS) fe80::345f:dfff:fea4:913d 16031 + 16032 SR (IS-IS) fe80::345f:dfff:fea4:913d 16032 + 16033 SR (IS-IS) fe80::345f:dfff:fea4:913d 16033 + 16033 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16033 + 16034 SR (IS-IS) fe80::345f:dfff:fea4:913d 16034 + 16034 SR (IS-IS) fe80::2065:5ff:fe72:d6c5 16034 + 16041 SR (IS-IS) fe80::345f:dfff:fea4:913d 16041 + 16500 PPR (IS-IS) lo - + 16501 PPR (IS-IS) fe80::345f:dfff:fea4:913d 16501 + 16502 PPR (IS-IS) lo - + 16503 PPR (IS-IS) fe80::345f:dfff:fea4:913d 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:2::1/128 [115/50] via fe80::345f:dfff:fea4:913d, eth-rt21, 00:00:41 + +Router RT12 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ---------------------------------------------------------------------- + 16 SR (IS-IS) fe80::60ad:96ff:fe3f:9989 implicit-null + 17 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 implicit-null + 18 SR (IS-IS) fe80::941c:12ff:fe55:8a12 implicit-null + 19 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 implicit-null + 16011 SR (IS-IS) fe80::60ad:96ff:fe3f:9989 16011 + 16012 SR (IS-IS) lo - + 16013 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16013 + 16014 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16014 + 16021 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16021 + 16022 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16022 + 16023 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16023 + 16023 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16023 + 16031 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16031 + 16032 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16032 + 16032 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16032 + 16033 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16033 + 16034 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16034 + 16034 SR (IS-IS) fe80::9cd2:25ff:febc:84c4 16034 + 16041 SR (IS-IS) fe80::78a7:59ff:fedc:48b8 16041 + 16041 SR (IS-IS) fe80::941c:12ff:fe55:8a12 16041 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT13 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + ------------------------------------------------------------------------------------------ + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ---------------------------------------------------------------------- + 16 SR (IS-IS) fe80::1c70:63ff:fe40:3a35 implicit-null + 17 SR (IS-IS) fe80::20:56ff:feff:b218 implicit-null + 18 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a implicit-null + 19 SR (IS-IS) fe80::387d:34ff:fe02:87c3 implicit-null + 16011 SR (IS-IS) fe80::20:56ff:feff:b218 16011 + 16012 SR (IS-IS) fe80::20:56ff:feff:b218 16012 + 16013 SR (IS-IS) lo - + 16014 SR (IS-IS) fe80::1c70:63ff:fe40:3a35 16014 + 16021 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16021 + 16021 SR (IS-IS) fe80::20:56ff:feff:b218 16021 + 16022 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16022 + 16023 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20023 + 16031 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16031 + 16031 SR (IS-IS) fe80::20:56ff:feff:b218 16031 + 16032 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16032 + 16033 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20033 + 16033 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16033 + 16034 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20034 + 16041 SR (IS-IS) fe80::44c5:3fff:fe1e:f34a 20041 + 16041 SR (IS-IS) fe80::387d:34ff:fe02:87c3 16041 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT14 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + -------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Head-End Up 00:00:46 + 1 L1 501 (MPLS) 5000::14/128 0 Tail-End Up 00:00:47 + 1 L1 502 (MPLS) 5000::11/128 0 Head-End Up 00:00:46 + 1 L1 503 (MPLS) 5000::14/128 0 Tail-End Up 00:00:47 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Head-End Up 00:00:46 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Tail-End - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad implicit-null + 17 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 implicit-null + 16011 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16011 + 16012 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16012 + 16013 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16013 + 16014 SR (IS-IS) lo - + 16021 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20021 + 16021 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16021 + 16022 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20022 + 16022 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16022 + 16023 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20023 + 16031 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20031 + 16031 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16031 + 16032 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20032 + 16032 SR (IS-IS) fe80::bcb5:99ff:fed7:22ad 16032 + 16033 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20033 + 16034 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20034 + 16041 SR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20041 + 16500 PPR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20500 + 16501 PPR (IS-IS) lo - + 16502 PPR (IS-IS) fe80::4c7b:a1ff:fe66:6ca7 20502 + 16503 PPR (IS-IS) lo - + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::4c7b:a1ff:fe66:6ca7, eth-rt23, 00:00:02 + +Router RT21 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:49 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:48 + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:49 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:48 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:49 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:48 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::b886:2cff:fe84:a76f implicit-null + 17 SR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 implicit-null + 18 SR (IS-IS) fe80::e877:a2ff:feb7:4438 implicit-null + 19 SR (IS-IS) fe80::a0c2:82ff:fe39:204c implicit-null + 20 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 implicit-null + 16011 SR (IS-IS) fe80::e877:a2ff:feb7:4438 16011 + 16012 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16012 + 16013 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16013 + 16013 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16013 + 16014 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16014 + 16014 SR (IS-IS) fe80::a0c2:82ff:fe39:204c 16014 + 16021 SR (IS-IS) lo - + 16022 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16022 + 16023 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16023 + 16031 SR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 16031 + 16032 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16032 + 16033 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16033 + 16033 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16033 + 16034 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16034 + 16034 SR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16034 + 16041 SR (IS-IS) fe80::b886:2cff:fe84:a76f 16041 + 16500 PPR (IS-IS) fe80::e877:a2ff:feb7:4438 16500 + 16501 PPR (IS-IS) fe80::ac6a:8aff:fe14:4f36 16501 + 16502 PPR (IS-IS) fe80::e877:a2ff:feb7:4438 16502 + 16503 PPR (IS-IS) fe80::bc7e:bbff:fe7f:ecb0 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::e877:a2ff:feb7:4438, eth-rt11, 00:00:04 + I>* 6000:2::1/128 [115/50] via fe80::ac6a:8aff:fe14:4f36, eth-rt22, 00:00:04 + +Router RT22 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:50 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:50 + 1 L1 502 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 503 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:50 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:50 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 implicit-null + 17 SR (IS-IS) fe80::c436:63ff:feb3:4f5d implicit-null + 18 SR (IS-IS) fe80::56:41ff:fe53:a6b2 implicit-null + 19 SR (IS-IS) fe80::b423:eaff:fea1:8247 implicit-null + 20 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 implicit-null + 21 SR (IS-IS) fe80::7402:b8ff:fee9:682e implicit-null + 16011 SR (IS-IS) fe80::b423:eaff:fea1:8247 16011 + 16011 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 16011 + 16012 SR (IS-IS) fe80::3432:84ff:fe9d:2e41 16012 + 16013 SR (IS-IS) fe80::c436:63ff:feb3:4f5d 16013 + 16014 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20014 + 16014 SR (IS-IS) fe80::c436:63ff:feb3:4f5d 16014 + 16021 SR (IS-IS) fe80::b423:eaff:fea1:8247 16021 + 16022 SR (IS-IS) lo - + 16023 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20023 + 16031 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16031 + 16031 SR (IS-IS) fe80::b423:eaff:fea1:8247 16031 + 16032 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16032 + 16033 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16033 + 16034 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16034 + 16034 SR (IS-IS) fe80::56:41ff:fe53:a6b2 20034 + 16041 SR (IS-IS) fe80::7402:b8ff:fee9:682e 16041 + 16041 SR (IS-IS) fe80::9c2f:11ff:fe0a:ab34 16041 + 16500 PPR (IS-IS) fe80::b423:eaff:fea1:8247 16500 + 16501 PPR (IS-IS) fe80::56:41ff:fe53:a6b2 20501 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::b423:eaff:fea1:8247, eth-rt21, 00:00:06 + I>* 6000:2::1/128 [115/50] via fe80::56:41ff:fe53:a6b2, eth-rt23, 00:00:06 + +Router RT23 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:52 + 1 L1 501 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:52 + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:52 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:52 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Mid-Point Up 00:00:52 + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Mid-Point Up 00:00:52 + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::c4ca:41ff:fe2d:de8c implicit-null + 17 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 implicit-null + 18 SR (IS-IS) fe80::5c15:8aff:feea:1d07 implicit-null + 19 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f implicit-null + 20 SR (IS-IS) fe80::d0dc:6eff:fe71:9f19 implicit-null + 20011 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16011 + 20011 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16011 + 20012 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16012 + 20012 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16012 + 20013 SR (IS-IS) fe80::a02b:1eff:fed6:97e4 16013 + 20014 SR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16014 + 20021 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16021 + 20022 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16022 + 20023 SR (IS-IS) lo - + 20031 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16031 + 20031 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16031 + 20032 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16032 + 20032 SR (IS-IS) fe80::5c15:8aff:feea:1d07 16032 + 20033 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16033 + 20034 SR (IS-IS) fe80::d0dc:6eff:fe71:9f19 16034 + 20041 SR (IS-IS) fe80::a42f:50ff:fe9c:af9f 16041 + 20500 PPR (IS-IS) fe80::5c15:8aff:feea:1d07 16500 + 20501 PPR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16501 + 20502 PPR (IS-IS) fe80::d0dc:6eff:fe71:9f19 16502 + 20503 PPR (IS-IS) fe80::c4ca:41ff:fe2d:de8c 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + Codes: K - kernel route, C - connected, S - static, R - RIPng, + O - OSPFv3, I - IS-IS, B - BGP, N - NHRP, T - Table, + v - VNC, V - VNC-Direct, A - Babel, D - SHARP, F - PBR, + f - OpenFabric, + > - selected route, * - FIB route, q - queued route, r - rejected route + + I>* 6000:1::1/128 [115/50] via fe80::5c15:8aff:feea:1d07, eth-rt22, 00:00:07 + I>* 6000:2::1/128 [115/50] via fe80::c4ca:41ff:fe2d:de8c, eth-rt14, 00:00:07 + +Router RT31 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:54 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:54 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 implicit-null + 17 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 implicit-null + 16011 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16011 + 16012 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16012 + 16013 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16013 + 16013 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16013 + 16014 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16014 + 16014 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16014 + 16021 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16021 + 16022 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16022 + 16022 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16022 + 16023 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16023 + 16023 SR (IS-IS) fe80::a067:c6ff:fe2c:3385 16023 + 16031 SR (IS-IS) lo - + 16032 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16032 + 16033 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16033 + 16034 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16034 + 16041 SR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16041 + 16502 PPR (IS-IS) fe80::a067:c6ff:fe2c:3385 16502 + 16503 PPR (IS-IS) fe80::f46d:c8ff:fe8a:a341 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT32 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:55 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:55 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::881f:d3ff:febd:9e8c implicit-null + 17 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 implicit-null + 18 SR (IS-IS) fe80::9863:abff:fed0:d7e implicit-null + 19 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 implicit-null + 20 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 implicit-null + 21 SR (IS-IS) fe80::40c4:e6ff:fe26:767f implicit-null + 16011 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16011 + 16012 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16012 + 16012 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16012 + 16013 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16013 + 16014 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16014 + 16014 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16014 + 16014 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16014 + 16021 SR (IS-IS) fe80::881f:d3ff:febd:9e8c 16021 + 16022 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16022 + 16023 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16023 + 16023 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16023 + 16023 SR (IS-IS) fe80::40c4:e6ff:fe26:767f 16023 + 16031 SR (IS-IS) fe80::9863:abff:fed0:d7e 16031 + 16032 SR (IS-IS) lo - + 16033 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16033 + 16033 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16033 + 16034 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16034 + 16034 SR (IS-IS) fe80::ec65:d1ff:fe32:b508 16034 + 16041 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 16041 + 16502 PPR (IS-IS) fe80::9863:abff:fed0:d7e 16502 + 16503 PPR (IS-IS) fe80::a4e9:77ff:feaa:f690 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT33 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:57 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:57 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::2832:a9ff:fec3:7078 implicit-null + 17 SR (IS-IS) fe80::7806:e1ff:fe72:9b1f implicit-null + 18 SR (IS-IS) fe80::5476:31ff:fe94:c39 implicit-null + 19 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 implicit-null + 20 SR (IS-IS) fe80::68c9:2ff:fe04:5eba implicit-null + 21 SR (IS-IS) fe80::d053:97ff:fee2:1711 implicit-null + 16011 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16011 + 16011 SR (IS-IS) fe80::5476:31ff:fe94:c39 16011 + 16011 SR (IS-IS) fe80::d053:97ff:fee2:1711 16011 + 16012 SR (IS-IS) fe80::d053:97ff:fee2:1711 16012 + 16013 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20013 + 16013 SR (IS-IS) fe80::d053:97ff:fee2:1711 16013 + 16014 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20014 + 16021 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16021 + 16021 SR (IS-IS) fe80::5476:31ff:fe94:c39 16021 + 16021 SR (IS-IS) fe80::d053:97ff:fee2:1711 16021 + 16022 SR (IS-IS) fe80::d053:97ff:fee2:1711 16022 + 16023 SR (IS-IS) fe80::68c9:2ff:fe04:5eba 20023 + 16031 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16031 + 16031 SR (IS-IS) fe80::5476:31ff:fe94:c39 16031 + 16032 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16032 + 16032 SR (IS-IS) fe80::5476:31ff:fe94:c39 16032 + 16033 SR (IS-IS) lo - + 16034 SR (IS-IS) fe80::7806:e1ff:fe72:9b1f 16034 + 16041 SR (IS-IS) fe80::a4e9:77ff:feaa:f690 16041 + 16502 PPR (IS-IS) fe80::a4e9:77ff:feaa:f690 16502 + 16503 PPR (IS-IS) fe80::7806:e1ff:fe72:9b1f 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT34 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:00:59 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:00:59 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::ac33:5dff:fe99:81ec implicit-null + 17 SR (IS-IS) fe80::f009:b9ff:fe05:e540 implicit-null + 16011 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16011 + 16011 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20011 + 16012 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16012 + 16012 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20012 + 16013 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20013 + 16014 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20014 + 16021 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16021 + 16021 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20021 + 16022 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16022 + 16022 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20022 + 16023 SR (IS-IS) fe80::f009:b9ff:fe05:e540 20023 + 16031 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16031 + 16032 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16032 + 16033 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16033 + 16034 SR (IS-IS) lo - + 16041 SR (IS-IS) fe80::ac33:5dff:fe99:81ec 16041 + 16502 PPR (IS-IS) fe80::ac33:5dff:fe99:81ec 16502 + 16503 PPR (IS-IS) fe80::f009:b9ff:fe05:e540 20503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Router RT41 +^^^^^^^^^^^ + +:: + + # show isis ppr + Area Level ID Prefix Metric Position Status Uptime + --------------------------------------------------------------------------------------------- + 1 L1 500 (MPLS) 5000::11/128 0 Off-Path - - + 1 L1 501 (MPLS) 5000::14/128 0 Off-Path - - + 1 L1 502 (MPLS) 5000::11/128 0 Mid-Point Up 00:01:01 + 1 L1 503 (MPLS) 5000::14/128 0 Mid-Point Up 00:01:01 + 1 L1 6000:1::1/128 (Native IPv6) 5000::11/128 50 Off-Path - - + 1 L1 6000:2::1/128 (Native IPv6) 5000::14/128 50 Off-Path - - + + # show mpls table + Inbound Label Type Nexthop Outbound Label + ----------------------------------------------------------------------- + 16 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 implicit-null + 17 SR (IS-IS) fe80::2832:a9ff:fec3:7078 implicit-null + 16011 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16011 + 16012 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16012 + 16012 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16012 + 16013 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16013 + 16013 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16013 + 16014 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16014 + 16021 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16021 + 16022 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16022 + 16022 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16022 + 16023 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16023 + 16031 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16031 + 16032 SR (IS-IS) fe80::2832:a9ff:fec3:7078 16032 + 16033 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16033 + 16034 SR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16034 + 16041 SR (IS-IS) lo - + 16502 PPR (IS-IS) fe80::2832:a9ff:fec3:7078 16502 + 16503 PPR (IS-IS) fe80::1c7e:c3ff:fe5e:7a54 16503 + + # show ipv6 route 6000::/16 longer-prefixes isis + +Notice how R23 uses a different SRGB compared to the other routers in +the network. As such, this router install different labels for PPR-IDs +500 and 501 (e.g. 20500 instead of 16500 using the default SRGB). + +Verification - Forwarding Plane +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Ping Host 3 from Host2 and use tcpdump or wireshark to verify that the +ICMP packets are being tunneled using MPLS LSPs and following the {R11 - +R21 - R22 - R23 - R14} path. Here’s a wireshark capture between R11 and +R21: + +.. figure:: https://user-images.githubusercontent.com/931662/64057179-2e980080-cb70-11e9-89c3-ff43e6d66cae.png + :alt: wireshark + + wireshark + +Using ``traceroute`` it’s also possible to see that the ICMP packets are +being tunneled through the IS-IS network: + +:: + + root@host2:~# traceroute -n fd00:20:1::1 -s fd00:10:2::1 + traceroute to fd00:20:1::1 (fd00:20:1::1), 30 hops max, 80 byte packets + 1 fd00:10:2::100 1.996 ms 1.832 ms 1.725 ms + 2 * * * + 3 * * * + 4 * * * + 5 * * * + 6 * * * + 7 * * * + 8 fd00:20::100 0.154 ms 0.191 ms 0.116 ms + 9 fd00:20:1::1 0.125 ms 0.105 ms 0.104 ms diff --git a/doc/developer/northbound/retrofitting-configuration-commands.rst b/doc/developer/northbound/retrofitting-configuration-commands.rst new file mode 100644 index 0000000000..c13332bf1f --- /dev/null +++ b/doc/developer/northbound/retrofitting-configuration-commands.rst @@ -0,0 +1,1916 @@ +Table of Contents +----------------- + +- `Introduction <#introduction>`__ +- `Retrofitting process <#retrofitting-process>`__ + + - `Step 1: writing a YANG module <#step1>`__ + - `Step 2: generate skeleton northbound callbacks <#step2>`__ + - `Step 3: update the frr_yang_module_info array of all relevant + daemons <#step3>`__ + - `Step 4: implement the northbound configuration + callbacks <#step4>`__ + - `Step 5: rewrite the CLI commands as dumb wrappers around the + northbound callbacks <#step5>`__ + - `Step 6: implement the ``cli_show`` callbacks <#step6>`__ + - `Step 7: consolidation <#step7>`__ + +- `Final Considerations <#final-considerations>`__ + +Introduction +------------ + +This page explains how to convert existing CLI configuration commands to +the new northbound model. This documentation is meant to be the primary +reference for developers working on the northbound retrofitting process. +We’ll show several examples taken from the ripd northbound conversion to +illustrate some concepts described herein. + +Retrofitting process +-------------------- + +Step 1: writing a YANG module +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first step is to write a YANG module that models faithfully the +commands that are going to be converted. As explained in the +[[Architecture]] page, the goal is to introduce the new YANG-based +Northbound API without introducing backward incompatible changes in the +CLI. The northbound retrofitting process should be completely +transparent to FRR users. + +The developer is free to choose whether to write a full YANG module or a +partial YANG module and increment it gradually. For developers who lack +experience with YANG it’s probably a better idea to model one command at +time. + +It’s recommended to reuse definitions from standard YANG models whenever +possible to facilitate the process of writing module translators using +the [[YANG module translator]]. As an example, the frr-ripd YANG module +incorporated several parts of the IETF RIP YANG module. The repositories +below contain big collections of YANG models that might be used as a +reference: \* https://github.com/YangModels/yang \* +https://github.com/openconfig/public + +When writing a YANG module, it’s highly recommended to follow the +guidelines from `RFC 6087 <https://tools.ietf.org/html/rfc6087>`__. In +general, most commands should be modeled fairly easy. Here are a few +guidelines specific to authors of FRR YANG models: \* Use +presence-containers or lists to model commands that change the CLI node +(e.g. ``router rip``, ``interface eth0``). This way, if the +presence-container or list entry is removed, all configuration options +below them are removed automatically (exactly like the CLI behaves when +a configuration object is removed using a *no* command). This +recommendation is orthogonal to the `YANG authoring guidelines for +OpenConfig +models <https://github.com/openconfig/public/blob/master/doc/openconfig_style_guide.md>`__ +where the use of presence containers is discouraged. OpenConfig YANG +models however were not designed to replicate the behavior of legacy CLI +commands. \* When using YANG lists, be careful to identify what should +be the key leaves. In the ``offset-list WORD <in|out> (0-16) IFNAME`` +command, for example, both the direction (``<in|out>``) and the +interface name should be the keys of the list. This can be only known by +analyzing the data structures used to store the commands. \* For +clarity, use non-presence containers to group leaves that are associated +to the same configuration command (as we’ll see later, this also +facilitate the process of writing ``cli_show`` callbacks). \* YANG +leaves of type *enumeration* should define explicitly the value of each +*enum* option based on the value used in the FRR source code. \* Default +values should be taken from the source code whenever they exist. + +Some commands are more difficult to model and demand the use of more +advanced YANG constructs like *choice*, *when* and *must* statements. +**One key requirement is that it should be impossible to load an invalid +JSON/XML configuration to FRR**. The YANG modules should model exactly +what the CLI accepts in the form of commands, and all restrictions +imposed by the CLI should be defined in the YANG models whenever +possible. As we’ll see later, not all constraints can be expressed using +the YANG language and sometimes we’ll need to resort to code-level +validation in the northbound callbacks. + + Tip: the [[YANG tools]] page details several tools and commands that + might be useful when writing a YANG module, like validating YANG + files, indenting YANG files, validating instance data, etc. + +In the example YANG snippet below, we can see the use of the *must* +statement that prevents ripd from redistributing RIP routes into itself. +Although ripd CLI doesn’t allow the operator to enter *redistribute rip* +under *router rip*, we don’t have the same protection when configuring +ripd using other northbound interfaces (e.g. NETCONF). So without this +constraint it would be possible to feed an invalid configuration to ripd +(i.e. a bug). + +.. code:: yang + + list redistribute { + key "protocol"; + description + "Redistributes routes learned from other routing protocols."; + leaf protocol { + type frr-route-types:frr-route-types-v4; + description + "Routing protocol."; + must '. != "rip"'; + } + [snip] + } + +In the example below, we use the YANG *choice* statement to ensure that +either the ``password`` leaf or the ``key-chain`` leaf is configured, +but not both. This is in accordance to the sanity checks performed by +the *ip rip authentication* commands. + +.. code:: yang + + choice authentication-data { + description + "Choose whether to use a simple password or a key-chain."; + leaf authentication-password { + type string { + length "1..16"; + } + description + "Authentication string."; + } + leaf authentication-key-chain { + type string; + description + "Key-chain name."; + } + } + +Once finished, the new YANG model should be put into the FRR *yang/* top +level directory. This will ensure it will be installed automatically by +``make install``. It’s also encouraged (but not required) to put sample +configurations under *yang/examples/* using either JSON or XML files. + +Step 2: generate skeleton northbound callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Use the *gen_northbound_callbacks* tool to generate skeleton callbacks +for the YANG module. Example: + +.. code:: sh + + $ tools/gen_northbound_callbacks frr-ripd > ripd/rip_northbound.c + +The tool will look for the given module in the ``YANG_MODELS_PATH`` +directory defined during the installation. For each schema node of the +YANG module, the tool will generate skeleton callbacks based on the +properties of the node. Example: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance + */ + static int ripd_instance_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + /* TODO: implement me. */ + return NB_OK; + } + + static int ripd_instance_delete(enum nb_event event, + const struct lyd_node *dnode) + { + /* TODO: implement me. */ + return NB_OK; + } + + /* + * XPath: /frr-ripd:ripd/instance/allow-ecmp + */ + static int ripd_instance_allow_ecmp_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + /* TODO: implement me. */ + return NB_OK; + } + + [snip] + + const struct frr_yang_module_info frr_ripd_info = { + .name = "frr-ripd", + .nodes = { + { + .xpath = "/frr-ripd:ripd/instance", + .cbs.create = ripd_instance_create, + .cbs.delete = ripd_instance_delete, + }, + { + .xpath = "/frr-ripd:ripd/instance/allow-ecmp", + .cbs.modify = ripd_instance_allow_ecmp_modify, + }, + [snip] + { + .xpath = "/frr-ripd:ripd/state/routes/route", + .cbs.get_next = ripd_state_routes_route_get_next, + .cbs.get_keys = ripd_state_routes_route_get_keys, + .cbs.lookup_entry = ripd_state_routes_route_lookup_entry, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/prefix", + .cbs.get_elem = ripd_state_routes_route_prefix_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/next-hop", + .cbs.get_elem = ripd_state_routes_route_next_hop_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/interface", + .cbs.get_elem = ripd_state_routes_route_interface_get_elem, + }, + { + .xpath = "/frr-ripd:ripd/state/routes/route/metric", + .cbs.get_elem = ripd_state_routes_route_metric_get_elem, + }, + { + .xpath = "/frr-ripd:clear-rip-route", + .cbs.rpc = clear_rip_route_rpc, + }, + [snip] + +After the C source file is generated, it’s necessary to add a copyright +header on it and indent the code using ``clang-format``. + +Step 3: update the *frr_yang_module_info* array of all relevant daemons +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We must inform the northbound about which daemons will implement the new +YANG module. This is done by updating the ``frr_daemon_info`` structure +of these daemons, with help of the ``FRR_DAEMON_INFO`` macro. + +When a YANG module is specific to a single daemon, like the frr-ripd +module, then only the corresponding daemon should be updated. When the +YANG module is related to a subset of libfrr (e.g. route-maps), then all +FRR daemons that make use of that subset must be updated. + +Example: + +.. code:: c + + static const struct frr_yang_module_info *ripd_yang_modules[] = { + &frr_interface_info, + &frr_ripd_info, + }; + + FRR_DAEMON_INFO(ripd, RIP, .vty_port = RIP_VTY_PORT, + [snip] + .yang_modules = ripd_yang_modules, + .n_yang_modules = array_size(ripd_yang_modules), ) + +Step 4: implement the northbound configuration callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Implementing the northbound configuration callbacks consists mostly of +copying code from the corresponding CLI commands and make the required +adaptations. + +It’s recommended to convert one command or a small group of related +commands per commit. Small commits are preferred to facilitate the +review process. Both “old” and “new” command can coexist without +problems, so the retrofitting process can happen gradually over time. + +The configuration callbacks +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +These are the four main northbound configuration callbacks, as defined +in the ``lib/northbound.h`` file: + +.. code:: c + + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or leaf of type + * empty has been created. + * + * For presence-containers and list entries, the callback is supposed to + * initialize the default values of its children (if any) from the YANG + * models. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being created. + * + * resource + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*create)(enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource); + + /* + * Configuration callback. + * + * The value of a leaf has been modified. + * + * List keys don't need to implement this callback. When a list key is + * modified, the northbound treats this as if the list was deleted and a + * new one created with the updated key value. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being modified + * + * resource + * Pointer to store resource(s) allocated during the NB_EV_PREPARE + * phase. The same pointer can be used during the NB_EV_ABORT and + * NB_EV_APPLY phases to either release or make use of the allocated + * resource(s). It's set to NULL when the event is NB_EV_VALIDATE. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_RESOURCE when the callback failed to allocate a resource. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*modify)(enum nb_event event, const struct lyd_node *dnode, + union nb_resource *resource); + + /* + * Configuration callback. + * + * A presence container, list entry, leaf-list entry or optional leaf + * has been deleted. + * + * The callback is supposed to delete the entire configuration object, + * including its children when they exist. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being deleted. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*delete)(enum nb_event event, const struct lyd_node *dnode); + + /* + * Configuration callback. + * + * A list entry or leaf-list entry has been moved. Only applicable when + * the "ordered-by user" statement is present. + * + * event + * The transaction phase. Refer to the documentation comments of + * nb_event for more details. + * + * dnode + * libyang data node that is being moved. + * + * Returns: + * - NB_OK on success. + * - NB_ERR_VALIDATION when a validation error occurred. + * - NB_ERR_INCONSISTENCY when an inconsistency was detected. + * - NB_ERR for other errors. + */ + int (*move)(enum nb_event event, const struct lyd_node *dnode); + +Since skeleton northbound callbacks are generated automatically by the +*gen_northbound_callbacks* tool, the developer doesn’t need to worry +about which callbacks need to be implemented. + + NOTE: once a daemon starts, it reads its YANG modules and validates + that all required northbound callbacks were implemented. If any + northbound callback is missing, an error is logged and the program + exists. + +Transaction phases +^^^^^^^^^^^^^^^^^^ + +Configuration transactions and their phases were described in detail in +the [[Architecture]] page. Here’s the definition of the ``nb_event`` +enumeration as defined in the *lib/northbound.h* file: + +.. code:: c + + /* Northbound events. */ + enum nb_event { + /* + * The configuration callback is supposed to verify that the changes are + * valid and can be applied. + */ + NB_EV_VALIDATE, + + /* + * The configuration callback is supposed to prepare all resources + * required to apply the changes. + */ + NB_EV_PREPARE, + + /* + * Transaction has failed, the configuration callback needs to release + * all resources previously allocated. + */ + NB_EV_ABORT, + + /* + * The configuration changes need to be applied. The changes can't be + * rejected at this point (errors are logged and ignored). + */ + NB_EV_APPLY, + }; + +When converting a CLI command, we must identify all error-prone +operations and perform them in the ``NB_EV_PREPARE`` phase of the +northbound callbacks. When the operation in question involves the +allocation of a specific resource (e.g. file descriptors), we can store +the allocated resource in the ``resource`` variable given to the +callback. This way the allocated resource can be obtained in the other +phases of the transaction using the same parameter. + +Here’s the ``create`` northbound callback associated to the +``router rip`` command: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance + */ + static int ripd_instance_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + int socket; + + switch (event) { + case NB_EV_VALIDATE: + break; + case NB_EV_PREPARE: + socket = rip_create_socket(); + if (socket < 0) + return NB_ERR_RESOURCE; + resource->fd = socket; + break; + case NB_EV_ABORT: + socket = resource->fd; + close(socket); + break; + case NB_EV_APPLY: + socket = resource->fd; + rip_create(socket); + break; + } + + return NB_OK; + } + +Note that the socket creation is an error-prone operation since it +depends on the underlying operating system, so the socket must be +created during the ``NB_EV_PREPARE`` phase and stored in +``resource->fd``. This socket is then either closed or used depending on +the outcome of the preparation phase of the whole transaction. + +During the ``NB_EV_VALIDATE`` phase, the northbound callbacks must +validate if the intended changes are valid. As an example, FRR doesn’t +allow the operator to deconfigure active interfaces: + +.. code:: c + + static int lib_interface_delete(enum nb_event event, + const struct lyd_node *dnode) + { + struct interface *ifp; + + ifp = yang_dnode_get_entry(dnode); + + switch (event) { + case NB_EV_VALIDATE: + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { + zlog_warn("%s: only inactive interfaces can be deleted", + __func__); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if_delete(ifp); + break; + } + + return NB_OK; + } + +Note however that it’s preferred to use YANG to model the validation +constraints whenever possible. Code-level validations should be used +only to validate constraints that can’t be modeled using the YANG +language. + +Most callbacks don’t need to perform any validations nor perform any +error-prone operations, so in these cases we can use the following +pattern to return early if ``event`` is different than ``NB_EV_APPLY``: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/distance/default + */ + static int ripd_instance_distance_default_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + if (event != NB_EV_APPLY) + return NB_OK; + + rip->distance = yang_dnode_get_uint8(dnode, NULL); + + return NB_OK; + } + +During development it’s recommend to use the *debug northbound* command +to debug configuration transactions and see what callbacks are being +called. Example: + +:: + + ripd# conf t + ripd(config)# debug northbound + ripd(config)# router rip + ripd(config-router)# allow-ecmp + ripd(config-router)# network eth0 + ripd(config-router)# redistribute ospf metric 2 + ripd(config-router)# commit + % Configuration committed successfully. + + ripd(config-router)# + +Now the ripd log: + +:: + + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [validate] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [prepare] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/allow-ecmp] value [true] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/interface[.='eth0']] value [eth0] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [create] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(none)] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [modify] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']/metric] value [2] + 2018/09/23 12:43:59 RIP: northbound callback: event [apply] op [apply_finish] xpath [/frr-ripd:ripd/instance/redistribute[protocol='ospf']] value [(null)] + +Getting the data +^^^^^^^^^^^^^^^^ + +One parameter that is common to all northbound configuration callbacks +is the ``dnode`` parameter. This is a libyang data node structure that +contains information relative to the configuration change that is being +performed. For ``create`` callbacks, it contains the configuration node +that is being added. For ``delete`` callbacks, it contains the +configuration node that is being deleted. For ``modify`` callbacks, it +contains the configuration node that is being modified. + +In order to get the actual data value out of the ``dnode`` variable, we +need to use the ``yang_dnode_get_*()`` wrappers documented in +*lib/yang_wrappers.h*. + +The advantage of passing a ``dnode`` structure to the northbound +callbacks is that the whole candidate being committed is made available, +so the callbacks can obtain values from other portions of the +configuration if necessary. This can be done by providing an xpath +expression to the second parameter of the ``yang_dnode_get_*()`` +wrappers to specify the element we want to get. The example below shows +a callback that gets the values of two leaves that are part of the same +list entry: + +.. code:: c + + static int + ripd_instance_redistribute_metric_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + int type; + uint8_t metric; + + if (event != NB_EV_APPLY) + return NB_OK; + + type = yang_dnode_get_enum(dnode, "../protocol"); + metric = yang_dnode_get_uint8(dnode, NULL); + + rip->route_map[type].metric_config = true; + rip->route_map[type].metric = metric; + rip_redistribute_conf_update(type); + + return NB_OK; + } + +.. + + NOTE: if the wrong ``yang_dnode_get_*()`` wrapper is used, the code + will log an error and abort. An example would be using + ``yang_dnode_get_enum()`` to get the value of a boolean data node. + +No need to check if the configuration value has changed +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A common pattern in CLI commands is this: + +.. code:: c + + DEFUN (...) + { + [snip] + if (new_value == old_value) + return CMD_SUCCESS; + [snip] + } + +Several commands need to check if the new value entered by the user is +the same as the one currently configured. Then, if yes, ignore the +command since nothing was changed. + +The northbound callbacks on the other hand don’t need to perform this +check since they act on effective configuration changes. Using the CLI +as an example, if the operator enters the same command multiple times, +the northbound layer will detect that nothing has changed in the +configuration and will avoid calling the northbound callbacks +unnecessarily. + +In some cases, however, it might be desirable to check for +inconsistencies and notify the northbound when that happens: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/interface + */ + static int ripd_instance_interface_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + const char *ifname; + + if (event != NB_EV_APPLY) + return NB_OK; + + ifname = yang_dnode_get_string(dnode, NULL); + + return rip_enable_if_add(ifname); + } + +.. code:: c + + /* Add interface to rip_enable_if. */ + int rip_enable_if_add(const char *ifname) + { + int ret; + + ret = rip_enable_if_lookup(ifname); + if (ret >= 0) + return NB_ERR_INCONSISTENCY; + + vector_set(rip_enable_interface, + XSTRDUP(MTYPE_RIP_INTERFACE_STRING, ifname)); + + rip_enable_apply_all(); /* TODOVJ */ + + return NB_OK; + } + +In the example above, the ``rip_enable_if_add()`` function should never +return ``NB_ERR_INCONSISTENCY`` in normal conditions. This is because +the northbound layer guarantees that the same interface will never be +added more than once (except when it’s removed and re-added again). But +to be on the safe side it’s probably wise to check for internal +inconsistencies to ensure everything is working as expected. + +Default values +^^^^^^^^^^^^^^ + +Whenever creating a new presence-container or list entry, it’s usually +necessary to initialize certain variables to their default values. FRR +most of the time uses special constants for that purpose +(e.g. ``RIP_DEFAULT_METRIC_DEFAULT``, ``DFLT_BGP_HOLDTIME``, etc). Now +that we have YANG models, we want to fetch the default values from these +models instead. This will allow us to changes default values smoothly +without needing to touch the code. Better yet, it will allow users to +create YANG deviations to define custom default values easily. + +To fetch default values from the loaded YANG models, use the +``yang_get_default_*()`` wrapper functions +(e.g. ``yang_get_default_bool()``) documented in *lib/yang_wrappers.h*. + +Example: + +.. code:: c + + int rip_create(int socket) + { + rip = XCALLOC(MTYPE_RIP, sizeof(struct rip)); + + /* Set initial values. */ + rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE); + rip->default_metric = + yang_get_default_uint8("%s/default-metric", RIP_INSTANCE); + [snip] + } + +Configuration options are edited individually +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Several CLI commands edit multiple configuration options at the same +time. Some examples taken from ripd: \* +``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` - +*/frr-ripd:ripd/instance/timers/flush-interval* - +*/frr-ripd:ripd/instance/timers/holddown-interval* - +*/frr-ripd:ripd/instance/timers/update-interval* \* +``distance (1-255) A.B.C.D/M [WORD]`` - +*/frr-ripd:ripd/instance/distance/source/prefix* - +*/frr-ripd:ripd/instance/distance/source/distance* - +*/frr-ripd:ripd/instance/distance/source/access-list* + +In the new northbound model, there’s one or more separate callbacks for +each configuration option. This usually has implications when converting +code from CLI commands to the northbound commands. An example of this is +the following commit from ripd: +`7cf2f2eaf <https://github.com/opensourcerouting/frr/commit/7cf2f2eaf43ef5df294625d1ab4c708db8293510>`__. +The ``rip_distance_set()`` and ``rip_distance_unset()`` functions were +torn apart and their code split into a few different callbacks. + +For lists and presence-containers, it’s possible to use the +``yang_dnode_set_entry()`` function to attach user data to a libyang +data node, and then retrieve this value in the other callbacks (for the +same node or any of its children) using the ``yang_dnode_get_entry()`` +function. Example: + +.. code:: c + + static int ripd_instance_distance_source_create(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + struct prefix_ipv4 prefix; + struct route_node *rn; + + if (event != NB_EV_APPLY) + return NB_OK; + + yang_dnode_get_ipv4p(&prefix, dnode, "./prefix"); + + /* Get RIP distance node. */ + rn = route_node_get(rip_distance_table, (struct prefix *)&prefix); + rn->info = rip_distance_new(); + yang_dnode_set_entry(dnode, rn); + + return NB_OK; + } + +.. code:: c + + static int + ripd_instance_distance_source_distance_modify(enum nb_event event, + const struct lyd_node *dnode, + union nb_resource *resource) + { + struct route_node *rn; + uint8_t distance; + struct rip_distance *rdistance; + + if (event != NB_EV_APPLY) + return NB_OK; + + /* Set distance value. */ + rn = yang_dnode_get_entry(dnode); + distance = yang_dnode_get_uint8(dnode, NULL); + rdistance = rn->info; + rdistance->distance = distance; + + return NB_OK; + } + +Commands that edit multiple configuration options at the same time can +also use the ``apply_finish`` optional callback, documented as follows +in the *lib/northbound.h* file: + +.. code:: c + + /* + * Optional configuration callback for YANG lists and containers. + * + * The 'apply_finish' callbacks are called after all other callbacks + * during the apply phase (NB_EV_APPLY). These callbacks are called only + * under one of the following two cases: + * * The container or a list entry has been created; + * * Any change is made within the descendants of the list entry or + * container (e.g. a child leaf was modified, created or deleted). + * + * This callback is useful in the cases where a single event should be + * triggered regardless if the container or list entry was changed once + * or multiple times. + * + * dnode + * libyang data node from the YANG list or container. + */ + void (*apply_finish)(const struct lyd_node *dnode); + +Here’s an example of how this callback can be used: + +.. code:: c + + /* + * XPath: /frr-ripd:ripd/instance/timers/ + */ + static void ripd_instance_timers_apply_finish(const struct lyd_node *dnode) + { + /* Reset update timer thread. */ + rip_event(RIP_UPDATE_EVENT, 0); + } + +.. code:: c + + { + .xpath = "/frr-ripd:ripd/instance/timers", + .cbs.apply_finish = ripd_instance_timers_apply_finish, + .cbs.cli_show = cli_show_rip_timers, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", + .cbs.modify = ripd_instance_timers_flush_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", + .cbs.modify = ripd_instance_timers_holddown_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/update-interval", + .cbs.modify = ripd_instance_timers_update_interval_modify, + }, + +In this example, we want to call the ``rip_event()`` function only once +regardless if all RIP timers were modified or only one of them. Without +the ``apply_finish`` callback we’d need to call ``rip_event()`` in the +``modify`` callback of each timer (a YANG leaf), resulting in redundant +call to the ``rip_event()`` function if multiple timers are changed at +once. + +Bonus: libyang user types +^^^^^^^^^^^^^^^^^^^^^^^^^ + +When writing YANG modules, it’s advisable to create derived types for +data types that are used on multiple places (e.g. MAC addresses, IS-IS +networks, etc). Here’s how `RFC +7950 <https://tools.ietf.org/html/rfc7950#page-25>`__ defines derived +types: > YANG can define derived types from base types using the +“typedef” > statement. A base type can be either a built-in type or a +derived > type, allowing a hierarchy of derived types. > > A derived +type can be used as the argument for the “type” statement. > > YANG +Example: > > typedef percent { > type uint8 { > range “0 .. 100”; > } > +} > > leaf completed { > type percent; > } + +Derived types are essentially built-in types with imposed restrictions. +As an example, the ``ipv4-address`` derived type from IETF is defined +using the ``string`` built-in type with a ``pattern`` constraint (a +regular expression): + +:: + + typedef ipv4-address { + type string { + pattern + '(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}' + + '([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' + + '(%[\p{N}\p{L}]+)?'; + } + description + "The ipv4-address type represents an IPv4 address in + dotted-quad notation. The IPv4 address may include a zone + index, separated by a % sign. + + The zone index is used to disambiguate identical address + values. For link-local addresses, the zone index will + typically be the interface index number or the name of an + interface. If the zone index is not present, the default + zone of the device will be used. + + The canonical format for the zone index is the numerical + format"; + } + +Sometimes, however, it’s desirable to have a binary representation of +the derived type that is different from the associated built-in type. +Taking the ``ipv4-address`` example above, it would be more convenient +to manipulate this YANG type using ``in_addr`` structures instead of +strings. libyang allow us to do that using the user types plugin: +https://netopeer.liberouter.org/doc/libyang/master/howtoschemaplugins.html#usertypes + +Here’s how the the ``ipv4-address`` derived type is implemented in FRR +(*yang/libyang_plugins/frr_user_types.c*): + +.. code:: c + + static int ipv4_address_store_clb(const char *type_name, const char *value_str, + lyd_val *value, char **err_msg) + { + value->ptr = malloc(sizeof(struct in_addr)); + if (!value->ptr) + return 1; + + if (inet_pton(AF_INET, value_str, value->ptr) != 1) { + free(value->ptr); + return 1; + } + + return 0; + } + +.. code:: c + + struct lytype_plugin_list frr_user_types[] = { + {"ietf-inet-types", "2013-07-15", "ipv4-address", + ipv4_address_store_clb, free}, + {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone", + ipv4_address_store_clb, free}, + [snip] + {NULL, NULL, NULL, NULL, NULL} /* terminating item */ + }; + +Now, in addition to the string representation of the data value, libyang +will also store the data in the binary format we specified (an +``in_addr`` structure). + +Whenever a new derived type is implemented in FRR, it’s also recommended +to write new wrappers in the *lib/yang_wrappers.c* file +(e.g. ``yang_dnode_get_ipv4()``, ``yang_get_default_ipv4()``, etc). + +Step 5: rewrite the CLI commands as dumb wrappers around the northbound callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once the northbound callbacks are implemented, we need to rewrite the +associated CLI commands on top of the northbound layer. This is the +easiest part of the retrofitting process. + +For protocol daemons, it’s recommended to put all CLI commands on a +separate C file (e.g. *ripd/rip_cli.c*). This helps to keep the code +more clean by separating the main protocol code from the user interface. +It should also help when moving the CLI to a separate program in the +future. + +For libfrr commands, it’s not possible to centralize all commands in a +single file because the *extract.pl* script from *vtysh* treats commands +differently depending on the file in which they are defined (e.g. DEFUNs +from *lib/routemap.c* are installed using the ``VTYSH_RMAP`` constant, +which identifies the daemons that support route-maps). In this case, the +CLI commands should be rewritten but maintained in the same file. + +Since all CLI configuration commands from FRR will need to be rewritten, +this is an excellent opportunity to rework this part of the code to make +the commands easier to maintain and extend. These are the three main +recommendations: 1. Always use DEFPY instead of DEFUN to improve code +readability. 2. Always try to join multiple DEFUNs into a single DEFPY +whenever possible. As an example, there’s no need to have both +``distance (1-255) A.B.C.D/M`` and ``distance (1-255) A.B.C.D/M WORD`` +when a single ``distance (1-255) A.B.C.D/M [WORD]`` would suffice. 3. +When necessary, create a separate DEFPY for ``no`` commands so that part +of the configuration command can be made optional for convenience. +Example: +``no timers basic [(5-2147483647) (5-2147483647) (5-2147483647)]``. In +this example, everything after ``no timers basic`` is ignored by FRR, so +it makes sense to accept ``no timers basic`` as a valid command. But it +also makes sense to accept all parameters +(``no timers basic (5-2147483647) (5-2147483647) (5-2147483647)``) to +make it easier to remove the command just by prefixing a “no” to it. + +To rewrite a CLI command as a dumb wrapper around the northbound +callbacks, use the ``nb_cli_cfg_change()`` function. This function +accepts as a parameter an array of ``cli_config_change`` structures that +specify the changes that need to performed on the candidate +configuration. Here’s the declaration of this structure (taken from the +*lib/northbound_cli.h* file): + +.. code:: c + + struct cli_config_change { + /* + * XPath (absolute or relative) of the configuration option being + * edited. + */ + char xpath[XPATH_MAXLEN]; + + /* + * Operation to apply (either NB_OP_CREATE, NB_OP_MODIFY or + * NB_OP_DELETE). + */ + enum nb_operation operation; + + /* + * New value of the configuration option. Should be NULL for typeless + * YANG data (e.g. presence-containers). For convenience, NULL can also + * be used to restore a leaf to its default value. + */ + const char *value; + }; + +The ``nb_cli_cfg_change()`` function positions the CLI command on top on +top of the northbound layer. Instead of changing the running +configuration directly, this function changes the candidate +configuration instead, as described in the [[Transactional CLI]] page. +When the transactional CLI is not in use (i.e. the default mode), then +``nb_cli_cfg_change()`` performs an implicit ``commit`` operation after +changing the candidate configuration. + + NOTE: the ``nb_cli_cfg_change()`` function clones the candidate + configuration before actually editing it. This way, if any error + happens during the editing, the original candidate is restored to + avoid inconsistencies. Either all changes from the configuration + command are performed successfully or none are. It’s like a + mini-transaction but happening on the candidate configuration (thus + the northbound callbacks are not involved). + +Other important details to keep in mind while rewriting the CLI +commands: \* ``nb_cli_cfg_change()`` returns CLI errors codes +(e.g. ``CMD_SUCCESS``, ``CMD_WARNING``), so the return value of this +function can be used as the return value of CLI commands. \* Calls to +``VTY_PUSH_CONTEXT`` and ``VTY_PUSH_CONTEXT_SUB`` should be converted to +calls to ``VTY_PUSH_XPATH``. Similarly, the following macros aren’t +necessary anymore and can be removed: ``VTY_DECLVAR_CONTEXT``, +``VTY_DECLVAR_CONTEXT_SUB``, ``VTY_GET_CONTEXT`` and +``VTY_CHECK_CONTEXT``. The ``nb_cli_cfg_change()`` functions uses the +``VTY_CHECK_XPATH`` macro to check if the data node being edited still +exists before doing anything else. + +The examples below provide additional details about how the conversion +should be done. + +Example 1 +^^^^^^^^^ + +In this first example, the *router rip* command becomes a dumb wrapper +around the ``ripd_instance_create()`` callback. Note that we don’t need +to check if the ``/frr-ripd:ripd/instance`` data path already exists +before trying to create it. The northbound will detect when this +presence-container already exists and do nothing. The +``VTY_PUSH_XPATH()`` macro is used to change the vty node and set the +context for other commands under *router rip*. + +.. code:: c + + DEFPY_NOSH (router_rip, + router_rip_cmd, + "router rip", + "Enable a routing process\n" + "Routing Information Protocol (RIP)\n") + { + int ret; + + struct cli_config_change changes[] = { + { + .xpath = "/frr-ripd:ripd/instance", + .operation = NB_OP_CREATE, + .value = NULL, + }, + }; + + ret = nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + if (ret == CMD_SUCCESS) + VTY_PUSH_XPATH(RIP_NODE, changes[0].xpath); + + return ret; + } + +Example 2 +^^^^^^^^^ + +Here we can see the use of relative xpaths (starting with ``./``), which +are more convenient that absolute xpaths (which would be +``/frr-ripd:ripd/instance/default-metric`` in this example). This is +possible because the use of ``VTY_PUSH_XPATH()`` in the *router rip* +command set the vty base xpath to ``/frr-ripd:ripd/instance``. + +.. code:: c + + DEFPY (rip_default_metric, + rip_default_metric_cmd, + "default-metric (1-16)", + "Set a metric of redistribute routes\n" + "Default metric\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./default-metric", + .operation = NB_OP_MODIFY, + .value = default_metric_str, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +In the command below we the ``value`` to NULL to indicate that we want +to set this leaf to its default value. This is better than hardcoding +the default value because the default might change in the future. Also, +users might define custom defaults by using YANG deviations, so it’s +better to write code that works correctly regardless of the default +values defined in the YANG models. + +.. code:: c + + DEFPY (no_rip_default_metric, + no_rip_default_metric_cmd, + "no default-metric [(1-16)]", + NO_STR + "Set a metric of redistribute routes\n" + "Default metric\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./default-metric", + .operation = NB_OP_MODIFY, + .value = NULL, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +Example 3 +^^^^^^^^^ + +This example shows how one command can change multiple leaves at the +same time. + +.. code:: c + + DEFPY (rip_timers, + rip_timers_cmd, + "timers basic (5-2147483647)$update (5-2147483647)$timeout (5-2147483647)$garbage", + "Adjust routing timers\n" + "Basic routing protocol update timers\n" + "Routing table update timer value in second. Default is 30.\n" + "Routing information timeout timer. Default is 180.\n" + "Garbage collection timer. Default is 120.\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./timers/update-interval", + .operation = NB_OP_MODIFY, + .value = update_str, + }, + { + .xpath = "./timers/holddown-interval", + .operation = NB_OP_MODIFY, + .value = timeout_str, + }, + { + .xpath = "./timers/flush-interval", + .operation = NB_OP_MODIFY, + .value = garbage_str, + }, + }; + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +Example 4 +^^^^^^^^^ + +This example shows how to create a list entry: + +.. code:: c + + DEFPY (rip_distance_source, + rip_distance_source_cmd, + "distance (1-255) A.B.C.D/M$prefix [WORD$acl]", + "Administrative distance\n" + "Distance value\n" + "IP source prefix\n" + "Access list name\n") + { + char xpath_list[XPATH_MAXLEN]; + struct cli_config_change changes[] = { + { + .xpath = ".", + .operation = NB_OP_CREATE, + }, + { + .xpath = "./distance", + .operation = NB_OP_MODIFY, + .value = distance_str, + }, + { + .xpath = "./access-list", + .operation = acl ? NB_OP_MODIFY : NB_OP_DELETE, + .value = acl, + }, + }; + + snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']", + prefix_str); + + return nb_cli_cfg_change(vty, xpath_list, changes, array_size(changes)); + } + +The ``xpath_list`` variable is used to hold the xpath that identifies +the list entry. The keys of the list entry should be embedded in this +xpath and don’t need to be part of the array of configuration changes. +All entries from the ``changes`` array use relative xpaths which are +based on the xpath of the list entry. + +The ``access-list`` optional leaf can be either modified or deleted +depending whether the optional *WORD* parameter is present or not. + +When deleting a list entry, all non-key leaves can be ignored: + +.. code:: c + + DEFPY (no_rip_distance_source, + no_rip_distance_source_cmd, + "no distance (1-255) A.B.C.D/M$prefix [WORD$acl]", + NO_STR + "Administrative distance\n" + "Distance value\n" + "IP source prefix\n" + "Access list name\n") + { + char xpath_list[XPATH_MAXLEN]; + struct cli_config_change changes[] = { + { + .xpath = ".", + .operation = NB_OP_DELETE, + }, + }; + + snprintf(xpath_list, sizeof(xpath_list), "./distance/source[prefix='%s']", + prefix_str); + + return nb_cli_cfg_change(vty, xpath_list, changes, 1); + } + +Example 5 +^^^^^^^^^ + +This example shows a DEFPY statement that performs two validations +before calling ``nb_cli_cfg_change()``: + +.. code:: c + + DEFPY (ip_rip_authentication_string, + ip_rip_authentication_string_cmd, + "ip rip authentication string LINE$password", + IP_STR + "Routing Information Protocol\n" + "Authentication control\n" + "Authentication string\n" + "Authentication string\n") + { + struct cli_config_change changes[] = { + { + .xpath = "./frr-ripd:rip/authentication/password", + .operation = NB_OP_MODIFY, + .value = password, + }, + }; + + if (strlen(password) > 16) { + vty_out(vty, + "%% RIPv2 authentication string must be shorter than 16\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + if (yang_dnode_exists(vty->candidate_config->dnode, "%s%s", + VTY_GET_XPATH, + "/frr-ripd:rip/authentication/key-chain")) { + vty_out(vty, "%% key-chain configuration exists\n"); + return CMD_WARNING_CONFIG_FAILED; + } + + return nb_cli_cfg_change(vty, NULL, changes, array_size(changes)); + } + +These two validations are not strictly necessary since the configuration +change is validated using libyang afterwards. The issue with the libyang +validation is that the error messages from libyang are too verbose: + +:: + + ripd# conf t + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + % Failed to edit candidate configuration. + + Value "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" does not satisfy the constraint "1..16" (range, length, or pattern). + Failed to create node "authentication-password" as a child of "rip". + YANG path: /frr-interface:lib/interface[name='eth0'][vrf='Default-IP-Routing-Table']/frr-ripd:rip/authentication-password + +On the other hand, the original error message from ripd is much cleaner: + +:: + + ripd# conf t + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication string XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + % RIPv2 authentication string must be shorter than 16 + +The second validation is a bit more complex. If we try to create the +``authentication/password`` leaf when the ``authentication/key-chain`` +leaf already exists (both are under a YANG *choice* statement), libyang +will automatically delete the ``authentication/key-chain`` and create +``authentication/password`` on its place. This is different from the +original ripd behavior where the *ip rip authentication key-chain* +command must be removed before configuring the *ip rip authentication +string* command. + +In the spirit of not introducing any backward-incompatible changes in +the CLI, converted commands should retain some of their validation +checks to preserve their original behavior. + +Step 6: implement the ``cli_show`` callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The traditional method used by FRR to display the running configuration +consists of looping through all CLI nodes all call their ``func`` +callbacks one by one, which in turn read the configuration from internal +variables and dump them to the terminal in the form of CLI commands. + +The problem with this approach is twofold. First, since the callbacks +read the configuration from internal variables, they can’t display +anything other than the running configuration. Second, they don’t have +the ability to display default values when requested by the user +(e.g. *show configuration candidate with-defaults*). + +The new northbound architecture solves these problems by introducing a +new callback: ``cli_show``. Here’s the signature of this function (taken +from the *lib/northbound.h* file): + +.. code:: c + + /* + * Optional callback to show the CLI command associated to the given + * YANG data node. + * + * vty + * the vty terminal to dump the configuration to + * + * dnode + * libyang data node that should be shown in the form of a CLI + * command + * + * show_defaults + * specify whether to display default configuration values or not. + * This parameter can be ignored most of the time since the + * northbound doesn't call this callback for default leaves or + * non-presence containers that contain only default child nodes. + * The exception are commands associated to multiple configuration + * options, in which case it might be desirable to hide one or more + * parts of the command when this parameter is set to false. + */ + void (*cli_show)(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); + +One of the main differences to the old CLI ``func`` callbacks is that +the ``cli_show`` callbacks are associated to YANG data paths and not to +CLI nodes. This means we can define one separate callback for each CLI +command, making the code more modular and easier to maintain (among +other advantages that will be more clear later). For enhanced code +readability, it’s recommended to position the ``cli_show`` callbacks +immediately after their associated command definitions (DEFPYs). + +The ``cli_show`` callbacks are used by the ``nb_cli_show_config_cmds()`` +function to display configurations stored inside ``nb_config`` +structures. The configuration being displayed can be anything from the +running configuration (*show configuration running*), a candidate +configuration (*show configuration candidate*) or a rollback +configuration (*show configuration transaction (1-4294967296)*). The +``nb_cli_show_config_cmds()`` function works by iterating over all data +nodes from the given configuration and calling the ``cli_show`` callback +for the nodes where it’s defined. If a list has dozens of entries, the +``cli_show`` callback associated to this list will be called multiple +times with the ``dnode`` parameter pointing to different list entries on +each iteration. + +For backward compatibility with the *show running-config* command, we +can’t get rid of the CLI ``func`` callbacks at this point in time. +However, we can make the CLI ``func`` callbacks call the corresponding +``cli_show`` callbacks to avoid code duplication. The +``nb_cli_show_dnode_cmds()`` function can be used for that purpose. Once +the CLI retrofitting process finishes for all FRR daemons, we can remove +the legacy CLI ``func`` callbacks and turn *show running-config* into a +shorthand for *show configuration running*. + +Regarding displaying configuration with default values, this is +something that is taken care of by the ``nb_cli_show_config_cmds()`` +function itself. When the *show configuration* command is used without +the *with-defaults* option, ``nb_cli_show_config_cmds()`` will skip +calling ``cli_show`` callbacks for data nodes that contain only default +values (e.g. default leaves or non-presence containers that contain only +default child nodes). There are however some exceptional cases where the +implementer of the ``cli_show`` callback should take into consideration +if default values should be displayed or not. This and other concepts +will be explained in more detail in the examples below. + +.. _example-1-1: + +Example 1 +^^^^^^^^^ + +Command: ``default-metric (1-16)`` + +YANG representation: + +.. code:: yang + + leaf default-metric { + type uint8 { + range "1..16"; + } + default "1"; + description + "Default metric of redistributed routes."; + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance/default-metric", + .cbs.modify = ripd_instance_default_metric_modify, + + .cbs.cli_show = cli_show_rip_default_metric, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_default_metric(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " default-metric %s\n", + yang_dnode_get_string(dnode, NULL)); + } + +In this first example, the *default-metric* command was modeled using a +YANG leaf, and we added a new ``cli_show`` callback attached to the YANG +path of this leaf. + +The callback makes use of the ``yang_dnode_get_string()`` function to +obtain the string value of the configuration option. The following would +also be possible: + +.. code:: c + + vty_out(vty, " default-metric %u\n", + yang_dnode_get_uint8(dnode, NULL)); + +Both options are possible because libyang stores both a binary +representation and a textual representation of all values stored in a +data node (``lyd_node``). For simplicity, it’s recommended to always use +``yang_dnode_get_string()`` in the ``cli_show`` callbacks. + +.. _example-2-1: + +Example 2 +^^^^^^^^^ + +Command: ``router rip`` + +YANG representation: + +.. code:: yang + + container instance { + presence "Present if the RIP protocol is enabled."; + description + "RIP routing instance."; + [snip] + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance", + .cbs.create = ripd_instance_create, + .cbs.delete = ripd_instance_delete, + + .cbs.cli_show = cli_show_router_rip, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_router_rip(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, "!\n"); + vty_out(vty, "router rip\n"); + } + +In this example, the ``cli_show`` callback doesn’t need to obtain any +value from the ``dnode`` parameter since presence-containers don’t hold +any data (apart from their child nodes, but they have their own +``cli_show`` callbacks). + +.. _example-3-1: + +Example 3 +^^^^^^^^^ + +Command: ``timers basic (5-2147483647) (5-2147483647) (5-2147483647)`` + +YANG representation: + +.. code:: yang + + container timers { + description + "Settings of basic timers"; + leaf flush-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "120"; + description + "Interval before a route is flushed from the routing + table."; + } + leaf holddown-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "180"; + description + "Interval before better routes are released."; + } + leaf update-interval { + type uint32 { + range "5..2147483647"; + } + units "seconds"; + default "30"; + description + "Interval at which RIP updates are sent."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + + .xpath = "/frr-ripd:ripd/instance/timers", + + .cbs.cli_show = cli_show_rip_timers, + + }, + + { + .xpath = "/frr-ripd:ripd/instance/timers/flush-interval", + .cbs.modify = ripd_instance_timers_flush_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/holddown-interval", + .cbs.modify = ripd_instance_timers_holddown_interval_modify, + }, + { + .xpath = "/frr-ripd:ripd/instance/timers/update-interval", + .cbs.modify = ripd_instance_timers_update_interval_modify, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_timers(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " timers basic %s %s %s\n", + yang_dnode_get_string(dnode, "./update-interval"), + yang_dnode_get_string(dnode, "./holddown-interval"), + yang_dnode_get_string(dnode, "./flush-interval")); + } + +This command is a bit different since it changes three leaves at the +same time. This means we need to have a single ``cli_show`` callback in +order to display the three leaves together in the same line. + +The new ``cli_show_rip_timers()`` callback was added attached to the +*timers* non-presence container that groups the three leaves. Without +the *timers* non-presence container we’d need to display the *timers +basic* command inside the ``cli_show_router_rip()`` callback, which +would break our requirement of having a separate ``cli_show`` callback +for each configuration command. + +.. _example-4-1: + +Example 4 +^^^^^^^^^ + +Command: +``redistribute <kernel|connected|static|ospf|isis|bgp|eigrp|nhrp|table|vnc|babel|sharp> [{metric (0-16)|route-map WORD}]`` + +YANG representation: + +.. code:: yang + + list redistribute { + key "protocol"; + description + "Redistributes routes learned from other routing protocols."; + leaf protocol { + type frr-route-types:frr-route-types-v4; + description + "Routing protocol."; + must '. != "rip"'; + } + leaf route-map { + type string { + length "1..max"; + } + description + "Applies the conditions of the specified route-map to + routes that are redistributed into the RIP routing + instance."; + } + leaf metric { + type uint8 { + range "0..16"; + } + description + "Metric used for the redistributed route. If a metric is + not specified, the metric configured with the + default-metric attribute in RIP router configuration is + used. If the default-metric attribute has not been + configured, the default metric for redistributed routes + is 0."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + { + .xpath = "/frr-ripd:ripd/instance/redistribute", + .cbs.create = ripd_instance_redistribute_create, + .cbs.delete = ripd_instance_redistribute_delete, + + .cbs.cli_show = cli_show_rip_redistribute, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/route-map", + .cbs.modify = ripd_instance_redistribute_route_map_modify, + .cbs.delete = ripd_instance_redistribute_route_map_delete, + }, + { + .xpath = "/frr-ripd:ripd/instance/redistribute/metric", + .cbs.modify = ripd_instance_redistribute_metric_modify, + .cbs.delete = ripd_instance_redistribute_metric_delete, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_rip_redistribute(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) + { + vty_out(vty, " redistribute %s", + yang_dnode_get_string(dnode, "./protocol")); + if (yang_dnode_exists(dnode, "./metric")) + vty_out(vty, " metric %s", + yang_dnode_get_string(dnode, "./metric")); + if (yang_dnode_exists(dnode, "./route-map")) + vty_out(vty, " route-map %s", + yang_dnode_get_string(dnode, "./route-map")); + vty_out(vty, "\n"); + } + +Similar to the previous example, the *redistribute* command changes +several leaves at the same time, and we need a single callback to +display all leaves in a single line in accordance to the CLI command. In +this case, the leaves are already grouped by a YANG list so there’s no +need to add a non-presence container. The new ``cli_show`` callback was +attached to the YANG path of the list. + +It’s also worth noting the use of the ``yang_dnode_exists()`` function +to check if optional leaves exist in the configuration before displaying +them. + +.. _example-5-1: + +Example 5 +^^^^^^^^^ + +Command: +``ip rip authentication mode <md5 [auth-length <rfc|old-ripd>]|text>`` + +YANG representation: + +.. code:: yang + + container authentication-scheme { + description + "Specify the authentication scheme for the RIP interface"; + leaf mode { + type enumeration { + [snip] + } + default "none"; + description + "Specify the authentication mode."; + } + leaf md5-auth-length { + when "../mode = 'md5'"; + type enumeration { + [snip] + } + default "20"; + description + "MD5 authentication data length."; + } + } + +Placement of the ``cli_show`` callback: + +.. code:: diff + + + { + + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme", + + .cbs.cli_show = cli_show_ip_rip_authentication_scheme, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/mode", + .cbs.modify = lib_interface_rip_authentication_scheme_mode_modify, + }, + { + .xpath = "/frr-interface:lib/interface/frr-ripd:rip/authentication-scheme/md5-auth-length", + .cbs.modify = lib_interface_rip_authentication_scheme_md5_auth_length_modify, + .cbs.delete = lib_interface_rip_authentication_scheme_md5_auth_length_delete, + }, + +Implementation of the ``cli_show`` callback: + +.. code:: c + + void cli_show_ip_rip_authentication_scheme(struct vty *vty, + struct lyd_node *dnode, + bool show_defaults) + { + switch (yang_dnode_get_enum(dnode, "./mode")) { + case RIP_NO_AUTH: + vty_out(vty, " no ip rip authentication mode\n"); + break; + case RIP_AUTH_SIMPLE_PASSWORD: + vty_out(vty, " ip rip authentication mode text\n"); + break; + case RIP_AUTH_MD5: + vty_out(vty, " ip rip authentication mode md5"); + if (show_defaults + || !yang_dnode_is_default(dnode, "./md5-auth-length")) { + if (yang_dnode_get_enum(dnode, "./md5-auth-length") + == RIP_AUTH_MD5_SIZE) + vty_out(vty, " auth-length rfc"); + else + vty_out(vty, " auth-length old-ripd"); + } + vty_out(vty, "\n"); + break; + } + } + +This is the most complex ``cli_show`` callback we have in ripd. Its +complexity comes from the following: \* The +``ip rip authentication mode ...`` command changes two YANG leaves at +the same time. \* Part of the command should be hidden when the +``show_defaults`` parameter is set to false. + +This is the behavior we want to implement: + +:: + + ripd(config)# interface eth0 + ripd(config-if)# ip rip authentication mode md5 + ripd(config-if)# + ripd(config-if)# show configuration candidate + Configuration: + ! + [snip] + ! + interface eth0 + ip rip authentication mode md5 + ! + end + ripd(config-if)# + ripd(config-if)# show configuration candidate with-defaults + Configuration: + ! + [snip] + ! + interface eth0 + [snip] + ip rip authentication mode md5 auth-length old-ripd + ! + end + +Note that ``auth-length old-ripd`` should be hidden unless the +configuration is shown using the *with-defaults* option. This is why the +``cli_show_ip_rip_authentication_scheme()`` callback needs to consult +the value of the *show_defaults* parameter. It’s expected that only a +very small minority of all ``cli_show`` callbacks will need to consult +the *show_defaults* parameter (there’s a chance this might be the only +case!) + +In the case of the *timers basic* command seen before, we need to +display the value of all leaves even if only one of them has a value +different from the default. Hence the ``cli_show_rip_timers()`` callback +was able to completely ignore the *show_defaults* parameter. + +Step 7: consolidation +~~~~~~~~~~~~~~~~~~~~~ + +As mentioned in the fourth step, the northbound retrofitting process can +happen gradually over time, since both “old” and “new” commands can +coexist without problems. Once all commands from a given daemon were +converted, we can proceed to the consolidation step, which consists of +the following: \* Remove the vty configuration lock, which is enabled by +default in all daemons. Now multiple users should be able to edit the +configuration concurrently, using either shared or private candidate +configurations. \* Reference commit: +`57dccdb1 <https://github.com/opensourcerouting/frr/commit/57dccdb18b799556214dcfb8943e248c0bf1f6a6>`__. +\* Stop using the qobj infrastructure to keep track of configuration +objects. This is not necessary anymore, the northbound uses a similar +mechanism to keep track of YANG data nodes in the candidate +configuration. \* Reference commit: +`4e6d63ce <https://github.com/opensourcerouting/frr/commit/4e6d63cebd988af650c1c29d0f2e5a251c8d2e7a>`__. +\* Make the daemon SIGHUP handler re-read the configuration file (and +ensure it’s not doing anything other than that). \* Reference commit: +`5e57edb4 <https://github.com/opensourcerouting/frr/commit/5e57edb4b71ff03f9a22d9ec1412c3c5167f90cf>`__. + +Final Considerations +-------------------- + +Testing +^^^^^^^ + +Converting CLI commands to the new northbound model can be a complicated +task for beginners, but the more commands one converts, the easier it +gets. It’s highly recommended to perform as much testing as possible on +the converted commands to reduce the likelihood of introducing +regressions. Tools like topotests, ANVL and the `CLI +fuzzer <https://github.com/rwestphal/frr-cli-fuzzer>`__ can be used to +catch hidden bugs that might be present. As usual, it’s also recommended +to use valgrind and static code analyzers to catch other types of +problems like memory leaks. + +Amount of work +^^^^^^^^^^^^^^ + +The output below gives a rough estimate of the total number of +configuration commands that need to be converted per daemon: + +.. code:: sh + + $ for dir in lib zebra bgpd ospfd ospf6d isisd ripd ripngd eigrpd pimd pbrd ldpd nhrpd babeld ; do echo -n "$dir: " && cd $dir && grep -ERn "DEFUN|DEFPY" * | grep -Ev "clippy|show|clear" | wc -l && cd ..; done + lib: 302 + zebra: 181 + bgpd: 569 + ospfd: 198 + ospf6d: 99 + isisd: 126 + ripd: 64 + ripngd: 44 + eigrpd: 58 + pimd: 113 + pbrd: 9 + ldpd: 46 + nhrpd: 24 + babeld: 28 + +As it can be seen, the northbound retrofitting process will demand a lot +of work from FRR developers and should take months to complete. Everyone +is welcome to collaborate! diff --git a/doc/developer/northbound/transactional-cli.rst b/doc/developer/northbound/transactional-cli.rst new file mode 100644 index 0000000000..439bb6afce --- /dev/null +++ b/doc/developer/northbound/transactional-cli.rst @@ -0,0 +1,244 @@ +Table of Contents +----------------- + +- `Introduction <#introduction>`__ +- `Configuration modes <#config-modes>`__ +- `New commands <#retrofitting-process>`__ + + - `commit check <#cmd1>`__ + - `commit <#cmd2>`__ + - `discard <#cmd3>`__ + - `configuration database max-transactions <#cmd4>`__ + - `configuration load <#cmd5>`__ + - `rollback configuration <#cmd6>`__ + - `show configuration candidate <#cmd7>`__ + - `show configuration compare <#cmd8>`__ + - `show configuration running <#cmd9>`__ + - `show configuration transaction <#cmd10>`__ + - `show yang module <#cmd11>`__ + - `show yang module-translator <#cmd12>`__ + - `update <#cmd13>`__ + - `yang module-translator load <#cmd14>`__ + - `yang module-translator unload <#cmd15>`__ + +Introduction +~~~~~~~~~~~~ + +All FRR daemons have built-in support for the CLI, which can be accessed +either through local telnet or via the vty socket (e.g. by using +*vtysh*). This will not change with the introduction of the Northbound +API. However, a new command-line option will be available for all FRR +daemons: ``--tcli``. When given, this option makes the daemon start with +a transactional CLI and configuration commands behave a bit different. +Instead of editing the running configuration, they will edit the +candidate configuration. In other words, the configuration commands +won’t be applied immediately, that has to be done on a separate step +using the new ``commit`` command. + +The transactional CLI simply leverages the new capabilities provided by +the Northbound API and exposes the concept of candidate configurations +to CLI users too. When the transactional mode is not used, the +configuration commands also edit the candidate configuration, but +there’s an implicit ``commit`` after each command. + +In order for the transactional CLI to work, all configuration commands +need to be converted to the new northbound model. Commands not converted +to the new northbound model will change the running configuration +directly since they bypass the FRR northbound layer. For this reason, +starting a daemon with the transactional CLI is not advisable unless all +of its commands have already been converted. When that’s not the case, +we can run into a situation like this: + +:: + + ospfd(config)# router ospf + ospfd(config-router)# ospf router-id 1.1.1.1 + [segfault in ospfd] + +The segfault above can happen if ``router ospf`` edits the candidate +configuration but ``ospf router-id 1.1.1.1`` edits the running +configuration. The second command tries to set +``ospf->router_id_static`` but, since the previous ``router ospf`` +command hasn’t been commited yet, the ``ospf`` global variable is set to +NULL, which leads to the crash. Besides this problem, having a set of +commands that edit the candidate configuration and others that edit the +running configuration is confusing at best. The ``--tcli`` option should +be used only by developers until the northbound retrofitting process is +complete. + +Configuration modes +~~~~~~~~~~~~~~~~~~~ + +When using the transactional CLI (``--tcli``), FRR supports three +different forms of the ``configure`` command: \* ``configure terminal``: +in this mode, a single candidate configuration is shared by all users. +This means that one user might delete a configuration object that’s +being edited by another user, in which case the CLI will detect and +report the problem. If one user issues the ``commit`` command, all +changes done by all users are committed. \* ``configure private``: users +have a private candidate configuration that is edited separately from +the other users. The ``commit`` command commits only the changes done by +the user. \* ``configure exclusive``: similar to ``configure private``, +but also locks the running configuration to prevent other users from +changing it. The configuration lock is released when the user exits the +configuration mode. + +When using ``configure terminal`` or ``configure private``, the +candidate configuration being edited might become outdated if another +user commits a different candidate configuration on another session. +TODO: show image to illustrate the problem. + +New commands +~~~~~~~~~~~~ + +The list below contains the new CLI commands introduced by Northbound +API. The commands are available when a daemon is started using the +transactional CLI (``--tcli``). Currently ``vtysh`` doesn’t support any +of these new commands. + +Please refer to the [[Demos]] page to see a demo of the transactional +CLI in action. + +-------------- + +``commit check`` +'''''''''''''''' + +Check if the candidate configuration is valid or not. + +``commit [force] [comment LINE...]`` +'''''''''''''''''''''''''''''''''''' + +Commit the changes done in the candidate configuration into the running +configuration. + +Options: \* ``force``: commit even if the candidate configuration is +outdated. It’s usually a better option to use the ``update`` command +instead. \* ``comment LINE...``: assign a comment to the configuration +transaction. This comment is displayed when viewing the recorded +transactions in the output of the ``show configuration transaction`` +command. + +``discard`` +''''''''''' + +Discard the changes done in the candidate configuration. + +``configuration database max-transactions (1-100)`` +''''''''''''''''''''''''''''''''''''''''''''''''''' + +Set the maximum number of transactions to store in the rollback log. + +``configuration load <file [<json|xml> [translate WORD]] FILENAME|transaction (1-4294967296)> [replace]`` +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Load a new configuration into the candidate configuration. When loading +the configuration from a file, it’s assumed that the configuration will +be in the form of CLI commands by default. The ``json`` and ``xml`` +options can be used to load configurations in the JSON and XML formats, +respectively. It’s also possible to load a configuration from a previous +transaction by specifying the desired transaction ID +(``(1-4294967296)``). + +Options: \* ``translate WORD``: translate the JSON/XML configuration +file using the YANG module translator. \* ``replace``: replace the +candidate by the loaded configuration. The default is to merge the +loaded configuration into the candidate configuration. + +``rollback configuration (1-4294967296)`` +''''''''''''''''''''''''''''''''''''''''' + +Roll back the running configuration to a previous configuration +identified by its transaction ID (``(1-4294967296)``). + +``show configuration candidate [<json|xml> [translate WORD]] [<with-defaults|changes>]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the candidate configuration. + +Options: \* ``json``: show the configuration in the JSON format. \* +``xml``: show the configuration in the XML format. \* +``translate WORD``: translate the JSON/XML output using the YANG module +translator. \* ``with-defaults``: show default values that are hidden by +default. \* ``changes``: show only the changes done in the candidate +configuration. + +``show configuration compare <candidate|running|transaction (1-4294967296)> <candidate|running|transaction (1-4294967296)> [<json|xml> [translate WORD]]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the difference between two different configurations. + +Options: \* ``json``: show the configuration differences in the JSON +format. \* ``xml``: show the configuration differences in the XML +format. \* ``translate WORD``: translate the JSON/XML output using the +YANG module translator. + +``show configuration running [<json|xml> [translate WORD]] [with-defaults]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +Show the running configuration. + +Options: \* ``json``: show the configuration in the JSON format. \* +``xml``: show the configuration in the XML format. \* +``translate WORD``: translate the JSON/XML output using the YANG module +translator. \* ``with-defaults``: show default values that are hidden by +default. + + NOTE: ``show configuration running`` shows only the running + configuration as known by the northbound layer. Configuration + commands not converted to the new northbound model will not be + displayed. To show the full running configuration, the legacy + ``show running-config`` command must be used. + +``show configuration transaction [(1-4294967296) [<json|xml> [translate WORD]] [changes]]`` +''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +When a transaction ID (``(1-4294967296)``) is given, show the +configuration associated to the previously committed transaction. + +When a transaction ID is not given, show all recorded transactions in +the rollback log. + +Options: \* ``json``: show the configuration in the JSON format. \* +``xml``: show the configuration in the XML format. \* +``translate WORD``: translate the JSON/XML output using the YANG module +translator. \* ``with-defaults``: show default values that are hidden by +default. \* ``changes``: show changes compared to the previous +transaction. + +``show yang module [module-translator WORD] [WORD <summary|tree|yang|yin>]`` +'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' + +When a YANG module is not given, show all loaded YANG modules. +Otherwise, show detailed information about the given module. + +Options: \* ``module-translator WORD``: change the context to modules +loaded by the specified YANG module translator. \* ``summary``: display +summary information about the module. \* ``tree``: display module in the +tree (RFC 8340) format. \* ``yang``: display module in the YANG format. +\* ``yin``: display module in the YIN format. + +``show yang module-translator`` +''''''''''''''''''''''''''''''' + +Show all loaded YANG module translators. + +``update`` +'''''''''' + +Rebase the candidate configuration on top of the latest running +configuration. Conflicts are resolved automatically by giving preference +to the changes done in the candidate configuration. + +The candidate configuration might be outdated if the running +configuration was updated after the candidate was created. + +``yang module-translator load FILENAME`` +'''''''''''''''''''''''''''''''''''''''' + +Load a YANG module translator from the filesystem. + +``yang module-translator unload WORD`` +'''''''''''''''''''''''''''''''''''''' + +Unload a YANG module translator identified by its name. diff --git a/doc/developer/northbound/yang-module-translator.rst b/doc/developer/northbound/yang-module-translator.rst new file mode 100644 index 0000000000..aa527ce6b2 --- /dev/null +++ b/doc/developer/northbound/yang-module-translator.rst @@ -0,0 +1,629 @@ +Table of Contents +----------------- + +- `Introduction <#introduction>`__ +- `Deviation Modules <#deviation-modules>`__ +- `Translation Tables <#translation-tables>`__ +- `CLI Demonstration <#cli-demonstration>`__ +- `Implementation Details <#implementation-details>`__ + +Introduction +------------ + +One key requirement for the FRR northbound architecture is that it +should be possible to configure/monitor FRR using different sets of YANG +models. This is especially important considering that the industry +hasn’t reached a consensus to provide a single source of standard models +for network management. At this moment both the IETF and OpenConfig +models are widely implemented and are unlikely to converge, at least not +in the short term. In the ideal scenario, management applications should +be able to use either IETF or OpenConfig models to configure and monitor +FRR programatically (or even both at the same time!). + +But how can FRR support multiple sets of YANG models at the same time? +There must be only a single source of truth that models the existing +implementation accurately (the native models). Writing different code +paths or callbacks for different models would be inviable, it would lead +to a lot of duplicated code and extra maintenance overhead. + +In order to support different sets of YANG modules without introducing +the overhead of writing additional code, the solution is to create a +mechanism that dynamically translates YANG instance data between +non-native models to native models and vice-versa. Based on this idea, +an experimental YANG module translator was implemented within the FRR +northbound layer. The translator works by translating XPaths at runtime +using translation tables provided by the user. The translator itself is +modeled using YANG and users can create translators using simple JSON +files. + +A YANG module translator consists of two components: deviation modules +and translation tables. + +Deviation Modules +----------------- + +The first step when writing a YANG module translator is to create a +`deviations <https://tools.ietf.org/html/rfc7950#page-131>`__ module for +each module that is going be translated. This is necessary because in +most cases it won’t be possible to create a perfect translator that +covers the non-native models on their entirety. Some non-native modules +might contain nodes that can’t be mapped to a corresponding node in the +FRR native models. This is either because the corresponding +functionality is not implemented in FRR or because it’s modeled in a +different way that is incompatible. + +An an example, *ripd* doesn’t have BFD support yet, so we need to create +a YANG deviation to modify the *ietf-rip* module and remove the ``bfd`` +container from it: + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:bfd" { + deviate not-supported; + } + +In the example below, while both the *frr-ripd* and *ietf-rip* modules +support RIP authentication, they model the authentication data in +different ways, making translation not possible given the constraints of +the current module translator. A new deviation is necessary to remove +the ``authentication`` container from the *ietf-rip* module: + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:interfaces/ietf-rip:interface/ietf-rip:authentication" { + deviate not-supported; + } + +.. + + NOTE: it should be possible to translate the + ``ietf-rip:authentication`` container if the *frr-ripd* module is + modified to model the corresponding data in a compatible way. Another + option is to improve the module translator to make more complex + translations possible, instead of requiring one-to-one XPath + mappings. + +Sometimes creating a mapping between nodes from the native and +non-native models is possible, but the nodes have different properties +that need to be normalized to allow the translation. In the example +below, a YANG deviation is used to change the type and the default value +from a node from the ``ietf-rip`` module. + +.. code:: yang + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:timers/ietf-rip:flush-interval" { + deviate replace { + default "120"; + } + deviate replace { + type uint32; + } + } + +The deviation modules allow the management applications to know which +parts of the custom modules (e.g. IETF/OC) can be used to configure and +monitor FRR. + +In order to facilitate the process of creating YANG deviation modules, +the *gen_yang_deviations* tool was created to automate part of the +process. This tool creates a “not-supported” deviation for all nodes +from the given non-native module. Example: + +:: + + $ tools/gen_yang_deviations ietf-rip > yang/ietf/frr-deviations-ietf-rip.yang + $ head -n 40 yang/ietf/frr-deviations-ietf-rip.yang + deviation "/ietf-rip:clear-rip-route" { + deviate not-supported; + } + + deviation "/ietf-rip:clear-rip-route/ietf-rip:input" { + deviate not-supported; + } + + deviation "/ietf-rip:clear-rip-route/ietf-rip:input/ietf-rip:rip-instance" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:enabled" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:originate-default-route/ietf-rip:route-policy" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:default-metric" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:distance" { + deviate not-supported; + } + + deviation "/ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol/ietf-rip:rip/ietf-rip:triggered-update-threshold" { + deviate not-supported; + } + +Once all existing nodes are listed in the deviation module, it’s easy to +check the deviations that need to be removed or modified. This is more +convenient than starting with a blank deviations module and listing +manually all nodes that need to be deviated. + +After removing and/or modifying the auto-generated deviations, the next +step is to write the module XPath translation table as we’ll see in the +next section. Before that, it’s possible to use the *yanglint* tool to +check how the non-native module looks like after applying the +deviations. Example: + +:: + + $ yanglint -f tree yang/ietf/ietf-rip@2018-02-03.yang yang/ietf/frr-deviations-ietf-rip.yang + module: ietf-rip + + augment /ietf-routing:routing/ietf-routing:control-plane-protocols/ietf-routing:control-plane-protocol: + +--rw rip + +--rw originate-default-route + | +--rw enabled? boolean <false> + +--rw default-metric? uint8 <1> + +--rw distance? uint8 <0> + +--rw timers + | +--rw update-interval? uint32 <30> + | +--rw holddown-interval? uint32 <180> + | +--rw flush-interval? uint32 <120> + +--rw interfaces + | +--rw interface* [interface] + | +--rw interface ietf-interfaces:interface-ref + | +--rw split-horizon? enumeration <simple> + +--ro ipv4 + +--ro neighbors + | +--ro neighbor* [ipv4-address] + | +--ro ipv4-address ietf-inet-types:ipv4-address + | +--ro last-update? ietf-yang-types:date-and-time + | +--ro bad-packets-rcvd? ietf-yang-types:counter32 + | +--ro bad-routes-rcvd? ietf-yang-types:counter32 + +--ro routes + +--ro route* [ipv4-prefix] + +--ro ipv4-prefix ietf-inet-types:ipv4-prefix + +--ro next-hop? ietf-inet-types:ipv4-address + +--ro interface? ietf-interfaces:interface-ref + +--ro metric? uint8 + + rpcs: + +---x clear-rip-route + +.. + + NOTE: the same output can be obtained using the + ``show yang module module-translator ietf ietf-rip tree`` command in + FRR once the *ietf* module translator is loaded. + +In the example above, it can be seen that the vast majority of the +*ietf-rip* nodes were removed because of the “not-supported” deviations. +When a module translator is loaded, FRR calculates the coverage of the +translator by dividing the number of YANG nodes before applying the +deviations by the number of YANG nodes after applying the deviations. +The calculated coverage is displayed in the output of the +``show yang module-translator`` command: + +:: + + ripd# show yang module-translator + Family Module Deviations Coverage (%) + ----------------------------------------------------------------------- + ietf ietf-interfaces frr-deviations-ietf-interfaces 3.92 + ietf ietf-routing frr-deviations-ietf-routing 1.56 + ietf ietf-rip frr-deviations-ietf-rip 13.60 + +As it can be seen in the output above, the *ietf* module translator +covers only ~13% of the original *ietf-rip* module. This is in part +because the *ietf-rip* module models both RIPv2 and RIPng. Also, +*ietf-rip.yang* contains several knobs that aren’t implemented in *ripd* +yet (e.g. BFD support, per-interface timers, statistics, etc). Work can +be done over time to increase the coverage to a more reasonable number. + +Translation Tables +------------------ + +Below is an example of a translator for the IETF family of models: + +.. code:: json + + { + "frr-module-translator:frr-module-translator": { + "family": "ietf", + "module": [ + { + "name": "ietf-interfaces@2018-01-09", + "deviations": "frr-deviations-ietf-interfaces", + "mappings": [ + { + "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']" + }, + { + "custom": "/ietf-interfaces:interfaces/interface[name='KEY1']/description", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/description" + } + ] + }, + { + "name": "ietf-routing@2018-01-25", + "deviations": "frr-deviations-ietf-routing", + "mappings": [ + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']", + "native": "/frr-ripd:ripd/instance" + } + ] + }, + { + "name": "ietf-rip@2018-02-03", + "deviations": "frr-deviations-ietf-rip", + "mappings": [ + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric", + "native": "/frr-ripd:ripd/instance/default-metric" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/distance", + "native": "/frr-ripd:ripd/instance/distance/default" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/originate-default-route/enabled", + "native": "/frr-ripd:ripd/instance/default-information-originate" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/update-interval", + "native": "/frr-ripd:ripd/instance/timers/update-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/holddown-interval", + "native": "/frr-ripd:ripd/instance/timers/holddown-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/timers/flush-interval", + "native": "/frr-ripd:ripd/instance/timers/flush-interval" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']", + "native": "/frr-ripd:ripd/instance/interface[.='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']/split-horizon", + "native": "/frr-interface:lib/interface[name='KEY1'][vrf='default']/frr-ripd:rip/split-horizon" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/last-update", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/last-update" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-packets-rcvd", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-packets-rcvd" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/neighbors/neighbor[ipv4-address='KEY1']/bad-routes-rcvd", + "native": "/frr-ripd:ripd/state/neighbors/neighbor[address='KEY1']/bad-routes-rcvd" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/next-hop", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/next-hop" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/interface", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/interface" + }, + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/ipv4/routes/route[ipv4-prefix='KEY1']/metric", + "native": "/frr-ripd:ripd/state/routes/route[prefix='KEY1']/metric" + }, + { + "custom": "/ietf-rip:clear-rip-route", + "native": "/frr-ripd:clear-rip-route" + } + ] + } + ] + } + } + +The main motivation to use YANG itself to model YANG module translators +was a practical one: leverage *libyang* to validate the structure of the +user input (JSON files) instead of doing that manually in the +*lib/yang_translator.c* file (tedious and error-prone work). + +Module translators can be loaded using the following CLI command: + +:: + + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Module translator "ietf" loaded successfully. + +Module translators can also be loaded/unloaded programatically using the +``yang_translator_load()/yang_translator_unload()`` functions within the +northbound plugins. These functions are documented in the +*lib/yang_translator.h* file. + +Each module translator must be assigned a “family” identifier +(e.g. IETF, OpenConfig), and can contain mappings for multiple +interrelated YANG modules. The mappings consist of pairs of +custom/native XPath expressions that should be equivalent, despite +belonging to different YANG modules. + +Example: + +.. code:: json + + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/default-metric", + "native": "/frr-ripd:ripd/instance/default-metric" + }, + +The nodes pointed by the custom and native XPaths must have compatible +types. In the case of the example above, both nodes point to a YANG leaf +of type ``uint8``, so the mapping is valid. + +In the example below, the “custom” XPath points to a YANG list +(typeless), and the “native” XPath points to a YANG leaf-list of +strings. In this exceptional case, the types are also considered to be +compatible. + +.. code:: json + + { + "custom": "/ietf-routing:routing/control-plane-protocols/control-plane-protocol[type='ietf-rip:ripv2'][name='main']/ietf-rip:rip/interfaces/interface[interface='KEY1']", + "native": "/frr-ripd:ripd/instance/interface[.='KEY1']" + }, + +The ``KEY1..KEY4`` values have a special meaning and are used to +preserve the list keys while performing the XPath translation. + +Once a YANG module translator is loaded and validated at a syntactic +level using *libyang*, further validations are performed to check for +missing mappings (after loading the deviation modules) and incompatible +YANG types. Example: + +:: + + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Failed to load "/usr/local/share/yang/ietf/frr-ietf-translator.json" + + Please check the logs for more details. + +:: + + 2018/09/03 15:18:45 RIP: yang_translator_validate_cb: YANG types are incompatible (xpath: "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/default-metric") + 2018/09/03 15:18:45 RIP: yang_translator_validate_cb: missing mapping for "/ietf-routing:routing/control-plane-protocols/control-plane-protocol/ietf-rip:rip/distance" + 2018/09/03 15:18:45 RIP: yang_translator_validate: failed to validate "ietf" module translator: 2 error(s) + +Overall, this translation mechanism based on XPath mappings is simple +and functional, but only to a certain extent. The native models need to +be reasonably similar to the models that are going be translated, +otherwise the translation is compromised and a good coverage can’t be +achieved. Other translation techniques must be investigated to address +this shortcoming and make it possible to create more powerful YANG +module translators. + +YANG module translators can be evaluated based on the following metrics: +\* Translation potential: is it possible to make complex translations, +taking several variables into account? \* Complexity: measure of how +easy or hard it is to write a module translator. \* Speed: measure of +how fast the translation can be achieved. Translation speed is of +fundamental importance, especially for operational data. \* Robustness: +can the translator be checked for inconsistencies at load time? A module +translator based on scripts wouldn’t fare well on this metric. \* +Round-trip conversions: can the translated data be translated back to +the original format without information loss? + +CLI Demonstration +----------------- + +As of now the only northbound client that supports the YANG module +translator is the FRR embedded CLI. The confd and sysrepo plugins need +to be extended to support the module translator, which might be used not +only for configuration data, but also for operational data, RPCs and +notifications. + +In this demonstration, we’ll use the CLI ``configuration load`` command +to load the following JSON configuration file specified using the IETF +data hierarchy: + +.. code:: json + + { + "ietf-interfaces:interfaces": { + "interface": [ + { + "description": "Engineering", + "name": "eth0" + } + ] + }, + "ietf-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "name": "main", + "type": "ietf-rip:ripv2", + "ietf-rip:rip": { + "default-metric": "2", + "distance": "80", + "interfaces": { + "interface": [ + { + "interface": "eth0", + "split-horizon": "poison-reverse" + } + ] + }, + "originate-default-route": { + "enabled": "true" + }, + "timers": { + "flush-interval": "241", + "holddown-interval": "181", + "update-interval": "31" + } + } + } + ] + } + } + } + +In order to load this configuration file, it’s necessary to load the +IETF module translator first. Then, when entering the +``configuration load`` command, the ``translate ietf`` parameters must +be given to specify that the input needs to be translated using the +previously loaded ``ietf`` module translator. Example: + +:: + + ripd(config)# configuration load file json /mnt/renato/git/frr/yang/example/ietf-rip.json + % Failed to load configuration: + + Unknown element "interfaces". + ripd(config)# + ripd(config)# yang module-translator load /usr/local/share/yang/ietf/frr-ietf-translator.json + % Module translator "ietf" loaded successfully. + + ripd(config)# + ripd(config)# configuration load file json translate ietf /mnt/renato/git/frr/yang/example/ietf-rip.json + +Now let’s check the candidate configuration to see if the configuration +file was loaded successfully: + +:: + + ripd(config)# show configuration candidate + Configuration: + ! + frr version 5.1-dev + frr defaults traditional + ! + interface eth0 + description Engineering + ip rip split-horizon poisoned-reverse + ! + router rip + default-metric 2 + distance 80 + network eth0 + default-information originate + timers basic 31 181 241 + ! + end + ripd(config)# show configuration candidate json + { + "frr-interface:lib": { + "interface": [ + { + "name": "eth0", + "vrf": "default", + "description": "Engineering", + "frr-ripd:rip": { + "split-horizon": "poison-reverse" + } + } + ] + }, + "frr-ripd:ripd": { + "instance": { + "default-metric": 2, + "distance": { + "default": 80 + }, + "interface": [ + "eth0" + ], + "default-information-originate": true, + "timers": { + "flush-interval": 241, + "holddown-interval": 181, + "update-interval": 31 + } + } + } + } + +As it can be seen, the candidate configuration is identical to the one +defined in the *ietf-rip.json* file, only the structure is different. +This means that the *ietf-rip.json* file was translated successfully. + +The ``ietf`` module translator can also be used to do the translation in +other direction: transform data from the native format to the IETF +format. This is shown below by altering the output of the +``show configuration candidate json`` command using the +``translate ietf`` parameter: + +:: + + ripd(config)# show configuration candidate json translate ietf + { + "ietf-interfaces:interfaces": { + "interface": [ + { + "name": "eth0", + "description": "Engineering" + } + ] + }, + "ietf-routing:routing": { + "control-plane-protocols": { + "control-plane-protocol": [ + { + "type": "ietf-rip:ripv2", + "name": "main", + "ietf-rip:rip": { + "interfaces": { + "interface": [ + { + "interface": "eth0", + "split-horizon": "poison-reverse" + } + ] + }, + "default-metric": 2, + "distance": 80, + "originate-default-route": { + "enabled": true + }, + "timers": { + "flush-interval": 241, + "holddown-interval": 181, + "update-interval": 31 + } + } + } + ] + } + } + } + +As expected, this output is exactly identical to the configuration +defined in the *ietf-rip.json* file. The module translator was able to +do a round-trip conversion without information loss. + +Implementation Details +---------------------- + +A different libyang context is allocated for each YANG module +translator. This is important to avoid collisions and ensure that +non-native data can’t be instantiated in the running and candidate +configurations. diff --git a/doc/developer/northbound/yang-tools.rst b/doc/developer/northbound/yang-tools.rst new file mode 100644 index 0000000000..4eb1a2fb2c --- /dev/null +++ b/doc/developer/northbound/yang-tools.rst @@ -0,0 +1,106 @@ +yanglint cheat sheet +~~~~~~~~~~~~~~~~~~~~ + + libyang project includes a feature-rich tool called yanglint(1) for + validation and conversion of the schemas and YANG modeled data. The + source codes are located at /tools/lint and can be used to explore + how an application is supposed to use the libyang library. + yanglint(1) binary as well as its man page are installed together + with the library itself. + +Validate a YANG module: + +.. code:: sh + + $ yanglint -p <yang-search-path> module.yang + +Generate tree representation of a YANG module: + +.. code:: sh + + $ yanglint -p <yang-search-path> -f tree module.yang + +Validate JSON/XML instance data: + +.. code:: sh + + $ yanglint -p <yang-search-path> module.yang data.{json,xml} + +Convert JSON/XML instance data to another format: + +.. code:: sh + + $ yanglint -p <yang-search-path> -f xml module.yang data.json + $ yanglint -p <yang-search-path> -f json module.yang data.xml + +*yanglint* also features an interactive mode which is very useful when +needing to validate data from multiple modules at the same time. The +*yanglint* README provides several examples: +https://github.com/CESNET/libyang/blob/master/tools/lint/examples/README.md + +Man page (groff): +https://github.com/CESNET/libyang/blob/master/tools/lint/yanglint.1 + +pyang cheat sheet +~~~~~~~~~~~~~~~~~ + + pyang is a YANG validator, transformator and code generator, written + in python. It can be used to validate YANG modules for correctness, + to transform YANG modules into other formats, and to generate code + from the modules. + +Obtaining and installing pyang: + +.. code:: sh + + $ git clone https://github.com/mbj4668/pyang.git + $ cd pyang/ + $ sudo python setup.py install + +Validate a YANG module: + +.. code:: sh + + $ pyang --ietf -p <yang-search-path> module.yang + +Generate tree representation of a YANG module: + +.. code:: sh + + $ pyang -f tree -p <yang-search-path> module.yang + +Indent a YANG file: + +.. code:: sh + + $ pyang -p <yang-search-path> \ + --keep-comments -f yang --yang-canonical \ + module.yang -o module.yang + +Generate skeleton instance data: \* XML: + +.. code:: sh + + $ pyang -p <yang-search-path> \ + -f sample-xml-skeleton --sample-xml-skeleton-defaults \ + module.yang [augmented-module1.yang ...] -o module.xml + +- JSON: + +.. code:: sh + + $ pyang -p <yang-search-path> \ + -f jsonxsl module.yang -o module.xsl + $ xsltproc -o module.json module.xsl module.xml + +Validate XML instance data (works only with YANG 1.0): + +.. code:: sh + + $ yang2dsdl -v module.xml module.yang + +vim +~~~ + +YANG syntax highlighting for vim: +https://github.com/nathanalderson/yang.vim diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am index 840afa9f74..f052956c54 100644 --- a/doc/developer/subdir.am +++ b/doc/developer/subdir.am @@ -67,6 +67,19 @@ dev_RSTFILES = \ doc/developer/workflow.rst \ doc/developer/xrefs.rst \ doc/developer/zebra.rst \ + doc/developer/northbound/advanced-topics.rst \ + doc/developer/northbound/architecture.rst \ + doc/developer/northbound/demos.rst \ + doc/developer/northbound/links.rst \ + doc/developer/northbound/northbound.rst \ + doc/developer/northbound/operational-data-rpcs-and-notifications.rst \ + doc/developer/northbound/plugins-sysrepo.rst \ + doc/developer/northbound/ppr-basic-test-topology.rst \ + doc/developer/northbound/ppr-mpls-basic-test-topology.rst \ + doc/developer/northbound/retrofitting-configuration-commands.rst \ + doc/developer/northbound/transactional-cli.rst \ + doc/developer/northbound/yang-module-translator.rst \ + doc/developer/northbound/yang-tools.rst \ # end EXTRA_DIST += \ diff --git a/doc/user/bgp-linkstate.rst b/doc/user/bgp-linkstate.rst new file mode 100644 index 0000000000..59e37b19f7 --- /dev/null +++ b/doc/user/bgp-linkstate.rst @@ -0,0 +1,146 @@ +.. _bgp-link-state: + +BGP Link-State +============== + +Overview +-------- + +BGP Link-State (BGP-LS) is an extension of the BGP protocol designed to +redistribute information from an IGP database to a remote controller (most +likely a Path Computation Element - PCE). BGP-LS does nothing more than +transport IGP information. Therefore, it cannot be used to replace a Link State +routing protocol like OSPF and IS-IS. + +Historically, the only way to get a network controller to collect an IGP +database was to have it participate in the IGP itself as if it were a standard +router. Since the controllers were usually located far from the IGP area, +tunnels such as GRE were used to connect the controllers to the IGP area. This +method was so impractical that an alternative solution was imagined: using the +already deployed inter-domain BGP protocol to redistribute the various IGP +databases. + +BGP Link-State as defined in `RFC7752 +<https://www.rfc-editor.org/rfc/rfc7752.html>`_ uses the AFI 16388 and SAFI 71. +The BGP Link-State pseudo-prefixes distributed by the `NLRI (Network Layer +Reachability Information)` uniquely define the following +IGP information: + +- Nodes +- Link +- IPv4 Prefix +- IPv6 Prefix + +They are called descriptors. In addition, a new type of BGP Attributes called +"BGP-LS attributes" carries the other information related to a descriptor. + +NLRI and attribute information for BGP-LS is organized using the TLV format +already used by IS-IS LSPs and OSPF opaque LSAs. The `list of TLV code points +<https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#node-descriptor-link-descriptor-prefix-descriptor-attribute-tlv>`_ +is maintained by IANA. + +Current implementation +---------------------- + +The current version can participate in BGP Link-State AFI / SAFI with +third-party routers and forward the BGP Link-State descriptors and attributes to +other routers. However, it can not generate BGP Link-State data from OSPF and +IS-IS. + +IANA maintains a `registry of BGP-LS NRLI descriptor types +<https://www.iana.org/assignments/bgp-ls-parameters/bgp-ls-parameters.xhtml#nlri-types>`_. +Only the following RFC7752 NRLI types are supported by the current version: + +- Nodes +- Link +- IPv4 Prefix +- IPv6 Prefix + +The BGP-LS attribute TLVs for these NLRI types are transmitted as is to other +routers which means that all the current and future version are already +supported. + +Show commands +------------- + +The following configuration enables the negotiation of the link-state AFI / SAFI +with the 192.0.2.2 eBGP peer. + +.. code-block:: frr + + router bgp 65003 + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 update-source 192.0.2.3 + ! + address-family link-state link-state + neighbor 192.0.2.2 activate + neighbor 192.0.2.2 route-map PERMIT-ALL in + neighbor 192.0.2.2 route-map PERMIT-ALL out + exit-address-family + exit + ! + route-map PERMIT-ALL permit 1 + +The BGP-LS table can be displayed. + +.. code-block:: frr + + frr# show bgp link-state link-state + BGP table version is 8, local router ID is 192.0.2.3, vrf id 0 + Default local pref 100, local AS 65003 + Network Next Hop Metric LocPrf Weight Path + *> Node OSPFv2 ID:0x20 Local{AS:65001 ID:0 Area:0 Rtr:10.10.10.10:1.1.1.1}/48 + 0 65002 65001 i + *> IPv4-Prefix OSPFv2 ID:0x20 Local{AS:65001 ID:0 Area:0 Rtr:10.10.10.10:1.1.1.1} Prefix{IPv4:89.10.11.0/24}/64 + 0 65002 65001 i + *> IPv6-Prefix ISIS-L2 ID:0x20 Local{AS:65001 ID:0 Rtr:0000.0000.1003.00} Prefix{IPv6:12:12::12:12/128 MT:2}/74 + 0 65002 65001 i + *> IPv6-Prefix OSPFv3 ID:0x20 Local{AS:65001 ID:0 Area:0 Rtr:10.10.10.10} Prefix{OSPF-Route-Type:1 IPv6:12:12::12:12/128 MT:2}/74 + 0 65002 65001 i + *> Node OSPFv2 ID:0x20 Local{AS:65001 ID:0 Area:0 Rtr:10.10.10.10}/48 + 0 65002 65001 i + *> Node ISIS-L1 ID:0x20 Local{AS:65001 ID:0 Rtr:0000.0000.1003.00}/48 + 0 65002 65001 i + *> Link ISIS-L1 ID:0x20 Local{AS:65001 ID:0 Rtr:0000.0000.1001} Remote{AS:65001 ID:0 Rtr:0000.0000.1000} Link{IPv4:10.1.0.1 Neigh-IPv4:10.1.0.2 IPv6:2001::1 Neigh-IPv6:2001::2 MT:0,2}/132 + 0 65002 65001 i + +The "detail-routes" allows displaying the BGP-LS attributes as well. + +.. code-block:: frr + + frr# show bgp link-state link-state detail-routes + (...) + BGP routing table entry for Link ISIS-L1 ID:0x20 Local{AS:65001 ID:0 Rtr:0000.0000.1001} Remote{AS:65001 ID:0 Rtr:0000.0000.1000} Link{IPv4:10.1.0.1 Neigh-IPv4:10.1.0.2 IPv6:2001::1 Neigh-IPv6:2001::2 MT:0,2}/116, version 1 + Paths: (1 available, best #1) + Advertised to non peer-group peers: + 192.0.2.1 192.0.2.3 + 65001 + :: from 192.0.2.1 (192.0.2.1) + Origin IGP, valid, external, best (First path received) + Last update: Mon Apr 17 15:45:42 2023 + BGP-LS attributes: + IPv4 Router-ID of Local Node: 1.1.1.1 + IPv4 Router-ID of Remote Node: 10.10.10.10 + Maximum link bandwidth: 1410.07 Mbps + Max. reservable link bandwidth: 1410.07 Mbps + Unreserved bandwidth: + [0]: 1410.07 Mbps [1]: 1410.07 Mbps + [2]: 1410.07 Mbps [3]: 1410.07 Mbps + [4]: 1410.07 Mbps [5]: 1410.07 Mbps + [6]: 1410.07 Mbps [7]: 1410.07 Mbps + TE Default Metric: 100 + IGP Metric: 10 + Adjacency SID: + Flags: 0b00110000 + Weight: 0 + SID: 15000 + Unidirectional Link Delay: 8500 microseconds + Min/Max Unidirectional Link Delay: 8000/9000 microseconds + Application-Specific Link Attributes: + SABM Flags : 0b00010000 00000000 00000000 00000000 + UDABM Flags: 0b00000000 00000000 00000000 00000000 + Administrative group: 0x00000001 + TE Default Metric: 100 + Min/Max Unidirectional Link Delay: 8000/9000 microseconds + Extended Administrative Group: 0x00000001 + diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index ba6c160560..1431cbdd28 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -818,6 +818,10 @@ from eBGP peers, :ref:`bgp-route-selection`. MED as an intra-AS metric to steer equal-length AS_PATH routes to, e.g., desired exit points. +.. [#med-transitivity-rant] For some set of objects to have an order, there *must* be some binary ordering relation that is defined for *every* combination of those objects, and that relation *must* be transitive. I.e.:, if the relation operator is <, and if a < b and b < c then that relation must carry over and it *must* be that a < c for the objects to have an order. The ordering relation may allow for equality, i.e. a < b and b < a may both be true and imply that a and b are equal in the order and not distinguished by it, in which case the set has a partial order. Otherwise, if there is an order, all the objects have a distinct place in the order and the set has a total order) +.. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 +.. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 +.. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 .. _bgp-graceful-restart: @@ -1814,6 +1818,13 @@ Configuring Peers and is not displayed. The `bgp default l2vpn-evpn` form of the command is displayed. +.. clicmd:: bgp default link-state + + This command allows the user to specify that the link-state link-state + address family is turned on by default or not. This command defaults to off + and is not displayed. + The `bgp default link-state` form of the command is displayed. + .. clicmd:: bgp default show-hostname This command shows the hostname of the peer in certain BGP commands @@ -5213,10 +5224,7 @@ Show command json output: .. include:: flowspec.rst -.. [#med-transitivity-rant] For some set of objects to have an order, there *must* be some binary ordering relation that is defined for *every* combination of those objects, and that relation *must* be transitive. I.e.:, if the relation operator is <, and if a < b and b < c then that relation must carry over and it *must* be that a < c for the objects to have an order. The ordering relation may allow for equality, i.e. a < b and b < a may both be true and imply that a and b are equal in the order and not distinguished by it, in which case the set has a partial order. Otherwise, if there is an order, all the objects have a distinct place in the order and the set has a total order) -.. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 -.. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 -.. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 +.. include:: bgp-linkstate.rst .. _bgp-fast-convergence: diff --git a/doc/user/conf.py b/doc/user/conf.py index 728f9c9364..40accc1bd2 100644 --- a/doc/user/conf.py +++ b/doc/user/conf.py @@ -130,6 +130,7 @@ exclude_patterns = [ "rpki.rst", "routeserver.rst", "ospf_fundamentals.rst", + "bgp-linkstate.rst", "flowspec.rst", "snmptrap.rst", "wecmp_linkbw.rst", diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index 26b2b43971..d61522b051 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -314,7 +314,7 @@ To start OSPF process you have to specify the OSPF router. This command controls the ospf instance's socket buffer sizes. The 'no' form resets one or both values to the default. - + .. clicmd:: no socket-per-interface Ordinarily, ospfd uses a socket per interface for sending @@ -603,9 +603,9 @@ Interfaces Specify that HMAC cryptographic authentication must be used on this interface using a key chain. Overrides any authentication enabled on a per-area basis - (:clicmd:`area A.B.C.D authentication message-digest`) + (:clicmd:`area A.B.C.D authentication message-digest`). - * ``KEYCHAIN``: Specifies the name of the key chain that contains the authentication + ``KEYCHAIN``: Specifies the name of the key chain that contains the authentication key(s) and cryptographic algorithms to be used for OSPF authentication. The key chain is a logical container that holds one or more authentication keys, allowing for key rotation and management. @@ -621,7 +621,7 @@ Interfaces Example: - .. code:: frr + .. code:: sh r1(config)#key chain temp r1(config-keychain)#key 13 diff --git a/doc/user/pbr.rst b/doc/user/pbr.rst index d0513018e3..83abfa220e 100644 --- a/doc/user/pbr.rst +++ b/doc/user/pbr.rst @@ -28,8 +28,8 @@ documented elsewhere. .. _nexthop-groups: -Nexthop Groups -============== +PBR Nexthop Groups +================== A nexthop group is a list of ECMP nexthops used to forward packets when a pbr-map is matched. diff --git a/doc/user/static.rst b/doc/user/static.rst index 6d8aca97bb..d405276573 100644 --- a/doc/user/static.rst +++ b/doc/user/static.rst @@ -164,3 +164,23 @@ network 9.9.9.9/24: .. code-block:: frr ip route 9.9.9.9/24 6.6.6.6 color 123 + +SRv6 Route Commands +==================== + +It is possible to specify a static route for ipv6 prefixes using an SRv6 +`segments` instruction. The `/` separator can be used to specify +multiple segments instructions. + +.. code-block:: frr + + ipv6 route X:X::X:X <X:X::X:X|nexthop> segments U:U::U:U/Y:Y::Y:Y/Z:Z::Z:Z + + +:: + + router(config)# ipv6 route 2005::1/64 ens3 segments 2001:db8:aaaa::7/2002::4/2002::3/2002::2 + + router# show ipv6 route + [..] + S>* 2005::/64 [1/0] is directly connected, ens3, seg6 2001:db8:aaaa::7,2002::4,2002::3,2002::2, weight 1, 00:00:06 diff --git a/doc/user/vtysh.rst b/doc/user/vtysh.rst index 1ab54f09ab..adbdf3451a 100644 --- a/doc/user/vtysh.rst +++ b/doc/user/vtysh.rst @@ -18,8 +18,9 @@ administrator with an external editor. .. warning:: - This also means the ``hostname`` and ``banner motd`` commands (which both do - have effect for vtysh) need to be manually updated in :file:`vtysh.conf`. + This also means the ``hostname``, ``domainname``, and ``banner motd`` commands + (which do have effect for vtysh) need to be manually updated + in :file:`vtysh.conf`. .. clicmd:: copy FILENAME running-config diff --git a/include/linux/seg6.h b/include/linux/seg6.h index 329163e4a0..5b572ba3db 100644 --- a/include/linux/seg6.h +++ b/include/linux/seg6.h @@ -1,4 +1,4 @@ -/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +// SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note /* * SR-IPv6 implementation * @@ -30,7 +30,7 @@ struct ipv6_sr_hdr { __u8 flags; __u16 tag; - struct in6_addr segments[0]; + struct in6_addr segments[]; }; #define SR6_FLAG1_PROTECTED (1 << 6) diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c index 788618ef8b..8252c1ac25 100644 --- a/isisd/isis_zebra.c +++ b/isisd/isis_zebra.c @@ -1136,13 +1136,17 @@ static int isis_zebra_process_srv6_locator_chunk(ZAPI_CALLBACK_ARGS) enum srv6_endpoint_behavior_codepoint behavior; bool allocated = false; - if (!isis) + if (!isis) { + srv6_locator_chunk_free(&chunk); return -1; + } /* Decode the received zebra message */ s = zclient->ibuf; - if (zapi_srv6_locator_chunk_decode(s, chunk) < 0) + if (zapi_srv6_locator_chunk_decode(s, chunk) < 0) { + srv6_locator_chunk_free(&chunk); return -1; + } sr_debug( "Received SRv6 locator chunk from zebra: name %s, " @@ -1225,6 +1229,9 @@ static int isis_zebra_process_srv6_locator_add(ZAPI_CALLBACK_ARGS) struct listnode *node; struct isis_area *area; + if (!isis) + return -1; + /* Decode the SRv6 locator */ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) return -1; @@ -1274,6 +1281,9 @@ static int isis_zebra_process_srv6_locator_delete(ZAPI_CALLBACK_ARGS) struct isis_srv6_sid *sid; struct srv6_adjacency *sra; + if (!isis) + return -1; + /* Decode the received zebra message */ if (zapi_srv6_locator_decode(zclient->ibuf, &loc) < 0) return -1; diff --git a/lib/command.h b/lib/command.h index 718d34b007..36640c493f 100644 --- a/lib/command.h +++ b/lib/command.h @@ -174,6 +174,7 @@ enum node_type { BMP_NODE, /* BMP config under router bgp */ ISIS_SRV6_NODE, /* ISIS SRv6 node */ ISIS_SRV6_NODE_MSD_NODE, /* ISIS SRv6 Node MSDs node */ + BGP_LS_NODE, /* BGP-LS configuration node */ NODE_TYPE_MAX, /* maximum */ }; /* clang-format on */ diff --git a/lib/elf_py.c b/lib/elf_py.c index 81ca668e70..643495d8c7 100644 --- a/lib/elf_py.c +++ b/lib/elf_py.c @@ -1089,7 +1089,9 @@ static void elffile_add_dynreloc(struct elffile *w, Elf_Data *reldata, symidx = relw->symidx = GELF_R_SYM(rela->r_info); sym = relw->sym = gelf_getsym(symdata, symidx, &relw->_sym); if (sym) { - relw->symname = elfdata_strptr(strdata, sym->st_name); + if (strdata) + relw->symname = elfdata_strptr(strdata, + sym->st_name); relw->symvalid = GELF_ST_TYPE(sym->st_info) != STT_NOTYPE; relw->unresolved = sym->st_shndx == SHN_UNDEF; diff --git a/lib/event.c b/lib/event.c index 37a30d2511..458e29f248 100644 --- a/lib/event.c +++ b/lib/event.c @@ -791,7 +791,12 @@ static struct event *thread_get(struct event_loop *m, uint8_t type, thread->master = m; thread->arg = arg; thread->yield = EVENT_YIELD_TIME_SLOT; /* default */ - thread->ref = NULL; + /* thread->ref is zeroed either by XCALLOC above or by memset before + * being put on the "unuse" list by thread_add_unuse(). + * Setting it here again makes coverity complain about a missing + * lock :( + */ + /* thread->ref = NULL; */ thread->ignore_timer_late = false; /* diff --git a/lib/iana_afi.h b/lib/iana_afi.h index b9c19cc3d5..9b4d33fa4d 100644 --- a/lib/iana_afi.h +++ b/lib/iana_afi.h @@ -26,6 +26,7 @@ typedef enum { IANA_AFI_IPV4 = 1, IANA_AFI_IPV6 = 2, IANA_AFI_L2VPN = 25, + IANA_AFI_LINKSTATE = 16388, /* BGP-LS RFC 7752 */ } iana_afi_t; typedef enum { @@ -35,6 +36,8 @@ typedef enum { IANA_SAFI_LABELED_UNICAST = 4, IANA_SAFI_ENCAP = 7, IANA_SAFI_EVPN = 70, + IANA_SAFI_LINKSTATE = 71, /* BGP-LS RFC 7752 */ + IANA_SAFI_LINKSTATE_VPN = 72, /* BGP-LS RFC 7752 */ IANA_SAFI_MPLS_VPN = 128, IANA_SAFI_FLOWSPEC = 133 } iana_safi_t; @@ -48,6 +51,8 @@ static inline afi_t afi_iana2int(iana_afi_t afi) return AFI_IP6; case IANA_AFI_L2VPN: return AFI_L2VPN; + case IANA_AFI_LINKSTATE: + return AFI_LINKSTATE; case IANA_AFI_RESERVED: return AFI_MAX; } @@ -64,6 +69,8 @@ static inline iana_afi_t afi_int2iana(afi_t afi) return IANA_AFI_IPV6; case AFI_L2VPN: return IANA_AFI_L2VPN; + case AFI_LINKSTATE: + return IANA_AFI_LINKSTATE; case AFI_UNSPEC: case AFI_MAX: return IANA_AFI_RESERVED; @@ -94,6 +101,10 @@ static inline safi_t safi_iana2int(iana_safi_t safi) return SAFI_LABELED_UNICAST; case IANA_SAFI_FLOWSPEC: return SAFI_FLOWSPEC; + case IANA_SAFI_LINKSTATE: + return SAFI_LINKSTATE; + case IANA_SAFI_LINKSTATE_VPN: + return SAFI_LINKSTATE_VPN; case IANA_SAFI_RESERVED: return SAFI_MAX; } @@ -118,6 +129,10 @@ static inline iana_safi_t safi_int2iana(safi_t safi) return IANA_SAFI_LABELED_UNICAST; case SAFI_FLOWSPEC: return IANA_SAFI_FLOWSPEC; + case SAFI_LINKSTATE: + return IANA_SAFI_LINKSTATE; + case SAFI_LINKSTATE_VPN: + return IANA_SAFI_LINKSTATE_VPN; case SAFI_UNSPEC: case SAFI_MAX: return IANA_SAFI_RESERVED; diff --git a/lib/ipaddr.h b/lib/ipaddr.h index e3ad14d7db..c86e38c867 100644 --- a/lib/ipaddr.h +++ b/lib/ipaddr.h @@ -28,6 +28,7 @@ struct ipaddr { enum ipaddr_type_t ipa_type; union { uint8_t addr; + uint8_t addrbytes[16]; struct in_addr _v4_addr; struct in6_addr _v6_addr; } ip; diff --git a/lib/nexthop.c b/lib/nexthop.c index 4f92ef9c8b..8df57e36a2 100644 --- a/lib/nexthop.c +++ b/lib/nexthop.c @@ -56,6 +56,7 @@ static int _nexthop_srv6_cmp(const struct nexthop *nh1, const struct nexthop *nh2) { int ret = 0; + int i = 0; if (!nh1->nh_srv6 && !nh2->nh_srv6) return 0; @@ -78,9 +79,26 @@ static int _nexthop_srv6_cmp(const struct nexthop *nh1, if (ret != 0) return ret; - ret = memcmp(&nh1->nh_srv6->seg6_segs, - &nh2->nh_srv6->seg6_segs, - sizeof(struct in6_addr)); + if (!nh1->nh_srv6->seg6_segs && !nh2->nh_srv6->seg6_segs) + return 0; + + if (!nh1->nh_srv6->seg6_segs && nh2->nh_srv6->seg6_segs) + return -1; + + if (nh1->nh_srv6->seg6_segs && !nh2->nh_srv6->seg6_segs) + return 1; + + if (nh1->nh_srv6->seg6_segs->num_segs != + nh2->nh_srv6->seg6_segs->num_segs) + return -1; + + for (i = 0; i < nh1->nh_srv6->seg6_segs->num_segs; i++) { + ret = memcmp(&nh1->nh_srv6->seg6_segs->seg[i], + &nh2->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); + if (ret != 0) + break; + } return ret; } @@ -362,7 +380,7 @@ struct nexthop *nexthop_new(void) * The linux kernel does some weird stuff with adding +1 to * all nexthop weights it gets over netlink. * To handle this, just default everything to 1 right from - * from the beginning so we don't have to special case + * the beginning so we don't have to special case * default weights in the linux netlink code. * * 1 should be a valid on all platforms anyway. @@ -568,15 +586,25 @@ void nexthop_del_srv6_seg6local(struct nexthop *nexthop) if (!nexthop->nh_srv6) return; + if (nexthop->nh_srv6->seg6local_action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + return; + nexthop->nh_srv6->seg6local_action = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; - if (sid_zero(&nexthop->nh_srv6->seg6_segs)) + if (nexthop->nh_srv6->seg6_segs && + (nexthop->nh_srv6->seg6_segs->num_segs == 0 || + sid_zero(nexthop->nh_srv6->seg6_segs))) + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6->seg6_segs); + + if (nexthop->nh_srv6->seg6_segs == NULL) XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6); } -void nexthop_add_srv6_seg6(struct nexthop *nexthop, - const struct in6_addr *segs) +void nexthop_add_srv6_seg6(struct nexthop *nexthop, const struct in6_addr *segs, + int num_segs) { + int i; + if (!segs) return; @@ -584,7 +612,22 @@ void nexthop_add_srv6_seg6(struct nexthop *nexthop, nexthop->nh_srv6 = XCALLOC(MTYPE_NH_SRV6, sizeof(struct nexthop_srv6)); - nexthop->nh_srv6->seg6_segs = *segs; + /* Enforce limit on srv6 seg stack size */ + if (num_segs > SRV6_MAX_SIDS) + num_segs = SRV6_MAX_SIDS; + + if (!nexthop->nh_srv6->seg6_segs) { + nexthop->nh_srv6->seg6_segs = + XCALLOC(MTYPE_NH_SRV6, + sizeof(struct seg6_seg_stack) + + num_segs * sizeof(struct in6_addr)); + } + + nexthop->nh_srv6->seg6_segs->num_segs = num_segs; + + for (i = 0; i < num_segs; i++) + memcpy(&nexthop->nh_srv6->seg6_segs->seg[i], &segs[i], + sizeof(struct in6_addr)); } void nexthop_del_srv6_seg6(struct nexthop *nexthop) @@ -592,12 +635,14 @@ void nexthop_del_srv6_seg6(struct nexthop *nexthop) if (!nexthop->nh_srv6) return; - memset(&nexthop->nh_srv6->seg6_segs, 0, - sizeof(nexthop->nh_srv6->seg6_segs)); - - if (nexthop->nh_srv6->seg6local_action == - ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) - XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6); + if (nexthop->nh_srv6->seg6local_action == ZEBRA_SEG6_LOCAL_ACTION_UNSPEC && + nexthop->nh_srv6->seg6_segs) { + memset(nexthop->nh_srv6->seg6_segs->seg, 0, + sizeof(struct in6_addr) * + nexthop->nh_srv6->seg6_segs->num_segs); + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6->seg6_segs); + } + XFREE(MTYPE_NH_SRV6, nexthop->nh_srv6); } const char *nexthop2str(const struct nexthop *nexthop, char *str, int size) @@ -743,11 +788,32 @@ uint32_t nexthop_hash_quick(const struct nexthop *nexthop) } if (nexthop->nh_srv6) { - key = jhash_1word(nexthop->nh_srv6->seg6local_action, key); - key = jhash(&nexthop->nh_srv6->seg6local_ctx, - sizeof(nexthop->nh_srv6->seg6local_ctx), key); - key = jhash(&nexthop->nh_srv6->seg6_segs, - sizeof(nexthop->nh_srv6->seg6_segs), key); + int segs_num = 0; + int i = 0; + + if (nexthop->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) { + key = jhash_1word(nexthop->nh_srv6->seg6local_action, + key); + key = jhash(&nexthop->nh_srv6->seg6local_ctx, + sizeof(nexthop->nh_srv6->seg6local_ctx), + key); + if (nexthop->nh_srv6->seg6_segs) + key = jhash(&nexthop->nh_srv6->seg6_segs->seg[0], + sizeof(struct in6_addr), key); + } else { + if (nexthop->nh_srv6->seg6_segs) { + segs_num = nexthop->nh_srv6->seg6_segs->num_segs; + while (segs_num >= 1) { + key = jhash(&nexthop->nh_srv6->seg6_segs + ->seg[i], + sizeof(struct in6_addr), + key); + segs_num -= 1; + i += 1; + } + } + } } return key; @@ -810,9 +876,13 @@ void nexthop_copy_no_recurse(struct nexthop *copy, nexthop_add_srv6_seg6local(copy, nexthop->nh_srv6->seg6local_action, &nexthop->nh_srv6->seg6local_ctx); - if (!sid_zero(&nexthop->nh_srv6->seg6_segs)) + if (nexthop->nh_srv6->seg6_segs && + nexthop->nh_srv6->seg6_segs->num_segs && + !sid_zero(nexthop->nh_srv6->seg6_segs)) nexthop_add_srv6_seg6(copy, - &nexthop->nh_srv6->seg6_segs); + &nexthop->nh_srv6->seg6_segs->seg[0], + nexthop->nh_srv6->seg6_segs + ->num_segs); } } diff --git a/lib/nexthop.h b/lib/nexthop.h index 2be89f8240..bed6447d49 100644 --- a/lib/nexthop.h +++ b/lib/nexthop.h @@ -157,8 +157,8 @@ void nexthop_del_labels(struct nexthop *); void nexthop_add_srv6_seg6local(struct nexthop *nexthop, uint32_t action, const struct seg6local_context *ctx); void nexthop_del_srv6_seg6local(struct nexthop *nexthop); -void nexthop_add_srv6_seg6(struct nexthop *nexthop, - const struct in6_addr *segs); +void nexthop_add_srv6_seg6(struct nexthop *nexthop, const struct in6_addr *seg, + int num_segs); void nexthop_del_srv6_seg6(struct nexthop *nexthop); /* diff --git a/lib/northbound.c b/lib/northbound.c index ef2344ee11..69b96d3656 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -2691,7 +2691,6 @@ void nb_init(struct event_loop *tm, size_t nmodules, bool db_enabled) { struct yang_module *loaded[nmodules], **loadedp = loaded; - bool explicit_compile; /* * Currently using this explicit compile feature in libyang2 leads to @@ -2699,8 +2698,9 @@ void nb_init(struct event_loop *tm, * of modules until they have all been loaded into the context. This * avoids multiple recompiles of the same modules as they are * imported/augmented etc. + * (Done as a #define to make coverity happy) */ - explicit_compile = false; +#define explicit_compile false nb_db_enabled = db_enabled; diff --git a/lib/prefix.c b/lib/prefix.c index 0b8664411d..cde8677cf0 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -20,6 +20,7 @@ DEFINE_MTYPE_STATIC(LIB, PREFIX, "Prefix"); DEFINE_MTYPE_STATIC(LIB, PREFIX_FLOWSPEC, "Prefix Flowspec"); +DEFINE_MTYPE_STATIC(LIB, PREFIX_LINKSTATE, "Prefix Link-State"); /* Maskbit. */ static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, @@ -32,6 +33,18 @@ static const uint8_t maskbit[] = {0x00, 0x80, 0xc0, 0xe0, 0xf0, #define MASKBIT(offset) ((0xff << (PNBBY - (offset))) & 0xff) +char *(*prefix_linkstate_display_hook)(char *buf, size_t size, + uint16_t nlri_type, uintptr_t ptr, + uint16_t len) = NULL; + +void prefix_set_linkstate_display_hook(char *(*func)(char *buf, size_t size, + uint16_t nlri_type, + uintptr_t ptr, + uint16_t len)) +{ + prefix_linkstate_display_hook = func; +} + int is_zero_mac(const struct ethaddr *mac) { int i = 0; @@ -81,6 +94,8 @@ int str2family(const char *string) return AF_ETHERNET; else if (!strcmp("evpn", string)) return AF_EVPN; + else if (!strcmp("link-state", string)) + return AF_LINKSTATE; return -1; } @@ -95,6 +110,8 @@ const char *family2str(int family) return "Ethernet"; case AF_EVPN: return "Evpn"; + case AF_LINKSTATE: + return "Link-State"; } return "?"; } @@ -109,6 +126,8 @@ int afi2family(afi_t afi) else if (afi == AFI_L2VPN) return AF_ETHERNET; /* NOTE: EVPN code should NOT use this interface. */ + else if (afi == AFI_LINKSTATE) + return AF_LINKSTATE; return 0; } @@ -120,6 +139,8 @@ afi_t family2afi(int family) return AFI_IP6; else if (family == AF_ETHERNET || family == AF_EVPN) return AFI_L2VPN; + else if (family == AF_LINKSTATE) + return AFI_LINKSTATE; return 0; } @@ -132,6 +153,8 @@ const char *afi2str_lower(afi_t afi) return "ipv6"; case AFI_L2VPN: return "l2vpn"; + case AFI_LINKSTATE: + return "link-state"; case AFI_MAX: case AFI_UNSPEC: return "bad-value"; @@ -149,6 +172,8 @@ const char *afi2str(afi_t afi) return "IPv6"; case AFI_L2VPN: return "l2vpn"; + case AFI_LINKSTATE: + return "link-state"; case AFI_MAX: case AFI_UNSPEC: return "bad-value"; @@ -174,6 +199,10 @@ const char *safi2str(safi_t safi) return "labeled-unicast"; case SAFI_FLOWSPEC: return "flowspec"; + case SAFI_LINKSTATE: + return "link-state"; + case SAFI_LINKSTATE_VPN: + return "link-state-vpn"; case SAFI_UNSPEC: case SAFI_MAX: return "unknown"; @@ -215,6 +244,21 @@ int prefix_match(union prefixconstptr unet, union prefixconstptr upfx) if (np[offset] != pp[offset]) return 0; return 1; + } else if (n->family == AF_LINKSTATE) { + if (n->u.prefix_linkstate.nlri_type != + p->u.prefix_linkstate.nlri_type) + return 0; + + /* Set both prefix's head pointer. */ + np = (const uint8_t *)&n->u.prefix_linkstate.ptr; + pp = (const uint8_t *)&p->u.prefix_linkstate.ptr; + + offset = n->prefixlen; /* length is checked above */ + + while (offset--) + if (np[offset] != pp[offset]) + return 0; + return 1; } /* Set both prefix's head pointer. */ @@ -261,7 +305,7 @@ int evpn_type5_prefix_match(const struct prefix *n, const struct prefix *p) return 0; prefixlen = evp->prefix.prefix_addr.ip_prefix_length; - np = &evp->prefix.prefix_addr.ip.ip.addr; + np = evp->prefix.prefix_addr.ip.ip.addrbytes; /* If n's prefix is longer than p's one return 0. */ if (prefixlen > p->prefixlen) @@ -316,6 +360,8 @@ void prefix_copy(union prefixptr udest, union prefixconstptr usrc) { struct prefix *dest = udest.p; const struct prefix *src = usrc.p; + void *temp; + int len; dest->family = src->family; dest->prefixlen = src->prefixlen; @@ -334,9 +380,6 @@ void prefix_copy(union prefixptr udest, union prefixconstptr usrc) dest->u.lp.id = src->u.lp.id; dest->u.lp.adv_router = src->u.lp.adv_router; } else if (src->family == AF_FLOWSPEC) { - void *temp; - int len; - len = src->u.prefix_flowspec.prefixlen; dest->u.prefix_flowspec.prefixlen = src->u.prefix_flowspec.prefixlen; @@ -347,6 +390,14 @@ void prefix_copy(union prefixptr udest, union prefixconstptr usrc) dest->u.prefix_flowspec.ptr = (uintptr_t)temp; memcpy((void *)dest->u.prefix_flowspec.ptr, (void *)src->u.prefix_flowspec.ptr, len); + } else if (src->family == AF_LINKSTATE) { + len = src->prefixlen; + dest->u.prefix_linkstate.nlri_type = + src->u.prefix_linkstate.nlri_type; + temp = XCALLOC(MTYPE_PREFIX_LINKSTATE, len); + dest->u.prefix_linkstate.ptr = (uintptr_t)temp; + memcpy((void *)dest->u.prefix_linkstate.ptr, + (void *)src->u.prefix_linkstate.ptr, len); } else { flog_err(EC_LIB_DEVELOPMENT, "prefix_copy(): Unknown address family %d", @@ -436,6 +487,14 @@ int prefix_same(union prefixconstptr up1, union prefixconstptr up2) p2->u.prefix_flowspec.prefixlen)) return 1; } + if (p1->family == AF_LINKSTATE) { + if (p1->u.prefix_linkstate.nlri_type != + p2->u.prefix_linkstate.nlri_type) + return 0; + if (!memcmp(&p1->u.prefix_linkstate.ptr, + &p2->u.prefix_linkstate.ptr, p2->prefixlen)) + return 1; + } } return 0; } @@ -483,6 +542,22 @@ int prefix_cmp(union prefixconstptr up1, union prefixconstptr up2) if (pp1[offset] != pp2[offset]) return numcmp(pp1[offset], pp2[offset]); return 0; + } else if (p1->family == AF_LINKSTATE) { + pp1 = (const uint8_t *)p1->u.prefix_linkstate.ptr; + pp2 = (const uint8_t *)p2->u.prefix_linkstate.ptr; + + if (p1->u.prefix_linkstate.nlri_type != + p2->u.prefix_linkstate.nlri_type) + return 1; + + if (p1->prefixlen != p2->prefixlen) + return numcmp(p1->prefixlen, p2->prefixlen); + + offset = p1->prefixlen; + while (offset--) + if (pp1[offset] != pp2[offset]) + return numcmp(pp1[offset], pp2[offset]); + return 0; } pp1 = p1->u.val; pp2 = p2->u.val; @@ -1072,10 +1147,26 @@ static const char *prefixevpn2str(const struct prefix_evpn *p, char *str, return str; } +const char *bgp_linkstate_nlri_type_2str(uint16_t nlri_type) +{ + switch (nlri_type) { + case BGP_LINKSTATE_NODE: + return "Node"; + case BGP_LINKSTATE_LINK: + return "Link"; + case BGP_LINKSTATE_PREFIX4: + return "IPv4-Prefix"; + case BGP_LINKSTATE_PREFIX6: + return "IPv6-Prefix"; + } + + return "Unknown"; +} + const char *prefix2str(union prefixconstptr pu, char *str, int size) { const struct prefix *p = pu.p; - char buf[PREFIX2STR_BUFFER]; + char buf[PREFIX_STRLEN_EXTENDED]; int byte, tmp, a, b; bool z = false; size_t l; @@ -1116,6 +1207,22 @@ const char *prefix2str(union prefixconstptr pu, char *str, int size) strlcpy(str, "FS prefix", size); break; + case AF_LINKSTATE: + if (prefix_linkstate_display_hook) + snprintf(str, size, "%s/%d", + prefix_linkstate_display_hook( + buf, sizeof(buf), + p->u.prefix_linkstate.nlri_type, + p->u.prefix_linkstate.ptr, + p->prefixlen), + p->prefixlen); + else + snprintf(str, size, "%s/%d", + bgp_linkstate_nlri_type_2str( + p->u.prefix_linkstate.nlri_type), + p->prefixlen); + break; + default: strlcpy(str, "UNK prefix", size); break; @@ -1127,7 +1234,7 @@ const char *prefix2str(union prefixconstptr pu, char *str, int size) static ssize_t prefixhost2str(struct fbuf *fbuf, union prefixconstptr pu) { const struct prefix *p = pu.p; - char buf[PREFIX2STR_BUFFER]; + char buf[PREFIX_STRLEN_EXTENDED]; switch (p->family) { case AF_INET: @@ -1139,6 +1246,17 @@ static ssize_t prefixhost2str(struct fbuf *fbuf, union prefixconstptr pu) prefix_mac2str(&p->u.prefix_eth, buf, sizeof(buf)); return bputs(fbuf, buf); + case AF_LINKSTATE: + if (prefix_linkstate_display_hook) + prefix_linkstate_display_hook( + buf, sizeof(buf), + p->u.prefix_linkstate.nlri_type, + p->u.prefix_linkstate.ptr, p->prefixlen); + else + snprintf(buf, sizeof(buf), "%s", + bgp_linkstate_nlri_type_2str( + p->u.prefix_linkstate.nlri_type)); + return bputs(fbuf, buf); default: return bprintfrr(fbuf, "{prefix.af=%dPF}", p->family); } @@ -1173,6 +1291,20 @@ const char *prefix_sg2str(const struct prefix_sg *sg, char *sg_str) return sg_str; } + +void prefix_linkstate_ptr_free(struct prefix *p) +{ + void *temp; + + if (!p || p->family != AF_LINKSTATE || !p->u.prefix_linkstate.ptr) + return; + + temp = (void *)p->u.prefix_linkstate.ptr; + XFREE(MTYPE_PREFIX_LINKSTATE, temp); + p->u.prefix_linkstate.ptr = (uintptr_t)NULL; +} + + struct prefix *prefix_new(void) { struct prefix *p; @@ -1322,17 +1454,16 @@ char *prefix_mac2str(const struct ethaddr *mac, char *buf, int size) unsigned prefix_hash_key(const void *pp) { struct prefix copy; + uint32_t len; + void *temp; - if (((struct prefix *)pp)->family == AF_FLOWSPEC) { - uint32_t len; - void *temp; + /* make sure *all* unused bits are zero, particularly including + * alignment / + * padding and unused prefix bytes. */ + memset(©, 0, sizeof(copy)); + prefix_copy(©, (struct prefix *)pp); - /* make sure *all* unused bits are zero, - * particularly including alignment / - * padding and unused prefix bytes. - */ - memset(©, 0, sizeof(copy)); - prefix_copy(©, (struct prefix *)pp); + if (((struct prefix *)pp)->family == AF_FLOWSPEC) { len = jhash((void *)copy.u.prefix_flowspec.ptr, copy.u.prefix_flowspec.prefixlen, 0x55aa5a5a); @@ -1340,12 +1471,13 @@ unsigned prefix_hash_key(const void *pp) XFREE(MTYPE_PREFIX_FLOWSPEC, temp); copy.u.prefix_flowspec.ptr = (uintptr_t)NULL; return len; + } else if (((struct prefix *)pp)->family == AF_LINKSTATE) { + len = jhash((void *)copy.u.prefix_linkstate.ptr, copy.prefixlen, + 0x55aa5a5a); + prefix_linkstate_ptr_free(©); + return len; } - /* make sure *all* unused bits are zero, particularly including - * alignment / - * padding and unused prefix bytes. */ - memset(©, 0, sizeof(copy)); - prefix_copy(©, (struct prefix *)pp); + return jhash(©, offsetof(struct prefix, u.prefix) + PSIZE(copy.prefixlen), 0x55aa5a5a); @@ -1620,7 +1752,7 @@ static ssize_t printfrr_pfx(struct fbuf *buf, struct printfrr_eargs *ea, if (host_only) return prefixhost2str(buf, (struct prefix *)ptr); else { - char cbuf[PREFIX_STRLEN]; + char cbuf[PREFIX_STRLEN_EXTENDED]; prefix2str(ptr, cbuf, sizeof(cbuf)); return bputs(buf, cbuf); diff --git a/lib/prefix.h b/lib/prefix.h index fc6e32dd54..f1aff43689 100644 --- a/lib/prefix.h +++ b/lib/prefix.h @@ -125,6 +125,15 @@ struct evpn_addr { #define prefix_addr u._prefix_addr }; +/* BGP Link-State NRLI types*/ +enum bgp_linkstate_nlri_type { + /* RFC7752 Table 1 */ + BGP_LINKSTATE_NODE = 1, + BGP_LINKSTATE_LINK = 2, + BGP_LINKSTATE_PREFIX4 = 3, /* IPv4 Topology Prefix */ + BGP_LINKSTATE_PREFIX6 = 4, /* IPv6 Topology Prefix */ +}; + /* * A struct prefix contains an address family, a prefix length, and an * address. This can represent either a 'network prefix' as defined @@ -158,12 +167,21 @@ struct evpn_addr { #define AF_FLOWSPEC (AF_MAX + 2) #endif +#if !defined(AF_LINKSTATE) +#define AF_LINKSTATE (AF_MAX + 3) +#endif + struct flowspec_prefix { uint8_t family; uint16_t prefixlen; /* length in bytes */ uintptr_t ptr; }; +struct linkstate_prefix { + uint16_t nlri_type; + uintptr_t ptr; +}; + /* FRR generic prefix structure. */ struct prefix { uint8_t family; @@ -182,6 +200,7 @@ struct prefix { uintptr_t ptr; struct evpn_addr prefix_evpn; /* AF_EVPN */ struct flowspec_prefix prefix_flowspec; /* AF_FLOWSPEC */ + struct linkstate_prefix prefix_linkstate; /* AF_LINKSTATE */ } u __attribute__((aligned(8))); }; @@ -279,6 +298,14 @@ struct prefix_fs { struct flowspec_prefix prefix __attribute__((aligned(8))); }; + +/* Prefix for a BGP-LS entry */ +struct prefix_bgpls { + uint8_t family; + uint16_t prefixlen; + struct linkstate_prefix prefix __attribute__((aligned(8))); +}; + struct prefix_sg { uint8_t family; uint16_t prefixlen; @@ -320,6 +347,11 @@ union prefixconstptr { /* Maximum string length of the result of prefix2str */ #define PREFIX_STRLEN 80 +/* Maximum string length of the result of prefix2str for + * long string prefixes (eg. BGP Link-State) + */ +#define PREFIX_STRLEN_EXTENDED 512 + /* * Longest possible length of a (S,G) string is 34 bytes * 123.123.123.123 = 15 * 2 @@ -376,6 +408,10 @@ static inline void ipv4_addr_copy(struct in_addr *dst, #define s6_addr32 __u6_addr.__u6_addr32 #endif /*s6_addr32*/ +extern void prefix_set_linkstate_display_hook( + char *(*func)(char *buf, size_t size, uint16_t nlri_type, uintptr_t ptr, + uint16_t len)); + /* Prototypes. */ extern int str2family(const char *string); extern int afi2family(afi_t afi); @@ -401,6 +437,8 @@ static inline afi_t prefix_afi(union prefixconstptr pu) */ extern unsigned int prefix_bit(const uint8_t *prefix, const uint16_t bit_index); +extern void prefix_linkstate_ptr_free(struct prefix *p); + extern struct prefix *prefix_new(void); extern void prefix_free(struct prefix **p); /* @@ -418,6 +456,7 @@ extern void prefix_mcast_inet4_dump(const char *onfail, struct in_addr addr, extern const char *prefix_sg2str(const struct prefix_sg *sg, char *str); extern const char *prefix2str(union prefixconstptr upfx, char *buffer, int size); +extern const char *bgp_linkstate_nlri_type_2str(uint16_t nlri_type); extern int evpn_type5_prefix_match(const struct prefix *evpn_pfx, const struct prefix *match_pfx); extern int prefix_match(union prefixconstptr unet, union prefixconstptr upfx); diff --git a/lib/printf/printfcommon.h b/lib/printf/printfcommon.h index b3a7ca0c13..f777be8805 100644 --- a/lib/printf/printfcommon.h +++ b/lib/printf/printfcommon.h @@ -68,7 +68,7 @@ io_print(struct io_state *iop, const CHAR * __restrict ptr, size_t len) { size_t copylen = len; - if (!iop->cb) + if (!iop->cb || !len) return 0; if (iop->avail < copylen) copylen = iop->avail; diff --git a/lib/sha256.c b/lib/sha256.c index ccf260fa7b..08e08eb06b 100644 --- a/lib/sha256.c +++ b/lib/sha256.c @@ -344,7 +344,7 @@ void HMAC__SHA256_Final(unsigned char digest[32], HMAC_SHA256_CTX *ctx) void PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen) { - HMAC_SHA256_CTX PShctx, hctx; + HMAC_SHA256_CTX PShctx = {}, hctx; size_t i; uint8_t ivec[4]; uint8_t U[32]; diff --git a/lib/srv6.h b/lib/srv6.h index 7c8c6b4a65..fb34f861c8 100644 --- a/lib/srv6.h +++ b/lib/srv6.h @@ -14,8 +14,11 @@ #include <arpa/inet.h> #include <netinet/in.h> -#define SRV6_MAX_SIDS 16 +#define SRV6_MAX_SIDS 16 +#define SRV6_MAX_SEGS 8 #define SRV6_LOCNAME_SIZE 256 +#define SRH_BASE_HEADER_LENGTH 8 +#define SRH_SEGMENT_LENGTH 16 #ifdef __cplusplus extern "C" { @@ -74,6 +77,8 @@ enum seg6local_flavor_op { ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID = 4, }; +#define SRV6_SEG_STRLEN 1024 + struct seg6_segs { size_t num_segs; struct in6_addr segs[256]; @@ -89,6 +94,11 @@ struct seg6local_flavors_info { uint8_t lcnode_func_len; }; +struct seg6_seg_stack { + uint8_t num_segs; + struct in6_addr seg[0]; /* 1 or more segs */ +}; + struct seg6local_context { struct in_addr nh4; struct in6_addr nh6; @@ -170,7 +180,7 @@ struct nexthop_srv6 { struct seg6local_context seg6local_ctx; /* SRv6 Headend-behaviour */ - struct in6_addr seg6_segs; + struct seg6_seg_stack *seg6_segs; }; static inline const char *seg6_mode2str(enum seg6_mode_t mode) @@ -206,12 +216,21 @@ static inline bool sid_diff( return !sid_same(a, b); } -static inline bool sid_zero( - const struct in6_addr *a) + +static inline bool sid_zero(const struct seg6_seg_stack *a) +{ + struct in6_addr zero = {}; + + assert(a); + + return sid_same(&a->seg[0], &zero); +} + +static inline bool sid_zero_ipv6(const struct in6_addr *a) { struct in6_addr zero = {}; - return sid_same(a, &zero); + return sid_same(&a[0], &zero); } static inline void *sid_copy(struct in6_addr *dst, diff --git a/lib/table.c b/lib/table.c index 3bf93894ec..dbfc3f8b91 100644 --- a/lib/table.c +++ b/lib/table.c @@ -142,7 +142,7 @@ static void route_common(const struct prefix *n, const struct prefix *p, const uint8_t *pp; uint8_t *newp; - if (n->family == AF_FLOWSPEC) + if (n->family == AF_FLOWSPEC || n->family == AF_LINKSTATE) return prefix_copy(new, p); np = (const uint8_t *)&n->u.prefix; pp = (const uint8_t *)&p->u.prefix; @@ -281,15 +281,22 @@ struct route_node *route_node_get(struct route_table *table, const uint8_t *prefix = &p->u.prefix; node = rn_hash_node_find(&table->hash, &search); - if (node && node->info) + if (node && node->info) { + if (family2afi(p->family) == AFI_LINKSTATE) + prefix_linkstate_ptr_free(p); + return route_lock_node(node); + } match = NULL; node = table->top; while (node && node->p.prefixlen <= prefixlen && prefix_match(&node->p, p)) { - if (node->p.prefixlen == prefixlen) + if (node->p.prefixlen == prefixlen) { + if (family2afi(p->family) == AFI_LINKSTATE) + prefix_linkstate_ptr_free(p); return route_lock_node(node); + } match = node; node = node->link[prefix_bit(prefix, node->p.prefixlen)]; @@ -324,6 +331,9 @@ struct route_node *route_node_get(struct route_table *table, table->count++; route_lock_node(new); + if (family2afi(p->family) == AFI_LINKSTATE) + prefix_linkstate_ptr_free(p); + return new; } diff --git a/lib/typesafe.h b/lib/typesafe.h index a84298b062..93258c5954 100644 --- a/lib/typesafe.h +++ b/lib/typesafe.h @@ -795,13 +795,16 @@ struct thash_head { uint8_t minshift, maxshift; }; -#define _HASH_SIZE(tabshift) \ - ((1U << (tabshift)) >> 1) +#define _HASH_SIZE(tabshift) \ + ({ \ + assume((tabshift) <= 31); \ + (1U << (tabshift)) >> 1; \ + }) #define HASH_SIZE(head) \ _HASH_SIZE((head).tabshift) #define _HASH_KEY(tabshift, val) \ ({ \ - assume((tabshift) >= 2 && (tabshift) <= 33); \ + assume((tabshift) >= 2 && (tabshift) <= 31); \ (val) >> (33 - (tabshift)); \ }) #define HASH_KEY(head, val) \ @@ -2368,8 +2368,7 @@ static void vtysh_read(struct event *thread) printf("result: %d\n", ret); printf("vtysh node: %d\n", vty->node); #endif /* VTYSH_DEBUG */ - - if (vty->pass_fd != -1) { + if (vty->pass_fd >= 0) { memset(vty->pass_fd_status, 0, 4); vty->pass_fd_status[3] = ret; vty->status = VTY_PASSFD; @@ -2387,6 +2386,13 @@ static void vtysh_read(struct event *thread) * => skip vty_event(VTYSH_READ, vty)! */ return; + } else { + assertf(vty->status != VTY_PASSFD, + "%p address=%s passfd=%d", vty, + vty->address, vty->pass_fd); + + /* normalize other invalid values */ + vty->pass_fd = -1; } /* hack for asynchronous "write integrated" diff --git a/lib/zclient.c b/lib/zclient.c index 68a3429822..f8f9cf7aba 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -1061,10 +1061,11 @@ int zapi_nexthop_encode(struct stream *s, const struct zapi_nexthop *api_nh, sizeof(struct seg6local_context)); } - if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6)) - stream_write(s, &api_nh->seg6_segs, - sizeof(struct in6_addr)); - + if (CHECK_FLAG(nh_flags, ZAPI_NEXTHOP_FLAG_SEG6)) { + stream_putc(s, api_nh->seg_num); + stream_put(s, &api_nh->seg6_segs[0], + api_nh->seg_num * sizeof(struct in6_addr)); + } done: return ret; } @@ -1430,9 +1431,18 @@ int zapi_nexthop_decode(struct stream *s, struct zapi_nexthop *api_nh, sizeof(struct seg6local_context)); } - if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6)) - STREAM_GET(&api_nh->seg6_segs, s, - sizeof(struct in6_addr)); + if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6)) { + STREAM_GETC(s, api_nh->seg_num); + if (api_nh->seg_num > SRV6_MAX_SIDS) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: invalid number of SRv6 Segs (%u)", + __func__, api_nh->seg_num); + return -1; + } + + STREAM_GET(&api_nh->seg6_segs[0], s, + api_nh->seg_num * sizeof(struct in6_addr)); + } /* Success */ ret = 0; @@ -2132,8 +2142,8 @@ struct nexthop *nexthop_from_zapi_nexthop(const struct zapi_nexthop *znh) nexthop_add_srv6_seg6local(n, znh->seg6local_action, &znh->seg6local_ctx); - if (!sid_zero(&znh->seg6_segs)) - nexthop_add_srv6_seg6(n, &znh->seg6_segs); + if (znh->seg_num && !sid_zero_ipv6(znh->seg6_segs)) + nexthop_add_srv6_seg6(n, &znh->seg6_segs[0], znh->seg_num); return n; } @@ -2193,10 +2203,14 @@ int zapi_nexthop_from_nexthop(struct zapi_nexthop *znh, sizeof(struct seg6local_context)); } - if (!sid_zero(&nh->nh_srv6->seg6_segs)) { + if (nh->nh_srv6->seg6_segs && nh->nh_srv6->seg6_segs->num_segs && + !sid_zero(nh->nh_srv6->seg6_segs)) { SET_FLAG(znh->flags, ZAPI_NEXTHOP_FLAG_SEG6); - memcpy(&znh->seg6_segs, &nh->nh_srv6->seg6_segs, - sizeof(struct in6_addr)); + znh->seg_num = nh->nh_srv6->seg6_segs->num_segs; + for (i = 0; i < nh->nh_srv6->seg6_segs->num_segs; i++) + memcpy(&znh->seg6_segs[i], + &nh->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); } } diff --git a/lib/zclient.h b/lib/zclient.h index 42c5a5fdac..2a3ce4e488 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -438,7 +438,8 @@ struct zapi_nexthop { struct seg6local_context seg6local_ctx; /* SRv6 Headend-behaviour */ - struct in6_addr seg6_segs; + int seg_num; + struct in6_addr seg6_segs[SRV6_MAX_SEGS]; }; /* diff --git a/lib/zebra.h b/lib/zebra.h index ecc87f58f1..cd0b72834c 100644 --- a/lib/zebra.h +++ b/lib/zebra.h @@ -326,13 +326,14 @@ struct in_pktinfo { #define INADDR_LOOPBACK 0x7f000001 /* Internet address 127.0.0.1. */ #endif -/* Address family numbers from RFC1700. */ +/* Address family numbers. */ typedef enum { AFI_UNSPEC = 0, AFI_IP = 1, AFI_IP6 = 2, AFI_L2VPN = 3, - AFI_MAX = 4 + AFI_LINKSTATE = 4, /* BGP-LS RFC 7752 */ + AFI_MAX = 5, } afi_t; #define IS_VALID_AFI(a) ((a) > AFI_UNSPEC && (a) < AFI_MAX) @@ -347,7 +348,9 @@ typedef enum { SAFI_EVPN = 5, SAFI_LABELED_UNICAST = 6, SAFI_FLOWSPEC = 7, - SAFI_MAX = 8 + SAFI_LINKSTATE = 8, /* BGP-LS RFC 7752 */ + SAFI_LINKSTATE_VPN = 9, /* BGP-LS RFC 7752 */ + SAFI_MAX = 10, } safi_t; #define FOREACH_AFI_SAFI(afi, safi) \ diff --git a/lib/zlog_5424.c b/lib/zlog_5424.c index c15bdece29..9bc1c819a8 100644 --- a/lib/zlog_5424.c +++ b/lib/zlog_5424.c @@ -877,10 +877,15 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) switch (zcf->dst) { case ZLOG_5424_DST_NONE: - break; + return -1; case ZLOG_5424_DST_FD: fd = dup(zcf->fd); + if (fd < 0) { + flog_err_sys(EC_LIB_SYSTEM_CALL, + "failed to dup() log file descriptor: %m (FD limit too low?)"); + return -1; + } optlen = sizeof(sock_type); if (!getsockopt(fd, SOL_SOCKET, SO_TYPE, &sock_type, &optlen)) { @@ -891,7 +896,7 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) case ZLOG_5424_DST_FIFO: if (!zcf->filename) - break; + return -1; if (!zcf->file_nocreate) { frr_with_privs (lib_privs) { @@ -904,7 +909,7 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) if (err == 0) do_chown = true; else if (errno != EEXIST) - break; + return -1; } flags = O_NONBLOCK; @@ -912,7 +917,7 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) case ZLOG_5424_DST_FILE: if (!zcf->filename) - break; + return -1; frr_with_privs (lib_privs) { fd = open(zcf->filename, flags | O_WRONLY | O_APPEND | @@ -924,7 +929,7 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) flog_err_sys(EC_LIB_SYSTEM_CALL, "could not open log file %pSE: %m", zcf->filename); - break; + return -1; } frr_with_privs (lib_privs) { @@ -952,11 +957,11 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) flog_err_sys(EC_LIB_SYSTEM_CALL, "could not open or create log file %pSE: %m", zcf->filename); - break; + return -1; case ZLOG_5424_DST_UNIX: if (!zcf->filename) - break; + return -1; memset(&sa, 0, sizeof(sa)); sa.sun_family = AF_UNIX; @@ -988,6 +993,7 @@ static int zlog_5424_open(struct zlog_cfg_5424 *zcf, int sock_type) "could not connect to log unix path %pSE: %m", zcf->filename); need_reconnect = true; + /* no return -1 here, trigger retry code below */ } else { /* datagram sockets are connectionless, restarting * the receiver may lose some packets but will resume diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c index bf6f0b1614..5baad1754d 100644 --- a/ospfd/ospf_asbr.c +++ b/ospfd/ospf_asbr.c @@ -987,6 +987,7 @@ static void ospf_handle_external_aggr_update(struct ospf *ospf) &aggr->match_extnl_hash, (void *)ospf_aggr_handle_external_info); + ospf_external_aggregator_free(aggr); } else if (aggr->action == OSPF_ROUTE_AGGR_MODIFY) { aggr->action = OSPF_ROUTE_AGGR_NONE; diff --git a/ospfd/ospf_snmp.c b/ospfd/ospf_snmp.c index fcc43e7311..c9aaa9f978 100644 --- a/ospfd/ospf_snmp.c +++ b/ospfd/ospf_snmp.c @@ -906,7 +906,7 @@ static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, area = ospf_area_lookup_by_area_id(ospf, *area_id); if (!area) return NULL; - offset += IN_ADDR_SIZE; + offset++; /* Type. */ *type = *offset; @@ -914,7 +914,7 @@ static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, /* LS ID. */ oid2in_addr(offset, IN_ADDR_SIZE, ls_id); - offset += IN_ADDR_SIZE; + offset++; /* Router ID. */ oid2in_addr(offset, IN_ADDR_SIZE, router_id); @@ -971,7 +971,7 @@ static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, } /* Router ID. */ - offset += IN_ADDR_SIZE; + offset++; offsetlen -= IN_ADDR_SIZE; len = offsetlen; @@ -996,11 +996,11 @@ static struct ospf_lsa *ospfLsdbLookup(struct variable *v, oid *name, /* Fill in value. */ offset = name + v->namelen; oid_copy_in_addr(offset, area_id); - offset += IN_ADDR_SIZE; + offset++; *offset = lsa->data->type; offset++; oid_copy_in_addr(offset, &lsa->data->id); - offset += IN_ADDR_SIZE; + offset++; oid_copy_in_addr(offset, &lsa->data->adv_router); @@ -1106,7 +1106,7 @@ static struct ospf_area_range *ospfAreaRangeLookup(struct variable *v, if (!area) return NULL; - offset += IN_ADDR_SIZE; + offset++; /* Lookup area range. */ oid2in_addr(offset, IN_ADDR_SIZE, range_net); @@ -1135,7 +1135,7 @@ static struct ospf_area_range *ospfAreaRangeLookup(struct variable *v, return NULL; do { - offset += IN_ADDR_SIZE; + offset++; offsetlen -= IN_ADDR_SIZE; len = offsetlen; @@ -1157,7 +1157,7 @@ static struct ospf_area_range *ospfAreaRangeLookup(struct variable *v, /* Fill in value. */ offset = name + v->namelen; oid_copy_in_addr(offset, area_id); - offset += IN_ADDR_SIZE; + offset++; oid_copy_in_addr(offset, range_net); return range; @@ -1560,7 +1560,7 @@ static struct ospf_interface *ospfIfLookup(struct variable *v, oid *name, *length = v->namelen + IN_ADDR_SIZE + 1; offset = name + v->namelen; oid_copy_in_addr(offset, ifaddr); - offset += IN_ADDR_SIZE; + offset++; *offset = *ifindex; return oi; } @@ -1704,7 +1704,7 @@ static struct ospf_interface *ospfIfMetricLookup(struct variable *v, oid *name, *length = v->namelen + IN_ADDR_SIZE + 1 + 1; offset = name + v->namelen; oid_copy_in_addr(offset, ifaddr); - offset += IN_ADDR_SIZE; + offset++; *offset = *ifindex; offset++; *offset = OSPF_SNMP_METRIC_VALUE; @@ -2242,7 +2242,7 @@ static struct ospf_lsa *ospfExtLsdbLookup(struct variable *v, oid *name, /* LS ID. */ oid2in_addr(offset, IN_ADDR_SIZE, ls_id); - offset += IN_ADDR_SIZE; + offset++; /* Router ID. */ oid2in_addr(offset, IN_ADDR_SIZE, router_id); @@ -2270,7 +2270,7 @@ static struct ospf_lsa *ospfExtLsdbLookup(struct variable *v, oid *name, oid2in_addr(offset, len, ls_id); - offset += IN_ADDR_SIZE; + offset++; offsetlen -= IN_ADDR_SIZE; /* Router ID. */ @@ -2293,7 +2293,7 @@ static struct ospf_lsa *ospfExtLsdbLookup(struct variable *v, oid *name, *offset = OSPF_AS_EXTERNAL_LSA; offset++; oid_copy_in_addr(offset, &lsa->data->id); - offset += IN_ADDR_SIZE; + offset++; oid_copy_in_addr(offset, &lsa->data->adv_router); return lsa; diff --git a/pathd/path_pcep_debug.c b/pathd/path_pcep_debug.c index 7d04587b8d..7bff9c7b9c 100644 --- a/pathd/path_pcep_debug.c +++ b/pathd/path_pcep_debug.c @@ -1312,6 +1312,8 @@ void _format_path_hop(int ps, struct path_hop *hop) void _format_pcep_event(int ps, pcep_event *event) { + char buf[32]; + if (event == NULL) { PATHD_FORMAT("NULL\n"); } else { @@ -1320,7 +1322,7 @@ void _format_pcep_event(int ps, pcep_event *event) PATHD_FORMAT("%*sevent_type: %s\n", ps2, "", pcep_event_type_name(event->event_type)); PATHD_FORMAT("%*sevent_time: %s", ps2, "", - ctime(&event->event_time)); + ctime_r(&event->event_time, buf)); if (event->session == NULL) { PATHD_FORMAT("%*ssession: NULL\n", ps2, ""); } else { diff --git a/pbrd/pbr_zebra.c b/pbrd/pbr_zebra.c index ee17a193f4..e8a49b3176 100644 --- a/pbrd/pbr_zebra.c +++ b/pbrd/pbr_zebra.c @@ -335,6 +335,11 @@ void route_add(struct pbr_nexthop_group_cache *pnhgc, struct nexthop_group nhg, "%s: Asked to install unsupported route type: L2VPN", __func__); break; + case AFI_LINKSTATE: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to install unsupported route type: Link-State", + __func__); + break; case AFI_UNSPEC: DEBUGD(&pbr_dbg_zebra, "%s: Asked to install unspecified route type", __func__); @@ -380,6 +385,11 @@ void route_delete(struct pbr_nexthop_group_cache *pnhgc, afi_t afi) "%s: Asked to delete unsupported route type: L2VPN", __func__); break; + case AFI_LINKSTATE: + DEBUGD(&pbr_dbg_zebra, + "%s: Asked to delete unsupported route type: Link-State", + __func__); + break; case AFI_UNSPEC: DEBUGD(&pbr_dbg_zebra, "%s: Asked to delete unspecified route type", __func__); diff --git a/pimd/pim_cmd_common.c b/pimd/pim_cmd_common.c index 4c7bc91213..c3eb49d5f3 100644 --- a/pimd/pim_cmd_common.c +++ b/pimd/pim_cmd_common.c @@ -1078,7 +1078,7 @@ void pim_show_state(struct pim_instance *pim, struct vty *vty, oil_mcastgrp(c_oil)); snprintfrr(src_str, sizeof(src_str), "%pPAs", oil_origin(c_oil)); - ifp_in = pim_if_find_by_vif_index(pim, *oil_parent(c_oil)); + ifp_in = pim_if_find_by_vif_index(pim, *oil_incoming_vif(c_oil)); if (ifp_in) strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); @@ -3723,7 +3723,7 @@ void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, if (pim_channel_oil_empty(c_oil)) strlcat(state_str, "P", sizeof(state_str)); - ifp_in = pim_if_find_by_vif_index(pim, *oil_parent(c_oil)); + ifp_in = pim_if_find_by_vif_index(pim, *oil_incoming_vif(c_oil)); if (ifp_in) strlcpy(in_ifname, ifp_in->name, sizeof(in_ifname)); @@ -3789,7 +3789,7 @@ void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_MUTE) continue; - if (*oil_parent(c_oil) == oif_vif_index && + if (*oil_incoming_vif(c_oil) == oif_vif_index && !pim_mroute_allow_iif_in_oil(c_oil, oif_vif_index)) continue; @@ -3840,7 +3840,7 @@ void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, "inboundInterface", in_ifname); json_object_int_add(json_ifp_out, "iVifI", - *oil_parent(c_oil)); + *oil_incoming_vif(c_oil)); json_object_string_add(json_ifp_out, "outboundInterface", out_ifname); @@ -3989,9 +3989,9 @@ void show_mroute(struct pim_instance *pim, struct vty *vty, pim_sgaddr *sg, json_object_string_add(json_ifp_out, "inboundInterface", in_ifname); - json_object_int_add( - json_ifp_out, "iVifI", - *oil_parent(&s_route->c_oil)); + json_object_int_add(json_ifp_out, "iVifI", + *oil_incoming_vif( + &s_route->c_oil)); json_object_string_add(json_ifp_out, "outboundInterface", out_ifname); diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c index 089b0feb37..7ea6ed9e14 100644 --- a/pimd/pim_mroute.c +++ b/pimd/pim_mroute.c @@ -253,7 +253,7 @@ int pim_mroute_msg_nocache(int fd, struct interface *ifp, const kernmsg *msg) up->channel_oil->cc.pktcnt++; // resolve mfcc_parent prior to mroute_add in channel_add_oif if (up->rpf.source_nexthop.interface && - *oil_parent(up->channel_oil) >= MAXVIFS) { + *oil_incoming_vif(up->channel_oil) >= MAXVIFS) { pim_upstream_mroute_iif_update(up->channel_oil, __func__); } pim_register_join(up); @@ -1042,10 +1042,10 @@ static inline void pim_mroute_copy(struct channel_oil *out, *oil_origin(out) = *oil_origin(in); *oil_mcastgrp(out) = *oil_mcastgrp(in); - *oil_parent(out) = *oil_parent(in); + *oil_incoming_vif(out) = *oil_incoming_vif(in); for (i = 0; i < MAXVIFS; ++i) { - if (*oil_parent(out) == i && + if (*oil_incoming_vif(out) == i && !pim_mroute_allow_iif_in_oil(in, i)) { oil_if_set(out, i, 0); continue; @@ -1080,7 +1080,7 @@ static int pim_mroute_add(struct channel_oil *c_oil, const char *name) * in the case of a (*,G). */ if (pim_addr_is_any(*oil_origin(c_oil))) { - oil_if_set(tmp_oil, *oil_parent(c_oil), 1); + oil_if_set(tmp_oil, *oil_incoming_vif(c_oil), 1); } /* @@ -1090,18 +1090,17 @@ static int pim_mroute_add(struct channel_oil *c_oil, const char *name) * the packets to be forwarded. Then set it * to the correct IIF afterwords. */ - if (!c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) - && *oil_parent(c_oil) != 0) { - *oil_parent(tmp_oil) = 0; + if (!c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) && + *oil_incoming_vif(c_oil) != 0) { + *oil_incoming_vif(tmp_oil) = 0; } /* For IPv6 MRT_ADD_MFC is defined to MRT6_ADD_MFC */ err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC, &tmp_oil->oil, sizeof(tmp_oil->oil)); - if (!err && !c_oil->installed - && !pim_addr_is_any(*oil_origin(c_oil)) - && *oil_parent(c_oil) != 0) { - *oil_parent(tmp_oil) = *oil_parent(c_oil); + if (!err && !c_oil->installed && !pim_addr_is_any(*oil_origin(c_oil)) && + *oil_incoming_vif(c_oil) != 0) { + *oil_incoming_vif(tmp_oil) = *oil_incoming_vif(c_oil); err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_ADD_MFC, &tmp_oil->oil, sizeof(tmp_oil->oil)); } @@ -1158,7 +1157,7 @@ static int pim_upstream_mroute_update(struct channel_oil *c_oil, { char buf[1000]; - if (*oil_parent(c_oil) >= MAXVIFS) { + if (*oil_incoming_vif(c_oil) >= MAXVIFS) { /* the c_oil cannot be installed as a mroute yet */ if (PIM_DEBUG_MROUTE) zlog_debug( @@ -1205,13 +1204,13 @@ int pim_upstream_mroute_add(struct channel_oil *c_oil, const char *name) iif = pim_upstream_get_mroute_iif(c_oil, name); - if (*oil_parent(c_oil) != iif) { - *oil_parent(c_oil) = iif; + if (*oil_incoming_vif(c_oil) != iif) { + *oil_incoming_vif(c_oil) = iif; if (pim_addr_is_any(*oil_origin(c_oil)) && c_oil->up) pim_upstream_all_sources_iif_update(c_oil->up); } else { - *oil_parent(c_oil) = iif; + *oil_incoming_vif(c_oil) = iif; } return pim_upstream_mroute_update(c_oil, name); @@ -1226,11 +1225,11 @@ int pim_upstream_mroute_iif_update(struct channel_oil *c_oil, const char *name) char buf[1000]; iif = pim_upstream_get_mroute_iif(c_oil, name); - if (*oil_parent(c_oil) == iif) { + if (*oil_incoming_vif(c_oil) == iif) { /* no change */ return 0; } - *oil_parent(c_oil) = iif; + *oil_incoming_vif(c_oil) = iif; if (pim_addr_is_any(*oil_origin(c_oil)) && c_oil->up) @@ -1255,10 +1254,10 @@ void pim_static_mroute_iif_update(struct channel_oil *c_oil, int input_vif_index, const char *name) { - if (*oil_parent(c_oil) == input_vif_index) + if (*oil_incoming_vif(c_oil) == input_vif_index) return; - *oil_parent(c_oil) = input_vif_index; + *oil_incoming_vif(c_oil) = input_vif_index; if (input_vif_index == MAXVIFS) pim_mroute_del(c_oil, name); else @@ -1277,8 +1276,8 @@ int pim_mroute_del(struct channel_oil *c_oil, const char *name) if (PIM_DEBUG_MROUTE) { char buf[1000]; struct interface *iifp = - pim_if_find_by_vif_index(pim, - *oil_parent(c_oil)); + pim_if_find_by_vif_index(pim, *oil_incoming_vif( + c_oil)); zlog_debug("%s %s: incoming interface %s for route is %s not installed, do not need to send del req. ", __FILE__, __func__, diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c index 1ae87a1bb3..d18406d55d 100644 --- a/pimd/pim_oil.c +++ b/pimd/pim_oil.c @@ -31,7 +31,7 @@ char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size) sg.src = *oil_origin(c_oil); sg.grp = *oil_mcastgrp(c_oil); - ifp = pim_if_find_by_vif_index(c_oil->pim, *oil_parent(c_oil)); + ifp = pim_if_find_by_vif_index(c_oil->pim, *oil_incoming_vif(c_oil)); snprintfrr(buf, size, "%pSG IIF: %s, OIFS: ", &sg, ifp ? ifp->name : "(?)"); @@ -135,7 +135,7 @@ struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, *oil_mcastgrp(c_oil) = sg->grp; *oil_origin(c_oil) = sg->src; - *oil_parent(c_oil) = MAXVIFS; + *oil_incoming_vif(c_oil) = MAXVIFS; c_oil->oil_ref_count = 1; c_oil->installed = 0; c_oil->up = pim_upstream_find(pim, sg); @@ -164,7 +164,7 @@ void pim_clear_nocache_state(struct pim_interface *pim_ifp) !(PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(c_oil->up->flags))) continue; - if (*oil_parent(c_oil) != pim_ifp->mroute_vif_index) + if (*oil_incoming_vif(c_oil) != pim_ifp->mroute_vif_index) continue; EVENT_OFF(c_oil->up->t_ka_timer); @@ -288,8 +288,7 @@ int pim_channel_del_oif(struct channel_oil *channel_oil, struct interface *oif, if (PIM_DEBUG_MROUTE) { struct interface *iifp = pim_if_find_by_vif_index(pim_ifp->pim, - *oil_parent(channel_oil)); - + *oil_incoming_vif(channel_oil)); zlog_debug("%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u IIF:%s OIF=%s vif_index=%d", __func__, caller, oil_origin(channel_oil), @@ -525,7 +524,7 @@ int pim_channel_add_oif(struct channel_oil *channel_oil, struct interface *oif, /* channel_oil->oil.mfcc_parent != MAXVIFS indicate this entry is not * valid to get installed in kernel. */ - if (*oil_parent(channel_oil) != MAXVIFS) { + if (*oil_incoming_vif(channel_oil) != MAXVIFS) { if (pim_upstream_mroute_add(channel_oil, __func__)) { if (PIM_DEBUG_MROUTE) { zlog_debug( diff --git a/pimd/pim_oil.h b/pimd/pim_oil.h index dc66eaace4..6a5222753b 100644 --- a/pimd/pim_oil.h +++ b/pimd/pim_oil.h @@ -112,7 +112,7 @@ static inline pim_addr *oil_mcastgrp(struct channel_oil *c_oil) return &c_oil->oil.mfcc_mcastgrp; } -static inline vifi_t *oil_parent(struct channel_oil *c_oil) +static inline vifi_t *oil_incoming_vif(struct channel_oil *c_oil) { return &c_oil->oil.mfcc_parent; } @@ -143,7 +143,7 @@ static inline pim_addr *oil_mcastgrp(struct channel_oil *c_oil) return &c_oil->oil.mf6cc_mcastgrp.sin6_addr; } -static inline mifi_t *oil_parent(struct channel_oil *c_oil) +static inline mifi_t *oil_incoming_vif(struct channel_oil *c_oil) { return &c_oil->oil.mf6cc_parent; } diff --git a/pimd/pim_ssmpingd.c b/pimd/pim_ssmpingd.c index dadf29f535..27dbb0d6b4 100644 --- a/pimd/pim_ssmpingd.c +++ b/pimd/pim_ssmpingd.c @@ -185,7 +185,6 @@ static int ssmpingd_socket(pim_addr addr, int port, int mttl) ret = ssmpingd_setsockopt(fd, addr, mttl); if (ret) { zlog_warn("ssmpingd_setsockopt failed"); - close(fd); return -1; } diff --git a/pimd/pim_static.c b/pimd/pim_static.c index f4320f0c62..b9effa26d1 100644 --- a/pimd/pim_static.c +++ b/pimd/pim_static.c @@ -44,7 +44,7 @@ static struct static_route *static_route_new(ifindex_t iif, ifindex_t oif, s_route->c_oil.oil_ref_count = 1; *oil_origin(&s_route->c_oil) = source; *oil_mcastgrp(&s_route->c_oil) = group; - *oil_parent(&s_route->c_oil) = iif; + *oil_incoming_vif(&s_route->c_oil) = iif; oil_if_set(&s_route->c_oil, oif, 1); s_route->c_oil.oif_creation[oif] = pim_time_monotonic_sec(); diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c index fd99e77761..743a047b0a 100644 --- a/pimd/pim_upstream.c +++ b/pimd/pim_upstream.c @@ -1977,7 +1977,7 @@ static bool pim_upstream_kat_start_ok(struct pim_upstream *up) return false; pim_ifp = ifp->info; - if (pim_ifp->mroute_vif_index != *oil_parent(c_oil)) + if (pim_ifp->mroute_vif_index != *oil_incoming_vif(c_oil)) return false; if (pim_if_connected_to_source(up->rpf.source_nexthop.interface, diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c index c9f4d7a5a1..6a026f9947 100644 --- a/pimd/pim_zlookup.c +++ b/pimd/pim_zlookup.c @@ -497,13 +497,13 @@ int pim_zlookup_sg_statistics(struct channel_oil *c_oil) int ret; pim_sgaddr more = {}; struct interface *ifp = - pim_if_find_by_vif_index(c_oil->pim, *oil_parent(c_oil)); + pim_if_find_by_vif_index(c_oil->pim, *oil_incoming_vif(c_oil)); if (PIM_DEBUG_ZEBRA) { more.src = *oil_origin(c_oil); more.grp = *oil_mcastgrp(c_oil); zlog_debug("Sending Request for New Channel Oil Information%pSG VIIF %d(%s:%s)", - &more, *oil_parent(c_oil), + &more, *oil_incoming_vif(c_oil), ifp ? ifp->name : "Unknown", c_oil->pim->vrf->name); } diff --git a/sharpd/sharp_vty.c b/sharpd/sharp_vty.c index f0a75a5fc2..e891c1b6be 100644 --- a/sharpd/sharp_vty.c +++ b/sharpd/sharp_vty.c @@ -402,7 +402,7 @@ DEFPY (install_seg6_routes, sg.r.nhop.gate.ipv6 = seg6_nh6; sg.r.nhop.vrf_id = vrf->vrf_id; sg.r.nhop_group.nexthop = &sg.r.nhop; - nexthop_add_srv6_seg6(&sg.r.nhop, &seg6_seg); + nexthop_add_srv6_seg6(&sg.r.nhop, &seg6_seg, 1); sg.r.vrf_id = vrf->vrf_id; sharp_install_routes_helper(&prefix, sg.r.vrf_id, sg.r.inst, 0, diff --git a/staticd/static_nb.c b/staticd/static_nb.c index d8b5b167cf..1c69a58035 100644 --- a/staticd/static_nb.c +++ b/staticd/static_nb.c @@ -75,6 +75,21 @@ const struct frr_yang_module_info frr_staticd_info = { } }, { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy, + + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy, + } + }, + { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry", .cbs = { .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create, @@ -183,6 +198,20 @@ const struct frr_yang_module_info frr_staticd_info = { } }, { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy, + } + }, + { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry", .cbs = { .create = routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create, diff --git a/staticd/static_nb.h b/staticd/static_nb.h index ce3644076c..9f80653b76 100644 --- a/staticd/static_nb.h +++ b/staticd/static_nb.h @@ -35,6 +35,14 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_color_destroy( struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( struct nb_cb_create_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( @@ -80,6 +88,14 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_color_destroy( struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( struct nb_cb_create_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_destroy( @@ -147,6 +163,10 @@ int routing_control_plane_protocols_name_validate( #define FRR_STATIC_ROUTE_NHLB_KEY_XPATH "/entry[id='%u']/label" +#define FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH "/srv6-segs-stack" + +#define FRR_STATIC_ROUTE_NH_SRV6_KEY_SEG_XPATH "/entry[id='%u']/seg" + /* route-list/srclist */ #define FRR_S_ROUTE_SRC_INFO_KEY_XPATH \ "/frr-routing:routing/control-plane-protocols/" \ diff --git a/staticd/static_nb_config.c b/staticd/static_nb_config.c index 6673cce108..ede2e38754 100644 --- a/staticd/static_nb_config.c +++ b/staticd/static_nb_config.c @@ -209,6 +209,98 @@ static bool static_nexthop_destroy(struct nb_cb_destroy_args *args) return NB_OK; } +static int nexthop_srv6_segs_stack_entry_create(struct nb_cb_create_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + memset(&nh->snh_seg.seg[index], 0, sizeof(struct in6_addr)); + nh->snh_seg.num_segs++; + break; + } + + return NB_OK; +} + +static int nexthop_srv6_segs_stack_entry_destroy(struct nb_cb_destroy_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + int old_num_segs; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(args->dnode); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + index = pos - 1; + old_num_segs = nh->snh_seg.num_segs; + memset(&nh->snh_seg.seg[index], 0, sizeof(struct in6_addr)); + nh->snh_seg.num_segs--; + + if (old_num_segs != nh->snh_seg.num_segs) + nh->state = STATIC_START; + break; + } + + return NB_OK; +} + +static int static_nexthop_srv6_segs_modify(struct nb_cb_modify_args *args) +{ + struct static_nexthop *nh; + uint32_t pos; + uint8_t index; + struct in6_addr old_seg; + struct in6_addr cli_seg; + + nh = nb_running_get_entry(args->dnode, NULL, true); + pos = yang_get_list_pos(lyd_parent(args->dnode)); + if (!pos) { + flog_warn(EC_LIB_NB_CB_CONFIG_APPLY, + "libyang returns invalid seg position"); + return NB_ERR; + } + /* Mapping to array = list-index -1 */ + index = pos - 1; + + old_seg = nh->snh_seg.seg[index]; + yang_dnode_get_ipv6(&cli_seg, args->dnode, NULL); + + memcpy(&nh->snh_seg.seg[index], &cli_seg, sizeof(struct in6_addr)); + + if (memcmp(&old_seg, &nh->snh_seg.seg[index], + sizeof(struct in6_addr)) != 0) + nh->state = STATIC_START; + + return NB_OK; +} + static int nexthop_mpls_label_stack_entry_create(struct nb_cb_create_args *args) { struct static_nexthop *nh; @@ -382,6 +474,13 @@ static int static_nexthop_bh_type_modify(struct nb_cb_modify_args *args) nh_vrf = yang_dnode_get_string(args->dnode, "../vrf"); if (nh_ifname && nh_vrf) { struct vrf *vrf = vrf_lookup_by_name(nh_vrf); + + if (!vrf) { + snprintf(args->errmsg, args->errmsg_len, + "nexthop vrf %s not found", nh_vrf); + return NB_ERR_VALIDATION; + } + struct interface *ifp = if_lookup_by_name(nh_ifname, vrf->vrf_id); @@ -636,6 +735,60 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_pa /* * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_srv6_segs_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_srv6_segs_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_srv6_segs_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_srv6_segs_stack_entry_destroy() will take care + * to reset the seg vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry */ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( @@ -1014,6 +1167,60 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_sr /* * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_create( + struct nb_cb_create_args *args) +{ + return nexthop_srv6_segs_stack_entry_create(args); +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_destroy( + struct nb_cb_destroy_args *args) +{ + return nexthop_srv6_segs_stack_entry_destroy(args); +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/srv6-segs-stack/entry/seg + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_modify( + struct nb_cb_modify_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + if (static_nexthop_srv6_segs_modify(args) != NB_OK) + return NB_ERR; + break; + } + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_srv6_segs_stack_entry_seg_destroy( + struct nb_cb_destroy_args *args) +{ + /* + * No operation is required in this call back. + * nexthop_mpls_seg_stack_entry_destroy() will take care + * to reset the seg vaue. + */ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } + return NB_OK; +} + +/* + * XPath: * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/route-list/src-list/path-list/frr-nexthops/nexthop/mpls-label-stack/entry */ int routing_control_plane_protocols_control_plane_protocol_staticd_route_list_src_list_path_list_frr_nexthops_nexthop_mpls_label_stack_entry_create( diff --git a/staticd/static_routes.h b/staticd/static_routes.h index 3be6fd6b14..548148b187 100644 --- a/staticd/static_routes.h +++ b/staticd/static_routes.h @@ -9,6 +9,7 @@ #include "lib/bfd.h" #include "lib/mpls.h" +#include "lib/srv6.h" #include "table.h" #include "memory.h" @@ -27,6 +28,12 @@ struct static_nh_label { mpls_label_t label[MPLS_MAX_LABELS]; }; +/* Static route seg information */ +struct static_nh_seg { + int num_segs; + struct in6_addr seg[SRV6_MAX_SIDS]; +}; + enum static_blackhole_type { STATIC_BLACKHOLE_DROP = 0, STATIC_BLACKHOLE_NULL, @@ -129,6 +136,9 @@ struct static_nexthop { /* Label information */ struct static_nh_label snh_label; + /* SRv6 Seg information */ + struct static_nh_seg snh_seg; + /* * Whether to pretend the nexthop is directly attached to the specified * link. Only meaningful when both a gateway address and interface name diff --git a/staticd/static_vty.c b/staticd/static_vty.c index 16e4cb7d83..4afc250493 100644 --- a/staticd/static_vty.c +++ b/staticd/static_vty.c @@ -47,6 +47,7 @@ struct static_route_args { const char *source; const char *gateway; const char *interface_name; + const char *segs; const char *flag; const char *tag; const char *distance; @@ -73,12 +74,16 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) char xpath_nexthop[XPATH_MAXLEN]; char xpath_mpls[XPATH_MAXLEN]; char xpath_label[XPATH_MAXLEN]; + char xpath_segs[XPATH_MAXLEN]; + char xpath_seg[XPATH_MAXLEN]; char ab_xpath[XPATH_MAXLEN]; char buf_prefix[PREFIX_STRLEN]; char buf_src_prefix[PREFIX_STRLEN] = {}; char buf_nh_type[PREFIX_STRLEN] = {}; char buf_tag[PREFIX_STRLEN]; uint8_t label_stack_id = 0; + uint8_t segs_stack_id = 0; + const char *buf_gate_str; uint8_t distance = ZEBRA_STATIC_DISTANCE_DEFAULT; route_tag_t tag = 0; @@ -126,6 +131,7 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) assert(!!str2prefix(args->source, &src)); break; case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_UNSPEC: case AFI_MAX: break; @@ -345,7 +351,39 @@ static int static_route_nb_run(struct vty *vty, struct static_route_args *args) nb_cli_enqueue_change(vty, xpath_mpls, NB_OP_DESTROY, NULL); } + if (args->segs) { + /* copy of seg string (start) */ + char *ostr; + /* pointer to next segment */ + char *nump; + strlcpy(xpath_segs, xpath_nexthop, sizeof(xpath_segs)); + strlcat(xpath_segs, FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH, + sizeof(xpath_segs)); + + nb_cli_enqueue_change(vty, xpath_segs, NB_OP_DESTROY, + NULL); + + ostr = XSTRDUP(MTYPE_TMP, args->segs); + while ((nump = strsep(&ostr, "/")) != NULL) { + snprintf(ab_xpath, sizeof(ab_xpath), + FRR_STATIC_ROUTE_NH_SRV6_KEY_SEG_XPATH, + segs_stack_id); + strlcpy(xpath_seg, xpath_segs, + sizeof(xpath_seg)); + strlcat(xpath_seg, ab_xpath, sizeof(xpath_seg)); + nb_cli_enqueue_change(vty, xpath_seg, + NB_OP_MODIFY, nump); + segs_stack_id++; + } + XFREE(MTYPE_TMP, ostr); + } else { + strlcpy(xpath_segs, xpath_nexthop, sizeof(xpath_segs)); + strlcat(xpath_segs, FRR_STATIC_ROUTE_NH_SRV6_SEGS_XPATH, + sizeof(xpath_segs)); + nb_cli_enqueue_change(vty, xpath_segs, NB_OP_DESTROY, + NULL); + } if (args->bfd) { char xpath_bfd[XPATH_MAXLEN]; @@ -951,9 +989,8 @@ DEFPY_YANG(ipv6_route_blackhole_vrf, return static_route_nb_run(vty, &args); } -DEFPY_YANG(ipv6_route_address_interface, - ipv6_route_address_interface_cmd, - "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ +DEFPY_YANG(ipv6_route_address_interface, ipv6_route_address_interface_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ X:X::X:X$gate \ <INTERFACE|Null0>$ifname \ [{ \ @@ -966,33 +1003,28 @@ DEFPY_YANG(ipv6_route_address_interface, |onlink$onlink \ |color (1-4294967295) \ |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ }]", - NO_STR - IPV6_STR - "Establish static routes\n" - "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" - "IPv6 source-dest route\n" - "IPv6 source prefix\n" - "IPv6 gateway address\n" - "IPv6 gateway interface name\n" - "Null interface\n" - "Set tag for this route\n" - "Tag value\n" - "Distance value for this prefix\n" - VRF_CMD_HELP_STR - MPLS_LABEL_HELPSTR - "Table to configure\n" - "The table number to configure\n" - VRF_CMD_HELP_STR - "Treat the nexthop as directly attached to the interface\n" - "SR-TE color\n" - "The SR-TE color to configure\n" - BFD_INTEGRATION_STR - BFD_INTEGRATION_MULTI_HOP_STR - BFD_INTEGRATION_SOURCE_STR - BFD_INTEGRATION_SOURCEV4_STR - BFD_PROFILE_STR - BFD_PROFILE_NAME_STR) + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") { struct static_route_args args = { .delete = !!no, @@ -1014,14 +1046,15 @@ DEFPY_YANG(ipv6_route_address_interface, .bfd_multi_hop = !!bfd_multi_hop, .bfd_source = bfd_source_str, .bfd_profile = bfd_profile, + .segs = segments, }; return static_route_nb_run(vty, &args); } DEFPY_YANG(ipv6_route_address_interface_vrf, - ipv6_route_address_interface_vrf_cmd, - "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ + ipv6_route_address_interface_vrf_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ X:X::X:X$gate \ <INTERFACE|Null0>$ifname \ [{ \ @@ -1033,32 +1066,28 @@ DEFPY_YANG(ipv6_route_address_interface_vrf, |onlink$onlink \ |color (1-4294967295) \ |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ }]", - NO_STR - IPV6_STR - "Establish static routes\n" - "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" - "IPv6 source-dest route\n" - "IPv6 source prefix\n" - "IPv6 gateway address\n" - "IPv6 gateway interface name\n" - "Null interface\n" - "Set tag for this route\n" - "Tag value\n" - "Distance value for this prefix\n" - MPLS_LABEL_HELPSTR - "Table to configure\n" - "The table number to configure\n" - VRF_CMD_HELP_STR - "Treat the nexthop as directly attached to the interface\n" - "SR-TE color\n" - "The SR-TE color to configure\n" - BFD_INTEGRATION_STR - BFD_INTEGRATION_MULTI_HOP_STR - BFD_INTEGRATION_SOURCE_STR - BFD_INTEGRATION_SOURCEV4_STR - BFD_PROFILE_STR - BFD_PROFILE_NAME_STR) + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR + "Treat the nexthop as directly attached to the interface\n" + "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") { struct static_route_args args = { .delete = !!no, @@ -1080,14 +1109,14 @@ DEFPY_YANG(ipv6_route_address_interface_vrf, .bfd_multi_hop = !!bfd_multi_hop, .bfd_source = bfd_source_str, .bfd_profile = bfd_profile, + .segs = segments, }; return static_route_nb_run(vty, &args); } -DEFPY_YANG(ipv6_route, - ipv6_route_cmd, - "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ +DEFPY_YANG(ipv6_route, ipv6_route_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ <X:X::X:X$gate|<INTERFACE|Null0>$ifname> \ [{ \ tag (1-4294967295) \ @@ -1098,32 +1127,26 @@ DEFPY_YANG(ipv6_route, |nexthop-vrf NAME \ |color (1-4294967295) \ |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ }]", - NO_STR - IPV6_STR - "Establish static routes\n" - "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" - "IPv6 source-dest route\n" - "IPv6 source prefix\n" - "IPv6 gateway address\n" - "IPv6 gateway interface name\n" - "Null interface\n" - "Set tag for this route\n" - "Tag value\n" - "Distance value for this prefix\n" - VRF_CMD_HELP_STR - MPLS_LABEL_HELPSTR - "Table to configure\n" - "The table number to configure\n" - VRF_CMD_HELP_STR - "SR-TE color\n" - "The SR-TE color to configure\n" - BFD_INTEGRATION_STR - BFD_INTEGRATION_MULTI_HOP_STR - BFD_INTEGRATION_SOURCE_STR - BFD_INTEGRATION_SOURCEV4_STR - BFD_PROFILE_STR - BFD_PROFILE_NAME_STR) + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" VRF_CMD_HELP_STR MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") { struct static_route_args args = { .delete = !!no, @@ -1144,14 +1167,15 @@ DEFPY_YANG(ipv6_route, .bfd_multi_hop = !!bfd_multi_hop, .bfd_source = bfd_source_str, .bfd_profile = bfd_profile, + .segs = segments, + }; return static_route_nb_run(vty, &args); } -DEFPY_YANG(ipv6_route_vrf, - ipv6_route_vrf_cmd, - "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ +DEFPY_YANG(ipv6_route_vrf, ipv6_route_vrf_cmd, + "[no] ipv6 route X:X::X:X/M$prefix [from X:X::X:X/M] \ <X:X::X:X$gate|<INTERFACE|Null0>$ifname> \ [{ \ tag (1-4294967295) \ @@ -1161,31 +1185,26 @@ DEFPY_YANG(ipv6_route_vrf, |nexthop-vrf NAME \ |color (1-4294967295) \ |bfd$bfd [{multi-hop$bfd_multi_hop|source X:X::X:X$bfd_source|profile BFDPROF$bfd_profile}] \ + |segments WORD \ }]", - NO_STR - IPV6_STR - "Establish static routes\n" - "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" - "IPv6 source-dest route\n" - "IPv6 source prefix\n" - "IPv6 gateway address\n" - "IPv6 gateway interface name\n" - "Null interface\n" - "Set tag for this route\n" - "Tag value\n" - "Distance value for this prefix\n" - MPLS_LABEL_HELPSTR - "Table to configure\n" - "The table number to configure\n" - VRF_CMD_HELP_STR - "SR-TE color\n" - "The SR-TE color to configure\n" - BFD_INTEGRATION_STR - BFD_INTEGRATION_MULTI_HOP_STR - BFD_INTEGRATION_SOURCE_STR - BFD_INTEGRATION_SOURCEV4_STR - BFD_PROFILE_STR - BFD_PROFILE_NAME_STR) + NO_STR IPV6_STR + "Establish static routes\n" + "IPv6 destination prefix (e.g. 3ffe:506::/32)\n" + "IPv6 source-dest route\n" + "IPv6 source prefix\n" + "IPv6 gateway address\n" + "IPv6 gateway interface name\n" + "Null interface\n" + "Set tag for this route\n" + "Tag value\n" + "Distance value for this prefix\n" MPLS_LABEL_HELPSTR + "Table to configure\n" + "The table number to configure\n" VRF_CMD_HELP_STR "SR-TE color\n" + "The SR-TE color to configure\n" BFD_INTEGRATION_STR + BFD_INTEGRATION_MULTI_HOP_STR BFD_INTEGRATION_SOURCE_STR + BFD_INTEGRATION_SOURCEV4_STR BFD_PROFILE_STR + BFD_PROFILE_NAME_STR "Value of segs\n" + "Segs (SIDs)\n") { struct static_route_args args = { .delete = !!no, @@ -1206,6 +1225,7 @@ DEFPY_YANG(ipv6_route_vrf, .bfd_multi_hop = !!bfd_multi_hop, .bfd_source = bfd_source_str, .bfd_profile = bfd_profile, + .segs = segments, }; return static_route_nb_run(vty, &args); @@ -1252,6 +1272,39 @@ static int mpls_label_iter_cb(const struct lyd_node *dnode, void *arg) return YANG_ITER_CONTINUE; } +struct srv6_seg_iter { + struct vty *vty; + bool first; +}; + +static int srv6_seg_iter_cb(const struct lyd_node *dnode, void *arg) +{ + struct srv6_seg_iter *iter = arg; + char buffer[INET6_ADDRSTRLEN]; + struct in6_addr cli_seg; + + if (yang_dnode_exists(dnode, "./seg")) { + if (iter->first) { + yang_dnode_get_ipv6(&cli_seg, dnode, "./seg"); + if (inet_ntop(AF_INET6, &cli_seg, buffer, + INET6_ADDRSTRLEN) == NULL) { + return 1; + } + vty_out(iter->vty, " segments %s", buffer); + } else { + yang_dnode_get_ipv6(&cli_seg, dnode, "./seg"); + if (inet_ntop(AF_INET6, &cli_seg, buffer, + INET6_ADDRSTRLEN) == NULL) { + return 1; + } + vty_out(iter->vty, "/%s", buffer); + } + iter->first = false; + } + + return YANG_ITER_CONTINUE; +} + static void nexthop_cli_show(struct vty *vty, const struct lyd_node *route, const struct lyd_node *src, const struct lyd_node *path, @@ -1266,6 +1319,7 @@ static void nexthop_cli_show(struct vty *vty, const struct lyd_node *route, uint32_t tag; uint8_t distance; struct mpls_label_iter iter; + struct srv6_seg_iter seg_iter; const char *nexthop_vrf; uint32_t table_id; bool onlink; @@ -1342,6 +1396,11 @@ static void nexthop_cli_show(struct vty *vty, const struct lyd_node *route, yang_dnode_iterate(mpls_label_iter_cb, &iter, nexthop, "./mpls-label-stack/entry"); + seg_iter.vty = vty; + seg_iter.first = true; + yang_dnode_iterate(srv6_seg_iter_cb, &seg_iter, nexthop, + "./srv6-segs-stack/entry"); + nexthop_vrf = yang_dnode_get_string(nexthop, "./vrf"); if (strcmp(vrf, nexthop_vrf)) vty_out(vty, " nexthop-vrf %s", nexthop_vrf); diff --git a/staticd/static_zebra.c b/staticd/static_zebra.c index 4f3ccde53d..6abbdadc08 100644 --- a/staticd/static_zebra.c +++ b/staticd/static_zebra.c @@ -499,6 +499,21 @@ extern void static_zebra_route_add(struct static_path *pn, bool install) for (i = 0; i < api_nh->label_num; i++) api_nh->labels[i] = nh->snh_label.label[i]; } + if (nh->snh_seg.num_segs) { + int i; + + api_nh->seg6local_action = + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_SEG6); + SET_FLAG(api.flags, ZEBRA_FLAG_ALLOW_RECURSION); + api.safi = SAFI_UNICAST; + + api_nh->seg_num = nh->snh_seg.num_segs; + for (i = 0; i < api_nh->seg_num; i++) + memcpy(&api_nh->seg6_segs[i], + &nh->snh_seg.seg[i], + sizeof(struct in6_addr)); + } nh_num++; } diff --git a/tests/bgpd/test_mpath.c b/tests/bgpd/test_mpath.c index bc2c1e8a44..ebbe3ac3e2 100644 --- a/tests/bgpd/test_mpath.c +++ b/tests/bgpd/test_mpath.c @@ -467,9 +467,10 @@ int main(void) { int pass_count, fail_count; time_t cur_time; + char buf[32]; time(&cur_time); - printf("BGP Multipath Tests Run at %s", ctime(&cur_time)); + printf("BGP Multipath Tests Run at %s", ctime_r(&cur_time, buf)); if (global_test_init() != 0) { printf("Global init failed. Terminating.\n"); exit(1); diff --git a/tests/bgpd/test_peer_attr.c b/tests/bgpd/test_peer_attr.c index bc6eba9069..761b23bf97 100644 --- a/tests/bgpd/test_peer_attr.c +++ b/tests/bgpd/test_peer_attr.c @@ -660,6 +660,8 @@ static const char *str_from_afi(afi_t afi) return "ipv6"; case AFI_L2VPN: return "l2vpn"; + case AFI_LINKSTATE: + return "link-state"; case AFI_MAX: case AFI_UNSPEC: return "bad-value"; diff --git a/tests/topotests/bgp_dynamic_capability/r1/bgpd.conf b/tests/topotests/bgp_dynamic_capability/r1/frr.conf index 3d2c611775..50280a9126 100644 --- a/tests/topotests/bgp_dynamic_capability/r1/bgpd.conf +++ b/tests/topotests/bgp_dynamic_capability/r1/frr.conf @@ -1,6 +1,9 @@ ! !debug bgp neighbor ! +int r1-eth0 + ip address 192.168.1.1/24 +! router bgp 65001 no bgp ebgp-requires-policy bgp graceful-restart diff --git a/tests/topotests/bgp_dynamic_capability/r1/zebra.conf b/tests/topotests/bgp_dynamic_capability/r1/zebra.conf deleted file mode 100644 index b29940f46a..0000000000 --- a/tests/topotests/bgp_dynamic_capability/r1/zebra.conf +++ /dev/null @@ -1,4 +0,0 @@ -! -int r1-eth0 - ip address 192.168.1.1/24 -! diff --git a/tests/topotests/bgp_dynamic_capability/r2/bgpd.conf b/tests/topotests/bgp_dynamic_capability/r2/frr.conf index 46f0b21a14..16b83a5c58 100644 --- a/tests/topotests/bgp_dynamic_capability/r2/bgpd.conf +++ b/tests/topotests/bgp_dynamic_capability/r2/frr.conf @@ -1,6 +1,12 @@ ! !debug bgp neighbor ! +int lo + ip address 10.10.10.10/32 +! +int r2-eth0 + ip address 192.168.1.2/24 +! router bgp 65002 bgp graceful-restart bgp long-lived stale-time 20 @@ -9,4 +15,8 @@ router bgp 65002 neighbor 192.168.1.1 timers 1 3 neighbor 192.168.1.1 timers connect 1 neighbor 192.168.1.1 capability dynamic + ! + address-family ipv4 unicast + redistribute connected + exit-address-family ! diff --git a/tests/topotests/bgp_dynamic_capability/r2/zebra.conf b/tests/topotests/bgp_dynamic_capability/r2/zebra.conf deleted file mode 100644 index cffe827363..0000000000 --- a/tests/topotests/bgp_dynamic_capability/r2/zebra.conf +++ /dev/null @@ -1,4 +0,0 @@ -! -int r2-eth0 - ip address 192.168.1.2/24 -! diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py index 846a68ea27..db1eb2723b 100644 --- a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_graceful_restart.py @@ -38,13 +38,8 @@ def setup_module(mod): router_list = tgen.routers() - for i, (rname, router) in enumerate(router_list.items(), 1): - router.load_config( - TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) - ) - router.load_config( - TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) - ) + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) tgen.start_router() @@ -85,8 +80,6 @@ def test_bgp_dynamic_capability_graceful_restart(): } }, }, - "connectionsEstablished": 1, - "connectionsDropped": 0, } } return topotest.json_cmp(output, expected) @@ -101,6 +94,9 @@ def test_bgp_dynamic_capability_graceful_restart(): "Change Graceful-Restart restart-time, LLGR stale-time and check if they changed dynamically" ) + # Clear message stats to check if we receive a notification or not after we + # change the settings fo LLGR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") r2.vtysh_cmd( """ configure terminal @@ -132,8 +128,10 @@ def test_bgp_dynamic_capability_graceful_restart(): } }, }, - "connectionsEstablished": 1, - "connectionsDropped": 0, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 2, + }, } } return topotest.json_cmp(output, expected) @@ -150,6 +148,9 @@ def test_bgp_dynamic_capability_graceful_restart(): "Disable Graceful-Restart notification support, and check if it's changed dynamically" ) + # Clear message stats to check if we receive a notification or not after we + # change the settings fo LLGR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") r2.vtysh_cmd( """ configure terminal @@ -180,8 +181,10 @@ def test_bgp_dynamic_capability_graceful_restart(): } }, }, - "connectionsEstablished": 1, - "connectionsDropped": 0, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, } } return topotest.json_cmp(output, expected) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py index 9f37440566..da45110e39 100644 --- a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_role.py @@ -36,13 +36,8 @@ def setup_module(mod): router_list = tgen.routers() - for i, (rname, router) in enumerate(router_list.items(), 1): - router.load_config( - TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) - ) - router.load_config( - TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) - ) + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) tgen.start_router() @@ -71,8 +66,6 @@ def test_bgp_dynamic_capability_role(): "neighborCapabilities": { "dynamic": "advertisedAndReceived", }, - "connectionsEstablished": 1, - "connectionsDropped": 0, } } return topotest.json_cmp(output, expected) @@ -85,6 +78,9 @@ def test_bgp_dynamic_capability_role(): step("Set local-role and check if it's exchanged dynamically") + # Clear message stats to check if we receive a notification or not after we + # change the settings fo LLGR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") r1.vtysh_cmd( """ configure terminal @@ -112,8 +108,10 @@ def test_bgp_dynamic_capability_role(): "dynamic": "advertisedAndReceived", "role": "advertisedAndReceived", }, - "connectionsEstablished": 1, - "connectionsDropped": 0, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, } } return topotest.json_cmp(output, expected) diff --git a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py index eb81ffeb69..d1069a876b 100644 --- a/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py +++ b/tests/topotests/bgp_dynamic_capability/test_bgp_dynamic_capability_software_version.py @@ -36,13 +36,8 @@ def setup_module(mod): router_list = tgen.routers() - for i, (rname, router) in enumerate(router_list.items(), 1): - router.load_config( - TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) - ) - router.load_config( - TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) - ) + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) tgen.start_router() @@ -73,8 +68,6 @@ def test_bgp_dynamic_capability_software_version(): "receivedSoftwareVersion": None, }, }, - "connectionsEstablished": 1, - "connectionsDropped": 0, } } return topotest.json_cmp(output, expected) @@ -87,6 +80,9 @@ def test_bgp_dynamic_capability_software_version(): step("Enable software version capability and check if it's exchanged dynamically") + # Clear message stats to check if we receive a notification or not after we + # change the settings fo LLGR. + r1.vtysh_cmd("clear bgp 192.168.1.2 message-stats") r1.vtysh_cmd( """ configure terminal @@ -133,8 +129,10 @@ def test_bgp_dynamic_capability_software_version(): "receivedSoftwareVersion": rcv, }, }, - "connectionsEstablished": 1, - "connectionsDropped": 0, + "messageStats": { + "notificationsRecv": 0, + "capabilityRecv": 1, + }, } } return topotest.json_cmp(output, expected) diff --git a/tests/topotests/bgp_linkstate_topo1/__init__.py b/tests/topotests/bgp_linkstate_topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/__init__.py diff --git a/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.cfg b/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.cfg new file mode 100644 index 0000000000..3e4ff6cde3 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.cfg @@ -0,0 +1,215 @@ +// Check content with +// cat bgp_injector.cfg | sed -e 's|//.*||g' | jq . +{ +"my_as": 65001, +"hold_time": 30, +"bgp_identifier": "192.0.2.1", +"local_address": "192.0.2.1", +"peer_address": "192.0.2.2", +"mss": 4000, +"port": 179, +"path_attributes": +{ + "as-path": "65001", + "next-hop": "192.0.2.1", + "origin": 0 +}, +"link_states": +[ + { + "nlri": + { + "proto": "01", // IS-IS L1 + "id": "0000000000000020", + "type": "0002", // Link-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "515": "000000001001" // router-id: 0000.0000.1001 + }, + "257": { // Remote Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "515": "000000001000" // router-id: 0000.0000.1000 + }, + "259": "0a010001", // IPv4 interface address TLV + "260": "0a010002", // IPv4 Neighbor address TLV + "261": "20010000000000000000000000000001", // IPv6 interface address TLV + "262": "20010000000000000000000000000002", // IPv6 Neighbor address TLV + "263": "00000002" // MT-ID + }, + "attr": + { + "1028": "01010101", //IPv4 Router-ID of Local Node TLV + "1030": "0a0a0a0a", //IPv4 Router-ID of Remote Node TLV + "1089": "4d2817c8", // Maximum link bandwidth TLV 1410.07 Mbps + "1090": "4d2817c8", // Maximum reservable link bandwidth TLV 1410.07 Mbps + "1091": "4d2817c84d2817c84d2817c84d2817c84d2817c84d2817c84d2817c84d2817c8", // Unreserved bandwidth TLV + "1092": "00000064", // TE Default Metric TLV + "1095": "00000a", // Metric TLV + // Adjacency SID TLV + // Flags: 0x30, Value Flag (V), Local Flag (L) + // Weight: 0 + // .... 0000 0011 1010 1001 1000 = SID/Label: 15000 + "1099": "30000000003a98", + //Unidirectional Link Delay TLV + // TE Metric Flags: 0x00 + // Delay: 8500 + "1114": "00002134", + //Min/Max Unidirectional Link Delay TLV + // TE Metric Flags: 0x00 + // Min Delay: 8000 + // Reserved: 0x00 + // Max Delay: 9000 + "1115": "00001f4000002328", + "1122": { //Application-Specific Link Attributes TLV + // Type: 1122 + // Length: 48 + // SABM Length: 4 + // UDABM Length: 4 + // Reserved: 0x0000 + // Standard Application Identifier Bit Mask: 0x10000000, Flexible Algorithm (X) + // User-Defined Application Identifier Bit Mask: 00 00 00 00 + "0": "040400001000000000000000", // 0 means encode data directly + "1088": "00000001", // Administrative group (color) TLV + "1092": "00000064", // TE Default Metric TLV + "1115": "00001f4000002328", // Min/Max Unidirectional Link Delay TLV + "1173": "00000001"// Extended Administrative Group TLV + } + } + }, + { + "nlri": + { + "proto": "01", // IS-IS L1 + "id": "0000000000000020", + "type": "0001", // Node-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "515": "00000000100300" // router-id: 0000.0000.1003.00 + } + }, + "attr": + { + "0": "0107000400000002010a00020108040200027233040300034910000404000403030303040a000cc000000fa004890003004e20040b0003008082040c000c00000003e804890003003a98" + } + }, + { + "nlri": + { + "proto": "03", // OSPFv2 + "id": "0000000000000020", + "type": "0001", // Node-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "514": "00000000", // Area 0 + "515": "0a0a0a0a" // router-id: 10.10.10.10 + } + }, + "attr": // Attributes to test display + { + "1152": "80", // IGP flags + "1097": "000b00020123000500020123", //opaque Link TLV + "1105": "d123ed", // RTM capabilities + "1172": "dc12fceb04400004000f0001", // L2 bundle member attributes + "1117": "00FFFFFF", //Packet Loss 50.331642% + "1039": "820000800411000400000001041500040000000a0413000480000000", //flex-algo definition + "1044": "82800000000003e8", // flex-algo prefix metric + "1121": "", // graceful link shutdown + "1171": "00120012000000000000000000120012", // source router IPv6 + "1871": "00120012000000000000000000" // Unknown TLV + } + }, + { + "nlri": + { + "proto": "03", // OSPFv2 + "id": "0000000000000020", + "type": "0001", // Node-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "514": "00000000", // Area 0 + "515": "0a0a0a0a01010101" // router-id: 10.10.10.10:1.1.1.1 + } + } + }, + { + "nlri": + { + "proto": "03", // OSPFv2 + "id": "0000000000000020", + "type": "0003", // IPv4-topo-prefix-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "514": "00000000", // Area 0 + "515": "0a0a0a0a01010101" // router-id: 10.10.10.10:1.1.1.1 + }, + "265": "18590a0b" // IP Reachability Information TLV (89.10.11.0/24) + } + }, + { + "nlri": + { + "proto": "02", // IS-IS L2 + "id": "0000000000000020", + "type": "0004", // IPv6-topo-prefix-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "515": "00000000100300" // router-id: 0000.0000.1003.00 + }, + "263": "0002", // MT-ID + // IP Reachability Information TLV (12:12::12:12/128) + "265": "8000120012000000000000000000120012" + } + }, + { + "nlri": + { + "proto": "06", // OSPFv3 + "id": "0000000000000020", + "type": "0004", // IPv6-topo-prefix-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "0000fde9", // AS 65001 + "513": "00000000", // BGP-LS ID + "514": "00000000", // Area 0 + "515": "0a0a0a0a" // router-id: 10.10.10.10 + }, + "263": "0002", // MT-ID + "264": "01", // OSPF: route-type Intra-Area (0x1) + // IP Reachability Information TLV (12:12::12:12/128) + "265": "8000120012000000000000000000120012" + } + }, + { + "nlri": + { + "proto": "06", // OSPFv3 + "id": "ffffffffffffffff", + "type": "0002", // Link-NLRI + "256": { // Local Link-Node Descriptor TLV + "512": "ffffffff", // AS + "513": "ffffffff", // BGP-LS ID + "514": "ffffffff", // OSPF area ID + "515": "0a0a0a0b02020202" // router-id: 10.10.10.11:2.2.2.2 + }, + "257": { // Remote Link-Node Descriptor TLV + "512": "ffffffff", // AS + "513": "ffffffff", // BGP-LS ID + "514": "ffffffff", // OSPF area ID + "515": "0a0a0a0a01010101" // router-id: 10.10.10.10:1.1.1.1 + }, + "259": "0a010001", // IPv4 interface address TLV + "260": "0a010002", // IPv4 Neighbor address TLV + "261": "20010000000000000000000000000001", // IPv6 interface address TLV + "262": "20010000000000000000000000000002", // IPv6 Neighbor address TLV + "263": "00000002", // MT-ID + "424": "200100000000000001" // unknown TLV + } + } +] +} diff --git a/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.py b/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.py new file mode 100755 index 0000000000..314b8fe44c --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r1/bgp_injector.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: MIT + +# +# Copyright 2018 Jorge Borreicho +# Copyright 2023 6WIND S.A. + +""" + BGP prefix injection tool +""" + +import socket +import sys +import time +from datetime import datetime +import struct +import threading +import json +import os +import re +import signal +import errno + + +AFI_IPV4 = 1 +SAFI_UNICAST = 1 + +AFI_LINKSTATE = 16388 +SAFI_LINKSTATE = 71 + +saved_pid = False +global pid_file + +class Unbuffered(object): + def __init__(self, stream): + self.stream = stream + def write(self, data): + self.stream.write(data) + self.stream.flush() + def writelines(self, datas): + self.stream.writelines(datas) + self.stream.flush() + def __getattr__(self, attr): + return getattr(self.stream, attr) + +def keepalive_thread(conn, interval): + + # infinite loop so that function do not terminate and thread do not end. + while True: + time.sleep(interval) + keepalive_bgp(conn) + + +def receive_thread(conn): + + # infinite loop so that function do not terminate and thread do not end. + while True: + + # Receiving from client + r = conn.recv(1500) + while True: + start_ptr = ( + r.find( + b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + ) + + 16 + ) + end_ptr = ( + r[16:].find( + b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + ) + + 16 + ) + if ( + start_ptr >= end_ptr + ): # a single message was sent in the BGP packet OR it is the last message of the BGP packet + decode_bgp(r[start_ptr:]) + break + else: # more messages left to decode + decode_bgp(r[start_ptr:end_ptr]) + r = r[end_ptr:] + + +def decode_bgp(msg): + if len(msg) < 3: + return + msg_length, msg_type = struct.unpack("!HB", msg[0:3]) + if msg_type == 4: + # print(timestamp + " - " + "Received KEEPALIVE") #uncomment to debug + pass + elif msg_type == 2: + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Received UPDATE") + elif msg_type == 1: + version, remote_as, holdtime, i1, i2, i3, i4, opt_length = struct.unpack( + "!BHHBBBBB", msg[3:13] + ) + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Received OPEN") + print() + print( + "--> Version:" + + str(version) + + ", Remote AS: " + + str(remote_as) + + ", Hold Time:" + + str(holdtime) + + ", Remote ID: " + + str(i1) + + "." + + str(i2) + + "." + + str(i3) + + "." + + str(i4) + ) + print() + elif msg_type == 3: + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Received NOTIFICATION") + + +def multiprotocol_capability(afi, safi): + hexstream = bytes.fromhex("02060104") + hexstream += struct.pack("!H", afi) + hexstream += struct.pack("!B", 0) + hexstream += struct.pack("!B", safi) + + return hexstream + + +def open_bgp(conn, config): + + # Build the BGP Message + bgp_version = b"\x04" + bgp_as = struct.pack("!H", config["my_as"]) + bgp_hold_time = struct.pack("!H", config["hold_time"]) + + octet = config["bgp_identifier"].split(".") + bgp_identifier = struct.pack( + "!BBBB", int(octet[0]), int(octet[1]), int(octet[2]), int(octet[3]) + ) + + bgp_opt = b"" + bgp_opt += multiprotocol_capability(AFI_IPV4, SAFI_UNICAST) + bgp_opt += multiprotocol_capability(AFI_LINKSTATE, SAFI_LINKSTATE) + + bgp_opt_lenght = struct.pack("!B", len(bgp_opt)) + + bgp_message = ( + bgp_version + bgp_as + bgp_hold_time + bgp_identifier + bgp_opt_lenght + bgp_opt + ) + + # Build the BGP Header + total_length = len(bgp_message) + 16 + 2 + 1 + bgp_marker = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + bgp_length = struct.pack("!H", total_length) + bgp_type = b"\x01" + bgp_header = bgp_marker + bgp_length + bgp_type + + bgp_packet = bgp_header + bgp_message + + conn.send(bgp_packet) + return 0 + + +def keepalive_bgp(conn): + + # Build the BGP Header + total_length = 16 + 2 + 1 + bgp_marker = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + bgp_length = struct.pack("!H", total_length) + bgp_type = b"\x04" + bgp_header = bgp_marker + bgp_length + bgp_type + + bgp_packet = bgp_header + + conn.send(bgp_packet) + return 0 + + +def encode_ipv4_prefix(address, netmask): + + octet = address.split(".") + length = struct.pack("!B", int(netmask)) + + if int(netmask) <= 8: + prefix = struct.pack("!B", int(octet[0])) + elif int(netmask) <= 16: + prefix = struct.pack("!BB", int(octet[0]), int(octet[1])) + elif int(netmask) <= 24: + prefix = struct.pack("!BBB", int(octet[0]), int(octet[1]), int(octet[2])) + else: + prefix = struct.pack( + "!BBBB", int(octet[0]), int(octet[1]), int(octet[2]), int(octet[3]) + ) + + return length + prefix + + +def encode_path_attribute_mp_reach_nrli(afi, safi, data, config): + hexstream = b"" + hexstream += b"\x90" # flags optional, extended + hexstream += struct.pack("!B", 14) # type code MP_REACH_NLRI + + hexstream2 = b"" + hexstream2 += struct.pack("!H", afi) + hexstream2 += struct.pack("!B", safi) + hexstream2 += struct.pack("!B", 4) # nexthop length + hexstream2 += socket.inet_aton(config["local_address"]) # nexthop IPv4 + hexstream2 += b"\x00" # SNPA + hexstream2 += data + + hexstream += struct.pack("!H", len(hexstream2)) # length + hexstream += hexstream2 + + return hexstream + + +def encode_path_attribute_linkstate(data): + hexstream = b"" + hexstream += b"\x80" # flags optional + hexstream += struct.pack("!B", 29) # type code BGP-LS + hexstream += struct.pack("!B", len(data)) # length + hexstream += data + + return hexstream + + +def encode_path_attribute(type, value): + + path_attributes = { + "origin": [b"\x40", 1], + "as-path": [b"\x40", 2], + "next-hop": [b"\x40", 3], + "med": [b"\x80", 4], + "local_pref": [b"\x40", 5], + "communities": [b"\xc0", 8], + } + + attribute_flag = path_attributes[type][0] + attribute_type_code = struct.pack("!B", int(path_attributes[type][1])) + + if type == "origin": + attribute_value = struct.pack("!B", value) + elif type == "as-path": + as_number_list = value.split(" ") + attribute_value = struct.pack("!BB", 2, len(as_number_list)) + for as_number in as_number_list: + attribute_value += struct.pack("!H", int(as_number)) + elif type == "next-hop": + octet = value.split(".") + attribute_value = struct.pack( + "!BBBB", int(octet[0]), int(octet[1]), int(octet[2]), int(octet[3]) + ) + elif type == "med": + attribute_value = struct.pack("!I", value) + elif type == "local_pref": + attribute_value = struct.pack("!I", value) + elif type == "communities": + communities_list = value.split(" ") + attribute_value = b"" + for community in communities_list: + aux = community.split(":") + attribute_value += struct.pack("!HH", int(aux[0]), int(aux[1])) + + attribute_length = struct.pack("!B", len(attribute_value)) + + return attribute_flag + attribute_type_code + attribute_length + attribute_value + + +def encode_tlvs(tlvs): + stream = b"" + for key, tlv_data in tlvs.items(): + if isinstance(key, str) and key.isdigit(): + tlv_type = int(key) + else: + # key is not a TLV + continue + if isinstance(tlv_data, str): + if tlv_type != 0: + # TLV type 0 is fake TLV + stream += struct.pack("!H", tlv_type) + stream += struct.pack("!H", len(bytes.fromhex(tlv_data))) + stream += bytes.fromhex(tlv_data) + elif isinstance(tlv_data, dict): + # TLV contains sub-TLV + stream += struct.pack("!H", tlv_type) + + stream_subtlv = encode_tlvs(tlv_data) + stream += struct.pack("!H", len(stream_subtlv)) + stream += stream_subtlv + else: + # invalid input + assert 0 + + return stream + + +def encode_linkstate_nrli_tlv(nlri): + stream = b"" + stream += bytes.fromhex(nlri["type"]) + + stream2 = b"" + stream2 += bytes.fromhex(nlri["proto"]) + stream2 += bytes.fromhex(nlri["id"]) + stream2 += encode_tlvs(nlri) + + stream += struct.pack("!H", len(stream2)) + stream += stream2 + + return stream + + +def update_bgp(conn, link_state, config): + + # Build the BGP Message + + # Expired Routes + # 1 - Withdrawn Routes + + bgp_withdrawn_routes = b"" + max_length_reached = False + + bgp_withdrawn_routes_length = struct.pack("!H", len(bgp_withdrawn_routes)) + bgp_withdrawn_routes = bgp_withdrawn_routes_length + bgp_withdrawn_routes + + # New Routes + # 2 - Path Attributes + + path_attributes = config["path_attributes"] + bgp_mss = config["mss"] + + bgp_total_path_attributes = b"" + + # encode link-state MP_REACH NLRI + data = encode_linkstate_nrli_tlv(link_state["nlri"]) + bgp_total_path_attributes += encode_path_attribute_mp_reach_nrli( + AFI_LINKSTATE, SAFI_LINKSTATE, data, config + ) + + # encode classic attributes + for key in path_attributes.keys(): + bgp_total_path_attributes += encode_path_attribute(key, path_attributes[key]) + + # encode link-state attributes + if "attr" in link_state: + data = encode_tlvs(link_state["attr"]) + else: + data = b"" + bgp_total_path_attributes += encode_path_attribute_linkstate(data) + + bgp_total_path_attributes_length = struct.pack("!H", len(bgp_total_path_attributes)) + bgp_total_path_attributes = ( + bgp_total_path_attributes_length + bgp_total_path_attributes + ) + + # 3- Network Layer Reachability Information (NLRI) + + bgp_new_routes = b"" + + bgp_message = bgp_withdrawn_routes + bgp_total_path_attributes + bgp_new_routes + + # Build the BGP Header + total_length = len(bgp_message) + 16 + 2 + 1 + bgp_marker = b"\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" + bgp_length = struct.pack("!H", total_length) + bgp_type = b"\x02" + bgp_header = bgp_marker + bgp_length + bgp_type + + bgp_packet = bgp_header + bgp_message + + conn.send(bgp_packet) + + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Sent UPDATE") + + return 0 + + +def str2ip(ip_str): + s_octet = ip_str.split(".") + ip_addr = struct.pack( + "!BBBB", int(s_octet[0]), int(s_octet[1]), int(s_octet[2]), int(s_octet[3]) + ) + return ip_addr + + +def check_pid(pid): + if pid < 0: # user input error + return False + if pid == 0: # all processes + return False + try: + os.kill(pid, 0) + return True + except OSError as err: + if err.errno == errno.EPERM: # a process we were denied access to + return True + if err.errno == errno.ESRCH: # No such process + return False + # should never happen + return False + + +def savepid(): + ownid = os.getpid() + + flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY + mode = ((os.R_OK | os.W_OK) << 6) | (os.R_OK << 3) | os.R_OK + + try: + fd = os.open(pid_file, flags, mode) + except OSError: + try: + pid = open(pid_file, "r").readline().strip() + if check_pid(int(pid)): + sys.stderr.write( + "PIDfile already exists and program still running %s\n" % pid_file + ) + return False + else: + # If pid is not running, reopen file without O_EXCL + fd = os.open(pid_file, flags ^ os.O_EXCL, mode) + except (OSError, IOError, ValueError): + sys.stderr.write( + "issue accessing PID file %s (most likely permission or ownership)\n" + % pid_file + ) + return False + + try: + f = os.fdopen(fd, "w") + line = "%d\n" % ownid + f.write(line) + f.close() + saved_pid = True + except IOError: + sys.stderr.write("Can not create PIDfile %s\n" % pid_file) + return False + print("Created PIDfile %s with value %d\n" % (pid_file, ownid)) + return True + + +def removepid(): + if not saved_pid: + return + try: + os.remove(pid_file) + except OSError as exc: + if exc.errno == errno.ENOENT: + pass + else: + sys.stderr.write("Can not remove PIDfile %s\n" % pid_file) + return + sys.stderr.write("Removed PIDfile %s\n" % pid_file) + + +def daemonize(): + try: + pid = os.fork() + if pid > 0: + # Exit first parent + sys.exit(0) + except OSError as e: + print("Fork #1 failed: %d (%s)" % (e.errno, e.strerror)) + sys.exit(1) + + # Decouple from parent environment + os.chdir("/") + os.setsid() + os.umask(0) + + # Do second fork + try: + pid = os.fork() + if pid > 0: + # Exit from second parent + sys.exit(0) + except OSError as e: + print("Fork #2 failed: %d (%s)" % (e.errno, e.strerror)) + sys.exit(1) + + # Redirect standard file descriptors + sys.stdout.flush() + sys.stderr.flush() + si = open(os.devnull, "r") + so = open(os.devnull, "a+") + se = open(os.devnull, "a+") + + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + +def term(signal, frame): + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "^C received, shutting down.\n") + bgp_socket.close() + removepid() + exit() + + +if __name__ == "__main__": + if len(sys.argv) > 1: + # daemonize and log to file + daemonize() + pid_file = os.path.join(sys.argv[1], "bgp_injector.pid") + savepid() + # deal with daemon termination + signal.signal(signal.SIGTERM, term) + signal.signal(signal.SIGINT, term) # CTRL + C + + log_dir = os.path.join(sys.argv[1], "bgp_injector.log") + f = open(log_dir, 'w') + sys.stdout = Unbuffered(f) + sys.stderr = Unbuffered(f) + + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Starting BGP injector ") + + CONFIG_FILENAME = os.path.join(sys.path[0], "bgp_injector.cfg") + + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Reading config file " + CONFIG_FILENAME) + + input_file = open(CONFIG_FILENAME, "r") + + input = input_file.read() + # cleanup comments that are not supported by JSON + json_input = re.sub(r"//.*\n", "", input, flags=re.MULTILINE) + + config = json.loads(json_input) + + bgp_peer = config["peer_address"] + bgp_local = config["local_address"] + bgp_mss = config["mss"] + bgp_port = config["port"] + rib = dict() + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Starting BGP... (peer: " + str(bgp_peer) + ")") + + retry = 30 + while retry: + retry -= 1 + try: + bgp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + bgp_socket.bind((bgp_local, 0)) + bgp_socket.connect((bgp_peer, bgp_port)) + open_bgp(bgp_socket, config) + break + except TimeoutError: + if retry == 0: + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Error: timeout connecting to the peer.") + exit() + time.sleep(1) + except OSError as e: + if retry == 0: + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "Error: cannot connect to the peer: " + str(e)) + exit() + time.sleep(1) + + receive_worker = threading.Thread( + target=receive_thread, args=(bgp_socket,) + ) # wait from BGP msg from peer and process them + receive_worker.setDaemon(True) + receive_worker.start() + + keepalive_worker = threading.Thread( + target=keepalive_thread, + args=( + bgp_socket, + (config["hold_time"]) / 3, + ), + ) # send keep alives every 10s by default + keepalive_worker.setDaemon(True) + keepalive_worker.start() + + # send a first keepalive packet before sending the initial UPDATE packet + keepalive_bgp(bgp_socket) + + timestamp = str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")) + print(timestamp + " - " + "BGP is up.") + + time.sleep(3) + for link_state in config["link_states"]: + update_bgp( + bgp_socket, + link_state, + config, + ) + + while True: + time.sleep(60) diff --git a/tests/topotests/bgp_linkstate_topo1/r1/staticd.conf b/tests/topotests/bgp_linkstate_topo1/r1/staticd.conf new file mode 100644 index 0000000000..7f2f057bfe --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r1/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.1.2 diff --git a/tests/topotests/bgp_linkstate_topo1/r1/zebra.conf b/tests/topotests/bgp_linkstate_topo1/r1/zebra.conf new file mode 100644 index 0000000000..3307c123f8 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r1/zebra.conf @@ -0,0 +1,7 @@ +! +interface lo + ip address 192.0.2.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +!
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r2/bgpd.conf b/tests/topotests/bgp_linkstate_topo1/r2/bgpd.conf new file mode 100644 index 0000000000..26ffee9c1b --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r2/bgpd.conf @@ -0,0 +1,20 @@ +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.0.2.1 remote-as 65001 + neighbor 192.0.2.1 timers connect 1 + neighbor 192.0.2.1 ebgp-multihop 3 + neighbor 192.0.2.1 update-source 192.0.2.2 + neighbor 192.0.2.3 remote-as 65003 + neighbor 192.0.2.3 timers 1 3 + neighbor 192.0.2.3 timers connect 1 + neighbor 192.0.2.3 ebgp-multihop 3 + neighbor 192.0.2.3 update-source 192.0.2.2 + address-family ipv4 unicast + no neighbor 192.0.2.1 activate + no neighbor 192.0.2.3 activate + exit-address-family + address-family link-state link-state + neighbor 192.0.2.1 activate + neighbor 192.0.2.3 activate + exit-address-family +!
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r2/linkstate.json b/tests/topotests/bgp_linkstate_topo1/r2/linkstate.json new file mode 100644 index 0000000000..1e653c5531 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r2/linkstate.json @@ -0,0 +1,189 @@ +{ + "routes": { + "Link OSPFv3 ID:0xffffffffffffffff {Local {AS:4294967295 ID:4294967295 Area:4294967295 Rtr:10.10.10.11:2.2.2.2} Remote {AS:4294967295 ID:4294967295 Area:4294967295 Rtr:10.10.10.10:1.1.1.1} IPv4:10.1.0.1 Neigh-IPv4:10.1.0.2 IPv6:2001::1 Neigh-IPv6:2001::2 MT:0,2 424:0x200100000000000001}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "Link", + "protocol": "OSPFv3", + "identifier": "0xffffffffffffffff", + "localNode": { + "as": 4294967295, + "identifier": 4294967295, + "area": 4294967295, + "routerID": "10.10.10.11:2.2.2.2" + }, + "remoteNode": { + "as": 4294967295, + "identifier": 4294967295, + "area": 4294967295, + "routerID": "10.10.10.10:1.1.1.1" + }, + "interfaceIPv4": "10.1.0.1", + "neighborIPv4": "10.1.0.2", + "interfaceIPv6": "2001::1", + "neighborIPv6": "2001::2", + "mtID": [0, 2], + "424": ["0x2001000000000000", "0x01"] + }, + "weight": 0, + "origin": "IGP" + } + ], + "IPv6-Prefix OSPFv3 ID:0x20 {Local {AS:65001 ID:0 Area:0 Rtr:10.10.10.10} MT:2 OSPF-Route-Type:1 IPv6:12:12::12:12/128}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "IPv6-Prefix", + "protocol": "OSPFv3", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "area": 0, + "routerID": "10.10.10.10" + }, + "ospfRouteType": 1, + "ipReachability": "12:12::12:12/128", + "mtID": [2] + }, + "weight": 0, + "origin": "IGP" + } + ], + "IPv6-Prefix ISIS-L2 ID:0x20 {Local {AS:65001 ID:0 Rtr:0000.0000.1003.00} MT:2 IPv6:12:12::12:12/128}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "IPv6-Prefix", + "protocol": "ISIS-L2", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "routerID": "0000.0000.1003.00" + }, + "ipReachability": "12:12::12:12/128", + "mtID": [2] + }, + "weight": 0, + "origin": "IGP" + } + ], + "IPv4-Prefix OSPFv2 ID:0x20 {Local {AS:65001 ID:0 Area:0 Rtr:10.10.10.10:1.1.1.1} IPv4:89.10.11.0/24}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "IPv4-Prefix", + "protocol": "OSPFv2", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "area": 0, + "routerID": "10.10.10.10:1.1.1.1" + }, + "ipReachability": "89.10.11.0/24" + }, + "weight": 0, + "origin": "IGP" + } + ], + "Node OSPFv2 ID:0x20 {Local {AS:65001 ID:0 Area:0 Rtr:10.10.10.10:1.1.1.1}}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "Node", + "protocol": "OSPFv2", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "area": 0, + "routerID": "10.10.10.10:1.1.1.1" + } + }, + "weight": 0, + "origin": "IGP" + } + ], + "Node OSPFv2 ID:0x20 {Local {AS:65001 ID:0 Area:0 Rtr:10.10.10.10}}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "Node", + "protocol": "OSPFv2", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "area": 0, + "routerID": "10.10.10.10" + } + }, + "weight": 0, + "origin": "IGP" + } + ], + "Node ISIS-L1 ID:0x20 {Local {AS:65001 ID:0 Rtr:0000.0000.1003.00}}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "Node", + "protocol": "ISIS-L1", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "routerID": "0000.0000.1003.00" + } + }, + "weight": 0, + "origin": "IGP" + } + ], + "Link ISIS-L1 ID:0x20 {Local {AS:65001 ID:0 Rtr:0000.0000.1001} Remote {AS:65001 ID:0 Rtr:0000.0000.1000} IPv4:10.1.0.1 Neigh-IPv4:10.1.0.2 IPv6:2001::1 Neigh-IPv6:2001::2 MT:0,2}/XX": [ + { + "valid": true, + "bestpath": true, + "pathFrom": "external", + "linkStateNLRI": { + "nlriType": "Link", + "protocol": "ISIS-L1", + "identifier": "0x20", + "localNode": { + "as": 65001, + "identifier": 0, + "routerID": "0000.0000.1001" + }, + "remoteNode": { + "as": 65001, + "identifier": 0, + "routerID": "0000.0000.1000" + }, + "interfaceIPv4": "10.1.0.1", + "neighborIPv4": "10.1.0.2", + "interfaceIPv6": "2001::1", + "neighborIPv6": "2001::2", + "mtID": [0, 2] + }, + "weight": 0, + "origin": "IGP" + } + ] + } +} diff --git a/tests/topotests/bgp_linkstate_topo1/r2/linkstate_detail.json b/tests/topotests/bgp_linkstate_topo1/r2/linkstate_detail.json new file mode 100644 index 0000000000..06498f3bb9 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r2/linkstate_detail.json @@ -0,0 +1,325 @@ +{ + "Node OSPFv2 ID:0x20 {Local {AS:65001 ID:0 Area:0 Rtr:10.10.10.10}}/XX": { + "1152": { + "description": "IGP Flags", + "type": 1152, + "length": 1, + "data": "0b10000000" + }, + "1097": { + "description": "Opaque Link Attribute", + "type": 1097, + "length": 12, + "data": { + "11": { + "type": 11, + "length": 2, + "data": "0x0123" + }, + "5": { + "type": 5, + "length": 2, + "data": "0x0123" + } + } + }, + "1105": { + "description": "RTM Capability", + "type": 1105, + "length": 3, + "data": { + "flags": "0b110", + "values": "0x11ed" + } + }, + "1172": { + "description": "L2 Bundle Member Attributes", + "type": 1172, + "length": 12, + "data": { + "descriptor": "0xdc12fceb", + "1088": { + "description": "Administrative group", + "type": 1088, + "length": 4, + "data": "0x000f0001" + } + } + }, + "1117": { + "description": "Unidirectional Link Loss", + "type": 1117, + "length": 4, + "data": { + "anomalous": false, + "lossPercent": 50.33164596557617 + } + }, + "1039": { + "description": "Flexible Algorithm Definition", + "type": 1039, + "length": 28, + "data": { + "flexAlgo": 130, + "metricType": 0, + "calcType": 0, + "priority": 128, + "1041": { + "description": "Flexible Algorithm Include Any Affinity", + "type": 1041, + "length": 4, + "data": "0x00000001" + }, + "1045": { + "description": "Flexible Algorithm Exclude SRLG", + "type": 1045, + "length": 4, + "data": [ + 10 + ] + }, + "1043": { + "description": "Flexible Algorithm Definition Flags", + "type": 1043, + "length": 4, + "data": "0b10000000 00000000 00000000 00000000" + } + } + }, + "1044": { + "description": "Flexible Algorithm Prefix Metric", + "type": 1044, + "length": 8, + "data": { + "flexAlgo": 130, + "flags": "0b10000000", + "metric": 1000 + } + }, + "1121": { + "description": "Graceful-Link-Shutdown TLV", + "type": 1121, + "length": 0 + }, + "1171": { + "description": "Source Router Identifier", + "type": 1171, + "length": 16, + "data": "12:12::12:12" + }, + "1871": { + "type": 1871, + "length": 13, + "data": "0x00120012000000000000000000" + } + }, + "Node ISIS-L1 ID:0x20 {Local {AS:65001 ID:0 Rtr:0000.0000.1003.00}}/XX": { + "263": { + "description": "Multi-Topology ID", + "type": 263, + "length": 4, + "data": [ + 0, + 2 + ] + }, + "266": { + "description": "Node MSD", + "type": 266, + "length": 2, + "data": [ + { + "type": 1, + "value": 8 + } + ] + }, + "1026": { + "description": "Node Name", + "type": 1026, + "length": 2, + "data": "r3" + }, + "1027": { + "description": "IS-IS Area Identifier", + "type": 1027, + "length": 3, + "data": "49.1000" + }, + "1028": { + "description": "IPv4 Router-ID of Local Node", + "type": 1028, + "length": 4, + "data": "3.3.3.3" + }, + "1034": { + "description": "SR Capabilities", + "type": 1034, + "length": 12, + "data": { + "flags": "0b11000000", + "range": 4000, + "1161": { + "description": "SID/Label", + "type": 1161, + "length": 3, + "data": { + "fromLabel": 20000 + } + } + } + }, + "1035": { + "description": "SR Algorithm", + "type": 1035, + "length": 3, + "data": [ + 0, + 128, + 130 + ] + }, + "1036": { + "description": "SR Local Block", + "type": 1036, + "length": 12, + "data": { + "flags": "0b00000000", + "range": 1000, + "1161": { + "description": "SID/Label", + "type": 1161, + "length": 3, + "data": { + "fromLabel": 15000 + } + } + } + } + }, + "Link ISIS-L1 ID:0x20 {Local {AS:65001 ID:0 Rtr:0000.0000.1001} Remote {AS:65001 ID:0 Rtr:0000.0000.1000} IPv4:10.1.0.1 Neigh-IPv4:10.1.0.2 IPv6:2001::1 Neigh-IPv6:2001::2 MT:0,2}/XX": { + "1028": { + "description": "IPv4 Router-ID of Local Node", + "type": 1028, + "length": 4, + "data": "1.1.1.1" + }, + "1030": { + "description": "IPv4 Router-ID of Remote Node", + "type": 1030, + "length": 4, + "data": "10.10.10.10" + }, + "1089": { + "description": "Maximum link bandwidth", + "type": 1089, + "length": 4, + "data": 176258176, + "dataUnit": "bps" + }, + "1090": { + "description": "Max. reservable link bandwidth", + "type": 1090, + "length": 4, + "data": 176258176, + "dataUnit": "bps" + }, + "1091": { + "description": "Unreserved bandwidth", + "type": 1091, + "length": 32, + "dataUnit": "bps", + "data": { + "0": 176258176, + "1": 176258176, + "2": 176258176, + "3": 176258176, + "4": 176258176, + "5": 176258176, + "6": 176258176, + "7": 176258176 + } + }, + "1092": { + "description": "TE Default Metric", + "type": 1092, + "length": 4, + "data": 100 + }, + "1095": { + "description": "IGP Metric", + "type": 1095, + "length": 3, + "data": 10 + }, + "1099": { + "description": "Adjacency SID", + "type": 1099, + "length": 7, + "data": { + "flags": "0b00110000", + "weight": 0, + "sid": 15000 + } + }, + "1114": { + "description": "Unidirectional Link Delay", + "type": 1114, + "length": 4, + "data": { + "anomalous": false, + "delay": 8500 + }, + "dataUnit": "microseconds" + }, + "1115": { + "description": "Min/Max Unidirectional Link Delay", + "type": 1115, + "length": 8, + "data": { + "anomalous": false, + "minDelay": 8000, + "maxDelay": 9000 + }, + "dataUnit": "microseconds" + }, + "1122": { + "description": "Application-Specific Link Attributes", + "type": 1122, + "length": 48, + "data": { + "sabmFlags": "0b00010000 00000000 00000000 00000000", + "udabmFlags": "0b00000000 00000000 00000000 00000000", + "1088": { + "description": "Administrative group", + "type": 1088, + "length": 4, + "data": "0x00000001" + }, + "1092": { + "description": "TE Default Metric", + "type": 1092, + "length": 4, + "data": 100 + }, + "1115": { + "description": "Min/Max Unidirectional Link Delay", + "type": 1115, + "length": 8, + "data": { + "anomalous": false, + "minDelay": 8000, + "maxDelay": 9000 + }, + "dataUnit": "microseconds" + }, + "1173": { + "description": "Extended Administrative Group", + "type": 1173, + "length": 4, + "data": "0x00000001" + } + } + } + } +} diff --git a/tests/topotests/bgp_linkstate_topo1/r2/staticd.conf b/tests/topotests/bgp_linkstate_topo1/r2/staticd.conf new file mode 100644 index 0000000000..4ce3f7b9b1 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r2/staticd.conf @@ -0,0 +1,2 @@ +ip route 192.0.2.1/32 192.168.1.1 +ip route 192.0.2.3/32 192.168.2.3
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r2/zebra.conf b/tests/topotests/bgp_linkstate_topo1/r2/zebra.conf new file mode 100644 index 0000000000..586dc0acad --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r2/zebra.conf @@ -0,0 +1,11 @@ +! +int lo + ip address 192.0.2.2/32 +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +interface r2-eth1 + ip address 192.168.2.2/24 +! + diff --git a/tests/topotests/bgp_linkstate_topo1/r3/bgpd.conf b/tests/topotests/bgp_linkstate_topo1/r3/bgpd.conf new file mode 100644 index 0000000000..88693a3750 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r3/bgpd.conf @@ -0,0 +1,14 @@ +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.0.2.2 remote-as 65002 + neighbor 192.0.2.2 timers 1 3 + neighbor 192.0.2.2 timers connect 1 + neighbor 192.0.2.2 ebgp-multihop 3 + neighbor 192.0.2.2 update-source 192.0.2.3 + address-family ipv4 unicast + no neighbor 192.0.2.2 activate + exit-address-family + address-family link-state link-state + neighbor 192.0.2.2 activate + exit-address-family +!
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r3/linkstate.json b/tests/topotests/bgp_linkstate_topo1/r3/linkstate.json new file mode 120000 index 0000000000..588691dcf7 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r3/linkstate.json @@ -0,0 +1 @@ +../r2/linkstate.json
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r3/linkstate_detail.json b/tests/topotests/bgp_linkstate_topo1/r3/linkstate_detail.json new file mode 120000 index 0000000000..ec06e56c66 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r3/linkstate_detail.json @@ -0,0 +1 @@ +../r2/linkstate_detail.json
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r3/staticd.conf b/tests/topotests/bgp_linkstate_topo1/r3/staticd.conf new file mode 100644 index 0000000000..e8a3198ba7 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r3/staticd.conf @@ -0,0 +1 @@ +ip route 192.0.2.2/32 192.168.2.2
\ No newline at end of file diff --git a/tests/topotests/bgp_linkstate_topo1/r3/zebra.conf b/tests/topotests/bgp_linkstate_topo1/r3/zebra.conf new file mode 100644 index 0000000000..532aede2e8 --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/r3/zebra.conf @@ -0,0 +1,7 @@ +! +int lo + ip address 192.0.2.3/32 +! +interface r3-eth0 + ip address 192.168.2.3/24 +! diff --git a/tests/topotests/bgp_linkstate_topo1/test_bgp_linkstate_topo1.py b/tests/topotests/bgp_linkstate_topo1/test_bgp_linkstate_topo1.py new file mode 100644 index 0000000000..5593b45fba --- /dev/null +++ b/tests/topotests/bgp_linkstate_topo1/test_bgp_linkstate_topo1.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# Copyright 2023 6WIND S.A. + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + if rname == "r1": + # use bgp_injector.py to inject BGP prefixes + continue + router.load_config( + TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname)) + ) + + tgen.start_router() + + r1_path = os.path.join(CWD, "r1") + log_dir = os.path.join(tgen.logdir, "r1") + tgen.gears["r1"].cmd("chmod u+x {}/bgp_injector.py".format(r1_path)) + tgen.gears["r1"].run("{}/bgp_injector.py {}".format(r1_path, log_dir)) + + +def teardown_module(mod): + tgen = get_topogen() + + log_dir = os.path.join(tgen.logdir, "r1") + pid_file = os.path.join(log_dir, "bgp_injector.pid") + + logger.info("r1: sending SIGTERM to bgp_injector") + tgen.gears["r1"].cmd("kill $(cat {})".format(pid_file)) + tgen.stop_topology() + + +def test_show_bgp_link_state(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _remove_prefixlen(tmp_json): + new_json = { + prefix.split("}/")[0] + "}/XX": data + for prefix, data in tmp_json["routes"].items() + } + + return new_json + + def _show_bgp_link_state_json(rname, tmp_expected): + tmp_output = json.loads( + tgen.gears[rname].vtysh_cmd("show bgp link-state link-state json") + ) + # prefix length is the size of prefix in memory + # which differs on 32 and 64 bits. + # do not compare the prefix length + output = _remove_prefixlen(tmp_output) + expected = _remove_prefixlen(tmp_expected) + + return topotest.json_cmp(output, expected) + + step("Check BGP Link-State tables") + for rname in ["r2", "r3"]: + expected = open(os.path.join(CWD, "{}/linkstate.json".format(rname))).read() + expected_json = json.loads(expected) + test_func = functools.partial(_show_bgp_link_state_json, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to see BGP prefixes on {}".format(rname) + + +def test_show_bgp_link_state_detail(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _show_bgp_link_state_json(rname, expected): + output = json.loads( + tgen.gears[rname].vtysh_cmd("show bgp link-state link-state detail-routes json") + ) + json_output = { + prefix.split("/")[0] + "/XX": item["linkStateAttributes"] + for prefix, data in output["routes"].items() + for item in data + if "linkStateAttributes" in item + } + + return topotest.json_cmp(json_output, expected) + + step("Check BGP Link-State Attributes tables") + for rname in ["r2", "r3"]: + expected = open( + os.path.join(CWD, "{}/linkstate_detail.json".format(rname)) + ).read() + expected_json = json.loads(expected) + test_func = functools.partial(_show_bgp_link_state_json, rname, expected_json) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed to display BGP-LS Attributes on {}".format(rname) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py index 583d89d40f..6b6153db46 100755 --- a/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py +++ b/tests/topotests/bgp_snmp_bgp4v2mib/test_bgp_snmp_bgp4v2mib.py @@ -146,8 +146,8 @@ def test_bgp_snmp_bgp4v2(): def _snmpwalk_remote_addr(): expected = { - "1.3.6.1.3.5.1.1.2.1.5.1.4.192.168.12.1": "C0 A8 0C 01", - "1.3.6.1.3.5.1.1.2.1.5.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "20 01 0D B8 00 00 00 00 00 00 00 00 00 12 00 01", + "1.3.6.1.3.5.1.1.2.1.5.1.1.192.168.12.1": "C0 A8 0C 01", + "1.3.6.1.3.5.1.1.2.1.5.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "20 01 0D B8 00 00 00 00 00 00 00 00 00 12 00 01", } # bgp4V2PeerRemoteAddr @@ -160,8 +160,8 @@ def test_bgp_snmp_bgp4v2(): def _snmpwalk_peer_state(): expected = { - "1.3.6.1.3.5.1.1.2.1.13.1.4.192.168.12.1": "6", - "1.3.6.1.3.5.1.1.2.1.13.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "6", + "1.3.6.1.3.5.1.1.2.1.13.1.1.192.168.12.1": "6", + "1.3.6.1.3.5.1.1.2.1.13.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "6", } # bgp4V2PeerState @@ -174,8 +174,8 @@ def test_bgp_snmp_bgp4v2(): def _snmpwalk_peer_last_error_code_received(): expected = { - "1.3.6.1.3.5.1.1.3.1.1.1.4.192.168.12.1": "0", - "1.3.6.1.3.5.1.1.3.1.1.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "0", + "1.3.6.1.3.5.1.1.3.1.1.1.1.192.168.12.1": "0", + "1.3.6.1.3.5.1.1.3.1.1.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "0", } # bgp4V2PeerLastErrorCodeReceived @@ -190,10 +190,10 @@ def test_bgp_snmp_bgp4v2(): def _snmpwalk_origin(): expected = { - "1.3.6.1.3.5.1.1.9.1.9.1.4.10.0.0.0.31.192.168.12.1": "1", - "1.3.6.1.3.5.1.1.9.1.9.1.4.10.0.0.2.32.192.168.12.1": "3", - "1.3.6.1.3.5.1.1.9.1.9.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "1", - "1.3.6.1.3.5.1.1.9.1.9.2.16.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.1.10.0.0.0.31.192.168.12.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.1.10.0.0.2.32.192.168.12.1": "3", + "1.3.6.1.3.5.1.1.9.1.9.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "1", + "1.3.6.1.3.5.1.1.9.1.9.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "3", } # bgp4V2NlriOrigin @@ -206,10 +206,10 @@ def test_bgp_snmp_bgp4v2(): def _snmpwalk_med(): expected = { - "1.3.6.1.3.5.1.1.9.1.17.1.4.10.0.0.0.31.192.168.12.1": "1", - "1.3.6.1.3.5.1.1.9.1.17.1.4.10.0.0.2.32.192.168.12.1": "2", - "1.3.6.1.3.5.1.1.9.1.17.2.16.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "1", - "1.3.6.1.3.5.1.1.9.1.17.2.16.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.1.10.0.0.0.31.192.168.12.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.1.10.0.0.2.32.192.168.12.1": "2", + "1.3.6.1.3.5.1.1.9.1.17.1.2.32.1.13.184.0.0.0.0.0.0.0.0.0.0.0.1.128.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "1", + "1.3.6.1.3.5.1.1.9.1.17.1.2.32.1.13.184.0.1.0.0.0.0.0.0.0.0.0.0.56.32.1.13.184.0.0.0.0.0.0.0.0.0.18.0.1": "2", } # bgp4V2NlriMed diff --git a/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py index 189d2baf17..984cf97e28 100755 --- a/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py +++ b/tests/topotests/bgp_srv6l3vpn_sid/test_bgp_srv6l3vpn_sid.py @@ -39,7 +39,7 @@ from lib.checkping import check_ping def build_topo(tgen): - """ + r""" CE1 CE3 CE5 (eth0) (eth0) (eth0) :2 :2 :2 diff --git a/tests/topotests/lib/bgp.py b/tests/topotests/lib/bgp.py index 21d4567d6b..3a16ed5a09 100644 --- a/tests/topotests/lib/bgp.py +++ b/tests/topotests/lib/bgp.py @@ -1325,7 +1325,7 @@ def verify_router_id(tgen, topo, input_dict, expected=True): @retry(retry_timeout=150) -def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True): +def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True, addr_type=None): """ API will verify if BGP is converged with in the given time frame. Running "show bgp summary json" command and verify bgp neighbor @@ -1336,6 +1336,7 @@ def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True): * `tgen`: topogen object * `topo`: input json file data * `dut`: device under test + * `addr_type` : address type for which verification to be done, by-default both v4 and v6 Usage ----- @@ -1439,20 +1440,27 @@ def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True): return errormsg else: total_peer = 0 - for addr_type in bgp_addr_type.keys(): - if not check_address_types(addr_type): + for _addr_type in bgp_addr_type.keys(): + if not check_address_types(_addr_type): + continue + + if addr_type and addr_type != _addr_type: continue - bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + bgp_neighbors = bgp_addr_type[_addr_type]["unicast"]["neighbor"] for bgp_neighbor in bgp_neighbors: total_peer += len(bgp_neighbors[bgp_neighbor]["dest_link"]) no_of_peer = 0 - for addr_type in bgp_addr_type.keys(): + for _addr_type in bgp_addr_type.keys(): if not check_address_types(addr_type): continue - bgp_neighbors = bgp_addr_type[addr_type]["unicast"]["neighbor"] + + if addr_type and addr_type != _addr_type: + continue + + bgp_neighbors = bgp_addr_type[_addr_type]["unicast"]["neighbor"] for bgp_neighbor, peer_data in bgp_neighbors.items(): for dest_link in peer_data["dest_link"].keys(): @@ -1473,7 +1481,7 @@ def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True): elif "source_link" in peer_details: neighbor_ip = topo["routers"][bgp_neighbor][ "links" - ][peer_details["source_link"]][addr_type].split( + ][peer_details["source_link"]][_addr_type].split( "/" )[ 0 @@ -1484,12 +1492,12 @@ def verify_bgp_convergence(tgen, topo=None, dut=None, expected=True): ): neighbor_ip = data[dest_link]["peer-interface"] else: - neighbor_ip = data[dest_link][addr_type].split("/")[ - 0 - ] + neighbor_ip = data[dest_link][_addr_type].split( + "/" + )[0] nh_state = None neighbor_ip = neighbor_ip.lower() - if addr_type == "ipv4": + if _addr_type == "ipv4": ipv4_data = show_bgp_json[vrf]["ipv4Unicast"][ "peers" ] diff --git a/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py b/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py index 826d6e2941..84a13aedee 100644 --- a/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py +++ b/tests/topotests/multicast_mld_join_topo1/test_multicast_mld_local_join.py @@ -42,6 +42,10 @@ from lib.pim import ( verify_pim_rp_info, verify_upstream_iif, ) +from lib.bgp import ( + verify_bgp_convergence, +) + from lib.topogen import Topogen, get_topogen from lib.topojson import build_config_from_json from lib.topolog import logger @@ -126,6 +130,13 @@ def setup_module(mod): # Creating configuration from JSON build_config_from_json(tgen, topo) + + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + # Verify PIM neighbors result = verify_pim_neighbors(tgen, topo) assert result is True, " Verify PIM neighbor: Failed Error: {}".format(result) @@ -177,6 +188,10 @@ def test_mld_local_joins_p0(request): reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + step("configure BGP on R1, R2, R3, R4 and enable redistribute static/connected") step("Enable the MLD on R11 interfac of R1 and configure local mld groups") intf_r1_i1 = topo["routers"]["r1"]["links"]["i1"]["interface"] @@ -249,6 +264,10 @@ def test_mroute_with_mld_local_joins_p0(request): reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + app_helper.stop_all_hosts() step("Enable the PIM on all the interfaces of R1, R2, R3, R4") @@ -442,6 +461,10 @@ def test_remove_add_mld_local_joins_p1(request): reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + app_helper.stop_all_hosts() step("Enable the PIM on all the interfaces of R1, R2, R3, R4") @@ -694,6 +717,10 @@ def test_remove_add_mld_config_with_local_joins_p1(request): reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + app_helper.stop_all_hosts() step("Enable the PIM on all the interfaces of R1, R2, R3, R4") diff --git a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py index aff623705c..7eb5838037 100644 --- a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py +++ b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm1.py @@ -61,6 +61,9 @@ from lib.pim import ( verify_sg_traffic, verify_upstream_iif, ) +from lib.bgp import ( + verify_bgp_convergence, +) from lib.topogen import Topogen, get_topogen from lib.topojson import build_config_from_json from lib.topolog import logger @@ -140,6 +143,12 @@ def setup_module(mod): global app_helper app_helper = McastTesterHelper(tgen) + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") @@ -276,6 +285,10 @@ def test_multicast_data_traffic_static_RP_send_traffic_then_join_p0(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -480,6 +493,10 @@ def test_verify_mroute_when_receiver_is_outside_frr_p0(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -648,6 +665,10 @@ def test_verify_mroute_when_frr_is_transit_router_p2(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -803,6 +824,10 @@ def test_verify_mroute_when_RP_unreachable_p1(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -929,6 +954,10 @@ def test_modify_mld_query_timer_p0(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -1108,6 +1137,10 @@ def test_modify_mld_max_query_response_timer_p0(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -1377,6 +1410,10 @@ def test_verify_impact_on_multicast_traffic_when_RP_removed_p0(request): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. diff --git a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py index 767264a7c0..8b174bf9b8 100644 --- a/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py +++ b/tests/topotests/multicast_pim6_sm_topo1/test_multicast_pim6_sm2.py @@ -43,6 +43,9 @@ from lib.pim import ( verify_sg_traffic, verify_upstream_iif, ) +from lib.bgp import ( + verify_bgp_convergence, +) from lib.topogen import Topogen, get_topogen from lib.topojson import build_config_from_json from lib.topolog import logger @@ -129,6 +132,12 @@ def setup_module(mod): # Creating configuration from JSON build_config_from_json(tgen, tgen.json_topo) + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") @@ -213,6 +222,10 @@ def test_clear_mroute_and_verify_multicast_data_p0(request, app_helper): # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + app_helper.stop_all_hosts() # Don"t run this test if we have any failure. @@ -444,6 +457,10 @@ def test_verify_SPT_switchover_when_RPT_and_SPT_path_is_different_p0( # Creating configuration from JSON reset_config_on_routers(tgen) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo, addr_type="ipv6") + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) diff --git a/tests/topotests/multicast_pim_uplink_topo1/test_multicast_pim_uplink_topo1.py b/tests/topotests/multicast_pim_uplink_topo1/test_multicast_pim_uplink_topo1.py index b50573e775..5728a4d08e 100644 --- a/tests/topotests/multicast_pim_uplink_topo1/test_multicast_pim_uplink_topo1.py +++ b/tests/topotests/multicast_pim_uplink_topo1/test_multicast_pim_uplink_topo1.py @@ -52,7 +52,10 @@ from lib.common_config import ( create_static_routes, required_linux_kernel_version, ) -from lib.bgp import create_router_bgp +from lib.bgp import ( + create_router_bgp, + verify_bgp_convergence, +) from lib.pim import ( create_pim_config, create_igmp_config, @@ -152,6 +155,12 @@ def setup_module(mod): global app_helper app_helper = McastTesterHelper(tgen) + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") @@ -191,7 +200,6 @@ def get_interfaces_names(topo): """ for link in range(1, 5): - intf = topo["routers"]["r1"]["links"]["r2-link{}".format(link)]["interface"] r1_r2_links.append(intf) @@ -401,6 +409,10 @@ def test_mroutes_updated_with_correct_oil_iif_when_receiver_is_in_and_outside_DU reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -795,6 +807,10 @@ def test_mroutes_updated_with_correct_oil_iif_when_source_is_in_and_outside_DUT_ reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -1164,6 +1180,10 @@ def test_verify_mroutes_forwarding_p0(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -1478,6 +1498,10 @@ def test_mroutes_updated_correctly_after_source_interface_shut_noshut_p1(request reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -1838,6 +1862,10 @@ def test_mroutes_updated_correctly_after_receiver_interface_shut_noshut_p1(reque reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -2153,6 +2181,10 @@ def test_mroutes_updated_after_sending_IGMP_prune_and_join_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -2412,6 +2444,10 @@ def test_mroutes_updated_after_after_clear_mroute_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -2601,6 +2637,10 @@ def test_mroutes_updated_after_changing_rp_config_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -3067,6 +3107,10 @@ def test_mroutes_after_restart_frr_services_p2(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) diff --git a/tests/topotests/multicast_pim_uplink_topo2/test_multicast_pim_uplink_topo2.py b/tests/topotests/multicast_pim_uplink_topo2/test_multicast_pim_uplink_topo2.py index eb3246b513..1fb81c0d70 100644 --- a/tests/topotests/multicast_pim_uplink_topo2/test_multicast_pim_uplink_topo2.py +++ b/tests/topotests/multicast_pim_uplink_topo2/test_multicast_pim_uplink_topo2.py @@ -23,6 +23,7 @@ import os import sys import time import pytest +from time import sleep # Save the Current Working Directory to find configuration files. CWD = os.path.dirname(os.path.realpath(__file__)) @@ -56,6 +57,9 @@ from lib.pim import ( verify_pim_interface_traffic, McastTesterHelper, ) +from lib.bgp import ( + verify_bgp_convergence, +) from lib.topolog import logger from lib.topojson import build_config_from_json @@ -131,6 +135,12 @@ def setup_module(mod): global app_helper app_helper = McastTesterHelper(tgen) + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") @@ -170,7 +180,6 @@ def get_interfaces_names(topo): """ for link in range(1, 5): - intf = topo["routers"]["r1"]["links"]["r2-link{}".format(link)]["interface"] r1_r2_links.append(intf) @@ -255,6 +264,10 @@ def test_iif_oil_when_RP_address_changes_from_static_to_BSR_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -418,7 +431,15 @@ def test_iif_oil_when_RP_address_changes_from_static_to_BSR_p1(request): } ] } - }, + } + } + result = create_pim_config(tgen, topo, input_dict) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + # Need to wait for 10 sec to make sure prune is received before below RP change is executed + sleep(10) + + input_dict = { "r5": { "pim": { "rp": [ @@ -432,7 +453,6 @@ def test_iif_oil_when_RP_address_changes_from_static_to_BSR_p1(request): } }, } - result = create_pim_config(tgen, topo, input_dict) assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) @@ -516,6 +536,10 @@ def test_mroute_when_RPT_and_SPT_path_is_different_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -724,6 +748,10 @@ def test_mroutes_updated_with_correct_oil_iif_after_shut_noshut_upstream_interfa reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -1094,6 +1122,10 @@ def test_mroutes_updated_with_correct_oil_iif_after_shut_noshut_downstream_inter reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) diff --git a/tests/topotests/multicast_pim_uplink_topo3/test_multicast_pim_uplink_topo3.py b/tests/topotests/multicast_pim_uplink_topo3/test_multicast_pim_uplink_topo3.py index 19a8cd0c17..bed2f2f322 100644 --- a/tests/topotests/multicast_pim_uplink_topo3/test_multicast_pim_uplink_topo3.py +++ b/tests/topotests/multicast_pim_uplink_topo3/test_multicast_pim_uplink_topo3.py @@ -53,6 +53,9 @@ from lib.pim import ( verify_local_igmp_groups, McastTesterHelper, ) +from lib.bgp import ( + verify_bgp_convergence, +) from lib.topolog import logger from lib.topojson import build_config_from_json @@ -181,6 +184,12 @@ def setup_module(mod): global app_helper app_helper = McastTesterHelper(tgen) + # Verify BGP convergence + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module : Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + logger.info("Running setup_module() done") @@ -220,7 +229,6 @@ def get_interfaces_names(topo): """ for link in range(1, 5): - intf = topo["routers"]["r1"]["links"]["r2-link{}".format(link)]["interface"] r1_r2_links.append(intf) @@ -331,6 +339,10 @@ def test_ip_igmp_local_joins_p0(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -412,6 +424,10 @@ def test_mroute_with_igmp_local_joins_p0(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -606,6 +622,10 @@ def test_igmp_local_join_with_reserved_address_p0(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) @@ -671,6 +691,10 @@ def test_remove_add_igmp_local_joins_p1(request): reset_config_on_routers(tgen) clear_pim_interface_traffic(tgen, topo) + # Verify BGP convergence + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error {}".format(tc_name, result) + # Don"t run this test if we have any failure. if tgen.routers_have_failure(): pytest.skip(tgen.errors) diff --git a/tests/topotests/srv6_static_route/__init__.py b/tests/topotests/srv6_static_route/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/srv6_static_route/__init__.py diff --git a/tests/topotests/srv6_static_route/expected_srv6_route.json b/tests/topotests/srv6_static_route/expected_srv6_route.json new file mode 100644 index 0000000000..45de6171fe --- /dev/null +++ b/tests/topotests/srv6_static_route/expected_srv6_route.json @@ -0,0 +1,49 @@ +{ + "2001:db8:aaaa::/64": [ + { + "prefix": "2001:db8:aaaa::/64", + "prefixLen": 64, + "protocol": "static", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "nexthops": [ + { + "directlyConnected": true, + "active": true, + "weight": 1 + } + ] + } + ], + "2005::/64": [ + { + "prefix": "2005::/64", + "prefixLen": 64, + "protocol": "static", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "nexthops": [ + { + "directlyConnected": true, + "active": true, + "weight": 1, + "seg6local": { + "action": "unspec" + }, + "seg6": [ + "2001:db8:aaaa::7", + "2002::2", + "2003::3", + "2004::4" + ] + } + ] + } + ] +} diff --git a/tests/topotests/srv6_static_route/r1/mgmtd.conf b/tests/topotests/srv6_static_route/r1/mgmtd.conf new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/srv6_static_route/r1/mgmtd.conf diff --git a/tests/topotests/srv6_static_route/r1/setup.sh b/tests/topotests/srv6_static_route/r1/setup.sh new file mode 100644 index 0000000000..36ed713f24 --- /dev/null +++ b/tests/topotests/srv6_static_route/r1/setup.sh @@ -0,0 +1,2 @@ +ip link add dummy0 type dummy +ip link set dummy0 up diff --git a/tests/topotests/srv6_static_route/r1/staticd.conf b/tests/topotests/srv6_static_route/r1/staticd.conf new file mode 100644 index 0000000000..a75c69f26b --- /dev/null +++ b/tests/topotests/srv6_static_route/r1/staticd.conf @@ -0,0 +1,9 @@ +hostname r1 +! +log stdout notifications +log monitor notifications +log commands +log file staticd.log debugging +! +ipv6 route 2001:db8:aaaa::/64 dummy0 +ipv6 route 2005::/64 dummy0 segments 2001:db8:aaaa::7/2002::2/2003::3/2004::4 diff --git a/tests/topotests/srv6_static_route/r1/zebra.conf b/tests/topotests/srv6_static_route/r1/zebra.conf new file mode 100644 index 0000000000..cc70418601 --- /dev/null +++ b/tests/topotests/srv6_static_route/r1/zebra.conf @@ -0,0 +1,10 @@ +hostname r1 +! +! debug zebra events +! debug zebra rib detailed +! +log stdout notifications +log monitor notifications +log commands +log file zebra.log debugging +! diff --git a/tests/topotests/srv6_static_route/test_srv6_route.py b/tests/topotests/srv6_static_route/test_srv6_route.py new file mode 100755 index 0000000000..7a4cd39f2d --- /dev/null +++ b/tests/topotests/srv6_static_route/test_srv6_route.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_srv6_route.py +# +# Copyright 2023 6WIND S.A. +# Dmytro Shytyi <dmytro.shytyi@6wind.com> +# + +""" +test_srv6_route.py: +Test for SRv6 static route on zebra +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +pytestmark = [pytest.mark.bgpd, pytest.mark.sharpd] + + +def open_json_file(filename): + try: + with open(filename, "r") as f: + return json.load(f) + except IOError: + assert False, "Could not read file {}".format(filename) + + +def setup_module(mod): + tgen = Topogen({None: "r1"}, mod.__name__) + tgen.start_topology() + for rname, router in tgen.routers().items(): + router.run("/bin/bash {}/{}/setup.sh".format(CWD, rname)) + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_MGMTD, os.path.join(CWD, "{}/mgmtd.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_STATIC, os.path.join(CWD, "{}/staticd.conf".format(rname)) + ) + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_srv6_static_route(): + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + router = tgen.gears["r1"] + + def _check_srv6_static_route(router, expected_route_file): + logger.info("checking zebra srv6 static route with multiple segs status") + output = json.loads(router.vtysh_cmd("show ipv6 route static json")) + expected = open_json_file("{}/{}".format(CWD, expected_route_file)) + return topotest.json_cmp(output, expected) + + def check_srv6_static_route(router, expected_file): + func = functools.partial(_check_srv6_static_route, router, expected_file) + success, result = topotest.run_and_expect(func, None, count=15, wait=1) + assert result is None, "Failed" + + # FOR DEVELOPER: + # If you want to stop some specific line and start interactive shell, + # please use tgen.mininet_cli() to start it. + + logger.info("Test for srv6 route configuration") + check_srv6_static_route(router, "expected_srv6_route.json") + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 113a15c172..050807ccd8 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -1447,6 +1447,13 @@ static struct cmd_node bgp_ipv6l_node = { .no_xpath = true, }; +static struct cmd_node bgp_ls_node = { + .name = "bgp link-state", + .node = BGP_LS_NODE, + .parent_node = BGP_NODE, + .prompt = "%s(config-router-af-ls)# ", +}; + #ifdef ENABLE_BGP_VNC static struct cmd_node bgp_vnc_defaults_node = { .name = "bgp vnc defaults", @@ -1758,6 +1765,14 @@ DEFUNSH(VTYSH_BGPD, address_family_flowspecv6, address_family_flowspecv6_cmd, return CMD_SUCCESS; } +DEFUNSH(VTYSH_BGPD, address_family_linkstate, address_family_linkstate_cmd, + "address-family link-state link-state", + "Enter Address Family command mode\n" BGP_AF_STR BGP_AF_MODIFIER_STR) +{ + vty->node = BGP_LS_NODE; + return CMD_SUCCESS; +} + DEFUNSH(VTYSH_BGPD, address_family_ipv4_multicast, address_family_ipv4_multicast_cmd, "address-family ipv4 multicast", "Enter Address Family command mode\n" @@ -2420,7 +2435,8 @@ DEFUNSH(VTYSH_BGPD, exit_address_family, exit_address_family_cmd, || vty->node == BGP_IPV6L_NODE || vty->node == BGP_IPV6M_NODE || vty->node == BGP_EVPN_NODE || vty->node == BGP_FLOWSPECV4_NODE - || vty->node == BGP_FLOWSPECV6_NODE) + || vty->node == BGP_FLOWSPECV6_NODE + || vty->node == BGP_LS_NODE) vty->node = BGP_NODE; return CMD_SUCCESS; } @@ -4671,6 +4687,13 @@ void vtysh_init_vty(void) install_element(BGP_EVPN_VNI_NODE, &vtysh_end_all_cmd); install_element(BGP_EVPN_VNI_NODE, &exit_vni_cmd); + install_node(&bgp_ls_node); + install_element(BGP_NODE, &address_family_linkstate_cmd); + install_element(BGP_LS_NODE, &vtysh_exit_bgpd_cmd); + install_element(BGP_LS_NODE, &vtysh_quit_bgpd_cmd); + install_element(BGP_LS_NODE, &vtysh_end_all_cmd); + install_element(BGP_LS_NODE, &exit_address_family_cmd); + install_node(&rpki_node); install_element(CONFIG_NODE, &rpki_cmd); install_element(RPKI_NODE, &rpki_exit_cmd); diff --git a/yang/frr-bgp-common-multiprotocol.yang b/yang/frr-bgp-common-multiprotocol.yang index c22bdf9964..f7f4ff5075 100644 --- a/yang/frr-bgp-common-multiprotocol.yang +++ b/yang/frr-bgp-common-multiprotocol.yang @@ -204,5 +204,15 @@ submodule frr-bgp-common-multiprotocol { description "IPv6 flowspec configuration options."; } + + container linkstate-linkstate { + when "derived-from-or-self(../afi-safi-name, 'frr-rt:linkstate-linkstate')" { + description + "Include this container for Link-State specific + configuration."; + } + description + "Link-State configuration options."; + } } } diff --git a/yang/frr-bgp.yang b/yang/frr-bgp.yang index b34fd43e78..557b9ef81b 100644 --- a/yang/frr-bgp.yang +++ b/yang/frr-bgp.yang @@ -819,6 +819,17 @@ module frr-bgp { uses structure-neighbor-group-filter-config; } + + augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/linkstate-linkstate" { + uses structure-neighbor-route-reflector; + + uses structure-neighbor-route-server; + + uses structure-neighbor-group-soft-reconfiguration; + + uses structure-neighbor-group-filter-config; + } + augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/bgp/neighbors/unnumbered-neighbor/afi-safis/afi-safi/ipv4-unicast" { uses structure-neighbor-group-add-paths; @@ -1090,6 +1101,16 @@ module frr-bgp { uses structure-neighbor-group-filter-config; } + augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/bgp/neighbors/unnumbered-neighbor/afi-safis/afi-safi/linkstate-linkstate" { + uses structure-neighbor-route-reflector; + + uses structure-neighbor-route-server; + + uses structure-neighbor-group-soft-reconfiguration; + + uses structure-neighbor-group-filter-config; + } + augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast" { uses structure-neighbor-group-add-paths; @@ -1366,4 +1387,14 @@ module frr-bgp { uses structure-neighbor-group-filter-config; } + + augment "/frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/linkstate-linkstate" { + uses structure-neighbor-route-reflector; + + uses structure-neighbor-route-server; + + uses structure-neighbor-group-soft-reconfiguration; + + uses structure-neighbor-group-filter-config; + } } diff --git a/yang/frr-nexthop.yang b/yang/frr-nexthop.yang index d73fdb8bd3..175487d78b 100644 --- a/yang/frr-nexthop.yang +++ b/yang/frr-nexthop.yang @@ -201,6 +201,11 @@ module frr-nexthop { description "Nexthop's MPLS label stack."; } + + uses srv6-segs-stack { + description + "Nexthop's SRv6 segs SIDs stack."; + } } /* @@ -298,6 +303,32 @@ module frr-nexthop { } } + /* Contaner for SRv6 segs SIDs */ + grouping srv6-segs-stack { + description + "This grouping specifies an SRv6 segs SIDs stack. The segs + SIDs stack is encoded as a list of SID entries. The + list key is an identifier that indicates the relative + ordering of each entry."; + container srv6-segs-stack { + description + "Container for a list of SRv6 segs SIDs entries."; + list entry { + key "id"; + description + "List of SRv6 segs SIDs entries."; + leaf id { + type uint8; + description + "Identifies the entry in a sequence of SRv6 segs SIDs + entries."; + } + leaf seg { + type inet:ipv6-address; + } + } + } + } container frr-nexthop-group { description "A nexthop-group, represented as a list of nexthop objects."; diff --git a/yang/frr-routing.yang b/yang/frr-routing.yang index b304c241a4..f6e1f8e74a 100644 --- a/yang/frr-routing.yang +++ b/yang/frr-routing.yang @@ -172,6 +172,13 @@ module frr-routing { } + identity linkstate-linkstate { + base afi-safi-type; + description + "This identity represents the link-state address family."; + } + + identity control-plane-protocol { description "Base identity from which control-plane protocol identities are diff --git a/zebra/connected.c b/zebra/connected.c index ee0823f56f..46f2a83a3c 100644 --- a/zebra/connected.c +++ b/zebra/connected.c @@ -234,6 +234,7 @@ void connected_up(struct interface *ifp, struct connected *ifc) break; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: flog_warn(EC_ZEBRA_CONNECTED_AFI_UNKNOWN, "Received unknown AFI: %s", afi2str(afi)); @@ -424,6 +425,7 @@ void connected_down(struct interface *ifp, struct connected *ifc) break; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: zlog_warn("Unknown AFI: %s", afi2str(afi)); break; diff --git a/zebra/router-id.c b/zebra/router-id.c index ef87d924fe..45edf45336 100644 --- a/zebra/router-id.c +++ b/zebra/router-id.c @@ -102,6 +102,7 @@ int router_id_get(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf) return 0; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: return -1; } @@ -126,6 +127,7 @@ static int router_id_set(afi_t afi, struct prefix *p, struct zebra_vrf *zvrf) break; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: return -1; } diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c index 9d8ef00b52..bfe1910a58 100644 --- a/zebra/rt_netlink.c +++ b/zebra/rt_netlink.c @@ -66,6 +66,7 @@ #include "zebra/zebra_evpn_mh.h" #include "zebra/zebra_trace.h" #include "zebra/zebra_neigh.h" +#include "lib/srv6.h" #ifndef AF_MPLS #define AF_MPLS 28 @@ -77,6 +78,8 @@ #define BR_SPH_LIST_SIZE 10 #endif +DEFINE_MTYPE_STATIC(LIB, NH_SRV6, "Nexthop srv6"); + static vlanid_t filter_vlan = 0; /* We capture whether the current kernel supports nexthop ids; by @@ -476,19 +479,19 @@ static int parse_encap_seg6(struct rtattr *tb, struct in6_addr *segs) { struct rtattr *tb_encap[SEG6_IPTUNNEL_MAX + 1] = {}; struct seg6_iptunnel_encap *ipt = NULL; - struct in6_addr *segments = NULL; + int i; netlink_parse_rtattr_nested(tb_encap, SEG6_IPTUNNEL_MAX, tb); - /* - * TODO: It's not support multiple SID list. - */ if (tb_encap[SEG6_IPTUNNEL_SRH]) { ipt = (struct seg6_iptunnel_encap *) RTA_DATA(tb_encap[SEG6_IPTUNNEL_SRH]); - segments = ipt->srh[0].segments; - *segs = segments[0]; - return 1; + + for (i = ipt->srh[0].first_segment; i >= 0; i--) + memcpy(&segs[i], &ipt->srh[0].segments[i], + sizeof(struct in6_addr)); + + return ipt->srh[0].first_segment + 1; } return 0; @@ -506,7 +509,7 @@ parse_nexthop_unicast(ns_id_t ns_id, struct rtmsg *rtm, struct rtattr **tb, int num_labels = 0; enum seg6local_action_t seg6l_act = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; struct seg6local_context seg6l_ctx = {}; - struct in6_addr seg6_segs = {}; + struct in6_addr segs[SRV6_MAX_SIDS] = {}; int num_segs = 0; vrf_id_t nh_vrf_id = vrf_id; @@ -555,7 +558,7 @@ parse_nexthop_unicast(ns_id_t ns_id, struct rtmsg *rtm, struct rtattr **tb, if (tb[RTA_ENCAP] && tb[RTA_ENCAP_TYPE] && *(uint16_t *)RTA_DATA(tb[RTA_ENCAP_TYPE]) == LWTUNNEL_ENCAP_SEG6) { - num_segs = parse_encap_seg6(tb[RTA_ENCAP], &seg6_segs); + num_segs = parse_encap_seg6(tb[RTA_ENCAP], segs); } if (rtm->rtm_flags & RTNH_F_ONLINK) @@ -581,7 +584,7 @@ parse_nexthop_unicast(ns_id_t ns_id, struct rtmsg *rtm, struct rtattr **tb, nexthop_add_srv6_seg6local(&nh, seg6l_act, &seg6l_ctx); if (num_segs) - nexthop_add_srv6_seg6(&nh, &seg6_segs); + nexthop_add_srv6_seg6(&nh, segs, num_segs); return nh; } @@ -601,7 +604,7 @@ static uint8_t parse_multipath_nexthops_unicast(ns_id_t ns_id, int num_labels = 0; enum seg6local_action_t seg6l_act = ZEBRA_SEG6_LOCAL_ACTION_UNSPEC; struct seg6local_context seg6l_ctx = {}; - struct in6_addr seg6_segs = {}; + struct in6_addr segs[SRV6_MAX_SIDS] = {}; int num_segs = 0; struct rtattr *rtnh_tb[RTA_MAX + 1] = {}; @@ -657,7 +660,7 @@ static uint8_t parse_multipath_nexthops_unicast(ns_id_t ns_id, && *(uint16_t *)RTA_DATA(rtnh_tb[RTA_ENCAP_TYPE]) == LWTUNNEL_ENCAP_SEG6) { num_segs = parse_encap_seg6(rtnh_tb[RTA_ENCAP], - &seg6_segs); + segs); } } @@ -700,7 +703,7 @@ static uint8_t parse_multipath_nexthops_unicast(ns_id_t ns_id, &seg6l_ctx); if (num_segs) - nexthop_add_srv6_seg6(nh, &seg6_segs); + nexthop_add_srv6_seg6(nh, segs, num_segs); if (rtnh->rtnh_flags & RTNH_F_ONLINK) SET_FLAG(nh->flags, NEXTHOP_FLAG_ONLINK); @@ -1514,37 +1517,40 @@ static bool _netlink_route_encode_nexthop_src(const struct nexthop *nexthop, } static ssize_t fill_seg6ipt_encap(char *buffer, size_t buflen, - const struct in6_addr *seg) + struct seg6_seg_stack *segs) { struct seg6_iptunnel_encap *ipt; struct ipv6_sr_hdr *srh; - const size_t srhlen = 24; + size_t srhlen; + int i; - /* - * Caution: Support only SINGLE-SID, not MULTI-SID - * This function only supports the case where segs represents - * a single SID. If you want to extend the SRv6 functionality, - * you should improve the Boundary Check. - * Ex. In case of set a SID-List include multiple-SIDs as an - * argument of the Transit Behavior, we must support variable - * boundary check for buflen. - */ - if (buflen < (sizeof(struct seg6_iptunnel_encap) + - sizeof(struct ipv6_sr_hdr) + 16)) + if (segs->num_segs > SRV6_MAX_SEGS) { + /* Exceeding maximum supported SIDs */ + return -1; + } + + srhlen = SRH_BASE_HEADER_LENGTH + SRH_SEGMENT_LENGTH * segs->num_segs; + + if (buflen < (sizeof(struct seg6_iptunnel_encap) + srhlen)) return -1; memset(buffer, 0, buflen); ipt = (struct seg6_iptunnel_encap *)buffer; ipt->mode = SEG6_IPTUN_MODE_ENCAP; - srh = ipt->srh; + + srh = (struct ipv6_sr_hdr *)&ipt->srh; srh->hdrlen = (srhlen >> 3) - 1; srh->type = 4; - srh->segments_left = 0; - srh->first_segment = 0; - memcpy(&srh->segments[0], seg, sizeof(struct in6_addr)); + srh->segments_left = segs->num_segs - 1; + srh->first_segment = segs->num_segs - 1; + + for (i = 0; i < segs->num_segs; i++) { + memcpy(&srh->segments[i], &segs->seg[i], + sizeof(struct in6_addr)); + } - return srhlen + 4; + return sizeof(struct seg6_iptunnel_encap) + srhlen; } static bool @@ -1726,7 +1732,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p, nl_attr_nest_end(nlmsg, nest); } - if (!sid_zero(&nexthop->nh_srv6->seg6_segs)) { + if (nexthop->nh_srv6->seg6_segs && + nexthop->nh_srv6->seg6_segs->num_segs && + !sid_zero(nexthop->nh_srv6->seg6_segs)) { char tun_buf[4096]; ssize_t tun_len; struct rtattr *nest; @@ -1737,8 +1745,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p, nest = nl_attr_nest(nlmsg, req_size, RTA_ENCAP); if (!nest) return false; - tun_len = fill_seg6ipt_encap(tun_buf, sizeof(tun_buf), - &nexthop->nh_srv6->seg6_segs); + tun_len = + fill_seg6ipt_encap(tun_buf, sizeof(tun_buf), + nexthop->nh_srv6->seg6_segs); if (tun_len < 0) return false; if (!nl_attr_put(nlmsg, req_size, SEG6_IPTUNNEL_SRH, @@ -2971,7 +2980,9 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd, nl_attr_nest_end(&req->n, nest); } - if (!sid_zero(&nh->nh_srv6->seg6_segs)) { + if (nh->nh_srv6->seg6_segs && + nh->nh_srv6->seg6_segs->num_segs && + !sid_zero(nh->nh_srv6->seg6_segs)) { char tun_buf[4096]; ssize_t tun_len; struct rtattr *nest; @@ -2984,9 +2995,9 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd, NHA_ENCAP | NLA_F_NESTED); if (!nest) return 0; - tun_len = fill_seg6ipt_encap(tun_buf, - sizeof(tun_buf), - &nh->nh_srv6->seg6_segs); + tun_len = fill_seg6ipt_encap( + tun_buf, sizeof(tun_buf), + nh->nh_srv6->seg6_segs); if (tun_len < 0) return 0; if (!nl_attr_put(&req->n, buflen, diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 6bed6d8727..86e4f2570c 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -565,6 +565,7 @@ int zsend_redistribute_route(int cmd, struct zserv *client, client->redist_v6_del_cnt++; break; case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: case AFI_UNSPEC: break; @@ -1794,7 +1795,8 @@ static bool zapi_read_nexthops(struct zserv *client, struct prefix *p, if (IS_ZEBRA_DEBUG_RECV) zlog_debug("%s: adding seg6", __func__); - nexthop_add_srv6_seg6(nexthop, &api_nh->seg6_segs); + nexthop_add_srv6_seg6(nexthop, &api_nh->seg6_segs[0], + api_nh->seg_num); } if (IS_ZEBRA_DEBUG_RECV) { diff --git a/zebra/zebra_nb.c b/zebra/zebra_nb.c index a0ef08273c..a93dbbb008 100644 --- a/zebra/zebra_nb.c +++ b/zebra/zebra_nb.c @@ -598,6 +598,28 @@ const struct frr_yang_module_info frr_zebra_info = { .get_elem = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_color_get_elem, } }, + + { + .xpath = "/frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry", + .cbs = { + .get_next = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_next, + .get_keys = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_keys, + .lookup_entry = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_lookup_entry, + } + }, + { + .xpath = "/frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry/id", + .cbs = { + .get_elem = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_id_get_elem, + } + }, + { + .xpath = "/frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry/seg", + .cbs = { + .get_elem = lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_seg_get_elem, + } + }, + { .xpath = "/frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/mpls-label-stack/entry", .cbs = { diff --git a/zebra/zebra_nb.h b/zebra/zebra_nb.h index a066a7f9dc..80d2aaa6fe 100644 --- a/zebra/zebra_nb.h +++ b/zebra/zebra_nb.h @@ -240,6 +240,20 @@ struct yang_data * lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_color_get_elem( struct nb_cb_get_elem_args *args); const void * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_next( + struct nb_cb_get_next_args *args); +int lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_keys( + struct nb_cb_get_keys_args *args); +const void * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_lookup_entry( + struct nb_cb_lookup_entry_args *args); +struct yang_data * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_id_get_elem( + struct nb_cb_get_elem_args *args); +struct yang_data * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_seg_get_elem( + struct nb_cb_get_elem_args *args); +const void * lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_mpls_label_stack_entry_get_next( struct nb_cb_get_next_args *args); int lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_mpls_label_stack_entry_get_keys( diff --git a/zebra/zebra_nb_state.c b/zebra/zebra_nb_state.c index acf0b80aca..ba537475cb 100644 --- a/zebra/zebra_nb_state.c +++ b/zebra/zebra_nb_state.c @@ -842,6 +842,58 @@ lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_color_get_elem( /* * XPath: + * /frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry + */ +const void * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_next( + struct nb_cb_get_next_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + +int lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_get_keys( + struct nb_cb_get_keys_args *args) +{ + /* TODO: implement me. */ + return NB_OK; +} + +const void * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_lookup_entry( + struct nb_cb_lookup_entry_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + +/* + * XPath: + * /frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry/id + */ +struct yang_data * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_id_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + +/* + * XPath: + * /frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/srv6-segs-stack/entry/seg + */ +struct yang_data * +lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_srv6_segs_stack_entry_seg_get_elem( + struct nb_cb_get_elem_args *args) +{ + /* TODO: implement me. */ + return NULL; +} + + +/* + * XPath: * /frr-vrf:lib/vrf/frr-zebra:zebra/ribs/rib/route/route-entry/nexthop-group/nexthop/mpls-label-stack/entry */ const void * diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c index 3a56bf2a50..d1a84491aa 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c @@ -1868,11 +1868,18 @@ static struct nexthop *nexthop_set_resolved(afi_t afi, labels); if (nexthop->nh_srv6) { - nexthop_add_srv6_seg6local(resolved_hop, - nexthop->nh_srv6->seg6local_action, - &nexthop->nh_srv6->seg6local_ctx); - nexthop_add_srv6_seg6(resolved_hop, - &nexthop->nh_srv6->seg6_segs); + if (nexthop->nh_srv6->seg6local_action != + ZEBRA_SEG6_LOCAL_ACTION_UNSPEC) + nexthop_add_srv6_seg6local(resolved_hop, + nexthop->nh_srv6 + ->seg6local_action, + &nexthop->nh_srv6 + ->seg6local_ctx); + if (nexthop->nh_srv6->seg6_segs) + nexthop_add_srv6_seg6(resolved_hop, + &nexthop->nh_srv6->seg6_segs->seg[0], + nexthop->nh_srv6->seg6_segs + ->num_segs); } resolved_hop->rparent = nexthop; @@ -2281,6 +2288,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, break; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", __func__, @@ -2324,6 +2332,7 @@ static int nexthop_active(struct nexthop *nexthop, struct nhg_hash_entry *nhe, break; case AFI_UNSPEC: case AFI_L2VPN: + case AFI_LINKSTATE: case AFI_MAX: assert(afi != AFI_IP && afi != AFI_IP6); break; diff --git a/zebra/zebra_rnh.c b/zebra/zebra_rnh.c index 28b83ce8b6..30d92c30f4 100644 --- a/zebra/zebra_rnh.c +++ b/zebra/zebra_rnh.c @@ -1268,6 +1268,7 @@ void show_nexthop_json_helper(json_object *json_nexthop, json_object *json_backups = NULL; json_object *json_seg6local = NULL; json_object *json_seg6 = NULL; + json_object *json_segs = NULL; int i; json_object_int_add(json_nexthop, "flags", nexthop->flags); @@ -1425,11 +1426,31 @@ void show_nexthop_json_helper(json_object *json_nexthop, nexthop->nh_srv6->seg6local_action)); json_object_object_add(json_nexthop, "seg6local", json_seg6local); - - json_seg6 = json_object_new_object(); - json_object_string_addf(json_seg6, "segs", "%pI6", - &nexthop->nh_srv6->seg6_segs); - json_object_object_add(json_nexthop, "seg6", json_seg6); + if (nexthop->nh_srv6->seg6_segs && + nexthop->nh_srv6->seg6_segs->num_segs == 1) { + json_seg6 = json_object_new_object(); + json_object_string_addf(json_seg6, "segs", "%pI6", + &nexthop->nh_srv6->seg6_segs + ->seg[0]); + json_object_object_add(json_nexthop, "seg6", json_seg6); + } else { + json_segs = json_object_new_array(); + if (nexthop->nh_srv6->seg6_segs) { + for (int seg_idx = 0; + seg_idx < + nexthop->nh_srv6->seg6_segs->num_segs; + seg_idx++) + json_object_array_add( + json_segs, + json_object_new_stringf( + "%pI6", + &nexthop->nh_srv6 + ->seg6_segs + ->seg[seg_idx])); + json_object_object_add(json_nexthop, "seg6", + json_segs); + } + } } } @@ -1440,7 +1461,9 @@ void show_route_nexthop_helper(struct vty *vty, const struct route_entry *re, const struct nexthop *nexthop) { char buf[MPLS_LABEL_STRLEN]; - int i; + char seg_buf[SRV6_SEG_STRLEN]; + struct seg6_segs segs; + uint8_t i; switch (nexthop->type) { case NEXTHOP_TYPE_IPV4: @@ -1538,9 +1561,17 @@ void show_route_nexthop_helper(struct vty *vty, const struct route_entry *re, seg6local_action2str( nexthop->nh_srv6->seg6local_action), buf); - if (IPV6_ADDR_CMP(&nexthop->nh_srv6->seg6_segs, &in6addr_any)) - vty_out(vty, ", seg6 %pI6", - &nexthop->nh_srv6->seg6_segs); + if (nexthop->nh_srv6->seg6_segs && + IPV6_ADDR_CMP(&nexthop->nh_srv6->seg6_segs->seg[0], + &in6addr_any)) { + segs.num_segs = nexthop->nh_srv6->seg6_segs->num_segs; + for (i = 0; i < segs.num_segs; i++) + memcpy(&segs.segs[i], + &nexthop->nh_srv6->seg6_segs->seg[i], + sizeof(struct in6_addr)); + snprintf_seg6_segs(seg_buf, SRV6_SEG_STRLEN, &segs); + vty_out(vty, ", seg6 %s", seg_buf); + } } if (nexthop->weight) diff --git a/zebra/zebra_snmp.c b/zebra/zebra_snmp.c index e06733cb8c..8cab184953 100644 --- a/zebra/zebra_snmp.c +++ b/zebra/zebra_snmp.c @@ -353,7 +353,7 @@ static void get_fwtable_route_node(struct variable *v, oid objid[], if (policy) /* Not supported (yet?) */ return; for (*np = route_top(table); *np; *np = route_next(*np)) { - if (!in_addr_cmp(&(*np)->p.u.prefix, + if (!in_addr_cmp((uint8_t *)&(*np)->p.u.prefix4, (uint8_t *)&dest)) { RNODE_FOREACH_RE (*np, *re) { if (!in_addr_cmp((uint8_t *)&(*re)->nhe @@ -374,13 +374,14 @@ static void get_fwtable_route_node(struct variable *v, oid objid[], for (np2 = route_top(table); np2; np2 = route_next(np2)) { /* Check destination first */ - if (in_addr_cmp(&np2->p.u.prefix, (uint8_t *)&dest) > 0) + if (in_addr_cmp((uint8_t *)&np2->p.u.prefix4, + (uint8_t *)&dest) > 0) RNODE_FOREACH_RE (np2, re2) { check_replace(np2, re2, np, re); } - if (in_addr_cmp(&np2->p.u.prefix, (uint8_t *)&dest) - == 0) { /* have to look at each re individually */ + if (in_addr_cmp((uint8_t *)&np2->p.u.prefix4, (uint8_t *)&dest) == + 0) { /* have to look at each re individually */ RNODE_FOREACH_RE (np2, re2) { int proto2, policy2; |
