Skip to content

IPv4 to IPv6 communication (and viceversa): some kludges

Ok, long title. A scenario that may occur sometimes is the need to communicate between an IPv4-only host and an IPv6-only host, and viceversa. There are kludges to do that at the IP level (eg NAT64 etc.), but let's assume we don't have any of those available. Let's use a different kludge, one that works at the application level.

If the service that we need to use runs on TCP and we have a dual-stacked intermediate machine that we can access, then we can use SSH's port forwarding mechanism. Here's an example (IPv4 to IPv6):

# the -4 is to emphasize that we're connecting over iPv4
ipv4box$ ssh -4 user@intermediate -L 1234:[2001:db8:0:1::1]:1234

After doing this, connecting to will actually connect to the service at 2001:db8:0:1::1 port 1234 (the chosen port numbers are of course arbitrary, but you get the idea).

The reverse works just the same; if we're IPv6-only (currently very unlikely, but anyway) and the service we need is IPv4-only, then we can do

# the -6 is to emphasize that we're connecting over iPv6
ipv6box$ ssh -6 user@intermediate -L 1234:

and then use [::1]:1234 to access the service on If the service being accessed is HTTP, most likely you'll need to set up some DNS or /etc/hosts entries to make the browser send the correct Host: header and so on, but overall things should work fine.

And so on: -R, -W, ProxyCommand and all the other tricks can of course be used as well.

Since SSH port forwarding works at the TCP level, it just has to move the data in the TCP payload back and forth between an IPv4 socket and an IPv6 one, so there's no complicated protocol translation stuff going on.

Actually, we don't strictly need SSH, any TCP proxy would do the job; it's just that SSH is universally available and has the functionality built-in. Let's see how to do the IPv4-to-IPv6 case with socat, for example. On the intermediate machine, we need to run

# TCP4-L accepts options to make it spawn children etc., see the man page
intermediate$ socat TCP4-L:1234 TCP6:[2001:db8:0:1::1]:1234

in this case then, we can connect over IPv4 to intermediate:1234 and access the service. In fact, with socat we can even protocol-translate UDP services, since (I believe) socat can move the UDP payload from one "connection" to the other. For example to send all the DNS queries to an IPv6-only server (doesn't make a lot of sense, but that's just the basic idea):

intermediate# socat UDP4-RECVFROM:53,fork UDP6-SENDTO:[2001:db8:0:1::2]:53

All that being said, the idea that the sooner you go dual-stack with native IPv6, the better still applies more than ever.