summaryrefslogtreecommitdiffhomepage
path: root/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'protocols')
-rw-r--r--protocols/luci-proto-3g/htdocs/luci-static/resources/protocol/3g.js37
-rw-r--r--protocols/luci-proto-autoip/Makefile14
-rw-r--r--protocols/luci-proto-autoip/htdocs/luci-static/resources/protocol/autoip.js16
-rw-r--r--protocols/luci-proto-batman-adv/Makefile14
-rw-r--r--protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv.js109
-rw-r--r--protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv_hardif.js58
-rw-r--r--protocols/luci-proto-bonding/htdocs/luci-static/resources/protocol/bonding.js15
-rw-r--r--protocols/luci-proto-external/Makefile8
-rw-r--r--protocols/luci-proto-external/htdocs/luci-static/resources/protocol/external.js47
-rw-r--r--protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gre.js20
-rw-r--r--protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gretap.js18
-rw-r--r--protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6.js18
-rw-r--r--protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6tap.js18
-rw-r--r--protocols/luci-proto-hnet/htdocs/luci-static/resources/protocol/hnet.js14
-rw-r--r--protocols/luci-proto-ipip/htdocs/luci-static/resources/protocol/ipip.js6
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/464xlat.js8
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6in4.js8
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6rd.js8
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6to4.js8
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dhcpv6.js24
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dslite.js8
-rw-r--r--protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/map.js12
-rw-r--r--protocols/luci-proto-mbim/Makefile14
-rw-r--r--protocols/luci-proto-mbim/htdocs/luci-static/resources/protocol/mbim.js152
-rw-r--r--protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js101
-rw-r--r--protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js113
-rw-r--r--protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js181
-rw-r--r--protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json13
-rw-r--r--protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json6
-rw-r--r--protocols/luci-proto-ncm/htdocs/luci-static/resources/protocol/ncm.js62
-rw-r--r--protocols/luci-proto-nebula/Makefile17
-rw-r--r--protocols/luci-proto-nebula/htdocs/luci-static/resources/protocol/nebula.js50
-rw-r--r--protocols/luci-proto-openconnect/Makefile2
-rw-r--r--protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js63
-rw-r--r--protocols/luci-proto-openfortivpn/Makefile14
-rw-r--r--protocols/luci-proto-openfortivpn/htdocs/luci-static/resources/protocol/openfortivpn.js175
-rwxr-xr-xprotocols/luci-proto-openfortivpn/root/usr/libexec/rpcd/luci.openfortivpn86
-rw-r--r--protocols/luci-proto-openfortivpn/root/usr/share/rpcd/acl.d/luci-openfortivpn.json15
-rw-r--r--protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/l2tp.js18
-rw-r--r--protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/ppp.js21
-rw-r--r--protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoa.js18
-rw-r--r--protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoe.js18
-rw-r--r--protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pptp.js18
-rw-r--r--protocols/luci-proto-pppossh/htdocs/luci-static/resources/protocol/pppossh.js18
-rw-r--r--protocols/luci-proto-qmi/htdocs/luci-static/resources/protocol/qmi.js59
-rw-r--r--protocols/luci-proto-sstp/htdocs/luci-static/resources/protocol/sstp.js19
-rw-r--r--protocols/luci-proto-unet/Makefile15
-rw-r--r--protocols/luci-proto-unet/htdocs/luci-static/resources/protocol/unet.js48
-rw-r--r--protocols/luci-proto-vpnc/Makefile3
-rw-r--r--protocols/luci-proto-vpnc/htdocs/luci-static/resources/protocol/vpnc.js4
-rw-r--r--protocols/luci-proto-vti/Makefile14
-rw-r--r--protocols/luci-proto-vti/htdocs/luci-static/resources/protocol/vti.js72
-rw-r--r--protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js9
-rw-r--r--protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js18
-rw-r--r--protocols/luci-proto-wireguard/Makefile4
-rw-r--r--protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js763
-rw-r--r--protocols/luci-proto-wireguard/htdocs/luci-static/resources/view/wireguard/status.js175
-rw-r--r--protocols/luci-proto-wireguard/root/usr/share/luci/menu.d/luci-proto-wireguard.json14
-rw-r--r--protocols/luci-proto-wireguard/root/usr/share/rpcd/acl.d/luci-wireguard.json25
-rw-r--r--protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard107
-rw-r--r--protocols/luci-proto-xfrm/Makefile14
-rw-r--r--protocols/luci-proto-xfrm/htdocs/luci-static/resources/protocol/xfrm.js51
-rw-r--r--protocols/luci-proto-yggdrasil/Makefile18
-rw-r--r--protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js271
-rwxr-xr-xprotocols/luci-proto-yggdrasil/root/usr/libexec/rpcd/luci.yggdrasil36
-rw-r--r--protocols/luci-proto-yggdrasil/root/usr/share/rpcd/acl.d/luci-proto-yggdrasil.json10
66 files changed, 3022 insertions, 390 deletions
diff --git a/protocols/luci-proto-3g/htdocs/luci-static/resources/protocol/3g.js b/protocols/luci-proto-3g/htdocs/luci-static/resources/protocol/3g.js
index 07bed36c23..a1c54984a6 100644
--- a/protocols/luci-proto-3g/htdocs/luci-static/resources/protocol/3g.js
+++ b/protocols/luci-proto-3g/htdocs/luci-static/resources/protocol/3g.js
@@ -70,7 +70,8 @@ return network.registerProtocol('3g', {
renderFormOptions: function(s) {
var o;
- o = s.taboption('general', form.Value, 'device', _('Modem device'));
+ o = s.taboption('general', form.Value, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
o.rmempty = false;
o.load = function(section_id) {
return callFileList('/dev/').then(L.bind(function(devices) {
@@ -91,8 +92,20 @@ return network.registerProtocol('3g', {
o.value('gprs_only', _('GPRS only'));
o.value('evdo', 'CDMA/EV-DO');
- s.taboption('general', form.Value, 'apn', _('APN'));
- s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o = s.taboption('general', form.Value, 'apn', _('APN'));
+ o.validate = function(section_id, value) {
+ if (value == null || value == '')
+ return true;
+
+ if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+ return _('Invalid APN provided');
+
+ return true;
+ };
+
+ o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
+
s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));
o = s.taboption('general', form.Value, 'password', _('PAP/CHAP password'));
@@ -102,7 +115,8 @@ return network.registerProtocol('3g', {
o.placeholder = '*99***1#';
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
@@ -113,21 +127,6 @@ return network.registerProtocol('3g', {
o.placeholder = '10';
o.datatype = 'min(1)';
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-autoip/Makefile b/protocols/luci-proto-autoip/Makefile
new file mode 100644
index 0000000000..1a8181a3e5
--- /dev/null
+++ b/protocols/luci-proto-autoip/Makefile
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for Avahi IPv4LL configuration
+LUCI_DEPENDS:=+avahi-autoipd
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-autoip/htdocs/luci-static/resources/protocol/autoip.js b/protocols/luci-proto-autoip/htdocs/luci-static/resources/protocol/autoip.js
new file mode 100644
index 0000000000..5c8ead45d6
--- /dev/null
+++ b/protocols/luci-proto-autoip/htdocs/luci-static/resources/protocol/autoip.js
@@ -0,0 +1,16 @@
+'use strict';
+'require network';
+
+return network.registerProtocol('autoip', {
+ getI18n: function() {
+ return _('Avahi IPv4LL');
+ },
+
+ getOpkgPackage: function() {
+ return 'avahi-autoipd';
+ },
+
+ renderFormOptions: function(s) {
+
+ }
+});
diff --git a/protocols/luci-proto-batman-adv/Makefile b/protocols/luci-proto-batman-adv/Makefile
new file mode 100644
index 0000000000..9258d7db9e
--- /dev/null
+++ b/protocols/luci-proto-batman-adv/Makefile
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2022 Marc Ahlgrim <marc@onemarcfifty.com>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for the batman-adv protocol
+LUCI_DEPENDS:=+kmod-batman-adv
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv.js b/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv.js
new file mode 100644
index 0000000000..2e90e41cfa
--- /dev/null
+++ b/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv.js
@@ -0,0 +1,109 @@
+'use strict';
+'require form';
+'require network';
+
+
+network.registerPatternVirtual(/^bat\d+/);
+
+return network.registerProtocol('batadv', {
+ getI18n: function() {
+ return _('Batman Device');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || this.sid;
+ },
+
+ getOpkgPackage: function() {
+ return 'kmod-batman-adv';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var dev = this.getL3Device() || this.getDevice(),
+ o;
+
+ s.tab('mesh', _('Mesh Routing'), _('Mesh and routing related options'));
+
+ // @FIXME - the list of routing protocols should not be hard coded but come from batctl
+ o = s.taboption('mesh', form.ListValue, 'routing_algo', _('Routing Algorithm'),
+ _('The algorithm that is used to discover mesh routes'));
+ o.value('BATMAN_IV', 'BATMAN_IV');
+ o.value('BATMAN_V', 'BATMAN_V');
+ o.default = 'BATMAN_IV';
+
+ o = s.taboption('mesh', form.Flag, 'aggregated_ogms', _('Aggregate Originator Messages'),
+ _('reduces overhead by collecting and aggregating originator messages in a single packet rather than many small ones'));
+ o.ucioption = 'aggregated_ogms';
+ o.default = o.disabled;
+
+ o = s.taboption('mesh', form.Value, 'orig_interval', _('Originator Interval'),
+ _('The value specifies the interval (milliseconds) in which batman-adv floods the network with its protocol information.'));
+ o.placeholder = '1000';
+ o.datatype = 'min(1)';
+
+ o = s.taboption('mesh', form.Flag, 'ap_isolation', _('Access Point Isolation'),
+ _('Prevents one wireless client to talk to another. This setting only affects packets without any VLAN tag (untagged packets).'));
+ o.ucioption = 'ap_isolation';
+ o.default = o.disabled;
+
+ o = s.taboption('mesh', form.Flag, 'bonding', _('Bonding Mode'),
+ _('When running the mesh over multiple WiFi interfaces per node batman-adv is capable of optimizing the traffic flow to gain maximum performance.'));
+ o.ucioption = 'bonding';
+ o.default = o.disabled;
+
+ o = s.taboption('mesh', form.Flag, 'bridge_loop_avoidance', _('Avoid Bridge Loops'),
+ _('In bridged LAN setups it is advisable to enable the bridge loop avoidance in order to avoid broadcast loops that can bring the entire LAN to a standstill.'));
+ o.ucioption = 'bridge_loop_avoidance';
+ o.default = o.disabled;
+
+ o = s.taboption('mesh', form.Flag, 'distributed_arp_table', _('Distributed ARP Table'),
+ _('When enabled the distributed ARP table forms a mesh-wide ARP cache that helps non-mesh clients to get ARP responses much more reliably and without much delay.'));
+ o.ucioption = 'distributed_arp_table';
+ o.default = o.enabled;
+
+ o = s.taboption('mesh', form.Flag, 'fragmentation', _('Fragmentation'),
+ _('Batman-adv has a built-in layer 2 fragmentation for unicast data flowing through the mesh which will allow to run batman-adv over interfaces / connections that don\'t allow to increase the MTU beyond the standard Ethernet packet size of 1500 bytes. When the fragmentation is enabled batman-adv will automatically fragment over-sized packets and defragment them on the other end. Per default fragmentation is enabled and inactive if the packet fits but it is possible to deactivate the fragmentation entirely.'));
+ o.ucioption = 'fragmentation';
+ o.default = o.enabled;
+
+ o = s.taboption('mesh', form.ListValue, 'gw_mode', _('Gateway Mode'),
+ _('A batman-adv node can either run in server mode (sharing its internet connection with the mesh) or in client mode (searching for the most suitable internet connection in the mesh) or having the gateway support turned off entirely (which is the default setting).'));
+ o.value('off', _('Off'));
+ o.value('client', _('Client'));
+ o.value('server', _('Server'));
+ o.default = 'off';
+
+ o = s.taboption('mesh', form.Value, 'hop_penalty', _('Hop Penalty'),
+ _('The hop penalty setting allows to modify batman-adv\'s preference for multihop routes vs. short routes. The value is applied to the TQ of each forwarded OGM, thereby propagating the cost of an extra hop (the packet has to be received and retransmitted which costs airtime)'));
+ o.ucioption = 'hop_penalty';
+ o.datatype = 'min(1)';
+ o.placeholder = '30';
+ o.default = '30';
+
+ o = s.taboption('mesh', form.Flag, 'multicast_mode', _('Multicast Mode'),
+ _('Enables more efficient, group aware multicast forwarding infrastructure in batman-adv.'));
+ o.ucioption = 'multicast_mode';
+ o.default = o.enabled;
+
+ o = s.taboption('mesh', form.Flag, 'network_coding', _('Network Coding'),
+ _('When enabled network coding increases the WiFi throughput by combining multiple frames into a single frame, thus reducing the needed air time.'));
+ o.ucioption = 'network_coding';
+ o.default = o.enabled;
+ }
+});
diff --git a/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv_hardif.js b/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv_hardif.js
new file mode 100644
index 0000000000..b8b1263b97
--- /dev/null
+++ b/protocols/luci-proto-batman-adv/htdocs/luci-static/resources/protocol/batadv_hardif.js
@@ -0,0 +1,58 @@
+'use strict';
+'require uci';
+'require form';
+'require network';
+
+network.registerPatternVirtual(/^bat.+$/);
+
+return network.registerProtocol('batadv_hardif', {
+ getI18n: function() {
+ return _('Batman Interface');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || this.sid;
+ },
+
+ getOpkgPackage: function() {
+ return 'kmod-batman-adv';
+ },
+
+ isFloating: function() {
+ return false;
+ },
+
+ isVirtual: function() {
+ return false;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var dev = this.getL3Device() || this.getDevice(),
+ o;
+
+ o = s.taboption('general', form.ListValue, 'master', _('Batman Device'),
+ _('This is the batman-adv device where you want to link the physical Device from above to. If this list is empty, then you need to create one first. If you want to route mesh traffic over a wired network device, then please select it from the above Device selector. If you want to assign the batman-adv interface to a Wi-fi mesh then do not select a Device in the Device selector but rather go to the Wireless settings and select this Interface as a network from there.'));
+ var uciInterfaces = uci.sections('network', 'interface');
+
+ for (var i = 0; i < uciInterfaces.length; i++)
+ {
+ if (uciInterfaces[i].proto == 'batadv')
+ {
+ var x=uciInterfaces[i]['.name'];
+ o.value(x);
+ }
+ }
+
+ o = s.taboption('general', form.Value, 'mtu', _('Override MTU'));
+ o.placeholder = dev ? (dev.getMTU() || '1536') : '1536';
+ o.datatype = 'max(9200)';
+ }
+});
diff --git a/protocols/luci-proto-bonding/htdocs/luci-static/resources/protocol/bonding.js b/protocols/luci-proto-bonding/htdocs/luci-static/resources/protocol/bonding.js
index aadc2306d3..4c774290f0 100644
--- a/protocols/luci-proto-bonding/htdocs/luci-static/resources/protocol/bonding.js
+++ b/protocols/luci-proto-bonding/htdocs/luci-static/resources/protocol/bonding.js
@@ -24,14 +24,16 @@ function getSelectableSlaves(section_id) {
var slaves = L.toArray(uci.get('network', interfaces[j]['.name'], 'slaves'));
for (var k = 0; k < slaves.length; k++) {
- if (devices[i].ifname == slaves[k] && interfaces[j]['.name'] != section_id) {
- in_use = true;
+ if (devices[i].ifname == slaves[k] || devices[i].device == slaves[k]) {
+ if (interfaces[j]['.name'] != section_id) {
+ in_use = true;
+ }
}
}
}
}
if (in_use == false) {
- rv.push(devices[i].ifname);
+ devices[i].device == null ? rv.push(devices[i].ifname) : rv.push(devices[i].device)
}
}
}
@@ -322,11 +324,10 @@ return network.registerProtocol('bonding', {
o.value('layer3+4', _('Use upper layer protocol information (layer3+4)'));
o.value('encap2+3', _('Use XOR of hardware MAC addresses and IP addresses, rely on skb_flow_dissect (encap2+3)'));
o.value('encap3+4', _('Use upper layer protocol information, rely on skb_flow_dissect (encap3+4)'));
- o.depends('bonding_policy', 'balance-rr');
- o.depends('bonding_policy', 'active-backup');
- o.depends('bonding_policy', 'balance-tlb');
- o.depends('bonding_policy', 'balance-alb');
o.depends('bonding_policy', 'balance-xor');
+ o.depends('bonding_policy', 'balance-alb');
+ o.depends('bonding_policy', 'balance-tlb');
+ o.depends('bonding_policy', '802.3ad');
o = s.taboption('advanced', form.Value, 'resend_igmp',
_('Number of IGMP membership reports'),
diff --git a/protocols/luci-proto-external/Makefile b/protocols/luci-proto-external/Makefile
new file mode 100644
index 0000000000..b9b146d7bd
--- /dev/null
+++ b/protocols/luci-proto-external/Makefile
@@ -0,0 +1,8 @@
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for externally managed protocol
+LUCI_DEPENDS:=+external-protocol
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-external/htdocs/luci-static/resources/protocol/external.js b/protocols/luci-proto-external/htdocs/luci-static/resources/protocol/external.js
new file mode 100644
index 0000000000..06bd5f3f66
--- /dev/null
+++ b/protocols/luci-proto-external/htdocs/luci-static/resources/protocol/external.js
@@ -0,0 +1,47 @@
+'use strict';
+'require form';
+'require network';
+
+return network.registerProtocol('external', {
+ getI18n: function () {
+ return _('Externally managed interface');
+ },
+
+ getOpkgPackage: function() {
+ return "external-protocol";
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ renderFormOptions: function(s) {
+ var o;
+
+ o = s.taboption('general', form.Value, '_device', _('Device'));
+ o.ucioption = 'device';
+ o.optional = false;
+ o.rmempty = false;
+
+ o = s.taboption('general', form.Value, '_delay', _('Delay'), _('Afer making changes to network using external protocol, network must be manually restarted.'));
+ o.ucioption = 'delay';
+ o.placeholder = '10';
+ o.datatype = 'min(1)';
+ o.optional = true;
+ o.rmempty = true;
+
+ o = s.taboption('general', form.Value, '_searchdomain', _('Search domain'));
+ o.ucioption = 'searchdomain'
+ o.optional = true;
+ o.rmempty = true;
+ }
+
+});
diff --git a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gre.js b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gre.js
index e431bccd76..91837b29c3 100644
--- a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gre.js
+++ b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gre.js
@@ -46,6 +46,13 @@ return network.registerProtocol('gre', {
o = s.taboption('general', form.Value, 'ipaddr', _("Local IPv4 address"), _("The local IPv4 address over which the tunnel is created (optional)."));
o.optional = true;
o.datatype = 'ip4addr("nomask")';
+ o.load = function(section_id) {
+ return network.getWANNetworks().then(L.bind(function(nets) {
+ if (nets.length)
+ this.placeholder = nets[0].getIPAddr();
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
// -- advanced ---------------------------------------------------------------------
@@ -59,16 +66,16 @@ return network.registerProtocol('gre', {
o.placeholder = 1280;
o.datatype = 'range(68, 9200)';
- o = s.taboption('advanced', form.Value, 'ttl', _("Override TTL"), _("Specify a TTL (Time to Live) for the encapsulating packet other than the default (64) (optional)."));
+ o = s.taboption('advanced', form.Value, 'ttl', _("Override TTL"), _("Specify a TTL (Time to Live) for the encapsulating packet other than the default (64)."));
o.optional = true;
o.placeholder = 64;
o.datatype = 'min(1)';
- o = s.taboption('advanced', form.Value, 'tos', _('Override TOS'), _("Specify a TOS (Type of Service). Can be either <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value starting with <code>0x</code> (optional)."));
+ o = s.taboption('advanced', form.Value, 'tos', _("Override TOS"), _("Specify a TOS (Type of Service). Can be <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value <code>00..FF</code> (optional)."));
o.optional = true;
o.validate = function(section_id, value) {
- if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,2}$/) && !value.match(/^inherit$/i))
- return _('Invalid value');
+ if (value.length > 0 && !value.match(/^[a-f0-9]{1,2}$/i) && !value.match(/^inherit$/i))
+ return _("Invalid TOS value, expected 00..FF or inherit");
return true;
};
@@ -79,11 +86,14 @@ return network.registerProtocol('gre', {
o = s.taboption('advanced', form.Flag, 'nohostroute', _("No host route"), _("Do not create host route to peer (optional)."));
o.optional = true;
+ o = s.taboption('advanced', form.Flag, 'multicast', _("Multicast"), _("Enable support for multicast traffic (optional)."));
+ o.optional = true;
+
o = s.taboption('advanced', form.Value, 'ikey', _("Incoming key"), _("Key for incoming packets (optional)."));
o.optional = true;
o.datatype = 'integer';
- o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optinal)."));
+ o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optional)."));
o.optional = true;
o.datatype = 'integer';
diff --git a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gretap.js b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gretap.js
index 426b5d98df..e1e9c8c39c 100644
--- a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gretap.js
+++ b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/gretap.js
@@ -46,6 +46,13 @@ return network.registerProtocol('gretap', {
o = s.taboption('general', form.Value, 'ipaddr', _("Local IPv4 address"), _("The local IPv4 address over which the tunnel is created (optional)."));
o.optional = true;
o.datatype = 'ip4addr("nomask")';
+ o.load = function(section_id) {
+ return network.getWANNetworks().then(L.bind(function(nets) {
+ if (nets.length)
+ this.placeholder = nets[0].getIPAddr();
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
o = s.taboption('general', widgets.NetworkSelect, 'network', _("Network interface"), _("Logical network to which the tunnel will be added (bridged) (optional)."));
o.exclude = s.section;
@@ -69,11 +76,11 @@ return network.registerProtocol('gretap', {
o.placeholder = 64;
o.datatype = 'min(1)';
- o = s.taboption('advanced', form.Value, 'tos', _('Override TOS'), _("Specify a TOS (Type of Service). Can be either <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value starting with <code>0x</code> (optional)."));
+ o = s.taboption('advanced', form.Value, 'tos', _("Override TOS"), _("Specify a TOS (Type of Service). Can be <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value <code>00..FF</code> (optional)."));
o.optional = true;
o.validate = function(section_id, value) {
- if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,2}$/) && !value.match(/^inherit$/i))
- return _('Invalid value');
+ if (value.length > 0 && !value.match(/^[a-f0-9]{1,2}$/i) && !value.match(/^inherit$/i))
+ return _("Invalid TOS value, expected 00..FF or inherit");
return true;
};
@@ -84,11 +91,14 @@ return network.registerProtocol('gretap', {
o = s.taboption('advanced', form.Flag, 'nohostroute', _("No host route"), _("Do not create host route to peer (optional)."));
o.optional = true;
+ o = s.taboption('advanced', form.Flag, 'multicast', _("Multicast"), _("Enable support for multicast traffic (optional)."));
+ o.optional = true;
+
o = s.taboption('advanced', form.Value, 'ikey', _("Incoming key"), _("Key for incoming packets (optional)."));
o.optional = true;
o.datatype = 'integer';
- o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optinal)."));
+ o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optional)."));
o.optional = true;
o.datatype = 'integer';
diff --git a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6.js b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6.js
index bd9a43e27b..97b84754ff 100644
--- a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6.js
+++ b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6.js
@@ -46,6 +46,13 @@ return network.registerProtocol('grev6', {
o = s.taboption('general', form.Value, 'ip6addr', _("Local IPv6 address"), _("The local IPv6 address over which the tunnel is created (optional)."));
o.optional = true;
o.datatype = 'ip6addr("nomask")';
+ o.load = function(section_id) {
+ return network.getWAN6Networks().then(L.bind(function(nets) {
+ if (Array.isArray(nets) && nets.length)
+ this.placeholder = nets[0].getIP6Addr();
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
o = s.taboption('general', widgets.NetworkSelect, 'weakif', _("Source interface"), _("Logical network from which to select the local endpoint if local IPv6 address is empty and no WAN IPv6 is available (optional)."));
o.exclude = s.section;
@@ -69,11 +76,11 @@ return network.registerProtocol('grev6', {
o.placeholder = 64;
o.datatype = 'min(1)';
- o = s.taboption('advanced', form.Value, 'tos', _('Traffic Class'), _("Specify a Traffic Class. Can be either <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value starting with <code>0x</code> (optional)."));
+ o = s.taboption('advanced', form.Value, 'tos', _("Traffic Class"), _("Specify a TOS (Type of Service). Can be <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value <code>00..FF</code> (optional)."));
o.optional = true;
o.validate = function(section_id, value) {
- if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,2}$/) && !value.match(/^inherit$/i))
- return _('Invalid value');
+ if (value.length > 0 && !value.match(/^[a-f0-9]{1,2}$/i) && !value.match(/^inherit$/i))
+ return _("Invalid Traffic Class value, expected 00..FF or inherit");
return true;
};
@@ -81,11 +88,14 @@ return network.registerProtocol('grev6', {
o = s.taboption('advanced', form.Flag, 'nohostroute', _("No host route"), _("Do not create host route to peer (optional)."));
o.optional = true;
+ o = s.taboption('advanced', form.Flag, 'multicast', _("Multicast"), _("Enable support for multicast traffic (optional)."));
+ o.optional = true;
+
o = s.taboption('advanced', form.Value, 'ikey', _("Incoming key"), _("Key for incoming packets (optional)."));
o.optional = true;
o.datatype = 'integer';
- o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optinal)."));
+ o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optional)."));
o.optional = true;
o.datatype = 'integer';
diff --git a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6tap.js b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6tap.js
index 3b1a503719..6203f83557 100644
--- a/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6tap.js
+++ b/protocols/luci-proto-gre/htdocs/luci-static/resources/protocol/grev6tap.js
@@ -42,6 +42,13 @@ return network.registerProtocol('grev6tap', {
o = s.taboption('general', form.Value, 'peer6addr', _("Remote IPv6 address or FQDN"), _("The IPv6 address or the fully-qualified domain name of the remote tunnel end."));
o.optional = false;
o.datatype = 'or(hostname,ip6addr("nomask"))';
+ o.load = function(section_id) {
+ return network.getWAN6Networks().then(L.bind(function(nets) {
+ if (Array.isArray(nets) && nets.length)
+ this.placeholder = nets[0].getIP6Addr();
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
o = s.taboption('general', form.Value, 'ip6addr', _("Local IPv6 address"), _("The local IPv6 address over which the tunnel is created (optional)."));
o.optional = true;
@@ -74,11 +81,11 @@ return network.registerProtocol('grev6tap', {
o.placeholder = 64;
o.datatype = 'min(1)';
- o = s.taboption('advanced', form.Value, 'tos', _('Traffic Class'), _("Specify a Traffic Class. Can be either <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value starting with <code>0x</code> (optional)."));
+ o = s.taboption('advanced', form.Value, 'tos', _("Traffic Class"), _("Specify a Traffic Class. Can be <code>inherit</code> (the outer header inherits the value of the inner header) or an hexadecimal value <code>00..FF</code> (optional)."));
o.optional = true;
o.validate = function(section_id, value) {
- if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,2}$/) && !value.match(/^inherit$/i))
- return _('Invalid value');
+ if (value.length > 0 && !value.match(/^[a-f0-9]{1,2}$/i) && !value.match(/^inherit$/i))
+ return _("Invalid Traffic Class value, expected 00..FF or inherit");
return true;
};
@@ -86,11 +93,14 @@ return network.registerProtocol('grev6tap', {
o = s.taboption('advanced', form.Flag, 'nohostroute', _("No host route"), _("Do not create host route to peer (optional)."));
o.optional = true;
+ o = s.taboption('advanced', form.Flag, 'multicast', _("Multicast"), _("Enable support for multicast traffic (optional)."));
+ o.optional = true;
+
o = s.taboption('advanced', form.Value, 'ikey', _("Incoming key"), _("Key for incoming packets (optional)."));
o.optional = true;
o.datatype = 'integer';
- o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optinal)."));
+ o = s.taboption('advanced', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optional)."));
o.optional = true;
o.datatype = 'integer';
diff --git a/protocols/luci-proto-hnet/htdocs/luci-static/resources/protocol/hnet.js b/protocols/luci-proto-hnet/htdocs/luci-static/resources/protocol/hnet.js
index 84396ede08..5762ab7bf2 100644
--- a/protocols/luci-proto-hnet/htdocs/luci-static/resources/protocol/hnet.js
+++ b/protocols/luci-proto-hnet/htdocs/luci-static/resources/protocol/hnet.js
@@ -12,7 +12,7 @@ return network.registerProtocol('hnet', {
},
renderFormOptions: function(s) {
- var dev = this.getL2Device() || this.getDevice(), o;
+ var o;
o = s.taboption('general', form.ListValue, 'mode', _('Category'));
o.value('auto', _('Automatic'));
@@ -24,10 +24,6 @@ return network.registerProtocol('hnet', {
o.value('hybrid', _('Hybrid'));
o.default = 'auto';
- o = s.taboption('advanced', form.Value, 'ip6assign', _('IPv6 assignment length'), _('Assign a part of given length of every public IPv6-prefix to this interface'));
- o.datatype = 'max(128)';
- o.default = '64';
-
s.taboption('advanced', form.Value, 'link_id', _('IPv6 assignment hint'), _('Assign prefix parts using this hexadecimal subprefix ID for this interface.'));
o = s.taboption('advanced', form.Value, 'ip4assign', _('IPv4 assignment length'));
@@ -36,13 +32,5 @@ return network.registerProtocol('hnet', {
o = s.taboption('advanced', form.Value, 'dnsname', _('DNS-Label / FQDN'));
o.default = s.section;
-
- o = s.taboption('advanced', form.Value, 'macaddr', _('Override MAC address'));
- o.datatype = 'macaddr';
- o.placeholder = dev ? (dev.getMAC() || '') : '';
-
- o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
- o.datatype = 'max(9200)';
- o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
}
});
diff --git a/protocols/luci-proto-ipip/htdocs/luci-static/resources/protocol/ipip.js b/protocols/luci-proto-ipip/htdocs/luci-static/resources/protocol/ipip.js
index 7423a08585..44a0e20ac6 100644
--- a/protocols/luci-proto-ipip/htdocs/luci-static/resources/protocol/ipip.js
+++ b/protocols/luci-proto-ipip/htdocs/luci-static/resources/protocol/ipip.js
@@ -64,6 +64,10 @@ return network.registerProtocol('ipip', {
o.optional = true;
o.datatype = 'range(0, 255)';
- s.taboption('advanced', form.Flag, 'df', _("Don't Fragment"), _("Enable the DF (Don't Fragment) flag of the encapsulating packets."));
+ o = s.taboption('advanced', form.Flag, 'df', _("Don't Fragment"), _("Enable the DF (Don't Fragment) flag of the encapsulating packets."));
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Flag, 'nohostroute', _("No host route"), _("Do not create host route to peer (optional)."));
+ o.optional = true;
}
});
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/464xlat.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/464xlat.js
index a74708fd01..9cfdadd9a6 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/464xlat.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/464xlat.js
@@ -45,14 +45,6 @@ return network.registerProtocol('464xlat', {
o.nocreate = true;
o.exclude = s.section;
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'mtu', _('Use MTU on tunnel interface'));
o.placeholder = '1280';
o.datatype = 'max(9200)';
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6in4.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6in4.js
index f26ced7b40..92540570ef 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6in4.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6in4.js
@@ -82,14 +82,6 @@ return network.registerProtocol('6in4', {
o.password = true;
o.depends('_update', '1');
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'ttl', _('Use TTL on tunnel interface'));
o.placeholder = '64';
o.datatype = 'range(1,255)';
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6rd.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6rd.js
index 6a8f506bc9..415c3a89e7 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6rd.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6rd.js
@@ -62,14 +62,6 @@ return network.registerProtocol('6rd', {
o.placeholder = '0';
o.datatype = 'range(0,32)';
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'ttl', _('Use TTL on tunnel interface'));
o.placeholder = '64';
o.datatype = 'range(1,255)';
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6to4.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6to4.js
index abfe1c8fc4..f572562332 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6to4.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/6to4.js
@@ -46,14 +46,6 @@ return network.registerProtocol('6to4', {
}, this));
};
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'ttl', _('Use TTL on tunnel interface'));
o.placeholder = '64';
o.datatype = 'range(1,255)';
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dhcpv6.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dhcpv6.js
index eba58f4248..2e75cb8632 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dhcpv6.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dhcpv6.js
@@ -12,7 +12,7 @@ return network.registerProtocol('dhcpv6', {
},
renderFormOptions: function(s) {
- var dev = this.getL2Device() || this.getDevice(), o;
+ var o;
o = s.taboption('general', form.ListValue, 'reqaddress', _('Request IPv6-address'));
o.value('try');
@@ -30,29 +30,7 @@ return network.registerProtocol('dhcpv6', {
o.value('64');
o.default = 'auto';
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'ip6prefix', _('Custom delegated IPv6-prefix'));
- o.datatype = 'cidr6';
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
o = s.taboption('advanced', form.Value, 'clientid', _('Client ID to send when requesting DHCP'));
o.datatype = 'hexstring';
-
- o = s.taboption('advanced', form.Value, 'macaddr', _('Override MAC address'));
- o.datatype = 'macaddr';
- o.placeholder = dev ? (dev.getMAC() || '') : '';
-
- o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
- o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
- o.datatype = 'max(9200)';
}
});
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dslite.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dslite.js
index fa0e68ddc3..5f1fbf4f7a 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dslite.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/dslite.js
@@ -64,14 +64,6 @@ return network.registerProtocol('dslite', {
for (var i = 0; i < 256; i++)
o.value(i);
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'mtu', _('Use MTU on tunnel interface'));
o.placeholder = '1280';
o.datatype = 'max(9200)';
diff --git a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/map.js b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/map.js
index 006ebfbf08..82e0169210 100644
--- a/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/map.js
+++ b/protocols/luci-proto-ipv6/htdocs/luci-static/resources/protocol/map.js
@@ -40,7 +40,7 @@ return network.registerProtocol('map', {
renderFormOptions: function(s) {
var o;
- o = s.taboption('general', form.ListValue, 'type', _('Type'));
+ o = s.taboption('general', form.ListValue, 'maptype', _('Type'));
o.value('map-e', 'MAP-E');
o.value('map-t', 'MAP-T');
o.value('lw4o6', 'LW4over6');
@@ -77,14 +77,6 @@ return network.registerProtocol('map', {
o.nocreate = true;
o.exclude = s.section;
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'ttl', _('Use TTL on tunnel interface'));
o.placeholder = '64';
o.datatype = 'range(1,255)';
@@ -92,5 +84,7 @@ return network.registerProtocol('map', {
o = s.taboption('advanced', form.Value, 'mtu', _('Use MTU on tunnel interface'));
o.placeholder = '1280';
o.datatype = 'max(9200)';
+
+ o = s.taboption('advanced', form.Flag, 'legacymap', _('Use legacy MAP'), _('Use legacy MAP interface identifier format (draft-ietf-softwire-map-00) instead of RFC7597'));
}
});
diff --git a/protocols/luci-proto-mbim/Makefile b/protocols/luci-proto-mbim/Makefile
new file mode 100644
index 0000000000..edcc9ee5ab
--- /dev/null
+++ b/protocols/luci-proto-mbim/Makefile
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2008-2014 The LuCI Team <luci@lists.subsignal.org>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for MBIM
+LUCI_DEPENDS:=+umbim
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-mbim/htdocs/luci-static/resources/protocol/mbim.js b/protocols/luci-proto-mbim/htdocs/luci-static/resources/protocol/mbim.js
new file mode 100644
index 0000000000..f6f548cc65
--- /dev/null
+++ b/protocols/luci-proto-mbim/htdocs/luci-static/resources/protocol/mbim.js
@@ -0,0 +1,152 @@
+'use strict';
+'require rpc';
+'require form';
+'require network';
+
+var callFileList = rpc.declare({
+ object: 'file',
+ method: 'list',
+ params: [ 'path' ],
+ expect: { entries: [] },
+ filter: function(list, params) {
+ var rv = [];
+ for (var i = 0; i < list.length; i++)
+ if (list[i].name.match(/^cdc-wdm/))
+ rv.push(params.path + list[i].name);
+ return rv.sort();
+ }
+});
+
+network.registerPatternVirtual(/^mbim-.+$/);
+
+return network.registerProtocol('mbim', {
+ getI18n: function() {
+ return _('MBIM Cellular');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || 'mbim-%s'.format(this.sid);
+ },
+
+ getOpkgPackage: function() {
+ return 'umbim';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var dev = this.getL3Device() || this.getDevice(), o;
+
+ o = s.taboption('general', form.Value, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
+ o.rmempty = false;
+ o.load = function(section_id) {
+ return callFileList('/dev/').then(L.bind(function(devices) {
+ for (var i = 0; i < devices.length; i++)
+ this.value(devices[i]);
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
+
+ o = s.taboption('general', form.Value, 'apn', _('APN'));
+ o.validate = function(section_id, value) {
+ if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+ return _('Invalid APN provided');
+
+ return true;
+ };
+
+ o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
+
+ o = s.taboption('general', form.ListValue, 'auth', _('Authentication Type'));
+ o.value('both', _('PAP/CHAP'));
+ o.value('pap', _('PAP'));
+ o.value('chap', _('CHAP'));
+ o.value('none', _('None'));
+ o.default = 'none';
+
+ o = s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));
+ o.depends('auth', 'pap');
+ o.depends('auth', 'chap');
+ o.depends('auth', 'both');
+
+ o = s.taboption('general', form.Value, 'password', _('PAP/CHAP password'));
+ o.depends('auth', 'pap');
+ o.depends('auth', 'chap');
+ o.depends('auth', 'both');
+ o.password = true;
+
+ o = s.taboption('general', form.ListValue, 'pdptype', _('PDP Type'));
+ o.value('ipv4v6', _('IPv4/IPv6'));
+ o.value('ipv4', _('IPv4'));
+ o.value('ipv6', _('IPv6'));
+ o.default = 'ipv4v6';
+
+ if (L.hasSystemFeature('ipv6')) {
+ o = s.taboption('advanced', form.Flag, 'mbim_ipv6', _('Enable IPv6 negotiation'));
+ o.ucioption = 'ipv6';
+ o.default = o.enabled;
+ }
+
+ o = s.taboption('advanced', form.ListValue, 'dhcp', _('Use DHCP'));
+ o.value('', _('Automatic'));
+ o.value('0', _('Disabled'));
+ o.value('1', _('Enabled'));
+ o.depends('pdptype', 'ipv4');
+ o.depends('pdptype', 'ipv4v6');
+ o.default = '';
+
+ if (L.hasSystemFeature('ipv6')) {
+ o = s.taboption('advanced', form.ListValue, 'dhcpv6', _('Use DHCPv6'));
+ o.value('', _('Automatic'));
+ o.value('0', _('Disabled'));
+ o.value('1', _('Enabled'));
+ o.depends('pdptype', 'ipv6');
+ o.depends('pdptype', 'ipv4v6');
+ o.default = '';
+ }
+
+ o = s.taboption('advanced', form.Value, 'delay', _('Modem init timeout'), _('Maximum amount of seconds to wait for the modem to become ready'));
+ o.placeholder = '10';
+ o.datatype = 'min(1)';
+
+ o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
+ o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
+ o.datatype = 'max(9200)';
+
+ o = s.taboption('advanced', form.Flag, 'defaultroute',
+ _('Use default gateway'),
+ _('If unchecked, no default route is configured'));
+ o.default = o.enabled;
+
+ o = s.taboption('advanced', form.Value, 'metric',
+ _('Use gateway metric'));
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.depends('defaultroute', '1');
+
+ o = s.taboption('advanced', form.Flag, 'peerdns',
+ _('Use DNS servers advertised by peer'),
+ _('If unchecked, the advertised DNS server addresses are ignored'));
+ o.default = o.enabled;
+
+ o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
+ o.depends('peerdns', '0');
+ o.datatype = 'ipaddr';
+ }
+});
diff --git a/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js
new file mode 100644
index 0000000000..b8558b885b
--- /dev/null
+++ b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/modemmanager_helper.js
@@ -0,0 +1,101 @@
+'use strict';
+'require baseclass';
+'require fs';
+
+return baseclass.extend({
+
+ _mmcliBin: '/usr/bin/mmcli',
+
+ _emptyStringValue: '--',
+
+ _parseIndex: function (dbusPath) {
+ var index = dbusPath.split('/').slice(-1);
+ return parseInt(index);
+ },
+
+ _parseOutput: function (output) {
+ try {
+ return this._removeEmptyStrings(JSON.parse(output));
+ } catch (err) {
+ return null;
+ }
+ },
+
+ _removeEmptyStrings: function (obj) {
+ if (obj == null) {
+ return obj;
+ }
+
+ if (typeof obj == 'string') {
+ if (obj == this._emptyStringValue) {
+ obj = null;
+ }
+ } else if (Array.isArray()) {
+ obj = obj.map(L.bind(function (it) {
+ return this._removeEmptyStrings(it);
+ }, this));
+ } else {
+ var keys = Object.keys(obj);
+ keys.forEach(L.bind(function (key) {
+ obj[key] = this._removeEmptyStrings(obj[key]);
+ }, this));
+ }
+
+ return obj;
+ },
+
+ getModems: function () {
+ return fs.exec_direct(this._mmcliBin, [ '-L', '-J' ]).then(L.bind(function (res) {
+ var json = this._parseOutput(res);
+ if (json == null) {
+ return [];
+ }
+ var modems = json['modem-list'];
+ var tasks = [];
+
+ modems.forEach(L.bind(function (modem) {
+ var index = this._parseIndex(modem);
+ if (!isNaN(index)) {
+ tasks.push(this.getModem(index));
+ }
+ }, this));
+ return Promise.all(tasks);
+ }, this));
+ },
+
+ getModem: function (index) {
+ return fs.exec_direct(this._mmcliBin, [ '-m', index, '-J' ]).then(L.bind(function (modem) {
+ return this._parseOutput(modem);
+ }, this));
+ },
+
+ getModemSims: function (modem) {
+ var tasks = [];
+ var simSlots = modem.generic['sim-slots'];
+ var sim = modem.generic.sim;
+ if (sim != null && !simSlots.includes(sim)) {
+ simSlots.push(sim);
+ }
+
+ simSlots.forEach(L.bind(function (modem) {
+ var index = this._parseIndex(modem);
+ if (!isNaN(index)) {
+ tasks.push(this.getSim(index));
+ }
+ }, this));
+ return Promise.all(tasks);
+ },
+
+ getSim: function (index) {
+ return fs.exec_direct(this._mmcliBin, [ '-i', index, '-J' ]).then(L.bind(function (sim) {
+ return this._parseOutput(sim);
+ }, this));
+ },
+
+ getModemLocation: function (modem) {
+ var index = this._parseIndex(modem['dbus-path']);
+ return fs.exec_direct(this._mmcliBin, [ '-m', index, '--location-get', '-J' ]).then(L.bind(function (location) {
+ return this._parseOutput(location);
+ }, this));
+ }
+});
diff --git a/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js
index ad8cc1484a..b13dd310c7 100644
--- a/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js
+++ b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/protocol/modemmanager.js
@@ -2,50 +2,19 @@
'require fs';
'require form';
'require network';
-
-function getModemList() {
- return fs.exec_direct('/usr/bin/mmcli', [ '-L' ]).then(function(res) {
- var lines = (res || '').split(/\n/),
- tasks = [];
-
- for (var i = 0; i < lines.length; i++) {
- var m = lines[i].match(/\/Modem\/(\d+)/);
- if (m)
- tasks.push(fs.exec_direct('/usr/bin/mmcli', [ '-m', m[1] ]));
- }
-
- return Promise.all(tasks).then(function(res) {
- var modems = [];
-
- for (var i = 0; i < res.length; i++) {
- var man = res[i].match(/manufacturer: ([^\n]+)/),
- mod = res[i].match(/model: ([^\n]+)/),
- dev = res[i].match(/device: ([^\n]+)/);
-
- if (dev) {
- modems.push({
- device: dev[1].trim(),
- manufacturer: (man ? man[1].trim() : '') || '?',
- model: (mod ? mod[1].trim() : '') || dev[1].trim()
- });
- }
- }
-
- return modems;
- });
- });
-}
+'require modemmanager_helper as helper';
network.registerPatternVirtual(/^mobiledata-.+$/);
network.registerErrorCode('MM_CONNECT_FAILED', _('Connection attempt failed.'));
-network.registerErrorCode('MM_DISCONNECT_IN_PROGRESS', _('Modem disconnection in progress. Please wait.'));
network.registerErrorCode('MM_CONNECT_IN_PROGRESS', _('Modem connection in progress. Please wait. This process will timeout after 2 minutes.'));
-network.registerErrorCode('MM_TEARDOWN_IN_PROGRESS', _('Modem bearer teardown in progress.'));
-network.registerErrorCode('MM_MODEM_DISABLED', _('Modem is disabled.'));
network.registerErrorCode('DEVICE_NOT_MANAGED', _('Device not managed by ModemManager.'));
network.registerErrorCode('INVALID_BEARER_LIST', _('Invalid bearer list. Possibly too many bearers created. This protocol supports one and only one bearer.'));
network.registerErrorCode('UNKNOWN_METHOD', _('Unknown and unsupported connection method.'));
network.registerErrorCode('DISCONNECT_FAILED', _('Disconnection attempt failed.'));
+network.registerErrorCode('MM_INVALID_ALLOWED_MODES_LIST', _('Unable to set allowed mode list.'));
+network.registerErrorCode('MM_NO_PREFERRED_MODE_CONFIGURED', _('No preferred mode configuration found.'));
+network.registerErrorCode('MM_NO_ALLOWED_MODE_CONFIGURED', _('No allowed mode configuration found.'));
+network.registerErrorCode('MM_FAILED_SETTING_PREFERRED_MODE', _('Unable to set preferred mode.'));
return network.registerProtocol('modemmanager', {
getI18n: function() {
@@ -79,19 +48,33 @@ return network.registerProtocol('modemmanager', {
renderFormOptions: function(s) {
var dev = this.getL3Device() || this.getDevice(), o;
- o = s.taboption('general', form.ListValue, 'device', _('Modem device'));
+ o = s.taboption('general', form.ListValue, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
o.rmempty = false;
o.load = function(section_id) {
- return getModemList().then(L.bind(function(devices) {
- for (var i = 0; i < devices.length; i++)
- this.value(devices[i].device,
- '%s - %s'.format(devices[i].manufacturer, devices[i].model));
+ return helper.getModems().then(L.bind(function(devices) {
+ for (var i = 0; i < devices.length; i++) {
+ var generic = devices[i].modem.generic;
+ this.value(generic.device,
+ '%s - %s'.format(generic.manufacturer, generic.model));
+ }
return form.Value.prototype.load.apply(this, [section_id]);
}, this));
};
- s.taboption('general', form.Value, 'apn', _('APN'));
- s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o = s.taboption('general', form.Value, 'apn', _('APN'));
+ o.validate = function(section_id, value) {
+ if (value == null || value == '')
+ return true;
+
+ if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+ return _('Invalid APN provided');
+
+ return true;
+ };
+
+ o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
o = s.taboption('general', form.ListValue, 'auth', _('Authentication Type'));
o.value('both', _('PAP/CHAP (both)'));
@@ -100,6 +83,45 @@ return network.registerProtocol('modemmanager', {
o.value('none', _('None'));
o.default = 'none';
+ o = s.taboption('general', form.ListValue, 'allowedmode', _('Allowed network technology'),
+ _('Setting the allowed network technology.'));
+ o.value('2g');
+ o.value('3g');
+ o.value('3g|2g');
+ o.value('4g');
+ o.value('4g|2g');
+ o.value('4g|3g');
+ o.value('4g|3g|2g');
+ o.value('5g');
+ o.value('5g|2g');
+ o.value('5g|3g');
+ o.value('5g|3g|2g');
+ o.value('5g|4g');
+ o.value('5g|4g|2g');
+ o.value('5g|4g|3g');
+ o.value('5g|4g|3g|2g');
+ o.value('',_('any'));
+ o.default = '';
+
+ o = s.taboption('general', form.ListValue, 'preferredmode', _('Preferred network technology'),
+ _('Setting the preferred network technology.'));
+ o.value('2g');
+ o.value('3g');
+ o.value('4g');
+ o.value('5g');
+ o.value('none', _('None'));
+ o.depends('allowedmode','3g|2g');
+ o.depends('allowedmode','4g|2g');
+ o.depends('allowedmode','4g|3g');
+ o.depends('allowedmode','4g|3g|2g');
+ o.depends('allowedmode','5g|2g');
+ o.depends('allowedmode','5g|3g');
+ o.depends('allowedmode','5g|3g|2g');
+ o.depends('allowedmode','5g|4g');
+ o.depends('allowedmode','5g|4g|2g');
+ o.depends('allowedmode','5g|4g|3g');
+ o.depends('allowedmode','5g|4g|3g|2g');
+
o = s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));
o.depends('auth', 'pap');
o.depends('auth', 'chap');
@@ -117,11 +139,12 @@ return network.registerProtocol('modemmanager', {
o.value('ipv6', _('IPv6 only'));
o.default = 'ipv4v6';
- s.taboption('general', form.Value, 'signalrate', _('Signal refresh rate'));
-
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
o.datatype = 'max(9200)';
+
+ o = s.taboption('general', form.Value, 'signalrate', _('Signal Refresh Rate'), _("In seconds"));
+ o.datatype = 'uinteger';
s.taboption('general', form.Value, 'metric', _('Gateway metric'));
diff --git a/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js
new file mode 100644
index 0000000000..3db79051d3
--- /dev/null
+++ b/protocols/luci-proto-modemmanager/htdocs/luci-static/resources/view/modemmanager/status.js
@@ -0,0 +1,181 @@
+'use strict';
+'require ui';
+'require view';
+'require poll';
+'require dom';
+'require modemmanager_helper as helper';
+
+return view.extend({
+ load: function() {
+ return helper.getModems().then(function (modems) {
+ return Promise.all(modems.filter(function (modem){
+ return modem != null;
+ }).map(function (modem) {
+ return helper.getModemSims(modem.modem).then(function (sims) {
+ modem.sims = sims.filter(function (sim) {
+ return sim != null;
+ });
+
+ return helper.getModemLocation(modem.modem).then(function (location) {
+ modem.location = location;
+ return modem;
+ });
+ });
+ }));
+ });
+ },
+
+ pollData: function (container) {
+ poll.add(L.bind(function () {
+ return this.load().then(L.bind(function (modems) {
+ dom.content(container, this.renderContent(modems));
+ }, this));
+ }, this));
+ },
+
+ renderSections: function (name, sections) {
+ if (sections.length == 0) {
+ sections.push(E('div', { 'class': 'cbi-section' }, [
+ E('span', {}, _('Section %s is empty.').format(name))
+ ]));
+ }
+
+ return E('div', { 'class': 'cbi-section' }, [
+ E('h1', {}, name),
+ ...sections
+ ]);
+ },
+
+ renderSection: function (name, table) {
+ var rowNodes = table.filter(function (row) {
+ return row[1] != null;
+ }).map(function (row) {
+ return E('tr', { 'class': 'tr' }, [
+ E('td', { 'class': 'td', 'width': '33%' }, E('strong', {}, [row[0]])),
+ E('td', { 'class': 'td' }, [row[1]])
+ ]);
+ });
+
+ var tableNode;
+ if (rowNodes.length == 0) {
+ tableNode = E('div', { 'class': 'cbi-section' }, [
+ E('span', {}, _('Section %s is empty.').format(name))
+ ])
+ } else {
+ tableNode = E('table', { 'class': 'table', }, rowNodes);
+ }
+
+ return E('div', { 'class': 'cbi-section' }, [
+ E('h2', {}, [name]),
+ tableNode
+ ]);
+ },
+
+ renderContent: function (modems) {
+ var node = E('div', {}, E('div'));
+
+ modems.forEach(L.bind(function (modem) {
+ var generic = modem.modem.generic;
+ var modem3gpp = modem.modem['3gpp'];
+
+ var modemSection = this.renderSection(_('Modem Info'), [
+ [_('Manufacturer'), generic.manufacturer],
+ [_('Model'), generic.model],
+ [_('Revision'), generic.revision],
+ [E('abbr', { 'title': _('International Mobile Station Equipment Identity') }, [
+ _('IMEI')
+ ]), modem3gpp.imei],
+ [_('Device Identifier'), generic['device-identifier']],
+ [_('Power State'), generic['power-state']],
+ [_('State'), generic.state],
+ [_('Failed Reason'), generic['state-failed-reason']]
+ ]);
+
+ var ownNumbersStr = generic['own-numbers'].join(', ');
+ var accessTechnologiesStr = generic['access-technologies'].join(', ');
+ var signalQualityValue = parseInt(generic['signal-quality'].value);
+ var networkSection = this.renderSection(_('Network Registration'), [
+ [_('Own Numbers'), ownNumbersStr],
+ [_('Access Technologies'), accessTechnologiesStr],
+ [_('Operator') , modem3gpp['operator-name']],
+ [_('Operator Code'), modem3gpp['operator-code']],
+ [_('Registration State'), modem3gpp['registration-state']],
+ [_('Packet Service State'), modem3gpp['packet-service-state']],
+ [_('Signal Quality'), E('div', { 'class': 'cbi-progressbar', 'title': '%d %'.format(signalQualityValue) }, [
+ E('div', { 'style': 'width: %d%%'.format(signalQualityValue) })
+ ])]
+ ]);
+
+ var location3gpp = {};
+ if (modem.location != null) {
+ location3gpp = modem.location.modem.location['3gpp'];
+ }
+ var locationSection = this.renderSection(_('Cell Location'), [
+ [E('abbr', { 'title': _('Cell ID') }, [
+ 'CID'
+ ]), location3gpp.cid],
+ [E('abbr', { 'title': _('Location Area Code') }, [
+ 'LAC'
+ ]), location3gpp.lac],
+ [E('abbr', { 'title': _('Mobile Country Code') }, [
+ 'MCC'
+ ]), location3gpp.mcc],
+ [E('abbr', { 'title': _('Mobile Network Code') }, [
+ 'MNC'
+ ]), location3gpp.mnc],
+ [E('abbr', { 'title': _('Tracking Area Code') }, [
+ 'TAC'
+ ]), location3gpp.tac]
+ ]);
+
+ var simTables = modem.sims.map(function (sim) {
+ var properties = sim.sim.properties;
+ return [
+ [_('Active'), properties.active],
+ [_('Operator Name'), properties['operator-name']],
+ [E('abbr', { 'title': _('Integrated Circuit Card Identifier') }, [
+ 'ICCID'
+ ]), properties.iccid],
+ [E('abbr', { 'title': _('International Mobile Subscriber Identity') }, [
+ 'IMSI'
+ ]), properties.imsi]
+ ];
+ });
+ var simSubSections = simTables.map(L.bind(function (table, index) {
+ return this.renderSection(_('SIM %d').format(index + 1), table)
+ }, this));
+ var simSection = this.renderSections(_('SIMs'), simSubSections);
+
+ var sections = [
+ E('div', { 'class': 'cbi-map-descr'}, []),
+ modemSection,
+ networkSection,
+ locationSection,
+ simSection
+ ].filter(function (section) {
+ return section != null;
+ });
+ node.firstElementChild.appendChild(E('div', { 'data-tab': generic.device, 'data-tab-title': generic.device }, sections));
+ }, this));
+ ui.tabs.initTabGroup(node.firstElementChild.childNodes);
+
+ return node;
+ },
+
+ render: function (modems) {
+ var content = E([], [
+ E('h2', {}, [_('Mobile Service')]),
+ E('div')
+ ]);
+ var container = content.lastElementChild;
+
+ dom.content(container, this.renderContent(modems));
+ this.pollData(container);
+
+ return content;
+ },
+
+ handleSaveApply: null,
+ handleSave: null,
+ handleReset: null
+});
diff --git a/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json b/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json
new file mode 100644
index 0000000000..7104f221ce
--- /dev/null
+++ b/protocols/luci-proto-modemmanager/root/usr/share/luci/menu.d/luci-proto-modemmanager.json
@@ -0,0 +1,13 @@
+{
+ "admin/status/modemmanager": {
+ "title": "Mobile Service",
+ "order": 10,
+ "action": {
+ "type": "view",
+ "path": "modemmanager/status"
+ },
+ "depends": {
+ "acl": [ "luci-proto-modemmanager" ]
+ }
+ }
+}
diff --git a/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json b/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json
index 716f4c4655..cde3e9cbb0 100644
--- a/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json
+++ b/protocols/luci-proto-modemmanager/root/usr/share/rpcd/acl.d/luci-proto-modemmanager.json
@@ -4,8 +4,10 @@
"read": {
"cgi-io": [ "exec" ],
"file": {
- "/usr/bin/mmcli -L": [ "exec" ],
- "/usr/bin/mmcli -m [0-9]": [ "exec" ]
+ "/usr/bin/mmcli -L -J": [ "exec" ],
+ "/usr/bin/mmcli -m [0-9]* -J": [ "exec" ],
+ "/usr/bin/mmcli -i [0-9]* -J": [ "exec" ],
+ "/usr/bin/mmcli -m [0-9]* --location-get -J": [ "exec" ]
}
}
}
diff --git a/protocols/luci-proto-ncm/htdocs/luci-static/resources/protocol/ncm.js b/protocols/luci-proto-ncm/htdocs/luci-static/resources/protocol/ncm.js
index 3ab6c01d61..28d65fceb1 100644
--- a/protocols/luci-proto-ncm/htdocs/luci-static/resources/protocol/ncm.js
+++ b/protocols/luci-proto-ncm/htdocs/luci-static/resources/protocol/ncm.js
@@ -18,12 +18,16 @@ var callFileList = rpc.declare({
});
network.registerPatternVirtual(/^ncm-.+$/);
-network.registerErrorCode('CONFIGURE_FAILED', _('Configuration failed'));
-network.registerErrorCode('DISCONNECT_FAILED', _('Disconnection attempt failed'));
-network.registerErrorCode('FINALIZE_FAILED', _('Finalizing failed'));
-network.registerErrorCode('GETINFO_FAILED', _('Modem information query failed'));
-network.registerErrorCode('INITIALIZE_FAILED', _('Initialization failure'));
-network.registerErrorCode('SETMODE_FAILED', _('Setting operation mode failed'));
+network.registerErrorCode('CONFIGURE_FAILED', _('Failed to configure modem'));
+network.registerErrorCode('CONNECT_FAILED', _('Failed to connect'));
+network.registerErrorCode('DISCONNECT_FAILED', _('Failed to disconnect'));
+network.registerErrorCode('FINALIZE_FAILED', _('Finalizing failed'));
+network.registerErrorCode('GETINFO_FAILED', _('Failed to get modem information'));
+network.registerErrorCode('INITIALIZE_FAILED', _('Failed to initialize modem'));
+network.registerErrorCode('NO_DEVICE', _('No control device specified'));
+network.registerErrorCode('NO_IFACE', _('The interface could not be found'));
+network.registerErrorCode('PIN_FAILED', _('Unable to verify PIN'));
+network.registerErrorCode('SETMODE_FAILED', _('Failed to set operating mode'));
network.registerErrorCode('UNSUPPORTED_MODEM', _('Unsupported modem'));
return network.registerProtocol('ncm', {
@@ -58,7 +62,8 @@ return network.registerProtocol('ncm', {
renderFormOptions: function(s) {
var o;
- o = s.taboption('general', form.Value, 'device', _('Modem device'));
+ o = s.taboption('general', form.Value, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
o.rmempty = false;
o.load = function(section_id) {
return callFileList('/dev/').then(L.bind(function(devices) {
@@ -68,7 +73,7 @@ return network.registerProtocol('ncm', {
}, this));
};
- o = s.taboption('general', form.Value, 'service', _('Service Type'));
+ o = s.taboption('general', form.Value, 'mode', _('Network Mode'));
o.value('', _('Modem default'));
o.value('preferlte', _('Prefer LTE'));
o.value('preferumts', _('Prefer UMTS'));
@@ -83,41 +88,36 @@ return network.registerProtocol('ncm', {
o.value('IPV4V6', _('IPv4+IPv6'));
o.value('IPV6', _('IPv6'));
- s.taboption('general', form.Value, 'apn', _('APN'));
- s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o = s.taboption('general', form.Value, 'apn', _('APN'));
+ o.validate = function(section_id, value) {
+ if (value == null || value == '')
+ return true;
+
+ if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+ return _('Invalid APN provided');
+
+ return true;
+ };
+
+ o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
+
s.taboption('general', form.Value, 'username', _('PAP/CHAP username'));
o = s.taboption('general', form.Value, 'password', _('PAP/CHAP password'));
o.password = true;
- o = s.taboption('general', form.Value, 'dialnumber', _('Dial number'));
- o.placeholder = '*99***1#';
-
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Value, 'delay', _('Modem init timeout'), _('Maximum amount of seconds to wait for the modem to become ready'));
- o.placeholder = '10';
- o.datatype = 'min(1)';
-
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
+ o = s.taboption('advanced', form.Value, 'delay', _('Modem init timeout'), _('Amount of seconds to wait for the modem to become ready'));
o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
+ o.datatype = 'min(0)';
}
});
diff --git a/protocols/luci-proto-nebula/Makefile b/protocols/luci-proto-nebula/Makefile
new file mode 100644
index 0000000000..7b29b54b6d
--- /dev/null
+++ b/protocols/luci-proto-nebula/Makefile
@@ -0,0 +1,17 @@
+# Copyright 2021 Stan Grishin (stangri@melmac.ca)
+# This is free software, licensed under the GNU General Public License v3.
+
+include $(TOPDIR)/rules.mk
+
+PKG_LICENSE:=GPL-3.0-or-later
+PKG_MAINTAINER:=Stan Grishin <stangri@melmac.ca>
+PKG_VERSION:=1.6.1-1
+
+LUCI_TITLE:=Support for Nebula
+LUCI_DESCRIPTION:=Provides Web UI for Nebula protocol/interface.
+LUCI_DEPENDS:=+nebula +nebula-proto
+LUCI_PKGARCH:=all
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-nebula/htdocs/luci-static/resources/protocol/nebula.js b/protocols/luci-proto-nebula/htdocs/luci-static/resources/protocol/nebula.js
new file mode 100644
index 0000000000..409416dd49
--- /dev/null
+++ b/protocols/luci-proto-nebula/htdocs/luci-static/resources/protocol/nebula.js
@@ -0,0 +1,50 @@
+'use strict';
+'require ui';
+'require uci';
+'require rpc';
+'require form';
+'require network';
+
+return network.registerProtocol('nebula', {
+ getI18n: function () {
+ return _('Nebula Network');
+ },
+
+ getIfname: function () {
+ return this._ubus('l3_device') || this.sid;
+ },
+
+ getOpkgPackage: function () {
+ return 'nebula';
+ },
+
+ isFloating: function () {
+ return true;
+ },
+
+ isVirtual: function () {
+ return true;
+ },
+
+ getDevices: function () {
+ return null;
+ },
+
+ containsDevice: function (ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function (s) {
+ var o;
+
+ o = s.taboption('general', form.Value, 'config_file', _('Config File'), _('Required. Path to the .yml config file for this interface.'));
+ o.rmempty = false;
+
+ },
+
+ deleteConfiguration: function () {
+ uci.sections('network', 'nebula_%s'.format(this.sid), function (s) {
+ uci.remove('network', s['.name']);
+ });
+ }
+});
diff --git a/protocols/luci-proto-openconnect/Makefile b/protocols/luci-proto-openconnect/Makefile
index 31f52749b9..9f12bcdedf 100644
--- a/protocols/luci-proto-openconnect/Makefile
+++ b/protocols/luci-proto-openconnect/Makefile
@@ -7,7 +7,7 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for OpenConnect VPN
-LUCI_DEPENDS:=+openconnect
+LUCI_DEPENDS:=+openconnect +luci-lua-runtime
include ../../luci.mk
diff --git a/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js
index 1f07f97513..929d50f063 100644
--- a/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js
+++ b/protocols/luci-proto-openconnect/htdocs/luci-static/resources/protocol/openconnect.js
@@ -2,6 +2,7 @@
'require rpc';
'require form';
'require network';
+'require validation';
var callGetCertificateFiles = rpc.declare({
object: 'luci.openconnect',
@@ -37,8 +38,8 @@ function sanitizeCert(s) {
}
function validateCert(priv, section_id, value) {
- var beg = priv ? /^-----BEGIN RSA PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/,
- end = priv ? /^-----END RSA PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/,
+ var beg = priv ? /^-----BEGIN (RSA )?PRIVATE KEY-----$/ : /^-----BEGIN CERTIFICATE-----$/,
+ end = priv ? /^-----END (RSA )?PRIVATE KEY-----$/ : /^-----END CERTIFICATE-----$/,
lines = value.trim().split(/[\r\n]/),
start = false,
i;
@@ -93,8 +94,48 @@ return network.registerProtocol('openconnect', {
certLoadPromise = null,
o;
+ o = s.taboption('general', form.ListValue, 'vpn_protocol', _('VPN Protocol'));
+ o.value('anyconnect', 'Cisco AnyConnect SSL VPN');
+ o.value('nc', 'Juniper Network Connect');
+ o.value('gp', 'GlobalProtect SSL VPN');
+ o.value('pulse', 'Pulse Connect Secure SSL VPN');
+
o = s.taboption('general', form.Value, 'server', _('VPN Server'));
- o.datatype = 'host(0)';
+ o.validate = function(section_id, value) {
+ var m = String(value).match(/^(?:(\w+):\/\/|)(?:\[([0-9a-f:.]{2,45})\]|([^\/:]+))(?::([0-9]{1,5}))?(?:\/.*)?$/i);
+
+ if (!m)
+ return _('Invalid server URL');
+
+ if (m[1] != null) {
+ if (!m[1].match(/^(?:http|https|socks|socks4|socks5)$/i))
+ return _('Unsupported protocol');
+ }
+
+ if (m[2] != null) {
+ if (!validation.parseIPv6(m[2]))
+ return _('Invalid IPv6 address');
+ }
+
+ if (m[3] != null) {
+ if (!validation.parseIPv4(m[3])) {
+ if (!(m[3].length <= 253 &&
+ (m[3].match(/^[a-zA-Z0-9_]+$/) != null ||
+ (m[3].match(/^[a-zA-Z0-9_][a-zA-Z0-9_\-.]*[a-zA-Z0-9]$/) &&
+ m[3].match(/[^0-9.]/)))))
+ return _('Invalid hostname or IPv4 address');
+ }
+ }
+
+ if (m[4] != null) {
+ var p = +m[4];
+
+ if (p < 0 || p > 65535)
+ return _('Invalid port');
+ }
+
+ return true;
+ };
o = s.taboption('general', form.Value, 'port', _('VPN Server port'));
o.placeholder = '443';
@@ -111,6 +152,9 @@ return network.registerProtocol('openconnect', {
o = s.taboption('general', form.Value, 'password2', _('Password2'));
o.password = true;
+ o = s.taboption('general', form.Value, 'proxy', _('Proxy Server'));
+ o.optional = true;
+
o = s.taboption('general', form.TextValue, 'usercert', _('User certificate (PEM encoded)'));
o.rows = 10;
o.monospace = true;
@@ -147,17 +191,14 @@ return network.registerProtocol('openconnect', {
return callSetCertificateFiles(section_id, null, null, sanitizeCert(value));
};
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', '1');
-
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.optional = true;
o.placeholder = 1406;
o.datatype = 'range(68, 9200)';
+
+ o = s.taboption('advanced', form.Value, 'reconnect_timeout', _('Reconnect Timeout'));
+ o.optional = true;
+ o.placeholder = 300;
+ o.datatype = 'min(10)';
}
});
diff --git a/protocols/luci-proto-openfortivpn/Makefile b/protocols/luci-proto-openfortivpn/Makefile
new file mode 100644
index 0000000000..fbdad18b3b
--- /dev/null
+++ b/protocols/luci-proto-openfortivpn/Makefile
@@ -0,0 +1,14 @@
+#
+# Copyright (C) 2008-2020 The LuCI Team <luci@lists.subsignal.org>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for OpenFortivpn
+LUCI_DEPENDS:=+openfortivpn +luci-lua-runtime
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-openfortivpn/htdocs/luci-static/resources/protocol/openfortivpn.js b/protocols/luci-proto-openfortivpn/htdocs/luci-static/resources/protocol/openfortivpn.js
new file mode 100644
index 0000000000..530eeea4ff
--- /dev/null
+++ b/protocols/luci-proto-openfortivpn/htdocs/luci-static/resources/protocol/openfortivpn.js
@@ -0,0 +1,175 @@
+'use strict';
+'require rpc';
+'require form';
+'require network';
+'require tools.widgets as widgets';
+
+var callGetCertificateFiles = rpc.declare({
+ object: 'luci.openfortivpn',
+ method: 'getCertificates',
+ params: [ 'interface' ],
+ expect: { '': {} }
+});
+
+var callSetCertificateFiles = rpc.declare({
+ object: 'luci.openfortivpn',
+ method: 'setCertificates',
+ params: [ 'interface', 'user_cert', 'user_key', 'ca_file' ],
+ expect: { '': {} }
+});
+
+network.registerPatternVirtual(/^vpn-.+$/);
+
+function sanitizeCert(s) {
+ if (typeof(s) != 'string')
+ return null;
+
+ s = s.trim();
+
+ if (s == '')
+ return null;
+
+ s = s.replace(/\r\n?/g, '\n');
+
+ if (!s.match(/\n$/))
+ s += '\n';
+
+ return s;
+}
+
+function validateCert(priv, section_id, value) {
+ var lines = value.trim().split(/[\r\n]/),
+ start = false,
+ i;
+
+ if (value === null || value === '')
+ return true;
+
+ for (i = 0; i < lines.length; i++) {
+ if (lines[i].match(/^-{5}BEGIN ((|RSA |DSA )PRIVATE KEY|(|TRUSTED |X509 )CERTIFICATE)-{5}$/))
+ start = true;
+ else if (start && !lines[i].match(/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/))
+ break;
+ }
+
+ if (!start || i < lines.length - 1 || !lines[i].match(/^-{5}END ((|RSA |DSA )PRIVATE KEY|(|TRUSTED |X509 )CERTIFICATE)-{5}$/))
+ return _('This does not look like a valid PEM file');
+
+ return true;
+}
+
+return network.registerProtocol('openfortivpn', {
+ getI18n: function() {
+ return _('OpenFortivpn');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || 'vpn-%s'.format(this.sid);
+ },
+
+ getOpkgPackage: function() {
+ return 'openfortivpn';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var o;
+
+ o = s.taboption('general', form.Value, 'peeraddr', _('VPN Server'));
+ o.datatype = 'host(0)';
+
+ o = s.taboption('general', form.Value, 'port', _('VPN Server port'));
+ o.placeholder = '443';
+ o.datatype = 'port';
+ o.optional = true;
+
+ s.taboption("general", form.Value, "username", _("Username"));
+
+ o = s.taboption('general', form.Value, 'password', _('Password'));
+ o.password = true;
+
+ o = s.taboption('general', form.TextValue, 'user_cert', _('User certificate (PEM encoded)'));
+ o.rows = 10;
+ o.monospace = true;
+ o.validate = L.bind(validateCert, o, false);
+ o.load = function(section_id) {
+ var certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id);
+ return certLoadPromise.then(function(certs) { return certs.user_cert });
+ };
+ o.write = function(section_id, value) {
+ return callSetCertificateFiles(section_id, sanitizeCert(value), null, null);
+ };
+
+ o = s.taboption('general', form.TextValue, 'user_key', _('User key (PEM encoded)'));
+ o.rows = 10;
+ o.monospace = true;
+ o.validate = L.bind(validateCert, o, true);
+ o.load = function(section_id) {
+ var certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id);
+ return certLoadPromise.then(function(certs) { return certs.user_key });
+ };
+ o.write = function(section_id, value) {
+ return callSetCertificateFiles(section_id, null, sanitizeCert(value), null);
+ };
+
+ o = s.taboption('general', form.TextValue, 'ca_file', _('CA certificate (PEM encoded; Use instead of system-wide store to verify the gateway certificate.'));
+ o.rows = 10;
+ o.monospace = true;
+ o.validate = L.bind(validateCert, o, false);
+ o.load = function(section_id) {
+ var certLoadPromise = certLoadPromise || callGetCertificateFiles(section_id);
+ return certLoadPromise.then(function(certs) { return certs.ca_file });
+ };
+ o.write = function(section_id, value) {
+ return callSetCertificateFiles(section_id, null, null, sanitizeCert(value));
+ };
+
+ o = s.taboption('advanced', widgets.NetworkSelect, 'tunlink', _('Bind interface'), _('Bind the tunnel to this interface (optional).'));
+ o.exclude = s.section;
+ o.nocreate = true;
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Value, 'persist_int', _('Persistent reconnect interval'), _("Optional, in seconds. If set to '0', no reconnect is attempted."));
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Value, 'trusted_cert', _("VPN Server certificate's SHA256 hash"));
+ o.datatype = 'and(hexstring,length(64))'
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
+ o.default = o.enabled;
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
+ o.default = o.enabled;
+ o.optional = true;
+
+ o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.optional = true;
+
+ o = s.taboption("advanced", form.Value, 'local_ip', _("Local IP address"));
+ o.placeholder = '192.168.0.5'
+ o.dataype = 'ipaddr'
+ o.optional = true;
+
+ }
+});
diff --git a/protocols/luci-proto-openfortivpn/root/usr/libexec/rpcd/luci.openfortivpn b/protocols/luci-proto-openfortivpn/root/usr/libexec/rpcd/luci.openfortivpn
new file mode 100755
index 0000000000..caca8fcaa5
--- /dev/null
+++ b/protocols/luci-proto-openfortivpn/root/usr/libexec/rpcd/luci.openfortivpn
@@ -0,0 +1,86 @@
+#!/usr/bin/env lua
+
+local json = require "luci.jsonc"
+local fs = require "nixio.fs"
+
+local function readfile(path)
+ if fs.stat(path, "type") == "reg" then
+ local s = fs.readfile(path)
+ return s and (s:gsub("^%s+", ""):gsub("%s+$", ""))
+ else
+ return null
+ end
+end
+
+local function writefile(path, data)
+ local n = fs.writefile(path, data)
+ return (n == #data)
+end
+
+local function parseInput()
+ local parse = json.new()
+ local done, err
+
+ while true do
+ local chunk = io.read(4096)
+ if not chunk then
+ break
+ elseif not done and not err then
+ done, err = parse:parse(chunk)
+ end
+ end
+
+ if not done then
+ print(json.stringify({ error = err or "Incomplete input" }))
+ os.exit(1)
+ end
+
+ return parse:get()
+end
+
+if arg[1] == "list" then
+ print(json.stringify({
+ getCertificates = {
+ interface = "interface"
+ },
+ setCertificates = {
+ interface = "interface",
+ user_cert = "user_cert",
+ user_key = "user_key",
+ ca_file = "ca_file"
+ }
+ }))
+elseif arg[1] == "call" then
+ local args = parseInput()
+
+ if not args.interface or
+ type(args.interface) ~= "string" or
+ not args.interface:match("^[a-zA-Z0-9_]+$")
+ then
+ print(json.stringify({ error = "Invalid interface name" }))
+ os.exit(1)
+ end
+
+ local user_cert_pem = string.format("/etc/openfortivpn/user-cert-%s.pem", args.interface)
+ local user_key_pem = string.format("/etc/openfortivpn/user-key-%s.pem", args.interface)
+ local ca_file_pem = string.format("/etc/openfortivpn/ca-%s.pem", args.interface)
+
+ if arg[2] == "getCertificates" then
+ print(json.stringify({
+ user_cert = readfile(user_cert_pem),
+ user_key = readfile(user_key_pem),
+ ca_file = readfile(ca_file_pem)
+ }))
+ elseif arg[2] == "setCertificates" then
+ if args.user_cert then
+ writefile(user_cert_pem, args.user_cert)
+ end
+ if args.user_key then
+ writefile(user_key_pem, args.user_key)
+ end
+ if args.ca_file then
+ writefile(ca_file_pem, args.ca_file)
+ end
+ print(json.stringify({ result = true }))
+ end
+end
diff --git a/protocols/luci-proto-openfortivpn/root/usr/share/rpcd/acl.d/luci-openfortivpn.json b/protocols/luci-proto-openfortivpn/root/usr/share/rpcd/acl.d/luci-openfortivpn.json
new file mode 100644
index 0000000000..5682928863
--- /dev/null
+++ b/protocols/luci-proto-openfortivpn/root/usr/share/rpcd/acl.d/luci-openfortivpn.json
@@ -0,0 +1,15 @@
+{
+ "luci-proto-openfortivpn": {
+ "description": "Grant access to LuCI openfortivpn procedures",
+ "read": {
+ "ubus": {
+ "luci.openfortivpn": [ "getCertificates" ]
+ }
+ },
+ "write": {
+ "ubus": {
+ "luci.openfortivpn": [ "setCertificates" ]
+ }
+ }
+ }
+}
diff --git a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/l2tp.js b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/l2tp.js
index 13bb3548a7..59eb0b8be3 100644
--- a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/l2tp.js
+++ b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/l2tp.js
@@ -46,28 +46,14 @@ return network.registerProtocol('l2tp', {
o.password = true;
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
o.datatype = 'max(9200)';
diff --git a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/ppp.js b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/ppp.js
index 57a7b6a0e1..d97d4bc29b 100644
--- a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/ppp.js
+++ b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/ppp.js
@@ -70,7 +70,8 @@ return network.registerProtocol('ppp', {
renderFormOptions: function(s) {
var dev = this.getL3Device() || this.getDevice(), o;
- o = s.taboption('general', form.Value, 'device', _('Modem device'));
+ o = s.taboption('general', form.Value, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
o.rmempty = false;
o.load = function(section_id) {
return callFileList('/dev/').then(L.bind(function(devices) {
@@ -90,28 +91,14 @@ return network.registerProtocol('ppp', {
o.password = true;
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoa.js b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoa.js
index 483ac9e555..79562994cf 100644
--- a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoa.js
+++ b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoa.js
@@ -77,28 +77,14 @@ return network.registerProtocol('pppoa', {
o.password = true;
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoe.js b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoe.js
index 5d71c43376..057e17fe81 100644
--- a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoe.js
+++ b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pppoe.js
@@ -51,28 +51,14 @@ return network.registerProtocol('pppoe', {
o.placeholder = _('auto');
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pptp.js b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pptp.js
index 006adc1a1e..a8d344fbe5 100644
--- a/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pptp.js
+++ b/protocols/luci-proto-ppp/htdocs/luci-static/resources/protocol/pptp.js
@@ -64,28 +64,14 @@ return network.registerProtocol('pptp', {
o.password = true;
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.ListValue, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.ListValue, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.value('auto', _('Automatic'));
o.value('0', _('Disabled'));
o.value('1', _('Manual'));
o.default = 'auto';
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-pppossh/htdocs/luci-static/resources/protocol/pppossh.js b/protocols/luci-proto-pppossh/htdocs/luci-static/resources/protocol/pppossh.js
index ec8e8a152e..52cf481a5e 100644
--- a/protocols/luci-proto-pppossh/htdocs/luci-static/resources/protocol/pppossh.js
+++ b/protocols/luci-proto-pppossh/htdocs/luci-static/resources/protocol/pppossh.js
@@ -90,25 +90,11 @@ return network.registerProtocol('pppossh', {
o.datatype = 'ipaddr("nomask")';
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.Flag, 'ipv6', _('Obtain IPv6-Address'), _('Enable IPv6 negotiation on the PPP link'));
+ o = s.taboption('advanced', form.Flag, 'ppp_ipv6', _('Obtain IPv6 address'), _('Enable IPv6 negotiation on the PPP link'));
+ o.ucioption = 'ipv6';
o.default = o.disabled;
}
- o = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
-
o = s.taboption('advanced', form.Value, '_keepalive_failure', _('LCP echo failure threshold'), _('Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures'));
o.placeholder = '0';
o.datatype = 'uinteger';
diff --git a/protocols/luci-proto-qmi/htdocs/luci-static/resources/protocol/qmi.js b/protocols/luci-proto-qmi/htdocs/luci-static/resources/protocol/qmi.js
index d496808b0a..1963bc0412 100644
--- a/protocols/luci-proto-qmi/htdocs/luci-static/resources/protocol/qmi.js
+++ b/protocols/luci-proto-qmi/htdocs/luci-static/resources/protocol/qmi.js
@@ -54,7 +54,8 @@ return network.registerProtocol('qmi', {
renderFormOptions: function(s) {
var dev = this.getL3Device() || this.getDevice(), o;
- o = s.taboption('general', form.Value, 'device', _('Modem device'));
+ o = s.taboption('general', form.Value, '_modem_device', _('Modem device'));
+ o.ucioption = 'device';
o.rmempty = false;
o.load = function(section_id) {
return callFileList('/dev/').then(L.bind(function(devices) {
@@ -64,8 +65,26 @@ return network.registerProtocol('qmi', {
}, this));
};
- s.taboption('general', form.Value, 'apn', _('APN'));
- s.taboption('general', form.Value, 'pincode', _('PIN'));
+ var validate_apn = function(section_id, value) {
+ if (value == null || value == '')
+ return true;
+
+ if (!/^[a-zA-Z0-9\-.]*[a-zA-Z0-9]$/.test(value))
+ return _('Invalid APN provided');
+
+ return true;
+ };
+ o = s.taboption('general', form.Value, 'apn', _('APN'));
+ o.validate = validate_apn;
+
+ if (L.hasSystemFeature('ipv6')) {
+ o = s.taboption('general', form.Value, 'v6apn', _('IPv6 APN'));
+ o.validate = validate_apn;
+ o.depends('pdptype', 'ipv4v6')
+ };
+
+ o = s.taboption('general', form.Value, 'pincode', _('PIN'));
+ o.datatype = 'and(uinteger,minlength(4),maxlength(8))';
o = s.taboption('general', form.ListValue, 'auth', _('Authentication Type'));
o.value('both', 'PAP/CHAP');
@@ -86,7 +105,8 @@ return network.registerProtocol('qmi', {
o.password = true;
if (L.hasSystemFeature('ipv6')) {
- o = s.taboption('advanced', form.Flag, 'ipv6', _('Enable IPv6 negotiation'));
+ o = s.taboption('advanced', form.Flag, 'ppp_ipv6', _('Enable IPv6 negotiation'));
+ o.ucioption = 'ipv6';
o.default = o.disabled;
}
@@ -97,11 +117,40 @@ return network.registerProtocol('qmi', {
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
o.datatype = 'max(9200)';
-
+
o = s.taboption('general', form.ListValue, 'pdptype', _('PDP Type'));
o.value('ipv4v6', 'IPv4/IPv6');
o.value('ipv4', 'IPv4');
o.value('ipv6', 'IPv6');
o.default = 'ipv4v6';
+
+ o = s.taboption('advanced', form.Flag, 'defaultroute',
+ _('Use default gateway'),
+ _('If unchecked, no default route is configured'));
+ o.default = o.enabled;
+
+ o = s.taboption('advanced', form.Value, 'metric',
+ _('Use gateway metric'));
+ o.placeholder = '0';
+ o.datatype = 'uinteger';
+ o.depends('defaultroute', '1');
+
+ o = s.taboption('advanced', form.Flag, 'peerdns',
+ _('Use DNS servers advertised by peer'),
+ _('If unchecked, the advertised DNS server addresses are ignored'));
+ o.default = o.enabled;
+
+ o = s.taboption('advanced', form.Value, 'profile',
+ _('APN profile index'));
+ o.placeholder = '1';
+ o.datatype = 'uinteger';
+
+ if (L.hasSystemFeature('ipv6')) {
+ o = s.taboption('advanced', form.Value, 'v6profile',
+ _('IPv6 APN profile index'));
+ o.placeholder = '1';
+ o.datatype = 'uinteger';
+ o.depends('pdptype', 'ipv4v6');
+ };
}
});
diff --git a/protocols/luci-proto-sstp/htdocs/luci-static/resources/protocol/sstp.js b/protocols/luci-proto-sstp/htdocs/luci-static/resources/protocol/sstp.js
index 55ae2f97e6..6a23407b50 100644
--- a/protocols/luci-proto-sstp/htdocs/luci-static/resources/protocol/sstp.js
+++ b/protocols/luci-proto-sstp/htdocs/luci-static/resources/protocol/sstp.js
@@ -48,7 +48,8 @@ return network.registerProtocol('sstp', {
// -- advanced --------------------------------------------------------------------
- o = s.taboption('advanced', form.Flag, 'ipv6', _('IPv6 support'), _('If checked, adds "+ipv6" to the pppd options'));
+ o = s.taboption('advanced', form.Flag, 'ppp_ipv6', _('IPv6 support'), _('If checked, adds "+ipv6" to the pppd options'));
+ o.ucioption = 'ipv6';
o = s.taboption('advanced', form.ListValue, 'log_level', _('sstpc Log-level'));
o.value('0', _('0', 'sstp log level value'));
@@ -58,22 +59,6 @@ return network.registerProtocol('sstp', {
o.value('4', _('4', 'sstp log level value'));
o.default = '0';
- var defaultroute = s.taboption('advanced', form.Flag, 'defaultroute', _('Use default gateway'), _('If unchecked, no default route is configured'));
- defaultroute.default = defaultroute.enabled;
-
- o = s.taboption('advanced', form.Value, 'metric', _('Use gateway metric'));
- o.placeholder = '0';
- o.datatype = 'uinteger';
- o.depends('defaultroute', defaultroute.enabled);
-
- o = s.taboption('advanced', form.Flag, 'peerdns', _('Use DNS servers advertised by peer'), _('If unchecked, the advertised DNS server addresses are ignored'));
- o.default = o.enabled;
-
- o = s.taboption('advanced', form.DynamicList, 'dns', _('Use custom DNS servers'));
- o.depends('peerdns', '0');
- o.datatype = 'ipaddr';
- o.cast = 'string';
-
o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'));
o.placeholder = dev ? (dev.getMTU() || '1500') : '1500';
o.datatype = 'max(9200)';
diff --git a/protocols/luci-proto-unet/Makefile b/protocols/luci-proto-unet/Makefile
new file mode 100644
index 0000000000..8cf94b309d
--- /dev/null
+++ b/protocols/luci-proto-unet/Makefile
@@ -0,0 +1,15 @@
+#
+# Copyright (C) 2022 Hannu Nyman <hannu.nyman@iki.fi>
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for unetd VPN
+LUCI_DEPENDS:=+unetd +unet-cli
+LUCI_PKGARCH:=all
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-unet/htdocs/luci-static/resources/protocol/unet.js b/protocols/luci-proto-unet/htdocs/luci-static/resources/protocol/unet.js
new file mode 100644
index 0000000000..ea9d9c50e2
--- /dev/null
+++ b/protocols/luci-proto-unet/htdocs/luci-static/resources/protocol/unet.js
@@ -0,0 +1,48 @@
+'use strict';
+'require form';
+'require network';
+'require tools.widgets as widgets';
+
+return network.registerProtocol('unet', {
+ getI18n: function() {
+ return _('Unet');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || this.sid;
+ },
+
+ getOpkgPackage: function() {
+ return 'unetd';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var o;
+
+ o = s.taboption('general', form.DummyValue, 'device', _('Name of the tunnel device'));
+ o.optional = false;
+
+ o = s.taboption('general', form.DummyValue, 'key', _('Local wireguard key'));
+ o.optional = false;
+
+ o = s.taboption('general', form.DummyValue, 'auth_key', _('Key used to sign network config'));
+ o.optional = false;
+
+ }
+});
diff --git a/protocols/luci-proto-vpnc/Makefile b/protocols/luci-proto-vpnc/Makefile
index 0800e279fe..33222842a2 100644
--- a/protocols/luci-proto-vpnc/Makefile
+++ b/protocols/luci-proto-vpnc/Makefile
@@ -8,13 +8,10 @@ include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for VPNC VPN
LUCI_DEPENDS:=+vpnc
-LUCI_PKGARCH:=all
PKG_MAINTAINER:=Daniel Dickinson <openwrt@daniel.thecshore.com>
PKG_LICENSE:=Apache-2.0
-LUA_TARGET:=source
-
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-vpnc/htdocs/luci-static/resources/protocol/vpnc.js b/protocols/luci-proto-vpnc/htdocs/luci-static/resources/protocol/vpnc.js
index 87ccc9df1c..bec6c7ffa2 100644
--- a/protocols/luci-proto-vpnc/htdocs/luci-static/resources/protocol/vpnc.js
+++ b/protocols/luci-proto-vpnc/htdocs/luci-static/resources/protocol/vpnc.js
@@ -103,9 +103,5 @@ return network.registerProtocol('vpnc', {
o = s.taboption('general', form.Value, 'target_network', _('Target network'));
o.placeholder = '0.0.0.0/0';
o.datatype = 'network';
-
- o = s.taboption('general', form.ListValue, 'defaultroute', _('Default Route'), _('Set VPN as Default Route'));
- o.value('0', _('No'));
- o.value('1', _('Yes'));
}
});
diff --git a/protocols/luci-proto-vti/Makefile b/protocols/luci-proto-vti/Makefile
new file mode 100644
index 0000000000..6520eab48b
--- /dev/null
+++ b/protocols/luci-proto-vti/Makefile
@@ -0,0 +1,14 @@
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for VTI interfaces
+LUCI_DEPENDS:=+vti
+
+PKG_MAINTAINER:=Jaymin Patel <jem.patel@gmail.com>
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-vti/htdocs/luci-static/resources/protocol/vti.js b/protocols/luci-proto-vti/htdocs/luci-static/resources/protocol/vti.js
new file mode 100644
index 0000000000..bbf037230a
--- /dev/null
+++ b/protocols/luci-proto-vti/htdocs/luci-static/resources/protocol/vti.js
@@ -0,0 +1,72 @@
+'use strict';
+'require form';
+'require network';
+'require tools.widgets as widgets';
+
+network.registerPatternVirtual(/^vti-.+$/);
+
+return network.registerProtocol('vti', {
+ getI18n: function() {
+ return _('VTI');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || 'vti-%s'.format(this.sid);
+ },
+
+ getOpkgPackage: function() {
+ return 'vti';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevices: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var dev = this.getL3Device() || this.getDevice(), o;
+
+ o = s.taboption('general', form.Value, 'peeraddr', _("Remote IPv4 address or FQDN"), _("The IPv4 address or the fully-qualified domain name of the remote tunnel end."));
+ 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.optional = true;
+ o.datatype = 'ip4addr("nomask")';
+ o.load = function(section_id) {
+ return network.getWANNetworks().then(L.bind(function(nets) {
+ if (nets.length)
+ this.placeholder = nets[0].getIPAddr();
+ return form.Value.prototype.load.apply(this, [section_id]);
+ }, this));
+ };
+
+ o = s.taboption('general', form.Value, 'mtu', _('Override MTU'));
+ o.placeholder = dev ? (dev.getMTU() || '1280') : '1280';
+ o.datatype = 'max(1500)';
+
+ o = s.taboption('general', widgets.NetworkSelect, 'tunlink', _("Bind interface"), _("Bind the tunnel to this interface (optional)."));
+ o.exclude = s.section;
+ o.nocreate = true;
+ o.optional = true;
+
+ o = s.taboption('general', form.Value, 'ikey', _("Incoming key"), _("Key for incoming packets (optional)."));
+ o.optional = true;
+ o.datatype = 'uinteger';
+
+ o = s.taboption('general', form.Value, 'okey', _("Outgoing key"), _("Key for outgoing packets (optional)."));
+ o.optional = true;
+ o.datatype = 'uinteger';
+ }
+});
diff --git a/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js b/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js
index 9927f0bc65..1d5052fdfc 100644
--- a/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js
+++ b/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan.js
@@ -59,11 +59,6 @@ return network.registerProtocol('vxlan', {
o.nocreate = true;
o.optional = true;
- o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'), _('Specify an MTU (Maximum Transmission Unit) other than the default (1280 bytes).'));
- o.optional = true;
- o.placeholder = 1280;
- o.datatype = 'range(68, 9200)';
-
o = s.taboption('advanced', form.Value, 'ttl', _('Override TTL'), _('Specify a TTL (Time to Live) for the encapsulating packet other than the default (64).'));
o.optional = true;
o.placeholder = 64;
@@ -73,10 +68,6 @@ return network.registerProtocol('vxlan', {
o.optional = true;
o.datatype = 'range(0, 255)';
- o = s.taboption('advanced', form.Value, 'macaddr', _('Override MAC address'));
- o.optional = true;
- o.datatype = 'macaddr';
-
o = s.taboption('advanced', form.Flag, 'rxcsum', _('Enable rx checksum'));
o.optional = true;
o.default = o.enabled;
diff --git a/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js b/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js
index f568e7e244..04e702897b 100644
--- a/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js
+++ b/protocols/luci-proto-vxlan/htdocs/luci-static/resources/protocol/vxlan6.js
@@ -39,11 +39,16 @@ return network.registerProtocol('vxlan6', {
o = s.taboption('general', form.Value, 'peer6addr', _('Remote IPv6 address'), _('The IPv6 address or the fully-qualified domain name of the remote end.'));
o.optional = false;
- o.datatype = 'or(hostname,cidr6)';
+ 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.optional = true;
- o.datatype = 'cidr6';
+ o.datatype = 'ip6addr("nomask")';
+
+ o = s.taboption('general', form.Value, 'port', _('Destination port'));
+ o.optional = true;
+ o.placeholder = 4789;
+ o.datatype = 'port';
o = s.taboption('general', form.Value, 'vid', _('VXLAN network identifier'), _('ID used to uniquely identify the VXLAN'));
o.optional = true;
@@ -54,11 +59,6 @@ return network.registerProtocol('vxlan6', {
o.nocreate = true;
o.optional = true;
- o = s.taboption('advanced', form.Value, 'mtu', _('Override MTU'), _('Specify an MTU (Maximum Transmission Unit) other than the default (1280 bytes).'));
- o.optional = true;
- o.placeholder = 1280;
- o.datatype = 'range(68, 9200)';
-
o = s.taboption('advanced', form.Value, 'ttl', _('Override TTL'), _('Specify a TTL (Time to Live) for the encapsulating packet other than the default (64).'));
o.optional = true;
o.placeholder = 64;
@@ -68,10 +68,6 @@ return network.registerProtocol('vxlan6', {
o.optional = true;
o.datatype = 'range(0, 255)';
- o = s.taboption('advanced', form.Value, 'macaddr', _('Override MAC address'));
- o.optional = true;
- o.datatype = 'macaddr';
-
o = s.taboption('advanced', form.Flag, 'rxcsum', _('Enable rx checksum'));
o.optional = true;
o.default = o.enabled;
diff --git a/protocols/luci-proto-wireguard/Makefile b/protocols/luci-proto-wireguard/Makefile
index 3540a74acd..12137fb19b 100644
--- a/protocols/luci-proto-wireguard/Makefile
+++ b/protocols/luci-proto-wireguard/Makefile
@@ -7,9 +7,11 @@
include $(TOPDIR)/rules.mk
LUCI_TITLE:=Support for WireGuard VPN
-LUCI_DEPENDS:=+kmod-wireguard +wireguard-tools
+LUCI_DEPENDS:=+wireguard-tools +ucode
LUCI_PKGARCH:=all
+PKG_PROVIDES:=luci-app-wireguard
+
include ../../luci.mk
# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js b/protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js
index e88c07c339..58537f38f8 100644
--- a/protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js
+++ b/protocols/luci-proto-wireguard/htdocs/luci-static/resources/protocol/wireguard.js
@@ -1,7 +1,33 @@
'use strict';
+'require fs';
+'require ui';
+'require dom';
'require uci';
+'require rpc';
'require form';
'require network';
+'require validation';
+
+var generateKey = rpc.declare({
+ object: 'luci.wireguard',
+ method: 'generateKeyPair',
+ expect: { keys: {} }
+});
+
+var getPublicAndPrivateKeyFromPrivate = rpc.declare({
+ object: 'luci.wireguard',
+ method: 'getPublicAndPrivateKeyFromPrivate',
+ params: ['privkey'],
+ expect: { keys: {} }
+});
+
+var generatePsk = rpc.declare({
+ object: 'luci.wireguard',
+ method: 'generatePsk',
+ expect: { psk: '' }
+});
+
+var qrIcon = '<svg viewBox="0 0 29 29" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M0 0h29v29H0z"/><path d="M4 4h1v1H4zM5 4h1v1H5zM6 4h1v1H6zM7 4h1v1H7zM8 4h1v1H8zM9 4h1v1H9zM10 4h1v1h-1zM12 4h1v1h-1zM13 4h1v1h-1zM14 4h1v1h-1zM15 4h1v1h-1zM16 4h1v1h-1zM18 4h1v1h-1zM19 4h1v1h-1zM20 4h1v1h-1zM21 4h1v1h-1zM22 4h1v1h-1zM23 4h1v1h-1zM24 4h1v1h-1zM4 5h1v1H4zM10 5h1v1h-1zM12 5h1v1h-1zM14 5h1v1h-1zM16 5h1v1h-1zM18 5h1v1h-1zM24 5h1v1h-1zM4 6h1v1H4zM6 6h1v1H6zM7 6h1v1H7zM8 6h1v1H8zM10 6h1v1h-1zM12 6h1v1h-1zM18 6h1v1h-1zM20 6h1v1h-1zM21 6h1v1h-1zM22 6h1v1h-1zM24 6h1v1h-1zM4 7h1v1H4zM6 7h1v1H6zM7 7h1v1H7zM8 7h1v1H8zM10 7h1v1h-1zM12 7h1v1h-1zM13 7h1v1h-1zM14 7h1v1h-1zM15 7h1v1h-1zM18 7h1v1h-1zM20 7h1v1h-1zM21 7h1v1h-1zM22 7h1v1h-1zM24 7h1v1h-1zM4 8h1v1H4zM6 8h1v1H6zM7 8h1v1H7zM8 8h1v1H8zM10 8h1v1h-1zM16 8h1v1h-1zM18 8h1v1h-1zM20 8h1v1h-1zM21 8h1v1h-1zM22 8h1v1h-1zM24 8h1v1h-1zM4 9h1v1H4zM10 9h1v1h-1zM12 9h1v1h-1zM13 9h1v1h-1zM15 9h1v1h-1zM18 9h1v1h-1zM24 9h1v1h-1zM4 10h1v1H4zM5 10h1v1H5zM6 10h1v1H6zM7 10h1v1H7zM8 10h1v1H8zM9 10h1v1H9zM10 10h1v1h-1zM12 10h1v1h-1zM14 10h1v1h-1zM16 10h1v1h-1zM18 10h1v1h-1zM19 10h1v1h-1zM20 10h1v1h-1zM21 10h1v1h-1zM22 10h1v1h-1zM23 10h1v1h-1zM24 10h1v1h-1zM13 11h1v1h-1zM14 11h1v1h-1zM15 11h1v1h-1zM16 11h1v1h-1zM4 12h1v1H4zM5 12h1v1H5zM8 12h1v1H8zM9 12h1v1H9zM10 12h1v1h-1zM13 12h1v1h-1zM15 12h1v1h-1zM19 12h1v1h-1zM21 12h1v1h-1zM22 12h1v1h-1zM23 12h1v1h-1zM24 12h1v1h-1zM5 13h1v1H5zM6 13h1v1H6zM8 13h1v1H8zM11 13h1v1h-1zM13 13h1v1h-1zM14 13h1v1h-1zM15 13h1v1h-1zM16 13h1v1h-1zM19 13h1v1h-1zM22 13h1v1h-1zM4 14h1v1H4zM5 14h1v1H5zM9 14h1v1H9zM10 14h1v1h-1zM11 14h1v1h-1zM15 14h1v1h-1zM18 14h1v1h-1zM19 14h1v1h-1zM20 14h1v1h-1zM21 14h1v1h-1zM22 14h1v1h-1zM23 14h1v1h-1zM7 15h1v1H7zM8 15h1v1H8zM9 15h1v1H9zM11 15h1v1h-1zM12 15h1v1h-1zM13 15h1v1h-1zM17 15h1v1h-1zM18 15h1v1h-1zM20 15h1v1h-1zM21 15h1v1h-1zM23 15h1v1h-1zM4 16h1v1H4zM6 16h1v1H6zM10 16h1v1h-1zM11 16h1v1h-1zM13 16h1v1h-1zM14 16h1v1h-1zM16 16h1v1h-1zM17 16h1v1h-1zM18 16h1v1h-1zM22 16h1v1h-1zM23 16h1v1h-1zM24 16h1v1h-1zM12 17h1v1h-1zM16 17h1v1h-1zM17 17h1v1h-1zM18 17h1v1h-1zM4 18h1v1H4zM5 18h1v1H5zM6 18h1v1H6zM7 18h1v1H7zM8 18h1v1H8zM9 18h1v1H9zM10 18h1v1h-1zM14 18h1v1h-1zM16 18h1v1h-1zM17 18h1v1h-1zM21 18h1v1h-1zM22 18h1v1h-1zM23 18h1v1h-1zM4 19h1v1H4zM10 19h1v1h-1zM12 19h1v1h-1zM13 19h1v1h-1zM15 19h1v1h-1zM16 19h1v1h-1zM19 19h1v1h-1zM21 19h1v1h-1zM23 19h1v1h-1zM24 19h1v1h-1zM4 20h1v1H4zM6 20h1v1H6zM7 20h1v1H7zM8 20h1v1H8zM10 20h1v1h-1zM12 20h1v1h-1zM13 20h1v1h-1zM15 20h1v1h-1zM18 20h1v1h-1zM19 20h1v1h-1zM20 20h1v1h-1zM22 20h1v1h-1zM23 20h1v1h-1zM24 20h1v1h-1zM4 21h1v1H4zM6 21h1v1H6zM7 21h1v1H7zM8 21h1v1H8zM10 21h1v1h-1zM13 21h1v1h-1zM15 21h1v1h-1zM16 21h1v1h-1zM19 21h1v1h-1zM21 21h1v1h-1zM23 21h1v1h-1zM24 21h1v1h-1zM4 22h1v1H4zM6 22h1v1H6zM7 22h1v1H7zM8 22h1v1H8zM10 22h1v1h-1zM13 22h1v1h-1zM15 22h1v1h-1zM18 22h1v1h-1zM19 22h1v1h-1zM20 22h1v1h-1zM21 22h1v1h-1zM22 22h1v1h-1zM4 23h1v1H4zM10 23h1v1h-1zM12 23h1v1h-1zM13 23h1v1h-1zM14 23h1v1h-1zM17 23h1v1h-1zM18 23h1v1h-1zM20 23h1v1h-1zM22 23h1v1h-1zM4 24h1v1H4zM5 24h1v1H5zM6 24h1v1H6zM7 24h1v1H7zM8 24h1v1H8zM9 24h1v1H9zM10 24h1v1h-1zM12 24h1v1h-1zM13 24h1v1h-1zM14 24h1v1h-1zM16 24h1v1h-1zM17 24h1v1h-1zM18 24h1v1h-1zM22 24h1v1h-1zM24 24h1v1h-1z"/></svg>';
function validateBase64(section_id, value) {
if (value.length == 0)
@@ -16,6 +42,78 @@ function validateBase64(section_id, value) {
return true;
}
+var stubValidator = {
+ factory: validation,
+ apply: function(type, value, args) {
+ if (value != null)
+ this.value = value;
+
+ return validation.types[type].apply(this, args);
+ },
+ assert: function(condition) {
+ return !!condition;
+ }
+};
+
+function generateDescription(name, texts) {
+ return E('li', { 'style': 'color: inherit;' }, [
+ E('span', name),
+ E('ul', texts.map(function (text) {
+ return E('li', { 'style': 'color: inherit;' }, text);
+ }))
+ ]);
+}
+
+function invokeQREncode(data, code) {
+ return fs.exec_direct('/usr/bin/qrencode', [
+ '--inline', '--8bit', '--type=SVG',
+ '--output=-', '--', data
+ ]).then(function(svg) {
+ code.style.opacity = '';
+ dom.content(code, Object.assign(E(svg), { style: 'width:100%;height:auto' }));
+ }).catch(function(error) {
+ code.style.opacity = '';
+
+ if (L.isObject(error) && error.name == 'NotFoundError') {
+ dom.content(code, [
+ Object.assign(E(qrIcon), { style: 'width:32px;height:32px;opacity:.2' }),
+ E('p', _('The <em>qrencode</em> package is required for generating an QR code image of the configuration.'))
+ ]);
+ }
+ else {
+ dom.content(code, [
+ _('Unable to generate QR code: %s').format(L.isObject(error) ? error.message : error)
+ ]);
+ }
+ });
+}
+
+var cbiKeyPairGenerate = form.DummyValue.extend({
+ cfgvalue: function(section_id, value) {
+ return E('button', {
+ 'class': 'btn',
+ 'click': ui.createHandlerFn(this, function(section_id, ev) {
+ var prv = this.section.getUIElement(section_id, 'private_key'),
+ pub = this.section.getUIElement(section_id, 'public_key'),
+ map = this.map;
+
+ if ((prv.getValue() || pub.getValue()) && !confirm(_('Do you want to replace the current keys?')))
+ return;
+
+ return generateKey().then(function(keypair) {
+ prv.setValue(keypair.priv);
+ pub.setValue(keypair.pub);
+ map.save(null, true);
+ });
+ }, section_id)
+ }, [ _('Generate new key pair') ]);
+ }
+});
+
+function handleWindowDragDropIgnore(ev) {
+ ev.preventDefault()
+}
+
return network.registerProtocol('wireguard', {
getI18n: function() {
return _('WireGuard VPN');
@@ -46,7 +144,7 @@ return network.registerProtocol('wireguard', {
},
renderFormOptions: function(s) {
- var o, ss;
+ var o, ss, ss2;
// -- general ---------------------------------------------------------------------
@@ -55,6 +153,26 @@ return network.registerProtocol('wireguard', {
o.validate = validateBase64;
o.rmempty = false;
+ var serverName = this.getIfname();
+
+ o = s.taboption('general', form.Value, 'public_key', _('Public Key'), _('Base64-encoded public key of this interface for sharing.'));
+ o.rmempty = false;
+ o.write = function() {/* write nothing */};
+
+ o.load = function(section_id) {
+ var privKey = s.formvalue(section_id, 'private_key') || uci.get('network', section_id, 'private_key');
+
+ return getPublicAndPrivateKeyFromPrivate(privKey).then(
+ function(keypair) {
+ return keypair.pub || '';
+ },
+ function(error) {
+ return _('Error getting PublicKey');
+ }, this)
+ };
+
+ s.taboption('general', cbiKeyPairGenerate, '_gen_server_keypair', ' ');
+
o = s.taboption('general', form.Value, 'listen_port', _('Listen Port'), _('Optional. UDP port used for outgoing and incoming packets.'));
o.datatype = 'port';
o.placeholder = _('random');
@@ -67,22 +185,23 @@ return network.registerProtocol('wireguard', {
o = s.taboption('general', form.Flag, 'nohostroute', _('No Host Routes'), _('Optional. Do not create host routes to peers.'));
o.optional = true;
- // -- advanced --------------------------------------------------------------------
+ o = s.taboption('general', form.Button, '_import', _('Import configuration'), _('Imports settings from an existing WireGuard configuration file'));
+ o.inputtitle = _('Load configuration…');
+ o.onclick = function() {
+ return ss.handleConfigImport('full');
+ };
- o = s.taboption('advanced', form.Value, 'metric', _('Metric'), _('Optional'));
- o.datatype = 'uinteger';
- o.placeholder = '0';
- o.optional = true;
+ // -- advanced --------------------------------------------------------------------
o = s.taboption('advanced', form.Value, 'mtu', _('MTU'), _('Optional. Maximum Transmission Unit of tunnel interface.'));
- o.datatype = 'range(1280,1420)';
+ o.datatype = 'range(0,8940)';
o.placeholder = '1420';
o.optional = true;
o = s.taboption('advanced', form.Value, 'fwmark', _('Firewall Mark'), _('Optional. 32-bit mark for outgoing encrypted packets. Enter value in hex, starting with <code>0x</code>.'));
o.optional = true;
o.validate = function(section_id, value) {
- if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,4}$/))
+ if (value.length > 0 && !value.match(/^0x[a-fA-F0-9]{1,8}$/))
return _('Invalid hexadecimal value');
return true;
@@ -96,59 +215,655 @@ return network.registerProtocol('wireguard', {
}
catch(e) {}
- o = s.taboption('peers', form.SectionValue, '_peers', form.TypedSection, 'wireguard_%s'.format(s.section));
+ o = s.taboption('peers', form.SectionValue, '_peers', form.GridSection, 'wireguard_%s'.format(s.section));
o.depends('proto', 'wireguard');
ss = o.subsection;
ss.anonymous = true;
ss.addremove = true;
ss.addbtntitle = _('Add peer');
+ ss.nodescriptions = true;
+ ss.modaltitle = _('Edit peer');
- ss.renderSectionPlaceholder = function() {
- return E([], [
- E('br'),
- E('em', _('No peers defined yet'))
+ ss.handleDragConfig = function(ev) {
+ ev.stopPropagation();
+ ev.preventDefault();
+ ev.dataTransfer.dropEffect = 'copy';
+ };
+
+ ss.handleDropConfig = function(mode, ev) {
+ var file = ev.dataTransfer.files[0],
+ nodes = ev.currentTarget,
+ input = nodes.querySelector('textarea'),
+ reader = new FileReader();
+
+ if (file) {
+ reader.onload = function(rev) {
+ input.value = rev.target.result.trim();
+ ss.handleApplyConfig(mode, nodes, file.name, ev);
+ };
+
+ reader.readAsText(file);
+ }
+
+ ev.stopPropagation();
+ ev.preventDefault();
+ };
+
+ ss.parseConfig = function(data) {
+ var lines = String(data).split(/(\r?\n)+/),
+ section = null,
+ config = { peers: [] },
+ s;
+
+ for (var i = 0; i < lines.length; i++) {
+ var line = lines[i].replace(/#.*$/, '').trim();
+
+ if (line.match(/^\[(\w+)\]$/)) {
+ section = RegExp.$1.toLowerCase();
+
+ if (section == 'peer')
+ config.peers.push(s = {});
+ else
+ s = config;
+ }
+ else if (section && line.match(/^(\w+)\s*=\s*(.+)$/)) {
+ var key = RegExp.$1,
+ val = RegExp.$2.trim();
+
+ if (val.length)
+ s[section + '_' + key.toLowerCase()] = val;
+ }
+ }
+
+ if (config.interface_address) {
+ config.interface_address = config.interface_address.split(/[, ]+/);
+
+ for (var i = 0; i < config.interface_address.length; i++)
+ if (!stubValidator.apply('ipaddr', config.interface_address[i]))
+ return _('Address setting is invalid');
+ }
+
+ if (config.interface_dns) {
+ config.interface_dns = config.interface_dns.split(/[, ]+/);
+
+ for (var i = 0; i < config.interface_dns.length; i++)
+ if (!stubValidator.apply('ipaddr', config.interface_dns[i], ['nomask']))
+ return _('DNS setting is invalid');
+ }
+
+ if (!config.interface_privatekey || validateBase64(null, config.interface_privatekey) !== true)
+ return _('PrivateKey setting is missing or invalid');
+
+ if (!stubValidator.apply('port', config.interface_listenport || '0'))
+ return _('ListenPort setting is invalid');
+
+ for (var i = 0; i < config.peers.length; i++) {
+ var pconf = config.peers[i];
+
+ if (pconf.peer_publickey != null && validateBase64(null, pconf.peer_publickey) !== true)
+ return _('PublicKey setting is invalid');
+
+ if (pconf.peer_presharedkey != null && validateBase64(null, pconf.peer_presharedkey) !== true)
+ return _('PresharedKey setting is invalid');
+
+ if (pconf.peer_allowedips) {
+ pconf.peer_allowedips = pconf.peer_allowedips.split(/[, ]+/);
+
+ for (var j = 0; j < pconf.peer_allowedips.length; j++)
+ if (!stubValidator.apply('ipaddr', pconf.peer_allowedips[j]))
+ return _('AllowedIPs setting is invalid');
+ }
+ else {
+ pconf.peer_allowedips = [ '0.0.0.0/0', '::/0' ];
+ }
+
+ if (pconf.peer_endpoint) {
+ var host_port = pconf.peer_endpoint.match(/^\[([a-fA-F0-9:]+)\]:(\d+)$/) || pconf.peer_endpoint.match(/^(.+):(\d+)$/);
+
+ if (!host_port || !stubValidator.apply('host', host_port[1]) || !stubValidator.apply('port', host_port[2]))
+ return _('Endpoint setting is invalid');
+
+ pconf.peer_endpoint = [ host_port[1], host_port[2] ];
+ }
+
+ if (pconf.peer_persistentkeepalive == 'off' || pconf.peer_persistentkeepalive == '0')
+ delete pconf.peer_persistentkeepalive;
+
+ if (!stubValidator.apply('port', pconf.peer_persistentkeepalive || '0'))
+ return _('PersistentKeepAlive setting is invalid');
+ }
+
+ return config;
+ };
+
+ ss.handleApplyConfig = function(mode, nodes, comment, ev) {
+ var input = nodes.querySelector('textarea').value,
+ error = nodes.querySelector('.alert-message'),
+ cancel = nodes.nextElementSibling.querySelector('.btn'),
+ config = this.parseConfig(input);
+
+ if (typeof(config) == 'string') {
+ error.firstChild.data = _('Cannot parse configuration: %s').format(config);
+ error.style.display = 'block';
+ return;
+ }
+
+ if (mode == 'full') {
+ var prv = s.formvalue(s.section, 'private_key');
+
+ if (prv && prv != config.interface_privatekey && !confirm(_('Overwrite the current settings with the imported configuration?')))
+ return;
+
+ return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) {
+ s.getOption('private_key').getUIElement(s.section).setValue(keypair.priv);
+ s.getOption('public_key').getUIElement(s.section).setValue(keypair.pub);
+ s.getOption('listen_port').getUIElement(s.section).setValue(config.interface_listenport || '');
+ s.getOption('addresses').getUIElement(s.section).setValue(config.interface_address);
+
+ if (config.interface_dns)
+ s.getOption('dns').getUIElement(s.section).setValue(config.interface_dns);
+
+ for (var i = 0; i < config.peers.length; i++) {
+ var pconf = config.peers[i];
+ var sid = uci.add('network', 'wireguard_' + s.section);
+
+ uci.sections('network', 'wireguard_' + s.section, function(peer) {
+ if (peer.public_key == pconf.peer_publickey)
+ uci.remove('network', peer['.name']);
+ });
+
+ uci.set('network', sid, 'description', comment || _('Imported peer configuration'));
+ uci.set('network', sid, 'public_key', pconf.peer_publickey);
+ uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey);
+ uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips);
+ uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive);
+
+ if (pconf.peer_endpoint) {
+ uci.set('network', sid, 'endpoint_host', pconf.peer_endpoint[0]);
+ uci.set('network', sid, 'endpoint_port', pconf.peer_endpoint[1]);
+ }
+ }
+
+ return s.map.save(null, true);
+ }).then(function() {
+ cancel.click();
+ });
+ }
+ else {
+ return getPublicAndPrivateKeyFromPrivate(config.interface_privatekey).then(function(keypair) {
+ var sid = uci.add('network', 'wireguard_' + s.section);
+ var pub = s.formvalue(s.section, 'public_key');
+
+ uci.sections('network', 'wireguard_' + s.section, function(peer) {
+ if (peer.public_key == keypair.pub)
+ uci.remove('network', peer['.name']);
+ });
+
+ uci.set('network', sid, 'description', comment || _('Imported peer configuration'));
+ uci.set('network', sid, 'public_key', keypair.pub);
+ uci.set('network', sid, 'private_key', keypair.priv);
+
+ for (var i = 0; i < config.peers.length; i++) {
+ var pconf = config.peers[i];
+
+ if (pconf.peer_publickey == pub) {
+ uci.set('network', sid, 'preshared_key', pconf.peer_presharedkey);
+ uci.set('network', sid, 'allowed_ips', pconf.peer_allowedips);
+ uci.set('network', sid, 'persistent_keepalive', pconf.peer_persistentkeepalive);
+ break;
+ }
+ }
+
+ return s.map.save(null, true);
+ }).then(function() {
+ cancel.click();
+ });
+ }
+ };
+
+ ss.handleConfigImport = function(mode) {
+ var mapNode = ss.getActiveModalMap(),
+ headNode = mapNode.parentNode.querySelector('h4'),
+ parent = this.map;
+
+ var nodes = E('div', {
+ 'dragover': this.handleDragConfig,
+ 'drop': this.handleDropConfig.bind(this, mode)
+ }, [
+ E([], (mode == 'full') ? [
+ E('p', _('Drag or paste a valid <em>*.conf</em> file below to configure the local WireGuard interface.'))
+ ] : [
+ E('p', _('Paste or drag a WireGuard configuration (commonly <em>wg0.conf</em>) from another system below to create a matching peer entry allowing that system to connect to the local WireGuard interface.')),
+ E('p', _('To fully configure the local WireGuard interface from an existing (e.g. provider supplied) configuration file, use the <strong><a class="full-import" href="#">configuration import</a></strong> instead.'))
+ ]),
+ E('p', [
+ E('textarea', {
+ 'placeholder': (mode == 'full')
+ ? _('Paste or drag supplied WireGuard configuration file…')
+ : _('Paste or drag WireGuard peer configuration (wg0.conf) file…'),
+ 'style': 'height:5em;width:100%; white-space:pre'
+ })
+ ]),
+ E('div', {
+ 'class': 'alert-message',
+ 'style': 'display:none'
+ }, [''])
]);
+
+ var cancelFn = function() {
+ nodes.parentNode.removeChild(nodes.nextSibling);
+ nodes.parentNode.removeChild(nodes);
+ mapNode.classList.remove('hidden');
+ mapNode.nextSibling.classList.remove('hidden');
+ headNode.removeChild(headNode.lastChild);
+ window.removeEventListener('dragover', handleWindowDragDropIgnore);
+ window.removeEventListener('drop', handleWindowDragDropIgnore);
+ };
+
+ var a = nodes.querySelector('a.full-import');
+
+ if (a) {
+ a.addEventListener('click', ui.createHandlerFn(this, function(mode) {
+ cancelFn();
+ this.handleConfigImport('full');
+ }));
+ }
+
+ mapNode.classList.add('hidden');
+ mapNode.nextElementSibling.classList.add('hidden');
+
+ headNode.appendChild(E('span', [ ' » ', (mode == 'full') ? _('Import configuration') : _('Import as peer') ]));
+ mapNode.parentNode.appendChild(E([], [
+ nodes,
+ E('div', {
+ 'class': 'right'
+ }, [
+ E('button', {
+ 'class': 'btn',
+ 'click': cancelFn
+ }, [ _('Cancel') ]),
+ ' ',
+ E('button', {
+ 'class': 'btn primary',
+ 'click': ui.createHandlerFn(this, 'handleApplyConfig', mode, nodes, null)
+ }, [ _('Import settings') ])
+ ])
+ ]));
+
+ window.addEventListener('dragover', handleWindowDragDropIgnore);
+ window.addEventListener('drop', handleWindowDragDropIgnore);
};
+ ss.renderSectionAdd = function(/* ... */) {
+ var nodes = this.super('renderSectionAdd', arguments);
+
+ nodes.appendChild(E('button', {
+ 'class': 'btn',
+ 'click': ui.createHandlerFn(this, 'handleConfigImport', 'peer')
+ }, [ _('Import configuration as peer…') ]));
+
+ return nodes;
+ };
+
+ ss.renderSectionPlaceholder = function() {
+ return E('em', _('No peers defined yet.'));
+ };
+
+ o = ss.option(form.Flag, 'disabled', _('Peer disabled'), _('Enable / Disable peer. Restart wireguard interface to apply changes.'));
+ o.modalonly = true;
+ o.optional = true;
+
o = ss.option(form.Value, 'description', _('Description'), _('Optional. Description of peer.'));
o.placeholder = 'My Peer';
o.datatype = 'string';
o.optional = true;
+ o.width = '30%';
+ o.textvalue = function(section_id) {
+ var dis = ss.getOption('disabled'),
+ pub = ss.getOption('public_key'),
+ prv = ss.getOption('private_key'),
+ psk = ss.getOption('preshared_key'),
+ name = this.cfgvalue(section_id),
+ key = pub.cfgvalue(section_id);
+
+ var desc = [
+ E('p', [
+ name ? E('span', [ name ]) : E('em', [ _('Untitled peer') ])
+ ])
+ ];
+
+ if (dis.cfgvalue(section_id) == '1')
+ desc.push(E('span', {
+ 'class': 'ifacebadge',
+ 'data-tooltip': _('WireGuard peer is disabled')
+ }, [
+ E('em', [ _('Disabled', 'Label indicating that WireGuard peer is disabled') ])
+ ]), ' ');
+
+ if (!key || !pub.isValid(section_id)) {
+ desc.push(E('span', {
+ 'class': 'ifacebadge',
+ 'data-tooltip': _('Public key is missing')
+ }, [
+ E('em', [ _('Key missing', 'Label indicating that WireGuard peer lacks public key') ])
+ ]));
+ }
+ else {
+ desc.push(
+ E('span', {
+ 'class': 'ifacebadge',
+ 'data-tooltip': _('Public key: %h', 'Tooltip displaying full WireGuard peer public key').format(key)
+ }, [
+ E('code', [ key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
+ ]),
+ ' ',
+ (prv.cfgvalue(section_id) && prv.isValid(section_id))
+ ? E('span', {
+ 'class': 'ifacebadge',
+ 'data-tooltip': _('Private key present')
+ }, [ _('Private', 'Label indicating that WireGuard peer private key is stored') ]) : '',
+ ' ',
+ (psk.cfgvalue(section_id) && psk.isValid(section_id))
+ ? E('span', {
+ 'class': 'ifacebadge',
+ 'data-tooltip': _('Preshared key in use')
+ }, [ _('PSK', 'Label indicating that WireGuard peer uses a PSK') ]) : ''
+ );
+ }
- o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Base64-encoded public key of peer.'));
+ return E([], desc);
+ };
+
+ function handleKeyChange(ev, section_id, value) {
+ var prv = this.section.getUIElement(section_id, 'private_key'),
+ btn = this.map.findElement('.btn.qr-code');
+
+ btn.disabled = (!prv.isValid() || !prv.getValue());
+ }
+
+ o = ss.option(form.Value, 'public_key', _('Public Key'), _('Required. Public key of the WireGuard peer.'));
+ o.modalonly = true;
o.validate = validateBase64;
- o.rmempty = false;
+ o.onchange = handleKeyChange;
- o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.'));
+ o = ss.option(form.Value, 'private_key', _('Private Key'), _('Optional. Private key of the WireGuard peer. The key is not required for establishing a connection but allows generating a peer configuration or QR code if available. It can be removed after the configuration has been exported.'));
+ o.modalonly = true;
+ o.validate = validateBase64;
+ o.onchange = handleKeyChange;
o.password = true;
+
+ o = ss.option(cbiKeyPairGenerate, '_gen_peer_keypair', ' ');
+ o.modalonly = true;
+
+ o = ss.option(form.Value, 'preshared_key', _('Preshared Key'), _('Optional. Base64-encoded preshared key. Adds in an additional layer of symmetric-key cryptography for post-quantum resistance.'));
+ o.modalonly = true;
o.validate = validateBase64;
- o.optional = true;
+ o.password = true;
+
+ o = ss.option(form.DummyValue, '_gen_psk', ' ');
+ o.modalonly = true;
+ o.cfgvalue = function(section_id, value) {
+ return E('button', {
+ 'class': 'btn',
+ 'click': ui.createHandlerFn(this, function(section_id, ev) {
+ var psk = this.section.getUIElement(section_id, 'preshared_key'),
+ map = this.map;
+
+ if (psk.getValue() && !confirm(_('Do you want to replace the current PSK?')))
+ return;
+
+ return generatePsk().then(function(key) {
+ psk.setValue(key);
+ map.save(null, true);
+ });
+ }, section_id)
+ }, [ _('Generate preshared key') ]);
+ };
- o = ss.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _("Required. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel."));
+ o = ss.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _("Optional. IP addresses and prefixes that this peer is allowed to use inside the tunnel. Usually the peer's tunnel IP addresses and the networks the peer routes through the tunnel."));
o.datatype = 'ipaddr';
- o.validate = function(section, value) {
- var opt = this.map.lookupOption('allowed_ips', section);
- var ips = opt[0].formvalue(section);
- if (ips.length == 0) {
- return _('Value must not be empty');
+ o.textvalue = function(section_id) {
+ var ips = L.toArray(this.cfgvalue(section_id)),
+ list = [];
+
+ for (var i = 0; i < ips.length; i++) {
+ if (i > 7) {
+ list.push(E('em', {
+ 'class': 'ifacebadge cbi-tooltip-container'
+ }, [
+ _('+ %d more', 'Label indicating further amount of allowed ips').format(ips.length - i),
+ E('span', {
+ 'class': 'cbi-tooltip'
+ }, [
+ E('ul', ips.map(function(ip) {
+ return E('li', [
+ E('span', { 'class': 'ifacebadge' }, [ ip ])
+ ]);
+ }))
+ ])
+ ]));
+
+ break;
+ }
+
+ list.push(E('span', { 'class': 'ifacebadge' }, [ ips[i] ]));
}
- return true;
+
+ if (!list.length)
+ list.push('*');
+
+ return E('span', { 'style': 'display:inline-flex;flex-wrap:wrap;gap:.125em' }, list);
};
o = ss.option(form.Flag, 'route_allowed_ips', _('Route Allowed IPs'), _('Optional. Create routes for Allowed IPs for this peer.'));
+ o.modalonly = true;
o = ss.option(form.Value, 'endpoint_host', _('Endpoint Host'), _('Optional. Host of peer. Names are resolved prior to bringing up the interface.'));
o.placeholder = 'vpn.example.com';
o.datatype = 'host';
+ o.textvalue = function(section_id) {
+ var host = this.cfgvalue(section_id),
+ port = this.section.cfgvalue(section_id, 'endpoint_port');
+
+ return (host && port)
+ ? '%h:%d'.format(host, port)
+ : (host
+ ? '%h:*'.format(host)
+ : (port
+ ? '*:%d'.format(port)
+ : '*'));
+ };
o = ss.option(form.Value, 'endpoint_port', _('Endpoint Port'), _('Optional. Port of peer.'));
+ o.modalonly = true;
o.placeholder = '51820';
o.datatype = 'port';
o = ss.option(form.Value, 'persistent_keepalive', _('Persistent Keep Alive'), _('Optional. Seconds between keep alive messages. Default is 0 (disabled). Recommended value if this device is behind a NAT is 25.'));
+ o.modalonly = true;
o.datatype = 'range(0,65535)';
o.placeholder = '0';
+
+
+
+ o = ss.option(form.DummyValue, '_keyops', _('Configuration Export'),
+ _('Generates a configuration suitable for import on a WireGuard peer'));
+
+ o.modalonly = true;
+
+ o.createPeerConfig = function(section_id, endpoint, ips) {
+ var pub = s.formvalue(s.section, 'public_key'),
+ port = s.formvalue(s.section, 'listen_port') || '51820',
+ prv = this.section.formvalue(section_id, 'private_key'),
+ psk = this.section.formvalue(section_id, 'preshared_key'),
+ eport = this.section.formvalue(section_id, 'endpoint_port'),
+ keep = this.section.formvalue(section_id, 'persistent_keepalive');
+
+ // If endpoint is IPv6 we must escape it with []
+ if (endpoint.indexOf(':') > 0) {
+ endpoint = '['+endpoint+']';
+ }
+
+ return [
+ '[Interface]',
+ 'PrivateKey = ' + prv,
+ eport ? 'ListenPort = ' + eport : '# ListenPort not defined',
+ '',
+ '[Peer]',
+ 'PublicKey = ' + pub,
+ psk ? 'PresharedKey = ' + psk : '# PresharedKey not used',
+ ips && ips.length ? 'AllowedIPs = ' + ips.join(', ') : '# AllowedIPs not defined',
+ endpoint ? 'Endpoint = ' + endpoint + ':' + port : '# Endpoint not defined',
+ keep ? 'PersistentKeepAlive = ' + keep : '# PersistentKeepAlive not defined'
+ ].join('\n');
+ };
+
+ o.handleGenerateQR = function(section_id, ev) {
+ var mapNode = ss.getActiveModalMap(),
+ headNode = mapNode.parentNode.querySelector('h4'),
+ configGenerator = this.createPeerConfig.bind(this, section_id),
+ parent = this.map;
+
+ return Promise.all([
+ network.getWANNetworks(),
+ network.getWAN6Networks(),
+ L.resolveDefault(uci.load('ddns')),
+ L.resolveDefault(uci.load('system')),
+ parent.save(null, true)
+ ]).then(function(data) {
+ var hostnames = [];
+
+ uci.sections('ddns', 'service', function(s) {
+ if (typeof(s.lookup_host) == 'string' && s.enabled == '1')
+ hostnames.push(s.lookup_host);
+ });
+
+ uci.sections('system', 'system', function(s) {
+ if (typeof(s.hostname) == 'string' && s.hostname.indexOf('.') > 0)
+ hostnames.push(s.hostname);
+ });
+
+ for (var i = 0; i < data[0].length; i++)
+ hostnames.push.apply(hostnames, data[0][i].getIPAddrs().map(function(ip) { return ip.split('/')[0] }));
+
+ for (var i = 0; i < data[1].length; i++)
+ hostnames.push.apply(hostnames, data[1][i].getIP6Addrs().map(function(ip) { return ip.split('/')[0] }));
+
+ var ips = [ '0.0.0.0/0', '::/0' ];
+
+ var qrm, qrs, qro;
+
+ qrm = new form.JSONMap({ config: { endpoint: hostnames[0], allowed_ips: ips } }, null, _('The generated configuration can be imported into a WireGuard client application to set up a connection towards this device.'));
+ qrm.parent = parent;
+
+ qrs = qrm.section(form.NamedSection, 'config');
+
+ function handleConfigChange(ev, section_id, value) {
+ var code = this.map.findElement('.qr-code'),
+ conf = this.map.findElement('.client-config'),
+ endpoint = this.section.getUIElement(section_id, 'endpoint'),
+ ips = this.section.getUIElement(section_id, 'allowed_ips');
+
+ if (this.isValid(section_id)) {
+ conf.firstChild.data = configGenerator(endpoint.getValue(), ips.getValue());
+ code.style.opacity = '.5';
+
+ invokeQREncode(conf.firstChild.data, code);
+ }
+ };
+
+ qro = qrs.option(form.Value, 'endpoint', _('Connection endpoint'), _('The public hostname or IP address of this system the peer should connect to. This usually is a static public IP address, a static hostname or a DDNS domain.'));
+ qro.datatype = 'or(ipaddr,hostname)';
+ hostnames.forEach(function(hostname) { qro.value(hostname) });
+ qro.onchange = handleConfigChange;
+
+ qro = qrs.option(form.DynamicList, 'allowed_ips', _('Allowed IPs'), _('IP addresses that are allowed inside the tunnel. The peer will accept tunnelled packets with source IP addresses matching this list and route back packets with matching destination IP.'));
+ qro.datatype = 'ipaddr';
+ qro.default = ips;
+ ips.forEach(function(ip) { qro.value(ip) });
+ qro.onchange = handleConfigChange;
+
+ qro = qrs.option(form.DummyValue, 'output');
+ qro.renderWidget = function() {
+ var peer_config = configGenerator(hostnames[0], ips);
+
+ var node = E('div', {
+ 'style': 'display:flex;flex-wrap:wrap;align-items:center;gap:.5em;width:100%'
+ }, [
+ E('div', {
+ 'class': 'qr-code',
+ 'style': 'width:320px;flex:0 1 320px;text-align:center'
+ }, [
+ E('em', { 'class': 'spinning' }, [ _('Generating QR code…') ])
+ ]),
+ E('pre', {
+ 'class': 'client-config',
+ 'style': 'flex:1;white-space:pre;overflow:auto',
+ 'click': function(ev) {
+ var sel = window.getSelection(),
+ range = document.createRange();
+
+ range.selectNodeContents(ev.currentTarget);
+
+ sel.removeAllRanges();
+ sel.addRange(range);
+ }
+ }, [ peer_config ])
+ ]);
+
+ invokeQREncode(peer_config, node.firstChild);
+
+ return node;
+ };
+
+ return qrm.render().then(function(nodes) {
+ mapNode.classList.add('hidden');
+ mapNode.nextElementSibling.classList.add('hidden');
+
+ headNode.appendChild(E('span', [ ' » ', _('Generate configuration') ]));
+ mapNode.parentNode.appendChild(E([], [
+ nodes,
+ E('div', {
+ 'class': 'right'
+ }, [
+ E('button', {
+ 'class': 'btn',
+ 'click': function() {
+ nodes.parentNode.removeChild(nodes.nextSibling);
+ nodes.parentNode.removeChild(nodes);
+ mapNode.classList.remove('hidden');
+ mapNode.nextSibling.classList.remove('hidden');
+ headNode.removeChild(headNode.lastChild);
+ }
+ }, [ _('Back to peer configuration') ])
+ ])
+ ]));
+
+ if (!s.formvalue(s.section, 'listen_port')) {
+ nodes.appendChild(E('div', { 'class': 'alert-message' }, [
+ E('p', [
+ _('No fixed interface listening port defined, peers might not be able to initiate connections to this WireGuard instance!')
+ ])
+ ]));
+ }
+ });
+ });
+ };
+
+ o.cfgvalue = function(section_id, value) {
+ var privkey = this.section.cfgvalue(section_id, 'private_key');
+
+ return E('button', {
+ 'class': 'btn qr-code',
+ 'style': 'display:inline-flex;align-items:center;gap:.5em',
+ 'click': ui.createHandlerFn(this, 'handleGenerateQR', section_id),
+ 'disabled': privkey ? null : ''
+ }, [
+ Object.assign(E(qrIcon), { style: 'width:22px;height:22px' }),
+ _('Generate configuration…')
+ ]);
+ };
},
deleteConfiguration: function() {
diff --git a/protocols/luci-proto-wireguard/htdocs/luci-static/resources/view/wireguard/status.js b/protocols/luci-proto-wireguard/htdocs/luci-static/resources/view/wireguard/status.js
new file mode 100644
index 0000000000..4344c36739
--- /dev/null
+++ b/protocols/luci-proto-wireguard/htdocs/luci-static/resources/view/wireguard/status.js
@@ -0,0 +1,175 @@
+'use strict';
+'require view';
+'require rpc';
+'require poll';
+'require dom';
+'require ui';
+
+
+var callGetWgInstances = rpc.declare({
+ object: 'luci.wireguard',
+ method: 'getWgInstances'
+});
+
+function timestampToStr(timestamp) {
+ if (timestamp < 1)
+ return _('Never', 'No WireGuard peer handshake yet');
+
+ var seconds = (Date.now() / 1000) - timestamp;
+ var ago;
+
+ if (seconds < 60)
+ ago = _('%ds ago').format(seconds);
+ else if (seconds < 3600)
+ ago = _('%dm ago').format(seconds / 60);
+ else if (seconds < 86401)
+ ago = _('%dh ago').format(seconds / 3600);
+ else
+ ago = _('over a day ago');
+
+ return (new Date(timestamp * 1000)).toUTCString() + ' (' + ago + ')';
+}
+
+function handleInterfaceDetails(iface) {
+ ui.showModal(_('Instance Details'), [
+ ui.itemlist(E([]), [
+ _('Name'), iface.name,
+ _('Public Key'), E('code', [ iface.public_key ]),
+ _('Listen Port'), iface.listen_port,
+ _('Firewall Mark'), iface.fwmark != 'off' ? iface.fwmark : E('em', _('none'))
+ ]),
+ E('div', { 'class': 'right' }, [
+ E('button', {
+ 'class': 'btn cbi-button',
+ 'click': ui.hideModal
+ }, [ _('Dismiss') ])
+ ])
+ ]);
+}
+
+function handlePeerDetails(peer) {
+ ui.showModal(_('Peer Details'), [
+ ui.itemlist(E([]), [
+ _('Description'), peer.name,
+ _('Public Key'), E('code', [ peer.public_key ]),
+ _('Endpoint'), peer.endpoint,
+ _('Allowed IPs'), (Array.isArray(peer.allowed_ips) && peer.allowed_ips.length) ? peer.allowed_ips.join(', ') : E('em', _('none')),
+ _('Received Data'), '%1024mB'.format(peer.transfer_rx),
+ _('Transmitted Data'), '%1024mB'.format(peer.transfer_tx),
+ _('Latest Handshake'), timestampToStr(+peer.latest_handshake),
+ _('Keep-Alive'), (peer.persistent_keepalive != 'off') ? _('every %ds', 'WireGuard keep alive interval').format(+peer.persistent_keepalive) : E('em', _('none')),
+ ]),
+ E('div', { 'class': 'right' }, [
+ E('button', {
+ 'class': 'btn cbi-button',
+ 'click': ui.hideModal
+ }, [ _('Dismiss') ])
+ ])
+ ]);
+}
+
+function renderPeerTable(instanceName, peers) {
+ var t = new L.ui.Table(
+ [
+ _('Peer'),
+ _('Endpoint'),
+ _('Data Received'),
+ _('Data Transmitted'),
+ _('Latest Handshake')
+ ],
+ {
+ id: 'peers-' + instanceName
+ },
+ E('em', [
+ _('No peers connected')
+ ])
+ );
+
+ t.update(peers.map(function(peer) {
+ return [
+ [
+ peer.name || '',
+ E('div', {
+ 'style': 'cursor:pointer',
+ 'click': ui.createHandlerFn(this, handlePeerDetails, peer)
+ }, [
+ E('p', [
+ peer.name ? E('span', [ peer.name ]) : E('em', [ _('Untitled peer') ])
+ ]),
+ E('span', {
+ 'class': 'ifacebadge hide-sm',
+ 'data-tooltip': _('Public key: %h', 'Tooltip displaying full WireGuard peer public key').format(peer.public_key)
+ }, [
+ E('code', [ peer.public_key.replace(/^(.{5}).+(.{6})$/, '$1…$2') ])
+ ])
+ ])
+ ],
+ peer.endpoint,
+ [ +peer.transfer_rx, '%1024mB'.format(+peer.transfer_rx) ],
+ [ +peer.transfer_tx, '%1024mB'.format(+peer.transfer_tx) ],
+ [ +peer.latest_handshake, timestampToStr(+peer.latest_handshake) ]
+ ];
+ }));
+
+ return t.render();
+}
+
+return view.extend({
+ renderIfaces: function(ifaces) {
+ var res = [
+ E('h2', [ _('WireGuard Status') ])
+ ];
+
+ for (var instanceName in ifaces) {
+ res.push(
+ E('h3', [ _('Instance "%h"', 'WireGuard instance heading').format(instanceName) ]),
+ E('p', {
+ 'style': 'cursor:pointer',
+ 'click': ui.createHandlerFn(this, handleInterfaceDetails, ifaces[instanceName])
+ }, [
+ E('span', { 'class': 'ifacebadge' }, [
+ E('img', { 'src': L.resource('icons', 'tunnel.png') }),
+ '\xa0',
+ instanceName
+ ]),
+ E('span', { 'style': 'opacity:.8' }, [
+ ' · ',
+ _('Port %d', 'WireGuard listen port').format(ifaces[instanceName].listen_port),
+ ' · ',
+ E('code', { 'click': '' }, [ ifaces[instanceName].public_key ])
+ ])
+ ]),
+ renderPeerTable(instanceName, ifaces[instanceName].peers)
+ );
+ }
+
+ if (res.length == 1)
+ res.push(E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
+ E('em', [ _('No WireGuard interfaces configured.') ])
+ ]));
+
+ return E([], res);
+ },
+
+ render: function() {
+ poll.add(L.bind(function () {
+ return callGetWgInstances().then(L.bind(function(ifaces) {
+ dom.content(
+ document.querySelector('#view'),
+ this.renderIfaces(ifaces)
+ );
+ }, this));
+ }, this), 5);
+
+ return E([], [
+ E('h2', [ _('WireGuard Status') ]),
+ E('p', { 'class': 'center', 'style': 'margin-top:5em' }, [
+ E('em', [ _('Loading data…') ])
+ ])
+ ]);
+ },
+
+ handleReset: null,
+ handleSaveApply: null,
+ handleSave: null
+});
diff --git a/protocols/luci-proto-wireguard/root/usr/share/luci/menu.d/luci-proto-wireguard.json b/protocols/luci-proto-wireguard/root/usr/share/luci/menu.d/luci-proto-wireguard.json
new file mode 100644
index 0000000000..06940ee7ef
--- /dev/null
+++ b/protocols/luci-proto-wireguard/root/usr/share/luci/menu.d/luci-proto-wireguard.json
@@ -0,0 +1,14 @@
+{
+ "admin/status/wireguard": {
+ "title": "WireGuard",
+ "order": 92,
+ "action": {
+ "type": "view",
+ "path": "wireguard/status"
+ },
+ "depends": {
+ "acl": [ "luci-proto-wireguard" ],
+ "uci": { "network": true }
+ }
+ }
+}
diff --git a/protocols/luci-proto-wireguard/root/usr/share/rpcd/acl.d/luci-wireguard.json b/protocols/luci-proto-wireguard/root/usr/share/rpcd/acl.d/luci-wireguard.json
new file mode 100644
index 0000000000..e7187c0e4f
--- /dev/null
+++ b/protocols/luci-proto-wireguard/root/usr/share/rpcd/acl.d/luci-wireguard.json
@@ -0,0 +1,25 @@
+{
+ "luci-proto-wireguard": {
+ "description": "Grant access to LuCI Wireguard procedures",
+ "read": {
+ "file": {
+ "/usr/bin/qrencode --inline --8bit --type=SVG --output=- -- *": [ "exec" ]
+ },
+ "ubus": {
+ "luci.wireguard": [
+ "getWgInstances"
+ ]
+ },
+ "uci": [ "ddns", "system" ]
+ },
+ "write": {
+ "ubus": {
+ "luci.wireguard": [
+ "generateKeyPair",
+ "getPublicAndPrivateKeyFromPrivate",
+ "generatePsk"
+ ]
+ }
+ }
+ }
+}
diff --git a/protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard b/protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard
new file mode 100644
index 0000000000..add810c8ae
--- /dev/null
+++ b/protocols/luci-proto-wireguard/root/usr/share/rpcd/ucode/luci.wireguard
@@ -0,0 +1,107 @@
+// Copyright 2022 Jo-Philipp Wich <jo@mein.io>
+// Licensed to the public under the Apache License 2.0.
+
+'use strict';
+
+import { cursor } from 'uci';
+import { popen } from 'fs';
+
+
+function shellquote(s) {
+ return `'${replace(s ?? '', "'", "'\\''")}'`;
+}
+
+function command(cmd) {
+ return trim(popen(cmd)?.read?.('all'));
+}
+
+
+const methods = {
+ generatePsk: {
+ call: function() {
+ return { psk: command('wg genpsk 2>/dev/null') };
+ }
+ },
+
+ generateKeyPair: {
+ call: function() {
+ const priv = command('wg genkey 2>/dev/null');
+ const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
+
+ return { keys: { priv, pub } };
+ }
+ },
+
+ getPublicAndPrivateKeyFromPrivate: {
+ args: { privkey: "privkey" },
+ call: function(req) {
+ const priv = req.args?.privkey;
+ const pub = command(`echo ${shellquote(priv)} | wg pubkey 2>/dev/null`);
+
+ return { keys: { priv, pub } };
+ }
+ },
+
+ getWgInstances: {
+ call: function() {
+ const data = {};
+ let last_device;
+ let qr_pubkey = {};
+
+ const uci = cursor();
+ const wg_dump = popen("wg show all dump 2>/dev/null");
+
+ if (wg_dump) {
+ uci.load("network");
+
+ for (let line = wg_dump.read('line'); length(line); line = wg_dump.read('line')) {
+ const record = split(rtrim(line, '\n'), '\t');
+
+ if (last_device != record[0]) {
+ last_device = record[0];
+ data[last_device] = {
+ name: last_device,
+ public_key: record[2],
+ listen_port: record[3],
+ fwmark: record[4],
+ peers: []
+ };
+
+ if (!length(record[2]) || record[2] == '(none)')
+ qr_pubkey[last_device] = '';
+ else
+ qr_pubkey[last_device] = `PublicKey = ${record[2]}`;
+ }
+ else {
+ let peer_name;
+
+ uci.foreach('network', `wireguard_${last_device}`, (s) => {
+ if (s.public_key == record[1])
+ peer_name = s.description;
+ });
+
+ const peer = {
+ name: peer_name,
+ public_key: record[1],
+ endpoint: record[3],
+ allowed_ips: [],
+ latest_handshake: record[5],
+ transfer_rx: record[6],
+ transfer_tx: record[7],
+ persistent_keepalive: record[8]
+ };
+
+ if (record[3] != '(none)' && length(record[4]))
+ push(peer.allowed_ips, ...split(record[4], ','));
+
+ push(data[last_device].peers, peer);
+ }
+ }
+ }
+
+ return data;
+ }
+ }
+};
+
+return { 'luci.wireguard': methods };
diff --git a/protocols/luci-proto-xfrm/Makefile b/protocols/luci-proto-xfrm/Makefile
new file mode 100644
index 0000000000..e59818e417
--- /dev/null
+++ b/protocols/luci-proto-xfrm/Makefile
@@ -0,0 +1,14 @@
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for XFRM interfaces
+LUCI_DEPENDS:=+xfrm
+
+PKG_MAINTAINER:=Glen Huang <heyhgl@gmail.com>
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-xfrm/htdocs/luci-static/resources/protocol/xfrm.js b/protocols/luci-proto-xfrm/htdocs/luci-static/resources/protocol/xfrm.js
new file mode 100644
index 0000000000..b630f3818b
--- /dev/null
+++ b/protocols/luci-proto-xfrm/htdocs/luci-static/resources/protocol/xfrm.js
@@ -0,0 +1,51 @@
+'use strict';
+'require uci';
+'require form';
+'require network';
+'require tools.widgets as widgets';
+
+return network.registerProtocol('xfrm', {
+ getI18n: function() {
+ return _('IPsec XFRM');
+ },
+
+ getIfname: function() {
+ return this._ubus('l3_device') || this.sid;
+ },
+
+ getOpkgPackage: function() {
+ return 'xfrm';
+ },
+
+ isFloating: function() {
+ return true;
+ },
+
+ isVirtual: function() {
+ return true;
+ },
+
+ getDevice: function() {
+ return null;
+ },
+
+ containsDevice: function(ifname) {
+ return (network.getIfnameOf(ifname) == this.getIfname());
+ },
+
+ renderFormOptions: function(s) {
+ var o, ss;
+
+ o = s.taboption('general', form.Value, 'ifid', _('Interface ID'), _('Required. XFRM interface ID to be used for SA.'));
+ o.datatype = 'integer';
+
+ o = s.taboption('general', widgets.NetworkSelect, 'tunlink', _('Required. Underlying interface.'));
+ o.exclude = s.section;
+ o.nocreate = true;
+
+ o = s.taboption('general', form.Value, 'mtu', _('MTU'), _('Optional. Maximum Transmission Unit of the XFRM interface.'));
+ o.datatype = 'range(68,65535)';
+ o.placeholder = '1280';
+ o.optional = true;
+ }
+});
diff --git a/protocols/luci-proto-yggdrasil/Makefile b/protocols/luci-proto-yggdrasil/Makefile
new file mode 100644
index 0000000000..ecd20fb655
--- /dev/null
+++ b/protocols/luci-proto-yggdrasil/Makefile
@@ -0,0 +1,18 @@
+#
+# Copyright (C) 2023 kulupu.io development team (turretkeeper@kulupu.io)
+#
+# This is free software, licensed under the Apache License, Version 2.0 .
+#
+
+include $(TOPDIR)/rules.mk
+
+LUCI_TITLE:=Support for Yggdrasil Network
+LUCI_DEPENDS:=+yggdrasil
+LUCI_PKGARCH:=all
+PKG_VERSION:=1.0.0
+
+PKG_PROVIDES:=luci-proto-yggdrasil
+
+include ../../luci.mk
+
+# call BuildPackage - OpenWrt buildroot signature
diff --git a/protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js b/protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js
new file mode 100644
index 0000000000..849242abff
--- /dev/null
+++ b/protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js
@@ -0,0 +1,271 @@
+'use strict';
+'require form';
+'require network';
+'require rpc';
+'require tools.widgets as widgets';
+'require uci';
+'require ui';
+network.registerPatternVirtual(/^yggdrasil-.+$/);
+
+function validatePrivateKey(section_id,value) {
+ if (value.length == 0) {
+ return true;
+ };
+ if (!value.match(/^([0-9a-fA-F]){128}$/)) {
+ if (value != "auto") {
+ return _('Invalid private key string %s').format(value);
+ }
+ return true;
+ }
+ return true;
+};
+
+function validatePublicKey(section_id,value) {
+ if (value.length == 0) {
+ return true;
+ };
+ if (!value.match(/^([0-9a-fA-F]){64}$/))
+ return _('Invalid public key string %s').format(value);
+ return true;
+};
+
+function validateYggdrasilListenUri(section_id,value) {
+ if (value.length == 0) {
+ return true;
+ };
+ if (!value.match(/^(tls|tcp|unix|quic):\/\//))
+ return _('Unsupported URI scheme in %s').format(value);
+ return true;
+};
+
+function validateYggdrasilPeerUri(section_id,value) {
+ if (!value.match(/^(tls|tcp|unix|quic|socks|sockstls):\/\//))
+ return _('URI scheme %s not supported').format(value);
+ return true;
+};
+
+var cbiKeyPairGenerate = form.DummyValue.extend({
+ cfgvalue: function(section_id, value) {
+ return E('button', {
+ 'class':'btn',
+ 'click':ui.createHandlerFn(this, function(section_id,ev) {
+ var prv = this.section.getUIElement(section_id,'private_key'),
+ pub = this.section.getUIElement(section_id,'public_key'),
+ map = this.map;
+
+ return generateKey().then(function(keypair){
+ prv.setValue(keypair.priv);
+ pub.setValue(keypair.pub);
+ map.save(null,true);
+ });
+ },section_id)
+ },[_('Generate new key pair')]);
+ }
+});
+
+function updateActivePeers(ifname) {
+ getPeers(ifname).then(function(peers){
+ var table = document.querySelector('#yggdrasil-active-peerings-' + ifname);
+ if (table) {
+ while (table.rows.length > 1) { table.deleteRow(1); }
+ peers.forEach(function(peer) {
+ var row = table.insertRow(-1);
+ row.style.fontSize = "xx-small";
+ if (!peer.up) {
+ row.style.opacity = "66%";
+ }
+ var cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = peer.remote;
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = peer.up ? "Up" : "Down";
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = peer.inbound ? "In" : "Out";
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.innerHTML = "<u style='cursor: default'>" + peer.address + "</u>"
+ cell.dataToggle = "tooltip";
+ cell.title = "Key: " + peer.key;
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = '%t'.format(peer.uptime);
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = '%.2mB'.format(peer.bytes_recvd);
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = '%.2mB'.format(peer.bytes_sent);
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ cell.textContent = peer.priority;
+
+ cell = row.insertCell(-1)
+ cell.className = "td"
+ if (!peer.up) {
+ cell.innerHTML = "<u style='cursor: default'>%t ago</u>".format(peer.last_error_time)
+ cell.dataToggle = "tooltip"
+ cell.title = peer.last_error
+ } else {
+ cell.innerHTML = "-"
+ }
+ });
+ setTimeout(updateActivePeers.bind(this, ifname), 5000);
+ }
+ });
+}
+
+var cbiActivePeers = form.DummyValue.extend({
+ cfgvalue: function(section_id, value) {
+ updateActivePeers(this.option);
+ return E('table', {
+ 'class': 'table',
+ 'id': 'yggdrasil-active-peerings-' + this.option,
+ },[
+ E('tr', {'class': 'tr'}, [
+ E('th', {'class': 'th'}, _('URI')),
+ E('th', {'class': 'th'}, _('State')),
+ E('th', {'class': 'th'}, _('Dir')),
+ E('th', {'class': 'th'}, _('IP Address')),
+ E('th', {'class': 'th'}, _('Uptime')),
+ E('th', {'class': 'th'}, _('RX')),
+ E('th', {'class': 'th'}, _('TX')),
+ E('th', {'class': 'th'}, _('Priority')),
+ E('th', {'class': 'th'}, _('Last Error')),
+ ])
+ ]);
+ }
+});
+
+var generateKey = rpc.declare({
+ object:'luci.yggdrasil',
+ method:'generateKeyPair',
+ expect:{keys:{}}
+});
+
+var getPeers = rpc.declare({
+ object:'luci.yggdrasil',
+ method:'getPeers',
+ params:['interface'],
+ expect:{peers:[]}
+});
+
+return network.registerProtocol('yggdrasil',
+ {
+ getI18n: function() {
+ return _('Yggdrasil Network');
+ },
+ getIfname: function() {
+ return this._ubus('l3_device') || this.sid;
+ },
+ getType: function() {
+ return "tunnel";
+ },
+ getOpkgPackage: function() {
+ return 'yggdrasil';
+ },
+ isFloating: function() {
+ return true;
+ },
+ isVirtual: function() {
+ return true;
+ },
+ getDevices: function() {
+ return null;
+ },
+ containsDevice: function(ifname) {
+ return(network.getIfnameOf(ifname)==this.getIfname());
+ },
+ renderFormOptions: function(s) {
+ var o, ss;
+ o=s.taboption('general',form.Value,'private_key',_('Private key'),_('The private key for your Yggdrasil node'));
+ o.optional=false;
+ o.password=true;
+ o.validate=validatePrivateKey;
+
+ o=s.taboption('general',form.Value,'public_key',_('Public key'),_('The public key for your Yggdrasil node'));
+ o.optional=true;
+ o.validate=validatePublicKey;
+
+ s.taboption('general',cbiKeyPairGenerate,'_gen_server_keypair',' ');
+
+ o=s.taboption('advanced',form.Value,'mtu',_('MTU'),_('A default MTU of 65535 is set by Yggdrasil. It is recomended to utilize the default.'));
+ o.optional=true;
+ o.placeholder=65535;
+ o.datatype='range(1280, 65535)';
+
+ o=s.taboption('general',form.TextValue,'node_info',_('Node info'),_('Optional node info. This must be a { "key": "value", ... } map or set as null. This is entirely optional but, if set, is visible to the whole network on request.'));
+ o.optional=true;
+ o.placeholder="{}";
+
+ o=s.taboption('general',form.Flag,'node_info_privacy',_('Node info privacy'),_('Enable node info privacy so that only items specified in "Node info" are sent back. Otherwise defaults including the platform, architecture and Yggdrasil version are included.'));
+ o.default=o.disabled;
+
+ try {
+ s.tab('peers',_('Peers'));
+ } catch(e) {};
+ o=s.taboption('peers', form.SectionValue, '_active', form.NamedSection, this.sid, "interface", _("Active peers"))
+ ss=o.subsection;
+ ss.option(cbiActivePeers, this.sid);
+
+ o=s.taboption('peers', form.SectionValue, '_listen', form.NamedSection, this.sid, "interface", _("Listen for peers"))
+ ss=o.subsection;
+
+ o=ss.option(form.DynamicList,'listen_address',_('Listen addresses'), _('Add listeners in order to accept incoming peerings from non-local nodes. Multicast peer discovery works regardless of listeners set here. URI Format: <code>tls://0.0.0.0:0</code> or <code>tls://[::]:0</code> to listen on all interfaces. Choose an acceptable URI <code>tls://</code>, <code>tcp://</code>, <code>unix://</code> or <code>quic://</code>'));
+ o.placeholder="tls://0.0.0.0:0"
+ o.validate=validateYggdrasilListenUri;
+
+ o=s.taboption('peers',form.DynamicList,'allowed_public_key',_('Accept from public keys'),_('If empty, all incoming connections will be allowed (default). This does not affect outgoing peerings, nor link-local peers discovered via multicast.'));
+ o.validate=validatePublicKey;
+
+ o=s.taboption('peers', form.SectionValue, '_peers', form.TableSection, 'yggdrasil_%s_peer'.format(this.sid), _("Peer addresses"))
+ ss=o.subsection;
+ ss.addremove=true;
+ ss.anonymous=true;
+ ss.addbtntitle=_("Add peer address");
+
+ o=ss.option(form.Value,"address",_("Peer URI"));
+ o.placeholder="tls://0.0.0.0:0"
+ o.validate=validateYggdrasilPeerUri;
+ ss.option(widgets.NetworkSelect,"interface",_("Peer interface"));
+
+ o=s.taboption('peers', form.SectionValue, '_interfaces', form.TableSection, 'yggdrasil_%s_interface'.format(this.sid), _("Multicast rules"))
+ ss=o.subsection;
+ ss.addbtntitle=_("Add multicast rule");
+ ss.addremove=true;
+ ss.anonymous=true;
+
+ o=ss.option(widgets.DeviceSelect,"interface",_("Devices"));
+ o.multiple=true;
+
+ ss.option(form.Flag,"beacon",_("Send multicast beacon"));
+
+ ss.option(form.Flag,"listen",_("Listen to multicast beacons"));
+
+ o=ss.option(form.Value,"port",_("Port"));
+ o.optional=true;
+ o.datatype='range(1, 65535)';
+
+ o=ss.option(form.Value,"password",_("Password"));
+ o.optional=true;
+
+ return;
+ },
+ deleteConfiguration: function() {
+ uci.sections('network', 'yggdrasil_%s_interface'.format(this.sid), function(s) {
+ uci.remove('network', s['.name']);
+ });
+ uci.sections('network', 'yggdrasil_%s_peer'.format(this.sid), function(s) {
+ uci.remove('network', s['.name']);
+ });
+ }
+ }
+);
diff --git a/protocols/luci-proto-yggdrasil/root/usr/libexec/rpcd/luci.yggdrasil b/protocols/luci-proto-yggdrasil/root/usr/libexec/rpcd/luci.yggdrasil
new file mode 100755
index 0000000000..35d6627be7
--- /dev/null
+++ b/protocols/luci-proto-yggdrasil/root/usr/libexec/rpcd/luci.yggdrasil
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+. /usr/share/libubox/jshn.sh
+
+case "$1" in
+ list)
+ json_init
+ json_add_object "generateKeyPair"
+ json_close_object
+ json_add_object "getPeers"
+ json_add_string "interface"
+ json_close_object
+ json_dump
+ ;;
+ call)
+ case "$2" in
+ generateKeyPair)
+ json_load "$(yggdrasil -genconf -json)"
+ json_get_vars PrivateKey
+ json_cleanup
+ json_init
+ json_add_object "keys"
+ json_add_string "priv" "$PrivateKey"
+ json_add_string "pub" "${PrivateKey:64}"
+ json_close_object
+ json_dump
+ ;;
+ getPeers)
+ read -r input
+ json_load "$input"
+ json_get_vars interface
+ yggdrasilctl -endpoint="unix:///tmp/yggdrasil/${interface}.sock" -json getPeers
+ ;;
+ esac
+ ;;
+esac
diff --git a/protocols/luci-proto-yggdrasil/root/usr/share/rpcd/acl.d/luci-proto-yggdrasil.json b/protocols/luci-proto-yggdrasil/root/usr/share/rpcd/acl.d/luci-proto-yggdrasil.json
new file mode 100644
index 0000000000..0351d8610d
--- /dev/null
+++ b/protocols/luci-proto-yggdrasil/root/usr/share/rpcd/acl.d/luci-proto-yggdrasil.json
@@ -0,0 +1,10 @@
+{
+ "luci-proto-yggdrasil": {
+ "description": "Grant access to LuCI Yggdrasil procedures",
+ "write": {
+ "ubus": {
+ "luci.yggdrasil": [ "generateKeyPair", "getPeers" ]
+ }
+ }
+ }
+}