From 63cdf781f4604ed38591d0be71fa92ae3667da73 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 21 Aug 2019 18:33:06 +0200 Subject: bridge: T1556: remove superfluous if statements --- src/conf_mode/interface-bridge.py | 9 --------- 1 file changed, 9 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 543349e7b..65b5c4066 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -178,9 +178,6 @@ def get_config(): return bridge def verify(bridge): - if bridge is None: - return None - conf = Config() for br in conf.list_nodes('interfaces bridge'): # it makes no sense to verify ourself in this case @@ -195,15 +192,9 @@ def verify(bridge): return None def generate(bridge): - if bridge is None: - return None - return None def apply(bridge): - if bridge is None: - return None - cmd = '' if bridge['deleted']: # bridges need to be shutdown first -- cgit v1.2.3 From d5e9512b8461f55d276182b4a75267378aa11f50 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 26 Aug 2019 16:19:31 +0200 Subject: bridge: T1556: reword exception error when beeing member of multiple bridges --- src/conf_mode/interface-bridge.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 65b5c4066..fc1243867 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -187,7 +187,8 @@ def verify(bridge): for intf in bridge['member']: tmp = conf.list_nodes('interfaces bridge {} member interface'.format(br)) if intf['name'] in tmp: - raise ConfigError('{} can be assigned to any one bridge only'.format(intf['name'])) + raise ConfigError('Interface "{}" belongs to bridge "{}" and can not be enslaved.'.format(intf['name'], bridge['intf'])) + return None -- cgit v1.2.3 From 4a8ab14dc3cbe4245b95250c51ee427eb6241372 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 26 Aug 2019 16:20:03 +0200 Subject: bridge: T1608: deny adding non existing interfaces to bridge config --- src/conf_mode/interface-bridge.py | 5 ++++ src/helpers/vyos-bridge-sync.py | 53 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100755 src/helpers/vyos-bridge-sync.py (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index fc1243867..c5c5bd4ac 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -23,6 +23,7 @@ import subprocess import vyos.configinterface as VyIfconfig +from netifaces import interfaces from vyos.config import Config from vyos import ConfigError @@ -189,6 +190,10 @@ def verify(bridge): if intf['name'] in tmp: raise ConfigError('Interface "{}" belongs to bridge "{}" and can not be enslaved.'.format(intf['name'], bridge['intf'])) + # the interface must exist prior adding it to a bridge + for intf in bridge['member']: + if intf['name'] not in interfaces(): + raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf'])) return None diff --git a/src/helpers/vyos-bridge-sync.py b/src/helpers/vyos-bridge-sync.py new file mode 100755 index 000000000..495eb5d40 --- /dev/null +++ b/src/helpers/vyos-bridge-sync.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2019 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# Script is used to synchronize configured bridge interfaces. +# one can add a non existing interface to a bridge group (e.g. VLAN) +# but the vlan interface itself does yet not exist. It should be added +# to the bridge automatically once it's available + +import argparse +import subprocess + +from sys import exit +from time import sleep +from vyos.config import Config + +def subprocess_cmd(command): + process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) + proc_stdout = process.communicate()[0].strip() + pass + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-i', '--interface', action='store', help='Interface name which should be added to bridge it is configured for', required=True) + args, unknownargs = parser.parse_known_args() + + conf = Config() + if not conf.list_nodes('interfaces bridge'): + # no bridge interfaces exist .. bail out early + exit(0) + else: + for bridge in conf.list_nodes('interfaces bridge'): + for member_if in conf.list_nodes('interfaces bridge {} member interface'.format(bridge)): + if args.interface == member_if: + cmd = 'brctl addif "{}" "{}"'.format(bridge, args.interface) + # let interfaces etc. settle - especially required for OpenVPN bridged interfaces + sleep(4) + subprocess_cmd(cmd) + + exit(0) -- cgit v1.2.3 From 6e36bafad6d8300b0bd90261f2a57cf65716ac7f Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 26 Aug 2019 22:29:16 +0200 Subject: bridge: T1556: migrate interface configuration to pyroute2 Tested with: set interfaces bridge br0 address '192.0.2.1/24' set interfaces bridge br0 aging '500' set interfaces bridge br0 disable-link-detect set interfaces bridge br0 forwarding-delay '11' set interfaces bridge br0 hello-time '5' set interfaces bridge br0 igmp querier set interfaces bridge br0 max-age '11' set interfaces bridge br0 member interface eth1 cost '1000' set interfaces bridge br0 member interface eth1 priority '4' set interfaces bridge br0 member interface eth2 cost '1001' set interfaces bridge br0 member interface eth2 priority '56' --- debian/control | 1 + src/conf_mode/interface-bridge.py | 238 +++++++++++++++++--------------------- 2 files changed, 109 insertions(+), 130 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/debian/control b/debian/control index 12eb7c309..7b75ca111 100644 --- a/debian/control +++ b/debian/control @@ -29,6 +29,7 @@ Depends: python3, python3-vici (>= 5.7.2), python3-bottle, python3-zmq, + python3-pyroute2, ipaddrcheck, tcpdump, tshark, diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index c5c5bd4ac..d5ef85940 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -16,13 +16,10 @@ # # -import os -import sys -import copy -import subprocess - -import vyos.configinterface as VyIfconfig - +from os import environ +from copy import deepcopy +from sys import exit +from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config from vyos import ConfigError @@ -30,44 +27,42 @@ from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], - 'aging': '300', - 'arp_cache_timeout_ms': '30000', + 'aging': 300, + 'arp_cache_timeout_ms': 30000, 'description': '', 'deleted': False, - 'dhcp_client_id': '', - 'dhcp_hostname': '', - 'dhcpv6_parameters_only': False, - 'dhcpv6_temporary': False, 'disable': False, - 'disable_link_detect': False, - 'forwarding_delay': '15', - 'hello_time': '2', + 'disable_link_detect': 1, + 'forwarding_delay': 14, + 'hello_time': 2, 'igmp_querier': 0, 'intf': '', 'mac' : '', - 'max_age': '20', + 'max_age': 20, 'member': [], 'member_remove': [], - 'priority': '32768', - 'stp': 'off' + 'priority': 32768, + 'stp': 0 } -def subprocess_cmd(command): - process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) - proc_stdout = process.communicate()[0].strip() - pass +def freeze(d): + if isinstance(d, dict): + return frozenset((key, freeze(value)) for key, value in d.items()) + elif isinstance(d, list): + return tuple(freeze(value) for value in d) + return d def diff(first, second): second = set(second) return [item for item in first if item not in second] def get_config(): - bridge = copy.deepcopy(default_config_data) + bridge = deepcopy(default_config_data) conf = Config() # determine tagNode instance try: - bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + bridge['intf'] = environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -85,27 +80,13 @@ def get_config(): # retrieve aging - how long addresses are retained if conf.exists('aging'): - bridge['aging'] = conf.return_value('aging') + bridge['aging'] = int(conf.return_value('aging')) # retrieve interface description if conf.exists('description'): bridge['description'] = conf.return_value('description') - - # DHCP client identifier - if conf.exists('dhcp-options client-id'): - bridge['dhcp_client_id'] = conf.return_value('dhcp-options client-id') - - # DHCP client hostname - if conf.exists('dhcp-options host-name'): - bridge['dhcp_hostname'] = conf.return_value('dhcp-options host-name') - - # DHCPv6 acquire only config parameters, no address - if conf.exists('dhcpv6-options parameters-only'): - bridge['dhcpv6_parameters_only'] = True - - # DHCPv6 IPv6 "temporary" address - if conf.exists('dhcpv6-options temporary'): - bridge['dhcpv6_temporary'] = True + else: + bridge['description'] = bridge['intf'] # Disable this bridge interface if conf.exists('disable'): @@ -113,15 +94,15 @@ def get_config(): # Ignore link state changes if conf.exists('disable-link-detect'): - bridge['disable_link_detect'] = True + bridge['disable_link_detect'] = 2 # Forwarding delay if conf.exists('forwarding-delay'): - bridge['forwarding_delay'] = conf.return_value('forwarding-delay') + bridge['forwarding_delay'] = int(conf.return_value('forwarding-delay')) # Hello packet advertisment interval if conf.exists('hello-time'): - bridge['hello_time'] = conf.return_value('hello-time') + bridge['hello_time'] = int(conf.return_value('hello-time')) # Enable Internet Group Management Protocol (IGMP) querier if conf.exists('igmp querier'): @@ -129,8 +110,7 @@ def get_config(): # ARP cache entry timeout in seconds if conf.exists('ip arp-cache-timeout'): - tmp = 1000 * int(conf.return_value('ip arp-cache-timeout')) - bridge['arp_cache_timeout_ms'] = str(tmp) + bridge['arp_cache_timeout_ms'] = int(conf.return_value('ip arp-cache-timeout')) * 1000 # Media Access Control (MAC) address if conf.exists('mac'): @@ -138,21 +118,24 @@ def get_config(): # Interval at which neighbor bridges are removed if conf.exists('max-age'): - bridge['max_age'] = conf.return_value('max-age') + bridge['max_age'] = int(conf.return_value('max-age')) # Determine bridge member interface (currently configured) for intf in conf.list_nodes('member interface'): + # cost and priority initialized with linux defaults + # by reading /sys/devices/virtual/net/br0/brif/eth2/{path_cost,priority} + # after adding interface to bridge after reboot iface = { 'name': intf, - 'cost': '', - 'priority': '' + 'cost': 100, + 'priority': 32 } if conf.exists('member interface {} cost'.format(intf)): - iface['cost'] = conf.return_value('member interface {} cost'.format(intf)) + iface['cost'] = int(conf.return_value('member interface {} cost'.format(intf))) if conf.exists('member interface {} priority'.format(intf)): - iface['priority'] = conf.return_value('member interface {} priority'.format(intf)) + iface['priority'] = int(conf.return_value('member interface {} priority'.format(intf))) bridge['member'].append(iface) @@ -170,11 +153,11 @@ def get_config(): # Priority for this bridge if conf.exists('priority'): - bridge['priority'] = conf.return_value('priority') + bridge['priority'] = int(conf.return_value('priority')) # Enable spanning tree protocol if conf.exists('stp'): - bridge['stp'] = 'on' + bridge['stp'] = 1 return bridge @@ -201,94 +184,89 @@ def generate(bridge): return None def apply(bridge): - cmd = '' - if bridge['deleted']: - # bridges need to be shutdown first - cmd += 'ip link set dev "{}" down'.format(bridge['intf']) - cmd += ' && ' - # delete bridge - cmd += 'brctl delbr "{}"'.format(bridge['intf']) - subprocess_cmd(cmd) + ipdb = IPDB(mode='explicit') + brif = bridge['intf'] + if bridge['deleted']: + try: + # delete bridge interface + with ipdb.interfaces[ brif ] as br: + br.remove() + except: + pass else: - # create bridge if it does not exist - if not os.path.exists("/sys/class/net/" + bridge['intf']): - # create bridge interface - cmd += 'brctl addbr "{}"'.format(bridge['intf']) - cmd += ' && ' - # activate "UP" the interface - cmd += 'ip link set dev "{}" up'.format(bridge['intf']) - cmd += ' && ' - - # set ageing time - cmd += 'brctl setageing "{}" "{}"'.format(bridge['intf'], bridge['aging']) - cmd += ' && ' - - # set bridge forward delay - cmd += 'brctl setfd "{}" "{}"'.format(bridge['intf'], bridge['forwarding_delay']) - cmd += ' && ' - - # set hello time - cmd += 'brctl sethello "{}" "{}"'.format(bridge['intf'], bridge['hello_time']) - cmd += ' && ' - - # set max message age - cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['intf'], bridge['max_age']) - cmd += ' && ' - + try: + # create bridge interface if it not already exists + ipdb.create(kind='bridge', ifname=brif).commit() + except: + pass + + # get handle in bridge interface + br = ipdb.interfaces[brif] + # begin() a transaction prior to make any change + br.begin() + # enable interface + br.up() + # set ageing time - - value is in centiseconds YES! centiseconds! + br.br_ageing_time = bridge['aging'] * 100 + # set bridge forward delay - value is in centiseconds YES! centiseconds! + br.br_forward_delay = bridge['forwarding_delay'] * 100 + # set hello time - value is in centiseconds YES! centiseconds! + br.br_hello_time = bridge['hello_time'] * 100 + # set max message age - value is in centiseconds YES! centiseconds! + br.br_max_age = bridge['max_age'] * 100 # set bridge priority - cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['intf'], bridge['priority']) - cmd += ' && ' - + br.br_priority = bridge['priority'] # turn stp on/off - cmd += 'brctl stp "{}" "{}"'.format(bridge['intf'], bridge['stp']) - - for intf in bridge['member_remove']: - # remove interface from bridge - cmd += ' && ' - cmd += 'brctl delif "{}" "{}"'.format(bridge['intf'], intf) - - for intf in bridge['member']: - # add interface to bridge - # but only if it is not yet member of this bridge - if not os.path.exists('/sys/devices/virtual/net/' + bridge['intf'] + '/brif/' + intf['name']): - cmd += ' && ' - cmd += 'brctl addif "{}" "{}"'.format(bridge['intf'], intf['name']) - - # set bridge port cost - if intf['cost']: - cmd += ' && ' - cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['cost']) - - # set bridge port priority - if intf['priority']: - cmd += ' && ' - cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['intf'], intf['name'], intf['priority']) - - subprocess_cmd(cmd) + br.br_stp_state = bridge['stp'] + # enable or disable IGMP querier + br.br_mcast_querier = bridge['igmp_querier'] + # update interface description used e.g. within SNMP + br.ifalias = bridge['description'] # Change interface MAC address if bridge['mac']: - VyIfconfig.set_mac_address(bridge['intf'], bridge['mac']) + br.set_address = bridge['mac'] - # update interface description used e.g. within SNMP - VyIfconfig.set_description(bridge['intf'], bridge['description']) - - # Ignore link state changes? - VyIfconfig.set_link_detect(bridge['intf'], bridge['disable_link_detect']) - - # enable or disable IGMP querier - VyIfconfig.set_multicast_querier(bridge['intf'], bridge['igmp_querier']) + # remove interface from bridge + for intf in bridge['member_remove']: + br.del_port( intf['name'] ) - # ARP cache entry timeout in seconds - VyIfconfig.set_arp_cache_timeout(bridge['intf'], bridge['arp_cache_timeout_ms']) + # configure bridge member interfaces + for member in bridge['member']: + # add interface + br.add_port(member['name']) # Configure interface address(es) for addr in bridge['address_remove']: - VyIfconfig.remove_interface_address(bridge['intf'], addr) - + br.del_ip(addr) for addr in bridge['address']: - VyIfconfig.add_interface_address(bridge['intf'], addr) + br.add_ip(addr) + + # up/down interface + if bridge['disable']: + br.down() + + # commit change son bridge interface + br.commit() + + # configure additional bridge member options + for member in bridge['member']: + # configure ARP cache timeout in milliseconds + with open('/proc/sys/net/ipv4/neigh/' + member['name'] + '/base_reachable_time_ms', 'w') as f: + f.write(str(bridge['arp_cache_timeout_ms'])) + # ignore link state changes + with open('/proc/sys/net/ipv4/conf/' + member['name'] + '/link_filter', 'w') as f: + f.write(str(bridge['disable_link_detect'])) + + # adjust member port stp attributes + member_if = ipdb.interfaces[ member['name'] ] + member_if.begin() + # set bridge port cost + member_if.brport_cost = member['cost'] + # set bridge port priority + member_if.brport_priority = member['priority'] + member_if.commit() return None @@ -300,4 +278,4 @@ if __name__ == '__main__': apply(c) except ConfigError as e: print(e) - sys.exit(1) + exit(1) -- cgit v1.2.3 From 3dbb258f5f02076aa96bb9cd840464bcf08725ab Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 26 Aug 2019 22:39:37 +0200 Subject: bridge: T1556: fix comment --- src/conf_mode/interface-bridge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index d5ef85940..85ea68e26 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -247,7 +247,7 @@ def apply(bridge): if bridge['disable']: br.down() - # commit change son bridge interface + # commit changes on bridge interface br.commit() # configure additional bridge member options -- cgit v1.2.3 From 343a48be36b1573286db8f944aa933911b09530b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 27 Aug 2019 12:24:09 +0200 Subject: bridge: T1556: remove unused function freeze() --- src/conf_mode/interface-bridge.py | 7 ------- 1 file changed, 7 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 85ea68e26..c1527e4a7 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -45,13 +45,6 @@ default_config_data = { 'stp': 0 } -def freeze(d): - if isinstance(d, dict): - return frozenset((key, freeze(value)) for key, value in d.items()) - elif isinstance(d, list): - return tuple(freeze(value) for value in d) - return d - def diff(first, second): second = set(second) return [item for item in first if item not in second] -- cgit v1.2.3 From 71f7a947539963112c61fef2a5f278d524d71198 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 28 Aug 2019 10:59:09 +0200 Subject: bridge: T1615: add missing support for DHCP/DHCPv6 interface address This feature is not well supported by pyroute2 and thus uses the proof-of-concept vyos.interfaceconfig library. Maybe it's a better idea to write our own library from scratch. --- src/conf_mode/interface-bridge.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index c1527e4a7..9918cbec7 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -22,6 +22,8 @@ from sys import exit from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config +from vyos.validate import is_ip +from vyos.interfaceconfig import Interface as IF from vyos import ConfigError default_config_data = { @@ -62,7 +64,9 @@ def get_config(): # Check if bridge has been removed if not conf.exists('interfaces bridge ' + bridge['intf']): bridge['deleted'] = True - return bridge + # we should not bail out early here b/c we should + # find possible DHCP interfaces later on. + # DHCP interfaces invoke dhclient which should be stopped, too # set new configuration level conf.set_level('interfaces bridge ' + bridge['intf']) @@ -185,6 +189,13 @@ def apply(bridge): # delete bridge interface with ipdb.interfaces[ brif ] as br: br.remove() + + # stop DHCP(v6) clients if configured + for addr in bridge['address_remove']: + if addr == 'dhcp': + IF(brif).del_dhcpv4() + elif addr == 'dhcpv6': + IF(brif).del_dhcpv6() except: pass else: @@ -225,16 +236,31 @@ def apply(bridge): for intf in bridge['member_remove']: br.del_port( intf['name'] ) - # configure bridge member interfaces + # add interfaces to bridge for member in bridge['member']: - # add interface br.add_port(member['name']) - # Configure interface address(es) + # remove configured network interface addresses/DHCP(v6) configuration for addr in bridge['address_remove']: - br.del_ip(addr) + try: + is_ip(addr) + br.del_ip(addr) + except ValueError: + if addr == 'dhcp': + IF(brif).del_dhcpv4() + elif addr == 'dhcpv6': + IF(brif).del_dhcpv6() + + # add configured network interface addresses/DHCP(v6) configuration for addr in bridge['address']: - br.add_ip(addr) + try: + is_ip(addr) + br.add_ip(addr) + except: + if addr == 'dhcp': + IF(brif).set_dhcpv4() + elif addr == 'dhcpv6': + IF(brif).set_dhcpv6() # up/down interface if bridge['disable']: -- cgit v1.2.3 From 24495a18b2ba39cd0c5b024dbe63f3e7df92e69c Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 30 Aug 2019 13:25:23 +0200 Subject: Python/ifconfig: rename interfaceconfig.py -> ifconfig.py --- python/vyos/ifconfig.py | 491 ++++++++++++++++++++++++++++++++++++++ python/vyos/interfaceconfig.py | 471 ------------------------------------ src/conf_mode/interface-bridge.py | 2 +- 3 files changed, 492 insertions(+), 472 deletions(-) create mode 100644 python/vyos/ifconfig.py delete mode 100644 python/vyos/interfaceconfig.py (limited to 'src/conf_mode/interface-bridge.py') diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py new file mode 100644 index 000000000..5f28125af --- /dev/null +++ b/python/vyos/ifconfig.py @@ -0,0 +1,491 @@ +#!/usr/bin/python3 + +# Copyright 2019 VyOS maintainers and contributors +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see . + +import sys +import os +import re +import json +import socket +import subprocess +import ipaddress + +from vyos.validate import * +from ipaddress import IPv4Network, IPv6Address +from netifaces import ifaddresses, AF_INET, AF_INET6 + +dhclient_conf_dir = r'/var/lib/dhcp/dhclient_' + +class Interface: + def __init__(self, ifname=None, type=None): + """ + Create instance of an IP interface + + Example: + + >>> from vyos.ifconfig import Interface + >>> i = Interface('br111', type='bridge') + """ + + 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))) + + if not os.path.exists('/sys/class/net/{0}'.format(ifname)): + try: + cmd = 'ip link add dev "{}" type "{}"'.format(ifname, type) + self._cmd(cmd) + 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 remove(self): + """ + Remove system interface + + Example: + + >>> from vyos.ifconfig import Interface + >>> i = Interface('br111', type='bridge') + >>> i.remove + """ + + # 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): + process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) + proc_stdout = process.communicate()[0].strip() + pass + + + @property + def mtu(self): + """ + Get/set interface mtu in bytes. + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').mtu + '1500' + """ + + mtu = 0 + with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'r') as f: + mtu = f.read().rstrip('\n') + return mtu + + + @mtu.setter + def mtu(self, mtu=None): + """ + Get/set interface mtu in bytes. + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('br100', type='bridge').mtu = 1400 + >>> Interface('br100').mtu + '1400' + """ + + if mtu < 68 or mtu > 9000: + raise ValueError('Invalid MTU size: "{}"'.format(mru)) + + with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'w') as f: + f.write(str(mtu)) + + + @property + def mac(self): + """ + Get/set interface mac address + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').mac + '00:0c:29:11:aa:cc' + """ + address = '' + with open('/sys/class/net/{0}/address'.format(self._ifname), 'r') as f: + address = f.read().rstrip('\n') + return address + + + @mac.setter + def mac(self, mac=None): + """ + Get/set interface mac address + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').mac = '00:90:43:fe:fe:1b' + >>> Interface('eth1').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 ifalias(self): + """ + Get/set interface alias name + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').ifalias + '' + """ + + alias = '' + with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'r') as f: + alias = f.read().rstrip('\n') + return alias + + + @ifalias.setter + def ifalias(self, ifalias=None): + """ + Get/set interface alias name + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').ifalias = 'VyOS upstream interface' + >>> Interface('eth1').ifalias + 'VyOS upstream interface' + + to clear interface alias e.g. delete it use: + + >>> Interface('eth1').ifalias = '' + >>> Interface('eth1').ifalias + '' + """ + + # clear interface alias + if not ifalias: + ifalias = '\0' + + with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'w') as f: + f.write(str(ifalias)) + + @property + def state(self): + """ + Enable (up) / Disable (down) an interface + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').state + 'up' + """ + + state = '' + with open('/sys/class/net/{0}/operstate'.format(self._ifname), 'r') as f: + state = f.read().rstrip('\n') + return state + + + @state.setter + def state(self, state=None): + """ + Enable (up) / Disable (down) an interface + + Example: + + >>> from vyos.ifconfig import Interface + >>> Interface('eth1').state = 'down' + >>> Interface('eth1').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) + self._cmd(cmd) + + + 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_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('eth1').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=None): + """ + Add IP address to interface. Address is only added if it yet not added + to that interface. + + Example: + + >>> from vyos.interfaceconfig import Interface + >>> j = Interface('br100', type='bridge') + >>> 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 not addr: + raise ValueError('No IP address specified') + + if not is_intf_addr_assigned(self._ifname, addr): + cmd = '' + if is_ipv4(addr): + cmd = 'sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, self._ifname) + elif is_ipv6(addr): + cmd = 'sudo ip -6 addr add "{}" dev "{}"'.format(addr, self._ifname) + + self._cmd(cmd) + + + def del_addr(self, addr=None): + """ + Remove IP address from interface. + + Example: + >>> from vyos.interfaceconfig import Interface + >>> j = Interface('br100', type='bridge') + >>> 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 not addr: + raise ValueError('No IP address specified') + + if is_intf_addr_assigned(self._ifname, addr): + cmd = '' + if is_ipv4(addr): + cmd = 'ip -4 addr del "{}" dev "{}"'.format(addr, self._ifname) + elif is_ipv6(addr): + cmd = 'ip -6 addr del "{}" dev "{}"'.format(addr, self._ifname) + + self._cmd(cmd) + + + # 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/interfaceconfig.py b/python/vyos/interfaceconfig.py deleted file mode 100644 index 9790fae49..000000000 --- a/python/vyos/interfaceconfig.py +++ /dev/null @@ -1,471 +0,0 @@ -#!/usr/bin/python3 - -# Copyright 2019 VyOS maintainers and contributors -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library. If not, see . - -import sys -import os -import re -import json -import socket -import subprocess -import ipaddress - -from vyos.validate import * -from ipaddress import IPv4Network, IPv6Address -from netifaces import ifaddresses, AF_INET, AF_INET6 - -dhclient_conf_dir = r'/var/lib/dhcp/dhclient_' - -class Interface: - def __init__(self, ifname=None, type=None): - """ - Create instance of an IP interface - - Example: - - from vyos.interfaceconfig import Interface - i = Interface('br111', type='bridge') - """ - - 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))) - - if not os.path.exists('/sys/class/net/{0}'.format(ifname)): - try: - cmd = 'ip link add dev "{}" type "{}"'.format(ifname, type) - self._cmd(cmd) - 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 remove(self): - """ - Remove system interface - - Example: - - from vyos.interfaceconfig import Interface - i = Interface('br111', type='bridge') - i.remove - """ - - # 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): - process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) - proc_stdout = process.communicate()[0].strip() - pass - - - @property - def mtu(self): - """ - Get/set interface mtu in bytes. - - Example: - - from vyos.interfaceconfig import Interface - mtu = Interface('ens192').mtu - print(mtu) - """ - - mtu = 0 - with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'r') as f: - mtu = f.read().rstrip('\n') - return mtu - - - @mtu.setter - def mtu(self, mtu=None): - """ - Get/set interface mtu in bytes. - - Example: - - from vyos.interfaceconfig import Interface - Interface('ens192').mtu = 1400 - """ - - if mtu < 68 or mtu > 9000: - raise ValueError('Invalid MTU size: "{}"'.format(mru)) - - with open('/sys/class/net/{0}/mtu'.format(self._ifname), 'w') as f: - f.write(str(mtu)) - - - @property - def mac(self): - """ - Get/set interface mac address - - Example: - - from vyos.interfaceconfig import Interface - mac = Interface('ens192').mac - """ - address = '' - with open('/sys/class/net/{0}/address'.format(self._ifname), 'r') as f: - address = f.read().rstrip('\n') - return address - - - @mac.setter - def mac(self, mac=None): - """ - Get/set interface mac address - - Example: - - from vyos.interfaceconfig import Interface - Interface('ens192').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 ifalias(self): - """ - Get/set interface alias name - - Example: - - from vyos.interfaceconfig import Interface - alias = Interface('ens192').ifalias - """ - - alias = '' - with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'r') as f: - alias = f.read().rstrip('\n') - return alias - - - @ifalias.setter - def ifalias(self, ifalias=None): - """ - Get/set interface alias name - - Example: - - from vyos.interfaceconfig import Interface - Interface('ens192').ifalias = 'VyOS upstream interface' - - to clear interface alias e.g. delete it use: - - Interface('ens192').ifalias = '' - """ - - # clear interface alias - if not ifalias: - ifalias = '\0' - - with open('/sys/class/net/{0}/ifalias'.format(self._ifname), 'w') as f: - f.write(str(ifalias)) - - @property - def state(self): - """ - Enable (up) / Disable (down) an interface - - Example: - - from vyos.interfaceconfig import Interface - i = Interface('ens192').link - """ - - state = '' - with open('/sys/class/net/{0}/operstate'.format(self._ifname), 'r') as f: - state = f.read().rstrip('\n') - return state - - - @state.setter - def state(self, state=None): - - 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) - self._cmd(cmd) - - - 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_addr(self): - """ - Retrieve assigned IPv4 and IPv6 addresses from given interface. - This is done using the netifaces and ipaddress python modules. - - Example: - - from vyos.interfaceconfig import Interface - i = Interface('ens192') - i.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=None): - """ - Add IP address to interface. Address is only added if it yet not added - to that interface. - - Example: - - >>> from vyos.interfaceconfig import Interface - >>> j = Interface('br100', type='bridge') - >>> 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 not addr: - raise ValueError('No IP address specified') - - if not is_intf_addr_assigned(self._ifname, addr): - cmd = '' - if is_ipv4(addr): - cmd = 'sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, self._ifname) - elif is_ipv6(addr): - cmd = 'sudo ip -6 addr add "{}" dev "{}"'.format(addr, self._ifname) - - self._cmd(cmd) - - - def del_addr(self, addr=None): - """ - Remove IP address from interface. - - Example: - >>> from vyos.interfaceconfig import Interface - >>> j = Interface('br100', type='bridge') - >>> 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 not addr: - raise ValueError('No IP address specified') - - if is_intf_addr_assigned(self._ifname, addr): - cmd = '' - if is_ipv4(addr): - cmd = 'ip -4 addr del "{}" dev "{}"'.format(addr, self._ifname) - elif is_ipv6(addr): - cmd = 'ip -6 addr del "{}" dev "{}"'.format(addr, self._ifname) - - self._cmd(cmd) - - - # 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/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 9918cbec7..d5661be93 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -23,7 +23,7 @@ from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config from vyos.validate import is_ip -from vyos.interfaceconfig import Interface as IF +from vyos.ifconfig import Interface as IF from vyos import ConfigError default_config_data = { -- cgit v1.2.3 From 04b192c7d1949a6c8223051f144287ac8dbbd1ec Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 31 Aug 2019 13:02:04 +0200 Subject: bridge: T1615: replace pyroute2 by vyos.ifconfig --- src/conf_mode/interface-bridge.py | 127 ++++++++++++++------------------------ 1 file changed, 47 insertions(+), 80 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index d5661be93..187be677e 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -16,21 +16,21 @@ # # -from os import environ +import os + from copy import deepcopy from sys import exit -from pyroute2 import IPDB from netifaces import interfaces from vyos.config import Config from vyos.validate import is_ip -from vyos.ifconfig import Interface as IF +from vyos.ifconfig import BridgeIf, Interface from vyos import ConfigError default_config_data = { 'address': [], 'address_remove': [], 'aging': 300, - 'arp_cache_timeout_ms': 30000, + 'arp_cache_tmo': 30, 'description': '', 'deleted': False, 'disable': False, @@ -57,7 +57,7 @@ def get_config(): # determine tagNode instance try: - bridge['intf'] = environ['VYOS_TAGNODE_VALUE'] + bridge['intf'] = os.environ['VYOS_TAGNODE_VALUE'] except KeyError as E: print("Interface not specified") @@ -107,7 +107,7 @@ def get_config(): # ARP cache entry timeout in seconds if conf.exists('ip arp-cache-timeout'): - bridge['arp_cache_timeout_ms'] = int(conf.return_value('ip arp-cache-timeout')) * 1000 + bridge['arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) # Media Access Control (MAC) address if conf.exists('mac'): @@ -181,56 +181,35 @@ def generate(bridge): return None def apply(bridge): - ipdb = IPDB(mode='explicit') - brif = bridge['intf'] + br = BridgeIf(bridge['intf']) if bridge['deleted']: - try: - # delete bridge interface - with ipdb.interfaces[ brif ] as br: - br.remove() - - # stop DHCP(v6) clients if configured - for addr in bridge['address_remove']: - if addr == 'dhcp': - IF(brif).del_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).del_dhcpv6() - except: - pass + # delete bridge interface + # DHCP is stopped inside remove() + br.remove() else: - try: - # create bridge interface if it not already exists - ipdb.create(kind='bridge', ifname=brif).commit() - except: - pass - - # get handle in bridge interface - br = ipdb.interfaces[brif] - # begin() a transaction prior to make any change - br.begin() # enable interface - br.up() - # set ageing time - - value is in centiseconds YES! centiseconds! - br.br_ageing_time = bridge['aging'] * 100 - # set bridge forward delay - value is in centiseconds YES! centiseconds! - br.br_forward_delay = bridge['forwarding_delay'] * 100 - # set hello time - value is in centiseconds YES! centiseconds! - br.br_hello_time = bridge['hello_time'] * 100 - # set max message age - value is in centiseconds YES! centiseconds! - br.br_max_age = bridge['max_age'] * 100 + br.state = 'up' + # set ageing time + br.ageing_time = bridge['aging'] + # set bridge forward delay + br.forward_delay = bridge['forwarding_delay'] + # set hello time + br.hello_time = bridge['hello_time'] + # set max message age + br.max_age = bridge['max_age'] # set bridge priority - br.br_priority = bridge['priority'] + br.priority = bridge['priority'] # turn stp on/off - br.br_stp_state = bridge['stp'] + br.stp_state = bridge['stp'] # enable or disable IGMP querier - br.br_mcast_querier = bridge['igmp_querier'] + br.multicast_querier = bridge['igmp_querier'] # update interface description used e.g. within SNMP br.ifalias = bridge['description'] # Change interface MAC address if bridge['mac']: - br.set_address = bridge['mac'] + br.mac = bridge['mac'] # remove interface from bridge for intf in bridge['member_remove']: @@ -240,52 +219,40 @@ def apply(bridge): for member in bridge['member']: br.add_port(member['name']) + # up/down interface + if bridge['disable']: + br.state = 'down' + # remove configured network interface addresses/DHCP(v6) configuration for addr in bridge['address_remove']: - try: - is_ip(addr) - br.del_ip(addr) - except ValueError: - if addr == 'dhcp': - IF(brif).del_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).del_dhcpv6() + if addr == 'dhcp': + br.del_dhcp() + elif addr == 'dhcpv6': + br.del_dhcpv6() + else: + br.del_addr(addr) # add configured network interface addresses/DHCP(v6) configuration for addr in bridge['address']: - try: - is_ip(addr) - br.add_ip(addr) - except: - if addr == 'dhcp': - IF(brif).set_dhcpv4() - elif addr == 'dhcpv6': - IF(brif).set_dhcpv6() - - # up/down interface - if bridge['disable']: - br.down() - - # commit changes on bridge interface - br.commit() + if addr == 'dhcp': + br.set_dhcp() + elif addr == 'dhcpv6': + br.set_dhcpv6() + else: + br.add_addr(addr) # configure additional bridge member options for member in bridge['member']: - # configure ARP cache timeout in milliseconds - with open('/proc/sys/net/ipv4/neigh/' + member['name'] + '/base_reachable_time_ms', 'w') as f: - f.write(str(bridge['arp_cache_timeout_ms'])) - # ignore link state changes - with open('/proc/sys/net/ipv4/conf/' + member['name'] + '/link_filter', 'w') as f: - f.write(str(bridge['disable_link_detect'])) - - # adjust member port stp attributes - member_if = ipdb.interfaces[ member['name'] ] - member_if.begin() # set bridge port cost - member_if.brport_cost = member['cost'] + br.set_cost(member['name'], member['cost']) # set bridge port priority - member_if.brport_priority = member['priority'] - member_if.commit() + br.set_priority(member['name'], member['priority']) + + i = Interface(member['name']) + # configure ARP cache timeout + i.arp_cache_tmo = bridge['arp_cache_tmo'] + # ignore link state changes + i.link_detect = bridge['disable_link_detect'] return None -- cgit v1.2.3 From 650293e0e02442803801896128f52624495c319b Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 1 Sep 2019 12:01:52 +0200 Subject: bridge: T1615: support deleting interface description --- src/conf_mode/interface-bridge.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 187be677e..7bc457680 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -82,8 +82,6 @@ def get_config(): # retrieve interface description if conf.exists('description'): bridge['description'] = conf.return_value('description') - else: - bridge['description'] = bridge['intf'] # Disable this bridge interface if conf.exists('disable'): -- cgit v1.2.3 From 81add62632bcdd02a96f6ec2a4bbb533865d68ee Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 1 Sep 2019 12:18:30 +0200 Subject: bridge: T1615: remove is_ip import from vyos.validate --- src/conf_mode/interface-bridge.py | 1 - 1 file changed, 1 deletion(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 7bc457680..6e48a1382 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -22,7 +22,6 @@ from copy import deepcopy from sys import exit from netifaces import interfaces from vyos.config import Config -from vyos.validate import is_ip from vyos.ifconfig import BridgeIf, Interface from vyos import ConfigError -- cgit v1.2.3 From 053f208b2ed9477fc70b770ab7ac884109d9a89a Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 4 Sep 2019 16:22:15 +0200 Subject: bridge: T1615: can not add member interface to bridge if it is also part of a bond --- src/conf_mode/interface-bridge.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 6e48a1382..1d3587114 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -172,6 +172,13 @@ def verify(bridge): if intf['name'] not in interfaces(): raise ConfigError('Can not add non existing interface "{}" to bridge "{}"'.format(intf['name'], bridge['intf'])) + # bridge members are not allowed to be bond members, too + for intf in bridge['member']: + for bond in conf.list_nodes('interfaces bonding'): + if conf.exists('interfaces bonding ' + bond + ' member interface'): + if intf['name'] in conf.return_values('interfaces bonding ' + bond + ' member interface'): + raise ConfigError('Interface {} belongs to bond {}, can not add it to {}'.format(intf['name'], bond, bridge['intf'])) + return None def generate(bridge): -- cgit v1.2.3 From 64d58eda4c1ebaa9d346d65d606d2a75694467ee Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Wed, 4 Sep 2019 21:50:04 +0200 Subject: Python/configdict: add list_diff function to compare two lists A list containing only unique elements not part of the other list is returned. This is usefull to check e.g. which IP addresses need to be removed from the OS. --- python/vyos/configdict.py | 8 ++++++++ src/conf_mode/interface-bonding.py | 11 +++++------ src/conf_mode/interface-bridge.py | 12 +++++------- src/conf_mode/interface-dummy.py | 10 ++++------ src/conf_mode/interface-loopback.py | 7 +++---- 5 files changed, 25 insertions(+), 23 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index 157011839..a723c5322 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -78,3 +78,11 @@ 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] diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py index 03d28954d..ba4577900 100755 --- a/src/conf_mode/interface-bonding.py +++ b/src/conf_mode/interface-bonding.py @@ -23,6 +23,7 @@ from sys import exit from netifaces import interfaces from vyos.ifconfig import BondIf, EthernetIf +from vyos.configdict import list_diff from vyos.config import Config from vyos import ConfigError @@ -55,9 +56,6 @@ default_config_data = { 'vif_remove': [] } -def diff(first, second): - second = set(second) - return [item for item in first if item not in second] def get_bond_mode(mode): if mode == 'round-robin': @@ -77,6 +75,7 @@ def get_bond_mode(mode): else: raise ConfigError('invalid bond mode "{}"'.format(mode)) + def get_ethertype(ethertype_val): if ethertype_val == '0x88A8': return '802.1ad' @@ -321,7 +320,7 @@ def get_config(): # 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') - bond['address_remove'] = diff(eff_addr, act_addr) + bond['address_remove'] = list_diff(eff_addr, act_addr) # Primary device interface if conf.exists('primary'): @@ -333,7 +332,7 @@ def get_config(): # interface is no longer present and needs to be removed eff_intf = conf.list_effective_nodes('vif-s') act_intf = conf.list_nodes('vif-s') - bond['vif_s_remove'] = diff(eff_intf, act_intf) + bond['vif_s_remove'] = list_diff(eff_intf, act_intf) if conf.exists('vif-s'): for vif_s in conf.list_nodes('vif-s'): @@ -347,7 +346,7 @@ def get_config(): # vif interface is no longer present and needs to be removed eff_intf = conf.list_effective_nodes('vif') act_intf = conf.list_nodes('vif') - bond['vif_remove'] = diff(eff_intf, act_intf) + bond['vif_remove'] = list_diff(eff_intf, act_intf) if conf.exists('vif'): for vif in conf.list_nodes('vif'): diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 1d3587114..b165428ee 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -21,8 +21,10 @@ import os from copy import deepcopy from sys import exit from netifaces import interfaces -from vyos.config import Config + from vyos.ifconfig import BridgeIf, Interface +from vyos.configdict import list_diff +from vyos.config import Config from vyos import ConfigError default_config_data = { @@ -46,10 +48,6 @@ default_config_data = { 'stp': 0 } -def diff(first, second): - second = set(second) - return [item for item in first if item not in second] - def get_config(): bridge = deepcopy(default_config_data) conf = Config() @@ -137,13 +135,13 @@ def get_config(): # interfaces is no longer assigend to the bridge and thus can be removed eff_intf = conf.list_effective_nodes('member interface') act_intf = conf.list_nodes('member interface') - bridge['member_remove'] = diff(eff_intf, act_intf) + bridge['member_remove'] = list_diff(eff_intf, act_intf) # Determine interface addresses (currently effective) - to determine which # address is no longer valid and needs to be removed from the bridge eff_addr = conf.return_effective_values('address') act_addr = conf.return_values('address') - bridge['address_remove'] = diff(eff_addr, act_addr) + bridge['address_remove'] = list_diff(eff_addr, act_addr) # Priority for this bridge if conf.exists('priority'): diff --git a/src/conf_mode/interface-dummy.py b/src/conf_mode/interface-dummy.py index 03afdc668..4a1179672 100755 --- a/src/conf_mode/interface-dummy.py +++ b/src/conf_mode/interface-dummy.py @@ -19,8 +19,10 @@ from os import environ from copy import deepcopy from sys import exit -from vyos.config import Config + from vyos.ifconfig import DummyIf +from vyos.configdict import list_diff +from vyos.config import Config from vyos import ConfigError default_config_data = { @@ -32,10 +34,6 @@ default_config_data = { 'intf': '' } -def diff(first, second): - second = set(second) - return [item for item in first if item not in second] - def get_config(): dummy = deepcopy(default_config_data) conf = Config() @@ -70,7 +68,7 @@ def get_config(): # address is no longer valid and needs to be removed from the interface eff_addr = conf.return_effective_values('address') act_addr = conf.return_values('address') - dummy['address_remove'] = diff(eff_addr, act_addr) + dummy['address_remove'] = list_diff(eff_addr, act_addr) return dummy diff --git a/src/conf_mode/interface-loopback.py b/src/conf_mode/interface-loopback.py index be47324c1..e2df37655 100755 --- a/src/conf_mode/interface-loopback.py +++ b/src/conf_mode/interface-loopback.py @@ -18,7 +18,9 @@ from os import environ from sys import exit from copy import deepcopy + from vyos.ifconfig import LoopbackIf +from vyos.configdict import list_diff from vyos.config import Config from vyos import ConfigError @@ -29,9 +31,6 @@ default_config_data = { 'description': '', } -def diff(first, second): - second = set(second) - return [item for item in first if item not in second] def get_config(): loopback = deepcopy(default_config_data) @@ -62,7 +61,7 @@ def get_config(): # address is no longer valid and needs to be removed from the interface eff_addr = conf.return_effective_values('address') act_addr = conf.return_values('address') - loopback['address_remove'] = diff(eff_addr, act_addr) + loopback['address_remove'] = list_diff(eff_addr, act_addr) return loopback -- cgit v1.2.3 From 35c7d66165da2102ee8986c3999fe9fea16c38da Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 6 Sep 2019 10:10:11 +0200 Subject: Python/ifconfig: T1557: {add,del}_addr() now supports dhcp/dhcpv6 Instead of manually starting DHCP/DHCPv6 for every interface and have an identical if/elif/else statement checking for dhcp/dhcpv6 rather move this repeating stement into add_addr()/del_addr(). Single source is always preferred. --- python/vyos/ifconfig.py | 111 ++++++++++++++++++++++++------------- src/conf_mode/interface-bonding.py | 56 ++++++++++--------- src/conf_mode/interface-bridge.py | 35 ++++-------- 3 files changed, 112 insertions(+), 90 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index c5d3e447b..1886addfc 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -61,6 +61,7 @@ class 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)) @@ -111,8 +112,8 @@ class Interface: # All subinterfaces are now removed, continue on the physical interface # stop DHCP(v6) if running - self.del_dhcp() - self.del_dhcpv6() + self._del_dhcp() + self._del_dhcpv6() # NOTE (Improvement): # after interface removal no other commands should be allowed @@ -359,6 +360,8 @@ class Interface: 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) @@ -495,8 +498,14 @@ class Interface: def add_addr(self, addr): """ - Add IP address to interface. Address is only added if it yet not added - to that interface. + 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 @@ -506,13 +515,25 @@ class Interface: >>> j.get_addr() ['192.0.2.1/24', '2001:db8::ffff/64'] """ - if not is_intf_addr_assigned(self._ifname, addr): - cmd = 'ip addr add "{}" dev "{}"'.format(addr, self._ifname) - self._cmd(cmd) + 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): """ - Remove IP address from interface. + 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 @@ -525,12 +546,17 @@ class Interface: >>> j.get_addr() ['2001:db8::ffff/64'] """ - if is_intf_addr_assigned(self._ifname, addr): - cmd = 'ip addr del "{}" dev "{}"'.format(addr, self._ifname) - self._cmd(cmd) + 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): + def _set_dhcp(self): """ Configure interface as DHCP client. The dhclient binary is automatically started in background! @@ -557,15 +583,17 @@ class Interface: 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) - self._cmd(cmd) + 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): + + def _del_dhcp(self): """ De-configure interface as DHCP clinet. All auto generated files like pid, config and lease will be removed. @@ -601,7 +629,8 @@ class Interface: if os.path.isfile(self._dhcp_lease_file): os.remove(self._dhcp_lease_file) - def set_dhcpv6(self): + + def _set_dhcpv6(self): """ Configure interface as DHCPv6 client. The dhclient binary is automatically started in background! @@ -622,25 +651,28 @@ class Interface: 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) + 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) + # no longer accept router announcements on this interface + cmd = 'sysctl -q -w net.ipv6.conf.{}.accept_ra=0'.format(self._ifname) + self._cmd(cmd) - 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) + # 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): + + def _del_dhcpv6(self): """ De-configure interface as DHCPv6 clinet. All auto generated files like pid, config and lease will be removed. @@ -1281,7 +1313,6 @@ class BondIf(EthernetIf): class WireGuardIf(Interface): - """ Wireguard interface class, contains a comnfig dictionary since wireguard VPN is being comnfigured via the wg command rather than @@ -1293,11 +1324,11 @@ class WireGuardIf(Interface): >>> 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, + {'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, + {'private-key': None, 'keepalive': 100, 'endpoint': None, 'port': 0, 'allowed-ips': [], 'pubkey': None, 'fwmark': 0, 'psk': '/dev/null'} """ diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py index 2ec764965..81667289d 100755 --- a/src/conf_mode/interface-bonding.py +++ b/src/conf_mode/interface-bonding.py @@ -85,12 +85,6 @@ def apply_vlan_config(vlan, config): if type(vlan) != type(EthernetIf("lo")): raise TypeError() - # Configure interface address(es) - for addr in config['address_remove']: - vlan.del_addr(addr) - for addr in config['address']: - vlan.add_addr(addr) - # update interface description used e.g. within SNMP vlan.ifalias = config['description'] # ignore link state changes @@ -107,6 +101,14 @@ def apply_vlan_config(vlan, config): else: vlan.state = 'up' + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second + for addr in config['address_remove']: + vlan.del_addr(addr) + for addr in config['address']: + vlan.add_addr(addr) + def get_config(): # initialize kernel module if not loaded @@ -139,6 +141,11 @@ def get_config(): if conf.exists('address'): bond['address'] = conf.return_values('address') + # get 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') + bond['address_remove'] = list_diff(eff_addr, bond['address']) + # ARP link monitoring frequency in milliseconds if conf.exists('arp-monitor interval'): bond['arp_mon_intvl'] = int(conf.return_value('arp-monitor interval')) @@ -209,12 +216,6 @@ def get_config(): if conf.exists('member interface'): bond['member'] = conf.return_values('member interface') - # get 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') - bond['address_remove'] = list_diff(eff_addr, act_addr) - # Primary device interface if conf.exists('primary'): bond['primary'] = conf.return_value('primary') @@ -314,6 +315,7 @@ def apply(bond): b = BondIf(bond['intf']) if bond['deleted']: + # # delete bonding interface b.remove() else: @@ -326,12 +328,6 @@ def apply(bond): for intf in b.get_slaves(): b.del_port(intf) - # Configure interface address(es) - for addr in bond['address_remove']: - b.del_addr(addr) - for addr in bond['address']: - b.add_addr(addr) - # ARP link monitoring frequency b.arp_interval = bond['arp_mon_intvl'] # reset miimon on arp-montior deletion @@ -348,8 +344,8 @@ def apply(bond): # from the kernel side this looks valid to me. We won't run into an error # when a user added manual adresses which would result in having more # then 16 adresses in total. - cur_addr = list(map(str, b.arp_ip_target.split())) - for addr in cur_addr: + arp_tgt_addr = list(map(str, b.arp_ip_target.split())) + for addr in arp_tgt_addr: b.arp_ip_target = '-' + addr # Add configured ARP target addresses @@ -391,6 +387,20 @@ def apply(bond): for intf in bond['member']: b.add_port(intf) + # As the bond interface is always disabled first when changing + # parameters we will only re-enable the interface if it is not + # administratively disabled + if not bond['disable']: + b.state = 'up' + + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second + for addr in bond['address_remove']: + b.del_addr(addr) + for addr in bond['address']: + b.add_addr(addr) + # remove no longer required service VLAN interfaces (vif-s) for vif_s in bond['vif_s_remove']: b.del_vlan(vif_s) @@ -420,12 +430,6 @@ def apply(bond): vlan = b.add_vlan(vif['id']) apply_vlan_config(vlan, vif) - # As the bond interface is always disabled first when changing - # parameters we will only re-enable the interface if it is not - # administratively disabled - if not bond['disable']: - b.state = 'up' - return None if __name__ == '__main__': diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index b165428ee..8996fceec 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -61,9 +61,7 @@ def get_config(): # Check if bridge has been removed if not conf.exists('interfaces bridge ' + bridge['intf']): bridge['deleted'] = True - # we should not bail out early here b/c we should - # find possible DHCP interfaces later on. - # DHCP interfaces invoke dhclient which should be stopped, too + return bridge # set new configuration level conf.set_level('interfaces bridge ' + bridge['intf']) @@ -72,6 +70,11 @@ def get_config(): if conf.exists('address'): bridge['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 bridge + eff_addr = conf.return_effective_values('address') + bridge['address_remove'] = list_diff(eff_addr, bridge['address']) + # retrieve aging - how long addresses are retained if conf.exists('aging'): bridge['aging'] = int(conf.return_value('aging')) @@ -137,12 +140,6 @@ def get_config(): act_intf = conf.list_nodes('member interface') bridge['member_remove'] = list_diff(eff_intf, act_intf) - # Determine interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed from the bridge - eff_addr = conf.return_effective_values('address') - act_addr = conf.return_values('address') - bridge['address_remove'] = list_diff(eff_addr, act_addr) - # Priority for this bridge if conf.exists('priority'): bridge['priority'] = int(conf.return_value('priority')) @@ -225,23 +222,13 @@ def apply(bridge): if bridge['disable']: br.state = 'down' - # remove configured network interface addresses/DHCP(v6) configuration + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second for addr in bridge['address_remove']: - if addr == 'dhcp': - br.del_dhcp() - elif addr == 'dhcpv6': - br.del_dhcpv6() - else: - br.del_addr(addr) - - # add configured network interface addresses/DHCP(v6) configuration + br.del_addr(addr) for addr in bridge['address']: - if addr == 'dhcp': - br.set_dhcp() - elif addr == 'dhcpv6': - br.set_dhcpv6() - else: - br.add_addr(addr) + br.add_addr(addr) # configure additional bridge member options for member in bridge['member']: -- cgit v1.2.3 From a9756cfd49b169e93afe70415ce9155ebf4e5ffa Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sat, 7 Sep 2019 14:18:23 +0200 Subject: bridge: bonding: minor comment cleanup --- src/conf_mode/interface-bonding.py | 2 +- src/conf_mode/interface-bridge.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/conf_mode/interface-bridge.py') diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py index bce8f98fb..cd4c447ac 100755 --- a/src/conf_mode/interface-bonding.py +++ b/src/conf_mode/interface-bonding.py @@ -142,7 +142,7 @@ def get_config(): bond['address'] = conf.return_values('address') # get interface addresses (currently effective) - to determine which - # address is no longer valid and needs to be removed from the bond + # address is no longer valid and needs to be removed eff_addr = conf.return_effective_values('address') bond['address_remove'] = list_diff(eff_addr, bond['address']) diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 8996fceec..401182a0d 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -71,7 +71,7 @@ def get_config(): bridge['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 bridge + # address is no longer valid and needs to be removed eff_addr = conf.return_effective_values('address') bridge['address_remove'] = list_diff(eff_addr, bridge['address']) -- cgit v1.2.3