This commit is contained in:
2026-01-21 09:37:52 +02:00
parent 51b770daac
commit 0269fd0258
4 changed files with 615 additions and 5 deletions

View File

@@ -589,7 +589,7 @@
changed_when: false
# ============================ PHASE 0 (LAST, BEFORE PPP CHECK): IN-PROGRESS MARKER GUARD ============================
- name: "PHASE 0 (LAST) | DEV2 check: /tmp/ptsd.inprogress marker exists (show-stopper) via wrapper"
- name: "PHASE 0 (LAST) | DEV2 check /tmp/ptsd.inprogress marker exists (show-stopper) via wrapper"
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="test -f /tmp/ptsd.inprogress && echo YES || echo NO"
@@ -615,7 +615,7 @@
# ============================ PHASE 0 (LAST): DEV2 STATE GUARD (OLD PPP EXPECTED) ============================
- name: "PHASE 0 (LAST) | DEV2 check: ppp0 has IPv4 (old expected) via wrapper"
- name: "PHASE 0 (LAST) | DEV2 check ppp0 has IPv4 (old expected) via wrapper"
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ip -4 addr show dev ppp0 2>/dev/null | grep -m1 'inet ' >/dev/null 2>&1 && echo YES || echo NO"
@@ -625,7 +625,7 @@
changed_when: false
failed_when: false
- name: "PHASE 0 (LAST) | DEV2 check: eth0.4000 exists (new/migrated indicator) via wrapper"
- name: "PHASE 0 (LAST) | DEV2 check eth0.4000 exists (new/migrated indicator) via wrapper"
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD="ip link show eth0.4000 >/dev/null 2>&1 && echo YES || echo NO"
@@ -635,7 +635,7 @@
changed_when: false
failed_when: false
- name: "PHASE 0 (LAST) | Detect mixed state: ppp0 present AND eth0.4000 present (should not happen)"
- name: "PHASE 0 (LAST) | Detect mixed state ppp0 present AND eth0.4000 present (should not happen)"
delegate_to: localhost
ansible.builtin.set_fact:
dev2_ppp_present: "{{ (dev2_ppp0_has_ipv4.stdout | default('') | trim) == 'YES' }}"
@@ -648,7 +648,7 @@
}}
changed_when: false
- name: "PHASE 0 (LAST) | Graceful stop: DEV2 has BOTH ppp0 and eth0.4000 (mixed/partial migration)"
- name: "PHASE 0 (LAST) | Graceful stop DEV2 has BOTH ppp0 and eth0.4000 (mixed/partial migration)"
when: dev2_mixed_state | bool
delegate_to: localhost
ansible.builtin.debug:

View File

@@ -0,0 +1,519 @@
# ptsd-stage-blockscripts.yml — Connectivity only + stage block scripts
# Scope:
# a) Thorough DEV2 connectivity via DEV1 (LLDP first, then tunnel, then fallbacks)
# b) Copy startblock.sh and stopblock.sh from controller to DEV2
#
# Everything else from ptsd-migrate.yml intentionally removed.
- name: "PTSD | Stage block scripts | Connectivity + copy only"
hosts: all
gather_facts: no
vars:
pathprefix: "PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH; "
dev1_user: "root"
dev1_pass: "wavewave"
dev2_host: "192.168.1.1"
dev2_port: 22
dev2_side_ip: "192.168.1.11/24"
dev2_side_ip_addr: "{{ dev2_side_ip.split('/')[0] }}"
dev1_iface: "br-wan"
arping_iface: "eth0"
dev2_ssh_user: "root"
dev2_passfiles:
- "basicpass"
- "basicpass2"
ssh_opts_common: >-
-o PreferredAuthentications=password
-o PubkeyAuthentication=no
-o StrictHostKeyChecking=no
-o UserKnownHostsFile=/dev/null
-o NumberOfPasswordPrompts=1
-o ConnectTimeout=30
-o ConnectionAttempts=1
-o LogLevel=ERROR
debugging: true
ssh_timeout: 30
block_src_dir: "/opt/containers/ansible-worker/app"
block_dst_dir: "/root/"
block_files:
- "startblock.sh"
- "stopblock.sh"
tasks:
- name: Initialize passfile facts defensively (avoid undefined vars later)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_direct: "NONE"
dev2_passfile_used_tunnel: "NONE"
dev2_passfile_used_lldp4: "NONE"
dev2_passfile_used_lldp6: "NONE"
changed_when: false
- name: Read DEV1 hostname (busybox-safe)
ansible.builtin.raw: >
{{ pathprefix }}
(cat /proc/sys/kernel/hostname 2>/dev/null || echo "")
register: dev1_host_read
changed_when: false
- name: Stop early if connected DEV1 hostname != inventory (guard)
ansible.builtin.meta: end_host
when: (dev1_host_read.stdout | trim | length > 0) and
((dev1_host_read.stdout | trim) != (inventory_hostname | string))
# ============================ PHASE 0: LLDP FIRST ============================
- name: Compute DEV2 LLDP digits from inventory hostname (DEV1 side)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_lldp_digits: "{{ (inventory_hostname | string | regex_search('([0-9]+)$')) | default('') }}"
changed_when: false
- name: Read LLDP JSON on DEV1 (best-effort)
ansible.builtin.raw: >
{{ pathprefix }}
(cat /var/run/lldp_server.json 2>/dev/null || echo "")
register: lldp_json
changed_when: false
- name: Parse LLDP IPv4 and IPv6 candidates for DEV2 (best-effort)
delegate_to: localhost
ansible.builtin.set_fact:
lldp_dev2_ip: >-
{{
(
lldp_json.stdout | default('') |
regex_search('"name"\\s*:\\s*"dev2-?'+(dev2_lldp_digits|default(''))+'".*?"ip"\\s*:\\s*"([^"]+)"', '\\1')
) | default('')
}}
lldp_dev2_ip6: >-
{{
(
lldp_json.stdout | default('') |
regex_search('"name"\\s*:\\s*"dev2-?'+(dev2_lldp_digits|default(''))+'".*?"ip6"\\s*:\\s*"([^"]+)"', '\\1')
) | default('')
}}
changed_when: false
- name: Classify LLDP IPv4 (none, 10, 192_168, other)
delegate_to: localhost
ansible.builtin.set_fact:
lldp_ipv4_class: >-
{% set ip = (lldp_dev2_ip | default('')) %}
{% if ip == '' %}none
{% elif ip.startswith('10.') %}10
{% elif ip.startswith('192.168.') %}192_168
{% else %}other{% endif %}
changed_when: false
- name: Debug LLDP candidates
when: debugging | bool
delegate_to: localhost
ansible.builtin.debug:
msg:
- "LLDP digits={{ dev2_lldp_digits | default('') }}"
- "LLDP IPv4 candidate={{ lldp_dev2_ip | default('<none>') }}"
- "LLDP IPv4 class={{ lldp_ipv4_class | default('none') }}"
- "LLDP IPv6 candidate={{ lldp_dev2_ip6 | default('<none>') }}"
changed_when: false
# ============================ PHASE 0: DEV2 MAC DISCOVERY VIA DEV1 (FOR TUNNEL SAFETY) ============================
- name: Find DEV2 MAC via DEV1 bridge fdb (best-effort)
ansible.builtin.raw: >
{{ pathprefix }}
(bridge fdb show br br-lan 2>/dev/null || echo "")
register: bridge_fdb
changed_when: false
failed_when: false
- name: Parse DEV2 MAC from bridge fdb output (best-effort)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_mac_from_fdb: >-
{{
(bridge_fdb.stdout | default('') |
regex_search('([0-9a-f]{2}(:[0-9a-f]{2}){5})\\s+dev\\s+{{ arping_iface }}', '\\1')) | default('')
}}
changed_when: false
- name: Debug DEV2 MAC candidate from DEV1
when: debugging | bool
delegate_to: localhost
ansible.builtin.debug:
msg:
- "DEV2 MAC from fdb={{ dev2_mac_from_fdb | default('<none>') }}"
changed_when: false
# ============================ PHASE 0: CONNECTION METHOD SELECTION (LLDP DIRECT IF 10.X ELSE TUNNEL) ============================
- name: Prefer LLDP IPv4 if 192.168.x.x as dev2_host override (tunnel path still allowed)
when: lldp_ipv4_class | default('none') == "192_168"
delegate_to: localhost
ansible.builtin.set_fact:
dev2_host: "{{ lldp_dev2_ip }}"
changed_when: false
- name: Set connection method initial (direct_lldp if 10.x else tunnel)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_conn_method: "{{ 'direct_lldp' if (lldp_ipv4_class | default('none') == '10') else 'tunnel' }}"
changed_when: false
# ============================ PHASE 0: DIRECT LLDP AUTH (CONTROLLER -> DEV2) ============================
- name: Try direct LLDP IPv4 auth with basicpass
when: dev2_conn_method == "direct_lldp"
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f basicpass ssh {{ ssh_opts_common }} -p {{ dev2_port }} "{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "true"
args: { executable: /bin/bash }
register: direct_try_basicpass
changed_when: false
failed_when: false
- name: Record direct LLDP passfile used basicpass
when: dev2_conn_method == "direct_lldp" and direct_try_basicpass.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_direct: "basicpass"
changed_when: false
- name: Try direct LLDP IPv4 auth with basicpass2
when: dev2_conn_method == "direct_lldp" and dev2_passfile_used_direct == "NONE"
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f basicpass2 ssh {{ ssh_opts_common }} -p {{ dev2_port }} "{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "true"
args: { executable: /bin/bash }
register: direct_try_basicpass2
changed_when: false
failed_when: false
- name: Record direct LLDP passfile used basicpass2
when: dev2_conn_method == "direct_lldp" and direct_try_basicpass2.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_direct: "basicpass2"
changed_when: false
# ============================ PHASE 0: TUNNEL SETUP (CONTROLLER -> DEV1 -> DEV2) ============================
- name: Prepare tunnel temp dir and choose local port
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
set -e
TMPDIR="$(mktemp -d -t ptsd-tunnel.XXXXXX)"
PORT="2222"
for p in $(seq 2222 2299); do
if ! ss -lnt | awk '{print $4}' | grep -q ":$p$"; then
PORT="$p"
break
fi
done
echo "$TMPDIR" > "$TMPDIR/tmpdir"
echo "$PORT" > "$TMPDIR/port"
echo "$TMPDIR"
args: { executable: /bin/bash }
register: tunnel_tmpdir
changed_when: false
- name: Set tunnel facts
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.set_fact:
_tmpdir: "{{ tunnel_tmpdir.stdout | trim }}"
_local_port: "{{ (lookup('file', (tunnel_tmpdir.stdout | trim) ~ '/port') | trim) }}"
_ctrl_sock: "{{ (tunnel_tmpdir.stdout | trim) ~ '/cm.sock' }}"
changed_when: false
- name: Add temporary IP on DEV1 for DEV2 side (best-effort)
when: dev2_conn_method == "tunnel"
ansible.builtin.raw: >
{{ pathprefix }}
ip a add {{ dev2_side_ip }} dev {{ dev1_iface }}
register: add_ip
changed_when: add_ip.rc == 0
failed_when: false
- name: Start SSH ControlMaster and forward 127.0.0.1 local_port to DEV2 port 22 via DEV1
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f "{{ dev1_passfiles_used | default('') }}" true
args: { executable: /bin/bash }
changed_when: false
failed_when: false
- name: Probe local forwarded port readiness (best-effort)
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
set -e
for i in $(seq 1 30); do
(echo >/dev/tcp/127.0.0.1/{{ _local_port }}) >/dev/null 2>&1 && exit 0
sleep 1
done
exit 1
args: { executable: /bin/bash }
register: tunnel_port_ready
changed_when: false
failed_when: false
- name: Try tunnel auth with basicpass
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f basicpass ssh {{ ssh_opts_common }} -p {{ _local_port }} "{{ dev2_ssh_user }}@127.0.0.1" "true"
args: { executable: /bin/bash }
register: tunnel_try_basicpass
changed_when: false
failed_when: false
- name: Record tunnel passfile used basicpass
when: dev2_conn_method == "tunnel" and tunnel_try_basicpass.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel: "basicpass"
changed_when: false
- name: Try tunnel auth with basicpass2
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE"
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f basicpass2 ssh {{ ssh_opts_common }} -p {{ _local_port }} "{{ dev2_ssh_user }}@127.0.0.1" "true"
args: { executable: /bin/bash }
register: tunnel_try_basicpass2
changed_when: false
failed_when: false
- name: Record tunnel passfile used basicpass2
when: dev2_conn_method == "tunnel" and tunnel_try_basicpass2.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel: "basicpass2"
changed_when: false
- name: Read DEV2 eth0 MAC through tunnel (best-effort)
when: dev2_conn_method == "tunnel" and (dev2_mac_from_fdb | default('') | length > 0) and (dev2_passfile_used_tunnel != "NONE")
delegate_to: localhost
ansible.builtin.shell: |
set -e
sshpass -f "{{ dev2_passfile_used_tunnel }}" ssh {{ ssh_opts_common }} -p {{ _local_port }} "{{ dev2_ssh_user }}@127.0.0.1" "cat /sys/class/net/eth0/address 2>/dev/null || echo ''"
args: { executable: /bin/bash }
register: dev2_eth0_mac_through_tunnel
changed_when: false
failed_when: false
- name: Fail if tunnel lands on unexpected DEV2 MAC (safety guard)
when: dev2_conn_method == "tunnel" and (dev2_mac_from_fdb | default('') | length > 0) and (dev2_passfile_used_tunnel != "NONE")
delegate_to: localhost
ansible.builtin.fail:
msg: "Tunnel connected to unexpected DEV2 MAC. Expected={{ dev2_mac_from_fdb }}, Got={{ dev2_eth0_mac_through_tunnel.stdout | trim }}"
when: (dev2_eth0_mac_through_tunnel.stdout | trim | lower) != (dev2_mac_from_fdb | trim | lower)
# ============================ PHASE 0: FALLBACKS IF TUNNEL AUTH FAILED ============================
- name: Fallback controller direct LLDP IPv4 if tunnel failed and LLDP IPv4 is 10.x
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_ipv4_class | default('none') == "10")
delegate_to: localhost
ansible.builtin.shell: |
set -e
for pf in {{ dev2_passfiles | join(' ') }}; do
if sshpass -f "$pf" ssh {{ ssh_opts_common }} -p {{ dev2_port }} "{{ dev2_ssh_user }}@{{ lldp_dev2_ip }}" "true" >/dev/null 2>&1; then
echo "$pf"
exit 0
fi
done
echo "NONE"
exit 0
args: { executable: /bin/bash }
register: lldp4_pf
changed_when: false
- name: Record LLDP IPv4 fallback passfile
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_ipv4_class | default('none') == "10")
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_lldp4: "{{ lldp4_pf.stdout | trim }}"
changed_when: false
- name: Stage passfiles to DEV1 for IPv6 nested fallback (best-effort)
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_dev2_ip6 | default('') | length > 0)
ansible.builtin.raw: >
{{ pathprefix }}
(mkdir -p /tmp/ptsd_passfiles && true)
register: stage_dir
changed_when: false
failed_when: false
- name: Copy passfiles to DEV1 (best-effort)
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_dev2_ip6 | default('') | length > 0)
delegate_to: localhost
ansible.builtin.shell: |
set -e
for pf in {{ dev2_passfiles | join(' ') }}; do
sshpass -f "{{ dev1_pass }}" scp {{ ssh_opts_common }} "$pf" "{{ dev1_user }}@{{ inventory_hostname }}:/tmp/ptsd_passfiles/$pf" >/dev/null 2>&1 || true
done
args: { executable: /bin/bash }
changed_when: false
failed_when: false
- name: Try IPv6 nested fallback via DEV1 to DEV2 (best-effort)
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_dev2_ip6 | default('') | length > 0)
ansible.builtin.raw: >
{{ pathprefix }}
(for pf in basicpass basicpass2; do
sshpass -f /tmp/ptsd_passfiles/$pf ssh {{ ssh_opts_common }} -6 "{{ dev2_ssh_user }}@{{ lldp_dev2_ip6 }}" "true" >/dev/null 2>&1 && echo $pf && exit 0;
done;
echo NONE;
exit 0)
register: lldp6_pf
changed_when: false
failed_when: false
- name: Record LLDP IPv6 via DEV1 fallback passfile
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and (lldp_dev2_ip6 | default('') | length > 0)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_lldp6: "{{ lldp6_pf.stdout | trim }}"
changed_when: false
# ============================ PHASE 0: FINAL CONNECTION MODE + WRAPPER BUILD ============================
- name: Decide final connection mode
delegate_to: localhost
ansible.builtin.set_fact:
dev2_conn_final: >-
{% if dev2_conn_method == 'direct_lldp' and dev2_passfile_used_direct != 'NONE' %}
direct_lldp
{% elif dev2_conn_method == 'tunnel' and dev2_passfile_used_tunnel != 'NONE' %}
tunnel
{% elif dev2_passfile_used_lldp4 != 'NONE' %}
lldp4_fallback
{% elif dev2_passfile_used_lldp6 != 'NONE' %}
lldp6_via_dev1
{% else %}
none
{% endif %}
changed_when: false
- name: Fail if no connectivity method succeeded
delegate_to: localhost
ansible.builtin.fail:
msg: "No DEV2 connectivity method succeeded (direct_lldp/tunnel/lldp4/lldp6)."
when: dev2_conn_final == "none"
- name: Build DEV2 wrapper command (controller-side) for staged commands
delegate_to: localhost
ansible.builtin.set_fact:
dev2_exec_cmd: |
set -e
MODE="{{ dev2_conn_final }}"
if [ -z "${DEV2_CMD:-}" ]; then
echo "ERROR: DEV2_CMD is empty" >&2
exit 2
fi
case "$MODE" in
tunnel)
PORT="{{ _local_port | default('') }}"
PASS="{{ dev2_passfile_used_tunnel }}"
sshpass -f "$PASS" ssh {{ ssh_opts_common }} -p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" '{{ pathprefix }} '"${DEV2_CMD}" 2>&1"
;;
direct_lldp)
HOST="{{ lldp_dev2_ip }}"
PASS="{{ dev2_passfile_used_direct }}"
sshpass -f "$PASS" ssh {{ ssh_opts_common }} -p "{{ dev2_port }}" "{{ dev2_ssh_user }}@${HOST}" '{{ pathprefix }} '"${DEV2_CMD}" 2>&1"
;;
lldp4_fallback)
HOST="{{ lldp_dev2_ip }}"
PASS="{{ dev2_passfile_used_lldp4 }}"
sshpass -f "$PASS" ssh {{ ssh_opts_common }} -p "{{ dev2_port }}" "{{ dev2_ssh_user }}@${HOST}" '{{ pathprefix }} '"${DEV2_CMD}" 2>&1"
;;
lldp6_via_dev1)
HOST6="{{ lldp_dev2_ip6 }}"
PASS="{{ dev2_passfile_used_lldp6 }}"
sshpass -f "{{ dev1_pass }}" ssh {{ ssh_opts_common }} "{{ dev1_user }}@{{ inventory_hostname }}" \
"sshpass -f /tmp/ptsd_passfiles/${PASS} ssh {{ ssh_opts_common }} -6 {{ dev2_ssh_user }}@${HOST6} '{{ pathprefix }} '"${DEV2_CMD}" 2>&1'"
;;
*)
echo "ERROR: unknown MODE=$MODE" >&2
exit 3
;;
esac
changed_when: false
# ============================ COPY BLOCK SCRIPTS TO DEV2 ============================
- name: Copy block scripts to DEV2 via wrapper
delegate_to: localhost
ansible.builtin.shell: |
export DEV2_CMD='mkdir -p "{{ block_dst_dir }}" \
&& cat > "{{ block_dst_dir }}/{{ item }}" <<'__PTSD_EOF__'
{{ lookup('file', block_src_dir ~ '/' ~ item) }}
__PTSD_EOF__
&& chmod 0755 "{{ block_dst_dir }}/{{ item }}"'
{{ dev2_exec_cmd }}
args: { executable: /bin/bash }
loop: "{{ block_files }}"
changed_when: true
post_tasks:
- name: Cleanup note
delegate_to: localhost
ansible.builtin.debug:
msg: "Cleanup: closing tunnel, removing temp IP, removing staged passfiles (best-effort)"
changed_when: false
- name: Close SSH ControlMaster (best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
ssh -S "{{ _ctrl_sock | default('/dev/null') }}" -O exit 2>/dev/null || true
args: { executable: /bin/bash }
changed_when: false
ignore_errors: true
- name: Remove tunnel temp dir (best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
rm -rf "{{ _tmpdir | default('') }}" 2>/dev/null || true
args: { executable: /bin/bash }
changed_when: false
ignore_errors: true
- name: Remove staged passfiles on DEV1 (best-effort)
when: dev2_conn_method | default('') == "tunnel"
ansible.builtin.raw: >
{{ pathprefix }}
(rm -rf /tmp/ptsd_passfiles 2>/dev/null || true)
changed_when: false
failed_when: false
- name: Remove temporary IP on DEV1 (tolerate 'Cannot assign requested address')
when: dev2_conn_method | default('') == "tunnel"
ansible.builtin.raw: >
{{ pathprefix }}
ip a del {{ dev2_side_ip }} dev {{ dev1_iface }}
register: del_ip
changed_when: del_ip.rc == 0
failed_when: >
del_ip.rc != 0
and ('Cannot assign requested address' not in (del_ip.stdout | default('')))
and ('Cannot assign requested address' not in (del_ip.stderr | default('')))

58
files/startblock.sh Normal file
View File

@@ -0,0 +1,58 @@
LAN_IF="br-lan"
LAN_NET="192.168.2.0/24"
EXCLUDE_IP="13.244.149.112/32"
PORTAL_IP="102.38.126.180"
PORTAL_HTTP_PORT="8082"
PORTAL_HTTPS_PORT="8083"
# -------------------------
# NAT CAPTURE
# -------------------------
iptables -t nat -N CAPTIVE 2>/dev/null || true
iptables -t nat -F CAPTIVE
while iptables -t nat -C PREROUTING -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE 2>/dev/null; do
iptables -t nat -D PREROUTING -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE
done
iptables -t nat -I PREROUTING 1 -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE
iptables -t nat -A CAPTIVE -d "$EXCLUDE_IP" -j RETURN
iptables -t nat -A CAPTIVE -p udp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -A CAPTIVE -p tcp --dport 53 -j REDIRECT --to-ports 53
iptables -t nat -A CAPTIVE -p tcp --dport 80 -j DNAT --to-destination ${PORTAL_IP}:${PORTAL_HTTP_PORT}
iptables -t nat -A CAPTIVE -p tcp --dport 443 -j DNAT --to-destination ${PORTAL_IP}:${PORTAL_HTTPS_PORT}
# -------------------------
# FILTER ENFORCEMENT (BLOCK EVERYTHING ELSE)
# -------------------------
iptables -N CAPTIVE_BLOCK 2>/dev/null || true
iptables -F CAPTIVE_BLOCK
# Allow established traffic
iptables -A CAPTIVE_BLOCK -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow DNS to router itself (client -> router)
iptables -A CAPTIVE_BLOCK -p udp -d 192.168.2.1 --dport 53 -j ACCEPT
iptables -A CAPTIVE_BLOCK -p tcp -d 192.168.2.1 --dport 53 -j ACCEPT
# Allow access to captive portal server (after DNAT)
iptables -A CAPTIVE_BLOCK -p tcp -d "$PORTAL_IP" --dport "$PORTAL_HTTP_PORT" -j ACCEPT
iptables -A CAPTIVE_BLOCK -p tcp -d "$PORTAL_IP" --dport "$PORTAL_HTTPS_PORT" -j ACCEPT
# Allow bypass destination fully (optional but usually desired)
iptables -A CAPTIVE_BLOCK -d "$EXCLUDE_IP" -j ACCEPT
# Block everything else from LAN clients
iptables -A CAPTIVE_BLOCK -j REJECT --reject-with icmp-admin-prohibited
# Hook CAPTIVE_BLOCK into FORWARD (ensure single jump)
while iptables -C FORWARD -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE_BLOCK 2>/dev/null; do
iptables -D FORWARD -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE_BLOCK
done
iptables -I FORWARD 1 -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE_BLOCK

33
files/stopblock.sh Normal file
View File

@@ -0,0 +1,33 @@
#!/bin/sh
set -eu
LAN_IF="br-lan"
LAN_NET="192.168.2.0/24"
# -------------------------
# FILTER: remove CAPTIVE_BLOCK hook + chain
# -------------------------
# Remove any FORWARD jumps that match our hook exactly
while iptables -C FORWARD -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE_BLOCK 2>/dev/null; do
iptables -D FORWARD -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE_BLOCK
done
# Flush/delete CAPTIVE_BLOCK chain if present
iptables -F CAPTIVE_BLOCK 2>/dev/null || true
iptables -X CAPTIVE_BLOCK 2>/dev/null || true
# -------------------------
# NAT: remove CAPTIVE hook + chain
# -------------------------
# Remove any PREROUTING jumps that match our hook exactly
while iptables -t nat -C PREROUTING -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE 2>/dev/null; do
iptables -t nat -D PREROUTING -i "$LAN_IF" -s "$LAN_NET" -j CAPTIVE
done
# Flush/delete CAPTIVE chain if present
iptables -t nat -F CAPTIVE 2>/dev/null || true
iptables -t nat -X CAPTIVE 2>/dev/null || true
echo "OK: captive NAT + filter rules removed (baseline preserved)."