views:

4298

answers:

6

What's the best way to validate that an IP entered by the user is valid? It comes in as a string.

+2  A: 

I think this would do it...

def validIP(address):
    parts = address.split(".")
    if len(parts) != 4:
        return False
    for item in parts:
        if not 0 <= int(item) <= 255:
            return False
    return True
chills42
You may want to catch the ValueError exception from int() in case the user types "a.b.c.d" and not integers.
Greg Hewgill
Wrong code, works only with IPv4 addresses.
bortzmeyer
+4  A: 
def is_valid_ip(ip):
    """Validates IP addresses.
    """
    return is_valid_ipv4(ip) or is_valid_ipv6(ip)

IPv4:

def is_valid_ipv4(ip):
    """Validates IPv4 addresses.
    """
    pattern = re.compile(r"""
        ^
        (?:
          # Dotted variants:
          (?:
            # Decimal 1-255 (no leading 0's)
            [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
          |
            0x0*[0-9a-f]{1,2}  # Hexadecimal 0x0 - 0xFF (possible leading 0's)
          |
            0+[1-3]?[0-7]{0,2} # Octal 0 - 0377 (possible leading 0's)
          )
          (?:                  # Repeat 0-3 times, separated by a dot
            \.
            (?:
              [3-9]\d?|2(?:5[0-5]|[0-4]?\d)?|1\d{0,2}
            |
              0x0*[0-9a-f]{1,2}
            |
              0+[1-3]?[0-7]{0,2}
            )
          ){0,3}
        |
          0x0*[0-9a-f]{1,8}    # Hexadecimal notation, 0x0 - 0xffffffff
        |
          0+[0-3]?[0-7]{0,10}  # Octal notation, 0 - 037777777777
        |
          # Decimal notation, 1-4294967295:
          429496729[0-5]|42949672[0-8]\d|4294967[01]\d\d|429496[0-6]\d{3}|
          42949[0-5]\d{4}|4294[0-8]\d{5}|429[0-3]\d{6}|42[0-8]\d{7}|
          4[01]\d{8}|[1-3]\d{0,9}|[4-9]\d{0,8}
        )
        $
    """, re.VERBOSE | re.IGNORECASE)
    return pattern.match(ip) is not None

IPv6:

def is_valid_ipv6(ip):
    """Validates IPv6 addresses.
    """
    pattern = re.compile(r"""
        ^
        \s*                         # Leading whitespace
        (?!.*::.*::)                # Only a single whildcard allowed
        (?:(?!:)|:(?=:))            # Colon iff it would be part of a wildcard
        (?:                         # Repeat 6 times:
            [0-9a-f]{0,4}           #   A group of at most four hexadecimal digits
            (?:(?<=::)|(?<!::):)    #   Colon unless preceeded by wildcard
        ){6}                        #
        (?:                         # Either
            [0-9a-f]{0,4}           #   Another group
            (?:(?<=::)|(?<!::):)    #   Colon unless preceeded by wildcard
            [0-9a-f]{0,4}           #   Last group
            (?: (?<=::)             #   Colon iff preceeded by exacly one colon
             |  (?<!:)              #
             |  (?<=:) (?<!::) :    #
             )                      # OR
         |                          #   A v4 address with NO leading zeros 
            (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            (?: \.
                (?:25[0-4]|2[0-4]\d|1\d\d|[1-9]?\d)
            ){3}
        )
        \s*                         # Trailing whitespace
        $
    """, re.VERBOSE | re.IGNORECASE | re.DOTALL)
    return pattern.match(ip) is not None

The IPv6 version uses "(?:(?<=::)|(?<!::):)", which could be replaced with "(?(?<!::):)" on regex engines that support conditionals with look-arounds. (i.e. PCRE, .NET)

Edit:

  • Dropped the native variant.
  • Expanded the regex to comply with the RFC.
  • Added another regex for IPv6 addresses.

Edit2:

I found some links discussing how to parse IPv6 addresses with regex:

Edit3:

Finally managed to write a pattern that passes all tests, and that I am also happy with.

MizardX
No, works only with IPv4 addresses.
bortzmeyer
+22  A: 

Don't parse it. Just ask.

import socket

try:
    socket.inet_aton(addr)
    # legal
except socket.error:
    # Not legal
Dustin
Hmm, seems to accept things like "4" and "192.168" and silently pads the rest with zeros. Technically valid, I'm sure, but not quite what I expected.
krupan
Those are valid representations of IP addresses. 127.1 is localhost, 1172703390 is my web server. If you want to ensure it's in dotted quad, you can also verify that len(addr.split('.')) == 4
Dustin
@krupan: you could combine the above with a test for "len(addr.split(".")) == 4" if you would like to reject shorter addresses.
Greg Hewgill
The Pythonic way ! :D
Federico Ramponi
No, does not work with all legal IP addresses: >>> socket.inet_aton("2001:660::1") Traceback (most recent call last): File "<stdin>", line 1, in <module> socket.error: illegal IP address string passed to inet_aton
bortzmeyer
@bortzmeyer: socket.inet_pton(socket_family, address) is what you want if you want to support ip6. You still need to specify the family. inet_aton specifically does not support anything but ip4.
Richo
+6  A: 

The IPy module (a module designed for dealing with IP addresses) will throw a ValueError exception for invalid addresses.

>>> from IPy import IP
>>> IP('127.0.0.1')
IP('127.0.0.1')
>>> IP('277.0.0.1')
Traceback (most recent call last):
 ...
ValueError: '277.0.0.1': single byte must be 0 <= byte < 256
>>> IP('foobar')
Traceback (most recent call last):
 ...
ValueError: invalid literal for long() with base 10: 'foobar'

However, like Dustin's answer, it will accept things like "4" and "192.168" since, as mentioned, these are valid representations of IP addresses.

Samat Jain
Excellent idea. The only solution presented until now which works with all IP addresses.>>> from IPy import IP>>> IP("2001:660::1")IP('2001:660::1')
bortzmeyer
A: 

if the following script is hosted on a webserver, it should print out visitors Ip address. You can also put it in a database by assigning it to a variable:

'import os

 print 'Content-Type:text/html'
 print 

 print os.environ["REMOTE_ADDR"]' 
Ali
A: 
import socket

def is_valid_ipv4_address(address):
    try:
        addr= socket.inet_pton(socket.AF_INET, address)
    except AttributeError: # no inet_pton here, sorry
        try:
            addr= socket.inet_aton(address)
        except socket.error:
            return False
        return address.count('.') == 3
    except socket.error: # not a valid address
        return False

    return True

def is_valid_ipv6_address(address):
    try:
        addr= socket.inet_pton(socket.AF_INET6, address)
    except socket.error: # not a valid address
        return False
    return True
ΤΖΩΤΖΙΟΥ