From ca1caa6c6effb82b22dad0db4c4f47247c3722ad Mon Sep 17 00:00:00 2001 From: Dmytro Aleksandrov Date: Thu, 22 Aug 2019 17:26:30 +0300 Subject: [op-mode] T1607 rewrite 'reset conntrack', 'reset & show ip[v6]' to python/xml syntax --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index ee01e5ad3..186e63678 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,9 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/show/node.def rm -f $(OP_TMPL_DIR)/show/interfaces/node.def rm -f $(OP_TMPL_DIR)/show/ip/node.def - rm -f $(OP_TMPL_DIR)/reset/node.def + rm -f $(OP_TMPL_DIR)/show/ip/route/node.def + rm -f $(OP_TMPL_DIR)/show/ipv6/node.def + rm -f $(OP_TMPL_DIR)/show/ipv6/route/node.def rm -f $(OP_TMPL_DIR)/restart/node.def rm -f $(OP_TMPL_DIR)/monitor/node.def rm -f $(OP_TMPL_DIR)/generate/node.def -- cgit v1.2.3 From 6205c4d6701bda5f8a859291a5e152e009252301 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Tue, 3 Sep 2019 13:52:33 +0200 Subject: bonding: T1614: Initial version in new style XML/Python interface The node 'interfaces ethernet eth0 bond-group' has been changed and de-nested. Bond members are now configured in the bond interface itself. set interfaces bonding bond0 member interface eth0 --- Makefile | 1 + interface-definitions/interfaces-bonding.xml | 673 +++++++++++++++++++++++++++ src/conf_mode/interface-bonding.py | 409 ++++++++++++++++ src/migration-scripts/interfaces/1-to-2 | 44 ++ 4 files changed, 1127 insertions(+) create mode 100644 interface-definitions/interfaces-bonding.xml create mode 100755 src/conf_mode/interface-bonding.py create mode 100755 src/migration-scripts/interfaces/1-to-2 (limited to 'Makefile') diff --git a/Makefile b/Makefile index 186e63678..03b4712fc 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ interface_definitions: rm -f $(TMPL_DIR)/firewall/node.def rm -f $(TMPL_DIR)/interfaces/node.def rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def + rm -f $(TMPL_DIR)/interfaces/bonding/node.tag/ip/node.def rm -f $(TMPL_DIR)/protocols/node.def rm -f $(TMPL_DIR)/protocols/static/node.def rm -f $(TMPL_DIR)/system/node.def diff --git a/interface-definitions/interfaces-bonding.xml b/interface-definitions/interfaces-bonding.xml new file mode 100644 index 000000000..d4c3bb704 --- /dev/null +++ b/interface-definitions/interfaces-bonding.xml @@ -0,0 +1,673 @@ + + + + + + + Bonding interface name + 315 + + bond[0-9]+$ + + Bonding interface must be named bondN + + bondN + Bonding interface name + + + + + + IP address + + dhcp dhcpv6 + + + ipv4net + IPv4 address and prefix length + + + ipv6net + IPv6 address and prefix length + + + dhcp + Dynamic Host Configuration Protocol + + + dhcpv6 + Dynamic Host Configuration Protocol for IPv6 + + + + (dhcp|dhcpv6) + + + + + + + ARP link monitoring parameters + + + + + ARP link monitoring interval + + 0-4294967295 + Specifies the ARP link monitoring frequency in milliseconds + + + + + + + + + IP address used for ARP monitoring + + ipv4 + Network Time Protocol (NTP) IPv4 address + + + + + + + + + + + + Interface description + + ^.{1,256}$ + + Interface description too long (limit 256 characters) + + + + + DHCP options + + + + + DHCP client identifier + + + + + DHCP client host name (overrides the system host name) + + + + + + + DHCPv6 options + 319 + + + + + Acquire only config parameters, no address + + + + + + IPv6 "temporary" address + + + + + + + + Ignore link state changes + + + + + + Disable this bridge interface + + + + + + Bonding transmit hash policy + + layer2 layer2+3 layer3+4 + + + layer2 + use MAC addresses to generate the hash (802.3ad, default) + + + layer2+3 + combine MAC address and IP address to make hash + + + layer3+4 + combine IP address and port to make hash + + + (layer2\\+3|layer3\\+4|layer2) + + hash-policy must be layer2 layer2+3 or layer3+4 + + + + + + + ARP cache entry timeout in seconds + + 1-86400 + ARP cache entry timout in seconds (default 30) + + + + + Bridge max aging value must be between 6 and 86400 seconds + + + + + Enable proxy-arp on this interface + + + + + + Enable private VLAN proxy ARP on this interface + + + + + + + + Media Access Control (MAC) address + + h:h:h:h:h:h + Hardware (MAC) address + + + + + + + + + Bonding mode + + 802.3ad active-backup broadcast round-robin transmit-load-balance adaptive-load-balance xor-hash + + + 802.3ad + IEEE 802.3ad Dynamic link aggregation (Default) + + + active-backup + Fault tolerant: only one slave in the bond is active + + + broadcast + Fault tolerant: transmits everything on all slave interfaces + + + round-robin + Load balance: transmit packets in sequential order + + + transmit-load-balance + Load balance: adapts based on transmit load and speed + + + adaptive-load-balance + Load balance: adapts based on transmit and receive plus ARP + + + xor-hash + Distribute based on MAC address + + + (802.3ad|active-backup|broadcast|round-robin|transmit-load-balance|adaptive-load-balance|xor-hash) + + mode must be 802.3ad, active-backup, broadcast, round-robin, transmit-load-balance, adaptive-load-balance, or xor + + + + + Bridge member interfaces + + + + + Member interface name + + + + + + + + + + + Maximum Transmission Unit (MTU) + + 68-9000 + Maximum Transmission Unit + + + + + MTU must be between 68 and 9000 + + + + + Primary device interface + + + + + + + + QinQ TAG-S Virtual Local Area Network (VLAN) ID + + + + VLAN ID must be between 0 and 4094 + + + + + IP address + + dhcp dhcpv6 + + + ipv4net + IPv4 address and prefix length + + + ipv6net + IPv6 address and prefix length + + + dhcp + Dynamic Host Configuration Protocol + + + dhcpv6 + Dynamic Host Configuration Protocol for IPv6 + + + + (dhcp|dhcpv6) + + + + + + + Interface description + + ^.{1,256}$ + + Interface description too long (limit 256 characters) + + + + + DHCP options + + + + + DHCP client identifier + + + + + DHCP client host name (overrides the system host name) + + + + + + + DHCPv6 options + 319 + + + + + Acquire only config parameters, no address + + + + + + IPv6 "temporary" address + + + + + + + + Ignore link state changes + + + + + + Disable this bridge interface + + + + + + Set Ethertype + + 0x88A8 0x8100 + + + 0x88A8 + 802.1ad + + + 0x8100 + 802.1q + + + (0x88A8|0x8100) + + Ethertype must be 0x88A8 or 0x8100 + + + + + Media Access Control (MAC) address + + h:h:h:h:h:h + Hardware (MAC) address + + + + + + + + + Maximum Transmission Unit (MTU) + + 68-9000 + Maximum Transmission Unit + + + + + MTU must be between 68 and 9000 + + + + + QinQ TAG-C Virtual Local Area Network (VLAN) ID + + + + VLAN ID must be between 0 and 4094 + + + + + IP address + + dhcp dhcpv6 + + + ipv4net + IPv4 address and prefix length + + + ipv6net + IPv6 address and prefix length + + + dhcp + Dynamic Host Configuration Protocol + + + dhcpv6 + Dynamic Host Configuration Protocol for IPv6 + + + + (dhcp|dhcpv6) + + + + + + + Interface description + + ^.{1,256}$ + + Interface description too long (limit 256 characters) + + + + + DHCP options + + + + + DHCP client identifier + + + + + DHCP client host name (overrides the system host name) + + + + + + + DHCPv6 options + 319 + + + + + Acquire only config parameters, no address + + + + + + IPv6 "temporary" address + + + + + + + + Ignore link state changes + + + + + + Disable this bridge interface + + + + + + Media Access Control (MAC) address + + h:h:h:h:h:h + Hardware (MAC) address + + + + + + + + + Maximum Transmission Unit (MTU) + + 68-9000 + Maximum Transmission Unit + + + + + MTU must be between 68 and 9000 + + + + + + + + + Virtual Local Area Network (VLAN) ID + + + + VLAN ID must be between 0 and 4094 + + + + + IP address + + dhcp dhcpv6 + + + ipv4net + IPv4 address and prefix length + + + ipv6net + IPv6 address and prefix length + + + dhcp + Dynamic Host Configuration Protocol + + + dhcpv6 + Dynamic Host Configuration Protocol for IPv6 + + + + (dhcp|dhcpv6) + + + + + + + Interface description + + ^.{1,256}$ + + Interface description too long (limit 256 characters) + + + + + DHCP options + + + + + DHCP client identifier + + + + + DHCP client host name (overrides the system host name) + + + + + + + DHCPv6 options + 319 + + + + + Acquire only config parameters, no address + + + + + + IPv6 "temporary" address + + + + + + + + Ignore link state changes + + + + + + Disable this bridge interface + + + + + + Media Access Control (MAC) address + + h:h:h:h:h:h + Hardware (MAC) address + + + + + + + + + Maximum Transmission Unit (MTU) + + 68-9000 + Maximum Transmission Unit + + + + + MTU must be between 68 and 9000 + + + + + + + + + diff --git a/src/conf_mode/interface-bonding.py b/src/conf_mode/interface-bonding.py new file mode 100755 index 000000000..6cd8fdc56 --- /dev/null +++ b/src/conf_mode/interface-bonding.py @@ -0,0 +1,409 @@ +#!/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 . +# +# + +import os + +from copy import deepcopy +from sys import exit +from netifaces import interfaces +from vyos.ifconfig import BondIf +from vyos.config import Config +from vyos import ConfigError + +default_config_data = { + 'address': [], + 'address_remove': [], + 'arp_mon_intvl': 0, + 'arp_mon_tgt': [], + 'description': '', + 'deleted': False, + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + 'disable': False, + 'disable_link_detect': 1, + 'hash_policy': 'layer2', + 'ip_arp_cache_tmo': 30, + 'ip_proxy_arp': 0, + 'ip_proxy_arp_pvlan': 0, + 'intf': '', + 'mac': '', + 'mode': '802.3ad', + 'member': [], + 'member_remove': [], + 'mtu': 1500, + 'primary': '', + 'vif_s': [], + 'vif': [] +} + +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': + return 'balance-rr' + elif mode == 'active-backup': + return 'active-backup' + elif mode == 'xor-hash': + return 'balance-xor' + elif mode == 'broadcast': + return 'broadcast' + elif mode == '802.3ad': + return '802.3ad' + elif mode == 'transmit-load-balance': + return 'balance-tlb' + elif mode == 'adaptive-load-balance': + return 'balance-alb' + else: + raise ConfigError('invalid bond mode "{}"'.format(mode)) + +def vlan_to_dict(conf): + """ + Common used function which will extract VLAN related information from config + and represent the result as Python dictionary. + + Function call's itself recursively if a vif-s/vif-c pair is detected. + """ + vlan = { + 'id': conf.get_level().split(' ')[-1], # get the '100' in 'interfaces bonding bond0 vif-s 100' + 'address': [], + 'address_remove': [], + 'description': '', + 'dhcp_client_id': '', + 'dhcp_hostname': '', + 'dhcpv6_prm_only': False, + 'dhcpv6_temporary': False, + 'disable': False, + 'disable_link_detect': 1, + 'mac': '', + 'mtu': 1500 + } + # retrieve configured interface addresses + if conf.exists('address'): + vlan['address'] = conf.return_values('address') + + # Determine interface addresses (currently effective) - to determine which + # address is no longer valid and needs to be removed from the bond + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + vlan['address_remove'] = diff(eff_addr, act_addr) + + # retrieve interface description + if conf.exists('description'): + vlan['description'] = conf.return_value('description') + + # get DHCP client identifier + if conf.exists('dhcp-options client-id'): + vlan['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + + # DHCP client host name (overrides the system host name) + if conf.exists('dhcp-options host-name'): + vlan['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + + # DHCPv6 only acquire config parameters, no address + if conf.exists('dhcpv6-options parameters-only'): + vlan['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + + # DHCPv6 temporary IPv6 address + if conf.exists('dhcpv6-options temporary'): + vlan['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + + # ignore link state changes + if conf.exists('disable-link-detect'): + vlan['disable_link_detect'] = 2 + + # disable bond interface + if conf.exists('disable'): + vlan['disable'] = True + + # ethertype (only on vif-s nodes) + if conf.exists('ethertype'): + vlan['ethertype'] = conf.return_value('ethertype') + + # Media Access Control (MAC) address + if conf.exists('mac'): + vlan['mac'] = conf.return_value('mac') + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + vlan['mtu'] = int(conf.return_value('mtu')) + + # check if there is a Q-in-Q vlan customer interface + # and call this function recursively + if conf.exists('vif-c'): + cfg_level = conf.get_level() + # add new key (vif-c) to dictionary + vlan['vif-c'] = [] + for vif in conf.list_nodes('vif-c'): + # set config level to vif interface + conf.set_level(cfg_level + ' vif-c ' + vif) + vlan['vif-c'].append(vlan_to_dict(conf)) + + return vlan + +def get_config(): + # initialize kernel module if not loaded + if not os.path.isfile('/sys/class/net/bonding_masters'): + import syslog + syslog.syslog(syslog.LOG_NOTICE, "loading bonding kernel module") + if os.system('modprobe bonding max_bonds=0 miimon=250') != 0: + syslog.syslog(syslog.LOG_NOTICE, "failed loading bonding kernel module") + raise ConfigError("failed loading bonding kernel module") + + bond = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + bond['intf'] = os.environ['VYOS_TAGNODE_VALUE'] + except KeyError as E: + print("Interface not specified") + + # check if bond has been removed + cfg_base = 'interfaces bonding ' + bond['intf'] + if not conf.exists(cfg_base): + bond['deleted'] = True + return bond + + # set new configuration level + conf.set_level(cfg_base) + + # retrieve configured interface addresses + if conf.exists('address'): + bond['address'] = conf.return_values('address') + + # ARP link monitoring frequency in milliseconds + if conf.exists('arp-monitor interval'): + bond['arp_mon_intvl'] = conf.return_value('arp-monitor interval') + + # IP address to use for ARP monitoring + if conf.exists('arp-monitor target'): + bond['arp_mon_tgt'] = conf.return_values('arp-monitor target') + + # retrieve interface description + if conf.exists('description'): + bond['description'] = conf.return_value('description') + else: + bond['description'] = bond['intf'] + + # get DHCP client identifier + if conf.exists('dhcp-options client-id'): + bond['dhcp_client_id'] = conf.return_value('dhcp-options client-id') + + # DHCP client host name (overrides the system host name) + if conf.exists('dhcp-options host-name'): + bond['dhcp_hostname'] = conf.return_value('dhcp-options host-name') + + # DHCPv6 only acquire config parameters, no address + if conf.exists('dhcpv6-options parameters-only'): + bond['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only') + + # DHCPv6 temporary IPv6 address + if conf.exists('dhcpv6-options temporary'): + bond['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary') + + # ignore link state changes + if conf.exists('disable-link-detect'): + bond['disable_link_detect'] = 2 + + # disable bond interface + if conf.exists('disable'): + bond['disable'] = True + + # Bonding transmit hash policy + if conf.exists('hash-policy'): + bond['hash_policy'] = conf.return_value('hash-policy') + + # ARP cache entry timeout in seconds + if conf.exists('ip arp-cache-timeout'): + bond['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) + + # Enable proxy-arp on this interface + if conf.exists('ip enable-proxy-arp'): + bond['ip_proxy_arp'] = 1 + + # Enable private VLAN proxy ARP on this interface + if conf.exists('ip proxy-arp-pvlan'): + bond['ip_proxy_arp_pvlan'] = 1 + + # Media Access Control (MAC) address + if conf.exists('mac'): + bond['mac'] = conf.return_value('mac') + + # Bonding mode + if conf.exists('mode'): + bond['mode'] = get_bond_mode(conf.return_value('mode')) + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + bond['mtu'] = int(conf.return_value('mtu')) + + # determine bond member interfaces (currently configured) + if conf.exists('member interface'): + bond['member'] = conf.return_values('member interface') + + # Determine bond member interface (currently effective) - to determine which + # interfaces is no longer assigend to the bond and thus can be removed + eff_intf = conf.return_effective_values('member interface') + act_intf = conf.return_values('member interface') + bond['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 bond + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + bond['address_remove'] = diff(eff_addr, act_addr) + + # Primary device interface + if conf.exists('primary'): + bond['primary'] = conf.return_value('primary') + + # re-set configuration level and retrieve vif-s interfaces + conf.set_level(cfg_base) + if conf.exists('vif-s'): + for vif_s in conf.list_nodes('vif-s'): + # set config level to vif-s interface + conf.set_level(cfg_base + ' vif-s ' + vif_s) + bond['vif_s'].append(vlan_to_dict(conf)) + + # re-set configuration level and retrieve vif-s interfaces + conf.set_level(cfg_base) + if conf.exists('vif'): + for vif in conf.list_nodes('vif'): + # set config level to vif interface + conf.set_level(cfg_base + ' vif ' + vif) + bond['vif'].append(vlan_to_dict(conf)) + + return bond + +def verify(bond): + if len (bond['arp_mon_tgt']) > 16: + raise ConfigError('The maximum number of targets that can be specified is 16') + + if bond['primary']: + if bond['mode'] not in ['active-backup', 'balance-tlb', 'balance-alb']: + raise ConfigError('Mode dependency failed, primary not supported in this mode.'.format()) + + return None + +def generate(bond): + return None + +def apply(bond): + b = BondIf(bond['intf']) + + if bond['deleted']: + # delete bonding interface + b.remove() + else: + # Some parameters can not be changed when the bond is up. + # Always disable the bond prior changing anything + b.state = 'down' + + # 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 + if bond['arp_mon_intvl'] == 0: + # reset miimon to default + b.bond_miimon = 250 + + # ARP monitor targets need to be synchronized between sysfs and CLI. + # Unfortunately an address can't be send twice to sysfs as this will + # result in the following exception: OSError: [Errno 22] Invalid argument. + # + # We remove ALL adresses prior adding new ones, this will remove addresses + # added manually by the user too - but as we are limited to 16 adresses + # 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: + b.arp_ip_target = '-' + addr + + # Add configured ARP target addresses + for addr in bond['arp_mon_tgt']: + b.arp_ip_target = '+' + addr + + # update interface description used e.g. within SNMP + b.ifalias = bond['description'] + + # + # missing DHCP/DHCPv6 options go here + # + + # ignore link state changes + b.link_detect = bond['disable_link_detect'] + # Bonding transmit hash policy + b.xmit_hash_policy = bond['hash_policy'] + # configure ARP cache timeout in milliseconds + b.arp_cache_tmp = bond['ip_arp_cache_tmo'] + # Enable proxy-arp on this interface + b.proxy_arp = bond['ip_proxy_arp'] + # Enable private VLAN proxy ARP on this interface + b.proxy_arp_pvlan = bond['ip_proxy_arp_pvlan'] + + # Change interface MAC address + if bond['mac']: + b.mac = bond['mac'] + + # The bonding mode can not be changed when there are interfaces enslaved + # to this bond, thus we will free all interfaces from the bond first! + for intf in b.get_slaves(): + b.del_port(intf) + + # Bonding policy + b.mode = bond['mode'] + # Maximum Transmission Unit (MTU) + b.mtu = bond['mtu'] + # Primary device interface + b.primary = bond['primary'] + + # + # VLAN config goes here + # + + # Add (enslave) interfaces to 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' + + return None + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) diff --git a/src/migration-scripts/interfaces/1-to-2 b/src/migration-scripts/interfaces/1-to-2 new file mode 100755 index 000000000..10d542d1d --- /dev/null +++ b/src/migration-scripts/interfaces/1-to-2 @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# Change syntax of bond interface +# - move interface based bond-group to actual bond (de-nest) +# https://phabricator.vyos.net/T1614 + +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', 'bonding'] + +if not config.exists(base): + # Nothing to do + sys.exit(0) +else: + # + # move interface based bond-group to actual bond (de-nest) + # + for intf in config.list_nodes(['interfaces', 'ethernet']): + # check if bond-group exists + if config.exists(['interfaces', 'ethernet', intf, 'bond-group']): + # get configured bond interface + bond = config.return_value(['interfaces', 'ethernet', intf, 'bond-group']) + # delete old interface asigned (nested) bond group + config.delete(['interfaces', 'ethernet', intf, 'bond-group']) + # create new bond member interface + config.set(base + [bond, 'member', 'interface'], value=intf, replace=False) + + 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 98aafc8f704ef54b6ece514c038b6aea414df734 Mon Sep 17 00:00:00 2001 From: Christian Poessinger Date: Thu, 5 Sep 2019 19:35:43 +0200 Subject: vxlan: T1636: initial rewrite with XML and Python Tested using: Site 1 (VyOS 1.2.2) ------------------- set interfaces vxlan vxlan100 address '10.10.10.2/24' set interfaces vxlan vxlan100 remote '172.18.201.10' set interfaces vxlan vxlan100 vni '100' Site 2 (rewrite) ---------------- set interfaces vxlan vxlan100 address '10.10.10.1/24' set interfaces vxlan vxlan100 description 'VyOS VXLAN' set interfaces vxlan vxlan100 remote '172.18.202.10' set interfaces vxlan vxlan100 vni '100' --- Makefile | 1 + interface-definitions/interfaces-vxlan.xml | 49 +++++++ python/vyos/ifconfig.py | 37 +++-- src/conf_mode/interface-vxlan.py | 208 +++++++++++++++++++++++++++++ 4 files changed, 282 insertions(+), 13 deletions(-) create mode 100755 src/conf_mode/interface-vxlan.py (limited to 'Makefile') diff --git a/Makefile b/Makefile index 03b4712fc..d7b3f047d 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ interface_definitions: rm -f $(TMPL_DIR)/interfaces/node.def rm -f $(TMPL_DIR)/interfaces/bridge/node.tag/ip/node.def rm -f $(TMPL_DIR)/interfaces/bonding/node.tag/ip/node.def + rm -f $(TMPL_DIR)/interfaces/vxlan/node.tag/ip/node.def rm -f $(TMPL_DIR)/protocols/node.def rm -f $(TMPL_DIR)/protocols/static/node.def rm -f $(TMPL_DIR)/system/node.def diff --git a/interface-definitions/interfaces-vxlan.xml b/interface-definitions/interfaces-vxlan.xml index 35a43f92c..b06c2860c 100644 --- a/interface-definitions/interfaces-vxlan.xml +++ b/interface-definitions/interfaces-vxlan.xml @@ -95,6 +95,55 @@ + + + Maximum Transmission Unit (MTU) + + 1450-9000 + Maximum Transmission Unit + + + + + MTU must be between 1450 and 9000 + + + + + Remote address of VXLAN tunnel + + ipv4 + Remote address of VXLAN tunnel + + + + + + + + + Destination port of VXLAN tunnel (default: 8472) + + 1-65535 + Numeric IP port + + + + + + + + + Virtual Network Identifier + + 0-16777214 + VXLAN virtual network identifier + + + + + + diff --git a/python/vyos/ifconfig.py b/python/vyos/ifconfig.py index bc22478a6..0479e3672 100644 --- a/python/vyos/ifconfig.py +++ b/python/vyos/ifconfig.py @@ -1407,32 +1407,43 @@ class VXLANIf(Interface, ): """ def __init__(self, ifname, config=''): if config: + self._ifname = ifname + if not os.path.exists('/sys/class/net/{}'.format(self._ifname)): # we assume that by default a multicast interface is created group = 'group {}'.format(config['group']) + # if remote host is specified we ignore the multicast address if config['remote']: group = 'remote {}'.format(config['remote']) + # an underlay device is not always specified dev = '' if config['dev']: - dev = 'dev'.format(config['dev']) + dev = 'dev {}'.format(config['dev']) - cmd = 'ip link add dev {intf} type vxlan id {vni} {group} {dev} {port}' - .format(intf=self._ifname, config['vni'], group=group, dev=dev, port=config['port']) + cmd = 'ip link add {intf} type vxlan id {vni} {grp_rem} {dev} dstport {port}' \ + .format(intf=self._ifname, vni=config['vni'], grp_rem=group, dev=dev, port=config['port']) self._cmd(cmd) super().__init__(ifname, type='vxlan') + @staticmethod + def get_config(): + """ + VXLAN interfaces require a configuration when they are added using + iproute2. This static method will provide the configuration dictionary + used by this class. - @staticmethod - def get_config(): - config = { - 'vni': 0, - 'dev': '', - 'group': '', - 'port': 8472 # The Linux implementation of VXLAN pre-dates + Example: + >> dict = VXLANIf().get_config() + """ + config = { + 'vni': 0, + 'dev': '', + 'group': '', + 'port': 8472, # The Linux implementation of VXLAN pre-dates # the IANA's selection of a standard destination port - 'remote': '', - 'ttl': 16 - } + 'remote': '' + } + return config diff --git a/src/conf_mode/interface-vxlan.py b/src/conf_mode/interface-vxlan.py new file mode 100755 index 000000000..59022238e --- /dev/null +++ b/src/conf_mode/interface-vxlan.py @@ -0,0 +1,208 @@ +#!/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 . +# + +from os import environ +from sys import exit +from copy import deepcopy + +from vyos.configdict import list_diff +from vyos.config import Config +from vyos.ifconfig import VXLANIf, Interface +from vyos.interfaces import get_type_of_interface +from vyos import ConfigError +from netifaces import interfaces + +default_config_data = { + 'address': [], + 'address_remove': [], + 'deleted': False, + 'description': '', + 'disable': False, + 'group': '', + 'intf': '', + 'ip_arp_cache_tmo': 30, + 'ip_proxy_arp': 0, + 'link': '', + 'mtu': 1450, + 'remote': '', + 'remote_port': 8472 # The Linux implementation of VXLAN pre-dates + # the IANA's selection of a standard destination port +} + + +def get_config(): + vxlan = deepcopy(default_config_data) + conf = Config() + + # determine tagNode instance + try: + vxlan['intf'] = environ['VYOS_TAGNODE_VALUE'] + except KeyError as E: + print("Interface not specified") + + # Check if interface has been removed + if not conf.exists('interfaces vxlan ' + vxlan['intf']): + vxlan['deleted'] = True + return vxlan + + # set new configuration level + conf.set_level('interfaces vxlan ' + vxlan['intf']) + + # retrieve configured interface addresses + if conf.exists('address'): + vxlan['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 interface + eff_addr = conf.return_effective_values('address') + act_addr = conf.return_values('address') + vxlan['address_remove'] = list_diff(eff_addr, act_addr) + + # retrieve interface description + if conf.exists('description'): + vxlan['description'] = conf.return_value('description') + + # Disable this interface + if conf.exists('disable'): + vxlan['disable'] = True + + # VXLAN multicast grou + if conf.exists('group'): + vxlan['group'] = conf.return_value('group') + + # ARP cache entry timeout in seconds + if conf.exists('ip arp-cache-timeout'): + vxlan['ip_arp_cache_tmo'] = int(conf.return_value('ip arp-cache-timeout')) + + # Enable proxy-arp on this interface + if conf.exists('ip enable-proxy-arp'): + vxlan['ip_proxy_arp'] = 1 + + # VXLAN underlay interface + if conf.exists('link'): + vxlan['link'] = conf.return_value('link') + + # Maximum Transmission Unit (MTU) + if conf.exists('mtu'): + vxlan['mtu'] = int(conf.return_value('mtu')) + + # Remote address of VXLAN tunnel + if conf.exists('remote'): + vxlan['remote'] = conf.return_value('remote') + + # Remote port of VXLAN tunnel + if conf.exists('port'): + vxlan['remote_port'] = int(conf.return_value('port')) + + # Virtual Network Identifier + if conf.exists('vni'): + vxlan['vni'] = conf.return_value('vni') + + return vxlan + + +def verify(vxlan): + if vxlan['deleted']: + # bail out early + return None + + if vxlan['mtu'] < 1500: + print('WARNING: RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') + + if vxlan['group'] and not vxlan['link']: + raise ConfigError('Multicast VXLAN requires an underlaying interface ') + + if not (vxlan['group'] or vxlan['remote']): + raise ConfigError('Group or remote must be configured') + + if not vxlan['vni']: + raise ConfigError('Must configure VNI for VXLAN') + + if vxlan['link']: + # VXLAN adds a 50 byte overhead - we need to check the underlaying MTU + # if our configured MTU is at least 50 bytes less + underlay_mtu = int(Interface(vxlan['link']).mtu) + if underlay_mtu < (vxlan['mtu'] + 50): + raise ConfigError('VXLAN has a 50 byte overhead, underlaying device ' \ + 'MTU is to small ({})'.format(underlay_mtu)) + + return None + + +def generate(vxlan): + return None + + +def apply(vxlan): + # Check if the VXLAN interface already exists + if vxlan['intf'] in interfaces(): + v = VXLANIf(vxlan['intf']) + # VXLAN is super picky and the tunnel always needs to be recreated, + # thus we can simply always delete it first. + v.remove() + + if not vxlan['deleted']: + # VXLAN interface needs to be created on-block + # instead of passing a ton of arguments, I just use a dict + # that is managed by vyos.ifconfig + conf = deepcopy(VXLANIf.get_config()) + + # Assign VXLAN instance configuration parameters to config dict + conf['vni'] = vxlan['vni'] + conf['group'] = vxlan['group'] + conf['dev'] = vxlan['link'] + conf['remote'] = vxlan['remote'] + conf['port'] = vxlan['remote_port'] + + # Finally create the new interface + v = VXLANIf(vxlan['intf'], config=conf) + # update interface description used e.g. by SNMP + v.ifalias = vxlan['description'] + # Maximum Transfer Unit (MTU) + v.mtu = vxlan['mtu'] + + # configure ARP cache timeout in milliseconds + v.arp_cache_tmp = vxlan['ip_arp_cache_tmo'] + # Enable proxy-arp on this interface + v.proxy_arp = vxlan['ip_proxy_arp'] + + # Configure interface address(es) + # - not longer required addresses get removed first + # - newly addresses will be added second + for addr in vxlan['address_remove']: + v.del_addr(addr) + for addr in vxlan['address']: + v.add_addr(addr) + + # 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 vxlan['disable']: + v.state='up' + + return None + + +if __name__ == '__main__': + try: + c = get_config() + verify(c) + generate(c) + apply(c) + except ConfigError as e: + print(e) + exit(1) -- cgit v1.2.3 From 1017c8103f12ebd6db4f250d8a154571fff32db1 Mon Sep 17 00:00:00 2001 From: hagbard Date: Mon, 9 Sep 2019 11:55:54 -0700 Subject: [wireguard]: T1572 - Wireguard keyPair per interface - param key location added in op-mode script - param delkey and listkey implemented in op-mode script - param delkey implemented in op-mode script - generate and store named keys - interface implementation tu use cli option 'private-key' --- Makefile | 1 + interface-definitions/interfaces-wireguard.xml | 8 ++ op-mode-definitions/wireguard.xml | 53 ++++++- src/conf_mode/interface-wireguard.py | 11 +- src/op_mode/wireguard.py | 188 ++++++++++++++++--------- 5 files changed, 188 insertions(+), 73 deletions(-) (limited to 'Makefile') diff --git a/Makefile b/Makefile index d7b3f047d..ad05acff5 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,7 @@ op_mode_definitions: rm -f $(OP_TMPL_DIR)/generate/node.def rm -f $(OP_TMPL_DIR)/show/vpn/node.def rm -f $(OP_TMPL_DIR)/show/system/node.def + rm -f $(OP_TMPL_DIR)/delete/node.def .PHONY: all all: clean interface_definitions op_mode_definitions diff --git a/interface-definitions/interfaces-wireguard.xml b/interface-definitions/interfaces-wireguard.xml index 6e2622018..f2a7cc316 100644 --- a/interface-definitions/interfaces-wireguard.xml +++ b/interface-definitions/interfaces-wireguard.xml @@ -77,6 +77,14 @@ + + + Private key to use on that interface + + + + + peer alias diff --git a/op-mode-definitions/wireguard.xml b/op-mode-definitions/wireguard.xml index fa5e4a206..785af202c 100644 --- a/op-mode-definitions/wireguard.xml +++ b/op-mode-definitions/wireguard.xml @@ -20,6 +20,12 @@ ${vyos_op_scripts_dir}/wireguard.py --genpsk + + + Generates named wireguard keypairs + + sudo ${vyos_op_scripts_dir}/wireguard.py --genkey --location "$4" + @@ -33,7 +39,7 @@ - show wireguard public key + Show wireguard public key ${vyos_op_scripts_dir}/wireguard.py --showpub @@ -43,6 +49,31 @@ ${vyos_op_scripts_dir}/wireguard.py --showpriv + + + Shows named wireguard keys + + + + + Show wireguard private named key + + + + + ${vyos_op_scripts_dir}/wireguard.py --showpub --location "$5" + + + + Show wireguard public named key + + + + + ${vyos_op_scripts_dir}/wireguard.py --showpriv --location "$5" + + + @@ -81,5 +112,25 @@ + + + + + Delete wireguard properties + + + + + Delete wireguard named keypair + + + + + sudo ${vyos_op_scripts_dir}/wireguard.py --delkdir --location "$4" + + + + + diff --git a/src/conf_mode/interface-wireguard.py b/src/conf_mode/interface-wireguard.py index 4c0e90ca6..0f9e66aa6 100755 --- a/src/conf_mode/interface-wireguard.py +++ b/src/conf_mode/interface-wireguard.py @@ -29,6 +29,9 @@ from vyos.ifconfig import WireGuardIf ifname = str(os.environ['VYOS_TAGNODE_VALUE']) intfc = WireGuardIf(ifname) +kdir = r'/config/auth/wireguard' + + def check_kmod(): if not os.path.exists('/sys/module/wireguard'): sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") @@ -52,7 +55,7 @@ def get_config(): 'fwmark': 0x00, 'mtu': 1420, 'peer': {}, - 'pk' : '/config/auth/wireguard/private.key' + 'pk': '{}/private.key'.format(kdir) } } @@ -77,6 +80,9 @@ def get_config(): ifname + ' description') if c.exists(ifname + ' mtu'): config_data[ifname]['mtu'] = c.return_value(ifname + ' mtu') + if c.exists(ifname + ' private-key'): + config_data[ifname]['pk'] = "{0}/{1}/private.key".format( + kdir, c.return_value(ifname + ' private-key')) if c.exists(ifname + ' peer'): for p in c.list_nodes(ifname + ' peer'): if not c.exists(ifname + ' peer ' + p + ' disable'): @@ -107,13 +113,14 @@ def get_config(): return config_data + def verify(c): if not c: return None if not os.path.exists(c[ifname]['pk']): raise ConfigError( - "No keys found, generate them by executing: \'run generate wireguard keypair\'") + "No keys found, generate them by executing: \'run generate wireguard [keypair|named-keypairs]\'") if c[ifname]['status'] != 'delete': if not c[ifname]['addr']: diff --git a/src/op_mode/wireguard.py b/src/op_mode/wireguard.py index 66622c04c..e48da2e40 100755 --- a/src/op_mode/wireguard.py +++ b/src/op_mode/wireguard.py @@ -19,91 +19,139 @@ import argparse import os import sys +import shutil import subprocess import syslog as sl + from vyos import ConfigError dir = r'/config/auth/wireguard' -pk = dir + '/private.key' -pub = dir + '/public.key' psk = dir + '/preshared.key' + def check_kmod(): - """ check if kmod is loaded, if not load it """ - if not os.path.exists('/sys/module/wireguard'): - sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") - if os.system('sudo modprobe wireguard') != 0: - sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") - raise ConfigError("modprobe wireguard failed") - -def generate_keypair(): - """ generates a keypair which is stored in /config/auth/wireguard """ - ret = subprocess.call(['wg genkey | tee ' + pk + '|wg pubkey > ' + pub], shell=True) - if ret != 0: - raise ConfigError("wireguard key-pair generation failed") - else: - sl.syslog(sl.LOG_NOTICE, "new keypair wireguard key generated in " + dir) - -def genkey(): - """ helper function to check, regenerate the keypair """ - old_umask = os.umask(0o077) - if os.path.exists(pk) and os.path.exists(pub): - try: - choice = input("You already have a wireguard key-pair already, do you want to re-generate? [y/n] ") - if choice == 'y' or choice == 'Y': - generate_keypair() - except KeyboardInterrupt: - sys.exit(0) - else: - """ if keypair is bing executed from a running iso """ - if not os.path.exists(dir): - os.umask(old_umask) - subprocess.call(['sudo mkdir -p ' + dir], shell=True) - subprocess.call(['sudo chgrp vyattacfg ' + dir], shell=True) - subprocess.call(['sudo chmod 770 ' + dir], shell=True) - generate_keypair() - os.umask(old_umask) + """ check if kmod is loaded, if not load it """ + if not os.path.exists('/sys/module/wireguard'): + sl.syslog(sl.LOG_NOTICE, "loading wirguard kmod") + if os.system('sudo modprobe wireguard') != 0: + sl.syslog(sl.LOG_ERR, "modprobe wireguard failed") + raise ConfigError("modprobe wireguard failed") -def showkey(key): - """ helper function to show privkey or pubkey """ - if key == "pub": - if os.path.exists(pub): - print ( open(pub).read().strip() ) + +def generate_keypair(pk, pub): + """ generates a keypair which is stored in /config/auth/wireguard """ + old_umask = os.umask(0o027) + ret = subprocess.call( + ['wg genkey | tee ' + pk + '|wg pubkey > ' + pub], shell=True) + if ret != 0: + raise ConfigError("wireguard key-pair generation failed") else: - print("no public key found") + sl.syslog( + sl.LOG_NOTICE, "new keypair wireguard key generated in " + dir) + os.umask(old_umask) - if key == "pk": - if os.path.exists(pk): - print ( open(pk).read().strip() ) + +def genkey(location): + """ helper function to check, regenerate the keypair """ + pk = "{}/private.key".format(location) + pub = "{}/public.key".format(location) + old_umask = os.umask(0o027) + if os.path.exists(pk) and os.path.exists(pub): + try: + choice = input( + "You already have a wireguard key-pair, do you want to re-generate? [y/n] ") + if choice == 'y' or choice == 'Y': + generate_keypair(pk, pub) + except KeyboardInterrupt: + sys.exit(0) + else: + """ if keypair is bing executed from a running iso """ + if not os.path.exists(location): + subprocess.call(['sudo mkdir -p ' + location], shell=True) + subprocess.call(['sudo chgrp vyattacfg ' + location], shell=True) + subprocess.call(['sudo chmod 750 ' + location], shell=True) + generate_keypair(pk, pub) + os.umask(old_umask) + + +def showkey(key): + """ helper function to show privkey or pubkey """ + if os.path.exists(key): + print (open(key).read().strip()) else: - print("no private key found") + print ("{} not found".format(key)) + def genpsk(): - """ generates a preshared key and shows it on stdout, it's stroed only in the config """ - subprocess.call(['wg genpsk'], shell=True) + """ + generates a preshared key and shows it on stdout, + it's stored only in the cli config + """ + + subprocess.call(['wg genpsk'], shell=True) + + +def list_key_dirs(): + """ lists all dirs under /config/auth/wireguard """ + if os.path.exists(dir): + nks = next(os.walk(dir))[1] + for nk in nks: + print (nk) + + +def del_key_dir(kname): + """ deletes /config/auth/wireguard/ """ + kdir = "{0}/{1}".format(dir, kname) + if not os.path.isdir(kdir): + print ("named keypair {} not found".format(kname)) + return 1 + shutil.rmtree(kdir) + if __name__ == '__main__': - check_kmod() - - parser = argparse.ArgumentParser(description='wireguard key management') - parser.add_argument('--genkey', action="store_true", help='generate key-pair') - parser.add_argument('--showpub', action="store_true", help='shows public key') - parser.add_argument('--showpriv', action="store_true", help='shows private key') - parser.add_argument('--genpsk', action="store_true", help='generates preshared-key') - args = parser.parse_args() - - try: - if args.genkey: - genkey() - if args.showpub: - showkey("pub") - if args.showpriv: - showkey("pk") - if args.genpsk: - genpsk() - - except ConfigError as e: - print(e) - sys.exit(1) + check_kmod() + parser = argparse.ArgumentParser(description='wireguard key management') + parser.add_argument( + '--genkey', action="store_true", help='generate key-pair') + parser.add_argument( + '--showpub', action="store_true", help='shows public key') + parser.add_argument( + '--showpriv', action="store_true", help='shows private key') + parser.add_argument( + '--genpsk', action="store_true", help='generates preshared-key') + parser.add_argument( + '--location', action="store", help='key location within {}'.format(dir)) + parser.add_argument( + '--listkdir', action="store_true", help='lists named keydirectories') + parser.add_argument( + '--delkdir', action="store_true", help='removes named keydirectories') + args = parser.parse_args() + + try: + if args.genkey: + if args.location: + genkey("{0}/{1}".format(dir, args.location)) + else: + genkey(dir) + + if args.showpub: + if args.location: + showkey("{0}/{1}/public.key".format(dir, args.location)) + else: + showkey("{}/public.key".format(dir)) + if args.showpriv: + if args.location: + showkey("{0}/{1}/private.key".format(dir, args.location)) + else: + showkey("{}/private".format(dir)) + if args.genpsk: + genpsk() + if args.listkdir: + list_key_dirs() + if args.delkdir: + del_key_dir(args.location) + except ConfigError as e: + print(e) + sys.exit(1) -- cgit v1.2.3