最后活跃于 1 month ago

nuno 修订了这个 Gist 1 month ago. 转到此修订

1 file changed, 464 insertions

gistfile1.txt(文件已创建)

@@ -0,0 +1,464 @@
1 + #!/usr/bin/env bash
2 +
3 + # ============================================
4 + # Rule File Parsing
5 + # ============================================
6 +
7 + function rule::is_base() {
8 + local name="${1:-}"
9 + [[ -f "$(ctx::rules::base)/${name}.rule" ]]
10 + }
11 +
12 + function rule::exists() {
13 + local name="${1:-}"
14 + local path
15 + path=$(json::find_rule_file "$(ctx::rules)" "$name")
16 + [[ -n "$path" ]]
17 + }
18 +
19 + function rule::require_assignable() {
20 + local name="${1:-}"
21 + if rule::is_base "$name"; then
22 + log::error "Cannot assign base rule '${name}' — base rules cannot be assigned directly"
23 + return 1
24 + fi
25 + }
26 +
27 + function rule::require_exists() {
28 + local name="${1:-}"
29 + if ! rule::exists "$name"; then
30 + log::error "Rule not found: ${name}"
31 + return 1
32 + fi
33 + }
34 +
35 + function rule::get() {
36 + local name="${1:-}" key="${2:-}"
37 + json::rule_resolve_field "$(ctx::rules)" "$name" "$key"
38 + }
39 +
40 + function rule::get_own() {
41 + local name="${1:-}" key="${2:-}"
42 + local file
43 + file=$(rule::path "$name") || return 0
44 + json::get_raw "$file" "$key"
45 + }
46 +
47 + function rule::get_resolved() {
48 + local name="${1:-}"
49 + json::rule_resolve "$(ctx::rules)" "$name"
50 + }
51 +
52 + function rule::path() {
53 + local name="${1:-}"
54 + local path
55 + path=$(json::find_rule_file "$(ctx::rules)" "$name")
56 + [[ -n "$path" ]] && echo "$path" || return 1
57 + }
58 +
59 + function rule::get_all() {
60 + local name="${1:-}"
61 + rule::get_resolved "$name"
62 + }
63 +
64 + function rule::is_applied() {
65 + local rule_name="${1:-}" client_ip="${2:-}"
66 +
67 + local first_port
68 + first_port=$(rule::get "$rule_name" "block_ports" | head -1)
69 + if [[ -n "$first_port" ]]; then
70 + local target port proto
71 + IFS=":" read -r target port proto <<< "$first_port"
72 + proto="${proto:-tcp}"
73 + fw::has_block_rule "$client_ip" "$target" "$proto" "$port"
74 + return $?
75 + fi
76 +
77 + local first_ip
78 + first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
79 + if [[ -n "$first_ip" ]]; then
80 + fw::has_block_rule "$client_ip" "$first_ip"
81 + return $?
82 + fi
83 +
84 + local first_allow
85 + first_allow=$(rule::get "$rule_name" "allow_ports" | head -1)
86 + if [[ -n "$first_allow" ]]; then
87 + local target port proto
88 + IFS=":" read -r target port proto <<< "$first_allow"
89 + proto="${proto:-tcp}"
90 + fw::_forward_exists -s "$client_ip" -d "$target" \
91 + -p "$proto" --dport "$port" -j ACCEPT
92 + return $?
93 + fi
94 +
95 + return 1
96 + }
97 +
98 + # ============================================
99 + # Rule Application
100 + # ============================================
101 +
102 + function rule::apply() {
103 + local rule_name="${1:?rule_name required}"
104 + local client_ip="${2:?client_ip required}"
105 + local peer_name="${3:-}"
106 +
107 + rule::require_exists "$rule_name" || return 1
108 +
109 + if [[ -z "$peer_name" ]]; then
110 + peer_name=$(peers::find_by_ip "$client_ip")
111 + fi
112 +
113 + log::debug "rule::apply: peer_name=$peer_name ip=$client_ip"
114 +
115 + if rule::is_applied "$rule_name" "$client_ip"; then
116 + log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
117 + [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
118 + return 0
119 + fi
120 +
121 + # Process block_ips
122 + while IFS= read -r block_ip; do
123 + [[ -z "$block_ip" ]] && continue
124 + fw::block_ip "$client_ip" "$block_ip"
125 + done < <(rule::get "$rule_name" "block_ips")
126 +
127 + # Process block_ports
128 + while IFS= read -r entry; do
129 + [[ -z "$entry" ]] && continue
130 + local target port proto
131 + IFS=":" read -r target port proto <<< "$entry"
132 + proto="${proto:-tcp}"
133 + fw::block_port "$client_ip" "$target" "$port" "$proto"
134 + done < <(rule::get "$rule_name" "block_ports")
135 +
136 + # Process allow_ips (inserted before blocks)
137 + while IFS= read -r allow_ip; do
138 + [[ -z "$allow_ip" ]] && continue
139 + fw::allow_ip "$client_ip" "$allow_ip"
140 + done < <(rule::get "$rule_name" "allow_ips")
141 +
142 + # Process allow_ports (highest priority)
143 + while IFS= read -r entry; do
144 + [[ -z "$entry" ]] && continue
145 + local target port proto
146 + IFS=":" read -r target port proto <<< "$entry"
147 + proto="${proto:-tcp}"
148 + fw::allow_port "$client_ip" "$target" "$port" "$proto"
149 + done < <(rule::get "$rule_name" "allow_ports")
150 +
151 + [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
152 +
153 + # DNS redirect
154 + local dns_redirect
155 + dns_redirect=$(rule::get "$rule_name" "dns_redirect")
156 + if [[ "$dns_redirect" == "true" ]]; then
157 + local peer_subnet
158 + peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
159 + if ! fw::_nat_exists -i wg0 -s "${peer_subnet}.0/24" \
160 + -p udp --dport 53 -j DNAT \
161 + --to-destination "$(config::dns):53" 2>/dev/null; then
162 + rule::apply_dns_redirect "${peer_subnet}.0/24"
163 + log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
164 + else
165 + log::debug "dns_redirect: already applied for ${peer_subnet}.0/24"
166 + fi
167 + fi
168 +
169 + log::debug "Applied rule '${rule_name}' to: ${client_ip}"
170 + }
171 +
172 + function rule::unapply() {
173 + local rule_name="${1:-}" client_ip="${2:-}"
174 +
175 + rule::require_exists "$rule_name" || return 1
176 +
177 + local peer_name
178 + peer_name=$(peers::find_by_ip "$client_ip")
179 +
180 + # Remove allow_ports first (reverse order of apply)
181 + while IFS= read -r entry; do
182 + [[ -z "$entry" ]] && continue
183 + local target port proto
184 + IFS=":" read -r target port proto <<< "$entry"
185 + proto="${proto:-tcp}"
186 + fw::unallow_port "$client_ip" "$target" "$port" "$proto"
187 + done < <(rule::get "$rule_name" "allow_ports")
188 +
189 + # Remove allow_ips
190 + while IFS= read -r allow_ip; do
191 + [[ -z "$allow_ip" ]] && continue
192 + if [[ "$allow_ip" == *"/"* ]]; then
193 + fw::unallow_subnet "$client_ip" "$allow_ip"
194 + else
195 + fw::unallow_ip "$client_ip" "$allow_ip"
196 + fi
197 + done < <(rule::get "$rule_name" "allow_ips")
198 +
199 + # Remove block_ports
200 + while IFS= read -r entry; do
201 + [[ -z "$entry" ]] && continue
202 + local target port proto
203 + IFS=":" read -r target port proto <<< "$entry"
204 + proto="${proto:-tcp}"
205 + fw::unblock_port "$client_ip" "$target" "$port" "$proto"
206 + done < <(rule::get "$rule_name" "block_ports")
207 +
208 + # Remove block_ips
209 + while IFS= read -r block_ip; do
210 + [[ -z "$block_ip" ]] && continue
211 + if [[ "$block_ip" == *"/"* ]]; then
212 + fw::unblock_subnet "$client_ip" "$block_ip"
213 + else
214 + fw::unblock_ip "$client_ip" "$block_ip"
215 + fi
216 + done < <(rule::get "$rule_name" "block_ips")
217 +
218 + # Remove DNS redirect if applicable
219 + local dns_redirect
220 + dns_redirect=$(rule::get "$rule_name" "dns_redirect")
221 + if [[ "$dns_redirect" == "true" ]]; then
222 + local peer_ip peer_subnet
223 + peer_ip=$(peers::get_ip "$peer_name")
224 + peer_subnet=$(echo "$peer_ip" | cut -d'.' -f1-3)
225 + rule::remove_dns_redirect "${peer_subnet}.0/24"
226 + fi
227 +
228 + [[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" ""
229 +
230 + log::debug "Removed rule '${rule_name}' from: ${client_ip}"
231 + }
232 +
233 + # ============================================
234 + # Bulk Operations
235 + # ============================================
236 +
237 + function rule::_apply_identity_rule() {
238 + local peer_name="${1:-}" client_ip="${2:-}"
239 +
240 + local identity_name
241 + identity_name=$(identity::get_name "$peer_name")
242 + [[ -z "$identity_name" ]] && return 0
243 +
244 + local rules
245 + rules=$(identity::rules "$identity_name")
246 + [[ -z "$rules" ]] && return 0
247 +
248 + local strict
249 + strict=$(identity::rule_flags "$identity_name" "strict_rule")
250 +
251 + if [[ "$strict" == "true" ]]; then
252 + # Strict: flush and apply only identity rules — peer rule ignored
253 + fw::flush_peer "$client_ip"
254 + while IFS= read -r rule_name; do
255 + [[ -z "$rule_name" ]] && continue
256 + rule::exists "$rule_name" && rule::apply "$rule_name" "$client_ip" "$peer_name" || true
257 + done <<< "$rules"
258 + else
259 + # Additive: apply identity rules on top of peer rule
260 + while IFS= read -r rule_name; do
261 + [[ -z "$rule_name" ]] && continue
262 + rule::exists "$rule_name" && rule::apply "$rule_name" "$client_ip" "$peer_name" || true
263 + done <<< "$rules"
264 + fi
265 + }
266 +
267 + # rule::full_restore_peer <peer_name> <client_ip>
268 + # Flush and fully restore all fw rules for a peer — rule rules + block rules.
269 + # Use this instead of calling rule::apply + block::restore_rules_for separately
270 + # to ensure block rules are never left missing after a flush.
271 + function rule::full_restore_peer() {
272 + local peer_name="${1:-}" client_ip="${2:-}"
273 + [[ -z "$peer_name" || -z "$client_ip" ]] && return 1
274 +
275 + fw::flush_peer "$client_ip"
276 +
277 + local peer_rule
278 + peer_rule=$(peers::get_meta "$peer_name" "rule")
279 +
280 + local strict
281 + strict=$(rule::_get_identity_strict "$peer_name")
282 +
283 + if [[ "$strict" == "true" ]]; then
284 + # Strict mode: only identity rules apply
285 + rule::_apply_identity_rule "$peer_name" "$client_ip"
286 + else
287 + # Normal mode: peer rule + identity rules (additive)
288 + [[ -n "$peer_rule" ]] && rule::apply "$peer_rule" "$client_ip" "$peer_name"
289 + rule::_apply_identity_rule "$peer_name" "$client_ip"
290 + fi
291 +
292 + block::restore_rules_for "$peer_name" "$client_ip"
293 + }
294 +
295 + function rule::_get_identity_strict() {
296 + local peer_name="${1:-}"
297 + local identity_name
298 + identity_name=$(identity::get_name "$peer_name")
299 + [[ -z "$identity_name" ]] && echo "false" && return 0
300 + identity::rule_flags "$identity_name" "strict_rule"
301 + }
302 +
303 + function rule::restore_all() {
304 + while IFS= read -r peer_name; do
305 + block::is_blocked "$peer_name" && continue
306 +
307 + local rule_name
308 + rule_name=$(peers::get_meta "$peer_name" "rule")
309 + [[ -z "$rule_name" ]] && continue
310 +
311 + if ! rule::exists "$rule_name"; then
312 + log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
313 + continue
314 + fi
315 +
316 + local client_ip
317 + client_ip=$(peers::get_ip "$peer_name")
318 + [[ -z "$client_ip" ]] && continue
319 +
320 + # full_restore_peer ensures block rules are restored alongside rule rules
321 + rule::full_restore_peer "$peer_name" "$client_ip"
322 + done < <(peers::all)
323 + log::wg "Rules restored for all peers"
324 + }
325 +
326 + # ============================================
327 + # Rendering
328 + # ============================================
329 +
330 + function rule::render_flat() {
331 + local rule_name="${1:-}"
332 +
333 + local allow_ports allow_ips block_ips block_ports dns
334 + allow_ports=$(rule::get "$rule_name" "allow_ports")
335 + allow_ips=$(rule::get "$rule_name" "allow_ips")
336 + block_ips=$(rule::get "$rule_name" "block_ips")
337 + block_ports=$(rule::get "$rule_name" "block_ports")
338 + dns=$(rule::get_own "$rule_name" "dns_redirect")
339 +
340 + local has_content=false
341 + [[ -n "${allow_ports}${allow_ips}${block_ips}${block_ports}" ]] && \
342 + has_content=true
343 +
344 + if ! $has_content; then
345 + printf "\n full access (no restrictions)\n"
346 + return 0
347 + fi
348 +
349 + if [[ -n "$allow_ports" || -n "$allow_ips" ]]; then
350 + printf "\n"
351 + while IFS= read -r e; do
352 + [[ -z "$e" ]] && continue
353 + net::print_entry "+" "$e" 2
354 + done <<< "$allow_ports"$'\n'"$allow_ips"
355 + fi
356 +
357 + if [[ -n "$block_ips" || -n "$block_ports" ]]; then
358 + printf "\n"
359 + while IFS= read -r e; do
360 + [[ -z "$e" ]] && continue
361 + net::print_entry "-" "$e" 2
362 + done <<< "$block_ips"$'\n'"$block_ports"
363 + fi
364 +
365 + [[ "${dns,,}" == "true" ]] && \
366 + net::print_dns_redirect "$(config::dns)" 6 "DNS"
367 +
368 + return 0
369 + }
370 +
371 + function rule::render_entries() {
372 + local rule_name="${1:-}" indent="${2:-4}"
373 +
374 + local allow_ports allow_ips block_ips block_ports dns
375 + allow_ports=$(rule::get "$rule_name" "allow_ports" 2>/dev/null || true)
376 + allow_ips=$(rule::get "$rule_name" "allow_ips" 2>/dev/null || true)
377 + block_ips=$(rule::get "$rule_name" "block_ips" 2>/dev/null || true)
378 + block_ports=$(rule::get "$rule_name" "block_ports" 2>/dev/null || true)
379 + dns=$(rule::get_own "$rule_name" "dns_redirect")
380 +
381 + while IFS= read -r e; do
382 + [[ -z "$e" ]] && continue
383 + net::print_entry "+" "$e"
384 + done <<< "$allow_ports"$'\n'"$allow_ips"
385 +
386 + while IFS= read -r e; do
387 + [[ -z "$e" ]] && continue
388 + net::print_entry "-" "$e"
389 + done <<< "$block_ips"$'\n'"$block_ports"
390 +
391 + [[ "${dns,,}" == "true" ]] && \
392 + net::print_dns_redirect "$(config::dns)" 6 "DNS"
393 + }
394 +
395 + function rule::render_own_entries() {
396 + local rule_name="${1:-}"
397 + local rule_file
398 + rule_file="$(rule::path "$rule_name")"
399 +
400 + local allow_ports allow_ips block_ips block_ports dns
401 + allow_ports=$(json::get "$rule_file" "allow_ports" 2>/dev/null || true)
402 + allow_ips=$(json::get "$rule_file" "allow_ips" 2>/dev/null || true)
403 + block_ips=$(json::get "$rule_file" "block_ips" 2>/dev/null || true)
404 + block_ports=$(json::get "$rule_file" "block_ports" 2>/dev/null || true)
405 + dns=$(json::get "$rule_file" "dns_redirect" 2>/dev/null || true)
406 +
407 + local combined="${allow_ports}${allow_ips}${block_ips}${block_ports}"
408 + [[ -z "${combined//[$'\n']/}" ]] && return 0
409 +
410 + while IFS= read -r e; do
411 + [[ -z "$e" ]] && continue
412 + net::print_entry "+" "$e"
413 + done <<< "$allow_ports"$'\n'"$allow_ips"
414 +
415 + while IFS= read -r e; do
416 + [[ -z "$e" ]] && continue
417 + net::print_entry "-" "$e"
418 + done <<< "$block_ips"$'\n'"$block_ports"
419 +
420 + [[ "${dns,,}" == "true" ]] && \
421 + net::print_dns_redirect "$(config::dns)" 6 "DNS"
422 +
423 + return 0
424 + }
425 +
426 + function rule::render_extends_tree() {
427 + local rule_name="${1:-}"
428 + local rule_file
429 + rule_file="$(rule::path "$rule_name")"
430 +
431 + local extends_raw=()
432 + mapfile -t extends_raw < <(json::get "$rule_file" "extends" 2>/dev/null || true)
433 +
434 + [[ ${#extends_raw[@]} -eq 0 || -z "${extends_raw[0]:-}" ]] && return 1
435 +
436 + for base_name in "${extends_raw[@]}"; do
437 + [[ -z "$base_name" ]] && continue
438 + printf "\n \033[0;37m↳ %s\033[0m\n" "$base_name"
439 + rule::render_entries "$base_name"
440 + done
441 +
442 + local own_output
443 + own_output=$(rule::render_own_entries "$rule_name")
444 + if [[ -n "$own_output" ]]; then
445 + printf "\n \033[0;37mOwn:\033[0m\n"
446 + printf "%s\n" "$own_output"
447 + fi
448 +
449 + return 0
450 + }
451 +
452 + # ============================================
453 + # DNS Redirect
454 + # ============================================
455 +
456 + function rule::apply_dns_redirect() {
457 + local client_subnet="${1:-}"
458 + fw::nat_add_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
459 + }
460 +
461 + function rule::remove_dns_redirect() {
462 + local client_subnet="${1:-}"
463 + fw::nat_remove_dns_redirect "$client_subnet" "$(config::dns)" "$(config::interface)"
464 + }
上一页 下一页