diff options
Diffstat (limited to 'python/vyos/ifconfig.py')
-rw-r--r-- | python/vyos/ifconfig.py | 1920 |
1 files changed, 0 insertions, 1920 deletions
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py deleted file mode 100644 index 81867d086..000000000 --- a/python/vyos/ifconfig.py +++ /dev/null @@ -1,1920 +0,0 @@ -# Copyright 2019 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 re -import jinja2 -import json -import glob -import time - -import vyos.interfaces - -from vyos.validate import * -from vyos.config import Config -from vyos import ConfigError - -from ipaddress import IPv4Network, IPv6Address -from netifaces import ifaddresses, AF_INET, AF_INET6 -from subprocess import Popen, PIPE, STDOUT -from time import sleep -from os.path import isfile -from tabulate import tabulate -from hurry.filesize import size,alternative -from datetime import timedelta - -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: - def __init__(self, ifname, type=None): - """ - This is the base interface class which supports basic IP/MAC address - operations as well as DHCP(v6). Other interface which represent e.g. - and ethernet bridge are implemented as derived classes adding all - additional functionality. - - DEBUG: - This class has embedded debugging (print) which can be enabled by - creating the following file: - vyos@vyos# touch /tmp/vyos.ifconfig.debug - - Example: - >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - """ - self._ifname = str(ifname) - - if not os.path.exists('/sys/class/net/{}'.format(ifname)) and not type: - raise Exception('interface "{}" not found'.format(self._ifname)) - - if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): - cmd = 'ip link add dev {} type {}'.format(self._ifname, type) - self._cmd(cmd) - - # per interface DHCP config files - self._dhcp_cfg_file = dhclient_base + self._ifname + '.conf' - self._dhcp_pid_file = dhclient_base + self._ifname + '.pid' - self._dhcp_lease_file = dhclient_base + self._ifname + '.leases' - - # per interface DHCPv6 config files - self._dhcpv6_cfg_file = dhclient_base + self._ifname + '.v6conf' - self._dhcpv6_pid_file = dhclient_base + self._ifname + '.v6pid' - self._dhcpv6_lease_file = dhclient_base + self._ifname + '.v6leases' - - # DHCP options - self._dhcp_options = { - 'intf' : self._ifname, - 'hostname' : '', - 'client_id' : '', - 'vendor_class_id' : '' - } - - # DHCPv6 options - self._dhcpv6_options = { - 'intf' : self._ifname, - 'dhcpv6_prm_only' : False, - 'dhcpv6_temporary' : False - } - - # list of assigned IP addresses - self._addr = [] - - def _debug_msg(self, msg): - if os.path.isfile('/tmp/vyos.ifconfig.debug'): - print('DEBUG/{:<6} {}'.format(self._ifname, msg)) - - def _cmd(self, command): - p = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True) - tmp = p.communicate()[0].strip() - self._debug_msg("cmd '{}'".format(command)) - if tmp.decode(): - self._debug_msg("returned:\n{}".format(tmp.decode())) - - # do we need some error checking code here? - return tmp.decode() - - def _read_sysfs(self, filename): - """ - Provide a single primitive w/ error checking for reading from sysfs. - """ - value = None - with open(filename, 'r') as f: - value = f.read().rstrip('\n') - - self._debug_msg("read '{}' < '{}'".format(value, filename)) - return value - - def _write_sysfs(self, filename, value): - """ - Provide a single primitive w/ error checking for writing to sysfs. - """ - self._debug_msg("write '{}' > '{}'".format(value, filename)) - with open(filename, 'w') as f: - f.write(str(value)) - - return None - - def remove(self): - """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses and clear possible DHCP(v6) - client processes. - - Example: - >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - >>> i.remove() - """ - # stop DHCP(v6) if running - self._del_dhcp() - self._del_dhcpv6() - - # remove all assigned IP addresses from interface - this is a bit redundant - # as the kernel will remove all addresses on interface deletion, but we - # can not delete ALL interfaces, see below - for addr in self.get_addr(): - self.del_addr(addr) - - # Ethernet interfaces can not be removed - if type(self) == type(EthernetIf(self._ifname)): - return - - # NOTE (Improvement): - # after interface removal no other commands should be allowed - # to be called and instead should raise an Exception: - cmd = 'ip link del dev {}'.format(self._ifname) - return self._cmd(cmd) - - def get_mtu(self): - """ - Get/set interface mtu in bytes. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_mtu() - '1500' - """ - return self._read_sysfs('/sys/class/net/{}/mtu' - .format(self._ifname)) - - def set_mtu(self, mtu): - """ - Get/set interface mtu in bytes. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_mtu(1400) - >>> Interface('eth0').get_mtu() - '1400' - """ - if mtu < 68 or mtu > 9000: - raise ValueError('Invalid MTU size: "{}"'.format(mru)) - - return self._write_sysfs('/sys/class/net/{}/mtu' - .format(self._ifname), mtu) - - def set_mac(self, mac): - """ - Set interface MAC (Media Access Contrl) address to given value. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_mac('00:50:ab:cd:ef:01') - """ - # on interface removal (ethernet) an empty string is passed - ignore it - if not mac: - return None - - # a mac address consits out of 6 octets - octets = len(mac.split(':')) - if octets != 6: - raise ValueError('wrong number of MAC octets: {} '.format(octets)) - - # validate against the first mac address byte if it's a multicast - # address - if int(mac.split(':')[0], 16) & 1: - raise ValueError('{} is a multicast MAC address'.format(mac)) - - # overall mac address is not allowed to be 00:00:00:00:00:00 - if sum(int(i, 16) for i in mac.split(':')) == 0: - raise ValueError('00:00:00:00:00:00 is not a valid MAC address') - - # check for VRRP mac address - if mac.split(':')[0] == '0' and addr.split(':')[1] == '0' and mac.split(':')[2] == '94' and mac.split(':')[3] == '0' and mac.split(':')[4] == '1': - raise ValueError('{} is a VRRP MAC address'.format(mac)) - - # Assemble command executed on system. Unfortunately there is no way - # of altering the MAC address via sysfs - cmd = 'ip link set dev {} address {}'.format(self._ifname, mac) - return self._cmd(cmd) - - - def set_arp_cache_tmo(self, tmo): - """ - Set ARP cache timeout value in seconds. Internal Kernel representation - is in milliseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_arp_cache_tmo(40) - """ - return self._write_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms' - .format(self._ifname), (int(tmo) * 1000)) - - def set_arp_filter(self, arp_filter): - """ - Filter ARP requests - - 1 - Allows you to have multiple network interfaces on the same - subnet, and have the ARPs for each interface be answered - based on whether or not the kernel would route a packet from - the ARP'd IP out that interface (therefore you must use source - based routing for this to work). In other words it allows control - of which cards (usually 1) will respond to an arp request. - - 0 - (default) The kernel can respond to arp requests with addresses - from other interfaces. This may seem wrong but it usually makes - sense, because it increases the chance of successful communication. - IP addresses are owned by the complete host on Linux, not by - particular interfaces. Only for more complex setups like load- - balancing, does this behaviour cause problems. - """ - if int(arp_filter) >= 0 and int(arp_filter) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/arp_filter' - .format(self._ifname), arp_filter) - else: - raise ValueError("Value out of range") - - def set_arp_accept(self, arp_accept): - """ - Define behavior for gratuitous ARP frames who's IP is not - already present in the ARP table: - 0 - don't create new entries in the ARP table - 1 - create new entries in the ARP table - - Both replies and requests type gratuitous arp will trigger the - ARP table to be updated, if this setting is on. - - If the ARP table already contains the IP address of the - gratuitous arp frame, the arp table will be updated regardless - if this setting is on or off. - """ - if int(arp_accept) >= 0 and int(arp_accept) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/arp_accept' - .format(self._ifname), arp_accept) - else: - raise ValueError("Value out of range") - - def set_arp_announce(self, arp_announce): - """ - Define different restriction levels for announcing the local - source IP address from IP packets in ARP requests sent on - interface: - 0 - (default) Use any local address, configured on any interface - 1 - Try to avoid local addresses that are not in the target's - subnet for this interface. This mode is useful when target - hosts reachable via this interface require the source IP - address in ARP requests to be part of their logical network - configured on the receiving interface. When we generate the - request we will check all our subnets that include the - target IP and will preserve the source address if it is from - such subnet. - - Increasing the restriction level gives more chance for - receiving answer from the resolved target while decreasing - the level announces more valid sender's information. - """ - if int(arp_announce) >= 0 and int(arp_announce) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/arp_announce' - .format(self._ifname), arp_announce) - else: - raise ValueError("Value out of range") - - def set_arp_ignore(self, arp_ignore): - """ - Define different modes for sending replies in response to received ARP - requests that resolve local target IP addresses: - - 0 - (default): reply for any local target IP address, configured - on any interface - 1 - reply only if the target IP address is local address - configured on the incoming interface - """ - if int(arp_ignore) >= 0 and int(arp_ignore) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/arp_ignore' - .format(self._ifname), arp_ignore) - else: - raise ValueError("Value out of range") - - def set_link_detect(self, link_filter): - """ - Configure kernel response in packets received on interfaces that are 'down' - - 0 - Allow packets to be received for the address on this interface - even if interface is disabled or no carrier. - - 1 - Ignore packets received if interface associated with the incoming - address is down. - - 2 - Ignore packets received if interface associated with the incoming - address is down or has no carrier. - - Default value is 0. Note that some distributions enable it in startup - scripts. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_link_detect(1) - """ - if int(link_filter) >= 0 and int(link_filter) <= 2: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter' - .format(self._ifname), link_filter) - else: - raise ValueError("Value out of range") - - def set_alias(self, ifalias=None): - """ - Set interface alias name used by e.g. SNMP - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_alias('VyOS upstream interface') - - to clear alias e.g. delete it use: - - >>> Interface('eth0').set_ifalias('') - """ - if not ifalias: - # clear interface alias - ifalias = '\0' - - self._write_sysfs('/sys/class/net/{}/ifalias' - .format(self._ifname), ifalias) - - def get_state(self): - """ - Enable (up) / Disable (down) an interface - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_state() - 'up' - """ - cmd = 'ip -json link show dev {}'.format(self._ifname) - tmp = self._cmd(cmd) - out = json.loads(tmp) - return out[0]['operstate'].lower() - - def set_state(self, state): - """ - Enable (up) / Disable (down) an interface - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_state('down') - >>> Interface('eth0').get_state() - 'down' - """ - if state not in ['up', 'down']: - raise ValueError('state must be "up" or "down"') - - # Assemble command executed on system. Unfortunately there is no way - # to up/down an interface via sysfs - cmd = 'ip link set dev {} {}'.format(self._ifname, state) - return self._cmd(cmd) - - def set_proxy_arp(self, enable): - """ - Set per interface proxy ARP configuration - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_proxy_arp(1) - """ - if int(enable) >= 0 and int(enable) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp' - .format(self._ifname), enable) - else: - raise ValueError("Value out of range") - - def set_proxy_arp_pvlan(self, enable): - """ - Private VLAN proxy arp. - Basically allow proxy arp replies back to the same interface - (from which the ARP request/solicitation was received). - - This is done to support (ethernet) switch features, like RFC - 3069, where the individual ports are NOT allowed to - communicate with each other, but they are allowed to talk to - the upstream router. As described in RFC 3069, it is possible - to allow these hosts to communicate through the upstream - router by proxy_arp'ing. Don't need to be used together with - proxy_arp. - - This technology is known by different names: - In RFC 3069 it is called VLAN Aggregation. - Cisco and Allied Telesyn call it Private VLAN. - Hewlett-Packard call it Source-Port filtering or port-isolation. - Ericsson call it MAC-Forced Forwarding (RFC Draft). - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_proxy_arp_pvlan(1) - """ - if int(enable) >= 0 and int(enable) <= 1: - return self._write_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan' - .format(self._ifname), enable) - else: - raise ValueError("Value out of range") - - def get_addr(self): - """ - Retrieve assigned IPv4 and IPv6 addresses from given interface. - This is done using the netifaces and ipaddress python modules. - - Example: - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').get_addrs() - ['172.16.33.30/24', 'fe80::20c:29ff:fe11:a174/64'] - """ - - ipv4 = [] - ipv6 = [] - - if AF_INET in ifaddresses(self._ifname).keys(): - for v4_addr in ifaddresses(self._ifname)[AF_INET]: - # we need to manually assemble a list of IPv4 address/prefix - prefix = '/' + \ - str(IPv4Network('0.0.0.0/' + v4_addr['netmask']).prefixlen) - ipv4.append(v4_addr['addr'] + prefix) - - if AF_INET6 in ifaddresses(self._ifname).keys(): - for v6_addr in ifaddresses(self._ifname)[AF_INET6]: - # Note that currently expanded netmasks are not supported. That means - # 2001:db00::0/24 is a valid argument while 2001:db00::0/ffff:ff00:: not. - # see https://docs.python.org/3/library/ipaddress.html - bits = bin( - int(v6_addr['netmask'].replace(':', ''), 16)).count('1') - prefix = '/' + str(bits) - - # we alsoneed to remove the interface suffix on link local - # addresses - v6_addr['addr'] = v6_addr['addr'].split('%')[0] - ipv6.append(v6_addr['addr'] + prefix) - - return ipv4 + ipv6 - - def add_addr(self, addr): - """ - Add IP(v6) address to interface. Address is only added if it is not - already assigned to that interface. - - addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6! - IPv4: add IPv4 address to interface - IPv6: add IPv6 address to interface - dhcp: start dhclient (IPv4) on interface - dhcpv6: start dhclient (IPv6) on interface - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.add_addr('192.0.2.1/24') - >>> j.add_addr('2001:db8::ffff/64') - >>> j.get_addr() - ['192.0.2.1/24', '2001:db8::ffff/64'] - """ - - # cache new IP address which is assigned to interface - self._addr.append(addr) - - # we can not have both DHCP and static IPv4 addresses assigned to an interface - if 'dhcp' in self._addr: - for addr in self._addr: - # do not change below 'if' ordering esle you will get an exception as: - # ValueError: 'dhcp' does not appear to be an IPv4 or IPv6 address - if addr != 'dhcp' and is_ipv4(addr): - raise ConfigError("Can't configure both static IPv4 and DHCP address on the same interface") - - if addr == 'dhcp': - self._set_dhcp() - elif addr == 'dhcpv6': - self._set_dhcpv6() - else: - if not is_intf_addr_assigned(self._ifname, addr): - cmd = 'ip addr add "{}" dev "{}"'.format(addr, self._ifname) - return self._cmd(cmd) - - def del_addr(self, addr): - """ - Delete IP(v6) address to interface. Address is only added if it is - assigned to that interface. - - addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6! - IPv4: delete IPv4 address from interface - IPv6: delete IPv6 address from interface - dhcp: stop dhclient (IPv4) on interface - dhcpv6: stop dhclient (IPv6) on interface - - Example: - >>> from vyos.ifconfig import Interface - >>> j = Interface('eth0') - >>> j.add_addr('2001:db8::ffff/64') - >>> j.add_addr('192.0.2.1/24') - >>> j.get_addr() - ['192.0.2.1/24', '2001:db8::ffff/64'] - >>> j.del_addr('192.0.2.1/24') - >>> j.get_addr() - ['2001:db8::ffff/64'] - """ - if addr == 'dhcp': - self._del_dhcp() - elif addr == 'dhcpv6': - self._del_dhcpv6() - else: - if is_intf_addr_assigned(self._ifname, addr): - cmd = 'ip addr del "{}" dev "{}"'.format(addr, self._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 --start --quiet --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._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._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) - - # https://bugs.launchpad.net/ubuntu/+source/ifupdown/+bug/1447715 - # - # wee need to wait for IPv6 DAD to finish once and interface is added - # this suxx :-( - sleep(5) - - # no longer accept router announcements on this interface - self._write_sysfs('/proc/sys/net/ipv6/conf/{}/accept_ra' - .format(self._ifname), 0) - - # assemble command-line to start DHCPv6 client (dhclient) - cmd = 'start-stop-daemon --start --quiet --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._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 --stop --quiet --pidfile {}'.format(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._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']]] - tx = [['bytes','packets','errors','dropped','carrier','collisions'],[stats['tx_bytes'],stats['tx_packets'],stats['tx_errors'],stats['tx_dropped'],stats['tx_carrier_errors'],stats['collisions']]] - output = "RX: \n" - output += tabulate(rx,headers="firstrow",numalign="right",tablefmt="plain") - output += "\n\nTX: \n" - output += tabulate(tx,headers="firstrow",numalign="right",tablefmt="plain") - print(' '.join(('\n'+output.lstrip()).splitlines(True))) - - def get_interface_stats(self): - interface_stats = dict() - devices = [f for f in glob.glob("/sys/class/net/**/statistics")] - for dev_path in devices: - metrics = [f for f in glob.glob(dev_path +"/**")] - dev = re.findall(r"/sys/class/net/(.*)/statistics",dev_path)[0] - dev_dict = dict() - for metric_path in metrics: - metric = metric_path.replace(dev_path+"/","") - if isfile(metric_path): - data = open(metric_path, 'r').read()[:-1] - dev_dict[metric] = int(data) - interface_stats[dev] = dev_dict - - return interface_stats[self._ifname] - -class LoopbackIf(Interface): - - """ - The loopback device is a special, virtual network interface that your router - uses to communicate with itself. - """ - - def __init__(self, ifname): - super().__init__(ifname, type='loopback') - - def remove(self): - """ - Loopback interface can not be deleted from operating system. We can - only remove all assigned IP addresses. - - Example: - >>> from vyos.ifconfig import Interface - >>> i = LoopbackIf('lo').remove() - """ - # remove all assigned IP addresses from interface - for addr in self.get_addr(): - if addr in ["127.0.0.1/8", "::1/128"]: - # Do not allow deletion of the default loopback addresses as - # this will cause weird system behavior like snmp/ssh no longer - # operating as expected, see https://phabricator.vyos.net/T2034. - continue - - self.del_addr(addr) - -class DummyIf(Interface): - - """ - A dummy interface is entirely virtual like, for example, the loopback - interface. The purpose of a dummy interface is to provide a device to route - packets through without actually transmitting them. - """ - - def __init__(self, ifname): - super().__init__(ifname, type='dummy') - - -class STPIf(Interface): - """ - A spanning-tree capable interface. This applies only to bridge port member - interfaces! - """ - def __init__(self, ifname): - super().__init__(ifname) - - def set_path_cost(self, cost): - """ - Set interface path cost, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_path_cost(4) - """ - if not os.path.isfile('/sys/class/net/{}/brport/path_cost' - .format(self._ifname)): - raise TypeError('{} is not a bridge port member'.format(self._ifname)) - - return self._write_sysfs('/sys/class/net/{}/brport/path_cost' - .format(self._ifname), cost) - - def set_path_priority(self, priority): - """ - Set interface path priority, only relevant for STP enabled interfaces - - Example: - - >>> from vyos.ifconfig import Interface - >>> Interface('eth0').set_path_priority(4) - """ - if not os.path.isfile('/sys/class/net/{}/brport/priority' - .format(self._ifname)): - raise TypeError('{} is not a bridge port member'.format(self._ifname)) - - return self._write_sysfs('/sys/class/net/{}/brport/priority' - .format(self._ifname), priority) - - -class BridgeIf(Interface): - - """ - A bridge is a way to connect two Ethernet segments together in a protocol - independent way. Packets are forwarded based on Ethernet address, rather - than IP address (like a router). Since forwarding is done at Layer 2, all - protocols can go transparently through a bridge. - - The Linux bridge code implements a subset of the ANSI/IEEE 802.1d standard. - """ - - def __init__(self, ifname): - super().__init__(ifname, type='bridge') - - def set_ageing_time(self, time): - """ - Set bridge interface MAC address aging time in seconds. Internal kernel - representation is in centiseconds. Kernel default is 300 seconds. - - Example: - >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').ageing_time(2) - """ - time = int(time) * 100 - return self._write_sysfs('/sys/class/net/{}/bridge/ageing_time' - .format(self._ifname), time) - - def set_forward_delay(self, time): - """ - Set bridge forwarding delay in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').forward_delay(15) - """ - return self._write_sysfs('/sys/class/net/{}/bridge/forward_delay' - .format(self._ifname), (int(time) * 100)) - - def set_hello_time(self, time): - """ - Set bridge hello time in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').set_hello_time(2) - """ - return self._write_sysfs('/sys/class/net/{}/bridge/hello_time' - .format(self._ifname), (int(time) * 100)) - - def set_max_age(self, time): - """ - Set bridge max message age in seconds. Internal Kernel representation - is in centiseconds. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').set_max_age(30) - """ - return self._write_sysfs('/sys/class/net/{}/bridge/max_age' - .format(self._ifname), (int(time) * 100)) - - def set_priority(self, priority): - """ - Set bridge max aging time in seconds. - - Example: - >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').set_priority(8192) - """ - return self._write_sysfs('/sys/class/net/{}/bridge/priority' - .format(self._ifname), priority) - - def set_stp(self, state): - """ - Set bridge STP (Spanning Tree) state. 0 -> STP disabled, 1 -> STP enabled - - Example: - >>> from vyos.ifconfig import BridgeIf - >>> BridgeIf('br0').set_stp(1) - """ - - if int(state) >= 0 and int(state) <= 1: - return self._write_sysfs('/sys/class/net/{}/bridge/stp_state' - .format(self._ifname), state) - else: - raise ValueError("Value out of range") - - - def set_multicast_querier(self, enable): - """ - Sets whether the bridge actively runs a multicast querier or not. When a - bridge receives a 'multicast host membership' query from another network - host, that host is tracked based on the time that the query was received - plus the multicast query interval time. - - Use enable=1 to enable or enable=0 to disable - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').set_multicast_querier(1) - """ - if int(enable) >= 0 and int(enable) <= 1: - return self._write_sysfs('/sys/class/net/{}/bridge/multicast_querier' - .format(self._ifname), enable) - else: - raise ValueError("Value out of range") - - - def add_port(self, interface): - """ - Add physical interface to bridge (member port) - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').add_port('eth0') - >>> BridgeIf('br0').add_port('eth1') - """ - cmd = 'ip link set dev {} master {}'.format(interface, self._ifname) - return self._cmd(cmd) - - def del_port(self, interface): - """ - Remove member port from bridge instance. - - Example: - >>> from vyos.ifconfig import Interface - >>> BridgeIf('br0').del_port('eth1') - """ - cmd = 'ip link set dev {} nomaster'.format(interface) - return self._cmd(cmd) - -class VLANIf(Interface): - """ - This class handels the creation and removal of a VLAN interface. It serves - as base class for BondIf and EthernetIf. - """ - def __init__(self, ifname, type=None): - super().__init__(ifname, type) - - def remove(self): - """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses and clear possible DHCP(v6) - client processes. - - Example: - >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - >>> i.remove() - """ - # 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._ifname + r'(?:\.\d+)(?:\.\d+)', f)] - - for vlan in vlan_ifs: - Interface(vlan).remove() - - # After deleting all Q-in-Q interfaces delete other VLAN interfaces - # 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._ifname + r'(?:\.\d+)', f)] - - for vlan in vlan_ifs: - Interface(vlan).remove() - - # All subinterfaces are now removed, continue on the physical interface - super().remove() - - - def add_vlan(self, vlan_id, ethertype='', ingress_qos='', egress_qos=''): - """ - A virtual LAN (VLAN) is any broadcast domain that is partitioned and - isolated in a computer network at the data link layer (OSI layer 2). - Use this function to create a new VLAN interface on a given physical - interface. - - This function creates both 802.1q and 802.1ad (Q-in-Q) interfaces. Proto - parameter is used to indicate VLAN type. - - A new object of type VLANIf is returned once the interface has been - created. - - @param ethertype: If specified, create 802.1ad or 802.1q Q-in-Q VLAN - interface - @param ingress_qos: Defines a mapping of VLAN header prio field to the - Linux internal packet priority on incoming frames. - @param ingress_qos: Defines a mapping of Linux internal packet priority - to VLAN header prio field but for outgoing frames. - - Example: - >>> from vyos.ifconfig import VLANIf - >>> i = VLANIf('eth0') - >>> i.add_vlan(10) - """ - vlan_ifname = self._ifname + '.' + str(vlan_id) - if not os.path.exists('/sys/class/net/{}'.format(vlan_ifname)): - self._vlan_id = int(vlan_id) - - if ethertype: - self._ethertype = ethertype - ethertype = 'proto {}'.format(ethertype) - - # Optional ingress QOS mapping - opt_i = '' - if ingress_qos: - opt_i = 'ingress-qos-map ' + ingress_qos - # Optional egress QOS mapping - opt_e = '' - if egress_qos: - opt_e = 'egress-qos-map ' + egress_qos - - # create interface in the system - cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \ - .format(intf=self._ifname, vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i) - self._cmd(cmd) - - # 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) - - - def del_vlan(self, vlan_id): - """ - Remove VLAN interface from operating system. Removing the interface - deconfigures all assigned IP addresses and clear possible DHCP(v6) - client processes. - - Example: - >>> from vyos.ifconfig import VLANIf - >>> i = VLANIf('eth0.10') - >>> i.del_vlan() - """ - vlan_ifname = self._ifname + '.' + str(vlan_id) - VLANIf(vlan_ifname).remove() - - -class EthernetIf(VLANIf): - """ - Abstraction of a Linux Ethernet Interface - """ - def __init__(self, ifname): - super().__init__(ifname) - - def get_driver_name(self): - """ - Return the driver name used by NIC. Some NICs don't support all - features e.g. changing link-speed, duplex - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.get_driver_name() - 'vmxnet3' - """ - link = os.readlink('/sys/class/net/{}/device/driver/module'.format(self._ifname)) - return os.path.basename(link) - - def set_flow_control(self, enable): - """ - Changes the pause parameters of the specified Ethernet device. - - @param enable: true -> enable pause frames, false -> disable pause frames - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_flow_control(True) - """ - if enable not in ['on', 'off']: - raise ValueError("Value out of range") - - if self.get_driver_name() in ['vmxnet3', 'virtio_net']: - self._debug_msg('{} driver does not support changing flow control settings!' - .format(self.get_driver_name())) - return - - # Get current flow control settings: - cmd = '/sbin/ethtool --show-pause {0}'.format(self._ifname) - tmp = self._cmd(cmd) - - # The above command returns - with tabs: - # - # Pause parameters for eth0: - # Autonegotiate: on - # RX: off - # TX: off - if re.search("Autonegotiate:\ton", tmp): - if enable == "on": - # flowcontrol is already enabled - no need to re-enable it again - # this will prevent the interface from flapping as applying the - # flow-control settings will take the interface down and bring - # it back up every time. - return - - # Assemble command executed on system. Unfortunately there is no way - # to change this setting via sysfs - cmd = '/sbin/ethtool --pause {0} autoneg {1} tx {1} rx {1}'.format( - self._ifname, enable) - try: - # An exception will be thrown if the settings are not changed - return self._cmd(cmd) - except CalledProcessError: - pass - - - def set_speed_duplex(self, speed, duplex): - """ - Set link speed in Mbit/s and duplex. - - @speed can be any link speed in MBit/s, e.g. 10, 100, 1000 auto - @duplex can be half, full, auto - - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_speed_duplex('auto', 'auto') - """ - - if speed not in ['auto', '10', '100', '1000', '2500', '5000', '10000', '25000', '40000', '50000', '100000', '400000']: - raise ValueError("Value out of range (speed)") - - if duplex not in ['auto', 'full', 'half']: - raise ValueError("Value out of range (duplex)") - - if self.get_driver_name() in ['vmxnet3', 'virtio_net']: - self._debug_msg('{} driver does not support changing speed/duplex settings!' - .format(self.get_driver_name())) - return - - # Get current speed and duplex settings: - cmd = '/sbin/ethtool {0}'.format(self._ifname) - tmp = self._cmd(cmd) - - if re.search("\tAuto-negotiation: on", tmp): - if speed == 'auto' and duplex == 'auto': - # bail out early as nothing is to change - return - else: - # read in current speed and duplex settings - cur_speed = 0 - cur_duplex = '' - for line in tmp.splitlines(): - if line.lstrip().startswith("Speed:"): - non_decimal = re.compile(r'[^\d.]+') - cur_speed = non_decimal.sub('', line) - continue - - if line.lstrip().startswith("Duplex:"): - cur_duplex = line.split()[-1].lower() - break - - if (cur_speed == speed) and (cur_duplex == duplex): - # bail out early as nothing is to change - return - - cmd = '/sbin/ethtool -s {}'.format(self._ifname) - if speed == 'auto' or duplex == 'auto': - cmd += ' autoneg on' - else: - cmd += ' speed {} duplex {} autoneg off'.format(speed, duplex) - - return self._cmd(cmd) - - - def set_gro(self, state): - """ - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_gro('on') - """ - if state not in ['on', 'off']: - raise ValueError('state must be "on" or "off"') - - cmd = '/sbin/ethtool -K {} gro {}'.format(self._ifname, state) - return self._cmd(cmd) - - - def set_gso(self, state): - """ - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_gso('on') - """ - if state not in ['on', 'off']: - raise ValueError('state must be "on" or "off"') - - cmd = '/sbin/ethtool -K {} gso {}'.format(self._ifname, state) - return self._cmd(cmd) - - - def set_sg(self, state): - """ - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_sg('on') - """ - if state not in ['on', 'off']: - raise ValueError('state must be "on" or "off"') - - cmd = '/sbin/ethtool -K {} sg {}'.format(self._ifname, state) - return self._cmd(cmd) - - - def set_tso(self, state): - """ - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_tso('on') - """ - if state not in ['on', 'off']: - raise ValueError('state must be "on" or "off"') - - cmd = '/sbin/ethtool -K {} tso {}'.format(self._ifname, state) - return self._cmd(cmd) - - - def set_ufo(self, state): - """ - Example: - >>> from vyos.ifconfig import EthernetIf - >>> i = EthernetIf('eth0') - >>> i.set_udp_offload('on') - """ - if state not in ['on', 'off']: - raise ValueError('state must be "on" or "off"') - - cmd = '/sbin/ethtool -K {} ufo {}'.format(self._ifname, state) - return self._cmd(cmd) - -class MACVLANIf(VLANIf): - """ - Abstraction of a Linux MACvlan interface - """ - def __init__(self, ifname, config=''): - self._ifname = ifname - - if not os.path.exists('/sys/class/net/{}'.format(self._ifname)) and config: - cmd = 'ip link add {intf} link {link} type macvlan mode {mode}' \ - .format(intf=self._ifname, link=config['link'], mode=config['mode']) - self._cmd(cmd) - - super().__init__(ifname, type='macvlan') - - @staticmethod - def get_config(): - """ - VXLAN interfaces require a configuration when they are added using - iproute2. This static method will provide the configuration dictionary - used by this class. - - Example: - >> dict = MACVLANIf().get_config() - """ - config = { - 'address': '', - 'link': 0, - 'mode': '' - } - return config - - def set_mode(self, mode): - """ - """ - - cmd = 'ip link set dev {} type macvlan mode {}'.format(self._ifname, mode) - return self._cmd(cmd) - - -class BondIf(VLANIf): - """ - The Linux bonding driver provides a method for aggregating multiple network - interfaces into a single logical "bonded" interface. The behavior of the - bonded interfaces depends upon the mode; generally speaking, modes provide - either hot standby or load balancing services. Additionally, link integrity - monitoring may be performed. - """ - def __init__(self, ifname): - super().__init__(ifname, type='bond') - - def remove(self): - """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses and clear possible DHCP(v6) - client processes. - Example: - >>> from vyos.ifconfig import Interface - >>> i = Interface('eth0') - >>> i.remove() - """ - # when a bond member gets deleted, all members are placed in A/D state - # even when they are enabled inside CLI. This will make the config - # and system look async. - slave_list = [] - for s in self.get_slaves(): - slave = { - 'ifname' : s, - 'state': Interface(s).get_state() - } - slave_list.append(slave) - - # remove bond master which places members in disabled state - super().remove() - - # replicate previous interface state before bond destruction back to - # physical interface - for slave in slave_list: - i = Interface(slave['ifname']) - i.set_state(slave['state']) - - - def set_hash_policy(self, mode): - """ - Selects the transmit hash policy to use for slave selection in - balance-xor, 802.3ad, and tlb modes. Possible values are: layer2, - layer2+3, layer3+4, encap2+3, encap3+4. - - The default value is layer2 - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').set_hash_policy('layer2+3') - """ - if not mode in ['layer2', 'layer2+3', 'layer3+4', 'encap2+3', 'encap3+4']: - raise ValueError("Value out of range") - return self._write_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy' - .format(self._ifname), mode) - - def set_arp_interval(self, interval): - """ - Specifies the ARP link monitoring frequency in milliseconds. - - The ARP monitor works by periodically checking the slave devices - to determine whether they have sent or received traffic recently - (the precise criteria depends upon the bonding mode, and the - state of the slave). Regular traffic is generated via ARP probes - issued for the addresses specified by the arp_ip_target option. - - If ARP monitoring is used in an etherchannel compatible mode - (modes 0 and 2), the switch should be configured in a mode that - evenly distributes packets across all links. If the switch is - configured to distribute the packets in an XOR fashion, all - replies from the ARP targets will be received on the same link - which could cause the other team members to fail. - - value of 0 disables ARP monitoring. The default value is 0. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').set_arp_interval('100') - """ - if int(interval) == 0: - """ - Specifies the MII link monitoring frequency in milliseconds. - This determines how often the link state of each slave is - inspected for link failures. A value of zero disables MII - link monitoring. A value of 100 is a good starting point. - """ - return self._write_sysfs('/sys/class/net/{}/bonding/miimon' - .format(self._ifname), interval) - else: - return self._write_sysfs('/sys/class/net/{}/bonding/arp_interval' - .format(self._ifname), interval) - - def get_arp_ip_target(self): - """ - Specifies the IP addresses to use as ARP monitoring peers when - arp_interval is > 0. These are the targets of the ARP request sent to - determine the health of the link to the targets. Specify these values - in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by - a comma. At least one IP address must be given for ARP monitoring to - function. The maximum number of targets that can be specified is 16. - - The default value is no IP addresses. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').get_arp_ip_target() - '192.0.2.1' - """ - return self._read_sysfs('/sys/class/net/{}/bonding/arp_ip_target' - .format(self._ifname)) - - def set_arp_ip_target(self, target): - """ - Specifies the IP addresses to use as ARP monitoring peers when - arp_interval is > 0. These are the targets of the ARP request sent to - determine the health of the link to the targets. Specify these values - in ddd.ddd.ddd.ddd format. Multiple IP addresses must be separated by - a comma. At least one IP address must be given for ARP monitoring to - function. The maximum number of targets that can be specified is 16. - - The default value is no IP addresses. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').set_arp_ip_target('192.0.2.1') - >>> BondIf('bond0').get_arp_ip_target() - '192.0.2.1' - """ - return self._write_sysfs('/sys/class/net/{}/bonding/arp_ip_target' - .format(self._ifname), target) - - def add_port(self, interface): - """ - Enslave physical interface to bond. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').add_port('eth0') - >>> BondIf('bond0').add_port('eth1') - """ - # An interface can only be added to a bond if it is in 'down' state. If - # interface is in 'up' state, the following Kernel error will be thrown: - # bond0: eth1 is up - this may be due to an out of date ifenslave. - Interface(interface).set_state('down') - - return self._write_sysfs('/sys/class/net/{}/bonding/slaves' - .format(self._ifname), '+' + interface) - - def del_port(self, interface): - """ - Remove physical port from bond - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').del_port('eth1') - """ - return self._write_sysfs('/sys/class/net/{}/bonding/slaves' - .format(self._ifname), '-' + interface) - - def get_slaves(self): - """ - Return a list with all configured slave interfaces on this bond. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').get_slaves() - ['eth1', 'eth2'] - """ - enslaved_ifs = [] - # retrieve real enslaved interfaces from OS kernel - sysfs_bond = '/sys/class/net/{}'.format(self._ifname) - if os.path.isdir(sysfs_bond): - for directory in os.listdir(sysfs_bond): - if 'lower_' in directory: - enslaved_ifs.append(directory.replace('lower_','')) - - return enslaved_ifs - - - def set_primary(self, interface): - """ - A string (eth0, eth2, etc) specifying which slave is the primary - device. The specified device will always be the active slave while it - is available. Only when the primary is off-line will alternate devices - be used. This is useful when one slave is preferred over another, e.g., - when one slave has higher throughput than another. - - The primary option is only valid for active-backup, balance-tlb and - balance-alb mode. - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').set_primary('eth2') - """ - if not interface: - # reset primary interface - interface = '\0' - - return self._write_sysfs('/sys/class/net/{}/bonding/primary' - .format(self._ifname), interface) - - def set_mode(self, mode): - """ - Specifies one of the bonding policies. The default is balance-rr - (round robin). - - Possible values are: balance-rr, active-backup, balance-xor, - broadcast, 802.3ad, balance-tlb, balance-alb - - NOTE: the bonding mode can not be changed when the bond itself has - slaves - - Example: - >>> from vyos.ifconfig import BondIf - >>> BondIf('bond0').set_mode('802.3ad') - """ - if not mode in [ - 'balance-rr', 'active-backup', 'balance-xor', 'broadcast', - '802.3ad', 'balance-tlb', 'balance-alb']: - raise ValueError("Value out of range") - - return self._write_sysfs('/sys/class/net/{}/bonding/mode' - .format(self._ifname), mode) - -class WireGuardIf(Interface): - """ - Wireguard interface class, contains a comnfig dictionary since - wireguard VPN is being comnfigured via the wg command rather than - writing the config into a file. Otherwise if a pre-shared key is used - (symetric enryption key), it would we exposed within multiple files. - Currently it's only within the config.boot if the config was saved. - - Example: - >>> from vyos.ifconfig import WireGuardIf as wg_if - >>> wg_intfc = wg_if("wg01") - >>> print (wg_intfc.wg_config) - {'private-key': None, 'keepalive': 0, 'endpoint': None, 'port': 0, - 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - >>> wg_intfc.wg_config['keepalive'] = 100 - >>> print (wg_intfc.wg_config) - {'private-key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, - 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} - """ - - def __init__(self, ifname): - super().__init__(ifname, type='wireguard') - - self.config = { - 'port': 0, - 'private-key': None, - 'pubkey': None, - 'psk': '/dev/null', - 'allowed-ips': [], - 'fwmark': 0x00, - 'endpoint': None, - 'keepalive': 0 - } - - def update(self): - if not self.config['private-key']: - raise ValueError("private key required") - else: - # fmask permission check? - pass - - cmd = "wg set {} ".format(self._ifname) - cmd += "listen-port {} ".format(self.config['port']) - cmd += "fwmark {} ".format(str(self.config['fwmark'])) - cmd += "private-key {} ".format(self.config['private-key']) - cmd += "peer {} ".format(self.config['pubkey']) - cmd += " preshared-key {} ".format(self.config['psk']) - cmd += " allowed-ips " - for aip in self.config['allowed-ips']: - if aip != self.config['allowed-ips'][-1]: - cmd += aip + "," - else: - cmd += aip - if self.config['endpoint']: - cmd += " endpoint {}".format(self.config['endpoint']) - cmd += " persistent-keepalive {}".format(self.config['keepalive']) - - self._cmd(cmd) - - # remove psk since it isn't required anymore and is saved in the cli - # config only !! - if self.config['psk'] != '/dev/null': - if os.path.exists(self.config['psk']): - os.remove(self.config['psk']) - - - def remove_peer(self, peerkey): - """ - Remove a peer of an interface, peers are identified by their public key. - Giving it a readable name is a vyos feature, to remove a peer the pubkey - and the interface is needed, to remove the entry. - """ - cmd = "wg set {0} peer {1} remove".format( - self._ifname, str(peerkey)) - return self._cmd(cmd) - - def op_show_interface(self): - wgdump = vyos.interfaces.wireguard_dump().get(self._ifname,None) - - c = Config() - c.set_level(["interfaces","wireguard",self._ifname]) - description = c.return_effective_value(["description"]) - ips = c.return_effective_values(["address"]) - - print ("interface: {}".format(self._ifname)) - if (description): - print (" description: {}".format(description)) - - if (ips): - print (" address: {}".format(", ".join(ips))) - print (" public key: {}".format(wgdump['public_key'])) - print (" private key: (hidden)") - print (" listening port: {}".format(wgdump['listen_port'])) - print () - - for peer in c.list_effective_nodes(["peer"]): - if wgdump['peers']: - pubkey = c.return_effective_value(["peer",peer,"pubkey"]) - if pubkey in wgdump['peers']: - wgpeer = wgdump['peers'][pubkey] - - print (" peer: {}".format(peer)) - print (" public key: {}".format(pubkey)) - - """ figure out if the tunnel is recently active or not """ - status = "inactive" - if (wgpeer['latest_handshake'] is None): - """ no handshake ever """ - status = "inactive" - else: - if int(wgpeer['latest_handshake']) > 0: - delta = timedelta(seconds=int(time.time() - wgpeer['latest_handshake'])) - print (" latest handshake: {}".format(delta)) - if (time.time() - int(wgpeer['latest_handshake']) < (60*5)): - """ Five minutes and the tunnel is still active """ - status = "active" - else: - """ it's been longer than 5 minutes """ - status = "inactive" - elif int(wgpeer['latest_handshake']) == 0: - """ no handshake ever """ - status = "inactive" - print (" status: {}".format(status)) - - if wgpeer['endpoint'] is not None: - print (" endpoint: {}".format(wgpeer['endpoint'])) - - if wgpeer['allowed_ips'] is not None: - print (" allowed ips: {}".format(",".join(wgpeer['allowed_ips']).replace(",",", "))) - - if wgpeer['transfer_rx'] > 0 or wgpeer['transfer_tx'] > 0: - rx_size =size(wgpeer['transfer_rx'],system=alternative) - tx_size =size(wgpeer['transfer_tx'],system=alternative) - print (" transfer: {} received, {} sent".format(rx_size,tx_size)) - - if wgpeer['persistent_keepalive'] is not None: - print (" persistent keepalive: every {} seconds".format(wgpeer['persistent_keepalive'])) - print() - super().op_show_interface_stats() - - -class VXLANIf(Interface): - """ - The VXLAN protocol is a tunnelling protocol designed to solve the - problem of limited VLAN IDs (4096) in IEEE 802.1q. With VXLAN the - size of the identifier is expanded to 24 bits (16777216). - - VXLAN is described by IETF RFC 7348, and has been implemented by a - number of vendors. The protocol runs over UDP using a single - destination port. This document describes the Linux kernel tunnel - device, there is also a separate implementation of VXLAN for - Openvswitch. - - Unlike most tunnels, a VXLAN is a 1 to N network, not just point to - point. A VXLAN device can learn the IP address of the other endpoint - either dynamically in a manner similar to a learning bridge, or make - use of statically-configured forwarding entries. - - For more information please refer to: - https://www.kernel.org/doc/Documentation/networking/vxlan.txt - """ - def __init__(self, ifname, config=''): - if config: - self._ifname = ifname - - if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): - # we assume that by default a multicast interface is created - group = 'group {}'.format(config['group']) - - # if remote host is specified we ignore the multicast address - if config['remote']: - group = 'remote {}'.format(config['remote']) - - # an underlay device is not always specified - dev = '' - if config['dev']: - dev = 'dev {}'.format(config['dev']) - - cmd = 'ip link add {intf} type vxlan id {vni} {grp_rem} {dev} dstport {port}' \ - .format(intf=self._ifname, vni=config['vni'], grp_rem=group, dev=dev, port=config['port']) - self._cmd(cmd) - - super().__init__(ifname, type='vxlan') - - @staticmethod - def get_config(): - """ - VXLAN interfaces require a configuration when they are added using - iproute2. This static method will provide the configuration dictionary - used by this class. - - Example: - >> dict = VXLANIf().get_config() - """ - config = { - 'vni': 0, - 'dev': '', - 'group': '', - 'port': 8472, # The Linux implementation of VXLAN pre-dates - # the IANA's selection of a standard destination port - 'remote': '' - } - return config - -class GeneveIf(Interface): - """ - Geneve: Generic Network Virtualization Encapsulation - - For more information please refer to: - https://tools.ietf.org/html/draft-gross-geneve-00 - https://www.redhat.com/en/blog/what-geneve - https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels/#geneve - https://lwn.net/Articles/644938/ - """ - def __init__(self, ifname, config=''): - if config: - self._ifname = ifname - if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): - cmd = 'ip link add name {} type geneve id {} remote {}' \ - .format(self._ifname, config['vni'], config['remote']) - self._cmd(cmd) - - # interface is always A/D down. It needs to be enabled explicitly - self.set_state('down') - - super().__init__(ifname, type='geneve') - - @staticmethod - def get_config(): - """ - GENEVE interfaces require a configuration when they are added using - iproute2. This static method will provide the configuration dictionary - used by this class. - - Example: - >> dict = GeneveIf().get_config() - """ - config = { - 'vni': 0, - 'remote': '' - } - return config - -class L2TPv3If(Interface): - """ - The Linux bonding driver provides a method for aggregating multiple network - interfaces into a single logical "bonded" interface. The behavior of the - bonded interfaces depends upon the mode; generally speaking, modes provide - either hot standby or load balancing services. Additionally, link integrity - monitoring may be performed. - """ - def __init__(self, ifname, config=''): - self._config = {} - if config: - self._ifname = ifname - self._config = config - if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): - # create tunnel interface - cmd = 'ip l2tp add tunnel tunnel_id {} '.format(config['tunnel_id']) - cmd += 'peer_tunnel_id {} '.format(config['peer_tunnel_id']) - cmd += 'udp_sport {} '.format(config['local_port']) - cmd += 'udp_dport {} '.format(config['remote_port']) - cmd += 'encap {} '.format(config['encapsulation']) - cmd += 'local {} '.format(config['local_address']) - cmd += 'remote {} '.format(config['remote_address']) - self._cmd(cmd) - - # setup session - cmd = 'ip l2tp add session name {} '.format(self._ifname) - cmd += 'tunnel_id {} '.format(config['tunnel_id']) - cmd += 'session_id {} '.format(config['session_id']) - cmd += 'peer_session_id {} '.format(config['peer_session_id']) - self._cmd(cmd) - - # interface is always A/D down. It needs to be enabled explicitly - self.set_state('down') - - super().__init__(ifname, type='l2tp') - - def remove(self): - """ - Remove interface from operating system. Removing the interface - deconfigures all assigned IP addresses. - Example: - >>> from vyos.ifconfig import L2TPv3If - >>> i = L2TPv3If('l2tpeth0') - >>> i.remove() - """ - - if os.path.exists('/sys/class/net/{}'.format(self._ifname)): - # interface is always A/D down. It needs to be enabled explicitly - self.set_state('down') - - if self._config['tunnel_id'] and self._config['session_id']: - cmd = 'ip l2tp del session tunnel_id {} '.format(self._config['tunnel_id']) - cmd += 'session_id {} '.format(self._config['session_id']) - self._cmd(cmd) - - if self._config['tunnel_id']: - cmd = 'ip l2tp del tunnel tunnel_id {} '.format(self._config['tunnel_id']) - self._cmd(cmd) - - @staticmethod - def get_config(): - """ - L2TPv3 interfaces require a configuration when they are added using - iproute2. This static method will provide the configuration dictionary - used by this class. - - Example: - >> dict = L2TPv3If().get_config() - """ - config = { - 'peer_tunnel_id': '', - 'local_port': 0, - 'remote_port': 0, - 'encapsulation': 'udp', - 'local_address': '', - 'remote_address': '', - 'session_id': '', - 'tunnel_id': '', - 'peer_session_id': '' - } - return config |