Validating IP Addresses (with Python) | Plaintextnerds
arrow Articles

Validating IP Addresses (with Python)

date September 29, 2025

date 4 min read

Learn how to validate IP addresses using python
Tim Armstrong Tim Armstrong, Founder, Consultant Engineer, Expert in Performance & Security
Validating IP Addresses (with Python)

SHARE

linkedin share twitter share reddit share

There are many reasons you might want to validate IP Addresses.

In his recent post on LinkedIn, Bob Belderbos talks about the problems of using RegEx and proposes Python’s built-in ipaddress library as an alternative.

In Bob’s post, he shows the following RegEx solution to validating an IP Address:

import re

def validate_ip(ip: str) -> bool:
    pattern = r'^\d{1,3}(\.\d{1,3}){3}$'
    return re.match(pattern, ip) is not None

As Bob points out, this is certainly one way to do it, but it only supports IPv4, isn’t very efficient, is easy to make mistakes in the regex, and is difficult to extend.

Bob also notes that this method allows impossible addresses to pass. For example, 666.666.666.666 would pass this validation, despite IPv4 Addresses being limited to four nibbles of 0-255, meaning the theoretical maximum IPv4 address is 255.255.255.255.

⚠️ The network engineers amongst you will know why this address is also impossible, but that is beyond the scope of this article.

Bob’s approach:

To counter these issues, Bob goes on to propose using the ipaddress module of Python’s built-in library with the following code:

import ipaddress

def validate_ip(ip: str) -> bool:
    try:
        ipaddress.ip_address(ip)
        return True
    except ValueError:
        return False

And while it’s certainly better than RegEx and now supports IPv6, there are a few issues with this approach:

  1. The built-in library is very slow! So if you’re processing a lot of addresses or need to respond in microseconds (e.g. for a game server), this will drastically slow you down.
  2. The built-in library is actually wrong in several places! Its implementations of is_global, is_reserved, etc., don’t conform to the RFCs.

Now, none of this is Bob’s fault; his approach is by far the most common one used by senior engineers everywhere, and if performance isn’t an issue, then it’s probably more than good enough.

A faster approach:

But, in most cases (that I’ve seen at least) where we’re validating IP addresses, performance is actually somewhat important. Even if for nothing other than reducing Time-To-First-Byte of the response.

Sticking to the built-in libraries, an alternative approach that would be more efficient while continuing to support IPv6 would be:

from socket import inet_pton, AF_INET, AF_INET6

def validate_ip(ip: str)-> bool:
    try:
        af = AF_INET
        if ":" in ip:
            af = AF_INET6
        inet_pton(af, ip)
        return True
    except OSError:
        return False

This is a considerably more performant solution, but it still falls short of a correct solution.

Invariably, however, when we’re checking the validity of an IP address, we rarely only care about whether or not it is an IP address. The general assumption is that it is an IP address, but the real question is whether or not it is a suitable IP address.

So, how do you do it properly then?

Well, that’s the entire reason we maintain CIDR-Man.

from cidr_man import CIDR
def validate_ip(ip: str) -> bool:
    try:
        cidr = CIDR(ip)
    except OSError:
        return False
    return cidr.prefix_len == cidr.max_prefixlen

⚠️ The additional check being done here is because CIDR supports IP address ranges (aka CIDRs)

This implementation maintains the same functionality as the prior two versions, with essentially no loss of performance compared to the fastest one; however, we’ve now gained the ability to extend our validation without significant effort or any measurable loss of performance.

For example, is the IP address a valid public address?

from cidr_man import CIDR

def validate_ip(ip: str) -> bool:
    try:
        cidr = CIDR(ip)
    except OSError:
        return False
    return cidr.prefix_len == cidr.max_prefixlen and cidr.is_global

Or is the IP address within one of a set of specific ranges?

from cidr_man import CIDR

def validate_ip(ip: str) -> bool:
    acceptable_ranges = (CIDR("198.51.100.0/24"), CIDR("2001:db8::/32"))
    try:
        cidr = CIDR(ip)
    except OSError:
        return False
    if cidr.prefix_len != cidr.max_prefixlen:
        return False
    for acceptable_range in acceptable_ranges:
        if cidr.version == acceptable_range.version and cidr in acceptable_range:
            return True
    return False

Or even, is it an IPv6 address?

from cidr_man import CIDR

def validate_ip(ip: str) -> bool:
    try:
        cidr = CIDR(ip)
    except OSError:
        return False
    return cidr.prefix_len == cidr.max_prefixlen and cidr.version == 6

Easy.

Further reading

If you hunger for more CIDR knowledge check out our project pages for CIDR-Man and CIDR-Bottle.

arrow Articles overview