232 lines
No EOL
7.6 KiB
Bash
232 lines
No EOL
7.6 KiB
Bash
#!/usr/bin/env bash
|
|
# subnet.module.sh — subnet map lookups, resolution, and validation
|
|
# All subnet data lives in subnets.json; this module wraps json:: calls
|
|
# and provides hardcoded fallbacks for safety.
|
|
|
|
# ===========================================================================
|
|
# Hardcoded fallbacks (mirrors production subnets.json)
|
|
# Used when subnets.json lookup fails — keeps existing peers working.
|
|
# These match the legacy DEVICE_SUBNETS / DEVICE_TUNNEL_MODE maps in config.module.sh.
|
|
# ===========================================================================
|
|
|
|
function subnet::_hardcoded_subnet() {
|
|
local type="${1:-}" subnet_name="${2:-}"
|
|
# If a subnet name was given, try legacy guest-* mapping first
|
|
if [[ -n "$subnet_name" ]]; then
|
|
case "$subnet_name" in
|
|
guests) echo "10.1.100.0/24" ;;
|
|
servers) echo "10.1.200.0/24" ;;
|
|
iot) echo "10.1.210.0/24" ;;
|
|
*) echo "10.1.0.0/24" ;;
|
|
esac
|
|
return 0
|
|
fi
|
|
case "$type" in
|
|
desktop) echo "10.1.1.0/24" ;;
|
|
laptop) echo "10.1.2.0/24" ;;
|
|
phone) echo "10.1.3.0/24" ;;
|
|
tablet) echo "10.1.4.0/24" ;;
|
|
# Legacy guest-* types — kept for existing peers during migration
|
|
guest) echo "10.1.100.0/24" ;;
|
|
guest-desktop) echo "10.1.101.0/24" ;;
|
|
guest-laptop) echo "10.1.102.0/24" ;;
|
|
guest-phone) echo "10.1.103.0/24" ;;
|
|
guest-tablet) echo "10.1.104.0/24" ;;
|
|
server) echo "10.1.200.0/24" ;;
|
|
iot) echo "10.1.210.0/24" ;;
|
|
*) echo "10.1.0.0/24" ;;
|
|
esac
|
|
}
|
|
|
|
function subnet::_hardcoded_type() {
|
|
local ip="${1:-}"
|
|
case "$ip" in
|
|
10.1.1.*) echo "desktop" ;;
|
|
10.1.2.*) echo "laptop" ;;
|
|
10.1.3.*) echo "phone" ;;
|
|
10.1.4.*) echo "tablet" ;;
|
|
10.1.100.*) echo "none" ;;
|
|
10.1.101.*) echo "desktop" ;;
|
|
10.1.102.*) echo "laptop" ;;
|
|
10.1.103.*) echo "phone" ;;
|
|
10.1.104.*) echo "tablet" ;;
|
|
10.1.200.*) echo "server" ;;
|
|
10.1.210.*) echo "iot" ;;
|
|
*) echo "unknown" ;;
|
|
esac
|
|
}
|
|
|
|
function subnet::_hardcoded_tunnel_mode() {
|
|
# All current subnets use split — placeholder for future full-tunnel entries
|
|
echo "split"
|
|
}
|
|
|
|
# ===========================================================================
|
|
# Core resolution
|
|
# ===========================================================================
|
|
|
|
# subnet::lookup <subnet_name> [type_key]
|
|
# Returns the CIDR for a given subnet name and optional type.
|
|
# Falls back to hardcoded map on failure.
|
|
function subnet::lookup() {
|
|
local subnet_name="${1:-}" type_key="${2:-}"
|
|
local result
|
|
result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then
|
|
echo "$result"
|
|
return 0
|
|
fi
|
|
subnet::_hardcoded_subnet "" "$subnet_name"
|
|
}
|
|
|
|
# subnet::resolve_for_add <type> [subnet_name]
|
|
# Main entry point for wgctl add.
|
|
# Returns the CIDR to allocate from.
|
|
# Resolution order:
|
|
# 1. subnet_name given + type given -> subnets[subnet_name][type]
|
|
# 2. subnet_name given, no type -> subnets[subnet_name]["none"]
|
|
# 3. no subnet_name -> subnets[type] (scalar, type-native)
|
|
# 4. fallback -> hardcoded map
|
|
function subnet::resolve_for_add() {
|
|
local peer_type="${1:-}" subnet_name="${2:-}"
|
|
local result
|
|
|
|
if [[ -n "$subnet_name" ]]; then
|
|
# Try with type key first
|
|
if [[ -n "$peer_type" ]]; then
|
|
result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" "$peer_type" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then echo "$result"; return 0; fi
|
|
fi
|
|
# Fall back to "none" slot in group, or scalar entry
|
|
result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then echo "$result"; return 0; fi
|
|
# Hardcoded fallback for subnet name
|
|
subnet::_hardcoded_subnet "" "$subnet_name"
|
|
return 0
|
|
fi
|
|
|
|
# No subnet_name — resolve from type (native allocation)
|
|
if [[ -n "$peer_type" ]]; then
|
|
result=$(json::subnet_lookup "$(ctx::subnets)" "$peer_type" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then echo "$result"; return 0; fi
|
|
fi
|
|
|
|
subnet::_hardcoded_subnet "$peer_type"
|
|
}
|
|
|
|
# subnet::type_for_add <type_flag> [subnet_name]
|
|
# Returns the canonical type string to store in meta.
|
|
# If --subnet given and it's a scalar, type comes from subnets.json entry.
|
|
# If --subnet is a group, type comes from --type flag (or "none").
|
|
# If no --subnet, type comes from --type flag directly.
|
|
function subnet::type_for_add() {
|
|
local type_flag="${1:-}" subnet_name="${2:-}"
|
|
local result
|
|
|
|
if [[ -n "$subnet_name" ]]; then
|
|
result=$(json::subnet_type "$(ctx::subnets)" "$subnet_name" "$type_flag" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then echo "$result"; return 0; fi
|
|
fi
|
|
|
|
# No subnet or lookup failed — use the type flag directly
|
|
if [[ -n "$type_flag" ]]; then
|
|
echo "$type_flag"
|
|
else
|
|
echo "none"
|
|
fi
|
|
}
|
|
|
|
# subnet::tunnel_mode <subnet_name> [type_key]
|
|
# Returns "split" or "full" for the given subnet.
|
|
function subnet::tunnel_mode() {
|
|
local subnet_name="${1:-}" type_key="${2:-}"
|
|
local result
|
|
result=$(json::subnet_tunnel_mode "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then echo "$result"; return 0; fi
|
|
subnet::_hardcoded_tunnel_mode
|
|
}
|
|
|
|
# subnet::type_from_ip <ip>
|
|
# Reverse-lookup: given a peer's IP, return its type.
|
|
# Tries meta file first (by peer name), then subnets.json, then hardcoded.
|
|
function subnet::type_from_ip() {
|
|
local ip="${1:-}"
|
|
local result
|
|
|
|
result=$(json::subnet_for_ip "$(ctx::subnets)" "$ip" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then
|
|
# result is "subnet_name|type_key"
|
|
echo "${result##*|}"
|
|
return 0
|
|
fi
|
|
|
|
subnet::_hardcoded_type "$ip"
|
|
}
|
|
|
|
# subnet::name_from_ip <ip>
|
|
# Returns the subnet name (e.g. "guests", "desktop") for an IP.
|
|
function subnet::name_from_ip() {
|
|
local ip="${1:-}"
|
|
local result
|
|
result=$(json::subnet_for_ip "$(ctx::subnets)" "$ip" 2>/dev/null) || true
|
|
if [[ -n "$result" ]]; then
|
|
echo "${result%%|*}"
|
|
return 0
|
|
fi
|
|
echo ""
|
|
}
|
|
|
|
# ===========================================================================
|
|
# Validation
|
|
# ===========================================================================
|
|
|
|
# subnet::exists <name>
|
|
# Returns 0 if subnet exists in subnets.json, 1 otherwise.
|
|
function subnet::exists() {
|
|
local name="${1:-}"
|
|
json::subnet_exists "$(ctx::subnets)" "$name" 2>/dev/null
|
|
}
|
|
|
|
# subnet::require_exists <name>
|
|
# Errors and exits if subnet doesn't exist.
|
|
function subnet::require_exists() {
|
|
local name="${1:-}"
|
|
if ! subnet::exists "$name"; then
|
|
log::error "Subnet '${name}' not found. Use 'wgctl subnet list' to see available subnets."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# subnet::peers_using <name>
|
|
# Returns comma-separated list of peer names using this subnet (from meta).
|
|
# Empty string if none.
|
|
function subnet::peers_using() {
|
|
local subnet_name="${1:-}"
|
|
local peers
|
|
peers=$(json::subnet_peers \
|
|
"$(ctx::meta)" \
|
|
"$(ctx::clients)" \
|
|
"$subnet_name" \
|
|
"$(ctx::subnets)" \
|
|
2>/dev/null) || true
|
|
echo "$peers" | tr '\n' ',' | sed 's/,$//'
|
|
}
|
|
|
|
|
|
# ===========================================================================
|
|
# Display helpers
|
|
# ===========================================================================
|
|
|
|
# subnet::list_data
|
|
# Returns all subnet entries formatted for display.
|
|
# Output per line: display_name|subnet|type|tunnel_mode|desc|is_group|group_parent
|
|
function subnet::list_data() {
|
|
json::subnet_list "$(ctx::subnets)" 2>/dev/null || true
|
|
}
|
|
|
|
# subnet::show_data <name>
|
|
# Returns detail lines for a single subnet entry.
|
|
function subnet::show_data() {
|
|
local name="${1:-}"
|
|
json::subnet_show "$(ctx::subnets)" "$name"
|
|
} |