diff options
Diffstat (limited to 'roles/gateway')
| -rw-r--r-- | roles/gateway/README.md | 65 | ||||
| -rw-r--r-- | roles/gateway/defaults/main.yml | 1 | ||||
| -rw-r--r-- | roles/gateway/handlers/main.yml | 6 | ||||
| -rw-r--r-- | roles/gateway/meta/argument_specs.yml | 5 | ||||
| -rw-r--r-- | roles/gateway/tasks/main.yml | 37 | ||||
| -rw-r--r-- | roles/gateway/templates/99-gateway.conf.j2 | 3 | ||||
| -rw-r--r-- | roles/gateway/templates/gateway-apply-rules.sh.j2 | 20 | ||||
| -rw-r--r-- | roles/gateway/templates/radvd.conf.j2 | 12 |
8 files changed, 0 insertions, 149 deletions
diff --git a/roles/gateway/README.md b/roles/gateway/README.md index 5340cc25..eda1b087 100644 --- a/roles/gateway/README.md +++ b/roles/gateway/README.md @@ -17,29 +17,6 @@ gateway_local_ipv4_subnet: "192.168.1.0/24" gateway_router_interface: "end0" ``` -### IPv6 Support (Optional) - -IPv6 gateway is enabled by setting `gateway_local_ipv6_subnet` to the ULA prefix advertised by radvd: - -```yaml -gateway_local_ipv6_subnet: "fd00:xxxx:xxxx:xxxx::/64" # ULA prefix (stable, router-independent) -``` - -When set, this role: -- Enables `net.ipv6.conf.all.forwarding=1` and `accept_ra=2` on `gateway_router_interface` -- Deploys radvd advertising the ULA prefix with `AdvDefaultPreference high` — Pi wins as IPv6 default router -- Adds ip6tables MASQUERADE/FORWARD rules so all client IPv6 traffic is forwarded through Pi -- Drops outbound ICMPv6 Redirect messages — prevents Pi from redirecting clients to bypass the gateway - -**ICMPv6 Redirect suppression is critical.** Pi, the ISP router (FritzBox), and LAN clients share the same L2 -segment. When Pi forwards a packet to an external destination and the kernel determines the ISP router is a -"better" next-hop on the same link, it sends an ICMPv6 Redirect to the client. The client then sends -subsequent connections directly to the ISP router — bypassing Pi's masquerade and content filtering entirely. -The first connection attempt fails (Pi forwarded nothing, just redirected), giving the symptom of "first -request fails every ~30-60s." Dropping Redirect messages in ip6tables OUTPUT fixes this. - -To disable IPv6, leave `gateway_local_ipv6_subnet: ""` (default). The role skips all ip6tables rules and disables radvd. - ## Network Architecture ```txt @@ -65,40 +42,10 @@ Client Devices (192.168.1.0/24) - All traffic NAT'd through `wg+` interfaces - Pi and client traffic both use VPN exit IP -- Most VPN providers handle IPv4/IPv6 protocol routing automatically **Direct Mode Characteristics:** - IPv4 traffic NAT'd through router interface (`end0`) -- IPv6 traffic masqueraded through router interface — clients appear as Pi's GUA to the ISP router, - preventing ISP-router-level content filtering from applying per-device rules -- ICMPv6 Redirects suppressed so clients always route through Pi - -## IPv6 Architecture Notes - -### Why masquerade IPv6 in direct mode? - -Clients have globally routable GUA addresses (assigned by ISP router SLAAC). Without masquerade, the ISP -router sees each client's real GUA and can apply per-device content filtering. With masquerade, all client -traffic appears to originate from Pi's GUA — bypassing per-device filtering consistently. - -### Why suppress ICMPv6 Redirects? - -When Pi, the ISP router, and clients share the same L2 segment (single broadcast domain), the Linux kernel -sends ICMPv6 Redirect messages when it detects a "better" next-hop for a destination on the same link. For -external GUA destinations reachable via the ISP router directly, Linux redirects clients from Pi to the ISP -router. This causes: -1. First SYN from client arrives at Pi — Pi sends Redirect, does NOT forward the packet -2. Client receives Redirect, caches ISP router as next-hop for that destination -3. Subsequent connections go directly to ISP router — masquerade and filtering bypassed - -Dropping `icmpv6-type redirect` in ip6tables OUTPUT prevents this entirely. - -### Why radvd with `AdvDefaultPreference high`? - -The ISP router (FritzBox) also sends RAs with default preference `medium` (or `low` if configured). -Pi's radvd sends `AdvDefaultPreference high`, so clients prefer Pi as their IPv6 default router. -Without this, clients split or prefer the ISP router depending on RA timing. ## Requirements @@ -135,18 +82,6 @@ journalctl -u gateway-direct-mode.service -f sudo iptables -L -n -v sudo iptables -t nat -L -n -v -# Verify ip6tables rules (redirect DROP must be first in OUTPUT) -sudo ip6tables -L OUTPUT -n -v --line-numbers -sudo ip6tables -t nat -L -n -v - -# Confirm no ICMPv6 Redirects being sent -sudo tcpdump -i end0 -n 'icmp6 and ip6[40] == 137' - -# Verify clients route through Pi (not ISP router) -# On client: ip -6 route show default -# On Pi: sudo tcpdump -i end0 -n -e ip6 and host <client-GUA> - # Test traffic routing curl -4 ifconfig.co # Should show VPN IP when VPN active -curl -6 ifconfig.co # Should show Pi's GUA in direct mode, VPN IPv6 in VPN mode ``` diff --git a/roles/gateway/defaults/main.yml b/roles/gateway/defaults/main.yml index dad64685..ebe33a12 100644 --- a/roles/gateway/defaults/main.yml +++ b/roles/gateway/defaults/main.yml @@ -1,4 +1,3 @@ --- gateway_enabled: false gateway_router_interface: "eth0" -gateway_local_ipv6_subnet: "" diff --git a/roles/gateway/handlers/main.yml b/roles/gateway/handlers/main.yml index 19088c82..12f901c0 100644 --- a/roles/gateway/handlers/main.yml +++ b/roles/gateway/handlers/main.yml @@ -15,9 +15,3 @@ ansible.builtin.command: cmd: "udevadm control --reload-rules" changed_when: false - -- name: "Restart radvd" - become: true - ansible.builtin.systemd_service: - name: "radvd.service" - state: "restarted" diff --git a/roles/gateway/meta/argument_specs.yml b/roles/gateway/meta/argument_specs.yml index b8efa0b3..d24251e3 100644 --- a/roles/gateway/meta/argument_specs.yml +++ b/roles/gateway/meta/argument_specs.yml @@ -11,11 +11,6 @@ argument_specs: description: "Local subnet CIDR for NAT and routing rules (e.g., '192.168.178.0/24')" type: "str" required: true - gateway_local_ipv6_subnet: - description: "Local IPv6 subnet CIDR for NAT and routing rules (e.g., '2a02:3102:4ced:ec00::/64')" - type: "str" - required: false - default: "" gateway_router_interface: description: "Default route interface for direct routing mode" type: "str" diff --git a/roles/gateway/tasks/main.yml b/roles/gateway/tasks/main.yml index 71484725..661630f7 100644 --- a/roles/gateway/tasks/main.yml +++ b/roles/gateway/tasks/main.yml @@ -6,7 +6,6 @@ - "iptables" - "iproute2" - "udev" - - "radvd" state: "present" - name: "Configure IP forwarding" @@ -68,39 +67,3 @@ name: "gateway-init.service" enabled: "{{ gateway_enabled }}" daemon_reload: true - -- name: "Configure radvd for IPv6 RA" - become: true - when: gateway_enabled and gateway_local_ipv6_subnet != "" - block: - - name: "Deploy radvd config" - ansible.builtin.template: - src: "radvd.conf.j2" - dest: "/etc/radvd.conf" - owner: "root" - group: "root" - mode: "0644" - notify: "Restart radvd" - - - name: "Enable radvd service" - ansible.builtin.systemd_service: - name: "radvd.service" - enabled: true - state: "started" - daemon_reload: true - -- name: "Disable radvd when no IPv6 subnet configured" - become: true - when: not gateway_enabled or gateway_local_ipv6_subnet == "" - block: - - name: "Stop and disable radvd" - ansible.builtin.systemd_service: - name: "radvd.service" - enabled: false - state: "stopped" - failed_when: false - - - name: "Remove radvd config" - ansible.builtin.file: - path: "/etc/radvd.conf" - state: "absent" diff --git a/roles/gateway/templates/99-gateway.conf.j2 b/roles/gateway/templates/99-gateway.conf.j2 index b0f99dd4..6f8a3fbd 100644 --- a/roles/gateway/templates/99-gateway.conf.j2 +++ b/roles/gateway/templates/99-gateway.conf.j2 @@ -1,8 +1,5 @@ {% if gateway_enabled %} net.ipv4.ip_forward=1 -net.ipv6.conf.all.forwarding=1 -net.ipv6.conf.{{ gateway_router_interface }}.accept_ra=2 {% else %} net.ipv4.ip_forward=0 -net.ipv6.conf.all.forwarding=0 {% endif %} diff --git a/roles/gateway/templates/gateway-apply-rules.sh.j2 b/roles/gateway/templates/gateway-apply-rules.sh.j2 index 59a960cf..785d595b 100644 --- a/roles/gateway/templates/gateway-apply-rules.sh.j2 +++ b/roles/gateway/templates/gateway-apply-rules.sh.j2 @@ -7,7 +7,6 @@ LOG_FACILITY="local0.info" # Ansible template variables - configured at deployment time GATEWAY_SUBNET_V4="{{ gateway_local_ipv4_subnet }}" -GATEWAY_SUBNET_V6="{{ gateway_local_ipv6_subnet }}" GATEWAY_INTERFACE="{{ gateway_router_interface }}" log_message() { echo "[$SCRIPT_NAME] $1" | logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY" @@ -25,11 +24,6 @@ clear_rules() { iptables -t nat -D POSTROUTING -s "$GATEWAY_SUBNET_V4" -o "$GATEWAY_INTERFACE" -j MASQUERADE 2>/dev/null || true iptables -D OUTPUT -o wg+ -j ACCEPT 2>/dev/null || true iptables -D OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT 2>/dev/null || true - if [ -n "$GATEWAY_SUBNET_V6" ]; then - ip6tables -F FORWARD 2>/dev/null || true - ip6tables -F OUTPUT 2>/dev/null || true - ip6tables -t nat -F POSTROUTING 2>/dev/null || true - fi } apply_vpn_mode() { @@ -39,13 +33,6 @@ apply_vpn_mode() { iptables -A OUTPUT -o wg+ -j ACCEPT iptables -A FORWARD -s "$GATEWAY_SUBNET_V4" -o wg+ -j ACCEPT iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - if [ -n "$GATEWAY_SUBNET_V6" ]; then - ip6tables -t nat -A POSTROUTING -o wg+ -j MASQUERADE - ip6tables -I OUTPUT 1 -p ipv6-icmp --icmpv6-type redirect -j DROP - ip6tables -A OUTPUT -o wg+ -j ACCEPT - ip6tables -A FORWARD -i "$GATEWAY_INTERFACE" -o wg+ -j ACCEPT - ip6tables -A FORWARD -i wg+ -o "$GATEWAY_INTERFACE" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - fi log_message "VPN mode rules applied - traffic routed through VPN" } @@ -55,13 +42,6 @@ apply_direct_rules() { iptables -A OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT iptables -A FORWARD -s "$GATEWAY_SUBNET_V4" -o "$GATEWAY_INTERFACE" -j ACCEPT iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - if [ -n "$GATEWAY_SUBNET_V6" ]; then - ip6tables -t nat -A POSTROUTING -o "$GATEWAY_INTERFACE" -j MASQUERADE - ip6tables -I OUTPUT 1 -p ipv6-icmp --icmpv6-type redirect -j DROP - ip6tables -A OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT - ip6tables -A FORWARD -s "$GATEWAY_SUBNET_V6" -i "$GATEWAY_INTERFACE" -o "$GATEWAY_INTERFACE" -j ACCEPT - ip6tables -A FORWARD -i "$GATEWAY_INTERFACE" -o "$GATEWAY_INTERFACE" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT - fi log_message "Direct mode rules applied - normal routing active" } diff --git a/roles/gateway/templates/radvd.conf.j2 b/roles/gateway/templates/radvd.conf.j2 deleted file mode 100644 index 49b3047d..00000000 --- a/roles/gateway/templates/radvd.conf.j2 +++ /dev/null @@ -1,12 +0,0 @@ -interface {{ gateway_router_interface }} { - AdvSendAdvert on; - AdvManagedFlag off; - AdvOtherConfigFlag on; - AdvDefaultPreference high; - - prefix {{ gateway_local_ipv6_subnet }} { - AdvOnLink on; - AdvAutonomous on; - AdvRouterAddr on; - }; -}; |
