CGNAT + IPv4 only LAN + jool = NAT64 not working

Hi there,

I have some IPv4-only networks with a lot of IoT sensors. Previously, I was able to port forward and access everything inside the NAT. However, after changing ISP providers, things broke.

The new ISP, Starlink, provides only IPv6 connectivity with prefix delegation. I still need to access the sensors inside those IPv4 networks. As a temporary solution, I set up a reverse tunnel using site-to-site WireGuard, but this approach isn't scalable.

To address the issue, I decided to implement NAT64 in a test environment to route traffic. I followed this OpenWrt tutorial but couldn't get it to work successfully.

For testing, I built a VirtualBox environment. The first VM runs OpenWrt with DHCP, and the second VM runs an eOS instance (IPv4-only with NAT).

I would really appreciate some guidance, as this is my first time managing an IPv6 network.

root@OpenWrt:~# ubus call system board
{
	"kernel": "5.15.167",
	"hostname": "OpenWrt",
	"system": "Intel(R) Xeon(R) CPU E5-2680 v4 @ 2.40GHz",
	"model": "innotek GmbH VirtualBox",
	"board_name": "innotek-gmbh-virtualbox",
	"rootfs_type": "ext4",
	"release": {
		"distribution": "OpenWrt",
		"version": "23.05.5",
		"revision": "r24106-10cc5fcd00",
		"target": "x86/64",
		"description": "OpenWrt 23.05.5 r24106-10cc5fcd00"
	}
}
root@OpenWrt:~# 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 'fdd9:3030:a437::/48'

config device
	option name 'br-lan'
	option type 'bridge'
	list ports 'eth0'

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

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

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

config interface 'jool'
	option proto 'static'
	option device 'jool'
	option ipaddr '192.168.164.1'
	option netmask '255.255.255.0'
	option ip6assign '64'
	option ip6hint '64'

config route6
	option interface 'jool'
	option target '64:ff9b::/96'
	option gateway 'fe80::64'
root@OpenWrt:~# cat /etc/config/dhcp

config dnsmasq
	option domainneeded '1'
	option boguspriv '1'
	option filterwin2k '0'
	option localise_queries '1'
	option rebind_protection '1'
	option rebind_localhost '1'
	option local '/lan/'
	option domain 'lan'
	option expandhosts '1'
	option nonegcache '0'
	option cachesize '1000'
	option authoritative '1'
	option readethers '1'
	option leasefile '/tmp/dhcp.leases'
	option resolvfile '/tmp/resolv.conf.d/resolv.conf.auto'
	option nonwildcard '1'
	option localservice '1'
	option ednspacket_max '1232'
	option filter_aaaa '0'
	option filter_a '0'

config dhcp 'lan'
	option interface 'lan'
	option start '100'
	option limit '150'
	option leasetime '12h'
	option dhcpv4 'server'
	option dhcpv6 'server'
	option ra 'server'
	option ra_slaac '1'
	list ra_flags 'managed-config'
	list ra_flags 'other-config'

config dhcp 'wan'
	option interface 'wan'
	option ignore '1'

config odhcpd 'odhcpd'
	option maindhcp '0'
	option leasefile '/tmp/hosts/odhcpd'
	option leasetrigger '/usr/sbin/odhcpd-update'
	option loglevel '4'

config dhcp 'jool'
	option interface 'jool'
	option start '100'
	option limit '150'
	option leasetime '12h'
	option ignore '1'
	option ra 'server'
	option ra_default '2'
root@OpenWrt:~# cat /etc/config/firewall

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

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

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

config zone
	option name 'jool'
	option input 'ACCEPT'
	option output 'ACCEPT'
	option forward 'REJECT'
	list network 'jool'

config forwarding
        option src 'jool'
        option dest 'wan'

config forwarding
	option src 'lan'
	option dest 'jool'

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
	option src 'wan'
	option target 'ACCEPT'
	option name 'Openwrt using WAN6'
	list proto 'tcp'
	option dest_port '80'
	option enabled '0'

config rule
	option name 'ssh '
	list proto 'tcp'
	option src 'wan'
	option src_port '22'
	option dest 'lan'
	list dest_ip 'fe80::b3d8:b641:dd73:c065'
	option dest_port '22'
	option target 'ACCEPT'
	option enabled '0'

config redirect
	option dest 'lan'
	option target 'DNAT'
	option name 'ssh'
	option family 'ipv6'
	list proto 'tcp'
	option src 'wan'
	option src_dport '22'
	option dest_ip 'fe80::b3d8:b641:dd73:c065'
	option dest_port '22'
	option enabled '0'
root@OpenWrt:~# cat /etc/rc.local 
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.
/etc/jool/setupjool.sh
exit 0
root@OpenWrt:~# cat /etc/jool/setupjool.sh 
#!/bin/sh
ip link add jool type veth peer openwrt
ip netns add jool
ip link set dev openwrt netns jool
ip netns exec jool sh <<EOF
    sysctl -w net.ipv4.conf.all.forwarding=1
    sysctl -w net.ipv6.conf.all.forwarding=1
    sysctl -w net.ipv6.conf.openwrt.accept_ra=2
    sysctl -w net.ipv4.ip_local_port_range="32768 32999"
    ip link set dev lo up
    ip link set dev openwrt up
    ip addr add dev openwrt 192.168.164.2/24
    ip addr add dev openwrt fe80::64
    ip route add default via 192.168.164.1
    modprobe jool
    jool instance add --netfilter --pool6 64:ff9b::/96
    jool global update lowest-ipv6-mtu 1500
    jool pool4 add 192.168.164.2 33000-65535 --tcp
    jool pool4 add 192.168.164.2 33000-65535 --udp
    jool pool4 add 192.168.164.2 33000-65535 --icmp
EOF

thales@VirtualBox-35ddbd0a:~$ ip addr show dev enp0s3 
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:31:86:f6 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.101/24 brd 192.168.56.255 scope global dynamic noprefixroute enp0s3
       valid_lft 42755sec preferred_lft 42755sec

When I try to ping it I get nothing.

root@OpenWrt:~# ping 64:ff9b::192.168.56.101
PING 64:ff9b::192.168.56.101 (64:ff9b::c0a8:3865): 56 data bytes

After digging some more, I realized that NAT64 works for outbound connection, not for inbound connections. Not sure if this is correct.

You'd be doing sort of reverse PLAT instead. You have a public /64 prefix on the outside, and you want users (v6 only) who enter on that to get translated to access an IPv4-only network inside, which happen to be RFC1918 IPs. It's the reverse of the usual NAT64 but jool should be able to do it.

Note that this does expose the IoTs to connection attempts from anyone on the Internet, so they need to be secure against that. A VPN is typically used instead if only you and/or a small group of authorized users will be making access. It is possible to run IPv4 inside a VPN tunnel which is transported via IPv6 on the outside.

Hi @mk24 ,thanks for pointing that out! The main issue is that I'm using really old Raspberry Pi devices. To use WireGuard without overloading OpenWrt, I need to enable IPv6 on those devices and then create a traffic rule for it. I'll give that a try.