--- /dev/null
+#!/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