Parsing IP address and port in python
Asked Answered
J

4

5

Using python, I'd like to accomplish two things:

  1. Need to split an ipv6 address and port combination in the format [fec2::10]:80 to fec2::10 and 80.

  2. Given an IP address and port combination, I need to determine if the IP is a v4 or v6 address. Eg: 1.2.3.4:80 and [fec2::10]:80

Please suggest a way to do it.

Thanks!

Sample code:

#!/usr/bin/env python


def main():
    server = "[fec1::1]:80"
    if server.find("[", 0, 2) == -1:
       print "IPv4"
       ip, port = server.split(':')
    else:
       print "IPv6"
       new_ip, port = server.rsplit(':', 1)
       print new_ip
       ip = new_ip.strip('[]')

    print ip
    print port

if __name__ == '__main__':
    main()

This works for all cases except when the input is specified without a port. Eg: 10.78.49.50 and [fec2::10]

Any suggestions to address this?

Jarib answered 20/2, 2014 at 12:55 Comment(2)
So what is the problem?Ism
My bash and python scripts would receive the IP:port combination as input and the scripts need to determine the address family of the IP, followed by splitting the IP and port.Jarib
J
0

This is the code I came up with. It looks lengthy and laborious, but it addresses all possible input scenarios. Any suggestion to condense/better it is most welcome :)

#!/usr/bin/env python

import optparse

def main():
    server = "[fec1::1]:80"

    if server.find("[", 0, 2) == -1:
       print "IPv4"
       if server.find(":", 0, len(server)) == -1:
          ip = server
          port = ""
       else:
          ip, port = server.split(':')
    else:
       print "IPv6"
       index = server.find("]", 0, len(server))
       if index == -1:
          print "Something wrong"
          new_ip = ""
          port = ""
       else:
          if server.find(":", index, len(server)) == -1:
             new_ip = server
             port = ""
          else:
             new_ip, port = server.rsplit(':', 1)
       print new_ip
       ip = new_ip.strip('[]')

    print ip
    print port

if __name__ == '__main__':
    main()
Jarib answered 24/2, 2014 at 5:11 Comment(0)
K
5

Assuming your_input is like "[fec2::10]:80" or "1.2.3.4:80", it is easy to split the port and find out the ip address:

#!/usr/bin/env python3
from ipaddress import ip_address

ip, separator, port = your_input.rpartition(':')
assert separator # separator (`:`) must be present
port = int(port) # convert to integer
ip = ip_address(ip.strip("[]")) # convert to `IPv4Address` or `IPv6Address` 
print(ip.version) # print ip version: `4` or `6`
Kibitzer answered 20/2, 2014 at 13:9 Comment(9)
The ipaddress module is only new as of 3.3, what if someone is running an earlier version of python? I do think that finding the version is simpler this way, though, don't get me wrong. :)Hereditament
@2rs2ts: you could use ipaddr-py on earlier versions.Kibitzer
Ah, nice to know. It's still not part of the standard modules, though.Hereditament
@J.F.Sebastian: I'm unable to use both ipaddress and ipaddr-py modules in my script. For ipaddress, it says: Traceback (most recent call last): File "./ip_parse.py", line 4, in <module> from ipaddress import ip_address ImportError: No module named ipaddress from ipaddress import ip_addressJarib
...and for ipaddr-py, it says: File "./ip_parse.py", line 5 from ipaddr-py import ip_address ^ SyntaxError: invalid syntaxJarib
I've added a module-independent code that I wrote, to my question. It is incomplete. Any suggestions for it?Jarib
@Maddy: this answer might be too complex for. You either should be able to install Python 3.3 or download ipaddress.py file from ipaddr-py distribution and put it in the same directory as the script.Kibitzer
This does not work when the port is missing.Reprehension
@Reprehension the question requires the port to be present. The explicit example inputs and the assert also point to it. The answer shouldn't work when the port is missingKibitzer
H
3

You can use urlparse (called urllib.parse in 3.x) to separate the URL into each of its components:

>>> from urlparse import urlparse
>>> ipv4address = urlparse("http://1.2.3.4:80")
>>> ipv4address
ParseResult(scheme='http', netloc='1.2.3.4:80', path='', params='', query='', fragment='')
>>> ipv6address = urlparse("http://[fec2::10]:80")
>>> ipv6address
ParseResult(scheme='http', netloc='[fec2::10]:80', path='', params='', query='', fragment='')

Then you can split the port off by finding the index of the last colon using rfind:

>>> ipv4address.netloc.rfind(':')
7
>>> ipv4address.netloc[:7], ipv4address.netloc[8:]
('1.2.3.4', '80')
>>> ipv6address.netloc.rfind(':')
10
>>> ipv6address.netloc[:10], ipv6address.netloc[11:]
('[fec2::10]', '80')

Identifying which type it is should then be as simple as if ':' in that_split_tuple[0], right? (Not 100% sure because it's been a while since I learned about how to write IPv6 addresses in URLs.)

Finally, removing the brackets from your IPv6 address is simple, there are many ways to do it:

>>> ipv6address.netloc[:10].replace('[', '').replace(']', '')
'fec2::10'
>>> ipv6address.netloc[:10].strip('[]')
'fec2::10'

Edit: since you expressed concern about not always having port numbers, you could simplify significantly by using a regular expression:

>>> import re
>>> f = lambda(n): re.split(r"(?<=\]):" if n.startswith('[') else r"(?<=\d):", n)
>>> f(ipv4address.netloc)
['1.2.3.4', '80']
>>> f(ipv6address.netloc)
['[fec2::10]', '80']
>>> f("1.2.3.4")
['1.2.3.4']
>>> f("[fec2::10]")
['[fec2::10]']

(I'm having trouble being more clever with my regular expression, hence the inline ternary.)

Hereditament answered 20/2, 2014 at 13:21 Comment(4)
I've added a module-independent code that I wrote, to my question. It is incomplete. Any suggestions for it?Jarib
@Jarib Well, at this point I'll write a regular expression for you, but you need to tell me if you're always going to get brackets around your IPv6 addresses, and whether you're going to use hybrid addresses e.g. ::ffff:1.2.3.4.Hereditament
Almost perfect answer, missing a couple things: 1. no need to parse netloc manually and no need for any evil regular expression: urlparse('//[::1]:123').hostname -> '::1' urlparse('//[::1]').port -> None. 2. No need for a full http://, the urllib documentation states that // is enough.Reprehension
3. Again no evil regular expression for this: ipaddress.ip_address('::1') -> IPv6Address('::1')Reprehension
J
0

This is the code I came up with. It looks lengthy and laborious, but it addresses all possible input scenarios. Any suggestion to condense/better it is most welcome :)

#!/usr/bin/env python

import optparse

def main():
    server = "[fec1::1]:80"

    if server.find("[", 0, 2) == -1:
       print "IPv4"
       if server.find(":", 0, len(server)) == -1:
          ip = server
          port = ""
       else:
          ip, port = server.split(':')
    else:
       print "IPv6"
       index = server.find("]", 0, len(server))
       if index == -1:
          print "Something wrong"
          new_ip = ""
          port = ""
       else:
          if server.find(":", index, len(server)) == -1:
             new_ip = server
             port = ""
          else:
             new_ip, port = server.rsplit(':', 1)
       print new_ip
       ip = new_ip.strip('[]')

    print ip
    print port

if __name__ == '__main__':
    main()
Jarib answered 24/2, 2014 at 5:11 Comment(0)
P
0

ip, port = server.rsplit(':', 1) suited for IPv4 and IPv6!

Pythagoras answered 5/10, 2024 at 12:10 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.