luci-proto-vxlan: introduce peers tab and additional vxlan settings
authorPaul Donald <newtwen+github@gmail.com>
Fri, 27 Sep 2024 01:11:02 +0000 (03:11 +0200)
committerPaul Donald <newtwen+github@gmail.com>
Fri, 25 Oct 2024 20:19:00 +0000 (22:19 +0200)
Added ip-bridge dep for bridge command to manage FDB entries.

Reference:
https://events.static.linuxfound.org/sites/events/files/slides/2013-linuxcon.pdf

Signed-off-by: Paul Donald <newtwen+github@gmail.com>
protocols/luci-proto-vxlan/Makefile
protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js
protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js

index 1b9cb45c8306ad92f7b51710f166bcfad876a56f..f78b8cdb5167ee3e4bf0268dec73d09bd60fbcc9 100644 (file)
@@ -7,7 +7,7 @@
 include $(TOPDIR)/rules.mk
 
 LUCI_TITLE:=Support for Virtual eXtensible Local Area Network (VXLAN, RFC7348)
-LUCI_DEPENDS:=+vxlan
+LUCI_DEPENDS:=+vxlan +ip-bridge
 
 PKG_MAINTAINER:=Wojciech Jowsa <wojciech.jowsa@gmail.com>
 PKG_LICENSE:=Apache-2.0
index 1d5052fdfca91bd60011dad924d0e67961d0fdd7..3997bc6277854261622e24562a20143ec357c382 100644 (file)
@@ -2,6 +2,7 @@
 'require form';
 'require network';
 'require tools.widgets as widgets';
+'require uci';
 
 network.registerPatternVirtual(/^vxlan-.+$/);
 
@@ -37,11 +38,14 @@ return network.registerProtocol('vxlan', {
        renderFormOptions: function(s) {
                var o;
 
-               o = s.taboption('general', form.Value, 'peeraddr', _('Remote IPv4 address'), _('The IPv4 address or the fully-qualified domain name of the remote end.'));
+               o = s.taboption('general', form.Value, 'peeraddr', _('Remote IPv4 address'), _('The IPv4 address or the fully-qualified domain name of the remote end.') + '<br/>' +
+                       _('Alternatively, a multicast address to reach a group of peers.') + '<br/>' +
+                       _('Remote VTEP'));
                o.optional = false;
                o.datatype = 'or(hostname,ip4addr("nomask"))';
 
-               o = s.taboption('general', form.Value, 'ipaddr', _('Local IPv4 address'), _('The local IPv4 address over which the tunnel is created (optional).'));
+               o = s.taboption('general', form.Value, 'ipaddr', _('Local IPv4 address'), _('The local IPv4 address over which the tunnel is created (optional).') + '<br/>' +
+                       _('Local VTEP'));
                o.optional = true;
                o.datatype = 'ip4addr("nomask")';
 
@@ -50,7 +54,78 @@ return network.registerProtocol('vxlan', {
                o.placeholder = 4789;
                o.datatype = 'port';
 
-               o = s.taboption('general', form.Value, 'vid', _('VXLAN network identifier'), _('ID used to uniquely identify the VXLAN'));
+               o = s.taboption('general', form.Value, 'srcport', _('Source port range'));
+               o.optional = true;
+               o.placeholder = '5000-6000';
+               o.datatype = 'portrange';
+               o.load = function(section_id) {
+                       const min = uci.get('network', section_id, 'srcportmin');
+                       const max = uci.get('network', section_id, 'srcportmax');
+                       if (!min || !max)
+                               return null;
+                       return `${min}-${max}`;
+               };
+               o.write = function(section_id, value) {
+                       const ports = value?.split('-') || null;
+                       if (ports){
+                               uci.set('network', section_id, 'srcportmin', ports[0]);
+                               uci.set('network', section_id, 'srcportmax', ports[1]);
+                       }
+               };
+
+               o = s.taboption('advanced', form.Value, 'ageing', _('Ageing'),
+                       _('FDB entry lifetime') + '<br/>' +
+                       _('Units: seconds'));
+               o.optional = true;
+               o.placeholder = 300;
+               o.datatype = 'uinteger';
+
+               o = s.taboption('advanced', form.Value, 'mtu', _('MTU'),
+                       _('Ensure MTU does not exceed that of parent interface') + '<br/>' +
+                       _('The VXLAN header adds 50 bytes of IPv4 encapsulation overhead, 74 bytes for IPv6.'));
+               o.optional = true;
+               o.placeholder = 1280;
+               o.datatype = 'min(128)';
+
+               o = s.taboption('advanced', form.Value, 'maxaddress', _('Max FDB size'),
+                       _('Maximum number of FDB entries'));
+               o.optional = true;
+               o.placeholder = 1000;
+               o.datatype = 'min(1)';
+
+               o = s.taboption('general', form.Flag, 'learning', _('Learning'),
+                       _('Automatic mac learning using multicast; inserts unknown source link layer addresses and IP addresses into the VXLAN device %s'
+                               .format('<abbr title="%s">%s</abbr>'.format(_('Forwarding DataBase'), _('FDB')))));
+               o.optional = true;
+               o.default = '1';
+               o.rmempty = false;
+
+               o = s.taboption('advanced', form.Flag, 'rsc', _('Route short-circuit (RSC)'),
+                       _('If destination MAC refers to router, replace it with destination MAC address'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'proxy', _('ARP proxy'),
+                       _('Reply on Neighbour request when mapping found in VXLAN FDB'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'l2miss', _('l2miss: Layer 2 miss'),
+                       _('On a l2miss, send ARP') + '<br/>' +
+                       _('Emits netlink LLADDR miss notifications') + '<br/>' +
+                       _('Expect netlink reply to add MAC address into VXLAN FDB'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'l3miss', _('l3miss: Layer 3 miss'),
+                       _('On a l3miss, send ARP for IP -> mac resolution') + '<br/>' +
+                       _('Emits netlink IP ADDR miss notifications') + '<br/>' + 
+                       _('Expect netlink reply to add destination IP address into Neighbour table'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'gbp', _('GBP'),
+                       _('Group Based Policy (VXLAN-GBP) extension'));
+               o.optional = true;
+
+               o = s.taboption('general', form.Value, 'vid', _('VXLAN network identifier'),
+                       _('VNI') + ': ' + _('ID used to uniquely identify the VXLAN'));
                o.optional = true;
                o.datatype = 'range(1, 16777216)';
 
@@ -66,7 +141,27 @@ return network.registerProtocol('vxlan', {
 
                o = s.taboption('advanced', form.Value, 'tos', _('Override TOS'), _('Specify a TOS (Type of Service).'));
                o.optional = true;
-               o.datatype = 'range(0, 255)';
+               // values 0xA0-0xE0 are valid, but don't work
+               o.value('2', '2: 0x02');
+               o.value('4', '4: 0x04');
+               for (var i = 32; i <= 152; i += 8) {
+                       o.value(i, i + ': 0x' + i.toString(16).padStart(2, '0'));
+               }
+               o.value('inherit');
+               o.validate = function(section_id, value) {
+                       if (!value || /^inherit$/.test(value)) return true;
+                       const v = parseInt(value)
+                       if (v % 4 != 0) return false
+                       if (v <= 152 && v > 0)
+                               return true;
+                       return false;
+               };
+               o.write = function(section_id, value) {
+                       return uci.set('network', section_id, 'tos', parseInt(value).toString(16).padStart(2, '0'));
+               };
+               o.load = function(section_id) {
+                       return parseInt(uci.get('network', section_id, 'tos'), 16).toString();
+               };
 
                o = s.taboption('advanced', form.Flag, 'rxcsum', _('Enable rx checksum'));
                o.optional = true;
@@ -76,5 +171,84 @@ return network.registerProtocol('vxlan', {
                o.optional = true;
                o.default = o.enabled;
 
+               try {
+                       s.tab('peers', _('Additional Peers'), _('Further information about VXLAN interfaces and peers %s.').format('<a href=\'https://openwrt.org/docs/guide-user/network/tunneling_interface_protocols#protocol_vxlan_vxlan_layer_2_virtualization_over_layer_3_network\'>here</a>'));
+               }
+               catch(e) {}
+
+               o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'vxlan_peers');
+               o.depends('proto', 'vxlan');
+
+               var ss = o.subsection;
+               ss.anonymous = true;
+               ss.addremove = true;
+               ss.addbtntitle = _('Add peer');
+               ss.nodescriptions = true;
+               ss.modaltitle = _('Edit peer');
+               ss.filter = function(section_id) {
+                       let peer = uci.get('network', section_id);
+                       return (peer.vxlan === s.section ? true: false);
+               };
+
+               o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
+               o.placeholder = _('My Peer');
+               o.datatype = 'string';
+               o.optional = true;
+
+               o = ss.option(form.Value, 'lladr', _('Layer 2 Address'),
+                       _('L2 (MAC) address of peer. Uses source-address learning when %s is specified')
+                       .format('<code>00:00:00:00:00:00</code>'));
+               o.editable = true;
+               o.datatype = 'macaddr';
+               o.optional = true;
+               o.modalonly = true;
+               o.value('00:00:00:00:00:00');
+
+               o = ss.option(form.Value, 'dst', _('Peer IP'), _('IP address of the remote VXLAN tunnel endpoint where the MAC address (Layer 2 Address) resides or a multicast address for a group of peers.')
+                       + '<br/>' + _('For multicast, an outgoing interface (%s) needs to be specified').format('<code>via</code>'));
+               o.editable = true;
+               o.datatype = 'ipaddr';
+               o.placeholder = '239.1.1.1'
+               o.rmempty = false;
+               o.write = function(section_id, value) {
+                       // Note: a heavier alternative is to chain vxlan parameter creation to handleAdd
+                       // but because ipaddr is also mandatory, use this write function
+                       uci.set('network', section_id, 'vxlan', s.section);
+                       return this.super('write', [ section_id, value ]);
+               };
+
+               o = ss.option(form.Value, 'port', _('Port'), _('UDP destination port number to use to connect to the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.datatype = 'port';
+               o.placeholder = 4789;
+               o.optional = true;
+
+               o = ss.option(widgets.NetworkSelect, 'via', _('Via'), _('Name of the outgoing interface to reach the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.exclude = s.section;
+               o.nocreate = true;
+               o.optional = true;
+               o.validate = function(section_id, value) {
+                       const dst = this.section.getOption('dst').formvalue(section_id).toLowerCase();
+                       const ipv4MulticastRegex = /^(22[4-9]|23[0-9])(\.\d{1,3}){3}$/;
+                       const ipv6MulticastRegex = /^ff[0-9a-fA-F]{0,2}:.*/;
+                       let isMulticastIP = ipv4MulticastRegex.test(dst) || ipv6MulticastRegex.test(dst);
+
+                       if (!value && isMulticastIP) {
+                               return _('Via shall be specified when %s is a multicast address'.format(_('Peer IP')));
+                       }
+                       return true;
+               };
+
+               o = ss.option(form.Value, 'vni', _('VNI'), _('the VXLAN Network Identifier (or VXLAN Segment ID) to use to connect to the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.datatype = 'range(1, 16777216)';
+               o.optional = true;
+
+               o = ss.option(form.Value, 'src_vni', _('Source VNI'), _('the source VNI Network Identifier (or VXLAN Segment ID) this entry belongs to. Used only when the VXLAN device is in external or collect metadata mode '));
+               o.editable = true;
+               o.datatype = 'range(1, 16777216)';
+               o.optional = true;
+
        }
 });
index 04e702897b2a8ebf06c43ad83ffe4fed2379fa63..9f54d4c1b8234f1999c9202d7351d571bb21b9cf 100644 (file)
@@ -2,6 +2,7 @@
 'require form';
 'require network';
 'require tools.widgets as widgets';
+'require uci';
 
 network.registerPatternVirtual(/^vxlan-.+$/);
 
@@ -37,11 +38,14 @@ return network.registerProtocol('vxlan6', {
        renderFormOptions: function(s) {
                var o;
 
-               o = s.taboption('general', form.Value, 'peer6addr', _('Remote IPv6 address'), _('The IPv6 address or the fully-qualified domain name of the remote end.'));
+               o = s.taboption('general', form.Value, 'peer6addr', _('Remote IPv6 address'), _('The IPv6 address or the fully-qualified domain name of the remote end.') + '<br/>' +
+                       _('Alternatively, a multicast address to reach a group of peers.') + '<br/>' +
+                       _('Remote VTEP'));
                o.optional = false;
                o.datatype = 'or(hostname,ip6addr("nomask"))';
 
-               o = s.taboption('general', form.Value, 'ip6addr', _('Local IPv6 address'), _('The local IPv6 address over which the tunnel is created (optional).'));
+               o = s.taboption('general', form.Value, 'ip6addr', _('Local IPv6 address'), _('The local IPv6 address over which the tunnel is created (optional).') + '<br/>' +
+                       _('Local VTEP'));
                o.optional = true;
                o.datatype = 'ip6addr("nomask")';
 
@@ -50,7 +54,78 @@ return network.registerProtocol('vxlan6', {
                o.placeholder = 4789;
                o.datatype = 'port';
 
-               o = s.taboption('general', form.Value, 'vid', _('VXLAN network identifier'), _('ID used to uniquely identify the VXLAN'));
+               o = s.taboption('general', form.Value, 'srcport', _('Source port range'));
+               o.optional = true;
+               o.placeholder = '5000-6000';
+               o.datatype = 'portrange';
+               o.load = function(section_id) {
+                       const min = uci.get('network', section_id, 'srcportmin');
+                       const max = uci.get('network', section_id, 'srcportmax');
+                       if (!min || !max)
+                               return null;
+                       return `${min}-${max}`;
+               };
+               o.write = function(section_id, value) {
+                       const ports = value?.split('-') || null;
+                       if (ports){
+                               uci.set('network', section_id, 'srcportmin', ports[0]);
+                               uci.set('network', section_id, 'srcportmax', ports[1]);
+                       }
+               };
+
+               o = s.taboption('advanced', form.Value, 'ageing', _('Ageing'),
+                       _('FDB entry lifetime') + '<br/>' +
+                       _('Units: seconds'));
+               o.optional = true;
+               o.placeholder = 300;
+               o.datatype = 'uinteger';
+
+               o = s.taboption('advanced', form.Value, 'mtu', _('MTU'),
+                       _('Ensure MTU does not exceed that of parent interface') + '<br/>' +
+                       _('The VXLAN header adds 50 bytes of IPv4 encapsulation overhead, 74 bytes for IPv6.'));
+               o.optional = true;
+               o.placeholder = 1280;
+               o.datatype = 'min(128)';
+
+               o = s.taboption('advanced', form.Value, 'maxaddress', _('Max FDB size'),
+                       _('Maximum number of FDB entries'));
+               o.optional = true;
+               o.placeholder = 1000;
+               o.datatype = 'min(1)';
+
+               o = s.taboption('general', form.Flag, 'learning', _('Learning'),
+                       _('Automatic mac learning using multicast; inserts unknown source link layer addresses and IP addresses into the VXLAN device %s'
+                               .format('<abbr title="%s">%s</abbr>'.format(_('Forwarding DataBase'), _('FDB')))));
+               o.optional = true;
+               o.default = '1';
+               o.rmempty = false;
+
+               o = s.taboption('advanced', form.Flag, 'rsc', _('Route short-circuit (RSC)'),
+                       _('If destination MAC refers to router, replace it with destination MAC address'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'proxy', _('ARP proxy'),
+                       _('Reply on Neighbour request when mapping found in VXLAN FDB'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'l2miss', _('l2miss: Layer 2 miss'),
+                       _('On a l2miss, send ARP') + '<br/>' +
+                       _('Emits netlink LLADDR miss notifications') + '<br/>' +
+                       _('Expect netlink reply to add MAC address into VXLAN FDB'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'l3miss', _('l3miss: Layer 3 miss'),
+                       _('On a l3miss, send ARP for IP -> mac resolution') + '<br/>' +
+                       _('Emits netlink IP ADDR miss notifications') + '<br/>' + 
+                       _('Expect netlink reply to add destination IP address into Neighbour table'));
+               o.optional = true;
+
+               o = s.taboption('advanced', form.Flag, 'gbp', _('GBP'),
+                       _('Group Based Policy (VXLAN-GBP) extension'));
+               o.optional = true;
+
+               o = s.taboption('general', form.Value, 'vid', _('VXLAN network identifier'),
+                       _('VNI') + ': ' + _('ID used to uniquely identify the VXLAN'));
                o.optional = true;
                o.datatype = 'range(1, 16777216)';
 
@@ -66,7 +141,27 @@ return network.registerProtocol('vxlan6', {
 
                o = s.taboption('advanced', form.Value, 'tos', _('Override TOS'), _('Specify a TOS (Type of Service).'));
                o.optional = true;
-               o.datatype = 'range(0, 255)';
+               // values 0xA0-0xE0 are valid, but don't work
+               o.value('2', '2: 0x02');
+               o.value('4', '4: 0x04');
+               for (var i = 32; i <= 152; i += 8) {
+                       o.value(i, i + ': 0x' + i.toString(16).padStart(2, '0'));
+               }
+               o.value('inherit');
+               o.validate = function(section_id, value) {
+                       if (!value || /^inherit$/.test(value)) return true;
+                       const v = parseInt(value)
+                       if (v % 4 != 0) return false
+                       if (v <= 152 && v > 0)
+                               return true;
+                       return false;
+               };
+               o.write = function(section_id, value) {
+                       return uci.set('network', section_id, 'tos', parseInt(value).toString(16).padStart(2, '0'));
+               };
+               o.load = function(section_id) {
+                       return parseInt(uci.get('network', section_id, 'tos'), 16).toString();
+               };
 
                o = s.taboption('advanced', form.Flag, 'rxcsum', _('Enable rx checksum'));
                o.optional = true;
@@ -76,5 +171,84 @@ return network.registerProtocol('vxlan6', {
                o.optional = true;
                o.default = o.enabled;
 
+               try {
+                       s.tab('peers', _('Additional Peers'), _('Further information about VXLAN interfaces and peers %s.').format('<a href=\'https://openwrt.org/docs/guide-user/network/tunneling_interface_protocols#protocol_vxlan_vxlan_layer_2_virtualization_over_layer_3_network\'>here</a>'));
+               }
+               catch(e) {}
+
+               o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'vxlan_peers');
+               o.depends('proto', 'vxlan');
+
+               var ss = o.subsection;
+               ss.anonymous = true;
+               ss.addremove = true;
+               ss.addbtntitle = _('Add peer');
+               ss.nodescriptions = true;
+               ss.modaltitle = _('Edit peer');
+               ss.filter = function(section_id) {
+                       let peer = uci.get('network', section_id);
+                       return (peer.vxlan === s.section ? true: false);
+               };
+
+               o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
+               o.placeholder = _('My Peer');
+               o.datatype = 'string';
+               o.optional = true;
+
+               o = ss.option(form.Value, 'lladr', _('Layer 2 Address'),
+                       _('L2 (MAC) address of peer. Uses source-address learning when %s is specified')
+                       .format('<code>00:00:00:00:00:00</code>'));
+               o.editable = true;
+               o.datatype = 'macaddr';
+               o.optional = true;
+               o.modalonly = true;
+               o.value('00:00:00:00:00:00');
+
+               o = ss.option(form.Value, 'dst', _('Peer IP'), _('IP address of the remote VXLAN tunnel endpoint where the MAC address (Layer 2 Address) resides or a multicast address for a group of peers.')
+                       + '<br/>' + _('For multicast, an outgoing interface (%s) needs to be specified').format('<code>via</code>'));
+               o.editable = true;
+               o.datatype = 'ipaddr';
+               o.placeholder = '239.1.1.1'
+               o.rmempty = false;
+               o.write = function(section_id, value) {
+                       // Note: a heavier alternative is to chain vxlan parameter creation to handleAdd
+                       // but because ipaddr is also mandatory, use this write function
+                       uci.set('network', section_id, 'vxlan', s.section);
+                       return this.super('write', [ section_id, value ]);
+               };
+
+               o = ss.option(form.Value, 'port', _('Port'), _('UDP destination port number to use to connect to the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.datatype = 'port';
+               o.placeholder = 4789;
+               o.optional = true;
+
+               o = ss.option(widgets.NetworkSelect, 'via', _('Via'), _('Name of the outgoing interface to reach the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.exclude = s.section;
+               o.nocreate = true;
+               o.optional = true;
+               o.validate = function(section_id, value) {
+                       const dst = this.section.getOption('dst').formvalue(section_id).toLowerCase();
+                       const ipv4MulticastRegex = /^(22[4-9]|23[0-9])(\.\d{1,3}){3}$/;
+                       const ipv6MulticastRegex = /^ff[0-9a-fA-F]{0,2}:.*/;
+                       let isMulticastIP = ipv4MulticastRegex.test(dst) || ipv6MulticastRegex.test(dst);
+
+                       if (!value && isMulticastIP) {
+                               return _('Via shall be specified when %s is a multicast address'.format(_('Peer IP')));
+                       }
+                       return true;
+               };
+
+               o = ss.option(form.Value, 'vni', _('VNI'), _('the VXLAN Network Identifier (or VXLAN Segment ID) to use to connect to the remote VXLAN tunnel endpoint'));
+               o.editable = true;
+               o.datatype = 'range(1, 16777216)';
+               o.optional = true;
+
+               o = ss.option(form.Value, 'src_vni', _('Source VNI'), _('the source VNI Network Identifier (or VXLAN Segment ID) this entry belongs to. Used only when the VXLAN device is in external or collect metadata mode '));
+               o.editable = true;
+               o.datatype = 'range(1, 16777216)';
+               o.optional = true;
+
        }
 });