diff --git a/net/yggdrasil/Makefile b/net/yggdrasil/Makefile index c2ffdbb07..61dc2ae54 100644 --- a/net/yggdrasil/Makefile +++ b/net/yggdrasil/Makefile @@ -2,7 +2,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=yggdrasil PKG_VERSION:=0.3.11 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/yggdrasil-network/yggdrasil-go/tar.gz/v$(PKG_VERSION)? @@ -33,7 +33,7 @@ define Package/yggdrasil SUBMENU:=Routing and Redirection TITLE:=Yggdrasil supports end-to-end encrypted IPv6 networks URL:=https://yggdrasil-network.github.io/ - DEPENDS:=$(GO_ARCH_DEPENDS) @IPV6 +kmod-tun + DEPENDS:=$(GO_ARCH_DEPENDS) @IPV6 +kmod-tun +dkjson +libuci-lua endef define Package/yggdrasil/description @@ -46,6 +46,10 @@ define Package/yggdrasil/description interfaces simultaneously with much greater throughput. endef +define Package/yggdrasil/conffiles +/etc/config/yggdrasil +endef + define Package/yggdrasil/install $(INSTALL_DIR) \ $(1)/etc/init.d \ @@ -60,6 +64,10 @@ define Package/yggdrasil/install $(GO_PKG_BUILD_BIN_DIR)/yggdrasilctl \ $(1)/usr/sbin + $(INSTALL_BIN) \ + ./files/ygguci \ + $(1)/usr/sbin + $(INSTALL_BIN) \ ./files/yggdrasil.defaults \ $(1)/etc/uci-defaults/yggdrasil diff --git a/net/yggdrasil/files/yggdrasil.defaults b/net/yggdrasil/files/yggdrasil.defaults index d5076cf6d..7f9aaf659 100644 --- a/net/yggdrasil/files/yggdrasil.defaults +++ b/net/yggdrasil/files/yggdrasil.defaults @@ -1,12 +1,13 @@ #!/bin/sh -yggConfig="/etc/yggdrasil.conf" +yggConfig="/etc/config/yggdrasil" first_boot_genConfig() { . /usr/share/libubox/jshn.sh boardcfg=$(ubus call system board) - yggcfg=$(yggdrasil -genconf -json | grep NodeInfo -v) + touch ${yggConfig} + yggdrasil -genconf -json | ygguci set json_load "$boardcfg" json_get_var kernel kernel @@ -14,23 +15,22 @@ first_boot_genConfig() json_get_var system system json_get_var model model json_get_var board_name board_name + nodeinfo='{"kernel": "'$kernel'", "hostname":"'$hostname'", "system": "'$system'", "model": "'$model'", "board_name": "'$board_name'"}' - json_load "$yggcfg" - json_add_string "IfName" "ygg0" - json_add_object "NodeInfo" - json_add_string "kernel" "$kernel" - json_add_string "hostname" "$hostname" - json_add_string "system" "$system" - json_add_string "model" "$model" - json_add_string "board_name" "$board_name" - json_close_object - json_dump + uci set yggdrasil.yggdrasil.IfName="ygg0" + uci set yggdrasil.yggdrasil.NodeInfo="$nodeinfo" + uci commit yggdrasil } -if [ ! -e ${yggConfig} ]; then +if [ -e /etc/yggdrasil.conf ]; then + echo "config: import config from /etc/yggdrasil.conf to /etc/config/yggdrasil" | logger -t yggdrasil + touch ${yggConfig} + cat /etc/yggdrasil.conf | ygguci set + mv /etc/yggdrasil.conf /etc/yggdrasil.conf.bak +elif [ ! -e ${yggConfig} ]; then echo "first_boot: adding system board details to NodeInfo[] in NEW config: ${yggConfig}" | logger -t yggdrasil - first_boot_genConfig > ${yggConfig} + first_boot_genConfig # create the network interface uci -q batch <<-EOF >/dev/null diff --git a/net/yggdrasil/files/yggdrasil.init b/net/yggdrasil/files/yggdrasil.init index 6ad3f6583..d981834bd 100755 --- a/net/yggdrasil/files/yggdrasil.init +++ b/net/yggdrasil/files/yggdrasil.init @@ -12,7 +12,7 @@ start_service() procd_open_instance procd_set_param respawn - procd_set_param command /usr/sbin/yggdrasil -useconffile /etc/yggdrasil.conf + procd_set_param command /bin/ash -c "ygguci get | yggdrasil -useconf -normaliseconf -json | yggdrasil -useconf" procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance diff --git a/net/yggdrasil/files/ygguci b/net/yggdrasil/files/ygguci new file mode 100755 index 000000000..29751c46e --- /dev/null +++ b/net/yggdrasil/files/ygguci @@ -0,0 +1,237 @@ +#!/usr/bin/env lua + +dkjson = require("dkjson") +uci = require("uci") + +UCI = {} + +--- Return the configuration defaults as a table suitable for JSON output +-- +-- Mostly taken from yggdrasil -genconf -json +-- @return table with configuration defaults +function UCI.defaults() + return { + AdminListen = "unix:///var/run/yggdrasil.sock", IfName = "ygg0", + NodeInfoPrivacy = false, IfTAPMode = false, + LinkLocalTCPPort = 0, IfMTU = 65535, + + Peers = { }, Listen = { }, MulticastInterfaces = { }, AllowedEncryptionPublicKeys = { }, + InterfacePeers = setmetatable({ }, {__jsontype = "object"}), + NodeInfo = setmetatable({ }, {__jsontype = "object"}), + + SessionFirewall = { + Enable = false, + AllowFromDirect = true, + AllowFromRemote = true, + AlwaysAllowOutbound = true, + WhitelistEncryptionPublicKeys = { }, + BlacklistEncryptionPublicKeys = { } + }, + TunnelRouting = { + Enable = false, + IPv6RemoteSubnets = setmetatable({ }, {__jsontype = "object"}), + IPv6LocalSubnets = { }, + IPv4RemoteSubnets = setmetatable({ }, {__jsontype = "object"}), + IPv4LocalSubnets = { } + }, + SwitchOptions = { MaxTotalQueueSize = 4194304 } + } +end + +--- Return the yggdrasil configuration as a table suitable for JSON output +-- +-- @return table with yggdrasil configuration +function UCI.get() + local obj = UCI.defaults() + + local cursor = uci.cursor() + local config = cursor:get_all("yggdrasil", "yggdrasil") + if not config then return obj end + + obj.EncryptionPublicKey = config.EncryptionPublicKey + obj.EncryptionPrivateKey = config.EncryptionPrivateKey + obj.SigningPublicKey = config.SigningPublicKey + obj.SigningPrivateKey = config.SigningPrivateKey + obj.AdminListen = config.AdminListen or obj.AdminListen + obj.IfName = config.IfName or obj.IfName + obj.NodeInfo = dkjson.decode(config.NodeInfo) or obj.NodeInfo + for _, v in pairs({ "NodeInfoPrivacy", "IfTAPMode" }) do + if config[v] ~= nil then obj[v] = to_bool(config[v]) end + end + for _, v in pairs({ "LinkLocalTCPPort", "IfMTU" }) do + if config[v] ~= nil then obj[v] = tonumber(config[v]) end + end + + cursor:foreach("yggdrasil", "peer", function (s) + table.insert(obj.Peers, s.uri) + end) + cursor:foreach("yggdrasil", "listen_address", function (s) + table.insert(obj.Listen, s.uri) + end) + cursor:foreach("yggdrasil", "multicast_interface", function (s) + table.insert(obj.MulticastInterfaces, s.name) + end) + cursor:foreach("yggdrasil", "allowed_encryption_public_key", function (s) + table.insert(obj.AllowedEncryptionPublicKeys, s.key) + end) + + cursor:foreach("yggdrasil", "interface_peer", function (s) + if obj.InterfacePeers[s.interface] == nil then + obj.InterfacePeers[s.interface] = {} + end + table.insert(obj.InterfacePeers[s["interface"]], s.uri) + end) + + -- session firewall config + local session_firewall_config = { "Enable", "AllowFromDirect", "AllowFromRemote", "AlwaysAllowOutbound" } + for _, v in pairs(session_firewall_config) do + if config["SessionFirewall_"..v] ~= nil then + obj.SessionFirewall[v] = to_bool(config["SessionFirewall_"..v]) + end + end + cursor:foreach("yggdrasil", "whitelisted_encryption_public_key", function (s) + table.insert(obj.SessionFirewall.WhitelistEncryptionPublicKeys, s.key) + end) + cursor:foreach("yggdrasil", "blacklisted_encryption_public_key", function (s) + table.insert(obj.SessionFirewall.BlacklistEncryptionPublicKeys, s.key) + end) + -- /session firewall config + + -- tunnel routing config + if config.TunnelRouting_Enable ~= nil then + obj.TunnelRouting.Enable = to_bool(config.TunnelRouting_Enable) + end + cursor:foreach("yggdrasil", "ipv6_remote_subnet", function (s) + obj.TunnelRouting.IPv6RemoteSubnets[s.subnet] = s.key + end) + cursor:foreach("yggdrasil", "ipv6_local_subnet", function (s) + table.insert(obj.TunnelRouting.IPv6LocalSubnets, s.subnet) + end) + cursor:foreach("yggdrasil", "ipv4_remote_subnet", function (s) + obj.TunnelRouting.IPv4RemoteSubnets[s.subnet] = s.key + end) + cursor:foreach("yggdrasil", "ipv4_local_subnet", function (s) + table.insert(obj.TunnelRouting.IPv4LocalSubnets, s.subnet) + end) + -- /tunnel routing config + + if config.SwitchOptions_MaxTotalQueueSize ~= nil then + obj.SwitchOptions.MaxTotalQueueSize = tonumber(config.SwitchOptions_MaxTotalQueueSize) + end + + return obj +end + +--- Parse and save updated configuration from JSON input +-- +-- Transforms general settings into UCI sections, and replaces the UCI config's +-- contents with them. +-- @param table JSON input +-- @return Boolean whether saving succeeded +function UCI.set(obj) + local cursor = uci.cursor() + + for i, section in pairs(cursor:get_all("yggdrasil")) do + cursor:delete("yggdrasil", section[".name"]) + end + + + cursor:set("yggdrasil", "yggdrasil", "yggdrasil") + cursor:set("yggdrasil", "yggdrasil", "EncryptionPublicKey", obj.EncryptionPublicKey) + cursor:set("yggdrasil", "yggdrasil", "EncryptionPrivateKey", obj.EncryptionPrivateKey) + cursor:set("yggdrasil", "yggdrasil", "SigningPublicKey", obj.SigningPublicKey) + cursor:set("yggdrasil", "yggdrasil", "SigningPrivateKey", obj.SigningPrivateKey) + cursor:set("yggdrasil", "yggdrasil", "AdminListen", obj.AdminListen) + cursor:set("yggdrasil", "yggdrasil", "IfName", obj.IfName) + cursor:set("yggdrasil", "yggdrasil", "NodeInfoPrivacy", to_int(obj.NodeInfoPrivacy)) + cursor:set("yggdrasil", "yggdrasil", "NodeInfo", dkjson.encode(obj.NodeInfo)) + cursor:set("yggdrasil", "yggdrasil", "IfTAPMode", to_int(obj.IfTAPMode)) + cursor:set("yggdrasil", "yggdrasil", "LinkLocalTCPPort", obj.LinkLocalTCPPort) + cursor:set("yggdrasil", "yggdrasil", "IfMTU", obj.IfMTU) + + set_values(cursor, "peer", "uri", obj.Peers) + set_values(cursor, "listen_address", "uri", obj.Listen) + set_values(cursor, "multicast_interface", "name", obj.MulticastInterfaces) + set_values(cursor, "allowed_encryption_public_key", "key", obj.AllowedEncryptionPublicKeys) + + for interface, peers in pairs(obj.InterfacePeers) do + for _, v in pairs(peers) do + local name = cursor:add("yggdrasil", "interface_peer") + cursor:set("yggdrasil", name, "interface", interface) + cursor:set("yggdrasil", name, "uri", v) + end + end + + -- session firewall config + cursor:set("yggdrasil", "yggdrasil", "SessionFirewall_Enable", to_int(obj.SessionFirewall.Enable)) + cursor:set("yggdrasil", "yggdrasil", "SessionFirewall_AllowFromDirect", to_int(obj.SessionFirewall.AllowFromDirect)) + cursor:set("yggdrasil", "yggdrasil", "SessionFirewall_AllowFromRemote", to_int(obj.SessionFirewall.AllowFromRemote)) + cursor:set("yggdrasil", "yggdrasil", "SessionFirewall_AlwaysAllowOutbound", to_int(obj.SessionFirewall.AlwaysAllowOutbound)) + set_values(cursor, "whitelisted_encryption_public_key", "key", obj.SessionFirewall.WhitelistEncryptionPublicKeys) + set_values(cursor, "blacklisted_encryption_public_key", "key", obj.SessionFirewall.BlacklistEncryptionPublicKeys) + -- /session firewall config + + -- tunnel routing config + cursor:set("yggdrasil", "yggdrasil", "TunnelRouting_Enable", to_int(obj.TunnelRouting.Enable)) + if obj.TunnelRouting.IPv6RemoteSubnets ~= nil then + for subnet, key in pairs(obj.TunnelRouting.IPv6RemoteSubnets) do + local name = cursor:add("yggdrasil", "ipv6_remote_subnet") + cursor:set("yggdrasil", name, "subnet", subnet) + cursor:set("yggdrasil", name, "key", key) + end + end + set_values(cursor, "ipv6_local_subnet", "subnet", obj.TunnelRouting.IPv6LocalSubnets) + if obj.TunnelRouting.IPv4RemoteSubnets ~= nil then + for subnet, key in pairs(obj.TunnelRouting.IPv4RemoteSubnets) do + local name = cursor:add("yggdrasil", "ipv4_remote_subnet") + cursor:set("yggdrasil", name, "subnet", subnet) + cursor:set("yggdrasil", name, "key", key) + end + end + set_values(cursor, "ipv4_local_subnet", "subnet", obj.TunnelRouting.IPv4LocalSubnets) + -- /tunnel routing config + + cursor:set("yggdrasil", "yggdrasil", "SwitchOptions_MaxTotalQueueSize", obj.SwitchOptions.MaxTotalQueueSize) + + return cursor:commit("yggdrasil") +end + +function set_values(cursor, section_name, parameter, values) + if values == nil then return false end + + for k, v in pairs(values) do + local name = cursor:add("yggdrasil", section_name) + cursor:set("yggdrasil", name, parameter, v) + end +end + +function to_int(bool) return bool and '1' or '0' end + +function to_bool(int) return int ~= '0' end + +function help() + print("JSON interface to /etc/config/yggdrasil\n\nExamples: \ + ygguci get > /tmp/etc/yggdrasil.conf \ + cat /tmp/etc/yggdrasil.conf | ygguci set \ + uci changes \ + ygguci get | yggdrasil -useconf") +end + +-- main + +if arg[1] == "get" then + local json = dkjson.encode(UCI.get(), { indent = true }) + print(json) +elseif arg[1] == "set" then + local json = io.stdin:read("*a") + local obj, pos, err = dkjson.decode(json, 1, nil) + + if obj then + UCI.set(obj) + else + print("dkjson: " .. err) + os.exit(1) + end +else + help() +end