gistfile1.txt
· 8.8 KiB · Text
Surowy
#!/usr/bin/env bash
# ============================================
# Rule File Parsing
# ============================================
function rule::exists() {
local name="$1"
[[ -f "$(ctx::rule::path "${name}.rule")" ]]
}
function rule::require_exists() {
local name="$1"
if ! rule::exists "$name"; then
log::error "Rule not found: ${name}"
return 1
fi
}
function rule::get() {
local name="$1" key="$2"
json::get "$(ctx::rule::path "${name}.rule")" "$key"
}
function rule::get_all() {
local name="$1"
local rule_file
rule_file="$(ctx::rule::path "${name}.rule")"
cat "$rule_file"
}
function rule::is_applied() {
local rule_name="$1"
local client_ip="$2"
# Try first block_ports entry
local first_port
first_port=$(rule::get "$rule_name" "block_ports" | head -1)
if [[ -n "$first_port" ]]; then
local target port proto
IFS=":" read -r target port proto <<< "$first_port"
proto="${proto:-tcp}"
iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j DROP 2>/dev/null
return $?
fi
# Fall back to first block_ips entry
local first_ip
first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
if [[ -n "$first_ip" ]]; then
iptables -C FORWARD -s "$client_ip" -d "$first_ip" -j DROP 2>/dev/null
return $?
fi
# No rules to check (admin rule) — check allow_ports
local first_allow
first_allow=$(rule::get "$rule_name" "allow_ports" | head -1)
if [[ -n "$first_allow" ]]; then
local target port proto
IFS=":" read -r target port proto <<< "$first_allow"
proto="${proto:-tcp}"
iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null
return $?
fi
# Empty rule (admin) — never "applied" in iptables sense
return 1
}
# ============================================
# Rule Application
# ============================================
function rule::apply() {
local rule_name="${1:?rule_name required}"
local client_ip="${2:?client_ip required}"
local peer_name="${3:-}" # optional, avoids find_by_ip call
rule::require_exists "$rule_name" || return 1
# Use provided peer_name or look it up
if [[ -z "$peer_name" ]]; then
peer_name=$(peers::find_by_ip "$client_ip")
fi
# Check if already applied
if rule::is_applied "$rule_name" "$client_ip"; then
log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
# Still update meta even if rules exist
if [[ -n "$peer_name" ]]; then
peers::set_meta "$peer_name" "rule" "$rule_name"
fi
return 0
fi
# Check if already applied
local peer_name
peer_name=$(peers::find_by_ip "$client_ip")
if [[ -n "$peer_name" ]]; then
# Check if already applied via iptables
if rule::is_applied "$rule_name" "$client_ip"; then
log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
return 0
fi
fi
# Process block_ips
while IFS= read -r block_ip; do
[[ -z "$block_ip" ]] && continue
fw::block_ip "$client_ip" "$block_ip"
done < <(rule::get "$rule_name" "block_ips")
# Process block_ports
while IFS= read -r entry; do
[[ -z "$entry" ]] && continue
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::block_port "$client_ip" "$target" "$port" "$proto"
done < <(rule::get "$rule_name" "block_ports")
# Process allow_ips (inserted before blocks)
while IFS= read -r allow_ip; do
[[ -z "$allow_ip" ]] && continue
fw::allow_ip "$client_ip" "$allow_ip"
done < <(rule::get "$rule_name" "allow_ips")
# allow_ports (inserted last = highest priority)
while IFS= read -r entry; do
[[ -z "$entry" ]] && continue
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::allow_port "$client_ip" "$target" "$port" "$proto"
done < <(rule::get "$rule_name" "allow_ports")
# Persist rule assignment in meta
if [[ -n "$peer_name" ]]; then
peers::set_meta "$peer_name" "rule" "$rule_name"
fi
local dns_redirect
dns_redirect=$(rule::get "$rule_name" "dns_redirect")
if [[ "$dns_redirect" == "true" ]]; then
local peer_subnet
peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
# Only apply if not already in PREROUTING
if ! iptables -t nat -C PREROUTING -i wg0 -s "${peer_subnet}.0/24" -p udp --dport 53 \
-j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
rule::apply_dns_redirect "${peer_subnet}.0/24"
log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
else
log::debug "dns_redirect: already applied for ${peer_subnet}.0/24"
fi
fi
log::debug "Applied rule '${rule_name}' to: ${client_ip}"
}
function rule::unapply() {
local rule_name="$1"
local client_ip="$2"
rule::require_exists "$rule_name" || return 1
local peer_name
peer_name=$(peers::find_by_ip "$client_ip")
local subtype
subtype=$(peers::get_meta "$peer_name" "subtype")
local subnet
subnet=$(config::subnet_for "$subtype")
# Remove allow_ports first (reverse order of apply)
while IFS= read -r entry; do
[[ -z "$entry" ]] && continue
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::unallow_port "$client_ip" "$target" "$port" "$proto"
done < <(rule::get "$rule_name" "allow_ports")
# Remove allow_ips
while IFS= read -r allow_ip; do
[[ -z "$allow_ip" ]] && continue
fw::unallow_ip "$client_ip" "$allow_ip"
done < <(rule::get "$rule_name" "allow_ips")
# Remove block_ports
while IFS= read -r entry; do
[[ -z "$entry" ]] && continue
local target port proto
IFS=":" read -r target port proto <<< "$entry"
proto="${proto:-tcp}"
fw::unblock_port "$client_ip" "$target" "$port" "$proto"
done < <(rule::get "$rule_name" "block_ports")
# Remove block_ips
while IFS= read -r block_ip; do
[[ -z "$block_ip" ]] && continue
fw::unblock_ip "$client_ip" "$block_ip"
done < <(rule::get "$rule_name" "block_ips")
# Remove DNS redirect if applicable
local dns_redirect
dns_redirect=$(rule::get "$rule_name" "dns_redirect")
if [[ "$dns_redirect" == "true" ]]; then
if [[ -z "$subtype" ]]; then
local peer_type
peer_type=$(peers::get_type "$peer_name") || true
[[ -z "$peer_type" ]] && peer_type="phone" # fallback
subnet=$(config::subnet_for "$peer_type")
fi
rule::remove_dns_redirect "${subnet}.0/24"
fi
# Clear rule from meta
if [[ -n "$peer_name" ]]; then
peers::set_meta "$peer_name" "rule" ""
fi
log::debug "Removed rule '${rule_name}' from: ${client_ip}"
}
function rule::reapply_all() {
local rule_name="$1"
rule::require_exists "$rule_name" || return 1
local peers=()
mapfile -t peers < <(peers::with_rule "$rule_name")
[[ ${#peers[@]} -eq 0 ]] && return 0
local count=0
for peer_name in "${peers[@]}"; do
local client_ip
client_ip=$(peers::get_ip "$peer_name")
[[ -z "$client_ip" ]] && continue
rule::unapply "$rule_name" "$client_ip"
rule::apply "$rule_name" "$client_ip" "$peer_name"
(( count++ )) || true
done
log::wg_success "Rule '${rule_name}' re-applied to ${count} peers"
}
function rule::restore_all() {
while IFS= read -r peer_name; do
local rule_name
rule_name=$(peers::get_meta "$peer_name" "rule")
[[ -z "$rule_name" ]] && continue
if ! rule::exists "$rule_name"; then
log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
continue
fi
local client_ip
client_ip=$(peers::get_ip "$peer_name")
[[ -z "$client_ip" ]] && continue
rule::apply "$rule_name" "$client_ip"
done < <(peers::all)
log::wg "Rules restored for all peers"
}
# ============================================
# Guest DNS Redirect (rule-level feature)
# ============================================
function rule::apply_dns_redirect() {
local client_subnet="$1"
local dns
dns="$(config::dns)"
iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4
iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
-j DNAT --to-destination "${dns}:53"
iptables -t nat -A PREROUTING -i wg0 -s "$client_subnet" -p tcp --dport 53 \
-j DNAT --to-destination "${dns}:53"
}
function rule::remove_dns_redirect() {
local client_subnet="$1"
local dns
dns="$(config::dns)"
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
-j DNAT --to-destination "${dns}:53" 2>/dev/null || true
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p tcp --dport 53 \
-j DNAT --to-destination "${dns}:53" 2>/dev/null || true
}
| 1 | #!/usr/bin/env bash |
| 2 | |
| 3 | # ============================================ |
| 4 | # Rule File Parsing |
| 5 | # ============================================ |
| 6 | |
| 7 | function rule::exists() { |
| 8 | local name="$1" |
| 9 | [[ -f "$(ctx::rule::path "${name}.rule")" ]] |
| 10 | } |
| 11 | |
| 12 | function 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 | |
| 20 | function rule::get() { |
| 21 | local name="$1" key="$2" |
| 22 | json::get "$(ctx::rule::path "${name}.rule")" "$key" |
| 23 | } |
| 24 | |
| 25 | function 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 | |
| 32 | function 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 | |
| 74 | function 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 | |
| 163 | function 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 | |
| 230 | function 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 | |
| 252 | function 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 | |
| 276 | function 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 | |
| 289 | function 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 | } |