#!/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, 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" }) 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", "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