summaryrefslogtreecommitdiff
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/vyos/configdict.py116
-rw-r--r--python/vyos/configinterface.py153
-rw-r--r--python/vyos/configsession.py3
-rw-r--r--python/vyos/configtree.py8
-rw-r--r--python/vyos/defaults.py2
-rw-r--r--python/vyos/hostsd_client.py69
-rw-r--r--python/vyos/ifconfig.py1449
-rw-r--r--python/vyos/interfaceconfig.py376
-rw-r--r--python/vyos/interfaces.py11
-rw-r--r--python/vyos/util.py23
-rw-r--r--python/vyos/validate.py6
11 files changed, 1686 insertions, 530 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py
index 157011839..4bc8863bb 100644
--- a/python/vyos/configdict.py
+++ b/python/vyos/configdict.py
@@ -18,6 +18,7 @@ A library for retrieving value dicts from VyOS configs in a declarative fashion.
"""
+from vyos import ConfigError
def retrieve_config(path_hash, base_path, config):
"""
@@ -78,3 +79,118 @@ def retrieve_config(path_hash, base_path, config):
config_hash[k][node] = retrieve_config(inner_hash, path + [node], config)
return config_hash
+
+
+def list_diff(first, second):
+ """
+ Diff two dictionaries and return only unique items
+ """
+ second = set(second)
+ return [item for item in first if item not in second]
+
+
+def get_ethertype(ethertype_val):
+ if ethertype_val == '0x88A8':
+ return '802.1ad'
+ elif ethertype_val == '0x8100':
+ return '802.1q'
+ else:
+ raise ConfigError('invalid ethertype "{}"'.format(ethertype_val))
+
+
+def vlan_to_dict(conf):
+ """
+ Common used function which will extract VLAN related information from config
+ and represent the result as Python dictionary.
+
+ Function call's itself recursively if a vif-s/vif-c pair is detected.
+ """
+ vlan = {
+ 'id': conf.get_level().split()[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100'
+ 'address': [],
+ 'address_remove': [],
+ 'description': '',
+ 'dhcp_client_id': '',
+ 'dhcp_hostname': '',
+ 'dhcpv6_prm_only': False,
+ 'dhcpv6_temporary': False,
+ 'disable': False,
+ 'disable_link_detect': 1,
+ 'mac': '',
+ 'mtu': 1500
+ }
+ # retrieve configured interface addresses
+ if conf.exists('address'):
+ vlan['address'] = conf.return_values('address')
+
+ # Determine interface addresses (currently effective) - to determine which
+ # address is no longer valid and needs to be removed from the bond
+ eff_addr = conf.return_effective_values('address')
+ act_addr = conf.return_values('address')
+ vlan['address_remove'] = list_diff(eff_addr, act_addr)
+
+ # retrieve interface description
+ if conf.exists('description'):
+ vlan['description'] = conf.return_value('description')
+
+ # get DHCP client identifier
+ if conf.exists('dhcp-options client-id'):
+ vlan['dhcp_client_id'] = conf.return_value('dhcp-options client-id')
+
+ # DHCP client host name (overrides the system host name)
+ if conf.exists('dhcp-options host-name'):
+ vlan['dhcp_hostname'] = conf.return_value('dhcp-options host-name')
+
+ # DHCPv6 only acquire config parameters, no address
+ if conf.exists('dhcpv6-options parameters-only'):
+ vlan['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only')
+
+ # DHCPv6 temporary IPv6 address
+ if conf.exists('dhcpv6-options temporary'):
+ vlan['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary')
+
+ # ignore link state changes
+ if conf.exists('disable-link-detect'):
+ vlan['disable_link_detect'] = 2
+
+ # disable bond interface
+ if conf.exists('disable'):
+ vlan['disable'] = True
+
+ # Media Access Control (MAC) address
+ if conf.exists('mac'):
+ vlan['mac'] = conf.return_value('mac')
+
+ # Maximum Transmission Unit (MTU)
+ if conf.exists('mtu'):
+ vlan['mtu'] = int(conf.return_value('mtu'))
+
+ # ethertype is mandatory on vif-s nodes and only exists here!
+ # check if this is a vif-s node at all:
+ if conf.get_level().split()[-2] == 'vif-s':
+ vlan['vif_c'] = []
+ vlan['vif_c_remove'] = []
+
+ # ethertype uses a default of 0x88A8
+ tmp = '0x88A8'
+ if conf.exists('ethertype'):
+ tmp = conf.return_value('ethertype')
+ vlan['ethertype'] = get_ethertype(tmp)
+
+ # get vif-c interfaces (currently effective) - to determine which vif-c
+ # interface is no longer present and needs to be removed
+ eff_intf = conf.list_effective_nodes('vif-c')
+ act_intf = conf.list_nodes('vif-c')
+ vlan['vif_c_remove'] = list_diff(eff_intf, act_intf)
+
+ # check if there is a Q-in-Q vlan customer interface
+ # and call this function recursively
+ if conf.exists('vif-c'):
+ cfg_level = conf.get_level()
+ # add new key (vif-c) to dictionary
+ for vif in conf.list_nodes('vif-c'):
+ # set config level to vif interface
+ conf.set_level(cfg_level + ' vif-c ' + vif)
+ vlan['vif_c'].append(vlan_to_dict(conf))
+
+ return vlan
diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py
deleted file mode 100644
index 0f5b0842c..000000000
--- a/python/vyos/configinterface.py
+++ /dev/null
@@ -1,153 +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 vyos.validate
-
-def validate_mac_address(addr):
- # a mac address consits out of 6 octets
- octets = len(addr.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(addr.split(':')[0]) & 1:
- raise ValueError('{} is a multicast MAC address'.format(addr))
-
- # overall mac address is not allowed to be 00:00:00:00:00:00
- if sum(int(i, 16) for i in addr.split(':')) == 0:
- raise ValueError('00:00:00:00:00:00 is not a valid MAC address')
-
- # check for VRRP mac address
- if addr.split(':')[0] == '0' and addr.split(':')[1] == '0' and addr.split(':')[2] == '94' and addr.split(':')[3] == '0' and addr.split(':')[4] == '1':
- raise ValueError('{} is a VRRP MAC address')
-
- pass
-
-def set_mac_address(intf, addr):
- """
- Configure interface mac address using iproute2 command
- """
- validate_mac_address(addr)
-
- os.system('ip link set {} address {}'.format(intf, addr))
- pass
-
-def set_description(intf, desc):
- """
- Sets the interface secription reported usually by SNMP
- """
- with open('/sys/class/net/' + intf + '/ifalias', 'w') as f:
- f.write(desc)
-
- pass
-
-def set_arp_cache_timeout(intf, tmoMS):
- """
- Configure the ARP cache entry timeout in milliseconds
- """
- with open('/proc/sys/net/ipv4/neigh/' + intf + '/base_reachable_time_ms', 'w') as f:
- f.write(tmoMS)
-
- pass
-
-def set_multicast_querier(intf, 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
- """
-
- if int(enable) >= 0 and int(enable) <= 1:
- with open('/sys/devices/virtual/net/' + intf + '/bridge/multicast_querier', 'w') as f:
- f.write(str(enable))
- else:
- raise ValueError("malformed configuration string on interface {}: enable={}".format(intf, enable))
-
- pass
-
-def set_link_detect(intf, enable):
- """
- 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.
-
- Kernel Source: Documentation/networking/ip-sysctl.txt
- """
-
- # Note can't use sysctl it is broken for vif name because of dots
- # link_filter values:
- # 0 - always receive
- # 1 - ignore receive if admin_down
- # 2 - ignore receive if admin_down or link down
-
- with open('/proc/sys/net/ipv4/conf/' + intf + '/link_filter', 'w') as f:
- if enable == True or enable == 1:
- f.write('2')
- if os.path.isfile('/usr/bin/vtysh'):
- os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "link-detect"'.format(intf))
- else:
- f.write('1')
- if os.path.isfile('/usr/bin/vtysh'):
- os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf))
-
- pass
-
-def add_interface_address(intf, addr):
- """
- Configure an interface IPv4/IPv6 address
- """
- if addr == "dhcp":
- os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=start'.format(intf))
- elif addr == "dhcpv6":
- os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --start -ifname "{}"'.format(intf))
- elif vyos.validate.is_ipv4(addr):
- if not vyos.validate.is_intf_addr_assigned(intf, addr):
- print("Assigning {} to {}".format(addr, intf))
- os.system('sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, intf))
- elif vyos.validate.is_ipv6(addr):
- if not vyos.validate.is_intf_addr_assigned(intf, addr):
- print("Assigning {} to {}".format(addr, intf))
- os.system('sudo ip -6 addr add "{}" dev "{}"'.format(addr, intf))
- else:
- raise ConfigError('{} is not a valid interface address'.format(addr))
-
- pass
-
-def remove_interface_address(intf, addr):
- """
- Remove IPv4/IPv6 address from given interface
- """
-
- if addr == "dhcp":
- os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=stop'.format(intf))
- elif addr == "dhcpv6":
- os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --stop -ifname "{}"'.format(intf))
- elif vyos.validate.is_ipv4(addr):
- os.system('ip -4 addr del "{}" dev "{}"'.format(addr, intf))
- elif vyos.validate.is_ipv6(addr):
- os.system('ip -6 addr del "{}" dev "{}"'.format(addr, intf))
- else:
- raise ConfigError('{} is not a valid interface address'.format(addr))
-
- pass
diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py
index 8626839f2..acbdd3d5f 100644
--- a/python/vyos/configsession.py
+++ b/python/vyos/configsession.py
@@ -24,6 +24,7 @@ COMMENT = '/opt/vyatta/sbin/my_comment'
COMMIT = '/opt/vyatta/sbin/my_commit'
DISCARD = '/opt/vyatta/sbin/my_discard'
SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig']
+LOAD_CONFIG = ['/bin/cli-shell-api', 'loadFile']
# Default "commit via" string
APP = "vyos-http-api"
@@ -155,3 +156,5 @@ class ConfigSession(object):
if format == 'raw':
return config_data
+ def load_config(self, file_path):
+ self.__run_command(LOAD_CONFIG + [file_path])
diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py
index a812b62ec..8832a5a63 100644
--- a/python/vyos/configtree.py
+++ b/python/vyos/configtree.py
@@ -185,6 +185,14 @@ class ConfigTree(object):
return self.__to_commands(self.__config).decode()
def set(self, path, value=None, replace=True):
+ """Set new entry in VyOS configuration.
+ path: configuration path e.g. 'system dns forwarding listen-address'
+ value: value to be added to node, e.g. '172.18.254.201'
+ replace: True: current occurance will be replaced
+ False: new value will be appended to current occurances - use
+ this for adding values to a multi node
+ """
+
check_path(path)
path_str = " ".join(map(str, path)).encode()
diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py
index 3e4c02562..85d27d60d 100644
--- a/python/vyos/defaults.py
+++ b/python/vyos/defaults.py
@@ -29,7 +29,7 @@ cfg_vintage = 'vyatta'
commit_lock = '/opt/vyatta/config/.lock'
https_data = {
- 'listen_address' : [ '127.0.0.1' ]
+ 'listen_addresses' : { '*': ['_'] }
}
api_data = {
diff --git a/python/vyos/hostsd_client.py b/python/vyos/hostsd_client.py
new file mode 100644
index 000000000..f009aba98
--- /dev/null
+++ b/python/vyos/hostsd_client.py
@@ -0,0 +1,69 @@
+import json
+
+import zmq
+
+
+SOCKET_PATH = "ipc:///run/vyos-hostsd.sock"
+
+
+class VyOSHostsdError(Exception):
+ pass
+
+
+class Client(object):
+ def __init__(self):
+ try:
+ context = zmq.Context()
+ self.__socket = context.socket(zmq.REQ)
+ self.__socket.RCVTIMEO = 10000 #ms
+ self.__socket.setsockopt(zmq.LINGER, 0)
+ self.__socket.connect(SOCKET_PATH)
+ except zmq.error.Again:
+ raise VyOSHostsdError("Could not connect to vyos-hostsd")
+
+ def _communicate(self, msg):
+ try:
+ request = json.dumps(msg).encode()
+ self.__socket.send(request)
+
+ reply_msg = self.__socket.recv().decode()
+ reply = json.loads(reply_msg)
+ if 'error' in reply:
+ raise VyOSHostsdError(reply['error'])
+ else:
+ return reply["data"]
+ except zmq.error.Again:
+ raise VyOSHostsdError("Could not connect to vyos-hostsd")
+
+ def set_host_name(self, host_name, domain_name, search_domains):
+ msg = {
+ 'type': 'host_name',
+ 'op': 'set',
+ 'data': {
+ 'host_name': host_name,
+ 'domain_name': domain_name,
+ 'search_domains': search_domains
+ }
+ }
+ self._communicate(msg)
+
+ def add_hosts(self, tag, hosts):
+ msg = {'type': 'hosts', 'op': 'add', 'tag': tag, 'data': hosts}
+ self._communicate(msg)
+
+ def delete_hosts(self, tag):
+ msg = {'type': 'hosts', 'op': 'delete', 'tag': tag}
+ self._communicate(msg)
+
+ def add_name_servers(self, tag, servers):
+ msg = {'type': 'name_servers', 'op': 'add', 'tag': tag, 'data': servers}
+ self._communicate(msg)
+
+ def delete_name_servers(self, tag):
+ msg = {'type': 'name_servers', 'op': 'delete', 'tag': tag}
+ self._communicate(msg)
+
+ def get_name_servers(self, tag):
+ msg = {'type': 'name_servers', 'op': 'get', 'tag': tag}
+ return self._communicate(msg)
+
diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py
new file mode 100644
index 000000000..62bf94d79
--- /dev/null
+++ b/python/vyos/ifconfig.py
@@ -0,0 +1,1449 @@
+# 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 subprocess
+import jinja2
+
+from vyos.validate import *
+from ipaddress import IPv4Network, IPv6Address
+from netifaces import ifaddresses, AF_INET, AF_INET6
+from time import sleep
+
+dhcp_cfg = """
+# generated by ifconfig.py
+option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
+interface "{{ intf }}" {
+ send host-name "{{ hostname }}";
+ request subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu;
+}
+"""
+
+dhcpv6_cfg = """
+# generated by ifconfig.py
+interface "{{ intf }}" {
+ request routers, domain-name-servers, domain-name;
+}
+"""
+
+dhclient_base = r'/var/lib/dhcp/dhclient_'
+
+
+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)
+ self._state = 'down'
+
+ 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'
+
+ def _debug_msg(self, msg):
+ if os.path.isfile('/tmp/vyos.ifconfig.debug'):
+ print('DEBUG/{:<6} {}'.format(self._ifname, msg))
+
+ 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. 'bond0(?:\.\d+){1,2}' will match vif and vif-s/vif-c
+ # subinterfaces
+ vlan_ifs = [f for f in os.listdir(r'/sys/class/net') \
+ if re.match(self._ifname + r'(?:\.\d+){1,2}', f)]
+
+ for vlan in vlan_ifs:
+ Interface(vlan).remove()
+
+ # All subinterfaces are now removed, continue on the physical interface
+
+ # stop DHCP(v6) if running
+ self._del_dhcp()
+ self._del_dhcpv6()
+
+ # 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)
+ self._cmd(cmd)
+
+ def _cmd(self, command):
+ self._debug_msg("cmd '{}'".format(command))
+
+ process = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True)
+ proc_stdout = process.communicate()[0].strip()
+
+ # add exception handling code
+ pass
+
+ 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
+
+ @property
+ def mtu(self):
+ """
+ Get/set interface mtu in bytes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').mtu
+ '1500'
+ """
+ return self._read_sysfs('/sys/class/net/{0}/mtu'
+ .format(self._ifname))
+
+ @mtu.setter
+ def mtu(self, mtu):
+ """
+ Get/set interface mtu in bytes.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').mtu = 1400
+ >>> Interface('eth0').mtu
+ '1400'
+ """
+ if mtu < 68 or mtu > 9000:
+ raise ValueError('Invalid MTU size: "{}"'.format(mru))
+
+ return self._write_sysfs('/sys/class/net/{0}/mtu'
+ .format(self._ifname), mtu)
+
+ @property
+ def mac(self):
+ """
+ Get/set interface mac address
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').mac
+ '00:0c:29:11:aa:cc'
+ """
+ return self._read_sysfs('/sys/class/net/{0}/address'
+ .format(self._ifname))
+
+ @mac.setter
+ def mac(self, mac):
+ """
+ Get/set interface mac address
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').mac = '00:90:43:fe:fe:1b'
+ >>> Interface('eth0').mac
+ '00:90:43:fe:fe:1b'
+ """
+ # 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]) & 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)
+ self._cmd(cmd)
+
+ @property
+ def arp_cache_tmo(self):
+ """
+ Get configured ARP cache timeout value from interface in seconds.
+ Internal Kernel representation is in milliseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').arp_cache_tmo
+ '30'
+ """
+ return (self._read_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms'
+ .format(self._ifname)) / 1000)
+
+ @arp_cache_tmo.setter
+ def 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').arp_cache_tmo = '40'
+ """
+ return self._write_sysfs('/proc/sys/net/ipv4/neigh/{0}/base_reachable_time_ms'
+ .format(self._ifname), (int(tmo) * 1000))
+
+ @property
+ def link_detect(self):
+ """
+ How does the kernel act when receiving packets on 'down' interfaces
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').link_detect
+ '0'
+ """
+ return self._read_sysfs('/proc/sys/net/ipv4/conf/{0}/link_filter'
+ .format(self._ifname))
+
+ @link_detect.setter
+ def link_detect(self, link_filter):
+ """
+ Konfigure 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').link_detect = '1'
+ """
+ if link_filter >= 0 and 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")
+
+ @property
+ def ifalias(self):
+ """
+ Get/set interface alias name
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').ifalias
+ ''
+ """
+ return self._read_sysfs('/sys/class/net/{0}/ifalias'
+ .format(self._ifname))
+
+ @ifalias.setter
+ def ifalias(self, ifalias=None):
+ """
+ Get/set interface alias name
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').ifalias = 'VyOS upstream interface'
+ >>> Interface('eth0').ifalias
+ 'VyOS upstream interface'
+
+ to clear interface alias e.g. delete it use:
+
+ >>> Interface('eth0').ifalias = ''
+ >>> Interface('eth0').ifalias
+ ''
+ """
+ if not ifalias:
+ # clear interface alias
+ ifalias = '\0'
+
+ self._write_sysfs('/sys/class/net/{0}/ifalias'
+ .format(self._ifname), ifalias)
+
+ @property
+ def state(self):
+ """
+ Enable (up) / Disable (down) an interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').state
+ 'up'
+ """
+ return self._read_sysfs('/sys/class/net/{0}/operstate'
+ .format(self._ifname))
+
+ @state.setter
+ def state(self, state):
+ """
+ Enable (up) / Disable (down) an interface
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').state = 'down'
+ >>> Interface('eth0').state
+ 'down'
+ """
+ if state not in ['up', 'down']:
+ raise ValueError('state must be "up" or "down"')
+
+ self._state = state
+
+ # 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)
+ self._cmd(cmd)
+
+ @property
+ def proxy_arp(self):
+ """
+ Get current proxy ARP configuration from sysfs. Default: 0
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').proxy_arp
+ '0'
+ """
+ return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp'
+ .format(self._ifname))
+
+ @proxy_arp.setter
+ def proxy_arp(self, enable):
+ """
+ Set per interface proxy ARP configuration
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').proxy_arp = 1
+ >>> Interface('eth0').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")
+
+ @property
+ def proxy_arp_pvlan(self):
+ """
+ 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').proxy_arp_pvlan
+ '0'
+ """
+ return self._read_sysfs('/proc/sys/net/ipv4/conf/{}/proxy_arp_pvlan'
+ .format(self._ifname))
+
+ @proxy_arp_pvlan.setter
+ def 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').proxy_arp_pvlan = 1
+ >>> Interface('eth0').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']
+ """
+ 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)
+ 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)
+ self._cmd(cmd)
+
+ # 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 = {
+ 'hostname': 'vyos',
+ 'intf': self._ifname
+ }
+
+ # 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)
+
+ if self._state == 'up':
+ 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)
+ 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
+ cmd = 'start-stop-daemon --stop --quiet --pidfile {}'.format(
+ self._dhcp_pid_file)
+ 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 = {
+ 'intf': self._ifname
+ }
+
+ # 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)
+
+ if self._state == 'up':
+ # 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
+ cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=0'.format(self._ifname)
+ self._cmd(cmd)
+
+ # 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, self._ifname)
+ 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
+ cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=1'.format(self._ifname)
+ self._cmd(cmd)
+
+ # 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)
+
+
+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')
+
+
+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 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')
+
+ @property
+ def ageing_time(self):
+ """
+ Return configured bridge interface MAC address aging time in seconds.
+ Internal kernel representation is in centiseconds, thus its converted
+ in the end. Kernel default is 300 seconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').aging_time
+ '300'
+ """
+ return (self._read_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ .format(self._ifname)) / 100)
+
+ @ageing_time.setter
+ def 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 Interface
+ >>> BridgeIf('br0').ageing_time = 2
+ """
+ time = int(time) * 100
+ return self._write_sysfs('/sys/class/net/{0}/bridge/ageing_time'
+ .format(self._ifname), time)
+
+ @property
+ def forward_delay(self):
+ """
+ Get bridge forwarding delay in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').ageing_time
+ '3'
+ """
+ return (self._read_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ .format(self._ifname)) / 100)
+
+ @forward_delay.setter
+ def forward_delay(self, time):
+ """
+ Set bridge forwarding delay in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').forward_delay = 15
+ """
+ return self._write_sysfs('/sys/class/net/{0}/bridge/forward_delay'
+ .format(self._ifname), (int(time) * 100))
+
+ @property
+ def hello_time(self):
+ """
+ Get bridge hello time in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').hello_time
+ '2'
+ """
+ return (self._read_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ .format(self._ifname)) / 100)
+
+ @hello_time.setter
+ def hello_time(self, time):
+ """
+ Set bridge hello time in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').hello_time = 2
+ """
+ return self._write_sysfs('/sys/class/net/{0}/bridge/hello_time'
+ .format(self._ifname), (int(time) * 100))
+
+ @property
+ def max_age(self):
+ """
+ Get bridge max max message age in seconds. Internal Kernel representation
+ is in centiseconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').max_age
+ '20'
+ """
+
+ return (self._read_sysfs('/sys/class/net/{0}/bridge/max_age'
+ .format(self._ifname)) / 100)
+
+ @max_age.setter
+ def 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').max_age = 30
+ """
+ return self._write_sysfs('/sys/class/net/{0}/bridge/max_age'
+ .format(self._ifname), (int(time) * 100))
+
+ @property
+ def priority(self):
+ """
+ Get bridge max aging time in seconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').priority
+ '32768'
+ """
+ return self._read_sysfs('/sys/class/net/{0}/bridge/priority'
+ .format(self._ifname))
+
+ @priority.setter
+ def priority(self, priority):
+ """
+ Set bridge max aging time in seconds.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').priority = 8192
+ """
+ return self._write_sysfs('/sys/class/net/{0}/bridge/priority'
+ .format(self._ifname), priority)
+
+ @property
+ def stp_state(self):
+ """
+ Get current bridge STP (Spanning Tree) state.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').stp_state
+ '0'
+ """
+
+ state = 0
+ with open('/sys/class/net/{0}/bridge/stp_state'.format(self._ifname), 'r') as f:
+ state = int(f.read().rstrip('\n'))
+
+ return state
+
+ @stp_state.setter
+ def stp_state(self, state):
+ """
+ Set bridge STP (Spannign Tree) state. 0 -> STP disabled, 1 -> STP enabled
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').stp_state = 1
+ """
+
+ if int(state) >= 0 and int(state) <= 1:
+ return self._write_sysfs('/sys/class/net/{0}/bridge/stp_state'
+ .format(self._ifname), state)
+ else:
+ raise ValueError("Value out of range")
+
+ @property
+ def multicast_querier(self):
+ """
+ Get bridge multicast querier membership state.
+
+ Example:
+ >>> from vyos.ifconfig import Interface
+ >>> BridgeIf('br0').multicast_querier
+ '0'
+ """
+ return self._read_sysfs('/sys/class/net/{0}/bridge/multicast_querier'
+ .format(self._ifname))
+
+ @multicast_querier.setter
+ def 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').multicast_querier = 1
+ """
+ if int(enable) >= 0 and int(enable) <= 1:
+ return self._write_sysfs('/sys/class/net/{0}/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)
+ 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)
+ self._cmd(cmd)
+
+ def set_cost(self, interface, cost):
+ """
+ Set interface path cost, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').path_cost(4)
+ """
+ return self._write_sysfs('/sys/class/net/{}/brif/{}/path_cost'
+ .format(self._ifname, interface), cost)
+
+ def set_priority(self, interface, priority):
+ """
+ Set interface path priority, only relevant for STP enabled interfaces
+
+ Example:
+
+ >>> from vyos.ifconfig import Interface
+ >>> Interface('eth0').priority(4)
+ """
+ return self._write_sysfs('/sys/class/net/{}/brif/{}/priority'
+ .format(self._ifname, interface), priority)
+
+
+class EthernetIf(Interface):
+
+ def __init__(self, ifname, type=None):
+ super().__init__(ifname, type)
+
+ def add_vlan(self, vlan_id, ethertype=''):
+ """
+ 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 EthernetIf is returned once the interface has been
+ created.
+ """
+ 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)
+
+ # create interface in the system
+ cmd = 'ip link add link {intf} name {intf}.{vlan} type vlan {proto} id {vlan}'.format(
+ intf=self._ifname, vlan=self._vlan_id, proto=ethertype)
+ 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 EthernetIf(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.
+ """
+ vlan_ifname = self._ifname + '.' + str(vlan_id)
+ tmp = EthernetIf(vlan_ifname)
+ tmp.remove()
+
+
+class BondIf(EthernetIf):
+
+ """
+ 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')
+
+ @property
+ def xmit_hash_policy(self):
+ """
+ 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').xmit_hash_policy
+ 'layer3+4'
+ """
+ # Linux Kernel appends has policy value to string, e.g. 'layer3+4 1',
+ # so remove the later part and only return the mode as string.
+ return self._read_sysfs('/sys/class/net/{}/bonding/xmit_hash_policy'
+ .format(self._ifname)).split()[0]
+
+ @xmit_hash_policy.setter
+ def xmit_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 Interface
+ >>> BondIf('bond0').xmit_hash_policy = 'layer2+3'
+ >>> BondIf('bond0').proxy_arp
+ '1'
+ """
+ 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)
+
+ @property
+ def arp_interval(self):
+ """
+ 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.
+
+ The default value is 0.
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').arp_interval
+ '0'
+ """
+ return self._read_sysfs('/sys/class/net/{}/bonding/arp_interval'
+ .format(self._ifname))
+
+ @arp_interval.setter
+ def arp_interval(self, time):
+ """
+ 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 Interface
+ >>> BondIf('bond0').arp_interval = '100'
+ >>> BondIf('bond0').arp_interval
+ '100'
+ """
+ return self._write_sysfs('/sys/class/net/{}/bonding/arp_interval'
+ .format(self._ifname), time)
+
+ @property
+ def 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').arp_ip_target
+ '192.0.2.1'
+ """
+ return self._read_sysfs('/sys/class/net/{}/bonding/arp_ip_target'
+ .format(self._ifname))
+
+ @arp_ip_target.setter
+ def 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 Interface
+ >>> BondIf('bond0').arp_ip_target = '192.0.2.1'
+ >>> BondIf('bond0').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 Interface
+ >>> 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).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 Interface
+ >>> 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 Interface
+ >>> BondIf('bond0').get_slaves()
+ ['eth1', 'eth2']
+ """
+ slaves = self._read_sysfs('/sys/class/net/{}/bonding/slaves'
+ .format(self._ifname))
+ return list(map(str, slaves.split()))
+
+ @property
+ def primary(self):
+ """
+ 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').primary
+ 'eth1'
+ """
+ return self._read_sysfs('/sys/class/net/{}/bonding/primary'
+ .format(self._ifname))
+
+ @primary.setter
+ def 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 Interface
+ >>> BondIf('bond0').primary = 'eth2'
+ >>> BondIf('bond0').primary
+ 'eth2'
+ """
+ if not interface:
+ # reset primary interface
+ interface = '\0'
+
+ return self._write_sysfs('/sys/class/net/{}/bonding/primary'
+ .format(self._ifname), interface)
+
+ @property
+ def mode(self):
+ """
+ Specifies one of the bonding policies. The default is balance-rr
+ (round robin).
+
+ Possible values are: balance-rr (0), active-backup (1), balance-xor (2),
+ broadcast (3), 802.3ad (4), balance-tlb (5), balance-alb (6)
+
+ Example:
+ >>> from vyos.ifconfig import BondIf
+ >>> BondIf('bond0').mode
+ 'balance-rr'
+ """
+ return self._read_sysfs('/sys/class/net/{}/bonding/mode'
+ .format(self._ifname)).split()[0]
+
+ @mode.setter
+ def 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 Interface
+ >>> BondIf('bond0').mode = '802.3ad'
+ >>> BondIf('bond0').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))
+ self._cmd(cmd)
+
+
+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
diff --git a/python/vyos/interfaceconfig.py b/python/vyos/interfaceconfig.py
deleted file mode 100644
index b8bfb707e..000000000
--- a/python/vyos/interfaceconfig.py
+++ /dev/null
@@ -1,376 +0,0 @@
-#!/usr/bin/python3
-
-# 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 sys
-import os
-import re
-import json
-import socket
-import subprocess
-
-dhclient_conf_dir = r'/var/lib/dhcp/dhclient_'
-
-class Interface:
- def __init__(self, ifname=None, type=None):
- if not ifname:
- raise Exception("interface name required")
- if not os.path.exists('/sys/class/net/{0}'.format(ifname)) and not type:
- raise Exception("interface {0} not found".format(str(ifname)))
- else:
- if not os.path.exists('/sys/class/net/{0}'.format(ifname)):
- try:
- ret = subprocess.check_output(['ip link add dev ' + str(ifname) + ' type ' + type], stderr=subprocess.STDOUT, shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- if "Operation not supported" in str(e.output.decode()):
- print(str(e.output.decode()))
- sys.exit(0)
-
- self._ifname = str(ifname)
-
-
- @property
- def mtu(self):
- return self._mtu
-
- @mtu.setter
- def mtu(self, mtu=None):
- if mtu < 68 or mtu > 9000:
- raise ValueError("mtu size invalid value")
- self._mtu = mtu
- try:
- ret = subprocess.check_output(['ip link set mtu ' + str(mtu) + ' dev ' + self._ifname], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
-
-
- @property
- def macaddr(self):
- return self._macaddr
-
- @macaddr.setter
- def macaddr(self, mac=None):
- if not re.search('^[a-f0-9:]{17}$', str(mac)):
- raise ValueError("mac address invalid")
- self._macaddr = str(mac)
- try:
- ret = subprocess.check_output(['ip link set address ' + mac + ' ' + self._ifname], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
-
- @property
- def ifalias(self):
- return self._ifalias
-
- @ifalias.setter
- def ifalias(self, ifalias=None):
- if not ifalias:
- self._ifalias = self._ifname
- else:
- self._ifalias = str(ifalias)
- open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write(self._ifalias)
-
- @property
- def linkstate(self):
- return self._linkstate
-
- @linkstate.setter
- def linkstate(self, state='up'):
- if str(state).lower() == 'up' or str(state).lower() == 'down':
- self._linkstate = str(state).lower()
- else:
- self._linkstate = 'up'
- try:
- ret = subprocess.check_output(['ip link set dev ' + self._ifname + ' ' + state], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
-
-
-
- def _debug(self, e=None):
- """
- export DEBUG=1 to see debug messages
- """
- if os.getenv('DEBUG') == '1':
- if e:
- print ("Exception raised:\ncommand: {0}\nerror code: {1}\nsubprocess output: {2}".format(e.cmd, e.returncode, e.output.decode()) )
- return True
- return False
-
- def get_mtu(self):
- try:
- ret = subprocess.check_output(['ip -j link list dev ' + self._ifname], shell=True).decode()
- a = json.loads(ret)[0]
- return a['mtu']
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def get_macaddr(self):
- try:
- ret = subprocess.check_output(['ip -j -4 link show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- j = json.loads(ret)
- return j[0]['address']
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def get_alias(self):
- return open('/sys/class/net/{0}/ifalias'.format(self._ifname),'r').read()
-
- def del_alias(self):
- open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write()
-
- def get_link_state(self):
- """
- returns either up/down or None if it can't find the state
- """
- try:
- ret = subprocess.check_output(['ip -j link show ' + self._ifname], shell=True).decode()
- s = json.loads(ret)
- return s[0]['operstate'].lower()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def remove_interface(self):
- try:
- ret = subprocess.check_output(['ip link del dev ' + self._ifname], shell=True).decode()
- return 0
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def get_ipv4_addr(self):
- """
- reads all IPs assigned to an interface and returns it in a list,
- or None if no IP address is assigned to the interface
- """
- ips = []
- try:
- ret = subprocess.check_output(['ip -j -4 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- j = json.loads(ret)
- for i in j:
- if len(i) != 0:
- for addr in i['addr_info']:
- ips.append(addr['local'])
- return ips
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
-
- def get_ipv6_addr(self):
- """
- reads all IPs assigned to an interface and returns it in a list,
- or None if no IP address is assigned to the interface
- """
- ips = []
- try:
- ret = subprocess.check_output(['ip -j -6 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- j = json.loads(ret)
- for i in j:
- if len(i) != 0:
- for addr in i['addr_info']:
- ips.append(addr['local'])
- return ips
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
-
- def add_ipv4_addr(self, ipaddr=[]):
- """
- add addresses on the interface
- """
- for ip in ipaddr:
- try:
- ret = subprocess.check_output(['ip -4 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
- return True
-
-
- def del_ipv4_addr(self, ipaddr=[]):
- """
- delete addresses on the interface
- """
- for ip in ipaddr:
- try:
- ret = subprocess.check_output(['ip -4 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
- return True
-
-
- def add_ipv6_addr(self, ipaddr=[]):
- """
- add addresses on the interface
- """
- for ip in ipaddr:
- try:
- ret = subprocess.check_output(['ip -6 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
- return True
-
-
- def del_ipv6_addr(self, ipaddr=[]):
- """
- delete addresses on the interface
- """
- for ip in ipaddr:
- try:
- ret = subprocess.check_output(['ip -6 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
- return True
-
-
- #### replace dhcpv4/v6 with systemd.networkd?
- def set_dhcpv4(self):
- conf_file = dhclient_conf_dir + self._ifname + '.conf'
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- leasefile = dhclient_conf_dir + self._ifname + '.leases'
-
- a = [
- '# generated by interface_config.py',
- 'option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;',
- 'interface \"' + self._ifname + '\" {',
- '\tsend host-name \"' + socket.gethostname() +'\";',
- '\trequest subnet-mask, broadcast-address, routers, domain-name-servers, rfc3442-classless-static-routes, domain-name, interface-mtu;',
- '}'
- ]
-
- cnf = ""
- for ln in a:
- cnf +=str(ln + "\n")
- open(conf_file, 'w').write(cnf)
- if os.path.exists(dhclient_conf_dir + self._ifname + '.pid'):
- try:
- ret = subprocess.check_output(['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- try:
- ret = subprocess.check_output(['/sbin/dhclient -4 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def del_dhcpv4(self):
- conf_file = dhclient_conf_dir + self._ifname + '.conf'
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- leasefile = dhclient_conf_dir + self._ifname + '.leases'
- if not os.path.exists(pidfile):
- return 1
- try:
- ret = subprocess.check_output(['/sbin/dhclient -4 -r -pf ' + pidfile], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def get_dhcpv4(self):
- pidfile = dhclient_conf_dir + self._ifname + '.pid'
- if not os.path.exists(pidfile):
- print ("no dhcp client running on interface {0}".format(self._ifname))
- return False
- else:
- pid = open(pidfile, 'r').read()
- print("dhclient running on {0} with pid {1}".format(self._ifname, pid))
- return True
-
-
- def set_dhcpv6(self):
- conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
- a = [
- '# generated by interface_config.py',
- 'interface \"' + self._ifname + '\" {',
- '\trequest routers, domain-name-servers, domain-name;',
- '}'
- ]
- cnf = ""
- for ln in a:
- cnf +=str(ln + "\n")
- open(conf_file, 'w').write(cnf)
- subprocess.call(['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=0'])
- if os.path.exists(pidfile):
- try:
- ret = subprocess.check_output(['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- try:
- ret = subprocess.check_output(['/sbin/dhclient -6 -q -nw -cf ' + conf_file + ' -pf ' + pidfile + ' -lf ' + leasefile + ' ' + self._ifname], shell=True).decode()
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def del_dhcpv6(self):
- conf_file = dhclient_conf_dir + self._ifname + '.v6conf'
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- leasefile = dhclient_conf_dir + self._ifname + '.v6leases'
- if not os.path.exists(pidfile):
- return 1
- try:
- ret = subprocess.check_output(['/sbin/dhclient -6 -q -x -pf ' + pidfile], shell=True).decode()
- subprocess.call(['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=1'])
- return True
- except subprocess.CalledProcessError as e:
- if self._debug():
- self._debug(e)
- return None
-
- def get_dhcpv6(self):
- pidfile = dhclient_conf_dir + self._ifname + '.v6pid'
- if not os.path.exists(pidfile):
- print ("no dhcpv6 client running on interface {0}".format(self._ifname))
- return False
- else:
- pid = open(pidfile, 'r').read()
- print("dhclientv6 running on {0} with pid {1}".format(self._ifname, pid))
- return True
-
-
-#### TODO: dhcpv6-pd via dhclient
-
diff --git a/python/vyos/interfaces.py b/python/vyos/interfaces.py
index 2e8ee4feb..d69ce9d04 100644
--- a/python/vyos/interfaces.py
+++ b/python/vyos/interfaces.py
@@ -43,3 +43,14 @@ def list_interfaces_of_type(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))
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 6ab606983..67a602f7a 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -15,9 +15,11 @@
import os
import re
+import getpass
import grp
import time
import subprocess
+import sys
import psutil
@@ -176,3 +178,24 @@ def wait_for_commit_lock():
while commit_in_progress():
time.sleep(1)
+def ask_yes_no(question, default=False) -> bool:
+ """Ask a yes/no question via input() and return their answer."""
+ default_msg = "[Y/n]" if default else "[y/N]"
+ while True:
+ sys.stdout.write("%s %s " % (question, default_msg))
+ c = input().lower()
+ if c == '':
+ return default
+ elif c in ("y", "ye", "yes"):
+ return True
+ elif c in ("n", "no"):
+ return False
+ else:
+ sys.stdout.write("Please respond with yes/y or no/n\n")
+
+
+def is_admin() -> bool:
+ """Look if current user is in sudo group"""
+ current_user = getpass.getuser()
+ (_, _, _, admin_group_members) = grp.getgrnam('sudo')
+ return current_user in admin_group_members
diff --git a/python/vyos/validate.py b/python/vyos/validate.py
index 97a401423..258f7f76a 100644
--- a/python/vyos/validate.py
+++ b/python/vyos/validate.py
@@ -16,6 +16,12 @@
import netifaces
import ipaddress
+def is_ip(addr):
+ """
+ Check addr if it is an IPv4 or IPv6 address
+ """
+ return is_ipv4(addr) or is_ipv6(addr)
+
def is_ipv4(addr):
"""
Check addr if it is an IPv4 address/network. Returns True/False