node.jsipv6ipv4amazon-lightsail

Getting Correct vs Wrong IPV4 Address in NodeJS running under AWS Lightsail [IP6to4]


We are running under a BitNami instance on AWS Lightsail.

Current NodeJS looks like this:

function getClientIPv4(req) {
  let ipAddress;
  const forwardedFor = req.headers['x-forwarded-for'];
  if (forwardedFor) {
    ipAddress = forwardedFor.split(',')[0];
  } else {
    ipAddress = req.connection.remoteAddress;
  }

  console.log(req.headers['x-forwarded-for'], "x-forwarded");
  console.log(req.connection.remoteAddress, "remoteAddress");

  if(net.isIPv6(ipAddress)) {
    ipAddress = IP6to4(ipAddress);
  }

  return ipAddress;
}

The developer added some debug to console: enter image description here

My IP Address V6 is 2603:8080:c940:120:5983:c3e9:aed3:c91d which should convert to 72.181.25.179 (which is in Texas).

I think his IP Address is converting correctly (he is in Poland I think).

But the above routine converts it to 174.211.201.29, which is in New York.

Some online web page converters, also make the same mistake:

enter image description here

This conversion routine is being used now, but it gives wrong result as well as other libraries that have been attempted:

function IP6to4(ip6) {
  function parseIp6(ip6str) {
    const str = ip6str.toString();

    // Initialize
    const ar = new Array();
    for (var i = 0; i < 8; i++) ar[i] = 0;

    // Check for trivial IPs
    if (str == '::') return ar;

    // Parse
    const sar = str.split(':');
    let slen = sar.length;
    if (slen > 8) slen = 8;
    let j = 0;
    i = 0
    for (i = 0; i < slen; i++) {
      // This is a "::", switch to end-run mode
      if (i && sar[i] == '') {
        j = 9 - slen + i;
        continue;
      }
      ar[j] = parseInt(`0x0${sar[i]}`);
      j++;
    }

    return ar;
  }

Solution

  • As a general rule, you cannot convert addresses from IPv6 to IPv4. They're two completely independent networks that merely go over the same wires but do not interact in any way – a network operator (an ISP) could have entirely different systems for IPv4 addressing and IPv6 addressing, and only that operator could possibly know the relationship between the two. They're not convertable.

    (For that matter, there are many servers and devices that have IPv6 but no IPv4 address at all. There are even whole networks that don't have IPv4, only IPv6.)

    Tools that claim to "convert" IPv6 to IPv4 only work correctly with a few very specific cases (exceptions to the above) that weren't generally applicable even in the 2000s when they were mildly common, and even less so today.

    For example, if the IPv6 address is a '6to4' or 'Teredo' or 'ISATAP' address, it can be converted because it was indeed based on an IPv4 address originally – those are all "transition" mechanisms that were meant for providing IPv6 connectivity across an otherwise IPv4-only network, so embedding the IPv4 address was a necessity. But when it comes to native IPv6 connectivity, that's no longer the case – native IPv6 does not rely on IPv4 in any way.

    The tool that you have in your screenshot overgeneralizes and tries to apply the ISATAP conversion rule ("take the last 4 bytes") to every address even if it doesn't have the hallmark of being an ISATAP address. So, of course, it gets completely nonsensical results.

    I have an IP Address database we purchased that IPV4 addresses in it. This is a different company, but talks about converting IPV6 to IPV4. Wondering if I can add new columns and index in my lookup DB, or do I have to find a provider that has IPV6 to locations?

    You have to find a provider that has 'IPv6 to locations'.

    windows IPCONFIG command shows:

    Connection-specific DNS Suffix . :
    Link-local IPv6 Address . . . . . : fe80::132f:ba8:74b9:6ae3%23
    IPv4 Address. . . . . . . . . . . : 172.30.96.1
    Temporary IPv6 Address. . . . . . : 2603:8080:c940:120:5983:c3e9:aed3:c91d
    

    So I don't just have one IP Address. What determines which one is passed to a server from the browser?

    The browser always starts by doing a DNS lookup for the domain name, to determine what kind of addresses the server has, again because the networks are independent and it has to use the same kind of address as the server. So if the domain has IPv6 addresses ('AAAA' DNS records) then the browser will try IPv6; and if the domain has IPv4 ('A' records) then the browser will try IPv4. When both are available, browsers usually try both simultaneously while other programs try one after another.

    While the browser decides which network to use, it's usually the client OS that selects the specific address to connect from. If IPv6 is being attempted, then the OS will choose the address based on several rules, one of which is "scope" – which rules out the 'link-local' address because it cannot be used for anything beyond the direct connection – and another is "longest prefix match" which matches global Internet addresses (2xxx:) with another global address, or private LAN addresses (fdXX:) with another private address. In addition, temporary "privacy" addresses are prioritized over permanent addresses.

    How does a website like "What is My IP Address" show both, and NodeJS cannot?

    The website tricks the browser into making multiple connections, one of each kind.

    The initial request to the server is always just one type, either IPv4 or IPv6. In order to get the other type, the website needs to use client-side XHR or iframes to force the browser to connect through an "IPv4-only" subdomain or through an "IPv6-only" subdomain.

    For example, if you visit whatismyip.com, it doesn't show any address at first – it just shows "Detecting…" for both – and you'll see these two XHR requests being made:

    enter image description here

    The first domain is IPv4-only, forcing the browser to make an IPv4 connection. (The second ought to be IPv6-only but it's apparently enough that it merely has IPv6 as the browser will try that first anyway.)

    All such websites work the same way, e.g. another example:

    enter image description here

    The important part here is that the server request handler always sees only one or the other at any time. It's only the client-side webapp that is able to combine both.