summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--data/templates/wifi/hostapd.conf.tmpl8
-rw-r--r--interface-definitions/interfaces-tunnel.xml.in41
-rw-r--r--interface-definitions/interfaces-wireguard.xml.in1
-rw-r--r--interface-definitions/interfaces-wireless.xml.in6
-rw-r--r--python/vyos/ifconfig/bridge.py12
-rw-r--r--python/vyos/ifconfig/interface.py18
-rw-r--r--python/vyos/ifconfig/tunnel.py194
-rw-r--r--python/vyos/ifconfig/wireguard.py29
-rw-r--r--python/vyos/ifconfig/wireless.py29
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_tunnel.py6
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py16
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py781
-rwxr-xr-xsrc/conf_mode/interfaces-wireguard.py3
-rwxr-xr-xsrc/conf_mode/interfaces-wireless.py1
15 files changed, 282 insertions, 864 deletions
diff --git a/Makefile b/Makefile
index 662b1b1e2..49765db2d 100644
--- a/Makefile
+++ b/Makefile
@@ -76,6 +76,7 @@ interface_definitions: $(BUILD_DIR) $(obj)
rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ip/node.def
rm -f $(TMPL_DIR)/interfaces/wireless/node.tag/vif/node.tag/ipv6/node.def
rm -f $(TMPL_DIR)/interfaces/wirelessmodem/node.tag/ipv6/node.def
+ rm -f $(TMPL_DIR)/interfaces/wireguard/node.tag/ipv6/node.def
rm -f $(TMPL_DIR)/protocols/node.def
rm -rf $(TMPL_DIR)/protocols/nbgp
rm -rf $(TMPL_DIR)/protocols/isis
diff --git a/data/templates/wifi/hostapd.conf.tmpl b/data/templates/wifi/hostapd.conf.tmpl
index 16d9f7c98..e66e3472b 100644
--- a/data/templates/wifi/hostapd.conf.tmpl
+++ b/data/templates/wifi/hostapd.conf.tmpl
@@ -451,14 +451,6 @@ macaddr_acl=0
max_num_sta={{ max_stations }}
{% endif %}
-{% if wds is defined %}
-# WDS (4-address frame) mode with per-station virtual interfaces
-# (only supported with driver=nl80211)
-# This mode allows associated stations to use 4-address frames to allow layer 2
-# bridging to be used.
-wds_sta=1
-{% endif %}
-
{% if isolate_stations is defined %}
# Client isolation can be used to prevent low-level bridging of frames between
# associated stations in the BSS. By default, this bridging is allowed.
diff --git a/interface-definitions/interfaces-tunnel.xml.in b/interface-definitions/interfaces-tunnel.xml.in
index c3f178d59..b322374b3 100644
--- a/interface-definitions/interfaces-tunnel.xml.in
+++ b/interface-definitions/interfaces-tunnel.xml.in
@@ -39,7 +39,6 @@
<script>${vyos_completion_dir}/list_local.py</script>
</completionHelp>
<constraint>
- <!-- does it need fixing/changing to be more restrictive ? -->
<validator name="ip-address"/>
</constraint>
</properties>
@@ -104,7 +103,7 @@
<script>${vyos_completion_dir}/list_interfaces.py</script>
</completionHelp>
<constraint>
- <regex>(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+</regex>
+ <regex>^(en|eth|br|bond|gnv|vxlan|wg|tun)[0-9]+$</regex>
</constraint>
</properties>
</leafNode>
@@ -112,36 +111,40 @@
<properties>
<help>Encapsulation of this tunnel interface</help>
<completionHelp>
- <list>gre gre-bridge ipip sit ipip6 ip6ip6 ip6gre</list>
+ <list>gre gre-bridge ip6gre ip6ip6 ipip ipip6 sit</list>
</completionHelp>
<valueHelp>
+ <format>gre</format>
+ <description>Generic Routing Encapsulation</description>
+ </valueHelp>
+ <valueHelp>
<format>gre-bridge</format>
<description>Generic Routing Encapsulation bridge interface</description>
</valueHelp>
<valueHelp>
- <format>ipip</format>
- <description>IP in IP encapsulation</description>
+ <format>ip6gre</format>
+ <description>GRE over IPv6 network</description>
</valueHelp>
<valueHelp>
- <format>sit</format>
- <description>Simple Internet Transition encapsulation</description>
+ <format>ip6ip6</format>
+ <description>IP6 in IP6 encapsulation</description>
</valueHelp>
<valueHelp>
- <format>ipip6</format>
- <description>IP in IP6 encapsulation</description>
+ <format>ipip</format>
+ <description>IP in IP encapsulation</description>
</valueHelp>
<valueHelp>
- <format>ip6ip6</format>
- <description>IP6 in IP6 encapsulation</description>
+ <format>ipip6</format>
+ <description>IP in IP6 encapsulation</description>
</valueHelp>
<valueHelp>
- <format>ip6gre</format>
- <description>GRE over IPv6 network</description>
+ <format>sit</format>
+ <description>Simple Internet Transition encapsulation</description>
</valueHelp>
<constraint>
- <regex>(gre|gre-bridge|ipip|sit|ipip6|ip6ip6|ip6gre)</regex>
+ <regex>^(gre|gre-bridge|ip6gre|ip6ip6|ipip|ipip6|sit)$</regex>
</constraint>
- <constraintErrorMessage>Must be one of 'gre' 'gre-bridge' 'ipip' 'sit' 'ipip6' 'ip6ip6' 'ip6gre'</constraintErrorMessage>
+ <constraintErrorMessage>Invalid encapsulation, must be one of: gre, gre-bridge, ipip, sit, ipip6, ip6ip6, ip6gre</constraintErrorMessage>
</properties>
</leafNode>
<leafNode name="multicast">
@@ -159,7 +162,7 @@
<description>Disable Multicast (default)</description>
</valueHelp>
<constraint>
- <regex>(enable|disable)</regex>
+ <regex>^(enable|disable)$</regex>
</constraint>
<constraintErrorMessage>Must be 'disable' or 'enable'</constraintErrorMessage>
</properties>
@@ -186,6 +189,7 @@
</constraint>
<constraintErrorMessage>TTL must be between 0 and 255</constraintErrorMessage>
</properties>
+ <defaultValue>255</defaultValue>
</leafNode>
<leafNode name="tos">
<properties>
@@ -199,6 +203,7 @@
</constraint>
<constraintErrorMessage>TOS must be between 0 and 99</constraintErrorMessage>
</properties>
+ <defaultValue>inherit</defaultValue>
</leafNode>
<leafNode name="key">
<properties>
@@ -232,6 +237,7 @@
</constraint>
<constraintErrorMessage>key must be between 0-255</constraintErrorMessage>
</properties>
+ <defaultValue>4</defaultValue>
</leafNode>
<leafNode name="flowlabel">
<properties>
@@ -245,6 +251,7 @@
</constraint>
<constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage>
</properties>
+ <defaultValue>inherit</defaultValue>
</leafNode>
<leafNode name="hoplimit">
<properties>
@@ -258,6 +265,7 @@
</constraint>
<constraintErrorMessage>hoplimit must be between 0-255</constraintErrorMessage>
</properties>
+ <defaultValue>64</defaultValue>
</leafNode>
<leafNode name="tclass">
<properties>
@@ -271,6 +279,7 @@
</constraint>
<constraintErrorMessage>Must be 'inherit' or a number</constraintErrorMessage>
</properties>
+ <defaultValue>inherit</defaultValue>
</leafNode>
</children>
</node>
diff --git a/interface-definitions/interfaces-wireguard.xml.in b/interface-definitions/interfaces-wireguard.xml.in
index aa63e4ac7..84f7803a0 100644
--- a/interface-definitions/interfaces-wireguard.xml.in
+++ b/interface-definitions/interfaces-wireguard.xml.in
@@ -22,6 +22,7 @@
#include <include/interface-vrf.xml.i>
#include <include/port-number.xml.i>
#include <include/interface-mtu-68-16000.xml.i>
+ #include <include/interface-ipv6-options.xml.i>
<leafNode name="fwmark">
<properties>
<help>A 32-bit fwmark value set on all outgoing packets</help>
diff --git a/interface-definitions/interfaces-wireless.xml.in b/interface-definitions/interfaces-wireless.xml.in
index fdea1e3ab..6b238e313 100644
--- a/interface-definitions/interfaces-wireless.xml.in
+++ b/interface-definitions/interfaces-wireless.xml.in
@@ -771,12 +771,6 @@
</leafNode>
#include <include/vif.xml.i>
#include <include/vif-s.xml.i>
- <leafNode name="wds">
- <properties>
- <help>Enable WDS (Wireless Distribution System)</help>
- <valueless/>
- </properties>
- </leafNode>
</children>
</tagNode>
</children>
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 7c77e050a..e6cda4adb 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -309,15 +309,12 @@ class BridgeIf(Interface):
vlan_filter = 1
cmd = f'bridge vlan del dev {interface} vid 1'
self._cmd(cmd)
- vlan_del.add(1)
vlan_id = interface_config['native_vlan']
+ if vlan_id != 1:
+ vlan_del.add(1)
cmd = f'bridge vlan add dev {interface} vid {vlan_id} pvid untagged master'
self._cmd(cmd)
vlan_add.add(vlan_id)
- else:
- cmd = f'bridge vlan del dev {interface} vid 1'
- self._cmd(cmd)
- vlan_del.add(1)
if 'allowed_vlan' in interface_config:
vlan_filter = 1
@@ -325,6 +322,11 @@ class BridgeIf(Interface):
cmd = f'bridge vlan add dev {interface} vid {vlan} master'
self._cmd(cmd)
vlan_add.add(vlan)
+
+ if vlan_filter:
+ if 'native_vlan' not in interface_config:
+ cmd = f'bridge vlan del dev {interface} vid 1'
+ self._cmd(cmd)
for vlan in vlan_del:
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 893623284..39b80ce08 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -942,6 +942,15 @@ class Interface(Control):
# method to apply()?
self._config = config
+ # Change interface MAC address - re-set to real hardware address (hw-id)
+ # if custom mac is removed. Skip if bond member.
+ if 'is_bond_member' not in config:
+ mac = config.get('hw_id')
+ if 'mac' in config:
+ mac = config.get('mac')
+ if mac:
+ self.set_mac(mac)
+
# Update interface description
self.set_alias(config.get('description', ''))
@@ -1058,15 +1067,6 @@ class Interface(Control):
for addr in tmp:
self.del_ipv6_eui64_address(addr)
- # Change interface MAC address - re-set to real hardware address (hw-id)
- # if custom mac is removed. Skip if bond member.
- if 'is_bond_member' not in config:
- mac = config.get('hw_id')
- if 'mac' in config:
- mac = config.get('mac')
- if mac:
- self.set_mac(mac)
-
# Manage IPv6 link-local addresses
tmp = dict_search('ipv6.address.no_default_link_local', config)
# we must check explicitly for None type as if the key is set we will
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index 4122d1a2f..4d1441a29 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-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
@@ -17,9 +17,11 @@
# https://community.hetzner.com/tutorials/linux-setup-gre-tunnel
from copy import deepcopy
+from netaddr import EUI
+from netaddr import mac_unix_expanded
+from random import getrandbits
from vyos.ifconfig.interface import Interface
-from vyos.ifconfig.afi import IP4, IP6
from vyos.validate import assert_list
def enable_to_on(value):
@@ -61,68 +63,73 @@ class _Tunnel(Interface):
},
}}
- # use for "options" and "updates"
- # If an key is only in the options list, it can only be set at creation time
- # the create comand will only be make using the key in options
-
- # If an option is in the updates list, it can be updated
- # upon, the creation, all key not yet applied will be updated
-
- # multicast/allmulticast can not be part of the create command
-
- # options matrix:
- # with ip = 4, we have multicast
- # wiht ip = 6, nothing
- # with tunnel = 4, we have tos, ttl, key
- # with tunnel = 6, we have encaplimit, hoplimit, tclass, flowlabel
-
- # TODO: For multicast, it is allowed on IP6IP6 and Sit6RD
- # TODO: to match vyatta but it should be checked for correctness
-
- updates = []
-
- create = ''
- change = ''
- delete = ''
-
- ip = [] # AFI of the families which can be used in the tunnel
- tunnel = 0 # invalid - need to be set by subclasses
-
def __init__(self, ifname, **config):
self.config = deepcopy(config) if config else {}
super().__init__(ifname, **config)
def _create(self):
+ create = 'ip tunnel add {ifname} mode {type}'
+
# add " option-name option-name-value ..." for all options set
options = " ".join(["{} {}".format(k, self.config[k])
for k in self.options if k in self.config and self.config[k]])
- self._cmd('{} {}'.format(self.create.format(**self.config), options))
+ self._cmd('{} {}'.format(create.format(**self.config), options))
self.set_admin_state('down')
- def _delete(self):
- self.set_admin_state('down')
- cmd = self.delete.format(**self.config)
- return self._cmd(cmd)
-
- def set_interface(self, option, value):
- try:
- return Interface.set_interface(self, option, value)
- except Exception:
- pass
-
- if value == '':
- # remove the value so that it is not used
- self.config.pop(option, '')
+ def change_options(self):
+ change = 'ip tunnel cha {ifname} mode {type}'
- if self.change:
- self._cmd('{} {} {}'.format(
- self.change.format(**self.config), option, value))
- return True
+ # add " option-name option-name-value ..." for all options set
+ options = " ".join(["{} {}".format(k, self.config[k])
+ for k in self.options if k in self.config and self.config[k]])
+ self._cmd('{} {}'.format(change.format(**self.config), options))
@classmethod
def get_config(cls):
return dict(zip(cls.options, ['']*len(cls.options)))
+ def get_mac(self):
+ """
+ Get current interface MAC (Media Access Contrl) address used.
+
+ NOTE: Tunnel interfaces have no "MAC" address by default. The content
+ of the 'address' file in /sys/class/net/device contains the
+ local-ip thus we generate a random MAC address instead
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_mac()
+ '00:50:ab:cd:ef:00'
+ """
+ # we choose 40 random bytes for the MAC address, this gives
+ # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A')
+ tmp = EUI(getrandbits(48)).value
+ # set locally administered bit in MAC address
+ tmp |= 0xf20000000000
+ # convert integer to "real" MAC address representation
+ mac = EUI(hex(tmp).split('x')[-1])
+ # change dialect to use : as delimiter instead of -
+ mac.dialect = mac_unix_expanded
+ return str(mac)
+
+ def update(self, config):
+ """ General helper function which works on a dictionary retrived by
+ get_config_dict(). It's main intention is to consolidate the scattered
+ interface setup code and provide a single point of entry when workin
+ on any interface. """
+
+ # call base class first
+ super().update(config)
+
+ # Enable/Disable of an interface must always be done at the end of the
+ # derived class to make use of the ref-counting set_admin_state()
+ # function. We will only enable the interface if 'up' was called as
+ # often as 'down'. This is required by some interface implementations
+ # as certain parameters can only be changed when the interface is
+ # in admin-down state. This ensures the link does not flap during
+ # reconfiguration.
+ state = 'down' if 'disable' in config else 'up'
+ self.set_admin_state(state)
class GREIf(_Tunnel):
"""
@@ -141,20 +148,8 @@ class GREIf(_Tunnel):
},
}
- ip = [IP4, IP6]
- tunnel = IP4
-
default = {'type': 'gre'}
- required = ['local', ] # mGRE is a GRE without remote endpoint
-
options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
- updates = ['local', 'remote', 'dev', 'ttl', 'tos',
- 'mtu', 'multicast', 'allmulticast']
-
- create = 'ip tunnel add {ifname} mode {type}'
- change = 'ip tunnel cha {ifname}'
- delete = 'ip tunnel del {ifname}'
-
# GreTap also called GRE Bridge
class GRETapIf(_Tunnel):
@@ -173,19 +168,8 @@ class GRETapIf(_Tunnel):
},
}
- ip = [IP4, ]
- tunnel = IP4
-
default = {'type': 'gretap'}
- required = ['local', ]
-
options = ['local', 'remote', 'ttl',]
- updates = ['mtu', ]
-
- create = 'ip link add {ifname} type {type}'
- change = ''
- delete = 'ip link del {ifname}'
-
class IP6GREIf(_Tunnel):
"""
@@ -196,30 +180,9 @@ class IP6GREIf(_Tunnel):
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_gre6.c
"""
- ip = [IP4, IP6]
- tunnel = IP6
-
default = {'type': 'ip6gre'}
- required = ['local', 'remote']
-
options = ['local', 'remote', 'dev', 'encaplimit',
'hoplimit', 'tclass', 'flowlabel']
- updates = ['local', 'remote', 'dev', 'encaplimit',
- 'hoplimit', 'tclass', 'flowlabel',
- 'mtu', 'multicast', 'allmulticast']
-
- create = 'ip tunnel add {ifname} mode {type}'
- change = 'ip tunnel cha {ifname} mode {type}'
- delete = 'ip tunnel del {ifname}'
-
- # using "ip tunnel change" without using "mode" causes errors
- # sudo ip tunnel add tun100 mode ip6gre local ::1 remote 1::1
- # sudo ip tunnel cha tun100 hoplimit 100
- # *** stack smashing detected ** *: < unknown > terminated
- # sudo ip tunnel cha tun100 local: : 2
- # Error: an IP address is expected rather than "::2"
- # works if mode is explicit
-
class IPIPIf(_Tunnel):
"""
@@ -232,20 +195,8 @@ class IPIPIf(_Tunnel):
# IPIP does not allow to pass multicast, unlike GRE
# but the interface itself can be set with multicast
- ip = [IP4,]
- tunnel = IP4
-
default = {'type': 'ipip'}
- required = ['local', 'remote']
-
options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
- updates = ['local', 'remote', 'dev', 'ttl', 'tos',
- 'mtu', 'multicast', 'allmulticast']
-
- create = 'ip tunnel add {ifname} mode {type}'
- change = 'ip tunnel cha {ifname}'
- delete = 'ip tunnel del {ifname}'
-
class IPIP6If(_Tunnel):
"""
@@ -255,22 +206,9 @@ class IPIP6If(_Tunnel):
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_ip6tnl.c
"""
- ip = [IP4,]
- tunnel = IP6
-
default = {'type': 'ipip6'}
- required = ['local', 'remote']
-
options = ['local', 'remote', 'dev', 'encaplimit',
'hoplimit', 'tclass', 'flowlabel']
- updates = ['local', 'remote', 'dev', 'encaplimit',
- 'hoplimit', 'tclass', 'flowlabel',
- 'mtu', 'multicast', 'allmulticast']
-
- create = 'ip -6 tunnel add {ifname} mode {type}'
- change = 'ip -6 tunnel cha {ifname}'
- delete = 'ip -6 tunnel del {ifname}'
-
class IP6IP6If(IPIP6If):
"""
@@ -279,9 +217,6 @@ class IP6IP6If(IPIP6If):
For more information please refer to:
https://tools.ietf.org/html/rfc2473
"""
-
- ip = [IP6,]
-
default = {'type': 'ip6ip6'}
@@ -293,20 +228,8 @@ class SitIf(_Tunnel):
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/link_iptnl.c
"""
- ip = [IP6, IP4]
- tunnel = IP4
-
default = {'type': 'sit'}
- required = ['local', 'remote']
-
options = ['local', 'remote', 'dev', 'ttl', 'tos', 'key']
- updates = ['local', 'remote', 'dev', 'ttl', 'tos',
- 'mtu', 'multicast', 'allmulticast']
-
- create = 'ip tunnel add {ifname} mode {type}'
- change = 'ip tunnel cha {ifname}'
- delete = 'ip tunnel del {ifname}'
-
class Sit6RDIf(SitIf):
"""
@@ -314,15 +237,8 @@ class Sit6RDIf(SitIf):
https://en.wikipedia.org/wiki/IPv6_rapid_deployment
"""
-
- ip = [IP6,]
-
- required = ['remote', '6rd-prefix']
-
# TODO: check if key can really be used with 6RD
options = ['remote', 'ttl', 'tos', 'key', '6rd-prefix', '6rd-relay-prefix']
- updates = ['remote', 'ttl', 'tos',
- 'mtu', 'multicast', 'allmulticast']
def _create(self):
# do not call _Tunnel.create, building fully here
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index da3bd4e89..ac6dc2109 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -17,6 +17,9 @@ import os
import time
from datetime import timedelta
+from netaddr import EUI
+from netaddr import mac_unix_expanded
+from random import getrandbits
from hurry.filesize import size
from hurry.filesize import alternative
@@ -169,6 +172,30 @@ class WireGuardIf(Interface):
['port', 'private_key', 'pubkey', 'psk',
'allowed_ips', 'fwmark', 'endpoint', 'keepalive']
+ def get_mac(self):
+ """
+ Get current interface MAC (Media Access Contrl) address used.
+
+ NOTE: Tunnel interfaces have no "MAC" address by default. The content
+ of the 'address' file in /sys/class/net/device contains the
+ local-ip thus we generate a random MAC address instead
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').get_mac()
+ '00:50:ab:cd:ef:00'
+ """
+ # we choose 40 random bytes for the MAC address, this gives
+ # us e.g. EUI('00-EA-EE-D6-A3-C8') or EUI('00-41-B9-0D-F2-2A')
+ tmp = EUI(getrandbits(48)).value
+ # set locally administered bit in MAC address
+ tmp |= 0xf20000000000
+ # convert integer to "real" MAC address representation
+ mac = EUI(hex(tmp).split('x')[-1])
+ # change dialect to use : as delimiter instead of -
+ mac.dialect = mac_unix_expanded
+ return str(mac)
+
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
@@ -221,7 +248,7 @@ class WireGuardIf(Interface):
# Endpoint configuration is optional
if {'address', 'port'} <= set(peer):
- if is_ipv6(config['address']):
+ if is_ipv6(peer['address']):
cmd += ' endpoint [{address}]:{port}'
else:
cmd += ' endpoint {address}:{port}'
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index deca68bf0..37703d242 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -23,10 +23,8 @@ class WiFiIf(Interface):
default = {
'type': 'wifi',
- 'phy': '',
- 'wds': 'off',
+ 'phy': 'phy0'
}
-
definition = {
**Interface.definition,
**{
@@ -35,19 +33,12 @@ class WiFiIf(Interface):
'bridgeable': True,
}
}
-
options = Interface.options + \
['phy', 'op_mode']
- _command_set = {**Interface._command_set, **{
- '4addr': {
- 'shellcmd': 'iw dev {ifname} set 4addr {value}',
- },
- }}
-
def _create(self):
# all interfaces will be added in monitor mode
- cmd = 'iw phy {phy} interface add {ifname} type monitor 4addr {wds}' \
+ cmd = 'iw phy {phy} interface add {ifname} type monitor' \
.format(**self.config)
self._cmd(cmd)
@@ -59,20 +50,28 @@ class WiFiIf(Interface):
.format(**self.config)
self._cmd(cmd)
- def set_4aadr_mode(self, state):
- return self.set_interface('4addr', state)
-
def update(self, config):
""" General helper function which works on a dictionary retrived by
get_config_dict(). It's main intention is to consolidate the scattered
interface setup code and provide a single point of entry when workin
on any interface. """
- self.set_4aadr_mode('on' if 'wds' in config else 'off')
+ # We can not call add_to_bridge() until wpa_supplicant is running, thus
+ # we will remove the key from the config dict and react to this specal
+ # case in thie derived class.
+ # re-add ourselves to any bridge we might have fallen out of
+ bridge_member = ''
+ if 'is_bridge_member' in config:
+ bridge_member = config['is_bridge_member']
+ del config['is_bridge_member']
# call base class first
super().update(config)
+ # re-add ourselves to any bridge we might have fallen out of
+ if bridge_member:
+ self.add_to_bridge(bridge_member)
+
# Enable/Disable of an interface must always be done at the end of the
# derived class to make use of the ref-counting set_admin_state()
# function. We will only enable the interface if 'up' was called as
diff --git a/smoketest/scripts/cli/test_interfaces_tunnel.py b/smoketest/scripts/cli/test_interfaces_tunnel.py
index 4817321cf..aaff92dea 100755
--- a/smoketest/scripts/cli/test_interfaces_tunnel.py
+++ b/smoketest/scripts/cli/test_interfaces_tunnel.py
@@ -168,12 +168,12 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
self.session.set(self._base_path + [interface, 'local-ip', self.local_v6])
self.session.set(self._base_path + [interface, 'remote-ip', remote_ip6])
- # Encapsulation mode requires IPv6 local-ip
+ # Encapsulation mode requires IPv4 local-ip
with self.assertRaises(ConfigSessionError):
self.session.commit()
self.session.set(self._base_path + [interface, 'local-ip', self.local_v4])
- # Encapsulation mode requires IPv6 local-ip
+ # Encapsulation mode requires IPv4 local-ip
with self.assertRaises(ConfigSessionError):
self.session.commit()
self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
@@ -360,7 +360,7 @@ class TunnelInterfaceTest(BasicInterfaceTest.BaseTest):
# No assertion is raised for GRE remote-ip when missing
self.session.set(self._base_path + [interface, 'remote-ip', remote_ip4])
- # Source interface can not be used with si
+ # Source interface can not be used with sit
self.session.set(self._base_path + [interface, 'source-interface', source_if])
with self.assertRaises(ConfigSessionError):
self.session.commit()
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index 076bdb63e..7af3e3d7c 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -123,12 +123,12 @@ def get_config(config=None):
# VLAN-aware bridge members must not have VLAN interface configuration
if 'native_vlan' in interface_config:
- if 'disable' not in interface_config['native_vlan']:
- vlan_aware = True
+ vlan_aware = True
if 'allowed_vlan' in interface_config:
vlan_aware = True
+
if vlan_aware:
tmp = has_vlan_subinterface_configured(conf,interface)
if tmp:
@@ -142,6 +142,8 @@ def verify(bridge):
verify_dhcpv6(bridge)
verify_vrf(bridge)
+
+ vlan_aware = False
if dict_search('member.interface', bridge):
for interface, interface_config in bridge['member']['interface'].items():
@@ -168,6 +170,16 @@ def verify(bridge):
if 'has_vlan' in interface_config:
raise ConfigError(error_msg + 'it has an VLAN subinterface assigned!')
+ # VLAN-aware bridge members must not have VLAN interface configuration
+ if 'native_vlan' in interface_config:
+ vlan_aware = True
+
+ if 'allowed_vlan' in interface_config:
+ vlan_aware = True
+
+ if vlan_aware and 'wlan' in interface:
+ raise ConfigError(error_msg + 'VLAN aware cannot be set!')
+
if 'allowed_vlan' in interface_config:
for vlan in interface_config['allowed_vlan']:
if re.search('[0-9]{1,4}-[0-9]{1,4}', vlan):
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index f1217b62d..1a7e9a96d 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2019 VyOS maintainers and contributors
+# Copyright (C) 2018-2020 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
@@ -15,354 +15,124 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
-import netifaces
from sys import exit
-from copy import deepcopy
from netifaces import interfaces
from vyos.config import Config
-from vyos.configdict import is_member
-from vyos.configdict import list_diff
-from vyos.dicts import FixedDict
-from vyos.ifconfig import Interface, GREIf, GRETapIf, IPIPIf, IP6GREIf, IPIP6If, IP6IP6If, SitIf, Sit6RDIf
-from vyos.ifconfig.afi import IP4, IP6
+from vyos.configdict import dict_merge
+from vyos.configdict import get_interface_dict
+from vyos.configdict import node_changed
+from vyos.configdict import leaf_node_changed
+from vyos.configverify import verify_vrf
+from vyos.configverify import verify_address
+from vyos.configverify import verify_bridge_delete
+from vyos.configverify import verify_mtu_ipv6
+from vyos.ifconfig import Interface
+from vyos.ifconfig import GREIf
+from vyos.ifconfig import GRETapIf
+from vyos.ifconfig import IPIPIf
+from vyos.ifconfig import IP6GREIf
+from vyos.ifconfig import IPIP6If
+from vyos.ifconfig import IP6IP6If
+from vyos.ifconfig import SitIf
+from vyos.ifconfig import Sit6RDIf
from vyos.template import is_ipv4
from vyos.template import is_ipv6
+from vyos.util import dict_search
from vyos import ConfigError
-
-
from vyos import airbag
airbag.enable()
-
-class ConfigurationState(object):
+def get_config(config=None):
"""
- The current API require a dict to be generated by get_config()
- which is then consumed by verify(), generate() and apply()
-
- ConfiguartionState is an helper class wrapping Config and providing
- an common API to this dictionary structure
-
- Its to_api() function return a dictionary containing three fields,
- each a dict, called options, changes, actions.
-
- options:
-
- contains the configuration options for the dict and its value
- {'options': {'commment': 'test'}} will be set if
- 'set interface dummy dum1 description test' was used and
- the key 'commment' is used to index the description info.
-
- changes:
-
- per key, let us know how the data was modified using one of the action
- a special key called 'section' is used to indicate what happened to the
- section. for example:
-
- 'set interface dummy dum1 description test' when no interface was setup
- will result in the following changes
- {'changes': {'section': 'create', 'comment': 'create'}}
-
- on an existing interface, depending if there was a description
- 'set interface dummy dum1 description test' will result in one of
- {'changes': {'comment': 'create'}} (not present before)
- {'changes': {'comment': 'static'}} (unchanged)
- {'changes': {'comment': 'modify'}} (changed from half)
-
- and 'delete interface dummy dummy1 description' will result in:
- {'changes': {'comment': 'delete'}}
-
- actions:
-
- for each action list the configuration key which were changes
- in our example if we added the 'description' and added an IP we would have
- {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}}
-
- the actions are:
- 'create': it did not exist previously and was created
- 'modify': it did exist previously but its content changed
- 'static': it did exist and did not change
- 'delete': it was present but was removed from the configuration
- 'absent': it was not and is not present
- which for each field represent how it was modified since the last commit
+ Retrive CLI config as dictionary. Dictionary can never be empty, as at least
+ the interface name will be added or a deleted flag
"""
+ if config:
+ conf = config
+ else:
+ conf = Config()
+ base = ['interfaces', 'tunnel']
+ tunnel = get_interface_dict(conf, base)
- def __init__(self, configuration, section, default):
- """
- initialise the class for a given configuration path:
-
- >>> conf = ConfigurationState(conf, 'interfaces ethernet eth1')
- all further references to get_value(s) and get_effective(s)
- will be for this part of the configuration (eth1)
- """
- self._conf = configuration
-
- self.default = deepcopy(default)
- self.options = FixedDict(**default)
- self.actions = {
- 'create': [], # the key did not exist and was added
- 'static': [], # the key exists and its value was not modfied
- 'modify': [], # the key exists and its value was modified
- 'absent': [], # the key is not present
- 'delete': [], # the key was present and was deleted
- }
- self.changes = {}
- if not self._conf.exists(section):
- self.changes['section'] = 'delete'
- elif self._conf.exists_effective(section):
- self.changes['section'] = 'modify'
- else:
- self.changes['section'] = 'create'
-
- self.set_level(section)
-
- def set_level(self, lpath):
- self.section = lpath
- self._conf.set_level(lpath)
-
- def _act(self, section):
- """
- Returns for a given configuration field determine what happened to it
-
- 'create': it did not exist previously and was created
- 'modify': it did exist previously but its content changed
- 'static': it did exist and did not change
- 'delete': it was present but was removed from the configuration
- 'absent': it was not and is not present
- """
- if self._conf.exists(section):
- if self._conf.exists_effective(section):
- if self._conf.return_value(section) != self._conf.return_effective_value(section):
- return 'modify'
- return 'static'
- return 'create'
- else:
- if self._conf.exists_effective(section):
- return 'delete'
- return 'absent'
-
- def _action(self, name, key):
- action = self._act(key)
- self.changes[name] = action
- self.actions[action].append(name)
- return action
-
- def _get(self, name, key, default, getter):
- value = getter(key)
- if not value:
- if default:
- self.options[name] = default
- return
- self.options[name] = self.default[name]
- return
- self.options[name] = value
-
- def get_value(self, name, key, default=None):
- """
- >>> conf.get_value('comment', 'description')
- will place the string of 'interface dummy description test'
- into the dictionnary entry 'comment' using Config.return_value
- (the data in the configuration to apply)
- """
- if self._action(name, key) in ('delete', 'absent'):
- return
- return self._get(name, key, default, self._conf.return_value)
-
- def get_values(self, name, key, default=None):
- """
- >>> conf.get_values('addresses', 'address')
- will place a list of the new IP present in 'interface dummy dum1 address'
- into the dictionnary entry "-add" (here 'addresses-add') using
- Config.return_values and will add the the one which were removed in into
- the entry "-del" (here addresses-del')
- """
- add_name = f'{name}-add'
-
- if self._action(add_name, key) in ('delete', 'absent'):
- return
-
- self._get(add_name, key, default, self._conf.return_values)
-
- # get the effective values to determine which data is no longer valid
- self.options['addresses-del'] = list_diff(
- self._conf.return_effective_values('address'),
- self.options['addresses-add']
- )
-
- def get_effective(self, name, key, default=None):
- """
- >>> conf.get_value('comment', 'description')
- will place the string of 'interface dummy description test'
- into the dictionnary entry 'comment' using Config.return_effective_value
- (the data in the configuration to apply)
- """
- self._action(name, key)
- return self._get(name, key, default, self._conf.return_effective_value)
-
- def get_effectives(self, name, key, default=None):
- """
- >>> conf.get_effectives('addresses-add', 'address')
- will place a list made of the IP present in 'interface ethernet eth1 address'
- into the dictionnary entry 'addresses-add' using Config.return_effectives_value
- (the data in the un-modified configuration)
- """
- self._action(name, key)
- return self._get(name, key, default, self._conf.return_effectives_value)
+ # Wireguard is "special" the default MTU is 1420 - update accordingly
+ # as the config_level is already st in get_interface_dict() - we can use []
+ tmp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ if 'mtu' not in tmp:
+ tunnel['mtu'] = '1476'
- def load(self, mapping):
- """
- load will take a dictionary defining how we wish the configuration
- to be parsed and apply this definition to set the data.
+ tmp = leaf_node_changed(conf, ['encapsulation'])
+ if tmp: tunnel.update({'encapsulation_changed': {}})
- >>> mapping = {
- 'addresses-add' : ('address', True, None),
- 'comment' : ('description', False, 'auto'),
- }
- >>> conf.load(mapping)
+ # We must check if our interface is configured to be a DMVPN member
+ nhrp_base = ['protocols', 'nhrp', 'tunnel']
+ conf.set_level(nhrp_base)
+ nhrp = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True)
+ if nhrp: tunnel.update({'nhrp' : list(nhrp.keys())})
- mapping is a dictionary where each key represents the name we wish
- to have (such as 'addresses-add'), with a list a content representing
- how the data should be parsed:
- - the configuration section name
- such as 'address' under 'interface ethernet eth1'
- - boolean indicating if this data can have multiple values
- for 'address', True, as multiple IPs can be set
- for 'description', False, as it is a single string
- - default represent the default value if absent from the configuration
- 'None' indicate that no default should be set if the configuration
- does not have the configuration section
+ return tunnel
- """
- for local_name, (config_name, multiple, default) in mapping.items():
- if multiple:
- self.get_values(local_name, config_name, default)
- else:
- self.get_value(local_name, config_name, default)
+def verify(tunnel):
+ if 'deleted' in tunnel:
+ verify_bridge_delete(tunnel)
- def remove_default(self,*options):
- """
- remove all the values which were not changed from the default
- """
- for option in options:
- if not self._conf.exists(option):
- del self.options[option]
- continue
+ if 'nhrp' in tunnel and tunnel['ifname'] in tunnel['nhrp']:
+ raise ConfigError('Tunnel used for NHRP, it can not be deleted!')
- if self._conf.return_value(option) == self.default[option]:
- del self.options[option]
- continue
+ return None
- if self._conf.return_values(option) == self.default[option]:
- del self.options[option]
- continue
+ if 'encapsulation' not in tunnel:
+ raise ConfigError('Must configure the tunnel encapsulation for '\
+ '{ifname}!'.format(**tunnel))
- def as_dict(self, lpath):
- l = self._conf.get_level()
- self._conf.set_level([])
- d = self._conf.get_config_dict(lpath)
- # XXX: that not what I would have expected from get_config_dict
- if lpath:
- d = d[lpath[-1]]
- # XXX: it should have provided me the content and not the key
- self._conf.set_level(l)
- return d
+ verify_mtu_ipv6(tunnel)
+ verify_address(tunnel)
+ verify_vrf(tunnel)
- def to_api(self):
- """
- provide a dictionary with the generated data for the configuration
- options: the configuration value for the key
- changes: per key how they changed from the previous configuration
- actions: per changes all the options which were changed
- """
- # as we have to use a dict() for the API for verify and apply the options
- return {
- 'options': self.options,
- 'changes': self.changes,
- 'actions': self.actions,
- }
+ if 'local_ip' not in tunnel and 'dhcp_interface' not in tunnel:
+ raise ConfigError('local-ip is mandatory for tunnel')
+ if 'remote_ip' not in tunnel and tunnel['encapsulation'] != 'gre':
+ raise ConfigError('remote-ip is mandatory for tunnel')
-default_config_data = {
- # interface definition
- 'vrf': '',
- 'addresses-add': [],
- 'addresses-del': [],
- 'state': 'up',
- 'dhcp-interface': '',
- 'link_detect': 1,
- 'ip': False,
- 'ipv6': False,
- 'nhrp': [],
- 'arp_filter': 1,
- 'arp_accept': 0,
- 'arp_announce': 0,
- 'arp_ignore': 0,
- 'ipv6_accept_ra': 1,
- 'ipv6_autoconf': 0,
- 'ipv6_forwarding': 1,
- 'ipv6_dad_transmits': 1,
- # internal
- 'interfaces': [],
- 'tunnel': {},
- 'bridge': '',
- # the following names are exactly matching the name
- # for the ip command and must not be changed
- 'ifname': '',
- 'type': '',
- 'alias': '',
- 'mtu': '1476',
- 'local': '',
- 'remote': '',
- 'dev': '',
- 'multicast': 'disable',
- 'allmulticast': 'disable',
- 'ttl': '255',
- 'tos': 'inherit',
- 'key': '',
- 'encaplimit': '4',
- 'flowlabel': 'inherit',
- 'hoplimit': '64',
- 'tclass': 'inherit',
- '6rd-prefix': '',
- '6rd-relay-prefix': '',
-}
+ if {'local_ip', 'dhcp_interface'} <= set(tunnel):
+ raise ConfigError('Can not use both local-ip and dhcp-interface')
+ if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+ error_ipv6 = 'Encapsulation mode requires IPv6'
+ if 'local_ip' in tunnel and not is_ipv6(tunnel['local_ip']):
+ raise ConfigError(f'{error_ipv6} local-ip')
-# dict name -> config name, multiple values, default
-mapping = {
- 'type': ('encapsulation', False, None),
- 'alias': ('description', False, None),
- 'mtu': ('mtu', False, None),
- 'local': ('local-ip', False, None),
- 'remote': ('remote-ip', False, None),
- 'multicast': ('multicast', False, None),
- 'dev': ('source-interface', False, None),
- 'ttl': ('parameters ip ttl', False, None),
- 'tos': ('parameters ip tos', False, None),
- 'key': ('parameters ip key', False, None),
- 'encaplimit': ('parameters ipv6 encaplimit', False, None),
- 'flowlabel': ('parameters ipv6 flowlabel', False, None),
- 'hoplimit': ('parameters ipv6 hoplimit', False, None),
- 'tclass': ('parameters ipv6 tclass', False, None),
- '6rd-prefix': ('6rd-prefix', False, None),
- '6rd-relay-prefix': ('6rd-relay-prefix', False, None),
- 'dhcp-interface': ('dhcp-interface', False, None),
- 'state': ('disable', False, 'down'),
- 'link_detect': ('disable-link-detect', False, 2),
- 'vrf': ('vrf', False, None),
- 'addresses': ('address', True, None),
- 'arp_filter': ('ip disable-arp-filter', False, 0),
- 'arp_accept': ('ip enable-arp-accept', False, 1),
- 'arp_announce': ('ip enable-arp-announce', False, 1),
- 'arp_ignore': ('ip enable-arp-ignore', False, 1),
- 'ipv6_autoconf': ('ipv6 address autoconf', False, 1),
- 'ipv6_forwarding': ('ipv6 disable-forwarding', False, 0),
- 'ipv6_dad_transmits:': ('ipv6 dup-addr-detect-transmits', False, None)
-}
+ if 'remote_ip' in tunnel and not is_ipv6(tunnel['remote_ip']):
+ raise ConfigError(f'{error_ipv6} remote-ip')
+ else:
+ error_ipv4 = 'Encapsulation mode requires IPv4'
+ if 'local_ip' in tunnel and not is_ipv4(tunnel['local_ip']):
+ raise ConfigError(f'{error_ipv4} local-ip')
+
+ if 'remote_ip' in tunnel and not is_ipv4(tunnel['remote_ip']):
+ raise ConfigError(f'{error_ipv4} remote-ip')
+
+ if tunnel['encapsulation'] in ['sit', 'gre-bridge']:
+ if 'source_interface' in tunnel:
+ raise ConfigError('Option source-interface can not be used with ' \
+ 'encapsulation "sit" or "gre-bridge"')
+ elif tunnel['encapsulation'] == 'gre':
+ if 'local_ip' in tunnel and is_ipv6(tunnel['local_ip']):
+ raise ConfigError('Can not use local IPv6 address is for mGRE tunnels')
+
+def generate(tunnel):
+ return None
+def apply(tunnel):
+ if 'deleted' in tunnel or 'encapsulation_changed' in tunnel:
+ if tunnel['ifname'] in interfaces():
+ tmp = Interface(tunnel['ifname'])
+ tmp.remove()
+ if 'deleted' in tunnel:
+ return None
-def get_class (options):
dispatch = {
'gre': GREIf,
'gre-bridge': GRETapIf,
@@ -373,353 +143,52 @@ def get_class (options):
'sit': SitIf,
}
- kls = dispatch[options['type']]
- if options['type'] == 'gre' and not options['remote'] \
- and not options['key'] and not options['multicast']:
- # will use GreTapIf on GreIf deletion but it does not matter
- return GRETapIf
- elif options['type'] == 'sit' and options['6rd-prefix']:
- # will use SitIf on Sit6RDIf deletion but it does not matter
- return Sit6RDIf
- return kls
-
-def get_interface_ip (ifname):
- if not ifname:
- return ''
- try:
- addrs = Interface(ifname).get_addr()
- if addrs:
- return addrs[0].split('/')[0]
- except Exception:
- return ''
-
-def get_afi (ip):
- return IP6 if is_ipv6(ip) else IP4
-
-def ip_proto (afi):
- return 6 if afi == IP6 else 4
-
-
-def get_config(config=None):
- ifname = os.environ.get('VYOS_TAGNODE_VALUE','')
- if not ifname:
- raise ConfigError('Interface not specified')
-
- if config:
- config = config
- else:
- config = Config()
-
- conf = ConfigurationState(config, ['interfaces', 'tunnel ', ifname], default_config_data)
- options = conf.options
- changes = conf.changes
- options['ifname'] = ifname
-
- if changes['section'] == 'delete':
- conf.get_effective('type', mapping['type'][0])
- config.set_level(['protocols', 'nhrp', 'tunnel'])
- options['nhrp'] = config.list_nodes('')
- return conf.to_api()
-
- # load all the configuration option according to the mapping
- conf.load(mapping)
-
- # remove default value if not set and not required
- afi_local = get_afi(options['local'])
- if afi_local == IP6:
- conf.remove_default('ttl', 'tos', 'key')
- if afi_local == IP4:
- conf.remove_default('encaplimit', 'flowlabel', 'hoplimit', 'tclass')
-
- # if the local-ip is not set, pick one from the interface !
- # hopefully there is only one, otherwise it will not be very deterministic
- # at time of writing the code currently returns ipv4 before ipv6 in the list
-
- # XXX: There is no way to trigger an update of the interface source IP if
- # XXX: the underlying interface IP address does change, I believe this
- # XXX: limit/issue is present in vyatta too
-
- if not options['local'] and options['dhcp-interface']:
- # XXX: This behaviour changes from vyatta which would return 127.0.0.1 if
- # XXX: the interface was not DHCP. As there is no easy way to find if an
- # XXX: interface is using DHCP, and using this feature to get 127.0.0.1
- # XXX: makes little sense, I feel the change in behaviour is acceptable
- picked = get_interface_ip(options['dhcp-interface'])
- if picked == '':
- picked = '127.0.0.1'
- print('Could not get an IP address from {dhcp-interface} using 127.0.0.1 instead')
- options['local'] = picked
- options['dhcp-interface'] = ''
-
- # to make IPv6 SLAAC and DHCPv6 work with forwarding=1,
- # accept_ra must be 2
- if options['ipv6_autoconf'] or 'dhcpv6' in options['addresses-add']:
- options['ipv6_accept_ra'] = 2
-
- # allmulticast fate is linked to multicast
- options['allmulticast'] = options['multicast']
-
- # check that per encapsulation all local-remote pairs are unique
- ct = conf.as_dict(['interfaces', 'tunnel'])
- options['tunnel'] = {}
-
- # check for bridges
- tmp = is_member(config, ifname, 'bridge')
- if tmp: options['bridge'] = next(iter(tmp))
- options['interfaces'] = interfaces()
-
- for name in ct:
- tunnel = ct[name]
- encap = tunnel.get('encapsulation', '')
- local = tunnel.get('local-ip', '')
- if not local:
- local = get_interface_ip(tunnel.get('dhcp-interface', ''))
- remote = tunnel.get('remote-ip', '<unset>')
- pair = f'{local}-{remote}'
- options['tunnel'][encap][pair] = options['tunnel'].setdefault(encap, {}).get(pair, 0) + 1
-
- return conf.to_api()
-
-
-def verify(conf):
- options = conf['options']
- changes = conf['changes']
- actions = conf['actions']
-
- ifname = options['ifname']
- iftype = options['type']
-
- if changes['section'] == 'delete':
- if ifname in options['nhrp']:
- raise ConfigError((
- f'Cannot delete interface tunnel {iftype} {ifname}, '
- 'it is used by NHRP'))
-
- if options['bridge']:
- raise ConfigError((
- f'Cannot delete interface "{options["ifname"]}" as it is a '
- f'member of bridge "{options["bridge"]}"!'))
-
- # done, bail out early
- return None
-
- # tunnel encapsulation checks
-
- if not iftype:
- raise ConfigError(f'Must provide an "encapsulation" for tunnel {iftype} {ifname}')
-
- if changes['type'] in ('modify', 'delete'):
- # TODO: we could now deal with encapsulation modification by deleting / recreating
- raise ConfigError(f'Encapsulation can only be set at tunnel creation for tunnel {iftype} {ifname}')
-
- if iftype != 'sit' and options['6rd-prefix']:
- # XXX: should be able to remove this and let the definition catch it
- print(f'6RD can only be configured for sit interfaces not tunnel {iftype} {ifname}')
-
- # what are the tunnel options we can set / modified / deleted
-
- kls = get_class(options)
- valid = kls.updates + ['alias', 'addresses-add', 'addresses-del', 'vrf', 'state']
- valid += ['arp_filter', 'arp_accept', 'arp_announce', 'arp_ignore']
- valid += ['ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits']
-
- if changes['section'] == 'create':
- valid.extend(['type',])
- valid.extend([o for o in kls.options if o not in kls.updates])
-
- for create in actions['create']:
- if create not in valid:
- raise ConfigError(f'Can not set "{create}" for tunnel {iftype} {ifname} at tunnel creation')
-
- for modify in actions['modify']:
- if modify not in valid:
- raise ConfigError(f'Can not modify "{modify}" for tunnel {iftype} {ifname}. it must be set at tunnel creation')
-
- for delete in actions['delete']:
- if delete in kls.required:
- raise ConfigError(f'Can not remove "{delete}", it is an mandatory option for tunnel {iftype} {ifname}')
-
- # tunnel information
-
- tun_local = options['local']
- afi_local = get_afi(tun_local)
- tun_remote = options['remote'] or tun_local
- afi_remote = get_afi(tun_remote)
- tun_ismgre = iftype == 'gre' and not options['remote']
- tun_is6rd = iftype == 'sit' and options['6rd-prefix']
- tun_dev = options['dev']
-
- # incompatible options
-
- if not tun_local and not options['dhcp-interface'] and not tun_is6rd:
- raise ConfigError(f'Must configure either local-ip or dhcp-interface for tunnel {iftype} {ifname}')
-
- if tun_local and options['dhcp-interface']:
- raise ConfigError(f'Must configure only one of local-ip or dhcp-interface for tunnel {iftype} {ifname}')
-
- if tun_dev and iftype in ('gre-bridge', 'sit'):
- raise ConfigError(f'source interface can not be used with {iftype} {ifname}')
-
- # tunnel endpoint
-
- if afi_local != afi_remote:
- raise ConfigError(f'IPv4/IPv6 mismatch between local-ip and remote-ip for tunnel {iftype} {ifname}')
-
- if afi_local != kls.tunnel:
- version = 4 if tun_local == IP4 else 6
- raise ConfigError(f'Invalid IPv{version} local-ip for tunnel {iftype} {ifname}')
-
- ipv4_count = len([ip for ip in options['addresses-add'] if is_ipv4(ip)])
- ipv6_count = len([ip for ip in options['addresses-add'] if is_ipv6(ip)])
-
- if tun_ismgre and afi_local == IP6:
- raise ConfigError(f'Using an IPv6 address is forbidden for mGRE tunnels such as tunnel {iftype} {ifname}')
-
- # check address family use
- # checks are not enforced (but ip command failing) for backward compatibility
-
- if ipv4_count and not IP4 in kls.ip:
- print(f'Should not use IPv4 addresses on tunnel {iftype} {ifname}')
-
- if ipv6_count and not IP6 in kls.ip:
- print(f'Should not use IPv6 addresses on tunnel {iftype} {ifname}')
-
- # vrf check
- if options['vrf']:
- if options['vrf'] not in options['interfaces']:
- raise ConfigError(f'VRF "{options["vrf"]}" does not exist')
-
- if options['bridge']:
- raise ConfigError((
- f'Interface "{options["ifname"]}" cannot be member of VRF '
- f'"{options["vrf"]}" and bridge {options["bridge"]} '
- f'at the same time!'))
-
- # bridge and address check
- if ( options['bridge']
- and ( options['addresses-add']
- or options['ipv6_autoconf'] ) ):
- raise ConfigError((
- f'Cannot assign address to interface "{options["name"]}" '
- f'as it is a member of bridge "{options["bridge"]}"!'))
-
- # source-interface check
-
- if tun_dev and tun_dev not in options['interfaces']:
- raise ConfigError(f'device "{tun_dev}" does not exist')
-
- # tunnel encapsulation check
-
- convert = {
- (6, 4, 'gre'): 'ip6gre',
- (6, 6, 'gre'): 'ip6gre',
- (4, 6, 'ipip'): 'ipip6',
- (6, 6, 'ipip'): 'ip6ip6',
+ # We need to re-map the tunnel encapsulation proto to a valid interface class
+ encap = tunnel['encapsulation']
+ klass = dispatch[encap]
+
+ # This is a special type of interface which needs additional parameters
+ # when created using iproute2. Instead of passing a ton of arguments,
+ # use a dictionary provided by the interface class which holds all the
+ # options necessary.
+ conf = klass.get_config()
+
+ # Copy/re-assign our dictionary values to values understood by the
+ # derived _Tunnel classes
+ mapping = {
+ # this : get_config()
+ 'local_ip' : 'local',
+ 'remote_ip' : 'remote',
+ 'source_interface' : 'dev',
+ 'parameters.ip.ttl' : 'ttl',
+ 'parameters.ip.tos' : 'tos',
+ 'parameters.ip.key' : 'key',
+ 'parameters.ipv6.encaplimit' : 'encaplimit'
}
- iprotos = []
- if ipv4_count:
- iprotos.append(4)
- if ipv6_count:
- iprotos.append(6)
-
- for iproto in iprotos:
- replace = convert.get((kls.tunnel, iproto, iftype), '')
- if replace:
- raise ConfigError(
- f'Using IPv6 address in local-ip or remote-ip is not possible with "encapsulation {iftype}". ' +
- f'Use "encapsulation {replace}" for tunnel {iftype} {ifname} instead.'
- )
-
- # tunnel options
-
- incompatible = []
- if afi_local == IP6:
- incompatible.extend(['ttl', 'tos', 'key',])
- if afi_local == IP4:
- incompatible.extend(['encaplimit', 'flowlabel', 'hoplimit', 'tclass'])
-
- for option in incompatible:
- if option in options:
- # TODO: raise converted to print as not enforced by vyatta
- # raise ConfigError(f'{option} is not valid for tunnel {iftype} {ifname}')
- print(f'Using "{option}" is invalid for tunnel {iftype} {ifname}')
-
- # duplicate tunnel pairs
-
- pair = '{}-{}'.format(options['local'], options['remote'])
- if options['tunnel'].get(iftype, {}).get(pair, 0) > 1:
- raise ConfigError(f'More than one tunnel configured for with the same encapulation and IPs for tunnel {iftype} {ifname}')
+ # Add additional IPv6 options if tunnel is IPv6 aware
+ if tunnel['encapsulation'] in ['ipip6', 'ip6ip6', 'ip6gre']:
+ mappingv6 = {
+ # this : get_config()
+ 'parameters.ipv6.encaplimit' : 'encaplimit'
+ }
+ mapping.update(mappingv6)
- return None
+ for our_key, their_key in mapping.items():
+ if dict_search(our_key, tunnel) and their_key in conf:
+ conf[their_key] = dict_search(our_key, tunnel)
+ tun = klass(tunnel['ifname'], **conf)
+ tun.change_options()
+ tun.update(tunnel)
-def generate(gre):
return None
-def apply(conf):
- options = conf['options']
- changes = conf['changes']
- actions = conf['actions']
- kls = get_class(options)
-
- # extract ifname as otherwise it is duplicated on the interface creation
- ifname = options.pop('ifname')
-
- # only the valid keys for creation of a Interface
- config = dict((k, options[k]) for k in kls.options if options[k])
-
- # setup or create the tunnel interface if it does not exist
- tunnel = kls(ifname, **config)
-
- if changes['section'] == 'delete':
- tunnel.remove()
- # The perl code was calling/opt/vyatta/sbin/vyatta-tunnel-cleanup
- # which identified tunnels type which were not used anymore to remove them
- # (ie: gre0, gretap0, etc.) The perl code did however nothing
- # This feature is also not implemented yet
- return
-
- # A GRE interface without remote will be mGRE
- # if the interface does not suppor the option, it skips the change
- for option in tunnel.updates:
- if changes['section'] in 'create' and option in tunnel.options:
- # it was setup at creation
- continue
- if not options[option]:
- # remote can be set to '' and it would generate an invalide command
- continue
- tunnel.set_interface(option, options[option])
-
- # set other interface properties
- for option in ('alias', 'mtu', 'link_detect', 'multicast', 'allmulticast',
- 'arp_accept', 'arp_filter', 'arp_announce', 'arp_ignore',
- 'ipv6_accept_ra', 'ipv6_autoconf', 'ipv6_forwarding', 'ipv6_dad_transmits'):
- if not options[option]:
- # should never happen but better safe
- continue
- tunnel.set_interface(option, options[option])
-
- # assign/remove VRF (ONLY when not a member of a bridge,
- # otherwise 'nomaster' removes it from it)
- if not options['bridge']:
- tunnel.set_vrf(options['vrf'])
-
- # Configure interface address(es)
- for addr in options['addresses-del']:
- tunnel.del_addr(addr)
- for addr in options['addresses-add']:
- tunnel.add_addr(addr)
-
- # now bring it up (or not)
- tunnel.set_admin_state(options['state'])
-
-
if __name__ == '__main__':
try:
c = get_config()
- verify(c)
generate(c)
+ verify(c)
apply(c)
except ConfigError as e:
print(e)
diff --git a/src/conf_mode/interfaces-wireguard.py b/src/conf_mode/interfaces-wireguard.py
index 9bda35d0a..7cfc76aa0 100755
--- a/src/conf_mode/interfaces-wireguard.py
+++ b/src/conf_mode/interfaces-wireguard.py
@@ -80,9 +80,6 @@ def verify(wireguard):
raise ConfigError('Wireguard private-key not found! Execute: ' \
'"run generate wireguard [default-keypair|named-keypairs]"')
- if 'address' not in wireguard:
- raise ConfigError('IP address required!')
-
if 'peer' not in wireguard:
raise ConfigError('At least one Wireguard peer is required!')
diff --git a/src/conf_mode/interfaces-wireless.py b/src/conf_mode/interfaces-wireless.py
index 5d723bbfd..d302c7df7 100755
--- a/src/conf_mode/interfaces-wireless.py
+++ b/src/conf_mode/interfaces-wireless.py
@@ -261,7 +261,6 @@ def apply(wifi):
# Assign WiFi instance configuration parameters to config dict
conf['phy'] = wifi['physical_device']
- conf['wds'] = 'on' if 'wds' in wifi else 'off'
# Finally create the new interface
w = WiFiIf(interface, **conf)