Skip to content

Commit

Permalink
luci-proto-yggdrasil: yggdrasil now supported by netifd
Browse files Browse the repository at this point in the history
- this package replaces luci-app-yggdrasil

Signed-off-by: William Fleurant <meshnet@protonmail.com>
  • Loading branch information
wfleurant committed Nov 11, 2023
1 parent e8029b0 commit 6395c4d
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 0 deletions.
18 changes: 18 additions & 0 deletions protocols/luci-proto-yggdrasil/Makefile
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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']);
});
}
}
);
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"luci-proto-yggdrasil": {
"description": "Grant access to LuCI Yggdrasil procedures",
"write": {
"ubus": {
"luci.yggdrasil": [ "generateKeyPair", "getPeers" ]
}
}
}
}

0 comments on commit 6395c4d

Please sign in to comment.