gistfile1.txt
· 9.7 KiB · Text
Raw
#!/usr/bin/env bash
# subnet.module.sh — subnet map lookups, resolution, validation, and CIDR utilities
# All subnet data lives in subnets.json; this module wraps json:: calls
# and provides hardcoded fallbacks for safety.
# ======================================================
# CIDR Utilities
# Pure functions — no external dependencies, no side effects.
# Suitable for unit testing.
# ======================================================
# subnet::prefix <cidr>
# Returns the first three octets of a CIDR (the allocation prefix).
# Example: 10.1.3.0/24 -> 10.1.3
function subnet::prefix() {
echo "${1%%/*}" | cut -d'.' -f1-3
}
# subnet::base_ip <cidr>
# Returns the network address without the mask.
# Example: 10.1.3.0/24 -> 10.1.3.0
function subnet::base_ip() {
echo "${1%%/*}"
}
# subnet::mask <cidr>
# Returns the prefix length.
# Example: 10.1.3.0/24 -> 24
function subnet::mask() {
echo "${1##*/}"
}
# subnet::host_range <cidr>
# Returns the iterable host range for a subnet.
# Currently supports /24 (1-254). Mask-aware implementation deferred.
function subnet::host_range() {
local cidr="${1:-}"
local mask
mask=$(subnet::mask "$cidr")
case "$mask" in
24) seq 1 254 ;;
*)
# Fallback for non-/24 — still seq 1 254, but noted for future upgrade
seq 1 254
;;
esac
}
# subnet::contains <cidr> <ip>
# Returns 0 if the IP falls within the CIDR, 1 otherwise.
# Example: subnet::contains 10.1.3.0/24 10.1.3.5 -> 0 (true)
function subnet::contains() {
local cidr="${1:-}" ip="${2:-}"
[[ -z "$cidr" || -z "$ip" ]] && return 1
local prefix mask
prefix=$(subnet::prefix "$cidr")
mask=$(subnet::mask "$cidr")
# For /24: check that the first three octets match
case "$mask" in
24)
local ip_prefix
ip_prefix=$(echo "$ip" | cut -d'.' -f1-3)
[[ "$ip_prefix" == "$prefix" ]]
;;
*)
# Delegate to Python for non-/24 subnets
python3 -c "
import ipaddress, sys
try:
net = ipaddress.ip_network('${cidr}', strict=False)
addr = ipaddress.ip_address('${ip}')
sys.exit(0 if addr in net else 1)
except Exception:
sys.exit(1)
"
;;
esac
}
# subnet::is_valid_cidr <cidr>
# Returns 0 if the string is a valid CIDR notation.
function subnet::is_valid_cidr() {
local cidr="${1:-}"
[[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] || return 1
# Also validate each octet is 0-255
local ip
ip=$(subnet::base_ip "$cidr")
local IFS='.'
read -ra octets <<< "$ip"
for octet in "${octets[@]}"; do
(( octet >= 0 && octet <= 255 )) || return 1
done
return 0
}
# subnet::ip_valid_for <cidr> <ip>
# Returns 0 if the IP is a valid host address within the given CIDR.
# Excludes network address (.0) and broadcast address (.255) for /24.
function subnet::ip_valid_for() {
local cidr="${1:-}" ip="${2:-}"
[[ -z "$cidr" || -z "$ip" ]] && return 1
# Must be a valid IP first
ip::is_valid "$ip" || return 1
# Must be within the subnet
subnet::contains "$cidr" "$ip" || return 1
# Must not be network or broadcast address (for /24)
local mask
mask=$(subnet::mask "$cidr")
if [[ "$mask" == "24" ]]; then
local last_octet
last_octet=$(echo "$ip" | cut -d'.' -f4)
[[ "$last_octet" == "0" || "$last_octet" == "255" ]] && return 1
fi
return 0
}
# subnet::require_valid_cidr <cidr>
# Errors and exits if the CIDR is not valid.
function subnet::require_valid_cidr() {
local cidr="${1:-}"
if ! subnet::is_valid_cidr "$cidr"; then
log::error "Invalid CIDR notation: '${cidr}'"
return 1
fi
}
# subnet::require_ip_valid_for <cidr> <ip>
# Errors and exits if the IP is not a valid host for the subnet.
function subnet::require_ip_valid_for() {
local cidr="${1:-}" ip="${2:-}"
if ! subnet::ip_valid_for "$cidr" "$ip"; then
log::error "IP '${ip}' is not a valid host address in subnet '${cidr}'"
return 1
fi
}
# ======================================================
# Hardcoded Fallbacks
# Mirror of production subnets.json.
# Used only when subnets.json lookup fails.
# ======================================================
function subnet::_hardcoded_cidr() {
local type="${1:-}" subnet_name="${2:-}"
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" ;;
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() {
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_cidr "" "$subnet_name"
}
# subnet::resolve_for_add <type> [subnet_name]
# Main entry point for wgctl add — returns the CIDR to allocate from.
function subnet::resolve_for_add() {
local peer_type="${1:-}" subnet_name="${2:-}"
local result
if [[ -n "$subnet_name" ]]; then
# Group entry: try type-specific child first, then "none" slot
if [[ -n "$peer_type" ]]; then
result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" "$peer_type" 2>/dev/null) || true
[[ -n "$result" ]] && { echo "$result"; return 0; }
fi
result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" 2>/dev/null) || true
[[ -n "$result" ]] && { echo "$result"; return 0; }
subnet::_hardcoded_cidr "" "$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
[[ -n "$result" ]] && { echo "$result"; return 0; }
fi
subnet::_hardcoded_cidr "$peer_type"
}
# subnet::type_for_add <type_flag> [subnet_name]
# Returns the canonical type string to store in meta.
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
[[ -n "$result" ]] && { echo "$result"; return 0; }
fi
echo "${type_flag:-none}"
}
# 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
[[ -n "$result" ]] && { echo "$result"; return 0; }
subnet::_hardcoded_tunnel_mode
}
function subnet::type_from_ip() {
local ip="${1:-}"
[[ -z "$ip" ]] && echo "unknown" && return 0
# Fast path: hardcoded map covers all production subnets — pure bash, no subshell
local type
type=$(subnet::_hardcoded_type "$ip")
if [[ "$type" != "unknown" ]]; then
echo "$type"
return 0
fi
# Slow path: Python lookup for dynamically-added subnets not in hardcoded map
local result
result=$(json::subnet_for_ip "$(ctx::subnets)" "$ip" 2>/dev/null) || true
if [[ -n "$result" ]]; then
echo "${result##*|}"
return 0
fi
echo "unknown"
}
# 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
[[ -n "$result" ]] && { echo "${result%%|*}"; return 0; }
echo ""
}
# ======================================================
# Validation
# ======================================================
function subnet::exists() {
local name="${1:-}"
json::subnet_exists "$(ctx::subnets)" "$name" 2>/dev/null
}
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
}
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 Data
# ======================================================
function subnet::list_data() {
json::subnet_list "$(ctx::subnets)" 2>/dev/null || true
}
function subnet::show_data() {
local name="${1:-}"
json::subnet_show "$(ctx::subnets)" "$name"
}
| 1 | #!/usr/bin/env bash |
| 2 | # subnet.module.sh — subnet map lookups, resolution, validation, and CIDR utilities |
| 3 | # All subnet data lives in subnets.json; this module wraps json:: calls |
| 4 | # and provides hardcoded fallbacks for safety. |
| 5 | |
| 6 | # ====================================================== |
| 7 | # CIDR Utilities |
| 8 | # Pure functions — no external dependencies, no side effects. |
| 9 | # Suitable for unit testing. |
| 10 | # ====================================================== |
| 11 | |
| 12 | # subnet::prefix <cidr> |
| 13 | # Returns the first three octets of a CIDR (the allocation prefix). |
| 14 | # Example: 10.1.3.0/24 -> 10.1.3 |
| 15 | function subnet::prefix() { |
| 16 | echo "${1%%/*}" | cut -d'.' -f1-3 |
| 17 | } |
| 18 | |
| 19 | # subnet::base_ip <cidr> |
| 20 | # Returns the network address without the mask. |
| 21 | # Example: 10.1.3.0/24 -> 10.1.3.0 |
| 22 | function subnet::base_ip() { |
| 23 | echo "${1%%/*}" |
| 24 | } |
| 25 | |
| 26 | # subnet::mask <cidr> |
| 27 | # Returns the prefix length. |
| 28 | # Example: 10.1.3.0/24 -> 24 |
| 29 | function subnet::mask() { |
| 30 | echo "${1##*/}" |
| 31 | } |
| 32 | |
| 33 | # subnet::host_range <cidr> |
| 34 | # Returns the iterable host range for a subnet. |
| 35 | # Currently supports /24 (1-254). Mask-aware implementation deferred. |
| 36 | function subnet::host_range() { |
| 37 | local cidr="${1:-}" |
| 38 | local mask |
| 39 | mask=$(subnet::mask "$cidr") |
| 40 | case "$mask" in |
| 41 | 24) seq 1 254 ;; |
| 42 | *) |
| 43 | # Fallback for non-/24 — still seq 1 254, but noted for future upgrade |
| 44 | seq 1 254 |
| 45 | ;; |
| 46 | esac |
| 47 | } |
| 48 | |
| 49 | # subnet::contains <cidr> <ip> |
| 50 | # Returns 0 if the IP falls within the CIDR, 1 otherwise. |
| 51 | # Example: subnet::contains 10.1.3.0/24 10.1.3.5 -> 0 (true) |
| 52 | function subnet::contains() { |
| 53 | local cidr="${1:-}" ip="${2:-}" |
| 54 | [[ -z "$cidr" || -z "$ip" ]] && return 1 |
| 55 | |
| 56 | local prefix mask |
| 57 | prefix=$(subnet::prefix "$cidr") |
| 58 | mask=$(subnet::mask "$cidr") |
| 59 | |
| 60 | # For /24: check that the first three octets match |
| 61 | case "$mask" in |
| 62 | 24) |
| 63 | local ip_prefix |
| 64 | ip_prefix=$(echo "$ip" | cut -d'.' -f1-3) |
| 65 | [[ "$ip_prefix" == "$prefix" ]] |
| 66 | ;; |
| 67 | *) |
| 68 | # Delegate to Python for non-/24 subnets |
| 69 | python3 -c " |
| 70 | import ipaddress, sys |
| 71 | try: |
| 72 | net = ipaddress.ip_network('${cidr}', strict=False) |
| 73 | addr = ipaddress.ip_address('${ip}') |
| 74 | sys.exit(0 if addr in net else 1) |
| 75 | except Exception: |
| 76 | sys.exit(1) |
| 77 | " |
| 78 | ;; |
| 79 | esac |
| 80 | } |
| 81 | |
| 82 | # subnet::is_valid_cidr <cidr> |
| 83 | # Returns 0 if the string is a valid CIDR notation. |
| 84 | function subnet::is_valid_cidr() { |
| 85 | local cidr="${1:-}" |
| 86 | [[ "$cidr" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$ ]] || return 1 |
| 87 | # Also validate each octet is 0-255 |
| 88 | local ip |
| 89 | ip=$(subnet::base_ip "$cidr") |
| 90 | local IFS='.' |
| 91 | read -ra octets <<< "$ip" |
| 92 | for octet in "${octets[@]}"; do |
| 93 | (( octet >= 0 && octet <= 255 )) || return 1 |
| 94 | done |
| 95 | return 0 |
| 96 | } |
| 97 | |
| 98 | # subnet::ip_valid_for <cidr> <ip> |
| 99 | # Returns 0 if the IP is a valid host address within the given CIDR. |
| 100 | # Excludes network address (.0) and broadcast address (.255) for /24. |
| 101 | function subnet::ip_valid_for() { |
| 102 | local cidr="${1:-}" ip="${2:-}" |
| 103 | [[ -z "$cidr" || -z "$ip" ]] && return 1 |
| 104 | |
| 105 | # Must be a valid IP first |
| 106 | ip::is_valid "$ip" || return 1 |
| 107 | |
| 108 | # Must be within the subnet |
| 109 | subnet::contains "$cidr" "$ip" || return 1 |
| 110 | |
| 111 | # Must not be network or broadcast address (for /24) |
| 112 | local mask |
| 113 | mask=$(subnet::mask "$cidr") |
| 114 | if [[ "$mask" == "24" ]]; then |
| 115 | local last_octet |
| 116 | last_octet=$(echo "$ip" | cut -d'.' -f4) |
| 117 | [[ "$last_octet" == "0" || "$last_octet" == "255" ]] && return 1 |
| 118 | fi |
| 119 | |
| 120 | return 0 |
| 121 | } |
| 122 | |
| 123 | # subnet::require_valid_cidr <cidr> |
| 124 | # Errors and exits if the CIDR is not valid. |
| 125 | function subnet::require_valid_cidr() { |
| 126 | local cidr="${1:-}" |
| 127 | if ! subnet::is_valid_cidr "$cidr"; then |
| 128 | log::error "Invalid CIDR notation: '${cidr}'" |
| 129 | return 1 |
| 130 | fi |
| 131 | } |
| 132 | |
| 133 | # subnet::require_ip_valid_for <cidr> <ip> |
| 134 | # Errors and exits if the IP is not a valid host for the subnet. |
| 135 | function subnet::require_ip_valid_for() { |
| 136 | local cidr="${1:-}" ip="${2:-}" |
| 137 | if ! subnet::ip_valid_for "$cidr" "$ip"; then |
| 138 | log::error "IP '${ip}' is not a valid host address in subnet '${cidr}'" |
| 139 | return 1 |
| 140 | fi |
| 141 | } |
| 142 | |
| 143 | # ====================================================== |
| 144 | # Hardcoded Fallbacks |
| 145 | # Mirror of production subnets.json. |
| 146 | # Used only when subnets.json lookup fails. |
| 147 | # ====================================================== |
| 148 | |
| 149 | function subnet::_hardcoded_cidr() { |
| 150 | local type="${1:-}" subnet_name="${2:-}" |
| 151 | if [[ -n "$subnet_name" ]]; then |
| 152 | case "$subnet_name" in |
| 153 | guests) echo "10.1.100.0/24" ;; |
| 154 | servers) echo "10.1.200.0/24" ;; |
| 155 | iot) echo "10.1.210.0/24" ;; |
| 156 | *) echo "10.1.0.0/24" ;; |
| 157 | esac |
| 158 | return 0 |
| 159 | fi |
| 160 | case "$type" in |
| 161 | desktop) echo "10.1.1.0/24" ;; |
| 162 | laptop) echo "10.1.2.0/24" ;; |
| 163 | phone) echo "10.1.3.0/24" ;; |
| 164 | tablet) echo "10.1.4.0/24" ;; |
| 165 | guest) echo "10.1.100.0/24" ;; |
| 166 | guest-desktop) echo "10.1.101.0/24" ;; |
| 167 | guest-laptop) echo "10.1.102.0/24" ;; |
| 168 | guest-phone) echo "10.1.103.0/24" ;; |
| 169 | guest-tablet) echo "10.1.104.0/24" ;; |
| 170 | server) echo "10.1.200.0/24" ;; |
| 171 | iot) echo "10.1.210.0/24" ;; |
| 172 | *) echo "10.1.0.0/24" ;; |
| 173 | esac |
| 174 | } |
| 175 | |
| 176 | function subnet::_hardcoded_type() { |
| 177 | local ip="${1:-}" |
| 178 | case "$ip" in |
| 179 | 10.1.1.*) echo "desktop" ;; |
| 180 | 10.1.2.*) echo "laptop" ;; |
| 181 | 10.1.3.*) echo "phone" ;; |
| 182 | 10.1.4.*) echo "tablet" ;; |
| 183 | 10.1.100.*) echo "none" ;; |
| 184 | 10.1.101.*) echo "desktop" ;; |
| 185 | 10.1.102.*) echo "laptop" ;; |
| 186 | 10.1.103.*) echo "phone" ;; |
| 187 | 10.1.104.*) echo "tablet" ;; |
| 188 | 10.1.200.*) echo "server" ;; |
| 189 | 10.1.210.*) echo "iot" ;; |
| 190 | *) echo "unknown" ;; |
| 191 | esac |
| 192 | } |
| 193 | |
| 194 | function subnet::_hardcoded_tunnel_mode() { |
| 195 | echo "split" |
| 196 | } |
| 197 | |
| 198 | # ====================================================== |
| 199 | # Core Resolution |
| 200 | # ====================================================== |
| 201 | |
| 202 | # subnet::lookup <subnet_name> [type_key] |
| 203 | # Returns the CIDR for a given subnet name and optional type. |
| 204 | # Falls back to hardcoded map on failure. |
| 205 | function subnet::lookup() { |
| 206 | local subnet_name="${1:-}" type_key="${2:-}" |
| 207 | local result |
| 208 | result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true |
| 209 | if [[ -n "$result" ]]; then |
| 210 | echo "$result" |
| 211 | return 0 |
| 212 | fi |
| 213 | subnet::_hardcoded_cidr "" "$subnet_name" |
| 214 | } |
| 215 | |
| 216 | # subnet::resolve_for_add <type> [subnet_name] |
| 217 | # Main entry point for wgctl add — returns the CIDR to allocate from. |
| 218 | function subnet::resolve_for_add() { |
| 219 | local peer_type="${1:-}" subnet_name="${2:-}" |
| 220 | local result |
| 221 | |
| 222 | if [[ -n "$subnet_name" ]]; then |
| 223 | # Group entry: try type-specific child first, then "none" slot |
| 224 | if [[ -n "$peer_type" ]]; then |
| 225 | result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" "$peer_type" 2>/dev/null) || true |
| 226 | [[ -n "$result" ]] && { echo "$result"; return 0; } |
| 227 | fi |
| 228 | result=$(json::subnet_lookup "$(ctx::subnets)" "$subnet_name" 2>/dev/null) || true |
| 229 | [[ -n "$result" ]] && { echo "$result"; return 0; } |
| 230 | subnet::_hardcoded_cidr "" "$subnet_name" |
| 231 | return 0 |
| 232 | fi |
| 233 | |
| 234 | # No subnet_name — resolve from type (native allocation) |
| 235 | if [[ -n "$peer_type" ]]; then |
| 236 | result=$(json::subnet_lookup "$(ctx::subnets)" "$peer_type" 2>/dev/null) || true |
| 237 | [[ -n "$result" ]] && { echo "$result"; return 0; } |
| 238 | fi |
| 239 | |
| 240 | subnet::_hardcoded_cidr "$peer_type" |
| 241 | } |
| 242 | |
| 243 | # subnet::type_for_add <type_flag> [subnet_name] |
| 244 | # Returns the canonical type string to store in meta. |
| 245 | function subnet::type_for_add() { |
| 246 | local type_flag="${1:-}" subnet_name="${2:-}" |
| 247 | local result |
| 248 | |
| 249 | if [[ -n "$subnet_name" ]]; then |
| 250 | result=$(json::subnet_type "$(ctx::subnets)" "$subnet_name" "$type_flag" 2>/dev/null) || true |
| 251 | [[ -n "$result" ]] && { echo "$result"; return 0; } |
| 252 | fi |
| 253 | |
| 254 | echo "${type_flag:-none}" |
| 255 | } |
| 256 | |
| 257 | # subnet::tunnel_mode <subnet_name> [type_key] |
| 258 | # Returns "split" or "full" for the given subnet. |
| 259 | function subnet::tunnel_mode() { |
| 260 | local subnet_name="${1:-}" type_key="${2:-}" |
| 261 | local result |
| 262 | result=$(json::subnet_tunnel_mode "$(ctx::subnets)" "$subnet_name" "$type_key" 2>/dev/null) || true |
| 263 | [[ -n "$result" ]] && { echo "$result"; return 0; } |
| 264 | subnet::_hardcoded_tunnel_mode |
| 265 | } |
| 266 | |
| 267 | function subnet::type_from_ip() { |
| 268 | local ip="${1:-}" |
| 269 | [[ -z "$ip" ]] && echo "unknown" && return 0 |
| 270 | |
| 271 | # Fast path: hardcoded map covers all production subnets — pure bash, no subshell |
| 272 | local type |
| 273 | type=$(subnet::_hardcoded_type "$ip") |
| 274 | if [[ "$type" != "unknown" ]]; then |
| 275 | echo "$type" |
| 276 | return 0 |
| 277 | fi |
| 278 | |
| 279 | # Slow path: Python lookup for dynamically-added subnets not in hardcoded map |
| 280 | local result |
| 281 | result=$(json::subnet_for_ip "$(ctx::subnets)" "$ip" 2>/dev/null) || true |
| 282 | if [[ -n "$result" ]]; then |
| 283 | echo "${result##*|}" |
| 284 | return 0 |
| 285 | fi |
| 286 | |
| 287 | echo "unknown" |
| 288 | } |
| 289 | |
| 290 | # subnet::name_from_ip <ip> |
| 291 | # Returns the subnet name (e.g. "guests", "desktop") for an IP. |
| 292 | function subnet::name_from_ip() { |
| 293 | local ip="${1:-}" |
| 294 | local result |
| 295 | result=$(json::subnet_for_ip "$(ctx::subnets)" "$ip" 2>/dev/null) || true |
| 296 | [[ -n "$result" ]] && { echo "${result%%|*}"; return 0; } |
| 297 | echo "" |
| 298 | } |
| 299 | |
| 300 | # ====================================================== |
| 301 | # Validation |
| 302 | # ====================================================== |
| 303 | |
| 304 | function subnet::exists() { |
| 305 | local name="${1:-}" |
| 306 | json::subnet_exists "$(ctx::subnets)" "$name" 2>/dev/null |
| 307 | } |
| 308 | |
| 309 | function subnet::require_exists() { |
| 310 | local name="${1:-}" |
| 311 | if ! subnet::exists "$name"; then |
| 312 | log::error "Subnet '${name}' not found. Use 'wgctl subnet list' to see available subnets." |
| 313 | return 1 |
| 314 | fi |
| 315 | } |
| 316 | |
| 317 | function subnet::peers_using() { |
| 318 | local subnet_name="${1:-}" |
| 319 | local peers |
| 320 | peers=$(json::subnet_peers \ |
| 321 | "$(ctx::meta)" \ |
| 322 | "$(ctx::clients)" \ |
| 323 | "$subnet_name" \ |
| 324 | "$(ctx::subnets)" \ |
| 325 | 2>/dev/null) || true |
| 326 | echo "$peers" | tr '\n' ',' | sed 's/,$//' |
| 327 | } |
| 328 | |
| 329 | |
| 330 | # ====================================================== |
| 331 | # Display Data |
| 332 | # ====================================================== |
| 333 | |
| 334 | function subnet::list_data() { |
| 335 | json::subnet_list "$(ctx::subnets)" 2>/dev/null || true |
| 336 | } |
| 337 | |
| 338 | function subnet::show_data() { |
| 339 | local name="${1:-}" |
| 340 | json::subnet_show "$(ctx::subnets)" "$name" |
| 341 | } |