Skip to content

Commit 8195188

Browse files
feat: Add the support for the optional route source parameter in nm provider
Enhancement: Add the optional route source parameter for the nm provider Reason: In a scenario where you have a machine with multiple public IP addresses, typically due to a multi-WAN setup, the src parameter in the context of routes allows you to specify which source IP address should be used when sending packets via a specific route. This is crucial when you want to ensure that outbound traffic uses a specific IP address tied to a particular network interface, especially when dealing with multiple WAN connections. Result: Adding support for the src parameter in routes results in a more powerful and flexible network configuration capability, especially important in environments with multiple network interfaces or multiple IP addresses, it provides better control over traffic routing. Resolves: https://issues.redhat.com/browse/RHEL-3252 Signed-off-by: Wen Liang <[email protected]>
1 parent 098e5e1 commit 8195188

File tree

5 files changed

+84
-4
lines changed

5 files changed

+84
-4
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -596,10 +596,11 @@ The IP configuration supports the following options:
596596

597597
Static route configuration can be specified via a list of routes given in the
598598
`route` option. The default value is an empty list. Each route is a dictionary with
599-
the following entries: `gateway`, `metric`, `network`, `prefix`, `table` and `type`.
600-
`network` and `prefix` specify the destination network. `table` supports both the
601-
numeric table and named table. In order to specify the named table, the users have to
602-
ensure the named table is properly defined in `/etc/iproute2/rt_tables` or
599+
the following entries: `gateway`, `metric`, `network`, `prefix`, `src`, `table` and
600+
`type`. `network` and `prefix` specify the destination network. `src` specifies the
601+
source IP address for a route. `table` supports both the numeric table and named
602+
table. In order to specify the named table, the users have to ensure the named table
603+
is properly defined in `/etc/iproute2/rt_tables` or
603604
`/etc/iproute2/rt_tables.d/*.conf`. The optional `type` key supports the values
604605
`blackhole`, `prohibit`, and `unreachable`.
605606
See [man 8 ip-route](https://man7.org/linux/man-pages/man8/ip-route.8.html#DESCRIPTION)

library/network_connections.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1263,6 +1263,10 @@ def connection_create(self, connections, idx, connection_current=None):
12631263
NM.IPRoute.set_attribute(
12641264
new_route, "table", Util.GLib().Variant.new_uint32(r["table"])
12651265
)
1266+
if r["src"]:
1267+
NM.IPRoute.set_attribute(
1268+
new_route, "src", Util.GLib().Variant.new_string(r["src"])
1269+
)
12661270

12671271
if r["family"] == socket.AF_INET:
12681272
s_ip4.add_route(new_route)

module_utils/network_lsr/argument_validator.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,9 @@ def __init__(self, name, family=None, required=False):
680680
enum_values=["blackhole", "prohibit", "unreachable"],
681681
),
682682
ArgValidatorRouteTable("table"),
683+
ArgValidatorIP(
684+
"src", family=family, default_value=None, plain_address=False
685+
),
683686
],
684687
default_value=None,
685688
)
@@ -716,6 +719,16 @@ def _validate_post(self, value, name, result):
716719
elif not Util.addr_family_valid_prefix(family, prefix):
717720
raise ValidationError(name, "invalid prefix %s in '%s'" % (prefix, value))
718721

722+
src = result["src"]
723+
if src is not None:
724+
if family != src["family"]:
725+
raise ValidationError(
726+
name,
727+
"conflicting address family between network and src "
728+
"address {0}".format(src["address"]),
729+
)
730+
result["src"] = src["address"]
731+
719732
return result
720733

721734

@@ -2627,6 +2640,12 @@ def _ipv6_is_not_configured(connection):
26272640
idx,
26282641
"type is not supported by initscripts",
26292642
)
2643+
if route["src"] is not None:
2644+
raise ValidationError.from_connection(
2645+
idx,
2646+
"configuring the route source is not supported by initscripts",
2647+
)
2648+
26302649
if connection["ip"]["routing_rule"]:
26312650
if mode == self.VALIDATE_ONE_MODE_INITSCRIPTS:
26322651
raise ValidationError.from_connection(

tests/playbooks/tests_route_table.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
gateway: 198.51.100.6
4848
metric: 4
4949
table: 30200
50+
- network: 192.0.2.64
51+
prefix: 26
52+
gateway: 198.51.100.8
53+
metric: 50
54+
table: 30200
55+
src: 198.51.100.3
5056

5157
- name: Get the routes from the route table 30200
5258
command: ip route show table 30200
@@ -65,6 +71,9 @@
6571
that:
6672
- route_table_30200.stdout is search("198.51.100.64/26 via
6773
198.51.100.6 dev ethtest0 proto static metric 4")
74+
- route_table_30200.stdout is search("192.0.2.64/26 via
75+
198.51.100.8 dev ethtest0 proto static src 198.51.100.3
76+
metric 50")
6877
msg: "the route table 30200 does not exist or does not contain the
6978
specified route"
7079

@@ -111,6 +120,12 @@
111120
gateway: 198.51.100.6
112121
metric: 4
113122
table: custom
123+
- network: 192.0.2.64
124+
prefix: 26
125+
gateway: 198.51.100.8
126+
metric: 50
127+
table: custom
128+
src: 198.51.100.3
114129

115130
- name: Get the routes from the named route table 'custom'
116131
command: ip route show table custom
@@ -126,6 +141,9 @@
126141
198.51.100.1 dev ethtest0 proto static metric 2")
127142
- route_table_custom.stdout is search("198.51.100.64/26 via
128143
198.51.100.6 dev ethtest0 proto static metric 4")
144+
- route_table_custom.stdout is search("192.0.2.64/26 via
145+
198.51.100.8 dev ethtest0 proto static src 198.51.100.3
146+
metric 50")
129147
msg: "the named route table 'custom' does not exist or does not contain
130148
the specified route"
131149

tests/unit/test_network_connections.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ def assert_nm_connection_routes_expected(self, connection, route_list_expected):
239239
"metric": int(r.get_metric()),
240240
"type": r.get_attribute("type"),
241241
"table": r.get_attribute("table"),
242+
"src": r.get_attribute("src"),
242243
}
243244
for r in route_list_new
244245
]
@@ -295,6 +296,12 @@ def do_connections_validate_nm(self, input_connections, **kwargs):
295296
"table",
296297
Util.GLib().Variant.new_uint32(r["table"]),
297298
)
299+
if r["src"]:
300+
NM.IPRoute.set_attribute(
301+
new_route,
302+
"src",
303+
Util.GLib().Variant.new_uint32(r["src"]),
304+
)
298305
if r["family"] == socket.AF_INET:
299306
s4.add_route(new_route)
300307
else:
@@ -1144,6 +1151,7 @@ def test_routes(self):
11441151
"metric": -1,
11451152
"type": None,
11461153
"table": None,
1154+
"src": None,
11471155
}
11481156
],
11491157
"routing_rule": [],
@@ -1485,6 +1493,7 @@ def test_vlan(self):
14851493
"metric": -1,
14861494
"type": None,
14871495
"table": None,
1496+
"src": None,
14881497
}
14891498
],
14901499
"routing_rule": [],
@@ -1635,6 +1644,7 @@ def test_macvlan(self):
16351644
"metric": -1,
16361645
"type": None,
16371646
"table": None,
1647+
"src": None,
16381648
}
16391649
],
16401650
"routing_rule": [],
@@ -1698,6 +1708,7 @@ def test_macvlan(self):
16981708
"metric": -1,
16991709
"type": None,
17001710
"table": None,
1711+
"src": None,
17011712
}
17021713
],
17031714
"routing_rule": [],
@@ -2661,6 +2672,7 @@ def test_route_metric_prefix(self):
26612672
"metric": 545,
26622673
"type": None,
26632674
"table": None,
2675+
"src": None,
26642676
},
26652677
{
26662678
"family": socket.AF_INET,
@@ -2670,6 +2682,7 @@ def test_route_metric_prefix(self):
26702682
"metric": -1,
26712683
"type": None,
26722684
"table": None,
2685+
"src": None,
26732686
},
26742687
],
26752688
"routing_rule": [],
@@ -2767,6 +2780,7 @@ def test_route_v6(self):
27672780
"metric": 545,
27682781
"type": None,
27692782
"table": None,
2783+
"src": None,
27702784
},
27712785
{
27722786
"family": socket.AF_INET,
@@ -2776,6 +2790,7 @@ def test_route_v6(self):
27762790
"metric": -1,
27772791
"type": None,
27782792
"table": None,
2793+
"src": None,
27792794
},
27802795
{
27812796
"family": socket.AF_INET6,
@@ -2785,6 +2800,7 @@ def test_route_v6(self):
27852800
"metric": -1,
27862801
"type": None,
27872802
"table": None,
2803+
"src": None,
27882804
},
27892805
],
27902806
"routing_rule": [],
@@ -2923,6 +2939,7 @@ def test_route_without_interface_name(self):
29232939
"metric": 545,
29242940
"type": None,
29252941
"table": None,
2942+
"src": None,
29262943
},
29272944
{
29282945
"family": socket.AF_INET,
@@ -2932,6 +2949,7 @@ def test_route_without_interface_name(self):
29322949
"metric": -1,
29332950
"type": None,
29342951
"table": None,
2952+
"src": None,
29352953
},
29362954
{
29372955
"family": socket.AF_INET6,
@@ -2941,6 +2959,7 @@ def test_route_without_interface_name(self):
29412959
"metric": -1,
29422960
"type": None,
29432961
"table": None,
2962+
"src": None,
29442963
},
29452964
],
29462965
"routing_rule": [],
@@ -5001,6 +5020,25 @@ def test_type_route_with_gateway(self):
50015020
self.test_connections,
50025021
)
50035022

5023+
def test_route_with_source_address(self):
5024+
"""
5025+
Test setting the route with src address specified
5026+
"""
5027+
self.test_connections[0]["ip"]["route"][0]["src"] = "2001:db8::2"
5028+
self.assertRaisesRegex(
5029+
ValidationError,
5030+
"conflicting address family between network and src "
5031+
"address {0}".format(
5032+
self.test_connections[0]["ip"]["route"][0]["src"],
5033+
),
5034+
self.validator.validate,
5035+
self.test_connections,
5036+
)
5037+
5038+
self.test_connections[0]["ip"]["route"][0]["src"] = "198.51.100.3"
5039+
result = self.validator.validate(self.test_connections)
5040+
self.assertEqual(result[0]["ip"]["route"][0]["src"], "198.51.100.3")
5041+
50045042

50055043
class TestValidatorRoutingRules(Python26CompatTestCase):
50065044
def setUp(self):

0 commit comments

Comments
 (0)