WireGuard connecting hosts WAN<->LAN

Legend:
A = internet host (WireGuard IP 10.1.1.3)
B = OpenWRT router (WireGuard IP 10.1.1.1)
C = LAN host (WireGuard IP 10.1.1.2)
On B I created a wg0 WireGuard interface with an IP of the form of 10.1.1.1 and I wanted the router to act as WireGuard gateway between A and C.
A can ping B, C can ping B, but A cannot ping C.
On the router the interface wg0 is not assigned to any firewall zone.
What can I do to let B to route WireGuard packets between A and C?

If C is a host in the LAN of B OpenWrt router, then you don't need a tunnel. Just allow A to access the LAN segment of B and route the packets.

The easy way is to assign it in lan zone.
If you add a new zone, say wg, then you need to fix the forwardings with lan and wan if necessary.

No. A knows C public key and viceversa. This ensures additional protection against compromission of B

I try

I don't follow, is C in the LAN segment of B or not?

1 Like

yes C is in the same LAN segment of B

That is a bit unusual but it should work.
Post the wireguard configs from all the devices for a start.

1 Like

Host A - WireGuard configuration file (Fedora)

[Interface]
Address = 10.1.1.3/24
PrivateKey = censored
ListenPort = 51820

# Host B
[Peer]
PublicKey = censored
Endpoint = tom.foo.bar:51820
AllowedIPs = 10.1.1.1/32

# Host C
[Peer]
PublicKey = censored
AllowedIPs = 10.1.1.2/32

Host B - OpenWRT /etc/config/network configuration file

root@OpenWrt:/etc# cat config/network 

config interface 'loopback'
        option ifname 'lo'
        option proto 'static'
        option ipaddr '127.0.0.1'
        option netmask '255.0.0.0'

config globals 'globals'
        option ula_prefix 'censored::/48'

config interface 'lan'
        option type 'bridge'
        option ifname 'eth0.1'
        option proto 'static'
        option ipaddr '192.168.1.1'
        option netmask '255.255.255.0'
        option ip6assign '60'

config interface 'wan'
        option ifname 'eth0.2'
        option proto 'pppoe'
        option username 'censored'
        option password 'censored'
        option ipv6 'auto'

config device 'wan_dev'
        option name 'eth0.2'
        option macaddr 'censored'

config interface 'wan6'
        option ifname 'eth0.2'
        option proto 'dhcpv6'

config switch
        option name 'switch0'
        option reset '1'
        option enable_vlan '1'

config switch_vlan
        option device 'switch0'
        option vlan '1'
        option ports '2 3 4 5 0t'

config switch_vlan
        option device 'switch0'
        option vlan '2'
        option ports '1 0t'

config interface 'wg0'
        option proto 'wireguard'
        option private_key 'censored'
        option listen_port '51820'
        option route_allowed_ips '1'
        list addresses '10.1.1.1/24'
# Host A
config wireguard_wg0 'wg_client_host_A'
        option public_key 'censored'
        list allowed_ips '10.1.1.3/32'
# Host C
config wireguard_wg0 'wg_client_host_C'
        option public_key 'censored'
        list allowed_ips '10.1.1.2/32'

Host C - WireGuard configuration file (CentOS)

[Interface]
Address = 10.1.1.2/24
ListenPort = 51820
PrivateKey = censored

# Host B
[Peer]
PublicKey = censored
Endpoint = 192.168.1.1:51820
AllowedIPs = 10.1.1.1/32

# Host A
[Peer]
PublicKey = censored
AllowedIPs = 10.1.1.3/32

In host A you are missing the endpoint for host C.
Likewise for C to A, but since A is in the internet it won't work most of the time, so the previous addition should be enough.
In case this doesn't work, change the IP addresses and use /30 for 2 hosts for each tunnel.

What is the point of B in this scenario? Why are you not just connecting A and C directly?

As it is, it doesn't currently work because the 'AllowedIPs' on A and C are incomplete. If you want all traffic to go through B then in the peer entry for B you need to add the IP address for A or C, respectively, to the 'AllowedIPs' entry.

So on A it should read 'AllowedIPs = 10.1.1.1/32, 10.1.1.2/32' and on C 'AllowedIPs = 10.1.1.1/32, 10.1.1.3/32'

No, one endpoint is enough, thanks to the routing that should work. I already done that on a similar configuration where the B was a CentOS machine instead of an OpenWRT machine.

From man wg

Endpoint — an endpoint IP or hostname, followed by a colon, and then a port number. This endpoint will be updated automatically to the most recent source IP address and port of correctly authenticated packets from the peer. Optional.

Moreover, from this message of WireGuard creator:

[...]The endpoint field is only ever a "hint" anyway, due to the roaming.

B is an OpenWRT router that has to connect A (WAN) to C (LAN). I don't connect A and C directly because of network design decisions.

If you do this on host A (and the equivalent on host C)

[Interface]
Address = 10.1.1.3/24
PrivateKey = censored
ListenPort = 51820

# Host B
[Peer]
PublicKey = censored
Endpoint = tom.foo.bar:51820
AllowedIPs = 10.1.1.1/32, 10.1.1.2/32

you will tell A to just use the public key of B to connect to B and C, which is a dangerous thing

What are these 'design decisions'? Because you appear to be overly complicating what should be a simple process.

I don't think you're understanding how wireguard works. In your scenario A is not connected to C. Having peer entries for A and C at each end are completely pointless because they're not doing anything. The keys you have in those entries will only ever be used if A and C are connected directly. As long as you have B in the middle then packets will be sent from A (or C) to B which will decrypt then with the appropriate public key. B will then re-encrypt them with it's own private key before sending them on to C (or A). If you don't want that to happen then you'll need to connect A and C directly.

1 Like

I think you need two WireGuard tunnels one between A and B, and one between A and C via the previous tunnel.

What would be the point in encapsulating a wireguard tunnel within another wireguard tunnel?

The communication between A and C can't be wire-tapped if B is compromised if you use a separate tunnel between A and C.

Yes, I know. But that doesn't explain why you suggested having a tunnel from A to B, and then another tunnel from A to C through the A-B tunnel. The A-B tunnel is completely redundant.

Avoid C to receive any internet packet directly from the internet. This avoids C receiving malformed packages and other things that can exploit C kernel bugs

I think this is the only solution to avoid exposing C to the internet

One endpoint is enough, but there is none.
You will either connect A and C together or let B do the routing.

It is pointless to create the B-C tunnel, since B and C are in the same LAN.
Just create the A-B tunnel and route whatever needs to be routed to C from A.

What is the best OpenWRT setting to achieve that? I am afraid of creating network loops

You cannot creating routing loops with a single link.
If C is in the LAN network of B, then there is route in B as connected network.
When you create the tunnel from A to B, then another route is added automatically since the tunnel network is connected.
So you have the routing ready. All you need to do is allow the flows on the firewall. In B either add the WG interface in LAN, or, if you want to have more control, add a new zone for WG and allow accordingly the interzone traffic.

Last question: I decided to connect A <-> B <-> C by just passing to A and C the B public key (and viceversa), plus adding the required AllowedIPs.
I need help in adding B wg0 to the right firewall zone, because A cannot connect to B

# cat /etc/config/firewall

config defaults
        option syn_flood '1'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'REJECT'

config zone
        option name 'lan'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'ACCEPT'
        option network 'lan wg0'

config zone
        option name 'wan'
        option input 'REJECT'
        option output 'ACCEPT'
        option forward 'REJECT'
        option masq '1'
        option mtu_fix '1'
        option network 'wan wan6'

config forwarding
        option src 'lan'
        option dest 'wan'

config rule
        option name 'Allow-DHCP-Renew'
        option src 'wan'
        option proto 'udp'
        option dest_port '68'
        option target 'ACCEPT'
        option family 'ipv4'

config rule
        option name 'Allow-Ping'
        option src 'wan'
        option proto 'icmp'
        option icmp_type 'echo-request'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-IGMP'
        option src 'wan'
        option proto 'igmp'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-DHCPv6'
        option src 'wan'
        option proto 'udp'
        option src_ip 'fc00::/6'
        option dest_ip 'fc00::/6'
        option dest_port '546'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-MLD'
        option src 'wan'
        option proto 'icmp'
        option src_ip 'fe80::/10'
        list icmp_type '130/0'
        list icmp_type '131/0'
        list icmp_type '132/0'
        list icmp_type '143/0'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-ICMPv6-Input'
        option src 'wan'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        list icmp_type 'router-solicitation'
        list icmp_type 'neighbour-solicitation'
        list icmp_type 'router-advertisement'
        list icmp_type 'neighbour-advertisement'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-ICMPv6-Forward'
        option src 'wan'
        option dest '*'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'

config rule
        option name 'Allow-IPSec-ESP'
        option src 'wan'
        option dest 'lan'
        option proto 'esp'
        option target 'ACCEPT'

config rule
        option name 'Allow-ISAKMP'
        option src 'wan'
        option dest 'lan'
        option dest_port '500'
        option proto 'udp'
        option target 'ACCEPT'

config include
        option path '/etc/firewall.user'