Add support for routing rules

In order to enable the user using the policy routing (e.g. source
routing) or instructing the system which table to look up to determine
the correct route, add support for routing rules.

Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
Wen Liang 2022-03-17 17:14:00 -04:00 committed by Fernando Fernández Mancera
parent 512d840fc4
commit 6da2df4ba0
7 changed files with 869 additions and 0 deletions

View file

@ -501,6 +501,57 @@ The IP configuration supports the following options:
Note that Classless inter-domain routing (CIDR) notation or network mask notation
are not supported yet.
- `routing_rule`
The policy routing rules can be specified via a list of rules given in the
`routing_rule` option, which allow routing the packets on other packet fields
except for destination address. The default value is a an empty list. Each rule is
a dictionary with the following entries:
- `priority` -
The priority of the rule. A valid priority ranges from 0 to 4294967295. Higher
number means lower priority.
- `action` -
The action of the rule. The possible values are `to-table` (default),
`blackhole`, `prohibit`, `unreachable`.
- `dport`-
The range of the destination port (e.g. `1000 - 2000`). A valid dport value for
both start and end ranges from 0 to 65534. And the start cannot be greater than
the end.
- `family` -
The IP family of the rule. The possible values are `ipv4` and `ipv6`.
- `from` -
The source address of the packet to match (e.g. `192.168.100.58/24`).
- `fwmark` -
The fwmark value of the packet to match.
- `fwmask` -
The fwmask value of the packet to match.
- `iif` -
Select the incoming interface name to match.
- `invert` -
Invert the selected match of the rule. The possible values are boolean values
`True` and `False` (default). If the value is `True`, this is equivalent to match
any packet that not satisfying selected match of the rule.
- `ipproto` -
Select the IP protocol value to match, the valid value ranges from 1 to 255.
- `oif` -
Select the outgoing interface name to match.
- `sport` -
The range of the source port (e.g. `1000 - 2000`). A valid sport value for both
start and end ranges from 0 to 65534. And the start cannot be greater than the
end.
- `suppress_prefixlength` -
Reject routing decisions that have a prefix length of the specified or less.
- `table` -
The route table to look up for the `to-table` action.
- `to` -
The destination address of the packet to match (e.g. `192.168.100.58/24`).
- `tos` -
Select the tos value to match.
- `uid` -
The range of the uid to match (e.g. `1000 - 2000`). A valid uid value for both
start and end ranges from 0 to 4294967295. And the start cannot be greater than
the end.
- `route_append_only`
The `route_append_only` option allows only to add new routes to the

View file

@ -1148,6 +1148,76 @@ class NMUtil:
s_ip4.add_route(rr)
else:
s_ip6.add_route(rr)
for routing_rule in ip["routing_rule"]:
nm_routing_rule = NM.IPRoutingRule.new(routing_rule["family"])
NM.IPRoutingRule.set_priority(nm_routing_rule, routing_rule["priority"])
# check the link below for the enum value of supported action
# https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/fib_rules.h?id=f443e374ae131c168a065ea1748feac6b2e76613#n88
action_ids = {
"to-table": 1,
"blackhole": 6,
"prohibit": 7,
"unreachable": 8,
}
NM.IPRoutingRule.set_action(
nm_routing_rule, action_ids[routing_rule["action"]]
)
if routing_rule["dport"]:
NM.IPRoutingRule.set_destination_port(
nm_routing_rule,
routing_rule["dport"][0],
routing_rule["dport"][1],
)
if routing_rule["from"]:
NM.IPRoutingRule.set_from(
nm_routing_rule,
routing_rule["from"]["address"],
routing_rule["from"]["prefix"],
)
if routing_rule["fwmark"]:
NM.IPRoutingRule.set_fwmark(
nm_routing_rule, routing_rule["fwmark"], routing_rule["fwmask"]
)
if routing_rule["iif"]:
NM.IPRoutingRule.set_iifname(nm_routing_rule, routing_rule["iif"])
NM.IPRoutingRule.set_invert(nm_routing_rule, routing_rule["invert"])
if routing_rule["ipproto"]:
NM.IPRoutingRule.set_ipproto(
nm_routing_rule, routing_rule["ipproto"]
)
if routing_rule["oif"]:
NM.IPRoutingRule.set_oifname(nm_routing_rule, routing_rule["oif"])
if routing_rule["sport"]:
NM.IPRoutingRule.set_source_port(
nm_routing_rule,
routing_rule["sport"][0],
routing_rule["sport"][1],
)
if routing_rule["suppress_prefixlength"] is not None:
NM.IPRoutingRule.set_suppress_prefixlength(
nm_routing_rule, routing_rule["suppress_prefixlength"]
)
if routing_rule["table"]:
NM.IPRoutingRule.set_table(nm_routing_rule, routing_rule["table"])
if routing_rule["to"]:
NM.IPRoutingRule.set_to(
nm_routing_rule,
routing_rule["to"]["address"],
routing_rule["to"]["prefix"],
)
if routing_rule["tos"]:
NM.IPRoutingRule.set_tos(nm_routing_rule, routing_rule["tos"])
if routing_rule["uid"]:
NM.IPRoutingRule.set_uid_range(
nm_routing_rule,
routing_rule["uid"][0],
routing_rule["uid"][1],
)
if routing_rule["family"] == socket.AF_INET:
s_ip4.add_routing_rule(nm_routing_rule)
else:
s_ip6.add_routing_rule(nm_routing_rule)
if connection["ieee802_1x"]:
s_8021x = self.connection_ensure_setting(con, NM.Setting8021x)

View file

@ -353,6 +353,67 @@ class ArgValidatorNum(ArgValidator):
return v
class ArgValidatorRange(ArgValidator):
def __init__( # pylint: disable=too-many-arguments
self,
name,
required=False,
val_min=None,
val_max=None,
default_value=None,
):
ArgValidator.__init__(self, name, required, default_value)
self.val_min = val_min
self.val_max = val_max
def _validate_impl(self, value, name):
range = None
if isinstance(value, Util.STRING_TYPE):
match_group = re.match(r"^ *([0-9]+) *- *([0-9]+) *$", value)
if match_group:
try:
range = (int(match_group.group(1)), int(match_group.group(2)))
except Exception:
pass
else:
try:
range = (int(value), int(value))
except Exception:
pass
elif isinstance(value, bool):
# bool can (probably) be converted to integer type,
# but here we don't want to accept a boolean value.
pass
elif isinstance(value, int):
range = (value, value)
if range is None:
raise ValidationError(name, "the range value {0} is invalid".format(value))
if range[0] > range[1]:
raise ValidationError(
name,
"the range start cannot be greater than range end",
)
if self.val_min is not None:
if range[0] < self.val_min:
raise ValidationError(
name,
"lower range value is {0} but cannot be less than {1}".format(
range[0], self.val_min
),
)
if self.val_max is not None:
if range[1] > self.val_max:
raise ValidationError(
name,
"upper range value is {0} but cannot be greater than {1}".format(
range[1], self.val_max
),
)
return range
class ArgValidatorBool(ArgValidator):
def __init__(self, name, required=False, default_value=False):
ArgValidator.__init__(self, name, required, default_value)
@ -633,6 +694,133 @@ class ArgValidatorIPRoute(ArgValidatorDict):
return result
class ArgValidatorIPRoutingRule(ArgValidatorDict):
def __init__(self, name, required=False):
ArgValidatorDict.__init__(
self,
name,
required,
nested=[
ArgValidatorNum(
"priority",
default_value=None,
required=True,
val_min=0,
val_max=0xFFFFFFFF,
),
ArgValidatorStr(
"action",
default_value="to-table",
enum_values=["to-table", "blackhole", "prohibit", "unreachable"],
),
ArgValidatorRange("dport", val_min=1, val_max=65534),
ArgValidatorStr(
"family",
default_value=None,
enum_values=["ipv4", "ipv6"],
),
ArgValidatorIPAddr("from"),
ArgValidatorNum(
"fwmark", default_value=None, val_min=1, val_max=0xFFFFFFFF
),
ArgValidatorNum(
"fwmask", default_value=None, val_min=1, val_max=0xFFFFFFFF
),
ArgValidatorStr("iif", default_value=None),
ArgValidatorBool("invert", default_value=False),
ArgValidatorNum("ipproto", default_value=None, val_min=1, val_max=255),
ArgValidatorStr("oif", default_value=None),
ArgValidatorRange("sport", val_min=1, val_max=65534),
ArgValidatorNum("suppress_prefixlength", default_value=None, val_min=0),
ArgValidatorRouteTable("table"),
ArgValidatorIPAddr("to"),
ArgValidatorNum("tos", default_value=None, val_min=1, val_max=255),
ArgValidatorRange("uid", val_min=0, val_max=0xFFFFFFFF),
],
default_value=None,
)
def _validate_post(self, value, name, result):
family = None
if result["family"]:
family = Util.addr_family_norm(result["family"])
elif result["from"]:
family = result["from"]["family"]
elif result["to"]:
family = result["to"]["family"]
if not family:
raise ValidationError(name, "specify the address family 'family'")
if result["from"]:
if result["from"]["family"] != family:
raise ValidationError(name, "invalid address family in 'from'")
if result["to"]:
if result["to"]["family"] != family:
raise ValidationError(name, "invalid address family in 'to'")
result["family"] = family
if result["action"] == "to-table":
if result["table"] is None:
raise ValidationError(
name,
"missing 'table' for the routing rule",
)
if result["from"] is not None:
if result["from"]["prefix"] == 0:
raise ValidationError(
name,
"the prefix length for 'from' cannot be zero",
)
if result["to"] is not None:
if result["to"]["prefix"] == 0:
raise ValidationError(
name,
"the prefix length for 'to' cannot be zero",
)
if (result["fwmask"] is None) != (result["fwmark"] is None):
raise ValidationError(
name,
"'fwmask' and 'fwmark' must be set together",
)
if result["iif"] is not None:
if not Util.ifname_valid(result["iif"]):
raise ValidationError(
name,
"the incoming interface '{0}' specified in the routing rule is "
"invalid interface_name".format(result["iif"]),
)
if result["oif"] is not None:
if not Util.ifname_valid(result["oif"]):
raise ValidationError(
name,
"the outgoing interface '{0}' specified in the routing rule is "
"invalid interface_name".format(result["oif"]),
)
if result["suppress_prefixlength"] is not None:
if not Util.addr_family_valid_prefix(
result["family"], result["suppress_prefixlength"]
):
raise ValidationError(
name,
"The specified 'suppress_prefixlength' cannot be greater than "
"{0}".format(Util.addr_family_prefix_length(result["family"])),
)
if result["action"] != "to-table":
raise ValidationError(
name,
"'suppress_prefixlength' is only allowed with the to-table action",
)
return result
class ArgValidator_DictIP(ArgValidatorDict):
REGEX_DNS_OPTIONS = [
r"^attempts:([1-9]\d*|0)$",
@ -699,6 +887,11 @@ class ArgValidator_DictIP(ArgValidatorDict):
),
default_value=list,
),
ArgValidatorList(
"routing_rule",
nested=ArgValidatorIPRoutingRule("routing_rule[?]"),
default_value=list,
),
],
default_value=lambda: {
"dhcp4": True,
@ -712,6 +905,7 @@ class ArgValidator_DictIP(ArgValidatorDict):
"address": [],
"auto_gateway": None,
"route": [],
"routing_rule": [],
"route_append_only": False,
"rule_append_only": False,
"dns": [],
@ -2311,6 +2505,34 @@ class ArgValidator_ListConnections(ArgValidatorList):
"the bond option peer_notif_delay is not supported in "
"NetworkManger until NM 1.30",
)
if connection["ip"]["routing_rule"]:
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS:
raise ValidationError.from_connection(
idx,
"ip.routing_rule is not supported by initscripts",
)
for routing_rule in connection["ip"]["routing_rule"]:
if routing_rule["suppress_prefixlength"] is not None:
if not hasattr(
Util.NM(), "NM_IP_ROUTING_RULE_ATTR_SUPPRESS_PREFIXLENGTH"
):
raise ValidationError.from_connection(
idx,
"the routing rule selector 'suppress_prefixlength' is not "
"supported in NetworkManger until NM 1.20",
)
for routing_rule in connection["ip"]["routing_rule"]:
if routing_rule["uid"] is not None:
if not hasattr(
Util.NM(), "NM_IP_ROUTING_RULE_ATTR_UID_RANGE_START"
):
raise ValidationError.from_connection(
idx,
"the routing rule selector 'uid' is not supported in "
"NetworkManger until NM 1.34",
)
self.validate_route_tables(connection, idx)

View file

@ -84,6 +84,7 @@ ibution_major_version | int < 9",
},
"playbooks/tests_reapply.yml": {},
"playbooks/tests_route_table.yml": {},
"playbooks/tests_routing_rules.yml": {},
# team interface is not supported on Fedora
"playbooks/tests_team.yml": {
EXTRA_RUN_CONDITION: "ansible_distribution != 'Fedora'",

View file

@ -0,0 +1,223 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- hosts: all
- name: Test configuring ethernet devices
hosts: all
vars:
type: veth
interface: ethtest0
tasks:
- name: "set type={{ type }} and interface={{ interface }}"
set_fact:
type: "{{ type }}"
interface: "{{ interface }}"
- include_tasks: tasks/show_interfaces.yml
- include_tasks: tasks/manage_test_interface.yml
vars:
state: present
- include_tasks: tasks/assert_device_present.yml
- name: Configure connection profile and specify the numeric table in
static routes
import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: "{{ interface }}"
interface_name: "{{ interface }}"
state: up
type: ethernet
autoconnect: yes
ip:
dhcp4: no
address:
- 198.51.100.3/26
- 2001:db8::2/32
route:
- network: 198.51.100.64
prefix: 26
gateway: 198.51.100.6
metric: 4
table: 30200
- network: 198.51.100.128
prefix: 26
gateway: 198.51.100.1
metric: 2
table: 30400
- network: 2001:db8::4
prefix: 32
gateway: 2001:db8::1
metric: 2
table: 30600
routing_rule:
- priority: 30200
from: 198.51.100.58/26
table: 30200
- priority: 30201
family: ipv4
fwmark: 1
fwmask: 1
table: 30200
- priority: 30202
family: ipv4
ipproto: 6
table: 30200
- priority: 30203
family: ipv4
sport: 128 - 256
table: 30200
- priority: 30204
family: ipv4
tos: 8
table: 30200
- priority: 30400
to: 198.51.100.128/26
table: 30400
- priority: 30401
family: ipv4
iif: iiftest
table: 30400
- priority: 30402
family: ipv4
oif: oiftest
table: 30400
- priority: 30600
to: 2001:db8::4/32
table: 30600
- priority: 30601
family: ipv6
dport: 128 - 256
invert: True
table: 30600
# the routing rule selector sport and ipproto are not supported by iproute
# since v4.17.0, and the iproute installed in CentOS-7 and RHEL-7 is
# v4.11.0
- name: Get the routing rule for looking up the table 30200
command: ip rule list table 30200
register: route_rule_table_30200
ignore_errors: yes
changed_when: false
when: ansible_distribution_major_version != "7"
- name: Get the routing rule for looking up the table 30400
command: ip rule list table 30400
register: route_rule_table_30400
ignore_errors: yes
changed_when: false
when: ansible_distribution_major_version != "7"
- name: Get the routing rule for looking up the table 30600
command: ip -6 rule list table 30600
register: route_rule_table_30600
ignore_errors: yes
changed_when: false
when: ansible_distribution_major_version != "7"
- name: Get the IPv4 routing rule for the connection "{{ interface }}"
command: nmcli -f ipv4.routing-rules c show "{{ interface }}"
register: connection_route_rule
ignore_errors: yes
changed_when: false
- name: Get the IPv6 routing rule for the connection "{{ interface }}"
command: nmcli -f ipv6.routing-rules c show "{{ interface }}"
register: connection_route_rule6
ignore_errors: yes
changed_when: false
- name: Assert that the routing rule with table lookup 30200 matches the
specified rule
assert:
that:
- route_rule_table_30200.stdout is search("30200:(\s+)from
198.51.100.58/26 lookup 30200")
- route_rule_table_30200.stdout is search("30201:(\s+)from all fwmark
0x1/0x1 lookup 30200")
- route_rule_table_30200.stdout is search("30202:(\s+)from all
ipproto tcp lookup 30200")
- route_rule_table_30200.stdout is search("30203:(\s+)from all sport
128-256 lookup 30200")
- route_rule_table_30200.stdout is search("30204:(\s+)from all tos
(0x08|throughput) lookup 30200")
msg: "the routing rule with table lookup 30200 does not match the
specified rule"
when: ansible_distribution_major_version != "7"
- name: Assert that the routing rule with table lookup 30400 matches the
specified rule
assert:
that:
- route_rule_table_30400.stdout is search("30400:(\s+)from all to
198.51.100.128/26 lookup 30400")
- route_rule_table_30400.stdout is search("30401:(\s+)from all iif
iiftest \[detached\] lookup 30400")
- route_rule_table_30400.stdout is search("30402:(\s+)from all oif
oiftest \[detached\] lookup 30400")
msg: "the routing rule with table lookup 30400 does not match the
specified rule"
when: ansible_distribution_major_version != "7"
- name: Assert that the routing rule with table lookup 30600 matches the
specified rule
assert:
that:
- route_rule_table_30600.stdout is search("30600:(\s+)from all to
2001:db8::4/32 lookup 30600")
- route_rule_table_30600.stdout is search("30601:(\s+)not from all
dport 128-256 lookup 30600")
msg: "the routing rule with table lookup 30600 does not match the
specified rule"
when: ansible_distribution_major_version != "7"
- name: Assert that the IPv4 routing rule in the connection
"{{ interface }}" matches the specified rule
assert:
that:
- connection_route_rule.stdout is search("priority 30200 from
198.51.100.58/26 table 30200")
- connection_route_rule.stdout is search("priority 30201 from
0.0.0.0/0 fwmark 0x1/0x1 table 30200")
- connection_route_rule.stdout is search("priority 30202 from
0.0.0.0/0 ipproto 6 table 30200")
- connection_route_rule.stdout is search("priority 30203 from
0.0.0.0/0 sport 128-256 table 30200")
- connection_route_rule.stdout is search("priority 30204 from
0.0.0.0/0 tos 0x08 table 30200")
- connection_route_rule.stdout is search("priority 30400 to
198.51.100.128/26 table 30400")
- connection_route_rule.stdout is search("priority 30401 from
0.0.0.0/0 iif iiftest table 30400")
- connection_route_rule.stdout is search("priority 30402 from
0.0.0.0/0 oif oiftest table 30400")
msg: "the IPv4 routing rule in the connection '{{ interface }}' does
not match the specified rule"
- name: Assert that the IPv6 routing rule in the connection
"{{ interface }}" matches the specified rule
assert:
that:
- connection_route_rule6.stdout is search("priority 30600 to
2001:db8::4/32 table 30600")
- connection_route_rule6.stdout is search("priority 30601 not from
::/0 dport 128-256 table 30600") or
connection_route_rule6.stdout is search("not priority 30601 from
::/0 dport 128-256 table 30600")
msg: "the IPv6 routing rule in the connection '{{ interface }}' does
not match the specified rule"
- import_playbook: down_profile+delete_interface.yml
vars:
profile: "{{ interface }}"
# FIXME: assert profile/device down
- import_playbook: remove_profile.yml
vars:
profile: "{{ interface }}"
- name: Assert device and profile are absent
hosts: all
tasks:
- include_tasks: tasks/assert_profile_absent.yml
vars:
profile: "{{ interface }}"
- include_tasks: tasks/assert_device_absent.yml
...

View file

@ -0,0 +1,20 @@
# SPDX-License-Identifier: BSD-3-Clause
# This file was generated by ensure_provider_tests.py
---
# set network provider and gather facts
- hosts: all
name: Run playbook 'playbooks/tests_routing_rules.yml' with nm as provider
tasks:
- include_tasks: tasks/el_repo_setup.yml
- name: Set network provider to 'nm'
set_fact:
network_provider: nm
tags:
- always
# The test requires or should run with NetworkManager, therefore it cannot run
# on RHEL/CentOS 6
- import_playbook: playbooks/tests_routing_rules.yml
when:
- ansible_distribution_major_version != '6'

View file

@ -194,6 +194,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
"route": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
"dns": [],
"dns_options": [],
@ -409,6 +410,45 @@ class TestValidator(Python26CompatTestCase):
self.assertValidationError(v, False)
self.assertValidationError(v, True)
def test_validate_range(self):
v = network_lsr.argument_validator.ArgValidatorRange(
"range", val_min=0, val_max=65534
)
self.assertEqual((1, 1), v.validate(1))
self.assertEqual((10, 1000), v.validate("10-1000"))
self.assertEqual((256, 256), v.validate("256"))
self.assertRaisesRegex(
ValidationError,
"the range value True is invalid",
v.validate,
True,
)
self.assertRaisesRegex(
ValidationError,
"the range value 2.5 is invalid",
v.validate,
2.5,
)
self.assertRaisesRegex(
ValidationError,
"the range start cannot be greater than range end",
v.validate,
"2000-1000",
)
self.assertRaisesRegex(
ValidationError,
"upper range value is 65535 but cannot be greater than 65534",
v.validate,
"1-65535",
)
self.assertRaisesRegex(
ValidationError,
"lower range value is -1 but cannot be less than 0",
v.validate,
-1,
)
def test_validate_bool(self):
v = network_lsr.argument_validator.ArgValidatorBool("state")
@ -543,6 +583,7 @@ class TestValidator(Python26CompatTestCase):
"rule_append_only": False,
"route": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
"dns": [],
"dns_options": [],
@ -602,6 +643,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -652,6 +694,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -747,6 +790,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": "52:54:00:44:9f:ba",
"controller": None,
@ -813,6 +857,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": None,
"match": {},
@ -887,6 +932,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -980,6 +1026,7 @@ class TestValidator(Python26CompatTestCase):
"route": [],
"route_metric6": None,
"route_metric4": None,
"routing_rule": [],
"dns_options": [],
"dns_search": [],
"dhcp4_send_hostname": None,
@ -1048,6 +1095,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
}
],
"routing_rule": [],
},
"mac": None,
"controller": None,
@ -1124,6 +1172,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": None,
"match": {},
@ -1200,6 +1249,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": None,
"match": {},
@ -1299,6 +1349,7 @@ class TestValidator(Python26CompatTestCase):
"route": [],
"route_metric6": None,
"route_metric4": None,
"routing_rule": [],
"dns_options": [],
"dns_search": [],
"dhcp4_send_hostname": None,
@ -1368,6 +1419,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
}
],
"routing_rule": [],
},
"mac": None,
"controller": None,
@ -1444,6 +1496,7 @@ class TestValidator(Python26CompatTestCase):
"route": [],
"route_metric6": None,
"route_metric4": None,
"routing_rule": [],
"dns_options": [],
"ipv6_disabled": False,
"dns_search": [],
@ -1507,6 +1560,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
}
],
"routing_rule": [],
},
"mac": None,
"macvlan": {"mode": "bridge", "promiscuous": True, "tap": False},
@ -1564,6 +1618,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
}
],
"routing_rule": [],
},
"mac": None,
"macvlan": {"mode": "passthru", "promiscuous": False, "tap": True},
@ -1654,6 +1709,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": None,
@ -1696,6 +1752,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": None,
@ -1790,6 +1847,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": None,
"match": {},
@ -1868,6 +1926,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
},
"mac": None,
"match": {},
@ -1918,6 +1977,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"rule_append_only": False,
"route": [],
"routing_rule": [],
"auto6": True,
"ipv6_disabled": False,
"dhcp4": True,
@ -1977,6 +2037,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -2054,6 +2115,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": None,
@ -2096,6 +2158,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": None,
@ -2154,6 +2217,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": None,
@ -2229,6 +2293,7 @@ class TestValidator(Python26CompatTestCase):
"route_append_only": False,
"route_metric4": None,
"route_metric6": None,
"routing_rule": [],
"rule_append_only": False,
},
"mac": "11:22:33:44:55:66:77:88:99:00:"
@ -2324,6 +2389,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
},
],
"routing_rule": [],
"dns": [],
"dns_options": [],
"dns_search": ["aa", "bb"],
@ -2431,6 +2497,7 @@ class TestValidator(Python26CompatTestCase):
"table": None,
},
],
"routing_rule": [],
"dns": [],
"dns_options": [],
"dns_search": ["aa", "bb"],
@ -2552,6 +2619,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -2631,6 +2699,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -2710,6 +2779,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -2787,6 +2857,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -2854,6 +2925,7 @@ class TestValidator(Python26CompatTestCase):
"dns_options": [],
"dns_search": [],
"route_metric6": None,
"routing_rule": [],
"dhcp4_send_hostname": None,
},
"mac": None,
@ -4374,6 +4446,216 @@ class TestValidatorRouteTable(Python26CompatTestCase):
)
class TestValidatorRoutingRules(Python26CompatTestCase):
def setUp(self):
self.test_connections = [
{
"name": "eth0",
"type": "ethernet",
"ip": {
"dhcp4": False,
"address": ["198.51.100.3/26"],
"route": [
{
"network": "198.51.100.128",
"prefix": 26,
"gateway": "198.51.100.1",
"metric": 2,
"table": 30400,
},
],
"routing_rule": [
{
"action": "to-table",
"priority": 256,
},
],
},
}
]
self.validator = network_lsr.argument_validator.ArgValidator_ListConnections()
def test_routing_rule_missing_address_family(self):
"""
Test that the address family has to be specified if cannot be derived from src
or dst address
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["suppress_prefixlength"] = 32
self.assertRaisesRegex(
ValidationError,
"specify the address family 'family'",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_validate_address_family(self):
"""
Test that the derived address family and the specified address family should be
consistent
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv6"
self.test_connections[0]["ip"]["routing_rule"][0]["from"] = "198.51.100.58/24"
self.assertRaisesRegex(
ValidationError,
"invalid address family in 'from'",
self.validator.validate,
self.test_connections,
)
self.test_connections[0]["ip"]["routing_rule"][0]["from"] = "2001:db8::2/32"
self.test_connections[0]["ip"]["routing_rule"][0]["to"] = "198.51.100.60/24"
self.assertRaisesRegex(
ValidationError,
"invalid address family in 'to'",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_missing_table(self):
"""
Test that table has to be defined when the action of the routing rule is
"to-table"
"""
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.assertRaisesRegex(
ValidationError,
"missing 'table' for the routing rule",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_invalid_from_prefix_length(self):
"""
Test that the prefix length for from/src cannot be zero when from/src is
specified
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["from"] = "198.51.100.58/0"
self.assertRaisesRegex(
ValidationError,
"the prefix length for 'from' cannot be zero",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_invalid_to_prefix_length(self):
"""
Test that the prefix length for to/dst cannot be zero when to/dst is specified
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["to"] = "198.51.100.58/0"
self.assertRaisesRegex(
ValidationError,
"the prefix length for 'to' cannot be zero",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_validate_fwmark(self):
"""
Test that fwmark requires fwmask to be specified
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.test_connections[0]["ip"]["routing_rule"][0]["fwmark"] = 1
self.assertRaisesRegex(
ValidationError,
"'fwmask' and 'fwmark' must be set together",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_validate_fwmask(self):
"""
Test that fwmask requires fwmark to be specified
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.test_connections[0]["ip"]["routing_rule"][0]["fwmask"] = 1
self.assertRaisesRegex(
ValidationError,
"'fwmask' and 'fwmark' must be set together",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_invalid_incoming_interface_name(self):
"""
Test the invalid incoming interface name specified in the routing rule
"""
self.test_connections[0]["ip"]["routing_rule"][0]["iif"] = " test "
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.assertRaisesRegex(
ValidationError,
"the incoming interface '{0}' specified in the routing rule is invalid "
"interface_name".format(
self.test_connections[0]["ip"]["routing_rule"][0]["iif"]
),
self.validator.validate,
self.test_connections,
)
def test_routing_rule_invalid_outgoing_interface_name(self):
"""
Test the invalid outgoing interface name specified in the routing rule
"""
self.test_connections[0]["ip"]["routing_rule"][0]["oif"] = " test "
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.assertRaisesRegex(
ValidationError,
"the outgoing interface '{0}' specified in the routing rule is invalid "
"interface_name".format(
self.test_connections[0]["ip"]["routing_rule"][0]["oif"]
),
self.validator.validate,
self.test_connections,
)
def test_routing_rule_validate_uid(self):
"""
Test the invalid uid specified in the routing rule
"""
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
self.test_connections[0]["ip"]["routing_rule"][0]["uid"] = "2000 - 1000"
self.assertRaisesRegex(
ValidationError,
"the range start cannot be greater than range end",
self.validator.validate,
self.test_connections,
)
def test_routing_rule_validate_suppress_prefixlength(self):
"""
Test the invalid suppress_prefixlength setting
"""
self.test_connections[0]["ip"]["routing_rule"][0]["suppress_prefixlength"] = 40
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv4"
self.test_connections[0]["ip"]["routing_rule"][0]["table"] = 256
suppress_prefixlength_val_max = Util.addr_family_prefix_length(
self.test_connections[0]["ip"]["routing_rule"][0]["family"]
)
self.assertRaisesRegex(
ValidationError,
"The specified 'suppress_prefixlength' cannot be greater than {0}".format(
suppress_prefixlength_val_max
),
self.validator.validate,
self.test_connections,
)
self.test_connections[0]["ip"]["routing_rule"][0]["family"] = "ipv6"
self.test_connections[0]["ip"]["routing_rule"][0]["action"] = "blackhole"
self.assertRaisesRegex(
ValidationError,
"'suppress_prefixlength' is only allowed with the to-table action",
self.validator.validate,
self.test_connections,
)
class TestValidatorDictBond(Python26CompatTestCase):
def setUp(self):
self.validator = network_lsr.argument_validator.ArgValidator_ListConnections()