5

I'd like to validate an IPv6 address using an algorithm that emphasizes readability. The ideal solution combines a dead-simple regular expression with source-code.

Using https://blogs.msdn.microsoft.com/oldnewthing/20060522-08/?p=31113 as an example:

function isDottedIPv4(s)
{
  var match = s.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
  return match != null &&
         match[1] <= 255 && match[2] <= 255 &&
         match[3] <= 255 && match[4] <= 255;
}

Notice how Raymond moves the complexity from the regular expression into code. I'd like a solution that does the same for IPv6.

3
  • 2
    @Tushar I solution you linked to is exactly the opposite of what I am looking for. If you read the link I provided you will see that I am asking to move as much of the complexity out of the regex into source-code. Commented Jan 3, 2017 at 3:30
  • 1
    @Tushar Please... read the link I provided. It's very explicit about what I mean. Essentially I want a regex that only contains capturing groups with minimal validation, and a separate piece of code that validates the capturing groups. Commented Jan 3, 2017 at 3:34

2 Answers 2

5

Here is a variant of Brandon's answer:

/**
 * @param {String} a String
 * @return {Boolean} true if the String is a valid IPv6 address; false otherwise
 */
function isIPv6(value)
{
  // See https://blogs.msdn.microsoft.com/oldnewthing/20060522-08/?p=31113 and
  // https://4sysops.com/archives/ipv6-tutorial-part-4-ipv6-address-syntax/
  const components = value.split(":");
  if (components.length < 2 || components.length > 8)
    return false;
  if (components[0] !== "" || components[1] !== "")
  {
    // Address does not begin with a zero compression ("::")
    if (!components[0].match(/^[\da-f]{1,4}/i))
    {
      // Component must contain 1-4 hex characters
      return false;
    }
  }

  let numberOfZeroCompressions = 0;
  for (let i = 1; i < components.length; ++i)
  {
    if (components[i] === "")
    {
      // We're inside a zero compression ("::")
      ++numberOfZeroCompressions;
      if (numberOfZeroCompressions > 1)
      {
        // Zero compression can only occur once in an address
        return false;
      }
      continue;
    }
    if (!components[i].match(/^[\da-f]{1,4}/i))
    {
      // Component must contain 1-4 hex characters
      return false;
    }
  }
  return true;
}


console.log('Expecting true...');
console.log(isIPv6('2001:cdba:0000:0000:0000:0000:3257:9652'));
console.log(isIPv6('2001:cdba:0:0:0:0:3257:9652'));
console.log(isIPv6('2001:cdba::3257:9652'));
console.log(isIPv6('2001:cdba::257:9652'));
console.log(isIPv6('2001:DB8:0:2F3B:2AA:FF:FE28:9C5A'));
console.log(isIPv6('::0:2F3B:2AA:FF:FE28:9C5A'));
console.log('\n');
console.log('Expecting false...');
console.log(isIPv6(':0:2F3B:2AA:FF:FE28:9C5A'));

Sign up to request clarification or add additional context in comments.

2 Comments

if (!components[i].match(/^[\da-f]{1,4}/i)) { // Component must contain 1-4 hex characters return false; } when this last condition will pass ?
in testcase, i am not able to cover the the condition.
1

This still may be too complex, but I think it covers most scenarios with IPv6 addresses. I went through something similar recently, it is really hard to replace a huge RegEx for something as complex as IPv6.

function isIPv6(s)
{
    // Check if there are more then 2 : together (ex. :::)
  if(/:{3,}/.test(s)) return false;
    // Check if there are more then 2 :: (ex. ::2001::)
  if(/::.+::/.test(s)) return false;
    // Check if there is a single : at the end (requires :: if any)
  if(/[^:]:$/.test(s)) return false;
    // Check for leading colon
  if(/^:(?!:)/.test(s)) return false;
    // Split all the part to check each
  var ipv6_parts = s.split(':');
    // Make sure there are at lease 2 parts and no more then 8
  if(ipv6_parts.length < 2 || ipv6_parts.length > 8) return false;
	
  var is_valid = true;
    // Loop through the parts
  ipv6_parts.forEach(function(part) {
      // If the part is not blank (ex. ::) it must have no more than 4 digits
    if(/^[0-9a-fA-F]{0,4}$/.test(part)) return;
      // Fail if none of the above match
    is_valid = false;
  });
  
  return is_valid;
}

console.log(isIPv6('2001:cdba:0000:0000:0000:0000:3257:9652'));
console.log(isIPv6('2001:cdba:0:0:0:0:3257:9652'));
console.log(isIPv6('2001:cdba::3257:9652'));
console.log(isIPv6('::2001:cdba:3257:9652'));

5 Comments

That's clever, but I think that last check should allow 1-4 digits. See "Leading zero suppression" at 4sysops.com/archives/ipv6-tutorial-part-4-ipv6-address-syntax ... then I believe you can remove if (part === 0) because it's now implied by the 1-4 digits check.
Added support for leading 0 check. The (part === 0) allows for :0: which is valid IPv6
If you changed /[0-9a-fA-F]{4}/.test(part) to /[0-9a-fA-F]{1,4}/.test(part) then you could remove the part === 0 check. Further, currently your code rejects 2001:cdba::257:9652 which I believe is legal. Lastly, the result of your forEach loop is being ignored. If it returns false, your outer function still returns true.
I completely missed the return false in the forEach. I have updated to account for all mentioned scenarios, I think :)
Looks good overall. I'll add any remaining concerns to your testcases.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.