From 25b733c347738063f64f7395016a78c3afe5923c Mon Sep 17 00:00:00 2001 From: Philippe Guibert Date: Mon, 15 Nov 2021 09:10:48 +0100 Subject: [PATCH] zebra, topotests: autorise nexthop interface labelled routes 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: https://github.com/FRRouting/frr/pull/10058 Signed-off-by: Philippe Guibert --- tests/topotests/connected_mpls/r1/zebra.conf | 18 ++ tests/topotests/connected_mpls/r2/zebra.conf | 9 + .../connected_mpls/test_connected_mpls.py | 219 ++++++++++++++++++ zebra/zapi_msg.c | 8 +- 4 files changed, 249 insertions(+), 5 deletions(-) create mode 100644 tests/topotests/connected_mpls/r1/zebra.conf create mode 100644 tests/topotests/connected_mpls/r2/zebra.conf create mode 100644 tests/topotests/connected_mpls/test_connected_mpls.py diff --git a/tests/topotests/connected_mpls/r1/zebra.conf b/tests/topotests/connected_mpls/r1/zebra.conf new file mode 100644 index 000000000000..f59e28b5f1d7 --- /dev/null +++ b/tests/topotests/connected_mpls/r1/zebra.conf @@ -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 +! + diff --git a/tests/topotests/connected_mpls/r2/zebra.conf b/tests/topotests/connected_mpls/r2/zebra.conf new file mode 100644 index 000000000000..c51feef9eef2 --- /dev/null +++ b/tests/topotests/connected_mpls/r2/zebra.conf @@ -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 +! + diff --git a/tests/topotests/connected_mpls/test_connected_mpls.py b/tests/topotests/connected_mpls/test_connected_mpls.py new file mode 100644 index 000000000000..9d1402c842ca --- /dev/null +++ b/tests/topotests/connected_mpls/test_connected_mpls.py @@ -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)) diff --git a/zebra/zapi_msg.c b/zebra/zapi_msg.c index 761eafeb1375..d0d33320907a 100644 --- a/zebra/zapi_msg.c +++ b/zebra/zapi_msg.c @@ -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;