From dbaaa56c53ce9af73601b54351d97ebce21a9df7 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 17:48:47 +0000 Subject: ifconfig: T2057: break down DHCP --- python/vyos/ifconfig/dhcp.py | 266 +++++++++++++++++++++++++++++++++++++ python/vyos/ifconfig/interface.py | 272 +------------------------------------- 2 files changed, 270 insertions(+), 268 deletions(-) create mode 100644 python/vyos/ifconfig/dhcp.py 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 +# +# 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 . + +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/interface.py b/python/vyos/ifconfig/interface.py index 4f72271c9..a1f8198c7 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -15,7 +15,6 @@ import os import re -import jinja2 import json import glob import time @@ -35,41 +34,9 @@ from tabulate import tabulate from hurry.filesize import size,alternative from datetime import timedelta -from vyos.ifconfig.control import Control +from vyos.ifconfig.dhcp import DHCP -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; -} - -""" - - - -class Interface(Control): +class Interface(DHCP): options = [] required = [] default = { @@ -165,6 +132,8 @@ class Interface(Control): >>> i = Interface('eth0') """ + DHCP.__init__(self, ifname) + self.config = deepcopy(self.default) self.config['ifname'] = ifname @@ -183,31 +152,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 +567,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']]] -- cgit v1.2.3 From 09f8e57535849aab83df6947fbe94a0e7228ed40 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:08:16 +0000 Subject: ifconfig: T2057: small cleanup VLANIf --- python/vyos/ifconfig/vlan.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py index 4e0db83c7..607e9aeaf 100644 --- a/python/vyos/ifconfig/vlan.py +++ b/python/vyos/ifconfig/vlan.py @@ -41,13 +41,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,7 +58,7 @@ 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() @@ -90,7 +92,7 @@ class VLANIf(Interface): >>> 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: -- cgit v1.2.3 From 8a4dd6c2816bf2289ad4af37ba301b31efdf30a8 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:14:49 +0000 Subject: ifconfig: T2057: add class Register --- python/vyos/ifconfig/bond.py | 18 ++++++-- python/vyos/ifconfig/bridge.py | 17 +++++-- python/vyos/ifconfig/control.py | 4 +- python/vyos/ifconfig/dummy.py | 8 ++++ python/vyos/ifconfig/ethernet.py | 20 ++++++-- python/vyos/ifconfig/geneve.py | 9 ++++ python/vyos/ifconfig/interface.py | 8 ++++ python/vyos/ifconfig/l2tpv3.py | 14 ++++-- python/vyos/ifconfig/loopback.py | 11 +++++ python/vyos/ifconfig/macvlan.py | 11 ++++- python/vyos/ifconfig/register.py | 96 +++++++++++++++++++++++++++++++++++++++ python/vyos/ifconfig/tunnel.py | 8 ++++ python/vyos/ifconfig/vxlan.py | 24 ++++++---- python/vyos/ifconfig/wireguard.py | 14 ++++-- python/vyos/ifconfig/wireless.py | 26 ++++++++++- python/vyos/ifconfig_vlan.py | 2 +- 16 files changed, 260 insertions(+), 30 deletions(-) create mode 100644 python/vyos/ifconfig/register.py diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index c9dac891f..af4082f8f 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -21,6 +21,7 @@ from vyos.ifconfig.vlan import VLANIf from vyos.validate import * +@Interface.register class BondIf(VLANIf): """ The Linux bonding driver provides a method for aggregating multiple network @@ -30,6 +31,19 @@ class BondIf(VLANIf): monitoring may be performed. """ + default = { + 'type': 'bond', + } + definition = { + **Interface.definition, + ** { + 'section': 'bonding', + 'prefixes': ['bond', ], + 'broadcast': True, + 'bridgeable': True, + }, + } + _sysfs_set = {**VLANIf._sysfs_set, **{ 'bond_hash_policy': { 'validate': lambda v: assert_list(v, ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']), @@ -69,10 +83,6 @@ class BondIf(VLANIf): } }} - 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/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..606161121 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -17,15 +17,31 @@ import os import re from vyos.ifconfig.vlan import VLANIf +from vyos.ifconfig.interface import Interface from vyos.validate import * +@Interface.register class EthernetIf(VLANIf): """ Abstraction of a Linux Ethernet Interface """ + default = { + 'type': 'ethernet', + } + definition = { + **Interface.definition, + **{ + 'section': 'ethernet', + 'prefixes': ['lan', 'eth', 'eno', 'ens', 'enp', 'enx'], + 'bondable': True, + 'broadcast': True, + 'bridgeable': True, + } + } + _command_set = {**VLANIf._command_set, **{ 'gro': { 'validate': lambda v: assert_list(v, ['on', 'off']), @@ -49,10 +65,6 @@ class EthernetIf(VLANIf): }, }} - default = { - 'type': 'ethernet', - } - def _delete(self): # Ethernet interfaces can not be removed pass 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 a1f8198c7..1759e3545 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -42,6 +42,14 @@ class Interface(DHCP): default = { 'type': '', } + definition = { + 'section': '', + 'prefixes': [], + 'vlan': False, + 'bondable': False, + 'broadcast': False, + 'bridgeable': False, + } _command_set = { 'state': { 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..a1dca5e41 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -14,18 +14,27 @@ # License along with this library. If not, see . +from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLANIf +@Interface.register class MACVLANIf(VLANIf): """ 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/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 +# +# 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 . + +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/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/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..f94509c80 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -15,19 +15,29 @@ import os +from vyos.ifconfig.interface import Interface from vyos.ifconfig.vlan import VLANIf + +@Interface.register class WiFiIf(VLANIf): """ 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 +64,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..245453307 100644 --- a/python/vyos/ifconfig_vlan.py +++ b/python/vyos/ifconfig_vlan.py @@ -23,7 +23,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 -- cgit v1.2.3 From d08df9402f6504582370a68666d8226350e53a19 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:23:03 +0000 Subject: ifconfig: T2057: add VTunIf class --- python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/vtun.py | 34 ++++++++++++++++++++++++++++++++++ src/conf_mode/interfaces-openvpn.py | 6 +++--- 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 python/vyos/ifconfig/vtun.py diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 16c29a704..8bc7ee6a7 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -27,6 +27,7 @@ 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.tunnel import GREIf from vyos.ifconfig.tunnel import GRETapIf from vyos.ifconfig.tunnel import IP6GREIf 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 +# +# 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 . + + +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/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py index 3a3c69e37..d72e4111c 100755 --- a/src/conf_mode/interfaces-openvpn.py +++ b/src/conf_mode/interfaces-openvpn.py @@ -31,7 +31,7 @@ from time import sleep 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' @@ -1025,14 +1025,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 -- cgit v1.2.3 From 8f02c42286ba92f1aa54502ebb22acd67c2a934d Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:25:21 +0000 Subject: ifconfig: T2057: add PPPoEIf class --- python/vyos/ifconfig/__init__.py | 1 + python/vyos/ifconfig/pppoe.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 python/vyos/ifconfig/pppoe.py diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 8bc7ee6a7..7b89d4cad 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -28,6 +28,7 @@ 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/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 +# +# 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 . + + +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 -- cgit v1.2.3 From 1a57edcb8e3052a70296808d394c3d166bcda275 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:33:42 +0000 Subject: ifconfig: T2057: convert VLAN to adapter --- python/vyos/ifconfig/__init__.py | 1 - python/vyos/ifconfig/bond.py | 9 +++++---- python/vyos/ifconfig/ethernet.py | 8 +++++--- python/vyos/ifconfig/macvlan.py | 5 +++-- python/vyos/ifconfig/vlan.py | 35 ++++++++++++++++++++++------------- python/vyos/ifconfig/wireless.py | 5 +++-- python/vyos/ifconfig_vlan.py | 1 - 7 files changed, 38 insertions(+), 26 deletions(-) diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index 7b89d4cad..d6584215b 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -24,7 +24,6 @@ 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 diff --git a/python/vyos/ifconfig/bond.py b/python/vyos/ifconfig/bond.py index af4082f8f..3c26b9b95 100644 --- a/python/vyos/ifconfig/bond.py +++ b/python/vyos/ifconfig/bond.py @@ -16,13 +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 * @Interface.register -class BondIf(VLANIf): +@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 @@ -44,7 +45,7 @@ class BondIf(VLANIf): }, } - _sysfs_set = {**VLANIf._sysfs_set, **{ + _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', @@ -77,7 +78,7 @@ 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', } diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py index 606161121..850bb34ae 100644 --- a/python/vyos/ifconfig/ethernet.py +++ b/python/vyos/ifconfig/ethernet.py @@ -16,14 +16,15 @@ 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 * @Interface.register -class EthernetIf(VLANIf): +@VLAN.enable +class EthernetIf(Interface): """ Abstraction of a Linux Ethernet Interface """ @@ -42,7 +43,8 @@ class EthernetIf(VLANIf): } } - _command_set = {**VLANIf._command_set, **{ + + _command_set = {**Interface._command_set, **{ 'gro': { 'validate': lambda v: assert_list(v, ['on', 'off']), 'shellcmd': '/sbin/ethtool -K {ifname} gro {value}', diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index a1dca5e41..4e4b563a1 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -15,11 +15,12 @@ from vyos.ifconfig.interface import Interface -from vyos.ifconfig.vlan import VLANIf +from vyos.ifconfig.vlan import VLAN @Interface.register -class MACVLANIf(VLANIf): +@VLAN.enable +class MACVLANIf(Interface): """ Abstraction of a Linux MACvlan interface """ diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py index 607e9aeaf..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): """ @@ -61,10 +69,11 @@ class VLANIf(Interface): 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=''): """ @@ -87,8 +96,8 @@ 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) @@ -116,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): """ @@ -125,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/wireless.py b/python/vyos/ifconfig/wireless.py index f94509c80..a1f50b71d 100644 --- a/python/vyos/ifconfig/wireless.py +++ b/python/vyos/ifconfig/wireless.py @@ -16,11 +16,12 @@ import os from vyos.ifconfig.interface import Interface -from vyos.ifconfig.vlan import VLANIf +from vyos.ifconfig.vlan import VLAN @Interface.register -class WiFiIf(VLANIf): +@VLAN.enable +class WiFiIf(Interface): """ Handle WIFI/WLAN interfaces. """ diff --git a/python/vyos/ifconfig_vlan.py b/python/vyos/ifconfig_vlan.py index 245453307..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 . from netifaces import interfaces -from vyos.ifconfig import VLANIf from vyos import ConfigError def apply_vlan_config(vlan, config): -- cgit v1.2.3 From 8ac524b6d12618d696fa883ff78ded8c35c26d05 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:34:59 +0000 Subject: ifconfig: T2057: convert STP to adapter --- python/vyos/ifconfig/__init__.py | 1 - python/vyos/ifconfig/stp.py | 19 ++++++++++--------- src/conf_mode/interfaces-bridge.py | 6 ++++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/python/vyos/ifconfig/__init__.py b/python/vyos/ifconfig/__init__.py index d6584215b..d08a8b528 100644 --- a/python/vyos/ifconfig/__init__.py +++ b/python/vyos/ifconfig/__init__.py @@ -23,7 +23,6 @@ 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.vxlan import VXLANIf from vyos.ifconfig.wireguard import WireGuardIf from vyos.ifconfig.vtun import VTunIf 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/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 -- cgit v1.2.3 From bbea850ea5f8ff0402cd276ab63963ece7e0c763 Mon Sep 17 00:00:00 2001 From: Thomas Mangin Date: Tue, 24 Mar 2020 18:36:46 +0000 Subject: ifconfig: T2057: remove need for interface-types.json --- data/interface-types.json | 19 --------------- python/vyos/ifconfig/interface.py | 2 -- python/vyos/interfaces.py | 36 +---------------------------- src/completion/list_interfaces.py | 33 +++++++++++++++----------- src/completion/list_openvpn_clients.py | 4 ++-- src/conf_mode/flow_accounting_conf.py | 9 ++++---- src/migration-scripts/dns-forwarding/1-to-2 | 10 ++++++-- 7 files changed, 34 insertions(+), 79 deletions(-) delete mode 100644 data/interface-types.json 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/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 1759e3545..f2b43fd35 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -20,8 +20,6 @@ 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 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[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[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[a-f\d\.:]+)/\d+$') 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 -- cgit v1.2.3