From 0faeedf5c381659d62164ee503127bca0b6897fd Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 2 Aug 2019 11:33:35 +0200 Subject: [bridge] T1156: first working implementation using Python and XML --- python/vyos/configinterface.py | 77 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 python/vyos/configinterface.py (limited to 'python') diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py new file mode 100644 index 000000000..b0d766b9c --- /dev/null +++ b/python/vyos/configinterface.py @@ -0,0 +1,77 @@ +# 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 os + +def set_description(intf, desc): + """ + Sets the interface secription reported usually by SNMP + """ + with open('/sys/class/net/' + intf + '/ifalias', 'w') as f: + f.write(desc) + + +def set_arp_cache_timeout(intf, tmoMS): + """ + Configure the ARP cache entry timeout in milliseconds + """ + with open('/proc/sys/net/ipv4/neigh/' + intf + '/base_reachable_time_ms', 'w') as f: + f.write(tmoMS) + +def set_multicast_querier(intf, enable): + """ + Sets whether the bridge actively runs a multicast querier or not. When a + bridge receives a 'multicast host membership' query from another network host, + that host is tracked based on the time that the query was received plus the + multicast query interval time. + + use enable=1 to enable or enable=0 to disable + """ + + if int(enable) >= 0 and int(enable) <= 1: + with open('/sys/devices/virtual/net/' + intf + '/bridge/multicast_querier', 'w') as f: + f.write(str(enable)) + else: + raise ValueError("malformed configuration string on interface {}: enable={}".format(intf, enable)) + +def set_link_detect(intf, enable): + """ + 0 - Allow packets to be received for the address on this interface + even if interface is disabled or no carrier. + + 1 - Ignore packets received if interface associated with the incoming + address is down. + + 2 - Ignore packets received if interface associated with the incoming + address is down or has no carrier. + + Kernel Source: Documentation/networking/ip-sysctl.txt + """ + + # Note can't use sysctl it is broken for vif name because of dots + # link_filter values: + # 0 - always receive + # 1 - ignore receive if admin_down + # 2 - ignore receive if admin_down or link down + + with open('/proc/sys/net/ipv4/conf/' + intf + '/link_filter', 'w') as f: + if enable == True or enable == 1: + f.write('2') + if os.path.isfile('/usr/bin/vtysh'): + os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "link-detect"'.format(intf)) + else: + f.write('1') + if os.path.isfile('/usr/bin/vtysh'): + os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf)) -- cgit v1.2.3 From 5b836709a15e4f6a8775e5dc26609febd5bc2480 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Fri, 2 Aug 2019 17:27:25 +0200 Subject: [bridge] T1156: support adding and removing bridge member interfaces This is the new syntax bridge br0 { member { interface eth0 { cost 10 } interface eth1 { cost 11 } } } --- interface-definitions/interfaces-bridge.xml | 76 +++++++++++++++---------- python/vyos/configinterface.py | 8 +++ src/conf_mode/interface-bridge.py | 86 +++++++++++++++++++++++------ src/migration-scripts/interface/0-to-1 | 82 --------------------------- src/migration-scripts/interfaces/0-to-1 | 82 +++++++++++++++++++++++++++ 5 files changed, 207 insertions(+), 127 deletions(-) delete mode 100755 src/migration-scripts/interface/0-to-1 create mode 100755 src/migration-scripts/interfaces/0-to-1 (limited to 'python') diff --git a/interface-definitions/interfaces-bridge.xml b/interface-definitions/interfaces-bridge.xml index a5f2df5b5..af19d9438 100644 --- a/interface-definitions/interfaces-bridge.xml +++ b/interface-definitions/interfaces-bridge.xml @@ -51,7 +51,8 @@ Address aging time for bridge seconds (default 300) - + + @@ -146,20 +147,7 @@ Enable or disable IGMP querier - - enable disable - - - enable - Enable IGMP querier - - - disable - Disable IGMP querier - - - (enable|disable) - + @@ -206,6 +194,49 @@ Bridge max aging value must be between 1 and 40 seconds + + + Bridge member interfaces + + + + + Member interface name + + + + + + + + Bridge port cost + + 1-65535 + Path cost value for Spanning Tree Protocol + + + + + Path cost value must be between 1 and 65535 + + + + + Bridge port priority + + 0-63 + Bridge port priority + + + + + Port priority value must be between 0 and 63 + + + + + + Priority for this bridge @@ -222,20 +253,7 @@ Enable spanning tree protocol - - true false - - - true - Enable Spanning Tree Protocol - - - false - Disable Spanning Tree Protocol - - - (true|false) - + diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py index b0d766b9c..37b6b92c1 100644 --- a/python/vyos/configinterface.py +++ b/python/vyos/configinterface.py @@ -15,6 +15,14 @@ import os +def set_mac_address(intf, addr): + """ + Configure interface mac address using iproute2 command + + NOTE: mac address should be validated here??? + """ + os.system('ip link set {} address {}'.format(intf, addr)) + def set_description(intf, desc): """ Sets the interface secription reported usually by SNMP diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index f7f70b15d..637c58a5e 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -44,6 +44,8 @@ default_config_data = { 'arp_cache_timeout_ms': '30000', 'mac' : '', 'max_age': '20', + 'member': [], + 'member_remove': [], 'priority': '32768', 'stp': 'off' } @@ -53,6 +55,10 @@ def subprocess_cmd(command): proc_stdout = process.communicate()[0].strip() print(proc_stdout) +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) conf = Config() @@ -60,7 +66,6 @@ def get_config(): # determine tagNode instance try: bridge['br_name'] = os.environ['VYOS_TAGNODE_VALUE'] - print("Executing script for interface: " + bridge['br_name']) except KeyError as E: print("Interface not specified") @@ -118,9 +123,7 @@ def get_config(): # Enable or disable IGMP querier if conf.exists('igmp-snooping querier'): - tmp = conf.return_value('igmp-snooping querier') - if tmp == "enable": - bridge['igmp_querier'] = 1 + bridge['igmp_querier'] = 1 # ARP cache entry timeout in seconds if conf.exists('ip arp-cache-timeout'): @@ -135,15 +138,35 @@ def get_config(): if conf.exists('max-age'): bridge['max_age'] = conf.return_value('max-age') + # Determine bridge member interface (currently configured) + for intf in conf.list_nodes('member interface'): + iface = { + 'name': intf, + 'cost': '', + 'priority': '' + } + + if conf.exists('member interface {} cost'.format(intf)): + iface['cost'] = 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)) + + bridge['member'].append(iface) + + # Determine bridge member interface (currently effective) - to determine which interfaces + # need to be removed from the bridge + eff_intf = conf.list_effective_nodes('member interface') + act_intf = conf.list_nodes('member interface') + bridge['member_remove'] = diff(eff_intf, act_intf) + # Priority for this bridge if conf.exists('priority'): bridge['priority'] = conf.return_value('priority') # Enable spanning tree protocol if conf.exists('stp'): - tmp = conf.return_value('stp') - if tmp == "true": - bridge['stp'] = 'on' + bridge['stp'] = 'on' return bridge @@ -151,6 +174,9 @@ def verify(bridge): if bridge is None: return None + # validate agains other bridge interfaces that the interface is not assigned + # to another bridge + return None def generate(bridge): @@ -165,43 +191,71 @@ def apply(bridge): if bridge['deleted']: # bridges need to be shutdown first - os.system("ip link set dev {0} down".format(bridge['br_name'])) + os.system("ip link set dev {} down".format(bridge['br_name'])) # delete bridge - os.system("brctl delbr {0}".format(bridge['br_name'])) + os.system("brctl delbr {}".format(bridge['br_name'])) else: # create bridge if it does not exist if not os.path.exists("/sys/class/net/" + bridge['br_name']): - os.system("brctl addbr {0}".format(bridge['br_name'])) + os.system("brctl addbr {}".format(bridge['br_name'])) # assemble bridge configuration # configuration is passed via subprocess to brctl cmd = '' # set ageing time - cmd += 'brctl setageing {0} {1}'.format(bridge['br_name'], bridge['aging']) + cmd += 'brctl setageing {} {}'.format(bridge['br_name'], bridge['aging']) cmd += ' && ' # set bridge forward delay - cmd += 'brctl setfd {0} {1}'.format(bridge['br_name'], bridge['forwarding_delay']) + cmd += 'brctl setfd {} {}'.format(bridge['br_name'], bridge['forwarding_delay']) cmd += ' && ' # set hello time - cmd += 'brctl sethello {0} {1}'.format(bridge['br_name'], bridge['hello_time']) + cmd += 'brctl sethello {} {}'.format(bridge['br_name'], bridge['hello_time']) cmd += ' && ' # set max message age - cmd += 'brctl setmaxage {0} {1}'.format(bridge['br_name'], bridge['max_age']) + cmd += 'brctl setmaxage {} {}'.format(bridge['br_name'], bridge['max_age']) cmd += ' && ' # set bridge priority - cmd += 'brctl setbridgeprio {0} {1}'.format(bridge['br_name'], bridge['priority']) + cmd += 'brctl setbridgeprio {} {}'.format(bridge['br_name'], bridge['priority']) cmd += ' && ' # turn stp on/off - cmd += 'brctl stp {0} {1}'.format(bridge['br_name'], bridge['stp']) + cmd += 'brctl stp {} {}'.format(bridge['br_name'], bridge['stp']) + + for intf in bridge['member_remove']: + # remove interface from bridge + cmd += ' && ' + cmd += 'brctl delif {} {}'.format(bridge['br_name'], 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['br_name'] + '/brif/' + intf['name']): + cmd += ' && ' + cmd += 'brctl addif {} {}'.format(bridge['br_name'], intf['name']) + + # set bridge port cost + if intf['cost']: + cmd += ' && ' + cmd += 'brctl setpathcost {} {} {}'.format(bridge['br_name'], intf['name'], intf['cost']) + + # set bridge port priority + if intf['priority']: + cmd += ' && ' + cmd += 'brctl setportprio {} {} {}'.format(bridge['br_name'], intf['name'], intf['priority']) subprocess_cmd(cmd) + # Change interface MAC address + if bridge['mac']: + VyIfconfig.set_mac_address(bridge['br_name'], bridge['mac']) + else: + print("TODO: change mac mac address to the autoselected one based on member interfaces" + # update interface description used e.g. within SNMP VyIfconfig.set_description(bridge['br_name'], bridge['description']) diff --git a/src/migration-scripts/interface/0-to-1 b/src/migration-scripts/interface/0-to-1 deleted file mode 100755 index 1c6119d86..000000000 --- a/src/migration-scripts/interface/0-to-1 +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 - -# Change syntax of bridge interface -# - move interface based bridge-group to actual bridge (de-nest) -# - make stp and igmp-snooping nodes valueless -# https://phabricator.vyos.net/T1556 - -import sys - -from vyos.configtree import ConfigTree - -if (len(sys.argv) < 1): - print("Must specify file name!") - sys.exit(1) - -file_name = sys.argv[1] - -with open(file_name, 'r') as f: - config_file = f.read() - -config = ConfigTree(config_file) -base = ['interfaces', 'bridge'] - -# -# make stp and igmp-snooping nodes valueless -# -for br in config.list_nodes(base): - # STP: check if enabled - stp_val = config.return_value(base + [br, 'stp']) - # STP: delete node with old syntax - config.delete(base + [br, 'stp']) - # STP: set new node - if enabled - if stp_val == "true": - config.set(base + [br, 'stp'], value=None) - - - # igmp-snooping: check if enabled - igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: delete node with old syntax - config.delete(base + [br, 'igmp-snooping', 'querier']) - # igmp-snooping: set new node - if enabled - if igmp_val == "enable": - config.set(base + [br, 'igmp-snooping', 'querier'], value=None) - -# -# move interface based bridge-group to actual bridge (de-nest) -# -bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] -for type in bridge_types: - if not config.exists(['interfaces', type]): - continue - - for intf in config.list_nodes(['interfaces', type]): - # check if bridge-group exists - if config.exists(['interfaces', type, intf, 'bridge-group']): - bridge = config.return_value(['interfaces', type, intf, 'bridge-group', 'bridge']) - - # create new bridge member interface - config.set(base + [bridge, 'member', 'interface', intf]) - # format as tag node to avoid loading problems - config.set_tag(base + [bridge, 'member', 'interface']) - - # cost: migrate if configured - if config.exists(['interfaces', type, intf, 'bridge-group', 'cost']): - cost = config.return_value(['interfaces', type, intf, 'bridge-group', 'cost']) - # set new node - config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) - - if config.exists(['interfaces', type, intf, 'bridge-group', 'priority']): - priority = config.return_value(['interfaces', type, intf, 'bridge-group', 'priority']) - # set new node - config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) - - # Delete the old bridge-group assigned to an interface - config.delete(['interfaces', type, intf, 'bridge-group']) - - try: - with open(file_name, 'w') as f: - f.write(config.to_string()) - except OSError as e: - print("Failed to save the modified config: {}".format(e)) - sys.exit(1) diff --git a/src/migration-scripts/interfaces/0-to-1 b/src/migration-scripts/interfaces/0-to-1 new file mode 100755 index 000000000..1c6119d86 --- /dev/null +++ b/src/migration-scripts/interfaces/0-to-1 @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +# Change syntax of bridge interface +# - move interface based bridge-group to actual bridge (de-nest) +# - make stp and igmp-snooping nodes valueless +# https://phabricator.vyos.net/T1556 + +import sys + +from vyos.configtree import ConfigTree + +if (len(sys.argv) < 1): + print("Must specify file name!") + sys.exit(1) + +file_name = sys.argv[1] + +with open(file_name, 'r') as f: + config_file = f.read() + +config = ConfigTree(config_file) +base = ['interfaces', 'bridge'] + +# +# make stp and igmp-snooping nodes valueless +# +for br in config.list_nodes(base): + # STP: check if enabled + stp_val = config.return_value(base + [br, 'stp']) + # STP: delete node with old syntax + config.delete(base + [br, 'stp']) + # STP: set new node - if enabled + if stp_val == "true": + config.set(base + [br, 'stp'], value=None) + + + # igmp-snooping: check if enabled + igmp_val = config.return_value(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: delete node with old syntax + config.delete(base + [br, 'igmp-snooping', 'querier']) + # igmp-snooping: set new node - if enabled + if igmp_val == "enable": + config.set(base + [br, 'igmp-snooping', 'querier'], value=None) + +# +# move interface based bridge-group to actual bridge (de-nest) +# +bridge_types = ['bonding', 'ethernet', 'l2tpv3', 'openvpn', 'vxlan', 'wireless'] +for type in bridge_types: + if not config.exists(['interfaces', type]): + continue + + for intf in config.list_nodes(['interfaces', type]): + # check if bridge-group exists + if config.exists(['interfaces', type, intf, 'bridge-group']): + bridge = config.return_value(['interfaces', type, intf, 'bridge-group', 'bridge']) + + # create new bridge member interface + config.set(base + [bridge, 'member', 'interface', intf]) + # format as tag node to avoid loading problems + config.set_tag(base + [bridge, 'member', 'interface']) + + # cost: migrate if configured + if config.exists(['interfaces', type, intf, 'bridge-group', 'cost']): + cost = config.return_value(['interfaces', type, intf, 'bridge-group', 'cost']) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'cost'], value=cost) + + if config.exists(['interfaces', type, intf, 'bridge-group', 'priority']): + priority = config.return_value(['interfaces', type, intf, 'bridge-group', 'priority']) + # set new node + config.set(base + [bridge, 'member', 'interface', intf, 'priority'], value=priority) + + # Delete the old bridge-group assigned to an interface + config.delete(['interfaces', type, intf, 'bridge-group']) + + try: + with open(file_name, 'w') as f: + f.write(config.to_string()) + except OSError as e: + print("Failed to save the modified config: {}".format(e)) + sys.exit(1) -- cgit v1.2.3 From 8545b650f0cc1e7319744c3da57d5a74472246dc Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 4 Aug 2019 22:31:06 +0200 Subject: [bridge] T1156: validate if supplied MAC address is valid --- python/vyos/configinterface.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py index 37b6b92c1..8ff93c02f 100644 --- a/python/vyos/configinterface.py +++ b/python/vyos/configinterface.py @@ -15,12 +15,32 @@ import os +def validate_mac_address(addr): + # a mac address consits out of 6 octets + octets = len(addr.split(':')) + if octets != 6: + raise ValueError('wrong number of MAC octets: {} '.format(octets)) + + # validate against the first mac address byte if it's a multicast address + if int(addr.split(':')[0]) & 1: + raise ValueError('{} is a multicast MAC address'.format(addr)) + + # overall mac address is not allowed to be 00:00:00:00:00:00 + if sum(int(i, 16) for i in addr.split(':')) == 0: + raise ValueError('00:00:00:00:00:00 is not a valid MAC address') + + # check for VRRP mac address + if addr.split(':')[0] == '0' and addr.split(':')[1] == '0' and addr.split(':')[2] == '94' and addr.split(':')[3] == '0' and addr.split(':')[4] == '1': + raise ValueError('{} is a VRRP MAC address') + + pass + def set_mac_address(intf, addr): """ Configure interface mac address using iproute2 command - - NOTE: mac address should be validated here??? """ + validate_mac_address(addr) + os.system('ip link set {} address {}'.format(intf, addr)) def set_description(intf, desc): -- cgit v1.2.3 From 2a2e3e8cded406ce209b94f565b16e5a766ec97e Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 4 Aug 2019 22:33:20 +0200 Subject: [bridge] T1156: add missing 'pass' statements --- python/vyos/configinterface.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'python') diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py index 8ff93c02f..0f25c4a89 100644 --- a/python/vyos/configinterface.py +++ b/python/vyos/configinterface.py @@ -42,6 +42,7 @@ def set_mac_address(intf, addr): validate_mac_address(addr) os.system('ip link set {} address {}'.format(intf, addr)) + pass def set_description(intf, desc): """ @@ -50,6 +51,7 @@ def set_description(intf, desc): with open('/sys/class/net/' + intf + '/ifalias', 'w') as f: f.write(desc) + pass def set_arp_cache_timeout(intf, tmoMS): """ @@ -58,6 +60,8 @@ def set_arp_cache_timeout(intf, tmoMS): with open('/proc/sys/net/ipv4/neigh/' + intf + '/base_reachable_time_ms', 'w') as f: f.write(tmoMS) + pass + def set_multicast_querier(intf, enable): """ Sets whether the bridge actively runs a multicast querier or not. When a @@ -74,6 +78,8 @@ def set_multicast_querier(intf, enable): else: raise ValueError("malformed configuration string on interface {}: enable={}".format(intf, enable)) + pass + def set_link_detect(intf, enable): """ 0 - Allow packets to be received for the address on this interface @@ -103,3 +109,5 @@ def set_link_detect(intf, enable): f.write('1') if os.path.isfile('/usr/bin/vtysh'): os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf)) + + pass -- cgit v1.2.3 From 752229cf7ef080d7a5dd723e7d9b1aa13e44ecd0 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Sun, 4 Aug 2019 23:44:03 +0200 Subject: Python/VyOS validate: improve logic on is_ipv4() and is_ipv6() Previosly the check failed when a network statement was passed which contained host bits set e.g. 192.0.2.1/24. This no longer is an issue b/c this is a valid v4 address. Address is now split on / and validated. --- python/vyos/validate.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'python') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 8def0a510..8f453f85d 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -18,22 +18,24 @@ import ipaddress def is_ipv4(addr): """ - Check addr if it is an IPv4 address/network. - - Return True/False + Check addr if it is an IPv4 address/network. Returns True/False """ - if ipaddress.ip_network(addr).version == 4: + + # With the below statement we can check for IPv4 networks and host + # addresses at the same time + if ipaddress.ip_address(addr.split(r'/')[0]).version == 4: return True else: return False def is_ipv6(addr): """ - Check addr if it is an IPv6 address/network. - - Return True/False + Check addr if it is an IPv6 address/network. Returns True/False """ - if ipaddress.ip_network(addr).version == 6: + + # With the below statement we can check for IPv4 networks and host + # addresses at the same time + if ipaddress.ip_network(addr.split(r'/')[0]).version == 6: return True else: return False -- cgit v1.2.3 From 6e9a0162f84a1baca9acf0ca675ab3c574c7e297 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 5 Aug 2019 01:15:20 +0200 Subject: Python/VyOS validate: add helper to check if an address belongs to a given interface --- python/vyos/validate.py | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) (limited to 'python') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index 8f453f85d..fb7fa3051 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -40,12 +40,9 @@ def is_ipv6(addr): else: return False -def is_addr_assigned(addr): +def is_intf_addr_assigned(intf, addr): """ - Verify if the given IPv4/IPv6 address is assigned to any interface on this - system. - - Return True/False + Verify if the given IPv4/IPv6 address is assigned to specific interface """ # determine IP version (AF_INET or AF_INET6) depending on passed address @@ -53,14 +50,31 @@ def is_addr_assigned(addr): if is_ipv6(addr): addr_type = netifaces.AF_INET6 - for interface in netifaces.interfaces(): - # check if the requested address type is configured at all - if addr_type in netifaces.ifaddresses(interface).keys(): - # Check every IP address on this interface for a match - for ip in netifaces.ifaddresses(interface)[addr_type]: - # Check if it matches to the address requested - if ip['addr'] == addr: - return True + # check if the requested address type is configured at all + try: + netifaces.ifaddresses(intf) + except ValueError as e: + print(e) + return False + + if addr_type in netifaces.ifaddresses(intf).keys(): + # Check every IP address on this interface for a match + for ip in netifaces.ifaddresses(intf)[addr_type]: + # Check if it matches to the address requested + if ip['addr'] == addr: + return True + + return False + +def is_addr_assigned(addr): + """ + Verify if the given IPv4/IPv6 address is assigned to any interface + """ + + for intf in netifaces.interfaces(): + tmp = is_intf_addr_assigned(intf, addr) + if tmp == True: + return True return False -- cgit v1.2.3 From d765eef461e53241cf57bcb6b409dc6fec0efc92 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 5 Aug 2019 11:28:23 +0200 Subject: Python/VyOS validate: extend is_intf_addr_assigned() Verify if the given IPv4/IPv6 address is assigned to specific interface. It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR address 192.0.2.1/24. Used testbench: =============== 20: br0: mtu 1500 qdisc noop state DOWN group default qlen 1000 inet 192.0.2.1/24 brd 192.0.2.255 scope global br0 inet 192.0.3.1/24 brd 192.0.3.255 scope global br0 inet6 2001:db8:2::ffff/64 scope global tentative inet6 2001:db8:1::ffff/64 scope global tentative is_intf_addr_assigned('br0', '192.0.2.1/24') -> True is_intf_addr_assigned('br0', '192.0.2.1') -> True is_intf_addr_assigned('br0', '2001:db8:2::ffff/64') -> True is_intf_addr_assigned('br0', '2001:db8:2::ffff') -> True is_intf_addr_assigned('br0', '192.0.100.1/24') -> False is_intf_addr_assigned('br0', '192.0.100.1') -> False is_intf_addr_assigned('br0', '2001:db8:100::ffff/64') -> False is_intf_addr_assigned('br0', '2001:db8:100::ffff') -> False --- python/vyos/validate.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) (limited to 'python') diff --git a/python/vyos/validate.py b/python/vyos/validate.py index fb7fa3051..97a401423 100644 --- a/python/vyos/validate.py +++ b/python/vyos/validate.py @@ -42,7 +42,9 @@ def is_ipv6(addr): def is_intf_addr_assigned(intf, addr): """ - Verify if the given IPv4/IPv6 address is assigned to specific interface + Verify if the given IPv4/IPv6 address is assigned to specific interface. + It can check both a single IP address (e.g. 192.0.2.1 or a assigned CIDR + address 192.0.2.1/24. """ # determine IP version (AF_INET or AF_INET6) depending on passed address @@ -61,8 +63,28 @@ def is_intf_addr_assigned(intf, addr): # Check every IP address on this interface for a match for ip in netifaces.ifaddresses(intf)[addr_type]: # Check if it matches to the address requested - if ip['addr'] == addr: - return True + # If passed address contains a '/' indicating a normalized IP + # address we have to take this into account, too + if r'/' in addr: + prefixlen = '' + if is_ipv6(addr): + # 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(ip['netmask'].replace(':',''), 16) ).count('1') + prefixlen = '/' + str(bits) + + else: + prefixlen = '/' + str(ipaddress.IPv4Network('0.0.0.0/' + ip['netmask']).prefixlen) + + # construct temporary variable holding IPv6 address and netmask + # in CIDR notation + tmp = ip['addr'] + prefixlen + if addr == tmp: + return True + + elif ip['addr'] == addr: + return True return False -- cgit v1.2.3 From 61cf03e22bbd1cef574e1884e9814cc3cc464a90 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Mon, 5 Aug 2019 11:41:39 +0200 Subject: [bridge] T1156: support adding interface addresses --- python/vyos/configinterface.py | 40 +++++++++++++++++++++++++ src/conf_mode/interface-bridge.py | 61 +++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 22 deletions(-) (limited to 'python') diff --git a/python/vyos/configinterface.py b/python/vyos/configinterface.py index 0f25c4a89..0f5b0842c 100644 --- a/python/vyos/configinterface.py +++ b/python/vyos/configinterface.py @@ -14,6 +14,7 @@ # License along with this library. If not, see . import os +import vyos.validate def validate_mac_address(addr): # a mac address consits out of 6 octets @@ -111,3 +112,42 @@ def set_link_detect(intf, enable): os.system('/usr/bin/vtysh -c "configure terminal" -c "interface {}" -c "no link-detect"'.format(intf)) pass + +def add_interface_address(intf, addr): + """ + Configure an interface IPv4/IPv6 address + """ + if addr == "dhcp": + os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=start'.format(intf)) + elif addr == "dhcpv6": + os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --start -ifname "{}"'.format(intf)) + elif vyos.validate.is_ipv4(addr): + if not vyos.validate.is_intf_addr_assigned(intf, addr): + print("Assigning {} to {}".format(addr, intf)) + os.system('sudo ip -4 addr add "{}" broadcast + dev "{}"'.format(addr, intf)) + elif vyos.validate.is_ipv6(addr): + if not vyos.validate.is_intf_addr_assigned(intf, addr): + print("Assigning {} to {}".format(addr, intf)) + os.system('sudo ip -6 addr add "{}" dev "{}"'.format(addr, intf)) + else: + raise ConfigError('{} is not a valid interface address'.format(addr)) + + pass + +def remove_interface_address(intf, addr): + """ + Remove IPv4/IPv6 address from given interface + """ + + if addr == "dhcp": + os.system('/opt/vyatta/sbin/vyatta-interfaces.pl --dev="{}" --dhcp=stop'.format(intf)) + elif addr == "dhcpv6": + os.system('/opt/vyatta/sbin/vyatta-dhcpv6-client.pl --stop -ifname "{}"'.format(intf)) + elif vyos.validate.is_ipv4(addr): + os.system('ip -4 addr del "{}" dev "{}"'.format(addr, intf)) + elif vyos.validate.is_ipv6(addr): + os.system('ip -6 addr del "{}" dev "{}"'.format(addr, intf)) + else: + raise ConfigError('{} is not a valid interface address'.format(addr)) + + pass diff --git a/src/conf_mode/interface-bridge.py b/src/conf_mode/interface-bridge.py index 4f1cbd17c..93eb3839c 100755 --- a/src/conf_mode/interface-bridge.py +++ b/src/conf_mode/interface-bridge.py @@ -28,6 +28,7 @@ from vyos import ConfigError default_config_data = { 'address': [], + 'address_remove': [], 'aging': '300', 'br_name': '', 'description': '', @@ -53,7 +54,7 @@ default_config_data = { def subprocess_cmd(command): process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True) proc_stdout = process.communicate()[0].strip() - print(proc_stdout) + pass def diff(first, second): second = set(second) @@ -154,12 +155,18 @@ def get_config(): bridge['member'].append(iface) - # Determine bridge member interface (currently effective) - to determine which interfaces - # need to be removed from the bridge + # Determine bridge member interface (currently effective) - to determine which + # 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) + # 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) + # Priority for this bridge if conf.exists('priority'): bridge['priority'] = conf.return_value('priority') @@ -197,72 +204,75 @@ def apply(bridge): if bridge is None: return None + cmd = '' if bridge['deleted']: # bridges need to be shutdown first - os.system("ip link set dev {} down".format(bridge['br_name'])) + cmd += 'ip link set dev "{}" down'.format(bridge['br_name']) + cmd += ' && ' # delete bridge - os.system("brctl delbr {}".format(bridge['br_name'])) + cmd += 'brctl delbr "{}"'.format(bridge['br_name']) + subprocess_cmd(cmd) + else: # create bridge if it does not exist if not os.path.exists("/sys/class/net/" + bridge['br_name']): - os.system("brctl addbr {}".format(bridge['br_name'])) - - # assemble bridge configuration - # configuration is passed via subprocess to brctl - cmd = '' + # create bridge interface + cmd += 'brctl addbr "{}"'.format(bridge['br_name']) + cmd += ' && ' + # activate "UP" the interface + cmd += 'ip link set dev "{}" up'.format(bridge['br_name']) + cmd += ' && ' # set ageing time - cmd += 'brctl setageing {} {}'.format(bridge['br_name'], bridge['aging']) + cmd += 'brctl setageing "{}" "{}"'.format(bridge['br_name'], bridge['aging']) cmd += ' && ' # set bridge forward delay - cmd += 'brctl setfd {} {}'.format(bridge['br_name'], bridge['forwarding_delay']) + cmd += 'brctl setfd "{}" "{}"'.format(bridge['br_name'], bridge['forwarding_delay']) cmd += ' && ' # set hello time - cmd += 'brctl sethello {} {}'.format(bridge['br_name'], bridge['hello_time']) + cmd += 'brctl sethello "{}" "{}"'.format(bridge['br_name'], bridge['hello_time']) cmd += ' && ' # set max message age - cmd += 'brctl setmaxage {} {}'.format(bridge['br_name'], bridge['max_age']) + cmd += 'brctl setmaxage "{}" "{}"'.format(bridge['br_name'], bridge['max_age']) cmd += ' && ' # set bridge priority - cmd += 'brctl setbridgeprio {} {}'.format(bridge['br_name'], bridge['priority']) + cmd += 'brctl setbridgeprio "{}" "{}"'.format(bridge['br_name'], bridge['priority']) cmd += ' && ' # turn stp on/off - cmd += 'brctl stp {} {}'.format(bridge['br_name'], bridge['stp']) + cmd += 'brctl stp "{}" "{}"'.format(bridge['br_name'], bridge['stp']) for intf in bridge['member_remove']: # remove interface from bridge cmd += ' && ' - cmd += 'brctl delif {} {}'.format(bridge['br_name'], intf) + cmd += 'brctl delif "{}" "{}"'.format(bridge['br_name'], 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['br_name'] + '/brif/' + intf['name']): cmd += ' && ' - cmd += 'brctl addif {} {}'.format(bridge['br_name'], intf['name']) + cmd += 'brctl addif "{}" "{}"'.format(bridge['br_name'], intf['name']) # set bridge port cost if intf['cost']: cmd += ' && ' - cmd += 'brctl setpathcost {} {} {}'.format(bridge['br_name'], intf['name'], intf['cost']) + cmd += 'brctl setpathcost "{}" "{}" "{}"'.format(bridge['br_name'], intf['name'], intf['cost']) # set bridge port priority if intf['priority']: cmd += ' && ' - cmd += 'brctl setportprio {} {} {}'.format(bridge['br_name'], intf['name'], intf['priority']) + cmd += 'brctl setportprio "{}" "{}" "{}"'.format(bridge['br_name'], intf['name'], intf['priority']) subprocess_cmd(cmd) # Change interface MAC address if bridge['mac']: VyIfconfig.set_mac_address(bridge['br_name'], bridge['mac']) - else: - print("TODO: change mac mac address to the autoselected one based on member interfaces" # update interface description used e.g. within SNMP VyIfconfig.set_description(bridge['br_name'], bridge['description']) @@ -276,6 +286,13 @@ def apply(bridge): # ARP cache entry timeout in seconds VyIfconfig.set_arp_cache_timeout(bridge['br_name'], bridge['arp_cache_timeout_ms']) + # Configure interface address(es) + for addr in bridge['address_remove']: + VyIfconfig.remove_interface_address(bridge['br_name'], addr) + + for addr in bridge['address']: + VyIfconfig.add_interface_address(bridge['br_name'], addr) + return None if __name__ == '__main__': -- cgit v1.2.3 From b4ed7280179e814b9837a0fbfa05ff8065dd8b50 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 5 Aug 2019 21:19:44 +0200 Subject: [vyos.configsession] Return the output of the external process from __run_command. --- python/vyos/configsession.py | 1 + 1 file changed, 1 insertion(+) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 78f332d66..1a8077edd 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -116,6 +116,7 @@ class ConfigSession(object): output = p.stdout.read().decode() if result != 0: raise ConfigSessionError(output) + return output def get_session_env(self): return self.__session_env -- cgit v1.2.3 From 9ac783472872329fe1a1683585b679a7afcc78f0 Mon Sep 17 00:00:00 2001 From: Daniil Baturin Date: Mon, 5 Aug 2019 21:20:54 +0200 Subject: T1431: add showConfig operation to the HTTP API. --- python/vyos/configsession.py | 8 ++++++++ src/services/vyos-http-api-server | 14 ++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'python') diff --git a/python/vyos/configsession.py b/python/vyos/configsession.py index 1a8077edd..8626839f2 100644 --- a/python/vyos/configsession.py +++ b/python/vyos/configsession.py @@ -23,6 +23,7 @@ DELETE = '/opt/vyatta/sbin/my_delete' COMMENT = '/opt/vyatta/sbin/my_comment' COMMIT = '/opt/vyatta/sbin/my_commit' DISCARD = '/opt/vyatta/sbin/my_discard' +SHOW_CONFIG = ['/bin/cli-shell-api', 'showConfig'] # Default "commit via" string APP = "vyos-http-api" @@ -147,3 +148,10 @@ class ConfigSession(object): def discard(self): self.__run_command([DISCARD]) + + def show_config(self, path, format='raw'): + config_data = self.__run_command(SHOW_CONFIG + path) + + if format == 'raw': + return config_data + diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server index e11eb6d52..afab9be70 100755 --- a/src/services/vyos-http-api-server +++ b/src/services/vyos-http-api-server @@ -179,6 +179,7 @@ def configure(): @app.route('/retrieve', method='POST') def get_value(): config = app.config['vyos_config'] + session = app.config['vyos_session'] api_keys = app.config['vyos_keys'] @@ -190,8 +191,11 @@ def get_value(): command = bottle.request.forms.get("data") command = json.loads(command) - op = command['op'] - path = " ".join(command['path']) + try: + op = command['op'] + path = " ".join(command['path']) + except KeyError: + return error(400, "Missing required field. \"op\" and \"path\" fields are required") try: if op == 'returnValue': @@ -200,6 +204,12 @@ def get_value(): res = config.return_values(path) elif op == 'exists': res = config.exists(path) + elif op == 'showConfig': + config_format = 'raw' + if 'configFormat' in command: + config_format = command['configFormat'] + + res = session.show_config(command['path'], format=config_format) else: return error(400, "\"{0}\" is not a valid operation".format(op)) except VyOSError as e: -- cgit v1.2.3 From d96cfc8a5b1e9f9a3484a4c4036dddabfc588f5b Mon Sep 17 00:00:00 2001 From: hagbard Date: Thu, 8 Aug 2019 13:19:02 -0700 Subject: [config] - T1557: Create generic abstraction for configuring interfaces e.g. IP address --- python/vyos/interfaceconfig.py | 357 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100755 python/vyos/interfaceconfig.py (limited to 'python') diff --git a/python/vyos/interfaceconfig.py b/python/vyos/interfaceconfig.py new file mode 100755 index 000000000..7f9cc00f8 --- /dev/null +++ b/python/vyos/interfaceconfig.py @@ -0,0 +1,357 @@ +#!/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 json +import socket +import subprocess + +dhclient_conf_dir = r'/var/lib/dhcp/dhclient_' + +class Interface(): + def __init__(self, ifname=None, type=None): + if not ifname: + raise Exception("interface name required") + if not os.path.exists('/sys/class/net/{0}'.format(ifname)) and not type: + raise Exception("interface {0} not found".format(str(ifname))) + else: + if not os.path.exists('/sys/class/net/{0}'.format(ifname)): + try: + ret = subprocess.check_output(['ip link add dev ' + str(ifname) + ' type ' + type], stderr=subprocess.STDOUT, shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + if "Operation not supported" in str(e.output.decode()): + print(str(e.output.decode())) + sys.exit(0) + + self.ifname = str(ifname) + + 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 set_alias(self, alias=None): + if not alias: + open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write(self.ifname) + else: + open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write(str(alias)) + + def get_alias(self): + return open('/sys/class/net/{0}/ifalias'.format(self.ifname),'r').read() + + def del_alias(self): + open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write() + + + def set_link_state(self, state="up"): + if state.lower() == 'up' or state.lower() == 'down': + try: + ret = subprocess.check_output(['ip link set dev ' + self.ifname + ' ' + state], shell=True).decode() + return 0 + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return 1 + else: + raise ValueError("value can only be up or down") + + def get_link_state(self): + """ + returns either up/down or None if it can't find the state + """ + try: + ret = subprocess.check_output(['ip -j link show ' + self.ifname], shell=True).decode() + s = json.loads(ret) + return s[0]['operstate'].lower() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + def remove_interface(self): + try: + ret = subprocess.check_output(['ip link del dev ' + self.ifname], shell=True).decode() + return 0 + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + def set_macaddr(self, mac=None): + # ip will give us an error if the mac is invalid + try: + ret = subprocess.check_output(['ip link set address ' + mac + ' ' + self.ifname], shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + def get_macaddr(self): + try: + ret = subprocess.check_output(['ip -j -4 link show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + j = json.loads(ret) + return j[0]['address'] + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + def get_ipv4_addr(self): + """ + reads all IPs assigned to an interface and returns it in a list, + or None if no IP address is assigned to the interface + """ + ips = [] + try: + ret = subprocess.check_output(['ip -j -4 addr show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + j = json.loads(ret) + for i in j: + if len(i) != 0: + for addr in i['addr_info']: + ips.append(addr['local']) + return ips + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + def get_ipv6_addr(self): + """ + reads all IPs assigned to an interface and returns it in a list, + or None if no IP address is assigned to the interface + """ + ips = [] + try: + ret = subprocess.check_output(['ip -j -6 addr show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + j = json.loads(ret) + for i in j: + if len(i) != 0: + for addr in i['addr_info']: + ips.append(addr['local']) + return ips + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + def add_ipv4_addr(self, ipaddr=[]): + """ + add addresses on the interface + """ + for ip in ipaddr: + try: + ret = subprocess.check_output(['ip -4 address add ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + return True + + + def del_ipv4_addr(self, ipaddr=[]): + """ + delete addresses on the interface + """ + for ip in ipaddr: + try: + ret = subprocess.check_output(['ip -4 address del ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + return True + + + def add_ipv6_addr(self, ipaddr=[]): + """ + add addresses on the interface + """ + for ip in ipaddr: + try: + ret = subprocess.check_output(['ip -6 address add ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + return True + + + def del_ipv6_addr(self, ipaddr=[]): + """ + delete addresses on the interface + """ + for ip in ipaddr: + try: + ret = subprocess.check_output(['ip -6 address del ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + return True + + + def get_mtu(self): + try: + ret = subprocess.check_output(['ip -j link list dev ' + self.ifname], shell=True).decode() + a = json.loads(ret)[0] + return a['mtu'] + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + def set_mtu(self, mtu=None): + if not mtu: + return None + try: + ret = subprocess.check_output(['ip link set mtu ' + str(mtu) + ' dev ' + self.ifname], shell=True).decode() + return True + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None + + + #### 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 + -- cgit v1.2.3 From b570f31e7fce099687a12095850ebf9ae7a469ac Mon Sep 17 00:00:00 2001 From: hagbard Date: Fri, 9 Aug 2019 15:51:50 -0700 Subject: [config] - T1557: setting object properties for the class --- python/vyos/interfaceconfig.py | 217 ++++++++++++++++++++++------------------- 1 file changed, 118 insertions(+), 99 deletions(-) mode change 100755 => 100644 python/vyos/interfaceconfig.py (limited to 'python') diff --git a/python/vyos/interfaceconfig.py b/python/vyos/interfaceconfig.py old mode 100755 new mode 100644 index 7f9cc00f8..b8bfb707e --- a/python/vyos/interfaceconfig.py +++ b/python/vyos/interfaceconfig.py @@ -17,13 +17,14 @@ import sys import os +import re import json import socket import subprocess dhclient_conf_dir = r'/var/lib/dhcp/dhclient_' -class Interface(): +class Interface: def __init__(self, ifname=None, type=None): if not ifname: raise Exception("interface name required") @@ -40,7 +41,69 @@ class Interface(): print(str(e.output.decode())) sys.exit(0) - self.ifname = str(ifname) + self._ifname = str(ifname) + + + @property + def mtu(self): + return self._mtu + + @mtu.setter + def mtu(self, mtu=None): + if mtu < 68 or mtu > 9000: + raise ValueError("mtu size invalid value") + self._mtu = mtu + try: + ret = subprocess.check_output(['ip link set mtu ' + str(mtu) + ' dev ' + self._ifname], shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + + + @property + def macaddr(self): + return self._macaddr + + @macaddr.setter + def macaddr(self, mac=None): + if not re.search('^[a-f0-9:]{17}$', str(mac)): + raise ValueError("mac address invalid") + self._macaddr = str(mac) + try: + ret = subprocess.check_output(['ip link set address ' + mac + ' ' + self._ifname], shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + + @property + def ifalias(self): + return self._ifalias + + @ifalias.setter + def ifalias(self, ifalias=None): + if not ifalias: + self._ifalias = self._ifname + else: + self._ifalias = str(ifalias) + open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write(self._ifalias) + + @property + def linkstate(self): + return self._linkstate + + @linkstate.setter + def linkstate(self, state='up'): + if str(state).lower() == 'up' or str(state).lower() == 'down': + self._linkstate = str(state).lower() + else: + self._linkstate = 'up' + try: + ret = subprocess.check_output(['ip link set dev ' + self._ifname + ' ' + state], shell=True).decode() + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + + def _debug(self, e=None): """ @@ -52,38 +115,38 @@ class Interface(): return True return False + def get_mtu(self): + try: + ret = subprocess.check_output(['ip -j link list dev ' + self._ifname], shell=True).decode() + a = json.loads(ret)[0] + return a['mtu'] + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None - def set_alias(self, alias=None): - if not alias: - open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write(self.ifname) - else: - open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write(str(alias)) + def get_macaddr(self): + try: + ret = subprocess.check_output(['ip -j -4 link show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() + j = json.loads(ret) + return j[0]['address'] + except subprocess.CalledProcessError as e: + if self._debug(): + self._debug(e) + return None def get_alias(self): - return open('/sys/class/net/{0}/ifalias'.format(self.ifname),'r').read() + return open('/sys/class/net/{0}/ifalias'.format(self._ifname),'r').read() def del_alias(self): - open('/sys/class/net/{0}/ifalias'.format(self.ifname),'w').write() - - - def set_link_state(self, state="up"): - if state.lower() == 'up' or state.lower() == 'down': - try: - ret = subprocess.check_output(['ip link set dev ' + self.ifname + ' ' + state], shell=True).decode() - return 0 - except subprocess.CalledProcessError as e: - if self._debug(): - self._debug(e) - return 1 - else: - raise ValueError("value can only be up or down") + open('/sys/class/net/{0}/ifalias'.format(self._ifname),'w').write() def get_link_state(self): """ returns either up/down or None if it can't find the state """ try: - ret = subprocess.check_output(['ip -j link show ' + self.ifname], shell=True).decode() + ret = subprocess.check_output(['ip -j link show ' + self._ifname], shell=True).decode() s = json.loads(ret) return s[0]['operstate'].lower() except subprocess.CalledProcessError as e: @@ -91,37 +154,15 @@ class Interface(): self._debug(e) return None - def remove_interface(self): try: - ret = subprocess.check_output(['ip link del dev ' + self.ifname], shell=True).decode() + ret = subprocess.check_output(['ip link del dev ' + self._ifname], shell=True).decode() return 0 except subprocess.CalledProcessError as e: if self._debug(): self._debug(e) return None - - def set_macaddr(self, mac=None): - # ip will give us an error if the mac is invalid - try: - ret = subprocess.check_output(['ip link set address ' + mac + ' ' + self.ifname], shell=True).decode() - except subprocess.CalledProcessError as e: - if self._debug(): - self._debug(e) - return None - - def get_macaddr(self): - try: - ret = subprocess.check_output(['ip -j -4 link show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() - j = json.loads(ret) - return j[0]['address'] - except subprocess.CalledProcessError as e: - if self._debug(): - self._debug(e) - return None - - def get_ipv4_addr(self): """ reads all IPs assigned to an interface and returns it in a list, @@ -129,7 +170,7 @@ class Interface(): """ ips = [] try: - ret = subprocess.check_output(['ip -j -4 addr show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -j -4 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() j = json.loads(ret) for i in j: if len(i) != 0: @@ -149,7 +190,7 @@ class Interface(): """ ips = [] try: - ret = subprocess.check_output(['ip -j -6 addr show dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -j -6 addr show dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() j = json.loads(ret) for i in j: if len(i) != 0: @@ -168,7 +209,7 @@ class Interface(): """ for ip in ipaddr: try: - ret = subprocess.check_output(['ip -4 address add ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -4 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() except subprocess.CalledProcessError as e: if self._debug(): self._debug(e) @@ -182,7 +223,7 @@ class Interface(): """ for ip in ipaddr: try: - ret = subprocess.check_output(['ip -4 address del ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -4 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() except subprocess.CalledProcessError as e: if self._debug(): self._debug(e) @@ -196,7 +237,7 @@ class Interface(): """ for ip in ipaddr: try: - ret = subprocess.check_output(['ip -6 address add ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -6 address add ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() except subprocess.CalledProcessError as e: if self._debug(): self._debug(e) @@ -210,7 +251,7 @@ class Interface(): """ for ip in ipaddr: try: - ret = subprocess.check_output(['ip -6 address del ' + ip + ' dev ' + self.ifname], stderr=subprocess.STDOUT, shell=True).decode() + ret = subprocess.check_output(['ip -6 address del ' + ip + ' dev ' + self._ifname], stderr=subprocess.STDOUT, shell=True).decode() except subprocess.CalledProcessError as e: if self._debug(): self._debug(e) @@ -218,38 +259,16 @@ class Interface(): return True - def get_mtu(self): - try: - ret = subprocess.check_output(['ip -j link list dev ' + self.ifname], shell=True).decode() - a = json.loads(ret)[0] - return a['mtu'] - except subprocess.CalledProcessError as e: - if self._debug(): - self._debug(e) - return None - - def set_mtu(self, mtu=None): - if not mtu: - return None - try: - ret = subprocess.check_output(['ip link set mtu ' + str(mtu) + ' dev ' + self.ifname], shell=True).decode() - return True - except subprocess.CalledProcessError as e: - if self._debug(): - self._debug(e) - return None - - #### 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' + 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 + '\" {', + '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;', '}' @@ -259,14 +278,14 @@ class Interface(): for ln in a: cnf +=str(ln + "\n") open(conf_file, 'w').write(cnf) - if os.path.exists(dhclient_conf_dir + self.ifname + '.pid'): + 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() + 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(): @@ -274,9 +293,9 @@ class Interface(): 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' + 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: @@ -288,23 +307,23 @@ class Interface(): return None def get_dhcpv4(self): - pidfile = dhclient_conf_dir + self.ifname + '.pid' + pidfile = dhclient_conf_dir + self._ifname + '.pid' if not os.path.exists(pidfile): - print ("no dhcp client running on interface {0}".format(self.ifname)) + 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)) + 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' + 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 + '\" {', + 'interface \"' + self._ifname + '\" {', '\trequest routers, domain-name-servers, domain-name;', '}' ] @@ -312,7 +331,7 @@ class Interface(): 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']) + 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() @@ -320,7 +339,7 @@ class Interface(): 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() + 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(): @@ -328,14 +347,14 @@ class Interface(): 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' + 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']) + subprocess.call(['sysctl', '-q', '-w', 'net.ipv6.conf.' + self._ifname + '.accept_ra=1']) return True except subprocess.CalledProcessError as e: if self._debug(): @@ -343,13 +362,13 @@ class Interface(): return None def get_dhcpv6(self): - pidfile = dhclient_conf_dir + self.ifname + '.v6pid' + pidfile = dhclient_conf_dir + self._ifname + '.v6pid' if not os.path.exists(pidfile): - print ("no dhcpv6 client running on interface {0}".format(self.ifname)) + 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)) + print("dhclientv6 running on {0} with pid {1}".format(self._ifname, pid)) return True -- cgit v1.2.3