summaryrefslogtreecommitdiffstats
path: root/roles/gateway/README.md
blob: 5340cc25236c2dd6d06174ee4d67fa39c62da859 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# Gateway Role

Configures a system as a network gateway with dynamic VPN routing capabilities.

## Overview

This role makes a system (typically Raspberry Pi) into a network gateway.
Paired with `wireguard`, and `wg-portal` roles, this can control network VPN transparently.

## Configuration

### Required Variables

```yaml
gateway_enabled: true
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
Client Devices (192.168.1.0/24)
              ↓
┌─────────────────────────────┐
│    Raspberry Pi Gateway     │
│      (192.168.1.254)        │
└─────────────────────────────┘
              ↓
┌─────────────┬───────────────┐
│  VPN Mode   │ Direct Mode   │
│             │               │
│ WireGuard   │ ISP Router    │
│ Tunnel      │ (192.168.1.1) │
│ wg-*        │               │
└─────────────┴───────────────┘
        ↓              ↓
    VPN Server ────────┴───── Internet
```

**VPN Mode:**

- 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

- **WireGuard Interfaces**: Must follow `wg*` naming pattern (`wg0`, `wg-us1`, etc.)
- **Client Configuration**: Devices must use Pi as default gateway (192.168.1.254)
- **IP Forwarding**: Kernel IP forwarding must be enabled (`net.ipv4.ip_forward=1`)
- **WireGuard Config**: Use default routing (`Table=auto` or unset, NOT `Table=off`)

## Operation

### Event-Based System

```bash
# Check udev rules
cat /etc/udev/rules.d/99-wireguard-gateway.rules

# Monitor VPN mode service
systemctl status gateway-vpn-mode.service
journalctl -u gateway-vpn-mode.service -f

# Monitor direct mode service
systemctl status gateway-direct-mode.service
journalctl -u gateway-direct-mode.service -f

# Test manual execution
/usr/local/bin/gateway-apply-rules vpn
/usr/local/bin/gateway-apply-rules direct
```

### Troubleshooting

```bash
# Verify iptables rules
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
```