diff --git a/interface-definitions/include/version/interfaces-version.xml.i b/interface-definitions/include/version/interfaces-version.xml.i index 76c5d3c05..f01fe1983 100644 --- a/interface-definitions/include/version/interfaces-version.xml.i +++ b/interface-definitions/include/version/interfaces-version.xml.i @@ -1,3 +1,3 @@ <!-- include start from include/version/interfaces-version.xml.i --> -<syntaxVersion component='interfaces' version='31'></syntaxVersion> -<!-- include end --> +<syntaxVersion component='interfaces' version='32'></syntaxVersion> +<!-- include end --> \ No newline at end of file diff --git a/interface-definitions/interfaces-vxlan.xml.in b/interface-definitions/interfaces-vxlan.xml.in index b246d9a09..3fae17178 100644 --- a/interface-definitions/interfaces-vxlan.xml.in +++ b/interface-definitions/interfaces-vxlan.xml.in @@ -1,124 +1,124 @@ <?xml version="1.0"?> <interfaceDefinition> <node name="interfaces"> <children> <tagNode name="vxlan" owner="${vyos_conf_scripts_dir}/interfaces-vxlan.py"> <properties> <help>Virtual Extensible LAN (VXLAN) Interface</help> <priority>460</priority> <constraint> <regex>vxlan[0-9]+</regex> </constraint> <constraintErrorMessage>VXLAN interface must be named vxlanN</constraintErrorMessage> <valueHelp> <format>vxlanN</format> <description>VXLAN interface name</description> </valueHelp> </properties> <children> #include <include/interface/address-ipv4-ipv6.xml.i> #include <include/generic-description.xml.i> #include <include/interface/disable.xml.i> <leafNode name="external"> <properties> <help>Use external control plane</help> <valueless/> </properties> </leafNode> <leafNode name="gpe"> <properties> <help>Enable Generic Protocol extension (VXLAN-GPE)</help> <valueless/> </properties> </leafNode> <leafNode name="group"> <properties> <help>Multicast group address for VXLAN interface</help> <valueHelp> <format>ipv4</format> <description>Multicast IPv4 group address</description> </valueHelp> <valueHelp> <format>ipv6</format> <description>Multicast IPv6 group address</description> </valueHelp> <constraint> <validator name="ipv4-multicast"/> <validator name="ipv6-multicast"/> </constraint> <constraintErrorMessage>Multicast IPv4/IPv6 address required</constraintErrorMessage> </properties> </leafNode> #include <include/interface/ipv4-options.xml.i> #include <include/interface/ipv6-options.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/mtu-1200-16000.xml.i> #include <include/interface/mirror.xml.i> <leafNode name="mtu"> <defaultValue>1450</defaultValue> </leafNode> <node name="parameters"> <properties> <help>VXLAN tunnel parameters</help> </properties> <children> <node name="ip"> <properties> <help>IPv4 specific tunnel parameters</help> </properties> <children> #include <include/interface/parameters-df.xml.i> #include <include/interface/parameters-tos.xml.i> #include <include/interface/parameters-ttl.xml.i> <leafNode name="ttl"> <defaultValue>16</defaultValue> </leafNode> </children> </node> <node name="ipv6"> <properties> <help>IPv6 specific tunnel parameters</help> </properties> <children> #include <include/interface/parameters-flowlabel.xml.i> </children> </node> <leafNode name="nolearning"> <properties> <help>Do not add unknown addresses into forwarding database</help> <valueless/> </properties> </leafNode> </children> </node> #include <include/port-number.xml.i> <leafNode name="port"> - <defaultValue>8472</defaultValue> + <defaultValue>4789</defaultValue> </leafNode> #include <include/source-address-ipv4-ipv6.xml.i> #include <include/source-interface.xml.i> #include <include/interface/tunnel-remote-multi.xml.i> #include <include/interface/redirect.xml.i> #include <include/interface/vrf.xml.i> #include <include/vni.xml.i> <tagNode name="vlan-to-vni"> <properties> <help>Configuring VLAN-to-VNI mappings for EVPN-VXLAN</help> <valueHelp> <format>u32:0-4094</format> <description>Virtual Local Area Network (VLAN) ID</description> </valueHelp> <constraint> <validator name="numeric" argument="--range 0-4094"/> </constraint> <constraintErrorMessage>VLAN ID must be between 0 and 4094</constraintErrorMessage> </properties> <children> #include <include/vni.xml.i> </children> </tagNode> </children> </tagNode> </children> </node> </interfaceDefinition> diff --git a/smoketest/configs/bgp-evpn-l2vpn-leaf b/smoketest/configs/bgp-evpn-l2vpn-leaf index 020490186..ab46fbb02 100644 --- a/smoketest/configs/bgp-evpn-l2vpn-leaf +++ b/smoketest/configs/bgp-evpn-l2vpn-leaf @@ -1,149 +1,148 @@ interfaces { bridge br100 { member { interface eth3 { } interface vxlan100 { } } } dummy dum0 { address 172.29.0.1/32 } ethernet eth0 { description "Out-of-Band Managament Port" address 2001:db8::41/64 address 192.0.2.41/27 vrf MGMT } ethernet eth1 { address 172.29.1.1/31 mtu 1600 } ethernet eth2 { address 172.29.2.1/31 mtu 1600 } ethernet eth3 { } loopback lo { } vxlan vxlan100 { mtu 1500 parameters { nolearning } - port 4789 source-address 172.29.0.1 vni 100 } } protocols { bgp 65010 { address-family { ipv4-unicast { maximum-paths { ibgp 4 } redistribute { connected { } } } l2vpn-evpn { advertise-all-vni } } neighbor 172.29.1.0 { peer-group evpn } neighbor 172.29.2.0 { peer-group evpn } parameters { log-neighbor-changes } peer-group evpn { address-family { ipv4-unicast { nexthop-self { } } l2vpn-evpn { nexthop-self { } } } remote-as 65010 } } vrf MGMT { static { route 0.0.0.0/0 { next-hop 192.0.2.62 { } } route6 ::/0 { next-hop 2001:db8::1 { } } } } } service { lldp { interface all { } } ssh { disable-host-validation vrf MGMT } } system { config-management { commit-revisions 100 } console { device ttyS0 { speed 115200 } } host-name vyos login { user vyos { authentication { encrypted-password $6$O5gJRlDYQpj$MtrCV9lxMnZPMbcxlU7.FI793MImNHznxGoMFgm3Q6QP3vfKJyOSRCt3Ka/GzFQyW1yZS4NS616NLHaIPPFHc0 plaintext-password "" } } } ntp { listen-address 192.0.2.41 listen-address 2001:db8::41 server 0.de.pool.ntp.org { prefer } vrf MGMT } syslog { global { facility all { level info } facility protocols { level debug } } } } vrf { name MGMT { table 1000 } } // Warning: Do not remove the following line. // vyos-config-version: "broadcast-relay@1:cluster@1:config-management@1:conntrack@2:conntrack-sync@1:dhcp-relay@2:dhcp-server@5:dhcpv6-server@1:dns-forwarding@3:firewall@5:https@2:interfaces@20:ipoe-server@1:ipsec@5:l2tp@3:lldp@1:mdns@1:nat@5:nat66@1:ntp@1:pppoe-server@5:pptp@2:qos@1:quagga@8:rpki@1:salt@1:snmp@2:ssh@2:sstp@3:system@20:vrrp@2:vyos-accel-ppp@2:wanloadbalance@3:webproxy@2:zone-policy@1" // Release version: 1.4-rolling-202103091038 diff --git a/src/conf_mode/interfaces-vxlan.py b/src/conf_mode/interfaces-vxlan.py index 05f68112a..ff8144e74 100755 --- a/src/conf_mode/interfaces-vxlan.py +++ b/src/conf_mode/interfaces-vxlan.py @@ -1,200 +1,207 @@ #!/usr/bin/env python3 # # Copyright (C) 2019-2023 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 <http://www.gnu.org/licenses/>. import os from sys import exit from netifaces import interfaces from vyos.base import Warning from vyos.config import Config from vyos.configdict import get_interface_dict from vyos.configdict import leaf_node_changed from vyos.configdict import is_node_changed from vyos.configdict import node_changed from vyos.configverify import verify_address from vyos.configverify import verify_bridge_delete from vyos.configverify import verify_mtu_ipv6 from vyos.configverify import verify_mirror_redirect from vyos.configverify import verify_source_interface from vyos.configverify import verify_bond_bridge_member from vyos.ifconfig import Interface from vyos.ifconfig import VXLANIf from vyos.template import is_ipv6 from vyos import ConfigError from vyos import airbag airbag.enable() def get_config(config=None): """ Retrive CLI config as dictionary. Dictionary can never be empty, as at least the interface name will be added or a deleted flag """ if config: conf = config else: conf = Config() base = ['interfaces', 'vxlan'] ifname, vxlan = get_interface_dict(conf, base) # VXLAN interfaces are picky and require recreation if certain parameters # change. But a VXLAN interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? for cli_option in ['parameters', 'external', 'gpe', 'group', 'port', 'remote', 'source-address', 'source-interface', 'vni']: if is_node_changed(conf, base + [ifname, cli_option]): vxlan.update({'rebuild_required': {}}) break tmp = node_changed(conf, base + [ifname, 'vlan-to-vni'], recursive=True) if tmp: vxlan.update({'vlan_to_vni_removed': tmp}) # We need to verify that no other VXLAN tunnel is configured when external # mode is in use - Linux Kernel limitation conf.set_level(base) vxlan['other_tunnels'] = conf.get_config_dict([], key_mangling=('-', '_'), get_first_key=True, no_tag_node_value_mangle=True) # This if-clause is just to be sure - it will always evaluate to true ifname = vxlan['ifname'] if ifname in vxlan['other_tunnels']: del vxlan['other_tunnels'][ifname] if len(vxlan['other_tunnels']) == 0: del vxlan['other_tunnels'] return vxlan def verify(vxlan): if 'deleted' in vxlan: verify_bridge_delete(vxlan) return None if int(vxlan['mtu']) < 1500: Warning('RFC7348 recommends VXLAN tunnels preserve a 1500 byte MTU') if 'group' in vxlan: if 'source_interface' not in vxlan: raise ConfigError('Multicast VXLAN requires an underlaying interface') verify_source_interface(vxlan) if not any(tmp in ['group', 'remote', 'source_address', 'source_interface'] for tmp in vxlan): raise ConfigError('Group, remote, source-address or source-interface must be configured') if 'vni' not in vxlan and 'external' not in vxlan: raise ConfigError( 'Must either configure VXLAN "vni" or use "external" CLI option!') if {'external', 'vni'} <= set(vxlan): raise ConfigError('Can not specify both "external" and "VNI"!') if {'external', 'other_tunnels'} <= set(vxlan): other_tunnels = ', '.join(vxlan['other_tunnels']) raise ConfigError(f'Only one VXLAN tunnel is supported when "external" '\ f'CLI option is used. Additional tunnels: {other_tunnels}') if 'gpe' in vxlan and 'external' not in vxlan: raise ConfigError(f'VXLAN-GPE is only supported when "external" '\ f'CLI option is used.') if 'source_interface' in vxlan: # VXLAN adds at least an overhead of 50 byte - we need to check the # underlaying device if our VXLAN package is not going to be fragmented! vxlan_overhead = 50 if 'source_address' in vxlan and is_ipv6(vxlan['source_address']): # IPv6 adds an extra 20 bytes overhead because the IPv6 header is 20 # bytes larger than the IPv4 header - assuming no extra options are # in use. vxlan_overhead += 20 # If source_address is not used - check IPv6 'remote' list elif 'remote' in vxlan: if any(is_ipv6(a) for a in vxlan['remote']): vxlan_overhead += 20 lower_mtu = Interface(vxlan['source_interface']).get_mtu() if lower_mtu < (int(vxlan['mtu']) + vxlan_overhead): raise ConfigError(f'Underlaying device MTU is to small ({lower_mtu} '\ f'bytes) for VXLAN overhead ({vxlan_overhead} bytes!)') # Check for mixed IPv4 and IPv6 addresses protocol = None if 'source_address' in vxlan: if is_ipv6(vxlan['source_address']): protocol = 'ipv6' else: protocol = 'ipv4' if 'remote' in vxlan: error_msg = 'Can not mix both IPv4 and IPv6 for VXLAN underlay' for remote in vxlan['remote']: if is_ipv6(remote): if protocol == 'ipv4': raise ConfigError(error_msg) protocol = 'ipv6' else: if protocol == 'ipv6': raise ConfigError(error_msg) protocol = 'ipv4' if 'vlan_to_vni' in vxlan: if 'is_bridge_member' not in vxlan: raise ConfigError('VLAN to VNI mapping requires that VXLAN interface '\ 'is member of a bridge interface!') vnis_used = [] for vif, vif_config in vxlan['vlan_to_vni'].items(): if 'vni' not in vif_config: raise ConfigError(f'Must define VNI for VLAN "{vif}"!') vni = vif_config['vni'] if vni in vnis_used: raise ConfigError(f'VNI "{vni}" is already assigned to a different VLAN!') vnis_used.append(vni) verify_mtu_ipv6(vxlan) verify_address(vxlan) verify_bond_bridge_member(vxlan) verify_mirror_redirect(vxlan) + + # We use a defaultValue for port, thus it's always safe to use + if vxlan['port'] == '8472': + Warning('Starting from VyOS 1.4, the default port for VXLAN '\ + 'has been changed to 4789. This matches the IANA assigned '\ + 'standard port number!') + return None def generate(vxlan): return None def apply(vxlan): # Check if the VXLAN interface already exists if 'rebuild_required' in vxlan or 'delete' in vxlan: if vxlan['ifname'] in interfaces(): v = VXLANIf(vxlan['ifname']) # VXLAN is super picky and the tunnel always needs to be recreated, # thus we can simply always delete it first. v.remove() if 'deleted' not in vxlan: # Finally create the new interface v = VXLANIf(**vxlan) v.update(vxlan) 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/31-to-32 b/src/migration-scripts/interfaces/31-to-32 new file mode 100755 index 000000000..35b397c39 --- /dev/null +++ b/src/migration-scripts/interfaces/31-to-32 @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2023 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 <http://www.gnu.org/licenses/>. +# +# T5671: change port to IANA assigned default port + +from sys import argv +from sys import exit +from vyos.configtree import ConfigTree + +if len(argv) < 2: + print("Must specify file name!") + exit(1) + +file_name = argv[1] +with open(file_name, 'r') as f: + config_file = f.read() + base = ['interfaces', 'vxlan'] + +config = ConfigTree(config_file) +if not config.exists(base): + # Nothing to do + exit(0) + +for vxlan in config.list_nodes(base): + if not config.exists(base + ['port']): + config.set(base + [vxlan, 'port'], value='8472') + +try: + with open(file_name, 'w') as f: + f.write(config.to_string()) +except OSError as e: + print(f'Failed to save the modified config: {e}') + exit(1)