summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_route.c19
-rw-r--r--doc/developer/ospf-sr.rst66
-rw-r--r--doc/user/fabricd.rst15
-rw-r--r--doc/user/isisd.rst13
-rw-r--r--doc/user/ospfd.rst14
-rw-r--r--docker/alpine/Dockerfile13
-rw-r--r--docker/centos-7/Dockerfile6
-rw-r--r--docker/centos-8/Dockerfile14
-rw-r--r--isisd/isis_cli.c53
-rw-r--r--isisd/isis_constants.h3
-rw-r--r--isisd/isis_lsp.c91
-rw-r--r--isisd/isis_nb.c15
-rw-r--r--isisd/isis_nb.h8
-rw-r--r--isisd/isis_nb_config.c32
-rw-r--r--isisd/isis_pdu.c4
-rw-r--r--isisd/isis_spf.c26
-rw-r--r--isisd/isisd.c24
-rw-r--r--isisd/isisd.h7
-rw-r--r--lib/frrlua.h11
-rw-r--r--lib/frrscript.c4
-rw-r--r--lib/zclient.c10
-rw-r--r--lib/zclient.h4
-rw-r--r--ospf6d/ospf6_asbr.c9
-rw-r--r--ospfd/ospf_dump.c30
-rw-r--r--ospfd/ospf_dump.h4
-rw-r--r--ospfd/ospf_lsa.c9
-rw-r--r--ospfd/ospf_lsa.h6
-rw-r--r--ospfd/ospf_memory.c2
-rw-r--r--ospfd/ospf_memory.h2
-rw-r--r--ospfd/ospf_route.c83
-rw-r--r--ospfd/ospf_route.h5
-rw-r--r--ospfd/ospf_spf.c596
-rw-r--r--ospfd/ospf_spf.h25
-rw-r--r--ospfd/ospf_sr.c61
-rw-r--r--ospfd/ospf_sr.h10
-rw-r--r--ospfd/ospf_ti_lfa.c1114
-rw-r--r--ospfd/ospf_ti_lfa.h41
-rw-r--r--ospfd/ospf_vty.c53
-rw-r--r--ospfd/ospf_vty.h4
-rw-r--r--ospfd/ospf_zebra.c126
-rw-r--r--ospfd/ospfd.c56
-rw-r--r--ospfd/ospfd.h88
-rw-r--r--ospfd/subdir.am2
-rw-r--r--sharpd/sharp_zebra.c32
-rw-r--r--tests/ospfd/.gitignore3
-rw-r--r--tests/ospfd/common.c248
-rw-r--r--tests/ospfd/common.h47
-rw-r--r--tests/ospfd/test_ospf_spf.c303
-rw-r--r--tests/ospfd/test_ospf_spf.in10
-rw-r--r--tests/ospfd/test_ospf_spf.py4
-rw-r--r--tests/ospfd/test_ospf_spf.refout130
-rw-r--r--tests/ospfd/topologies.c575
-rw-r--r--tests/subdir.am19
-rw-r--r--tests/topotests/bgp_blackhole_community/r2/bgpd.conf1
-rw-r--r--tests/topotests/bgp_blackhole_community/r2/zebra.conf3
-rw-r--r--tests/topotests/bgp_blackhole_community/r4/bgpd.conf5
-rw-r--r--tests/topotests/bgp_blackhole_community/r4/zebra.conf6
-rw-r--r--tests/topotests/bgp_blackhole_community/test_bgp_blackhole_community.py45
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/__init__.py0
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/isisd.conf27
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ip_route.ref89
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_ipv6_route.ref65
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step1/show_yang_interface_isis_adjacencies.ref32
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ip_route.ref82
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step2/show_ipv6_route.ref59
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ip_route.ref62
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step3/show_ipv6_route.ref40
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ip_route.ref89
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/step4/show_ipv6_route.ref65
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt1/zebra.conf19
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt2/isisd.conf35
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ip_route.ref77
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_ipv6_route.ref40
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt2/step1/show_yang_interface_isis_adjacencies.ref58
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt2/zebra.conf22
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt3/isisd.conf35
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ip_route.ref97
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_ipv6_route.ref59
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt3/step1/show_yang_interface_isis_adjacencies.ref51
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt3/zebra.conf22
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt4/isisd.conf42
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ip_route.ref94
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_ipv6_route.ref21
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt4/step1/show_yang_interface_isis_adjacencies.ref63
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt4/zebra.conf25
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt5/isisd.conf42
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ip_route.ref118
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_ipv6_route.ref40
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt5/step1/show_yang_interface_isis_adjacencies.ref63
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt5/zebra.conf25
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/isisd.conf32
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ip_route.ref107
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_ipv6_route.ref46
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step1/show_yang_interface_isis_adjacencies.ref44
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ip_route.ref100
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step2/show_ipv6_route.ref40
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ip_route.ref80
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step3/show_ipv6_route.ref21
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ip_route.ref107
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/step4/show_ipv6_route.ref46
-rw-r--r--tests/topotests/isis-lsp-bits-topo1/rt6/zebra.conf22
-rwxr-xr-xtests/topotests/isis-lsp-bits-topo1/test_isis_lsp_bits_topo1.py353
-rw-r--r--tests/topotests/lib/topotest.py2
-rw-r--r--tests/topotests/ospf-tilfa-topo1/__init__.py0
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/ospfd.conf27
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/step1/show_ip_route_initial.ref156
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_initial.ref156
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/step2/show_ip_route_link_protection.ref226
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_initial.ref156
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/step3/show_ip_route_node_protection.ref192
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt1/zebra.conf17
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt2/ospfd.conf27
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt2/zebra.conf17
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt3/ospfd.conf27
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt3/zebra.conf17
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt4/ospfd.conf27
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt4/zebra.conf17
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt5/ospfd.conf27
-rw-r--r--tests/topotests/ospf-tilfa-topo1/rt5/zebra.conf17
-rw-r--r--tests/topotests/ospf-tilfa-topo1/test_ospf_tilfa_topo1.py242
-rwxr-xr-xtests/topotests/simple-snmp-test/test_simple_snmp.py13
-rw-r--r--yang/frr-isisd.yang15
-rw-r--r--zebra/zapi_msg.c40
-rw-r--r--zebra/zapi_msg.h3
-rw-r--r--zebra/zebra_nhg.c32
-rw-r--r--zebra/zebra_nhg.h7
-rw-r--r--zebra/zebra_routemap.c2
-rw-r--r--zebra/zebra_routemap.h2
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