Support routing tables in static routes

The users want to use the policy routing (e.g. source routing), so
that they can forward the packet based on the other criteria except for
the destination address in the packet. In such scenario, the routing
tables have to be supported beforehand in static routes, so that the
users can define policy routing rules later to instruct the system
which table to use to determine the correct route.

Signed-off-by: Wen Liang <liangwen12year@gmail.com>
This commit is contained in:
Wen Liang 2022-01-05 22:30:20 -05:00 committed by Fernando Fernández Mancera
parent 20667b0860
commit 5eb03fa992
8 changed files with 620 additions and 6 deletions

View file

@ -5,6 +5,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
import posixpath
import socket
import re
@ -246,6 +247,62 @@ class ArgValidatorStr(ArgValidator):
return True
class ArgValidatorRouteTable(ArgValidator):
def __init__(
self,
name,
required=False,
default_value=None,
):
ArgValidator.__init__(self, name, required, default_value)
def _validate_impl(self, value, name):
table = None
try:
if 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):
table = int(value)
elif isinstance(value, Util.STRING_TYPE):
try:
table = int(value)
except Exception:
table = value
except Exception:
pass
if table is None:
raise ValidationError(
name,
"route table must be the named or numeric tables but is {0}".format(
value
),
)
if isinstance(table, int):
if table < 1:
raise ValidationError(
name,
"route table value is {0} but cannot be less than 1".format(value),
)
elif table > 0xFFFFFFFF:
raise ValidationError(
name,
"route table value is {0} but cannot be greater than 4294967295".format(
value
),
)
if isinstance(table, Util.STRING_TYPE):
if table == "":
raise ValidationError(name, "route table name cannot be empty string")
if not IPRouteUtils.ROUTE_TABLE_ALIAS_RE.match(table):
raise ValidationError(
name, "route table name contains invalid characters"
)
return table
class ArgValidatorNum(ArgValidator):
def __init__( # pylint: disable=too-many-arguments
self,
@ -543,6 +600,7 @@ class ArgValidatorIPRoute(ArgValidatorDict):
ArgValidatorNum(
"metric", default_value=-1, val_min=-1, val_max=0xFFFFFFFF
),
ArgValidatorRouteTable("table"),
],
default_value=None,
)
@ -1858,6 +1916,19 @@ class ArgValidator_ListConnections(ArgValidatorList):
VALIDATE_ONE_MODE_NM = "nm"
VALIDATE_ONE_MODE_INITSCRIPTS = "initscripts"
def validate_route_tables(self, connection, idx):
for r in connection["ip"]["route"]:
if isinstance(r["table"], Util.STRING_TYPE):
mapping = IPRouteUtils.get_route_tables_mapping()
if r["table"] in mapping:
r["table"] = mapping[r["table"]]
else:
raise ValidationError.from_connection(
idx,
"cannot find route table {0} in `/etc/iproute2/rt_tables` or "
"`/etc/iproute2/rt_tables.d/`".format(r["table"]),
)
def validate_connection_one(self, mode, connections, idx):
def _ipv4_enabled(connection):
has_addrs4 = any(
@ -2012,3 +2083,99 @@ class ArgValidator_ListConnections(ArgValidatorList):
"match.path is not supported by the running version of "
"NetworkManger.",
)
self.validate_route_tables(connection, idx)
class IPRouteUtils(object):
ROUTE_TABLE_ALIAS_RE = re.compile("^[a-zA-Z0-9_.-]+$")
@classmethod
def _parse_route_tables_mapping(cls, file_content, mapping):
for line in file_content.split(b"\n"):
# iproute2 only skips over leading ' ' and '\t'.
line = line.lstrip()
# skip empty lines or comments
if not line:
continue
# In Python 2.x, there is no new type called `bytes`. And Python 2.x
# `bytes` is just an alias to the str type
if Util.PY3:
if line[0] == ord(b"#"):
continue
else:
if line[0] == "#":
continue
# iproute2 splits at the first space.
ll = line.split(b" ", 1)
if len(ll) != 2:
continue
line1 = ll[0]
line2 = ll[1]
comment_space = line2.find(b" ")
if comment_space >= 0:
# when commenting the route table entry in the same line,
# iproute2 only accepts one ' ', followed by '#'
if not re.match(b"^[a-zA-Z0-9_.-]+ #", line2):
continue
line2 = line2[:comment_space]
# convert to UTF-8 and only accept benign characters.
try:
line2 = line2.decode("utf-8")
except Exception:
continue
if not cls.ROUTE_TABLE_ALIAS_RE.match(line2):
continue
tableid = None
try:
tableid = int(line1)
except Exception:
if line.startswith(b"0x"):
try:
tableid = int(line1[2:], 16)
except Exception:
pass
if tableid is None or tableid < 0 or tableid > 0xFFFFFFFF:
continue
mapping[line2] = tableid
@classmethod
def _parse_route_tables_mapping_from_file(cls, filename, mapping):
try:
with open(filename, "rb") as f:
file_content = f.read()
except Exception:
return
cls._parse_route_tables_mapping(file_content, mapping)
@classmethod
def get_route_tables_mapping(cls):
if not hasattr(cls, "_cached_rt_tables"):
mapping = {}
cls._parse_route_tables_mapping_from_file(
"/etc/iproute2/rt_tables", mapping
)
# In iproute2, the directory `/etc/iproute2/rt_tables/rt_tables.d`
# is also iterated when get the mapping between the route table name
# and route table id,
# https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/rt_names.c?id=ade99e208c1843ed3b6eb9d138aa15a6a5eb5219#n391
try:
fnames = os.listdir("/etc/iproute2/rt_tables.d")
except Exception:
fnames = []
for f in fnames:
if f.endswith(".conf") and f[0] != ".":
cls._parse_route_tables_mapping_from_file(
"/etc/iproute2/rt_tables.d/" + f, mapping
)
cls._cached_rt_tables = mapping
return cls._cached_rt_tables