summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/interface-types.json19
-rw-r--r--debian/control1
-rw-r--r--interface-definitions/https.xml.in46
-rw-r--r--interface-definitions/service-router-advert.xml.in266
-rw-r--r--python/vyos/ifconfig/__init__.py4
-rw-r--r--python/vyos/ifconfig/bond.py27
-rw-r--r--python/vyos/ifconfig/bridge.py17
-rw-r--r--python/vyos/ifconfig/control.py4
-rw-r--r--python/vyos/ifconfig/dhcp.py266
-rw-r--r--python/vyos/ifconfig/dummy.py8
-rw-r--r--python/vyos/ifconfig/ethernet.py32
-rw-r--r--python/vyos/ifconfig/geneve.py9
-rw-r--r--python/vyos/ifconfig/interface.py282
-rw-r--r--python/vyos/ifconfig/l2tpv3.py14
-rw-r--r--python/vyos/ifconfig/loopback.py11
-rw-r--r--python/vyos/ifconfig/macvlan.py16
-rw-r--r--python/vyos/ifconfig/pppoe.py33
-rw-r--r--python/vyos/ifconfig/register.py96
-rw-r--r--python/vyos/ifconfig/stp.py19
-rw-r--r--python/vyos/ifconfig/tunnel.py8
-rw-r--r--python/vyos/ifconfig/vlan.py43
-rw-r--r--python/vyos/ifconfig/vtun.py34
-rw-r--r--python/vyos/ifconfig/vxlan.py24
-rw-r--r--python/vyos/ifconfig/wireguard.py14
-rw-r--r--python/vyos/ifconfig/wireless.py31
-rw-r--r--python/vyos/ifconfig_vlan.py3
-rw-r--r--python/vyos/interfaces.py36
-rwxr-xr-xsrc/completion/list_interfaces.py33
-rwxr-xr-xsrc/completion/list_openvpn_clients.py4
-rwxr-xr-xsrc/conf_mode/flow_accounting_conf.py9
-rwxr-xr-xsrc/conf_mode/https.py27
-rwxr-xr-xsrc/conf_mode/interfaces-bridge.py6
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py27
-rwxr-xr-xsrc/conf_mode/service-router-advert.py207
-rwxr-xr-xsrc/migration-scripts/dns-forwarding/1-to-210
-rwxr-xr-xsrc/migration-scripts/https/0-to-172
-rwxr-xr-xsrc/migration-scripts/interfaces/5-to-6111
37 files changed, 1410 insertions, 459 deletions
diff --git a/data/interface-types.json b/data/interface-types.json
deleted file mode 100644
index f174d3c39..000000000
--- a/data/interface-types.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "loopback": "lo",
- "dummy": "dum",
- "ethernet": "eth",
- "bonding": "bond",
- "bridge": "br",
- "pseudo-ethernet": "peth",
- "openvpn": "vtun",
- "tunnel": "tun",
- "vti": "vti",
- "l2tpv3": "l2tpeth",
- "vxlan": "vxlan",
- "wireguard": "wg",
- "wireless": "wlan",
- "wirelessmodem": "wlm",
- "input": "ifb",
- "pppoe": "pppoe",
- "geneve": "gnv"
-}
diff --git a/debian/control b/debian/control
index 53c4130d7..366e8df94 100644
--- a/debian/control
+++ b/debian/control
@@ -77,6 +77,7 @@ Depends: python3,
iperf,
iperf3,
frr,
+ radvd,
dbus,
hostapd (>= 0.6.8),
wpasupplicant (>= 0.6.7),
diff --git a/interface-definitions/https.xml.in b/interface-definitions/https.xml.in
index 4f940f7f6..1d986b2b4 100644
--- a/interface-definitions/https.xml.in
+++ b/interface-definitions/https.xml.in
@@ -1,6 +1,7 @@
<?xml version="1.0"?>
<!-- HTTPS configuration -->
<interfaceDefinition>
+ <syntaxVersion component='https' version='1'></syntaxVersion>
<node name="service">
<children>
<node name="https" owner="${vyos_conf_scripts_dir}/https.py">
@@ -9,28 +10,37 @@
<priority>1001</priority>
</properties>
<children>
- <tagNode name="listen-address">
+ <tagNode name="virtual-host">
<properties>
- <help>Addresses to listen for HTTPS requests</help>
- <valueHelp>
- <format>ipv4</format>
- <description>HTTPS IPv4 address</description>
- </valueHelp>
- <valueHelp>
- <format>ipv6</format>
- <description>HTTPS IPv6 address</description>
- </valueHelp>
- <valueHelp>
- <format>'*'</format>
- <description>any</description>
- </valueHelp>
+ <help>Identifier for virtual host</help>
<constraint>
- <validator name="ipv4-address"/>
- <validator name="ipv6-address"/>
- <regex>\*$</regex>
+ <regex>[a-zA-Z0-9-_.:]{1,255}</regex>
</constraint>
+ <constraintErrorMessage>illegal characters in identifier or identifier longer than 255 characters</constraintErrorMessage>
</properties>
<children>
+ <leafNode name="listen-address">
+ <properties>
+ <help>Address to listen for HTTPS requests</help>
+ <valueHelp>
+ <format>ipv4</format>
+ <description>HTTPS IPv4 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>HTTPS IPv6 address</description>
+ </valueHelp>
+ <valueHelp>
+ <format>'*'</format>
+ <description>any</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv4-address"/>
+ <validator name="ipv6-address"/>
+ <regex>\*$</regex>
+ </constraint>
+ </properties>
+ </leafNode>
<leafNode name='listen-port'>
<properties>
<help>Port to listen for HTTPS requests; default 443</help>
@@ -45,7 +55,7 @@
</leafNode>
<leafNode name="server-name">
<properties>
- <help>Server names: exact, wildcard, regex, or '_' (any)</help>
+ <help>Server names: exact, wildcard, or regex</help>
<multi/>
</properties>
</leafNode>
diff --git a/interface-definitions/service-router-advert.xml.in b/interface-definitions/service-router-advert.xml.in
new file mode 100644
index 000000000..bd63b15a3
--- /dev/null
+++ b/interface-definitions/service-router-advert.xml.in
@@ -0,0 +1,266 @@
+<?xml version="1.0"?>
+<interfaceDefinition>
+ <node name="service">
+ <children>
+ <node name="router-advert" owner="${vyos_conf_scripts_dir}/service-router-advert.py">
+ <properties>
+ <help>IPv6 Router Advertisements (RAs) service</help>
+ <priority>900</priority>
+ </properties>
+ <children>
+ <tagNode name="interface">
+ <properties>
+ <help>Interface to send DDNS updates for [REQUIRED]</help>
+ <completionHelp>
+ <script>${vyos_completion_dir}/list_interfaces.py</script>
+ </completionHelp>
+ </properties>
+ <children>
+ <leafNode name="hop-limit">
+ <properties>
+ <help>Set Hop Count field of the IP header for outgoing packets (default: 64)</help>
+ <valueHelp>
+ <format>1-255</format>
+ <description>Value should represent current diameter of the Internet</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Unspecified (by this router)</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-255"/>
+ </constraint>
+ <constraintErrorMessage>Hop count must be between 0 and 255</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="default-lifetime">
+ <properties>
+ <help>Lifetime associated with the default router in units of seconds</help>
+ <valueHelp>
+ <format>4-9000</format>
+ <description>Router Lifetime in seconds</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Not a default router</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 4-9000"/>
+ </constraint>
+ <constraintErrorMessage>Default router livetime bust be 0 or between 4 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="default-preference">
+ <properties>
+ <help>Preference associated with the default router,</help>
+ <completionHelp>
+ <list>low medium high</list>
+ </completionHelp>
+ <valueHelp>
+ <format>low</format>
+ <description>Default router has low preference</description>
+ </valueHelp>
+ <valueHelp>
+ <format>medium</format>
+ <description>Default router has medium preference (default)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>high</format>
+ <description>Default router has high preference</description>
+ </valueHelp>
+ <constraint>
+ <regex>(low|medium|high)</regex>
+ </constraint>
+ <constraintErrorMessage>Default preference must be low, medium or high</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="dnssl">
+ <properties>
+ <help>DNS search list</help>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="link-mtu">
+ <properties>
+ <help>Link MTU value placed in RAs, exluded in RAs if unset</help>
+ <valueHelp>
+ <format>1280-9000</format>
+ <description>Link MTU value in RAs</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 1280-9000"/>
+ </constraint>
+ <constraintErrorMessage>Link MTU must be between 1280 and 9000</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="managed-flag">
+ <properties>
+ <help>Hosts use the administered (stateful) protocol for address autoconfiguration in addition to any addresses autoconfigured using SLAAC</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <node name="interval">
+ <properties>
+ <help>Set interval between unsolicited multicast RAs</help>
+ </properties>
+ <children>
+ <leafNode name="max">
+ <properties>
+ <help>Maximum interval between unsolicited multicast RAs (default: 600)</help>
+ <valueHelp>
+ <format>4-1800</format>
+ <description>Maximum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 4-1800"/>
+ </constraint>
+ <constraintErrorMessage>Maximum interval must be between 4 and 1800 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="min">
+ <properties>
+ <help>Minimum interval between unsolicited multicast RAs</help>
+ <valueHelp>
+ <format>3-1350</format>
+ <description>Minimum interval in seconds</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 3-1350"/>
+ </constraint>
+ <constraintErrorMessage>Minimum interval must be between 3 and 1350 seconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ </children>
+ </node>
+ <leafNode name="name-server">
+ <properties>
+ <help>IPv6 address of recursive DNS server</help>
+ <valueHelp>
+ <format>ipv6</format>
+ <description>IPv6 address of DNS name server</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-address"/>
+ </constraint>
+ <multi/>
+ </properties>
+ </leafNode>
+ <leafNode name="other-config-flag">
+ <properties>
+ <help>Hosts use the administered (stateful) protocol for autoconfiguration of other (non-address) information</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <tagNode name="prefix">
+ <properties>
+ <help>IPv6 prefix to be advertised in Router Advertisements (RAs)</help>
+ <valueHelp>
+ <format>ipv6net</format>
+ <description>IPv6 prefix to be advertized</description>
+ </valueHelp>
+ <constraint>
+ <validator name="ipv6-prefix"/>
+ </constraint>
+ </properties>
+ <children>
+ <leafNode name="no-autonomous-flag">
+ <properties>
+ <help>Prefix can not be used for stateless address auto-configuration</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="no-on-link-flag">
+ <properties>
+ <help>Prefix can not be used for on-link determination</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ <leafNode name="preferred-lifetime">
+ <properties>
+ <help>Time in seconds that the prefix will remain preferred (default 4 hours)</help>
+ <completionHelp>
+ <list>infinity</list>
+ </completionHelp>
+ <valueHelp>
+ <format>0-4294967295</format>
+ <description>Time in seconds that the prefix will remain preferred</description>
+ </valueHelp>
+ <valueHelp>
+ <format>infinity</format>
+ <description>Prefix will remain preferred forever</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>(infinity)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ <leafNode name="valid-lifetime">
+ <properties>
+ <help>Time in seconds that the prefix will remain valid (default: 30 days)</help>
+ <completionHelp>
+ <list>infinity</list>
+ </completionHelp>
+ <valueHelp>
+ <format>1-4294967295</format>
+ <description>Time in seconds that the prefix will remain valid</description>
+ </valueHelp>
+ <valueHelp>
+ <format>infinity</format>
+ <description>Prefix will remain preferred forever</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-4294967295"/>
+ <regex>(infinity)</regex>
+ </constraint>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ <leafNode name="reachable-time">
+ <properties>
+ <help>Time, in milliseconds, that a node assumes a neighbor is reachable after having received a reachability confirmation</help>
+ <valueHelp>
+ <format>1-3600000</format>
+ <description>Reachable Time value in RAs (in milliseconds)</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Reachable Time unspecified by this router</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 1-3600000"/>
+ </constraint>
+ <constraintErrorMessage>Reachable time must be 0 or between 1 and 3600000 milliseconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="retrans-timer">
+ <properties>
+ <help>Time in milliseconds between retransmitted Neighbor Solicitation messages</help>
+ <valueHelp>
+ <format>1-4294967295</format>
+ <description>Minimum interval in milliseconds</description>
+ </valueHelp>
+ <valueHelp>
+ <format>0</format>
+ <description>Time, in milliseconds, between retransmitted Neighbor Solicitation messages</description>
+ </valueHelp>
+ <constraint>
+ <validator name="numeric" argument="--range 0-0 --range 1-4294967295"/>
+ </constraint>
+ <constraintErrorMessage>Retransmit interval must be 0 or between 1 and 4294967295 milliseconds</constraintErrorMessage>
+ </properties>
+ </leafNode>
+ <leafNode name="no-send-advert">
+ <properties>
+ <help>Do not send router adverts</help>
+ <valueless/>
+ </properties>
+ </leafNode>
+ </children>
+ </tagNode>
+ </children>
+ </node>
+ </children>
+ </node>
+</interfaceDefinition>
diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py
index 16c29a704..d08a8b528 100644
--- a/python/vyos/ifconfig/__init__.py
+++ b/python/vyos/ifconfig/__init__.py
@@ -23,10 +23,10 @@ from vyos.ifconfig.ethernet import EthernetIf
from vyos.ifconfig.geneve import GeneveIf
from vyos.ifconfig.loopback import LoopbackIf
from vyos.ifconfig.macvlan import MACVLANIf
-from vyos.ifconfig.stp import STPIf
-from vyos.ifconfig.vlan import VLANIf
from vyos.ifconfig.vxlan import VXLANIf
from vyos.ifconfig.wireguard import WireGuardIf
+from vyos.ifconfig.vtun import VTunIf
+from vyos.ifconfig.pppoe import PPPoEIf
from vyos.ifconfig.tunnel import GREIf
from vyos.ifconfig.tunnel import GRETapIf
from vyos.ifconfig.tunnel import IP6GREIf
diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py
index c9dac891f..3c26b9b95 100644
--- a/python/vyos/ifconfig/bond.py
+++ b/python/vyos/ifconfig/bond.py
@@ -16,12 +16,14 @@
import os
from vyos.ifconfig.interface import Interface
-from vyos.ifconfig.vlan import VLANIf
+from vyos.ifconfig.vlan import VLAN
from vyos.validate import *
-class BondIf(VLANIf):
+@Interface.register
+@VLAN.enable
+class BondIf(Interface):
"""
The Linux bonding driver provides a method for aggregating multiple network
interfaces into a single logical "bonded" interface. The behavior of the
@@ -30,7 +32,20 @@ class BondIf(VLANIf):
monitoring may be performed.
"""
- _sysfs_set = {**VLANIf._sysfs_set, **{
+ default = {
+ 'type': 'bond',
+ }
+ definition = {
+ **Interface.definition,
+ ** {
+ 'section': 'bonding',
+ 'prefixes': ['bond', ],
+ 'broadcast': True,
+ 'bridgeable': True,
+ },
+ }
+
+ _sysfs_set = {**Interface._sysfs_set, **{
'bond_hash_policy': {
'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']),
'location': '/sys/class/net/{ifname}/bonding/xmit_hash_policy',
@@ -63,16 +78,12 @@ class BondIf(VLANIf):
},
}}
- _sysfs_get = {**VLANIf._sysfs_get, **{
+ _sysfs_get = {**Interface._sysfs_get, **{
'bond_arp_ip_target': {
'location': '/sys/class/net/{ifname}/bonding/arp_ip_target',
}
}}
- default = {
- 'type': 'bond',
- }
-
def remove(self):
"""
Remove interface from operating system. Removing the interface
diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py
index 90c44af13..94b0075d8 100644
--- a/python/vyos/ifconfig/bridge.py
+++ b/python/vyos/ifconfig/bridge.py
@@ -18,6 +18,8 @@ from vyos.ifconfig.interface import Interface
from vyos.validate import *
+
+@Interface.register
class BridgeIf(Interface):
"""
A bridge is a way to connect two Ethernet segments together in a protocol
@@ -28,6 +30,18 @@ class BridgeIf(Interface):
The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard.
"""
+ default = {
+ 'type': 'bridge',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'bridge',
+ 'prefixes': ['br', ],
+ 'broadcast': True,
+ },
+ }
+
_sysfs_set = {**Interface._sysfs_set, **{
'ageing_time': {
'validate': assert_positive,
@@ -72,9 +86,6 @@ class BridgeIf(Interface):
},
}}
- default = {
- 'type': 'bridge',
- }
def set_ageing_time(self, time):
"""
diff --git a/python/vyos/ifconfig/control.py b/python/vyos/ifconfig/control.py
index 89deba40a..28adc80d1 100644
--- a/python/vyos/ifconfig/control.py
+++ b/python/vyos/ifconfig/control.py
@@ -17,8 +17,10 @@
import os
from subprocess import Popen, PIPE, STDOUT
+from vyos.ifconfig.register import Register
-class Control:
+
+class Control(Register):
_command_get = {}
_command_set = {}
diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py
new file mode 100644
index 000000000..8d3653433
--- /dev/null
+++ b/python/vyos/ifconfig/dhcp.py
@@ -0,0 +1,266 @@
+# 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/>.
+
+import os
+import jinja2
+
+from vyos.ifconfig.control import Control
+
+template_v4 = """
+# generated by ifconfig.py
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+timeout 60;
+retry 300;
+
+interface "{{ intf }}" {
+ send host-name "{{ hostname }}";
+ {% if client_id -%}
+ send dhcp-client-identifier "{{ client_id }}";
+ {% endif -%}
+ {% if vendor_class_id -%}
+ send vendor-class-identifier "{{ vendor_class_id }}";
+ {% endif -%}
+ request subnet-mask, broadcast-address, routers, domain-name-servers,
+ rfc3442-classless-static-routes, domain-name, interface-mtu;
+ require subnet-mask;
+}
+
+"""
+
+template_v6 = """
+# generated by ifconfig.py
+interface "{{ intf }}" {
+ request routers, domain-name-servers, domain-name;
+}
+
+"""
+
+class DHCP (Control):
+ client_base = r'/var/lib/dhcp/dhclient_'
+
+ def __init__ (self, ifname):
+ # per interface DHCP config files
+ self._dhcp = {
+ 4: {
+ 'ifname': ifname,
+ 'conf': self.client_base + ifname + '.conf',
+ 'pid': self.client_base + ifname + '.pid',
+ 'lease': self.client_base + ifname + '.leases',
+ 'options': {
+ 'intf': ifname,
+ 'hostname': '',
+ 'client_id': '',
+ 'vendor_class_id': ''
+ },
+ },
+ 6: {
+ 'ifname': ifname,
+ 'conf': self.client_base + ifname + '.v6conf',
+ 'pid': self.client_base + ifname + '.v6pid',
+ 'lease': self.client_base + ifname + '.v6leases',
+ 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
+ 'options': {
+ 'intf': ifname,
+ 'dhcpv6_prm_only': False,
+ 'dhcpv6_temporary': False
+ },
+ },
+ }
+
+ def get_dhcp_options(self):
+ """
+ Return dictionary with supported DHCP options.
+
+ Dictionary should be altered and send back via set_dhcp_options()
+ so those options are applied when DHCP is run.
+ """
+ return self._dhcp[4]['options']
+
+ def set_dhcp_options(self, options):
+ """
+ Store new DHCP options used by next run of DHCP client.
+ """
+ self._dhcp[4]['options'] = options
+
+ def get_dhcpv6_options(self):
+ """
+ Return dictionary with supported DHCPv6 options.
+
+ Dictionary should be altered and send back via set_dhcp_options()
+ so those options are applied when DHCP is run.
+ """
+ return self._dhcp[6]['options']
+
+ def set_dhcpv6_options(self, options):
+ """
+ Store new DHCP options used by next run of DHCP client.
+ """
+ self._dhcp[6]['options'] = options
+
+ # replace dhcpv4/v6 with systemd.networkd?
+ def _set_dhcp(self):
+ """
+ Configure interface as DHCP client. The dhclient binary is automatically
+ started in background!
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcp()
+ """
+
+ dhcp = self.get_dhcp_options()
+ if not dhcp['hostname']:
+ # read configured system hostname.
+ # maybe change to vyos hostd client ???
+ with open('/etc/hostname', 'r') as f:
+ dhcp['hostname'] = f.read().rstrip('\n')
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(template_v4)
+ dhcp_text = tmpl.render(dhcp)
+ with open(self._dhcp[4]['conf'], 'w') as f:
+ f.write(dhcp_text)
+
+ cmd = 'start-stop-daemon'
+ cmd += ' --start'
+ cmd += ' --oknodo'
+ cmd += ' --quiet'
+ cmd += ' --pidfile {pid}'
+ cmd += ' --exec /sbin/dhclient'
+ cmd += ' --'
+ # now pass arguments to dhclient binary
+ cmd += ' -4 -nw -cf {conf} -pf {pid} -lf {lease} {ifname}'
+ return self._cmd(cmd.format(**self._dhcp[4]))
+
+ def _del_dhcp(self):
+ """
+ De-configure interface as DHCP clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcp()
+ """
+ if not os.path.isfile(self._dhcp[4]['pid']):
+ self._debug_msg('No DHCP client PID found')
+ return None
+
+ # with open(self._dhcp[4]['pid'], 'r') as f:
+ # pid = int(f.read())
+
+ # stop dhclient, we need to call dhclient and tell it should release the
+ # aquired IP address. tcpdump tells me:
+ # 172.16.35.103.68 > 172.16.35.254.67: [bad udp cksum 0xa0cb -> 0xb943!] BOOTP/DHCP, Request from 00:50:56:9d:11:df, length 300, xid 0x620e6946, Flags [none] (0x0000)
+ # Client-IP 172.16.35.103
+ # Client-Ethernet-Address 00:50:56:9d:11:df
+ # Vendor-rfc1048 Extensions
+ # Magic Cookie 0x63825363
+ # DHCP-Message Option 53, length 1: Release
+ # Server-ID Option 54, length 4: 172.16.35.254
+ # Hostname Option 12, length 10: "vyos"
+ #
+ cmd = '/sbin/dhclient -cf {conf} -pf {pid} -lf {lease} -r {ifname}'
+ self._cmd(cmd.format(**self._dhcp[4]))
+
+ # cleanup old config files
+ for name in ('conf', 'pid', 'lease'):
+ if os.path.isfile(self._dhcp[4][name]):
+ os.remove(self._dhcp[4][name])
+
+ def _set_dhcpv6(self):
+ """
+ Configure interface as DHCPv6 client. The dhclient binary is automatically
+ started in background!
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.set_dhcpv6()
+ """
+ dhcpv6 = self.get_dhcpv6_options()
+
+ # better save then sorry .. should be checked in interface script
+ # but if you missed it we are safe!
+ if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']:
+ raise Exception(
+ 'DHCPv6 temporary and parameters-only options are mutually exclusive!')
+
+ # render DHCP configuration
+ tmpl = jinja2.Template(template_v6)
+ dhcpv6_text = tmpl.render(dhcpv6)
+ with open(self._dhcp[6]['conf'], 'w') as f:
+ f.write(dhcpv6_text)
+
+ # no longer accept router announcements on this interface
+ self._write_sysfs(self._dhcp[6]['accept_ra'], 0)
+
+ # assemble command-line to start DHCPv6 client (dhclient)
+ cmd = 'start-stop-daemon'
+ cmd += ' --start'
+ cmd += ' --oknodo'
+ cmd += ' --quiet'
+ cmd += ' --pidfile {pid}'
+ cmd += ' --exec /sbin/dhclient'
+ cmd += ' --'
+ # now pass arguments to dhclient binary
+ cmd += ' -6 -nw -cf {conf} -pf {pid} -lf {lease}'
+ # add optional arguments
+ if dhcpv6['dhcpv6_prm_only']:
+ cmd += ' -S'
+ if dhcpv6['dhcpv6_temporary']:
+ cmd += ' -T'
+ cmd += ' {ifname}'
+
+ return self._cmd(cmd.format(**self._dhcp[6]))
+
+ def _del_dhcpv6(self):
+ """
+ De-configure interface as DHCPv6 clinet. All auto generated files like
+ pid, config and lease will be removed.
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> j = Interface('eth0')
+ >>> j.del_dhcpv6()
+ """
+ if not os.path.isfile(self._dhcp[6]['pid']):
+ self._debug_msg('No DHCPv6 client PID found')
+ return None
+
+ # with open(self._dhcp[6]['pid'], 'r') as f:
+ # pid = int(f.read())
+
+ # stop dhclient
+ cmd = 'start-stop-daemon'
+ cmd += ' --start'
+ cmd += ' --oknodo'
+ cmd += ' --quiet'
+ cmd += ' --pidfile {pid}'
+ self._cmd(cmd.format(**self._dhcp[6]))
+
+ # accept router announcements on this interface
+ self._write_sysfs(self._dhcp[6]['accept_ra'], 1)
+
+ # cleanup old config files
+ for name in ('conf', 'pid', 'lease'):
+ if os.path.isfile(self._dhcp[6][name]):
+ os.remove(self._dhcp[6][name])
+
diff --git a/python/vyos/ifconfig/dummy.py b/python/vyos/ifconfig/dummy.py
index 58b89fe68..404c490c7 100644
--- a/python/vyos/ifconfig/dummy.py
+++ b/python/vyos/ifconfig/dummy.py
@@ -17,6 +17,7 @@
from vyos.ifconfig.interface import Interface
+@Interface.register
class DummyIf(Interface):
"""
A dummy interface is entirely virtual like, for example, the loopback
@@ -27,3 +28,10 @@ class DummyIf(Interface):
default = {
'type': 'dummy',
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'dummy',
+ 'prefixes': ['dum', ],
+ },
+ }
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 30e3a3bef..b3e652409 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -16,17 +16,35 @@
import os
import re
-from vyos.ifconfig.vlan import VLANIf
+from vyos.ifconfig.interface import Interface
+from vyos.ifconfig.vlan import VLAN
from vyos.validate import *
-class EthernetIf(VLANIf):
+@Interface.register
+@VLAN.enable
+class EthernetIf(Interface):
"""
Abstraction of a Linux Ethernet Interface
"""
- _command_set = {**VLANIf._command_set, **{
+ default = {
+ 'type': 'ethernet',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'ethernet',
+ 'prefixes': ['lan', 'eth', 'eno', 'ens', 'enp', 'enx'],
+ 'bondable': True,
+ 'broadcast': True,
+ 'bridgeable': True,
+ }
+ }
+
+
+ _command_set = {**Interface._command_set, **{
'gro': {
'validate': lambda v: assert_list(v, ['on', 'off']),
'shellcmd': '/sbin/ethtool -K {ifname} gro {value}',
@@ -49,10 +67,6 @@ class EthernetIf(VLANIf):
},
}}
- default = {
- 'type': 'ethernet',
- }
-
def _delete(self):
# Ethernet interfaces can not be removed
pass
@@ -90,7 +104,7 @@ class EthernetIf(VLANIf):
if enable not in ['on', 'off']:
raise ValueError("Value out of range")
- if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']:
self._debug_msg('{} driver does not support changing flow control settings!'
.format(self.get_driver_name()))
return
@@ -142,7 +156,7 @@ class EthernetIf(VLANIf):
if duplex not in ['auto', 'full', 'half']:
raise ValueError("Value out of range (duplex)")
- if self.get_driver_name() in ['vmxnet3', 'virtio_net']:
+ if self.get_driver_name() in ['vmxnet3', 'virtio_net', 'xen_netfront']:
self._debug_msg('{} driver does not support changing speed/duplex settings!'
.format(self.get_driver_name()))
return
diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py
index a3b3a4c4a..f27786417 100644
--- a/python/vyos/ifconfig/geneve.py
+++ b/python/vyos/ifconfig/geneve.py
@@ -18,6 +18,7 @@ from copy import deepcopy
from vyos.ifconfig.interface import Interface
+@Interface.register
class GeneveIf(Interface):
"""
Geneve: Generic Network Virtualization Encapsulation
@@ -34,6 +35,14 @@ class GeneveIf(Interface):
'vni': 0,
'remote': '',
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'geneve',
+ 'prefixes': ['gnv', ],
+ 'bridgeable': True,
+ }
+ }
def _create(self):
cmd = 'ip link add name {ifname} type geneve id {vni} remote {remote}'.format(**self.config)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 4f72271c9..f2b43fd35 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -15,14 +15,11 @@
import os
import re
-import jinja2
import json
import glob
import time
from copy import deepcopy
-import vyos.interfaces
-
from vyos.validate import * # should not * include
from vyos.config import Config # not used anymore
from vyos import ConfigError
@@ -35,46 +32,22 @@ from tabulate import tabulate
from hurry.filesize import size,alternative
from datetime import timedelta
-from vyos.ifconfig.control import Control
-
-dhclient_base = r'/var/lib/dhcp/dhclient_'
-dhcp_cfg = """
-# generated by ifconfig.py
-option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
-timeout 60;
-retry 300;
-
-interface "{{ intf }}" {
- send host-name "{{ hostname }}";
- {% if client_id -%}
- send dhcp-client-identifier "{{ client_id }}";
- {% endif -%}
- {% if vendor_class_id -%}
- send vendor-class-identifier "{{ vendor_class_id }}";
- {% endif -%}
- request subnet-mask, broadcast-address, routers, domain-name-servers,
- rfc3442-classless-static-routes, domain-name, interface-mtu;
- require subnet-mask;
-}
-
-"""
-
-dhcpv6_cfg = """
-# generated by ifconfig.py
-interface "{{ intf }}" {
- request routers, domain-name-servers, domain-name;
-}
+from vyos.ifconfig.dhcp import DHCP
-"""
-
-
-
-class Interface(Control):
+class Interface(DHCP):
options = []
required = []
default = {
'type': '',
}
+ definition = {
+ 'section': '',
+ 'prefixes': [],
+ 'vlan': False,
+ 'bondable': False,
+ 'broadcast': False,
+ 'bridgeable': False,
+ }
_command_set = {
'state': {
@@ -165,6 +138,8 @@ class Interface(Control):
>>> i = Interface('eth0')
"""
+ DHCP.__init__(self, ifname)
+
self.config = deepcopy(self.default)
self.config['ifname'] = ifname
@@ -183,31 +158,6 @@ class Interface(Control):
self._create()
- # per interface DHCP config files
- self._dhcp_cfg_file = dhclient_base + self.config['ifname'] + '.conf'
- self._dhcp_pid_file = dhclient_base + self.config['ifname'] + '.pid'
- self._dhcp_lease_file = dhclient_base + self.config['ifname'] + '.leases'
-
- # per interface DHCPv6 config files
- self._dhcpv6_cfg_file = dhclient_base + self.config['ifname'] + '.v6conf'
- self._dhcpv6_pid_file = dhclient_base + self.config['ifname'] + '.v6pid'
- self._dhcpv6_lease_file = dhclient_base + self.config['ifname'] + '.v6leases'
-
- # DHCP options
- self._dhcp_options = {
- 'intf' : self.config['ifname'],
- 'hostname' : '',
- 'client_id' : '',
- 'vendor_class_id' : ''
- }
-
- # DHCPv6 options
- self._dhcpv6_options = {
- 'intf' : self.config['ifname'],
- 'dhcpv6_prm_only' : False,
- 'dhcpv6_temporary' : False
- }
-
# list of assigned IP addresses
self._addr = []
@@ -623,214 +573,6 @@ class Interface(Control):
cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname'])
return self._cmd(cmd)
-
- def get_dhcp_options(self):
- """
- Return dictionary with supported DHCP options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcp_options
-
- def set_dhcp_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcp_options = options
-
- def get_dhcpv6_options(self):
- """
- Return dictionary with supported DHCPv6 options.
-
- Dictionary should be altered and send back via set_dhcp_options()
- so those options are applied when DHCP is run.
- """
- return self._dhcpv6_options
-
- def set_dhcpv6_options(self, options):
- """
- Store new DHCP options used by next run of DHCP client.
- """
- self._dhcpv6_options = options
-
- # replace dhcpv4/v6 with systemd.networkd?
- def _set_dhcp(self):
- """
- Configure interface as DHCP client. The dhclient binary is automatically
- started in background!
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.set_dhcp()
- """
-
- dhcp = self.get_dhcp_options()
- if not dhcp['hostname']:
- # read configured system hostname.
- # maybe change to vyos hostd client ???
- with open('/etc/hostname', 'r') as f:
- dhcp['hostname'] = f.read().rstrip('\n')
-
- # render DHCP configuration
- tmpl = jinja2.Template(dhcp_cfg)
- dhcp_text = tmpl.render(dhcp)
- with open(self._dhcp_cfg_file, 'w') as f:
- f.write(dhcp_text)
-
- cmd = 'start-stop-daemon'
- cmd += ' --start '
- cmd += ' --quiet'
- cmd += ' --oknodo'
- cmd += ' --pidfile ' + self._dhcp_pid_file
- cmd += ' --exec /sbin/dhclient --'
- # now pass arguments to dhclient binary
- cmd += ' -4 -nw -cf {} -pf {} -lf {} {}'.format(
- self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
- return self._cmd(cmd)
-
-
- def _del_dhcp(self):
- """
- De-configure interface as DHCP clinet. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.del_dhcp()
- """
- pid = 0
- if os.path.isfile(self._dhcp_pid_file):
- with open(self._dhcp_pid_file, 'r') as f:
- pid = int(f.read())
- else:
- self._debug_msg('No DHCP client PID found')
- return None
-
- # stop dhclient, we need to call dhclient and tell it should release the
- # aquired IP address. tcpdump tells me:
- # 172.16.35.103.68 > 172.16.35.254.67: [bad udp cksum 0xa0cb -> 0xb943!] BOOTP/DHCP, Request from 00:50:56:9d:11:df, length 300, xid 0x620e6946, Flags [none] (0x0000)
- # Client-IP 172.16.35.103
- # Client-Ethernet-Address 00:50:56:9d:11:df
- # Vendor-rfc1048 Extensions
- # Magic Cookie 0x63825363
- # DHCP-Message Option 53, length 1: Release
- # Server-ID Option 54, length 4: 172.16.35.254
- # Hostname Option 12, length 10: "vyos"
- #
- cmd = '/sbin/dhclient -cf {} -pf {} -lf {} -r {}'.format(
- self._dhcp_cfg_file, self._dhcp_pid_file, self._dhcp_lease_file, self.config['ifname'])
- self._cmd(cmd)
-
- # cleanup old config file
- if os.path.isfile(self._dhcp_cfg_file):
- os.remove(self._dhcp_cfg_file)
-
- # cleanup old pid file
- if os.path.isfile(self._dhcp_pid_file):
- os.remove(self._dhcp_pid_file)
-
- # cleanup old lease file
- if os.path.isfile(self._dhcp_lease_file):
- os.remove(self._dhcp_lease_file)
-
-
- def _set_dhcpv6(self):
- """
- Configure interface as DHCPv6 client. The dhclient binary is automatically
- started in background!
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.set_dhcpv6()
- """
- dhcpv6 = self.get_dhcpv6_options()
-
- # better save then sorry .. should be checked in interface script
- # but if you missed it we are safe!
- if dhcpv6['dhcpv6_prm_only'] and dhcpv6['dhcpv6_temporary']:
- raise Exception('DHCPv6 temporary and parameters-only options are mutually exclusive!')
-
- # render DHCP configuration
- tmpl = jinja2.Template(dhcpv6_cfg)
- dhcpv6_text = tmpl.render(dhcpv6)
- with open(self._dhcpv6_cfg_file, 'w') as f:
- f.write(dhcpv6_text)
-
- # no longer accept router announcements on this interface
- self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
- .format(self.config['ifname']), 0)
-
- # assemble command-line to start DHCPv6 client (dhclient)
- cmd = 'start-stop-daemon'
- cmd += ' --start '
- cmd += ' --quiet'
- cmd += ' --oknodo'
- cmd += ' --pidfile ' + self._dhcpv6_pid_file
- cmd += ' --exec /sbin/dhclient --'
- # now pass arguments to dhclient binary
- cmd += ' -6 -nw -cf {} -pf {} -lf {}'.format(
- self._dhcpv6_cfg_file, self._dhcpv6_pid_file, self._dhcpv6_lease_file)
-
- # add optional arguments
- if dhcpv6['dhcpv6_prm_only']:
- cmd += ' -S'
- if dhcpv6['dhcpv6_temporary']:
- cmd += ' -T'
-
- cmd += ' {}'.format(self.config['ifname'])
- return self._cmd(cmd)
-
-
- def _del_dhcpv6(self):
- """
- De-configure interface as DHCPv6 clinet. All auto generated files like
- pid, config and lease will be removed.
-
- Example:
-
- >>> from vyos.ifconfig import Interface
- >>> j = Interface('eth0')
- >>> j.del_dhcpv6()
- """
- pid = 0
- if os.path.isfile(self._dhcpv6_pid_file):
- with open(self._dhcpv6_pid_file, 'r') as f:
- pid = int(f.read())
- else:
- self._debug_msg('No DHCPv6 client PID found')
- return None
-
- # stop dhclient
- cmd = 'start-stop-daemon'
- cmd += ' --stop'
- cmd += ' --oknodo'
- cmd += ' --quiet'
- cmd += ' --pidfile ' + self._dhcpv6_pid_file
- self._cmd(cmd)
-
- # accept router announcements on this interface
- self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra'
- .format(self.config['ifname']), 1)
-
- # cleanup old config file
- if os.path.isfile(self._dhcpv6_cfg_file):
- os.remove(self._dhcpv6_cfg_file)
-
- # cleanup old pid file
- if os.path.isfile(self._dhcpv6_pid_file):
- os.remove(self._dhcpv6_pid_file)
-
- # cleanup old lease file
- if os.path.isfile(self._dhcpv6_lease_file):
- os.remove(self._dhcpv6_lease_file)
-
def op_show_interface_stats(self):
stats = self.get_interface_stats()
rx = [['bytes','packets','errors','dropped','overrun','mcast'],[stats['rx_bytes'],stats['rx_packets'],stats['rx_errors'],stats['rx_dropped'],stats['rx_over_errors'],stats['multicast']]]
diff --git a/python/vyos/ifconfig/l2tpv3.py b/python/vyos/ifconfig/l2tpv3.py
index a87535277..fbfab4c6e 100644
--- a/python/vyos/ifconfig/l2tpv3.py
+++ b/python/vyos/ifconfig/l2tpv3.py
@@ -19,6 +19,7 @@ import os
from vyos.ifconfig.interface import Interface
+@Interface.register
class L2TPv3If(Interface):
"""
The Linux bonding driver provides a method for aggregating multiple network
@@ -28,12 +29,19 @@ class L2TPv3If(Interface):
monitoring may be performed.
"""
- options = Interface.options + \
- ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port',
- 'encapsulation', 'local_address', 'remote_address']
default = {
'type': 'l2tp',
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'l2tpeth',
+ 'prefixes': ['l2tpeth', ],
+ 'bridgeable': True,
+ }
+ }
+ options = Interface.options + \
+ ['tunnel_id', 'peer_tunnel_id', 'local_port', 'remote_port', 'encapsulation', 'local_address', 'remote_address']
def _create(self):
# create tunnel interface
diff --git a/python/vyos/ifconfig/loopback.py b/python/vyos/ifconfig/loopback.py
index 37b8e9e3b..8e4438662 100644
--- a/python/vyos/ifconfig/loopback.py
+++ b/python/vyos/ifconfig/loopback.py
@@ -17,6 +17,7 @@
from vyos.ifconfig.interface import Interface
+@Interface.register
class LoopbackIf(Interface):
"""
The loopback device is a special, virtual network interface that your router
@@ -26,6 +27,16 @@ class LoopbackIf(Interface):
default = {
'type': 'loopback',
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'loopback',
+ 'prefixes': ['lo', ],
+ 'bridgeable': True,
+ }
+ }
+
+ name = 'loopback'
def remove(self):
"""
diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py
index da3beea8b..4e4b563a1 100644
--- a/python/vyos/ifconfig/macvlan.py
+++ b/python/vyos/ifconfig/macvlan.py
@@ -14,18 +14,28 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
-from vyos.ifconfig.vlan import VLANIf
+from vyos.ifconfig.interface import Interface
+from vyos.ifconfig.vlan import VLAN
-class MACVLANIf(VLANIf):
+@Interface.register
+@VLAN.enable
+class MACVLANIf(Interface):
"""
Abstraction of a Linux MACvlan interface
"""
- options = VLANIf.options + ['link', 'mode']
default = {
'type': 'macvlan',
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'pseudo-ethernet',
+ 'prefixes': ['peth', ],
+ },
+ }
+ options = Interface.options + ['link', 'mode']
def _create(self):
cmd = 'ip link add {ifname} link {link} type macvlan mode {mode}'.format(
diff --git a/python/vyos/ifconfig/pppoe.py b/python/vyos/ifconfig/pppoe.py
new file mode 100644
index 000000000..7504408cf
--- /dev/null
+++ b/python/vyos/ifconfig/pppoe.py
@@ -0,0 +1,33 @@
+# 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 vyos.ifconfig.interface import Interface
+
+
+@Interface.register
+class PPPoEIf(Interface):
+ default = {
+ 'type': 'pppoe',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'pppoe',
+ 'prefixes': ['pppoe', ],
+ },
+ }
+
+ # The _create and _delete need to be moved from interface-ppoe to here
diff --git a/python/vyos/ifconfig/register.py b/python/vyos/ifconfig/register.py
new file mode 100644
index 000000000..2d4b0d4c0
--- /dev/null
+++ b/python/vyos/ifconfig/register.py
@@ -0,0 +1,96 @@
+# 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/>.
+
+import netifaces
+
+
+class Register:
+ # the known interface prefixes
+ _prefixes = {}
+
+ # class need to define: definition['prefixes']
+ # the interface prefixes declared by a class used to name interface with
+ # prefix[0-9]*(\.[0-9]+)?(\.[0-9]+)?, such as lo, eth0 or eth0.1.2
+
+ @classmethod
+ def register(cls, klass):
+ if not klass.definition.get('prefixes',[]):
+ raise RuntimeError(f'valid interface prefixes not defined for {klass.__name__}')
+
+ for ifprefix in klass.definition['prefixes']:
+ if ifprefix in cls._prefixes:
+ raise RuntimeError(f'only one class can be registered for prefix "{ifprefix}" type')
+ cls._prefixes[ifprefix] = klass
+
+ return klass
+
+ @classmethod
+ def _basename (cls, name, vlan):
+ # remove number from interface name
+ name = name.rstrip('0123456789')
+ name = name.rstrip('.')
+ if vlan:
+ name = name.rstrip('0123456789')
+ return name
+
+ @classmethod
+ def section(cls, name, vlan=True):
+ # return the name of a section an interface should be under
+ name = cls._basename(name, vlan)
+
+ # XXX: To leave as long as vti and input are not moved to vyos
+ if name == 'vti':
+ return 'vti'
+ if name == 'ifb':
+ return 'input'
+
+ if name in cls._prefixes:
+ return cls._prefixes[name].defintion['section']
+ return ''
+
+ @classmethod
+ def klass(cls, name, vlan=True):
+ name = cls._basename(name, vlan)
+ if name in cls._prefixes:
+ return cls._prefixes[name]
+ raise ValueError(f'No type found for interface name: {name}')
+
+ @classmethod
+ def _listing (cls):
+ interfaces = netifaces.interfaces()
+
+ for ifname in interfaces:
+ if '@' in ifname:
+ # Tunnels: sit0@NONE, gre0@NONE, gretap0@NONE, erspan0@NONE, tunl0@NONE, ip6tnl0@NONE, ip6gre0@NONE
+ continue
+
+ # XXX: Temporary hack as vti and input are not yet moved from vyatta to vyos
+ if ifname.startswith('vti') or ifname.startswith('input'):
+ yield ifname
+ continue
+
+ if not cls.section(ifname):
+ continue
+ yield ifname
+
+ @classmethod
+ def listing(cls, section=''):
+ if not section:
+ return list(cls._listing())
+ return [_ for _ in cls._listing() if cls._basename(_,False) in self.prefixes]
+
+
+# XXX: TODO - limit name for VRF interfaces
+
diff --git a/python/vyos/ifconfig/stp.py b/python/vyos/ifconfig/stp.py
index 741322d0d..97a3c1ff3 100644
--- a/python/vyos/ifconfig/stp.py
+++ b/python/vyos/ifconfig/stp.py
@@ -19,12 +19,20 @@ from vyos.ifconfig.interface import Interface
from vyos.validate import *
-class STPIf(Interface):
+class STP:
"""
A spanning-tree capable interface. This applies only to bridge port member
interfaces!
"""
- _sysfs_set = {**Interface._sysfs_set, **{
+
+ @classmethod
+ def enable (cls, adaptee):
+ adaptee._sysfs_set = {**adaptee._sysfs_set, **cls._sysfs_set}
+ adaptee.set_path_cost = cls.set_path_cost
+ adaptee.set_path_priority = cls.set_path_priority
+ return adaptee
+
+ _sysfs_set = {
'path_cost': {
# XXX: we should set a maximum
'validate': assert_positive,
@@ -37,15 +45,8 @@ class STPIf(Interface):
'location': '/sys/class/net/{ifname}/brport/priority',
'errormsg': '{ifname} is not a bridge port member'
},
- }}
-
- default = {
- 'type': 'stp',
}
- def __init__(self, ifname, **kargs):
- super().__init__(ifname, **kargs)
-
def set_path_cost(self, cost):
"""
Set interface path cost, only relevant for STP enabled interfaces
diff --git a/python/vyos/ifconfig/tunnel.py b/python/vyos/ifconfig/tunnel.py
index c82727eee..a49bdd51c 100644
--- a/python/vyos/ifconfig/tunnel.py
+++ b/python/vyos/ifconfig/tunnel.py
@@ -38,6 +38,14 @@ class _Tunnel(Interface):
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/tunnel.c
https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ip6tunnel.c
"""
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'tunnel',
+ 'prefixes': ['tun',],
+ 'bridgeable': True,
+ },
+ }
# TODO: This is surely used for more than tunnels
# TODO: could be refactored elsewhere
diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py
index 4e0db83c7..7b1e00d87 100644
--- a/python/vyos/ifconfig/vlan.py
+++ b/python/vyos/ifconfig/vlan.py
@@ -20,15 +20,23 @@ import re
from vyos.ifconfig.interface import Interface
-class VLANIf(Interface):
+# This is an internal implementation class
+class VLAN:
"""
This class handels the creation and removal of a VLAN interface. It serves
as base class for BondIf and EthernetIf.
"""
- default = {
- 'type': 'vlan',
- }
+ _novlan_remove = lambda : None
+
+ @classmethod
+ def enable (cls,adaptee):
+ adaptee._novlan_remove = adaptee.remove
+ adaptee.remove = cls.remove
+ adaptee.add_vlan = cls.add_vlan
+ adaptee.del_vlan = cls.del_vlan
+ adaptee.definition['vlan'] = True
+ return adaptee
def remove(self):
"""
@@ -41,13 +49,15 @@ class VLANIf(Interface):
>>> i = Interface('eth0')
>>> i.remove()
"""
+ ifname = self.config['ifname']
+
# Do we have sub interfaces (VLANs)? We apply a regex matching
# subinterfaces (indicated by a .) of a parent interface.
#
# As interfaces need to be deleted "in order" starting from Q-in-Q
# we delete them first.
vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
- if re.match(self.config['ifname'] + r'(?:\.\d+)(?:\.\d+)', f)]
+ if re.match(ifname + r'(?:\.\d+)(?:\.\d+)', f)]
for vlan in vlan_ifs:
Interface(vlan).remove()
@@ -56,13 +66,14 @@ class VLANIf(Interface):
# which probably acted as parent to Q-in-Q or have been regular 802.1q
# interface.
vlan_ifs = [f for f in os.listdir(r'/sys/class/net')
- if re.match(self.config['ifname'] + r'(?:\.\d+)', f)]
+ if re.match(ifname + r'(?:\.\d+)', f)]
for vlan in vlan_ifs:
- Interface(vlan).remove()
+ # self.__class__ is already VLAN.enabled
+ self.__class__(vlan)._novlan_remove()
# All subinterfaces are now removed, continue on the physical interface
- super().remove()
+ self._novlan_remove()
def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''):
"""
@@ -85,12 +96,12 @@ class VLANIf(Interface):
to VLAN header prio field but for outgoing frames.
Example:
- >>> from vyos.ifconfig import VLANIf
- >>> i = VLANIf('eth0')
+ >>> from vyos.ifconfig import MACVLANIf
+ >>> i = MACVLANIf('eth0')
>>> i.add_vlan(10)
"""
vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)):
+ if not os.path.exists(f'/sys/class/net/{vlan_ifname}'):
self._vlan_id = int(vlan_id)
if ethertype:
@@ -114,7 +125,7 @@ class VLANIf(Interface):
# return new object mapping to the newly created interface
# we can now work on this object for e.g. IP address setting
# or interface description and so on
- return VLANIf(vlan_ifname)
+ return self.__class__(vlan_ifname)
def del_vlan(self, vlan_id):
"""
@@ -123,9 +134,9 @@ class VLANIf(Interface):
client processes.
Example:
- >>> from vyos.ifconfig import VLANIf
- >>> i = VLANIf('eth0.10')
+ >>> from vyos.ifconfig import MACVLANIf
+ >>> i = MACVLANIf('eth0.10')
>>> i.del_vlan()
"""
- vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- VLANIf(vlan_ifname).remove()
+ ifname = self.config['ifname']
+ self.__class__(f'{ifname}.{vlan_id}')._novlan_remove()
diff --git a/python/vyos/ifconfig/vtun.py b/python/vyos/ifconfig/vtun.py
new file mode 100644
index 000000000..07d39fcbb
--- /dev/null
+++ b/python/vyos/ifconfig/vtun.py
@@ -0,0 +1,34 @@
+# 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 vyos.ifconfig.interface import Interface
+
+
+@Interface.register
+class VTunIf(Interface):
+ default = {
+ 'type': 'vtun',
+ }
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'openvpn',
+ 'prefixes': ['vtun', ],
+ 'bridgeable': True,
+ },
+ }
+
+ # The _create and _delete need to be moved from interface-ppoe to here
diff --git a/python/vyos/ifconfig/vxlan.py b/python/vyos/ifconfig/vxlan.py
index 75cdf8957..5678ad62e 100644
--- a/python/vyos/ifconfig/vxlan.py
+++ b/python/vyos/ifconfig/vxlan.py
@@ -19,6 +19,7 @@ from vyos import ConfigError
from vyos.ifconfig.interface import Interface
+@Interface.register
class VXLANIf(Interface):
"""
The VXLAN protocol is a tunnelling protocol designed to solve the
@@ -40,14 +41,6 @@ class VXLANIf(Interface):
https://www.kernel.org/doc/Documentation/networking/vxlan.txt
"""
- options = ['group', 'remote', 'dev', 'port', 'vni']
-
- mapping = {
- 'ifname': 'add',
- 'vni': 'id',
- 'port': 'dstport',
- }
-
default = {
'type': 'vxlan',
'vni': 0,
@@ -57,6 +50,21 @@ class VXLANIf(Interface):
'port': 8472, # The Linux implementation of VXLAN pre-dates
# the IANA's selection of a standard destination port
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'vxlan',
+ 'prefixes': ['vxlan', ],
+ 'bridgeable': True,
+ }
+ }
+ options = ['group', 'remote', 'dev', 'port', 'vni']
+
+ mapping = {
+ 'ifname': 'add',
+ 'vni': 'id',
+ 'port': 'dstport',
+ }
def _create(self):
cmdline = set()
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index 71ee67c98..8cf1ff58c 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -22,10 +22,8 @@ from datetime import timedelta
import time
from hurry.filesize import size,alternative
+@Interface.register
class WireGuardIf(Interface):
- options = ['port', 'private-key', 'pubkey', 'psk',
- 'allowed-ips', 'fwmark', 'endpoint', 'keepalive']
-
default = {
'type': 'wireguard',
'port': 0,
@@ -37,6 +35,16 @@ class WireGuardIf(Interface):
'endpoint': None,
'keepalive': 0
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'wireguard',
+ 'prefixes': ['wg', ],
+ 'bridgeable': True,
+ }
+ }
+ options = ['port', 'private-key', 'pubkey', 'psk',
+ 'allowed-ips', 'fwmark', 'endpoint', 'keepalive']
"""
Wireguard interface class, contains a comnfig dictionary since
diff --git a/python/vyos/ifconfig/wireless.py b/python/vyos/ifconfig/wireless.py
index 7f507ff6e..a1f50b71d 100644
--- a/python/vyos/ifconfig/wireless.py
+++ b/python/vyos/ifconfig/wireless.py
@@ -15,19 +15,30 @@
import os
-from vyos.ifconfig.vlan import VLANIf
+from vyos.ifconfig.interface import Interface
+from vyos.ifconfig.vlan import VLAN
-class WiFiIf(VLANIf):
+
+@Interface.register
+@VLAN.enable
+class WiFiIf(Interface):
"""
Handle WIFI/WLAN interfaces.
"""
- options = ['phy', 'op_mode']
-
default = {
'type': 'wifi',
'phy': 'phy0'
}
+ definition = {
+ **Interface.definition,
+ **{
+ 'section': 'wireless',
+ 'prefixes': ['wlan', ],
+ 'bridgeable': True,
+ }
+ }
+ options = ['phy', 'op_mode']
def _create(self):
# all interfaces will be added in monitor mode
@@ -54,3 +65,15 @@ class WiFiIf(VLANIf):
'phy': 'phy0'
}
return config
+
+
+
+@Interface.register
+class WiFiModemIf(WiFiIf):
+ definition = {
+ **WiFiIf.definition,
+ **{
+ 'section': 'wirelessmodem',
+ 'prefixes': ['wlm', ],
+ }
+ }
diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py
index fe94a5af4..2b934cdfc 100644
--- a/python/vyos/ifconfig_vlan.py
+++ b/python/vyos/ifconfig_vlan.py
@@ -14,7 +14,6 @@
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
from netifaces import interfaces
-from vyos.ifconfig import VLANIf
from vyos import ConfigError
def apply_vlan_config(vlan, config):
@@ -23,7 +22,7 @@ def apply_vlan_config(vlan, config):
to a VLAN interface
"""
- if vlan.__class__ != VLANIf:
+ if not vlan.definition['vlan']:
raise TypeError()
# get DHCP config dictionary and update values
diff --git a/python/vyos/interfaces.py b/python/vyos/interfaces.py
index 37c093aca..4697c0acc 100644
--- a/python/vyos/interfaces.py
+++ b/python/vyos/interfaces.py
@@ -16,44 +16,10 @@
import re
import json
+from vyos.ifconfig import Interface
import subprocess
import netifaces
-intf_type_data_file = '/usr/share/vyos/interface-types.json'
-
-def list_interfaces():
- interfaces = netifaces.interfaces()
-
- # Remove "fake" interfaces associated with drivers
- for i in ["dummy0", "ip6tnl0", "tunl0", "ip_vti0", "ip6_vti0"]:
- try:
- interfaces.remove(i)
- except ValueError:
- pass
-
- return interfaces
-
-def list_interfaces_of_type(typ):
- with open(intf_type_data_file, 'r') as f:
- types_data = json.load(f)
-
- all_intfs = list_interfaces()
- if not (typ in types_data.keys()):
- raise ValueError("Unknown interface type: {0}".format(typ))
- else:
- r = re.compile('^{0}\d+'.format(types_data[typ]))
- return list(filter(lambda i: re.match(r, i), all_intfs))
-
-def get_type_of_interface(intf):
- with open(intf_type_data_file, 'r') as f:
- types_data = json.load(f)
-
- for key,val in types_data.items():
- r = re.compile('^{0}\d+'.format(val))
- if re.match(r, intf):
- return key
-
- raise ValueError("No type found for interface name: {0}".format(intf))
def wireguard_dump():
"""Dump wireguard data in a python friendly way."""
diff --git a/src/completion/list_interfaces.py b/src/completion/list_interfaces.py
index 8cd59917d..77de4e327 100755
--- a/src/completion/list_interfaces.py
+++ b/src/completion/list_interfaces.py
@@ -3,6 +3,7 @@
import sys
import argparse
import vyos.interfaces
+from vyos.ifconfig import Interface
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
@@ -13,35 +14,39 @@ group.add_argument("-bo", "--bondable", action="store_true", help="List all bond
args = parser.parse_args()
+# XXX: Need to be rewritten using the data in the class definition
+# XXX: It can be done once vti and input are moved into vyos
+# XXX: We store for each class what type they are (broadcast, bridgeabe, ...)
+
if args.type:
try:
- interfaces = vyos.interfaces.list_interfaces_of_type(args.type)
+ interfaces = Interface.listing(args.type)
except ValueError as e:
print(e, file=sys.stderr)
print("")
elif args.broadcast:
- eth = vyos.interfaces.list_interfaces_of_type("ethernet")
- bridge = vyos.interfaces.list_interfaces_of_type("bridge")
- bond = vyos.interfaces.list_interfaces_of_type("bonding")
+ eth = Interface.listing("ethernet")
+ bridge = Interface.listing("bridge")
+ bond = Interface.listing("bonding")
interfaces = eth + bridge + bond
elif args.bridgeable:
- eth = vyos.interfaces.list_interfaces_of_type("ethernet")
- bond = vyos.interfaces.list_interfaces_of_type("bonding")
- l2tpv3 = vyos.interfaces.list_interfaces_of_type("l2tpv3")
- openvpn = vyos.interfaces.list_interfaces_of_type("openvpn")
- wireless = vyos.interfaces.list_interfaces_of_type("wireless")
- tunnel = vyos.interfaces.list_interfaces_of_type("tunnel")
- vxlan = vyos.interfaces.list_interfaces_of_type("vxlan")
- geneve = vyos.interfaces.list_interfaces_of_type("geneve")
+ eth = Interface.listing("ethernet")
+ bond = Interface.listing("bonding")
+ l2tpv3 = Interface.listing("l2tpv3")
+ openvpn = Interface.listing("openvpn")
+ wireless = Interface.listing("wireless")
+ tunnel = Interface.listing("tunnel")
+ vxlan = Interface.listing("vxlan")
+ geneve = Interface.listing("geneve")
interfaces = eth + bond + l2tpv3 + openvpn + vxlan + tunnel + wireless + geneve
elif args.bondable:
interfaces = []
- eth = vyos.interfaces.list_interfaces_of_type("ethernet")
+ eth = Interface.listing("ethernet")
# we need to filter out VLAN interfaces identified by a dot (.) in their name
for intf in eth:
@@ -49,6 +54,6 @@ elif args.bondable:
interfaces.append(intf)
else:
- interfaces = vyos.interfaces.list_interfaces()
+ interfaces = Interface.listing()
print(" ".join(interfaces))
diff --git a/src/completion/list_openvpn_clients.py b/src/completion/list_openvpn_clients.py
index 828ce6b5e..17b0c7008 100755
--- a/src/completion/list_openvpn_clients.py
+++ b/src/completion/list_openvpn_clients.py
@@ -18,7 +18,7 @@ import os
import sys
import argparse
-from vyos.interfaces import list_interfaces_of_type
+from vyos.ifconfig import Interface
def get_client_from_interface(interface):
clients = []
@@ -50,7 +50,7 @@ if __name__ == "__main__":
if args.interface:
clients = get_client_from_interface(args.interface)
elif args.all:
- for interface in list_interfaces_of_type("openvpn"):
+ for interface in Interface.listing("openvpn"):
clients += get_client_from_interface(interface)
print(" ".join(clients))
diff --git a/src/conf_mode/flow_accounting_conf.py b/src/conf_mode/flow_accounting_conf.py
index 0bc50482c..2e941de0a 100755
--- a/src/conf_mode/flow_accounting_conf.py
+++ b/src/conf_mode/flow_accounting_conf.py
@@ -22,7 +22,6 @@ import subprocess
from vyos.config import Config
from vyos import ConfigError
-import vyos.interfaces
from vyos.ifconfig import Interface
from jinja2 import Template
@@ -129,7 +128,7 @@ def _sflow_default_agentip(config):
return config.return_value('protocols ospfv3 parameters router-id')
# if router-id was not found, use first available ip of any interface
- for iface in vyos.interfaces.list_interfaces():
+ for iface in Interface.listing():
for address in Interface(iface).get_addr():
# return an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -300,7 +299,7 @@ def verify(config):
# check that all configured interfaces exists in the system
for iface in config['interfaces']:
- if not iface in vyos.interfaces.list_interfaces():
+ if not iface in Interface.listing():
# chnged from error to warning to allow adding dynamic interfaces and interface templates
# raise ConfigError("The {} interface is not presented in the system".format(iface))
print("Warning: the {} interface is not presented in the system".format(iface))
@@ -328,7 +327,7 @@ def verify(config):
# check if configured sFlow agent-id exist in the system
agent_id_presented = None
- for iface in vyos.interfaces.list_interfaces():
+ for iface in Interface.listing():
for address in Interface(iface).get_addr():
# check an IP, if this is not loopback
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
@@ -348,7 +347,7 @@ def verify(config):
# check if configured netflow source-ip exist in the system
if config['netflow']['source-ip']:
source_ip_presented = None
- for iface in vyos.interfaces.list_interfaces():
+ for iface in Interface.listing():
for address in Interface(iface).get_addr():
# check an IP
regex_filter = re.compile('^(?!(127)|(::1)|(fe80))(?P<ipaddr>[a-f\d\.:]+)/\d+$')
diff --git a/src/conf_mode/https.py b/src/conf_mode/https.py
index fcbc3d384..a0fe9cf2f 100755
--- a/src/conf_mode/https.py
+++ b/src/conf_mode/https.py
@@ -18,6 +18,7 @@
import sys
import os
+from copy import deepcopy
import jinja2
@@ -111,22 +112,22 @@ def get_config():
else:
conf.set_level('service https')
- if conf.exists('listen-address'):
- for addr in conf.list_nodes('listen-address'):
- server_block = {'address' : addr}
- server_block['port'] = '443'
- server_block['name'] = ['_']
- if conf.exists('listen-address {0} listen-port'.format(addr)):
- port = conf.return_value('listen-address {0} listen-port'.format(addr))
+ if not conf.exists('virtual-host'):
+ server_block_list.append(default_server_block)
+ else:
+ for vhost in conf.list_nodes('virtual-host'):
+ server_block = deepcopy(default_server_block)
+ if conf.exists(f'virtual-host {vhost} listen-address'):
+ addr = conf.return_value(f'virtual-host {vhost} listen-address')
+ server_block['address'] = addr
+ if conf.exists(f'virtual-host {vhost} listen-port'):
+ port = conf.return_value(f'virtual-host {vhost} listen-port')
server_block['port'] = port
- if conf.exists('listen-address {0} server-name'.format(addr)):
- names = conf.return_values('listen-address {0} server-name'.format(addr))
+ if conf.exists(f'virtual-host {vhost} server-name'):
+ names = conf.return_values(f'virtual-host {vhost} server-name')
server_block['name'] = names[:]
server_block_list.append(server_block)
- if not server_block_list:
- server_block_list.append(default_server_block)
-
vyos_cert_data = {}
if conf.exists('certificates system-generated-certificate'):
vyos_cert_data = vyos.defaults.vyos_cert_data
@@ -170,7 +171,7 @@ def verify(https):
for sb in https['server_block_list']:
if sb['certbot']:
return None
- raise ConfigError("At least one 'listen-address x.x.x.x server-name' "
+ raise ConfigError("At least one 'virtual-host <id> server-name' "
"matching the 'certbot domain-name' is required.")
return None
diff --git a/src/conf_mode/interfaces-bridge.py b/src/conf_mode/interfaces-bridge.py
index f8f20bf5c..c45ab13a8 100755
--- a/src/conf_mode/interfaces-bridge.py
+++ b/src/conf_mode/interfaces-bridge.py
@@ -20,7 +20,8 @@ from copy import deepcopy
from sys import exit
from netifaces import interfaces
-from vyos.ifconfig import BridgeIf, STPIf
+from vyos.ifconfig import BridgeIf
+from vyos.ifconfig.stp import STP
from vyos.configdict import list_diff
from vyos.config import Config
from vyos import ConfigError
@@ -322,9 +323,10 @@ def apply(bridge):
for addr in bridge['address']:
br.add_addr(addr)
+ STPBridgeIf = STP.enable(BridgeIf)
# configure additional bridge member options
for member in bridge['member']:
- i = STPIf(member['name'])
+ i = STPBridgeIf(member['name'])
# configure ARP cache timeout
i.set_arp_cache_tmo(bridge['arp_cache_tmo'])
# ignore link state changes
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 3a3c69e37..9313e339b 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -28,10 +28,11 @@ from psutil import pid_exists
from pwd import getpwnam
from subprocess import Popen, PIPE
from time import sleep
+from shutil import rmtree
from vyos import ConfigError
from vyos.config import Config
-from vyos.ifconfig import Interface
+from vyos.ifconfig import VTunIf
from vyos.validate import is_addr_assigned
user = 'openvpn'
@@ -899,6 +900,10 @@ def generate(openvpn):
interface = openvpn['intf']
directory = os.path.dirname(get_config_name(interface))
+ # we can't know which clients were deleted, remove all client configs
+ if os.path.isdir(os.path.join(directory, 'ccd', interface)):
+ rmtree(os.path.join(directory, 'ccd', interface), ignore_errors=True)
+
# create config directory on demand
openvpn_mkdir(directory)
# create status directory on demand
@@ -920,6 +925,11 @@ def generate(openvpn):
fixup_permission(auth_file)
+ else:
+ # delete old auth file if present
+ if os.path.isfile('/tmp/openvpn-{}-pw'.format(interface)):
+ os.remove('/tmp/openvpn-{}-pw'.format(interface))
+
# get numeric uid/gid
uid = getpwnam(user).pw_uid
gid = getgrnam(group).gr_gid
@@ -977,11 +987,12 @@ def apply(openvpn):
# cleanup client config dir
directory = os.path.dirname(get_config_name(openvpn['intf']))
- if os.path.isdir(directory + '/ccd/' + openvpn['intf']):
- try:
- os.remove(directory + '/ccd/' + openvpn['intf'] + '/*')
- except:
- pass
+ if os.path.isdir(os.path.join(directory, 'ccd', openvpn['intf'])):
+ rmtree(os.path.join(directory, 'ccd', openvpn['intf']), ignore_errors=True)
+
+ # cleanup auth file
+ if os.path.isfile('/tmp/openvpn-{}-pw'.format(openvpn['intf'])):
+ os.remove('/tmp/openvpn-{}-pw'.format(openvpn['intf']))
return None
@@ -1025,14 +1036,14 @@ def apply(openvpn):
try:
# we need to catch the exception if the interface is not up due to
# reason stated above
- Interface(openvpn['intf']).set_alias(openvpn['description'])
+ VTunIf(openvpn['intf']).set_alias(openvpn['description'])
except:
pass
# TAP interface needs to be brought up explicitly
if openvpn['type'] == 'tap':
if not openvpn['disable']:
- Interface(openvpn['intf']).set_state('up')
+ VTunIf(openvpn['intf']).set_state('up')
return None
diff --git a/src/conf_mode/service-router-advert.py b/src/conf_mode/service-router-advert.py
new file mode 100755
index 000000000..5ae719c29
--- /dev/null
+++ b/src/conf_mode/service-router-advert.py
@@ -0,0 +1,207 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2018-2019 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/>.
+
+import os
+import sys
+import jinja2
+
+from stat import S_IRUSR, S_IWUSR, S_IRGRP
+from vyos.config import Config
+from vyos import ConfigError
+
+config_file = r'/etc/radvd.conf'
+
+config_tmpl = """
+### Autogenerated by service-router-advert.py ###
+
+{% for i in interfaces -%}
+interface {{ i.name }} {
+ IgnoreIfMissing on;
+ AdvDefaultPreference {{ i.default_preference }};
+ AdvManagedFlag {{ i.managed_flag }};
+ MaxRtrAdvInterval {{ i.interval_max }};
+{% if i.interval_min %}
+ MinRtrAdvInterval {{ i.interval_min }};
+{% endif %}
+ AdvReachableTime {{ i.reachable_time }};
+ AdvIntervalOpt {{ i.send_advert }};
+ AdvSendAdvert {{ i.send_advert }};
+{% if i.default_lifetime %}
+ AdvDefaultLifetime {{ i.default_lifetime }};
+{% endif %}
+ AdvLinkMTU {{ i.link_mtu }};
+ AdvOtherConfigFlag {{ i.other_config_flag }};
+ AdvRetransTimer {{ i.retrans_timer }};
+ AdvCurHopLimit {{ i.hop_limit }};
+{% for p in i.prefixes %}
+ prefix {{ p.prefix }} {
+ AdvAutonomous {{ p.autonomous_flag }};
+ AdvValidLifetime {{ p.valid_lifetime }};
+ AdvOnLink {{ p.on_link }};
+ AdvPreferredLifetime {{ p.preferred_lifetime }};
+ };
+{% endfor %}
+{% if i.name_server %}
+ RDNSS {{ i.name_server | join(" ") }} {
+ };
+{% endif %}
+};
+{% endfor -%}
+"""
+
+default_config_data = {
+ 'interfaces': []
+}
+
+def get_config():
+ rtradv = default_config_data
+ conf = Config()
+ base_level = ['service', 'router-advert']
+
+ if not conf.exists(base_level):
+ return rtradv
+
+ for interface in conf.list_nodes(base_level + ['interface']):
+ intf = {
+ 'name': interface,
+ 'hop_limit' : '64',
+ 'default_lifetime': '',
+ 'default_preference': 'medium',
+ 'dnssl': [],
+ 'link_mtu': '0',
+ 'managed_flag': 'off',
+ 'interval_max': '600',
+ 'interval_min': '',
+ 'name_server': [],
+ 'other_config_flag': 'off',
+ 'prefixes' : [],
+ 'reachable_time': '0',
+ 'retrans_timer': '0',
+ 'send_advert': 'on'
+ }
+
+ # set config level first to reduce boilerplate code
+ conf.set_level(base_level + ['interface', interface])
+
+ if conf.exists(['hop-limit']):
+ intf['hop_limit'] = conf.return_value(['hop-limit'])
+
+ if conf.exists(['default-lifetim']):
+ intf['default_lifetime'] = conf.return_value(['default-lifetim'])
+
+ if conf.exists(['default-preference']):
+ intf['default_preference'] = conf.return_value(['default-preference'])
+
+ if conf.exists(['dnssl']):
+ intf['dnssl'] = conf.return_values(['dnssl'])
+
+ if conf.exists(['link-mtu']):
+ intf['link_mtu'] = conf.return_value(['link-mtu'])
+
+ if conf.exists(['managed-flag']):
+ intf['managed_flag'] = 'on'
+
+ if conf.exists(['interval', 'max']):
+ intf['interval_max'] = conf.return_value(['interval', 'max'])
+
+ if conf.exists(['interval', 'min']):
+ intf['interval_min'] = conf.return_value(['interval', 'min'])
+
+ if conf.exists(['name-server']):
+ intf['name_server'] = conf.return_values(['name-server'])
+
+ if conf.exists(['other-config-flag']):
+ intf['other_config_flag'] = 'on'
+
+ if conf.exists(['reachable-time']):
+ intf['reachable_time'] = conf.return_value(['reachable-time'])
+
+ if conf.exists(['retrans-timer']):
+ intf['retrans_timer'] = conf.return_value(['retrans-timer'])
+
+ if conf.exists(['no-send-advert']):
+ intf['send_advert'] = 'off'
+
+ for prefix in conf.list_nodes(['prefix']):
+ tmp = {
+ 'prefix' : prefix,
+ 'autonomous_flag' : 'on',
+ 'on_link' : 'on',
+ 'preferred_lifetime': '14400',
+ 'valid_lifetime' : '2592000'
+
+ }
+
+ # set config level first to reduce boilerplate code
+ conf.set_level(base_level + ['interface', interface, 'prefix', prefix])
+
+ if conf.exists(['no-autonomous-flag']):
+ tmp['autonomous_flag'] = 'off'
+
+ if conf.exists(['no-on-link-flag']):
+ tmp['on_link'] = 'off'
+
+ if conf.exists(['preferred-lifetime']):
+ tmp['preferred_lifetime'] = conf.return_value(['preferred-lifetime'])
+
+ if conf.exists(['valid-lifetime']):
+ tmp['valid_lifetime'] = conf.return_value(['valid-lifetime'])
+
+ intf['prefixes'].append(tmp)
+
+ rtradv['interfaces'].append(intf)
+
+ return rtradv
+
+def verify(rtradv):
+ return None
+
+def generate(rtradv):
+ if not rtradv['interfaces']:
+ return None
+
+ tmpl = jinja2.Template(config_tmpl, trim_blocks=True)
+ config_text = tmpl.render(rtradv)
+ with open(config_file, 'w') as f:
+ f.write(config_text)
+
+ # adjust file permissions of new configuration file
+ if os.path.exists(config_file):
+ os.chmod(config_file, S_IRUSR | S_IWUSR | S_IRGRP)
+
+ return None
+
+def apply(rtradv):
+ if not rtradv['interfaces']:
+ # bail out early - looks like removal from running config
+ os.system('sudo systemctl stop radvd.service')
+ if os.path.exists(config_file):
+ os.unlink(config_file)
+
+ return None
+
+ os.system('sudo systemctl restart radvd.service')
+ return None
+
+if __name__ == '__main__':
+ try:
+ c = get_config()
+ verify(c)
+ generate(c)
+ apply(c)
+ except ConfigError as e:
+ print(e)
+ sys.exit(1)
diff --git a/src/migration-scripts/dns-forwarding/1-to-2 b/src/migration-scripts/dns-forwarding/1-to-2
index 31ba5573f..9a50b6aa3 100755
--- a/src/migration-scripts/dns-forwarding/1-to-2
+++ b/src/migration-scripts/dns-forwarding/1-to-2
@@ -23,8 +23,8 @@
import sys
from ipaddress import ip_interface
+from vyos.ifconfig import Interface
from vyos.configtree import ConfigTree
-from vyos.interfaces import get_type_of_interface
if (len(sys.argv) < 1):
print("Must specify file name!")
@@ -41,7 +41,10 @@ base = ['service', 'dns', 'forwarding']
if not config.exists(base):
# Nothing to do
sys.exit(0)
+
else:
+ # XXX: we can remove the else and un-indent this whole block
+
if config.exists(base + ['listen-on']):
listen_intf = config.return_values(base + ['listen-on'])
# Delete node with abandoned command
@@ -60,7 +63,10 @@ else:
# this is a QinQ VLAN interface
intf = intf.split('.')[0] + ' vif-s ' + intf.split('.')[1] + ' vif-c ' + intf.split('.')[2]
- path = ['interfaces', get_type_of_interface(intf), intf, 'address']
+ section = Interface.section(intf)
+ if not section:
+ raise ValueError(f'Invalid interface name {intf}')
+ path = ['interfaces', section, intf, 'address']
# retrieve corresponding interface addresses in CIDR format
# those need to be converted in pure IP addresses without network information
diff --git a/src/migration-scripts/https/0-to-1 b/src/migration-scripts/https/0-to-1
new file mode 100755
index 000000000..c6ed12fae
--- /dev/null
+++ b/src/migration-scripts/https/0-to-1
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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/>.
+
+# * remove "system login user <user> group" node, Why should be add a user to a
+# 3rd party group when the system is fully managed by CLI?
+# * remove "system login user <user> level" node
+# This is the only privilege level left and also the default, what is the
+# sense in keeping this orphaned node?
+
+import sys
+
+from vyos.configtree import ConfigTree
+
+if (len(sys.argv) < 2):
+ print("Must specify file name!")
+ sys.exit(1)
+
+file_name = sys.argv[1]
+
+with open(file_name, 'r') as f:
+ config_file = f.read()
+
+config = ConfigTree(config_file)
+
+old_base = ['service', 'https', 'listen-address']
+if not config.exists(old_base):
+ # Nothing to do
+ sys.exit(0)
+else:
+ new_base = ['service', 'https', 'virtual-host']
+ config.set(new_base)
+ config.set_tag(new_base)
+
+ index = 0
+ for addr in config.list_nodes(old_base):
+ tag_name = f'vhost{index}'
+ config.set(new_base + [tag_name])
+ config.set(new_base + [tag_name, 'listen-address'], value=addr)
+
+ if config.exists(old_base + [addr, 'listen-port']):
+ port = config.return_value(old_base + [addr, 'listen-port'])
+ config.set(new_base + [tag_name, 'listen-port'], value=port)
+
+ if config.exists(old_base + [addr, 'server-name']):
+ names = config.return_values(old_base + [addr, 'server-name'])
+ for name in names:
+ config.set(new_base + [tag_name, 'server-name'], value=name,
+ replace=False)
+
+ index += 1
+
+ config.delete(old_base)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)
diff --git a/src/migration-scripts/interfaces/5-to-6 b/src/migration-scripts/interfaces/5-to-6
new file mode 100755
index 000000000..9dbfd30e1
--- /dev/null
+++ b/src/migration-scripts/interfaces/5-to-6
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 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
+# 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/>.
+
+# Migrate IPv6 router advertisments from a nested interface configuration to
+# a denested "service router-advert"
+
+import sys
+from vyos.configtree import ConfigTree
+
+def copy_rtradv(c, old_base, interface):
+ base = ['service', 'router-advert', 'interface']
+
+ if c.exists(old_base):
+ if not c.exists(base):
+ c.set(base)
+ c.set_tag(base)
+
+ # take the old node as a whole and copy it to new new path,
+ # additional migrations will be done afterwards
+ new_base = base + [interface]
+ c.copy(old_base, new_base)
+ c.delete(old_base)
+
+ # cur-hop-limit has been renamed to hop-limit
+ if c.exists(new_base + ['cur-hop-limit']):
+ c.rename(new_base + ['cur-hop-limit'], 'hop-limit')
+
+ bool_cleanup = ['managed-flag', 'other-config-flag']
+ for bool in bool_cleanup:
+ if c.exists(new_base + [bool]):
+ tmp = c.return_value(new_base + [bool])
+ c.delete(new_base + [bool])
+ if tmp == 'true':
+ c.set(new_base + [bool])
+
+ # max/min interval moved to subnode
+ intervals = ['max-interval', 'min-interval']
+ for interval in intervals:
+ if c.exists(new_base + [interval]):
+ tmp = c.return_value(new_base + [interval])
+ c.delete(new_base + [interval])
+ min_max = interval.split('-')[0]
+ c.set(new_base + ['interval', min_max], value=tmp)
+
+ # cleanup boolean nodes in individual prefix
+ prefix_base = new_base + ['prefix']
+ if c.exists(prefix_base):
+ for prefix in config.list_nodes(prefix_base):
+ bool_cleanup = ['autonomous-flag', 'on-link-flag']
+ for bool in bool_cleanup:
+ if c.exists(prefix_base + [prefix, bool]):
+ tmp = c.return_value(prefix_base + [prefix, bool])
+ c.delete(prefix_base + [prefix, bool])
+ if tmp == 'true':
+ c.set(prefix_base + [prefix, bool])
+
+ # router advertisement can be individually disabled per interface
+ # the node has been renamed from send-advert {true | false} to no-send-advert
+ if c.exists(new_base + ['send-advert']):
+ tmp = c.return_value(new_base + ['send-advert'])
+ c.delete(new_base + ['send-advert'])
+ if tmp == 'false':
+ c.set(new_base + ['no-send-advert'])
+
+if __name__ == '__main__':
+ if (len(sys.argv) < 1):
+ print("Must specify file name!")
+ exit(1)
+
+ file_name = sys.argv[1]
+ with open(file_name, 'r') as f:
+ config_file = f.read()
+
+ config = ConfigTree(config_file)
+
+ # list all individual interface types like dummy, ethernet and so on
+ for if_type in config.list_nodes(['interfaces']):
+ base_if_type = ['interfaces', if_type]
+
+ # for every individual interface we need to check if there is an
+ # ipv6 ra configured ... and also for every VIF (VLAN) interface
+ for intf in config.list_nodes(base_if_type):
+ old_base = base_if_type + [intf, 'ipv6', 'router-advert']
+ copy_rtradv(config, old_base, intf)
+
+ vif_base = base_if_type + [intf, 'vif']
+ if config.exists(vif_base):
+ for vif in config.list_nodes(vif_base):
+ old_base = vif_base + [vif, 'ipv6', 'router-advert']
+ vlan_name = f'{intf}.{vif}'
+ copy_rtradv(config, old_base, vlan_name)
+
+ try:
+ with open(file_name, 'w') as f:
+ f.write(config.to_string())
+ except OSError as e:
+ print("Failed to save the modified config: {}".format(e))
+ sys.exit(1)