Última actividad 1 month ago

Revisión 95065a3dbc7ee593a1cda3259591a3865838dc85

gistfile1.txt Sin formato
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
15function 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
22function 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
29function 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.
36function 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)
52function 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 "
70import ipaddress, sys
71try:
72 net = ipaddress.ip_network('${cidr}', strict=False)
73 addr = ipaddress.ip_address('${ip}')
74 sys.exit(0 if addr in net else 1)
75except 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.
84function 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.
101function 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.
125function 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.
135function 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
149function 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
176function 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
194function 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.
205function 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.
218function 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.
245function 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.
259function 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
267function 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.
292function 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
304function subnet::exists() {
305 local name="${1:-}"
306 json::subnet_exists "$(ctx::subnets)" "$name" 2>/dev/null
307}
308
309function 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
317function 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
334function subnet::list_data() {
335 json::subnet_list "$(ctx::subnets)" 2>/dev/null || true
336}
337
338function subnet::show_data() {
339 local name="${1:-}"
340 json::subnet_show "$(ctx::subnets)" "$name"
341}