Skip to content

Commit

Permalink
nat64: T160: Implement Jool-based NAT64 translator
Browse files Browse the repository at this point in the history
Signed-off-by: Joe Groocock <me@frebib.net>
  • Loading branch information
frebib committed May 10, 2023
1 parent 70e4767 commit 4566b92
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 0 deletions.
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Depends:
isc-dhcp-relay,
isc-dhcp-server,
iw,
jool,
keepalived (>=2.0.5),
lcdproc,
lcdproc-extra-drivers,
Expand Down
27 changes: 27 additions & 0 deletions interface-definitions/include/nat64-protocol.xml.i
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<!-- include start from nat64-protocol.xml.i -->
<node name="protocol">
<properties>
<help>Apply translation address to a specfic protocol</help>
</properties>
<children>
<leafNode name="tcp">
<properties>
<help>Transmission Control Protocol</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="udp">
<properties>
<help>User Datagram Protocol</help>
<valueless/>
</properties>
</leafNode>
<leafNode name="icmp">
<properties>
<help>Internet Message Control Protocol</help>
<valueless/>
</properties>
</leafNode>
</children>
</node>
<!-- include end -->
112 changes: 112 additions & 0 deletions interface-definitions/nat64.xml.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?xml version="1.0"?>
<interfaceDefinition>
<node name="nat64" owner="${vyos_conf_scripts_dir}/nat64.py">
<properties>
<help>IPv6-to-IPv4 Network Address Translation (NAT64) Settings</help>
<priority>500</priority>
</properties>
<children>
<node name="source">
<properties>
<help>IPv6 source to IPv4 destination address translation</help>
</properties>
<children>
<tagNode name="rule">
<properties>
<help>Source NAT64 rule number</help>
<valueHelp>
<format>u32:1-999999</format>
<description>Number for this rule</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
</constraint>
<constraintErrorMessage>NAT64 rule number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
#include <include/generic-description.xml.i>
#include <include/generic-disable-node.xml.i>
<leafNode name="mode">
<properties>
<help>Operation mode of Jool packet translator</help>
<valueHelp>
<format>netfilter</format>
<description>Use netfilter translation mode (default)</description>
</valueHelp>
<valueHelp>
<format>iptables</format>
<description>Use iptables translation mode</description>
</valueHelp>
<constraint>
<regex>(netfilter|iptables)</regex>
</constraint>
</properties>
<defaultValue>netfilter</defaultValue>
</leafNode>
<node name="source">
<properties>
<help>IPv6 source prefix options</help>
</properties>
<children>
<leafNode name="prefix">
<properties>
<help>IPv6 prefix to be translated</help>
<valueHelp>
<format>ipv6net</format>
<description>IPv6 prefix</description>
</valueHelp>
<constraint>
<validator name="ipv6-prefix"/>
</constraint>
</properties>
</leafNode>
</children>
</node>
<node name="translation">
<properties>
<help>Translated IPv4 address options</help>
</properties>
<children>
<tagNode name="pool">
<properties>
<help>Translation IPv4 pool number</help>
<valueHelp>
<format>u32:1-999999</format>
<description>Number for this rule</description>
</valueHelp>
<constraint>
<validator name="numeric" argument="--range 1-999999"/>
</constraint>
<constraintErrorMessage>NAT64 pool number must be between 1 and 999999</constraintErrorMessage>
</properties>
<children>
<leafNode name="address">
<properties>
<help>IPv4 address or prefix to translate to</help>
<valueHelp>
<format>ipv4</format>
<description>IPv4 address</description>
</valueHelp>
<valueHelp>
<format>ipv4net</format>
<description>IPv4 prefix</description>
</valueHelp>
<constraint>
<validator name="ipv4-address"/>
<validator name="ipv4-prefix"/>
</constraint>
</properties>
</leafNode>
#include <include/nat-translation-port.xml.i>
#include <include/nat64-protocol.xml.i>
</children>
</tagNode>
</children>
</node>
</children>
</tagNode>
</children>
</node>
</children>
</node>
</interfaceDefinition>
155 changes: 155 additions & 0 deletions src/conf_mode/nat64.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
#
# Copyright (C) 2020-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

# pylint: disable=empty-docstring,missing-module-docstring

from operator import itemgetter
import csv
import json
import os
import re

from vyos import ConfigError, airbag
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.util import check_kmod, cmd, dict_search, run
from vyos.xml import defaults

airbag.enable()

INSTANCE_REGEX = re.compile(r"instance-(\d+)")
JOOL_CONFIG_DIR = "/run/jool"


def get_config(config: Config | None = None) -> None:
""" """
if config is None:
conf = Config()

base = ["nat64"]
nat64 = conf.get_config_dict(base, key_mangling=("-", "_"), get_first_key=True)

# T2665: we must add the tagNode defaults individually until this is
# moved to the base class
for direction in ["source"]:
if direction in nat64:
default_values = defaults(base + [direction, "rule"])
if "rule" in nat64[direction]:
for rule in nat64[direction]["rule"]:
nat64[direction]["rule"][rule] = dict_merge(
default_values, nat64[direction]["rule"][rule]
)

# Load in existing instances so we can destroy any unknown
lines = cmd("jool instance display --csv").splitlines()
for namespace, instance, mode in csv.reader(lines):
match = INSTANCE_REGEX.fullmatch(instance)
if not match:
# FIXME: Instances that don't match should be ignored but WARN'ed to the user
continue
num = match.group(1)

rules = nat64.setdefault("source", {}).setdefault("rule", {})
# Mark it for deletion
if num not in rules:
print(f"Deleting unknown instance: {num}")
rules[num] = {"deleted": True}
continue

# If the user changes the mode, recreate the instance else Jool fails with:
# Jool error: Sorry; you can't change an instance's framework for now.
if rules[num]["mode"] != mode:
print(f"Recreating instance {num} due to changed mode: {rules[num]['mode']} -> {mode}")
rules[num]["recreate"] = True

return nat64


def verify(nat64) -> None:
""" """
if not nat64 or "deleted" in nat64:
# no need to verify the CLI as nat64 is going to be deactivated
return

# Verify that source.prefix is set and is a /96

return


def generate(nat64) -> None:
""" """
os.makedirs(JOOL_CONFIG_DIR, exist_ok=True)

if dict_search("source.rule", nat64):
for rule, instance in dict_search("source.rule", nat64).items():
name = f"instance-{rule}"
config = {
"instance": name,
"framework": instance["mode"],
"global": {
"pool6": instance["source"]["prefix"],
},
# "pool4": [],
# "bib": [],
}

if "description" in instance:
config["comment"] = instance["description"]

# pylint: disable=invalid-name
with open(f"{JOOL_CONFIG_DIR}/{name}.json", "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)


def apply(nat64) -> None:
""" """
if not nat64:
return

if dict_search("source.rule", nat64):
# Deletions first to avoid conflicts
for rule, instance in dict_search("source.rule", nat64).items():
if not any(k in instance for k in ("deleted", "recreate")):
continue

ret = run(f"jool instance remove instance-{rule}")
if ret != 0:
raise ConfigError(
f"Failed to remove nat64 source rule {rule} (jool instance instance-{rule})"
)

# Now creations
for rule, instance in dict_search("source.rule", nat64).items():
name = f"instance-{rule}"
ret = run(f"jool -i {name} file handle {JOOL_CONFIG_DIR}/{name}.json")
if ret != 0:
raise ConfigError(f"Failed to set jool instance {name}")

return


if __name__ == "__main__":
import sys

try:
check_kmod(["jool"])
c = get_config()
verify(c)
generate(c)
apply(c)
except ConfigError as e:
print(e)
sys.exit(1)

0 comments on commit 4566b92

Please sign in to comment.