linuxero
Goto Top

Wireguard with systemd and nftables

Hi;

I am trying to configure Wireguard VPN using systemd.networkd on a Server with NFTables as a firewall.

These are the files of my configuration:

/etc/systemd/network/def_if.network

[Match]
MACAddress=52:54:00:12:12:12

[Network]
Description=Underlying main ethernet interface
Bridge=br0
LinkLocalAddressing=no


/etc/systemd/network/br0.netdev

[NetDev]
Description=Main bridge interface
Name=br0
Kind=bridge

/etc/systemd/network/br0.network

[Match]
Name=br0

[Link]
MACAddress=52:54:00:12:12:12

[Network]
Description=Main bridge interface

Address=2a01:4f8:10a:68b::15/64
Address=88.99.30.87/27

Gateway=fe80::1
Gateway=148.251.52.1

IPv6AcceptRA=no
IPForward=yes
IPMasquerade=yes


/etc/systemd/network/wg0.netdev

[NetDev]
Name=wg0
Kind=wireguard
Description=Wireguard VPN

[WireGuard]
PrivateKey=sKEIBk773uHzTMEVayvzC0n7UslviO8yj/j7dU7+ANWc=
ListenPort=51820

[WireGuardPeer]
PublicKey=ImVcJRxLrjTEJ62rLwLEGxgT5E4iOA5zth9NDeappA0=
AllowedIPs=0.0.0.0/0,::/0
EndPoint=192.168.129.129/32 [fdf6:fd:f6:fdf6::aa13]/128
PresharedKey=OdGpK/xuTJtiH6Nc12xEQ99sooFJz3x8tveU84czrQ8=


/etc/systemd/network/wg0.network

[Match]
Name=wg0

[Network]
Address=192.168.129.220/24
Address=fdf6:fd:f6:fdf6::3003/72
DNS=192.168.129.129
IPForward=yes
IPMasquerade=yes


/etc/nftables/inet-filter.nft

table inet filter {
        chain input             {
            type filter hook input priority 0; policy drop
            ct state invalid drop
            ct state established accept
            ct state related accept
            iifname lo accept;
            iifname wg0  accept
            ip protocol icmp icmp type { echo-request, destination-unreachable } counter packets 0 bytes 0 accept;
            ip6 nexthdr ipv6-icmp accept
            icmpv6 type { nd-neighbor-advert, nd-neighbor-solicit, nd-router-advert, destination-unreachable} ip6 hoplimit 255 counter accept
            udp dport { 51820 } accept;
       }

       chain forward           {
            type filter hook forward priority 0; policy drop
            iifname br0 oifname wg0 accept comment "wireguard wg0"  
            iifname wg0 oifname br0 accept comment "Wireguard wg0"  
            log
        }

       chain output            {
            type filter hook output priority 0; accept
            oifname wg0 accept
            oifname lo accept
            log
        }

I am still not able to connect a client successfully nor get internet access.

Can you point me in the right direction?

Thank you in advance.

Content-ID: 92586419613

Url: https://administrator.de/contentid/92586419613

Ausgedruckt am: 23.11.2024 um 16:11 Uhr

Boomercringe
Boomercringe 04.05.2024 um 16:06:48 Uhr
Goto Top
Hello,

sorry for not being helpful, but you shouldn't post your wireguard keys on the internet...
Linuxero
Linuxero 04.05.2024 um 16:14:26 Uhr
Goto Top
Thank you for the hint. As a matter of fact none of the information in the post correspond to my real configuration. I have just tried to make it as correct as possible, similar to my real configuration face-smile

Thanks again face-smile
aqui
aqui 04.05.2024 aktualisiert um 17:47:56 Uhr
Goto Top
The above configuration is very weired and totally different from a standard Wireguard configuration. Why didnt you use the classical approach with a text config file in /etc/wireguard which is fully integrated into systemd as well?? It makes a WG setup much easier and quicker.
All steps are described HERE. Unfortunately in German but maybe a translation tool can help here.
You should also start in a strategic way which means setup your VPN connection first without nftables running to make sure you do not step into firewall issues.
In case the VPN connection runs fine you should activate the firewall. So you can make sure any possible malfunction could only be a firewall misconfiguration and NOT the VPN itself.

Your correct nftables setup is based on how your WG server looks like (interface etc.). You can find some good examples here:
https://www.procustodibus.com/blog/2021/11/wireguard-nftables/
But keep in mind: First VPN checks always without firewall.
12764050420
12764050420 04.05.2024 aktualisiert um 18:02:21 Uhr
Goto Top
You have forgotten to add a real network adapter to the bridge! A bridge alone without hardware has no contact to the outside world 🤪.
So add another *.network file for the real Ethernet adapter which makes it a member of the bridge.
[Match]
Name=eth0

[Network]
Bridge=br0

Regards
Linuxero
Linuxero 04.05.2024 aktualisiert um 18:07:29 Uhr
Goto Top
@aqui

Thank you for your reply. However, the configuration that you mention is using systemd service files to start wireguard, which I do not wish to use, and I would not consider it a full integration either. I have actually used it before and it works fine face-smile

my goal is to use SystemD for wireguard configuration. I cannot see much difference to have one way easier than the other. Could you please explain?

This leads me to my second question about, what do you mean by standard configuration? I can see that systemd has quite some nifty capabilities, which I would like to use, I actually do not see much difference in the configuration called standard other than having wireguard directly configured through systemd.network. I am not claiming this is better or worse..I just would like to have it done using pure systemd face-smile

About the firewall, I totally agree with you. I have just shown my current configuration in order to make it easier for others to try and help me.

Thank you for your insight face-smile
Linuxero
Linuxero 04.05.2024 um 18:07:05 Uhr
Goto Top
@12764050420

Sorry I have missed to add that in my post, I actually already have it face-smile

/etc/systemd/network/def_if.network

[Match]
MACAddress=52:54:00:12:12:12

[Network]
Description=Underlying main ethernet interface
Bridge=br0
LinkLocalAddressing=no

Thank you for the reminder face-smile
12764050420
12764050420 04.05.2024 aktualisiert um 19:28:04 Uhr
Goto Top
There is another catastrophic failure here, private IPs as Endpoint addresses does not make any sense when connecting from the outside world ... also a missing comma delimiter, and the brackets for the IPv6 address are obsolete:
AllowedIPs=0.0.0.0/0,::/0
EndPoint = 192.168.129.129/32 [fdf6:fd:f6:fdf6::aa13]/128

The IPs have to be in the AllowedIPs not in the Endpoint on the Server-Side!

The peer section should look like this
[WireGuardPeer]
PublicKey=xxxxxxxxx
AllowedIPs=192.168.129.129/32, fdf6:fd:f6:fdf6::aa13/128
PresharedKey=xxxxxxxxx
Linuxero
Linuxero 04.05.2024 aktualisiert um 23:12:23 Uhr
Goto Top
@12764050420

Thank you. You're right, how did I miss that. face-smile

On the server I can see this in dmesg output:

[718058.070069] IN=br0 OUT= PHYSIN=eth0 MAC=xx:xx:xx:xx:xx:xx:yy:yy:yy:yy:yy:yy:00:00 SRC=xxx.xxx.xxx.xxx DST=xxx.xxx.xxx.xxx LEN=63 TOS=0x00 PREC=0x00 TTL=57 ID=28320 DF PROTO=UDP SPT=58024 DPT=51820 LEN=43

...

[718258.873489] IN=br0 OUT= PHYSIN=eth0 MAC=xx:xx:xx:xx:xx:xx:yy:yy:yy:yy:yy:yy:00:00 SRC=xxx.xxx.xxx.xxx DST=xxx.xxx.xxx.xxx LEN=54 TOS=0x00 PREC=0x00 TTL=57 ID=33109 DF PROTO=UDP SPT=60888 DPT=51820 LEN=34
12764050420
12764050420 05.05.2024 aktualisiert um 09:35:05 Uhr
Goto Top
Zitat von @Linuxero:
On the server I can see this in dmesg output:

[718058.070069] IN=br0 OUT= PHYSIN=eth0 MAC=xx:xx:xx:xx:xx:xx:yy:yy:yy:yy:yy:yy:00:00 SRC=xxx.xxx.xxx.xxx DST=xxx.xxx.xxx.xxx LEN=63 TOS=0x00 PREC=0x00 TTL=57 ID=28320 DF PROTO=UDP SPT=58024 DPT=51820 LEN=43

...

[718258.873489] IN=br0 OUT= PHYSIN=eth0 MAC=xx:xx:xx:xx:xx:xx:yy:yy:yy:yy:yy:yy:00:00 SRC=xxx.xxx.xxx.xxx DST=xxx.xxx.xxx.xxx LEN=54 TOS=0x00 PREC=0x00 TTL=57 ID=33109 DF PROTO=UDP SPT=60888 DPT=51820 LEN=34

Normal, you forgott to add established related traffic to the forward chain as well as you block local forward traffic, so you only have a statefull firewall on the input chain but not the forward chain 😉.
As @aqui said, first bring up your tunnel successfully and after that your firewall.

This will do the job for your scenario (tested successfully in a VM with above systemd config)
#!/usr/bin/nft -f
flush ruleset

table inet filter {
    	chain INPUT {
	    	type filter hook input priority 0; policy drop;
	    	ct state vmap { established : accept, related : accept, invalid : drop }
	    	ip6 saddr ::1 ip6 daddr ::1 counter accept
	    	iifname vmap { "lo" : accept, "br0" : jump INPUT_WAN, "wg0" : accept }  
	    	log
    	}

    	chain FORWARD {
	    	type filter hook forward priority 0; policy drop;
	    	ct state vmap { established : accept, related : accept, invalid : drop }
	    	ct status dnat counter accept
	    	iifname vmap { "lo" : accept, "br0" : drop, "wg0" : accept }  
	    	log
    	}

    	chain OUTPUT {
	    	type filter hook output priority 0; policy accept;
    	}

	chain INPUT_WAN {
	    	# icmpv6
    		icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } counter accept
	    	icmpv6 type { nd-neighbor-solicit,nd-neighbor-advert } ip6 hoplimit 255 counter accept

	    	# icmpv4		
	     	icmp type { destination-unreachable, echo-request } limit rate 5/second accept

	    	# wireguard
    		udp dport 51820 counter accept
    	}
 }

table inet nat {
    	chain PREROUTING {
	    	type nat hook prerouting priority -100; policy accept;		
    	}
    	chain POSTROUTING {
	    	type nat hook postrouting priority 100; policy accept;
		oifname br0 counter masquerade
    	}
}
Linuxero
Linuxero 11.05.2024 um 19:22:20 Uhr
Goto Top
Hi;

I took sometime to test everything and I started a fresh clean setup of everything.

I can reach the peer on ipv4 and ipv6, however I cannot use the peer as a gateway or dns. I do not even get the dns configuration from the server.

I have seen that this thread is marked as solved, but it is sadly not face-sad

I still cannot see what the issue is. Even with a clean fresh installation.
aqui
aqui 12.05.2024 aktualisiert um 12:03:42 Uhr
Goto Top
I have seen that this thread is marked as solved, but it is sadly not
Due to good reasons this can ONLY be done by YOU as the threadowner!
If you do not want it to be marked as "solved" YOU can always change it by yourself using the Edit/Bearbeiten button!

I cannot use the peer as a gateway or dns.
What do you exactly mean with "gateway"? In general WG has only 2 options as all VPNs have it:
  • Split Tunneling = only relevant traffic is routed into the tunnel which is done by WG's own crypto routing and declared by the IP networks under "AllowedIPs"
  • Gateway Redirect = All traffic is by default routed into the tunnel (default gateway) in case the peer is active and established.
So it depends on YOU and your configuration how this is handled by WG.
Gateway redirect implies also a DNS traffic redirect, because it includes of course TCP and UDP 53 traffic as well as all other traffic. Here you have to take care by yourself that your configured DNS servers are fully reachable over the tunnel.
In case of split tunneling you declare an alternative DNS server who will be active when the peer is established with the extra "dns" parameter.
"ip r" as well as dig and host are your best friends (dnsutils, bindutils) to check this. face-wink
Linuxero
Linuxero 12.05.2024 um 18:07:54 Uhr
Goto Top
Thank you @aqui;

I am aware of that. I'll try to depict my problem in details.

I can connect to the wireguard peer/server. I can ping the peer address. This is ok so long as my default route is not the wireguard peer/server. At the moment, where my peer is designated as the default route, I can neither ping the peer itself nor go into the internet.

Let's look at it like this:

CR is my wireguard server
RW is a client

When RW connects to CR and the default route on RW is not the CR I can ping CR and get to the network there. However when RW connects to CR and the default route on RW is CR, then RW has no internet connectivity and it cannot reach CR.

I am trying to do some other tests now :|
aqui
aqui 12.05.2024 um 21:18:57 Uhr
Goto Top
If the peer is active what does the route table say on clientsite? ip r
Linuxero
Linuxero 14.05.2024 um 15:59:22 Uhr
Goto Top
Well, when I configure it to use the peer as a gateway, this is how my routing table looks like.

default via 192.168.11.1 dev Tawa proto static metric 50
default via 10.11.12.153 dev enp7s0 proto dhcp src 10.11.12.154 metric 100
10.11.12.128/26 dev enp7s0 proto kernel scope link src 10.11.12.154 metric 100
192.168.11.0/24 dev Tawa proto kernel scope link src 192.168.11.2 metric 50

I think this can only be a firewall issue now, which I do not seem to be able to get around using nftables :|
aqui
aqui 18.05.2024 um 13:12:22 Uhr
Goto Top
I think this can only be a firewall issue now
Definitely!
As you can see in the above routing table: 192.168.11.1 is the default gateway in case the client is active, cause it has the highest routing metric and is most likeley your WG server IP address or HAS to be the WG server address!
Linuxero
Linuxero 19.05.2024 um 00:12:36 Uhr
Goto Top
You're right, it is actually the IP address of the WG server. So now I am investigating the firewall on both the server and the client.
13034433319
13034433319 19.05.2024 aktualisiert um 08:40:16 Uhr
Goto Top
Zitat von @aqui:

I think this can only be a firewall issue now
Definitely!
As you can see in the above routing table: 192.168.11.1 is the default gateway in case the client is active, cause it has the highest routing metric and is most likeley your WG server IP address or HAS to be the WG server address!

With ip r you cannot see the wireguard rules!!

When wireguard default redirect is enabled the rule is not listet in the main routing table but in another one with higher precedence to which the traffic is redirected by a firewall mark! The main table does not list this, but using ip route list table all does. And ip rule show lists the rules which redirect to the tables.

Another error lies here:


[Network]
Address=192.168.129.220/24
Address=fdf6:fd:f6:fdf6::3003/72
DNS=192.168.129.129
IPForward=yes
IPMasquerade=yes

The DNS-Line is wrong. You have set the DNS Server for the Wireguard interface to the client address! Just remove the line and only set this on the client WG config

A DNS address should only be set on the WAN Interface and on the client wireguard config not on the WG interface of the server itself. WG has no DHCP so it does not distribute this setting to the clients !

I tested the config from the user above who posted the firewall sample and it is definitely working if you fix the DNS error .

I will post my complete working config later on.
aqui
aqui 19.05.2024 um 11:29:51 Uhr
Goto Top
With ip r you cannot see the wireguard rules!!
Are you sure? If you issue a wg-quick down wg0 all WG related routing entries are gone in the main table even with the short ip r. So i guess ip r shows some basic WG routing rules as well. The longer version just shows some more detailed local connected interface, broad- and multicast rules on top but the major rules are shown with ip r as well.
13034433319
13034433319 19.05.2024 aktualisiert um 11:49:49 Uhr
Goto Top
Zitat von @aqui:
Are you sure?
Without a doubt!
If you issue a wg-quick down wg0 all WG related routing entries are gone in the main table even with the short ip r. So i guess ip r shows some basic WG routing rules as well.
But only the basic routes for the tunnel-subnet itself, if you use a gateway redirect these are set in a separate routing table and the traffic is marked with firewall marks to flow through the created table!
aqui
aqui 19.05.2024 um 11:55:39 Uhr
Goto Top
Thanks for clarification! Due to performance reasons I use only split tunneling so far and hence never saw the difference with gateway redirect. Again something learned. 😉
13034433319
13034433319 19.05.2024 aktualisiert um 12:30:07 Uhr
Goto Top
As promised here is my working setup for the TO:

back-to-topSystemd-Networkd Setup


br0 = Bridge to WAN
eth0 = Member of bridge br0 WAN facing
wg0 = Wireguard interface

IPv4 and IPv6 addresses and gateways on WAN bridge are set manually without using DHCP or RA as in your setup above, so router advertisements are not needed and disabled on the bridge and in the firewall.

/etc/systemd/network/10-br0.netdev

[NetDev]
Name=br0
Kind=bridge

/etc/systemd/network/20-br0.network

[Match]
Name=br0

[Network]
Address=1.2.3.4/24
Gateway=1.2.3.1
Address=2a00:1111:2222::1/64
Gateway=fe80::1
DNS=1.1.1.1
IPv6AcceptRA=no
IPForward=yes

/etc/systemd/network/30-eth0.network

[Match]
Name=eth0

[Network]
Bridge=br0

/etc/systemd/network/40-wg0.netdev

[NetDev]
Name=wg0
Kind=wireguard

[WireGuard]
PrivateKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ListenPort=51820

[WireGuardPeer]
PublicKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AllowedIPs=10.80.0.2/32,fd99:bade:affe::2/128

/etc/systemd/network/50-wg0.network

[Match]
Name=wg0

[Network]
Address=10.80.0.1/24
Address=fd99:bade:affe::1/64
IPForward=yes

back-to-topNFTables Firewall Config


#!/usr/bin/nft -f
flush ruleset

table inet filter {
    	chain INPUT {
	    	type filter hook input priority 0; policy drop;
	    	ct state vmap { established : accept, related : accept, invalid : drop }
	    	ip6 saddr ::1 ip6 daddr ::1 counter accept
	    	iifname vmap { "lo" : accept, "br0" : jump INPUT_WAN, "wg0" : accept }    
	    	log
    	}

    	chain FORWARD {
	    	type filter hook forward priority 0; policy drop;
	    	ct state vmap { established : accept, related : accept, invalid : drop }
	    	ct status dnat counter accept
	    	iifname vmap { "lo" : accept, "br0" : drop, "wg0" : accept }    
	    	log
    	}

    	chain OUTPUT {
	    	type filter hook output priority 0; policy accept;
    	}

	chain INPUT_WAN {
	    	# icmpv6
    		icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } counter accept
	    	icmpv6 type { nd-neighbor-solicit,nd-neighbor-advert} ip6 hoplimit 255 counter accept
	    	# icmpv4		
	     	icmp type { destination-unreachable, echo-request } limit rate 5/second accept
	    	# wireguard
    		udp dport 51820 counter accept
    	}
 }

table inet nat {
    	chain PREROUTING {
	    	type nat hook prerouting priority -100; policy accept;		
    	}
    	chain POSTROUTING {
	    	type nat hook postrouting priority 100; policy accept;
		oifname br0 counter masquerade
    	}
}


back-to-topWireguard Client config


[Interface]
PrivateKey=xxxxxxxxxxxxxxxxxxxxxxxxxxx
Address=10.80.0.2/24, fd99:bade:affe::2/64
DNS=1.1.1.1

[Peer]
PublicKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxx
AllowedIPs=0.0.0.0/0, ::/0
Endpoint=1.2.3.4:51820
PersistentKeepalive=25

back-to-topTest from client


screenshot


Just works 100% as promised face-wink! 👍

Good luck