diff options
128 files changed, 8085 insertions, 244 deletions
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 6735c1a952..e6276d060e 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -1836,7 +1836,8 @@ bool subgroup_announce_check(struct bgp_dest *dest, struct bgp_path_info *pi, /* If community is not disabled check the no-export and local. */ if (!transparent && bgp_community_filter(peer, piattr)) { if (bgp_debug_update(NULL, p, subgrp->update_group, 0)) - zlog_debug("%s: community filter check fail", __func__); + zlog_debug("%s: community filter check fail for %pFX", + __func__, p); return false; } @@ -3505,18 +3506,20 @@ bool bgp_update_martian_nexthop(struct bgp *bgp, afi_t afi, safi_t safi, return ret; } -static void bgp_attr_add_no_advertise_community(struct attr *attr) +static void bgp_attr_add_no_export_community(struct attr *attr) { struct community *old; struct community *new; struct community *merge; - struct community *noadv; + struct community *no_export; old = attr->community; - noadv = community_str2com("no-advertise"); + no_export = community_str2com("no-export"); + + assert(no_export); if (old) { - merge = community_merge(community_dup(old), noadv); + merge = community_merge(community_dup(old), no_export); if (!old->refcnt) community_free(&old); @@ -3524,10 +3527,10 @@ static void bgp_attr_add_no_advertise_community(struct attr *attr) new = community_uniq_sort(merge); community_free(&merge); } else { - new = community_dup(noadv); + new = community_dup(no_export); } - community_free(&noadv); + community_free(&no_export); attr->community = new; attr->flag |= ATTR_FLAG_BIT(BGP_ATTR_COMMUNITIES); @@ -3737,7 +3740,7 @@ int bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id, if (new_attr.community && community_include(new_attr.community, COMMUNITY_BLACKHOLE)) - bgp_attr_add_no_advertise_community(&new_attr); + bgp_attr_add_no_export_community(&new_attr); /* If we receive the graceful-shutdown community from an eBGP * peer we must lower local-preference */ diff --git a/doc/developer/ospf-sr.rst b/doc/developer/ospf-sr.rst index efe9b1b12f..263db9dfb9 100644 --- a/doc/developer/ospf-sr.rst +++ b/doc/developer/ospf-sr.rst @@ -17,6 +17,7 @@ Supported Features * Automatic provisioning of MPLS table * Equal Cost Multi-Path (ECMP) * Static route configuration with label stack up to 32 labels +* TI-LFA (for P2P interfaces only) Interoperability ---------------- @@ -182,6 +183,71 @@ called. Once check the validity of labels, they are send to ZEBRA layer through command for deletion. This is completed by a new labelled route through `ZEBRA_ROUTE_ADD` command, respectively `ZEBRA_ROUTE_DELETE` command. +TI-LFA +^^^^^^ + +Experimental support for Topology Independent LFA (Loop-Free Alternate), see +for example 'draft-bashandy-rtgwg-segment-routing-ti-lfa-05'. The related +files are `ospf_ti_lfa.c/h`. + +The current implementation is rather naive and does not support the advanced +optimizations suggested in e.g. RFC7490 or RFC8102. It focuses on providing +the essential infrastructure which can also later be used to enhance the +algorithmic aspects. + +Supported features: + +* Link and node protection +* Intra-area support +* Proper use of Prefix- and Adjacency-SIDs in label stacks +* Asymmetric weights (using reverse SPF) +* Non-adjacent P/Q spaces +* Protection of Prefix-SIDs + +If configured for every SPF run the routing table is enriched with additional +backup paths for every prefix. The corresponding Prefix-SIDs are updated with +backup paths too within the OSPF SR update task. + +Informal High-Level Algorithm Description: + +:: + + p_spaces = empty_list() + + for every protected_resource (link or node): + p_space = generate_p_space(protected_resource) + p_space.q_spaces = empty_list() + + for every destination that is affected by the protected_resource: + q_space = generate_q_space(destination) + + # The label stack is stored in q_space + generate_label_stack(p_space, q_space) + + # The p_space collects all its q_spaces + p_spaces.q_spaces.add(q_space) + + p_spaces.add(p_space) + + adjust_routing_table(p_spaces) + +Possible Performance Improvements: + +* Improve overall datastructures, get away from linked lists for vertices +* Don't calculate a Q space for every destination, but for a minimum set of + backup paths that cover all destinations in the post-convergence SPF. The + thinking here is that once a backup path is known that it is also a backup + path for all nodes on the path themselves. This can be done by using the + leafs of a trimmed minimum spanning tree generated out of the post- + convergence SPF tree for that particular P space. +* For an alternative (maybe better) optimization look at + https://tools.ietf.org/html/rfc7490#section-5.2.1.3 which describes using + the Q space of the node which is affected by e.g. a link failure. Note that + this optimization is topology dependent. + +It is highly recommended to read e.g. `Segment Routing I/II` by Filsfils to +understand the basics of Ti-LFA. + Configuration ------------- diff --git a/doc/user/fabricd.rst b/doc/user/fabricd.rst index a74d3e098b..17a51ccb3c 100644 --- a/doc/user/fabricd.rst +++ b/doc/user/fabricd.rst @@ -57,6 +57,19 @@ in the configuration: Configure the authentication password for a domain, as clear text or md5 one. +.. index:: attached-bit [receive ignore | send] +.. clicmd:: attached-bit [receive ignore | send] + +.. index:: attached-bit +.. clicmd:: no attached-bit + + Set attached bit for inter-area traffic: + + - receive + If LSP received with attached bit set, create default route to neighbor + - send + If L1|L2 router, set attached bit in LSP sent to L1 router + .. index:: log-adjacency-changes .. clicmd:: log-adjacency-changes @@ -64,7 +77,7 @@ in the configuration: .. clicmd:: no log-adjacency-changes Log changes in adjacency state. - + .. index:: set-overload-bit .. clicmd:: set-overload-bit diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst index 7e198564b5..352701728d 100644 --- a/doc/user/isisd.rst +++ b/doc/user/isisd.rst @@ -72,6 +72,19 @@ writing, *isisd* does not support multiple ISIS processes. Configure the authentication password for an area, respectively a domain, as clear text or md5 one. +.. index:: attached-bit [receive ignore | send] +.. clicmd:: attached-bit [receive ignore | send] + +.. index:: attached-bit +.. clicmd:: no attached-bit + + Set attached bit for inter-area traffic: + + - receive + If LSP received with attached bit set, create default route to neighbor + - send + If L1|L2 router, set attached bit in LSP sent to L1 router + .. index:: log-adjacency-changes .. clicmd:: log-adjacency-changes diff --git a/doc/user/ospfd.rst b/doc/user/ospfd.rst index 7184a0e197..ee02a9dae5 100644 --- a/doc/user/ospfd.rst +++ b/doc/user/ospfd.rst @@ -1233,6 +1233,20 @@ Summary Route will be originated on-behalf of all matched external LSAs. Show configuration for display all configured summary routes with matching external LSA information. +TI-LFA +====== + +Experimental support for Topology Independent LFA (Loop-Free Alternate), see +for example 'draft-bashandy-rtgwg-segment-routing-ti-lfa-05'. Note that +TI-LFA requires a proper Segment Routing configuration. + +.. index:: fast-reroute ti-lfa [node-protection] +.. clicmd:: fast-reroute ti-lfa [node-protection] + + Configured on the router level. Activates TI-LFA for all interfaces. + + Note that so far only P2P interfaces are supported. + Debugging OSPF ============== diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index ed6453e2b1..126710f8c2 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -1,10 +1,9 @@ # This stage builds a dist tarball from the source -FROM alpine:edge as source-builder +FROM alpine:latest as source-builder RUN mkdir -p /src/alpine COPY alpine/APKBUILD.in /src/alpine RUN source /src/alpine/APKBUILD.in \ - && echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \ && apk add \ --no-cache \ --update-cache \ @@ -22,10 +21,9 @@ RUN cd /src \ && make dist # This stage builds an apk from the dist tarball -FROM alpine:edge as alpine-builder +FROM alpine:latest as alpine-builder # Don't use nocache here so that abuild can use the cache -RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \ - && apk add \ +RUN apk add \ --update-cache \ abuild \ alpine-conf \ @@ -46,11 +44,10 @@ RUN cd /dist \ && abuild -r -P /pkgs/apk # This stage installs frr from the apk -FROM alpine:edge +FROM alpine:latest RUN mkdir -p /pkgs/apk COPY --from=alpine-builder /pkgs/apk/ /pkgs/apk/ -RUN echo 'http://dl-cdn.alpinelinux.org/alpine/edge/testing' >> /etc/apk/repositories \ - && apk add \ +RUN apk add \ --no-cache \ --update-cache \ tini \ diff --git a/docker/centos-7/Dockerfile b/docker/centos-7/Dockerfile index cca8baa147..a92326fcf3 100644 --- a/docker/centos-7/Dockerfile +++ b/docker/centos-7/Dockerfile @@ -5,8 +5,8 @@ RUN yum install -y rpm-build autoconf automake libtool make \ readline-devel texinfo net-snmp-devel groff pkgconfig \ json-c-devel pam-devel bison flex pytest c-ares-devel \ python3-devel python3-sphinx systemd-devel libcap-devel \ - https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-0.16.111-0.x86_64.rpm \ - https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-devel-0.16.111-0.x86_64.rpm \ + https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-7-x86_64-Packages/libyang1-1.0.184-0.x86_64.rpm \ + https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-7-x86_64-Packages/libyang-devel-1.0.184-0.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-0.7.0-1.el7.centos.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-devel-0.7.0-1.el7.centos.x86_64.rpm @@ -32,7 +32,7 @@ RUN echo '%_smp_mflags %( echo "-j$(/usr/bin/getconf _NPROCESSORS_ONLN)"; )' >> # This stage installs frr from the rpm FROM centos:centos7 RUN mkdir -p /pkgs/rpm \ - && yum install -y https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-0.16.111-0.x86_64.rpm \ + && yum install -y https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-7-x86_64-Packages/libyang1-1.0.184-0.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-0.7.0-1.el7.centos.x86_64.rpm COPY --from=centos-7-builder /rpmbuild/RPMS/ /pkgs/rpm/ diff --git a/docker/centos-8/Dockerfile b/docker/centos-8/Dockerfile index 6c1f873589..7ed7948927 100644 --- a/docker/centos-8/Dockerfile +++ b/docker/centos-8/Dockerfile @@ -1,17 +1,15 @@ # This stage builds an rpm from the source FROM centos:centos8 as centos-8-builder -RUN dnf install --enablerepo=PowerTools -y rpm-build git autoconf pcre-devel \ +RUN dnf install --enablerepo=powertools -y rpm-build git autoconf pcre-devel \ automake libtool make readline-devel texinfo net-snmp-devel pkgconfig \ - groff pkgconfig json-c-devel pam-devel bison flex python2-pytest \ - c-ares-devel python2-devel systemd-devel libcap-devel platform-python-devel \ - https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-0.16.111-0.x86_64.rpm \ - https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-devel-0.16.111-0.x86_64.rpm \ + groff pkgconfig json-c-devel pam-devel bison flex python3-pytest \ + c-ares-devel python3-devel python3-sphinx systemd-devel libcap-devel platform-python-devel \ + https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-8-x86_64-Packages/libyang1-1.0.184-0.x86_64.rpm \ + https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-8-x86_64-Packages/libyang-devel-1.0.184-0.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-0.7.0-1.el7.centos.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-devel-0.7.0-1.el7.centos.x86_64.rpm -RUN pip2 install sphinx - COPY . /src ARG PKGVER @@ -35,7 +33,7 @@ RUN echo '%_smp_mflags %( echo "-j$(/usr/bin/getconf _NPROCESSORS_ONLN)"; )' >> # This stage installs frr from the rpm FROM centos:centos8 RUN mkdir -p /pkgs/rpm \ - && yum install -y https://ci1.netdef.org/artifact/LIBYANG-YANGRELEASE/shared/build-10/CentOS-7-x86_64-Packages/libyang-0.16.111-0.x86_64.rpm \ + && yum install -y https://ci1.netdef.org/artifact/LIBYANG-LY1REL/shared/build-4/CentOS-8-x86_64-Packages/libyang1-1.0.184-0.x86_64.rpm \ https://ci1.netdef.org/artifact/RPKI-RTRLIB/shared/build-110/CentOS-7-x86_64-Packages/librtr-0.7.0-1.el7.centos.x86_64.rpm COPY --from=centos-8-builder /rpmbuild/RPMS/ /pkgs/rpm/ diff --git a/isisd/isis_cli.c b/isisd/isis_cli.c index 5ca70eab0f..b48da9312f 100644 --- a/isisd/isis_cli.c +++ b/isisd/isis_cli.c @@ -593,6 +593,10 @@ void cli_show_isis_overload(struct vty *vty, struct lyd_node *dnode, vty_out(vty, " set-overload-bit\n"); } +#if CONFDATE > 20220119 +CPP_NOTICE( + "Use of `set-attached-bit` is deprecated please use attached-bit [send | receive]") +#endif /* * XPath: /frr-isisd:isis/instance/attached */ @@ -600,18 +604,57 @@ DEFPY_YANG(set_attached_bit, set_attached_bit_cmd, "[no] set-attached-bit", "Reset attached bit\n" "Set attached bit to identify as L1/L2 router for inter-area traffic\n") { - nb_cli_enqueue_change(vty, "./attached", NB_OP_MODIFY, + vty_out(vty, + "set-attached-bit deprecated please use attached-bit [send | receive]\n"); + + return CMD_SUCCESS; +} + +/* + * XPath: /frr-isisd:isis/instance/attach-send + */ +DEFPY_YANG(attached_bit_send, attached_bit_send_cmd, "[no] attached-bit send", + "Reset attached bit\n" + "Set attached bit for inter-area traffic\n" + "Set attached bit in LSP sent to L1 router\n") +{ + nb_cli_enqueue_change(vty, "./attach-send", NB_OP_MODIFY, no ? "false" : "true"); return nb_cli_apply_changes(vty, NULL); } -void cli_show_isis_attached(struct vty *vty, struct lyd_node *dnode, - bool show_defaults) +void cli_show_isis_attached_send(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) +{ + if (!yang_dnode_get_bool(dnode, NULL)) + vty_out(vty, " no"); + vty_out(vty, " attached-bit send\n"); +} + +/* + * XPath: /frr-isisd:isis/instance/attach-receive-ignore + */ +DEFPY_YANG( + attached_bit_receive_ignore, attached_bit_receive_ignore_cmd, + "[no] attached-bit receive ignore", + "Reset attached bit\n" + "Set attach bit for inter-area traffic\n" + "If LSP received with attached bit set, create default route to neighbor\n" + "Do not process attached bit\n") +{ + nb_cli_enqueue_change(vty, "./attach-receive-ignore", NB_OP_MODIFY, + no ? "false" : "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +void cli_show_isis_attached_receive(struct vty *vty, struct lyd_node *dnode, + bool show_defaults) { if (!yang_dnode_get_bool(dnode, NULL)) vty_out(vty, " no"); - vty_out(vty, " set-attached-bit\n"); + vty_out(vty, " attached-bit receive ignore\n"); } /* @@ -3206,6 +3249,8 @@ void isis_cli_init(void) install_element(ISIS_NODE, &set_overload_bit_cmd); install_element(ISIS_NODE, &set_attached_bit_cmd); + install_element(ISIS_NODE, &attached_bit_send_cmd); + install_element(ISIS_NODE, &attached_bit_receive_ignore_cmd); install_element(ISIS_NODE, &metric_style_cmd); install_element(ISIS_NODE, &no_metric_style_cmd); diff --git a/isisd/isis_constants.h b/isisd/isis_constants.h index 25eae06cb0..3d6a20ee66 100644 --- a/isisd/isis_constants.h +++ b/isisd/isis_constants.h @@ -140,7 +140,7 @@ * LSP bit masks */ #define LSPBIT_P 0x80 -#define LSPBIT_ATT 0x78 +#define LSPBIT_ATT 0x08 /* only use the Default ATT bit */ #define LSPBIT_OL 0x04 #define LSPBIT_IST 0x03 @@ -158,7 +158,6 @@ #define ISIS_MASK_LSP_ATT_ERROR_BIT(x) ((x)&0x40) #define ISIS_MASK_LSP_ATT_EXPENSE_BIT(x) ((x)&0x20) #define ISIS_MASK_LSP_ATT_DELAY_BIT(x) ((x)&0x10) -#define ISIS_MASK_LSP_ATT_DEFAULT_BIT(x) ((x)&0x8) #define LLC_LEN 3 diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c index 47225ea2c3..4c70bd12b7 100644 --- a/isisd/isis_lsp.c +++ b/isisd/isis_lsp.c @@ -399,7 +399,50 @@ static void lsp_seqno_update(struct isis_lsp *lsp0) return; } -static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit) +static bool isis_level2_adj_up(struct isis_area *curr_area) +{ + struct listnode *node, *cnode; + struct isis_circuit *circuit; + struct list *adjdb; + struct isis_adjacency *adj; + struct isis *isis = curr_area->isis; + struct isis_area *area; + + /* lookup for a Level2 adjacency up in another area */ + for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) { + if (area->area_tag + && strcmp(area->area_tag, curr_area->area_tag) == 0) + continue; + + for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) { + if (circuit->circ_type == CIRCUIT_T_BROADCAST) { + adjdb = circuit->u.bc.adjdb[1]; + if (adjdb && adjdb->count) { + for (ALL_LIST_ELEMENTS_RO(adjdb, node, + adj)) + if ((adj->level + == ISIS_ADJ_LEVEL2 + || adj->level + == ISIS_ADJ_LEVEL1AND2) + && adj->adj_state + == ISIS_ADJ_UP) + return true; + } + } else if (circuit->circ_type == CIRCUIT_T_P2P + && circuit->u.p2p.neighbor) { + adj = circuit->u.p2p.neighbor; + if ((adj->level == ISIS_ADJ_LEVEL2 + || adj->level == ISIS_ADJ_LEVEL1AND2) + && adj->adj_state == ISIS_ADJ_UP) + return true; + } + } + } + return false; +} + +static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit, + struct isis_area *area) { uint8_t lsp_bits = 0; if (level == IS_LEVEL_1) @@ -408,8 +451,13 @@ static uint8_t lsp_bits_generate(int level, int overload_bit, int attached_bit) lsp_bits = IS_LEVEL_1_AND_2; if (overload_bit) lsp_bits |= overload_bit; - if (attached_bit) - lsp_bits |= attached_bit; + + /* only set the attach bit if we are a level-1-2 router and this is + * a level-1 LSP and we have a level-2 adjacency up from another area + */ + if (area->is_type == IS_LEVEL_1_AND_2 && level == IS_LEVEL_1 + && attached_bit && isis_level2_adj_up(area)) + lsp_bits |= LSPBIT_ATT; return lsp_bits; } @@ -632,13 +680,13 @@ static const char *lsp_bits2string(uint8_t lsp_bits, char *buf, size_t buf_size) return " error"; /* we only focus on the default metric */ - pos += sprintf(pos, "%d/", - ISIS_MASK_LSP_ATT_DEFAULT_BIT(lsp_bits) ? 1 : 0); + pos += snprintf(pos, buf_size, "%d/", + ISIS_MASK_LSP_ATT_BITS(lsp_bits) ? 1 : 0); - pos += sprintf(pos, "%d/", - ISIS_MASK_LSP_PARTITION_BIT(lsp_bits) ? 1 : 0); + pos += snprintf(pos, buf_size, "%d/", + ISIS_MASK_LSP_PARTITION_BIT(lsp_bits) ? 1 : 0); - sprintf(pos, "%d", ISIS_MASK_LSP_OL_BIT(lsp_bits) ? 1 : 0); + snprintf(pos, buf_size, "%d", ISIS_MASK_LSP_OL_BIT(lsp_bits) ? 1 : 0); return buf; } @@ -838,7 +886,7 @@ static struct isis_lsp *lsp_next_frag(uint8_t frag_num, struct isis_lsp *lsp0, lsp = lsp_new(area, frag_id, lsp0->hdr.rem_lifetime, 0, lsp_bits_generate(level, area->overload_bit, - area->attached_bit), + area->attached_bit_send, area), 0, lsp0, level); lsp->own_lsp = 1; lsp_insert(&area->lspdb[level - 1], lsp); @@ -864,7 +912,7 @@ static void lsp_build(struct isis_lsp *lsp, struct isis_area *area) area->area_tag, level); lsp->hdr.lsp_bits = lsp_bits_generate(level, area->overload_bit, - area->attached_bit); + area->attached_bit_send, area); lsp_add_auth(lsp); @@ -1223,10 +1271,10 @@ int lsp_generate(struct isis_area *area, int level) oldlsp->hdr.lsp_id); } rem_lifetime = lsp_rem_lifetime(area, level); - newlsp = - lsp_new(area, lspid, rem_lifetime, seq_num, - area->is_type | area->overload_bit | area->attached_bit, - 0, NULL, level); + newlsp = lsp_new(area, lspid, rem_lifetime, seq_num, + lsp_bits_generate(area->is_type, area->overload_bit, + area->attached_bit_send, area), + 0, NULL, level); newlsp->area = area; newlsp->own_lsp = 1; @@ -1310,8 +1358,9 @@ static int lsp_regenerate(struct isis_area *area, int level) continue; } - frag->hdr.lsp_bits = lsp_bits_generate( - level, area->overload_bit, area->attached_bit); + frag->hdr.lsp_bits = + lsp_bits_generate(level, area->overload_bit, + area->attached_bit_send, area); /* Set the lifetime values of all the fragments to the same * value, * so that no fragment expires before the lsp is refreshed. @@ -1518,8 +1567,8 @@ static void lsp_build_pseudo(struct isis_lsp *lsp, struct isis_circuit *circuit, lsp->level = level; /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ - lsp->hdr.lsp_bits = - lsp_bits_generate(level, 0, circuit->area->attached_bit); + lsp->hdr.lsp_bits = lsp_bits_generate( + level, 0, circuit->area->attached_bit_send, area); /* * add self to IS neighbours @@ -1617,8 +1666,10 @@ int lsp_generate_pseudo(struct isis_circuit *circuit, int level) rem_lifetime = lsp_rem_lifetime(circuit->area, level); /* RFC3787 section 4 SHOULD not set overload bit in pseudo LSPs */ lsp = lsp_new(circuit->area, lsp_id, rem_lifetime, 1, - circuit->area->is_type | circuit->area->attached_bit, 0, - NULL, level); + lsp_bits_generate(circuit->area->is_type, 0, + circuit->area->attached_bit_send, + circuit->area), + 0, NULL, level); lsp->area = circuit->area; lsp_build_pseudo(lsp, circuit, level); diff --git a/isisd/isis_nb.c b/isisd/isis_nb.c index a02e6a45b1..6d46e6b67e 100644 --- a/isisd/isis_nb.c +++ b/isisd/isis_nb.c @@ -60,9 +60,22 @@ const struct frr_yang_module_info frr_isisd_info = { }, }, { + .xpath = "/frr-isisd:isis/instance/attach-send", + .cbs = { + .cli_show = cli_show_isis_attached_send, + .modify = isis_instance_attached_send_modify, + }, + }, + { + .xpath = "/frr-isisd:isis/instance/attach-receive-ignore", + .cbs = { + .cli_show = cli_show_isis_attached_receive, + .modify = isis_instance_attached_receive_modify, + }, + }, + { .xpath = "/frr-isisd:isis/instance/attached", .cbs = { - .cli_show = cli_show_isis_attached, .modify = isis_instance_attached_modify, }, }, diff --git a/isisd/isis_nb.h b/isisd/isis_nb.h index 679bc6345d..8ecd8134e6 100644 --- a/isisd/isis_nb.h +++ b/isisd/isis_nb.h @@ -34,6 +34,8 @@ int isis_instance_is_type_modify(struct nb_cb_modify_args *args); int isis_instance_area_address_create(struct nb_cb_create_args *args); int isis_instance_area_address_destroy(struct nb_cb_destroy_args *args); int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args); +int isis_instance_attached_send_modify(struct nb_cb_modify_args *args); +int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args); int isis_instance_attached_modify(struct nb_cb_modify_args *args); int isis_instance_overload_modify(struct nb_cb_modify_args *args); int isis_instance_metric_style_modify(struct nb_cb_modify_args *args); @@ -424,8 +426,10 @@ void cli_show_isis_is_type(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_dynamic_hostname(struct vty *vty, struct lyd_node *dnode, bool show_defaults); -void cli_show_isis_attached(struct vty *vty, struct lyd_node *dnode, - bool show_defaults); +void cli_show_isis_attached_send(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); +void cli_show_isis_attached_receive(struct vty *vty, struct lyd_node *dnode, + bool show_defaults); void cli_show_isis_overload(struct vty *vty, struct lyd_node *dnode, bool show_defaults); void cli_show_isis_metric_style(struct vty *vty, struct lyd_node *dnode, diff --git a/isisd/isis_nb_config.c b/isisd/isis_nb_config.c index ed0fea8824..45bbc9737b 100644 --- a/isisd/isis_nb_config.c +++ b/isisd/isis_nb_config.c @@ -272,9 +272,9 @@ int isis_instance_dynamic_hostname_modify(struct nb_cb_modify_args *args) } /* - * XPath: /frr-isisd:isis/instance/attached + * XPath: /frr-isisd:isis/instance/attach-send */ -int isis_instance_attached_modify(struct nb_cb_modify_args *args) +int isis_instance_attached_send_modify(struct nb_cb_modify_args *args) { struct isis_area *area; bool attached; @@ -284,12 +284,38 @@ int isis_instance_attached_modify(struct nb_cb_modify_args *args) area = nb_running_get_entry(args->dnode, NULL, true); attached = yang_dnode_get_bool(args->dnode, NULL); - isis_area_attached_bit_set(area, attached); + isis_area_attached_bit_send_set(area, attached); return NB_OK; } /* + * XPath: /frr-isisd:isis/instance/attach-receive-ignore + */ +int isis_instance_attached_receive_modify(struct nb_cb_modify_args *args) +{ + struct isis_area *area; + bool attached; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + area = nb_running_get_entry(args->dnode, NULL, true); + attached = yang_dnode_get_bool(args->dnode, NULL); + isis_area_attached_bit_receive_set(area, attached); + + return NB_OK; +} + +/* + * XPath: /frr-isisd:isis/instance/attached + */ +int isis_instance_attached_modify(struct nb_cb_modify_args *args) +{ + return NB_OK; +} + +/* * XPath: /frr-isisd:isis/instance/overload */ int isis_instance_overload_modify(struct nb_cb_modify_args *args) diff --git a/isisd/isis_pdu.c b/isisd/isis_pdu.c index 72de5d6543..2d68aaa9ed 100644 --- a/isisd/isis_pdu.c +++ b/isisd/isis_pdu.c @@ -729,8 +729,8 @@ static int process_hello(uint8_t pdu_type, struct isis_circuit *circuit, if (!memcmp(iih.sys_id, circuit->isis->sysid, ISIS_SYS_ID_LEN)) { zlog_warn( - "ISIS-Adj (%s): Received IIH with own sysid - discard", - circuit->area->area_tag); + "ISIS-Adj (%s): Received IIH with own sysid on %s - discard", + circuit->area->area_tag, circuit->interface->name); circuit->rej_adjacencies++; #ifndef FABRICD isis_notif_reject_adjacency( diff --git a/isisd/isis_spf.c b/isisd/isis_spf.c index dee082fce1..ec0313f21e 100644 --- a/isisd/isis_spf.c +++ b/isisd/isis_spf.c @@ -1046,6 +1046,32 @@ lspfragloop: } end: + + /* if attach bit set and we are a level-1 router + * and attach-bit-rcv-ignore is not configured + * add a default route toward this neighbor + */ + if ((lsp->hdr.lsp_bits & LSPBIT_ATT) == LSPBIT_ATT + && !spftree->area->attached_bit_rcv_ignore + && spftree->area->is_type == IS_LEVEL_1) { + struct prefix_pair ip_info = { {0} }; + if (IS_DEBUG_SPF_EVENTS) + zlog_debug("ISIS-Spf (%s): add default %s route", + rawlspid_print(lsp->hdr.lsp_id), + spftree->family == AF_INET ? "ipv4" + : "ipv6"); + + if (spftree->family == AF_INET) { + ip_info.dest.family = AF_INET; + vtype = VTYPE_IPREACH_INTERNAL; + } else { + ip_info.dest.family = AF_INET6; + vtype = VTYPE_IP6REACH_INTERNAL; + } + process_N(spftree, vtype, &ip_info, cost, depth + 1, NULL, + parent); + } + if (fragnode == NULL) fragnode = listhead(lsp->lspu.frags); else diff --git a/isisd/isisd.c b/isisd/isisd.c index eabebab4e0..d45690f4dc 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -316,6 +316,11 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) "/frr-isisd:isis/instance/fast-reroute/level-1/lfa/load-sharing"); area->lfa_load_sharing[1] = yang_get_default_bool( "/frr-isisd:isis/instance/fast-reroute/level-2/lfa/load-sharing"); + area->attached_bit_send = + yang_get_default_bool("/frr-isisd:isis/instance/attach-send"); + area->attached_bit_rcv_ignore = yang_get_default_bool( + "/frr-isisd:isis/instance/attach-receive-ignore"); + #else area->max_lsp_lifetime[0] = DEFAULT_LSP_LIFETIME; /* 1200 */ area->max_lsp_lifetime[1] = DEFAULT_LSP_LIFETIME; /* 1200 */ @@ -332,6 +337,8 @@ struct isis_area *isis_area_create(const char *area_tag, const char *vrf_name) area->lsp_mtu = DEFAULT_LSP_MTU; area->lfa_load_sharing[0] = true; area->lfa_load_sharing[1] = true; + area->attached_bit_send = true; + area->attached_bit_rcv_ignore = false; #endif /* ifndef FABRICD */ area->lfa_priority_limit[0] = SPF_PREFIX_PRIO_LOW; area->lfa_priority_limit[1] = SPF_PREFIX_PRIO_LOW; @@ -2547,12 +2554,21 @@ void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit) #endif /* ifndef FABRICD */ } -void isis_area_attached_bit_set(struct isis_area *area, bool attached_bit) +void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit) +{ + + if (attached_bit != area->attached_bit_send) { + area->attached_bit_send = attached_bit; + lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); + } +} + +void isis_area_attached_bit_receive_set(struct isis_area *area, + bool attached_bit) { - char new_attached_bit = attached_bit ? LSPBIT_ATT : 0; - if (new_attached_bit != area->attached_bit) { - area->attached_bit = new_attached_bit; + if (attached_bit != area->attached_bit_rcv_ignore) { + area->attached_bit_rcv_ignore = attached_bit; lsp_regenerate_schedule(area, IS_LEVEL_1 | IS_LEVEL_2, 1); } } diff --git a/isisd/isisd.h b/isisd/isisd.h index 9b903eed48..a09f741522 100644 --- a/isisd/isisd.h +++ b/isisd/isisd.h @@ -169,7 +169,8 @@ struct isis_area { /* are we overloaded? */ char overload_bit; /* L1/L2 router identifier for inter-area traffic */ - char attached_bit; + char attached_bit_send; + char attached_bit_rcv_ignore; uint16_t lsp_refresh[ISIS_LEVELS]; /* minimum time allowed before lsp retransmission */ uint16_t lsp_gen_interval[ISIS_LEVELS]; @@ -253,7 +254,9 @@ void isis_area_invalidate_routes(struct isis_area *area, int levels); void isis_area_verify_routes(struct isis_area *area); void isis_area_overload_bit_set(struct isis_area *area, bool overload_bit); -void isis_area_attached_bit_set(struct isis_area *area, bool attached_bit); +void isis_area_attached_bit_send_set(struct isis_area *area, bool attached_bit); +void isis_area_attached_bit_receive_set(struct isis_area *area, + bool attached_bit); void isis_area_dynhostname_set(struct isis_area *area, bool dynhostname); void isis_area_metricstyle_set(struct isis_area *area, bool old_metric, bool new_metric); diff --git a/lib/frrlua.h b/lib/frrlua.h index 8e52931e50..6fb30938b0 100644 --- a/lib/frrlua.h +++ b/lib/frrlua.h @@ -35,6 +35,17 @@ extern "C" { #endif /* + * gcc-10 is complaining about the wrapper function + * not being compatible with lua_pushstring returning + * a char *. Let's wrapper it here to make our life + * easier + */ +static inline void lua_pushstring_wrapper(lua_State *L, const char *str) +{ + (void)lua_pushstring(L, str); +} + +/* * Converts a prefix to a Lua value and pushes it on the stack. */ void lua_pushprefix(lua_State *L, const struct prefix *prefix); diff --git a/lib/frrscript.c b/lib/frrscript.c index a3de474a4e..10d400886d 100644 --- a/lib/frrscript.c +++ b/lib/frrscript.c @@ -39,7 +39,7 @@ struct frrscript_codec frrscript_codecs_lib[] = { .encoder = (encoder_func)lua_pushintegerp, .decoder = lua_tointegerp}, {.typename = "string", - .encoder = (encoder_func)lua_pushstring, + .encoder = (encoder_func)lua_pushstring_wrapper, .decoder = lua_tostringp}, {.typename = "prefix", .encoder = (encoder_func)lua_pushprefix, @@ -208,7 +208,7 @@ struct frrscript *frrscript_load(const char *name, fs->L = luaL_newstate(); frrlua_export_logging(fs->L); - char fname[MAXPATHLEN]; + char fname[MAXPATHLEN * 2]; snprintf(fname, sizeof(fname), "%s/%s.lua", scriptdir, fs->name); int ret = luaL_loadfile(fs->L, fname); diff --git a/lib/zclient.c b/lib/zclient.c index f16c94369b..bfdf4971bb 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -996,7 +996,7 @@ done: return ret; } -int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) +static int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) { int i; @@ -1007,6 +1007,13 @@ int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) return -1; } + if (api_nhg->nexthop_num >= MULTIPATH_NUM || + api_nhg->backup_nexthop_num >= MULTIPATH_NUM) { + flog_err(EC_LIB_ZAPI_ENCODE, + "%s: zapi NHG encode with invalid input\n", __func__); + return -1; + } + stream_reset(s); zclient_create_header(s, cmd, VRF_DEFAULT); @@ -1024,7 +1031,6 @@ int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) zapi_nexthop_encode(s, &api_nhg->nexthops[i], 0, 0); /* Backup nexthops */ - stream_putw(s, api_nhg->backup_nexthop_num); for (i = 0; i < api_nhg->backup_nexthop_num; i++) diff --git a/lib/zclient.h b/lib/zclient.h index 57bad7c2e6..cf52ea91a0 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -1021,9 +1021,7 @@ bool zapi_ipset_notify_decode(struct stream *s, uint32_t *unique, enum zapi_ipset_notify_owner *note); - -extern int zapi_nhg_encode(struct stream *s, int cmd, struct zapi_nhg *api_nhg); -extern int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg); +/* Nexthop-group message apis */ extern enum zclient_send_status zclient_nhg_send(struct zclient *zclient, int cmd, struct zapi_nhg *api_nhg); diff --git a/ospf6d/ospf6_asbr.c b/ospf6d/ospf6_asbr.c index af99bc0c88..fdfd53276e 100644 --- a/ospf6d/ospf6_asbr.c +++ b/ospf6d/ospf6_asbr.c @@ -1439,8 +1439,7 @@ static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6, struct ospf6_redist *red; total = 0; - for (type = 0; type < ZEBRA_ROUTE_MAX; type++) - nroute[type] = 0; + memset(nroute, 0, sizeof(nroute)); for (route = ospf6_route_head(ospf6->external_table); route; route = ospf6_route_next(route)) { info = route->route_option; @@ -1448,12 +1447,11 @@ static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6, total++; } - if (use_json) - json_route = json_object_new_object(); - else + if (!use_json) vty_out(vty, "Redistributing External Routes from:\n"); for (type = 0; type < ZEBRA_ROUTE_MAX; type++) { + red = ospf6_redist_lookup(ospf6, type, 0); if (!red) @@ -1462,6 +1460,7 @@ static void ospf6_redistribute_show_config(struct vty *vty, struct ospf6 *ospf6, continue; if (use_json) { + json_route = json_object_new_object(); json_object_string_add(json_route, "routeType", ZROUTE_NAME(type)); json_object_int_add(json_route, "numberOfRoutes", diff --git a/ospfd/ospf_dump.c b/ospfd/ospf_dump.c index e15c9c42c7..b98852eeee 100644 --- a/ospfd/ospf_dump.c +++ b/ospfd/ospf_dump.c @@ -56,6 +56,7 @@ unsigned long conf_debug_ospf_nssa = 0; unsigned long conf_debug_ospf_te = 0; unsigned long conf_debug_ospf_ext = 0; unsigned long conf_debug_ospf_sr = 0; +unsigned long conf_debug_ospf_ti_lfa = 0; unsigned long conf_debug_ospf_defaultinfo = 0; unsigned long conf_debug_ospf_ldp_sync = 0; unsigned long conf_debug_ospf_gr = 0; @@ -71,6 +72,7 @@ unsigned long term_debug_ospf_nssa = 0; unsigned long term_debug_ospf_te = 0; unsigned long term_debug_ospf_ext = 0; unsigned long term_debug_ospf_sr = 0; +unsigned long term_debug_ospf_ti_lfa = 0; unsigned long term_debug_ospf_defaultinfo; unsigned long term_debug_ospf_ldp_sync; unsigned long term_debug_ospf_gr = 0; @@ -1470,6 +1472,24 @@ DEFUN (no_debug_ospf_sr, return CMD_SUCCESS; } +DEFUN(debug_ospf_ti_lfa, debug_ospf_ti_lfa_cmd, "debug ospf ti-lfa", + DEBUG_STR OSPF_STR "OSPF-SR TI-LFA information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_ON(ti_lfa, TI_LFA); + TERM_DEBUG_ON(ti_lfa, TI_LFA); + return CMD_SUCCESS; +} + +DEFUN(no_debug_ospf_ti_lfa, no_debug_ospf_ti_lfa_cmd, "no debug ospf ti-lfa", + NO_STR DEBUG_STR OSPF_STR "OSPF-SR TI-LFA information\n") +{ + if (vty->node == CONFIG_NODE) + CONF_DEBUG_OFF(ti_lfa, TI_LFA); + TERM_DEBUG_OFF(ti_lfa, TI_LFA); + return CMD_SUCCESS; +} + DEFUN (debug_ospf_default_info, debug_ospf_default_info_cmd, "debug ospf default-information", @@ -1891,6 +1911,12 @@ static int config_write_debug(struct vty *vty) write = 1; } + /* debug ospf sr ti-lfa */ + if (IS_CONF_DEBUG_OSPF(sr, TI_LFA) == OSPF_DEBUG_TI_LFA) { + vty_out(vty, "debug ospf%s ti-lfa\n", str); + write = 1; + } + /* debug ospf ldp-sync */ if (IS_CONF_DEBUG_OSPF(ldp_sync, LDP_SYNC) == OSPF_DEBUG_LDP_SYNC) { vty_out(vty, "debug ospf%s ldp-sync\n", str); @@ -1920,6 +1946,7 @@ void ospf_debug_init(void) install_element(ENABLE_NODE, &debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &debug_ospf_te_cmd); install_element(ENABLE_NODE, &debug_ospf_sr_cmd); + install_element(ENABLE_NODE, &debug_ospf_ti_lfa_cmd); install_element(ENABLE_NODE, &debug_ospf_default_info_cmd); install_element(ENABLE_NODE, &debug_ospf_ldp_sync_cmd); install_element(ENABLE_NODE, &no_debug_ospf_ism_cmd); @@ -1930,6 +1957,7 @@ void ospf_debug_init(void) install_element(ENABLE_NODE, &no_debug_ospf_nssa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_te_cmd); install_element(ENABLE_NODE, &no_debug_ospf_sr_cmd); + install_element(ENABLE_NODE, &no_debug_ospf_ti_lfa_cmd); install_element(ENABLE_NODE, &no_debug_ospf_default_info_cmd); install_element(ENABLE_NODE, &no_debug_ospf_ldp_sync_cmd); install_element(ENABLE_NODE, &debug_ospf_gr_cmd); @@ -1962,6 +1990,7 @@ void ospf_debug_init(void) install_element(CONFIG_NODE, &debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &debug_ospf_te_cmd); install_element(CONFIG_NODE, &debug_ospf_sr_cmd); + install_element(CONFIG_NODE, &debug_ospf_ti_lfa_cmd); install_element(CONFIG_NODE, &debug_ospf_default_info_cmd); install_element(CONFIG_NODE, &debug_ospf_ldp_sync_cmd); install_element(CONFIG_NODE, &no_debug_ospf_nsm_cmd); @@ -1971,6 +2000,7 @@ void ospf_debug_init(void) install_element(CONFIG_NODE, &no_debug_ospf_nssa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_te_cmd); install_element(CONFIG_NODE, &no_debug_ospf_sr_cmd); + install_element(CONFIG_NODE, &no_debug_ospf_ti_lfa_cmd); install_element(CONFIG_NODE, &no_debug_ospf_default_info_cmd); install_element(CONFIG_NODE, &no_debug_ospf_ldp_sync_cmd); install_element(CONFIG_NODE, &debug_ospf_gr_cmd); diff --git a/ospfd/ospf_dump.h b/ospfd/ospf_dump.h index ea607fef7c..c4c5606663 100644 --- a/ospfd/ospf_dump.h +++ b/ospfd/ospf_dump.h @@ -60,6 +60,7 @@ #define OSPF_DEBUG_TE 0x04 #define OSPF_DEBUG_EXT 0x08 #define OSPF_DEBUG_SR 0x10 +#define OSPF_DEBUG_TI_LFA 0x11 #define OSPF_DEBUG_DEFAULTINFO 0x20 #define OSPF_DEBUG_LDP_SYNC 0x40 @@ -110,6 +111,8 @@ #define IS_DEBUG_OSPF_SR IS_DEBUG_OSPF(sr, SR) +#define IS_DEBUG_OSPF_TI_LFA IS_DEBUG_OSPF(ti_lfa, TI_LFA) + #define IS_DEBUG_OSPF_DEFAULT_INFO IS_DEBUG_OSPF(defaultinfo, DEFAULTINFO) #define IS_DEBUG_OSPF_LDP_SYNC IS_DEBUG_OSPF(ldp_sync, LDP_SYNC) @@ -133,6 +136,7 @@ extern unsigned long term_debug_ospf_nssa; extern unsigned long term_debug_ospf_te; extern unsigned long term_debug_ospf_ext; extern unsigned long term_debug_ospf_sr; +extern unsigned long term_debug_ospf_ti_lfa; extern unsigned long term_debug_ospf_defaultinfo; extern unsigned long term_debug_ospf_ldp_sync; extern unsigned long term_debug_ospf_gr; diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c index f7d144d35b..72201355e7 100644 --- a/ospfd/ospf_lsa.c +++ b/ospfd/ospf_lsa.c @@ -337,7 +337,7 @@ void lsa_header_set(struct stream *s, uint8_t options, uint8_t type, /* router-LSA related functions. */ /* Get router-LSA flags. */ -static uint8_t router_lsa_flags(struct ospf_area *area) +uint8_t router_lsa_flags(struct ospf_area *area) { uint8_t flags; @@ -420,9 +420,8 @@ static uint16_t ospf_link_cost(struct ospf_interface *oi) } /* Set a link information. */ -static char link_info_set(struct stream **s, struct in_addr id, - struct in_addr data, uint8_t type, uint8_t tos, - uint16_t cost) +char link_info_set(struct stream **s, struct in_addr id, struct in_addr data, + uint8_t type, uint8_t tos, uint16_t cost) { /* LSA stream is initially allocated to OSPF_MAX_LSA_SIZE, suits * vast majority of cases. Some rare routers with lots of links need @@ -679,7 +678,7 @@ static int router_lsa_link_set(struct stream **s, struct ospf_area *area) } /* Set router-LSA body. */ -static void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area) +void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area) { unsigned long putp; uint16_t cnt; diff --git a/ospfd/ospf_lsa.h b/ospfd/ospf_lsa.h index c5de287948..f2a0d36e7e 100644 --- a/ospfd/ospf_lsa.h +++ b/ospfd/ospf_lsa.h @@ -260,6 +260,8 @@ extern struct lsa_header *ospf_lsa_data_dup(struct lsa_header *); extern void ospf_lsa_data_free(struct lsa_header *); /* Prototype for various LSAs */ +extern void ospf_router_lsa_body_set(struct stream **s, struct ospf_area *area); +extern uint8_t router_lsa_flags(struct ospf_area *area); extern int ospf_router_lsa_update(struct ospf *); extern int ospf_router_lsa_update_area(struct ospf_area *); @@ -333,6 +335,10 @@ extern int is_prefix_default(struct prefix_ipv4 *); extern int metric_type(struct ospf *, uint8_t, unsigned short); extern int metric_value(struct ospf *, uint8_t, unsigned short); +extern char link_info_set(struct stream **s, struct in_addr id, + struct in_addr data, uint8_t type, uint8_t tos, + uint16_t cost); + extern struct in_addr ospf_get_nssa_ip(struct ospf_area *); extern int ospf_translated_nssa_compare(struct ospf_lsa *, struct ospf_lsa *); extern struct ospf_lsa *ospf_translated_nssa_refresh(struct ospf *, diff --git a/ospfd/ospf_memory.c b/ospfd/ospf_memory.c index ae22cec414..f4fb68cbdf 100644 --- a/ospfd/ospf_memory.c +++ b/ospfd/ospf_memory.c @@ -58,3 +58,5 @@ DEFINE_MTYPE(OSPFD, OSPF_EXT_PARAMS, "OSPF Extended parameters") DEFINE_MTYPE(OSPFD, OSPF_SR_PARAMS, "OSPF Segment Routing parameters") DEFINE_MTYPE(OSPFD, OSPF_GR_HELPER, "OSPF Graceful Restart Helper") DEFINE_MTYPE(OSPFD, OSPF_EXTERNAL_RT_AGGR, "OSPF External Route Summarisation") +DEFINE_MTYPE(OSPFD, OSPF_P_SPACE, "OSPF TI-LFA P-Space") +DEFINE_MTYPE(OSPFD, OSPF_Q_SPACE, "OSPF TI-LFA Q-Space") diff --git a/ospfd/ospf_memory.h b/ospfd/ospf_memory.h index 624b1d3306..42bc8d7b77 100644 --- a/ospfd/ospf_memory.h +++ b/ospfd/ospf_memory.h @@ -57,5 +57,7 @@ DECLARE_MTYPE(OSPF_SR_PARAMS) DECLARE_MTYPE(OSPF_EXT_PARAMS) DECLARE_MTYPE(OSPF_GR_HELPER) DECLARE_MTYPE(OSPF_EXTERNAL_RT_AGGR) +DECLARE_MTYPE(OSPF_P_SPACE) +DECLARE_MTYPE(OSPF_Q_SPACE) #endif /* _QUAGGA_OSPF_MEMORY_H */ diff --git a/ospfd/ospf_route.c b/ospfd/ospf_route.c index 590122e223..7cfcaf14be 100644 --- a/ospfd/ospf_route.c +++ b/ospfd/ospf_route.c @@ -71,15 +71,31 @@ struct ospf_path *ospf_path_new(void) static struct ospf_path *ospf_path_dup(struct ospf_path *path) { struct ospf_path *new; + int memsize; new = ospf_path_new(); memcpy(new, path, sizeof(struct ospf_path)); + /* optional TI-LFA backup paths */ + if (path->srni.backup_label_stack) { + memsize = sizeof(struct mpls_label_stack) + + (sizeof(mpls_label_t) + * path->srni.backup_label_stack->num_labels); + new->srni.backup_label_stack = + XCALLOC(MTYPE_OSPF_PATH, memsize); + memcpy(new->srni.backup_label_stack, + path->srni.backup_label_stack, memsize); + } + return new; } void ospf_path_free(struct ospf_path *op) { + /* optional TI-LFA backup paths */ + if (op->srni.backup_label_stack) + XFREE(MTYPE_OSPF_PATH, op->srni.backup_label_stack); + XFREE(MTYPE_OSPF_PATH, op); } @@ -140,6 +156,35 @@ static int ospf_route_exist_new_table(struct route_table *rt, return 1; } +static int ospf_route_backup_path_same(struct sr_nexthop_info *srni1, + struct sr_nexthop_info *srni2) +{ + struct mpls_label_stack *ls1, *ls2; + uint8_t label_count; + + ls1 = srni1->backup_label_stack; + ls2 = srni2->backup_label_stack; + + if (!ls1 && !ls2) + return 1; + + if ((ls1 && !ls2) || (!ls1 && ls2)) + return 0; + + if (ls1->num_labels != ls2->num_labels) + return 0; + + for (label_count = 0; label_count < ls1->num_labels; label_count++) { + if (ls1->label[label_count] != ls2->label[label_count]) + return 0; + } + + if (!IPV4_ADDR_SAME(&srni1->backup_nexthop, &srni2->backup_nexthop)) + return 0; + + return 1; +} + /* If a prefix and a nexthop match any route in the routing table, then return 1, otherwise return 0. */ int ospf_route_match_same(struct route_table *rt, struct prefix_ipv4 *prefix, @@ -180,6 +225,11 @@ int ospf_route_match_same(struct route_table *rt, struct prefix_ipv4 *prefix, return 0; if (op->ifindex != newop->ifindex) return 0; + + /* check TI-LFA backup paths */ + if (!ospf_route_backup_path_same(&op->srni, + &newop->srni)) + return 0; } return 1; } else if (prefix_same(&rn->p, (struct prefix *)prefix)) @@ -664,38 +714,6 @@ void ospf_route_table_dump(struct route_table *rt) zlog_debug("========================================"); } -void ospf_route_table_print(struct vty *vty, struct route_table *rt) -{ - struct route_node *rn; - struct ospf_route * or ; - struct listnode *pnode; - struct ospf_path *path; - - vty_out(vty, "========== OSPF routing table ==========\n"); - for (rn = route_top(rt); rn; rn = route_next(rn)) - if ((or = rn->info) != NULL) { - if (or->type == OSPF_DESTINATION_NETWORK) { - vty_out(vty, "N %-18pFX %-15pI4 %s %d\n", - &rn->p, & or->u.std.area_id, - ospf_path_type_str[or->path_type], - or->cost); - for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, - path)) - if (path->nexthop.s_addr != INADDR_ANY) - vty_out(vty, " -> %pI4\n", - &path->nexthop); - else - vty_out(vty, " -> %s\n", - "directly connected"); - } else - vty_out(vty, "R %-18pI4 %-15pI4 %s %d\n", - &rn->p.u.prefix4, & or->u.std.area_id, - ospf_path_type_str[or->path_type], - or->cost); - } - vty_out(vty, "========================================\n"); -} - /* This is 16.4.1 implementation. o Intra-area paths using non-backbone areas are always the most preferred. o The other paths, intra-area backbone paths and inter-area paths, @@ -802,6 +820,7 @@ void ospf_route_copy_nexthops_from_vertex(struct ospf_area *area, || area->spf_dry_run) { path = ospf_path_new(); path->nexthop = nexthop->router; + path->adv_router = v->id; if (oi) { path->ifindex = oi->ifp->ifindex; diff --git a/ospfd/ospf_route.h b/ospfd/ospf_route.h index c3fa5954d5..811581c0d3 100644 --- a/ospfd/ospf_route.h +++ b/ospfd/ospf_route.h @@ -42,6 +42,10 @@ struct sr_nexthop_info { * or NULL if next hop is the destination of the prefix */ struct sr_node *nexthop; + + /* TI-LFA */ + struct mpls_label_stack *backup_label_stack; + struct in_addr backup_nexthop; }; /* OSPF Path. */ @@ -132,7 +136,6 @@ extern void ospf_route_table_free(struct route_table *); extern void ospf_route_install(struct ospf *, struct route_table *); extern void ospf_route_table_dump(struct route_table *); -extern void ospf_route_table_print(struct vty *vty, struct route_table *rt); extern void ospf_intra_add_router(struct route_table *, struct vertex *, struct ospf_area *); diff --git a/ospfd/ospf_spf.c b/ospfd/ospf_spf.c index 4665f53edb..fae4f1e10c 100644 --- a/ospfd/ospf_spf.c +++ b/ospfd/ospf_spf.c @@ -46,6 +46,7 @@ #include "ospfd/ospf_abr.h" #include "ospfd/ospf_dump.h" #include "ospfd/ospf_sr.h" +#include "ospfd/ospf_ti_lfa.h" #include "ospfd/ospf_errors.h" /* Variables to ensure a SPF scheduled log message is printed only once */ @@ -141,11 +142,16 @@ static void ospf_canonical_nexthops_free(struct vertex *root) ospf_canonical_nexthops_free(child); /* Free child nexthops pointing back to this root vertex */ - for (ALL_LIST_ELEMENTS(child->parents, n2, nn2, vp)) + for (ALL_LIST_ELEMENTS(child->parents, n2, nn2, vp)) { if (vp->parent == root && vp->nexthop) { vertex_nexthop_free(vp->nexthop); vp->nexthop = NULL; + if (vp->local_nexthop) { + vertex_nexthop_free(vp->local_nexthop); + vp->local_nexthop = NULL; + } } + } } } @@ -154,7 +160,8 @@ static void ospf_canonical_nexthops_free(struct vertex *root) * vertex_nexthop, with refcounts. */ static struct vertex_parent *vertex_parent_new(struct vertex *v, int backlink, - struct vertex_nexthop *hop) + struct vertex_nexthop *hop, + struct vertex_nexthop *lhop) { struct vertex_parent *new; @@ -163,6 +170,7 @@ static struct vertex_parent *vertex_parent_new(struct vertex *v, int backlink, new->parent = v; new->backlink = backlink; new->nexthop = hop; + new->local_nexthop = lhop; return new; } @@ -172,7 +180,7 @@ static void vertex_parent_free(void *p) XFREE(MTYPE_OSPF_VERTEX_PARENT, p); } -static int vertex_parent_cmp(void *aa, void *bb) +int vertex_parent_cmp(void *aa, void *bb) { struct vertex_parent *a = aa, *b = bb; return IPV4_ADDR_CMP(&a->nexthop->router, &b->nexthop->router); @@ -284,6 +292,257 @@ static void ospf_vertex_add_parent(struct vertex *v) } } +/* Find a vertex according to its router id */ +struct vertex *ospf_spf_vertex_find(struct in_addr id, struct list *vertex_list) +{ + struct listnode *node; + struct vertex *found; + + for (ALL_LIST_ELEMENTS_RO(vertex_list, node, found)) { + if (found->id.s_addr == id.s_addr) + return found; + } + + return NULL; +} + +/* Find a vertex parent according to its router id */ +struct vertex_parent *ospf_spf_vertex_parent_find(struct in_addr id, + struct vertex *vertex) +{ + struct listnode *node; + struct vertex_parent *found; + + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, found)) { + if (found->parent->id.s_addr == id.s_addr) + return found; + } + + return NULL; +} + +struct vertex *ospf_spf_vertex_by_nexthop(struct vertex *root, + struct in_addr *nexthop) +{ + struct listnode *node; + struct vertex *child; + struct vertex_parent *vertex_parent; + + for (ALL_LIST_ELEMENTS_RO(root->children, node, child)) { + vertex_parent = ospf_spf_vertex_parent_find(root->id, child); + if (vertex_parent->nexthop->router.s_addr == nexthop->s_addr) + return child; + } + + return NULL; +} + +/* Create a deep copy of a SPF vertex without children and parents */ +static struct vertex *ospf_spf_vertex_copy(struct vertex *vertex) +{ + struct vertex *copy; + + copy = XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex)); + + memcpy(copy, vertex, sizeof(struct vertex)); + copy->parents = list_new(); + copy->parents->del = vertex_parent_free; + copy->parents->cmp = vertex_parent_cmp; + copy->children = list_new(); + + return copy; +} + +/* Create a deep copy of a SPF vertex_parent */ +static struct vertex_parent * +ospf_spf_vertex_parent_copy(struct vertex_parent *vertex_parent) +{ + struct vertex_parent *vertex_parent_copy; + struct vertex_nexthop *nexthop_copy, *local_nexthop_copy; + + vertex_parent_copy = + XCALLOC(MTYPE_OSPF_VERTEX, sizeof(struct vertex_parent)); + + nexthop_copy = vertex_nexthop_new(); + local_nexthop_copy = vertex_nexthop_new(); + + memcpy(vertex_parent_copy, vertex_parent, sizeof(struct vertex_parent)); + memcpy(nexthop_copy, vertex_parent->nexthop, + sizeof(struct vertex_nexthop)); + memcpy(local_nexthop_copy, vertex_parent->local_nexthop, + sizeof(struct vertex_nexthop)); + + vertex_parent_copy->nexthop = nexthop_copy; + vertex_parent_copy->local_nexthop = local_nexthop_copy; + + return vertex_parent_copy; +} + +/* Create a deep copy of a SPF tree */ +void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list) +{ + struct listnode *node; + struct vertex *vertex_copy, *child, *child_copy, *parent_copy; + struct vertex_parent *vertex_parent, *vertex_parent_copy; + + /* First check if the node is already in the vertex list */ + vertex_copy = ospf_spf_vertex_find(vertex->id, vertex_list); + if (!vertex_copy) { + vertex_copy = ospf_spf_vertex_copy(vertex); + listnode_add(vertex_list, vertex_copy); + } + + /* Copy all parents, create parent nodes if necessary */ + for (ALL_LIST_ELEMENTS_RO(vertex->parents, node, vertex_parent)) { + parent_copy = ospf_spf_vertex_find(vertex_parent->parent->id, + vertex_list); + if (!parent_copy) { + parent_copy = + ospf_spf_vertex_copy(vertex_parent->parent); + listnode_add(vertex_list, parent_copy); + } + vertex_parent_copy = ospf_spf_vertex_parent_copy(vertex_parent); + vertex_parent_copy->parent = parent_copy; + listnode_add(vertex_copy->parents, vertex_parent_copy); + } + + /* Copy all children, create child nodes if necessary */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + child_copy = ospf_spf_vertex_find(child->id, vertex_list); + if (!child_copy) { + child_copy = ospf_spf_vertex_copy(child); + listnode_add(vertex_list, child_copy); + } + listnode_add(vertex_copy->children, child_copy); + } + + /* Finally continue copying with child nodes */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) + ospf_spf_copy(child, vertex_list); +} + +static void ospf_spf_remove_branch(struct vertex_parent *vertex_parent, + struct vertex *child, + struct list *vertex_list) +{ + struct listnode *node, *nnode, *inner_node, *inner_nnode; + struct vertex *grandchild; + struct vertex_parent *vertex_parent_found; + bool has_more_links = false; + + /* + * First check if there are more nexthops for that parent to that child + */ + for (ALL_LIST_ELEMENTS_RO(child->parents, node, vertex_parent_found)) { + if (vertex_parent_found->parent->id.s_addr + == vertex_parent->parent->id.s_addr + && vertex_parent_found->nexthop->router.s_addr + != vertex_parent->nexthop->router.s_addr) + has_more_links = true; + } + + /* + * No more links from that parent? Then delete the child from its + * children list. + */ + if (!has_more_links) + listnode_delete(vertex_parent->parent->children, child); + + /* + * Delete the vertex_parent from the child parents list, this needs to + * be done anyway. + */ + listnode_delete(child->parents, vertex_parent); + + /* + * Are there actually more parents left? If not, then delete the child! + * This is done by recursively removing the links to the grandchildren, + * such that finally the child can be removed without leaving unused + * partial branches. + */ + if (child->parents->count == 0) { + for (ALL_LIST_ELEMENTS(child->children, node, nnode, + grandchild)) { + for (ALL_LIST_ELEMENTS(grandchild->parents, inner_node, + inner_nnode, + vertex_parent_found)) { + ospf_spf_remove_branch(vertex_parent_found, + grandchild, vertex_list); + } + } + listnode_delete(vertex_list, child); + ospf_vertex_free(child); + } +} + +static int ospf_spf_remove_link(struct vertex *vertex, struct list *vertex_list, + struct router_lsa_link *link) +{ + struct listnode *node, *inner_node; + struct vertex *child; + struct vertex_parent *vertex_parent; + + /* + * Identify the node who shares a subnet (given by the link) with a + * child and remove the branch of this particular child. + */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node, + vertex_parent)) { + if ((vertex_parent->local_nexthop->router.s_addr + & link->link_data.s_addr) + == (link->link_id.s_addr + & link->link_data.s_addr)) { + ospf_spf_remove_branch(vertex_parent, child, + vertex_list); + return 0; + } + } + } + + /* No link found yet, move on recursively */ + for (ALL_LIST_ELEMENTS_RO(vertex->children, node, child)) { + if (ospf_spf_remove_link(child, vertex_list, link) == 0) + return 0; + } + + /* link was not removed yet */ + return 1; +} + +void ospf_spf_remove_resource(struct vertex *vertex, struct list *vertex_list, + struct protected_resource *resource) +{ + struct listnode *node, *nnode; + struct vertex *found; + struct vertex_parent *vertex_parent; + + switch (resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + ospf_spf_remove_link(vertex, vertex_list, resource->link); + break; + case OSPF_TI_LFA_NODE_PROTECTION: + found = ospf_spf_vertex_find(resource->router_id, vertex_list); + if (!found) + break; + + /* + * Remove the node by removing all links from its parents. Note + * that the child is automatically removed here with the last + * link from a parent, hence no explicit removal of the node. + */ + for (ALL_LIST_ELEMENTS(found->parents, node, nnode, + vertex_parent)) + ospf_spf_remove_branch(vertex_parent, found, + vertex_list); + + break; + default: + /* do nothing */ + break; + } +} + static void ospf_spf_init(struct ospf_area *area, struct ospf_lsa *root_lsa, bool is_dry_run, bool is_root_node) { @@ -427,6 +686,7 @@ static void ospf_spf_flush_parents(struct vertex *w) */ static void ospf_spf_add_parent(struct vertex *v, struct vertex *w, struct vertex_nexthop *newhop, + struct vertex_nexthop *newlhop, unsigned int distance) { struct vertex_parent *vp, *wp; @@ -482,7 +742,8 @@ static void ospf_spf_add_parent(struct vertex *v, struct vertex *w, } } - vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop); + vp = vertex_parent_new(v, ospf_lsa_has_link(w->lsa, v->lsa), newhop, + newlhop); listnode_add_sort(w->parents, vp); return; @@ -541,7 +802,7 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, unsigned int distance, int lsa_pos) { struct listnode *node, *nnode; - struct vertex_nexthop *nh; + struct vertex_nexthop *nh, *lnh; struct vertex_parent *vp; unsigned int added = 0; char buf1[BUFSIZ]; @@ -586,17 +847,22 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, struct ospf_interface *oi = NULL; struct in_addr nexthop = {.s_addr = 0}; - oi = ospf_if_lookup_by_lsa_pos(area, lsa_pos); - if (!oi) { - zlog_debug( - "%s: OI not found in LSA: lsa_pos: %d link_id:%s link_data:%s", - __func__, lsa_pos, - inet_ntop(AF_INET, &l->link_id, - buf1, BUFSIZ), - inet_ntop(AF_INET, - &l->link_data, buf2, - BUFSIZ)); - return 0; + if (area->spf_root_node) { + oi = ospf_if_lookup_by_lsa_pos(area, + lsa_pos); + if (!oi) { + zlog_debug( + "%s: OI not found in LSA: lsa_pos: %d link_id:%s link_data:%s", + __func__, lsa_pos, + inet_ntop(AF_INET, + &l->link_id, + buf1, BUFSIZ), + inet_ntop(AF_INET, + &l->link_data, + buf2, + BUFSIZ)); + return 0; + } } /* @@ -644,7 +910,21 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, * as described above using a reverse lookup to * figure out the nexthop. */ - if (oi->type == OSPF_IFTYPE_POINTOPOINT) { + + /* + * HACK: we don't know (yet) how to distinguish + * between P2P and P2MP interfaces by just + * looking at LSAs, which is important for + * TI-LFA since you want to do SPF calculations + * from the perspective of other nodes. Since + * TI-LFA is currently not implemented for P2MP + * we just check here if it is enabled and then + * blindly assume that P2P is used. Ultimately + * the interface code needs to be removed + * somehow. + */ + if (area->ospf->ti_lfa_enabled + || (oi && oi->type == OSPF_IFTYPE_POINTOPOINT)) { struct ospf_neighbor *nbr_w = NULL; /* Calculating node is root node, link @@ -673,7 +953,7 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, } } } - } else if (oi->type + } else if (oi && oi->type == OSPF_IFTYPE_POINTOMULTIPOINT) { struct prefix_ipv4 la; @@ -703,12 +983,22 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, nh = vertex_nexthop_new(); nh->router = nexthop; nh->lsa_pos = lsa_pos; - ospf_spf_add_parent(v, w, nh, distance); + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + + ospf_spf_add_parent(v, w, nh, lnh, + distance); return 1; } else zlog_info( "%s: could not determine nexthop for link %s", - __func__, oi->ifp->name); + __func__, oi ? oi->ifp->name : ""); } /* end point-to-point link from V to W */ else if (l->m[0].type == LSA_LINK_TYPE_VIRTUALLINK) { /* @@ -733,7 +1023,17 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, nh = vertex_nexthop_new(); nh->router = vl_data->nexthop.router; nh->lsa_pos = vl_data->nexthop.lsa_pos; - ospf_spf_add_parent(v, w, nh, distance); + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + + ospf_spf_add_parent(v, w, nh, lnh, + distance); return 1; } else zlog_info( @@ -747,7 +1047,15 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, nh = vertex_nexthop_new(); nh->router.s_addr = 0; /* Nexthop not required */ nh->lsa_pos = lsa_pos; - ospf_spf_add_parent(v, w, nh, distance); + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, sizeof(struct vertex_nexthop)); + + ospf_spf_add_parent(v, w, nh, lnh, distance); return 1; } } /* end V is the root */ @@ -780,8 +1088,18 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, nh = vertex_nexthop_new(); nh->router = l->link_data; nh->lsa_pos = vp->nexthop->lsa_pos; + + /* + * Since v is the root the nexthop and + * local nexthop are the same. + */ + lnh = vertex_nexthop_new(); + memcpy(lnh, nh, + sizeof(struct vertex_nexthop)); + added = 1; - ospf_spf_add_parent(v, w, nh, distance); + ospf_spf_add_parent(v, w, nh, lnh, + distance); } /* * Note lack of return is deliberate. See next @@ -829,12 +1147,154 @@ static unsigned int ospf_nexthop_calculation(struct ospf_area *area, for (ALL_LIST_ELEMENTS(v->parents, node, nnode, vp)) { added = 1; - ospf_spf_add_parent(v, w, vp->nexthop, distance); + + /* + * The nexthop is inherited, but the local nexthop still needs + * to be created. + */ + if (l) { + lnh = vertex_nexthop_new(); + lnh->router = l->link_data; + lnh->lsa_pos = lsa_pos; + } else { + lnh = NULL; + } + + ospf_spf_add_parent(v, w, vp->nexthop, lnh, distance); } return added; } +static int ospf_spf_is_protected_resource(struct ospf_area *area, + struct router_lsa_link *link, + struct lsa_header *lsa) +{ + uint8_t *p, *lim; + struct router_lsa_link *p_link; + struct router_lsa_link *l = NULL; + struct in_addr router_id; + int link_type; + + if (!area->spf_protected_resource) + return 0; + + link_type = link->m[0].type; + + switch (area->spf_protected_resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + p_link = area->spf_protected_resource->link; + if (!p_link) + return 0; + + /* For P2P: check if the link belongs to the same subnet */ + if (link_type == LSA_LINK_TYPE_POINTOPOINT + && (p_link->link_id.s_addr & p_link->link_data.s_addr) + == (link->link_data.s_addr + & p_link->link_data.s_addr)) + return 1; + + /* For stub: check if this the same subnet */ + if (link_type == LSA_LINK_TYPE_STUB + && (p_link->link_id.s_addr == link->link_id.s_addr) + && (p_link->link_data.s_addr == link->link_data.s_addr)) + return 1; + + break; + case OSPF_TI_LFA_NODE_PROTECTION: + router_id = area->spf_protected_resource->router_id; + if (router_id.s_addr == INADDR_ANY) + return 0; + + /* For P2P: check if the link leads to the protected node */ + if (link_type == LSA_LINK_TYPE_POINTOPOINT + && link->link_id.s_addr == router_id.s_addr) + return 1; + + /* The rest is about stub links! */ + if (link_type != LSA_LINK_TYPE_STUB) + return 0; + + /* + * Check if there's a P2P link in the router LSA with the + * corresponding link data in the same subnet. + */ + + p = ((uint8_t *)lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)lsa) + ntohs(lsa->length); + + while (p < lim) { + l = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* We only care about P2P with the proper link id */ + if ((l->m[0].type != LSA_LINK_TYPE_POINTOPOINT) + || (l->link_id.s_addr != router_id.s_addr)) + continue; + + /* Link data in the subnet given by the link? */ + if ((link->link_id.s_addr & link->link_data.s_addr) + == (l->link_data.s_addr & link->link_data.s_addr)) + return 1; + } + + break; + case OSPF_TI_LFA_UNDEFINED_PROTECTION: + break; + } + + return 0; +} + +/* + * For TI-LFA we need the reverse SPF for Q spaces. The reverse SPF is created + * by honoring the weight of the reverse 'edge', e.g. the edge from W to V, and + * NOT the weight of the 'edge' from V to W as usual. Hence we need to find the + * corresponding link in the LSA of W and extract the particular weight. + * + * TODO: Only P2P supported by now! + */ +static uint16_t get_reverse_distance(struct vertex *v, + struct router_lsa_link *l, + struct ospf_lsa *w_lsa) +{ + uint8_t *p, *lim; + struct router_lsa_link *w_link; + uint16_t distance = 0; + + assert(w_lsa && w_lsa->data); + + p = ((uint8_t *)w_lsa->data) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)w_lsa->data) + ntohs(w_lsa->data->length); + + while (p < lim) { + w_link = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (w_link->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* Only care about P2P with link ID equal to V's router id */ + if (w_link->m[0].type == LSA_LINK_TYPE_POINTOPOINT + && w_link->link_id.s_addr == v->id.s_addr) { + distance = ntohs(w_link->m[0].metric); + break; + } + } + + /* + * This might happen if the LSA for W is not complete yet. In this + * case we take the weight of the 'forward' link from V. When the LSA + * for W is completed the reverse SPF is run again anyway. + */ + if (distance == 0) + distance = ntohs(l->m[0].metric); + + if (IS_DEBUG_OSPF_EVENT) + zlog_debug("%s: reversed distance is %u", __func__, distance); + + return distance; +} + /* * RFC2328 16.1 (2). * v is on the SPF tree. Examine the links in v's LSA. Update the list of @@ -850,6 +1310,7 @@ static void ospf_spf_next(struct vertex *v, struct ospf_area *area, struct router_lsa_link *l = NULL; struct in_addr *r; int type = 0, lsa_pos = -1, lsa_pos_next = 0; + uint16_t link_distance; /* * If this is a router-LSA, and bit V of the router-LSA (see Section @@ -892,6 +1353,16 @@ static void ospf_spf_next(struct vertex *v, struct ospf_area *area, continue; /* + * Don't process TI-LFA protected resources. + * + * TODO: Replace this by a proper solution, e.g. remove + * corresponding links from the LSDB and run the SPF + * algo with the stripped-down LSDB. + */ + if (ospf_spf_is_protected_resource(area, l, v->lsa)) + continue; + + /* * (b) Otherwise, W is a transit vertex (router or * transit network). Look up the vertex W's LSA * (router-LSA or network-LSA) in Area A's link state @@ -928,8 +1399,19 @@ static void ospf_spf_next(struct vertex *v, struct ospf_area *area, continue; } + /* + * For TI-LFA we might need the reverse SPF. + * Currently only works with P2P! + */ + if (type == LSA_LINK_TYPE_POINTOPOINT + && area->spf_reversed) + link_distance = + get_reverse_distance(v, l, w_lsa); + else + link_distance = ntohs(l->m[0].metric); + /* step (d) below */ - distance = v->distance + ntohs(l->m[0].metric); + distance = v->distance + link_distance; } else { /* In case of V is Network-LSA. */ r = (struct in_addr *)p; @@ -1069,8 +1551,7 @@ void ospf_spf_print(struct vty *vty, struct vertex *v, int i) struct vertex_parent *parent; if (v->type == OSPF_VERTEX_ROUTER) { - vty_out(vty, "SPF Result: depth %d [R] %pI4\n", i, - &v->lsa->id); + vty_out(vty, "SPF Result: depth %d [R] %pI4\n", i, &v->lsa->id); } else { struct network_lsa *lsa = (struct network_lsa *)v->lsa; vty_out(vty, "SPF Result: depth %d [N] %pI4/%d\n", i, @@ -1078,9 +1559,11 @@ void ospf_spf_print(struct vty *vty, struct vertex *v, int i) } for (ALL_LIST_ELEMENTS_RO(v->parents, nnode, parent)) { - vty_out(vty, " nexthop %pI4 lsa pos %d\n", - &parent->nexthop->router, - parent->nexthop->lsa_pos); + vty_out(vty, + " nexthop %pI4 lsa pos %d -- local nexthop %pI4 lsa pos %d\n", + &parent->nexthop->router, parent->nexthop->lsa_pos, + &parent->local_nexthop->router, + parent->local_nexthop->lsa_pos); } i++; @@ -1128,7 +1611,9 @@ static void ospf_spf_process_stubs(struct ospf_area *area, struct vertex *v, p += (OSPF_ROUTER_LSA_LINK_SIZE + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); - if (l->m[0].type == LSA_LINK_TYPE_STUB) + /* Don't process TI-LFA protected resources */ + if (l->m[0].type == LSA_LINK_TYPE_STUB + && !ospf_spf_is_protected_resource(area, l, v->lsa)) ospf_intra_add_stub(rt, l, v, area, parent_is_root, lsa_pos); lsa_pos++; @@ -1190,10 +1675,12 @@ void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list) * attached the first level of router vertices attached to the * root vertex, see ospf_nexthop_calculation. */ - ospf_canonical_nexthops_free(spf); + if (spf) + ospf_canonical_nexthops_free(spf); /* Free SPF vertices list with deconstructor ospf_vertex_free. */ - list_delete(&vertex_list); + if (vertex_list) + list_delete(&vertex_list); } #if 0 @@ -1359,19 +1846,27 @@ void ospf_spf_calculate(struct ospf_area *area, struct ospf_lsa *root_lsa, if (IS_DEBUG_OSPF_EVENT) zlog_debug("ospf_spf_calculate: Stop. %zd vertices", mtype_stats_alloc(MTYPE_OSPF_VERTEX)); +} + +void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area, + struct route_table *new_table, + struct route_table *new_rtrs) +{ + ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs, + false, true); - /* If this is a dry run then keep the SPF data in place */ - if (!area->spf_dry_run) - ospf_spf_cleanup(area->spf, area->spf_vertex_list); + if (ospf->ti_lfa_enabled) + ospf_ti_lfa_compute(area, new_table, + ospf->ti_lfa_protection_type); + + ospf_spf_cleanup(area->spf, area->spf_vertex_list); } -int ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table, - struct route_table *new_rtrs, bool is_dry_run, - bool is_root_node) +void ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table, + struct route_table *new_rtrs) { struct ospf_area *area; struct listnode *node, *nnode; - int areas_processed = 0; /* Calculate SPF for each area. */ for (ALL_LIST_ELEMENTS(ospf->areas, node, nnode, area)) { @@ -1380,20 +1875,13 @@ int ospf_spf_calculate_areas(struct ospf *ospf, struct route_table *new_table, if (ospf->backbone && ospf->backbone == area) continue; - ospf_spf_calculate(area, area->router_lsa_self, new_table, - new_rtrs, is_dry_run, is_root_node); - areas_processed++; + ospf_spf_calculate_area(ospf, area, new_table, new_rtrs); } /* SPF for backbone, if required */ - if (ospf->backbone) { - area = ospf->backbone; - ospf_spf_calculate(area, area->router_lsa_self, new_table, - new_rtrs, is_dry_run, is_root_node); - areas_processed++; - } - - return areas_processed; + if (ospf->backbone) + ospf_spf_calculate_area(ospf, ospf->backbone, new_table, + new_rtrs); } /* Worker for SPF calculation scheduler. */ @@ -1402,7 +1890,6 @@ static int ospf_spf_calculate_schedule_worker(struct thread *thread) struct ospf *ospf = THREAD_ARG(thread); struct route_table *new_table, *new_rtrs; struct timeval start_time, spf_start_time; - int areas_processed; unsigned long ia_time, prune_time, rt_time; unsigned long abr_time, total_spf_time, spf_time; char rbuf[32]; /* reason_buf */ @@ -1418,8 +1905,7 @@ static int ospf_spf_calculate_schedule_worker(struct thread *thread) monotime(&spf_start_time); new_table = route_table_init(); /* routing table */ new_rtrs = route_table_init(); /* ABR/ASBR routing table */ - areas_processed = ospf_spf_calculate_areas(ospf, new_table, new_rtrs, - false, true); + ospf_spf_calculate_areas(ospf, new_table, new_rtrs); spf_time = monotime_since(&spf_start_time, NULL); ospf_vl_shut_unapproved(ospf); @@ -1512,7 +1998,7 @@ static int ospf_spf_calculate_schedule_worker(struct thread *thread) zlog_info(" RouteInstall: %ld", rt_time); if (IS_OSPF_ABR(ospf)) zlog_info(" ABR: %ld (%d areas)", - abr_time, areas_processed); + abr_time, ospf->areas->count); zlog_info("Reason(s) for SPF: %s", rbuf); } diff --git a/ospfd/ospf_spf.h b/ospfd/ospf_spf.h index 2dc0f8b886..66555be4b7 100644 --- a/ospfd/ospf_spf.h +++ b/ospfd/ospf_spf.h @@ -47,15 +47,15 @@ struct vertex { struct list *children; /* list of children in SPF tree*/ }; -/* A nexthop taken on the root node to get to this (parent) vertex */ struct vertex_nexthop { struct in_addr router; /* router address to send to */ int lsa_pos; /* LSA position for resolving the interface */ }; struct vertex_parent { - struct vertex_nexthop *nexthop; /* nexthop address for this parent */ - struct vertex *parent; /* parent vertex */ + struct vertex_nexthop *nexthop; /* nexthop taken on the root node */ + struct vertex_nexthop *local_nexthop; /* local nexthop of the parent */ + struct vertex *parent; /* parent vertex */ int backlink; /* index back to parent for router-lsa's */ }; @@ -77,12 +77,25 @@ extern void ospf_spf_calculate(struct ospf_area *area, struct route_table *new_table, struct route_table *new_rtrs, bool is_dry_run, bool is_root_node); -extern int ospf_spf_calculate_areas(struct ospf *ospf, +extern void ospf_spf_calculate_area(struct ospf *ospf, struct ospf_area *area, struct route_table *new_table, - struct route_table *new_rtrs, - bool is_dry_run, bool is_root_node); + struct route_table *new_rtrs); +extern void ospf_spf_calculate_areas(struct ospf *ospf, + struct route_table *new_table, + struct route_table *new_rtrs); extern void ospf_rtrs_free(struct route_table *); extern void ospf_spf_cleanup(struct vertex *spf, struct list *vertex_list); +extern void ospf_spf_copy(struct vertex *vertex, struct list *vertex_list); +extern void ospf_spf_remove_resource(struct vertex *vertex, + struct list *vertex_list, + struct protected_resource *resource); +extern struct vertex *ospf_spf_vertex_find(struct in_addr id, + struct list *vertex_list); +extern struct vertex *ospf_spf_vertex_by_nexthop(struct vertex *root, + struct in_addr *nexthop); +extern struct vertex_parent *ospf_spf_vertex_parent_find(struct in_addr id, + struct vertex *vertex); +extern int vertex_parent_cmp(void *aa, void *bb); extern void ospf_spf_print(struct vty *vty, struct vertex *v, int i); diff --git a/ospfd/ospf_sr.c b/ospfd/ospf_sr.c index e2218957d2..efe07e1411 100644 --- a/ospfd/ospf_sr.c +++ b/ospfd/ospf_sr.c @@ -159,6 +159,16 @@ static struct sr_node *sr_node_new(struct in_addr *rid) return new; } +/* Supposed to be used for testing */ +struct sr_node *ospf_sr_node_create(struct in_addr *rid) +{ + struct sr_node *srn; + + srn = hash_get(OspfSR.neighbors, (void *)rid, (void *)sr_node_new); + + return srn; +} + /* Delete Segment Routing node */ static void sr_node_del(struct sr_node *srn) { @@ -653,6 +663,56 @@ static mpls_label_t index2label(uint32_t index, struct sr_block srgb) return label; } +/* Get the prefix sid for a specific router id */ +mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id) +{ + struct sr_node *srn; + struct sr_prefix *srp; + mpls_label_t label; + + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, id); + + if (srn) { + /* + * TODO: Here we assume that the SRGBs are the same, + * and that the node's prefix SID is at the head of + * the list, probably needs tweaking. + */ + srp = listnode_head(srn->ext_prefix); + label = index2label(srp->sid, srn->srgb); + } else { + label = MPLS_INVALID_LABEL; + } + + return label; +} + +/* Get the adjacency sid for a specific 'root' id and 'neighbor' id */ +mpls_label_t ospf_sr_get_adj_sid_by_id(struct in_addr *root_id, + struct in_addr *neighbor_id) +{ + struct sr_node *srn; + struct sr_link *srl; + mpls_label_t label; + struct listnode *node; + + srn = (struct sr_node *)hash_lookup(OspfSR.neighbors, root_id); + + label = MPLS_INVALID_LABEL; + + if (srn) { + for (ALL_LIST_ELEMENTS_RO(srn->ext_link, node, srl)) { + if (srl->type == ADJ_SID + && srl->remote_id.s_addr == neighbor_id->s_addr) { + label = srl->sid[0]; + break; + } + } + } + + return label; +} + /* Get neighbor full structure from address */ static struct ospf_neighbor *get_neighbor_by_addr(struct ospf *top, struct in_addr addr) @@ -1562,6 +1622,7 @@ void ospf_sr_ext_itf_add(struct ext_itf *exti) srl->itf_addr = exti->link.link_data; srl->instance = SET_OPAQUE_LSID(OPAQUE_TYPE_EXTENDED_LINK_LSA, exti->instance); + srl->remote_id = exti->link.link_id; switch (exti->stype) { case ADJ_SID: srl->type = ADJ_SID; diff --git a/ospfd/ospf_sr.h b/ospfd/ospf_sr.h index 222675944d..d8079c9905 100644 --- a/ospfd/ospf_sr.h +++ b/ospfd/ospf_sr.h @@ -290,6 +290,9 @@ struct sr_link { /* 24-bit Opaque-ID field value according to RFC 7684 specification */ uint32_t instance; + /* Addressed (remote) router id */ + struct in_addr remote_id; + /* Interface address */ struct in_addr itf_addr; @@ -361,4 +364,11 @@ extern void ospf_sr_update_local_prefix(struct interface *ifp, struct prefix *p); /* Segment Routing re-routing function */ extern void ospf_sr_update_task(struct ospf *ospf); + +/* Support for TI-LFA */ +extern mpls_label_t ospf_sr_get_prefix_sid_by_id(struct in_addr *id); +extern mpls_label_t ospf_sr_get_adj_sid_by_id(struct in_addr *root_id, + struct in_addr *neighbor_id); +extern struct sr_node *ospf_sr_node_create(struct in_addr *rid); + #endif /* _FRR_OSPF_SR_H */ diff --git a/ospfd/ospf_ti_lfa.c b/ospfd/ospf_ti_lfa.c new file mode 100644 index 0000000000..4a0186bfb9 --- /dev/null +++ b/ospfd/ospf_ti_lfa.c @@ -0,0 +1,1114 @@ +/* + * OSPF TI-LFA + * Copyright (C) 2020 NetDEF, Inc. + * Sascha Kattelmann + * + * This file is part of FRR. + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "prefix.h" +#include "table.h" +#include "printfrr.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_sr.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_ti_lfa.h" +#include "ospfd/ospf_dump.h" + + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func) +DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item, + q_spaces_compare_func) + +static void +ospf_ti_lfa_generate_p_space(struct ospf_area *area, struct vertex *child, + struct protected_resource *protected_resource, + bool recursive, struct list *pc_path); + +void ospf_print_protected_resource( + struct protected_resource *protected_resource, char *buf) +{ + struct router_lsa_link *link; + + switch (protected_resource->type) { + case OSPF_TI_LFA_LINK_PROTECTION: + link = protected_resource->link; + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "protected link: %pI4 %pI4", &link->link_id, + &link->link_data); + break; + case OSPF_TI_LFA_NODE_PROTECTION: + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "protected node: %pI4", + &protected_resource->router_id); + break; + case OSPF_TI_LFA_UNDEFINED_PROTECTION: + snprintfrr(buf, PROTECTED_RESOURCE_STRLEN, + "undefined protected resource"); + break; + } +} + +static enum ospf_ti_lfa_p_q_space_adjacency +ospf_ti_lfa_find_p_node(struct vertex *pc_node, struct p_space *p_space, + struct q_space *q_space) +{ + struct listnode *curr_node; + struct vertex *p_node = NULL, *pc_node_parent, *p_node_pc_parent; + struct vertex_parent *pc_vertex_parent; + + curr_node = listnode_lookup(q_space->pc_path, pc_node); + pc_node_parent = listgetdata(curr_node->next); + + q_space->p_node_info->type = OSPF_TI_LFA_UNDEFINED_NODE; + + p_node = ospf_spf_vertex_find(pc_node_parent->id, p_space->vertex_list); + + if (p_node) { + q_space->p_node_info->node = p_node; + q_space->p_node_info->type = OSPF_TI_LFA_P_NODE; + + if (curr_node->next->next) { + p_node_pc_parent = listgetdata(curr_node->next->next); + pc_vertex_parent = ospf_spf_vertex_parent_find( + p_node_pc_parent->id, pc_node_parent); + q_space->p_node_info->nexthop = + pc_vertex_parent->nexthop->router; + } else { + /* + * It can happen that the P node is the root node itself + * (hence there can be no parents). In this case we + * don't need to set a nexthop. + */ + q_space->p_node_info->nexthop.s_addr = INADDR_ANY; + } + + return OSPF_TI_LFA_P_Q_SPACE_ADJACENT; + } + + ospf_ti_lfa_find_p_node(pc_node_parent, p_space, q_space); + return OSPF_TI_LFA_P_Q_SPACE_NON_ADJACENT; +} + +static void ospf_ti_lfa_find_q_node(struct vertex *pc_node, + struct p_space *p_space, + struct q_space *q_space) +{ + struct listnode *curr_node, *next_node; + struct vertex *p_node, *q_node, *q_space_parent = NULL, *pc_node_parent; + struct vertex_parent *pc_vertex_parent; + + curr_node = listnode_lookup(q_space->pc_path, pc_node); + next_node = curr_node->next; + pc_node_parent = listgetdata(next_node); + pc_vertex_parent = + ospf_spf_vertex_parent_find(pc_node_parent->id, pc_node); + + p_node = ospf_spf_vertex_find(pc_node->id, p_space->vertex_list); + q_node = ospf_spf_vertex_find(pc_node->id, q_space->vertex_list); + + /* The Q node is always present. */ + assert(q_node); + + q_space->q_node_info->type = OSPF_TI_LFA_UNDEFINED_NODE; + + if (p_node && q_node) { + q_space->q_node_info->node = pc_node; + q_space->q_node_info->type = OSPF_TI_LFA_PQ_NODE; + q_space->q_node_info->nexthop = + pc_vertex_parent->nexthop->router; + return; + } + + /* + * Note that the Q space has the 'reverse' direction of the PC + * SPF. Hence compare PC SPF parent to Q space children. + */ + q_space_parent = + ospf_spf_vertex_find(pc_node_parent->id, q_node->children); + + /* + * If the Q space parent doesn't exist we 'hit' the border to the P + * space and hence got our Q node. + */ + if (!q_space_parent) { + q_space->q_node_info->node = pc_node; + q_space->q_node_info->type = OSPF_TI_LFA_Q_NODE; + q_space->q_node_info->nexthop = + pc_vertex_parent->nexthop->router; + return; + } + + return ospf_ti_lfa_find_q_node(pc_node_parent, p_space, q_space); +} + +static void ospf_ti_lfa_append_label_stack(struct mpls_label_stack *label_stack, + mpls_label_t labels[], + uint32_t num_labels) +{ + int i, offset, limit; + + limit = label_stack->num_labels + num_labels; + offset = label_stack->num_labels; + + for (i = label_stack->num_labels; i < limit; i++) { + label_stack->label[i] = labels[i - offset]; + label_stack->num_labels++; + } +} + + +static struct mpls_label_stack * +ospf_ti_lfa_create_label_stack(mpls_label_t labels[], uint32_t num_labels) +{ + struct mpls_label_stack *label_stack; + uint32_t i; + + /* Sanity check */ + for (i = 0; i < num_labels; i++) { + if (labels[i] == MPLS_INVALID_LABEL) + return NULL; + } + + label_stack = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct mpls_label_stack) + + MPLS_MAX_LABELS * sizeof(mpls_label_t)); + label_stack->num_labels = num_labels; + + for (i = 0; i < num_labels; i++) + label_stack->label[i] = labels[i]; + + return label_stack; +} + +static struct list * +ospf_ti_lfa_map_path_to_pc_vertices(struct list *path, + struct list *pc_vertex_list) +{ + struct listnode *node; + struct vertex *vertex, *pc_vertex; + struct list *pc_path; + + pc_path = list_new(); + + for (ALL_LIST_ELEMENTS_RO(path, node, vertex)) { + pc_vertex = ospf_spf_vertex_find(vertex->id, pc_vertex_list); + listnode_add(pc_path, pc_vertex); + } + + return pc_path; +} + +static struct list *ospf_ti_lfa_cut_out_pc_path(struct list *pc_vertex_list, + struct list *pc_path, + struct vertex *p_node, + struct vertex *q_node) +{ + struct list *inner_pc_path; + struct vertex *current_vertex; + struct listnode *current_listnode; + + inner_pc_path = list_new(); + current_vertex = ospf_spf_vertex_find(q_node->id, pc_vertex_list); + current_listnode = listnode_lookup(pc_path, current_vertex); + + /* Note that the post-convergence paths are reversed. */ + for (;;) { + current_vertex = listgetdata(current_listnode); + listnode_add(inner_pc_path, current_vertex); + + if (current_vertex->id.s_addr == p_node->id.s_addr) + break; + + current_listnode = current_listnode->next; + } + + return inner_pc_path; +} + +static void ospf_ti_lfa_generate_inner_label_stack( + struct ospf_area *area, struct p_space *p_space, + struct q_space *q_space, + struct ospf_ti_lfa_inner_backup_path_info *inner_backup_path_info) +{ + struct route_table *new_table, *new_rtrs; + struct vertex *q_node; + struct vertex *start_vertex, *end_vertex; + struct vertex_parent *vertex_parent; + struct listnode *pc_p_node, *pc_q_node; + struct vertex *spf_orig; + struct list *vertex_list_orig; + struct p_spaces_head *p_spaces_orig; + struct p_space *inner_p_space; + struct q_space *inner_q_space; + struct ospf_ti_lfa_node_info *p_node_info, *q_node_info; + struct protected_resource *protected_resource; + struct list *inner_pc_path; + mpls_label_t start_label, end_label; + + p_node_info = q_space->p_node_info; + q_node_info = q_space->q_node_info; + protected_resource = p_space->protected_resource; + + start_vertex = p_node_info->node; + end_vertex = q_node_info->node; + + /* + * It can happen that the P node and/or the Q node are the root or + * the destination, therefore we need to force one step forward (resp. + * backward) using an Adjacency-SID. + */ + start_label = MPLS_INVALID_LABEL; + end_label = MPLS_INVALID_LABEL; + if (p_node_info->node->id.s_addr == p_space->root->id.s_addr) { + pc_p_node = listnode_lookup(q_space->pc_path, p_space->pc_spf); + start_vertex = listgetdata(pc_p_node->prev); + start_label = ospf_sr_get_adj_sid_by_id(&p_node_info->node->id, + &start_vertex->id); + } + if (q_node_info->node->id.s_addr == q_space->root->id.s_addr) { + pc_q_node = listnode_lookup(q_space->pc_path, + listnode_head(q_space->pc_path)); + end_vertex = listgetdata(pc_q_node->next); + end_label = ospf_sr_get_adj_sid_by_id(&end_vertex->id, + &q_node_info->node->id); + } + + /* Corner case: inner path is just one node */ + if (start_vertex->id.s_addr == end_vertex->id.s_addr) { + inner_backup_path_info->label_stack = + ospf_ti_lfa_create_label_stack(&start_label, 1); + inner_backup_path_info->q_node_info.node = end_vertex; + inner_backup_path_info->q_node_info.type = OSPF_TI_LFA_PQ_NODE; + inner_backup_path_info->p_node_info.type = + OSPF_TI_LFA_UNDEFINED_NODE; + vertex_parent = ospf_spf_vertex_parent_find(p_space->root->id, + end_vertex); + inner_backup_path_info->p_node_info.nexthop = + vertex_parent->nexthop->router; + return; + } + + inner_pc_path = ospf_ti_lfa_cut_out_pc_path(p_space->pc_vertex_list, + q_space->pc_path, + start_vertex, end_vertex); + + new_table = route_table_init(); + new_rtrs = route_table_init(); + + /* Copy the current state ... */ + spf_orig = area->spf; + vertex_list_orig = area->spf_vertex_list; + p_spaces_orig = area->p_spaces; + + area->p_spaces = + XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_spaces_head)); + + /* dry run true, root node false */ + ospf_spf_calculate(area, start_vertex->lsa_p, new_table, new_rtrs, true, + false); + + q_node = ospf_spf_vertex_find(end_vertex->id, area->spf_vertex_list); + + ospf_ti_lfa_generate_p_space(area, q_node, protected_resource, false, + inner_pc_path); + + /* There's just one P and Q space */ + inner_p_space = p_spaces_pop(area->p_spaces); + inner_q_space = q_spaces_pop(inner_p_space->q_spaces); + + /* Copy over inner backup path information from the inner q_space */ + + /* In case the outer P node is also the root of the P space */ + if (start_label != MPLS_INVALID_LABEL) { + inner_backup_path_info->label_stack = + ospf_ti_lfa_create_label_stack(&start_label, 1); + ospf_ti_lfa_append_label_stack( + inner_backup_path_info->label_stack, + inner_q_space->label_stack->label, + inner_q_space->label_stack->num_labels); + inner_backup_path_info->p_node_info.node = start_vertex; + inner_backup_path_info->p_node_info.type = OSPF_TI_LFA_P_NODE; + vertex_parent = ospf_spf_vertex_parent_find(p_space->root->id, + start_vertex); + inner_backup_path_info->p_node_info.nexthop = + vertex_parent->nexthop->router; + } else { + memcpy(inner_backup_path_info->label_stack, + inner_q_space->label_stack, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) + * inner_q_space->label_stack + ->num_labels); + memcpy(&inner_backup_path_info->p_node_info, + inner_q_space->p_node_info, + sizeof(struct ospf_ti_lfa_node_info)); + } + + /* In case the outer Q node is also the root of the Q space */ + if (end_label != MPLS_INVALID_LABEL) { + inner_backup_path_info->q_node_info.node = end_vertex; + inner_backup_path_info->q_node_info.type = OSPF_TI_LFA_Q_NODE; + } else { + memcpy(&inner_backup_path_info->q_node_info, + inner_q_space->q_node_info, + sizeof(struct ospf_ti_lfa_node_info)); + } + + /* Cleanup */ + ospf_ti_lfa_free_p_spaces(area); + ospf_spf_cleanup(area->spf, area->spf_vertex_list); + + /* ... and copy the current state back. */ + area->spf = spf_orig; + area->spf_vertex_list = vertex_list_orig; + area->p_spaces = p_spaces_orig; +} + +static void ospf_ti_lfa_generate_label_stack(struct ospf_area *area, + struct p_space *p_space, + struct q_space *q_space) +{ + enum ospf_ti_lfa_p_q_space_adjacency adjacency_result; + mpls_label_t labels[MPLS_MAX_LABELS]; + struct vertex *pc_node; + struct ospf_ti_lfa_inner_backup_path_info inner_backup_path_info; + + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: Generating Label stack for src %pI4 and dest %pI4.", + __func__, &p_space->root->id, &q_space->root->id); + + pc_node = listnode_head(q_space->pc_path); + + if (!pc_node) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: There seems to be no post convergence path (yet).", + __func__); + return; + } + + ospf_ti_lfa_find_q_node(pc_node, p_space, q_space); + if (q_space->q_node_info->type == OSPF_TI_LFA_UNDEFINED_NODE) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug("%s: Q node not found!", __func__); + return; + } + + /* Found a PQ node? Then we are done here. */ + if (q_space->q_node_info->type == OSPF_TI_LFA_PQ_NODE) { + /* + * If the PQ node is a child of the root, then we can use an + * adjacency SID instead of a prefix SID for the backup path. + */ + if (ospf_spf_vertex_parent_find(p_space->root->id, + q_space->q_node_info->node)) + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->q_node_info->node->id); + else + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->q_node_info->node->id); + + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + q_space->nexthop = q_space->q_node_info->nexthop; + + return; + } + + /* Otherwise find a (hopefully adjacent) P node. */ + pc_node = ospf_spf_vertex_find(q_space->q_node_info->node->id, + p_space->pc_vertex_list); + adjacency_result = ospf_ti_lfa_find_p_node(pc_node, p_space, q_space); + + if (q_space->p_node_info->type == OSPF_TI_LFA_UNDEFINED_NODE) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug("%s: P node not found!", __func__); + return; + } + + /* + * This should be the regular case: P and Q space are adjacent or even + * overlapping. This is guaranteed for link protection when used with + * symmetric weights. + */ + if (adjacency_result == OSPF_TI_LFA_P_Q_SPACE_ADJACENT) { + /* + * It can happen that the P node is the root itself, therefore + * we don't need a label for it. So just one adjacency SID for + * the Q node. + */ + if (q_space->p_node_info->node->id.s_addr + == p_space->root->id.s_addr) { + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->q_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + q_space->nexthop = q_space->q_node_info->nexthop; + return; + } + + /* + * Otherwise we have a P and also a Q node (which are adjacent). + * + * It can happen that the P node is a child of the root, + * therefore we might just need the adjacency SID for the P node + * instead of the prefix SID. For the Q node always take the + * adjacency SID. + */ + if (ospf_spf_vertex_parent_find(p_space->root->id, + q_space->p_node_info->node)) + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->p_node_info->node->id); + else + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->p_node_info->node->id); + + labels[1] = ospf_sr_get_adj_sid_by_id( + &q_space->p_node_info->node->id, + &q_space->q_node_info->node->id); + + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 2); + q_space->nexthop = q_space->p_node_info->nexthop; + + } else { + /* + * It can happen that the P and Q space are not adjacent when + * e.g. node protection or asymmetric weights are used. In this + * case the found P and Q nodes are used as a reference for + * another run of the algorithm! + * + * After having found the inner label stack it is stitched + * together with the outer labels. + */ + inner_backup_path_info.label_stack = XCALLOC( + MTYPE_OSPF_PATH, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) * MPLS_MAX_LABELS); + ospf_ti_lfa_generate_inner_label_stack(area, p_space, q_space, + &inner_backup_path_info); + + /* + * First stitch together the outer P node label with the inner + * label stack. + */ + if (q_space->p_node_info->node->id.s_addr + == p_space->root->id.s_addr) { + /* + * It can happen that the P node is the root itself, + * therefore we don't need a label for it. Just take + * the inner label stack first. + */ + q_space->label_stack = ospf_ti_lfa_create_label_stack( + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + + /* Use the inner P or Q node for the nexthop */ + if (inner_backup_path_info.p_node_info.type + != OSPF_TI_LFA_UNDEFINED_NODE) + q_space->nexthop = inner_backup_path_info + .p_node_info.nexthop; + else + q_space->nexthop = inner_backup_path_info + .q_node_info.nexthop; + + } else if (ospf_spf_vertex_parent_find( + p_space->root->id, + q_space->p_node_info->node)) { + /* + * It can happen that the outer P node is a child of + * the root, therefore we might just need the + * adjacency SID for the outer P node instead of the + * prefix SID. Then just append the inner label stack. + */ + labels[0] = ospf_sr_get_adj_sid_by_id( + &p_space->root->id, + &q_space->p_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + ospf_ti_lfa_append_label_stack( + q_space->label_stack, + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + q_space->nexthop = q_space->p_node_info->nexthop; + } else { + /* The outer P node needs a Prefix-SID here */ + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->p_node_info->node->id); + q_space->label_stack = + ospf_ti_lfa_create_label_stack(labels, 1); + ospf_ti_lfa_append_label_stack( + q_space->label_stack, + inner_backup_path_info.label_stack->label, + inner_backup_path_info.label_stack->num_labels); + q_space->nexthop = q_space->p_node_info->nexthop; + } + + /* Now the outer Q node needs to be considered */ + if (ospf_spf_vertex_parent_find( + inner_backup_path_info.q_node_info.node->id, + q_space->q_node_info->node)) { + /* + * The outer Q node can be a child of the inner Q node, + * hence just add an Adjacency-SID. + */ + labels[0] = ospf_sr_get_adj_sid_by_id( + &inner_backup_path_info.q_node_info.node->id, + &q_space->q_node_info->node->id); + ospf_ti_lfa_append_label_stack(q_space->label_stack, + labels, 1); + } else { + /* Otherwise a Prefix-SID is needed */ + labels[0] = ospf_sr_get_prefix_sid_by_id( + &q_space->q_node_info->node->id); + ospf_ti_lfa_append_label_stack(q_space->label_stack, + labels, 1); + } + /* + * Note that there's also the case where the inner and outer Q + * node are the same, but then there's nothing to do! + */ + } +} + +static struct list * +ospf_ti_lfa_generate_post_convergence_path(struct list *pc_vertex_list, + struct vertex *dest) +{ + struct list *pc_path; + struct vertex *current_vertex; + struct vertex_parent *parent; + + current_vertex = ospf_spf_vertex_find(dest->id, pc_vertex_list); + if (!current_vertex) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: There seems to be no post convergence path (yet).", + __func__); + return NULL; + } + + pc_path = list_new(); + listnode_add(pc_path, current_vertex); + + /* Generate a backup path in reverse order */ + for (;;) { + parent = listnode_head(current_vertex->parents); + if (!parent) + break; + + listnode_add(pc_path, parent->parent); + current_vertex = parent->parent; + } + + return pc_path; +} + +static void ospf_ti_lfa_generate_q_spaces(struct ospf_area *area, + struct p_space *p_space, + struct vertex *dest, bool recursive, + struct list *pc_path) +{ + struct listnode *node; + struct vertex *child; + struct route_table *new_table, *new_rtrs; + struct q_space *q_space, q_space_search; + char label_buf[MPLS_LABEL_STRLEN]; + char res_buf[PROTECTED_RESOURCE_STRLEN]; + bool node_protected; + + ospf_print_protected_resource(p_space->protected_resource, res_buf); + node_protected = + p_space->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION + && dest->id.s_addr + == p_space->protected_resource->router_id.s_addr; + + /* + * If node protection is used, don't build a Q space for the protected + * node of that particular P space. Move on with children instead. + */ + if (node_protected) { + if (recursive) { + /* Recursively generate Q spaces for all children */ + for (ALL_LIST_ELEMENTS_RO(dest->children, node, child)) + ospf_ti_lfa_generate_q_spaces(area, p_space, + child, recursive, + pc_path); + } + return; + } + + /* Check if we already have a Q space for this destination */ + q_space_search.root = dest; + if (q_spaces_find(p_space->q_spaces, &q_space_search)) + return; + + q_space = XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_space)); + q_space->p_node_info = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct ospf_ti_lfa_node_info)); + q_space->q_node_info = XCALLOC(MTYPE_OSPF_Q_SPACE, + sizeof(struct ospf_ti_lfa_node_info)); + + new_table = route_table_init(); + new_rtrs = route_table_init(); + + /* + * Generate a new (reversed!) SPF tree for this vertex, + * dry run true, root node false + */ + area->spf_reversed = true; + ospf_spf_calculate(area, dest->lsa_p, new_table, new_rtrs, true, false); + + /* Reset the flag for reverse SPF */ + area->spf_reversed = false; + + q_space->root = area->spf; + q_space->vertex_list = area->spf_vertex_list; + q_space->label_stack = NULL; + + if (pc_path) + q_space->pc_path = ospf_ti_lfa_map_path_to_pc_vertices( + pc_path, p_space->pc_vertex_list); + else + q_space->pc_path = ospf_ti_lfa_generate_post_convergence_path( + p_space->pc_vertex_list, q_space->root); + + /* If there's no backup path available then we are done here. */ + if (!q_space->pc_path) { + zlog_info( + "%s: NO backup path found for root %pI4 and destination %pI4 for %s, aborting ...", + __func__, &p_space->root->id, &q_space->root->id, + res_buf); + return; + } + + /* 'Cut' the protected resource out of the new SPF tree */ + ospf_spf_remove_resource(q_space->root, q_space->vertex_list, + p_space->protected_resource); + + /* + * Generate the smallest possible label stack from the root of the P + * space to the root of the Q space. + */ + ospf_ti_lfa_generate_label_stack(area, p_space, q_space); + + if (q_space->label_stack) { + mpls_label2str(q_space->label_stack->num_labels, + q_space->label_stack->label, label_buf, + MPLS_LABEL_STRLEN, true); + zlog_info( + "%s: Generated label stack %s for root %pI4 and destination %pI4 for %s", + __func__, label_buf, &p_space->root->id, + &q_space->root->id, res_buf); + } else { + zlog_info( + "%s: NO label stack generated for root %pI4 and destination %pI4 for %s", + __func__, &p_space->root->id, &q_space->root->id, + res_buf); + } + + /* We are finished, store the new Q space in the P space struct */ + q_spaces_add(p_space->q_spaces, q_space); + + /* Recursively generate Q spaces for all children */ + if (recursive) { + for (ALL_LIST_ELEMENTS_RO(dest->children, node, child)) + ospf_ti_lfa_generate_q_spaces(area, p_space, child, + recursive, pc_path); + } +} + +static void ospf_ti_lfa_generate_post_convergence_spf(struct ospf_area *area, + struct p_space *p_space) +{ + struct route_table *new_table, *new_rtrs; + + new_table = route_table_init(); + new_rtrs = route_table_init(); + + area->spf_protected_resource = p_space->protected_resource; + + /* + * The 'post convergence' SPF tree is generated here + * dry run true, root node false + * + * So how does this work? During the SPF calculation the algorithm + * checks if a link belongs to a protected resource and then just + * ignores it. + * This is actually _NOT_ a good way to calculate the post + * convergence SPF tree. The preferred way would be to delete the + * relevant links (and nodes) from a copy of the LSDB and then just run + * the SPF algorithm on that as usual. + * However, removing links from router LSAs appears to be its own + * endeavour (because LSAs are stored as a 'raw' stream), so we go with + * this rather hacky way for now. + */ + ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs, + true, false); + + p_space->pc_spf = area->spf; + p_space->pc_vertex_list = area->spf_vertex_list; + + area->spf_protected_resource = NULL; +} + +static void +ospf_ti_lfa_generate_p_space(struct ospf_area *area, struct vertex *child, + struct protected_resource *protected_resource, + bool recursive, struct list *pc_path) +{ + struct vertex *spf_orig; + struct list *vertex_list, *vertex_list_orig; + struct p_space *p_space; + + p_space = XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_space)); + vertex_list = list_new(); + + /* The P-space will get its own SPF tree, so copy the old one */ + ospf_spf_copy(area->spf, vertex_list); + p_space->root = listnode_head(vertex_list); + p_space->vertex_list = vertex_list; + p_space->protected_resource = protected_resource; + + /* Initialize the Q spaces for this P space and protected resource */ + p_space->q_spaces = + XCALLOC(MTYPE_OSPF_Q_SPACE, sizeof(struct q_spaces_head)); + q_spaces_init(p_space->q_spaces); + + /* 'Cut' the protected resource out of the new SPF tree */ + ospf_spf_remove_resource(p_space->root, p_space->vertex_list, + p_space->protected_resource); + + /* + * Since we are going to calculate more SPF trees for Q spaces, keep the + * 'original' one here temporarily + */ + spf_orig = area->spf; + vertex_list_orig = area->spf_vertex_list; + + /* Generate the post convergence SPF as a blueprint for backup paths */ + ospf_ti_lfa_generate_post_convergence_spf(area, p_space); + + /* Generate the relevant Q spaces for this particular P space */ + ospf_ti_lfa_generate_q_spaces(area, p_space, child, recursive, pc_path); + + /* Put the 'original' SPF tree back in place */ + area->spf = spf_orig; + area->spf_vertex_list = vertex_list_orig; + + /* We are finished, store the new P space */ + p_spaces_add(area->p_spaces, p_space); +} + +void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area, + enum protection_type protection_type) +{ + struct listnode *node, *inner_node; + struct vertex *root, *child; + struct vertex_parent *vertex_parent; + uint8_t *p, *lim; + struct router_lsa_link *l = NULL; + struct prefix stub_prefix, child_prefix; + struct protected_resource *protected_resource; + + area->p_spaces = + XCALLOC(MTYPE_OSPF_P_SPACE, sizeof(struct p_spaces_head)); + p_spaces_init(area->p_spaces); + + root = area->spf; + + /* Root or its router LSA was not created yet? */ + if (!root || !root->lsa) + return; + + stub_prefix.family = AF_INET; + child_prefix.family = AF_INET; + child_prefix.prefixlen = IPV4_MAX_PREFIXLEN; + + p = ((uint8_t *)root->lsa) + OSPF_LSA_HEADER_SIZE + 4; + lim = ((uint8_t *)root->lsa) + ntohs(root->lsa->length); + + zlog_info("%s: Generating P spaces for area %pI4", __func__, + &area->area_id); + + /* + * Iterate over all stub networks which target other OSPF neighbors. + * Check the nexthop of the child vertex if a stub network is relevant. + */ + while (p < lim) { + l = (struct router_lsa_link *)p; + p += (OSPF_ROUTER_LSA_LINK_SIZE + + (l->m[0].tos_count * OSPF_ROUTER_LSA_TOS_SIZE)); + + /* First comes node protection */ + if (protection_type == OSPF_TI_LFA_NODE_PROTECTION) { + if (l->m[0].type == LSA_LINK_TYPE_POINTOPOINT) { + protected_resource = XCALLOC( + MTYPE_OSPF_P_SPACE, + sizeof(struct protected_resource)); + protected_resource->type = protection_type; + protected_resource->router_id = l->link_id; + child = ospf_spf_vertex_find( + protected_resource->router_id, + root->children); + if (child) + ospf_ti_lfa_generate_p_space( + area, child, protected_resource, + true, NULL); + } + + continue; + } + + /* The rest is about link protection */ + if (protection_type != OSPF_TI_LFA_LINK_PROTECTION) + continue; + + if (l->m[0].type != LSA_LINK_TYPE_STUB) + continue; + + stub_prefix.prefixlen = ip_masklen(l->link_data); + stub_prefix.u.prefix4 = l->link_id; + + for (ALL_LIST_ELEMENTS_RO(root->children, node, child)) { + + if (child->type != OSPF_VERTEX_ROUTER) + continue; + + for (ALL_LIST_ELEMENTS_RO(child->parents, inner_node, + vertex_parent)) { + + child_prefix.u.prefix4 = + vertex_parent->nexthop->router; + + /* + * If there's a link for that stub network then + * we will protect it. Hence generate a P space + * for that particular link including the + * Q spaces so we can later on generate a + * backup path for the link. + */ + if (prefix_match(&stub_prefix, &child_prefix)) { + zlog_info( + "%s: Generating P space for %pI4", + __func__, &l->link_id); + + protected_resource = XCALLOC( + MTYPE_OSPF_P_SPACE, + sizeof(struct + protected_resource)); + protected_resource->type = + protection_type; + protected_resource->link = l; + + ospf_ti_lfa_generate_p_space( + area, child, protected_resource, + true, NULL); + } + } + } + } +} + +static struct p_space *ospf_ti_lfa_get_p_space_by_path(struct ospf_area *area, + struct ospf_path *path) +{ + struct p_space *p_space; + struct router_lsa_link *link; + struct vertex *child; + int type; + + frr_each(p_spaces, area->p_spaces, p_space) { + type = p_space->protected_resource->type; + + if (type == OSPF_TI_LFA_LINK_PROTECTION) { + link = p_space->protected_resource->link; + if ((path->nexthop.s_addr & link->link_data.s_addr) + == (link->link_id.s_addr & link->link_data.s_addr)) + return p_space; + } + + if (type == OSPF_TI_LFA_NODE_PROTECTION) { + child = ospf_spf_vertex_by_nexthop(area->spf, + &path->nexthop); + if (child + && p_space->protected_resource->router_id.s_addr + == child->id.s_addr) + return p_space; + } + } + + return NULL; +} + +void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area, + struct route_table *new_table) +{ + struct route_node *rn; + struct ospf_route *or; + struct ospf_path *path; + struct listnode *node; + struct p_space *p_space; + struct q_space *q_space, q_space_search; + struct vertex root_search; + char label_buf[MPLS_LABEL_STRLEN]; + + for (rn = route_top(new_table); rn; rn = route_next(rn)) { + or = rn->info; + if (or == NULL) + continue; + + /* Insert a backup path for all OSPF paths */ + for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) { + + if (path->adv_router.s_addr == INADDR_ANY + || path->nexthop.s_addr == INADDR_ANY) + continue; + + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: attempting to insert backup path for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, &rn->p, &path->adv_router, + &path->nexthop); + + p_space = ospf_ti_lfa_get_p_space_by_path(area, path); + if (!p_space) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: P space not found for router id %pI4 and nexthop %pI4.", + __func__, &path->adv_router, + &path->nexthop); + continue; + } + + root_search.id = path->adv_router; + q_space_search.root = &root_search; + q_space = q_spaces_find(p_space->q_spaces, + &q_space_search); + if (!q_space) { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: Q space not found for advertising router %pI4.", + __func__, &path->adv_router); + continue; + } + + /* If there's a backup label stack, insert it*/ + if (q_space->label_stack) { + /* Init the backup path data in path */ + path->srni.backup_label_stack = XCALLOC( + MTYPE_OSPF_PATH, + sizeof(struct mpls_label_stack) + + sizeof(mpls_label_t) + * q_space->label_stack + ->num_labels); + + /* Copy over the label stack */ + path->srni.backup_label_stack->num_labels = + q_space->label_stack->num_labels; + memcpy(path->srni.backup_label_stack->label, + q_space->label_stack->label, + sizeof(mpls_label_t) + * q_space->label_stack + ->num_labels); + + /* Set the backup nexthop too */ + path->srni.backup_nexthop = q_space->nexthop; + } + + if (path->srni.backup_label_stack) { + mpls_label2str( + path->srni.backup_label_stack + ->num_labels, + path->srni.backup_label_stack->label, + label_buf, MPLS_LABEL_STRLEN, true); + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: inserted backup path %s for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, label_buf, &rn->p, + &path->adv_router, + &path->nexthop); + } else { + if (IS_DEBUG_OSPF_TI_LFA) + zlog_debug( + "%s: inserted NO backup path for prefix %pFX, router id %pI4 and nexthop %pI4.", + __func__, &rn->p, + &path->adv_router, + &path->nexthop); + } + } + } +} + +void ospf_ti_lfa_free_p_spaces(struct ospf_area *area) +{ + struct p_space *p_space; + struct q_space *q_space; + + while ((p_space = p_spaces_pop(area->p_spaces))) { + while ((q_space = q_spaces_pop(p_space->q_spaces))) { + ospf_spf_cleanup(q_space->root, q_space->vertex_list); + + if (q_space->pc_path) + list_delete(&q_space->pc_path); + + XFREE(MTYPE_OSPF_Q_SPACE, q_space->p_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->q_node_info); + XFREE(MTYPE_OSPF_Q_SPACE, q_space->label_stack); + XFREE(MTYPE_OSPF_Q_SPACE, q_space); + } + + ospf_spf_cleanup(p_space->root, p_space->vertex_list); + ospf_spf_cleanup(p_space->pc_spf, p_space->pc_vertex_list); + XFREE(MTYPE_OSPF_P_SPACE, p_space->protected_resource); + + q_spaces_fini(p_space->q_spaces); + XFREE(MTYPE_OSPF_Q_SPACE, p_space->q_spaces); + } + + p_spaces_fini(area->p_spaces); + XFREE(MTYPE_OSPF_P_SPACE, area->p_spaces); +} + +void ospf_ti_lfa_compute(struct ospf_area *area, struct route_table *new_table, + enum protection_type protection_type) +{ + /* + * Generate P spaces per protected link/node and their respective Q + * spaces, generate backup paths (MPLS label stacks) by finding P/Q + * nodes. + */ + ospf_ti_lfa_generate_p_spaces(area, protection_type); + + /* Insert the generated backup paths into the routing table. */ + ospf_ti_lfa_insert_backup_paths(area, new_table); + + /* Cleanup P spaces and related datastructures including Q spaces. */ + ospf_ti_lfa_free_p_spaces(area); +} diff --git a/ospfd/ospf_ti_lfa.h b/ospfd/ospf_ti_lfa.h new file mode 100644 index 0000000000..bc8f19b98f --- /dev/null +++ b/ospfd/ospf_ti_lfa.h @@ -0,0 +1,41 @@ +/* + * OSPF calculation. + * Copyright (C) 2020 NetDEF, Inc. + * Sascha Kattelmann + * + * This file is part of FRR. + * + * FRR is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * FRR is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _OSPF_TI_LFA_H +#define _OSPF_TI_LFA_H + +#define PROTECTED_RESOURCE_STRLEN 100 + +extern void ospf_ti_lfa_compute(struct ospf_area *area, + struct route_table *new_table, + enum protection_type protection_type); + +/* unit testing */ +extern void ospf_ti_lfa_generate_p_spaces(struct ospf_area *area, + enum protection_type protection_type); +extern void ospf_ti_lfa_insert_backup_paths(struct ospf_area *area, + struct route_table *new_table); +extern void ospf_ti_lfa_free_p_spaces(struct ospf_area *area); +void ospf_print_protected_resource( + struct protected_resource *protected_resource, char *buf); + +#endif /* _OSPF_TI_LFA_H */ diff --git a/ospfd/ospf_vty.c b/ospfd/ospf_vty.c index 4c67d33cb9..a4a220b1f6 100644 --- a/ospfd/ospf_vty.c +++ b/ospfd/ospf_vty.c @@ -2602,6 +2602,43 @@ ALIAS(no_ospf_write_multiplier, no_write_multiplier_cmd, "Write multiplier\n" "Maximum number of interface serviced per write\n") +DEFUN(ospf_ti_lfa, ospf_ti_lfa_cmd, "fast-reroute ti-lfa [node-protection]", + "Fast Reroute for MPLS and IP resilience\n" + "Topology Independent LFA (Loop-Free Alternate)\n" + "TI-LFA node protection (default is link protection)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->ti_lfa_enabled = true; + + if (argc == 3) + ospf->ti_lfa_protection_type = OSPF_TI_LFA_NODE_PROTECTION; + else + ospf->ti_lfa_protection_type = OSPF_TI_LFA_LINK_PROTECTION; + + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + + return CMD_SUCCESS; +} + +DEFUN(no_ospf_ti_lfa, no_ospf_ti_lfa_cmd, + "no fast-reroute ti-lfa [node-protection]", + NO_STR + "Fast Reroute for MPLS and IP resilience\n" + "Topology Independent LFA (Loop-Free Alternate)\n" + "TI-LFA node protection (default is link protection)\n") +{ + VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf); + + ospf->ti_lfa_enabled = false; + + ospf->ti_lfa_protection_type = OSPF_TI_LFA_UNDEFINED_PROTECTION; + + ospf_spf_calculate_schedule(ospf, SPF_FLAG_CONFIG_CHANGE); + + return CMD_SUCCESS; +} + static const char *const ospf_abr_type_descr_str[] = { "Unknown", "Standard (RFC2328)", "Alternative IBM", "Alternative Cisco", "Alternative Shortcut" @@ -6681,8 +6718,8 @@ static void show_lsa_detail_adv_router(struct vty *vty, struct ospf *ospf, json_lstype); } -static void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, - int self, json_object *json) +void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self, + json_object *json) { struct ospf_lsa *lsa; struct route_node *rn; @@ -12364,6 +12401,14 @@ static int ospf_config_write_one(struct vty *vty, struct ospf *ospf) oi->ifp->name, &oi->address->u.prefix4); } + /* TI-LFA print. */ + if (ospf->ti_lfa_enabled) { + if (ospf->ti_lfa_protection_type == OSPF_TI_LFA_NODE_PROTECTION) + vty_out(vty, " fast-reroute ti-lfa node-protection\n"); + else + vty_out(vty, " fast-reroute ti-lfa\n"); + } + /* Network area print. */ config_write_network_area(vty, ospf); @@ -12828,6 +12873,10 @@ void ospf_vty_init(void) install_element(OSPF_NODE, &ospf_proactive_arp_cmd); install_element(OSPF_NODE, &no_ospf_proactive_arp_cmd); + /* TI-LFA commands */ + install_element(OSPF_NODE, &ospf_ti_lfa_cmd); + install_element(OSPF_NODE, &no_ospf_ti_lfa_cmd); + /* Init interface related vty commands. */ ospf_vty_if_init(); diff --git a/ospfd/ospf_vty.h b/ospfd/ospf_vty.h index 79aabe7b4e..bf9c971710 100644 --- a/ospfd/ospf_vty.h +++ b/ospfd/ospf_vty.h @@ -54,4 +54,8 @@ extern void ospf_vty_show_init(void); extern void ospf_vty_clear_init(void); extern int str2area_id(const char *, struct in_addr *, int *); +/* unit tests */ +void show_ip_ospf_database_summary(struct vty *vty, struct ospf *ospf, int self, + json_object *json); + #endif /* _QUAGGA_OSPF_VTY_H */ diff --git a/ospfd/ospf_zebra.c b/ospfd/ospf_zebra.c index 2d02619ae3..130108d348 100644 --- a/ospfd/ospf_zebra.c +++ b/ospfd/ospf_zebra.c @@ -198,15 +198,70 @@ static int ospf_interface_vrf_update(ZAPI_CALLBACK_ARGS) return 0; } +/* Nexthop, ifindex, distance and metric information. */ +static void ospf_zebra_add_nexthop(struct ospf *ospf, struct ospf_path *path, + struct zapi_route *api) +{ + struct zapi_nexthop *api_nh; + struct zapi_nexthop *api_nh_backup; + + /* TI-LFA backup path label stack comes first, if present */ + if (path->srni.backup_label_stack) { + api_nh_backup = &api->backup_nexthops[api->backup_nexthop_num]; + api_nh_backup->vrf_id = ospf->vrf_id; + + api_nh_backup->type = NEXTHOP_TYPE_IPV4; + api_nh_backup->gate.ipv4 = path->srni.backup_nexthop; + + api_nh_backup->label_num = + path->srni.backup_label_stack->num_labels; + memcpy(api_nh_backup->labels, + path->srni.backup_label_stack->label, + sizeof(mpls_label_t) * api_nh_backup->label_num); + + api->backup_nexthop_num++; + } + + /* And here comes the primary nexthop */ + api_nh = &api->nexthops[api->nexthop_num]; +#ifdef HAVE_NETLINK + if (path->unnumbered + || (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0)) { +#else /* HAVE_NETLINK */ + if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) { +#endif /* HAVE_NETLINK */ + api_nh->gate.ipv4 = path->nexthop; + api_nh->ifindex = path->ifindex; + api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; + } else if (path->nexthop.s_addr != INADDR_ANY) { + api_nh->gate.ipv4 = path->nexthop; + api_nh->type = NEXTHOP_TYPE_IPV4; + } else { + api_nh->ifindex = path->ifindex; + api_nh->type = NEXTHOP_TYPE_IFINDEX; + } + api_nh->vrf_id = ospf->vrf_id; + + /* Set TI-LFA backup nexthop info if present */ + if (path->srni.backup_label_stack) { + SET_FLAG(api->message, ZAPI_MESSAGE_BACKUP_NEXTHOPS); + SET_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + + /* Just care about a single TI-LFA backup path for now */ + api_nh->backup_num = 1; + api_nh->backup_idx[0] = api->backup_nexthop_num - 1; + } + + api->nexthop_num++; +} + void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, struct ospf_route * or) { struct zapi_route api; - struct zapi_nexthop *api_nh; uint8_t distance; struct ospf_path *path; struct listnode *node; - int count = 0; memset(&api, 0, sizeof(api)); api.vrf_id = ospf->vrf_id; @@ -241,29 +296,11 @@ void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, api.distance = distance; } - /* Nexthop, ifindex, distance and metric information. */ for (ALL_LIST_ELEMENTS_RO(or->paths, node, path)) { - if (count >= MULTIPATH_NUM) + if (api.nexthop_num >= MULTIPATH_NUM) break; - api_nh = &api.nexthops[count]; -#ifdef HAVE_NETLINK - if (path->unnumbered || (path->nexthop.s_addr != INADDR_ANY - && path->ifindex != 0)) { -#else /* HAVE_NETLINK */ - if (path->nexthop.s_addr != INADDR_ANY && path->ifindex != 0) { -#endif /* HAVE_NETLINK */ - api_nh->gate.ipv4 = path->nexthop; - api_nh->ifindex = path->ifindex; - api_nh->type = NEXTHOP_TYPE_IPV4_IFINDEX; - } else if (path->nexthop.s_addr != INADDR_ANY) { - api_nh->gate.ipv4 = path->nexthop; - api_nh->type = NEXTHOP_TYPE_IPV4; - } else { - api_nh->ifindex = path->ifindex; - api_nh->type = NEXTHOP_TYPE_IFINDEX; - } - api_nh->vrf_id = ospf->vrf_id; - count++; + + ospf_zebra_add_nexthop(ospf, path, &api); if (IS_DEBUG_OSPF(zebra, ZEBRA_REDISTRIBUTE)) { struct interface *ifp; @@ -276,7 +313,6 @@ void ospf_zebra_add(struct ospf *ospf, struct prefix_ipv4 *p, ifp ? ifp->name : " "); } } - api.nexthop_num = count; zclient_route_send(ZEBRA_ROUTE_ADD, zclient, &api); } @@ -501,6 +537,7 @@ void ospf_zebra_update_prefix_sid(const struct sr_prefix *srp) { struct zapi_labels zl; struct zapi_nexthop *znh; + struct zapi_nexthop *znh_backup; struct listnode *node; struct ospf_path *path; @@ -542,12 +579,53 @@ void ospf_zebra_update_prefix_sid(const struct sr_prefix *srp) if (zl.nexthop_num >= MULTIPATH_NUM) break; + /* + * TI-LFA backup path label stack comes first, if + * present. + */ + if (path->srni.backup_label_stack) { + znh_backup = &zl.backup_nexthops + [zl.backup_nexthop_num++]; + znh_backup->type = NEXTHOP_TYPE_IPV4; + znh_backup->gate.ipv4 = + path->srni.backup_nexthop; + + memcpy(znh_backup->labels, + path->srni.backup_label_stack->label, + sizeof(mpls_label_t) + * path->srni.backup_label_stack + ->num_labels); + + znh_backup->label_num = + path->srni.backup_label_stack + ->num_labels; + if (path->srni.label_out + != MPLS_LABEL_IPV4_EXPLICIT_NULL + && path->srni.label_out + != MPLS_LABEL_IMPLICIT_NULL) + znh_backup->labels + [znh_backup->label_num++] = + path->srni.label_out; + } + znh = &zl.nexthops[zl.nexthop_num++]; znh->type = NEXTHOP_TYPE_IPV4_IFINDEX; znh->gate.ipv4 = path->nexthop; znh->ifindex = path->ifindex; znh->label_num = 1; znh->labels[0] = path->srni.label_out; + + /* Set TI-LFA backup nexthop info if present */ + if (path->srni.backup_label_stack) { + SET_FLAG(zl.message, ZAPI_LABELS_HAS_BACKUPS); + SET_FLAG(znh->flags, + ZAPI_NEXTHOP_FLAG_HAS_BACKUP); + + /* Just care about a single TI-LFA backup path + * for now */ + znh->backup_num = 1; + znh->backup_idx[0] = zl.backup_nexthop_num - 1; + } } break; default: diff --git a/ospfd/ospfd.c b/ospfd/ospfd.c index bab75995b7..56424abeca 100644 --- a/ospfd/ospfd.c +++ b/ospfd/ospfd.c @@ -87,6 +87,30 @@ static void ospf_finish_final(struct ospf *); #define OSPF_EXTERNAL_LSA_ORIGINATE_DELAY 1 +int p_spaces_compare_func(const struct p_space *a, const struct p_space *b) +{ + if (a->protected_resource->type == OSPF_TI_LFA_LINK_PROTECTION + && b->protected_resource->type == OSPF_TI_LFA_LINK_PROTECTION) + return (a->protected_resource->link->link_id.s_addr + - b->protected_resource->link->link_id.s_addr); + + if (a->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION + && b->protected_resource->type == OSPF_TI_LFA_NODE_PROTECTION) + return (a->protected_resource->router_id.s_addr + - b->protected_resource->router_id.s_addr); + + /* This should not happen */ + return 0; +} + +int q_spaces_compare_func(const struct q_space *a, const struct q_space *b) +{ + return (a->root->id.s_addr - b->root->id.s_addr); +} + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func) + void ospf_process_refresh_data(struct ospf *ospf, bool reset) { struct vrf *vrf = vrf_lookup_by_id(ospf->vrf_id); @@ -264,8 +288,7 @@ static int ospf_area_id_cmp(struct ospf_area *a1, struct ospf_area *a2) return 0; } -/* Allocate new ospf structure. */ -static struct ospf *ospf_new(unsigned short instance, const char *name) +struct ospf *ospf_new_alloc(unsigned short instance, const char *name) { int i; struct vrf *vrf = NULL; @@ -340,8 +363,6 @@ static struct ospf *ospf_new(unsigned short instance, const char *name) new->maxage_delay = OSPF_LSA_MAXAGE_REMOVE_DELAY_DEFAULT; new->maxage_lsa = route_table_init(); new->t_maxage_walker = NULL; - thread_add_timer(master, ospf_lsa_maxage_walker, new, - OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker); /* Distance table init. */ new->distance_table = route_table_init(); @@ -349,8 +370,6 @@ static struct ospf *ospf_new(unsigned short instance, const char *name) new->lsa_refresh_queue.index = 0; new->lsa_refresh_interval = OSPF_LSA_REFRESH_INTERVAL_DEFAULT; new->t_lsa_refresher = NULL; - thread_add_timer(master, ospf_lsa_refresh_walker, new, - new->lsa_refresh_interval, &new->t_lsa_refresher); new->lsa_refresher_started = monotime(NULL); new->ibuf = stream_new(OSPF_MAX_PACKET_SIZE + 1); @@ -368,6 +387,17 @@ static struct ospf *ospf_new(unsigned short instance, const char *name) QOBJ_REG(new, ospf); new->fd = -1; + + return new; +} + +/* Allocate new ospf structure. */ +static struct ospf *ospf_new(unsigned short instance, const char *name) +{ + struct ospf *new; + + new = ospf_new_alloc(instance, name); + if ((ospf_sock_init(new)) < 0) { if (new->vrf_id != VRF_UNKNOWN) flog_err( @@ -376,6 +406,12 @@ static struct ospf *ospf_new(unsigned short instance, const char *name) __func__); return new; } + + thread_add_timer(master, ospf_lsa_maxage_walker, new, + OSPF_LSA_MAXAGE_CHECK_INTERVAL, &new->t_maxage_walker); + thread_add_timer(master, ospf_lsa_refresh_walker, new, + new->lsa_refresh_interval, &new->t_lsa_refresher); + thread_add_read(master, ospf_read, new, new->fd, &new->t_read); return new; @@ -887,8 +923,7 @@ static void ospf_finish_final(struct ospf *ospf) /* allocate new OSPF Area object */ -static struct ospf_area *ospf_area_new(struct ospf *ospf, - struct in_addr area_id) +struct ospf_area *ospf_area_new(struct ospf *ospf, struct in_addr area_id) { struct ospf_area *new; @@ -1035,7 +1070,8 @@ void ospf_area_del_if(struct ospf_area *area, struct ospf_interface *oi) } -static void add_ospf_interface(struct connected *co, struct ospf_area *area) +struct ospf_interface *add_ospf_interface(struct connected *co, + struct ospf_area *area) { struct ospf_interface *oi; @@ -1072,6 +1108,8 @@ static void add_ospf_interface(struct connected *co, struct ospf_area *area) if ((area->ospf->router_id.s_addr != INADDR_ANY) && if_is_operative(co->ifp)) ospf_if_up(oi); + + return oi; } static void update_redistributed(struct ospf *ospf, int add_to_ospf) diff --git a/ospfd/ospfd.h b/ospfd/ospfd.h index 6960d151c2..9d5aa6a4f9 100644 --- a/ospfd/ospfd.h +++ b/ospfd/ospfd.h @@ -23,6 +23,7 @@ #define _ZEBRA_OSPFD_H #include <zebra.h> +#include "typesafe.h" #include "qobj.h" #include "libospf.h" #include "ldp_sync.h" @@ -126,6 +127,13 @@ enum { OSPF_LOG_ADJACENCY_DETAIL = (1 << 4), }; +/* TI-LFA */ +enum protection_type { + OSPF_TI_LFA_UNDEFINED_PROTECTION, + OSPF_TI_LFA_LINK_PROTECTION, + OSPF_TI_LFA_NODE_PROTECTION, +}; + /* OSPF instance structure. */ struct ospf { /* OSPF's running state based on the '[no] router ospf [<instance>]' @@ -374,10 +382,71 @@ struct ospf { /* MPLS LDP-IGP Sync */ struct ldp_sync_info_cmd ldp_sync_cmd; + /* TI-LFA support for all interfaces. */ + bool ti_lfa_enabled; + enum protection_type ti_lfa_protection_type; + QOBJ_FIELDS }; DECLARE_QOBJ_TYPE(ospf) +enum ospf_ti_lfa_p_q_space_adjacency { + OSPF_TI_LFA_P_Q_SPACE_ADJACENT, + OSPF_TI_LFA_P_Q_SPACE_NON_ADJACENT, +}; + +enum ospf_ti_lfa_node_type { + OSPF_TI_LFA_UNDEFINED_NODE, + OSPF_TI_LFA_PQ_NODE, + OSPF_TI_LFA_P_NODE, + OSPF_TI_LFA_Q_NODE, +}; + +struct ospf_ti_lfa_node_info { + struct vertex *node; + enum ospf_ti_lfa_node_type type; + struct in_addr nexthop; +}; + +struct ospf_ti_lfa_inner_backup_path_info { + struct ospf_ti_lfa_node_info p_node_info; + struct ospf_ti_lfa_node_info q_node_info; + struct mpls_label_stack *label_stack; +}; + +struct protected_resource { + enum protection_type type; + + /* Link Protection */ + struct router_lsa_link *link; + + /* Node Protection */ + struct in_addr router_id; +}; + +PREDECL_RBTREE_UNIQ(q_spaces) +struct q_space { + struct vertex *root; + struct list *vertex_list; + struct mpls_label_stack *label_stack; + struct in_addr nexthop; + struct list *pc_path; + struct ospf_ti_lfa_node_info *p_node_info; + struct ospf_ti_lfa_node_info *q_node_info; + struct q_spaces_item q_spaces_item; +}; + +PREDECL_RBTREE_UNIQ(p_spaces) +struct p_space { + struct vertex *root; + struct protected_resource *protected_resource; + struct q_spaces_head *q_spaces; + struct list *vertex_list; + struct vertex *pc_spf; + struct list *pc_vertex_list; + struct p_spaces_item p_spaces_item; +}; + /* OSPF area structure. */ struct ospf_area { /* OSPF instance. */ @@ -475,6 +544,12 @@ struct ospf_area { bool spf_root_node; /* flag for checking if the calculating node is the root node of the SPF tree */ + /* TI-LFA protected link for SPF calculations */ + struct protected_resource *spf_protected_resource; + + /* P/Q spaces for TI-LFA */ + struct p_spaces_head *p_spaces; + /* Threads. */ struct thread *t_stub_router; /* Stub-router timer */ struct thread *t_opaque_lsa_self; /* Type-10 Opaque-LSAs origin. */ @@ -482,6 +557,9 @@ struct ospf_area { /* Statistics field. */ uint32_t spf_calculation; /* SPF Calculation Count. */ + /* reverse SPF (used for TI-LFA Q spaces) */ + bool spf_reversed; + /* Time stamps. */ struct timeval ts_spf; /* SPF calculation time stamp. */ @@ -566,6 +644,7 @@ extern const char *ospf_redist_string(unsigned int route_type); extern struct ospf *ospf_lookup_instance(unsigned short); extern struct ospf *ospf_get(unsigned short instance, const char *name, bool *created); +extern struct ospf *ospf_new_alloc(unsigned short instance, const char *name); extern struct ospf *ospf_get_instance(unsigned short, bool *created); extern struct ospf *ospf_lookup_by_inst_name(unsigned short instance, const char *name); @@ -619,6 +698,8 @@ extern struct ospf_nbr_nbma *ospf_nbr_nbma_lookup_next(struct ospf *, struct in_addr *, int); extern int ospf_oi_count(struct interface *); +extern struct ospf_area *ospf_area_new(struct ospf *ospf, + struct in_addr area_id); extern struct ospf_area *ospf_area_get(struct ospf *, struct in_addr); extern void ospf_area_check_free(struct ospf *, struct in_addr); extern struct ospf_area *ospf_area_lookup_by_area_id(struct ospf *, @@ -640,4 +721,11 @@ const char *ospf_vrf_id_to_name(vrf_id_t vrf_id); int ospf_area_nssa_no_summary_set(struct ospf *, struct in_addr); const char *ospf_get_name(const struct ospf *ospf); +extern struct ospf_interface *add_ospf_interface(struct connected *co, + struct ospf_area *area); + +extern int p_spaces_compare_func(const struct p_space *a, + const struct p_space *b); +extern int q_spaces_compare_func(const struct q_space *a, + const struct q_space *b); #endif /* _ZEBRA_OSPFD_H */ diff --git a/ospfd/subdir.am b/ospfd/subdir.am index 1a807ea12b..28d58452df 100644 --- a/ospfd/subdir.am +++ b/ospfd/subdir.am @@ -52,6 +52,7 @@ ospfd_libfrrospf_a_SOURCES = \ ospfd/ospf_route.c \ ospfd/ospf_routemap.c \ ospfd/ospf_spf.c \ + ospfd/ospf_ti_lfa.c \ ospfd/ospf_sr.c \ ospfd/ospf_te.c \ ospfd/ospf_vty.c \ @@ -100,6 +101,7 @@ noinst_HEADERS += \ ospfd/ospf_ri.h \ ospfd/ospf_route.h \ ospfd/ospf_spf.h \ + ospfd/ospf_ti_lfa.h \ ospfd/ospf_sr.h \ ospfd/ospf_te.h \ ospfd/ospf_vty.h \ diff --git a/sharpd/sharp_zebra.c b/sharpd/sharp_zebra.c index 4445bc0132..fed732b843 100644 --- a/sharpd/sharp_zebra.c +++ b/sharpd/sharp_zebra.c @@ -539,6 +539,7 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, struct zapi_nhg api_nhg = {}; struct zapi_nexthop *api_nh; struct nexthop *nh; + bool is_valid = true; api_nhg.id = id; for (ALL_NEXTHOPS_PTR(nhg, nh)) { @@ -549,12 +550,25 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, break; } + /* Unresolved nexthops will lead to failure - only send + * nexthops that zebra will consider valid. + */ + if (nh->ifindex == 0) + continue; + api_nh = &api_nhg.nexthops[api_nhg.nexthop_num]; zapi_nexthop_from_nexthop(api_nh, nh); api_nhg.nexthop_num++; } + if (api_nhg.nexthop_num == 0) { + zlog_debug("%s: nhg %u not sent: no valid nexthops", + __func__, id); + is_valid = false; + goto done; + } + if (backup_nhg) { for (ALL_NEXTHOPS_PTR(backup_nhg, nh)) { if (api_nhg.backup_nexthop_num >= MULTIPATH_NUM) { @@ -563,6 +577,20 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, __func__); break; } + + /* Unresolved nexthop: will be rejected by zebra. + * That causes a problem, since the primary nexthops + * rely on array indexing into the backup nexthops. If + * that array isn't valid, the backup indexes won't be + * valid. + */ + if (nh->ifindex == 0) { + zlog_debug("%s: nhg %u: invalid backup nexthop", + __func__, id); + is_valid = false; + break; + } + api_nh = &api_nhg.backup_nexthops [api_nhg.backup_nexthop_num]; @@ -571,7 +599,9 @@ void nhg_add(uint32_t id, const struct nexthop_group *nhg, } } - zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); +done: + if (is_valid) + zclient_nhg_send(zclient, ZEBRA_NHG_ADD, &api_nhg); } void nhg_del(uint32_t id) diff --git a/tests/ospfd/.gitignore b/tests/ospfd/.gitignore new file mode 100644 index 0000000000..c659b645db --- /dev/null +++ b/tests/ospfd/.gitignore @@ -0,0 +1,3 @@ +/*_afl/* +test_ospf_spf +core diff --git a/tests/ospfd/common.c b/tests/ospfd/common.c new file mode 100644 index 0000000000..eb30c4016e --- /dev/null +++ b/tests/ospfd/common.c @@ -0,0 +1,248 @@ +#include <zebra.h> + +#include "lib/stream.h" +#include "lib/vty.h" +#include "lib/mpls.h" +#include "lib/if.h" +#include "lib/table.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_flood.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_lsdb.h" +#include "ospfd/ospf_interface.h" +#include "ospfd/ospf_sr.h" + +#include "common.h" + +struct thread_master *master; +struct zebra_privs_t ospfd_privs; + + +struct ospf_topology *test_find_topology(const char *name) +{ + if (strmatch(name, "topo1")) + return &topo1; + else if (strmatch(name, "topo2")) + return &topo2; + else if (strmatch(name, "topo3")) + return &topo3; + else if (strmatch(name, "topo4")) + return &topo4; + else if (strmatch(name, "topo5")) + return &topo5; + + return NULL; +} + +int sort_paths(const void **path1, const void **path2) +{ + const struct ospf_path *p1 = *path1; + const struct ospf_path *p2 = *path2; + + return (p1->nexthop.s_addr - p2->nexthop.s_addr); +} + +void print_route_table(struct vty *vty, struct route_table *rt) +{ + struct route_node *rn; + struct ospf_route * or ; + struct listnode *pnode; + struct ospf_path *path; + struct mpls_label_stack *label_stack; + char buf[MPLS_LABEL_STRLEN]; + + for (rn = route_top(rt); rn; rn = route_next(rn)) { + if ((or = rn->info) == NULL) + continue; + + vty_out(vty, "N %-18pFX %-15pI4 %d\n", &rn->p, + & or->u.std.area_id, or->cost); + + list_sort(or->paths, sort_paths); + + for (ALL_LIST_ELEMENTS_RO(or->paths, pnode, path)) { + if (path->nexthop.s_addr == 0) + continue; + + vty_out(vty, " -> %pI4 with adv router %pI4", + &path->nexthop, &path->adv_router); + + if (path->srni.backup_label_stack) { + label_stack = path->srni.backup_label_stack; + mpls_label2str(label_stack->num_labels, + label_stack->label, buf, + MPLS_LABEL_STRLEN, true); + vty_out(vty, " and backup path %s", buf); + } + vty_out(vty, "\n"); + } + } +} + +struct ospf_test_node *test_find_node(struct ospf_topology *topology, + const char *hostname) +{ + for (int i = 0; topology->nodes[i].hostname[0]; i++) + if (strmatch(hostname, topology->nodes[i].hostname)) + return &topology->nodes[i]; + + return NULL; +} + +static void inject_router_lsa(struct vty *vty, struct ospf *ospf, + struct ospf_topology *topology, + struct ospf_test_node *root, + struct ospf_test_node *tnode) +{ + struct ospf_area *area; + struct in_addr router_id; + struct in_addr adj_router_id; + struct prefix_ipv4 prefix; + struct in_addr data; + struct stream *s; + struct lsa_header *lsah; + struct ospf_lsa *new; + int length; + unsigned long putp; + uint16_t link_count; + struct ospf_test_node *tfound_adj_node; + struct ospf_test_adj *tadj; + bool is_self_lsa = false; + + area = ospf->backbone; + inet_aton(tnode->router_id, &router_id); + + if (strncmp(root->router_id, tnode->router_id, 256) == 0) + is_self_lsa = true; + + s = stream_new(OSPF_MAX_LSA_SIZE); + lsa_header_set(s, LSA_OPTIONS_GET(area) | LSA_OPTIONS_NSSA_GET(area), + OSPF_ROUTER_LSA, router_id, router_id); + + stream_putc(s, router_lsa_flags(area)); + stream_putc(s, 0); + + putp = stream_get_endp(s); + stream_putw(s, 0); + + for (link_count = 0; tnode->adjacencies[link_count].hostname[0]; + link_count++) { + tadj = &tnode->adjacencies[link_count]; + tfound_adj_node = test_find_node(topology, tadj->hostname); + str2prefix_ipv4(tnode->adjacencies[link_count].network, + &prefix); + + inet_aton(tfound_adj_node->router_id, &adj_router_id); + data.s_addr = prefix.prefix.s_addr; + link_info_set(&s, adj_router_id, data, + LSA_LINK_TYPE_POINTOPOINT, 0, tadj->metric); + + masklen2ip(prefix.prefixlen, &data); + link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0, + tadj->metric); + } + + /* Don't forget the node itself (just a stub) */ + str2prefix_ipv4(tnode->router_id, &prefix); + data.s_addr = 0xffffffff; + link_info_set(&s, prefix.prefix, data, LSA_LINK_TYPE_STUB, 0, 0); + + /* Take twice the link count (for P2P and stub) plus the local stub */ + stream_putw_at(s, putp, (2 * link_count) + 1); + + length = stream_get_endp(s); + lsah = (struct lsa_header *)STREAM_DATA(s); + lsah->length = htons(length); + + new = ospf_lsa_new_and_data(length); + new->area = area; + new->vrf_id = area->ospf->vrf_id; + + if (is_self_lsa) + SET_FLAG(new->flags, OSPF_LSA_SELF | OSPF_LSA_SELF_CHECKED); + + memcpy(new->data, lsah, length); + stream_free(s); + + ospf_lsdb_add(area->lsdb, new); + + if (is_self_lsa) { + ospf_lsa_unlock(&area->router_lsa_self); + area->router_lsa_self = ospf_lsa_lock(new); + } +} + +static void inject_sr_db_entry(struct vty *vty, struct ospf_test_node *tnode, + struct ospf_topology *topology) +{ + struct ospf_test_node *tfound_adj_node; + struct ospf_test_adj *tadj; + struct in_addr router_id; + struct in_addr remote_id; + struct sr_node *srn; + struct sr_prefix *srp; + struct sr_link *srl; + int link_count; + + inet_aton(tnode->router_id, &router_id); + + srn = ospf_sr_node_create(&router_id); + + srn->srgb.range_size = 8000; + srn->srgb.lower_bound = 16000; + srn->msd = 16; + + srn->srlb.range_size = 1000; + srn->srlb.lower_bound = 15000; + + /* Prefix SID */ + srp = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_prefix)); + srp->adv_router = router_id; + srp->sid = tnode->label; + srp->srn = srn; + + listnode_add(srn->ext_prefix, srp); + + /* Adjacency SIDs for all adjacencies */ + for (link_count = 0; tnode->adjacencies[link_count].hostname[0]; + link_count++) { + tadj = &tnode->adjacencies[link_count]; + tfound_adj_node = test_find_node(topology, tadj->hostname); + + srl = XCALLOC(MTYPE_OSPF_SR_PARAMS, sizeof(struct sr_link)); + srl->adv_router = router_id; + + inet_aton(tfound_adj_node->router_id, &remote_id); + srl->remote_id = remote_id; + + srl->type = ADJ_SID; + srl->sid[0] = srn->srlb.lower_bound + tadj->label; + srl->srn = srn; + + listnode_add(srn->ext_link, srl); + } +} + +int topology_load(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, struct ospf *ospf) +{ + struct ospf_test_node *tnode; + + for (int i = 0; topology->nodes[i].hostname[0]; i++) { + tnode = &topology->nodes[i]; + + /* Inject a router LSA for each node, used for SPF */ + inject_router_lsa(vty, ospf, topology, root, tnode); + + /* + * SR information could also be inected via LSAs, but directly + * filling the SR DB with labels is just easier. + */ + inject_sr_db_entry(vty, tnode, topology); + } + + return 0; +} diff --git a/tests/ospfd/common.h b/tests/ospfd/common.h new file mode 100644 index 0000000000..6d3e63e359 --- /dev/null +++ b/tests/ospfd/common.h @@ -0,0 +1,47 @@ +#ifndef _COMMON_OSPF_H +#define _COMMON_OSPF_H + +#define MAX_ADJACENCIES 8 +#define MAX_NODES 12 + +struct ospf_test_adj { + char hostname[256]; + char network[256]; + uint32_t metric; + mpls_label_t label; +}; + +struct ospf_test_node { + char hostname[256]; + const char *router_id; + mpls_label_t label; + struct ospf_test_adj adjacencies[MAX_ADJACENCIES + 1]; +}; + +struct ospf_topology { + struct ospf_test_node nodes[MAX_NODES + 1]; +}; + +/* Prototypes. */ +extern struct ospf_topology *test_find_topology(const char *name); +extern struct ospf_test_node *test_find_node(struct ospf_topology *topology, + const char *hostname); +extern int topology_load(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, struct ospf *ospf); + +/* Global variables. */ +extern struct thread_master *master; +extern struct ospf_topology topo1; +extern struct ospf_topology topo2; +extern struct ospf_topology topo3; +extern struct ospf_topology topo4; +extern struct ospf_topology topo5; +extern struct zebra_privs_t ospfd_privs; + +/* For stable order in unit tests */ +extern int sort_paths(const void **path1, const void **path2); + +/* Print the routing table */ +extern void print_route_table(struct vty *vty, struct route_table *rt); + +#endif /* _COMMON_OSPF_H */ diff --git a/tests/ospfd/test_ospf_spf.c b/tests/ospfd/test_ospf_spf.c new file mode 100644 index 0000000000..a85f7e14ec --- /dev/null +++ b/tests/ospfd/test_ospf_spf.c @@ -0,0 +1,303 @@ +#include <zebra.h> + +#include "getopt.h" +#include "thread.h" +#include <lib/version.h> +#include "vty.h" +#include "command.h" +#include "log.h" +#include "vrf.h" +#include "table.h" +#include "mpls.h" + +#include "ospfd/ospfd.h" +#include "ospfd/ospf_asbr.h" +#include "ospfd/ospf_lsa.h" +#include "ospfd/ospf_route.h" +#include "ospfd/ospf_spf.h" +#include "ospfd/ospf_ti_lfa.h" +#include "ospfd/ospf_vty.h" +#include "ospfd/ospf_dump.h" +#include "ospfd/ospf_sr.h" + +#include "common.h" + +DECLARE_RBTREE_UNIQ(p_spaces, struct p_space, p_spaces_item, + p_spaces_compare_func) +DECLARE_RBTREE_UNIQ(q_spaces, struct q_space, q_spaces_item, + q_spaces_compare_func) + +static struct ospf *test_init(struct ospf_test_node *root) +{ + struct ospf *ospf; + struct ospf_area *area; + struct in_addr area_id; + struct in_addr router_id; + + ospf = ospf_new_alloc(0, NULL); + + area_id.s_addr = OSPF_AREA_BACKBONE; + area = ospf_area_new(ospf, area_id); + listnode_add_sort(ospf->areas, area); + + inet_aton(root->router_id, &router_id); + ospf->router_id = router_id; + ospf->router_id_static = router_id; + ospf->ti_lfa_enabled = true; + + return ospf; +} + +static void test_run_spf(struct vty *vty, struct ospf *ospf, + enum protection_type protection_type, bool verbose) +{ + struct route_table *new_table, *new_rtrs; + struct ospf_area *area; + struct p_space *p_space; + struct q_space *q_space; + char label_buf[MPLS_LABEL_STRLEN]; + char res_buf[PROTECTED_RESOURCE_STRLEN]; + + /* Just use the backbone for testing */ + area = ospf->backbone; + + new_table = route_table_init(); + new_rtrs = route_table_init(); + + /* dryrun true, root_node false */ + ospf_spf_calculate(area, area->router_lsa_self, new_table, new_rtrs, + true, false); + + if (verbose) { + vty_out(vty, "SPF Tree without TI-LFA backup paths:\n\n"); + ospf_spf_print(vty, area->spf, 0); + + vty_out(vty, + "\nRouting Table without TI-LFA backup paths:\n\n"); + print_route_table(vty, new_table); + } + + if (verbose) + vty_out(vty, "\n... generating TI-LFA backup paths ...\n"); + + /* TI-LFA testrun */ + ospf_ti_lfa_generate_p_spaces(area, protection_type); + ospf_ti_lfa_insert_backup_paths(area, new_table); + + /* Print P/Q space information */ + if (verbose) { + vty_out(vty, "\nP and Q space info:\n"); + frr_each (p_spaces, area->p_spaces, p_space) { + ospf_print_protected_resource( + p_space->protected_resource, res_buf); + vty_out(vty, "\nP Space for root %pI4 and %s\n", + &p_space->root->id, res_buf); + ospf_spf_print(vty, p_space->root, 0); + + frr_each (q_spaces, p_space->q_spaces, q_space) { + vty_out(vty, + "\nQ Space for destination %pI4:\n", + &q_space->root->id); + ospf_spf_print(vty, q_space->root, 0); + if (q_space->label_stack) { + mpls_label2str( + q_space->label_stack + ->num_labels, + q_space->label_stack->label, + label_buf, MPLS_LABEL_STRLEN, + true); + vty_out(vty, "\nLabel stack: %s\n", + label_buf); + } else { + vty_out(vty, + "\nLabel stack not generated!\n"); + } + } + + vty_out(vty, "\nPost-convergence SPF Tree:\n"); + ospf_spf_print(vty, p_space->pc_spf, 0); + } + } + + /* Cleanup */ + ospf_ti_lfa_free_p_spaces(area); + ospf_spf_cleanup(area->spf, area->spf_vertex_list); + + /* + * Print the new routing table which is augmented with TI-LFA backup + * paths (label stacks). + */ + if (verbose) + vty_out(vty, + "\n\nFinal Routing Table including backup paths:\n\n"); + + print_route_table(vty, new_table); +} + +static int test_run(struct vty *vty, struct ospf_topology *topology, + struct ospf_test_node *root, + enum protection_type protection_type, bool verbose) +{ + struct ospf *ospf; + + ospf = test_init(root); + + /* Inject LSAs into the OSPF backbone according to the topology */ + if (topology_load(vty, topology, root, ospf)) { + vty_out(vty, "%% Failed to load topology\n"); + return CMD_WARNING; + } + + if (verbose) { + vty_out(vty, "\n"); + show_ip_ospf_database_summary(vty, ospf, 0, NULL); + } + + test_run_spf(vty, ospf, protection_type, verbose); + + return 0; +} + +DEFUN(test_ospf, test_ospf_cmd, + "test ospf topology WORD root HOSTNAME ti-lfa [node-protection] [verbose]", + "Test mode\n" + "Choose OSPF for SPF testing\n" + "Network topology to choose\n" + "Name of the network topology to choose\n" + "Root node to choose\n" + "Hostname of the root node to choose\n" + "Use Topology-Independent LFA\n" + "Use node protection (default is link protection)\n" + "Verbose output\n") +{ + struct ospf_topology *topology; + struct ospf_test_node *root; + enum protection_type protection_type = OSPF_TI_LFA_LINK_PROTECTION; + int idx = 0; + bool verbose = false; + + /* Parse topology. */ + argv_find(argv, argc, "topology", &idx); + topology = test_find_topology(argv[idx + 1]->arg); + if (!topology) { + vty_out(vty, "%% Topology not found\n"); + return CMD_WARNING; + } + + argv_find(argv, argc, "root", &idx); + root = test_find_node(topology, argv[idx + 1]->arg); + if (!root) { + vty_out(vty, "%% Root not found\n"); + return CMD_WARNING; + } + + if (argv_find(argv, argc, "node-protection", &idx)) + protection_type = OSPF_TI_LFA_NODE_PROTECTION; + + if (argv_find(argv, argc, "verbose", &idx)) + verbose = true; + + return test_run(vty, topology, root, protection_type, verbose); +} + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + + cmd_terminate(); + vty_terminate(); + thread_master_free(master); + + if (!isexit) + exit(0); +} + +struct option longopts[] = {{"help", no_argument, NULL, 'h'}, + {"debug", no_argument, NULL, 'd'}, + {0} }; + +/* Help information display. */ +static void usage(char *progname, int status) +{ + if (status != 0) + fprintf(stderr, "Try `%s --help' for more information.\n", + progname); + else { + printf("Usage : %s [OPTION...]\n\ +ospfd SPF test program.\n\n\ +-u, --debug Enable debugging\n\ +-h, --help Display this help and exit\n\ +\n\ +Report bugs to %s\n", + progname, FRR_BUG_ADDRESS); + } + exit(status); +} + +int main(int argc, char **argv) +{ + char *p; + char *progname; + struct thread thread; + bool debug = false; + + /* Set umask before anything for security */ + umask(0027); + + /* get program name */ + progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]); + + while (1) { + int opt; + + opt = getopt_long(argc, argv, "hd", longopts, 0); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + case 'd': + debug = true; + break; + case 'h': + usage(progname, 0); + break; + default: + usage(progname, 1); + break; + } + } + + /* master init. */ + master = thread_master_create(NULL); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + vty_init(master, false); + if (debug) + zlog_aux_init("NONE: ", LOG_DEBUG); + else + zlog_aux_init("NONE: ", ZLOG_DISABLED); + + /* Install test command. */ + install_element(VIEW_NODE, &test_ospf_cmd); + + /* needed for SR DB init */ + ospf_vty_init(); + ospf_sr_init(); + + term_debug_ospf_ti_lfa = 1; + + /* Read input from .in file. */ + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (thread_fetch(master, &thread)) + thread_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/ospfd/test_ospf_spf.in b/tests/ospfd/test_ospf_spf.in new file mode 100644 index 0000000000..f1e746745f --- /dev/null +++ b/tests/ospfd/test_ospf_spf.in @@ -0,0 +1,10 @@ +test ospf topology topo1 root rt1 ti-lfa +test ospf topology topo1 root rt1 ti-lfa node-protection +test ospf topology topo2 root rt1 ti-lfa +test ospf topology topo2 root rt1 ti-lfa node-protection +test ospf topology topo3 root rt1 ti-lfa +test ospf topology topo3 root rt1 ti-lfa node-protection +test ospf topology topo4 root rt1 ti-lfa +test ospf topology topo4 root rt1 ti-lfa node-protection +test ospf topology topo5 root rt1 ti-lfa +test ospf topology topo5 root rt1 ti-lfa node-protection diff --git a/tests/ospfd/test_ospf_spf.py b/tests/ospfd/test_ospf_spf.py new file mode 100644 index 0000000000..92a1c6a145 --- /dev/null +++ b/tests/ospfd/test_ospf_spf.py @@ -0,0 +1,4 @@ +import frrtest + +class TestOspfSPF(frrtest.TestRefOut): + program = './test_ospf_spf' diff --git a/tests/ospfd/test_ospf_spf.refout b/tests/ospfd/test_ospf_spf.refout new file mode 100644 index 0000000000..d1e3c7bc65 --- /dev/null +++ b/tests/ospfd/test_ospf_spf.refout @@ -0,0 +1,130 @@ +test# test ospf topology topo1 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 3.3.3.3/32 0.0.0.0 10 + -> 10.0.3.2 with adv router 3.3.3.3 and backup path 15001 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 + -> 10.0.3.2 with adv router 3.3.3.3 and backup path 15001 +N 10.0.3.0/24 0.0.0.0 10 +test# test ospf topology topo1 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 10 + -> 10.0.3.2 with adv router 3.3.3.3 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 + -> 10.0.3.2 with adv router 3.3.3.3 +N 10.0.3.0/24 0.0.0.0 10 +test# test ospf topology topo2 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.1.2 with adv router 3.3.3.3 and backup path 15002 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 15002 +N 10.0.3.0/24 0.0.0.0 30 +test# test ospf topology topo2 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.1.2 with adv router 3.3.3.3 and backup path 15002 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 20 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 30 +test# test ospf topology topo3 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo3 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo4 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030/15006 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 60 + -> 10.0.1.2 with adv router 2.2.2.2 and backup path 16030/15006 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo4 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 10 + -> 10.0.1.2 with adv router 2.2.2.2 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 10 +N 10.0.2.0/24 0.0.0.0 60 + -> 10.0.1.2 with adv router 2.2.2.2 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo5 root rt1 ti-lfa +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 30 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004/15006 +N 10.0.1.0/24 0.0.0.0 40 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 and backup path 15001/15004/15006 +N 10.0.4.0/24 0.0.0.0 10 +test# test ospf topology topo5 root rt1 ti-lfa node-protection +N 1.1.1.1/32 0.0.0.0 0 +N 2.2.2.2/32 0.0.0.0 30 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 3.3.3.3/32 0.0.0.0 20 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 4.4.4.4/32 0.0.0.0 10 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.1.0/24 0.0.0.0 40 + -> 10.0.4.2 with adv router 2.2.2.2 and backup path 15001 +N 10.0.2.0/24 0.0.0.0 30 + -> 10.0.4.2 with adv router 3.3.3.3 and backup path 15001/15004 +N 10.0.3.0/24 0.0.0.0 20 + -> 10.0.4.2 with adv router 4.4.4.4 +N 10.0.4.0/24 0.0.0.0 10 +test# +end. diff --git a/tests/ospfd/topologies.c b/tests/ospfd/topologies.c new file mode 100644 index 0000000000..2dc611ce96 --- /dev/null +++ b/tests/ospfd/topologies.c @@ -0,0 +1,575 @@ +#include <zebra.h> + +#include "mpls.h" +#include "if.h" + +#include "ospfd/ospfd.h" + +#include "common.h" + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt2 eth-rt1| RT2 | + * | 1.1.1.1 +---------------------+ 2.2.2.2 | + * | | 10.0.1.0/24 | | + * +---------+ +---------+ + * |eth-rt3 eth-rt3| + * | | + * |10.0.3.0/24 | + * | | + * |eth-rt1 | + * +---------+ | + * | |eth-rt2 10.0.2.0/24| + * | RT3 +--------------------------+ + * | 3.3.3.3 | + * | | + * +---------+ + * + * Link Protection: + * P and Q spaces overlap here, hence just one P/Q node regardless of which + * link is protected. Hence the backup label stack just has one label. + * + * Node Protection: + * Obviously no backup paths involved. + */ +struct ospf_topology topo1 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt3", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 10, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 5, + }, + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 6, + }, + }, + }, + }, +}; + + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt2 eth-rt1| RT2 | + * | 1.1.1.1 +---------------------+ 2.2.2.2 | + * | | 10.0.1.0/24 (10) | | + * +---------+ +---------+ + * |eth-rt3 eth-rt3| + * | | + * |10.0.3.0/24 (30) | + * | | + * |eth-rt1 | + * +---------+ | + * | |eth-rt2 10.0.2.0/24|(10) + * | RT3 +--------------------------+ + * | 3.3.3.3 | + * | | + * +---------+ + * + * Link Protection: + * Regarding the subnet 10.0.1.0/24, the P space of RT1 is just RT1 itself + * while the Q space of RT3 consists of RT3 and RT2. Hence the P and Q + * nodes are disjunct (tricky: the root node is the P node here). For the + * backup label stack just one label is necessary. + * + * Node Protection: + * For protected node RT2 and route from RT1 to RT3 there is just the backup + * path consisting of the label 15002. + */ +struct ospf_topology topo2 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt3", + .network = + "10.0.3.1/24", + .metric = 30, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 10, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.3.2/24", + .metric = 30, + .label = 5, + }, + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 6, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 (10) | | + * +---------+ +---------+ + * |eth-rt2 eth-rt3| + * | | + * |10.0.1.0/24 (10) | + * | 10.0.3.0/24 (10) | + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 (20) | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * Link Protection: + * Regarding the protected subnet 10.0.4.0/24, the P and Q spaces for root RT1 + * and destination RT4 are disjunct and the P node is RT2 while RT3 is the Q + * node. Hence the backup label stack here is 16020/15004. Note that here the + * P and Q nodes are neither the root nor the destination nodes, so this is a + * case where you really need a label stack consisting of two labels. + * + * Node Protection: + * For the protected node RT4 and the route from RT1 to RT3 there is a backup + * path with the single label 15001. + */ +struct ospf_topology topo3 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 20, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 20, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 8, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 (10) | | + * +---------+ +---------+ + * |eth+rt2 eth-rt3| + * | | + * |10.0.1.0/24 (10) | + * | 10.0.3.0/24 (10) | + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 (40) | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * This case was specifically created for Node Protection with RT4 as + * protected node from the perspective of RT1. Note the weight of 40 + * on the link between RT2 and RT3. + * The P space of RT1 is just RT2 while the Q space of RT3 is empty. + * This means that the P and Q spaces are disjunct and there are two + * labels needed to get from RT1 to RT3. + */ +struct ospf_topology topo4 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 10, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 50, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 50, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 10, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 10, + .label = 8, + }, + }, + }, + }, +}; + +/* + * +---------+ +---------+ + * | | | | + * | RT1 |eth-rt4 eth-rt1| RT4 | + * | 1.1.1.1 +---------------------+ 4.4.4.4 | + * | | 10.0.4.0/24 | | + * +---------+ +---------+ + * |eth+rt2 eth-rt3| + * | | + * |10.0.1.0/24 | + * | 10.0.3.0/24| + * |eth-rt1 eth-rt4| + * +---------+ +---------+ + * | |eth-rt3 eth-rt2| | + * | RT2 +---------------------+ RT3 | + * | 2.2.2.2 | 10.0.2.0/24 | 3.3.3.3 | + * | | | | + * +---------+ +---------+ + * + * Weights: + * - clockwise: 10 + * - counterclockwise: 40 + * + * This is an example where 3 (!) labels are needed for the protected + * link RT1<->RT2, e.g. the subnet 10.0.1.0/24, to reach RT4. + * + * Because the initial P and Q spaces will not be overlapping or + * adjacent for this case the TI-LFA will be applied recursively. + */ +struct ospf_topology topo5 = { + .nodes = + { + { + .hostname = "rt1", + .router_id = "1.1.1.1", + .label = 10, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.1.1/24", + .metric = 40, + .label = 1, + }, + { + .hostname = "rt4", + .network = + "10.0.4.1/24", + .metric = 10, + .label = 2, + }, + }, + }, + { + .hostname = "rt2", + .router_id = "2.2.2.2", + .label = 20, + .adjacencies = + { + { + .hostname = "rt1", + .network = + "10.0.1.2/24", + .metric = 10, + .label = 3, + }, + { + .hostname = "rt3", + .network = + "10.0.2.1/24", + .metric = 40, + .label = 4, + }, + }, + }, + { + .hostname = "rt3", + .router_id = "3.3.3.3", + .label = 30, + .adjacencies = + { + { + .hostname = "rt2", + .network = + "10.0.2.2/24", + .metric = 10, + .label = 5, + }, + { + .hostname = "rt4", + .network = + "10.0.3.1/24", + .metric = 40, + .label = 6, + }, + }, + }, + { + .hostname = "rt4", + .router_id = "4.4.4.4", + .label = 40, + .adjacencies = + { + { + .hostname = "rt3", + .network = + "10.0.3.2/24", + .metric = 10, + .label = 7, + }, + { + .hostname = "rt1", + .network = + "10.0.4.2/24", + .metric = 40, + .label = 8, + }, + }, + }, + }, +}; diff --git a/tests/subdir.am b/tests/subdir.am index 1f173d7f1a..b2ce54ba66 100644 --- a/tests/subdir.am +++ b/tests/subdir.am @@ -31,6 +31,14 @@ TESTS_ISISD = IGNORE_ISISD = --ignore=isisd/ endif +if OSPFD +TESTS_OSPFD = \ + tests/ospfd/test_ospf_spf \ + # end +else +TESTS_OSPFD = +endif + if OSPF6D TESTS_OSPF6D = \ tests/ospf6d/test_lsdb \ @@ -90,6 +98,7 @@ check_PROGRAMS = \ tests/lib/northbound/test_oper_data \ $(TESTS_BGPD) \ $(TESTS_ISISD) \ + $(TESTS_OSPFD) \ $(TESTS_OSPF6D) \ $(TESTS_ZEBRA) \ # end @@ -126,6 +135,7 @@ noinst_HEADERS += \ tests/lib/cli/common_cli.h \ tests/lib/test_typelist.h \ tests/isisd/test_common.h \ + tests/ospfd/common.h \ # end # @@ -145,6 +155,7 @@ TESTS_CFLAGS = \ ALL_TESTS_LDADD = lib/libfrr.la $(LIBCAP) BGP_TEST_LDADD = bgpd/libbgp.a $(RFPLDADD) $(ALL_TESTS_LDADD) -lm ISISD_TEST_LDADD = isisd/libisis.a $(ALL_TESTS_LDADD) +OSPFD_TEST_LDADD = ospfd/libfrrospf.a $(ALL_TESTS_LDADD) OSPF6_TEST_LDADD = ospf6d/libospf6.a $(ALL_TESTS_LDADD) ZEBRA_TEST_LDADD = zebra/label_manager.o $(ALL_TESTS_LDADD) @@ -213,6 +224,11 @@ tests_isisd_test_isis_vertex_queue_CPPFLAGS = $(TESTS_CPPFLAGS) tests_isisd_test_isis_vertex_queue_LDADD = $(ISISD_TEST_LDADD) tests_isisd_test_isis_vertex_queue_SOURCES = tests/isisd/test_isis_vertex_queue.c tests/isisd/test_common.c +tests_ospfd_test_ospf_spf_CFLAGS = $(TESTS_CFLAGS) +tests_ospfd_test_ospf_spf_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_ospfd_test_ospf_spf_LDADD = $(OSPFD_TEST_LDADD) +tests_ospfd_test_ospf_spf_SOURCES = tests/ospfd/test_ospf_spf.c tests/ospfd/common.c tests/ospfd/topologies.c + tests_lib_cxxcompat_CFLAGS = $(TESTS_CFLAGS) $(CXX_COMPAT_CFLAGS) $(WERROR) tests_lib_cxxcompat_CPPFLAGS = $(TESTS_CPPFLAGS) tests_lib_cxxcompat_SOURCES = tests/lib/cxxcompat.c @@ -370,6 +386,9 @@ EXTRA_DIST += \ tests/isisd/test_isis_spf.in \ tests/isisd/test_isis_spf.refout \ tests/isisd/test_isis_vertex_queue.py \ + tests/ospfd/test_ospf_spf.py \ + tests/ospfd/test_ospf_spf.in \ + tests/ospfd/test_ospf_spf.refout \ tests/lib/cli/test_commands.in \ tests/lib/cli/test_commands.py \ tests/lib/cli/test_commands.refout \ diff --git a/tests/topotests/bgp_blackhole_community/r2/bgpd.conf b/tests/topotests/bgp_blackhole_community/r2/bgpd.conf index a4fb45e1ff..5a69b99810 100644 --- a/tests/topotests/bgp_blackhole_community/r2/bgpd.conf +++ b/tests/topotests/bgp_blackhole_community/r2/bgpd.conf @@ -3,4 +3,5 @@ router bgp 65002 no bgp ebgp-requires-policy neighbor r2-eth0 interface remote-as external neighbor r2-eth1 interface remote-as external + neighbor r2-eth2 interface remote-as internal ! diff --git a/tests/topotests/bgp_blackhole_community/r2/zebra.conf b/tests/topotests/bgp_blackhole_community/r2/zebra.conf index 307e5187ca..cf6fb6d984 100644 --- a/tests/topotests/bgp_blackhole_community/r2/zebra.conf +++ b/tests/topotests/bgp_blackhole_community/r2/zebra.conf @@ -5,5 +5,8 @@ interface r2-eth0 interface r2-eth1 ip address 192.168.1.1/24 ! +interface r2-eth2 + ip address 192.168.2.1/24 +! ip forwarding ! diff --git a/tests/topotests/bgp_blackhole_community/r4/bgpd.conf b/tests/topotests/bgp_blackhole_community/r4/bgpd.conf new file mode 100644 index 0000000000..40dd72f3ec --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r4/bgpd.conf @@ -0,0 +1,5 @@ +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor r4-eth0 interface remote-as internal +! diff --git a/tests/topotests/bgp_blackhole_community/r4/zebra.conf b/tests/topotests/bgp_blackhole_community/r4/zebra.conf new file mode 100644 index 0000000000..e2ccaed52a --- /dev/null +++ b/tests/topotests/bgp_blackhole_community/r4/zebra.conf @@ -0,0 +1,6 @@ +! +interface r4-eth0 + ip address 192.168.2.2/24 +! +ip forwarding +! diff --git a/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py b/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py index b61ad354e2..a856c9278f 100644 --- a/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py +++ b/tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py @@ -21,7 +21,7 @@ """ Test if 172.16.255.254/32 tagged with BLACKHOLE community is not -re-advertised downstream. +re-advertised downstream outside local AS. """ import os @@ -38,13 +38,14 @@ from lib import topotest from lib.topogen import Topogen, TopoRouter, get_topogen from lib.topolog import logger from mininet.topo import Topo +from lib.common_config import step class TemplateTopo(Topo): def build(self, *_args, **_opts): tgen = get_topogen(self) - for routern in range(1, 4): + for routern in range(1, 5): tgen.add_router("r{}".format(routern)) switch = tgen.add_switch("s1") @@ -55,6 +56,10 @@ class TemplateTopo(Topo): switch.add_link(tgen.gears["r2"]) switch.add_link(tgen.gears["r3"]) + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["r2"]) + switch.add_link(tgen.gears["r4"]) + def setup_module(mod): tgen = Topogen(TemplateTopo, mod.__name__) @@ -88,10 +93,10 @@ def test_bgp_blackhole_community(): output = json.loads( tgen.gears["r2"].vtysh_cmd("show ip bgp 172.16.255.254/32 json") ) - expected = {"paths": [{"community": {"list": ["blackhole", "noAdvertise"]}}]} + expected = {"paths": [{"community": {"list": ["blackhole", "noExport"]}}]} return topotest.json_cmp(output, expected) - def _bgp_no_advertise(): + def _bgp_no_advertise_ebgp(): output = json.loads( tgen.gears["r2"].vtysh_cmd( "show ip bgp neighbor r2-eth1 advertised-routes json" @@ -105,15 +110,43 @@ def test_bgp_blackhole_community(): return topotest.json_cmp(output, expected) + def _bgp_no_advertise_ibgp(): + output = json.loads( + tgen.gears["r2"].vtysh_cmd( + "show ip bgp neighbor r2-eth2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": {"172.16.255.254/32": {}}, + "totalPrefixCounter": 2, + } + + return topotest.json_cmp(output, expected) + test_func = functools.partial(_bgp_converge) success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) assert result is None, 'Failed bgp convergence in "{}"'.format(tgen.gears["r2"]) - test_func = functools.partial(_bgp_no_advertise) + step("Check if 172.16.255.254/32 is not advertised to eBGP peers") + + test_func = functools.partial(_bgp_no_advertise_ebgp) + success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + + assert ( + result is None + ), 'Advertised blackhole tagged prefix to eBGP peers in "{}"'.format( + tgen.gears["r2"] + ) + + step("Check if 172.16.255.254/32 is advertised to iBGP peers") + + test_func = functools.partial(_bgp_no_advertise_ibgp) success, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) - assert result is None, 'Advertised blackhole tagged prefix in "{}"'.format( + assert ( + result is None + ), 'Withdrawn blackhole tagged prefix to iBGP peers in "{}"'.format( tgen.gears["r2"] ) diff --git a/tests/topotests/isis-lsp-bits-topo1/__init__.py b/tests/topotests/isis-lsp-bits-topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/__init__.py diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt1/isisd.conf new file mode 100644 index 0000000000..90764a0d0f --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/isisd.conf @@ -0,0 +1,27 @@ +password 1 +hostname rt1 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 + isis priority 100 +! +router isis 1 + net 49.0000.0000.0000.0001.00 + is-type level-1 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ip_route.ref new file mode 100644 index 0000000000..8557f4b010 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ip_route.ref @@ -0,0 +1,89 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + }, + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..fa76533756 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ipv6_route.ref @@ -0,0 +1,65 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + }, + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::2\/128":[ + { + "prefix":"2001:db8:1000::2\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::3\/128":[ + { + "prefix":"2001:db8:1000::3\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..26f0dffa7a --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,32 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-sw1", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0003", + "hold-timer": 9, + "neighbor-priority": 64, + "state": "up" + }, + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0002", + "hold-timer": 9, + "neighbor-priority": 64, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ip_route.ref new file mode 100644 index 0000000000..c826efdcfe --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ip_route.ref @@ -0,0 +1,82 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ipv6_route.ref new file mode 100644 index 0000000000..a386b45dad --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ipv6_route.ref @@ -0,0 +1,59 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::2\/128":[ + { + "prefix":"2001:db8:1000::2\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::3\/128":[ + { + "prefix":"2001:db8:1000::3\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ip_route.ref new file mode 100644 index 0000000000..2b281b74fb --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ip_route.ref @@ -0,0 +1,62 @@ +{ + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ipv6_route.ref new file mode 100644 index 0000000000..4b920eda01 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ipv6_route.ref @@ -0,0 +1,40 @@ +{ + "2001:db8:1000::2\/128":[ + { + "prefix":"2001:db8:1000::2\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::3\/128":[ + { + "prefix":"2001:db8:1000::3\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ip_route.ref new file mode 100644 index 0000000000..8557f4b010 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ip_route.ref @@ -0,0 +1,89 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + }, + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ipv6_route.ref new file mode 100644 index 0000000000..fa76533756 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ipv6_route.ref @@ -0,0 +1,65 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + }, + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::2\/128":[ + { + "prefix":"2001:db8:1000::2\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::3\/128":[ + { + "prefix":"2001:db8:1000::3\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt1/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt1/zebra.conf new file mode 100644 index 0000000000..9d71d3005f --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt1/zebra.conf @@ -0,0 +1,19 @@ +log file zebra.log +! +hostname rt1 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 1.1.1.1/32 + ipv6 address 2001:db8:1000::1/128 +! +interface eth-sw1 + ip address 10.0.1.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt2/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt2/isisd.conf new file mode 100644 index 0000000000..2bc4c4ad97 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt2/isisd.conf @@ -0,0 +1,35 @@ +hostname rt2 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 +! +interface eth-rt4 + ip router isis 2 + ipv6 router isis 2 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0002.00 + lsp-gen-interval 2 + topology ipv6-unicast +! +router isis 2 + net 49.0002.0000.0000.0002.00 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ip_route.ref new file mode 100644 index 0000000000..d7e886ce86 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ip_route.ref @@ -0,0 +1,77 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.1", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "3.3.3.3\/32":[ + { + "prefix":"3.3.3.3\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.1", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.3", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.2.4", + "afi":"ipv4", + "interfaceName":"eth-rt4" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..a92272f6d0 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ipv6_route.ref @@ -0,0 +1,40 @@ +{ + "2001:db8:1000::1\/128":[ + { + "prefix":"2001:db8:1000::1\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::3\/128":[ + { + "prefix":"2001:db8:1000::3\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..c70b44e1c9 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,58 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-rt4", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0004", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-sw1", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0001", + "hold-timer": 9, + "neighbor-priority": 100, + "state": "up" + }, + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0003", + "hold-timer": 9, + "neighbor-priority": 64, + "state": "up" + }, + { + "neighbor-sys-type": "level-2", + "neighbor-sysid": "0000.0000.0003", + "hold-timer": 9, + "neighbor-priority": 64, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt2/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt2/zebra.conf new file mode 100644 index 0000000000..234e10efa9 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt2/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt2 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 2.2.2.2/32 + ipv6 address 2001:db8:1000::2/128 +! +interface eth-sw1 + ip address 10.0.1.2/24 +! +interface eth-rt4 + ip address 10.0.2.2/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt3/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt3/isisd.conf new file mode 100644 index 0000000000..9ad97109b5 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt3/isisd.conf @@ -0,0 +1,35 @@ +hostname rt3 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 1 + ipv6 router isis 1 + isis passive +! +interface eth-sw1 + ip router isis 1 + ipv6 router isis 1 + isis hello-multiplier 3 +! +interface eth-rt5 + ip router isis 2 + ipv6 router isis 2 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 1 + net 49.0000.0000.0000.0003.00 + lsp-gen-interval 2 + topology ipv6-unicast +! +router isis 2 + net 49.0002.0000.0000.0003.00 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ip_route.ref new file mode 100644 index 0000000000..55f0aedef5 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ip_route.ref @@ -0,0 +1,97 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.1", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2.2.2.2\/32":[ + { + "prefix":"2.2.2.2\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "5.5.5.5\/32":[ + { + "prefix":"5.5.5.5\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.4.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.1.1", + "afi":"ipv4", + "interfaceName":"eth-sw1" + }, + { + "ip":"10.0.1.2", + "afi":"ipv4", + "interfaceName":"eth-sw1" + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.4.5", + "afi":"ipv4", + "interfaceName":"eth-rt5" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..5d6dfca76a --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ipv6_route.ref @@ -0,0 +1,59 @@ +{ + "2001:db8:1000::1\/128":[ + { + "prefix":"2001:db8:1000::1\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::2\/128":[ + { + "prefix":"2001:db8:1000::2\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-sw1", + "active":true + } + ] + } + ], + "2001:db8:1000::5\/128":[ + { + "prefix":"2001:db8:1000::5\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..6950086b1e --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,51 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-rt5", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0005", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-sw1", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0001", + "hold-timer": 9, + "neighbor-priority": 100, + "state": "up" + }, + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0002", + "hold-timer": 9, + "neighbor-priority": 64, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt3/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt3/zebra.conf new file mode 100644 index 0000000000..9a0defd62b --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt3/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt3 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 3.3.3.3/32 + ipv6 address 2001:db8:1000::3/128 +! +interface eth-sw1 + ip address 10.0.1.3/24 +! +interface eth-rt5 + ip address 10.0.4.3/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt4/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt4/isisd.conf new file mode 100644 index 0000000000..e85412a71d --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt4/isisd.conf @@ -0,0 +1,42 @@ +hostname rt4 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 4 + ipv6 router isis 4 + isis passive +! +interface eth-rt2 + ip router isis 2 + ipv6 router isis 2 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt5 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt6 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 2 + net 49.0002.0000.0000.0004.00 + lsp-gen-interval 2 + topology ipv6-unicast +! +router isis 4 + net 49.0004.0000.0000.0004.00 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ip_route.ref new file mode 100644 index 0000000000..2cf5c40635 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ip_route.ref @@ -0,0 +1,94 @@ +{ + "6.6.6.6\/32":[ + { + "prefix":"6.6.6.6\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.6", + "afi":"ipv4", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.2.2", + "afi":"ipv4", + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.6.5", + "afi":"ipv4", + "interfaceName":"eth-rt5" + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.7.6", + "afi":"ipv4", + "interfaceName":"eth-rt6" + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.6.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + }, + { + "fib":true, + "ip":"10.0.7.6", + "afi":"ipv4", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..cde7287943 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ipv6_route.ref @@ -0,0 +1,21 @@ +{ + "2001:db8:1000::6\/128":[ + { + "prefix":"2001:db8:1000::6\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..233180ceb8 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,63 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-rt2", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0002", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-rt5", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0005", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-rt6", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0006", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt4/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt4/zebra.conf new file mode 100644 index 0000000000..adcf433249 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt4/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +! +hostname rt4 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 4.4.4.4/32 + ipv6 address 2001:db8:1000::4/128 +! +interface eth-rt2 + ip address 10.0.2.4/24 +! +interface eth-rt5 + ip address 10.0.6.4/24 +! +interface eth-rt6 + ip address 10.0.7.4/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt5/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt5/isisd.conf new file mode 100644 index 0000000000..2cab0c88fc --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt5/isisd.conf @@ -0,0 +1,42 @@ +hostname rt5 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 2 + ipv6 router isis 2 + isis passive +! +interface eth-rt3 + ip router isis 2 + ipv6 router isis 2 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt4 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt6 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 2 + net 49.0002.0000.0000.0005.00 + lsp-gen-interval 2 + topology ipv6-unicast +! +router isis 4 + net 49.0004.0000.0000.0005.00 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ip_route.ref new file mode 100644 index 0000000000..fe34b03890 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ip_route.ref @@ -0,0 +1,118 @@ +{ + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.6.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "6.6.6.6\/32":[ + { + "prefix":"6.6.6.6\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.8.6", + "afi":"ipv4", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.4.3", + "afi":"ipv4", + "interfaceIndex":2, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.6.4", + "afi":"ipv4", + "interfaceIndex":3, + "interfaceName":"eth-rt4" + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "table":254, + "nexthops":[ + { + "fib":true, + "ip":"10.0.6.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.6", + "afi":"ipv4", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.8.6", + "afi":"ipv4", + "interfaceName":"eth-rt6", + "weight":1 + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..7586c73852 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ipv6_route.ref @@ -0,0 +1,40 @@ +{ + "2001:db8:1000::4\/128":[ + { + "prefix":"2001:db8:1000::4\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "2001:db8:1000::6\/128":[ + { + "prefix":"2001:db8:1000::6\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt6", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..f939a6abff --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,63 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-rt3", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0003", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-rt4", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0004", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-rt6", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1", + "neighbor-sysid": "0000.0000.0006", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt5/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt5/zebra.conf new file mode 100644 index 0000000000..0f10ce921f --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt5/zebra.conf @@ -0,0 +1,25 @@ +log file zebra.log +! +hostname rt5 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 5.5.5.5/32 + ipv6 address 2001:db8:1000::5/128 +! +interface eth-rt3 + ip address 10.0.4.5/24 +! +interface eth-rt4 + ip address 10.0.6.5/24 +! +interface eth-rt6 + ip address 10.0.8.5/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/isisd.conf b/tests/topotests/isis-lsp-bits-topo1/rt6/isisd.conf new file mode 100644 index 0000000000..249f945e0c --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/isisd.conf @@ -0,0 +1,32 @@ +hostname rt6 +log file isisd.log +! +debug isis events +debug isis route-events +debug isis spf-events +debug isis sr-events +debug isis lsp-gen +! +interface lo + ip router isis 4 + ipv6 router isis 4 + isis passive +! +interface eth-rt4 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +interface eth-rt5 + ip router isis 4 + ipv6 router isis 4 + isis network point-to-point + isis hello-multiplier 3 +! +router isis 4 + net 49.0004.0000.0000.0006.00 + is-type level-1 + lsp-gen-interval 2 + topology ipv6-unicast +! diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ip_route.ref new file mode 100644 index 0000000000..2840514e6e --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ip_route.ref @@ -0,0 +1,107 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4" + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceIndex":3, + "interfaceName":"eth-rt5" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ipv6_route.ref new file mode 100644 index 0000000000..278129f481 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ipv6_route.ref @@ -0,0 +1,46 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt5", + "active":true + }, + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "2001:db8:1000::4\/128":[ + { + "prefix":"2001:db8:1000::4\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_yang_interface_isis_adjacencies.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_yang_interface_isis_adjacencies.ref new file mode 100644 index 0000000000..b4e8c23189 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_yang_interface_isis_adjacencies.ref @@ -0,0 +1,44 @@ +{ + "frr-interface:lib": { + "interface": [ + { + "name": "eth-rt4", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0004", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + }, + { + "name": "eth-rt5", + "vrf": "default", + "state": { + "frr-isisd:isis": { + "adjacencies": { + "adjacency": [ + { + "neighbor-sys-type": "level-1-2", + "neighbor-sysid": "0000.0000.0005", + "hold-timer": 9, + "neighbor-priority": 0, + "state": "up" + } + ] + } + } + } + } + ] + } +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ip_route.ref new file mode 100644 index 0000000000..61efcb9e98 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ip_route.ref @@ -0,0 +1,100 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4" + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceIndex":3, + "interfaceName":"eth-rt5" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ipv6_route.ref new file mode 100644 index 0000000000..2e982e0c37 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ipv6_route.ref @@ -0,0 +1,40 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "2001:db8:1000::4\/128":[ + { + "prefix":"2001:db8:1000::4\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ip_route.ref new file mode 100644 index 0000000000..8fecf14687 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ip_route.ref @@ -0,0 +1,80 @@ +{ + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4" + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceIndex":3, + "interfaceName":"eth-rt5" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ipv6_route.ref new file mode 100644 index 0000000000..9b53a1d760 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ipv6_route.ref @@ -0,0 +1,21 @@ +{ + "2001:db8:1000::4\/128":[ + { + "prefix":"2001:db8:1000::4\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ip_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ip_route.ref new file mode 100644 index 0000000000..2840514e6e --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ip_route.ref @@ -0,0 +1,107 @@ +{ + "0.0.0.0\/0":[ + { + "prefix":"0.0.0.0\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "4.4.4.4\/32":[ + { + "prefix":"4.4.4.4\/32", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "10.0.6.0\/24":[ + { + "prefix":"10.0.6.0\/24", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4", + "active":true + }, + { + "fib":true, + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceName":"eth-rt5", + "active":true + } + ] + } + ], + "10.0.7.0\/24":[ + { + "prefix":"10.0.7.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.7.4", + "afi":"ipv4", + "interfaceName":"eth-rt4" + } + ] + } + ], + "10.0.8.0\/24":[ + { + "prefix":"10.0.8.0\/24", + "protocol":"isis", + "distance":115, + "metric":20, + "nexthops":[ + { + "ip":"10.0.8.5", + "afi":"ipv4", + "interfaceIndex":3, + "interfaceName":"eth-rt5" + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ipv6_route.ref b/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ipv6_route.ref new file mode 100644 index 0000000000..278129f481 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ipv6_route.ref @@ -0,0 +1,46 @@ +{ + "::\/0":[ + { + "prefix":"::\/0", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":10, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt5", + "active":true + }, + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ], + "2001:db8:1000::4\/128":[ + { + "prefix":"2001:db8:1000::4\/128", + "protocol":"isis", + "selected":true, + "destSelected":true, + "distance":115, + "metric":20, + "installed":true, + "nexthops":[ + { + "fib":true, + "afi":"ipv6", + "interfaceName":"eth-rt4", + "active":true + } + ] + } + ] +} diff --git a/tests/topotests/isis-lsp-bits-topo1/rt6/zebra.conf b/tests/topotests/isis-lsp-bits-topo1/rt6/zebra.conf new file mode 100644 index 0000000000..6084010a93 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/rt6/zebra.conf @@ -0,0 +1,22 @@ +log file zebra.log +! +hostname rt6 +! +debug zebra kernel +debug zebra packet +debug zebra mpls +! +interface lo + ip address 6.6.6.6/32 + ipv6 address 2001:db8:1000::6/128 +! +interface eth-rt4 + ip address 10.0.7.6/24 +! +interface eth-rt5 + ip address 10.0.8.6/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/isis-lsp-bits-topo1/test_isis_lsp_bits_topo1.py b/tests/topotests/isis-lsp-bits-topo1/test_isis_lsp_bits_topo1.py new file mode 100755 index 0000000000..95a0d87c33 --- /dev/null +++ b/tests/topotests/isis-lsp-bits-topo1/test_isis_lsp_bits_topo1.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python + +# +# test_isis_lsp_bits_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2021 by Volta Networks +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_isis_lsp_bits_topo1.py: + + +---------+ + | | + | RT1 | + | 1.1.1.1 | + | L1 | + +---------+ + |eth-sw1 + | + | + | + +---------+ | +---------+ + | | | | | + | RT2 |eth-sw1 | eth-sw1| RT3 | + | 2.2.2.2 +----------+----------+ 3.3.3.3 | + | L1|L2 | 10.0.1.0/24 | L1|L2 | + +---------+ +---------+ + eth-rt4| eth-rt5| + | | + 10.0.2.0/24| |10.0.4.0/24 + | | + eth-rt2| eth-rt3| + +---------+ +---------+ + | | | | + | RT4 | 10.0.6.0/24 | RT5 | + | 4.4.4.4 +---------------------+ 5.5.5.5 | + | L1|L2 |eth-rt5 eth-rt4| L1|L2 | + +---------+ +---------+ + eth-rt6| |eth-rt6 + | | + 10.0.7.0/24| |10.0.8.0/24 + | +---------+ | + | | | | + | | RT6 | | + +----------+ 6.6.6.6 +-----------+ + eth-rt4| L1 |eth-rt5 + +---------+ +""" + +import os +import sys +import pytest +import json +import re +import tempfile +from time import sleep +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, '../')) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + +# Global multi-dimensional dictionary containing all expected outputs +outputs = {} + +class TemplateTopo(Topo): + "Test topology builder" + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # + # Define FRR Routers + # + for router in ['rt1', 'rt2', 'rt3', 'rt4', 'rt5', 'rt6']: + tgen.add_router(router) + + # + # Define connections + # + switch = tgen.add_switch('s1') + switch.add_link(tgen.gears['rt1'], nodeif="eth-sw1") + switch.add_link(tgen.gears['rt2'], nodeif="eth-sw1") + switch.add_link(tgen.gears['rt3'], nodeif="eth-sw1") + + switch = tgen.add_switch('s2') + switch.add_link(tgen.gears['rt2'], nodeif="eth-rt4") + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt2") + + switch = tgen.add_switch('s4') + switch.add_link(tgen.gears['rt3'], nodeif="eth-rt5") + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt3") + + switch = tgen.add_switch('s6') + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt5") + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt4") + + switch = tgen.add_switch('s7') + switch.add_link(tgen.gears['rt4'], nodeif="eth-rt6") + switch.add_link(tgen.gears['rt6'], nodeif="eth-rt4") + + switch = tgen.add_switch('s8') + switch.add_link(tgen.gears['rt5'], nodeif="eth-rt6") + switch.add_link(tgen.gears['rt6'], nodeif="eth-rt5") + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(TemplateTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, + os.path.join(CWD, '{}/zebra.conf'.format(rname)) + ) + router.load_config( + TopoRouter.RD_ISIS, + os.path.join(CWD, '{}/isisd.conf'.format(rname)) + ) + + tgen.start_router() + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + +def router_compare_json_output(rname, command, reference): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + +# +# Step 1 +# +# Test initial network convergence +# Attach-bit defaults to on, so expect default route pointing to L1|L2 router +# +def test_isis_adjacencies_step1(): + logger.info("Test (step 1): check IS-IS adjacencies") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt2', 'rt3', 'rt4', 'rt5', 'rt6']: + router_compare_json_output( + rname, + "show yang operational-data /frr-interface:lib isisd", + "step1/show_yang_interface_isis_adjacencies.ref", + ) + +def test_rib_ipv4_step1(): + logger.info("Test (step 1): verify IPv4 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt2', 'rt3', 'rt4', 'rt5', 'rt6']: + router_compare_json_output( + rname, "show ip route isis json", "step1/show_ip_route.ref" + ) + +def test_rib_ipv6_step1(): + logger.info("Test (step 1): verify IPv6 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt2', 'rt3', 'rt4', 'rt5', 'rt6']: + router_compare_json_output( + rname, "show ipv6 route isis json", "step1/show_ipv6_route.ref" + ) + +# +# Step 2 +# +# Action(s): +# -Disable sending Attach bit on RT2 and RT4 +# +# Expected changes: +# -RT1 should remove the default route pointing to RT2 +# -RT6 should remove the default route pointing to RT4 +# +def test_rib_ipv4_step2(): + logger.info("Test (step 2): verify IPv4 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info('Disabling setting the attached-bit on RT2 and RT4') + tgen.net['rt2'].cmd('vtysh -c "conf t" -c "router isis 1" -c "no attached-bit send"') + tgen.net['rt4'].cmd('vtysh -c "conf t" -c "router isis 1" -c "no attached-bit send"') + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ip route isis json", "step2/show_ip_route.ref" + ) + +def test_rib_ipv6_step2(): + logger.info("Test (step 2): verify IPv6 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ipv6 route isis json", "step2/show_ipv6_route.ref" + ) + +# +# Step 3 +# +# Action(s): +# -restore attach-bit, enable sending attach-bit +# -disble processing a LSP with attach bit set +# +# Expected changes: +# -RT1 and RT6 should not install a default route +# +def test_rib_ipv4_step3(): + logger.info("Test (step 3): verify IPv4 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info('Enable setting the attached-bit on RT2 and RT4') + tgen.net['rt2'].cmd('vtysh -c "conf t" -c "router isis 1" -c "attached-bit send"') + tgen.net['rt4'].cmd('vtysh -c "conf t" -c "router isis 1" -c "attached-bit send"') + + logger.info('Disable processing received attached-bit in LSP on RT1 and RT6') + tgen.net['rt1'].cmd('vtysh -c "conf t" -c "router isis 1" -c "attached-bit receive ignore"') + tgen.net['rt6'].cmd('vtysh -c "conf t" -c "router isis 1" -c "attached-bit receive ignore"') + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ip route isis json", "step3/show_ip_route.ref" + ) + +def test_rib_ipv6_step3(): + logger.info("Test (step 3): verify IPv6 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ipv6 route isis json", "step3/show_ipv6_route.ref" + ) + +# +# Step 4 +# +# Action(s): +# -restore back to default attach-bit config +# +# Expected changes: +# -RT1 and RT6 should add default route +# -no changes on other routers +# +def test_rib_ipv4_step4(): + logger.info("Test (step 4): verify IPv4 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + logger.info('restore default processing on received attached-bit in LSP on RT1 and RT6') + tgen.net['rt1'].cmd('vtysh -c "conf t" -c "router isis 1" -c "no attached-bit receive ignore"') + tgen.net['rt6'].cmd('vtysh -c "conf t" -c "router isis 1" -c "no attached-bit receive ignore"') + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ip route isis json", "step4/show_ip_route.ref") + +def test_rib_ipv6_step4(): + logger.info("Test (step 4): verify IPv6 RIB") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for rname in ['rt1', 'rt6']: + router_compare_json_output( + rname, "show ipv6 route isis json", "step4/show_ipv6_route.ref") + +# Memory leak test template +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip('Memory leak test/report is disabled') + + tgen.report_memory_leaks() + +if __name__ == '__main__': + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/topotest.py b/tests/topotests/lib/topotest.py index 3ab5663066..1e6ef1b2b3 100644 --- a/tests/topotests/lib/topotest.py +++ b/tests/topotests/lib/topotest.py @@ -1625,6 +1625,8 @@ class Router(Node): return "%s: vtysh killed by AddressSanitizer" % (self.name) for daemon in self.daemons: + if daemon == "snmpd": + continue if (self.daemons[daemon] == 1) and not (daemon in daemonsRunning): sys.stderr.write("%s: Daemon %s not running\n" % (self.name, daemon)) if daemon == "staticd": diff --git a/tests/topotests/ospf-tilfa-topo1/__init__.py b/tests/topotests/ospf-tilfa-topo1/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/__init__.py diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/ospfd.conf b/tests/topotests/ospf-tilfa-topo1/rt1/ospfd.conf new file mode 100644 index 0000000000..eaef49225f --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/ospfd.conf @@ -0,0 +1,27 @@ +debug ospf sr +debug ospf ti-lfa +! +interface lo +! +interface eth-rt2 + ip ospf network point-to-point +! +interface eth-rt3 + ip ospf network point-to-point +! +router ospf + ospf router-id 1.1.1.1 + network 1.1.1.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 1.1.1.0/24 + capability opaque + mpls-te on + mpls-te router-address 1.1.1.1 + router-info area 0.0.0.0 + passive-interface lo + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.1/32 index 10 +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/step1/show_ip_route_initial.ref b/tests/topotests/ospf-tilfa-topo1/rt1/step1/show_ip_route_initial.ref new file mode 100644 index 0000000000..0ad2aaeade --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/step1/show_ip_route_initial.ref @@ -0,0 +1,156 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + }, + { + "prefix":"1.1.1.1\/32", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + } + ], + "1.1.1.2\/32":[ + { + "prefix":"1.1.1.2\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.3\/32":[ + { + "prefix":"1.1.1.3\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "1.1.1.4\/32":[ + { + "prefix":"1.1.1.4\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.5\/32":[ + { + "prefix":"1.1.1.5\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + }, + { + "prefix":"10.0.1.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + }, + { + "prefix":"10.0.2.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.3.0\/24":[ + { + "prefix":"10.0.3.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.5.0\/24":[ + { + "prefix":"10.0.5.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + }, + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ] +} diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_initial.ref b/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_initial.ref new file mode 100644 index 0000000000..0ad2aaeade --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_initial.ref @@ -0,0 +1,156 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + }, + { + "prefix":"1.1.1.1\/32", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + } + ], + "1.1.1.2\/32":[ + { + "prefix":"1.1.1.2\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.3\/32":[ + { + "prefix":"1.1.1.3\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "1.1.1.4\/32":[ + { + "prefix":"1.1.1.4\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.5\/32":[ + { + "prefix":"1.1.1.5\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + }, + { + "prefix":"10.0.1.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + }, + { + "prefix":"10.0.2.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.3.0\/24":[ + { + "prefix":"10.0.3.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.5.0\/24":[ + { + "prefix":"10.0.5.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + }, + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ] +} diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_link_protection.ref b/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_link_protection.ref new file mode 100644 index 0000000000..968570e193 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_link_protection.ref @@ -0,0 +1,226 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + }, + { + "prefix":"1.1.1.1\/32", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + } + ], + "1.1.1.2\/32":[ + { + "prefix":"1.1.1.2\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ], + "backupNexthops":[ + { + "ip":"10.0.2.2", + "labels":[ + 16050 + ] + } + ] + } + ], + "1.1.1.3\/32":[ + { + "prefix":"1.1.1.3\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ], + "backupNexthops":[ + { + "ip":"10.0.1.2", + "labels":[ + 16040 + ] + } + ] + } + ], + "1.1.1.4\/32":[ + { + "prefix":"1.1.1.4\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2", + "labels":[ + 16040 + ] + } + ], + "backupNexthops":[ + { + "ip":"10.0.2.2", + "labels":[ + 16050, + 16040 + ] + } + ] + } + ], + "1.1.1.5\/32":[ + { + "prefix":"1.1.1.5\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3", + "labels":[ + 16050 + ] + } + ], + "backupNexthops":[ + { + "ip":"10.0.1.2", + "labels":[ + 16040, + 16050 + ] + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + }, + { + "prefix":"10.0.1.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + }, + { + "prefix":"10.0.2.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.3.0\/24":[ + { + "prefix":"10.0.3.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ], + "backupNexthops":[ + { + "ip":"10.0.2.2", + "labels":[ + 16050 + ] + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ], + "backupNexthops":[ + { + "ip":"10.0.1.2", + "labels":[ + 16040 + ] + } + ] + } + ], + "10.0.5.0\/24":[ + { + "prefix":"10.0.5.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + }, + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ], + "backupNexthops":[ + { + "ip":"10.0.1.2", + "labels":[ + 16040 + ] + }, + { + "ip":"10.0.2.2", + "labels":[ + 16050 + ] + } + ] + } + ] +} diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_initial.ref b/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_initial.ref new file mode 100644 index 0000000000..0ad2aaeade --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_initial.ref @@ -0,0 +1,156 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + }, + { + "prefix":"1.1.1.1\/32", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + } + ], + "1.1.1.2\/32":[ + { + "prefix":"1.1.1.2\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.3\/32":[ + { + "prefix":"1.1.1.3\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "1.1.1.4\/32":[ + { + "prefix":"1.1.1.4\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.5\/32":[ + { + "prefix":"1.1.1.5\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + }, + { + "prefix":"10.0.1.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + }, + { + "prefix":"10.0.2.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.3.0\/24":[ + { + "prefix":"10.0.3.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.5.0\/24":[ + { + "prefix":"10.0.5.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + }, + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ] +} diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_node_protection.ref b/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_node_protection.ref new file mode 100644 index 0000000000..46a80d298e --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_node_protection.ref @@ -0,0 +1,192 @@ +{ + "1.1.1.1\/32":[ + { + "prefix":"1.1.1.1\/32", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + }, + { + "prefix":"1.1.1.1\/32", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"lo" + } + ] + } + ], + "1.1.1.2\/32":[ + { + "prefix":"1.1.1.2\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "1.1.1.3\/32":[ + { + "prefix":"1.1.1.3\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "1.1.1.4\/32":[ + { + "prefix":"1.1.1.4\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2", + "labels":[ + 16040 + ] + } + ], + "backupNexthops":[ + { + "ip":"10.0.2.2", + "labels":[ + 16050 + ] + } + ] + } + ], + "1.1.1.5\/32":[ + { + "prefix":"1.1.1.5\/32", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3", + "labels":[ + 16050 + ] + } + ], + "backupNexthops":[ + { + "ip":"10.0.1.2", + "labels":[ + 16040 + ] + } + ] + } + ], + "10.0.1.0\/24":[ + { + "prefix":"10.0.1.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + }, + { + "prefix":"10.0.1.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.2.0\/24":[ + { + "prefix":"10.0.2.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + }, + { + "prefix":"10.0.2.0\/24", + "protocol":"connected", + "nexthops":[ + { + "directlyConnected":true, + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.3.0\/24":[ + { + "prefix":"10.0.3.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + } + ] + } + ], + "10.0.4.0\/24":[ + { + "prefix":"10.0.4.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ] + } + ], + "10.0.5.0\/24":[ + { + "prefix":"10.0.5.0\/24", + "protocol":"ospf", + "nexthops":[ + { + "ip":"10.0.1.2", + "interfaceName":"eth-rt2" + }, + { + "ip":"10.0.2.2", + "interfaceName":"eth-rt3" + } + ], + "backupNexthops":[ + { + "ip":"10.0.2.2", + "labels":[ + 16050 + ] + }, + { + "ip":"10.0.1.2", + "labels":[ + 16040 + ] + } + ] + } + ] +} diff --git a/tests/topotests/ospf-tilfa-topo1/rt1/zebra.conf b/tests/topotests/ospf-tilfa-topo1/rt1/zebra.conf new file mode 100644 index 0000000000..bf0e77a17b --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt1/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname rt1 +! +interface lo + ip address 1.1.1.1/32 +! +interface eth-rt2 + ip address 10.0.1.1/24 +! +interface eth-rt3 + ip address 10.0.2.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt2/ospfd.conf b/tests/topotests/ospf-tilfa-topo1/rt2/ospfd.conf new file mode 100644 index 0000000000..7548aad7f8 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt2/ospfd.conf @@ -0,0 +1,27 @@ +debug ospf sr +debug ospf ti-lfa +! +interface lo +! +interface eth-rt1 + ip ospf network point-to-point +! +interface eth-rt4 + ip ospf network point-to-point +! +router ospf + ospf router-id 1.1.1.2 + network 1.1.1.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 1.1.1.0/24 + capability opaque + mpls-te on + mpls-te router-address 1.1.1.2 + router-info area 0.0.0.0 + passive-interface lo + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.2/32 index 20 +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt2/zebra.conf b/tests/topotests/ospf-tilfa-topo1/rt2/zebra.conf new file mode 100644 index 0000000000..add2933571 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt2/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname rt2 +! +interface lo + ip address 1.1.1.2/32 +! +interface eth-rt1 + ip address 10.0.1.2/24 +! +interface eth-rt4 + ip address 10.0.3.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt3/ospfd.conf b/tests/topotests/ospf-tilfa-topo1/rt3/ospfd.conf new file mode 100644 index 0000000000..6258295b6f --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt3/ospfd.conf @@ -0,0 +1,27 @@ +debug ospf sr +debug ospf ti-lfa +! +interface lo +! +interface eth-rt1 + ip ospf network point-to-point +! +interface eth-rt5 + ip ospf network point-to-point +! +router ospf + ospf router-id 1.1.1.3 + network 1.1.1.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 1.1.1.0/24 + capability opaque + mpls-te on + mpls-te router-address 1.1.1.3 + router-info area 0.0.0.0 + passive-interface lo + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.3/32 index 30 +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt3/zebra.conf b/tests/topotests/ospf-tilfa-topo1/rt3/zebra.conf new file mode 100644 index 0000000000..1bb64bc585 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt3/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname rt3 +! +interface lo + ip address 1.1.1.3/32 +! +interface eth-rt1 + ip address 10.0.2.2/24 +! +interface eth-rt5 + ip address 10.0.4.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt4/ospfd.conf b/tests/topotests/ospf-tilfa-topo1/rt4/ospfd.conf new file mode 100644 index 0000000000..ad02214017 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt4/ospfd.conf @@ -0,0 +1,27 @@ +debug ospf sr +debug ospf ti-lfa +! +interface lo +! +interface eth-rt2 + ip ospf network point-to-point +! +interface eth-rt5 + ip ospf network point-to-point +! +router ospf + ospf router-id 1.1.1.4 + network 1.1.1.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 1.1.1.0/24 + capability opaque + mpls-te on + mpls-te router-address 1.1.1.4 + router-info area 0.0.0.0 + passive-interface lo + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.4/32 index 40 +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt4/zebra.conf b/tests/topotests/ospf-tilfa-topo1/rt4/zebra.conf new file mode 100644 index 0000000000..306f0d4925 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt4/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname rt4 +! +interface lo + ip address 1.1.1.4/32 +! +interface eth-rt2 + ip address 10.0.3.2/24 +! +interface eth-rt5 + ip address 10.0.5.1/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt5/ospfd.conf b/tests/topotests/ospf-tilfa-topo1/rt5/ospfd.conf new file mode 100644 index 0000000000..1b95858f53 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt5/ospfd.conf @@ -0,0 +1,27 @@ +debug ospf sr +debug ospf ti-lfa +! +interface lo +! +interface eth-rt3 + ip ospf network point-to-point +! +interface eth-rt4 + ip ospf network point-to-point +! +router ospf + ospf router-id 1.1.1.5 + network 1.1.1.0/24 area 0.0.0.0 + network 10.0.0.0/16 area 0.0.0.0 + area 0.0.0.0 range 10.0.0.0/16 + area 0.0.0.0 range 1.1.1.0/24 + capability opaque + mpls-te on + mpls-te router-address 1.1.1.5 + router-info area 0.0.0.0 + passive-interface lo + segment-routing on + segment-routing global-block 16000 23999 + segment-routing node-msd 8 + segment-routing prefix 1.1.1.5/32 index 50 +! diff --git a/tests/topotests/ospf-tilfa-topo1/rt5/zebra.conf b/tests/topotests/ospf-tilfa-topo1/rt5/zebra.conf new file mode 100644 index 0000000000..46f759580e --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/rt5/zebra.conf @@ -0,0 +1,17 @@ +log file zebra.log +! +hostname rt5 +! +interface lo + ip address 1.1.1.5/32 +! +interface eth-rt3 + ip address 10.0.4.2/24 +! +interface eth-rt4 + ip address 10.0.5.2/24 +! +ip forwarding +! +line vty +! diff --git a/tests/topotests/ospf-tilfa-topo1/test_ospf_tilfa_topo1.py b/tests/topotests/ospf-tilfa-topo1/test_ospf_tilfa_topo1.py new file mode 100644 index 0000000000..eb3ad5d995 --- /dev/null +++ b/tests/topotests/ospf-tilfa-topo1/test_ospf_tilfa_topo1.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python + +# +# test_ospf_tilfa_topo1.py +# Part of NetDEF Topology Tests +# +# Copyright (c) 2020 by +# Network Device Education Foundation, Inc. ("NetDEF") +# +# Permission to use, copy, modify, and/or distribute this software +# for any purpose with or without fee is hereby granted, provided +# that the above copyright notice and this permission notice appear +# in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY +# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS +# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE +# OF THIS SOFTWARE. +# + +""" +test_ospf_tilfa_topo1.py: + +This topology is intentionally kept simple, its main purpose is to verify that +generated backup label stacks are inserted correctly into the RIB. For fancy +topologies please use the unit test framework provided in `/tests/ospfd`. + + + +---------+ +---------+ + | | | | + 10.0.1.0/24 eth+rt1| RT2 |eth+rt4 eth+rt2| RT2 | + +---------------------+ 2.2.2.2 +---------------------+ 4.4.4.4 | + | | | 10.0.3.0/24 | | + |eth+rt2 +---------+ +---------+ + +---------+ eth+rt5| + | | | + | RT1 | 10.0.5.0/24| + | 1.1.1.1 | | + | | | + +---------+ eth+rt4| + |eth+rt3 +---------+ +---------+ + | | | 10.0.4.0/24 | | + +---------------------+ RT3 +---------------------+ RT5 | + 10.0.2.0/24 eth+rt1| 3.3.3.3 |eth+rt5 eth-rt3| 5.5.5.5 | + | | | | + +---------+ +---------+ +""" + +import os +import sys +import pytest +import json +import re +from time import sleep +from functools import partial + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.topolog import logger + +# Required to instantiate the topology builder class. +from mininet.topo import Topo + + +class TemplateTopo(Topo): + "Test topology builder" + + def build(self, *_args, **_opts): + "Build function" + tgen = get_topogen(self) + + # + # Define FRR Routers + # + for router in ["rt1", "rt2", "rt3", "rt4", "rt5"]: + tgen.add_router(router) + + # + # Define connections + # + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["rt1"], nodeif="eth-rt2") + switch.add_link(tgen.gears["rt2"], nodeif="eth-rt1") + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["rt1"], nodeif="eth-rt3") + switch.add_link(tgen.gears["rt3"], nodeif="eth-rt1") + + switch = tgen.add_switch("s3") + switch.add_link(tgen.gears["rt2"], nodeif="eth-rt4") + switch.add_link(tgen.gears["rt4"], nodeif="eth-rt2") + + switch = tgen.add_switch("s4") + switch.add_link(tgen.gears["rt3"], nodeif="eth-rt5") + switch.add_link(tgen.gears["rt5"], nodeif="eth-rt3") + + switch = tgen.add_switch("s5") + switch.add_link(tgen.gears["rt4"], nodeif="eth-rt5") + switch.add_link(tgen.gears["rt5"], nodeif="eth-rt4") + + +def setup_module(mod): + "Sets up the pytest environment" + tgen = Topogen(TemplateTopo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + # For all registered routers, load the zebra configuration file + for rname, router in router_list.items(): + router.load_config( + TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname)) + ) + router.load_config( + TopoRouter.RD_OSPF, os.path.join(CWD, "{}/ospfd.conf".format(rname)) + ) + + tgen.start_router() + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + + # This function tears down the whole topology. + tgen.stop_topology() + + +def router_compare_json_output(rname, command, reference): + "Compare router JSON output" + + logger.info('Comparing router "%s" "%s" output', rname, command) + + tgen = get_topogen() + filename = "{}/{}/{}".format(CWD, rname, reference) + expected = json.loads(open(filename).read()) + + # Run test function until we get an result. Wait at most 60 seconds. + test_func = partial(topotest.router_json_cmp, tgen.gears[rname], command, expected) + _, diff = topotest.run_and_expect(test_func, None, count=120, wait=0.5) + assertmsg = '"{}" JSON output mismatches the expected result'.format(rname) + assert diff is None, assertmsg + + +def test_ospf_initial_convergence_step1(): + logger.info("Test (step 1): check initial convergence") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + router_compare_json_output( + "rt1", + "show ip route json", + "step1/show_ip_route_initial.ref", + ) + +def test_ospf_link_protection_step2(): + logger.info("Test (step 2): check OSPF link protection") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # enable TI-LFA link protection on all interfaces + tgen.net["rt1"].cmd( + 'vtysh -c "conf t" -c "router ospf" -c "fast-reroute ti-lfa"' + ) + + router_compare_json_output( + "rt1", + "show ip route json", + "step2/show_ip_route_link_protection.ref", + ) + + # disable TI-LFA link protection on all interfaces + tgen.net["rt1"].cmd( + 'vtysh -c "conf t" -c "router ospf" -c "no fast-reroute ti-lfa"' + ) + + # check if we got back to the initial route table + router_compare_json_output( + "rt1", + "show ip route json", + "step2/show_ip_route_initial.ref", + ) + +def test_ospf_node_protection_step3(): + logger.info("Test (step 3): check OSPF node protection") + tgen = get_topogen() + + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # enable TI-LFA node protection on all interfaces + tgen.net["rt1"].cmd( + 'vtysh -c "conf t" -c "router ospf" -c "fast-reroute ti-lfa node-protection"' + ) + + router_compare_json_output( + "rt1", + "show ip route json", + "step3/show_ip_route_node_protection.ref", + ) + + # disable TI-LFA node protection on all interfaces + tgen.net["rt1"].cmd( + 'vtysh -c "conf t" -c "router ospf" -c "no fast-reroute ti-lfa node-protection"' + ) + + # check if we got back to the initial route table + router_compare_json_output( + "rt1", + "show ip route json", + "step3/show_ip_route_initial.ref", + ) + +# Memory leak test template +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/simple-snmp-test/test_simple_snmp.py b/tests/topotests/simple-snmp-test/test_simple_snmp.py index 1e56252ea3..88ff01bf0a 100755 --- a/tests/topotests/simple-snmp-test/test_simple_snmp.py +++ b/tests/topotests/simple-snmp-test/test_simple_snmp.py @@ -125,6 +125,10 @@ def test_r1_bgp_version(): "Wait for protocol convergence" tgen = get_topogen() + # Skip if previous fatal error condition is raised + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + # tgen.mininet_cli() r1 = tgen.net.get("r1") r1_snmp = SnmpTester(r1, "1.1.1.1", "public", "2c") @@ -134,6 +138,15 @@ def test_r1_bgp_version(): assert r1_snmp.test_oid_walk("bgpVersion", ["10"], ["0"]) +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/yang/frr-isisd.yang b/yang/frr-isisd.yang index 812dd4159d..8757ab6b8b 100644 --- a/yang/frr-isisd.yang +++ b/yang/frr-isisd.yang @@ -1043,9 +1043,24 @@ module frr-isisd { "Dynamic hostname support for IS-IS."; } + leaf attach-send { + type boolean; + default "true"; + description + "If true, attached bits are sent in LSP if L1/L2 router for inter-area traffic."; + } + + leaf attach-receive-ignore { + type boolean; + default "false"; + description + "If false, attached bits received in LSP, cause default route add, if L1 router for inter-area traffic."; + } + leaf attached { type boolean; default "false"; + status deprecated; description "If true, identify as L1/L2 router for inter-area traffic."; } diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 90c6a24e7b..8e68984823 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -62,6 +62,8 @@ #include "zebra/zebra_opaque.h" #include "zebra/zebra_srte.h" +static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg); + /* Encoding helpers -------------------------------------------------------- */ static void zserv_encode_interface(struct stream *s, struct interface *ifp) @@ -707,13 +709,13 @@ static int zsend_ipv4_nexthop_lookup_mrib(struct zserv *client, return zserv_send_message(client, s); } -static int nhg_notify(uint16_t type, uint16_t instance, uint32_t id, - enum zapi_nhg_notify_owner note) +int zsend_nhg_notify(uint16_t type, uint16_t instance, uint32_t session_id, + uint32_t id, enum zapi_nhg_notify_owner note) { struct zserv *client; struct stream *s; - client = zserv_find_client(type, instance); + client = zserv_find_client_session(type, instance, session_id); if (!client) { if (IS_ZEBRA_DEBUG_PACKET) { zlog_debug("Not Notifying Owner: %u(%u) about %u(%d)", @@ -722,6 +724,10 @@ static int nhg_notify(uint16_t type, uint16_t instance, uint32_t id, return 0; } + if (IS_ZEBRA_DEBUG_SEND) + zlog_debug("%s: type %d, id %d, note %d", + __func__, type, id, note); + s = stream_new(ZEBRA_MAX_PACKET_SIZ); stream_reset(s); @@ -1742,7 +1748,7 @@ static bool zapi_read_nexthops(struct zserv *client, struct prefix *p, return true; } -int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) +static int zapi_nhg_decode(struct stream *s, int cmd, struct zapi_nhg *api_nhg) { uint16_t i; struct zapi_nexthop *znh; @@ -1820,16 +1826,17 @@ static void zread_nhg_del(ZAPI_HANDLER_ARGS) /* * Delete the received nhg id */ - nhe = zebra_nhg_proto_del(api_nhg.id, api_nhg.proto); if (nhe) { zebra_nhg_decrement_ref(nhe); - nhg_notify(api_nhg.proto, client->instance, api_nhg.id, - ZAPI_NHG_REMOVED); + zsend_nhg_notify(api_nhg.proto, client->instance, + client->session_id, api_nhg.id, + ZAPI_NHG_REMOVED); } else - nhg_notify(api_nhg.proto, client->instance, api_nhg.id, - ZAPI_NHG_REMOVE_FAIL); + zsend_nhg_notify(api_nhg.proto, client->instance, + client->session_id, api_nhg.id, + ZAPI_NHG_REMOVE_FAIL); } static void zread_nhg_add(ZAPI_HANDLER_ARGS) @@ -1863,7 +1870,8 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) /* * Create the nhg */ - nhe = zebra_nhg_proto_add(api_nhg.id, api_nhg.proto, nhg, 0); + nhe = zebra_nhg_proto_add(api_nhg.id, api_nhg.proto, client->instance, + client->session_id, nhg, 0); nexthop_group_delete(&nhg); zebra_nhg_backup_free(&bnhg); @@ -1874,12 +1882,12 @@ static void zread_nhg_add(ZAPI_HANDLER_ARGS) * * Resolution is going to need some more work. */ - if (nhe) - nhg_notify(api_nhg.proto, client->instance, api_nhg.id, - ZAPI_NHG_INSTALLED); - else - nhg_notify(api_nhg.proto, client->instance, api_nhg.id, - ZAPI_NHG_FAIL_INSTALL); + + /* If there's a failure, notify sender immediately */ + if (nhe == NULL) + zsend_nhg_notify(api_nhg.proto, client->instance, + client->session_id, api_nhg.id, + ZAPI_NHG_FAIL_INSTALL); } static void zread_route_add(ZAPI_HANDLER_ARGS) diff --git a/zebra/zapi_msg.h b/zebra/zapi_msg.h index 9822d72022..c03278669a 100644 --- a/zebra/zapi_msg.h +++ b/zebra/zapi_msg.h @@ -108,6 +108,9 @@ extern int zsend_sr_policy_notify_status(uint32_t color, extern int zsend_client_close_notify(struct zserv *client, struct zserv *closed_client); +int zsend_nhg_notify(uint16_t type, uint16_t instance, uint32_t session_id, + uint32_t id, enum zapi_nhg_notify_owner note); + #ifdef __cplusplus } #endif diff --git a/zebra/zebra_nhg.c b/zebra/zebra_nhg.c index 0a692feb35..604480b68b 100644 --- a/zebra/zebra_nhg.c +++ b/zebra/zebra_nhg.c @@ -43,6 +43,7 @@ #include "zebra_errors.h" #include "zebra_dplane.h" #include "zebra/interface.h" +#include "zebra/zapi_msg.h" DEFINE_MTYPE_STATIC(ZEBRA, NHG, "Nexthop Group Entry"); DEFINE_MTYPE_STATIC(ZEBRA, NHG_CONNECTED, "Nexthop Group Connected"); @@ -2105,10 +2106,10 @@ done_with_match: /* This function verifies reachability of one given nexthop, which can be * numbered or unnumbered, IPv4 or IPv6. The result is unconditionally stored * in nexthop->flags field. The nexthop->ifindex will be updated - * appropriately as well. An existing route map can turn - * (otherwise active) nexthop into inactive, but not vice versa. + * appropriately as well. An existing route map can turn an + * otherwise active nexthop into inactive, but not vice versa. * - * If it finds a nexthop recursivedly, set the resolved_id + * If it finds a nexthop recursively, set the resolved_id * to match that nexthop's nhg_hash_entry ID; * * The return value is the final value of 'ACTIVE' flag. @@ -2119,7 +2120,7 @@ static unsigned nexthop_active_check(struct route_node *rn, { struct interface *ifp; route_map_result_t ret = RMAP_PERMITMATCH; - int family; + afi_t family; const struct prefix *p, *src_p; struct zebra_vrf *zvrf; @@ -2131,6 +2132,7 @@ static unsigned nexthop_active_check(struct route_node *rn, family = AFI_IP6; else family = 0; + switch (nexthop->type) { case NEXTHOP_TYPE_IFINDEX: ifp = if_lookup_by_index(nexthop->ifindex, nexthop->vrf_id); @@ -2641,6 +2643,7 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx) EC_ZEBRA_DP_DELETE_FAIL, "Failed to uninstall Nexthop ID (%u) from the kernel", id); + /* We already free'd the data, nothing to do */ break; case DPLANE_OP_NH_INSTALL: @@ -2661,12 +2664,26 @@ void zebra_nhg_dplane_result(struct zebra_dplane_ctx *ctx) SET_FLAG(nhe->flags, NEXTHOP_GROUP_VALID); SET_FLAG(nhe->flags, NEXTHOP_GROUP_INSTALLED); zebra_nhg_handle_install(nhe); - } else + + /* If daemon nhg, send it an update */ + if (nhe->id >= ZEBRA_NHG_PROTO_LOWER) + zsend_nhg_notify(nhe->type, nhe->zapi_instance, + nhe->zapi_session, nhe->id, + ZAPI_NHG_INSTALLED); + } else { + /* If daemon nhg, send it an update */ + if (nhe->id >= ZEBRA_NHG_PROTO_LOWER) + zsend_nhg_notify(nhe->type, nhe->zapi_instance, + nhe->zapi_session, nhe->id, + ZAPI_NHG_FAIL_INSTALL); + flog_err( EC_ZEBRA_DP_INSTALL_FAIL, "Failed to install Nexthop ID (%u) into the kernel", nhe->id); + } break; + case DPLANE_OP_ROUTE_INSTALL: case DPLANE_OP_ROUTE_UPDATE: case DPLANE_OP_ROUTE_DELETE: @@ -2775,6 +2792,7 @@ bool zebra_nhg_proto_nexthops_only(void) /* Add NHE from upper level proto */ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, + uint16_t instance, uint32_t session, struct nexthop_group *nhg, afi_t afi) { struct nhg_hash_entry lookup; @@ -2858,6 +2876,10 @@ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, zebra_nhg_increment_ref(new); + /* Capture zapi client info */ + new->zapi_instance = instance; + new->zapi_session = session; + zebra_nhg_set_valid_if_active(new); zebra_nhg_install_kernel(new); diff --git a/zebra/zebra_nhg.h b/zebra/zebra_nhg.h index 9382b8c65d..db20f2beaf 100644 --- a/zebra/zebra_nhg.h +++ b/zebra/zebra_nhg.h @@ -50,8 +50,14 @@ struct nhg_hash_entry { uint32_t id; afi_t afi; vrf_id_t vrf_id; + + /* Source protocol - zebra or another daemon */ int type; + /* zapi instance and session id, for groups from other daemons */ + uint16_t zapi_instance; + uint32_t zapi_session; + struct nexthop_group nhg; /* If supported, a mapping of backup nexthops. */ @@ -292,6 +298,7 @@ zebra_nhg_rib_find_nhe(struct nhg_hash_entry *rt_nhe, afi_t rt_afi); * Returns allocated NHE on success, otherwise NULL. */ struct nhg_hash_entry *zebra_nhg_proto_add(uint32_t id, int type, + uint16_t instance, uint32_t session, struct nexthop_group *nhg, afi_t afi); diff --git a/zebra/zebra_routemap.c b/zebra/zebra_routemap.c index 229b705ec9..17a9bf97f9 100644 --- a/zebra/zebra_routemap.c +++ b/zebra/zebra_routemap.c @@ -1659,7 +1659,7 @@ void zebra_routemap_finish(void) } route_map_result_t -zebra_route_map_check(int family, int rib_type, uint8_t instance, +zebra_route_map_check(afi_t family, int rib_type, uint8_t instance, const struct prefix *p, struct nexthop *nexthop, struct zebra_vrf *zvrf, route_tag_t tag) { diff --git a/zebra/zebra_routemap.h b/zebra/zebra_routemap.h index 251e07af72..c016d95875 100644 --- a/zebra/zebra_routemap.h +++ b/zebra/zebra_routemap.h @@ -42,7 +42,7 @@ zebra_import_table_route_map_check(int family, int rib_type, uint8_t instance, struct nexthop *nexthop, vrf_id_t vrf_id, route_tag_t tag, const char *rmap_name); extern route_map_result_t -zebra_route_map_check(int family, int rib_type, uint8_t instance, +zebra_route_map_check(afi_t family, int rib_type, uint8_t instance, const struct prefix *p, struct nexthop *nexthop, struct zebra_vrf *zvrf, route_tag_t tag); extern route_map_result_t |
