pbrd: add vlan filters pcp/vlan-id/vlan-flags; ip-protocol any (doc, tests)

Subset: doc and tests

    doc
	PBR section updated with new fields and some copy-editing

    tests
	pbr_topo1: ensure new vlan fields arrive at zebra

    Changes by:
	Josh Werner <joshuawerner@mitre.org>
	Eli Baum <ebaum@mitre.org>
	G. Paul Ziemba <paulz@labn.net>

Signed-off-by: G. Paul Ziemba <paulz@labn.net>
This commit is contained in:
G. Paul Ziemba 2023-07-19 07:49:18 -07:00
parent 657882c430
commit 8b330fe8b7
2 changed files with 278 additions and 126 deletions

View File

@ -4,10 +4,11 @@
PBR PBR
*** ***
:abbr:`PBR` is Policy Based Routing. This implementation supports a very simple :abbr:`PBR` is Policy Based Routing, which means forwarding based on
interface to allow admins to influence routing on their router. At this time packet fields other than solely the destination IP address.
you can only match on destination and source prefixes for an incoming interface. This implementation currently works only on Linux. Note that some
At this point in time, this implementation will only work on Linux. functionality (VLAN matching, packet mangling) is not supported by
the default Linux kernel dataplane provider.
.. _starting-pbr: .. _starting-pbr:
@ -17,12 +18,12 @@ Starting PBR
Default configuration file for *pbrd* is :file:`pbrd.conf`. The typical Default configuration file for *pbrd* is :file:`pbrd.conf`. The typical
location of :file:`pbrd.conf` is |INSTALL_PREFIX_ETC|/pbrd.conf. location of :file:`pbrd.conf` is |INSTALL_PREFIX_ETC|/pbrd.conf.
If the user is using integrated config, then :file:`pbrd.conf` need not be If FRR is using integrated config, then :file:`pbrd.conf` need not be
present and the :file:`frr.conf` is read instead. present and the :file:`frr.conf` is read instead.
.. program:: pbrd .. program:: pbrd
:abbr:`PBR` supports all the common FRR daemon start options which are :abbr:`PBR` supports all the common FRR daemon start options, which are
documented elsewhere. documented elsewhere.
.. _nexthop-groups: .. _nexthop-groups:
@ -30,9 +31,9 @@ documented elsewhere.
Nexthop Groups Nexthop Groups
============== ==============
Nexthop groups are a way to encapsulate ECMP information together. It's a A nexthop group is a list of ECMP nexthops used to forward packets
listing of ECMP nexthops used to forward packets for when a pbr-map is matched. when a pbr-map is matched.
For detailed instructions on how to specify a nexthop group on the CLI, see For details on specifying a nexthop group in the CLI, see
the nexthop-groups section. the nexthop-groups section.
Showing Nexthop Group Information Showing Nexthop Group Information
@ -42,7 +43,7 @@ Showing Nexthop Group Information
Display information on a PBR nexthop-group. If ``NAME`` is omitted, all Display information on a PBR nexthop-group. If ``NAME`` is omitted, all
nexthop groups are shown. Setting ``json`` will provide the same nexthop groups are shown. Setting ``json`` will provide the same
information in an array of objects which obey the schema below: information in an array of objects that adhere to the schema below:
+-----------+----------------------------+---------+ +-----------+----------------------------+---------+
| Key | Description | Type | | Key | Description | Type |
@ -74,118 +75,152 @@ Showing Nexthop Group Information
PBR Maps PBR Maps
======== ========
PBR maps are a way to group policies that we would like to apply to individual PBR maps are a way to specify a set of rules that are applied to
interfaces. These policies when applied are matched against incoming packets. packets received on individual interfaces.
If matched the nexthop-group or nexthop is used to forward the packets to the If a received packet matches a rule, the rule's nexthop-group or
end destination. nexthop is used to forward it; any other actions
specified in the rule are also applied to the packet.
.. clicmd:: pbr-map NAME seq (1-700) .. clicmd:: pbr-map NAME seq (1-700)
Create a pbr-map with NAME and sequence number specified. This command puts Create a pbr-map rule with map NAME and specified sequence number.
you into a new submode for pbr-map specification. To exit this mode type This command puts the CLI into a new submode for pbr-map rule specification.
exit or end as per normal conventions for leaving a sub-mode. To exit this submode, type ``exit`` or ``end``.
.. clicmd:: match src-ip PREFIX .. clicmd:: match src-ip PREFIX
When a incoming packet matches the source prefix specified, take the packet Match the packet's source IP address.
and forward according to the nexthops specified. This command accepts both
v4 and v6 prefixes. This command is used in conjunction of the This command accepts both v4 and v6 prefixes.
:clicmd:`match dst-ip PREFIX` command for matching.
.. clicmd:: match dst-ip PREFIX .. clicmd:: match dst-ip PREFIX
When a incoming packet matches the destination prefix specified, take the Match the packet's destination IP address.
packet and forward according to the nexthops specified. This command accepts
both v4 and v6 prefixes. This command is used in conjunction of the This command accepts both v4 and v6 prefixes.
:clicmd:`match src-ip PREFIX` command for matching.
.. clicmd:: match src-port (1-65535) .. clicmd:: match src-port (1-65535)
When a incoming packet matches the source port specified, take the Match the packet's UDP or TCP source port.
packet and forward according to the nexthops specified.
.. clicmd:: match dst-port (1-65535) .. clicmd:: match dst-port (1-65535)
When a incoming packet matches the destination port specified, take the Match the packet's UDP or TCP destination port.
packet and forward according to the nexthops specified.
.. clicmd:: match ip-protocol [tcp|udp] .. clicmd:: match ip-protocol PROTOCOL
When a incoming packet matches the specified ip protocol, take the Match the packet's IP protocol.
packet and forward according to the nexthops specified.
Protocol names are queried from the protocols database (``/etc/protocols``;
see ``man 5 protocols`` and ``man 3 getprotobyname``).
.. clicmd:: match mark (1-4294967295) .. clicmd:: match mark (1-4294967295)
Select the mark to match. This is a linux only command and if attempted Match the packet's meta-information mark.
on another platform it will be denied. This mark translates to the The mark value is attached to the packet by the kernel/dataplane and
underlying `ip rule .... fwmark XXXX` command. is platform-specific.
Currently, this field is supported only on linux and corresponds to
the underlying `ip rule .... fwmark XXXX` command.
.. clicmd:: match dscp (DSCP|0-63) .. clicmd:: match dscp (DSCP|0-63)
Match packets according to the specified differentiated services code point Match the packet's IP differentiated services code point (DSCP).
(DSCP) in the IP header; if this value matches then forward the packet The specified DSCP may also be a standard name for a
according to the nexthop(s) specified. The passed DSCP value may also be a differentiated service code point such as ``cs0`` or ``af11``.
standard name for a differentiated service code point like cs0 or af11.
You may only specify one dscp per route map sequence; to match on multiple You may only specify one dscp per route map rule; to match on multiple
dscp values you will need to create several sequences, one for each value. dscp values you will need to create several rules, one for each value.
.. clicmd:: match ecn (0-3) .. clicmd:: match ecn (0-3)
Match packets according to the specified explicit congestion notification Match the packet's IP explicit congestion notification (ECN) field.
(ECN) field in the IP header; if this value matches then forward the packet
according to the nexthop(s) specified.
.. clicmd:: match pcp (0-7)
Match the packet's 802.1Q Priority Code Point.
Zero is the default (nominally, "best effort").
The Linux kernel dataplane provider does not currently support
matching PCPs,
so this field will be ignored unless other dataplane providers are used.
.. clicmd:: match vlan (1-4094)
Match the packet's VLAN (802.1Q) identifier.
Note that VLAN IDs 0 and 4095 are reserved.
The Linux kernel dataplane provider does not currently support
VLAN-matching facilities,
so this field will be ignored unless other dataplane providers are used.
.. clicmd:: match vlan (tagged|untagged|untagged-or-zero)
Match packets according to whether or not they have a VLAN tag.
Use `untagged-or-zero` to also match packets with either no VLAN tag
or with the reserved VLAN ID of 0 (indicating an untagged frame that
includes other 802.1Q fields).
The Linux kernel dataplane provider does not currently support
VLAN-matching facilities,
so this field will be ignored unless other dataplane providers are used.
.. clicmd:: set queue-id (1-65535) .. clicmd:: set queue-id (1-65535)
Set the egress port queue identifier for matched packets. The Linux Kernel Action:
provider does not currently support packet mangling, so this field will be set the egress port queue identifier.
ignored unless another provider is used. The Linux Kernel dataplane provider does not currently support
packet mangling,
so this field will be ignored unless another dataplane provider is used.
.. clicmd:: set pcp (0-7) .. clicmd:: set pcp (0-7)
Set the 802.1Q priority code point (PCP) for matched packets. A PCP of zero Action:
is the defaul (nominally, "best effort"). The Linux Kernel provider does not set the 802.1Q priority code point (PCP).
currently support packet mangling, so this field will be ignored unless A PCP of zero is the default (nominally, "best effort").
another provider is used. The Linux Kernel dataplane provider does not currently support
packet mangling,
so this field will be ignored unless another dataplane provider is used.
.. clicmd:: set vlan (1-4094) .. clicmd:: set vlan (1-4094)
Set the VLAN tag for matched packets. Identifiers 0 and 4095 are reserved. Action:
The Linux Kernel provider does not currently support packet mangling, so set the VLAN tag. Identifiers 0 and 4095 are reserved.
this field will be ignored unless another provider is used. The Linux Kernel dataplane provider does not currently support
packet mangling,
so this field will be ignored unless another dataplane provider is used.
.. clicmd:: strip vlan .. clicmd:: strip vlan
Strip inner vlan tags from matched packets. The Linux Kernel provider does not currently support packet mangling, so this field will be ignored unless another provider is used. It is invalid to specify both a `strip` and `set Action:
vlan` action. strip inner vlan tags.
The Linux Kernel dataplane provider does not currently support
packet mangling,
so this field will be ignored unless another dataplane provider is used.
It is invalid to specify both a `strip` and `set vlan` action.
.. clicmd:: set nexthop-group NAME .. clicmd:: set nexthop-group NAME
Use the nexthop-group NAME as the place to forward packets when the match Action:
commands have matched a packet. forward the packet using nexthop-group NAME.
.. clicmd:: set nexthop [A.B.C.D|X:X::X:XX] [interface] [nexthop-vrf NAME] .. clicmd:: set nexthop [A.B.C.D|X:X::X:XX] [interface] [nexthop-vrf NAME]
Use this individual nexthop as the place to forward packets when the match Action:
commands have matched a packet. forward the packet using the specified single nexthop.
.. clicmd:: set vrf unchanged|NAME .. clicmd:: set vrf unchanged|NAME
If unchanged is set, the rule will use the vrf table the interface is in Action:
as its lookup. If NAME is specified, the rule will use that vrf table as If set to ``unchanged``, the rule will use the vrf table the interface
its lookup. is in as its lookup.
If set to NAME, the rule will use that vrf table as its lookup.
Not supported with NETNS VRF backend. Not supported with NETNS VRF backend.
.. clicmd:: show pbr map [NAME] [detail|json] .. clicmd:: show pbr map [NAME] [detail|json]
Display pbr maps either all or by ``NAME``. If ``detail`` is set, it will Display pbr maps either all or by ``NAME``. If ``detail`` is set, it will
give information about the rules unique ID used internally and some extra give information about each rule's unique internal ID and some extra
debugging information about install state for the nexthop/nexthop group. debugging information about install state for the nexthop/nexthop group.
Setting ``json`` will provide the same information in an array of objects Setting ``json`` will provide the same information in an array of objects
which obey the schema below: that adher to the schema below:
+----------+--------------------------------+---------+ +----------+--------------------------------+---------+
| Key | Description | Type | | Key | Description | Type |
@ -197,9 +232,9 @@ end destination.
| policies | Rules to match packets against | Array | | policies | Rules to match packets against | Array |
+----------+--------------------------------+---------+ +----------+--------------------------------+---------+
Each element of the ``policies`` array is composed of a handful of objects Each element of the ``policies`` array is composed of a set of objects
representing the policies associated with this map. Each policy is representing the policies associated with this map. Each policy is
described as below (not all fields are required): described below (not all fields are required):
+-----------------+-------------------------------------------+---------+ +-----------------+-------------------------------------------+---------+
| Key | Description | Type | | Key | Description | Type |
@ -227,8 +262,8 @@ end destination.
| nexthopGroup | This policy's nexthop group (if relevant) | Object | | nexthopGroup | This policy's nexthop group (if relevant) | Object |
+-----------------+-------------------------------------------+---------+ +-----------------+-------------------------------------------+---------+
Finally, the ``nexthopGroup`` object above cotains information we know Finally, the ``nexthopGroup`` object above contains information FRR
about the configured nexthop for this policy: knows about the configured nexthop for this policy:
+---------------------+--------------------------------------+---------+ +---------------------+--------------------------------------+---------+
| Key | Description | Type | | Key | Description | Type |
@ -239,7 +274,7 @@ end destination.
+---------------------+--------------------------------------+---------+ +---------------------+--------------------------------------+---------+
| installed | Is this nexthop group installed? | Boolean | | installed | Is this nexthop group installed? | Boolean |
+---------------------+--------------------------------------+---------+ +---------------------+--------------------------------------+---------+
| installedInternally | Do we think this group is installed? | Integer | | installedInternally | Does FRR think NHG is installed? | Integer |
+---------------------+--------------------------------------+---------+ +---------------------+--------------------------------------+---------+
@ -251,19 +286,19 @@ end destination.
PBR Policy PBR Policy
========== ==========
After you have specified a PBR map, in order for it to be turned on, you must After you have specified a PBR map, in order for it to be enabled, it must
apply the PBR map to an interface. This policy application to an interface be applied to an interface. This policy application to an interface
causes the policy to be installed into the kernel. causes the policy to be installed into the kernel.
.. clicmd:: pbr-policy NAME .. clicmd:: pbr-policy NAME
This command is available under interface sub-mode. This turns This command is available under interface sub-mode.
on the PBR map NAME and allows it to work properly. It enables the PBR map NAME on the interface.
.. note:: .. note::
This will not dynamically create PBR maps on sub-interfaces (i.e. vlans) This command will not dynamically create PBR maps on sub-interfaces
even if one is on the master. Each must have the PBR map explicitly added (i.e. vlans), even if one is on the master.
to the interface. Each sub-interface must have the PBR map enabled explicitly.
.. clicmd:: show pbr interface [NAME] [json] .. clicmd:: show pbr interface [NAME] [json]
@ -285,9 +320,9 @@ causes the policy to be installed into the kernel.
.. clicmd:: pbr table range (10000-4294966272) (10000-4294966272) .. clicmd:: pbr table range (10000-4294966272) (10000-4294966272)
Set or unset the range used to assign numeric table ID's to new Set or unset the range used to assign numeric table IDs to new
nexthop-group tables. Existing tables will not be modified to fit in this nexthop-group tables. Existing tables will not be modified to fit in this
range, so it is recommended to configure this before adding nexthop groups. range, so this range should be configured before adding nexthop groups.
.. seealso:: :ref:`pbr-details` .. seealso:: :ref:`pbr-details`
@ -299,23 +334,23 @@ PBR Debugs
.. clicmd:: debug pbr events|map|nht|zebra .. clicmd:: debug pbr events|map|nht|zebra
Debug pbr in pbrd daemon. You specify what types of debugs to turn on. Debug pbr in pbrd daemon. You must specify what types of debugs to turn on.
.. _pbr-details: .. _pbr-details:
PBR Details PBR Details
=========== ===========
Under the covers a PBR map is translated into two separate constructs in the Internally, a PBR map is translated into two separate constructs in the
Linux kernel. Linux kernel.
The PBR map specified creates a `ip rule ...` that is inserted into the Linux The PBR map creates an `ip rule ...` that is inserted into the Linux
kernel that points to a table to use for forwarding once the rule matches. kernel that points to a table to use for forwarding once the rule matches.
The creation of a nexthop or nexthop-group is translated to a default route in a The creation of a nexthop or nexthop-group is translated to a
table with the nexthops specified as the nexthops for the default route. table with a default route having the specified nexthop(s).
Sample configuration Sample configuration

View File

@ -8,6 +8,8 @@
# Cumulus Networks, Inc. # Cumulus Networks, Inc.
# Donald Sharp # Donald Sharp
# #
# Copyright (c) 2023 LabN Consulting, L.L.C.
#
""" """
test_pbr_topo1.py: Testing PBR test_pbr_topo1.py: Testing PBR
@ -19,6 +21,7 @@ import sys
import pytest import pytest
import json import json
import platform import platform
import re
from functools import partial from functools import partial
# Save the Current Working Directory to find configuration files. # Save the Current Working Directory to find configuration files.
@ -109,8 +112,29 @@ def test_converge_protocols():
topotest.sleep(5, "Waiting for PBR convergence") topotest.sleep(5, "Waiting for PBR convergence")
#
# router: r1
# tag: "show pbr interface"
# cmd: "show pbr interface json"
# expfile: "{}/{}/pbr-interface.json".format(CWD, router.name)
#
def runit(router, tag, cmd, expfile):
logger.info(expfile)
# Read expected result from file
expected = json.loads(open(expfile).read())
# Actual output from router
test_func = partial(topotest.router_json_cmp, router, cmd, expected)
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
assertmsg = '"{}" mismatches on {}'.format(tag, router.name)
if result is not None:
gather_pbr_data_on_error(router)
assert result is None, assertmsg
def test_pbr_data(): def test_pbr_data():
"Test PBR 'show ip eigrp'" "Test PBR"
tgen = get_topogen() tgen = get_topogen()
# Don't run this test if we have any failure. # Don't run this test if we have any failure.
@ -122,53 +146,145 @@ def test_pbr_data():
router_list = tgen.routers().values() router_list = tgen.routers().values()
for router in router_list: for router in router_list:
intf_file = "{}/{}/pbr-interface.json".format(CWD, router.name) runit(
logger.info(intf_file) router,
"show pbr interface",
# Read expected result from file "show pbr interface json",
expected = json.loads(open(intf_file).read()) "{}/{}/pbr-interface.json".format(CWD, router.name),
# Actual output from router
test_func = partial(
topotest.router_json_cmp, router, "show pbr interface json", expected
) )
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
assertmsg = '"show pbr interface" mismatches on {}'.format(router.name) runit(
router,
"show pbr map",
"show pbr map json",
"{}/{}/pbr-map.json".format(CWD, router.name),
)
runit(
router,
"show pbr nexthop-groups",
"show pbr nexthop-groups json",
"{}/{}/pbr-nexthop-groups.json".format(CWD, router.name),
)
########################################################################
# Field test - START
########################################################################
#
# New fields:
# match ip-protocol (was only tcp|udp, now any value in /etc/protocols)
# match pcp (0-7)
# match vlan (1-4094)
# match vlan (tagged|untagged|untagged-or-zero)
#
#
# tm: must-match pattern
# tN: must Not match pattern
#
# Note we are searching amid a bunch of other rules, so these elements
# should be unique.
#
ftest = [
{"c": "match ip-protocol icmp", "tm": r"IP protocol Match: 1$"},
{"c": "no match ip-protocol icmp", "tN": r"IP protocol Match:"},
{"c": "match pcp 6", "tm": r"PCP Match: 6$"},
{"c": "match pcp 0", "tm": r"PCP Match: 0$"},
{"c": "no match pcp 0", "tN": r"PCP Match:"},
{"c": "match vlan 33", "tm": r"VLAN ID Match: 33$"},
{"c": "no match vlan 33", "tN": r"VLAN ID Match:"},
{"c": "match vlan tagged", "tm": r"VLAN Flags Match: tagged$"},
{"c": "match vlan untagged", "tm": r"VLAN Flags Match: untagged$"},
{"c": "match vlan untagged-or-zero", "tm": r"VLAN Flags Match: untagged-or-zero$"},
{"c": "no match vlan tagged", "tN": r"VLAN Flags Match:"},
]
# returns None if command output is correct, otherwise returns output
def rtr_field_cmp(rtr, cmd, pat_mustmatch, pat_mustnotmatch):
outstr = rtr.vtysh_cmd(cmd)
if pat_mustmatch is not None:
logger.info("MUSTMATCH: {}".format(pat_mustmatch))
m = re.search(pat_mustmatch, outstr, flags=re.M)
if not m:
logger.info('Missing MUSTMATCH "{}"'.format(pat_mustmatch))
return "MISSING MUSTMATCH: " + outstr
if pat_mustnotmatch is not None:
logger.info("MUSTNOTMATCH: {}".format(pat_mustnotmatch))
m = re.search(pat_mustnotmatch, outstr, flags=re.M)
if m:
logger.info('Has MUSTNOTMATCH "{}"'.format(pat_mustnotmatch))
return "HAS MUSTNOTMATCH: " + outstr
return None
#
# This test sets fields in pbrd and looks for them in zebra via "sh pbr map"
#
def test_pbr_fields():
"Test setting and clearing rule fields"
tgen = get_topogen()
# Don't run this test if we have any failure.
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
logger.info("Verifying PBR rule fields")
tag = "field"
router_list = tgen.routers().values()
for router in router_list:
for t in ftest:
# send field-setting command
# always have a match dst-ip to satisfy rule non-empty check
vcmd = "c t\npbr-map ASAKUSA seq 100\n{}\nmatch dst-ip 9.9.9.9/32\nset nexthop-group A\nend\nend".format(
t["c"]
)
router.vtysh_multicmd(vcmd)
# debug
router.vtysh_cmd("sh pbr map")
match = None
notmatch = None
if "tm" in t:
match = t["tm"]
logger.info("MUSTMATCH: {}".format(match))
if "tN" in t:
notmatch = t["tN"]
logger.info("NOTMATCH: {}".format(notmatch))
test_func = partial(rtr_field_cmp, router, "sh pbr rule", match, notmatch)
_, result = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertmsg = '"{}" mismatches on {}'.format(tag, router.name)
if result is not None:
gather_pbr_data_on_error(router)
assert result is None, assertmsg
#
# clean up
#
vcmd = "c t\nno pbr-map ASAKUSA seq 100\nend"
router.vtysh_multicmd(vcmd)
match = None
notmatch = r"Seq 100\w"
test_func = partial(rtr_field_cmp, router, "sh pbr rule", match, notmatch)
_, result = topotest.run_and_expect(test_func, None, count=10, wait=1)
assertmsg = '"{}" mismatches on {}'.format(tag, router.name)
if result is not None: if result is not None:
gather_pbr_data_on_error(router) gather_pbr_data_on_error(router)
assert result is None, assertmsg assert result is None, assertmsg
map_file = "{}/{}/pbr-map.json".format(CWD, router.name)
logger.info(map_file)
# Read expected result from file ########################################################################
expected = json.loads(open(map_file).read()) # Field test - END
########################################################################
# Actual output from router
test_func = partial(
topotest.router_json_cmp, router, "show pbr map json", expected
)
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
assertmsg = '"show pbr map" mismatches on {}'.format(router.name)
if result is not None:
gather_pbr_data_on_error(router)
assert result is None, assertmsg
nexthop_file = "{}/{}/pbr-nexthop-groups.json".format(CWD, router.name)
logger.info(nexthop_file)
# Read expected result from file
expected = json.loads(open(nexthop_file).read())
# Actual output from router
test_func = partial(
topotest.router_json_cmp, router, "show pbr nexthop-groups json", expected
)
_, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
assertmsg = '"show pbr nexthop-groups" mismatches on {}'.format(router.name)
if result is not None:
gather_pbr_data_on_error(router)
assert result is None, assertmsg
def test_pbr_flap(): def test_pbr_flap():
@ -244,6 +360,7 @@ if __name__ == "__main__":
args = ["-s"] + sys.argv[1:] args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args)) sys.exit(pytest.main(args))
# #
# EXTRA SAUCE # EXTRA SAUCE
# #