summaryrefslogtreecommitdiffhomepage
path: root/protocols/luci-proto-yggdrasil
diff options
context:
space:
mode:
authorWilliam Fleurant <meshnet@protonmail.com>2023-11-11 20:16:20 +0100
committerWilliam Fleurant <meshnet@protonmail.com>2023-11-11 20:16:20 +0100
commit6395c4d7a6f60618a83fcd35bf6850d2ab157470 (patch)
tree75fe0115e8d38fca211015c34398297ec7f9f7b6 /protocols/luci-proto-yggdrasil
parente8029b08287c666a43d5ef25c7ea83ce7d606903 (diff)
luci-proto-yggdrasil: yggdrasil now supported by netifd
- this package replaces luci-app-yggdrasil Signed-off-by: William Fleurant <meshnet@protonmail.com>
Diffstat (limited to 'protocols/luci-proto-yggdrasil')
-rw-r--r--protocols/luci-proto-yggdrasil/Makefile18
-rw-r--r--protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js267
-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
4 files changed, 331 insertions, 0 deletions
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..08c2450118
--- /dev/null
+++ b/protocols/luci-proto-yggdrasil/htdocs/luci-static/resources/protocol/yggdrasil.js
@@ -0,0 +1,267 @@
+'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');
+ }
+ 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');
+ return true;
+};
+
+function validateYggdrasilUri(section_id,value) {
+ if (!value.match(/^(tls|tcp|unix|quic):\/\//))
+ return _('URI scheme not supported');
+ return true;
+};
+
+function validateYggdrasilPeerUri(section_id,value) {
+ if (!value.match(/^(tls|tcp|unix|socks|quic):\/\//))
+ return _('URI scheme not supported');
+ 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;
+ 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 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=false;
+ o.validate=validatePublicKey;
+
+ s.taboption('general',cbiKeyPairGenerate,'_gen_server_keypair',' ');
+
+ o=s.taboption('advanced',form.Value,'mtu',_('MTU'),_('Specify an MTU (Maximum Transmission Unit) for your local TUN interface. Default is the largest supported size for your platform. The lowest possible value is 1280.'));
+ o.optional=true;
+ o.placeholder=1280;
+ 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'),_('By default, node info contains some defaults including the platform, architecture and Yggdrasil version. These can help when surveying the network and diagnosing network routing problems. Enabling node info privacy prevents this, so that only items specified in "Node info" are sent back if specified.'));
+ 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'),_('Listen addresses for incoming connections. You will need to add listeners in order to accept incoming peerings from non-local nodes. Multicast peer discovery will work regardless of any listeners set here. Each listener should be specified in URI format, e.g.tls://0.0.0.0:0 or tls://[::]:0 to listen on all interfaces.'));
+ o.placeholder="tls://0.0.0.0:0"
+
+ o=s.taboption('peers',form.DynamicList,'allowed_public_key',_('Allowed public keys'),_('List of peer public keys to allow incoming peering connections from. If left empty then all connections will be allowed by default. This does not affect outgoing peerings, nor does it affect 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"
+ 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..921195f65f
--- /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 PublicKey PrivateKey
+ json_cleanup
+ json_init
+ json_add_object "keys"
+ json_add_string "priv" "$PrivateKey"
+ json_add_string "pub" "$PublicKey"
+ json_close_object
+ json_dump
+ ;;
+ getPeers)
+ read -r input
+ json_load "$input"
+ json_get_vars interface
+ echo "$(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" ]
+ }
+ }
+ }
+}