1

As mentioned in the title, I am using a WireGuard Hub and Spoke configuration to connect my network at home to RoadWarrior peers. Unfortunately I have no public IPv4 and v6 address at home and on the road, so I need the hub. So far the routing of private IP addresses works, but I want to use the VPS (HUB and Host B) also as an exit point for normal traffic, because I am sometimes on the road with my mobile in insecure networks. So on my Host A (smartphone) I have set the Allowed-IPs to 0.0.0.0/0, but unfortunately this does not go through. I suspect that routing rules are missing on the VPS (HUB and Host B). Here you have how I thought about the whole thing.

RoadWarrior (Host A)   <--->   VPS HUB (Host B)   <---->  Home-net (Host C)
                                    |
                                    |
                                 Internet 

So here is my WireGuard configuration on the VPS:

# /etc/wireguard/wg0.conf

[Interface]
Address = 10.210.1.1/16
ListenPort = 51828
PrivateKey = 

PreUp = sysctl -w net.ipv4.ip_forward=1
PreUp = sysctl -w net.ipv6.conf.all.forwarding=1
PreUp = nft -f /etc/nftables.wg

PostDown = nft -f /etc/nftables.conf
PostDown = sysctl -w net.ipv4.ip_forward=0
PostDown = sysctl -w net.ipv6.conf.all.forwarding=0

# Home-net (Host C)

[Peer]
PublicKey = 
PresharedKey = 
AllowedIPs = 10.211.1.1/24, 10.1.0.0/16, 10.2.0.0/16


#Road-Warrior Peers (Host A)

[Peer]
PublicKey = 
PresharedKey = 
AllowedIPs = 10.212.1.1/32

My nftables config when WireGuard is running:

#!/usr/sbin/nft -f

flush ruleset

define pub_iface = eth0
define wg_iface = wg0
define wg_port = 51828

table inet basic-filter {
        chain input {
                type filter hook input priority 0; policy drop;
                ct state { established, related } accept
                iif lo accept
                ip protocol icmp accept
                ip6 nexthdr ipv6-icmp accept
                ct state new tcp dport 51829 log prefix "Neue SSH-Verbindung" accept
                iif $pub_iface udp dport $wg_port accept
                iifname $wg_iface accept
                reject
        }
        chain forward {
                type filter hook forward priority 0; policy drop;
                iifname $wg_iface oifname $wg_iface accept
                reject with icmpx type host-unreachable
        }
        chain output {
                type filter hook output priority 0; policy accept;
        }
}

And my WireGuard configuration when WireGuard is down:

#!/usr/sbin/nft -f

flush ruleset

table inet basic-filter {
        chain input {
                type filter hook input priority 0; policy drop;
                ct state { established, related } accept
                iif lo accept
                ip protocol icmp accept
                ip6 nexthdr ipv6-icmp accept
                ct state new tcp dport 51829 log prefix "Neue SSH-Verbindung " accept
                reject
        }
}

Maybe you can also check out my nftables configuration, because it' relatively new to me :) Thank you :)

Jonathan
  • 43
  • 6

1 Answers1

1

The routes (ip route ...) weren't provided but I'll assume Host's B routes are already correctly configured by the system and by wg-quick.

Since the WG IP addresses are not public they have to be NATed when reaching Internet with a chain hooking in postrouting. Add this block, still within the table inet basic-filter block:

        chain natpostrouting {
                type nat hook postrouting priority 100; policy accept;
                iifname $wg_iface oifname $pub_iface masquerade
        }

If the VPS' kernel is too old (older than kernel 5.5) the input interface name expression has to be replaced with a source address expression instead, which depends on the precise setup. For example, instead of above:

        chain natpostrouting {
                type nat hook postrouting priority 100; policy accept;
                ip saddr 10.0.0.0/8 oifname $pub_iface masquerade
        }

Now, an nftables forward rule to allow (above) traffic with Internet is missing. Insert the following two rules in the forward chain before the reject statement rule. The second is to allow forwarding from WG to Internet, the first is to allow traffic back in a stateful generic way, just like what's done in the input chain.

                ct state { established,related } accept
                iifname $wg_iface oifname $pub_iface accept

There are a few other minor details.

  • there shouldn't be the need to install and remove a different nftables ruleset when the tunnel is up or down

    but it's at your own convenience.

  • rejecting packets that might have been classified as invalid can cause TCP connections to abort unexpectedly when receiving delayed packets

    So drop them before rejecting them. Insert this rule anywhere before the final reject statement rule, once in the input chain and once in the forward chain:

                    ct state invalid drop
    

    A possible future patch might make this not needed anymore.

  • IPv6's nexthdr is not directly equivalent to IPv4's protocol

    because there can be extension headers between the fixed header and the upper-layer protocol header.

    A lot of documentation on Internet has this wrong.

    So this should almost never be used (because it might not match in cases where the intent was to match):

    ip6 nexthdr ipv6-icmp accept
    

    and be replaced with meta information already known by the system because it correctly parsed the whole packet:

    meta nfproto ipv6 meta l4proto ipv6-icmp accept
    

    or simply:

    meta l4proto ipv6-icmp accept
    
A.B
  • 11,090
  • 2
  • 24
  • 45
  • Thank you so much for your help. I almost figured it out. I have never received such a quick and comprehensive answer in a forum. Thanks also for the help with nft, you are a hero! – Jonathan Jul 29 '22 at 21:14