Skip to content

Commit

Permalink
zebra, topotests: autorise nexthop interface labelled routes
Browse files Browse the repository at this point in the history
When coming from staticd, some nexthop interface based routes
should be able to add a label. This helps recursive routes to
reuse the label.

> interface eth0
>  ip address 192.0.2.1/32
> !
> ip route 192.0.2.2/32 eth0 label 55
> ip route 172.31.0.0/24 192.0.2.2
> ip route 172.31.1.0/24 192.0.2.2

The nexthop interface route is a connected route with no next-hop,
and the destination packets with DA=192.0.2.2/32 be sent with a
broadcast DMAC. This will result in dropping those packets on
the receiving router.

However, the recursive routes will take the real MAC address of the
192.0.2.2 IP and the label 55 to forge outgoing packets going to
the 172.31.0.0 or 172.31.1.0 sub-networks.

Fixes: ("7fcb24bbaa91") zebra: reject routes without nexthops

Link: #10058

Signed-off-by: Philippe Guibert <[email protected]>
  • Loading branch information
pguibert6WIND committed Dec 22, 2023
1 parent eba1c1e commit 25b733c
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 5 deletions.
18 changes: 18 additions & 0 deletions tests/topotests/connected_mpls/r1/zebra.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
hostname r1
ip forwarding
ipv6 forwarding

int r1-eth0
ip addr 192.168.1.1/24
!
int r1-eth1
ip addr 192.168.2.1/32
mpls enable
!
int lo
ip addr 192.0.2.1/32
!
ip route 192.168.2.2/32 r1-eth1 label 100 10
ip route 192.168.3.0/24 192.168.2.2
!

9 changes: 9 additions & 0 deletions tests/topotests/connected_mpls/r2/zebra.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
hostname r2
int r2-eth0
mpls enable
ip addr 192.168.2.2/24
!
int r2-eth1
ip addr 192.168.3.2/24
!

219 changes: 219 additions & 0 deletions tests/topotests/connected_mpls/test_connected_mpls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
#!/usr/bin/env python
# SPDX-License-Identifier: ISC

#
# test_connected_mpls.py
# Part of NetDEF Topology Tests
#
# Copyright (c) 2023 by 6WIND
#

"""
test_connected_mpls.py: Testing MPLS configuration with mpls connected route
"""

import os
import re
import sys
import pytest
import json
from functools import partial
import functools

# Save the Current Working Directory to find configuration files.
CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))

# pylint: disable=C0413
# Import topogen and topotest helpers
from lib import topotest
from lib.checkping import check_ping
from lib.topogen import Topogen, TopoRouter, get_topogen
from lib.topolog import logger

# Required to instantiate the topology builder class.

pytestmark = [pytest.mark.zebra]

#####################################################
##
## Network Topology Definition
##
#####################################################


def build_topo(tgen):
"Build function"

tgen.add_router("r1")
tgen.add_router("r2")

switch = tgen.add_switch("sw1")
switch.add_link(tgen.gears["r1"])

switch = tgen.add_switch("sw2")
switch.add_link(tgen.gears["r1"])
switch.add_link(tgen.gears["r2"])

switch = tgen.add_switch("sw3")
switch.add_link(tgen.gears["r2"])


#####################################################
##
## Tests starting
##
#####################################################
def _populate_iface():
tgen = get_topogen()
tgen.net["r1"].cmd("echo 100000 > /proc/sys/net/mpls/platform_labels")
tgen.net["r2"].cmd("echo 100000 > /proc/sys/net/mpls/platform_labels")
tgen.net["r2"].cmd("ip -f mpls route add 100 dev lo")


def setup_module(module):
"Setup topology"
tgen = Topogen(build_topo, module.__name__)
tgen.start_topology()

_populate_iface()

# This is a sample of configuration loading.
router_list = tgen.routers()
for rname, router in router_list.items():
router.load_config(
TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)

tgen.start_router()


def teardown_module(_mod):
"Teardown the pytest environment"
tgen = get_topogen()

# This function tears down the whole topology.
tgen.stop_topology()


def check_show_static_mpls_route_installed(rname):
tgen = get_topogen()
output = json.loads(
tgen.gears[rname].vtysh_cmd("show ip route 192.168.2.2/32 json")
)
found = False
logmsg = f"{rname}, prefix 192.168.2.2/32 not installed as it should be"
errmsg = f"{logmsg}, not found"
for path in output["192.168.2.2/32"]:
if "installed" not in path.keys() or not path["installed"]:
errmsg = f"{logmsg} : path not installed"
continue
for nh in path["nexthops"]:
if "directlyConnected" not in nh.keys() or not nh["directlyConnected"]:
errmsg = f"{logmsg} : nexthop not directly connected"
elif "interfaceName" not in nh.keys() or nh["interfaceName"] != "r1-eth1":
errmsg = f"{logmsg} : wrong nexthop interface"
elif "labels" not in nh.keys() or nh["labels"] != [100]:
errmsg = f"{logmsg} : wrong nexthop label"
else:
found = True
else:
errmsg = f"{logmsg}, nexthop not found"
if found:
return None
return errmsg


def check_show_linux_mpls_label_installed(rname):
tgen = get_topogen()
output = tgen.net[rname].cmd("ip route show 192.168.2.2/32")
if "encap mpls 100" not in output:
return f"{rname}, prefix 192.168.2.2/32 not installed: iproute2 has not expected label value"
return None


def check_show_static_recursive_route_installed(rname):
tgen = get_topogen()
output = json.loads(
tgen.gears[rname].vtysh_cmd("show ip route 192.168.3.0/24 json")
)
found_recursive = False
found_label = False
logmsg = f"{rname}, prefix 192.168.3.0/24 not installed as it should be"
errmsg = f"{logmsg}, not found"
for path in output["192.168.3.0/24"]:
if "installed" not in path.keys() or not path["installed"]:
errmsg = f"{logmsg} : path not installed"
continue
for nh in path["nexthops"]:
if "recursive" in nh.keys() and nh["recursive"]:
if "ip" not in nh.keys() or nh["ip"] != "192.168.2.2":
errmsg = f"{logmsg} : nexthop not found"
else:
found_recursive = True
elif "interfaceName" not in nh.keys() or nh["interfaceName"] != "r1-eth1":
errmsg = f"{logmsg} : wrong nexthop interface"
elif "labels" not in nh.keys() or nh["labels"] != [100]:
errmsg = f"{logmsg} : wrong nexthop label"
else:
found_label = True
else:
errmsg = f"{logmsg}, nexthop not found"
if found_recursive and found_label:
return None
return errmsg


def test_connected_mpls_route():
"Test that the static MPLS route can be installed with MPLS label"

tgen = get_topogen()
# Don't run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)

logger.info(
"Checking that static MPLS route 192.168.2.2/32 is installed with labels on ZEBRA"
)
test_func = functools.partial(check_show_static_mpls_route_installed, "r1")
success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
assert success, result

logger.info(
"Checking that static MPLS route 192.168.2.2/32 is installed with labels on system"
)
test_func = functools.partial(check_show_linux_mpls_label_installed, "r1")
success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
assert success, result

# ping does not work because sent MPLS packet is broadcase
# like all connected routes, packets are broadcast
# consequently, on receiving router, broadcast packets received can not be forwarded
# this is the case for MPLS packets.
# logger.info("Checking that ping via 192.168.2.2 is working")
# check_ping("r1", "192.168.2.2", True, 10, 0.5)


def test_recursive_mpls_route():
"Test that a recursive route can re-use the label from the static MPLS route."

tgen = get_topogen()
# Don't run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)

logger.info(
"Checking that route 192.168.3.0/24 is installed with labels from recursive route on ZEBRA"
)

test_func = functools.partial(check_show_static_recursive_route_installed, "r1")
success, result = topotest.run_and_expect(test_func, None, count=10, wait=0.5)
assert success, result

logger.info("Checking that ping via 192.168.3.2 is working")
check_ping("r1", "192.168.3.2", True, 10, 0.5)


if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))
8 changes: 3 additions & 5 deletions zebra/zapi_msg.c
Original file line number Diff line number Diff line change
Expand Up @@ -1787,11 +1787,9 @@ static bool zapi_read_nexthops(struct zserv *client, struct prefix *p,
}

/* Labels for MPLS BGP-LU or Segment Routing or EVPN */
if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL)
&& api_nh->type != NEXTHOP_TYPE_IFINDEX
&& api_nh->type != NEXTHOP_TYPE_BLACKHOLE
&& api_nh->label_num > 0) {

if (CHECK_FLAG(api_nh->flags, ZAPI_NEXTHOP_FLAG_LABEL) &&
api_nh->type != NEXTHOP_TYPE_BLACKHOLE &&
api_nh->label_num > 0) {
/* If label type was passed, use it */
if (api_nh->label_type)
label_type = api_nh->label_type;
Expand Down

0 comments on commit 25b733c

Please sign in to comment.