secubox-openwrt/package/secubox/luci-app-meshname-dns/root/usr/libexec/rpcd/luci.meshname
CyberMind-FR 07705f458c feat(meshname-dns): Add decentralized .ygg domain resolution
Implements Meshname DNS for Yggdrasil mesh networks with gossip-based
service discovery and dnsmasq integration.

New packages:
- secubox-app-meshname-dns: Core service with meshnamectl CLI
- luci-app-meshname-dns: LuCI dashboard for service management

Features:
- Services announce .ygg domains via gossip protocol (meshname_announce)
- dnsmasq integration via /tmp/hosts/meshname dynamic hosts file
- Cross-node resolution through gossip message propagation
- RPCD handler with 8 methods for LuCI integration

CLI commands: announce, revoke, resolve, list, sync, status, daemon

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-28 07:57:16 +01:00

335 lines
7.7 KiB
Bash

#!/bin/sh
# SPDX-License-Identifier: MIT
# RPCD backend for Meshname DNS LuCI app
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
CONFIG="meshname-dns"
STATE_DIR="/var/lib/meshname-dns"
DOMAINS_FILE="$STATE_DIR/domains.json"
LOCAL_FILE="$STATE_DIR/local.json"
HOSTS_FILE="/tmp/hosts/meshname"
log_msg() {
logger -t "luci.meshname" "$1"
}
# Get Yggdrasil IPv6
get_ygg_ipv6() {
ip -6 addr show tun0 2>/dev/null | grep -oP '(?<=inet6 )2[0-9a-f:]+(?=/)' || \
ip -6 addr show 2>/dev/null | grep -oP '(?<=inet6 )2[0-9a-f][0-9a-f]:[0-9a-f:]+(?=/)' | head -1
}
# Get service status
get_status() {
local enabled running ygg_ipv6 local_count domain_count last_sync
enabled=$(uci -q get $CONFIG.main.enabled)
ygg_ipv6=$(get_ygg_ipv6)
# Check if daemon is running
if pgrep -f "meshnamectl daemon" >/dev/null 2>&1; then
running="1"
else
running="0"
fi
# Count services
local_count=0
domain_count=0
if [ -f "$LOCAL_FILE" ] && [ -s "$LOCAL_FILE" ]; then
local_count=$(python3 -c "import json; print(len(json.load(open('$LOCAL_FILE'))))" 2>/dev/null || echo 0)
fi
if [ -f "$DOMAINS_FILE" ] && [ -s "$DOMAINS_FILE" ]; then
domain_count=$(python3 -c "import json; print(len(json.load(open('$DOMAINS_FILE'))))" 2>/dev/null || echo 0)
fi
# Last sync time
if [ -f "$STATE_DIR/last_sync" ]; then
last_sync=$(cat "$STATE_DIR/last_sync")
else
last_sync=""
fi
json_init
json_add_string "enabled" "${enabled:-0}"
json_add_string "running" "$running"
json_add_string "ygg_ipv6" "${ygg_ipv6:-}"
json_add_int "local_count" "$local_count"
json_add_int "domain_count" "$domain_count"
json_add_string "last_sync" "$last_sync"
json_dump
}
# List all domains (local + remote)
list_domains() {
json_init
json_add_array "local"
if [ -f "$LOCAL_FILE" ] && [ -s "$LOCAL_FILE" ]; then
python3 -c "
import json, sys
try:
with open('$LOCAL_FILE') as f:
data = json.load(f)
for name, info in data.items():
print(json.dumps(info))
except:
pass
" | while read -r line; do
json_add_object ""
local name=$(echo "$line" | jsonfilter -e '@.name')
local fqdn=$(echo "$line" | jsonfilter -e '@.fqdn')
local ipv6=$(echo "$line" | jsonfilter -e '@.ipv6')
local port=$(echo "$line" | jsonfilter -e '@.port')
json_add_string "name" "$name"
json_add_string "fqdn" "$fqdn"
json_add_string "ipv6" "$ipv6"
json_add_int "port" "${port:-0}"
json_add_boolean "local" 1
json_close_object
done
fi
json_close_array
json_add_array "remote"
if [ -f "$DOMAINS_FILE" ] && [ -s "$DOMAINS_FILE" ]; then
python3 -c "
import json, sys
try:
with open('$DOMAINS_FILE') as f:
data = json.load(f)
for name, info in data.items():
print(json.dumps(info))
except:
pass
" | while read -r line; do
json_add_object ""
local name=$(echo "$line" | jsonfilter -e '@.name')
local fqdn=$(echo "$line" | jsonfilter -e '@.fqdn')
local ipv6=$(echo "$line" | jsonfilter -e '@.ipv6')
local port=$(echo "$line" | jsonfilter -e '@.port')
local origin=$(echo "$line" | jsonfilter -e '@.origin')
json_add_string "name" "$name"
json_add_string "fqdn" "$fqdn"
json_add_string "ipv6" "$ipv6"
json_add_int "port" "${port:-0}"
json_add_string "origin" "$origin"
json_add_boolean "local" 0
json_close_object
done
fi
json_close_array
json_dump
}
# Announce a service
do_announce() {
local name="$1"
local port="${2:-0}"
if [ -z "$name" ]; then
json_init
json_add_string "error" "Name is required"
json_dump
return
fi
local result
result=$(/usr/sbin/meshnamectl announce "$name" "$port" 2>&1)
json_init
if echo "$result" | grep -q "Announced"; then
json_add_string "status" "ok"
json_add_string "fqdn" "${name}.ygg"
else
json_add_string "status" "error"
json_add_string "error" "$result"
fi
json_dump
}
# Revoke a service
do_revoke() {
local name="$1"
if [ -z "$name" ]; then
json_init
json_add_string "error" "Name is required"
json_dump
return
fi
local result
result=$(/usr/sbin/meshnamectl revoke "$name" 2>&1)
json_init
if echo "$result" | grep -q "Revoked"; then
json_add_string "status" "ok"
else
json_add_string "status" "error"
json_add_string "error" "$result"
fi
json_dump
}
# Resolve a domain
do_resolve() {
local domain="$1"
if [ -z "$domain" ]; then
json_init
json_add_string "error" "Domain is required"
json_dump
return
fi
local ipv6
ipv6=$(/usr/sbin/meshnamectl resolve "$domain" 2>/dev/null)
json_init
if [ -n "$ipv6" ]; then
json_add_string "status" "ok"
json_add_string "domain" "$domain"
json_add_string "ipv6" "$ipv6"
else
json_add_string "status" "error"
json_add_string "error" "Cannot resolve $domain"
fi
json_dump
}
# Force sync
do_sync() {
/usr/sbin/meshnamectl sync >/dev/null 2>&1 &
json_init
json_add_string "status" "ok"
json_add_string "message" "Sync initiated"
json_dump
}
# Get configuration
get_config() {
local enabled auto_announce sync_interval cache_ttl gossip_enabled
enabled=$(uci -q get $CONFIG.main.enabled)
auto_announce=$(uci -q get $CONFIG.main.auto_announce)
sync_interval=$(uci -q get $CONFIG.main.sync_interval)
cache_ttl=$(uci -q get $CONFIG.resolver.cache_ttl)
gossip_enabled=$(uci -q get $CONFIG.gossip.enabled)
json_init
json_add_string "enabled" "${enabled:-0}"
json_add_string "auto_announce" "${auto_announce:-0}"
json_add_string "sync_interval" "${sync_interval:-60}"
json_add_string "cache_ttl" "${cache_ttl:-300}"
json_add_string "gossip_enabled" "${gossip_enabled:-1}"
json_dump
}
# Set configuration
set_config() {
local enabled="$1" auto_announce="$2" sync_interval="$3" gossip_enabled="$4"
[ -n "$enabled" ] && uci set $CONFIG.main.enabled="$enabled"
[ -n "$auto_announce" ] && uci set $CONFIG.main.auto_announce="$auto_announce"
[ -n "$sync_interval" ] && uci set $CONFIG.main.sync_interval="$sync_interval"
[ -n "$gossip_enabled" ] && uci set $CONFIG.gossip.enabled="$gossip_enabled"
uci commit $CONFIG
# Restart service if config changed
/etc/init.d/meshname-dns restart >/dev/null 2>&1 &
json_init
json_add_string "status" "ok"
json_dump
}
# Main RPC handler
case "$1" in
list)
json_init
json_add_object "status"
json_close_object
json_add_object "list"
json_close_object
json_add_object "announce"
json_add_string "name" "string"
json_add_string "port" "int"
json_close_object
json_add_object "revoke"
json_add_string "name" "string"
json_close_object
json_add_object "resolve"
json_add_string "domain" "string"
json_close_object
json_add_object "sync"
json_close_object
json_add_object "get_config"
json_close_object
json_add_object "set_config"
json_add_string "enabled" "string"
json_add_string "auto_announce" "string"
json_add_string "sync_interval" "string"
json_add_string "gossip_enabled" "string"
json_close_object
json_dump
;;
call)
case "$2" in
status)
get_status
;;
list)
list_domains
;;
announce)
read input
json_load "$input"
json_get_var name name
json_get_var port port
do_announce "$name" "$port"
;;
revoke)
read input
json_load "$input"
json_get_var name name
do_revoke "$name"
;;
resolve)
read input
json_load "$input"
json_get_var domain domain
do_resolve "$domain"
;;
sync)
do_sync
;;
get_config)
get_config
;;
set_config)
read input
json_load "$input"
json_get_var enabled enabled
json_get_var auto_announce auto_announce
json_get_var sync_interval sync_interval
json_get_var gossip_enabled gossip_enabled
set_config "$enabled" "$auto_announce" "$sync_interval" "$gossip_enabled"
;;
*)
echo '{"error":"Unknown method"}'
;;
esac
;;
*)
echo '{"error":"Invalid action"}'
;;
esac