diff options
author | Jo-Philipp Wich <jo@mein.io> | 2023-12-01 00:37:22 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-01 00:37:22 +0100 |
commit | ab3f8254753d18935b1f6d8d015709613b3990a1 (patch) | |
tree | 83d49eb3b8e47f282091bba968bb95a26aa68d20 | |
parent | a6753d44e7bcff305f789e182031fb5f089a8836 (diff) | |
parent | c9786be7518929f076ad9a1abe190b2573851128 (diff) |
Merge pull request #6689 from yggdrasil-openwrt/yggdrasil-2023-11-11
luci-proto-yggdrasil: yggdrasil now supported by netifd
4 files changed, 335 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..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" ] + } + } + } +} |