最終更新 1 month ago

gistfile1.txt Raw
1#!/usr/bin/env bash
2
3# ============================================
4# Rule File Parsing
5# ============================================
6
7function rule::exists() {
8 local name="$1"
9 [[ -f "$(ctx::rule::path "${name}.rule")" ]]
10}
11
12function rule::require_exists() {
13 local name="$1"
14 if ! rule::exists "$name"; then
15 log::error "Rule not found: ${name}"
16 return 1
17 fi
18}
19
20function rule::get() {
21 local name="$1" key="$2"
22 json::get "$(ctx::rule::path "${name}.rule")" "$key"
23}
24
25function rule::get_all() {
26 local name="$1"
27 local rule_file
28 rule_file="$(ctx::rule::path "${name}.rule")"
29 cat "$rule_file"
30}
31
32function rule::is_applied() {
33 local rule_name="$1"
34 local client_ip="$2"
35
36 # Try first block_ports entry
37 local first_port
38 first_port=$(rule::get "$rule_name" "block_ports" | head -1)
39 if [[ -n "$first_port" ]]; then
40 local target port proto
41 IFS=":" read -r target port proto <<< "$first_port"
42 proto="${proto:-tcp}"
43 iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j DROP 2>/dev/null
44 return $?
45 fi
46
47 # Fall back to first block_ips entry
48 local first_ip
49 first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
50 if [[ -n "$first_ip" ]]; then
51 iptables -C FORWARD -s "$client_ip" -d "$first_ip" -j DROP 2>/dev/null
52 return $?
53 fi
54
55 # No rules to check (admin rule) — check allow_ports
56 local first_allow
57 first_allow=$(rule::get "$rule_name" "allow_ports" | head -1)
58 if [[ -n "$first_allow" ]]; then
59 local target port proto
60 IFS=":" read -r target port proto <<< "$first_allow"
61 proto="${proto:-tcp}"
62 iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null
63 return $?
64 fi
65
66 # Empty rule (admin) — never "applied" in iptables sense
67 return 1
68}
69
70# ============================================
71# Rule Application
72# ============================================
73
74function rule::apply() {
75 local rule_name="${1:?rule_name required}"
76 local client_ip="${2:?client_ip required}"
77 local peer_name="${3:-}" # optional, avoids find_by_ip call
78
79 rule::require_exists "$rule_name" || return 1
80
81 # Use provided peer_name or look it up
82 if [[ -z "$peer_name" ]]; then
83 peer_name=$(peers::find_by_ip "$client_ip")
84 fi
85
86 # Check if already applied
87 if rule::is_applied "$rule_name" "$client_ip"; then
88 log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
89
90 # Still update meta even if rules exist
91 if [[ -n "$peer_name" ]]; then
92 peers::set_meta "$peer_name" "rule" "$rule_name"
93 fi
94
95 return 0
96 fi
97
98 # Check if already applied
99 local peer_name
100 peer_name=$(peers::find_by_ip "$client_ip")
101 if [[ -n "$peer_name" ]]; then
102 # Check if already applied via iptables
103 if rule::is_applied "$rule_name" "$client_ip"; then
104 log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
105 return 0
106 fi
107 fi
108
109 # Process block_ips
110 while IFS= read -r block_ip; do
111 [[ -z "$block_ip" ]] && continue
112 fw::block_ip "$client_ip" "$block_ip"
113 done < <(rule::get "$rule_name" "block_ips")
114
115 # Process block_ports
116 while IFS= read -r entry; do
117 [[ -z "$entry" ]] && continue
118 local target port proto
119 IFS=":" read -r target port proto <<< "$entry"
120 proto="${proto:-tcp}"
121 fw::block_port "$client_ip" "$target" "$port" "$proto"
122 done < <(rule::get "$rule_name" "block_ports")
123
124 # Process allow_ips (inserted before blocks)
125 while IFS= read -r allow_ip; do
126 [[ -z "$allow_ip" ]] && continue
127 fw::allow_ip "$client_ip" "$allow_ip"
128 done < <(rule::get "$rule_name" "allow_ips")
129
130 # allow_ports (inserted last = highest priority)
131 while IFS= read -r entry; do
132 [[ -z "$entry" ]] && continue
133 local target port proto
134 IFS=":" read -r target port proto <<< "$entry"
135 proto="${proto:-tcp}"
136 fw::allow_port "$client_ip" "$target" "$port" "$proto"
137 done < <(rule::get "$rule_name" "allow_ports")
138
139 # Persist rule assignment in meta
140 if [[ -n "$peer_name" ]]; then
141 peers::set_meta "$peer_name" "rule" "$rule_name"
142 fi
143
144 local dns_redirect
145 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
146
147 if [[ "$dns_redirect" == "true" ]]; then
148 local peer_subnet
149 peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
150 # Only apply if not already in PREROUTING
151 if ! iptables -t nat -C PREROUTING -i wg0 -s "${peer_subnet}.0/24" -p udp --dport 53 \
152 -j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
153 rule::apply_dns_redirect "${peer_subnet}.0/24"
154 log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
155 else
156 log::debug "dns_redirect: already applied for ${peer_subnet}.0/24"
157 fi
158 fi
159
160 log::debug "Applied rule '${rule_name}' to: ${client_ip}"
161}
162
163function rule::unapply() {
164 local rule_name="$1"
165 local client_ip="$2"
166
167 rule::require_exists "$rule_name" || return 1
168
169 local peer_name
170 peer_name=$(peers::find_by_ip "$client_ip")
171
172 local subtype
173 subtype=$(peers::get_meta "$peer_name" "subtype")
174
175 local subnet
176 subnet=$(config::subnet_for "$subtype")
177
178 # Remove allow_ports first (reverse order of apply)
179 while IFS= read -r entry; do
180 [[ -z "$entry" ]] && continue
181 local target port proto
182 IFS=":" read -r target port proto <<< "$entry"
183 proto="${proto:-tcp}"
184 fw::unallow_port "$client_ip" "$target" "$port" "$proto"
185 done < <(rule::get "$rule_name" "allow_ports")
186
187 # Remove allow_ips
188 while IFS= read -r allow_ip; do
189 [[ -z "$allow_ip" ]] && continue
190 fw::unallow_ip "$client_ip" "$allow_ip"
191 done < <(rule::get "$rule_name" "allow_ips")
192
193 # Remove block_ports
194 while IFS= read -r entry; do
195 [[ -z "$entry" ]] && continue
196 local target port proto
197 IFS=":" read -r target port proto <<< "$entry"
198 proto="${proto:-tcp}"
199 fw::unblock_port "$client_ip" "$target" "$port" "$proto"
200 done < <(rule::get "$rule_name" "block_ports")
201
202 # Remove block_ips
203 while IFS= read -r block_ip; do
204 [[ -z "$block_ip" ]] && continue
205 fw::unblock_ip "$client_ip" "$block_ip"
206 done < <(rule::get "$rule_name" "block_ips")
207
208 # Remove DNS redirect if applicable
209 local dns_redirect
210 dns_redirect=$(rule::get "$rule_name" "dns_redirect")
211 if [[ "$dns_redirect" == "true" ]]; then
212 if [[ -z "$subtype" ]]; then
213 local peer_type
214 peer_type=$(peers::get_type "$peer_name") || true
215 [[ -z "$peer_type" ]] && peer_type="phone" # fallback
216 subnet=$(config::subnet_for "$peer_type")
217 fi
218
219 rule::remove_dns_redirect "${subnet}.0/24"
220 fi
221
222 # Clear rule from meta
223 if [[ -n "$peer_name" ]]; then
224 peers::set_meta "$peer_name" "rule" ""
225 fi
226
227 log::debug "Removed rule '${rule_name}' from: ${client_ip}"
228}
229
230function rule::reapply_all() {
231 local rule_name="$1"
232 rule::require_exists "$rule_name" || return 1
233
234 local peers=()
235 mapfile -t peers < <(peers::with_rule "$rule_name")
236
237 [[ ${#peers[@]} -eq 0 ]] && return 0
238
239 local count=0
240 for peer_name in "${peers[@]}"; do
241 local client_ip
242 client_ip=$(peers::get_ip "$peer_name")
243 [[ -z "$client_ip" ]] && continue
244 rule::unapply "$rule_name" "$client_ip"
245 rule::apply "$rule_name" "$client_ip" "$peer_name"
246 (( count++ )) || true
247 done
248
249 log::wg_success "Rule '${rule_name}' re-applied to ${count} peers"
250}
251
252function rule::restore_all() {
253 while IFS= read -r peer_name; do
254 local rule_name
255 rule_name=$(peers::get_meta "$peer_name" "rule")
256 [[ -z "$rule_name" ]] && continue
257
258 if ! rule::exists "$rule_name"; then
259 log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
260 continue
261 fi
262
263 local client_ip
264 client_ip=$(peers::get_ip "$peer_name")
265 [[ -z "$client_ip" ]] && continue
266
267 rule::apply "$rule_name" "$client_ip"
268 done < <(peers::all)
269 log::wg "Rules restored for all peers"
270}
271
272# ============================================
273# Guest DNS Redirect (rule-level feature)
274# ============================================
275
276function rule::apply_dns_redirect() {
277 local client_subnet="$1"
278 local dns
279 dns="$(config::dns)"
280
281 iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
282 ! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
283 iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
284 -j DNAT --to-destination "${dns}:53"
285 iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p tcp --dport 53 \
286 -j DNAT --to-destination "${dns}:53"
287}
288
289function rule::remove_dns_redirect() {
290 local client_subnet="$1"
291 local dns
292 dns="$(config::dns)"
293
294 iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
295 ! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
296 iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
297 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
298 iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p tcp --dport 53 \
299 -j DNAT --to-destination "${dns}:53" 2>/dev/null || true
300}