Merge pull request #639 from kernelkit/ipv4ll-request-address

IPv4 autoconf request-address support

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-09-26 16:41:10 +02:00
committed by GitHub
53 changed files with 2819 additions and 1479 deletions
@@ -1,24 +1,4 @@
#!/bin/sh
# This file is part of avahi.
#
# avahi is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# avahi 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 Lesser General Public
# License along with avahi; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
# USA.
set -e
# Command line arguments:
# $1 event that happened:
# BIND: Successfully claimed address
@@ -29,60 +9,54 @@ set -e
# $3 IP adddress
PATH="$PATH:/usr/bin:/usr/sbin:/bin:/sbin"
NAME="/etc/frr/static.d/$2-zeroconf.conf"
NEXT="${NAME}+"
# Use a different metric for each interface, so that we can set
# identical routes to multiple interfaces.
log()
{
logger -I $$ -t zeroconf -p user.notice "$*"
}
METRIC=$((1000 + `cat "/sys/class/net/$2/ifindex" 2>/dev/null || echo 0`))
if [ -x /bin/ip -o -x /sbin/ip ] ; then
# We have the Linux ip tool from the iproute package
case "$1" in
BIND)
ip addr flush dev "$2" label "$2:avahi"
ip addr add "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2" proto 6
ip route add default dev "$2" metric "$METRIC" scope link proto zeroconf ||:
;;
CONFLICT|UNBIND|STOP)
ip route del default dev "$2" metric "$METRIC" scope link ||:
ip addr del "$3"/16 brd 169.254.255.255 label "$2:avahi" scope link dev "$2"
;;
*)
echo "Unknown event $1" >&2
exit 1
;;
# Reduce changes needed by comparing with previous route(s)
act()
{
case $1 in
add)
echo "! Generated by avahi-autoipd" > "$NEXT"
echo "ip route 0.0.0.0/0 $2 254" >> "$NEXT"
cmp -s "$NAME" "$NEXT" && return
mv "$NEXT" "$NAME"
;;
del)
[ -f "$NAME" ] || return
rm "$NAME"
;;
*)
return
;;
esac
elif [ -x /bin/ifconfig -o -x /sbin/ifconfig ] ; then
initctl -nbq restart staticd
}
# We have the old ifconfig tool
case "$1" in
BIND)
ip addr flush dev "$2" proto random
ip addr add "$3"/16 brd 169.254.255.255 scope link dev "$2" proto random
act add "$2"
log "set ipv4ll $3 on iface $2"
;;
case "$1" in
BIND)
ifconfig "$2:avahi" inet "$3" netmask 255.255.0.0 broadcast 169.254.255.255 up
route add default dev "$2:avahi" metric "$METRIC" ||:
;;
CONFLICT|UNBIND|STOP)
act del "$2"
ip addr flush dev "$2" proto random
log "clr ipv4ll on iface $2"
;;
CONFLICT|STOP|UNBIND)
route del default dev "$2:avahi" metric "$METRIC" ||:
ifconfig "$2:avahi" down
;;
*)
echo "Unknown event $1" >&2
exit 1
;;
esac
else
echo "No network configuration tool found." >&2
exit 1
fi
*)
log "Unknown event $1 on iface $2"
exit 1
;;
esac
exit 0
@@ -0,0 +1,9 @@
! Default settings for staticd, used for both
! confd generated routes, udhcpc and zeroconf
frr defaults traditional
hostname Router
password zebra
enable password zebra
no log unique-id
log syslog informational
log facility local2
+5
View File
@@ -1 +1,6 @@
# Micro GNU Emacs default configuration file
auto-indent-mode
auto-fill-mode
make-backup-files 0
require-final-newline T
set-fill-column 72
+3
View File
@@ -1 +1,4 @@
# Allow client to pass locale environment
AcceptEnv LANG LC_*
Include /etc/ssh/sshd_config.d/*.conf
@@ -1 +0,0 @@
Subsystem cli /usr/bin/klish
@@ -0,0 +1 @@
Subsystem sftp /usr/libexec/sftp-server
+366
View File
@@ -0,0 +1,366 @@
#!/bin/sh
# Displays basic information about the system
# shellcheck disable=SC2048,SC2086
. /etc/os-release
bopt="-c"
opt="-br"
all=""
plain=""
h1()
{
STR="$*"
if [ -n "$plain" ]; then
echo "$STR" | tr '[:lower:]' '[:upper:]'
else
printf "\033[7m%-${COLUMNS}s\033[0m\n" "$STR"
fi
}
h2()
{
STR="$*"
if [ -n "$plain" ]; then
echo "$STR"
echo "$STR" | sed 's/./-/g'
else
printf "\033[1m%-${COLUMNS}s\033[0m\n" "$STR"
fi
}
dm()
{
if [ -n "$plain" ]; then
echo "${*}"
else
printf "\033[2m%s\033[0m\n" "$*"
fi
}
ul()
{
if [ -n "$plain" ]; then
printf "__%s__" "$*"
else
printf "\033[4%s\033[0m" "$*"
fi
}
em()
{
if [ -n "$plain" ]; then
printf "**%s**" "$*"
else
printf "\033[5m%s\033[0m" "$*"
fi
}
usage()
{
cat <<EOF
usage:
show [opt] cmd
options:
-a Show all, of something
-f Show full output, not brief port/iface listings
-h Show this help text
-n Show output without any footer
-p Show plain output, no bells or whistles
commands:
port PORT Show port configuration and link information
ports Show ports available for bridging
vlans Show port groups in bridge
ifaces Show interfaces and their addresses
fdb Show forwarding database (unicast)
mdb Show multicast forwarding database
ip addr Show IPv4 addresses
route Show routing table
ipv6 addr Show IPv6 addresses
route Show routing table
log [FILE] Show latest entries from syslog, or other FILE
rmon PORT Show RMON counters for PORT (when applicable)
system Show OS details
version Show OS verson
EOF
}
# Usage 1: show port eth0
# Usage 2: show port
# Usage 3: show ports
#
# The first show ethtool output for 'eth0' (in this case). The latter
# two are the same, showing a summary of all interfaces classified as
# access ports.
ports()
{
if [ $# -gt 0 ] && [ -e "/sys/class/net/$1" ]; then
for port in $*; do
ethtool "$port"
done
return
fi
h1 "PORT STATE MAC ADDRESS FLAGS"
if grep -q port /etc/iproute2/group; then
ip $opt link show group port
else
ip $opt link show
fi
if [ -z "$plain" ] && [ -z "$nofoot" ]; then
dm "______________________________________________________________________________"
dm "Use: '[ip|bridge] --help' and '[ip|bridge] link help' for more details."
fi
}
vlans()
{
h1 "INTERFACE VLAN FLAGS"
bridge $bopt vlan show |tail +2 | awk 'NF { iface=$1; vid=$2; printf("%-16s %4d ", iface, vid); for (i=3; i <= NF; i++) printf("%s ", $i); printf("\n"); }'
if [ -z "$plain" ] && [ -z "$nofoot" ]; then
dm "______________________________________________________________________________"
dm "See: 'bridge --help' and 'bridge vlan help' for more details."
fi
}
ifaces()
{
if [ -n "$all" ]; then
if [ $# -gt 0 ]; then
for iface in $*; do
ip $opt addr show dev "$iface"
done
return
fi
h1 "INTERFACE STATE ADDRESS"
ip $opt addr show
if [ -z "$plain" ] && [ -z "$nofoot" ]; then
dm "______________________________________________________________________________"
dm "See: 'ip --help' and 'ip address help' for more details."
fi
else
if [ $# -gt 0 ]; then
for iface in $*; do
sysrepocfg -f json -X -d operational -x \
"/ietf-interfaces:interfaces/interface[name='$iface']" | \
/usr/libexec/statd/cli-pretty "show-interfaces" -n "$iface"
done
return
fi
sysrepocfg -f json -X -d operational -m ietf-interfaces | \
/usr/libexec/statd/cli-pretty "show-interfaces"
fi
}
log()
{
if [ -n "$1" ] && [ -r "/var/log/$1" ]; then
fn="/var/log/$1"
else
fn="/var/log/syslog"
fi
less +G $fn
}
rmon()
{
if [ -z "$*" ]; then
echo "Missing argument, see 'show port' for available interfaces"
exit 1
fi
for port in $*; do
ethtool -S "$port"
done
if [ -z "$plain" ] && [ -z "$nofoot" ]; then
dm "______________________________________________________________________________"
dm "See: 'ethtool --help' for more details."
fi
}
rstp()
{
mstpctl showbridge
echo "br0 port info"
mstpctl showport br0
}
fdb()
{
bridge $bopt fdb show
}
mdb()
{
bridge $bopt mdb show
}
routes()
{
if [ -n "$1" ]; then
arg="-i $1"
else
arg="-i ipv4"
fi
sysrepocfg -f json -X -d operational -x "/ietf-routing:routing/ribs" | \
/usr/libexec/statd/cli-pretty "show-routing-table" $arg
}
igmp()
{
mctl $@
}
system()
{
h1 "SYSTEM INFORMATION"
echo "System Name : $(uname -n)"
echo "System Variant : $VARIANT"
echo "System Description : $PRETTY_NAME"
echo "System Contact : $HOME_URL"
echo "System Timezone : $(cat /etc/timezone)"
echo "System Type : $NAME"
echo "System Version : $(cat /etc/version)"
echo "System Arch : $(uname -m)"
echo "Kernel Version : $(uname -sr)"
}
version()
{
cat /etc/version
}
while [ "$1" != "" ]; do
case $1 in
-a)
all=1
;;
-f)
opt=""
if [ -n "$plain" ]; then
opt="-color=never"
bopt="-color=never"
fi
opt="-d $opt"
bopt="-d $bopt"
;;
-n)
nofoot="yes"
;;
-p)
plain="yes"
opt="$opt -color=never"
bopt="$bopt -color=never"
;;
-h)
usage
exit 0
;;
*)
break
esac
shift
done
if [ -z "$plain" ]; then
TTY=$(resize)
eval "$TTY"
# COLUMS and ROWS should be set on the console, if not, use fallback
if [ -z "$COLUMNS" ]; then
if command -v tput; then
COLUMNS=$(tput cols)
else
COLUMNS=80
fi
fi
fi
cmd=$1
if [ -n "$cmd" ]; then
shift
fi
case $cmd in
help)
usage
;;
port*)
ports $*
;;
vlan*)
vlans
;;
fdb)
fdb
;;
mdb)
mdb
;;
ifa*)
ifaces $*
;;
ip)
cmd=$1
shift
case $cmd in
addr*)
all=1
opt="$opt -d"
ifaces $*
;;
route*)
routes ipv4
;;
igmp*)
igmp $*
;;
*)
usage
;;
esac
;;
ipv6)
cmd=$1
shift
case $cmd in
addr*)
ifaces
;;
route*)
routes ipv6
;;
mld*)
igmp $*
;;
*)
usage
;;
esac
;;
log)
log $1
;;
rmon)
rmon $*
;;
route*)
routes
;;
span*)
rstp
;;
sys*)
system
;;
ver*)
version
;;
*)
usage
exit 1
;;
esac
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# Sort and collate all /etc/frr/static.d/*.conf files managed by confd,
# udhcpc, and avahi-autoipd before starting staticd.
DIRD=/etc/frr/static.d
NAME=/etc/frr/staticd.conf
NEXT=${NAME}+
rm -f "$NEXT"
find "$DIRD" -type f -name '*.conf' ! -name '*~' | sort | while read -r file; do
cat "$file" >> "$NEXT"
done
cmp -s "$NAME" "$NEXT" && exit 0
mv "$NEXT" "$NAME"
@@ -7,9 +7,12 @@ ACTION="$1"
IP_CACHE="/var/lib/misc/${interface}.cache"
RESOLV_CONF="/run/resolvconf/interfaces/${interface}.conf"
NTPFILE="/run/chrony/dhcp-sources.d/${interface}.sources"
NAME="/etc/frr/static.d/${interface}-dhcp.conf"
NEXT="${NAME}+"
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] || subnet=32
[ -n "$metric" ] || metric=100
[ -n "$metric" ] || metric=5
# Handle stateful DHCPv6 like DHCPv4
[ -n "$ipv6" ] && ip="$ipv6/128"
@@ -23,7 +26,7 @@ log()
logger -I $$ -t udhcpc -p user.notice "$*"
}
wwait_for_ipv6_default_route()
wait_for_ipv6_default_route()
{
log "waiting for IPv6 default route to be installed."
while [ $IF_WAIT_DELAY -gt 0 ]; do
@@ -42,28 +45,35 @@ wwait_for_ipv6_default_route()
# client MUST ignore the Router option.
set_dhcp_routes()
{
echo "! Generated by udhcpc" > "$NEXT"
if [ -n "$staticroutes" ]; then
# format: dest1/mask gw1 ... destn/mask gwn
set -- $staticroutes
while [ -n "$1" -a -n "$2" ]; do
log "adding route $1 via $2 dev $interface proto dhcp"
ip route add "$1" via "$2" dev $interface metric $metric proto dhcp
echo "ip route $1 $2 $metric tag 100" >> "$NEXT"
shift 2
done
elif [ -n "$router" ] ; then
for i in $router ; do
ip route add default via $i dev $interface metric $((metric++)) proto dhcp
echo "ip route 0.0.0.0/0 $i $metric tag 100" >> "$NEXT"
done
fi
# Reduce changes needed by comparing with previous route(s)
cmp -s "$NAME" "$NEXT" && return
mv "$NEXT" "$NAME"
initctl -nbq restart staticd
}
clr_dhcp_routes()
{
log "deleting DHCP routes from $interface"
ip route show proto dhcp dev $interface | while read rt via nh dev dev; do
log "removing $rt nh $nh on $dev"
ip route del $rt via $nh dev $dev proto dhcp
done
[ -f "$NAME" ] || return
rm "$NAME"
initctl -nbq restart staticd
}
clr_dhcp_addresses()
@@ -112,7 +122,6 @@ case "$ACTION" in
wait_for_ipv6_default_route
fi
clr_dhcp_routes
set_dhcp_routes
# set hostname if given
+24 -1
View File
@@ -8,11 +8,21 @@ All notable changes to the project are documented in this file.
-------------------------
### Changes
- Upgrade Buildroot to 2024.02.6 (LTS)
- Upgrade Linux kernel to 6.6.52 (LTS)
- Upgrade libyang to 3.4.2
- Upgrade sysrepo to 2.11.7
- Upgrade netopeer2 (NETCONF) to 2.2.31
- Updated `infix-routing.yang` to declare deviations for unsupported
OSPF RPCs and Notifications in `ietf-ospf.yang`
- The CLI admin-exec command `show dns` now also shows any configured
name servers, not just ones acquired via DHCP. Issue #510
- Add support for IPv4 (autoconf) `request-address`. This instructs the
ZeroConf client to start with the requested address. If this is not
successful the client falls back to its default behavior. Issue #628
- Major speedup (10x) in operational data, in particular when querying
interface status. Very noticeable in the CLI `show interfaces`
command on devices with large port counts. Issue #651
- Silence `yanger` log warnings for failing `mctl` command. Caused
by `mctl` reporting no multicast filtering enabled on bridge
@@ -30,20 +40,32 @@ All notable changes to the project are documented in this file.
- Fix #613: CLI regression in tab completion of container commands,
e.g., `container shell <TAB>`. Regression introduced in v24.08.0
- Fix #616: Silent failure when selecting bash as login shell for
non-admin user, this silent lock has been removed.
non-admin user, this silent lock has been removed
- Fix #618: CLI command `show interfaces` does not show bridges and
bridge ports, regression introduced in v24.08.0 -- only affects
bridges without multicast snooping
- Fix #623: CLI command `container upgrade NAME` does not work,
regression introduced in v24.06.0
- Fix #625: initialize sysrepo startup datastore at boot. Improves
usability when working directly against the sysrepo datastores from
the shell with `sysrepocfg` and `sysrepoctl` tools
- Fix #635: OSPF: all router neighbors reported as neighbor on every
interface
- Fix #638: Disabling IPv4LL (autoconf) on an interface does not clean
up 169.254/16 addresses
- Fix #640: unable to set static default route due to priority inversion
from DHCP or IPv4LL (ZeroConf) clients setting their routes directly
in the kernel. This has resulted in a complete overhaul of route
management, using FRRouting for all routes, including DHCP and IPv4LL
routes, presentation in the CLI, and also support for custom route
preference for static routes.
- Spellcheck path to `/var/lib/containers` when unpacking OCI archives
on container upgrade
- The timeout before giving up on loading the `startup-config` at boot
is now 1 minute, just like operations via other front-ends (NETCONF
and RESTCONF). This was previously (incorrectly) set to 10 seconds.
[v24.08.0][] - 2024-08-30
-------------------------
@@ -1109,6 +1131,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
[buildroot]: https://buildroot.org/
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v24.08.0...HEAD
[v24.09.0]: https://github.com/kernelkit/infix/compare/v24.08.0...v24.09.0
[v24.08.0]: https://github.com/kernelkit/infix/compare/v24.06.0...v24.08.0
[v24.06.0]: https://github.com/kernelkit/infix/compare/v24.04.0...v24.06.0
[v24.04.2]: https://github.com/kernelkit/infix/compare/v24.04.1...v24.04.2
+17 -11
View File
@@ -70,17 +70,6 @@ $ sudo apt install bc binutils build-essential bzip2 cpio \
autopoint bison flex autoconf automake
```
For testing, a few more tools and services are required on your system:
```bash
$ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \
ethtool gdb-multiarch tcpdump tshark
```
To be able to build the test specification you also need:
```bash
$ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf
```
> For details, see the Getting Started and System Requirements sections
> of the [excellent manual][1].
@@ -98,6 +87,23 @@ To see available defconfigs for supported targets, use:
make list-defconfigs
### Test
Working with the regression test framework, *Infamy*, a few more tools
and services are required on your system:
```bash
$ sudo apt install jq graphviz qemu-system-x86 qemu-system-arm \
ethtool gdb-multiarch tcpdump tshark
```
To be able to build the test specification you also need:
```bash
$ sudo apt-get install python3-graphviz ruby-asciidoctor-pdf
```
Development
-----------
+153 -42
View File
@@ -598,6 +598,13 @@ As shown, the link-local IPv4 address is configured with `set autconf
enabled true`. The resulting address (169.254.1.3/16) is of type
*random* ([ietf-ip.yang][2]).
The IPv4LL client also supports a `request-address` setting which can be
used to "seed" the client's starting address. If the address is free it
will be used, otherwise it falls back to the default algorithm.
admin@example:/config/interface/eth0/ipv4/> set autoconf request-address 169.254.1.2
#### Use of DHCP for IPv4 address assignment
![Using DHCP for IPv4 address assignment](img/ip-address-example-ipv4-dhcp.svg)
@@ -657,6 +664,7 @@ admin@example:/>
ipv6 ::1/128 (static)
admin@example:/>
#### Stateless Auto-configuration of Global IPv6 Address
![Auto-configuration of global IPv6](img/ip-address-example-ipv6-auto-global.svg)
@@ -693,6 +701,7 @@ below.
ipv6 ::1/128 (static)
admin@example:/>
#### Random Link Identifiers for IPv6 Stateless Autoconfiguration
![Auto-configuration of global IPv6](img/ip-address-example-ipv6-auto-global.svg)
@@ -731,7 +740,9 @@ possible to specify use of a random identifier ([ietf-ip.yang][2] and
Both the link-local address (fe80::) and the global address (2001:)
have changed type to *random*.
### IPv4 forwarding
To be able to route (static or dynamic) on the interface it is
required to enable forwarding. This setting controlls if packets
received on this interface can be forwarded.
@@ -742,7 +753,9 @@ received on this interface can be forwarded.
admin@example:/>
```
### IPv6 forwarding
This flag behaves totally different than for IPv4. For IPv6 the
ability to route between interfaces is always enabled, instead this
flag controls if the interface will be in host/router mode.
@@ -760,18 +773,36 @@ flag controls if the interface will be in host/router mode.
admin@example:/config/interface/eth0/> leave
admin@example:/>
```
## Routing support
| **Yang Model** | **Description** |
|:--------------------------|:--------------------------------------------------------------------------------------|
| ietf-routing | Base model, used to set configuration and read operational status in the other models |
| ietf-ipv4-unicast-routing | Static IPv4 unicast routing |
| ietf-ipv6-unicast-routing | Static IPv6 unicast routing |
| ietf-ospf | OSPF routing |
| infix-routing | Infix deviations |
Currently supported YANG models:
| **YANG Model** | **Description** |
|:--------------------------|:--------------------------------|
| ietf-routing | Base model for all other models |
| ietf-ipv4-unicast-routing | Static IPv4 unicast routing |
| ietf-ipv6-unicast-routing | Static IPv6 unicast routing |
| ietf-ospf | OSPF routing |
| infix-routing | Infix deviations and extensions |
The base model, ietf-routing, is where all the other models hook in. It
is used to set configuration and read operational status (RIB tables) in
the other models.
> **Note:** the standard IETF routing models allows multiple instances,
> but Infix currently *only support one instance* per routing protocol!
> In the examples presented here, the instance name `default` is used.
### IPv4 Static routes
Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces.
The standard IETF model for static routes reside under the `static`
control plane protocol. For our examples we use the instance name
`default`, you can use any name.
For a route with destination 192.168.200.0/24 via 192.168.1.1:
admin@example:/> configure
admin@example:/config/> edit routing control-plane-protocol static name default
@@ -779,7 +810,18 @@ Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces.
admin@example:/config/routing/control-plane-protocol/static/name/default/> leave
admin@example:/>
> **Note:** You can only have one instance per routing protocol.
For a "floating" static route with destination 10.0.0.0/16 via a backup
router 192.168.1.1, using the highest possible distance:
admin@example:/> configure
admin@example:/config/> edit routing control-plane-protocol static name default
admin@example:/config/routing/control-plane-protocol/static/name/default/> set ipv4 route 10.0.0.0/16 next-hop next-hop-address 192.168.1.1 route-preference 254
admin@example:/config/routing/control-plane-protocol/static/name/default/> leave
admin@example:/>
> Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the
> interfaces you want to route between.
### IPv6 Static routes
@@ -789,24 +831,25 @@ Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the interfaces.
admin@example:/config/routing/control-plane-protocol/static/name/default/> leave
admin@example:/>
> **Note:** You can only have one instance per routing protocol.
### OSPFv2 Routing
Infix supports OSPF dynamic routing for IPv4, i.e., OSPFv2.
Remember to enable [IPv4 forwarding](#IPv4-forwarding) for the
interfaces you want to run OSPFv2.
The system supports OSPF dynamic routing for IPv4, i.e., OSPFv2. To
enable OSPF and set one active interface in area 0:
admin@example:/config/> edit routing control-plane-protocol ospfv2 name default
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.0 interface e0 enabled true
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.0 interface e0 enabled
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave
admin@example:/>
> **Note:** You can only have one instance per routing protocol.
> Remember to enable [IPv4 forwarding](#IPv4-forwarding) for all the
> interfaces you want to route between.
#### OSPF area types
In addition to *regular* OSPF areas, area types *NSSA* and *Stub* are supported.
To configure a NSSA area with summary routes:
In addition to *regular* OSPF areas, area types *NSSA* and *Stub* are
also supported. To configure an NSSA area with summary routes:
admin@example:/config/> edit routing control-plane-protocol ospfv2 name default
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> set ospf area 0.0.0.1 area-type nssa-area
@@ -814,8 +857,10 @@ To configure a NSSA area with summary routes:
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave
admin@example:/>
#### Bidirectional Forwarding Detection (BFD)
It is possible to enable BFD per interface to speed up detection of
It is possible to enable BFD per OSPF interface to speed up detection of
link loss.
admin@example:/config/> edit routing control-plane-protocol ospfv2 name default
@@ -823,12 +868,13 @@ link loss.
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> leave
admin@example:/>
#### OSPF interface settings
We have already seen how to enable OSPF per interface (*enabled true*)
and BFD for OSPF per interface (*bfd enabled true*). These and other
OSPF interface settings are done in context of an OSFP area, e.g.,
*area 0.0.0.0*. Available commands can be listed using the `?` mark.
and BFD for OSPF per interface (*bfd enabled true*). These and other
OSPF interface settings are done in context of an OSFP area, e.g., *area
0.0.0.0*. Available commands can be listed using the `?` mark.
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/> edit ospf area 0.0.0.0
admin@example:/config/routing/control-plane-protocol/ospfv2/name/default/ospf/area/0.0.0.0/> edit interface e0
@@ -870,47 +916,112 @@ routes`.
### View routing table
The routing table can be viewed from the operational datastore over
NETCONF or using the CLI:
The routing table can be inspected from the operational datastore, XPath
`/routing/ribs`, using sysrepocfg, NETCONF/RESTCONF, or using the CLI.
#### IPv4 routing table
admin@example:/> show routes ipv4
PREFIX NEXT-HOP PREF PROTOCOL
192.168.1.0/24 e0 kernel
192.168.200.0/24 192.168.1.1 20 static
This CLI example shows the IPv4 routing table with a few connected
routes and some routes learned from OSPF. See the next section for
an explanation of route preferences (PREF).
The `>` at the start of a line marks a selected route (in the IETF YANG
model referred to as *active*), if there are more than one route with
the same destination the `*` marks the next-hop used and installed in
the kernel FIB (the YANG model refers to this as *installed*).
admin@example:/> show ip route
DESTINATION PREF NEXT-HOP PROTO UPTIME
>* 0.0.0.0/0 110/2 10.0.23.1 ospfv2 4h2m43s
>* 10.0.0.1/32 110/4000 10.0.13.1 ospfv2 4h2m43s
10.0.0.3/32 110/0 lo ospfv2 4h2m57s
>* 10.0.0.3/32 0/0 lo direct 4h2m58s
10.0.13.0/30 110/2000 e5 ospfv2 4h2m57s
>* 10.0.13.0/30 0/0 e5 direct 4h2m58s
10.0.23.0/30 110/1 e6 ospfv2 4h2m57s
>* 10.0.23.0/30 0/0 e6 direct 4h2m58s
192.168.3.0/24 110/1 e2 ospfv2 4h2m57s
>* 192.168.3.0/24 0/0 e2 direct 4h2m58s
admin@example:/>
#### IPv6 routing table
admin@example:/> show routes ipv6
PREFIX NEXT-HOP PREF PROTOCOL
2001:db8:3c4d:50::/64 eth4 256 kernel
fe80::/64 eth5 256 kernel
fe80::/64 eth3 256 kernel
fe80::/64 eth1 256 kernel
fe80::/64 eth0 256 kernel
fe80::/64 eth2 256 kernel
fe80::/64 eth4 256 kernel
This CLI example show the IPv6 routing table.
admin@example:/> show ipv6 route
DESTINATION PREF NEXT-HOP PROTO UPTIME
>* ::/0 1/0 2001:db8:3c4d:50::1 static 0h1m20s
>* 2001:db8:3c4d:50::/64 0/0 e6 direct 0h1m20s
>* 2001:db8:3c4d:200::1/128 0/0 lo direct 0h1m20s
* fe80::/64 0/0 e7 direct 0h1m20s
* fe80::/64 0/0 e6 direct 0h1m20s
* fe80::/64 0/0 e5 direct 0h1m20s
* fe80::/64 0/0 e4 direct 0h1m20s
* fe80::/64 0/0 e3 direct 0h1m20s
* fe80::/64 0/0 e2 direct 0h1m20s
>* fe80::/64 0/0 e1 direct 0h1m20s
admin@example:/>
#### Route Preference
The operating system leverages FRRouting ([Frr][4]) as routing engine
for both static and dynamic routing. Even routes injected from a DHCP
client, and IPv4 link-local (IPv4) routes, are injected into Frr to let
it weigh all routes before installing them into the kernel routing table
(sometimes referred to as FIB).
Routes have different weights made up from a *distance* and a *metric*.
The kernel routing table only talks about *metric*, which unfortunately
is **not the same** -- this is one of the reasons why the term *route
preference* is used instead. It is recommended to use the CLI, or any
of the other previously mentioned YANG based front-ends, to inspect the
routing table.
Default distances used (lower numeric value wins):
| **Distance** | **Protocol** |
|--------------|-----------------------------------------|
| 0 | Kernel routes, i.e., connected routes |
| 1 | Static routes |
| 5 | DHCP routes |
| 110 | OSPF |
| 254 | IPv4LL (ZeroConf) device routes |
| 255 | Route will not be used or redistributed |
Hence, a route learned from OSPF may be overridden by a static route set
locally. By default, even a route to the same destination, but with a
different next-hop, learned from a DHCP server wins over an OSPF route.
The distance used for static routes and DHCP routes can be changed by
setting a different *routing preference* value.
> **Note:** the kernel metric is an unsigned 32-bit value, which is read
> by Frr as (upper) 8 bits distance and 24 bits metric. But it does not
> write it back to the kernel FIB this way, only selected routes are
> candidates to be installed in the FIB by Frr.
#### Source protocol
The source protocol describes the origin of the route.
| **Protocol** | **Description** |
|:-------------|:---------------------------------------------------------------------|
| kernel | Added when setting a subnet address on an interface |
| static | User created static routes |
| dhcp | Routes retrieved from DHCP |
| ospf | Routes retreived from OSPFv2 |
| **Protocol** | **Description** |
|:-------------|:----------------------------------------------------|
| kernel | Added when setting a subnet address on an interface |
| static | User created, learned from DHCP, or IPv4LL |
| ospfv2 | Routes learned from OSPFv2 |
The YANG model *ietf-routing* support multiple ribs but only two are
currently supported, namely `ipv4` and `ipv6`.
[1]: https://www.rfc-editor.org/rfc/rfc8343
[2]: https://www.rfc-editor.org/rfc/rfc8344
[3]: https://www.rfc-editor.org/rfc/rfc8981
[4]: https://frrouting.org/
[^1]: Please note, link aggregates are not yet supported in Infix.
[^2]: Link-local IPv6 addresses are implicitly enabled when enabling
+8 -5
View File
@@ -281,17 +281,18 @@ or, when running interactively:
$ make test-sh
09:08:17 infamy0:test # ./9pm/9pm.py -o"--transport=restconf" case/ietf_system/hostname.py
### Test specification
The test specification is automaticly generated from the test cases,
this put some constaints on the test cases:
this puts some constraints on the test cases:
All test cases must be written so that it can be understandable with
only the steps so the result can be reproduced
manually. For example: Intead of `set ip address on
the interface` it should be more human readable, for example:
only the steps so the result can be reproduced manually. E.g., instead
of `set ip address on the interface` it should be more human readable,
for example:
```
```python
test.step("Set IPv4 address on the interface dut:cross")
test.step("Verify the IP address is set on dut:cross")
@@ -303,9 +304,11 @@ Each test case must begin with the test name followed by a empty line and
the description of the test. For example:
```
"""
Set hostname
Verify that it it possible to change hostname
"""
```
The test specifaction can be genererated with:
@@ -45,8 +45,10 @@ endif
ifeq ($(BR2_PACKAGE_AVAHI_AUTOIPD),y)
define SKELETON_INIT_FINIT_SET_AVAHI_AUTOIPD
echo "service [2345789] name:zeroconf :%i avahi-autoipd --force-bind --syslog %i -- ZeroConf for %i" \
> $(FINIT_D)/available/zeroconf@.conf
echo 'service name:zeroconf :%i env:-/etc/default/zeroconf-%i \' >$(FINIT_D)/available/zeroconf@.conf
echo ' <!> kill:5 pid:!/run/avahi-autoipd.%i.pid \'>>$(FINIT_D)/available/zeroconf@.conf
echo ' [2345789] log avahi-autoipd $$ZEROCONF_ARGS %i \'>>$(FINIT_D)/available/zeroconf@.conf
echo ' -- ZeroConf for %i' >>$(FINIT_D)/available/zeroconf@.conf
endef
SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_AVAHI_AUTOIPD
endif
@@ -87,6 +89,7 @@ define SKELETON_INIT_FINIT_SET_FRR
for svc in babeld bfdd bgpd eigrpd isisd ldpd ospfd ospf6d pathd ripd ripng staticd vrrpd zebra; do \
cp $(SKELETON_INIT_FINIT_AVAILABLE)/frr/$$svc.conf $(FINIT_D)/available/$$svc.conf; \
done
ln -sf ../available/staticd.conf $(FINIT_D)/enabled/staticd.conf
ln -sf ../available/zebra.conf $(FINIT_D)/enabled/zebra.conf
endef
SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_FRR
@@ -1 +1,4 @@
service [2345] log:null <!pid/zebra> staticd -A 127.0.0.1 -u frr -g frr -f /etc/frr/staticd.conf -- Static routing daemon
# Run staticd-helper to collate /etc/static.d/*.conf before starting staticd
service log:null <!pid/zebra> pre:/usr/sbin/staticd-helper \
[2345] staticd -A 127.0.0.1 -u frr -g frr -f /etc/frr/staticd.conf \
-- Static routing daemon
@@ -0,0 +1,46 @@
From 74ec0f8335f811e4f5becc8bbea4a52af4d3e749 Mon Sep 17 00:00:00 2001
From: Joachim Wiberg <troglobit@gmail.com>
Date: Tue, 17 Sep 2024 04:08:12 +0200
Subject: [PATCH] On SIGTERM, allow dispatcher to process event before exiting
Organization: Addiva Elektronik
On non-systemd systems, like those managed by Finit, when the main PID
used by avahi-autoipd call the dispatcher on reception of SIGTERM, it
may not have time to complete the callout because the main PID exits
almost immediately.
Even though the dispatcher run as a separate process, when the main PID
is collected by FInit it will unconditionally reap lingering processes
in the same process group.
Add a small enough sleep to allow the dispatcher to complete the call to
/etc/avahi/avahi-autoipd.action before we exit the main PID. A better
patch would be to add support for the dispatcher to messages the main
PID when it has run the script, but there does not seem to be any such
bidirectional communication available today.
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
---
avahi-autoipd/main.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/avahi-autoipd/main.c b/avahi-autoipd/main.c
index 96c9188..8f7a7e5 100644
--- a/avahi-autoipd/main.c
+++ b/avahi-autoipd/main.c
@@ -1404,8 +1404,11 @@ static int loop(int iface, uint32_t addr) {
fail:
- if (state == STATE_RUNNING || state == STATE_ANNOUNCING)
+ if (state == STATE_RUNNING || state == STATE_ANNOUNCING) {
do_callout(dispatcher, CALLOUT_STOP, iface, addr);
+ /* Don't leave yet, allow dispatcher to process event */
+ sleep(2);
+ }
avahi_free(out_packet);
avahi_free(in_packet);
--
2.43.0
+10 -7
View File
@@ -28,12 +28,12 @@ perms()
note()
{
logger -s -I $$ -p user.notice -t load "$*"
logger -I $$ -p user.notice -t load "$*"
}
err()
{
logger -s -I $$ -p user.error -t load "$*"
logger -I $$ -p user.error -t load "$*"
}
@@ -60,12 +60,12 @@ config=$1
if [ -f "/mnt/aux/test-mode" ] && [ "$config" = "startup-config" ]; then
if [ -f "/mnt/aux/test-override-startup" ]; then
rm -f "/mnt/aux/test-override-startup"
else
if [ -f "/mnt/aux/test-override-startup" ]; then
rm -f "/mnt/aux/test-override-startup"
else
note "Test mode detected, switching to test-config"
config="test-config"
fi
fi
fi
if [ -f "$config" ]; then
@@ -100,7 +100,7 @@ if [ ! -f "$fn" ]; then
fi
note "Loading $config ..."
if ! $sysrepocfg -v3 -I"$fn" -f json; then
if ! $sysrepocfg -v2 -I"$fn" -f json; then
case "$config" in
startup-config)
err "Failed loading $fn, reverting to Fail Secure mode!"
@@ -126,6 +126,9 @@ if ! $sysrepocfg -v3 -I"$fn" -f json; then
esac
exit 1
else
note "Success, syncing with startup datastore."
$sysrepocfg -v2 -d startup -C running
fi
note "Loaded $fn successfully."
+9 -5
View File
@@ -1,11 +1,11 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <srx/common.h>
#include "core.h"
struct confd confd;
uint32_t core_hook_prio(void)
{
static uint32_t hook_prio = CB_PRIO_PASSIVE;
@@ -16,6 +16,10 @@ uint32_t core_hook_prio(void)
int core_startup_save(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
/* skip in bootstrap, triggered by load script to initialize startup datastore */
if (systemf("runlevel >/dev/null 2>&1"))
return SR_ERR_OK;
if (systemf("sysrepocfg -X/cfg/startup-config.cfg -d startup -f json"))
return SR_ERR_SYS;
@@ -177,10 +181,10 @@ err:
void sr_plugin_cleanup_cb(sr_session_ctx_t *session, void *priv)
{
struct confd *confd = (struct confd *)priv;
struct confd *ptr = (struct confd *)priv;
sr_unsubscribe(confd->sub);
sr_unsubscribe(confd->fsub);
json_decref(confd->root);
sr_unsubscribe(ptr->sub);
sr_unsubscribe(ptr->fsub);
json_decref(ptr->root);
closelog();
}
+4 -4
View File
@@ -100,11 +100,11 @@ int core_startup_save (sr_session_ctx_t *, uint32_t, const char *, const ch
static inline int register_change(sr_session_ctx_t *session, const char *module, const char *xpath,
int flags, sr_module_change_cb cb, void *arg, sr_subscription_ctx_t **sub)
{
struct confd *confd = (struct confd *)arg;
struct confd *ptr = (struct confd *)arg;
int rc;
if (!flags) {
sr_module_change_subscribe(confd->session, module, xpath, core_pre_hook, NULL,
sr_module_change_subscribe(ptr->session, module, xpath, core_pre_hook, NULL,
0, SR_SUBSCR_PASSIVE, sub);
}
@@ -123,9 +123,9 @@ static inline int register_change(sr_session_ctx_t *session, const char *module,
* the startup-save hook for running -> startup copying.
*/
if (!flags) {
sr_module_change_subscribe(confd->session, module, xpath, core_post_hook, NULL,
sr_module_change_subscribe(ptr->session, module, xpath, core_post_hook, NULL,
core_hook_prio(), SR_SUBSCR_PASSIVE, sub);
sr_module_change_subscribe(confd->startup, module, xpath, core_startup_save, NULL,
sr_module_change_subscribe(ptr->startup, module, xpath, core_startup_save, NULL,
core_hook_prio(), SR_SUBSCR_PASSIVE, sub);
}
+66 -23
View File
@@ -537,45 +537,89 @@ static int netdag_gen_ipv6_autoconf(struct dagger *net, struct lyd_node *cif,
return 0;
}
/*
* Check if ipv4 is enabled, only then can autoconf be enabled, in all
* other cases it must be disabled. Since we have multiple settings in
* autoconf, we check if either is modified (diff), in which case we not
* only enable, but also "touch" the Finit service for avahi-autoipd to
* ensure it is (re)started.
*
* Note: in Infix, regardless of the IPv4 configuration, any link-local
* link-local address is disabled when the interface is being used
* as a bridge port.
*
* Also, IPv4LL is not defined for loopback, so always skip there.
*/
static int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
struct lyd_node *dif)
{
struct lyd_node *ipconf = lydx_get_child(cif, "ipv4");
struct lyd_node *ipdiff = lydx_get_child(dif, "ipv4");
const char *ifname = lydx_get_cattr(dif, "name");
struct lyd_node *node, *zcip;
struct lydx_diff nd;
struct lyd_node *zcip;
char defaults[64];
FILE *initctl;
int err = 0;
if (!strcmp(ifname, "lo"))
return 0;
/* client defults for this interface, needed in both cases */
snprintf(defaults, sizeof(defaults), "/etc/default/zeroconf-%s", ifname);
/* no ipv4 at all, ipv4 selectively disabled, or interface is a bridge port */
if (!ipconf || !lydx_is_enabled(ipconf, "enabled") || is_bridge_port(cif))
goto disable;
if (lydx_get_op(lydx_get_child(ipdiff, "enabled")) == LYDX_OP_REPLACE) {
node = lydx_get_child(ipconf, "autoconf");
if (node && lydx_is_enabled(node, "enabled"))
goto enable;
goto disable;
}
/*
* when enabled, we may have been enabled before, but skipped
* for various reasons: was bridge port, ipv4 was disabled...
*/
zcip = lydx_get_child(ipconf, "autoconf");
if (zcip && lydx_is_enabled(zcip, "enabled")) {
struct lyd_node *node;
const char *addr;
int diff = 0;
FILE *fp;
zcip = lydx_get_child(ipdiff, "autoconf");
if (!zcip || !(node = lydx_get_child(zcip, "enabled")))
return 0;
/* check for any changes in this container */
node = lydx_get_child(ipdiff, "autoconf");
if (node) {
const struct lyd_node *tmp;
lydx_get_diff(node, &nd);
if (nd.new && !strcmp(nd.val, "true")) {
enable:
initctl = dagger_fopen_next(net, "init", ifname,
60, "zeroconf-up.sh");
tmp = lydx_get_child(node, "enabled");
if (tmp)
diff++;
tmp = lydx_get_child(node, "request-address");
if (tmp)
diff++;
}
fp = fopen(defaults, "w");
if (!fp) {
ERRNO("Failed creating %s, cannot enable IPv4LL on %s", defaults, ifname);
return -EIO;
}
fprintf(fp, "ZEROCONF_ARGS=\"--force-bind --syslog ");
addr = lydx_get_cattr(zcip, "request-address");
if (addr)
fprintf(fp, "--start=%s", addr);
fprintf(fp, "\"\n");
fclose(fp);
initctl = dagger_fopen_next(net, "init", ifname, 60, "zeroconf-up.sh");
if (!initctl)
return -EIO;
fprintf(initctl,
"initctl -bnq enable zeroconf@%s.conf\n", ifname);
/* on enable, or reactivation, it is enough to ensure the service is enabled */
fprintf(initctl, "initctl -bnq enable zeroconf@%s.conf\n", ifname);
/* on changes to autoconf we must ensure Finit restarts the service */
if (diff)
fprintf(initctl, "initctl -bnq touch zeroconf@%s.conf\n", ifname);
} else {
disable:
initctl = dagger_fopen_current(net, "exit", ifname,
40, "zeroconf-down.sh");
initctl = dagger_fopen_current(net, "exit", ifname, 40, "zeroconf-down.sh");
if (!initctl) {
/* check if in bootstrap (pre gen 0) */
if (errno == EUNATCH)
@@ -583,9 +627,8 @@ static int netdag_gen_ipv4_autoconf(struct dagger *net, struct lyd_node *cif,
return -EIO;
}
fprintf(initctl,
"initctl -bnq disable zeroconf@%s.conf\n", ifname);
fprintf(initctl, "initctl -bnq disable zeroconf@%s.conf\n", ifname);
fprintf(initctl, "rm -f %s\n", defaults);
err = netdag_exit_reload(net);
}
+26 -38
View File
@@ -8,7 +8,7 @@
#define XPATH_BASE_ "/ietf-routing:routing/control-plane-protocols/control-plane-protocol"
#define XPATH_OSPF_ XPATH_BASE_ "/ietf-ospf:ospf"
#define STATICD_CONF "/etc/frr/staticd.conf"
#define STATICD_CONF "/etc/frr/static.d/confd.conf"
#define STATICD_CONF_NEXT STATICD_CONF "+"
#define STATICD_CONF_PREV STATICD_CONF "-"
#define OSPFD_CONF "/etc/frr/ospfd.conf"
@@ -129,11 +129,11 @@ int parse_ospf_areas(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp
int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf)
{
const char *static_debug = "! OSPF default debug\
debug ospf bfd\n \
debug ospf packet all detail\n \
debug ospf ism\n \
debug ospf nsm\n \
debug ospf default-information\n \
debug ospf bfd\n\
debug ospf packet all detail\n\
debug ospf ism\n\
debug ospf nsm\n\
debug ospf default-information\n\
debug ospf nssa\n\
! OSPF configuration\n";
struct lyd_node *areas, *default_route;
@@ -187,12 +187,14 @@ debug ospf nssa\n\
return 0;
}
static int parse_route(struct lyd_node *parent, FILE *fp, char *ip)
static int parse_route(struct lyd_node *parent, FILE *fp, const char *ip)
{
const char *outgoing_interface, *next_hop_address, *special_next_hop, *destination_prefix;
const char *outgoing_interface, *next_hop_address, *special_next_hop,
*destination_prefix, *route_preference;
struct lyd_node *next_hop;
destination_prefix = lydx_get_cattr(parent, "destination-prefix");
route_preference = lydx_get_cattr(parent, "route-preference");
next_hop = lydx_get_child(parent, "next-hop");
outgoing_interface = lydx_get_cattr(next_hop, "outgoing-interface");
next_hop_address = lydx_get_cattr(next_hop, "next-hop-address");
@@ -211,7 +213,7 @@ static int parse_route(struct lyd_node *parent, FILE *fp, char *ip)
fputs("reject", fp);
else if (strcmp(special_next_hop, "receive") == 0)
fputs("Null0", fp);
fputs("\n", fp);
fprintf(fp, " %s\n", route_preference);
return SR_ERR_OK;
}
@@ -224,14 +226,13 @@ static int parse_static_routes(sr_session_ctx_t *session, struct lyd_node *paren
ipv4 = lydx_get_child(parent, "ipv4");
ipv6 = lydx_get_child(parent, "ipv6");
ipv4 = lydx_get_child(parent, "ipv4");
ipv6 = lydx_get_child(parent, "ipv6");
v4routes = lydx_get_child(ipv4, "route");
v6routes = lydx_get_child(ipv6, "route");
LY_LIST_FOR(v4routes, route) {
parse_route(route, fp, "ip");
num_routes++;
}
v6routes = lydx_get_child(ipv6, "route");
LY_LIST_FOR(v6routes, route) {
parse_route(route, fp, "ipv6");
num_routes++;
@@ -245,7 +246,7 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
int staticd_enabled = 0, ospfd_enabled = 0, bfdd_enabled = 0;
bool ospfd_running, staticd_running, bfdd_running;
bool ospfd_running, bfdd_running;
struct lyd_node *cplane, *tmp;
bool restart_zebra = false;
int rc = SR_ERR_OK;
@@ -260,7 +261,7 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su
ERROR("Failed to open %s", STATICD_CONF_NEXT);
return SR_ERR_INTERNAL;
}
fputs(FRR_STATIC_CONFIG, fp);
fputs("! Generated by Infix confd\n", fp);
break;
case SR_EV_ABORT: /* User abort, or other plugin failed */
@@ -272,18 +273,8 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su
staticd_enabled = fexist(STATICD_CONF_NEXT);
ospfd_enabled = fexist(OSPFD_CONF_NEXT);
bfdd_enabled = fexist(BFDD_CONF_NEXT);
staticd_running = !systemf("initctl -bfq status staticd");
ospfd_running = !systemf("initctl -bfq status ospfd");
bfdd_running = !systemf("initctl -bfq status bfdd");
if (staticd_running && !staticd_enabled) {
if (systemf("initctl -bfq disable staticd")) {
ERROR("Failed to disable static routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
/* Remove all generated files */
(void)remove(STATICD_CONF);
}
if (bfdd_running && !bfdd_enabled) {
if (systemf("initctl -bfq disable bfdd")) {
@@ -321,12 +312,10 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su
(void)rename(OSPFD_CONF, OSPFD_CONF_PREV);
(void)rename(OSPFD_CONF_NEXT, OSPFD_CONF);
if (!ospfd_running) {
if (ospfd_enabled) {
if (systemf("initctl -bfq enable ospfd")) {
ERROR("Failed to enable OSPF routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
if (systemf("initctl -bnq enable ospfd")) {
ERROR("Failed to enable OSPF routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
} else {
restart_zebra = true;
@@ -337,18 +326,17 @@ static int change_control_plane_protocols(sr_session_ctx_t *session, uint32_t su
(void)remove(STATICD_CONF_PREV);
(void)rename(STATICD_CONF, STATICD_CONF_PREV);
(void)rename(STATICD_CONF_NEXT, STATICD_CONF);
if (!staticd_running) {
if (systemf("initctl -bfq enable staticd")) {
ERROR("Failed to enable static routing daemon");
rc = SR_ERR_INTERNAL;
goto err_abandon;
}
} else {
restart_zebra = true;
} else {
if (!remove(STATICD_CONF))
restart_zebra = true;
}
}
if (restart_zebra) {
/* skip in runlevel S, no routing daemons run here anyway */
if (systemf("runlevel >/dev/null 2>&1"))
return SR_ERR_OK;
if (systemf("initctl -bfq restart zebra")) {
ERROR("Failed to restart zebra routing daemon");
rc = SR_ERR_INTERNAL;
+4 -4
View File
@@ -25,19 +25,19 @@ MODULES=(
"ietf-hardware@2018-03-13.yang -e hardware-state"
"infix-hardware@2024-04-25.yang"
"ieee802-dot1q-types@2022-10-29.yang"
"infix-ip@2023-09-14.yang"
"infix-ip@2024-09-16.yang"
"infix-if-type@2024-01-29.yang"
"infix-routing@2024-09-20.yang"
"infix-routing@2024-09-23.yang"
"ieee802-dot1ab-lldp@2022-03-15.yang"
"infix-lldp@2023-08-23.yang"
"infix-dhcp-client@2024-04-12.yang"
"infix-dhcp-client@2024-09-20.yang"
"infix-meta@2024-06-19.yang"
"infix-system@2024-09-13.yang"
"infix-services@2024-05-30.yang"
"ieee802-ethernet-interface@2019-06-21.yang"
"infix-ethernet-interface@2024-02-27.yang"
"infix-factory-default@2023-06-28.yang"
"infix-interfaces@2024-01-15.yang -e vlan-filtering"
"infix-interfaces@2024-09-23.yang -e vlan-filtering"
# from rousette
"ietf-restconf@2017-01-26.yang"
+1 -1
View File
@@ -1,6 +1,6 @@
# -*- sh -*-
# REMEMBER TO UPDATE infix-interfaces ALSO IN confd.inc
MODULES=(
"infix-interfaces@2024-01-15.yang -e vlan-filtering -e containers"
"infix-interfaces@2024-09-23.yang -e vlan-filtering -e containers"
"infix-containers@2024-03-27.yang"
)
@@ -10,6 +10,11 @@ module infix-dhcp-client {
contact "kernelkit@googlegroups.com";
description "This module implements an IPv4 DHCP client";
revision 2024-09-20 {
description "Routes are installed in Frr (staticd), clarify preference
vs metric and adjust default preference 100 -> 5.";
reference "internal";
}
revision 2024-04-12 {
description "Adjust DHCP client hostname option, max 64 charachters.";
reference "internal";
@@ -29,7 +34,7 @@ module infix-dhcp-client {
typedef route-preference {
type uint32;
description "This type is used for route preferences (metric).";
description "This type is used for selecting route preference (distance).";
}
typedef dhcp-options {
@@ -149,12 +154,12 @@ module infix-dhcp-client {
}
leaf route-preference {
type route-preference;
default 100;
default 5;
description
"The metric any route is installed with, both option 33 and 121.
The default (100) is based on a plain Ethernet interface, the
interface type determines this, e.g. a WiFi interface would be
metric 500.";
"The preference (administrative distance) that all DHCP routes are
installed with, option 3, 33 and 121. The default preferfence (5)
is higher (less worth) than static routes, but lower than those
learned via dynamic routing protocols, like OSPF.";
}
}
}
@@ -20,6 +20,11 @@ module infix-interfaces {
contact "kernelkit@googlegroups.com";
description "Linux bridge and lag extensions for ietf-interfaces.";
revision 2024-09-23 {
description "Drop interfaces-state deviation, already marked deprecated.";
reference "internal";
}
revision 2024-01-15 {
description "Add support for container ports (CNI networks).";
reference "internal";
@@ -71,9 +76,4 @@ module infix-interfaces {
config true;
}
}
deviation "/if:interfaces-state" {
deviate not-supported;
description "Deprecated";
}
}
-68
View File
@@ -1,68 +0,0 @@
module infix-ip {
yang-version 1.1;
namespace "urn:infix:params:xml:ns:yang:infix-ip";
prefix infix-ip;
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import ietf-inet-types {
prefix inet;
}
import ietf-yang-types {
prefix yang;
}
description
"This module augments ietf-ip with an IPv4 link-local autoconf";
revision 2023-09-14 {
description
"Added deviations for unsupported parts of ietf-ip.";
}
revision 2023-04-24 {
description
"Initial revision.";
reference
"RFC 7277: A YANG Data Model for IP Management";
}
/*
* Data nodes
*/
augment "/if:interfaces/if:interface/ip:ipv4" {
container autoconf {
description
"Parameters to control the autoconfiguration of IPv4 address.";
leaf enabled {
type boolean;
default false;
description
"Use a ZeroConf/IPv4LL agent to retrieve an 169.254/16 address.";
reference
"RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses";
}
}
}
deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" {
deviate not-supported;
}
}
+79
View File
@@ -0,0 +1,79 @@
module infix-ip {
yang-version 1.1;
namespace "urn:infix:params:xml:ns:yang:infix-ip";
prefix infix-ip;
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import ietf-inet-types {
prefix inet;
}
import ietf-yang-types {
prefix yang;
}
description "This module augments ietf-ip with Infix extensions and deviations.";
revision 2024-09-16 {
description "Add support for IPv4LL request-address.";
reference "Internal.";
}
revision 2023-09-14 {
description "Added deviations for unsupported parts of ietf-ip.";
reference "Internal.";
}
revision 2023-04-24 {
description "Initial revision.";
reference "RFC 7277: A YANG Data Model for IP Management";
}
/*
* Data nodes
*/
augment "/if:interfaces/if:interface/ip:ipv4" {
container autoconf {
description "Parameters to control the autoconfiguration of IPv4 address.";
reference "RFC 3927: Dynamic Configuration of IPv4 Link-Local Addresses";
leaf enabled {
description "Use a ZeroConf/IPv4LL agent to retrieve an 169.254/16 address.";
type boolean;
}
leaf request-address {
description "Try to acquire the specified IP address, if available.
With this setting the IPv4LL client will start by
requesting this address. However, if it is not
available it falls back to the default algorithm.";
type inet:ipv4-address;
must "substring(., 1, 7) = '169.254'" {
error-message "Must be from the IPv4LL range 169.254.0.0/16.";
}
must "not(substring(., string-length(.) - 1, 2) = '.0' or substring(., string-length(.) - 3, 4) = '.255')" {
error-message "Addresses ending in .0 or .255 are reserved.";
}
}
}
}
deviation "/if:interfaces/if:interface/ip:ipv4/ip:address/ip:subnet/ip:netmask" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv4/ip:neighbor" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv6/ip:address/ip:status" {
deviate not-supported;
}
deviation "/if:interfaces/if:interface/ip:ipv6/ip:neighbor" {
deviate not-supported;
}
}
@@ -1,496 +0,0 @@
module infix-routing {
yang-version 1.1;
namespace "urn:infix:routing:ns:yang:1.0";
prefix infix-r;
import ietf-routing {
prefix ietf-r;
}
import ietf-ipv4-unicast-routing {
prefix v4ur;
}
import ietf-ipv6-unicast-routing {
prefix v6ur;
}
import ietf-ospf {
prefix ospf;
}
import ietf-interfaces {
prefix if;
}
import ietf-routing-types {
prefix rt-types;
}
revision 2024-09-20 {
description "Deviate to only show supported routing types";
reference "internal";
}
revision 2024-09-13 {
description "Declare deviations for non-supported OSPF RPCs and Notifications";
reference "internal";
}
revision 2024-03-06 {
description "Obsolete leaf enable in favor for enabled in default-route-advertise";
reference "internal";
}
revision 2024-01-09 {
description "Add operational, area, bfd support
and other minor features";
reference "internal";
}
revision 2023-11-23 {
description "Limit to one instance per control plane protocol,
and change to infix (iproute2) specific source protocol names
for routes";
reference "internal";
}
revision 2023-10-27 {
description "Initial revision.";
reference "internal";
}
/* General routing */
deviation "/ietf-r:routing/ietf-r:interfaces" {
description "Initial limitation";
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:router-id" {
description "Set in OSPF";
deviate not-supported;
}
deviation "/ietf-r:routing-state" {
description "Obsolete";
deviate not-supported;
}
identity infix-source-protocol {
description "Infix naming of source protocols";
}
identity kernel {
description "Kernel added route";
base infix-source-protocol;
}
identity ospf {
description "OSPF added route";
base infix-source-protocol;
}
identity dhcp {
description "DHCP added route";
base infix-source-protocol;
}
identity zeroconf {
description "ZeroConf added route";
base infix-source-protocol;
}
identity ra {
description "RA IPv6 route";
base infix-source-protocol;
}
typedef infix-distribute-protocol {
type enumeration {
enum ospf {
description "Redistribute Ospf";
}
enum static {
description "Redistribute Static routes";
}
enum connected {
description "Redistribute Connected routes";
}
}
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:source-protocol" {
description "Use type names from iproute2";
deviate replace {
type identityref {
base infix-source-protocol;
}
}
}
identity infix-routing-type {
description "Infix routing type";
}
identity ospfv2 {
base ospf:ospfv2;
base infix-routing-type;
description "OSPv2 (IPv4) routing protocol";
}
identity static {
base ietf-r:static;
base infix-routing-type;
base infix-source-protocol;
description "Static route";
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:type" {
deviate replace {
type identityref {
base infix-routing-type;
}
}
}
/* Static routes */
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:static-routes/v4ur:ipv4/v4ur:route/v4ur:next-hop/v4ur:next-hop-options/v4ur:next-hop-list" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:static-routes/v6ur:ipv6/v6ur:route/v6ur:next-hop/v6ur:next-hop-options/v6ur:next-hop-list" {
deviate not-supported;
}
/* show routes */
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:default-rib" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:active-route" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:metric" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:tag" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ospf:route-type" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:active" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:routes/ietf-r:route/ietf-r:last-updated" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:ribs/ietf-r:rib/ietf-r:description" {
deviate not-supported;
}
/* OSPF */
typedef infix-ospf-interface-type {
type enumeration {
enum broadcast {
description
"Specifies an OSPF broadcast multi-access network.";
}
enum point-to-point {
description
"Specifies an OSPF point-to-point network.";
}
}
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol" {
deviate add {
must "count(../control-plane-protocol[type = current()/type]) = 1" {
error-message "Only one instance per routing protocol is allowed.";
}
}
}
/*
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ietf-r:type" {
deviate add {
must "derived-from-or-self(., 'ospf:ospfv2') or "+
"derived-from-or-self(., 'ietf-r:static')" {
description "Only OSPFv2 and Static routes are supported in Infix.";
}
}
}
*/
augment "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf" {
description "ietf-ospf lack the setting to generate a default route";
container default-route-advertise {
description "Distribute default route to network";
leaf enable {
status obsolete;
description "Legacy, replaced by 'enabled'.";
type boolean;
}
leaf enabled {
description "Distribute default route";
type boolean;
}
leaf always {
description "Always distribute default route, even if no local default route exist.
If set to false, a default route will only be distributed if a default route exist
in the local routing table.";
type boolean;
}
}
}
augment "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf" {
description "ietf-ospf does not contain redistribution
7. How should route redistribution be configured? I see ietf-rip.yang has a separate
container for that purpose, but ietf-ospf.yang (and other IGP modules) don't do the
same. I also noticed the BGP model is using definition from ietf-routing-policy.yang.
Different vendors handle redistribution in different ways. This could be added with
an augmentation if there were agreement.
https://marc.info/?l=ms-ospf&m=166258444409552&w=2";
container redistribute {
list redistribute {
key "protocol";
description "Redistribute protocols into OSPF";
leaf protocol {
type infix-distribute-protocol;
description "Set protocol to redistribute";
}
}
}
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface" {
deviate add {
must "current()/name = 'lo' or count(../../../../ospf:areas/ospf:area/ospf:interfaces/ospf:interface[ospf:name=current()/name]) <= 1" {
error-message "Only one area per interface is allowed.";
}
}
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" {
deviate replace {
type infix-ospf-interface-type;
}
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:auto-cost" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:graceful-restart" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:enabled" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:spf-control" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:preference" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:stub-router" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:topologies" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:statistics" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database-control" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:lsa-log" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:spf-log" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:fast-reroute" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:node-tags" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:mpls" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:nsr" {
deviate not-supported;
}
/* OSPF Area */
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:virtual-links" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:sham-links" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:ranges" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:database" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:topologies" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:statistics" {
deviate not-supported;
}
/* OSPF Local RIB */
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:metric" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:route-tag" {
deviate not-supported;
}
/* OSPF Area Interface */
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:node-flag" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:local-multiplier"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:interval-config-type"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:priority"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:fast-reroute"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:authentication"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:statistics"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:statistics"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:cost"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:database"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:topologies"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:instance-id"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-id"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:demand-circuit"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:ttl-security"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:lls"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:mtu-ignore"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:prefix-suppression"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:hello-timer"
{
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:wait-timer"
{
deviate not-supported;
}
/* OSPF database */
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv3" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:opaque" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:external" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:summary" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:router" {
deviate not-supported;
}
deviation "/ietf-r:routing/ietf-r:control-plane-protocols/ietf-r:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:header" {
deviate not-supported;
}
/* OSPF RPCs */
deviation "/ospf:clear-neighbor" {
deviate not-supported;
}
deviation "/ospf:clear-database" {
deviate not-supported;
}
/* OSPF Notifications */
deviation "/ospf:if-state-change" {
deviate not-supported;
}
deviation "/ospf:if-config-error" {
deviate not-supported;
}
deviation "/ospf:nbr-state-change" {
deviate not-supported;
}
deviation "/ospf:nbr-restart-helper-status-change" {
deviate not-supported;
}
deviation "/ospf:if-rx-bad-packet" {
deviate not-supported;
}
deviation "/ospf:lsdb-approaching-overflow" {
deviate not-supported;
}
deviation "/ospf:lsdb-overflow" {
deviate not-supported;
}
deviation "/ospf:nssa-translator-status-change" {
deviate not-supported;
}
deviation "/ospf:restart-status-change" {
deviate not-supported;
}
}
@@ -0,0 +1,522 @@
module infix-routing {
yang-version 1.1;
namespace "urn:infix:routing:ns:yang:1.0";
prefix infix-rt;
import ietf-routing {
prefix rt;
}
import ietf-ipv4-unicast-routing {
prefix v4ur;
}
import ietf-ipv6-unicast-routing {
prefix v6ur;
}
import ietf-ospf {
prefix ospf;
}
import ietf-interfaces {
prefix if;
}
import ietf-routing-types {
prefix rt-types;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Deviations and augments for ietf-routing and ietf-ospf.";
revision 2024-09-23 {
description "Augment static routes with optional route-preference (distance).";
reference "internal";
}
revision 2024-09-22 {
description "- Enable OSPF route metrics in RIB
- Enable 'active' route flag for RIB
- Enable 'last-updated' route field for RIB
- Augment route in RIB with 'installed' flag
- Aadjust YANG prefix for consistency: ietf-r: -> rt:";
reference "internal";
}
revision 2024-09-21 {
description "Drop RIB source-protocol deviation for standard, add kernel proto.";
reference "internal";
}
revision 2024-09-20 {
description "Deviate to only show supported routing types";
reference "internal";
}
revision 2024-09-13 {
description "Declare deviations for non-supported OSPF RPCs and Notifications";
reference "internal";
}
revision 2024-03-06 {
description "Obsolete leaf enable in favor for enabled in default-route-advertise";
reference "internal";
}
revision 2024-01-09 {
description "Add operational, area, bfd support
and other minor features";
reference "internal";
}
revision 2023-11-23 {
description "Limit to one instance per control plane protocol,
and change to infix (iproute2) specific source protocol names
for routes";
reference "internal";
}
revision 2023-10-27 {
description "Initial revision.";
reference "internal";
}
/* General routing */
deviation "/rt:routing/rt:interfaces" {
description "Initial limitation";
deviate not-supported;
}
deviation "/rt:routing/rt:router-id" {
description "Set in OSPF";
deviate not-supported;
}
deviation "/rt:routing-state" {
description "Obsolete";
deviate not-supported;
}
identity kernel {
base rt:routing-protocol;
description
"'Kernel' routing pseudo-protocol.";
}
typedef infix-distribute-protocol {
type enumeration {
enum ospf {
description "Redistribute Ospf";
}
enum static {
description "Redistribute Static routes";
}
enum connected {
description "Redistribute Connected routes";
}
}
}
identity infix-routing-type {
description "Infix routing type";
}
identity ospfv2 {
base ospf:ospfv2;
base infix-routing-type;
description "OSPv2 (IPv4) routing protocol";
}
identity static {
base rt:static;
base infix-routing-type;
description "Static route";
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:type" {
deviate replace {
type identityref {
base infix-routing-type;
}
}
}
/* Static routes */
typedef distance {
description "Capped IETF route-preference, 0-255, for distance.";
type rt:route-preference {
range "0..255";
}
}
grouping route-pref {
leaf route-preference {
description "Adjust preference between routes of same destination prefix.
This attribute, also known as 'administrative distance', allows
for selecting the preferred route among routes with the same
destination prefix. A smaller value indicates a route that is
more preferred.";
type distance;
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v4ur:ipv4/v4ur:route/v4ur:next-hop/v4ur:next-hop-options/v4ur:next-hop-list" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v6ur:ipv6/v6ur:route/v6ur:next-hop/v6ur:next-hop-options/v6ur:next-hop-list" {
deviate not-supported;
}
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v4ur:ipv4/v4ur:route" {
uses route-pref {
refine route-preference {
default "1";
}
}
}
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:static-routes/v6ur:ipv6/v6ur:route" {
uses route-pref {
refine route-preference {
default "1";
}
}
}
/* show routes */
deviation "/rt:routing/rt:ribs/rt:rib/rt:default-rib" {
deviate not-supported;
}
deviation "/rt:routing/rt:ribs/rt:rib/rt:active-route" {
deviate not-supported;
}
deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:tag" {
deviate not-supported;
}
deviation "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/ospf:route-type" {
deviate not-supported;
}
deviation "/rt:routing/rt:ribs/rt:rib/rt:description" {
deviate not-supported;
}
augment "/rt:routing/rt:ribs/rt:rib/rt:routes/rt:route/"
+ "rt:next-hop/rt:next-hop-options/rt:next-hop-list/"
+ "rt:next-hop-list/rt:next-hop" {
description "IETF use 'active' for selected routes within a protocol (RIB).
This augment adds an 'installed' leaf for marking routes that
are installed in the kernel FIB with the given next-hop.";
leaf installed {
description "The presence of this leaf indicates that the route is
installed in the kernel FIB with the given next-hop.";
type empty;
}
}
/* OSPF */
typedef infix-ospf-interface-type {
type enumeration {
enum broadcast {
description
"Specifies an OSPF broadcast multi-access network.";
}
enum point-to-point {
description
"Specifies an OSPF point-to-point network.";
}
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol" {
deviate add {
must "count(../control-plane-protocol[type = current()/type]) = 1" {
error-message "Only one instance per routing protocol is allowed.";
}
}
}
/*
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/rt:type" {
deviate add {
must "derived-from-or-self(., 'ospf:ospfv2') or "+
"derived-from-or-self(., 'rt:static')" {
description "Only OSPFv2 and Static routes are supported in Infix.";
}
}
}
*/
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf" {
description "ietf-ospf lack the setting to generate a default route";
container default-route-advertise {
description "Distribute default route to network";
leaf enable {
status obsolete;
description "Legacy, replaced by 'enabled'.";
type boolean;
}
leaf enabled {
description "Distribute default route";
type boolean;
}
leaf always {
description "Always distribute default route, even if no local default route exist.
If set to false, a default route will only be distributed if a default route exist
in the local routing table.";
type boolean;
}
}
}
augment "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf" {
description "ietf-ospf does not contain redistribution
7. How should route redistribution be configured? I see ietf-rip.yang has a separate
container for that purpose, but ietf-ospf.yang (and other IGP modules) don't do the
same. I also noticed the BGP model is using definition from ietf-routing-policy.yang.
Different vendors handle redistribution in different ways. This could be added with
an augmentation if there were agreement.
https://marc.info/?l=ms-ospf&m=166258444409552&w=2";
container redistribute {
list redistribute {
key "protocol";
description "Redistribute protocols into OSPF";
leaf protocol {
type infix-distribute-protocol;
description "Set protocol to redistribute";
}
}
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface" {
deviate add {
must "current()/name = 'lo' or count(../../../../ospf:areas/ospf:area/ospf:interfaces/ospf:interface[ospf:name=current()/name]) <= 1" {
error-message "Only one area per interface is allowed.";
}
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" {
deviate replace {
type infix-ospf-interface-type;
}
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:graceful-restart" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:enabled" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:spf-control" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:preference" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:stub-router" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:topologies" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:statistics" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database-control" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:lsa-log" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:spf-log" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:fast-reroute" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:node-tags" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:mpls" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:nsr" {
deviate not-supported;
}
/* OSPF Area */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:virtual-links" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:sham-links" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:ranges" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:database" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:topologies" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:statistics" {
deviate not-supported;
}
/* OSPF Local RIB */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:metric" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:local-rib/ospf:route/ospf:route-tag" {
deviate not-supported;
}
/* OSPF Area Interface */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:node-flag" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:local-multiplier"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:bfd/ospf:interval-config-type"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:priority"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:fast-reroute"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:authentication"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:statistics"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:statistics"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:neighbors/ospf:neighbor/ospf:cost"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:database"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:topologies"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:instance-id"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-id"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:demand-circuit"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:ttl-security"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:lls"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:mtu-ignore"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:prefix-suppression"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:hello-timer"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:wait-timer"
{
deviate not-supported;
}
/* OSPF database */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv3" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:opaque" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:external" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:summary" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:body/ospf:router" {
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:database/ospf:as-scope-lsa-type/ospf:as-scope-lsas/ospf:as-scope-lsa/ospf:version/ospf:ospfv2/ospf:ospfv2/ospf:header" {
deviate not-supported;
}
/* OSPF RPCs */
deviation "/ospf:clear-neighbor" {
deviate not-supported;
}
deviation "/ospf:clear-database" {
deviate not-supported;
}
/* OSPF Notifications */
deviation "/ospf:if-state-change" {
deviate not-supported;
}
deviation "/ospf:if-config-error" {
deviate not-supported;
}
deviation "/ospf:nbr-state-change" {
deviate not-supported;
}
deviation "/ospf:nbr-restart-helper-status-change" {
deviate not-supported;
}
deviation "/ospf:if-rx-bad-packet" {
deviate not-supported;
}
deviation "/ospf:lsdb-approaching-overflow" {
deviate not-supported;
}
deviation "/ospf:lsdb-overflow" {
deviate not-supported;
}
deviation "/ospf:nssa-translator-status-change" {
deviate not-supported;
}
deviation "/ospf:restart-status-change" {
deviate not-supported;
}
}
+23 -7
View File
@@ -331,13 +331,15 @@
</COMPL>
</PARAM>
<ACTION sym="script">
if [ "$KLISH_PARAM_ip" != "ipv4" ]; then
if [ "$KLISH_PARAM_ip" != "ipv6" ]; then
KLISH_PARAM_ip=ipv4
fi
fi
sysrepocfg -f json -X -d operational -x "/ietf-routing:routing/ribs" | \
/usr/libexec/statd/cli-pretty "show-routing-table" -i "$KLISH_PARAM_ip"
case $KLISH_PARAM_ip in
ipv4)
KLISH_PARAM_ip=ip
;;
ipv6)
KLISH_PARAM_ip=ipv6
;;
esac
show -p $KLISH_PARAM_ip route
</ACTION>
</COMMAND>
@@ -383,6 +385,20 @@
<COMMAND name="multicast" help="Show IGMP/MLD status" mode="switch">
<ACTION sym="script">mctl</ACTION>
</COMMAND>
<COMMAND name="route" help="Show IPv4 routing table" mode="switch">
<ACTION sym="script">show -p ip route</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
<COMMAND name="ipv6" help="Show IPv6 level status (Layer-3)">
<ACTION sym="script">ip -color -6 addr</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="route" help="Show IPv6 routing table" mode="switch">
<ACTION sym="script">show -p ipv6 route</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
+122 -31
View File
@@ -3,6 +3,8 @@ import json
import argparse
import sys
import re
from datetime import datetime, timezone
class Pad:
iface = 16
@@ -10,17 +12,33 @@ class Pad:
state = 12
data = 41
class PadMdb:
bridge = 7
vlan = 6
group = 20
ports = 45
class PadRoute:
prefix = 30
protocol = 10
next_hop = 30
dest = 30
pref = 8
next_hop = 30
protocol = 6
uptime = 9
@staticmethod
def set(ipv):
"""Set default padding based on the IP version ('ipv4' or 'ipv6')."""
if ipv == 'ipv4':
PadRoute.dest = 18
PadRoute.next_hop = 15
elif ipv == 'ipv6':
PadRoute.dest = 43
PadRoute.next_hop = 39
else:
raise ValueError(f"unknown IP version: {ipv}")
class PadSoftware:
name = 10
@@ -35,6 +53,7 @@ class PadUsbPort:
name = 20
state = 10
class Decore():
@staticmethod
def decorate(sgr, txt, restore="0"):
@@ -60,6 +79,7 @@ class Decore():
def underline(txt):
return Decore.decorate("4", txt, "24")
def get_json_data(default, indata, *args):
data = indata
for arg in args:
@@ -76,22 +96,30 @@ def remove_yang_prefix(key):
return parts[1]
return key
class Route:
def __init__(self,data,ip):
def __init__(self, data, ip):
self.data = data
self.ip = ip
self.prefix = data.get(f'ietf-{ip}-unicast-routing:destination-prefix', '')
self.protocol = data.get('source-protocol','')[14:]
self.pref = data.get('route-preference','')
self.protocol = data.get('source-protocol', '').split(':')[-1]
self.last_updated = data.get('last-updated', '')
self.active = data.get('active', False)
self.pref = data.get('route-preference', '')
self.metric = data.get('ietf-ospf:metric', 0)
self.next_hop = []
next_hop_list=get_json_data(None, self.data, 'next-hop', 'next-hop-list')
next_hop_list = get_json_data(None, self.data, 'next-hop', 'next-hop-list')
if next_hop_list:
for nh in next_hop_list["next-hop"]:
if(nh.get(f"ietf-{ip}-unicast-routing:address")):
self.next_hop.append(nh[f"ietf-{ip}-unicast-routing:address"])
elif(nh.get("outgoing-interface")):
self.next_hop.append(nh["outgoing-interface"])
if nh.get(f"ietf-{ip}-unicast-routing:address"):
hop = nh[f"ietf-{ip}-unicast-routing:address"]
elif nh.get("outgoing-interface"):
hop = nh["outgoing-interface"]
else:
self.next_hop.append("unspecified")
hop = "unspecified"
fib = nh.get('infix-routing:installed', False)
self.next_hop.append((hop, fib))
else:
interface = get_json_data(None, self.data, 'next-hop', 'outgoing-interface')
address = get_json_data(None, self.data, 'next-hop', f'ietf-{ip}-unicast-routing:next-hop-address')
@@ -105,17 +133,64 @@ class Route:
self.next_hop.append(special)
else:
self.next_hop.append("unspecified")
def get_distance_and_metric(self):
if isinstance(self.pref, int):
distance = self.pref
metric = self.metric
else:
distance, metric = 0, 0
return distance, metric
def datetime2uptime(self):
"""Convert 'last-updated' string to uptime in AAhBBmCCs format."""
if not self.last_updated:
return "0h0m0s"
# Replace the colon in the timezone offset (e.g., +00:00 -> +0000)
pos = self.last_updated.rfind('+')
if pos != -1:
adjusted = self.last_updated[:pos] + self.last_updated[pos:].replace(':', '')
else:
adjusted = self.last_updated
last_updated = datetime.strptime(adjusted, '%Y-%m-%dT%H:%M:%S%z')
current_time = datetime.now(timezone.utc)
uptime_delta = current_time - last_updated
hours, remainder = divmod(int(uptime_delta.total_seconds()), 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours}h{minutes}m{seconds}s"
def print(self):
row = f"{self.prefix:<{PadRoute.prefix}}"
row += f"{self.next_hop[0]:<{PadRoute.next_hop}}"
row += f"{self.pref:>{PadRoute.pref}} "
row += f"{self.protocol:<{PadRoute.protocol}}"
PadRoute.set(self.ip)
distance, metric = self.get_distance_and_metric()
uptime = self.datetime2uptime()
pref = f"{distance}/{metric}"
hop, fib = self.next_hop[0]
row = ">" if self.active else " "
row += "*" if fib else " "
row += " "
row += f"{self.prefix:<{PadRoute.dest}} "
row += f"{pref:>{PadRoute.pref}} "
row += f"{hop:<{PadRoute.next_hop}} "
row += f"{self.protocol:<{PadRoute.protocol}} "
row += f"{uptime:>{PadRoute.uptime}}"
print(row)
for nh in self.next_hop[1:]:
row = f"{'':<{PadRoute.prefix}}"
row += f"{nh:<{PadRoute.next_hop}}"
hop, fib = nh
row = " "
row += "*" if fib else " "
row += " "
row += f"{'':<{PadRoute.dest}} "
row += f"{'':>{PadRoute.pref}} "
row += f"{hop:<{PadRoute.next_hop}} "
print(row)
class Software:
"""Software bundle class """
def __init__(self, data):
@@ -431,6 +506,7 @@ class Iface:
row += f"{ports}"
print(row)
def find_iface(_ifaces, name):
for _iface in [Iface(data) for data in _ifaces]:
if _iface.name == name:
@@ -438,8 +514,11 @@ def find_iface(_ifaces, name):
return False
def version_sort(iface):
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)', iface['name'])]
return [int(x) if x.isdigit() else x for x in re.split(r'(\d+)',
iface['name'])]
def print_interface(iface):
iface.pr_name()
@@ -447,6 +526,7 @@ def print_interface(iface):
iface.pr_proto_ipv4()
iface.pr_proto_ipv6()
def pr_interface_list(json):
hdr = (f"{'INTERFACE':<{Pad.iface}}"
f"{'PROTOCOL':<{Pad.proto}}"
@@ -455,8 +535,8 @@ def pr_interface_list(json):
print(Decore.invert(hdr))
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort)
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"],
key=version_sort)
iface = find_iface(ifaces, "lo")
if iface:
print_interface(iface)
@@ -484,6 +564,7 @@ def pr_interface_list(json):
continue
print_interface(iface)
def show_interfaces(json, name):
if name:
if not json.get("ietf-interfaces:interfaces"):
@@ -497,16 +578,19 @@ def show_interfaces(json, name):
iface.pr_iface()
else:
if not json.get("ietf-interfaces:interfaces"):
print(f"Error, top level \"ietf-interfaces:interfaces\" missing")
print("Error, top level \"ietf-interfaces:interfaces\" missing")
sys.exit(1)
pr_interface_list(json)
def show_bridge_mdb(json):
header_printed = False
if not json.get("ietf-interfaces:interfaces"):
print(f"Error, top level \"ietf-interfaces:interface\" missing")
print("Error, top level \"ietf-interfaces:interface\" missing")
sys.exit(1)
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"], key=version_sort)
ifaces = sorted(json["ietf-interfaces:interfaces"]["interface"],
key=version_sort)
for iface in [Iface(data) for data in ifaces]:
if iface.type != "infix-if-type:bridge":
continue
@@ -520,25 +604,31 @@ def show_bridge_mdb(json):
iface.pr_mdb(iface.name)
iface.pr_vlans_mdb(iface.name)
def show_routing_table(json, ip):
if not json.get("ietf-routing:routing"):
print(f"Error, top level \"ietf-routing:routing\" missing")
print("Error, top level \"ietf-routing:routing\" missing")
sys.exit(1)
hdr = (f"{'PREFIX':<{PadRoute.prefix}}"
f"{'NEXT-HOP':<{PadRoute.next_hop}}"
f"{'PREF':>{PadRoute.pref}} "
f"{'PROTOCOL':<{PadRoute.protocol}}")
PadRoute.set(ip)
hdr = (f" {'DESTINATION':<{PadRoute.dest}} "
f"{'PREF':>{PadRoute.pref}} "
f"{'NEXT-HOP':<{PadRoute.next_hop}} "
f"{'PROTO':<{PadRoute.protocol}} "
f"{'UPTIME':>{PadRoute.uptime}}")
print(Decore.invert(hdr))
for rib in get_json_data({}, json, 'ietf-routing:routing','ribs', 'rib'):
for rib in get_json_data({}, json, 'ietf-routing:routing', 'ribs', 'rib'):
if rib["name"] != ip:
continue;
continue
routes = get_json_data(None, rib, "routes", "route")
if routes:
for r in routes:
route = Route(r, ip)
route.print()
def find_slot(_slots, name):
for _slot in [Software(data) for data in _slots]:
if _slot.name == name:
@@ -546,6 +636,7 @@ def find_slot(_slots, name):
return False
def show_software(json, name):
if not json.get("ietf-system:system-state", "infix-system:software"):
print("Error, cannot find infix-system:software")
+30 -11
View File
@@ -6,21 +6,38 @@
# This makes the parsing for the operational parts of YANG model more easy
#
import sys
import json
import subprocess
def main():
iface_out=subprocess.check_output("vtysh -c 'show ip ospf interface json'", shell=True)
ospf_out=subprocess.check_output("vtysh -c 'show ip ospf json'", shell=True)
neighbor_out=subprocess.check_output("vtysh -c 'show ip ospf neighbor detail json'", shell=True)
try:
interfaces=json.loads(iface_out)
ospf=json.loads(ospf_out)
neighbors=json.loads(neighbor_out)
except json.JSONDecodeError:
return {} # OSPFD is most likely not running
for ifname,iface in interfaces["interfaces"].items():
def run_json_cmd(cmd, default=None, check=True):
"""Run a command (array of args) with JSON output and return the JSON"""
try:
cmd.append("2>/dev/null")
result = subprocess.run(cmd, check=check, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, text=True)
output = result.stdout
data = json.loads(output)
except (subprocess.CalledProcessError, json.JSONDecodeError):
if default is not None:
return default
raise
return data
def main():
ospf_interfaces = ['sudo', 'vtysh', '-c', "show ip ospf interface json"]
ip_ospf = ['sudo', 'vtysh', '-c', "show ip ospf json"]
ospf_neigh = ['sudo', 'vtysh', '-c', "show ip ospf neighbor detail json"]
try:
interfaces = run_json_cmd(ospf_interfaces)
ospf = run_json_cmd(ip_ospf)
neighbors = run_json_cmd(ospf_neigh)
except (subprocess.CalledProcessError, json.JSONDecodeError):
return {}
for ifname, iface in interfaces["interfaces"].items():
iface["name"] = ifname
iface["neighbors"] = []
for area_id in ospf["areas"]:
@@ -62,5 +79,7 @@ def main():
print(json.dumps(ospf))
if __name__ == "__main__":
main()
sys.exit(0)
+82 -42
View File
@@ -6,7 +6,7 @@ import json
import sys # (built-in module)
import os
import argparse
from datetime import datetime
from datetime import datetime, timedelta, timezone
TESTPATH = ""
logger = None
@@ -137,8 +137,9 @@ def run_json_cmd(cmd, testfile, default=None, check=True):
return default
raise
except json.JSONDecodeError as err:
logger.error(f"failed parsing JSON output of command: {' '.join(cmd)}"
f", error: {err}")
if check is True:
logger.error("failed parsing JSON output of command: "
f"{' '.join(cmd)}, error: {err}")
if default is not None:
return default
raise
@@ -229,8 +230,29 @@ def add_hardware(hw_out):
insert(hw_out, "component", components)
def uptime2datetime(uptime):
"""Convert uptime (HH:MM:SS) to YANG format (YYYY-MM-DDTHH:MM:SS+00:00)"""
h, m, s = map(int, uptime.split(':'))
uptime_delta = timedelta(hours=h, minutes=m, seconds=s)
current_time = datetime.now(timezone.utc)
last_updated = current_time - uptime_delta
date_timestd = last_updated.strftime('%Y-%m-%dT%H:%M:%S%z')
return date_timestd[:-2] + ':' + date_timestd[-2:]
def get_routes(routes, proto, data):
"""Populate routes"""
"""Populate routes from vtysh JSON output"""
# Mapping of FRR protocol names to IETF routing-protocol
pmap = {
'kernel': 'infix-routing:kernel',
'connected': 'direct',
'static': 'static',
'ospf': 'ietf-ospf:ospfv2',
'ospf6': 'ietf-ospf:ospfv3',
}
out = {}
out["route"] = []
@@ -241,58 +263,75 @@ def get_routes(routes, proto, data):
default = "::/0"
host_prefix_length = "128"
for entry in data:
new = {}
if entry['dst'] == "default":
entry['dst'] = default
if entry['dst'].find('/') == -1:
entry['dst'] = entry['dst'] + "/" + host_prefix_length
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = entry['dst']
new['source-protocol'] = "infix-routing:" + entry['protocol']
if entry.get("metric"):
new['route-preference'] = entry['metric']
else:
new['route-preference'] = 0
for prefix, entries in data.items():
for route in entries:
new = {}
dst = route.get('prefix', default)
if '/' not in dst:
dst = f"{dst}/{route.get('prefixLen', host_prefix_length)}"
new[f'ietf-{proto}-unicast-routing:destination-prefix'] = dst
frr = route.get('protocol', 'infix-routing:kernel')
new['source-protocol'] = pmap.get(frr, 'infix-routing:kernel')
new['route-preference'] = route.get('distance', 0)
# Metric only available in the model for OSPF routes
if 'ospf' in frr:
new['ietf-ospf:metric'] = route.get('metric', 0)
# See https://datatracker.ietf.org/doc/html/rfc7951#section-6.9
# for details on how presence leaves are encoded in JSON: [null]
if route.get('selected', False):
new['active'] = [None]
new['last-updated'] = uptime2datetime(route.get('uptime', 0))
installed = route.get('installed', False)
if entry.get('nexthops'):
next_hops = []
for hop in entry.get('nexthops'):
for hop in route.get('nexthops', []):
next_hop = {}
if hop.get("dev"):
next_hop['outgoing-interface'] = hop['dev']
if hop.get("gateway"):
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['gateway']
if hop.get('ip'):
next_hop[f'ietf-{proto}-unicast-routing:address'] = hop['ip']
elif hop.get('interfaceName'):
next_hop['outgoing-interface'] = hop['interfaceName']
# See zebra/zebra_vty.c:re_status_outpupt_char()
if installed and hop.get('fib', False):
next_hop['infix-routing:installed'] = [None]
next_hops.append(next_hop)
insert(new, 'next-hop', 'next-hop-list', 'next-hop', next_hops)
else:
next_hop = {}
if entry['type'] == "blackhole":
next_hop['special-next-hop'] = "blackhole"
if entry['type'] == "unreachable":
next_hop['special-next-hop'] = "unreachable"
if entry['type'] == "unicast":
if entry.get("dev"):
next_hop['outgoing-interface'] = entry['dev']
if entry.get("gateway"):
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = entry['gateway']
if next_hops:
new['next-hop'] = {'next-hop-list': {'next-hop': next_hops}}
else:
next_hop = {}
protocol = route.get('protocol', 'unicast')
if protocol == "blackhole":
next_hop['special-next-hop'] = "blackhole"
elif protocol == "unreachable":
next_hop['special-next-hop'] = "unreachable"
else:
if route.get('interfaceName'):
next_hop['outgoing-interface'] = route['interfaceName']
if route.get('nexthop'):
next_hop[f'ietf-{proto}-unicast-routing:next-hop-address'] = route['nexthop']
new['next-hop'] = next_hop
new['next-hop'] = next_hop
out['route'].append(new)
out['route'].append(new)
insert(routes, 'routes', out)
def add_ipv4_route(routes):
"""Fetch IPv4 routes from kernel and populate tree"""
data = run_json_cmd(['ip', '-4', '-s', '-d', '-j', 'route'], "ip-4-route.json")
data = run_json_cmd(['sudo', 'vtysh', '-c', "show ip route json"],
"vtysh-ip4-route.json", check=False, default={})
get_routes(routes, "ipv4", data)
def add_ipv6_route(routes):
"""Fetch IPv6 routes from kernel and populate tree"""
data = run_json_cmd(['ip', '-6', '-s', '-d', '-j', 'route'], "ip-6-route.json")
data = run_json_cmd(['sudo', 'vtysh', '-c', "show ipv6 route json"],
"vtysh-ip6-route.json", check=False, default={})
get_routes(routes, "ipv6", data)
@@ -307,9 +346,11 @@ def frr_to_ietf_neighbor_state(state):
def add_ospf_routes(ospf):
"""Fetch OSPF routes from Frr"""
cmd = ['vtysh', '-c', "show ip ospf rout json"]
data = run_json_cmd(cmd, "")
routes = []
data = run_json_cmd(cmd, "", check=False, default=[])
if data == []:
return # No OSPF routes available
routes = []
for prefix, info in data.items():
if prefix.find("/") == -1: # Ignore router IDs
continue
@@ -348,8 +389,7 @@ def add_ospf_routes(ospf):
def add_ospf(control_protocols):
"""Populate OSPF status"""
cmd = ['/usr/libexec/statd/ospf-status']
data = run_json_cmd(cmd, "")
data = run_json_cmd(cmd, "", check=False, default={})
if data == {}:
return # No OSPF data available
+11 -13
View File
@@ -1,13 +1,11 @@
PREFIX NEXT-HOP PREF PROTOCOL 
10.0.0.1/32 10.0.13.1 20 ospf
10.0.0.2/32 10.0.23.1 20 ospf
10.0.0.4/32 10.0.13.1 20 ospf
10.0.23.1
10.0.12.0/30 10.0.13.1 20 ospf
10.0.23.1
10.0.13.0/30 eth3 0 kernel
10.0.23.0/30 eth4 0 kernel
10.0.24.0/30 10.0.23.1 20 ospf
10.0.41.0/30 10.0.13.1 20 ospf
192.168.4.0/24 10.0.13.1 20 ospf
10.0.23.1
 DESTINATION PREF NEXT-HOP PROTO UPTIME
>* 0.0.0.0/0 110/2 10.0.23.1 ospfv2 0h0m0s
>* 10.0.0.1/32 110/4000 10.0.13.1 ospfv2 0h0m0s
10.0.0.3/32 110/0 lo ospfv2 0h0m0s
>* 10.0.0.3/32 0/0 lo direct 0h0m0s
10.0.13.0/30 110/2000 e5 ospfv2 0h0m0s
>* 10.0.13.0/30 0/0 e5 direct 0h0m0s
10.0.23.0/30 110/1 e6 ospfv2 0h0m0s
>* 10.0.23.0/30 0/0 e6 direct 0h0m0s
192.168.3.0/24 110/1 e2 ospfv2 0h0m0s
>* 192.168.3.0/24 0/0 e2 direct 0h0m0s
+11 -10
View File
@@ -1,10 +1,11 @@
PREFIX NEXT-HOP PREF PROTOCOL 
2001:db8:3c4d:50::/64 eth4 256 kernel
2001:db8:3c4d:200::1/128 lo 256 kernel
fe80::/64 eth5 256 kernel
fe80::/64 eth3 256 kernel
fe80::/64 eth2 256 kernel
fe80::/64 eth1 256 kernel
fe80::/64 eth0 256 kernel
fe80::/64 eth4 256 kernel
::/0 2001:db8:3c4d:50::1 20 static
 DESTINATION PREF NEXT-HOP PROTO UPTIME
>* ::/0 1/0 2001:db8:3c4d:50::1 static 0h0m0s
>* 2001:db8:3c4d:50::/64 0/0 e6 direct 0h0m0s
>* 2001:db8:3c4d:200::1/128 0/0 lo direct 0h0m0s
* fe80::/64 0/0 e7 direct 0h0m0s
* fe80::/64 0/0 e6 direct 0h0m0s
* fe80::/64 0/0 e5 direct 0h0m0s
* fe80::/64 0/0 e4 direct 0h0m0s
* fe80::/64 0/0 e3 direct 0h0m0s
* fe80::/64 0/0 e2 direct 0h0m0s
>* fe80::/64 0/0 e1 direct 0h0m0s
-207
View File
@@ -1,207 +0,0 @@
[
{
"type": "unicast",
"dst": "10.0.0.1",
"nhid": 33,
"gateway": "10.0.13.1",
"dev": "eth3",
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 33,
"gateway": "10.0.13.1",
"dev": "eth3",
"scope": "link",
"protocol": "zebra",
"flags": []
}
},
{
"type": "unicast",
"dst": "10.0.0.2",
"nhid": 34,
"gateway": "10.0.23.1",
"dev": "eth4",
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 34,
"gateway": "10.0.23.1",
"dev": "eth4",
"scope": "link",
"protocol": "zebra",
"flags": []
}
},
{
"type": "unicast",
"dst": "10.0.0.4",
"nhid": 35,
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 35,
"group": [
{
"id": 33
},
{
"id": 34
}
],
"scope": "global",
"protocol": "zebra",
"flags": []
},
"nexthops": [
{
"gateway": "10.0.13.1",
"dev": "eth3",
"weight": 1,
"flags": []
},
{
"gateway": "10.0.23.1",
"dev": "eth4",
"weight": 1,
"flags": []
}
]
},
{
"type": "unicast",
"dst": "10.0.12.0/30",
"nhid": 35,
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 35,
"group": [
{
"id": 33
},
{
"id": 34
}
],
"scope": "global",
"protocol": "zebra",
"flags": []
},
"nexthops": [
{
"gateway": "10.0.13.1",
"dev": "eth3",
"weight": 1,
"flags": []
},
{
"gateway": "10.0.23.1",
"dev": "eth4",
"weight": 1,
"flags": []
}
]
},
{
"type": "unicast",
"dst": "10.0.13.0/30",
"dev": "eth3",
"protocol": "kernel",
"scope": "link",
"prefsrc": "10.0.13.2",
"flags": []
},
{
"type": "unicast",
"dst": "10.0.23.0/30",
"dev": "eth4",
"protocol": "kernel",
"scope": "link",
"prefsrc": "10.0.23.2",
"flags": []
},
{
"type": "unicast",
"dst": "10.0.24.0/30",
"nhid": 34,
"gateway": "10.0.23.1",
"dev": "eth4",
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 34,
"gateway": "10.0.23.1",
"dev": "eth4",
"scope": "link",
"protocol": "zebra",
"flags": []
}
},
{
"type": "unicast",
"dst": "10.0.41.0/30",
"nhid": 33,
"gateway": "10.0.13.1",
"dev": "eth3",
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 33,
"gateway": "10.0.13.1",
"dev": "eth3",
"scope": "link",
"protocol": "zebra",
"flags": []
}
},
{
"type": "unicast",
"dst": "192.168.4.0/24",
"nhid": 35,
"protocol": "ospf",
"scope": "global",
"metric": 20,
"flags": [],
"nh_info": {
"id": 35,
"group": [
{
"id": 33
},
{
"id": 34
}
],
"scope": "global",
"protocol": "zebra",
"flags": []
},
"nexthops": [
{
"gateway": "10.0.13.1",
"dev": "eth3",
"weight": 1,
"flags": []
},
{
"gateway": "10.0.23.1",
"dev": "eth4",
"weight": 1,
"flags": []
}
]
}
]
@@ -1 +0,0 @@
[{"type":"unicast","dst":"2001:db8:3c4d:50::/64","dev":"eth4","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"2001:db8:3c4d:200::1","dev":"lo","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth5","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth3","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth2","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth1","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth0","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"fe80::/64","dev":"eth4","protocol":"kernel","scope":"global","metric":256,"flags":[],"pref":"medium"},{"type":"unicast","dst":"default","nhid":23,"gateway":"2001:db8:3c4d:50::1","dev":"eth4","protocol":"static","scope":"global","metric":20,"flags":[],"pref":"medium","nh_info":{"id":23,"gateway":"2001:db8:3c4d:50::1","dev":"eth4","scope":"link","protocol":"zebra","flags":[]}}]
@@ -0,0 +1,302 @@
{
"0.0.0.0/0":[
{
"prefix":"0.0.0.0/0",
"prefixLen":0,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":110,
"metric":2,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":42,
"installedNexthopGroupId":42,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"ip":"10.0.23.1",
"afi":"ipv4",
"interfaceIndex":7,
"interfaceName":"e6",
"active":true,
"weight":1
}
]
}
],
"10.0.0.1/32":[
{
"prefix":"10.0.0.1/32",
"prefixLen":32,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":110,
"metric":4000,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":43,
"installedNexthopGroupId":43,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"ip":"10.0.13.1",
"afi":"ipv4",
"interfaceIndex":6,
"interfaceName":"e5",
"active":true,
"weight":1
}
]
}
],
"10.0.0.3/32":[
{
"prefix":"10.0.0.3/32",
"prefixLen":32,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"distance":110,
"metric":0,
"table":254,
"internalStatus":0,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":30,
"uptime":"00:00:00",
"nexthops":[
{
"flags":1,
"directlyConnected":true,
"interfaceIndex":1,
"interfaceName":"lo",
"active":true,
"weight":1
}
]
},
{
"prefix":"10.0.0.3/32",
"prefixLen":32,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":25,
"installedNexthopGroupId":25,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":1,
"interfaceName":"lo",
"active":true
}
]
}
],
"10.0.13.0/30":[
{
"prefix":"10.0.13.0/30",
"prefixLen":30,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"distance":110,
"metric":2000,
"table":254,
"internalStatus":0,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":31,
"uptime":"00:00:00",
"nexthops":[
{
"flags":1,
"directlyConnected":true,
"interfaceIndex":6,
"interfaceName":"e5",
"active":true,
"weight":1
}
]
},
{
"prefix":"10.0.13.0/30",
"prefixLen":30,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":20,
"installedNexthopGroupId":20,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":6,
"interfaceName":"e5",
"active":true
}
]
}
],
"10.0.23.0/30":[
{
"prefix":"10.0.23.0/30",
"prefixLen":30,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"distance":110,
"metric":1,
"table":254,
"internalStatus":0,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":32,
"uptime":"00:00:00",
"nexthops":[
{
"flags":1,
"directlyConnected":true,
"interfaceIndex":7,
"interfaceName":"e6",
"active":true,
"weight":1
}
]
},
{
"prefix":"10.0.23.0/30",
"prefixLen":30,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":23,
"installedNexthopGroupId":23,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":7,
"interfaceName":"e6",
"active":true
}
]
}
],
"192.168.3.0/24":[
{
"prefix":"192.168.3.0/24",
"prefixLen":24,
"protocol":"ospf",
"vrfId":0,
"vrfName":"default",
"distance":110,
"metric":1,
"table":254,
"internalStatus":0,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":33,
"uptime":"00:00:00",
"nexthops":[
{
"flags":1,
"directlyConnected":true,
"interfaceIndex":3,
"interfaceName":"e2",
"active":true,
"weight":1
}
]
},
{
"prefix":"192.168.3.0/24",
"prefixLen":24,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":17,
"installedNexthopGroupId":17,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":3,
"interfaceName":"e2",
"active":true
}
]
}
]
}
@@ -0,0 +1,294 @@
{
"::/0":[
{
"prefix":"::/0",
"prefixLen":0,
"protocol":"static",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":1,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":73,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":24,
"installedNexthopGroupId":24,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"ip":"2001:db8:3c4d:50::1",
"afi":"ipv6",
"interfaceIndex":7,
"interfaceName":"e6",
"active":true,
"weight":1
}
]
}
],
"2001:db8:3c4d:50::/64":[
{
"prefix":"2001:db8:3c4d:50::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":15,
"installedNexthopGroupId":15,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":7,
"interfaceName":"e6",
"active":true
}
]
}
],
"2001:db8:3c4d:200::1/128":[
{
"prefix":"2001:db8:3c4d:200::1/128",
"prefixLen":128,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":13,
"installedNexthopGroupId":13,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":1,
"interfaceName":"lo",
"active":true
}
]
}
],
"fe80::/64":[
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":14,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":8,
"interfaceName":"e7",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":15,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":7,
"interfaceName":"e6",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":16,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":6,
"interfaceName":"e5",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":17,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":5,
"interfaceName":"e4",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":18,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":4,
"interfaceName":"e3",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":0,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":19,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":3,
"interfaceName":"e2",
"active":true
}
]
},
{
"prefix":"fe80::/64",
"prefixLen":64,
"protocol":"connected",
"vrfId":0,
"vrfName":"default",
"selected":true,
"destSelected":true,
"distance":0,
"metric":0,
"installed":true,
"table":254,
"internalStatus":16,
"internalFlags":8,
"internalNextHopNum":1,
"internalNextHopActiveNum":1,
"nexthopGroupId":20,
"installedNexthopGroupId":20,
"uptime":"00:00:00",
"nexthops":[
{
"flags":3,
"fib":true,
"directlyConnected":true,
"interfaceIndex":2,
"interfaceName":"e1",
"active":true
}
]
}
]
}
@@ -1,23 +1,28 @@
=== IPv4 linklocal
=== IPv4 link-local
==== Description
Verifies that linklocal (ZeroConf) work as expected
Verifies that link-local (IPv4LL/ZeroConf) address assignment work as
expected. Checks random address, the request-address setting, and
address removal on autoconf disable.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/ipv4_autoconf/topology.png[IPv4 linklocal topology]
image::../../test/case/ietf_interfaces/ipv4_autoconf/topology.png[IPv4 link-local topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::ipv4_autoconf/topology.png[IPv4 linklocal topology]
image::ipv4_autoconf/topology.png[IPv4 link-local topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.png[IPv4 linklocal topology]
image::topology.png[IPv4 link-local topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Initialize
. Configure an interface with IPv4 ZeroConf IP
. Wait for linklocal address on interface
. Verify link local address exist on target:mgmt
. Configure target:mgmt with a specific IPv4 ZeroConf IP
. Verify target:mgmt has link local address 169.254.42.42
. Remove IPv4 link-local addresses from target:mgmt
<<<
@@ -3,24 +3,46 @@
# PC ---- e0: 10.0.0.2
#
"""
IPv4 linklocal
IPv4 link-local
Verifies that linklocal (ZeroConf) work as expected
Verifies that link-local (IPv4LL/ZeroConf) address assignment work as
expected. Checks random address, the request-address setting, and
address removal on autoconf disable.
"""
import ipaddress
import infamy
import time
import infamy.iface
from infamy.util import until
def has_linklocal(target, iface):
"""Check if interface as a linklocal address"""
def has_linklocal(target, iface, request=None):
"""Check if interface as a link-local address"""
addrs = infamy.iface.get_ipv4_address(target, iface)
if not addrs:
return False
for addr in addrs:
if addr['origin'] == "random":
return True
if request is None:
print(f"Got IP address {addr['ip']}")
return True
if addr['ip'] == request:
print(f"Got requested IP address {addr['ip']}")
return True
return False
def no_linklocal(target, iface):
"""Check if interface has no link-local address in the 169.254/16 range"""
addrs = infamy.iface.get_ipv4_address(target, iface)
if not addrs:
return True
for addr in addrs:
ip = ipaddress.IPv4Address(addr['ip'])
if ip in ipaddress.IPv4Network('169.254.0.0/16'):
return False
return True
with infamy.Test() as test:
with test.step("Initialize"):
@@ -46,7 +68,37 @@ with infamy.Test() as test:
}
})
with test.step("Wait for linklocal address on interface"):
until(lambda: has_linklocal(target, tport), attempts=30)
with test.step("Verify link local address exist on target:mgmt"):
until(lambda: has_linklocal(target, tport), attempts=30)
with test.step("Configure target:mgmt with a specific IPv4 ZeroConf IP"):
_, tport = env.ltop.xlate("target", "data")
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
{
"name": tport,
"enabled": True,
"ipv4": {
"autoconf": {
"enabled": True,
"request-address": "169.254.42.42"
}
}
}
]
}
})
with test.step("Verify target:mgmt has link local address 169.254.42.42"):
until(lambda: has_linklocal(target, tport, request="169.254.42.42"),
attempts=30)
with test.step("Remove IPv4 link-local addresses from target:mgmt"):
xpath = f"/ietf-interfaces:interfaces/interface[name='{tport}']" \
"/ietf-ip:ipv4/infix-ip:autoconf"
target.delete_xpath(xpath)
until(lambda: no_linklocal(target, tport), attempts=30)
test.succeed()
+2 -2
View File
@@ -171,8 +171,8 @@ with infamy.Test() as test:
config_target2(R2, R2link))
with test.step("Wait for OSPF routes"):
print("Waiting for OSPF routes..")
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Test connectivity from PC:data to 192.168.200.1"):
_, hport0 = env.ltop.xlate("PC", "data")
+121 -117
View File
@@ -197,30 +197,30 @@ def config_target2(target, ring1, ring2, cross):
"enabled": True,
"ipv4": {
"address": [{
"ip": "10.0.0.2",
"prefix-length": 32
},{
"ip": "11.0.9.1",
"prefix-length": 24
},{
"ip": "11.0.10.1",
"prefix-length": 24
},{
"ip": "11.0.11.1",
"prefix-length": 24
},{
"ip": "11.0.12.1",
"prefix-length": 24
},{
"ip": "11.0.13.1",
"prefix-length": 24
},{
"ip": "11.0.14.1",
"prefix-length": 24
},{
"ip": "11.0.15.1",
"prefix-length": 24
}]
"ip": "10.0.0.2",
"prefix-length": 32
}, {
"ip": "11.0.9.1",
"prefix-length": 24
}, {
"ip": "11.0.10.1",
"prefix-length": 24
}, {
"ip": "11.0.11.1",
"prefix-length": 24
}, {
"ip": "11.0.12.1",
"prefix-length": 24
}, {
"ip": "11.0.13.1",
"prefix-length": 24
}, {
"ip": "11.0.14.1",
"prefix-length": 24
}, {
"ip": "11.0.15.1",
"prefix-length": 24
}]
}
}
]
@@ -243,7 +243,7 @@ def config_target2(target, ring1, ring2, cross):
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces":{
"interfaces": {
"interface": [{
"bfd": {
"enabled": True
@@ -251,32 +251,30 @@ def config_target2(target, ring1, ring2, cross):
"name": ring2,
"hello-interval": 1,
"enabled": True
},{
}, {
"name": "lo",
"enabled": True
}]
}
},
{
}, {
"area-id": "0.0.0.1",
"area-type": "nssa-area",
"summary": False,
"interfaces":{
"interfaces": {
"interface": [{
"bfd": {
"enabled": True
},
"name": ring1,
"hello-interval": 1,
},{
}, {
"name": "lo",
"enabled": True
}]
}
},
{
}, {
"area-id": "0.0.0.2",
"interfaces":{
"interfaces": {
"interface": [{
"bfd": {
"enabled": True
@@ -284,7 +282,7 @@ def config_target2(target, ring1, ring2, cross):
"name": cross,
"hello-interval": 1,
"cost": 2000
},{
}, {
"name": "lo",
"enabled": True
}]
@@ -297,6 +295,7 @@ def config_target2(target, ring1, ring2, cross):
}
})
def config_target3(target, ring2, cross, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -366,7 +365,7 @@ def config_target3(target, ring2, cross, link):
"area-id": "0.0.0.1",
"area-type": "nssa-area",
"summary": False,
"interfaces":{
"interfaces": {
"interface": [{
"bfd": {
"enabled": True
@@ -375,18 +374,18 @@ def config_target3(target, ring2, cross, link):
"hello-interval": 1,
"enabled": True,
"cost": 2000
},{
}, {
"bfd": {
"enabled": True
},
"name": ring2,
"hello-interval": 1,
"enabled": True
},{
}, {
"name": link,
"enabled": True,
"passive": True
},{
}, {
"name": "lo",
"enabled": True
}]
@@ -399,6 +398,7 @@ def config_target3(target, ring2, cross, link):
}
})
def config_target4(target, ring1, cross, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -429,8 +429,8 @@ def config_target4(target, ring1, cross, link):
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.4.1",
"prefix-length": 24
"ip": "192.168.4.1",
"prefix-length": 24
}]
}
},
@@ -439,8 +439,8 @@ def config_target4(target, ring1, cross, link):
"enabled": True,
"ipv4": {
"address": [{
"ip": "10.0.0.4",
"prefix-length": 32
"ip": "10.0.0.4",
"prefix-length": 32
}]
}
}
@@ -459,48 +459,48 @@ def config_target4(target, ring1, cross, link):
"control-plane-protocols": {
"control-plane-protocol": [
{
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"redistribute": {
"redistribute": [
{
"protocol": "connected"
}]
},
"areas": {
"area": [{
"area-id": "0.0.0.2",
"interfaces":{
"interface": [{
"bfd": {
"enabled": True
},
"name": ring1,
"hello-interval": 1,
"enabled": True
},{
"bfd": {
"enabled": True
},
"name": cross,
"hello-interval": 1,
"enabled": True,
"cost": 5000
},{
"name": "lo",
"enabled": True
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"redistribute": {
"redistribute": [{
"protocol": "connected"
}]
}
}]
},
"areas": {
"area": [{
"area-id": "0.0.0.2",
"interfaces": {
"interface": [{
"bfd": {
"enabled": True
},
"name": ring1,
"hello-interval": 1,
"enabled": True
}, {
"bfd": {
"enabled": True
},
"name": cross,
"hello-interval": 1,
"enabled": True,
"cost": 5000
}, {
"name": "lo",
"enabled": True
}]
}
}]
}
}
}
}
]
}
}
})
def disable_link(target, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -512,6 +512,7 @@ def disable_link(target, link):
}
})
with infamy.Test() as test:
with test.step("Configure targets"):
env = infamy.Env()
@@ -520,7 +521,6 @@ with infamy.Test() as test:
R3 = env.attach("R3", "mgmt")
R4 = env.attach("R4", "mgmt")
_, R1ring1 = env.ltop.xlate("R1", "ring1")
_, R1ring2 = env.ltop.xlate("R1", "ring2")
_, R2ring1 = env.ltop.xlate("R2", "ring1")
@@ -528,17 +528,18 @@ with infamy.Test() as test:
_, R3ring2 = env.ltop.xlate("R3", "ring2")
_, R4ring1 = env.ltop.xlate("R4", "ring1")
_, R3data = env.ltop.xlate("R3", "data")
_, R4data = env.ltop.xlate("R4", "data")
_, R3data = env.ltop.xlate("R3", "data")
_, R4data = env.ltop.xlate("R4", "data")
_, R1cross = env.ltop.xlate("R1", "cross")
_, R2cross = env.ltop.xlate("R2", "cross")
_, R3cross = env.ltop.xlate("R3", "cross")
_, R4cross = env.ltop.xlate("R4", "cross")
_, R1cross = env.ltop.xlate("R1", "cross")
_, R2cross = env.ltop.xlate("R2", "cross")
_, R3cross = env.ltop.xlate("R3", "cross")
_, R4cross = env.ltop.xlate("R4", "cross")
parallel(config_target1(R1, R1ring1, R1ring2, R1cross),
config_target2(R2, R2ring1, R2ring2, R2cross),
config_target3(R3, R3ring2, R3cross, R3data),
config_target4(R4, R4ring1, R4cross, R4data))
with test.step("Wait for all neighbor to peer"):
print("Waiting for neighbors to peer")
until(lambda: route.ospf_get_neighbor(R1, "0.0.0.0", R1ring1, "10.0.0.2"), attempts=200)
@@ -549,33 +550,34 @@ with infamy.Test() as test:
with test.step("Wait for routes from OSPF on all routers"):
print("Waiting for routes from OSPF")
until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.0.2/32", nexthop="10.0.12.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.0.3/32", nexthop="10.0.13.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.0.4/32", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "192.168.4.0/24", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "10.0.24.0/30", nexthop="10.0.41.1", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.1/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.3/32", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.0.4/32", nexthop="10.0.24.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0", nexthop="10.0.23.1", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "10.0.13.0/30", nexthop="10.0.23.2", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Verify Area 0.0.0.1 on R3 is NSSA area"):
assert(route.ospf_is_area_nssa(R3, "0.0.0.1"))
with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"): # Should be only default route out of the area.
parallel(until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200),
until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") == False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") == False, attempts=5))
with test.step("Verify on R3, there are no routes beyond 10.0.23.1, just a default route"):
# Should be only default route out of the area.
parallel(until(lambda: route.ipv4_route_exist(R3, "0.0.0.0/0"), attempts=200),
until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "10.0.12.0/30") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.8.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.9.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.10.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.11.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.12.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.13.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.14.0/24") is False, attempts=5),
until(lambda: route.ipv4_route_exist(R3, "11.0.15.0/24") is False, attempts=5))
_, hport0 = env.ltop.xlate("PC", "data3")
with infamy.IsolatedMacVlan(hport0) as ns0:
@@ -589,21 +591,23 @@ with infamy.Test() as test:
ns0.addip("192.168.4.2")
ns0.addroute("0.0.0.0/0", "192.168.4.1")
with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.41.2"):
trace=ns0.traceroute("10.0.0.3")
assert(len(trace) == 3)
assert(trace[1][1] == "10.0.41.2")
assert(trace[2][1] == "10.0.0.3")
trace = ns0.traceroute("10.0.0.3")
assert len(trace) == 3
assert trace[1][1] == "10.0.41.2"
assert trace[2][1] == "10.0.0.3"
with test.step("Break link R1:ring2 --- R4:ring1"):
disable_link(R1, R1ring2) # Here we should test with link breakers, to test BFD recouppling, for now disable the link
# Here we should test with link breakers, to test BFD
# recouppling, for now disable the link
disable_link(R1, R1ring2)
with test.step("Verify that the route to 10.0.0.3 from PC:data4, go through 10.0.24.1"):
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", source_protocol = "infix-routing:ospf"), attempts=100)
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") == False, attempts = 10)
trace=ns0.traceroute("10.0.0.3")
assert(len(trace) == 3)
assert(trace[1][1] == "10.0.24.1")
assert(trace[2][1] == "10.0.0.3")
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.24.1", proto="ietf-ospf:ospfv2"), attempts=100)
until(lambda: route.ipv4_route_exist(R4, "10.0.0.3/32", nexthop="10.0.41.2") is False, attempts=10)
trace = ns0.traceroute("10.0.0.3")
assert len(trace) == 3
assert trace[1][1] == "10.0.24.1"
assert trace[2][1] == "10.0.0.3"
ns0.must_reach("10.0.0.3")
test.succeed()
@@ -37,8 +37,8 @@ def config_target1(target, data, link):
"ipv4": {
"forwarding": True,
"address": [{
"ip": "10.0.0.1",
"prefix-length": 32
"ip": "10.0.0.1",
"prefix-length": 32
}]
}
},
@@ -47,8 +47,8 @@ def config_target1(target, data, link):
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.100.1",
"prefix-length": 32
"ip": "192.168.100.1",
"prefix-length": 32
}]
}
}
@@ -73,12 +73,10 @@ def config_target1(target, data, link):
"hello-interval": 1,
"dead-interval": 3,
"interface-type": "point-to-point"
},
{
}, {
"name": data,
"passive": True
},
{
}, {
"name": "lo",
"passive": True
}]
@@ -91,6 +89,7 @@ def config_target1(target, data, link):
}
})
def config_target2(target, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -112,8 +111,8 @@ def config_target2(target, link):
"forwarding": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
"ip": "192.168.200.1",
"prefix-length": 32
}]
}
}
@@ -126,33 +125,33 @@ def config_target2(target, link):
"control-plane-protocols": {
"control-plane-protocol": [
{
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces":{
"interface": [{
"name": link,
"hello-interval": 1,
"dead-interval": 3,
"interface-type": "point-to-point"
},
{
"name": "lo",
"passive": True
}]
}
}]
"type": "infix-routing:ospfv2",
"name": "default",
"ospf": {
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"name": link,
"hello-interval": 1,
"dead-interval": 3,
"interface-type": "point-to-point"
}, {
"name": "lo",
"passive": True
}]
}
}]
}
}
}
}
]
}
}
})
with infamy.Test() as test:
with test.step("Configure targets"):
env = infamy.Env()
@@ -167,9 +166,9 @@ with infamy.Test() as test:
lambda: config_target2(R2, R2link))
with test.step("Wait for OSPF routes"):
print("Waiting for OSPF routes..")
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", source_protocol = "infix-routing:ospf"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.100.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R2, "192.168.10.0/24", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Check interface type"):
assert(route.ospf_get_interface_type(R1, "0.0.0.0", R1link) == "point-to-point")
@@ -1,7 +1,7 @@
=== Static routing
==== Description
Test that it is possible to use static routes (IPv4 and IPv6)
works as expected
Verify that it is possible to add static routes, both IPv4 and IPv6, and
that data forwarding works as expected via an intermediate device.
==== Topology
ifdef::topdoc[]
@@ -19,7 +19,7 @@ endif::topdoc[]
. Configure targets
. Wait for routes
. Configure host addresses and routes
. Verify that dut2 is reachable on 192.168.200.1 from PC:data
. Verify that R2 is reachable on 192.168.200.1 from PC:data
. Verify that R2 is reachable on 2001:db8:3c4d:200::1 from PC:data
. Remove all static routes from R1
. Verify R2 is no longer reachable on either IPv4 or IPv6 from PC:data
+81 -80
View File
@@ -3,7 +3,7 @@
# |
# +--------------+ +-------------+
# | |192.168.50.0/24 | |
# | DUT1 .1 +----------------+ .2 DUT2 |-192.168.200.1/32 (lo)
# | R1 .1 +----------------+ .2 R2 |-192.168.200.1/32 (lo)
# | .1 | | .1 |
# +--+------+----+ +---+-----+---+
# | | |
@@ -18,75 +18,73 @@
"""
Static routing
Test that it is possible to use static routes (IPv4 and IPv6)
works as expected
Verify that it is possible to add static routes, both IPv4 and IPv6, and
that data forwarding works as expected via an intermediate device.
"""
import copy
import infamy
import time
import infamy.iface as iface
import infamy.route as route
from infamy.util import until, parallel
def config_target1(target, data, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
{
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.10.1",
"prefix-length": 24
}]},
"ipv6": {
"forwarding": True,
"address": [{
"ip": "2001:db8:3c4d:10::1",
"prefix-length": 64
}]
}
},
{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"interfaces": {
"interface": [
{
"name": data,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.10.1",
"prefix-length": 24
}]},
"ipv6": {
"forwarding": True,
"address": [{
"ip": "2001:db8:3c4d:10::1",
"prefix-length": 64
}]
}
},
{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": "192.168.50.1",
"prefix-length": 24
}]
},
"ipv6": {
"forwarding": True,
"address": [{
"ip": "2001:db8:3c4d:50::1",
"prefix-length": 64
}]
}
}]
},
{
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ipv6": {
"forwarding": True,
"address": [{
"ip": "2001:db8:3c4d:50::1",
"prefix-length": 64
}]
}
},
{
"name": "lo",
"enabled": True,
"ipv4": {
"address": [{
"ip": "192.168.100.1",
"prefix-length": 32
}]
},
"ipv6": {
"address": [{
"ip": "2001:db8:3c4d:100::1",
"prefix-length": 128
}]
}
}]
},
"ipv6": {
"address": [{
"ip": "2001:db8:3c4d:100::1",
"prefix-length": 128
}]
}
}
]
}
})
]
}
})
target.put_config_dict("ietf-routing", {
"routing": {
"control-plane-protocols": {
@@ -104,10 +102,10 @@ def config_target1(target, data, link):
},
"ipv6": {
"route": [{
"destination-prefix": "2001:db8:3c4d:200::1/128",
"next-hop": {
"next-hop-address": "2001:db8:3c4d:50::2"
}
"destination-prefix": "2001:db8:3c4d:200::1/128",
"next-hop": {
"next-hop-address": "2001:db8:3c4d:50::2"
}
}]
}
}
@@ -115,6 +113,8 @@ def config_target1(target, data, link):
}
}
})
def config_target2(target, link):
target.put_config_dict("ietf-interfaces", {
"interfaces": {
@@ -143,8 +143,8 @@ def config_target2(target, link):
"forwarding": True,
"ipv4": {
"address": [{
"ip": "192.168.200.1",
"prefix-length": 32
"ip": "192.168.200.1",
"prefix-length": 32
}]
},
"ipv6": {
@@ -168,18 +168,18 @@ def config_target2(target, link):
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "0.0.0.0/0",
"next-hop": {
"next-hop-address": "192.168.50.1"
}
}]
"destination-prefix": "0.0.0.0/0",
"next-hop": {
"next-hop-address": "192.168.50.1"
}
}]
},
"ipv6": {
"route": [{
"destination-prefix": "::/0",
"next-hop": {
"next-hop-address": "2001:db8:3c4d:50::1"
}
"destination-prefix": "::/0",
"next-hop": {
"next-hop-address": "2001:db8:3c4d:50::1"
}
}]
}
}
@@ -188,6 +188,7 @@ def config_target2(target, link):
}
})
with infamy.Test() as test:
with test.step("Configure targets"):
env = infamy.Env()
@@ -210,21 +211,21 @@ with infamy.Test() as test:
_, hport0 = env.ltop.xlate("PC", "data")
with infamy.IsolatedMacVlan(hport0) as ns0:
with test.step("Configure host addresses and routes"):
ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6")
ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6")
ns0.addip("192.168.10.2")
ns0.addroute("192.168.200.1/32", "192.168.10.1")
ns0.addip("2001:db8:3c4d:10::2", prefix_length=64, proto="ipv6")
ns0.addroute("2001:db8:3c4d:200::1/128", "2001:db8:3c4d:10::1", proto="ipv6")
ns0.addip("192.168.10.2")
ns0.addroute("192.168.200.1/32", "192.168.10.1")
with test.step("Verify that dut2 is reachable on 192.168.200.1 from PC:data"):
ns0.must_reach("192.168.200.1")
with test.step("Verify that R2 is reachable on 192.168.200.1 from PC:data"):
ns0.must_reach("192.168.200.1")
with test.step("Verify that R2 is reachable on 2001:db8:3c4d:200::1 from PC:data"):
ns0.must_reach("2001:db8:3c4d:200::1")
ns0.must_reach("2001:db8:3c4d:200::1")
with test.step("Remove all static routes from R1"):
R1.delete_xpath("/ietf-routing:routing/control-plane-protocols")
parallel(until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32") == False),
until(lambda: route.ipv6_route_exist(R1, "2001:db8:3c4d:200::1/128") == False))
parallel(until(lambda: route.ipv4_route_exist(R1, "192.168.200.1/32") is False),
until(lambda: route.ipv6_route_exist(R1, "2001:db8:3c4d:200::1/128") is False))
with test.step("Verify R2 is no longer reachable on either IPv4 or IPv6 from PC:data"):
infamy.parallel(ns0.must_not_reach("192.168.200.1"),
+15 -7
View File
@@ -1,22 +1,30 @@
=== DHCP router
=== DHCP option 121 vs option 3
==== Description
Verify DHCP option 121 (staticroutes) is used over option 3 and that the routes exist in
the operational datastore
Verify DHCP option 121 (staticroutes) is used over option 3 and that the
routes exist in the operational datastore.
Installing unrelated routes from a DHCP server should not affect already
existing routes. To verify this a canary route is set up in the client
before initiating DHCP. This canary route does not need to be reachable
before a DHCP lease has been acquired.
==== Topology
ifdef::topdoc[]
image::../../test/case/infix_dhcp/dhcp_routes/topology.png[DHCP router topology]
image::../../test/case/infix_dhcp/dhcp_routes/topology.png[DHCP option 121 vs option 3 topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::dhcp_routes/topology.png[DHCP router topology]
image::dhcp_routes/topology.png[DHCP option 121 vs option 3 topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.png[DHCP router topology]
image::topology.png[DHCP option 121 vs option 3 topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Initialize
. Setting up client
. Verify client use classless routes, option 121
. Verify client did *not* use option 3
. Verify client has canary route, 20.0.0.0/24
<<<
+60 -27
View File
@@ -1,47 +1,80 @@
#!/usr/bin/env python3
# Verify DHCP option 121 (staticroutes) is used over option 3
"""
DHCP router
DHCP option 121 vs option 3
Verify DHCP option 121 (staticroutes) is used over option 3 and that the routes exist in
the operational datastore
Verify DHCP option 121 (staticroutes) is used over option 3 and that the
routes exist in the operational datastore.
Installing unrelated routes from a DHCP server should not affect already
existing routes. To verify this a canary route is set up in the client
before initiating DHCP. This canary route does not need to be reachable
before a DHCP lease has been acquired.
"""
import time
import infamy, infamy.dhcp
import infamy.iface as iface
import infamy
import infamy.dhcp
import infamy.route as route
from infamy.util import until
with infamy.Test() as test:
PREFIX = '10.0.0.0/24'
ROUTER = '192.168.0.254'
with test.step("Initialize"):
PREFIX = '10.0.0.0/24'
ROUTER = '192.168.0.254'
CANARY = '20.0.0.0/24'
CANHOP = '192.168.0.2'
with test.step("Setting up client"):
env = infamy.Env()
client = env.attach("client", "mgmt")
_, host = env.ltop.xlate("host", "data")
_, port = env.ltop.xlate("client", "data")
# Install canary route to smoke out any regressions in
# how the DHCP client installs routes in the kernel FIB
client.put_config_dict("ietf-routing", {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": CANARY,
"next-hop": {
"next-hop-address": CANHOP
},
"infix-routing:route-preference": 250
}]
}
}
}]
}
}
})
client.put_config_dict("infix-dhcp-client", {
"dhcp-client": {
"client-if": [{
"if-name": f"{port}",
"option": [
{"name": "router"},
{"name": "staticroutes"}
]
}]
}
})
with infamy.IsolatedMacVlan(host) as netns:
netns.addip("192.168.0.1")
with infamy.dhcp.Server(netns, prefix=PREFIX, router=ROUTER):
_, port = env.ltop.xlate("client", "data")
config = {
"dhcp-client": {
"client-if": [{
"if-name": f"{port}",
"option": [
{ "name": "router" },
{ "name": "staticroutes" }
]
}]
}
}
client.put_config_dict("infix-dhcp-client", config)
with test.step(f"Verify client sets up correct route via {ROUTER}"):
# Wait for client to set the classless static route, option 121
with test.step("Verify client use classless routes, option 121"):
until(lambda: route.ipv4_route_exist(client, PREFIX, ROUTER))
# Ensure client did *not* use option 3 (option 121 takes precedence)
with test.step("Verify client did *not* use option 3"):
if route.ipv4_route_exist(client, "0.0.0.0/0", ROUTER):
test.fail()
with test.step("Verify client has canary route, 20.0.0.0/24"):
until(lambda: route.ipv4_route_exist(client, CANARY,
CANHOP, pref=250))
test.succeed()
@@ -3,11 +3,6 @@
Verify that basic services like mDNS and LLDP can be enabled and
disabled. We verify operation and non-operation by using tcpdump.
XXX: with socat in the Docker container we could speed up the LLDP
detection considerably by sending a probe:
echo -ne '\x01\x80\xc2\x00\x00\x0e\x01\x80\xc2\x00\x00\x0e\x88\xcc\x02\x07\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast
==== Topology
ifdef::topdoc[]
image::../../test/case/infix_services/services_basic/topology.png[Services basic topology]
@@ -24,7 +19,7 @@ endif::topdoc[]
. Initialize
. Set static IPv4 address and disable services
. Start sniffer and enable services on target ...
. Disable services on target, verify they're not running anymore ...
. Disable services on target, verify no longer running ...
<<<
+30 -34
View File
@@ -1,32 +1,20 @@
#!/usr/bin/env python3
#
# Verify that basic services like mDNS and LLDP can be enabled and
# disabled. We verify operation and non-operation by using tcpdump.
#
# XXX: with socat in the Docker container we could speed up the LLDP
# detection considerably by sending a probe:
#
# echo -ne "\x01\x80\xc2\x00\x00\x0e\x01\x80\xc2\x00\x00\x0e\x88\xcc\x02\x07\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast
#
"""
Services basic
Verify that basic services like mDNS and LLDP can be enabled and
disabled. We verify operation and non-operation by using tcpdump.
XXX: with socat in the Docker container we could speed up the LLDP
detection considerably by sending a probe:
echo -ne '\\x01\\x80\\xc2\\x00\\x00\\x0e\\x01\\x80\\xc2\\x00\\x00\\x0e\\x88\\xcc\\x02\\x07\\x04\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00' | socat - UDP4-DATAGRAM:255.255.255.255:7010,broadcast
"""
import time
import infamy
def toggle(updown):
"""Toggle port down/up to kick services"""
_, port = env.ltop.xlate("target", "data")
act = "UP" if updown else "DOWN"
print(f"target: taking interface {port} {act} ...")
target.put_config_dict("ietf-interfaces", {
"interfaces": {
"interface": [
@@ -38,38 +26,46 @@ def toggle(updown):
}
})
def verify(enabled, sec):
"""Verify service traffic, or no traffic in case service not enabled"""
_, hport = env.ltop.xlate("host", "data")
with infamy.IsolatedMacVlan(hport) as netns:
snif = infamy.Sniffer(netns, "port 5353 or ether proto 0x88cc")
act = "enabling" if enabled else "disabling"
netns.addip("10.0.0.1")
netns.addroute("0.0.0.0/0", "10.0.0.1")
# Put service enable/disable before starting tcpdump, because
# LLDP lingers and will send a final shutdown message that
# otherwise would get in the capture for disable.
target.put_config_dict("infix-services", {
"mdns": {
"enabled": enabled
}
})
target.put_config_dict("ieee802-dot1ab-lldp", {
"lldp": {
"enabled": enabled
}
})
# Prevent old-state traffic from being captured before reconf
toggle(False)
with snif:
time.sleep(1)
print("host: collecting network traffic ...")
# Put service enable/disable before starting tcpdump,
# because LLDP lingers and will send a final shutdown
# message that otherwise would get in the capture for
# disable.
print(f"target: {act} LLDP and mDNS services ...")
target.put_config_dict("infix-services", {
"mdns": {
"enabled": enabled
}
})
target.put_config_dict("ieee802-dot1ab-lldp", {
"lldp": {
"enabled": enabled
}
})
toggle(True)
time.sleep(sec - 1)
time.sleep(sec)
return snif.output()
with infamy.Test() as test:
with test.step("Initialize"):
env = infamy.Env()
@@ -109,16 +105,16 @@ with infamy.Test() as test:
with test.step("Start sniffer and enable services on target ..."):
rc = verify(True, 10)
#print(rc.stdout)
print(rc.stdout)
# breakpoint()
if "10.0.0.10.5353" not in rc.stdout:
test.fail()
if "LLDP" not in rc.stdout:
test.fail()
with test.step("Disable services on target, verify they're not running anymore ..."):
with test.step("Disable services on target, verify no longer running ..."):
rc = verify(False, 10)
#print(rc.stdout)
print(rc.stdout)
if "10.0.0.10.5353" in rc.stdout:
test.fail()
if "LLDP" in rc.stdout:
+12 -4
View File
@@ -287,8 +287,8 @@ class Device(Transport):
def get_data(self, xpath=None, parse=True):
"""RPC <get-data> to fetch operational data"""
if parse==False:
return self._get_data(xpath,parse)
if parse is False:
return self._get_data(xpath, parse)
data = self._get_data(xpath)
if not data:
@@ -391,28 +391,36 @@ class Device(Transport):
def get_iface(self, name):
"""Fetch target dict for iface and extract param from JSON"""
content = self.get_data(iface.get_iface_xpath(name))
interface=content.get("interfaces", {}).get("interface", None)
interface = content.get("interfaces", {}).get("interface", None)
if interface is None:
return None
# Restconf (rousette) address by id and netconf (netopper2) address by name
# Restconf (rousette) address by id, otherwise (netopper2) by name
return interface[name]
def delete_xpath(self, xpath):
# Split out the model and the container from xpath'
pattern = r"^/(?P<module>[^:]+):(?P<path>[^/]+)"
match = re.search(pattern, xpath)
if not match:
raise ValueError(f"Failed parsing xpath:{xpath}")
module = match.group('module')
modpath = f"/{match.group('module')}:{match.group('path')}"
old = self.get_config_dict(modpath)
new = copy.deepcopy(old)
libyang.xpath_del(new, xpath)
mod = self.ly.get_module(module)
oldd = mod.parse_data_dict(old, no_state=True)
newd = mod.parse_data_dict(new, no_state=True)
lyd = oldd.diff(newd)
if lyd is None:
raise ValueError(f"Failed generating diff for xpath:{xpath}")
return self.put_config(lyd.print_mem("xml", with_siblings=True,
pretty=False))
+63 -33
View File
@@ -1,92 +1,122 @@
"""
IETF Routing helper methods
"""
def _get_routes(target, protocol):
xpath="/ietf-routing:routing/ribs"
xpath = "/ietf-routing:routing/ribs"
rib = target.get_data(xpath)["routing"]["ribs"]["rib"]
for r in rib:
if r["name"] != protocol:
continue
return r.get("routes", {}).get("route",{})
return r.get("routes", {}).get("route", {})
return {}
def _exist_route(target, prefix, nexthop, version, source_protocol):
routes = _get_routes(target, version)
route_found = False
def _exist_route(target, dest, nexthop=None, ip=None, proto=None, pref=None):
routes = _get_routes(target, ip)
for r in routes:
# netconf presents destination-prefix, restconf prefix with model
p=r.get("destination-prefix") or (version == "ipv4" and r.get("ietf-ipv4-unicast-routing:destination-prefix")) or (version == "ipv6" and r.get("ietf-ipv6-unicast-routing:destination-prefix"))
if p != prefix:
dst = r.get("destination-prefix") or \
r.get(f"ietf-{ip}-unicast-routing:destination-prefix")
if dst != dest:
continue
if source_protocol and r.get("source-protocol") != source_protocol:
if proto is not None and r.get("source-protocol") != proto:
return False
route_found = True
nh = r["next-hop"]
next_hop_list = nh.get("next-hop-list")
if nexthop:
if pref is not None and r.get("route-preference") != pref:
return False
if nexthop is not None:
nh = r["next-hop"]
if not nh:
return False
next_hop_list = nh.get("next-hop-list")
if next_hop_list:
for nhl in next_hop_list["next-hop"]:
address = nhl.get("address")
# netconf presents address, restconf prefix with
# model ietf-ipv4-unicast-routing:address
address = nhl.get("address") or \
nhl.get(f"ietf-{ip}-unicast-routing:address")
if address == nexthop:
return True
return False
else:
# netconf presents next-hop-address, restconf prefix with model ietf-ipv4-unicast-routing:next-hop-address
nh_addr=nh.get("next-hop-address", None) or (version == "ipv4" and nh.get("ietf-ipv4-unicast-routing:next-hop-address", None)) or (version == "ipv6" and nh.get("ietf-ipv6-unicast-routing:next-hop-address", None))
# netconf presents next-hop-address, restconf prefix
# with model ietf-ipv4-unicast-routing:next-hop-address
nh_addr = nh.get("next-hop-address") or \
nh.get(f"ietf-{ip}-unicast-routing:next-hop-address")
if nh_addr == nexthop:
return True
return False
return route_found
return False
else:
return True
return False
def ipv4_route_exist(target, prefix, nexthop=None,source_protocol=None):
return _exist_route(target, prefix, nexthop, "ipv4",source_protocol)
def ipv6_route_exist(target, prefix, nexthop=None,source_protocol=None):
return _exist_route(target, prefix, nexthop, "ipv6",source_protocol)
def ipv4_route_exist(target, dest, nexthop=None, proto=None, pref=None):
return _exist_route(target, dest, nexthop=nexthop, ip="ipv4", proto=proto, pref=pref)
def ipv6_route_exist(target, dest, nexthop=None, proto=None, pref=None):
return _exist_route(target, dest, nexthop=nexthop, ip="ipv6", proto=proto, pref=pref)
def _get_ospf_status(target):
xpath="/ietf-routing:routing/control-plane-protocols"
rib = target.get_data(xpath)["routing"]["control-plane-protocols"].get("control-plane-protocol", {})
xpath = "/ietf-routing:routing/control-plane-protocols"
protos = target.get_data(xpath)["routing"]["control-plane-protocols"]
rib = protos.get("control-plane-protocol", {})
for p in rib:
if p["type"] == "infix-routing:ospfv2":
return p.get("ospf") or p.get("ietf-ospf:ospf")
return []
def _get_ospf_status_area(target, area_id):
ospf=_get_ospf_status(target)
ospf = _get_ospf_status(target)
for area in ospf.get("areas", {}).get("area", {}):
if area["area-id"] == area_id:
return area
return {}
def _get_ospf_status_area_interface(target, area_id, ifname):
area=_get_ospf_status_area(target,area_id)
def _get_ospf_status_area_interface(target, area_id, ifname):
area = _get_ospf_status_area(target, area_id)
for interface in area.get("interfaces", {}).get("interface", {}):
if interface.get("name") == ifname:
return interface
return {}
def ospf_get_neighbor(target, area_id, ifname, neighbour_id, full=True):
ospf_interface=_get_ospf_status_area_interface(target,area_id, ifname)
ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname)
for neighbor in ospf_interface.get("neighbors", {}).get("neighbor", {}):
if neighbor.get("neighbor-router-id") == neighbour_id:
if full == False:
if full is False:
return True
if(neighbor.get("state") == "full"):
if neighbor.get("state") == "full":
return True
return False
def ospf_get_interface_type(target, area_id, ifname):
ospf_interface=_get_ospf_status_area_interface(target,area_id,ifname)
ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname)
return ospf_interface.get("interface-type", None)
def ospf_get_interface_passive(target, area_id, ifname):
ospf_interface=_get_ospf_status_area_interface(target,area_id,ifname)
ospf_interface = _get_ospf_status_area_interface(target, area_id, ifname)
return ospf_interface.get("passive", False)
def ospf_is_area_nssa(target, area_id):
area=_get_ospf_status_area(target, area_id)
def ospf_is_area_nssa(target, area_id):
area = _get_ospf_status_area(target, area_id)
if area.get("area-type", "") == "ietf-ospf:nssa-area":
return True