summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/labeler.yml12
-rw-r--r--CODEOWNERS1
-rw-r--r--interface-definitions/include/nat-translation-options.xml.i8
-rw-r--r--interface-definitions/include/version/nat-version.xml.i2
-rw-r--r--interface-definitions/nat.xml.in1
-rw-r--r--op-mode-definitions/force-commit-archive.xml.in2
-rw-r--r--op-mode-definitions/nat.xml.in20
-rw-r--r--op-mode-definitions/show-log.xml.in50
-rw-r--r--python/vyos/config_mgmt.py2
-rw-r--r--python/vyos/nat.py6
-rw-r--r--python/vyos/qos/base.py11
-rw-r--r--python/vyos/utils/io.py2
-rw-r--r--smoketest/config-tests/nat-basic85
-rw-r--r--smoketest/configs/nat-basic256
-rwxr-xr-xsmoketest/scripts/cli/test_qos.py21
-rwxr-xr-xsrc/conf_mode/nat.py18
-rwxr-xr-xsrc/conf_mode/nat66.py22
-rwxr-xr-xsrc/conf_mode/service_dhcpv6-server.py8
-rwxr-xr-xsrc/migration-scripts/nat/7-to-862
-rwxr-xr-xsrc/op_mode/cgnat.py44
20 files changed, 577 insertions, 56 deletions
diff --git a/.github/labeler.yml b/.github/labeler.yml
deleted file mode 100644
index e0b9ee430..000000000
--- a/.github/labeler.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-equuleus:
- - any:
- - base-branch: 'equuleus'
-current:
- - any:
- - base-branch: 'current'
-crux:
- - any:
- - base-branch: 'crux'
-sagitta:
- - any:
- - base-branch: 'sagitta'
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 000000000..191394298
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1 @@
+* @vyos/reviewers \ No newline at end of file
diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i
index 6b95de045..c8900590f 100644
--- a/interface-definitions/include/nat-translation-options.xml.i
+++ b/interface-definitions/include/nat-translation-options.xml.i
@@ -28,22 +28,18 @@
<properties>
<help>Port mapping options</help>
<completionHelp>
- <list>random fully-random none</list>
+ <list>random none</list>
</completionHelp>
<valueHelp>
<format>random</format>
<description>Randomize source port mapping</description>
</valueHelp>
<valueHelp>
- <format>fully-random</format>
- <description>Full port randomization</description>
- </valueHelp>
- <valueHelp>
<format>none</format>
<description>Do not apply port randomization</description>
</valueHelp>
<constraint>
- <regex>(random|fully-random|none)</regex>
+ <regex>(random|none)</regex>
</constraint>
</properties>
<defaultValue>none</defaultValue>
diff --git a/interface-definitions/include/version/nat-version.xml.i b/interface-definitions/include/version/nat-version.xml.i
index 656da6e14..173e91ed3 100644
--- a/interface-definitions/include/version/nat-version.xml.i
+++ b/interface-definitions/include/version/nat-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/nat-version.xml.i -->
-<syntaxVersion component='nat' version='7'></syntaxVersion>
+<syntaxVersion component='nat' version='8'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/nat.xml.in b/interface-definitions/nat.xml.in
index 0a639bd80..73a748137 100644
--- a/interface-definitions/nat.xml.in
+++ b/interface-definitions/nat.xml.in
@@ -141,6 +141,7 @@
</children>
</node>
#include <include/inbound-interface.xml.i>
+ #include <include/firewall/log.xml.i>
<node name="translation">
<properties>
<help>Translation address or prefix</help>
diff --git a/op-mode-definitions/force-commit-archive.xml.in b/op-mode-definitions/force-commit-archive.xml.in
index 162323c20..46836f967 100644
--- a/op-mode-definitions/force-commit-archive.xml.in
+++ b/op-mode-definitions/force-commit-archive.xml.in
@@ -6,7 +6,7 @@
<properties>
<help>Manually archive configuration</help>
</properties>
- <command>/usr/bin/config-mgmt</command>
+ <command>/etc/commit/post-hooks.d/02vyos-commit-archive; printf "\n"</command>
</leafNode>
</children>
</node>
diff --git a/op-mode-definitions/nat.xml.in b/op-mode-definitions/nat.xml.in
index 6398c0e07..13e7fd81d 100644
--- a/op-mode-definitions/nat.xml.in
+++ b/op-mode-definitions/nat.xml.in
@@ -16,6 +16,26 @@
<properties>
<help>Show allocated CGNAT parameters</help>
</properties>
+ <children>
+ <tagNode name="external-address">
+ <properties>
+ <help>Show CGNAT allocations for an external IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --external-address "$6"</command>
+ </tagNode>
+ <tagNode name="internal-address">
+ <properties>
+ <help>Show CGNAT allocations for an internal IP address</help>
+ <completionHelp>
+ <list>&lt;x.x.x.x&gt;</list>
+ </completionHelp>
+ </properties>
+ <command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation --internal-address "$6"</command>
+ </tagNode>
+ </children>
<command>sudo ${vyos_op_scripts_dir}/cgnat.py show_allocation</command>
</node>
</children>
diff --git a/op-mode-definitions/show-log.xml.in b/op-mode-definitions/show-log.xml.in
index e13270364..c3aa324ba 100644
--- a/op-mode-definitions/show-log.xml.in
+++ b/op-mode-definitions/show-log.xml.in
@@ -464,12 +464,56 @@
</properties>
<command>journalctl --no-hostname --boot --unit lldpd.service</command>
</leafNode>
- <leafNode name="nat">
+ <node name="nat">
<properties>
<help>Show log for Network Address Translation (NAT)</help>
</properties>
- <command>egrep -i "kernel:.*\[NAT-[A-Z]{3,}-[0-9]+(-MASQ)?\]" $(find /var/log -maxdepth 1 -type f -name messages\* | sort -t. -k2nr)</command>
- </leafNode>
+ <children>
+ <node name="destination">
+ <properties>
+ <help>Show NAT destination log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-[0-9]+\]"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT destination log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[DST-NAT-$6\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="source">
+ <properties>
+ <help>Show NAT source log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-[0-9]+(-MASQ)?\]"&quot;"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT source log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[SRC-NAT-$6(-MASQ)?\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ <node name="static">
+ <properties>
+ <help>Show NAT static log</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-[0-9]+\]"</command>
+ <children>
+ <tagNode name="rule">
+ <properties>
+ <help>Show NAT static log for specified rule</help>
+ </properties>
+ <command>journalctl --no-hostname --boot -k | egrep "\[STATIC-(SRC|DST)-NAT-$6\]"</command>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ <command>journalctl --no-hostname --boot -k | egrep "\[(STATIC-)?(DST|SRC)-NAT-[0-9]+(-MASQ)?\]"</command>
+ </node>
<leafNode name="ndp-proxy">
<properties>
<help>Show log for Neighbor Discovery Protocol (NDP) Proxy</help>
diff --git a/python/vyos/config_mgmt.py b/python/vyos/config_mgmt.py
index fc51d781c..70b6ea203 100644
--- a/python/vyos/config_mgmt.py
+++ b/python/vyos/config_mgmt.py
@@ -283,6 +283,8 @@ Proceed ?'''
rollback_ct = self._get_config_tree_revision(rev)
try:
load(rollback_ct, switch='explicit')
+ print('Rollback diff has been applied.')
+ print('Use "compare" to review the changes or "commit" to apply them.')
except LoadConfigError as e:
raise ConfigMgmtError(e) from e
diff --git a/python/vyos/nat.py b/python/vyos/nat.py
index 2ada29add..e54548788 100644
--- a/python/vyos/nat.py
+++ b/python/vyos/nat.py
@@ -300,12 +300,12 @@ def parse_nat_static_rule(rule_conf, rule_id, nat_type):
output.append('counter')
- if translation_str:
- output.append(translation_str)
-
if 'log' in rule_conf:
output.append(f'log prefix "[{log_prefix}{log_suffix}]"')
+ if translation_str:
+ output.append(translation_str)
+
output.append(f'comment "{log_prefix}"')
return " ".join(output)
diff --git a/python/vyos/qos/base.py b/python/vyos/qos/base.py
index 87927ba9d..98e486e42 100644
--- a/python/vyos/qos/base.py
+++ b/python/vyos/qos/base.py
@@ -247,9 +247,15 @@ class QoSBase:
filter_cmd_base += ' protocol all'
if 'match' in cls_config:
- is_filtered = False
+ has_filter = False
for index, (match, match_config) in enumerate(cls_config['match'].items(), start=1):
filter_cmd = filter_cmd_base
+ if not has_filter:
+ for key in ['mark', 'vif', 'ip', 'ipv6']:
+ if key in match_config:
+ has_filter = True
+ break
+
if self.qostype == 'shaper' and 'prio ' not in filter_cmd:
filter_cmd += f' prio {index}'
if 'mark' in match_config:
@@ -332,13 +338,12 @@ class QoSBase:
cls = int(cls)
filter_cmd += f' flowid {self._parent:x}:{cls:x}'
self._cmd(filter_cmd)
- is_filtered = True
vlan_expression = "match.*.vif"
match_vlan = jmespath.search(vlan_expression, cls_config)
if any(tmp in ['exceed', 'bandwidth', 'burst'] for tmp in cls_config) \
- and is_filtered:
+ and has_filter:
# For "vif" "basic match" is used instead of "action police" T5961
if not match_vlan:
filter_cmd += f' action police'
diff --git a/python/vyos/utils/io.py b/python/vyos/utils/io.py
index a8c430f28..205210b66 100644
--- a/python/vyos/utils/io.py
+++ b/python/vyos/utils/io.py
@@ -72,6 +72,8 @@ def ask_yes_no(question, default=False) -> bool:
stdout.write("Please respond with yes/y or no/n\n")
except EOFError:
stdout.write("\nPlease respond with yes/y or no/n\n")
+ except KeyboardInterrupt:
+ return False
def is_interactive():
"""Try to determine if the routine was called from an interactive shell."""
diff --git a/smoketest/config-tests/nat-basic b/smoketest/config-tests/nat-basic
new file mode 100644
index 000000000..9fea08b02
--- /dev/null
+++ b/smoketest/config-tests/nat-basic
@@ -0,0 +1,85 @@
+set interfaces ethernet eth0 offload rps
+set interfaces ethernet eth0 disable
+set interfaces ethernet eth1 offload gro
+set interfaces ethernet eth1 offload gso
+set interfaces ethernet eth1 offload rps
+set interfaces ethernet eth1 offload sg
+set interfaces ethernet eth1 offload tso
+set interfaces ethernet eth2 offload gro
+set interfaces ethernet eth2 offload gso
+set interfaces ethernet eth2 offload rps
+set interfaces ethernet eth2 offload sg
+set interfaces ethernet eth2 offload tso
+set interfaces ethernet eth3 offload gro
+set interfaces ethernet eth3 offload gso
+set interfaces ethernet eth3 offload rps
+set interfaces ethernet eth3 offload sg
+set interfaces ethernet eth3 offload tso
+set interfaces bonding bond10 hash-policy 'layer3+4'
+set interfaces bonding bond10 member interface 'eth2'
+set interfaces bonding bond10 member interface 'eth3'
+set interfaces bonding bond10 mode '802.3ad'
+set interfaces bonding bond10 vif 50 address '192.168.189.1/24'
+set interfaces loopback lo
+set interfaces pppoe pppoe7 authentication password 'vyos'
+set interfaces pppoe pppoe7 authentication username 'vyos'
+set interfaces pppoe pppoe7 dhcpv6-options pd 0 interface bond10.50 address '1'
+set interfaces pppoe pppoe7 dhcpv6-options pd 0 length '56'
+set interfaces pppoe pppoe7 ip adjust-mss '1452'
+set interfaces pppoe pppoe7 ipv6 address autoconf
+set interfaces pppoe pppoe7 ipv6 adjust-mss '1432'
+set interfaces pppoe pppoe7 mtu '1492'
+set interfaces pppoe pppoe7 no-peer-dns
+set interfaces pppoe pppoe7 source-interface 'eth1'
+set service lldp interface eth1 disable
+set service ntp allow-client address '192.168.189.0/24'
+set service ntp server time1.vyos.net
+set service ntp server time2.vyos.net
+set service ntp listen-address '192.168.189.1'
+set service ssh dynamic-protection
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 lease '604800'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option default-router '192.168.189.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option domain-name 'vyos.net'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '1.1.1.1'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 option name-server '9.9.9.9'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 start '192.168.189.20'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 range 0 stop '192.168.189.254'
+set service dhcp-server shared-network-name LAN subnet 192.168.189.0/24 subnet-id '1'
+set service router-advert interface bond10.50 prefix ::/64 preferred-lifetime '2700'
+set service router-advert interface bond10.50 prefix ::/64 valid-lifetime '5400'
+set system config-management commit-revisions '100'
+set system domain-name 'vyos.net'
+set system host-name 'R1'
+set system login user vyos authentication encrypted-password '$6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/'
+set system login user vyos authentication plaintext-password ''
+set system name-server '1.1.1.1'
+set system name-server '9.9.9.9'
+set system console device ttyS0 speed '115200'
+set nat destination rule 1000 destination port '3389'
+set nat destination rule 1000 inbound-interface name 'pppoe7'
+set nat destination rule 1000 protocol 'tcp'
+set nat destination rule 1000 translation address '192.168.189.5'
+set nat destination rule 1000 translation port '3389'
+set nat destination rule 10022 destination port '10022'
+set nat destination rule 10022 inbound-interface name 'pppoe7'
+set nat destination rule 10022 protocol 'tcp'
+set nat destination rule 10022 translation address '192.168.189.2'
+set nat destination rule 10022 translation port '22'
+set nat destination rule 10300 destination port '10300'
+set nat destination rule 10300 inbound-interface name 'pppoe7'
+set nat destination rule 10300 protocol 'udp'
+set nat destination rule 10300 translation address '192.168.189.2'
+set nat destination rule 10300 translation port '10300'
+set nat source rule 10 outbound-interface name 'eth1'
+set nat source rule 10 source address '192.168.189.0/24'
+set nat source rule 10 translation address 'masquerade'
+set nat source rule 10 translation options port-mapping 'random'
+set nat source rule 50 outbound-interface name 'pppoe7'
+set nat source rule 50 protocol 'udp'
+set nat source rule 50 source address '192.168.189.2'
+set nat source rule 50 source port '10300'
+set nat source rule 50 translation address 'masquerade'
+set nat source rule 50 translation port '10300'
+set nat source rule 100 outbound-interface name 'pppoe7'
+set nat source rule 100 source address '192.168.189.0/24'
+set nat source rule 100 translation address 'masquerade'
diff --git a/smoketest/configs/nat-basic b/smoketest/configs/nat-basic
new file mode 100644
index 000000000..52f369f34
--- /dev/null
+++ b/smoketest/configs/nat-basic
@@ -0,0 +1,256 @@
+interfaces {
+ bonding bond10 {
+ hash-policy "layer3+4"
+ member {
+ interface "eth2"
+ interface "eth3"
+ }
+ mode "802.3ad"
+ vif 50 {
+ address "192.168.189.1/24"
+ }
+ }
+ ethernet eth0 {
+ disable
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth1 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth2 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ ethernet eth3 {
+ offload {
+ gro
+ gso
+ rps
+ sg
+ tso
+ }
+ }
+ loopback lo {
+ }
+ pppoe pppoe7 {
+ authentication {
+ password "vyos"
+ username "vyos"
+ }
+ dhcpv6-options {
+ pd 0 {
+ interface bond10.50 {
+ address "1"
+ }
+ length "56"
+ }
+ }
+ ip {
+ adjust-mss "1452"
+ }
+ ipv6 {
+ address {
+ autoconf
+ }
+ adjust-mss "1432"
+ }
+ mtu "1492"
+ no-peer-dns
+ source-interface "eth1"
+ }
+}
+nat {
+ destination {
+ rule 1000 {
+ destination {
+ port "3389"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "tcp"
+ translation {
+ address "192.168.189.5"
+ port "3389"
+ }
+ }
+ rule 10022 {
+ destination {
+ port "10022"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "tcp"
+ translation {
+ address "192.168.189.2"
+ port "22"
+ }
+ }
+ rule 10300 {
+ destination {
+ port "10300"
+ }
+ inbound-interface {
+ name "pppoe7"
+ }
+ protocol "udp"
+ translation {
+ address "192.168.189.2"
+ port "10300"
+ }
+ }
+ }
+ source {
+ rule 10 {
+ outbound-interface {
+ name "eth1"
+ }
+ source {
+ address "192.168.189.0/24"
+ }
+ translation {
+ address "masquerade"
+ options {
+ port-mapping fully-random
+ }
+ }
+ }
+ rule 50 {
+ outbound-interface {
+ name "pppoe7"
+ }
+ protocol "udp"
+ source {
+ address "192.168.189.2"
+ port "10300"
+ }
+ translation {
+ address "masquerade"
+ port "10300"
+ }
+ }
+ rule 100 {
+ outbound-interface {
+ name "pppoe7"
+ }
+ source {
+ address "192.168.189.0/24"
+ }
+ translation {
+ address "masquerade"
+ }
+ }
+ }
+}
+service {
+ dhcp-server {
+ shared-network-name LAN {
+ subnet 192.168.189.0/24 {
+ default-router "192.168.189.1"
+ domain-name "vyos.net"
+ lease "604800"
+ name-server "1.1.1.1"
+ name-server "9.9.9.9"
+ range 0 {
+ start "192.168.189.20"
+ stop "192.168.189.254"
+ }
+ }
+ }
+ }
+ lldp {
+ interface all {
+ }
+ interface eth1 {
+ disable
+ }
+ }
+ ntp {
+ allow-client {
+ address "192.168.189.0/24"
+ }
+ listen-address "192.168.189.1"
+ server time1.vyos.net {
+ }
+ server time2.vyos.net {
+ }
+ }
+ router-advert {
+ interface bond10.50 {
+ prefix ::/64 {
+ preferred-lifetime "2700"
+ valid-lifetime "5400"
+ }
+ }
+ }
+ ssh {
+ disable-host-validation
+ dynamic-protection {
+ }
+ }
+}
+system {
+ config-management {
+ commit-revisions "100"
+ }
+ conntrack {
+ modules {
+ ftp
+ h323
+ nfs
+ pptp
+ sip
+ sqlnet
+ tftp
+ }
+ }
+ console {
+ device ttyS0 {
+ speed "115200"
+ }
+ }
+ domain-name "vyos.net"
+ host-name "R1"
+ login {
+ user vyos {
+ authentication {
+ encrypted-password $6$2Ta6TWHd/U$NmrX0x9kexCimeOcYK1MfhMpITF9ELxHcaBU/znBq.X2ukQOj61fVI2UYP/xBzP4QtiTcdkgs7WOQMHWsRymO/
+ plaintext-password ""
+ }
+ }
+ }
+ name-server "1.1.1.1"
+ name-server "9.9.9.9"
+ syslog {
+ global {
+ facility all {
+ level "info"
+ }
+ facility local7 {
+ level "debug"
+ }
+ }
+ }
+}
+
+// Warning: Do not remove the following line.
+// vyos-config-version: "bgp@5:broadcast-relay@1:cluster@2:config-management@1:conntrack@5:conntrack-sync@2:container@2:dhcp-relay@2:dhcp-server@8:dhcpv6-server@1:dns-dynamic@4:dns-forwarding@4:firewall@15:flow-accounting@1:https@6:ids@1:interfaces@32:ipoe-server@3:ipsec@13:isis@3:l2tp@9:lldp@2:mdns@1:monitoring@1:nat@7:nat66@3:ntp@3:openconnect@3:ospf@2:pim@1:policy@8:pppoe-server@10:pptp@5:qos@2:quagga@11:rip@1:rpki@2:salt@1:snmp@3:ssh@2:sstp@6:system@27:vrf@3:vrrp@4:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2"
+// Release version: 1.4.0-epa3
diff --git a/smoketest/scripts/cli/test_qos.py b/smoketest/scripts/cli/test_qos.py
index bcf5139c7..5977b2f41 100755
--- a/smoketest/scripts/cli/test_qos.py
+++ b/smoketest/scripts/cli/test_qos.py
@@ -738,6 +738,27 @@ class TestQoS(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.assertEqual('', cmd(f'tc filter show dev {interface}'))
+ def test_14_policy_limiter_marked_traffic(self):
+ policy_name = 'smoke_test'
+ base_policy_path = ['qos', 'policy', 'limiter', policy_name]
+
+ self.cli_set(['qos', 'interface', self._interfaces[0], 'ingress', policy_name])
+ self.cli_set(base_policy_path + ['class', '100', 'bandwidth', '20gbit'])
+ self.cli_set(base_policy_path + ['class', '100', 'burst', '3760k'])
+ self.cli_set(base_policy_path + ['class', '100', 'match', 'INTERNAL', 'mark', '100'])
+ self.cli_set(base_policy_path + ['class', '100', 'priority', '20'])
+ self.cli_set(base_policy_path + ['default', 'bandwidth', '1gbit'])
+ self.cli_set(base_policy_path + ['default', 'burst', '125000000b'])
+ self.cli_commit()
+
+ tc_filters = cmd(f'tc filter show dev {self._interfaces[0]} ingress')
+ # class 100
+ self.assertIn('filter parent ffff: protocol all pref 20 fw chain 0', tc_filters)
+ self.assertIn('action order 1: police 0x1 rate 20Gbit burst 3847500b mtu 2Kb action drop overhead 0b', tc_filters)
+ # default
+ self.assertIn('filter parent ffff: protocol all pref 255 basic chain 0', tc_filters)
+ self.assertIn('action order 1: police 0x2 rate 1Gbit burst 125000000b mtu 2Kb action drop overhead 0b', tc_filters)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/nat.py b/src/conf_mode/nat.py
index 4cd9b570d..f74bb217e 100755
--- a/src/conf_mode/nat.py
+++ b/src/conf_mode/nat.py
@@ -17,7 +17,6 @@
import os
from sys import exit
-from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
@@ -30,6 +29,7 @@ from vyos.utils.dict import dict_search_args
from vyos.utils.process import cmd
from vyos.utils.process import run
from vyos.utils.network import is_addr_assigned
+from vyos.utils.network import interface_exists
from vyos import ConfigError
from vyos import airbag
@@ -149,8 +149,12 @@ def verify(nat):
if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"')
elif 'name' in config['outbound_interface']:
- if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
- Warning(f'NAT interface "{config["outbound_interface"]["name"]}" for source NAT rule "{rule}" does not exist!')
+ interface_name = config['outbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for source NAT rule "{rule}" does not exist!')
else:
group_name = config['outbound_interface']['group']
if group_name[0] == '!':
@@ -182,8 +186,12 @@ def verify(nat):
if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"')
elif 'name' in config['inbound_interface']:
- if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
- Warning(f'NAT interface "{config["inbound_interface"]["name"]}" for destination NAT rule "{rule}" does not exist!')
+ interface_name = config['inbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for destination NAT rule "{rule}" does not exist!')
else:
group_name = config['inbound_interface']['group']
if group_name[0] == '!':
diff --git a/src/conf_mode/nat66.py b/src/conf_mode/nat66.py
index fe017527d..075738dad 100755
--- a/src/conf_mode/nat66.py
+++ b/src/conf_mode/nat66.py
@@ -17,15 +17,15 @@
import os
from sys import exit
-from netifaces import interfaces
from vyos.base import Warning
from vyos.config import Config
from vyos.configdep import set_dependents, call_dependents
from vyos.template import render
-from vyos.utils.process import cmd
-from vyos.utils.kernel import check_kmod
from vyos.utils.dict import dict_search
+from vyos.utils.kernel import check_kmod
+from vyos.utils.network import interface_exists
+from vyos.utils.process import cmd
from vyos.template import is_ipv6
from vyos import ConfigError
from vyos import airbag
@@ -64,8 +64,12 @@ def verify(nat):
if 'name' in config['outbound_interface'] and 'group' in config['outbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for nat source rule "{rule}"')
elif 'name' in config['outbound_interface']:
- if config['outbound_interface']['name'] not in 'any' and config['outbound_interface']['name'] not in interfaces():
- Warning(f'NAT66 interface "{config["outbound_interface"]["name"]}" for source NAT66 rule "{rule}" does not exist!')
+ interface_name = config['outbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for source NAT66 rule "{rule}" does not exist!')
addr = dict_search('translation.address', config)
if addr != None:
@@ -88,8 +92,12 @@ def verify(nat):
if 'name' in config['inbound_interface'] and 'group' in config['inbound_interface']:
raise ConfigError(f'{err_msg} cannot specify both interface group and interface name for destination nat rule "{rule}"')
elif 'name' in config['inbound_interface']:
- if config['inbound_interface']['name'] not in 'any' and config['inbound_interface']['name'] not in interfaces():
- Warning(f'NAT66 interface "{config["inbound_interface"]["name"]}" for destination NAT66 rule "{rule}" does not exist!')
+ interface_name = config['inbound_interface']['name']
+ if interface_name not in 'any':
+ if interface_name.startswith('!'):
+ interface_name = interface_name[1:]
+ if not interface_exists(interface_name):
+ Warning(f'Interface "{interface_name}" for destination NAT66 rule "{rule}" does not exist!')
return None
diff --git a/src/conf_mode/service_dhcpv6-server.py b/src/conf_mode/service_dhcpv6-server.py
index c7333dd3a..7af88007c 100755
--- a/src/conf_mode/service_dhcpv6-server.py
+++ b/src/conf_mode/service_dhcpv6-server.py
@@ -106,14 +106,14 @@ def verify(dhcpv6):
# Stop address must be greater or equal to start address
if not ip_address(stop) >= ip_address(start):
- raise ConfigError(f'Range stop address "{stop}" must be greater then or equal ' \
+ raise ConfigError(f'Range stop address "{stop}" must be greater than or equal ' \
f'to the range start address "{start}"!')
# DHCPv6 range start address must be unique - two ranges can't
# start with the same address - makes no sense
if start in range6_start:
raise ConfigError(f'Conflicting DHCPv6 lease range: '\
- f'Pool start address "{start}" defined multipe times!')
+ f'Pool start address "{start}" defined multiple times!')
range6_start.append(start)
@@ -121,7 +121,7 @@ def verify(dhcpv6):
# end with the same address - makes no sense
if stop in range6_stop:
raise ConfigError(f'Conflicting DHCPv6 lease range: '\
- f'Pool stop address "{stop}" defined multipe times!')
+ f'Pool stop address "{stop}" defined multiple times!')
range6_stop.append(stop)
@@ -180,7 +180,7 @@ def verify(dhcpv6):
if 'option' in subnet_config:
if 'vendor_option' in subnet_config['option']:
if len(dict_search('option.vendor_option.cisco.tftp_server', subnet_config)) > 2:
- raise ConfigError(f'No more then two Cisco tftp-servers should be defined for subnet "{subnet}"!')
+ raise ConfigError(f'No more than two Cisco tftp-servers should be defined for subnet "{subnet}"!')
# Subnets must be unique
if subnet in subnets:
diff --git a/src/migration-scripts/nat/7-to-8 b/src/migration-scripts/nat/7-to-8
new file mode 100755
index 000000000..ab2ffa6d3
--- /dev/null
+++ b/src/migration-scripts/nat/7-to-8
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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/>.
+
+# T6345: random - In kernel 5.0 and newer this is the same as fully-random.
+# In earlier kernels the port mapping will be randomized using a seeded
+# MD5 hash mix using source and destination address and destination port.
+# drop fully-random from CLI
+
+from sys import argv,exit
+from vyos.configtree import ConfigTree
+
+if len(argv) < 2:
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+if not config.exists(['nat']):
+ # Nothing to do
+ exit(0)
+
+for direction in ['source', 'destination']:
+ # If a node doesn't exist, we obviously have nothing to do.
+ if not config.exists(['nat', direction]):
+ continue
+
+ # However, we also need to handle the case when a 'source' or 'destination' sub-node does exist,
+ # but there are no rules under it.
+ if not config.list_nodes(['nat', direction]):
+ continue
+
+ for rule in config.list_nodes(['nat', direction, 'rule']):
+ port_mapping = ['nat', direction, 'rule', rule, 'translation', 'options', 'port-mapping']
+ if config.exists(port_mapping):
+ tmp = config.return_value(port_mapping)
+ if tmp == 'fully-random':
+ config.set(port_mapping, value='random')
+
+try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+except OSError as e:
+ print(f'Failed to save the modified config: {e}')
+ exit(1)
diff --git a/src/op_mode/cgnat.py b/src/op_mode/cgnat.py
index e58b15809..9ad8f92f9 100755
--- a/src/op_mode/cgnat.py
+++ b/src/op_mode/cgnat.py
@@ -16,6 +16,7 @@
import json
import sys
+import typing
from tabulate import tabulate
@@ -27,15 +28,11 @@ from vyos.utils.process import cmd
CGNAT_TABLE = 'cgnat'
-def _get_raw_data():
- """ Get CGNAT dictionary
- """
+def _get_raw_data(external_address: str = '', internal_address: str = '') -> list[dict]:
+ """Get CGNAT dictionary and filter by external or internal address if provided."""
cmd_output = cmd(f'nft --json list table ip {CGNAT_TABLE}')
data = json.loads(cmd_output)
- return data
-
-def _get_formatted_output(data):
elements = data['nftables'][2]['map']['elem']
allocations = []
for elem in elements:
@@ -44,23 +41,48 @@ def _get_formatted_output(data):
start_port = elem[1]['concat'][1]['range'][0]
end_port = elem[1]['concat'][1]['range'][1]
port_range = f'{start_port}-{end_port}'
- allocations.append((internal, external, port_range))
+ if (internal_address and internal != internal_address) or (
+ external_address and external != external_address
+ ):
+ continue
+
+ allocations.append(
+ {
+ 'internal_address': internal,
+ 'external_address': external,
+ 'port_range': port_range,
+ }
+ )
+
+ return allocations
+
+
+def _get_formatted_output(allocations: list[dict]) -> str:
+ # Convert the list of dictionaries to a list of tuples for tabulate
headers = ['Internal IP', 'External IP', 'Port range']
- output = tabulate(allocations, headers, numalign="left")
+ data = [
+ (alloc['internal_address'], alloc['external_address'], alloc['port_range'])
+ for alloc in allocations
+ ]
+ output = tabulate(data, headers, numalign="left")
return output
-def show_allocation(raw: bool):
+def show_allocation(
+ raw: bool,
+ external_address: typing.Optional[str],
+ internal_address: typing.Optional[str],
+) -> str:
config = ConfigTreeQuery()
if not config.exists('nat cgnat'):
raise vyos.opmode.UnconfiguredSubsystem('CGNAT is not configured')
if raw:
- return _get_raw_data()
+ return _get_raw_data(external_address, internal_address)
else:
- raw_data = _get_raw_data()
+ raw_data = _get_raw_data(external_address, internal_address)
return _get_formatted_output(raw_data)