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
***
:abbr:`PBR` is Policy Based Routing. This implementation supports a very simple
interface to allow admins to influence routing on their router. At this time
you can only match on destination and source prefixes for an incoming interface.
At this point in time, this implementation will only work on Linux.
:abbr:`PBR` is Policy Based Routing, which means forwarding based on
packet fields other than solely the destination IP address.
This implementation currently works only on Linux. Note that some
functionality (VLAN matching, packet mangling) is not supported by
the default Linux kernel dataplane provider.
.. _starting-pbr:
@ -17,12 +18,12 @@ Starting PBR
Default configuration file for *pbrd* is :file:`pbrd.conf`. The typical
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.
.. 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.
.. _nexthop-groups:
@ -30,9 +31,9 @@ documented elsewhere.
Nexthop Groups
==============
Nexthop groups are a way to encapsulate ECMP information together. It's a
listing of ECMP nexthops used to forward packets for when a pbr-map is matched.
For detailed instructions on how to specify a nexthop group on the CLI, see
A nexthop group is a list of ECMP nexthops used to forward packets
when a pbr-map is matched.
For details on specifying a nexthop group in the CLI, see
the nexthop-groups section.
Showing Nexthop Group Information
@ -42,7 +43,7 @@ Showing Nexthop Group Information
Display information on a PBR nexthop-group. If ``NAME`` is omitted, all
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 |
@ -74,118 +75,152 @@ Showing Nexthop Group Information
PBR Maps
========
PBR maps are a way to group policies that we would like to apply to individual
interfaces. These policies when applied are matched against incoming packets.
If matched the nexthop-group or nexthop is used to forward the packets to the
end destination.
PBR maps are a way to specify a set of rules that are applied to
packets received on individual interfaces.
If a received packet matches a rule, the rule's nexthop-group or
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)
Create a pbr-map with NAME and sequence number specified. This command puts
you into a new submode for pbr-map specification. To exit this mode type
exit or end as per normal conventions for leaving a sub-mode.
Create a pbr-map rule with map NAME and specified sequence number.
This command puts the CLI into a new submode for pbr-map rule specification.
To exit this submode, type ``exit`` or ``end``.
.. clicmd:: match src-ip PREFIX
When a incoming packet matches the source prefix specified, take the packet
and forward according to the nexthops specified. This command accepts both
v4 and v6 prefixes. This command is used in conjunction of the
:clicmd:`match dst-ip PREFIX` command for matching.
Match the packet's source IP address.
This command accepts both v4 and v6 prefixes.
.. clicmd:: match dst-ip PREFIX
When a incoming packet matches the destination prefix specified, take the
packet and forward according to the nexthops specified. This command accepts
both v4 and v6 prefixes. This command is used in conjunction of the
:clicmd:`match src-ip PREFIX` command for matching.
Match the packet's destination IP address.
This command accepts both v4 and v6 prefixes.
.. clicmd:: match src-port (1-65535)
When a incoming packet matches the source port specified, take the
packet and forward according to the nexthops specified.
Match the packet's UDP or TCP source port.
.. clicmd:: match dst-port (1-65535)
When a incoming packet matches the destination port specified, take the
packet and forward according to the nexthops specified.
Match the packet's UDP or TCP destination port.
.. clicmd:: match ip-protocol [tcp|udp]
.. clicmd:: match ip-protocol PROTOCOL
When a incoming packet matches the specified ip protocol, take the
packet and forward according to the nexthops specified.
Match the packet's IP protocol.
Protocol names are queried from the protocols database (``/etc/protocols``;
see ``man 5 protocols`` and ``man 3 getprotobyname``).
.. clicmd:: match mark (1-4294967295)
Select the mark to match. This is a linux only command and if attempted
on another platform it will be denied. This mark translates to the
underlying `ip rule .... fwmark XXXX` command.
Match the packet's meta-information mark.
The mark value is attached to the packet by the kernel/dataplane and
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)
Match packets according to the specified differentiated services code point
(DSCP) in the IP header; if this value matches then forward the packet
according to the nexthop(s) specified. The passed DSCP value may also be a
standard name for a differentiated service code point like cs0 or af11.
Match the packet's IP differentiated services code point (DSCP).
The specified DSCP may also be a standard name for a
differentiated service code point such as ``cs0`` or ``af11``.
You may only specify one dscp per route map sequence; to match on multiple
dscp values you will need to create several sequences, one for each value.
You may only specify one dscp per route map rule; to match on multiple
dscp values you will need to create several rules, one for each value.
.. clicmd:: match ecn (0-3)
Match packets according to the specified explicit congestion notification
(ECN) field in the IP header; if this value matches then forward the packet
according to the nexthop(s) specified.
Match the packet's IP explicit congestion notification (ECN) field.
.. 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)
Set the egress port queue identifier for matched packets. The Linux Kernel
provider does not currently support packet mangling, so this field will be
ignored unless another provider is used.
Action:
set the egress port queue identifier.
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)
Set the 802.1Q priority code point (PCP) for matched packets. A PCP of zero
is the defaul (nominally, "best effort"). The Linux Kernel provider does not
currently support packet mangling, so this field will be ignored unless
another provider is used.
Action:
set the 802.1Q priority code point (PCP).
A PCP of zero is the default (nominally, "best effort").
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)
Set the VLAN tag for matched packets. Identifiers 0 and 4095 are reserved.
The Linux Kernel provider does not currently support packet mangling, so
this field will be ignored unless another provider is used.
Action:
set the VLAN tag. Identifiers 0 and 4095 are reserved.
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
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
vlan` action.
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
Use the nexthop-group NAME as the place to forward packets when the match
commands have matched a packet.
Action:
forward the packet using nexthop-group 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
commands have matched a packet.
Action:
forward the packet using the specified single nexthop.
.. clicmd:: set vrf unchanged|NAME
If unchanged is set, the rule will use the vrf table the interface is in
as its lookup. If NAME is specified, the rule will use that vrf table as
its lookup.
Action:
If set to ``unchanged``, the rule will use the vrf table the interface
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.
.. clicmd:: show pbr map [NAME] [detail|json]
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.
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 |
@ -197,9 +232,9 @@ end destination.
| 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
described as below (not all fields are required):
described below (not all fields are required):
+-----------------+-------------------------------------------+---------+
| Key | Description | Type |
@ -227,8 +262,8 @@ end destination.
| nexthopGroup | This policy's nexthop group (if relevant) | Object |
+-----------------+-------------------------------------------+---------+
Finally, the ``nexthopGroup`` object above cotains information we know
about the configured nexthop for this policy:
Finally, the ``nexthopGroup`` object above contains information FRR
knows about the configured nexthop for this policy:
+---------------------+--------------------------------------+---------+
| Key | Description | Type |
@ -239,7 +274,7 @@ end destination.
+---------------------+--------------------------------------+---------+
| 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
==========
After you have specified a PBR map, in order for it to be turned on, you must
apply the PBR map to an interface. This policy application to an interface
After you have specified a PBR map, in order for it to be enabled, it must
be applied to an interface. This policy application to an interface
causes the policy to be installed into the kernel.
.. clicmd:: pbr-policy NAME
This command is available under interface sub-mode. This turns
on the PBR map NAME and allows it to work properly.
This command is available under interface sub-mode.
It enables the PBR map NAME on the interface.
.. note::
This will not dynamically create PBR maps on sub-interfaces (i.e. vlans)
even if one is on the master. Each must have the PBR map explicitly added
to the interface.
This command will not dynamically create PBR maps on sub-interfaces
(i.e. vlans), even if one is on the master.
Each sub-interface must have the PBR map enabled explicitly.
.. 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)
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
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`
@ -299,23 +334,23 @@ PBR Debugs
.. 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
===========
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.
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.
The creation of a nexthop or nexthop-group is translated to a default route in a
table with the nexthops specified as the nexthops for the default route.
The creation of a nexthop or nexthop-group is translated to a
table with a default route having the specified nexthop(s).
Sample configuration

View File

@ -8,6 +8,8 @@
# Cumulus Networks, Inc.
# Donald Sharp
#
# Copyright (c) 2023 LabN Consulting, L.L.C.
#
"""
test_pbr_topo1.py: Testing PBR
@ -19,6 +21,7 @@ import sys
import pytest
import json
import platform
import re
from functools import partial
# Save the Current Working Directory to find configuration files.
@ -109,8 +112,29 @@ def test_converge_protocols():
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():
"Test PBR 'show ip eigrp'"
"Test PBR"
tgen = get_topogen()
# Don't run this test if we have any failure.
@ -122,53 +146,145 @@ def test_pbr_data():
router_list = tgen.routers().values()
for router in router_list:
intf_file = "{}/{}/pbr-interface.json".format(CWD, router.name)
logger.info(intf_file)
# Read expected result from file
expected = json.loads(open(intf_file).read())
# Actual output from router
test_func = partial(
topotest.router_json_cmp, router, "show pbr interface json", expected
runit(
router,
"show pbr interface",
"show pbr interface json",
"{}/{}/pbr-interface.json".format(CWD, router.name),
)
_, 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:
gather_pbr_data_on_error(router)
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())
# 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
########################################################################
# Field test - END
########################################################################
def test_pbr_flap():
@ -244,6 +360,7 @@ if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))
#
# EXTRA SAUCE
#