yggdrasil: overhaul package with netifd support
authorWilliam Fleurant <meshnet@protonmail.com>
Sat, 11 Nov 2023 16:01:21 +0000 (17:01 +0100)
committerWilliam Fleurant <meshnet@protonmail.com>
Sat, 6 Jan 2024 14:22:10 +0000 (15:22 +0100)
- package is bumped to 0.5.2
- new protocol changes prevent peering with 0.4.x peers
- @turretkeeper revamps package with netifd support
- do not use with luci-app-yggdrasil please install luci-proto-yggdrasil

Signed-off-by: William Fleurant <meshnet@protonmail.com>
(cherry picked from commit 99c7c36ce16de656ed730b7a04fd55afceabb6dd)

net/yggdrasil/Makefile
net/yggdrasil/files/yggdrasil.defaults [deleted file]
net/yggdrasil/files/yggdrasil.init [deleted file]
net/yggdrasil/files/yggdrasil.sh [new file with mode: 0755]
net/yggdrasil/files/ygguci [deleted file]

index a40a9f5be82c845555b92386121f9ac559726f76..32db0306eb0c62466b43954dbfe863f35a61fca1 100644 (file)
@@ -1,12 +1,12 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=yggdrasil
-PKG_VERSION:=0.4.7
+PKG_VERSION:=0.5.2
 PKG_RELEASE:=1
 
 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
 PKG_SOURCE_URL:=https://codeload.github.com/yggdrasil-network/yggdrasil-go/tar.gz/v$(PKG_VERSION)?
-PKG_HASH:=47429f75b87d9b2450108471991e84c90d748606642e8778e9f578485b05a56f
+PKG_HASH:=ed908594ab687e141dd2202e1b360e5bd93f910de1fd1f737d210cc784cf2470
 PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)-go-$(PKG_VERSION)
 
 PKG_MAINTAINER:=William Fleurant <meshnet@protonmail.com>
@@ -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 +dkjson +libuci-lua
+       DEPENDS:=$(GO_ARCH_DEPENDS) @IPV6 +kmod-tun
 endef
 
 define Package/yggdrasil/description
@@ -46,14 +46,9 @@ 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 \
-               $(1)/etc/uci-defaults \
+               $(1)/lib/netifd/proto \
                $(1)/usr/sbin
 
        $(INSTALL_BIN) \
@@ -65,16 +60,8 @@ define Package/yggdrasil/install
                $(1)/usr/sbin
 
        $(INSTALL_BIN) \
-               ./files/ygguci \
-               $(1)/usr/sbin
-
-       $(INSTALL_BIN) \
-               ./files/yggdrasil.defaults \
-               $(1)/etc/uci-defaults/yggdrasil
-
-       $(INSTALL_BIN) \
-               ./files/yggdrasil.init \
-               $(1)/etc/init.d/yggdrasil
+               ./files/yggdrasil.sh \
+               $(1)/lib/netifd/proto
 endef
 
 $(eval $(call GoBinPackage,yggdrasil))
diff --git a/net/yggdrasil/files/yggdrasil.defaults b/net/yggdrasil/files/yggdrasil.defaults
deleted file mode 100644 (file)
index 2697262..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/bin/sh
-
-yggConfig="/etc/config/yggdrasil"
-
-first_boot_genConfig()
-{
-  . /usr/share/libubox/jshn.sh
-  boardcfg=$(ubus call system board)
-  touch ${yggConfig}
-  yggdrasil -genconf -json | ygguci set
-
-  json_load "$boardcfg"
-  json_get_var kernel     kernel
-  json_get_var system     system
-  json_get_var model      model
-  json_get_var board_name board_name
-  nodeinfo='{"kernel": "'$kernel'", "hostname":"'OpenWrt'", "system": "'$system'", "model": "'$model'", "board_name": "'$board_name'"}'
-
-  uci set yggdrasil.yggdrasil.IfName="ygg0"
-  uci set yggdrasil.yggdrasil.NodeInfo="$nodeinfo"
-  uci commit yggdrasil
-}
-
-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
-
-  # create the network interface
-  uci -q batch <<-EOF >/dev/null
-    set network.yggdrasil=interface
-    set network.yggdrasil.device=ygg0
-    set network.yggdrasil.proto=none
-EOF
-
-  # create the firewall zone
-  uci -q batch <<-EOF >/dev/null
-    set firewall.yggdrasil=zone
-    set firewall.yggdrasil.name=yggdrasil
-    add_list firewall.yggdrasil.network=yggdrasil
-    set firewall.yggdrasil.input=REJECT
-    set firewall.yggdrasil.output=ACCEPT
-    set firewall.yggdrasil.forward=REJECT
-    set firewall.yggdrasil.conntrack=1
-EOF
-
-  # allow ICMP from yggdrasil zone, e.g. ping6
-  uci -q batch <<-EOF >/dev/null
-    add firewall rule
-    set firewall.@rule[-1].name='Allow-ICMPv6-yggdrasil'
-    set firewall.@rule[-1].src=yggdrasil
-    set firewall.@rule[-1].proto=icmp
-    add_list firewall.@rule[-1].icmp_type=echo-request
-    add_list firewall.@rule[-1].icmp_type=echo-reply
-    add_list firewall.@rule[-1].icmp_type=destination-unreachable
-    add_list firewall.@rule[-1].icmp_type=packet-too-big
-    add_list firewall.@rule[-1].icmp_type=time-exceeded
-    add_list firewall.@rule[-1].icmp_type=bad-header
-    add_list firewall.@rule[-1].icmp_type=unknown-header-type
-    set firewall.@rule[-1].limit='1000/sec'
-    set firewall.@rule[-1].family=ipv6
-    set firewall.@rule[-1].target=ACCEPT
-EOF
-
-  # allow SSH from yggdrasil zone, needs to be explicitly enabled
-  uci -q batch <<-EOF >/dev/null
-    add firewall rule
-    set firewall.@rule[-1].enabled=0
-    set firewall.@rule[-1].name='Allow-SSH-yggdrasil'
-    set firewall.@rule[-1].src=yggdrasil
-    set firewall.@rule[-1].proto=tcp
-    set firewall.@rule[-1].dest_port=22
-    set firewall.@rule[-1].target=ACCEPT
-EOF
-
-  # allow LuCI access from yggdrasil zone, needs to be explicitly enabled
-  uci -q batch <<-EOF >/dev/null
-    add firewall rule
-    set firewall.@rule[-1].enabled=0
-    set firewall.@rule[-1].name='Allow-HTTP-yggdrasil'
-    set firewall.@rule[-1].src=yggdrasil
-    set firewall.@rule[-1].proto=tcp
-    set firewall.@rule[-1].dest_port=80
-    set firewall.@rule[-1].target=ACCEPT
-EOF
-
-  # allow LuCI access with SSL from yggdrasil zone, needs to be explicitly enabled
-  uci -q batch <<-EOF >/dev/null
-    add firewall rule
-    set firewall.@rule[-1].enabled=0
-    set firewall.@rule[-1].name='Allow-HTTPS-yggdrasil'
-    set firewall.@rule[-1].src=yggdrasil
-    set firewall.@rule[-1].proto=tcp
-    set firewall.@rule[-1].dest_port=443
-    set firewall.@rule[-1].target=ACCEPT
-EOF
-
-  uci commit firewall
-  uci commit network
-
-else
-  :
-fi
-
-exit 0
diff --git a/net/yggdrasil/files/yggdrasil.init b/net/yggdrasil/files/yggdrasil.init
deleted file mode 100755 (executable)
index 3510e3a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/sh /etc/rc.common
-
-START=90
-STOP=85
-
-USE_PROCD=1
-BIN_FILE="/usr/sbin/yggdrasil"
-CONFIG_FILE="/tmp/yggdrasil.conf"
-DAEMON_OPTS="-useconffile $CONFIG_FILE"
-
-start_service()
-{
-       [ -f /etc/uci-defaults/yggdrasil ] && ( . /etc/uci-defaults/yggdrasil )
-
-       /usr/sbin/ygguci get | $BIN_FILE -useconf -normaliseconf -json > $CONFIG_FILE
-
-       procd_open_instance
-       procd_set_param respawn
-       procd_set_param command $BIN_FILE $DAEMON_OPTS
-       procd_set_param stdout 1
-       procd_set_param stderr 1
-       procd_close_instance
-}
-
-reload_service()
-{
-       restart
-}
-
-service_triggers()
-{
-       procd_add_reload_trigger yggdrasil
-}
diff --git a/net/yggdrasil/files/yggdrasil.sh b/net/yggdrasil/files/yggdrasil.sh
new file mode 100755 (executable)
index 0000000..a850920
--- /dev/null
@@ -0,0 +1,205 @@
+#!/bin/sh
+
+
+[ -n "$INCLUDE_ONLY" ] || {
+       . /lib/functions.sh
+       . ../netifd-proto.sh
+       init_proto "$@"
+}
+
+proto_yggdrasil_init_config() {
+       proto_config_add_string "private_key"
+       available=1
+}
+
+proto_yggdrasil_setup_peer_if_non_interface() {
+       local peer_config="$1"
+       local peer_address
+       local peer_interface
+       config_get peer_address "${peer_config}" "address"
+       config_get peer_interface "${peer_config}" "interface"
+       if [ -z ${peer_interface} ]; then
+               json_add_string "" ${peer_address}
+       fi;
+}
+
+proto_yggdrasil_dump_peer_interface() {
+       local peer_config="$1"
+       local peer_interface
+
+       config_get peer_interface "${peer_config}" "interface"
+
+       if [ ! -z ${peer_interface} ]; then
+               peer_interfaces="${peer_interfaces}\n${peer_interface}"
+       fi;
+}
+
+proto_yggdrasil_setup_peer_if_interface() {
+       local peer_config="$1"
+       local peer_address
+       local peer_interface
+       config_get peer_interface "${peer_config}" "interface"
+       if [ "${peer_interface}" = "${peer_interface_filter}" ]; then
+               config_get peer_address "${peer_config}" "address"
+               json_add_string "" ${peer_address}
+       fi;
+}
+
+proto_yggdrasil_append_to_interface_regex() {
+       if [ -z "${regex}" ]; then
+               regex="$1"
+       else
+               regex="${regex}|$1";
+       fi;
+}
+
+proto_yggdrasil_setup_multicast_interface() {
+       local interface_config="$1"
+       local beacon
+       local listen
+       local port=0
+       local password
+       local regex=""
+
+       config_get beacon "${interface_config}" "beacon"
+       config_get listen "${interface_config}" "listen"
+       config_get port "${interface_config}" "port"
+       config_get password "${interface_config}" "password"
+
+       json_add_object ""
+       json_add_boolean "Beacon" $beacon
+       json_add_boolean "Listen" $listen
+       if [ ! -z ${port} ]; then
+               json_add_int "Port" $port
+       else
+               json_add_int "Port" 0
+       fi;
+       if [ ! -z ${password} ]; then
+               json_add_string "Password" $password
+       fi;
+
+       config_list_foreach "${interface_config}" interface proto_yggdrasil_append_to_interface_regex
+
+       json_add_string "Regex" "^(${regex})\$"
+
+       json_close_object
+}
+
+proto_yggdrasil_add_string() {
+       json_add_string "" $1
+}
+
+proto_yggdrasil_generate_keypair() {
+       json_load "$(yggdrasil -genconf -json)"
+       json_get_vars PublicKey PrivateKey
+       json_cleanup
+       private_key=$PrivateKey
+       public_key=$PublicKey
+}
+
+proto_yggdrasil_setup() {
+       local config="$1"
+       local device="$2"
+       local ygg_dir="/tmp/yggdrasil"
+       local ygg_cfg="${ygg_dir}/${config}.conf"
+       local ygg_sock="unix://${ygg_dir}/${config}.sock"
+
+
+       local private_key
+       local public_key
+       local mtu
+       local listen_addresses
+       local whitelisted_keys
+       local node_info
+       local node_info_privacy
+
+       config_load network
+       config_get private_key "${config}" "private_key"
+       config_get public_key "${config}" "public_key"
+       config_get mtu "${config}" "mtu"
+       config_get node_info "${config}" "node_info"
+       config_get node_info_privacy "${config}" "node_info_privacy"
+
+       if [ -z $private_key ]; then
+               proto_yggdrasil_generate_keypair
+       fi;
+
+       umask 077
+       mkdir -p "${ygg_dir}"
+
+       if [ $private_key = "auto" ]; then
+               proto_yggdrasil_generate_keypair
+               uci -t ${ygg_dir}/.uci.${config} batch <<EOF
+                       set network.${config}.private_key='${private_key}'
+                       set network.${config}.public_key='${public_key}'
+EOF
+               uci -t ${ygg_dir}/.uci.${config} commit;
+       fi;
+
+       # Generate config file
+       json_init
+       json_add_string "IfName" ${config}
+       json_add_string "AdminListen" ${ygg_sock}
+
+       json_add_string "PrivateKey" ${private_key}
+       json_add_string "PublicKey" ${public_key}
+
+       if [ ! -z $mtu ]; then
+               json_add_int "IfMTU" ${mtu}
+       fi;
+
+       if [ ! -z $node_info ]; then
+               json_add_string "NodeInfo" "%%_YGGDRASIL_NODEINFO_TEMPLATE_%%"
+       fi;
+
+       json_add_boolean "NodeInfoPrivacy" ${node_info_privacy}
+
+       # Peers
+       json_add_array "Peers"
+       config_foreach proto_yggdrasil_setup_peer_if_non_interface "yggdrasil_${config}_peer"
+       json_close_array
+
+       local peer_interfaces
+       peer_interfaces=""
+       config_foreach proto_yggdrasil_dump_peer_interface "yggdrasil_${config}_peer"
+       peer_interfaces=$(echo -e ${peer_interfaces} | sort | uniq)
+
+       json_add_object "InterfacePeers"
+       for peer_interface_filter in ${peer_interfaces}; do
+               json_add_array "${peer_interface_filter}"
+               config_foreach proto_yggdrasil_setup_peer_if_interface "yggdrasil_${config}_peer"
+               json_close_array
+       done
+       json_close_object
+
+       json_add_array "AllowedPublicKeys"
+       config_list_foreach "$config" allowed_public_key proto_yggdrasil_add_string
+       json_close_array
+
+       json_add_array "Listen"
+       config_list_foreach "$config" listen_address proto_yggdrasil_add_string
+       json_close_array
+
+       json_add_array "MulticastInterfaces"
+       config_foreach proto_yggdrasil_setup_multicast_interface "yggdrasil_${config}_interface"
+       json_close_array
+
+       json_dump > "${ygg_cfg}.1"
+       awk -v s='"%%_YGGDRASIL_NODEINFO_TEMPLATE_%%"' -v r="${node_info}" '{gsub(s, r)} 1' "${ygg_cfg}.1" > ${ygg_cfg}
+       rm "${ygg_cfg}.1"
+
+       proto_run_command "$config" /usr/sbin/yggdrasil -useconffile "${ygg_cfg}"
+       proto_init_update "$config" 1
+       proto_add_ipv6_address "$(yggdrasil -useconffile "${ygg_cfg}" -address)" "7"
+       proto_add_ipv6_prefix "$(yggdrasil -useconffile "${ygg_cfg}" -subnet)"
+       proto_send_update "$config"
+}
+
+proto_yggdrasil_teardown() {
+       local interface="$1"
+       proto_kill_command "$interface"
+}
+
+[ -n "$INCLUDE_ONLY" ] || {
+       add_protocol yggdrasil
+}
diff --git a/net/yggdrasil/files/ygguci b/net/yggdrasil/files/ygguci
deleted file mode 100755 (executable)
index cdeb3c1..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/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,
-               IfMTU = 65535,
-
-               Peers = { }, Listen = { }, MulticastInterfaces = { }, AllowedPublicKeys = { },
-               InterfacePeers = setmetatable({ }, {__jsontype = "object"}),
-               NodeInfo = setmetatable({ }, {__jsontype = "object"})
-       }
-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.PublicKey = config.PublicKey
-       obj.PrivateKey = config.PrivateKey
-       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
-       if config["IfMTU"] ~= nil then obj["IfMTU"] = tonumber(config["IfMTU"]) 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, {
-                       Beacon = to_bool(s.beacon), Listen = to_bool(s.listen),
-                       Port = tonumber(s.port), Regex = s.regex
-               })
-       end)
-       cursor:foreach("yggdrasil", "allowed_public_key", function (s) 
-               table.insert(obj.AllowedPublicKeys, 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)
-
-       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", "PublicKey", obj.PublicKey) 
-       cursor:set("yggdrasil", "yggdrasil", "PrivateKey", obj.PrivateKey) 
-       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", "IfMTU", obj.IfMTU)
-
-       set_values(cursor, "peer", "uri", obj.Peers)
-       set_values(cursor, "listen_address", "uri", obj.Listen)
-
-       for _, interface in pairs(obj.MulticastInterfaces) do
-               local name = cursor:add("yggdrasil", "multicast_interface")
-               cursor:set("yggdrasil", name, "beacon", to_int(interface.Beacon))
-               cursor:set("yggdrasil", name, "listen", to_int(interface.Listen))
-               cursor:set("yggdrasil", name, "port", interface.Port)
-               cursor:set("yggdrasil", name, "regex", interface.Regex)
-       end
-
-       set_values(cursor, "allowed_public_key", "key", obj.AllowedPublicKeys)
-
-       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
-
-       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