summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/bcast-relay/udp-broadcast-relay.tmpl2
-rw-r--r--debian/control3
-rw-r--r--op-mode-definitions/monitor-ndp.xml44
-rw-r--r--op-mode-definitions/show-interfaces-ethernet.xml1
-rw-r--r--op-mode-definitions/show-interfaces-pppoe.xml14
-rw-r--r--op-mode-definitions/show-interfaces-wirelessmodem.xml14
-rw-r--r--op-mode-definitions/wireguard.xml14
-rw-r--r--python/vyos/config.py32
-rw-r--r--python/vyos/configdiff.py249
-rw-r--r--python/vyos/ifconfig/interface.py6
-rwxr-xr-xscripts/build-command-op-templates2
-rwxr-xr-xsrc/conf_mode/bcast_relay.py135
-rwxr-xr-xsrc/conf_mode/interfaces-pseudo-ethernet.py11
13 files changed, 410 insertions, 117 deletions
diff --git a/data/templates/bcast-relay/udp-broadcast-relay.tmpl b/data/templates/bcast-relay/udp-broadcast-relay.tmpl
index 3d8c3fe94..d0c7d8bf9 100644
--- a/data/templates/bcast-relay/udp-broadcast-relay.tmpl
+++ b/data/templates/bcast-relay/udp-broadcast-relay.tmpl
@@ -4,4 +4,4 @@
{%- if description %}
# Comment: {{ description }}
{% endif %}
-DAEMON_ARGS="{% if address %}-s {{ address }} {% endif %}{{ id }} {{ port }} {{ interfaces | join(' ') }}"
+DAEMON_ARGS="{{ '-s ' + address if address is defined }} {{ instance }} {{ port }} {{ interface | join(' ') }}"
diff --git a/debian/control b/debian/control
index 6746fe647..3a441b47b 100644
--- a/debian/control
+++ b/debian/control
@@ -105,7 +105,8 @@ Depends: python3,
nftables (>= 0.9.3),
conntrack,
libatomic1,
- fastnetmon
+ fastnetmon,
+ libndp-tools
Description: VyOS configuration scripts and data
VyOS configuration scripts, interface definitions, and everything
diff --git a/op-mode-definitions/monitor-ndp.xml b/op-mode-definitions/monitor-ndp.xml
new file mode 100644
index 000000000..e25eccf3a
--- /dev/null
+++ b/op-mode-definitions/monitor-ndp.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="monitor">
+ <children>
+ <node name="ndp">
+ <properties>
+ <help>Monitors the NDP information received by the router through the device</help>
+ </properties>
+ <command>sudo ndptool monitor</command>
+ <children>
+ <tagNode name="interface">
+ <command>sudo ndptool monitor --ifname=$4</command>
+ <properties>
+ <help>Monitor ndp protocol on specified interface</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <tagNode name="type">
+ <command>sudo ndptool monitor --ifname=$4 --msg-type=$6</command>
+ <properties>
+ <help>Monitor ndp protocol on specified interface</help>
+ <completionHelp>
+ <list>rs ra ns na</list>
+ </completionHelp>
+ </properties>
+ </tagNode>
+ </children>
+ </tagNode>
+ <tagNode name="type">
+ <command>sudo ndptool monitor --msg-type=$4</command>
+ <properties>
+ <help>Monitor ndp protocol on specified interface</help>
+ <completionHelp>
+ <list>rs ra ns na</list>
+ </completionHelp>
+ </properties>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/op-mode-definitions/show-interfaces-ethernet.xml b/op-mode-definitions/show-interfaces-ethernet.xml
index 80f07c2bd..bdcfa55f1 100644
--- a/op-mode-definitions/show-interfaces-ethernet.xml
+++ b/op-mode-definitions/show-interfaces-ethernet.xml
@@ -74,6 +74,7 @@
<properties>
<help>Show ethernet interface information</help>
</properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=ethernet --action=show-brief</command>
<children>
<leafNode name="detail">
<properties>
diff --git a/op-mode-definitions/show-interfaces-pppoe.xml b/op-mode-definitions/show-interfaces-pppoe.xml
index 01acd4fc6..4263a2f0a 100644
--- a/op-mode-definitions/show-interfaces-pppoe.xml
+++ b/op-mode-definitions/show-interfaces-pppoe.xml
@@ -30,6 +30,20 @@
</leafNode>
</children>
</tagNode>
+ <node name="pppoe">
+ <properties>
+ <help>Show PPPoE interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pppoe --action=show-brief</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed PPPoE interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=pppoe --action=show</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/show-interfaces-wirelessmodem.xml b/op-mode-definitions/show-interfaces-wirelessmodem.xml
index 1f710b3dc..46f872c85 100644
--- a/op-mode-definitions/show-interfaces-wirelessmodem.xml
+++ b/op-mode-definitions/show-interfaces-wirelessmodem.xml
@@ -30,6 +30,20 @@
</leafNode>
</children>
</tagNode>
+ <node name="wirelessmodem">
+ <properties>
+ <help>Show Wireless Modem (WWAN) interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show-brief</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed Wireless Modem (WWAN( interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wirelessmodem --action=show</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/op-mode-definitions/wireguard.xml b/op-mode-definitions/wireguard.xml
index 1795fb820..a7bfa36a3 100644
--- a/op-mode-definitions/wireguard.xml
+++ b/op-mode-definitions/wireguard.xml
@@ -96,6 +96,20 @@
<!-- more commands upon request -->
</children>
</tagNode>
+ <node name="wireguard">
+ <properties>
+ <help>Show wireguard interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show-brief</command>
+ <children>
+ <leafNode name="detail">
+ <properties>
+ <help>Show detailed wireguard interface information</help>
+ </properties>
+ <command>${vyos_op_scripts_dir}/show_interfaces.py --intf-type=wireguard --action=show</command>
+ </leafNode>
+ </children>
+ </node>
</children>
</node>
</children>
diff --git a/python/vyos/config.py b/python/vyos/config.py
index 780b48a7b..5d58316e7 100644
--- a/python/vyos/config.py
+++ b/python/vyos/config.py
@@ -67,6 +67,7 @@ import os
import re
import json
import subprocess
+from copy import deepcopy
import vyos.util
import vyos.configtree
@@ -91,6 +92,8 @@ class Config(object):
def __init__(self, session_env=None):
self._cli_shell_api = "/bin/cli-shell-api"
self._level = []
+ self._dict_cache = {}
+
if session_env:
self.__session_env = session_env
else:
@@ -287,6 +290,24 @@ class Config(object):
self.__session_env = save_env
return(default)
+ def get_cached_dict(self, effective=False):
+ cached = self._dict_cache.get(effective, {})
+ if cached:
+ config_dict = cached
+ else:
+ config_dict = {}
+
+ if effective:
+ if self._running_config:
+ config_dict = json.loads((self._running_config).to_json())
+ else:
+ if self._session_config:
+ config_dict = json.loads((self._session_config).to_json())
+
+ self._dict_cache[effective] = config_dict
+
+ return config_dict
+
def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False):
"""
Args:
@@ -297,14 +318,7 @@ class Config(object):
Returns: a dict representation of the config under path
"""
- config_dict = {}
-
- if effective:
- if self._running_config:
- config_dict = json.loads((self._running_config).to_json())
- else:
- if self._session_config:
- config_dict = json.loads((self._session_config).to_json())
+ config_dict = self.get_cached_dict(effective)
config_dict = vyos.util.get_sub_dict(config_dict, self._make_path(path), get_first_key)
@@ -316,6 +330,8 @@ class Config(object):
raise ValueError("key_mangling must be a tuple of two strings")
else:
config_dict = vyos.util.mangle_dict_keys(config_dict, key_mangling[0], key_mangling[1])
+ else:
+ config_dict = deepcopy(config_dict)
return config_dict
diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py
new file mode 100644
index 000000000..b79893507
--- /dev/null
+++ b/python/vyos/configdiff.py
@@ -0,0 +1,249 @@
+# Copyright 2020 VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+from enum import IntFlag, auto
+
+from vyos.config import Config
+from vyos.configdict import dict_merge
+from vyos.util import get_sub_dict, mangle_dict_keys
+from vyos.xml import defaults
+
+class ConfigDiffError(Exception):
+ """
+ Raised on config dict access errors, for example, calling get_value on
+ a non-leaf node.
+ """
+ pass
+
+def enum_to_key(e):
+ return e.name.lower()
+
+class Diff(IntFlag):
+ MERGE = auto()
+ DELETE = auto()
+ ADD = auto()
+ STABLE = auto()
+
+requires_effective = [enum_to_key(Diff.DELETE)]
+target_defaults = [enum_to_key(Diff.MERGE)]
+
+def _key_sets_from_dicts(session_dict, effective_dict):
+ session_keys = list(session_dict)
+ effective_keys = list(effective_dict)
+
+ ret = {}
+ stable_keys = [k for k in session_keys if k in effective_keys]
+
+ ret[enum_to_key(Diff.MERGE)] = session_keys
+ ret[enum_to_key(Diff.DELETE)] = [k for k in effective_keys if k not in stable_keys]
+ ret[enum_to_key(Diff.ADD)] = [k for k in session_keys if k not in stable_keys]
+ ret[enum_to_key(Diff.STABLE)] = stable_keys
+
+ return ret
+
+def _dict_from_key_set(key_set, d):
+ # This will always be applied to a key_set obtained from a get_sub_dict,
+ # hence there is no possibility of KeyError, as get_sub_dict guarantees
+ # a return type of dict
+ ret = {k: d[k] for k in key_set}
+
+ return ret
+
+def get_config_diff(config, key_mangling=None):
+ """
+ Check type and return ConfigDiff instance.
+ """
+ if not config or not isinstance(config, Config):
+ raise TypeError("argument must me a Config instance")
+ if key_mangling and not (isinstance(key_mangling, tuple) and \
+ (len(key_mangling) == 2) and \
+ isinstance(key_mangling[0], str) and \
+ isinstance(key_mangling[1], str)):
+ raise ValueError("key_mangling must be a tuple of two strings")
+
+ return ConfigDiff(config, key_mangling)
+
+class ConfigDiff(object):
+ """
+ The class of config changes as represented by comparison between the
+ session config dict and the effective config dict.
+ """
+ def __init__(self, config, key_mangling=None):
+ self._level = config.get_level()
+ self._session_config_dict = config.get_cached_dict()
+ self._effective_config_dict = config.get_cached_dict(effective=True)
+ self._key_mangling = key_mangling
+
+ # mirrored from Config; allow path arguments relative to level
+ def _make_path(self, path):
+ if isinstance(path, str):
+ path = path.split()
+ elif isinstance(path, list):
+ pass
+ else:
+ raise TypeError("Path must be a whitespace-separated string or a list")
+
+ ret = self._level + path
+ return ret
+
+ def set_level(self, path):
+ """
+ Set the *edit level*, that is, a relative config dict path.
+ Once set, all operations will be relative to this path,
+ for example, after ``set_level("system")``, calling
+ ``get_value("name-server")`` is equivalent to calling
+ ``get_value("system name-server")`` without ``set_level``.
+
+ Args:
+ path (str|list): relative config path
+ """
+ if isinstance(path, str):
+ if path:
+ self._level = path.split()
+ else:
+ self._level = []
+ elif isinstance(path, list):
+ self._level = path.copy()
+ else:
+ raise TypeError("Level path must be either a whitespace-separated string or a list")
+
+ def get_level(self):
+ """
+ Gets the current edit level.
+
+ Returns:
+ str: current edit level
+ """
+ ret = self._level.copy()
+ return ret
+
+ def _mangle_dict_keys(self, config_dict):
+ config_dict = mangle_dict_keys(config_dict, self._key_mangling[0],
+ self._key_mangling[1])
+ return config_dict
+
+ def get_child_nodes_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
+ """
+ Args:
+ path (str|list): config path
+ expand_nodes=Diff(0): bit mask of enum indicating for which nodes
+ to provide full dict; for example, Diff.MERGE
+ will expand dict['merge'] into dict under
+ value
+ no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default
+ values to ret['merge']
+
+ Returns: dict of lists, representing differences between session
+ and effective config, under path
+ dict['merge'] = session config values
+ dict['delete'] = effective config values, not in session
+ dict['add'] = session config values, not in effective
+ dict['stable'] = config values in both session and effective
+ """
+ session_dict = get_sub_dict(self._session_config_dict,
+ self._make_path(path), get_first_key=True)
+ effective_dict = get_sub_dict(self._effective_config_dict,
+ self._make_path(path), get_first_key=True)
+
+ ret = _key_sets_from_dicts(session_dict, effective_dict)
+
+ if not expand_nodes:
+ return ret
+
+ for e in Diff:
+ if expand_nodes & e:
+ k = enum_to_key(e)
+ if k in requires_effective:
+ ret[k] = _dict_from_key_set(ret[k], effective_dict)
+ else:
+ ret[k] = _dict_from_key_set(ret[k], session_dict)
+
+ if self._key_mangling:
+ ret[k] = self._mangle_dict_keys(ret[k])
+
+ if k in target_defaults and not no_defaults:
+ default_values = defaults(self._make_path(path))
+ ret[k] = dict_merge(default_values, ret[k])
+
+ return ret
+
+ def get_node_diff(self, path=[], expand_nodes=Diff(0), no_defaults=False):
+ """
+ Args:
+ path (str|list): config path
+ expand_nodes=Diff(0): bit mask of enum indicating for which nodes
+ to provide full dict; for example, Diff.MERGE
+ will expand dict['merge'] into dict under
+ value
+ no_detaults=False: if expand_nodes & Diff.MERGE, do not merge default
+ values to ret['merge']
+
+ Returns: dict of lists, representing differences between session
+ and effective config, at path
+ dict['merge'] = session config values
+ dict['delete'] = effective config values, not in session
+ dict['add'] = session config values, not in effective
+ dict['stable'] = config values in both session and effective
+ """
+ session_dict = get_sub_dict(self._session_config_dict, self._make_path(path))
+ effective_dict = get_sub_dict(self._effective_config_dict, self._make_path(path))
+
+ ret = _key_sets_from_dicts(session_dict, effective_dict)
+
+ if not expand_nodes:
+ return ret
+
+ for e in Diff:
+ if expand_nodes & e:
+ k = enum_to_key(e)
+ if k in requires_effective:
+ ret[k] = _dict_from_key_set(ret[k], effective_dict)
+ else:
+ ret[k] = _dict_from_key_set(ret[k], session_dict)
+
+ if self._key_mangling:
+ ret[k] = self._mangle_dict_keys(ret[k])
+
+ if k in target_defaults and not no_defaults:
+ default_values = defaults(self._make_path(path))
+ ret[k] = dict_merge(default_values, ret[k])
+
+ return ret
+
+ def get_value_diff(self, path=[]):
+ """
+ Args:
+ path (str|list): config path
+
+ Returns: (new, old) tuple of values in session config/effective config
+ """
+ # one should properly use is_leaf as check; for the moment we will
+ # deduce from type, which will not catch call on non-leaf node if None
+ new_value_dict = get_sub_dict(self._session_config_dict, self._make_path(path))
+ old_value_dict = get_sub_dict(self._effective_config_dict, self._make_path(path))
+
+ new_value = None
+ old_value = None
+ if new_value_dict:
+ new_value = next(iter(new_value_dict.values()))
+ if old_value_dict:
+ old_value = next(iter(old_value_dict.values()))
+
+ if new_value and isinstance(new_value, dict):
+ raise ConfigDiffError("get_value_changed called on non-leaf node")
+ if old_value and isinstance(old_value, dict):
+ raise ConfigDiffError("get_value_changed called on non-leaf node")
+
+ return new_value, old_value
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 1819ffc82..c73a3bbf8 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -322,7 +322,11 @@ class Interface(Control):
self.set_admin_state('down')
self.set_interface('mac', mac)
-
+
+ # Turn an interface to the 'up' state if it was changed to 'down' by this fucntion
+ if prev_state == 'up':
+ self.set_admin_state('up')
+
def set_vrf(self, vrf=''):
"""
Add/Remove interface from given VRF instance.
diff --git a/scripts/build-command-op-templates b/scripts/build-command-op-templates
index 689d19ece..c60b32a1e 100755
--- a/scripts/build-command-op-templates
+++ b/scripts/build-command-op-templates
@@ -111,7 +111,7 @@ def get_properties(p):
for i in lists:
comp_exprs.append("echo \"{0}\"".format(i.text))
for i in paths:
- comp_exprs.append("/bin/cli-shell-api listActiveNodes {0} | sed -e \"s/'//g\"".format(i.text))
+ comp_exprs.append("/bin/cli-shell-api listActiveNodes {0} | sed -e \"s/'//g\" && echo".format(i.text))
for i in scripts:
comp_exprs.append("{0}".format(i.text))
comp_help = " && ".join(comp_exprs)
diff --git a/src/conf_mode/bcast_relay.py b/src/conf_mode/bcast_relay.py
index 5c7294296..a3e141a00 100755
--- a/src/conf_mode/bcast_relay.py
+++ b/src/conf_mode/bcast_relay.py
@@ -15,151 +15,84 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import fnmatch
+from glob import glob
+from netifaces import interfaces
from sys import exit
-from copy import deepcopy
from vyos.config import Config
-from vyos import ConfigError
from vyos.util import call
from vyos.template import render
-
+from vyos import ConfigError
from vyos import airbag
airbag.enable()
-config_file = r'/etc/default/udp-broadcast-relay'
-
-default_config_data = {
- 'disabled': False,
- 'instances': []
-}
+config_file_base = r'/etc/default/udp-broadcast-relay'
def get_config():
- relay = deepcopy(default_config_data)
conf = Config()
base = ['service', 'broadcast-relay']
- if not conf.exists(base):
- return None
- else:
- conf.set_level(base)
-
- # Service can be disabled by user
- if conf.exists('disable'):
- relay['disabled'] = True
- return relay
-
- # Parse configuration of each individual instance
- if conf.exists('id'):
- for id in conf.list_nodes('id'):
- conf.set_level(base + ['id', id])
- config = {
- 'id': id,
- 'disabled': False,
- 'address': '',
- 'description': '',
- 'interfaces': [],
- 'port': ''
- }
-
- # Check if individual broadcast relay service is disabled
- if conf.exists(['disable']):
- config['disabled'] = True
-
- # Source IP of forwarded packets, if empty original senders address is used
- if conf.exists(['address']):
- config['address'] = conf.return_value(['address'])
-
- # A description for each individual broadcast relay service
- if conf.exists(['description']):
- config['description'] = conf.return_value(['description'])
-
- # UDP port to listen on for broadcast frames
- if conf.exists(['port']):
- config['port'] = conf.return_value(['port'])
-
- # Network interfaces to listen on for broadcast frames to be relayed
- if conf.exists(['interface']):
- config['interfaces'] = conf.return_values(['interface'])
-
- relay['instances'].append(config)
-
+ relay = conf.get_config_dict(base, key_mangling=('-', '_'), get_first_key=True)
return relay
def verify(relay):
- if relay is None:
- return None
-
- if relay['disabled']:
+ if not relay or 'disabled' in relay:
return None
- for r in relay['instances']:
+ for instance, config in relay.get('id', {}).items():
# we don't have to check this instance when it's disabled
- if r['disabled']:
+ if 'disabled' in config:
continue
# we certainly require a UDP port to listen to
- if not r['port']:
- raise ConfigError('UDP broadcast relay "{0}" requires a port number'.format(r['id']))
+ if 'port' not in config:
+ raise ConfigError(f'Port number mandatory for udp broadcast relay "{instance}"')
+ # if only oone interface is given it's a string -> move to list
+ if isinstance(config.get('interface', []), str):
+ config['interface'] = [ config['interface'] ]
# Relaying data without two interface is kinda senseless ...
- if len(r['interfaces']) < 2:
- raise ConfigError('UDP broadcast relay "id {0}" requires at least 2 interfaces'.format(r['id']))
+ if len(config.get('interface', [])) < 2:
+ raise ConfigError('At least two interfaces are required for udp broadcast relay "{instance}"')
- return None
+ for interface in config.get('interface', []):
+ if interface not in interfaces():
+ raise ConfigError('Interface "{interface}" does not exist!')
+ return None
def generate(relay):
- if relay is None:
+ if not relay or 'disabled' in relay:
return None
- config_dir = os.path.dirname(config_file)
- config_filename = os.path.basename(config_file)
- active_configs = []
-
- for config in fnmatch.filter(os.listdir(config_dir), config_filename + '*'):
- # determine prefix length to identify service instance
- prefix_len = len(config_filename)
- active_configs.append(config[prefix_len:])
-
- # sort our list
- active_configs.sort()
+ for config in glob(config_file_base + '*'):
+ os.remove(config)
- # delete old configuration files
- for id in active_configs[:]:
- if os.path.exists(config_file + id):
- os.unlink(config_file + id)
-
- # If the service is disabled, we can bail out here
- if relay['disabled']:
- print('Warning: UDP broadcast relay service will be deactivated because it is disabled')
- return None
-
- for r in relay['instances']:
- # Skip writing instance config when it's disabled
- if r['disabled']:
+ for instance, config in relay.get('id').items():
+ # we don't have to check this instance when it's disabled
+ if 'disabled' in config:
continue
- # configuration filename contains instance id
- file = config_file + str(r['id'])
- render(file, 'bcast-relay/udp-broadcast-relay.tmpl', r)
+ config['instance'] = instance
+ render(config_file_base + instance, 'bcast-relay/udp-broadcast-relay.tmpl', config)
return None
def apply(relay):
# first stop all running services
- call('systemctl stop udp-broadcast-relay@{1..99}.service')
+ call('systemctl stop udp-broadcast-relay@*.service')
- if (relay is None) or relay['disabled']:
+ if not relay or 'disable' in relay:
return None
# start only required service instances
- for r in relay['instances']:
- # Don't start individual instance when it's disabled
- if r['disabled']:
+ for instance, config in relay.get('id').items():
+ # we don't have to check this instance when it's disabled
+ if 'disabled' in config:
continue
- call('systemctl start udp-broadcast-relay@{0}.service'.format(r['id']))
+
+ call(f'systemctl start udp-broadcast-relay@{instance}.service')
return None
diff --git a/src/conf_mode/interfaces-pseudo-ethernet.py b/src/conf_mode/interfaces-pseudo-ethernet.py
index 70710e97c..fb8237bee 100755
--- a/src/conf_mode/interfaces-pseudo-ethernet.py
+++ b/src/conf_mode/interfaces-pseudo-ethernet.py
@@ -36,7 +36,7 @@ default_config_data = {
'ip_arp_cache_tmo': 30,
'ip_proxy_arp_pvlan': 0,
'source_interface': '',
- 'source_interface_changed': False,
+ 'recreating_required': False,
'mode': 'private',
'vif_s': {},
'vif_s_remove': [],
@@ -79,11 +79,14 @@ def get_config():
peth['source_interface'] = conf.return_value(['source-interface'])
tmp = conf.return_effective_value(['source-interface'])
if tmp != peth['source_interface']:
- peth['source_interface_changed'] = True
+ peth['recreating_required'] = True
# MACvlan mode
if conf.exists(['mode']):
peth['mode'] = conf.return_value(['mode'])
+ tmp = conf.return_effective_value(['mode'])
+ if tmp != peth['mode']:
+ peth['recreating_required'] = True
add_to_dict(conf, disabled, peth, 'vif', 'vif')
add_to_dict(conf, disabled, peth, 'vif-s', 'vif_s')
@@ -139,10 +142,10 @@ def apply(peth):
return None
# Check if MACVLAN interface already exists. Parameters like the underlaying
- # source-interface device can not be changed on the fly and the interface
+ # source-interface device or mode can not be changed on the fly and the interface
# needs to be recreated from the bottom.
if peth['intf'] in interfaces():
- if peth['source_interface_changed']:
+ if peth['recreating_required']:
MACVLANIf(peth['intf']).remove()
# MACVLAN interface needs to be created on-block instead of passing a ton