summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/firewall/nftables-nat.tmpl7
-rw-r--r--data/templates/frr/staticd.frr.j28
-rw-r--r--data/templates/monitoring/telegraf.tmpl6
-rw-r--r--interface-definitions/dhcp-server.xml.in12
-rw-r--r--interface-definitions/include/interface/default-route-distance.xml.i15
-rw-r--r--interface-definitions/include/interface/dhcp-options.xml.i21
-rw-r--r--interface-definitions/include/interface/no-default-route.xml.i8
-rw-r--r--interface-definitions/include/version/interfaces-version.xml.i2
-rw-r--r--interface-definitions/interfaces-pppoe.xml.in27
-rw-r--r--python/vyos/configdict.py75
-rwxr-xr-xpython/vyos/ifconfig/interface.py8
-rw-r--r--python/vyos/ifconfig/pppoe.py80
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py114
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py5
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py109
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bgp.py26
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py6
-rwxr-xr-xsrc/conf_mode/interfaces-pppoe.py35
-rwxr-xr-xsrc/conf_mode/protocols_bgp.py6
-rwxr-xr-xsrc/conf_mode/protocols_static.py5
-rwxr-xr-xsrc/etc/ppp/ip-up.d/99-vyos-pppoe-callback12
-rwxr-xr-xsrc/migration-scripts/interfaces/25-to-2654
22 files changed, 414 insertions, 227 deletions
diff --git a/data/templates/firewall/nftables-nat.tmpl b/data/templates/firewall/nftables-nat.tmpl
index 922f3dcb4..7a925b264 100644
--- a/data/templates/firewall/nftables-nat.tmpl
+++ b/data/templates/firewall/nftables-nat.tmpl
@@ -138,8 +138,9 @@
{% endif %}
{% endmacro %}
-# Start with clean NAT table
-flush table ip nat
+# Start with clean SNAT and DNAT chains
+flush chain ip nat PREROUTING
+flush chain ip nat POSTROUTING
{% if helper_functions is vyos_defined('remove') %}
{# NAT if going to be disabled - remove rules and targets from nftables #}
{% set base_command = 'delete rule ip raw' %}
@@ -164,6 +165,7 @@ add rule ip raw NAT_CONNTRACK counter accept
#
# Destination NAT rules build up here
#
+add rule ip nat PREROUTING counter jump VYOS_PRE_DNAT_HOOK
{% if destination.rule is vyos_defined %}
{% for rule, config in destination.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'PREROUTING') }}
@@ -172,6 +174,7 @@ add rule ip raw NAT_CONNTRACK counter accept
#
# Source NAT rules build up here
#
+add rule ip nat POSTROUTING counter jump VYOS_PRE_SNAT_HOOK
{% if source.rule is vyos_defined %}
{% for rule, config in source.rule.items() if config.disable is not vyos_defined %}
{{ nat_rule(rule, config, 'POSTROUTING') }}
diff --git a/data/templates/frr/staticd.frr.j2 b/data/templates/frr/staticd.frr.j2
index 08b2a3dab..589f03c2c 100644
--- a/data/templates/frr/staticd.frr.j2
+++ b/data/templates/frr/staticd.frr.j2
@@ -20,10 +20,16 @@ vrf {{ vrf }}
{% for interface, interface_config in dhcp.items() %}
{% set next_hop = interface | get_dhcp_router %}
{% if next_hop is vyos_defined %}
-{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.distance }}
+{{ ip_prefix }} route 0.0.0.0/0 {{ next_hop }} {{ interface }} tag 210 {{ interface_config.dhcp_options.default_route_distance if interface_config.dhcp_options.default_route_distance is vyos_defined }}
{% endif %}
{% endfor %}
{% endif %}
+{# IPv4 default routes from PPPoE interfaces #}
+{% if pppoe is vyos_defined %}
+{% for interface, interface_config in pppoe.items() %}
+{{ ip_prefix }} route 0.0.0.0/0 {{ interface }} tag 210 {{ interface_config.default_route_distance if interface_config.default_route_distance is vyos_defined }}
+{% endfor %}
+{% endif %}
{# IPv6 routing #}
{% if route6 is vyos_defined %}
{% for prefix, prefix_config in route6.items() %}
diff --git a/data/templates/monitoring/telegraf.tmpl b/data/templates/monitoring/telegraf.tmpl
index d3145a500..cf33eec4e 100644
--- a/data/templates/monitoring/telegraf.tmpl
+++ b/data/templates/monitoring/telegraf.tmpl
@@ -1,12 +1,12 @@
# Generated by /usr/libexec/vyos/conf_mode/service_monitoring_telegraf.py
[agent]
- interval = "10s"
+ interval = "15s"
round_interval = true
metric_batch_size = 1000
metric_buffer_limit = 10000
- collection_jitter = "0s"
- flush_interval = "10s"
+ collection_jitter = "5s"
+ flush_interval = "15s"
flush_jitter = "0s"
precision = ""
debug = false
diff --git a/interface-definitions/dhcp-server.xml.in b/interface-definitions/dhcp-server.xml.in
index 4ea2d471d..3c2c82a9d 100644
--- a/interface-definitions/dhcp-server.xml.in
+++ b/interface-definitions/dhcp-server.xml.in
@@ -374,6 +374,18 @@
<leafNode name="tftp-server-name">
<properties>
<help>TFTP server name</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>TFTP server IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>hostname</format>
+ <description>TFTP server FQDN</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="fqdn"/>
+ </constraint>
</properties>
</leafNode>
<leafNode name="time-offset">
diff --git a/interface-definitions/include/interface/default-route-distance.xml.i b/interface-definitions/include/interface/default-route-distance.xml.i
new file mode 100644
index 000000000..6eda52c91
--- /dev/null
+++ b/interface-definitions/include/interface/default-route-distance.xml.i
@@ -0,0 +1,15 @@
+<!-- include start from interface/default-route-distance.xml.i -->
+<leafNode name="default-route-distance">
+ <properties>
+ <help>Distance for installed default route</help>
+ <valueHelp>
+ <format>u32:1-255</format>
+ <description>Distance for the default route from DHCP server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1-255"/>
+ </constraint>
+ </properties>
+ <defaultValue>210</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/interface/dhcp-options.xml.i b/interface-definitions/include/interface/dhcp-options.xml.i
index 098d02919..914b60503 100644
--- a/interface-definitions/include/interface/dhcp-options.xml.i
+++ b/interface-definitions/include/interface/dhcp-options.xml.i
@@ -19,25 +19,8 @@
<help>Identify the vendor client type to the DHCP server</help>
</properties>
</leafNode>
- <leafNode name="no-default-route">
- <properties>
- <help>Do not request routers from DHCP server</help>
- <valueless/>
- </properties>
- </leafNode>
- <leafNode name="default-route-distance">
- <properties>
- <help>Distance for the default route from DHCP server</help>
- <valueHelp>
- <format>u32:1-255</format>
- <description>Distance for the default route from DHCP server</description>
- </valueHelp>
- <constraint>
- <validator name="numeric" argument="--range 1-255"/>
- </constraint>
- </properties>
- <defaultValue>210</defaultValue>
- </leafNode>
+ #include <include/interface/no-default-route.xml.i>
+ #include <include/interface/default-route-distance.xml.i>
<leafNode name="reject">
<properties>
<help>IP addresses or subnets from which to reject DHCP leases</help>
diff --git a/interface-definitions/include/interface/no-default-route.xml.i b/interface-definitions/include/interface/no-default-route.xml.i
new file mode 100644
index 000000000..307fcff1e
--- /dev/null
+++ b/interface-definitions/include/interface/no-default-route.xml.i
@@ -0,0 +1,8 @@
+<!-- include start from interface/dhcp-options.xml.i -->
+<leafNode name="no-default-route">
+ <properties>
+ <help>Do not install default route to system</help>
+ <valueless/>
+ </properties>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i
index b97971531..0a209bc3a 100644
--- a/interface-definitions/include/version/interfaces-version.xml.i
+++ b/interface-definitions/include/version/interfaces-version.xml.i
@@ -1,3 +1,3 @@
<!-- include start from include/version/interfaces-version.xml.i -->
-<syntaxVersion component='interfaces' version='25'></syntaxVersion>
+<syntaxVersion component='interfaces' version='26'></syntaxVersion>
<!-- include end -->
diff --git a/interface-definitions/interfaces-pppoe.xml.in b/interface-definitions/interfaces-pppoe.xml.in
index 3a0b7a40c..8cd8f8c86 100644
--- a/interface-definitions/interfaces-pppoe.xml.in
+++ b/interface-definitions/interfaces-pppoe.xml.in
@@ -21,31 +21,8 @@
#include <include/interface/dial-on-demand.xml.i>
#include <include/interface/interface-firewall.xml.i>
#include <include/interface/interface-policy.xml.i>
- <leafNode name="default-route">
- <properties>
- <help>Default route insertion behaviour</help>
- <completionHelp>
- <list>auto none force</list>
- </completionHelp>
- <constraint>
- <regex>^(auto|none|force)$</regex>
- </constraint>
- <constraintErrorMessage>PPPoE default-route option must be 'auto', 'none', or 'force'</constraintErrorMessage>
- <valueHelp>
- <format>auto</format>
- <description>Automatically install a default route</description>
- </valueHelp>
- <valueHelp>
- <format>none</format>
- <description>Do not install a default route</description>
- </valueHelp>
- <valueHelp>
- <format>force</format>
- <description>Replace existing default route</description>
- </valueHelp>
- </properties>
- <defaultValue>auto</defaultValue>
- </leafNode>
+ #include <include/interface/no-default-route.xml.i>
+ #include <include/interface/default-route-distance.xml.i>
#include <include/interface/dhcpv6-options.xml.i>
#include <include/interface/description.xml.i>
#include <include/interface/disable.xml.i>
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index f50db0c99..399ac6feb 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -104,6 +104,12 @@ def list_diff(first, second):
second = set(second)
return [item for item in first if item not in second]
+def is_node_changed(conf, path):
+ from vyos.configdiff import get_config_diff
+ D = get_config_diff(conf, key_mangling=('-', '_'))
+ D.set_level(conf.get_level())
+ return D.is_node_changed(path)
+
def leaf_node_changed(conf, path):
"""
Check if a leaf node was altered. If it has been altered - values has been
@@ -133,9 +139,6 @@ def leaf_node_changed(conf, path):
elif isinstance(new, type(None)):
new = []
return list_diff(old, new)
- if old is None and new is not None:
- # node was added to the CLI
- return True
return None
@@ -328,45 +331,83 @@ def is_source_interface(conf, interface, intftype=None):
def get_dhcp_interfaces(conf, vrf=None):
""" Common helper functions to retrieve all interfaces from current CLI
sessions that have DHCP configured. """
+ # Cache and reset config level
+ old_level = conf.get_level()
+ conf.set_level([])
+
dhcp_interfaces = {}
dict = conf.get_config_dict(['interfaces'], get_first_key=True)
if not dict:
return dhcp_interfaces
- def check_dhcp(config, ifname):
+ def check_dhcp(config):
+ ifname = config['ifname']
tmp = {}
if 'address' in config and 'dhcp' in config['address']:
options = {}
- if 'dhcp_options' in config and 'default_route_distance' in config['dhcp_options']:
- options.update({'distance' : config['dhcp_options']['default_route_distance']})
+ if dict_search('dhcp_options.default_route_distance', config) != None:
+ options.update({'dhcp_options' : config['dhcp_options']})
if 'vrf' in config:
if vrf is config['vrf']: tmp.update({ifname : options})
else: tmp.update({ifname : options})
+
return tmp
for section, interface in dict.items():
for ifname in interface:
+ # always reset config level, as get_interface_dict() will alter it
+ conf.set_level([])
# we already have a dict representation of the config from get_config_dict(),
# but with the extended information from get_interface_dict() we also
# get the DHCP client default-route-distance default option if not specified.
ifconfig = get_interface_dict(conf, ['interfaces', section], ifname)
- tmp = check_dhcp(ifconfig, ifname)
+ tmp = check_dhcp(ifconfig)
dhcp_interfaces.update(tmp)
# check per VLAN interfaces
for vif, vif_config in ifconfig.get('vif', {}).items():
- tmp = check_dhcp(vif_config, f'{ifname}.{vif}')
+ tmp = check_dhcp(vif_config)
dhcp_interfaces.update(tmp)
# check QinQ VLAN interfaces
- for vif_s, vif_s_config in ifconfig.get('vif-s', {}).items():
- tmp = check_dhcp(vif_s_config, f'{ifname}.{vif_s}')
+ for vif_s, vif_s_config in ifconfig.get('vif_s', {}).items():
+ tmp = check_dhcp(vif_s_config)
dhcp_interfaces.update(tmp)
- for vif_c, vif_c_config in vif_s_config.get('vif-c', {}).items():
- tmp = check_dhcp(vif_c_config, f'{ifname}.{vif_s}.{vif_c}')
+ for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ tmp = check_dhcp(vif_c_config)
dhcp_interfaces.update(tmp)
+ # reset old config level
return dhcp_interfaces
+def get_pppoe_interfaces(conf, vrf=None):
+ """ Common helper functions to retrieve all interfaces from current CLI
+ sessions that have DHCP configured. """
+ # Cache and reset config level
+ old_level = conf.get_level()
+ conf.set_level([])
+
+ pppoe_interfaces = {}
+ for ifname in conf.list_nodes(['interfaces', 'pppoe']):
+ # always reset config level, as get_interface_dict() will alter it
+ conf.set_level([])
+ # we already have a dict representation of the config from get_config_dict(),
+ # but with the extended information from get_interface_dict() we also
+ # get the DHCP client default-route-distance default option if not specified.
+ ifconfig = get_interface_dict(conf, ['interfaces', 'pppoe'], ifname)
+
+ options = {}
+ if 'default_route_distance' in ifconfig:
+ options.update({'default_route_distance' : ifconfig['default_route_distance']})
+ if 'no_default_route' in ifconfig:
+ options.update({'no_default_route' : {}})
+ if 'vrf' in ifconfig:
+ if vrf is ifconfig['vrf']: pppoe_interfaces.update({ifname : options})
+ else: pppoe_interfaces.update({ifname : options})
+
+ # reset old config level
+ conf.set_level(old_level)
+ return pppoe_interfaces
+
def get_interface_dict(config, base, ifname=''):
"""
Common utility function to retrieve and mangle the interfaces configuration
@@ -376,7 +417,6 @@ def get_interface_dict(config, base, ifname=''):
Return a dictionary with the necessary interface config keys.
"""
-
if not ifname:
from vyos import ConfigError
# determine tagNode instance
@@ -477,6 +517,9 @@ def get_interface_dict(config, base, ifname=''):
# identical for all types of VLAN interfaces as they all include the same
# XML definitions which hold the defaults.
for vif, vif_config in dict.get('vif', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif'][vif].update({'ifname' : f'{ifname}.{vif}'})
+
default_vif_values = defaults(base + ['vif'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
# remove the default values from the dict.
@@ -508,6 +551,9 @@ def get_interface_dict(config, base, ifname=''):
if dhcp: dict['vif'][vif].update({'dhcp_options_changed' : ''})
for vif_s, vif_s_config in dict.get('vif_s', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif_s'][vif_s].update({'ifname' : f'{ifname}.{vif_s}'})
+
default_vif_s_values = defaults(base + ['vif-s'])
# XXX: T2665: we only wan't the vif-s defaults - do not care about vif-c
if 'vif_c' in default_vif_s_values: del default_vif_s_values['vif_c']
@@ -544,6 +590,9 @@ def get_interface_dict(config, base, ifname=''):
if dhcp: dict['vif_s'][vif_s].update({'dhcp_options_changed' : ''})
for vif_c, vif_c_config in vif_s_config.get('vif_c', {}).items():
+ # Add subinterface name to dictionary
+ dict['vif_s'][vif_s]['vif_c'][vif_c].update({'ifname' : f'{ifname}.{vif_s}.{vif_c}'})
+
default_vif_c_values = defaults(base + ['vif-s', 'vif-c'])
# XXX: T2665: When there is no DHCPv6-PD configuration given, we can safely
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index ea7497e92..22441d1d2 100755
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1587,12 +1587,10 @@ class Interface(Control):
tmp['source_interface'] = ifname
tmp['vlan_id'] = vif_s_id
- vif_s_ifname = f'{ifname}.{vif_s_id}'
- vif_s_config['ifname'] = vif_s_ifname
-
# It is not possible to change the VLAN encapsulation protocol
# "on-the-fly". For this "quirk" we need to actively delete and
# re-create the VIF-S interface.
+ vif_s_ifname = f'{ifname}.{vif_s_id}'
if self.exists(vif_s_ifname):
cur_cfg = get_interface_config(vif_s_ifname)
protocol = dict_search('linkinfo.info_data.protocol', cur_cfg).lower()
@@ -1614,7 +1612,6 @@ class Interface(Control):
tmp['vlan_id'] = vif_c_id
vif_c_ifname = f'{vif_s_ifname}.{vif_c_id}'
- vif_c_config['ifname'] = vif_c_ifname
c_vlan = VLANIf(vif_c_ifname, **tmp)
c_vlan.update(vif_c_config)
@@ -1625,10 +1622,7 @@ class Interface(Control):
# create/update 802.1q VLAN interfaces
for vif_id, vif_config in config.get('vif', {}).items():
-
vif_ifname = f'{ifname}.{vif_id}'
- vif_config['ifname'] = vif_ifname
-
tmp = deepcopy(VLANIf.get_config())
tmp['source_interface'] = ifname
tmp['vlan_id'] = vif_id
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
index 1d13264bf..63ffc8069 100644
--- a/python/vyos/ifconfig/pppoe.py
+++ b/python/vyos/ifconfig/pppoe.py
@@ -27,12 +27,13 @@ class PPPoEIf(Interface):
},
}
- def _remove_routes(self, vrf=''):
+ def _remove_routes(self, vrf=None):
# Always delete default routes when interface is removed
+ vrf_cmd = ''
if vrf:
- vrf = f'-c "vrf {vrf}"'
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"')
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no ipv6 route ::/0 {self.ifname} tag 210"')
+ vrf_cmd = f'-c "vrf {vrf}"'
+ self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ip route 0.0.0.0/0 {self.ifname} tag 210"')
+ self._cmd(f'vtysh -c "conf t" {vrf_cmd} -c "no ipv6 route ::/0 {self.ifname} tag 210"')
def remove(self):
"""
@@ -44,11 +45,11 @@ class PPPoEIf(Interface):
>>> i = Interface('pppoe0')
>>> i.remove()
"""
-
+ vrf = None
tmp = get_interface_config(self.ifname)
- vrf = ''
if 'master' in tmp:
- self._remove_routes(tmp['master'])
+ vrf = tmp['master']
+ self._remove_routes(vrf)
# remove bond master which places members in disabled state
super().remove()
@@ -84,10 +85,12 @@ class PPPoEIf(Interface):
self._config = config
# remove old routes from an e.g. old VRF assignment
- vrf = ''
- if 'vrf_old' in config:
- vrf = config['vrf_old']
- self._remove_routes(vrf)
+ if 'shutdown_required':
+ vrf = None
+ tmp = get_interface_config(self.ifname)
+ if 'master' in tmp:
+ vrf = tmp['master']
+ self._remove_routes(vrf)
# DHCPv6 PD handling is a bit different on PPPoE interfaces, as we do
# not require an 'address dhcpv6' CLI option as with other interfaces
@@ -98,54 +101,15 @@ class PPPoEIf(Interface):
super().update(config)
- if 'default_route' not in config or config['default_route'] == 'none':
- return
-
- #
- # Set default routes pointing to pppoe interface
- #
- vrf = ''
- sed_opt = '^ip route'
-
- install_v4 = True
- install_v6 = True
-
# generate proper configuration string when VRFs are in use
+ vrf = ''
if 'vrf' in config:
tmp = config['vrf']
vrf = f'-c "vrf {tmp}"'
- sed_opt = f'vrf {tmp}'
-
- if config['default_route'] == 'auto':
- # only add route if there is no default route present
- tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"')
- for line in tmp.splitlines():
- line = line.lstrip()
- if line.startswith('ip route 0.0.0.0/0'):
- install_v4 = False
- continue
-
- if 'ipv6' in config and line.startswith('ipv6 route ::/0'):
- install_v6 = False
- continue
-
- elif config['default_route'] == 'force':
- # Force means that all static routes are replaced with the ones from this interface
- tmp = self._cmd(f'vtysh -c "show running-config staticd no-header" | sed -n "/{sed_opt}/,/!/p"')
- for line in tmp.splitlines():
- if self.ifname in line:
- # It makes no sense to remove a route with our interface and the later re-add it.
- # This will only make traffic disappear - which is a no-no!
- continue
-
- line = line.lstrip()
- if line.startswith('ip route 0.0.0.0/0'):
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"')
-
- if 'ipv6' in config and line.startswith('ipv6 route ::/0'):
- self._cmd(f'vtysh -c "conf t" {vrf} -c "no {line}"')
-
- if install_v4:
- self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210"')
- if install_v6 and 'ipv6' in config:
- self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210"')
+
+ if 'no_default_route' not in config:
+ # Set default route(s) pointing to PPPoE interface
+ distance = config['default_route_distance']
+ self._cmd(f'vtysh -c "conf t" {vrf} -c "ip route 0.0.0.0/0 {self.ifname} tag 210 {distance}"')
+ if 'ipv6' in config:
+ self._cmd(f'vtysh -c "conf t" {vrf} -c "ipv6 route ::/0 {self.ifname} tag 210 {distance}"')
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index f8a6ae986..518bda880 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2022 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
@@ -37,10 +37,46 @@ PROCESS_NAME = 'openvpn'
base_path = ['interfaces', 'openvpn']
-cert_data = 'MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIwWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIxMDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu+JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3LftzngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93+dm/LDnp7C0='
-key_data = 'MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww'
-dh_data = 'MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFOFxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZUHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS67q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg=='
-ovpn_key_data = '443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa431d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e7042a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e65210b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a'
+cert_data = """
+MIICFDCCAbugAwIBAgIUfMbIsB/ozMXijYgUYG80T1ry+mcwCgYIKoZIzj0EAwIw
+WTELMAkGA1UEBhMCR0IxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNv
+bWUtQ2l0eTENMAsGA1UECgwEVnlPUzESMBAGA1UEAwwJVnlPUyBUZXN0MB4XDTIx
+MDcyMDEyNDUxMloXDTI2MDcxOTEyNDUxMlowWTELMAkGA1UEBhMCR0IxEzARBgNV
+BAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCVNvbWUtQ2l0eTENMAsGA1UECgwEVnlP
+UzESMBAGA1UEAwwJVnlPUyBUZXN0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+01HrLcNttqq4/PtoMua8rMWEkOdBu7vP94xzDO7A8C92ls1v86eePy4QllKCzIw3
+QxBIoCuH2peGRfWgPRdFsKNhMF8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMB0GA1UdDgQWBBSu
++JnU5ZC4mkuEpqg2+Mk4K79oeDAKBggqhkjOPQQDAgNHADBEAiBEFdzQ/Bc3Lftz
+ngrY605UhA6UprHhAogKgROv7iR4QgIgEFUxTtW3xXJcnUPWhhUFhyZoqfn8dE93
++dm/LDnp7C0=
+"""
+
+key_data = """
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgPLpD0Ohhoq0g4nhx
+2KMIuze7ucKUt/lBEB2wc03IxXyhRANCAATTUestw222qrj8+2gy5rysxYSQ50G7
+u8/3jHMM7sDwL3aWzW/zp54/LhCWUoLMjDdDEEigK4fal4ZF9aA9F0Ww
+"""
+
+dh_data = """
+MIIBCAKCAQEApzGAPcQlLJiOyfGZgl1qxNgufXkdpjG7lMaOrO4TGr1giFe3jIFO
+FxJNC/G9Dn+KSukaWssVVR+Jwr/JesZFPawihS03wC7cZsccykNRIjiteqJDwYJZ
+UHieOxyCuCeY4pqOUCl1uswRGjLvIFtwynpnXKKuz2YtjNifma90PEgv/vVWKix+
+Q0TAbdbzJzO5xp8UVn9DuYfSr10k3LbDqDM7w5ezHZxFk24S5pN/yoOpdbxB8TS6
+7q3IYXxR3F+RseKu4J3AvkxXSP1j7COXddPpLnvbJT/SW8NrjuC/n0eKGvmeyqNv
+108Y89jnT79MxMMRQk66iwlsd1m4pa/OYwIBAg==
+"""
+
+ovpn_key_data = """
+443f2a710ac411c36894b2531e62c4550b079b8f3f08997f4be57c64abfdaaa4
+31d2396b01ecec3a2c0618959e8186d99f489742d25673ffb3268841ebb2e704
+2a2daabe584e79d51d2b1d7409bf8840f7e42efa3e660a521719b04ee88b9043
+e6315ae12da7c9abd55f67eeed71a9ee8c6e163b5d2661fc332cf90cb45658b4
+adf892f79537d37d3a3d90da283ce885adf325ffd2b5be92067cdf0345c7712c
+9d36b642c170351b6d9ce9f6230c7a2617b0c181121bce7d5373404fb68e6521
+0b36e6d40ef2769cf8990503859f6f2db3c85ba74420430a6250d6a74ca51ece
+4b85124bfdfec0c8a530cefa7350378d81a4539f74bed832a902ae4798142e4a
+"""
remote_port = '1194'
protocol = 'udp'
@@ -59,20 +95,28 @@ def get_vrf(interface):
return tmp
class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
- self.cli_set(['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
- self.cli_set(['vrf', 'name', vrf_name, 'table', '12345'])
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
- self.cli_set(['pki', 'ca', 'ovpn_test', 'certificate', cert_data])
- self.cli_set(['pki', 'certificate', 'ovpn_test', 'certificate', cert_data])
- self.cli_set(['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data])
- self.cli_set(['pki', 'dh', 'ovpn_test', 'parameters', dh_data])
- self.cli_set(['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data])
+ cls.cli_set(cls, ['interfaces', 'dummy', dummy_if, 'address', '192.0.2.1/32'])
+ cls.cli_set(cls, ['vrf', 'name', vrf_name, 'table', '12345'])
+
+ cls.cli_set(cls, ['pki', 'ca', 'ovpn_test', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'certificate', cert_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'certificate', 'ovpn_test', 'private', 'key', key_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'dh', 'ovpn_test', 'parameters', dh_data.replace('\n','')])
+ cls.cli_set(cls, ['pki', 'openvpn', 'shared-secret', 'ovpn_test', 'key', ovpn_key_data.replace('\n','')])
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cli_delete(cls, ['interfaces', 'dummy', dummy_if])
+ cls.cli_delete(cls, ['vrf'])
+
+ super(cls, cls).tearDownClass()
def tearDown(self):
self.cli_delete(base_path)
- self.cli_delete(['interfaces', 'dummy', dummy_if])
- self.cli_delete(['vrf'])
self.cli_commit()
def test_openvpn_client_verify(self):
@@ -532,6 +576,46 @@ class TestInterfacesOpenVPN(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
+ def test_openvpn_options(self):
+ # Ensure OpenVPN process restart on openvpn-option CLI node change
+
+ interface = 'vtun5001'
+ path = base_path + [interface]
+
+ self.cli_set(path + ['mode', 'site-to-site'])
+ self.cli_set(path + ['local-address', '10.0.0.2'])
+ self.cli_set(path + ['remote-address', '192.168.0.3'])
+ self.cli_set(path + ['shared-secret-key', 'ovpn_test'])
+
+ self.cli_commit()
+
+ # Now verify the OpenVPN "raw" option passing. Once an openvpn-option is
+ # added, modified or deleted from the CLI, OpenVPN daemon must be restarted
+ cur_pid = process_named_running('openvpn')
+ self.cli_set(path + ['openvpn-option', '--persist-tun'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_set(path + ['openvpn-option', '--persist-key'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
+ self.cli_delete(path + ['openvpn-option'])
+ self.cli_commit()
+
+ # PID must be different as OpenVPN Must be restarted
+ new_pid = process_named_running('openvpn')
+ self.assertNotEqual(cur_pid, new_pid)
+ cur_pid = new_pid
+
def test_openvpn_site2site_interfaces_tun(self):
# Create two OpenVPN site-to-site interfaces
diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 4f1e1ee99..16f6e542b 100755
--- a/smoketest/scripts/cli/test_interfaces_pppoe.py
+++ b/smoketest/scripts/cli/test_interfaces_pppoe.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019-2021 VyOS maintainers and contributors
+# Copyright (C) 2019-2022 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
@@ -60,7 +60,6 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + [interface, 'authentication', 'user', user])
self.cli_set(base_path + [interface, 'authentication', 'password', passwd])
- self.cli_set(base_path + [interface, 'default-route', 'auto'])
self.cli_set(base_path + [interface, 'mtu', mtu])
self.cli_set(base_path + [interface, 'no-peer-dns'])
@@ -136,7 +135,7 @@ class PPPoEInterfaceTest(VyOSUnitTestSHIM.TestCase):
for interface in self._interfaces:
self.cli_set(base_path + [interface, 'authentication', 'user', 'vyos'])
self.cli_set(base_path + [interface, 'authentication', 'password', 'vyos'])
- self.cli_set(base_path + [interface, 'default-route', 'none'])
+ self.cli_set(base_path + [interface, 'no-default-route'])
self.cli_set(base_path + [interface, 'no-peer-dns'])
self.cli_set(base_path + [interface, 'source-interface', self._source_interface])
self.cli_set(base_path + [interface, 'ipv6', 'address', 'autoconf'])
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 2e1b8d431..1f8e4c307 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -59,36 +59,44 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- tmp = cmd('sudo nft -j list table nat')
+ tmp = cmd('sudo nft -j list chain ip nat POSTROUTING')
data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
for idx in range(0, len(data_json)):
- rule = str(rules[idx])
data = data_json[idx]
- network = f'192.168.{rule}.0/24'
-
- self.assertEqual(data['chain'], 'POSTROUTING')
- self.assertEqual(data['comment'], f'SRC-NAT-{rule}')
- self.assertEqual(data['family'], 'ip')
- self.assertEqual(data['table'], 'nat')
+ if idx == 0:
+ self.assertEqual(data['chain'], 'POSTROUTING')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
- iface = dict_search('match.right', data['expr'][0])
- direction = dict_search('match.left.payload.field', data['expr'][1])
- address = dict_search('match.right.prefix.addr', data['expr'][1])
- mask = dict_search('match.right.prefix.len', data['expr'][1])
-
- if int(rule) < 200:
- self.assertEqual(direction, 'saddr')
- self.assertEqual(iface, outbound_iface_100)
- # check for masquerade keyword
- self.assertIn('masquerade', data['expr'][3])
+ jump_target = dict_search('jump.target', data['expr'][1])
+ self.assertEqual(jump_target,'VYOS_PRE_SNAT_HOOK')
else:
- self.assertEqual(direction, 'daddr')
- self.assertEqual(iface, outbound_iface_200)
- # check for return keyword due to 'exclude'
- self.assertIn('return', data['expr'][3])
-
- self.assertEqual(f'{address}/{mask}', network)
+ rule = str(rules[idx - 1])
+ network = f'192.168.{rule}.0/24'
+
+ self.assertEqual(data['chain'], 'POSTROUTING')
+ self.assertEqual(data['comment'], f'SRC-NAT-{rule}')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ address = dict_search('match.right.prefix.addr', data['expr'][1])
+ mask = dict_search('match.right.prefix.len', data['expr'][1])
+
+ if int(rule) < 200:
+ self.assertEqual(direction, 'saddr')
+ self.assertEqual(iface, outbound_iface_100)
+ # check for masquerade keyword
+ self.assertIn('masquerade', data['expr'][3])
+ else:
+ self.assertEqual(direction, 'daddr')
+ self.assertEqual(iface, outbound_iface_200)
+ # check for return keyword due to 'exclude'
+ self.assertIn('return', data['expr'][3])
+
+ self.assertEqual(f'{address}/{mask}', network)
def test_dnat(self):
rules = ['100', '110', '120', '130', '200', '210', '220', '230']
@@ -111,33 +119,42 @@ class TestNAT(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
- tmp = cmd('sudo nft -j list table nat')
+ tmp = cmd('sudo nft -j list chain ip nat PREROUTING')
data_json = jmespath.search('nftables[?rule].rule[?chain]', json.loads(tmp))
for idx in range(0, len(data_json)):
- rule = str(rules[idx])
data = data_json[idx]
- port = int(f'10{rule}')
-
- self.assertEqual(data['chain'], 'PREROUTING')
- self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}')
- self.assertEqual(data['family'], 'ip')
- self.assertEqual(data['table'], 'nat')
-
- iface = dict_search('match.right', data['expr'][0])
- direction = dict_search('match.left.payload.field', data['expr'][1])
- protocol = dict_search('match.left.payload.protocol', data['expr'][1])
- dnat_addr = dict_search('dnat.addr', data['expr'][3])
- dnat_port = dict_search('dnat.port', data['expr'][3])
-
- self.assertEqual(direction, 'sport')
- self.assertEqual(dnat_addr, '192.0.2.1')
- self.assertEqual(dnat_port, port)
- if int(rule) < 200:
- self.assertEqual(iface, inbound_iface_100)
- self.assertEqual(protocol, inbound_proto_100)
+ if idx == 0:
+ self.assertEqual(data['chain'], 'PREROUTING')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ jump_target = dict_search('jump.target', data['expr'][1])
+ self.assertEqual(jump_target,'VYOS_PRE_DNAT_HOOK')
else:
- self.assertEqual(iface, inbound_iface_200)
+
+ rule = str(rules[idx - 1])
+ port = int(f'10{rule}')
+
+ self.assertEqual(data['chain'], 'PREROUTING')
+ self.assertEqual(data['comment'].split()[0], f'DST-NAT-{rule}')
+ self.assertEqual(data['family'], 'ip')
+ self.assertEqual(data['table'], 'nat')
+
+ iface = dict_search('match.right', data['expr'][0])
+ direction = dict_search('match.left.payload.field', data['expr'][1])
+ protocol = dict_search('match.left.payload.protocol', data['expr'][1])
+ dnat_addr = dict_search('dnat.addr', data['expr'][3])
+ dnat_port = dict_search('dnat.port', data['expr'][3])
+
+ self.assertEqual(direction, 'sport')
+ self.assertEqual(dnat_addr, '192.0.2.1')
+ self.assertEqual(dnat_port, port)
+ if int(rule) < 200:
+ self.assertEqual(iface, inbound_iface_100)
+ self.assertEqual(protocol, inbound_proto_100)
+ else:
+ self.assertEqual(iface, inbound_iface_200)
def test_snat_required_translation_address(self):
# T2813: Ensure translation address is specified
diff --git a/smoketest/scripts/cli/test_protocols_bgp.py b/smoketest/scripts/cli/test_protocols_bgp.py
index f1db5350a..5fa4c2d69 100755
--- a/smoketest/scripts/cli/test_protocols_bgp.py
+++ b/smoketest/scripts/cli/test_protocols_bgp.py
@@ -882,5 +882,29 @@ class TestProtocolsBGP(VyOSUnitTestSHIM.TestCase):
self.assertIn(f' rt vpn import {rt_import}', afi_config)
self.assertIn(f' exit-address-family', afi_config)
+ def test_bgp_14_remote_as_peer_group_override(self):
+ # Peer-group member cannot override remote-as of peer-group
+ remote_asn = str(int(ASN) + 150)
+ neighbor = '192.0.2.1'
+ peer_group = 'bar'
+
+ self.cli_set(base_path + ['local-as', ASN])
+ self.cli_set(base_path + ['neighbor', neighbor, 'remote-as', remote_asn])
+ self.cli_set(base_path + ['neighbor', neighbor, 'peer-group', peer_group])
+ self.cli_set(base_path + ['peer-group', peer_group, 'remote-as', remote_asn])
+
+ # Peer-group member cannot override remote-as of peer-group
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ self.cli_delete(base_path + ['neighbor', neighbor, 'remote-as'])
+
+ self.cli_commit()
+
+ frrconfig = self.getFRRconfig(f'router bgp {ASN}')
+ self.assertIn(f'router bgp {ASN}', frrconfig)
+ self.assertIn(f' neighbor {neighbor} peer-group {peer_group}', frrconfig)
+ self.assertIn(f' neighbor {peer_group} peer-group', frrconfig)
+ self.assertIn(f' neighbor {peer_group} remote-as {remote_asn}', frrconfig)
+
if __name__ == '__main__':
- unittest.main(verbosity=2, failfast=True)
+ unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index a9be093c2..6c1a01dab 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -32,7 +32,7 @@ from shutil import rmtree
from vyos.config import Config
from vyos.configdict import get_interface_dict
-from vyos.configdict import leaf_node_changed
+from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
from vyos.configverify import verify_bridge_delete
from vyos.configverify import verify_mirror_redirect
@@ -90,8 +90,8 @@ def get_config(config=None):
if 'deleted' not in openvpn:
openvpn['pki'] = tmp_pki
- tmp = leaf_node_changed(conf, ['openvpn-option'])
- if tmp: openvpn['restart_required'] = ''
+ if is_node_changed(conf, ['openvpn-option']):
+ openvpn.update({'restart_required': {}})
# We have to get the dict using 'get_config_dict' instead of 'get_interface_dict'
# as 'get_interface_dict' merges the defaults in, so we can not check for defaults in there.
diff --git a/src/conf_mode/interfaces-pppoe.py b/src/conf_mode/interfaces-pppoe.py
index bfb1fadd5..279369a32 100755
--- a/src/conf_mode/interfaces-pppoe.py
+++ b/src/conf_mode/interfaces-pppoe.py
@@ -22,7 +22,9 @@ from netifaces import interfaces
from vyos.config import Config
from vyos.configdict import get_interface_dict
+from vyos.configdict import is_node_changed
from vyos.configdict import leaf_node_changed
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_authentication
from vyos.configverify import verify_source_interface
from vyos.configverify import verify_interface_exists
@@ -52,28 +54,12 @@ def get_config(config=None):
# We should only terminate the PPPoE session if critical parameters change.
# All parameters that can be changed on-the-fly (like interface description)
# should not lead to a reconnect!
- tmp = leaf_node_changed(conf, ['access-concentrator'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['connect-on-demand'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['service-name'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['source-interface'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['vrf'])
- # leaf_node_changed() returns a list, as VRF is a non-multi node, there
- # will be only one list element
- if tmp: pppoe.update({'vrf_old': tmp[0]})
-
- tmp = leaf_node_changed(conf, ['authentication', 'user'])
- if tmp: pppoe.update({'shutdown_required': {}})
-
- tmp = leaf_node_changed(conf, ['authentication', 'password'])
- if tmp: pppoe.update({'shutdown_required': {}})
+ for options in ['access-concentrator', 'connect-on-demand', 'service-name',
+ 'source-interface', 'vrf', 'no-default-route', 'authentication']:
+ if is_node_changed(conf, options):
+ pppoe.update({'shutdown_required': {}})
+ # bail out early - no need to further process other nodes
+ break
return pppoe
@@ -120,7 +106,7 @@ def apply(pppoe):
return None
# reconnect should only be necessary when certain config options change,
- # like ACS name, authentication, no-peer-dns, source-interface
+ # like ACS name, authentication ... (see get_config() for details)
if ((not is_systemd_service_running(f'ppp@{ifname}.service')) or
'shutdown_required' in pppoe):
@@ -130,6 +116,9 @@ def apply(pppoe):
p.remove()
call(f'systemctl restart ppp@{ifname}.service')
+ # When interface comes "live" a hook is called:
+ # /etc/ppp/ip-up.d/99-vyos-pppoe-callback
+ # which triggers PPPoEIf.update()
else:
if os.path.isdir(f'/sys/class/net/{ifname}'):
p = PPPoEIf(ifname)
diff --git a/src/conf_mode/protocols_bgp.py b/src/conf_mode/protocols_bgp.py
index 8d9d3e99a..a9173ab87 100755
--- a/src/conf_mode/protocols_bgp.py
+++ b/src/conf_mode/protocols_bgp.py
@@ -164,6 +164,12 @@ def verify(bgp):
if not verify_remote_as(peer_config, bgp):
raise ConfigError(f'Neighbor "{peer}" remote-as must be set!')
+ # Peer-group member cannot override remote-as of peer-group
+ if 'peer_group' in peer_config:
+ peer_group = peer_config['peer_group']
+ if 'remote_as' in peer_config and 'remote_as' in bgp['peer_group'][peer_group]:
+ raise ConfigError(f'Peer-group member "{peer}" cannot override remote-as of peer-group "{peer_group}"!')
+
# Only checks for ipv4 and ipv6 neighbors
# Check if neighbor address is assigned as system interface address
vrf = None
diff --git a/src/conf_mode/protocols_static.py b/src/conf_mode/protocols_static.py
index 87432bc1c..58e202928 100755
--- a/src/conf_mode/protocols_static.py
+++ b/src/conf_mode/protocols_static.py
@@ -22,6 +22,7 @@ from sys import argv
from vyos.config import Config
from vyos.configdict import dict_merge
from vyos.configdict import get_dhcp_interfaces
+from vyos.configdict import get_pppoe_interfaces
from vyos.configverify import verify_common_route_maps
from vyos.configverify import verify_vrf
from vyos.template import render_to_string
@@ -59,7 +60,9 @@ def get_config(config=None):
# T3680 - get a list of all interfaces currently configured to use DHCP
tmp = get_dhcp_interfaces(conf, vrf)
- if tmp: static['dhcp'] = tmp
+ if tmp: static.update({'dhcp' : tmp})
+ tmp = get_pppoe_interfaces(conf, vrf)
+ if tmp: static.update({'pppoe' : tmp})
return static
diff --git a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
index bb918a468..78ca09010 100755
--- a/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
+++ b/src/etc/ppp/ip-up.d/99-vyos-pppoe-callback
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2021 VyOS maintainers and contributors
+# Copyright (C) 2021-2022 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
@@ -28,7 +28,8 @@ from syslog import openlog
from syslog import LOG_PID
from syslog import LOG_INFO
-from vyos.configquery import ConfigTreeQuery
+from vyos.configquery import Config
+from vyos.configdict import get_interface_dict
from vyos.ifconfig import PPPoEIf
from vyos.util import read_file
@@ -50,10 +51,9 @@ dialer_pid = read_file(f'/var/run/{interface}.pid')
openlog(ident=f'pppd[{dialer_pid}]', facility=LOG_INFO)
syslog('executing ' + argv[0])
-conf = ConfigTreeQuery()
-pppoe = conf.get_config_dict(['interfaces', 'pppoe', argv[6]],
- get_first_key=True, key_mangling=('-', '_'))
-pppoe['ifname'] = argv[6]
+conf = Config()
+pppoe = get_interface_dict(conf, ['interfaces', 'pppoe'], argv[6])
+# Update the config
p = PPPoEIf(pppoe['ifname'])
p.update(pppoe)
diff --git a/src/migration-scripts/interfaces/25-to-26 b/src/migration-scripts/interfaces/25-to-26
new file mode 100755
index 000000000..a8936235e
--- /dev/null
+++ b/src/migration-scripts/interfaces/25-to-26
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2022 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/>.
+
+# T4384: pppoe: replace default-route CLI option with common CLI nodes already
+# present for DHCP
+
+from sys import argv
+
+from vyos.ethtool import Ethtool
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+base = ['interfaces', 'pppoe']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+ exit(0)
+
+for ifname in config.list_nodes(base):
+ tmp_config = base + [ifname, 'default-route']
+ if config.exists(tmp_config):
+ # Retrieve current config value
+ value = config.return_value(tmp_config)
+ # Delete old Config node
+ config.delete(tmp_config)
+ if value == 'none':
+ config.set(base + [ifname, 'no-default-route'])
+
+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)