Port forwarding through wireguard s2s connection to circumvent ISP's CGNAT

Hi,

I have a working s2s VPN setup with Wireguard, between two routers running OpenWRT. SiteA has a public IP address and SiteB is behind an ISP's CGNAT without a public IP. There is an IoT device on SiteB's LAN, that I want to access from the internet. So the plan is to set up public access through SiteA's public IP, and forward the request to the IoT device on SiteB's LAN through the WG tunnel. With below setup the connection times out. I am posting SiteA's config. When I try to access the IoT device from SiteA LAN with it's local IP it works like a charm. What could be the issue?

network:

# cat /etc/config/network 

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

config globals 'globals'
        option ula_prefix 'xxx'

config device
        option name 'br-lan'
        option type 'bridge'
        list ports 'eth1'
        option igmp_snooping '1'

config interface 'lan'
        option device 'br-lan'
        option proto 'static'
        option ipaddr '192.168.10.1'
        option netmask '255.255.255.0'
        option ip6assign '60'

config interface 'wan'
        option device 'eth0'
        option proto 'dhcp'

config interface 'wan6'
        option device 'eth0'
        option proto 'dhcpv6'

config interface 'wg_lan'
        option proto 'wireguard'
        option private_key 'xxx'
        option listen_port 'yyy'
        list addresses '10.10.10.1/24'
        option mtu '1420'

config wireguard_wg_lan
        option public_key 'xxx'
        option preshared_key 'xxx'
        option description 'SiteB'
        option route_allowed_ips '1'
        option persistent_keepalive '25'
        list allowed_ips '10.10.10.2/32'
        list allowed_ips '192.168.20.0/24'
        option endpoint_host 'xxx.duckdns.org'
        option endpoint_port 'yyy'

firewall:

# cat /etc/config/firewall

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

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

config zone 'wan'
        option name 'wan'
        option input 'REJECT'
        option output 'ACCEPT'
        option forward 'REJECT'
        option masq '1'
        option mtu_fix '1'
        list network 'wan'
        list network '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 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 rule 'wg'
        option name 'Allow-WireGuard-lan'
        option src 'wan'
        option dest_port 'yyy'
        option proto 'udp'
        option target 'ACCEPT'

config redirect
        option target 'DNAT'
        option name 'IoT'
        list proto 'tcp'
        option src 'wan'
        option src_dport 'zzz'
        option dest_ip '192.168.20.10'
        option dest_port '80'

First something else, this is the config from site A, so if I understand correctly the connection is started from Site B.
If that is the case remove:

        option endpoint_host 'xxx.duckdns.org'
        option endpoint_port 'yyy'

Now on to your question, without seeing the config of Site B it is a bit of a guessing game

But I suspect that SiteB does not allow traffic coming from the internet and it looks like it is from the internet you want to connect to site B via a port forward (correct me if I am wrong)

Two possible solutions:

  1. connect from the internet with a Wireguard client to your server then you can use the Local IP to connect
  2. Set as Allowed IPs on siteB 0.0.0.0/0 to allow all traffic also from the internet.
    You probably do not want to have a default route from siteB to siteA in which case you can disable Default Route on the WireGuard interface of SiteB Advanced settings > "Use Default Gateway" (`option defaultroute '0')

But as said a lot of guessing

1 Like

There is a more direct solution using Tailscale.

Tailscale supports NAT Traversal and hence works behind CGNAT.

Install Tailscale on the Site B Router and Tailscale on your remote client.

There is no need to involve a commercial third party if you already have a working connection.

1 Like

Yes Site A should not have an endpoint_host or port in the peer section for B since it is not possible for B to accept an incoming connection anyway. B must initiate the connection. B should be pointed at A's ddns IP and listen_port.

At site A in your port forwarding redirect you need to add option dest lan since the wireguard tunnel is in that zone. These rules do not work without a specific dest (it does not try to figure out the dest interface/zone from the IP address, and option dest * also does not work). You may need to make a separate zone for the tunnel; I have not tried this with a combined zone.

At site B make sure that a persistent_keepalive is active so the tunnel is brought up immediately. If you don't have that, there is no connection unless B initiates some traffic.

If this does work, note that opening the web interface of an IoT to the whole Internet is asking for trouble.

Can't you just use IPV6 addresses