Merge charm-advanced-routing:bug/1900866 into charm-advanced-routing:master

Proposed by Adam Dyess
Status: Merged
Approved by: Drew Freiberger
Approved revision: c7e3387aff9bc6a4d7bc07c17d42028e0be4b6ac
Merged at revision: c7e3387aff9bc6a4d7bc07c17d42028e0be4b6ac
Proposed branch: charm-advanced-routing:bug/1900866
Merge into: charm-advanced-routing:master
Diff against target: 222 lines (+134/-6)
4 files modified
src/lib/routing_entry.py (+46/-4)
src/lib/routing_validator.py (+38/-2)
src/tests/unit/test_RoutingConfigValidator.py (+32/-0)
src/tests/unit/test_RoutingEntryRule.py (+18/-0)
Reviewer Review Type Date Requested Status
Drew Freiberger (community) Approve
Peter Sabaini (community) Approve
Paul Goins Approve
Review via email: mp+392645@code.launchpad.net

Commit message

implement fwmark and iif policy rules

To post a comment you must log in.
Revision history for this message
Paul Goins (vultaire) wrote :

Code looks good. Locally tested on an LXD ubuntu, works as advertised. +1.

review: Approve
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Hey, thanks for the addition!

I have left some inline comments/remarks.

Otherwise lgtm.

review: Needs Fixing
Revision history for this message
Peter Sabaini (peter-sabaini) wrote :

Cheers, lgtm!

review: Approve
Revision history for this message
Drew Freiberger (afreiberger) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/lib/routing_entry.py b/src/lib/routing_entry.py
2index d45170d..6a61e88 100644
3--- a/src/lib/routing_entry.py
4+++ b/src/lib/routing_entry.py
5@@ -9,9 +9,11 @@ concrete implementations, that model a routing table
6 RoutingEntryTable RoutingEntryRoute RoutingEntryRule
7 """
8 import collections
9+import re
10 import subprocess
11 from abc import ABCMeta, abstractmethod, abstractproperty
12
13+
14 from charmhelpers.core import hookenv
15
16
17@@ -233,6 +235,30 @@ class RoutingEntryRoute(RoutingEntryType):
18 class RoutingEntryRule(RoutingEntryType):
19 """RoutingEntryType used for rules."""
20
21+ MARK_PATTERN_TXT = (
22+ # 13 digit int | 8 digit hex value [optional 8 digit hex mask]
23+ r"^(\d{1,13}|0x[0-9a-f]{1,8})(?:\/(0x[0-9a-f]{1,8}))?$"
24+ )
25+ MARK_PATTERN = re.compile(MARK_PATTERN_TXT, re.IGNORECASE)
26+
27+ @staticmethod
28+ def fwmark_user(fwmark):
29+ """
30+ Convert user fwmark to match the output from ip rules list.
31+ @param str fwmark: marking from the user config
32+ @returns str in hex/hex format
33+ """
34+ match = RoutingEntryRule.MARK_PATTERN.search(fwmark)
35+ if not match:
36+ return None
37+ hex_vals = match.groups()
38+ as_ints = [
39+ int(val, 16 if val.lower().startswith("0x") else 10)
40+ for val in hex_vals
41+ if val
42+ ]
43+ return "/".join(map(hex, as_ints))
44+
45 def __init__(self, config):
46 """Object init function."""
47 hookenv.log("Created {}".format(self.__class__.__name__), level=hookenv.INFO)
48@@ -259,12 +285,18 @@ class RoutingEntryRule(RoutingEntryType):
49 ip rule add from X.X.X.X/XX table mytable priority NNN
50 # any dst, table main
51 ip rule add from X.X.X.X/XX priority NNN
52+ # any src, fwmark 0x1/0xF, iif bond0, table mytable
53+ ip rule add from any fwmark 1/0xF iif bond0 table mytable priority NNN
54 """
55 cmd = ["ip", "rule", "add", "from", self.config["from-net"]]
56- opts = collections.OrderedDict(
57- {"to-net": "to", "table": "table", "priority": "priority"}
58- )
59- for opt, keyword in opts.items():
60+ opts = [
61+ ("fwmark", "fwmark"),
62+ ("iif", "iif"),
63+ ("to-net", "to"),
64+ ("table", "table"),
65+ ("priority", "priority"),
66+ ]
67+ for opt, keyword in opts:
68 try:
69 cmd.extend([keyword, str(self.config[opt])])
70 except KeyError:
71@@ -291,9 +323,19 @@ class RoutingEntryRule(RoutingEntryType):
72 """Ip rule add does not prevent duplicates in older kernel versions."""
73 # https://patchwork.ozlabs.org/patch/624553/
74 matchparams = ["from", self.config["from-net"]]
75+
76 to = self.config.get("to-net")
77 if to and to != "all": # ip rule omits to=all as it's implied
78 matchparams.extend(("to", to))
79+
80+ fwmark = self.config.get("fwmark")
81+ if fwmark:
82+ matchparams.extend(("fwmark", fwmark))
83+
84+ iif = self.config.get("iif")
85+ if iif:
86+ matchparams.extend(("iif", iif))
87+
88 matchparams.extend(("lookup", self.config.get("table", "main")))
89 matchline = " ".join(matchparams)
90 prio = str(self.config.get("priority", ""))
91diff --git a/src/lib/routing_validator.py b/src/lib/routing_validator.py
92index 04d1ad6..bc3984c 100644
93--- a/src/lib/routing_validator.py
94+++ b/src/lib/routing_validator.py
95@@ -18,8 +18,8 @@ from routing_entry import (
96 RoutingEntryType,
97 )
98
99-TABLE_NAME_PATTERN = "[a-zA-Z0-9]+[a-zA-Z0-9-]*"
100-TABLE_NAME_PATTERN_RE = "^{}$".format(TABLE_NAME_PATTERN)
101+TABLE_NAME_PATTERN = r"[a-zA-Z0-9]+[a-zA-Z0-9-]*"
102+TABLE_NAME_PATTERN_RE = r"^{}$".format(TABLE_NAME_PATTERN)
103
104
105 class RoutingConfigValidatorError(Exception):
106@@ -240,6 +240,8 @@ class RoutingConfigValidator:
107 )
108
109 # Verify items in configuration
110+ self.verify_rule_mark(conf)
111+ self.verify_rule_iif(conf)
112 self.verify_rule_from_net(conf)
113 self.verify_rule_to_net(conf)
114 self.verify_rule_table(conf)
115@@ -247,6 +249,40 @@ class RoutingConfigValidator:
116
117 RoutingEntryType.add_entry(RoutingEntryRule(conf))
118
119+ def verify_rule_mark(self, conf):
120+ """
121+ Verify fwmark config.
122+
123+ "fwmark" key isn't required.
124+ if fwmark is correct and from-net is unset -- set from-net=all
125+ """
126+ fwmark = conf.get("fwmark")
127+ if not fwmark:
128+ return None
129+
130+ fwmark_hex = RoutingEntryRule.fwmark_user(fwmark)
131+ from_net = conf.get("from-net")
132+ if not fwmark_hex:
133+ msg = "fwmark {} is in the wrong format".format(fwmark)
134+ self.report_error(msg)
135+ else:
136+ # now that its valid, update the config with hex version
137+ conf["fwmark"] = fwmark_hex
138+
139+ if fwmark_hex and not from_net:
140+ conf["from-net"] = "all"
141+
142+ def verify_rule_iif(self, conf):
143+ """
144+ Verify rule input interface.
145+
146+ "iif" key isn't required, but verify the network device exists
147+ """
148+ iif = conf.get("iif")
149+ if iif and iif not in netifaces.interfaces():
150+ msg = "Device {} does not exist".format(iif)
151+ self.report_error(msg)
152+
153 def verify_rule_from_net(self, conf):
154 """Verify rule source network.
155
156diff --git a/src/tests/unit/test_RoutingConfigValidator.py b/src/tests/unit/test_RoutingConfigValidator.py
157new file mode 100644
158index 0000000..fc7504f
159--- /dev/null
160+++ b/src/tests/unit/test_RoutingConfigValidator.py
161@@ -0,0 +1,32 @@
162+"""routing validator unit testing module."""
163+
164+import pytest
165+
166+import routing_validator
167+
168+
169+@pytest.mark.parametrize(
170+ "fwmark",
171+ [
172+ "abc", # not valid hex -- 0xabc is right
173+ "1 0x0f", # space isn't a valid delimiter
174+ "99999999999999", # too many digits
175+ "0x1000000000", # too many hex digits
176+ "2/0x1000000000", # too many hex digits
177+ "1/1", # mask must be hex
178+ ],
179+ ids=[
180+ "not valid hex -- 0xabc is right",
181+ "space isn't a valid delimiter",
182+ "too many digits",
183+ "too many hex digits",
184+ "too many hex digits in mask",
185+ "mask must be hex",
186+ ],
187+)
188+def test_routing_validate_rule_fwmark_failure(fwmark):
189+ """Test different versions of user input on fwmark."""
190+ validator = routing_validator.RoutingConfigValidator()
191+ with pytest.raises(routing_validator.RoutingConfigValidatorError) as ie:
192+ validator.verify_rule({"fwmark": fwmark})
193+ ie.match("fwmark {} is in the wrong format".format(fwmark))
194diff --git a/src/tests/unit/test_RoutingEntryRule.py b/src/tests/unit/test_RoutingEntryRule.py
195index fe8724a..c9e8f67 100644
196--- a/src/tests/unit/test_RoutingEntryRule.py
197+++ b/src/tests/unit/test_RoutingEntryRule.py
198@@ -33,6 +33,24 @@ import routing_entry
199 True,
200 id="From-ToAll-Table-Prio-Found=True",
201 ),
202+ pytest.param(
203+ {
204+ "from-net": "all",
205+ "fwmark": "0x10/0xff",
206+ "iif": "lo",
207+ "to-net": "10.0.0.0/24",
208+ "table": "SF1",
209+ "priority": 100,
210+ },
211+ (
212+ b"0:\tfrom all lookup local\n"
213+ b"100:\tfrom all to 10.0.0.0/24 fwmark 0x10/0xff iif lo lookup SF1\n"
214+ b"32766:\tfrom all lookup main\n"
215+ b"32767:\tfrom all lookup default\n"
216+ ),
217+ True,
218+ id="FromAll-ToNet-Fwmark-Iif-Table-Prio-Found=True",
219+ ),
220 ],
221 )
222 def test_routing_entry_rule_is_duplicate(

Subscribers

No one subscribed via source and target branches