Skip to content

Commit

Permalink
Route functions->methods and added logic to check for host routes
Browse files Browse the repository at this point in the history
  • Loading branch information
ewlumpkin committed Aug 31, 2021
1 parent d22935c commit ddb2cfa
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 112 deletions.
8 changes: 4 additions & 4 deletions src/json2netns/netns.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from json2netns.consts import DEFAULT_IP, IPInterface
from json2netns.interfaces import Interface, Loopback, MacVlan, Veth
from json2netns.route import Route, get_route
from json2netns.route import Route


LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -159,17 +159,17 @@ def route_add(self) -> None:
attributes["egress_if_name"],
)
# Send route to return formatted command list
cmd = get_route(route_obj)
cmd = route_obj.get_route()
if cmd != []:
rc = self.exec_in_ns(cmd).returncode
if rc == 0:
LOG.info(
f"Installed route {route_obj.dest_prefix} into {route_obj.name} namespace"
f"Installed route {route_obj.dest_prefix} into {route_obj.netns_name} namespace"
)
else:
# Debug if you see this; the route should be valid by this point
LOG.error(
f"Route {route_obj.dest_prefix} was not installed into {route_obj.name} namespace, plese check logs"
f"Route {route_obj.dest_prefix} was not installed into {route_obj.netns_name} namespace, plese check logs"
)

def setup_links(self) -> None:
Expand Down
222 changes: 114 additions & 108 deletions src/json2netns/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,122 +20,128 @@ class Route:
next_hop_ip: str
egress_if_name: str


def __proto_match_validated(dest_prefix: str, next_hop_ip: str) -> bool:
"""Check to make sure the destination IP and next-hop match protocols (v4/v6)"""
if ip_network(dest_prefix).version == ip_address(next_hop_ip).version:
return True
else:
return False


def __route_validated(dest_prefix: str, next_hop_ip: str, egress_if_name: str) -> bool:
"""Check to make sure all elements exist that are needed for a valid route
, then validate those elements"""
if dest_prefix and (next_hop_ip or egress_if_name):
# Check for bad destination prefix, if so skip route installation
try:
ip_network(dest_prefix)
except ValueError:
LOG.error(f"{dest_prefix} is not a valid network address.")
def __proto_match_validated(self) -> bool:
"""Check to make sure the destination IP and next-hop match protocols (v4/v6)"""
if ip_network(self.dest_prefix).version == ip_address(self.next_hop_ip).version:
return True
else:
return False
if next_hop_ip:
# Check if next hop IP is specified, if so check for bad next hop address
# and skip route installation
try:
ip_address(next_hop_ip)
except ValueError:
LOG.error(f"{next_hop_ip} is not a valid ip address.")
return False
return True


def route_exists(dest_prefix: str, netns_name: str) -> bool:
"""Checks if route exists already (maintain idempotency)"""
route_4_cmd = [
IP,
"netns",
"exec",
netns_name,
IP,
"route",
"show",
]
route_6_cmd = [
IP,
"netns",
"exec",
netns_name,
IP,
"-6",
"route",
"show",
]

cp = (check_output(route_4_cmd)).decode("utf-8")
cp = cp + (check_output(route_6_cmd)).decode("utf-8")
if cp.find(dest_prefix) == -1:
return False
else:
def __route_validated(self) -> bool:
"""Check to make sure all elements exist that are needed for a valid route
, then validate those elements"""
if self.dest_prefix and (self.next_hop_ip or self.egress_if_name):
# Check for bad destination prefix, if so skip route installation
try:
ip_network(self.dest_prefix)
except ValueError:
LOG.error(f"{self.dest_prefix} is not a valid network address.")
return False
if self.next_hop_ip:
# Check if next hop IP is specified, if so check for bad next hop address
# and skip route installation
try:
ip_address(self.next_hop_ip)
except ValueError:
LOG.error(f"{self.next_hop_ip} is not a valid ip address.")
return False
return True


def get_route(route_object: Route) -> List[str]:
"""Generate cmd list for use with ns class"""
# check that it's a valid destination address and next hop format
if not __route_validated(
route_object.dest_prefix, route_object.next_hop_ip, route_object.egress_if_name
):
LOG.error(
f"Route validation failed, skipping installation of {route_object.dest_prefix}"
)
return []
# check that the destination and next hop are members of same protocol (v4/v6)
# Add support for IPv4 via IPv6 next hops (should probably open separate issue)
if not __proto_match_validated(route_object.dest_prefix, route_object.next_hop_ip):
LOG.error(
f"Destination and next hop protocol mismatch, skipping installation of {route_object.dest_prefix}"
)
return []
# check to see if the destination prefix exists in the namespace route table
if route_exists(route_object.dest_prefix, route_object.netns_name):
LOG.error(
f"Route already exists in table, skipping installation of {route_object.dest_prefix}"
)
return []
# We have checked the route doesn't exist, generate cmd list:
# send route with next hop ip and next hop interface
if route_object.next_hop_ip and route_object.egress_if_name:
cmd = [
def route_exists(self) -> bool:
"""Checks if route exists already (maintain idempotency)"""
route_4_cmd = [
IP,
"route",
"add",
route_object.dest_prefix,
"via",
route_object.next_hop_ip,
"dev",
route_object.egress_if_name,
]

elif route_object.next_hop_ip:
# send route with next hop ip
cmd = [
"netns",
"exec",
self.netns_name,
IP,
"route",
"add",
route_object.dest_prefix,
"via",
route_object.next_hop_ip,
"show",
]

elif route_object.egress_if_name:
# send route with next hop dev
cmd = [
route_6_cmd = [
IP,
"netns",
"exec",
self.netns_name,
IP,
"-6",
"route",
"add",
route_object.dest_prefix,
"dev",
route_object.egress_if_name,
"show",
]
return cmd

cp = (check_output(route_4_cmd)).decode("utf-8")
cp = cp + (check_output(route_6_cmd)).decode("utf-8")
print(cp)

# Linux outputs host routes without /32 subnet mask
# Search for /32 in route, if doesn't exist search in table, return result
if self.dest_prefix.find("/32") == -1:
if cp.find(self.dest_prefix) == -1:
return False
else:
return True
# If /32 exists in route, strip it, search in table, return result
else:
if cp.find(self.dest_prefix.strip(r"\/32")) == -1:
return False
else:
return True

def get_route(self) -> List[str]:
"""Generate cmd list for use with ns class"""
# check that it's a valid destination address and next hop format
if not self.__route_validated():
LOG.error(
f"Route validation failed, skipping installation of {self.dest_prefix}"
)
return []
# check that the destination and next hop are members of same protocol (v4/v6)
# Add support for IPv4 via IPv6 next hops (should probably open separate issue)
print(self.__proto_match_validated())
if not self.__proto_match_validated():
LOG.error(
f"Destination and next hop protocol mismatch, skipping installation of {self.dest_prefix}"
)
return []
# check to see if the destination prefix exists in the namespace route table
if self.route_exists():
LOG.error(
f"Route already exists in table, skipping installation of {self.dest_prefix}"
)
return []
# We have checked the route doesn't exist, generate cmd list:
# send route with next hop ip and next hop interface
if self.next_hop_ip and self.egress_if_name:
cmd = [
IP,
"route",
"add",
self.dest_prefix,
"via",
self.next_hop_ip,
"dev",
self.egress_if_name,
]

elif self.dest_prefix:
# send route with next hop ip
cmd = [
IP,
"route",
"add",
self.dest_prefix,
"via",
self.next_hop_ip,
]

elif self.egress_if_name:
# send route with next hop dev
cmd = [
IP,
"route",
"add",
self.dest_prefix,
"dev",
self.egress_if_name,
]
return cmd

0 comments on commit ddb2cfa

Please sign in to comment.