feat(vpn): prefer IPv4 in nodeAddrs selection for outbound VPN dial#93
Conversation
Both Wireguard.parseConfig and V2Ray.parseConfig pick `nodeAddrs[0]` for the outbound endpoint. Sentinel chain does not guarantee ordering of `result.addrs`, so on dual-stack nodes that return IPv6 first, consumers on v4-only networks (the common home / mobile environment) silently fail to dial. Add `preferIPv4` helper in utils and use it from both VPN classes. Falls back to addrs[0] when no IPv4 is present, so v6-only consumers are unaffected.
Per maintainer feedback: validate IPv4 with Node's net.isIP (returns 4 for
IPv4), matching how src/vpn/v2ray already detects address families, rather
than a hand-rolled /\d{1,3}.../ regex. isIP also rejects out-of-range octets
(e.g. 999.1.1.1) that the loose regex would accept.
|
Switched to One thing I want to confirm before considering this fully correct — a question about the input shape:
If there's any path where an entry in |
Summary
Both `Wireguard.parseConfig` and `V2Ray.parseConfig` pick `nodeAddrs[0]` for the outbound endpoint:
Sentinel chain does not guarantee ordering of `result.addrs` returned by the node handshake. On dual-stack nodes that return IPv6 first, consumers on v4-only networks (typical home / mobile) silently fail to dial — `UDP send: network unreachable` for WireGuard, `connection refused` / no route for V2Ray.
Fix
Add a small `preferIPv4` helper in `src/utils.ts` and use it from both VPN classes. Falls back to `addrs[0]` when no IPv4 entry is present, so v6-only consumers see no behavior change.
Diff (helper)
```ts
export function preferIPv4(addrs: string[]): string {
return addrs.find(a => /^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$/.test(a)) ?? addrs[0];
}
```
Diff (call sites)
```diff
-const host = nodeAddrs[0];
+const host = preferIPv4(nodeAddrs);
```
```diff
-const address = nodeAddrs[0];
+const address = preferIPv4(nodeAddrs);
```
Note
If you'd rather inline the helper at the call sites instead of exporting it from utils, happy to refactor. Centralized helper keeps the rationale comment in one place.
Test plan