What do you do when your machines only communicate on IPv6 and you need to connect to an IPv4 only service? Translate.
When a client connects to a network it can connect in one of three method. IPv4 only (simple IOT devices usually connect like this), Dual Stack IPv4+IPv6 (the typical way PC's connect to the network even if users are unaware), and IPv6 only.
If the client uses the first or second method then connecting to a service that is only available via IPv4 no issues will arise. However if the client is IPv6 only, then the service will be unavailable without DNS64 and NAT64.
DNS64 is the first part of this equation. When a DNS lookup for a service is preformed (for example ipv4.google.com) the DNS server will only return an A record. With a DNS64 aware DNS server, the DNS server will create and return a IPv6 address. Where did this address come from? This address will be made up of a predetermined network portion and the IPv4 address that was returned encoded into the tail end of the IPv6 address. RFC 6502 defines a well know prefix for this use case of 64:ff9b::/96.
Now that the IPv4 address has been translated into an IPv6 address, normal LAN routing will take over and move the IPv6 packet from the client to the firewall. This is where NAT64 comes into play. (It is assumed that the firewall is Dual Stacked.) The firewall will accept the traffic for the 64:ff9b::* address and preform a protocol network address translation (PNAT). It will literally take payload from the IPv6 packet and put it into an IPv4 packet. Once that is done, the new IPv4 packet is sent off to the IPv4 service. Return traffic is process in reverse. Now, let's set this up.
DNS64 - Unbound
unbound(8) is a recursive DNS resolver that come bundled in OpenBSD base install. To setup unbound, edit the file located in /var/unbound/etc/unbound.conf and add the following line. This will enable DNS64 translation.
module-config: "dns64 validator iterator"
That is all you'll need to add. (I'm making an assumption that you already have a running unbound server. If not see this article) Now just restart unbound and test.
# dig +short ipv4.google.com AAAA ipv4.l.google.com. 64:ff9b::8efa:be4e
NAT64 - pf.conf
Now that we are resolving the IPv6 address we need to setup our firewall to do the protocol translation. This is done with one simple line
# NOTE: $LAN is the lan interface of the firewall and # $WAN is the wan interface. pass in on $LAN inet6 from any to 64:ff9b::/96 af-to inet from ($WAN:0)
That's it. Now reload you pf.conf(5) file and test things out.
# ping6 ipv4.google.com PING ipv4.l.google.com (64:ff9b::8efa:be4e): 56 data bytes 64 bytes from 64:ff9b::8efa:be4e: icmp_seq=0 hlim=63 time=28.806 ms 64 bytes from 64:ff9b::8efa:be4e: icmp_seq=1 hlim=63 time=29.585 ms 64 bytes from 64:ff9b::8efa:be4e: icmp_seq=2 hlim=63 time=28.106 ms ^C --- ipv4.l.google.com ping statistics --- 3 packets transmitted, 3 packets received, 0.0% packet loss round-trip min/avg/max/std-dev = 28.106/28.832/29.585/0.604 ms
Like I said. Easy.
There were a couple of issues I had when setting this up that will hopefully be correct soon, but I add them here for reference.
 unbound-checkconf[78482:0] fatal error: module conf 'respip dns64 validator iterator' is not known to work
If you run and RPZ in addition to adding the DNS64 you may receive this error. This will prevent unbound from starting using the rcctl(8) script. You can work around this issue by either editing the /etc/rc.d/unbound rc-script and comment out the rc_pre() function (not recommended) or start unbound manually 'unbound -c /var/unbound/etc/unbound.conf'. Only slightly better.
Protocol translation not working or Internal address is not being NAT'd
When the protocol translation happens on the packet input any output matches on the WAN interface appear to be ignored. I'm not sure why this happens. However setting the 'from' address to the WAN IPv4 address makes translation work like we would expect.