This commit is contained in:
2026-01-26 18:46:14 +02:00
parent e5f0d53ac5
commit 13d5776d2a
2 changed files with 940 additions and 8 deletions

View File

@@ -44,6 +44,7 @@
ansible.builtin.set_fact:
dev2_passfile_used_direct: "NONE"
dev2_passfile_used_tunnel: "NONE"
dev2_passfile_used_tunnel6: "NONE"
dev2_passfile_used_lldp4: "NONE"
dev2_passfile_used_lldp6: "NONE"
changed_when: false
@@ -75,7 +76,6 @@
# sleep 10
# ignore_errors: true
- name: Discover DEV2 candidate IPv4 via LLDP on DEV1 (best-effort)
ansible.builtin.raw: >
{{ pathprefix }}
@@ -312,12 +312,19 @@
changed_when: false
ignore_errors: true
- name: Stop if tunnel TCP probe failed
when: dev2_conn_method == "tunnel" and nc_probe.rc != 0
- name: Record IPv4 tunnel probe status
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.set_fact:
tunnel_v4_ok: "{{ (nc_probe.rc | default(1)) == 0 }}"
changed_when: false
- name: Stop if tunnel TCP probe failed and no IPv6 candidate exists
when: dev2_conn_method == "tunnel" and not (tunnel_v4_ok | bool) and (lldp_dev2_ip6 | default('') | trim | length) == 0
ansible.builtin.meta: end_host
- name: Pick DEV2 password for root (tunnel) try basicpass
when: dev2_conn_method == "tunnel"
when: dev2_conn_method == "tunnel" and (tunnel_v4_ok | bool)
delegate_to: localhost
ansible.builtin.shell: |
set -e
@@ -331,14 +338,14 @@
ignore_errors: true
- name: Select basicpass if tunnel login succeeded
when: dev2_conn_method == "tunnel" and dev2_try_basicpass.rc == 0
when: dev2_conn_method == "tunnel" and (tunnel_v4_ok | bool) and dev2_try_basicpass.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel: "basicpass"
changed_when: false
- name: Try DEV2 login through tunnel with basicpass2 (only if first failed)
when: dev2_conn_method == "tunnel" and (dev2_passfile_used_tunnel == "NONE")
when: dev2_conn_method == "tunnel" and (tunnel_v4_ok | bool) and (dev2_passfile_used_tunnel == "NONE")
delegate_to: localhost
ansible.builtin.shell: |
set -e
@@ -352,13 +359,149 @@
ignore_errors: true
- name: Select basicpass2 if tunnel login succeeded
when: dev2_conn_method == "tunnel" and dev2_passfile_used_tunnel == "NONE" and dev2_try_basicpass2.rc == 0
when: dev2_conn_method == "tunnel" and (tunnel_v4_ok | bool) and dev2_passfile_used_tunnel == "NONE" and dev2_try_basicpass2.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel: "basicpass2"
changed_when: false
- name: Decide final DEV2 connection mode (direct_lldp > tunnel > lldp4 > lldp6)
- name: Record need for IPv6 tunnel attempt
when: dev2_conn_method == "tunnel"
delegate_to: localhost
ansible.builtin.set_fact:
need_tunnel6: >-
{{
(
(not (tunnel_v4_ok | default(false) | bool))
or
((dev2_passfile_used_tunnel | default('NONE')) == 'NONE')
)
and
((lldp_dev2_ip6 | default('') | trim | length) > 0)
}}
changed_when: false
- name: Pick a free local TCP port for the IPv6 tunnel (controller side)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.shell: |
set -e
pick() {
for i in $(seq 1 25); do
p="$(shuf -i 20000-39999 -n 1)"
if command -v ss >/dev/null 2>&1; then
if ! ss -ltn | awk '{print $4}' | grep -qE "(:|\.)${p}$"; then
echo "$p"; return 0
fi
else
if ! nc -z 127.0.0.1 "$p" >/dev/null 2>&1; then
echo "$p"; return 0
fi
fi
done
return 1
}
pick
args: { executable: /bin/bash }
register: pick_port6
changed_when: false
- name: Stop if no free local port was found for IPv6 tunnel
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool) and (pick_port6.stdout | trim | length) == 0
ansible.builtin.meta: end_host
- name: Create control dir for SSH ControlMaster (IPv6 tunnel)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.shell: "mktemp -d"
args: { executable: /bin/bash }
register: mktemp_dir6
changed_when: false
- name: Record chosen local port and build ControlMaster socket path (IPv6 tunnel)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.set_fact:
_local_port6: "{{ pick_port6.stdout | trim }}"
_ctrl_dir6: "{{ mktemp_dir6.stdout | trim }}"
_ctrl_sock6: "{{ (mktemp_dir6.stdout | trim) }}/ssh_tunnel_ctl6"
changed_when: false
- name: Start SSH ControlMaster and forward 127.0.0.1:local_port6 to DEV2 22 via DEV1 (IPv6)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.shell: |
set -e
USER="{{ dev1_user }}"
HOST="{{ ansible_host | default(inventory_hostname) }}"
IP6="{{ lldp_dev2_ip6 | trim }}"
sshpass -p '{{ dev1_pass }}' ssh -f -N {{ ssh_opts_common }} \
-M -S "{{ _ctrl_sock6 }}" \
-L "127.0.0.1:{{ _local_port6 }}:[${IP6}%{{ dev1_iface }}]:{{ dev2_port }}" \
"${USER}@${HOST}"
args: { executable: /bin/bash }
register: start_tunnel6
changed_when: true
ignore_errors: true
- name: Probe TCP reachability to DEV2 through the IPv6 tunnel (nc)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.shell: |
set -e
nc -z -w5 127.0.0.1 "{{ _local_port6 }}"
args: { executable: /bin/bash }
register: nc_probe6
changed_when: false
ignore_errors: true
- name: Stop if IPv6 tunnel TCP probe failed
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool) and (nc_probe6.rc | default(1)) != 0
ansible.builtin.meta: end_host
- name: Pick DEV2 password for root (IPv6 tunnel) try basicpass
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool)
delegate_to: localhost
ansible.builtin.shell: |
set -e
PORT="{{ _local_port6 }}"
sshpass -f basicpass ssh {{ ssh_opts_common }} \
-o AddressFamily=inet \
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
args: { executable: /bin/bash }
register: dev2_try_basicpass6
changed_when: false
ignore_errors: true
- name: Select basicpass if IPv6 tunnel login succeeded
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool) and dev2_try_basicpass6.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel6: "basicpass"
changed_when: false
- name: Try DEV2 login through IPv6 tunnel with basicpass2 (only if first failed)
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool) and (dev2_passfile_used_tunnel6 == "NONE")
delegate_to: localhost
ansible.builtin.shell: |
set -e
PORT="{{ _local_port6 }}"
sshpass -f basicpass2 ssh {{ ssh_opts_common }} \
-o AddressFamily=inet \
-p "$PORT" root@127.0.0.1 echo OK >/dev/null 2>&1
args: { executable: /bin/bash }
register: dev2_try_basicpass26
changed_when: false
ignore_errors: true
- name: Select basicpass2 if IPv6 tunnel login succeeded
when: dev2_conn_method == "tunnel" and (need_tunnel6 | bool) and dev2_passfile_used_tunnel6 == "NONE" and dev2_try_basicpass26.rc == 0
delegate_to: localhost
ansible.builtin.set_fact:
dev2_passfile_used_tunnel6: "basicpass2"
changed_when: false
- name: Decide final DEV2 connection mode (direct_lldp > tunnel > tunnel6 > lldp4 > lldp6)
delegate_to: localhost
ansible.builtin.set_fact:
dev2_conn_final: >-
@@ -366,6 +509,8 @@
direct_lldp
{%- elif dev2_conn_method == 'tunnel' and (dev2_passfile_used_tunnel | default('NONE')) != 'NONE' -%}
tunnel
{%- elif dev2_conn_method == 'tunnel' and (dev2_passfile_used_tunnel6 | default('NONE')) != 'NONE' -%}
tunnel6
{%- else -%}
none
{%- endif -%}
@@ -377,6 +522,7 @@
msg: >
DEV2 unreachable:
tunnel={{ dev2_passfile_used_tunnel | default('n/a') }},
tunnel6={{ dev2_passfile_used_tunnel6 | default('n/a') }},
direct={{ dev2_passfile_used_direct | default('n/a') }}.
- name: Debug final connectivity decision
@@ -386,8 +532,10 @@
msg:
- "dev2_conn_final={{ dev2_conn_final }}"
- "tunnel passfile={{ dev2_passfile_used_tunnel | default('n/a') }}"
- "tunnel6 passfile={{ dev2_passfile_used_tunnel6 | default('n/a') }}"
- "direct passfile={{ dev2_passfile_used_direct | default('n/a') }}"
- "tunnel target={{ dev2_host }}:{{ dev2_port }} forwarded 127.0.0.1:{{ _local_port | default('na') }}"
- "tunnel6 target={{ lldp_dev2_ip6 | default('') }}%{{ dev1_iface }}:{{ dev2_port }} forwarded 127.0.0.1:{{ _local_port6 | default('na') }}"
- "lldp ipv4={{ lldp_dev2_ip | default('') }}"
- "lldp ipv6={{ lldp_dev2_ip6 | default('') }}"
@@ -411,6 +559,14 @@
-p "$PORT" "{{ dev2_ssh_user }}@127.0.0.1" \
"{{ pathprefix }} ${DEV2_CMD}" 2>&1
;;
tunnel6)
PORT="{{ _local_port6 | default('') }}"
PASS="{{ dev2_passfile_used_tunnel6 }}"
sshpass -f "$PASS" ssh {{ ssh_opts_common }} \
-o AddressFamily=inet \
-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 }}"
@@ -730,6 +886,15 @@
changed_when: false
ignore_errors: true
- name: Close SSH ControlMaster (IPv6 tunnel best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.shell: |
ssh -S "{{ _ctrl_sock6 | default('/dev/null') }}" -O exit 2>/dev/null || true
args: { executable: /bin/bash }
changed_when: false
ignore_errors: true
- name: Remove tunnel control dir (best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
@@ -738,6 +903,14 @@
state: absent
ignore_errors: true
- name: Remove tunnel control dir (IPv6 tunnel best-effort)
when: dev2_conn_method | default('') == "tunnel"
delegate_to: localhost
ansible.builtin.file:
path: "{{ _ctrl_dir6 | default('/tmp/none') }}"
state: absent
ignore_errors: true
- name: Remove staged passfiles from DEV1 (best-effort)
when: dev2_conn_method | default('') == "tunnel"
ansible.builtin.raw: >