How to bypass always-on WireGuard VPN and custom system resolver to access Wi-Fi captive portal on Gentoo

On my system, Wi-Fi and WireGuard are configured and started by netifrc. wlp3s0 is the wireless interface and wg0 is the wireguard interface. dhcpcd is used as DHCP client. /etc/conf.d/net:

modules_wlp3s0="wpa_supplicant"
config_wlp3s0="dhcp"

config_wg0="192.168.10.2/32"
wireguard_wg0="/etc/wireguard/wg0.conf"

postup() {
        if [ "$IFACE" = "wg0" ]; then
                wg set wg0 fwmark 1234
                ip route add default dev wg0 table 2468
                ip rule add not fwmark 1234 table 2468
                ip rule add table main suppress_prefixlength 0
        fi
}

predown() {
        if [ "$IFACE" = "wg0" ]; then
                ip route del default dev wg0 table 2468
                ip rule del not fwmark 1234 table 2468
                ip rule del table main suppress_prefixlength 0
        fi
}

System resolver is dnsmasq, listening on 127.0.0.1:53. /etc/resolv.conf:

nameserver 127.0.0.1

/etc/resolv.conf is marked immutable by chattr +i.

Now when I'm at some place with an open Wi-Fi that requires authentication in captive portal, I can't access it because of custom DNS resolver and routing rules that pass all traffic through wg0.

Solution

The solution is to use separate mount and network interface with a pair of veth devices. Make sure you enabled at least CONFIG_NAMESPACES, CONFIG_NET_NS, CONFIG_VETH in kernel.

Create netns and veth pair:

VETH_ADDR=10.200.1.1
VPEER_ADDR=10.200.1.2

WIFI_IFACE=wlp3s0
VETH_IFACE=vethcap1
VPEER_IFACE=vpeercap1
CAPTIVE_NS=captive

ip netns add $CAPTIVE_NS
ip link add $VETH_IFACE type veth peer name $VPEER_IFACE
ip link set $VPEER_IFACE netns $CAPTIVE_NS
ip addr add $VETH_ADDR/24 dev $VETH_IFACE
ip link set $VETH_IFACE up

ip netns exec $CAPTIVE_NS ip addr add $VPEER_ADDR/24 dev $VPEER_IFACE
ip netns exec $CAPTIVE_NS ip link set $VPEER_IFACE up
ip netns exec $CAPTIVE_NS ip link set lo up
ip netns exec $CAPTIVE_NS ip route add default via $VETH_ADDR

Firewall rules to forward traffic coming from netns directly via wlp3s0:

iptables -t nat -A POSTROUTING -s $VPEER_ADDR/24 -o $WIFI_IFACE -j MASQUERADE
iptables -A FORWARD -i $WIFI_IFACE -o $VETH_IFACE -j ACCEPT
iptables -A FORWARD -o $WIFI_IFACE -i $VETH_IFACE -j ACCEPT

# very important: here we set the same fwmark as the one that wg0 uses
iptables -t mangle -A PREROUTING -s $VPEER_ADDR/24 -j MARK --set-mark 1234

Don't forget to enable packet forwarding in /etc/sysctl.conf:

net.ipv4.ip_forward = 1

And apply it if needed:

# sysctl -p

Add to /etc/dhcpcd.conf:

denyinterfaces vethcap1,vpeercap1

And the last but not least: a program that will create a mount namespace, bind-mount /etc/resolv.conf, then enter the $CAPTIVE_NS network namespace and launch browser (or anything else) in there.

https://github.com/gch1p/captive-netns-helper

If you have any comments, contact me by email.