summaryrefslogtreecommitdiff
path: root/python/vyos/ifconfig
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/ifconfig')
-rw-r--r--python/vyos/ifconfig/dhcp.py129
-rw-r--r--python/vyos/ifconfig/interface.py161
-rw-r--r--python/vyos/ifconfig/section.py21
-rw-r--r--python/vyos/ifconfig/vlan.py40
-rw-r--r--python/vyos/ifconfig/wireguard.py2
5 files changed, 178 insertions, 175 deletions
diff --git a/python/vyos/ifconfig/dhcp.py b/python/vyos/ifconfig/dhcp.py
index d4ff9c2cd..f8fdeb6a9 100644
--- a/python/vyos/ifconfig/dhcp.py
+++ b/python/vyos/ifconfig/dhcp.py
@@ -19,28 +19,19 @@ from vyos.dicts import FixedDict
from vyos.ifconfig.control import Control
from vyos.template import render
-
-class _DHCP (Control):
- client_base = r'/var/lib/dhcp/dhclient_'
-
- def __init__(self, ifname, version, **kargs):
- super().__init__(**kargs)
- self.version = version
- self.file = {
- 'ifname': ifname,
- 'conf': self.client_base + ifname + '.' + version + 'conf',
- 'pid': self.client_base + ifname + '.' + version + 'pid',
- 'lease': self.client_base + ifname + '.' + version + 'leases',
- }
-
-class _DHCPv4 (_DHCP):
+class _DHCPv4 (Control):
def __init__(self, ifname):
- super().__init__(ifname, '')
+ super().__init__()
+ config_base = r'/var/lib/dhcp/dhclient_'
self.options = FixedDict(**{
'ifname': ifname,
'hostname': '',
'client_id': '',
- 'vendor_class_id': ''
+ 'vendor_class_id': '',
+ 'conf_file': config_base + f'{ifname}.conf',
+ 'options_file': config_base + f'{ifname}.options',
+ 'pid_file': config_base + f'{ifname}.pid',
+ 'lease_file': config_base + f'{ifname}.leases',
})
# replace dhcpv4/v6 with systemd.networkd?
@@ -55,25 +46,16 @@ class _DHCPv4 (_DHCP):
>>> j = Interface('eth0')
>>> j.dhcp.v4.set()
"""
-
if not self.options['hostname']:
# read configured system hostname.
# maybe change to vyos hostd client ???
with open('/etc/hostname', 'r') as f:
self.options['hostname'] = f.read().rstrip('\n')
- render(self.file['conf'], 'dhcp-client/ipv4.tmpl' ,self.options)
+ render(self.options['options_file'], 'dhcp-client/daemon-options.tmpl', self.options)
+ render(self.options['conf_file'], 'dhcp-client/ipv4.tmpl', self.options)
- cmd = 'start-stop-daemon'
- cmd += ' --start'
- cmd += ' --oknodo'
- cmd += ' --quiet'
- cmd += ' --pidfile {pid}'
- cmd += ' --exec /sbin/dhclient'
- cmd += ' --'
- # now pass arguments to dhclient binary
- cmd += ' -4 -nw -cf {conf} -pf {pid} -lf {lease} {ifname}'
- return self._cmd(cmd.format(**self.file))
+ return self._cmd('systemctl restart dhclient@{ifname}.service'.format(**self.options))
def delete(self):
"""
@@ -86,44 +68,27 @@ class _DHCPv4 (_DHCP):
>>> j = Interface('eth0')
>>> j.dhcp.v4.delete()
"""
- if not os.path.isfile(self.file['pid']):
+ if not os.path.isfile(self.options['pid_file']):
self._debug_msg('No DHCP client PID found')
return None
- # with open(self.file['pid'], 'r') as f:
- # pid = int(f.read())
-
- # stop dhclient, we need to call dhclient and tell it should release the
- # aquired IP address. tcpdump tells me:
- # 172.16.35.103.68 > 172.16.35.254.67: [bad udp cksum 0xa0cb -> 0xb943!] BOOTP/DHCP, Request from 00:50:56:9d:11:df, length 300, xid 0x620e6946, Flags [none] (0x0000)
- # Client-IP 172.16.35.103
- # Client-Ethernet-Address 00:50:56:9d:11:df
- # Vendor-rfc1048 Extensions
- # Magic Cookie 0x63825363
- # DHCP-Message Option 53, length 1: Release
- # Server-ID Option 54, length 4: 172.16.35.254
- # Hostname Option 12, length 10: "vyos"
- #
- cmd = '/sbin/dhclient -cf {conf} -pf {pid} -lf {lease} -r {ifname}'
- self._cmd(cmd.format(**self.file))
+ self._cmd('systemctl stop dhclient@{ifname}.service'.format(**self.options))
# cleanup old config files
- for name in ('conf', 'pid', 'lease'):
- if os.path.isfile(self.file[name]):
- os.remove(self.file[name])
-
+ for name in ('conf_file', 'options_file', 'pid_file', 'lease_file'):
+ if os.path.isfile(self.options[name]):
+ os.remove(self.options[name])
-class _DHCPv6 (_DHCP):
+class _DHCPv6 (Control):
def __init__(self, ifname):
- super().__init__(ifname, 'v6')
+ super().__init__()
self.options = FixedDict(**{
'ifname': ifname,
'dhcpv6_prm_only': False,
'dhcpv6_temporary': False,
+ 'dhcpv6_pd': [],
})
- self.file.update({
- 'accept_ra': f'/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
- })
+ self._conf_file = f'/run/dhcp6c/dhcp6c.{ifname}.conf'
def set(self):
"""
@@ -134,7 +99,7 @@ class _DHCPv6 (_DHCP):
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
- >>> j.set_dhcpv6()
+ >>> j.dhcp.v6.set()
"""
# better save then sorry .. should be checked in interface script
@@ -143,29 +108,8 @@ class _DHCPv6 (_DHCP):
raise Exception(
'DHCPv6 temporary and parameters-only options are mutually exclusive!')
- render(self.file['conf'], 'dhcp-client/ipv6.tmpl', self.options)
-
- # no longer accept router announcements on this interface
- self._write_sysfs(self.file['accept_ra'], 0)
-
- # assemble command-line to start DHCPv6 client (dhclient)
- cmd = 'start-stop-daemon'
- cmd += ' --start'
- cmd += ' --oknodo'
- cmd += ' --quiet'
- cmd += ' --pidfile {pid}'
- cmd += ' --exec /sbin/dhclient'
- cmd += ' --'
- # now pass arguments to dhclient binary
- cmd += ' -6 -nw -cf {conf} -pf {pid} -lf {lease}'
- # add optional arguments
- if self.options['dhcpv6_prm_only']:
- cmd += ' -S'
- if self.options['dhcpv6_temporary']:
- cmd += ' -T'
- cmd += ' {ifname}'
-
- return self._cmd(cmd.format(**self.file))
+ render(self._conf_file, 'dhcp-client/ipv6.tmpl', self.options, trim_blocks=True)
+ return self._cmd('systemctl restart dhcp6c@{ifname}.service'.format(**self.options))
def delete(self):
"""
@@ -176,33 +120,16 @@ class _DHCPv6 (_DHCP):
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
- >>> j.del_dhcpv6()
+ >>> j.dhcp.v6.delete()
"""
- if not os.path.isfile(self.file['pid']):
- self._debug_msg('No DHCPv6 client PID found')
- return None
-
- # with open(self.file['pid'], 'r') as f:
- # pid = int(f.read())
-
- # stop dhclient
- cmd = 'start-stop-daemon'
- cmd += ' --start'
- cmd += ' --oknodo'
- cmd += ' --quiet'
- cmd += ' --pidfile {pid}'
- self._cmd(cmd.format(**self.file))
-
- # accept router announcements on this interface
- self._write_sysfs(self.options['accept_ra'], 1)
+ self._cmd('systemctl stop dhcp6c@{ifname}.service'.format(**self.options))
# cleanup old config files
- for name in ('conf', 'pid', 'lease'):
- if os.path.isfile(self.file[name]):
- os.remove(self.file[name])
+ if os.path.isfile(self._conf_file):
+ os.remove(self._conf_file)
-class DHCP (object):
+class DHCP(object):
def __init__(self, ifname):
self.v4 = _DHCPv4(ifname)
self.v6 = _DHCPv6(ifname)
diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py
index 62c30dbf7..61f2c6482 100644
--- a/python/vyos/ifconfig/interface.py
+++ b/python/vyos/ifconfig/interface.py
@@ -1,4 +1,4 @@
-# Copyright 2019 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2019-2020 VyOS maintainers and contributors <maintainers@vyos.io>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
@@ -42,6 +42,7 @@ from vyos.ifconfig.control import Control
from vyos.ifconfig.dhcp import DHCP
from vyos.ifconfig.vrrp import VRRP
from vyos.ifconfig.operational import Operational
+from vyos.ifconfig import Section
class Interface(Control):
@@ -133,8 +134,12 @@ class Interface(Control):
'validate': assert_boolean,
'location': '/proc/sys/net/ipv4/conf/{ifname}/arp_ignore',
},
+ 'ipv6_accept_ra': {
+ 'validate': lambda ara: assert_range(ara,0,3),
+ 'location': '/proc/sys/net/ipv6/conf/{ifname}/accept_ra',
+ },
'ipv6_autoconf': {
- 'validate': lambda fwd: assert_range(fwd,0,2),
+ 'validate': lambda aco: assert_range(aco,0,2),
'location': '/proc/sys/net/ipv6/conf/{ifname}/autoconf',
},
'ipv6_forwarding': {
@@ -219,7 +224,7 @@ class Interface(Control):
else:
raise Exception('interface "{}" not found'.format(self.config['ifname']))
- # list of assigned IP addresses
+ # temporary list of assigned IP addresses
self._addr = []
self.operational = self.OperationalClass(ifname)
@@ -240,15 +245,11 @@ class Interface(Control):
>>> i = Interface('eth0')
>>> i.remove()
"""
- # stop DHCP(v6) if running
- self.dhcp.v4.delete()
- self.dhcp.v6.delete()
# 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)
+ self.flush_addrs()
# ---------------------------------------------------------------------
# Any class can define an eternal regex in its definition
@@ -412,6 +413,21 @@ class Interface(Control):
"""
return self.set_interface('arp_ignore', arp_ignore)
+ def set_ipv6_accept_ra(self, accept_ra):
+ """
+ Accept Router Advertisements; autoconfigure using them.
+
+ It also determines whether or not to transmit Router Solicitations.
+ If and only if the functional setting is to accept Router
+ Advertisements, Router Solicitations will be transmitted.
+
+ 0 - Do not accept Router Advertisements.
+ 1 - (default) Accept Router Advertisements if forwarding is disabled.
+ 2 - Overrule forwarding behaviour. Accept Router Advertisements even if
+ forwarding is enabled.
+ """
+ return self.set_interface('ipv6_accept_ra', accept_ra)
+
def set_ipv6_autoconf(self, autoconf):
"""
Autoconfigure addresses using Prefix Information in Router
@@ -419,39 +435,28 @@ class Interface(Control):
"""
return self.set_interface('ipv6_autoconf', autoconf)
- def set_ipv6_eui64_address(self, prefix):
+ def add_ipv6_eui64_address(self, prefix):
"""
Extended Unique Identifier (EUI), as per RFC2373, allows a host to
- assign iteslf a unique IPv6 address based on a given IPv6 prefix.
+ assign itself a unique IPv6 address based on a given IPv6 prefix.
- If prefix is passed address is assigned, if prefix is '' address is
- removed from interface.
+ Calculate the EUI64 from the interface's MAC, then assign it
+ with the given prefix to the interface.
"""
- # if prefix is an empty string convert it to None so mac2eui64 works
- # as expected
- if not prefix:
- prefix = None
eui64 = mac2eui64(self.get_mac(), prefix)
+ prefixlen = prefix.split('/')[1]
+ self.add_addr(f'{eui64}/{prefixlen}')
- if not prefix:
- # if prefix is empty - thus removed - we need to walk through all
- # interface IPv6 addresses and find the one with the calculated
- # EUI-64 identifier. The address is then removed
- for addr in self.get_addr():
- addr_wo_prefix = addr.split('/')[0]
- if is_ipv6(addr_wo_prefix):
- if eui64 in IPv6Address(addr_wo_prefix).exploded:
- self.del_addr(addr)
-
- return None
+ def del_ipv6_eui64_address(self, prefix):
+ """
+ Delete the address based on the interface's MAC-based EUI64
+ combined with the prefix address.
+ """
+ eui64 = mac2eui64(self.get_mac(), prefix)
+ prefixlen = prefix.split('/')[1]
+ self.del_addr(f'{eui64}/{prefixlen}')
- # calculate and add EUI-64 IPv6 address
- if IPv6Network(prefix):
- # we also need to take the subnet length into account
- prefix = prefix.split('/')[1]
- eui64 = f'{eui64}/{prefix}'
- self.add_addr(eui64 )
def set_ipv6_forwarding(self, forwarding):
"""
@@ -632,7 +637,8 @@ class Interface(Control):
def add_addr(self, addr):
"""
Add IP(v6) address to interface. Address is only added if it is not
- already assigned to that interface.
+ already assigned to that interface. Address format must be validated
+ and compressed/normalized before calling this function.
addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
IPv4: add IPv4 address to interface
@@ -640,6 +646,7 @@ class Interface(Control):
dhcp: start dhclient (IPv4) on interface
dhcpv6: start dhclient (IPv6) on interface
+ Returns False if address is already assigned and wasn't re-added.
Example:
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
@@ -648,32 +655,41 @@ class Interface(Control):
>>> j.get_addr()
['192.0.2.1/24', '2001:db8::ffff/64']
"""
+ # XXX: normalize/compress with ipaddress if calling functions don't?
+ # is subnet mask always passed, and in the same way?
- # cache new IP address which is assigned to interface
- self._addr.append(addr)
+ # do not add same address twice
+ if addr in self._addr:
+ return False
- # 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")
+ # we can't have both DHCP and static IPv4 addresses assigned
+ for a in self._addr:
+ if ( ( addr == 'dhcp' and a != 'dhcpv6' and is_ipv4(a) ) or
+ ( a == 'dhcp' and addr != 'dhcpv6' and is_ipv4(addr) ) ):
+ raise ConfigError((
+ "Can't configure both static IPv4 and DHCP address "
+ "on the same interface"))
+ # add to interface
if addr == 'dhcp':
self.dhcp.v4.set()
elif addr == 'dhcpv6':
self.dhcp.v6.set()
+ elif not is_intf_addr_assigned(self.ifname, addr):
+ self._cmd(f'ip addr add "{addr}" dev "{self.ifname}"')
else:
- if not is_intf_addr_assigned(self.config['ifname'], addr):
- cmd = 'ip addr add "{}" dev "{}"'.format(addr, self.config['ifname'])
- return self._cmd(cmd)
+ return False
+
+ # add to cache
+ self._addr.append(addr)
+
+ return True
def del_addr(self, addr):
"""
- Delete IP(v6) address to interface. Address is only added if it is
- assigned to that interface.
+ Delete IP(v6) address from interface. Address is only deleted if it is
+ assigned to that interface. Address format must be exactly the same as
+ was used when adding the address.
addr: can be an IPv4 address, IPv6 address, dhcp or dhcpv6!
IPv4: delete IPv4 address from interface
@@ -681,6 +697,7 @@ class Interface(Control):
dhcp: stop dhclient (IPv4) on interface
dhcpv6: stop dhclient (IPv6) on interface
+ Returns False if address isn't already assigned and wasn't deleted.
Example:
>>> from vyos.ifconfig import Interface
>>> j = Interface('eth0')
@@ -692,11 +709,51 @@ class Interface(Control):
>>> j.get_addr()
['2001:db8::ffff/64']
"""
+
+ # remove from interface
if addr == 'dhcp':
self.dhcp.v4.delete()
elif addr == 'dhcpv6':
self.dhcp.v6.delete()
+ elif is_intf_addr_assigned(self.ifname, addr):
+ self._cmd(f'ip addr del "{addr}" dev "{self.ifname}"')
else:
- if is_intf_addr_assigned(self.config['ifname'], addr):
- cmd = 'ip addr del "{}" dev "{}"'.format(addr, self.config['ifname'])
- return self._cmd(cmd)
+ return False
+
+ # remove from cache
+ if addr in self._addr:
+ self._addr.remove(addr)
+
+ return True
+
+ def flush_addrs(self):
+ """
+ Flush all addresses from an interface, including DHCP.
+
+ Will raise an exception on error.
+ """
+ # stop DHCP(v6) if running
+ self.dhcp.v4.delete()
+ self.dhcp.v6.delete()
+
+ # flush all addresses
+ self._cmd(f'ip addr flush dev "{self.ifname}"')
+
+ def add_to_bridge(self, br):
+ """
+ Adds the interface to the bridge with the passed port config.
+
+ Returns False if bridge doesn't exist.
+ """
+
+ # check if the bridge exists (on boot it doesn't)
+ if br not in Section.interfaces('bridge'):
+ return False
+
+ self.flush_addrs()
+ # add interface to bridge - use Section.klass to get BridgeIf class
+ Section.klass(br)(br, create=False).add_port(self.ifname)
+
+ # TODO: port config (STP)
+
+ return True
diff --git a/python/vyos/ifconfig/section.py b/python/vyos/ifconfig/section.py
index 092236fef..926c22e8a 100644
--- a/python/vyos/ifconfig/section.py
+++ b/python/vyos/ifconfig/section.py
@@ -54,7 +54,7 @@ class Section:
name = name.rstrip('0123456789')
name = name.rstrip('.')
if vlan:
- name = name.rstrip('0123456789')
+ name = name.rstrip('0123456789.')
return name
@classmethod
@@ -137,3 +137,22 @@ class Section:
eth, lo, vxlan, dum, ...
"""
return list(cls._prefixes.keys())
+
+ @classmethod
+ def get_config_path(cls, name):
+ """
+ get config path to interface with .vif or .vif-s.vif-c
+ example: eth0.1.2 -> 'ethernet eth0 vif-s 1 vif-c 2'
+ Returns False if interface name is invalid (not found in sections)
+ """
+ sect = cls.section(name)
+ if sect:
+ splinterface = name.split('.')
+ intfpath = f'{sect} {splinterface[0]}'
+ if len(splinterface) == 2:
+ intfpath += f' vif {splinterface[1]}'
+ elif len(splinterface) == 3:
+ intfpath += f' vif-s {splinterface[1]} vif-c {splinterface[2]}'
+ return intfpath
+ else:
+ return False
diff --git a/python/vyos/ifconfig/vlan.py b/python/vyos/ifconfig/vlan.py
index 7b1e00d87..d68e8f6cd 100644
--- a/python/vyos/ifconfig/vlan.py
+++ b/python/vyos/ifconfig/vlan.py
@@ -101,26 +101,26 @@ class VLAN:
>>> i.add_vlan(10)
"""
vlan_ifname = self.config['ifname'] + '.' + str(vlan_id)
- if not os.path.exists(f'/sys/class/net/{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 {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
- .format(ifname=self.config['ifname'], vlan=self._vlan_id, proto=ethertype, opt_e=opt_e, opt_i=opt_i)
- self._cmd(cmd)
+ if os.path.exists(f'/sys/class/net/{vlan_ifname}'):
+ return self.__class__(vlan_ifname)
+
+ 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 {ifname} name {ifname}.{vlan} type vlan {proto} id {vlan} {opt_e} {opt_i}' \
+ .format(ifname=self.ifname, vlan=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
diff --git a/python/vyos/ifconfig/wireguard.py b/python/vyos/ifconfig/wireguard.py
index fdf5d9347..027b5ea8c 100644
--- a/python/vyos/ifconfig/wireguard.py
+++ b/python/vyos/ifconfig/wireguard.py
@@ -208,7 +208,7 @@ class WireGuardIf(Interface):
else:
cmd += aip
if self.config['endpoint']:
- cmd += " endpoint {}".format(self.config['endpoint'])
+ cmd += " endpoint '{}'".format(self.config['endpoint'])
cmd += " persistent-keepalive {}".format(self.config['keepalive'])
self._cmd(cmd)