Running a Node app on both IPv4 and IPv6

engineering grouparoo node.js 
2021-08-16 - Originally posted at https://www.grouparoo.com/blog/node-js-and-ipv6
↞ See all posts


We want to make Grouparoo as easy as possible to run, which means considering many different server environments. We recently had a customer who wanted to run Grouparoo in a Docker cluster that only had IPv6 addresses enabled. There are lots of reasons why IPv6 might be better (including the fact that we are running out of public IPv4 Addresses), but it’s rare to find a deployment environment that only has IPv6 addresses by default. That said, it’s easy to tell your Node.js application to listen to all hosts on both IPv4 and IPv6 - and that's what Grouparoo does now!

Twisty Roads

The Node.js HTTP Server

When starting a Node.js server, you can to choose both the port to listen on and the hostname to bind to (docs). Depending on how your server is configured, choosing a specific hostname might route traffic in different ways. Maybe you have 2 network cards (one for internal traffic and one for external), or perhaps you have different networks for IPv4 and IPv6 traffic - choosing a certain hostname may have different effects.

The node HTTP example looks like this:

1// from https://nodejs.org/en/docs/guides/getting-started-guide/ 2const http = require("http"); 3 4const hostname = "127.0.0.1"; 5const port = 3000; 6 7const server = http.createServer((req, res) => { 8 res.statusCode = 200; 9 res.setHeader("Content-Type", "text/plain"); 10 res.end("Hello World"); 11}); 12 13server.listen(port, hostname, () => { 14 console.log(`Server running at http://${hostname}:${port}/`); 15});

In this example, we are binding only to 127.0.0.1 which is the IPv4 version of what is called a loopback - this means that only the same computer can talk to itself. This is a very safe way to test and develop, and a very bad way to run a web server 🤣.

Conversely, what if we wanted to allow traffic in from the widest variety of hosts? This is the default configuration of Grouparoo - we want the application to be as widely available as possible, and for the infrastructure to be be in charge of restricting who the application can talk to. In IPv4, that would mean choosing a host of 0.0.0.0 which would allow traffic from anywhere. What about IPv6? It turns out that the string :: (yes, that’s two colons) means "everywhere" in IPv6, and is shorthand for 0.0.0.0.0.0.0.0.

So, in our node.js example above, that would mean that the most permissive host options would be:

1const hostname = "::"; 2const port = 3000; 3//... 4server.listen(port, hostname);

Testing

How can we test that both IPv4 and IPv6 clients can reach your application? Grouparoo exposes a public "status" endpoint we can use to make sure that the application is reachable, and we can try to connect via cURL over a few hostnames & IP Addresses. Then, we pipe the response through the jq command to parse out just the "status" response key:

IPv4 testing:

1$ curl -s "http://localhost:3000/api/v1/status/public" | jq .status 2"ok" 3 4$ curl -s "http://127.0.0.1:3000/api/v1/status/public" | jq .status 5"ok" 6 7$ curl -s "http://0.0.0.0:3000/api/v1/status/public" | jq .status 8"ok"

IPv6 testing:

1$ curl -s "http://[::1]:3000/api/v1/status/public" | jq .status 2"ok" 3 4$ curl -s "http://[::]:3000/api/v1/status/public" | jq .status 5"ok" 6 7$ curl -s "http://[::ffff:127.0.0.1]:3000/api/v1/status/public" | jq .status 8"ok" 9 10$ curl -s "http://[0:0:0:0:0:0:0:1]:3000/api/v1/status/public" | jq .status 11"ok" 12 13$ curl -s "http://[0:0:0:0:0:0:0:0]:3000/api/v1/status/public" | jq .status 14"ok"

You can see that for both IPv4 connections (eg: 127.0.0.1) and IPv6 connections (eg 0:0:0:0:0:0:0:1) we can connect to our app!

IPv6 Also Support IPv4 Addresses

The hostname of :: works with IPv4 addresses because it is backwards compatible. Technically, we have only bound to an IPv6 address, but IPv6 can still handle the older style of connections. This is visible in Grouparoo's logs when we try 127.0.0.1:

2021-08-05T18:03:53.046Z - info: [ action @ web ] to=::ffff:127.0.0.1 action=status:public params={"action":"status:public","apiVersion":"1"} duration=3 method=GET pathname=/api/v1/status/public

The address 127.0.0.1 is translated to ::ffff:127.0.0.1: which is the IPV6 interpretation of 127.0.0.1, with the 4 missing first IPv6 sections replaced by f.


Thanks to Stack Overflow for some of this information.

Hi, I'm Evan

I write about Technology, Software, and Startups. I use my Product Management, Software Engineering, and Leadership skills to build teams that create world-class digital products.

Get in touch