|
46 | 46 | run: | |
47 | 47 | go test -c -tags vpn_hostnet -o gw-hostnet.test ./vpn/routes/ |
48 | 48 | sudo ./gw-hostnet.test -test.run '^TestGatewayHostNet' -test.v |
| 49 | + - name: VPN gateway host-network integration test (Windows, admin) |
| 50 | + # Windows counterpart of the step above: exercises WinNAT + WFP + |
| 51 | + # per-interface forwarding (SetupNAT/TeardownNAT) against a real NIC |
| 52 | + # and the /1 client routes against a real Wintun adapter. GitHub |
| 53 | + # Windows runners execute as Administrator, so no sudo equivalent is |
| 54 | + # needed. The diagnostic Get-NetNat shows what the runner image holds |
| 55 | + # in WinNAT (a Docker/HNS instance would conflict — decide about |
| 56 | + # pre-cleaning based on what this prints). |
| 57 | + if: matrix.os == 'windows-latest' |
| 58 | + shell: pwsh |
| 59 | + run: | |
| 60 | + Get-NetNat | Format-List |
| 61 | + go test -c -tags vpn_hostnet -o gw-hostnet.test.exe ./vpn/routes/ |
| 62 | + # Flags are quoted: pwsh splits an unquoted `-test.run` at the dot |
| 63 | + # into `-test` + `.run`, which the test binary rejects. |
| 64 | + ./gw-hostnet.test.exe '-test.run' '^TestGatewayHostNet' '-test.v' |
49 | 65 | - name: Build cmd/awl |
50 | 66 | run: go build github.com/anywherelan/awl/cmd/awl |
51 | 67 | - name: Upload cmd/awl build |
@@ -143,7 +159,6 @@ jobs: |
143 | 159 |
|
144 | 160 | # Probe the egress IP via https://ifconfig.me |
145 | 161 | curl4() { curl -4 -s --max-time 20 --retry 3 --retry-delay 2 https://ifconfig.me/; } |
146 | | -
|
147 | 162 | IP_DIRECT=$(curl4); echo "egress IP (direct): $IP_DIRECT" |
148 | 163 |
|
149 | 164 | ./awl cli gateway client use --name awl-tester |
@@ -209,11 +224,101 @@ jobs: |
209 | 224 | sleep 10 |
210 | 225 | awl_pid=`jobs -l | grep './awl' | awk '{print $2}'` |
211 | 226 | ./awl.exe cli peers status |
| 227 | +
|
212 | 228 | ./librespeed-cli.exe --local-json config_librespeed.json --server 2 --json --share --telemetry-level disabled | python3 -m json.tool |
213 | 229 |
|
214 | 230 | ping -w 20000 -n 10 10.66.0.2 |
215 | 231 | ping -w 20000 -n 10 -a awl-tester.awl |
216 | 232 |
|
| 233 | + # ---- VPN gateway server (exit-node) mode: runtime enable/disable round-trips OS state ---- |
| 234 | + # Diagnostic first: what the runner already holds in WinNAT (a |
| 235 | + # Docker/HNS instance here would explain a New-NetNat conflict). |
| 236 | + powershell -NoProfile -Command 'Get-NetNat | Format-List' |
| 237 | + ./awl.exe cli gateway server enable |
| 238 | + powershell -NoProfile -Command 'if (-not (Get-NetNat -Name awl-gateway -ErrorAction SilentlyContinue)) { exit 1 }' |
| 239 | + # WinNAT must not break p2p traffic inside the TUN |
| 240 | + ping -w 20000 -n 4 10.66.0.2 |
| 241 | + ./awl.exe cli gateway server disable |
| 242 | + powershell -NoProfile -Command 'if (Get-NetNat -Name awl-gateway -ErrorAction SilentlyContinue) { exit 1 }' |
| 243 | +
|
| 244 | + # ---- VPN gateway full-tunnel e2e: client = this runner, exit node = awl-tester ---- |
| 245 | + # Requires awl-tester to permit this CI peer as an exit node |
| 246 | + # (WeAllowUsingAsExitNode) — the Windows CI peer id differs from the |
| 247 | + # Linux one. |
| 248 | + for i in $(seq 1 15); do |
| 249 | + if ./awl.exe cli gateway list | grep -E 'awl-tester.*\[connected\]'; then break; fi |
| 250 | + sleep 2 |
| 251 | + done |
| 252 | + ./awl.exe cli gateway list | grep -E 'awl-tester.*\[connected\]' # fail if never became available |
| 253 | +
|
| 254 | + curl4() { curl -4 -s --max-time 20 --retry 3 --retry-delay 2 https://ifconfig.me/; } |
| 255 | + IP_DIRECT=$(curl4); echo "egress IP (direct): $IP_DIRECT" |
| 256 | +
|
| 257 | + # NRPT diagnostics before enable (split-DNS rules for the awl zone) |
| 258 | + powershell -NoProfile -Command 'Get-DnsClientNrptPolicy | Format-List Namespace,NameServers' |
| 259 | +
|
| 260 | + ./awl.exe cli gateway client use --name awl-tester |
| 261 | + ./awl.exe cli gateway status |
| 262 | + ROUTES_ON=$(powershell -NoProfile -Command '(Get-NetRoute -DestinationPrefix 0.0.0.0/1,128.0.0.0/1 -ErrorAction SilentlyContinue | Measure-Object).Count' | tr -d '\r') |
| 263 | + NRPT_ON=$(powershell -NoProfile -Command '(Get-DnsClientNrptPolicy | Where-Object { $_.Namespace -eq "." } | Measure-Object).Count' | tr -d '\r') |
| 264 | + powershell -NoProfile -Command 'Get-DnsClientNrptPolicy | Format-List Namespace,NameServers' |
| 265 | + # Route diagnostics incl. lifetimes/protocol: a route created without |
| 266 | + # InitializeIpForwardEntry shows zero lifetimes and is ignored by the |
| 267 | + # forwarding path while still being listed here. |
| 268 | + powershell -NoProfile -Command 'Get-NetRoute -DestinationPrefix 0.0.0.0/1,128.0.0.0/1,::/1,8000::/1 -ErrorAction SilentlyContinue | Format-List DestinationPrefix,InterfaceAlias,RouteMetric,Protocol,ValidLifetime,PreferredLifetime' |
| 269 | + # Tolerate a failed curl here so we always reach `client stop` below |
| 270 | + # (a broken tunnel must not leave the runner black-holed); the |
| 271 | + # `test -n "$IP_GW"` assertion after teardown turns it into a clean failure. |
| 272 | + IP_GW=$(curl4 || true); echo "egress IP (via gateway): $IP_GW" |
| 273 | +
|
| 274 | + ./awl.exe cli gateway client stop |
| 275 | + ROUTES_OFF=$(powershell -NoProfile -Command '(Get-NetRoute -DestinationPrefix 0.0.0.0/1,128.0.0.0/1 -ErrorAction SilentlyContinue | Measure-Object).Count' | tr -d '\r') |
| 276 | + NRPT_OFF=$(powershell -NoProfile -Command '(Get-DnsClientNrptPolicy | Where-Object { $_.Namespace -eq "." } | Measure-Object).Count' | tr -d '\r') |
| 277 | + IP_REVERTED=$(curl4); echo "egress IP (reverted): $IP_REVERTED" |
| 278 | +
|
| 279 | + # Assertions run with the gateway already disabled, so a failure here |
| 280 | + # can't leave the runner's egress black-holed. Values are echoed |
| 281 | + # first so a failed assertion is attributable from the log alone. |
| 282 | + echo "ROUTES_ON=$ROUTES_ON NRPT_ON=$NRPT_ON ROUTES_OFF=$ROUTES_OFF NRPT_OFF=$NRPT_OFF" |
| 283 | + test "$ROUTES_ON" -eq 2 # /1 pair installed while gateway is on |
| 284 | + test "$NRPT_ON" -ge 1 # full-capture NRPT rule while gateway is on |
| 285 | + test -n "$IP_GW" # traffic really flowed through the exit node |
| 286 | + test "$IP_GW" != "$IP_DIRECT" # egress changed => full-tunnel works |
| 287 | + test "$IP_REVERTED" = "$IP_DIRECT" # runtime disable reverted egress |
| 288 | + test "$ROUTES_OFF" -eq 0 # /1 routes removed on disable |
| 289 | + test "$NRPT_OFF" -eq 0 # full-capture NRPT rule removed on disable |
| 290 | +
|
| 291 | + # Re-enable the gateway and hard-kill awl. A graceful-shutdown test is |
| 292 | + # not possible here: Git-Bash `kill -SIGINT` cannot deliver a console |
| 293 | + # ctrl event to a native Windows process on a console-less CI runner |
| 294 | + # (verified: awl kept logging after the kill). Graceful teardown is |
| 295 | + # already covered by the `client stop` assertions above and by the |
| 296 | + # Linux branch; what a hard kill lets us assert is the documented |
| 297 | + # CRASH semantics instead. |
| 298 | + ./awl.exe cli gateway client use --name awl-tester |
217 | 299 | sleep 1 |
218 | | - kill -SIGINT $awl_pid |
219 | | - sleep 1 |
| 300 | + taskkill //F //IM awl.exe |
| 301 | +
|
| 302 | + # Crash fail-open: the /1 routes (v4 + v6 fence) are bound to the |
| 303 | + # Wintun LUID and must disappear together with the adapter. Driver |
| 304 | + # cleanup after a hard kill is asynchronous — poll. |
| 305 | + DANGLING=-1 |
| 306 | + for i in $(seq 1 30); do |
| 307 | + DANGLING=$(powershell -NoProfile -Command '(Get-NetRoute -DestinationPrefix 0.0.0.0/1,128.0.0.0/1,::/1,8000::/1 -ErrorAction SilentlyContinue | Measure-Object).Count' | tr -d '\r') |
| 308 | + if [ "$DANGLING" -eq 0 ]; then break; fi |
| 309 | + sleep 1 |
| 310 | + done |
| 311 | + echo "DANGLING=$DANGLING" |
| 312 | + test "$DANGLING" -eq 0 |
| 313 | +
|
| 314 | + # The NRPT catch-all rule lives in the registry and must SURVIVE a |
| 315 | + # hard kill — that's the documented crash behaviour (the next awl |
| 316 | + # start reconfigures DNS and cleans it up). |
| 317 | + NRPT_CRASH=$(powershell -NoProfile -Command '(Get-DnsClientNrptPolicy | Where-Object { $_.Namespace -eq "." } | Measure-Object).Count' | tr -d '\r') |
| 318 | + echo "NRPT_CRASH=$NRPT_CRASH" |
| 319 | + test "$NRPT_CRASH" -ge 1 |
| 320 | +
|
| 321 | + # Runner hygiene: with awl dead, the leftover catch-all rule points |
| 322 | + # all DNS at a resolver that no longer exists — remove it so the |
| 323 | + # runner's post-job steps keep working DNS. |
| 324 | + powershell -NoProfile -Command 'Get-DnsClientNrptRule | Where-Object { $_.Namespace -eq "." } | Remove-DnsClientNrptRule -Force' |
0 commit comments