Skip to content

Commit

Permalink
feat: Add support for the route_type
Browse files Browse the repository at this point in the history
When users have BGP routing setups, it is common practice to blackhole
some less-specific routes in order to avoid routing loops, and the BGP
router might insert a more specific route dynamically afterwards.

Signed-off-by: Wen Liang <liangwen12year@gmail.com>
  • Loading branch information
liangwen12year authored and Wen Liang committed Jan 9, 2024
1 parent e4d4997 commit 659adcb
Show file tree
Hide file tree
Showing 8 changed files with 336 additions and 6 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -586,13 +586,16 @@ The IP configuration supports the following options:

Static route configuration can be specified via a list of routes given in the
`route` option. The default value is an empty list. Each route is a dictionary with
the following entries: `network`, `prefix`, `gateway`, `metric` and `table`.
the following entries: `gateway`, `metric`, `network`, `prefix`, `table` and `type`.
`network` and `prefix` specify the destination network. `table` supports both the
numeric table and named table. In order to specify the named table, the users have
to ensure the named table is properly defined in `/etc/iproute2/rt_tables` or
`/etc/iproute2/rt_tables.d/*.conf`.
Note that Classless inter-domain routing (CIDR) notation or network mask notation
are not supported yet.
numeric table and named table. In order to specify the named table, the users have to
ensure the named table is properly defined in `/etc/iproute2/rt_tables` or
`/etc/iproute2/rt_tables.d/*.conf`. The supported `type` are `blackhole`, `prohibit`,
`unreachable` and these three type routes can not have next hop (gateway) specified.
It is possible to create a route without `type` specified. For the route `type`
definition, please refer to [man 8 ip-route](https://man7.org/linux/man-pages/man8/ip-route.8.html#DESCRIPTION).
Note that Classless inter-domain routing(CIDR) notation or network mask notation are
not supported yet.

- `routing_rule`

Expand Down
51 changes: 51 additions & 0 deletions examples/route_type_support.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- name: Manage route type routes
hosts: all
tasks:
- name: Configure connection profile and route type routes
import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: eth0
interface_name: eth0
state: up
type: ethernet
autoconnect: true
ip:
dhcp4: false
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.53.100.18
prefix: 32
metric: 20
type: blackhole
table: 30200
- network: 198.53.100.12
prefix: 32
metric: 24
type: unreachable
table: 30200
- network: 198.53.100.10
prefix: 32
metric: 30
type: prohibit
table: 30200
- network: 2001:db8::4
prefix: 128
metric: 2
type: blackhole
table: 30600
- network: 2001:db8::6
prefix: 128
metric: 4
type: prohibit
table: 30600
4 changes: 4 additions & 0 deletions library/network_connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -1255,6 +1255,10 @@ def connection_create(self, connections, idx, connection_current=None):
new_route = NM.IPRoute.new(
r["family"], r["network"], r["prefix"], r["gateway"], r["metric"]
)
if r["type"]:
NM.IPRoute.set_attribute(

Check warning on line 1259 in library/network_connections.py

View check run for this annotation

Codecov / codecov/patch

library/network_connections.py#L1258-L1259

Added lines #L1258 - L1259 were not covered by tests
new_route, "type", Util.GLib().Variant("s", r["type"])
)
if r["table"]:
NM.IPRoute.set_attribute(
new_route, "table", Util.GLib().Variant.new_uint32(r["table"])
Expand Down
20 changes: 20 additions & 0 deletions module_utils/network_lsr/argument_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,11 @@ def __init__(self, name, family=None, required=False):
ArgValidatorNum(
"metric", default_value=-1, val_min=-1, val_max=UINT32_MAX
),
ArgValidatorStr(
"type",
default_value=None,
enum_values=["blackhole", "prohibit", "unreachable"],
),
ArgValidatorRouteTable("table"),
],
default_value=None,
Expand All @@ -688,13 +693,20 @@ def _validate_post(self, value, name, result):
result["family"] = family

gateway = result["gateway"]
type = result["type"]
if gateway is not None:
if family != gateway["family"]:
raise ValidationError(
name,
"conflicting address family between network and gateway '%s'"
% (gateway["address"]),
)
if type is not None:
raise ValidationError(
name,
"a %s route can not have a next hop '%s'"
% (type, gateway["address"]),
)
result["gateway"] = gateway["address"]

prefix = result["prefix"]
Expand Down Expand Up @@ -2607,6 +2619,14 @@ def _ipv6_is_not_configured(connection):
"NetworkManger until NM 1.30",
)

if connection["ip"]["route"]:
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS:
for route in connection["ip"]["route"]:
if route["type"] is not None:
raise ValidationError.from_connection(

Check warning on line 2626 in module_utils/network_lsr/argument_validator.py

View check run for this annotation

Codecov / codecov/patch

module_utils/network_lsr/argument_validator.py#L2626

Added line #L2626 was not covered by tests
idx,
"type is not supported by initscripts",
)
if connection["ip"]["routing_rule"]:
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS:
raise ValidationError.from_connection(
Expand Down
5 changes: 5 additions & 0 deletions tests/ensure_provider_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@
},
"playbooks/tests_reapply.yml": {},
"playbooks/tests_route_table.yml": {},
"playbooks/tests_route_type.yml": {
MINIMUM_VERSION: "'1.36.0'",
"comment": "# NetworkManager 1.36.0 added support for special route types: \
prohibit, blackhole and unreachable",
},
"playbooks/tests_routing_rules.yml": {},
# team interface is not supported on Fedora
"playbooks/tests_team.yml": {
Expand Down
167 changes: 167 additions & 0 deletions tests/playbooks/tests_route_type.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# SPDX-License-Identifier: BSD-3-Clause
---
- name: Play for testing route types
hosts: all
vars:
type: veth
interface: ethtest0
tasks:
- name: "Set type={{ type }} and interface={{ interface }}" # noqa name
set_fact:
type: "{{ type }}"
interface: "{{ interface }}"
- name: Include the task 'show_interfaces.yml'
include_tasks: tasks/show_interfaces.yml
- name: Include the task 'manage_test_interface.yml'
include_tasks: tasks/manage_test_interface.yml
vars:
state: present
- name: Include the task 'assert_device_present.yml'
include_tasks: tasks/assert_device_present.yml

- name: Configure connection profile and specify the route types in
static routes
import_role:
name: linux-system-roles.network
vars:
network_connections:
- name: "{{ interface }}"
interface_name: "{{ interface }}"
state: up
type: ethernet
autoconnect: true
ip:
dhcp4: false
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.53.100.18
prefix: 32
metric: 20
type: blackhole
table: 30200
- network: 198.53.100.12
prefix: 32
metric: 24
type: unreachable
table: 30200
- network: 198.53.100.10
prefix: 32
metric: 30
type: prohibit
table: 30200
- network: 2001:db8::4
prefix: 128
metric: 2
type: blackhole
table: 30600
- network: 2001:db8::6
prefix: 128
metric: 4
type: prohibit
table: 30600

- name: Get the routes from the route table 30200
command: ip route show table 30200
register: route_table_30200
changed_when: false

- name: Get the routes from the route table 30600
command: ip -6 route show table 30600
register: route_table_30600
changed_when: false

- name: Assert that the route table 30200 contains the specified route
assert:
that:
- route_table_30200.stdout is search("198.51.100.64/26 via
198.51.100.6 dev ethtest0 proto static metric 4")
- route_table_30200.stdout is search("blackhole 198.53.100.18
proto static scope link metric 20")
- route_table_30200.stdout is search("unreachable 198.53.100.12
proto static scope link metric 24")
- route_table_30200.stdout is search("prohibit 198.53.100.10
proto static scope link metric 30")
msg: "the route table 30200 does not exist or does not contain the
specified route"

- name: Assert that the route table 30600 contains the specified route
assert:
that:
- route_table_30600.stdout is search("blackhole 2001:db8::4
dev lo proto static metric 2 pref medium")
- route_table_30600.stdout is search("prohibit 2001:db8::6
dev lo proto static metric 4 pref medium")
msg: "the route table 30600 does not exist or does not contain the
specified route"

- name: Remove routes in table 30200
import_role:
name: linux-system-roles.network
vars:
network_state:
routes:
config:
- table-id: 30200
state: absent

- name: Get the routes from the route table 30200 after removing routes
command: ip route show table 30200
register: table_30200
changed_when: false

- name: Get the routes from the route table 30600 after removing routes
command: ip -6 route show table 30600
register: table_30600
changed_when: false

- name: Assert that the route table 30200 does not contain the specified
route after removing routes
assert:
that:
- table_30200.stdout is not search("198.51.100.64/26 via
198.51.100.6 dev ethtest0 proto static metric 4")
- table_30200.stdout is not search("blackhole 198.53.100.18
proto static scope link metric 20")
- table_30200.stdout is not search("unreachable 198.53.100.12
proto static scope link metric 24")
- table_30200.stdout is not search("prohibit 198.53.100.10
proto static scope link metric 30")
msg: "the route table 30200 contains the specified route"

- name: Assert that the route table 30600 still contains the specified
routes
assert:
that:
- table_30600.stdout is search("blackhole 2001:db8::4
dev lo proto static metric 2 pref medium")
- table_30600.stdout is search("prohibit 2001:db8::6
dev lo proto static metric 4 pref medium")
msg: "the route table 30600 does not exist or does not contain the
specified route"

- name: Import the playbook 'down_profile+delete_interface.yml'
import_playbook: down_profile+delete_interface.yml
vars:
profile: "{{ interface }}"
# FIXME: assert profile/device down
- name: Import the playbook 'remove_profile.yml'
import_playbook: remove_profile.yml
vars:
profile: "{{ interface }}"
- name: Assert device and profile are absent
hosts: all
tasks:
- name: Include the task 'assert_profile_absent.yml'
include_tasks: tasks/assert_profile_absent.yml
vars:
profile: "{{ interface }}"
- name: Include the task 'assert_device_absent.yml'
include_tasks: tasks/assert_device_absent.yml
...
45 changes: 45 additions & 0 deletions tests/tests_route_type_nm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: BSD-3-Clause
# This file was generated by ensure_provider_tests.py
---
# set network provider and gather facts
- hosts: all
# yamllint disable rule:line-length
name: Run playbook 'playbooks/tests_route_type.yml' with nm as provider
tasks:
- name: Include the task 'el_repo_setup.yml'
include_tasks: tasks/el_repo_setup.yml
- name: Set network provider to 'nm'
set_fact:
network_provider: nm
tags:
- always

- name: Install NetworkManager and get NetworkManager version
when:
- ansible_distribution_major_version != '6'
tags:
- always
block:
- name: Install NetworkManager
package:
name: NetworkManager
state: present
use: "{{ (__network_is_ostree | d(false)) |
ternary('ansible.posix.rhel_rpm_ostree', omit) }}"
- name: Get package info
package_facts:
- name: Get NetworkManager version
set_fact:
networkmanager_version: "{{
ansible_facts.packages['NetworkManager'][0]['version'] }}"


# The test requires or should run with NetworkManager, therefore it cannot run
# on RHEL/CentOS 6
# NetworkManager 1.36.0 added support for special route types: prohibit, blackhole and unreachable
- name: Import the playbook 'playbooks/tests_route_type.yml'
import_playbook: playbooks/tests_route_type.yml
when:
- ansible_distribution_major_version != '6'

- networkmanager_version is version('1.36.0', '>=')
Loading

0 comments on commit 659adcb

Please sign in to comment.