Parse an IP Address

From Rosetta Code
Revision as of 16:30, 24 November 2011 by rosettacode>NevilleDNZ (→‎{{header|Python}}: # turned range checking off until debugged)
Parse an IP Address is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The purpose of this task is to demonstrate parsing of text-format IP addresses, using IPv4 and IPv6.

Taking the following as inputs:

127.0.0.1 The "localhost" IPv4 address
127.0.0.1:80 The "localhost" IPv4 address, with a specified port (80)
::1 The "localhost" IPv6 address
[::1]:80 The "localhost" IPv6 address, with a specified port (80)
2605:2700:0:3::4713:93e3 Rosetta Code's primary server's public IPv6 address
[2605:2700:0:3::4713:93e3]:80 Rosetta Code's primary server's public IPv6 address, with a specified port (80)

Emit each described IP address as a hexadecimal integer representing the address, the address space, and the port number specified, if any. In languages where variant result types are clumsy, the result should be ipv4 or ipv6 address number, something which says which address space was represented, port number and something that says if the port was specified.

For example 127.0.0.1 has the address number 7F000001 (2130706433 decimal) in the ipv4 address space.  ::ffff:127.0.0.1 represents the same address in the ipv6 address space where it has the address number FFFF7F000001 (281472812449793 decimal). Meanwhile ::1 has address number 1 and serves the same purpose in the ipv6 address space that 127.0.0.1 serves in the ipv4 address space.

Go

<lang go>package main

import (

   "fmt"
   "net"
   "strconv"

)

var testCases = []string{

   "127.0.0.1",
   "127.0.0.1:80",
   "::1",
   "[::1]:80",
   "2605:2700:0:3::4713:93e3",
   "[2605:2700:0:3::4713:93e3]:80",

}

func main() {

   var max int
   for _, addr := range testCases {
       if len(addr) > max {
           max = len(addr)
       }
   }
   fmt.Printf("%-*s  ", max, "Input")
   fmt.Println("                         Address  Space  Port")
   for _, addr := range testCases {
       fmt.Printf("%-*s  ", max, addr)
       ip := net.ParseIP(addr)
       var host, port string
       var err error
       if ip == nil {
           host, port, err = net.SplitHostPort(addr)
           if err != nil {
               fmt.Println(err)
               continue
           }
           ip = net.ParseIP(host)
       }
       if ip == nil {
           fmt.Println("Invalid address format")
           continue
       }
       if port > "" {
           pn, err := strconv.Atoi(port)
           if err != nil {
               fmt.Println("Invalid port: ", err)
               continue
           }
           if pn < 0 || pn > 65535 {
               fmt.Println("Invalid port number")
               continue
           }
       }
       space := "ipv6"
       if ip4 := ip.To4(); ip4 != nil {
           space = "ipv4"
           fmt.Print("                        ")
           ip = ip4
       }
       for _, b := range ip {
           fmt.Printf("%02x", b)
       }
       fmt.Printf("  %s   %s\n", space, port)
   }

}</lang> Output:

Input                                                   Address  Space  Port
127.0.0.1                                              7f000001  ipv4   
127.0.0.1:80                                           7f000001  ipv4   80
::1                            00000000000000000000000000000001  ipv6   
[::1]:80                       00000000000000000000000000000001  ipv6   80
2605:2700:0:3::4713:93e3       260527000000000300000000471393e3  ipv6   
[2605:2700:0:3::4713:93e3]:80  260527000000000300000000471393e3  ipv6   80

Icon and Unicon

<lang Icon>link printf, hexcvt

procedure main()

  L := ["192.168.0.1",                                # private
        "127.0.0.1",                                  # loop back
        "127.0.0.1:80",                               # loop back +port
        "2001:db8:85a3:0:0:8a2e:370:7334",            # doc, IPv6 for 555-1234
        "2001:db8:85a3::8a2e:370:7334",               # doc
        "::1",                                        # loop back
        "[::1]:80",                                   # loop back +port
        "::",                                         # unspecified
        "::ffff:192.168.0.1",                         # transition
        "2605:2700:0:3::4713:93e3",                   # RC 	
        "[2605:2700:0:3::4713:93e3]:80",              # RC 
        "::ffff:71.19.147.227",                       # RC transition
        "[::ffff:71.19.147.227]:80",                  # RC transition  +port        
        "[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443", # doc +port
        "256.0.0.0",                                  # invalid
        "g::1"]                                       # invalid
                
  every x := !L do {
     if x ?  (ip := ipmatch(), port := portmatch(), pos(0)) then {
        if i := IPv4decode(ip) then 
           printf("%s is the IPv4 address = x'%s'",x,i)
        else if i := IPv6decode(ip) then 
              printf("%s is the IPv6 address = x'%s'",x,i)
        else {
           printf("%s is not a valid IP address\n",x)
           next
           }
        if \port then printf(" port=%s\n",port) else printf("\n")
        }
     else printf("%s is not an IP address\n",x)
     }

end


procedure ipmatch() #: match an ip v4/v6 address static c4,c6 initial {

  c4 := &digits ++ '.'
  c6 := &digits ++ 'abcdef:'
  }
  suspend (="[" || ( (="::ffff:" || tab(many(c4))) | tab(many(c6)) ) || ="]") |
          ( ="::ffff:" || tab(many(c4))) |  tab(many(c6|c4))

end

procedure portmatch() #: match a port number

  return (=":",0 < (65536 > tab(many(&digits)))) | &null

end

procedure IPv4decode(s) #: match IPv4 to hex string

  s ? ( ip  := (0 <= (256 > tab(many(&digits)))), ip *:= 256, =".", 
        ip +:= (0 <= (256 > tab(many(&digits)))), ip *:= 256, =".",
        ip +:= (0 <= (256 > tab(many(&digits)))), ip *:= 256, =".",
        ip +:= (0 <= (256 > tab(many(&digits)))),
        return right(hexstring(ip,,&lcase),8) )            

end

procedure IPv6decode(s) #: IPv6 to hex string

  s ?:=  2(="[", tab(-1), ="]")                         # remove any [] 
  if find(".",s) then                                   # transitional
     s ? ( tab(many(':0')), ="ffff:", 
           return right("ffff" || IPv4decode(tab(0)),32,"0") )
  else { 
     h := t := ""
     s ? {
        while x := tab(find(":")) do {                  # head
           if *x <= 4 then h ||:= right(x,4,"0")
           if ="::" then break
           else move(1)
           }
        while x := tab(find(":")|0) do {                # tail 
           if *x <= 4 then t ||:= right(x,4,"0")          
           move(1) | break
           }
        if x := h || repl("0",32-(*h+*t)) || t then     # and insides
           return x
        }
     }

end</lang>

printf.icn provides the printf family hexcvt.icn provides hex and hexstring

Output:

192.168.0.1 is the IPv4 address = x'c0a80001'
127.0.0.1 is the IPv4 address = x'7f000001'
127.0.0.1:80 is the IPv4 address = x'7f000001' port=80
2001:db8:85a3:0:0:8a2e:370:7334 is the IPv6 address = x'20010db885a3000000008a2e03707334'
2001:db8:85a3::8a2e:370:7334 is the IPv6 address = x'20010db885a3000000008a2e03707334'
::1 is the IPv6 address = x'00000000000000000000000000000001'
[::1]:80 is the IPv6 address = x'00000000000000000000000000000001' port=80
:: is the IPv6 address = x'00000000000000000000000000000000'
::ffff:192.168.0.1 is the IPv6 address = x'00000000000000000000ffffc0a80001'
2605:2700:0:3::4713:93e3 is the IPv6 address = x'260527000000000300000000471393e3'
[2605:2700:0:3::4713:93e3]:80 is the IPv6 address = x'260527000000000300000000471393e3' port=80
::ffff:71.19.147.227 is the IPv6 address = x'00000000000000000000ffff471393e3'
[::ffff:71.19.147.227]:80 is the IPv6 address = x'00000000000000000000ffff471393e3' port=80
[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 is the IPv6 address = x'20010db885a308d313198a2e03707348' port=443
256.0.0.0 is not a valid IP address
g::1 is not an IP address

Perl

<lang perl>sub parse_v4 { my ($ip, $port) = @_; my @quad = split(/\./, $ip);

return unless @quad == 4; for (@quad) { return if ($_ > 255) }

if (!length $port) { $port = -1 } elsif ($port =~ /^(\d+)$/) { $port = $1 } else { return }

my $h = join => map(sprintf("%02x", $_), @quad); return $h, $port }

sub parse_v6 { my $ip = shift; my $omits;

return unless $ip =~ /^[\da-f:.]+$/i; # invalid char

$ip =~ s/^:/0:/; $omits = 1 if $ip =~ s/::/:z:/g; return if $ip =~ /z.*z/; # multiple omits illegal

my $v4 = ; my $len = 8;

if ($ip =~ s/:((?:\d+\.){3}\d+)$//) { # hybrid 4/6 ip ($v4) = parse_v4($1) or return; $len -= 2;

} # what's left should be v6 only return unless $ip =~ /^[:a-fz\d]+$/i;

my @h = split(/:/, $ip); return if @h + $omits > $len; # too many segments

@h = map( $_ eq 'z' ? (0) x ($len - @h + 1) : ($_), @h); return join( => map(sprintf("%04x", hex($_)), @h)).$v4; }

sub parse_ip { my $str = shift; $str =~ s/^\s*//; $str =~ s/\s*$//;

if ($str =~ s/^((?:\d+\.)+\d+)(?::(\d+))?$//) { return 'v4', parse_v4($1, $2); }

my ($ip, $port); if ($str =~ /^\[(.*?)\]:(\d+)$/) { $port = $2; $ip = parse_v6($1); } else { $port = -1; $ip = parse_v6($str); }

return unless $ip; return 'v6', $ip, $port; }

for (qw/127.0.0.1 127.0.0.1:80 ::1 [::1]:80 2605:2700:0:3::4713:93e3 [2605:2700:0:3::4713:93e3]:80 ::ffff:192.168.0.1 [::ffff:192.168.0.1]:22 ::ffff:127.0.0.0.1 a::b::1/) { print "$_\n\t"; my ($ver, $ip, $port) = parse_ip($_) or print "parse error\n" and next;

print "$ver $ip\tport $port\n\n"; }</lang>output<lang>127.0.0.1

       v4 7f000001     port -1

127.0.0.1:80

       v4 7f000001     port 80
1
       v6 00000000000000000000000000000001     port -1

[::1]:80

       v6 00000000000000000000000000000001     port 80

2605:2700:0:3::4713:93e3

       v6 260527000000000300000000471393e3     port -1

[2605:2700:0:3::4713:93e3]:80

       v6 260527000000000300000000471393e3     port 80
ffff:192.168.0.1
       v6 00000000000000000000ffffc0a80001     port -1

[::ffff:192.168.0.1]:22

       v6 00000000000000000000ffffc0a80001     port 22
ffff:127.0.0.0.1
       parse error

a::b::1

       parse error</lang>

PicoLisp

<lang PicoLisp># Return a cons pair of address and port: (address . port) (de ipAddress (Adr)

  (use (@A @B @C @D @Port)
     (cond
        ((match '("[" @A "]" ":" @Port) Adr)
           (adrIPv6 (split @A ":") @Port) )
        ((match '("[" @A "]") Adr)
           (adrIPv6 (split @A ":")) )
        ((match '(@A ":" @B ":" @C) Adr)
           (adrIPv6 (cons @A @B (split @C ":"))) )
        ((match '(@A "." @B "." @C "." @D ":" @Port) Adr)
           (adrIPv4 (list @A @B @C @D) @Port) )
        ((match '(@A "." @B "." @C "." @D) Adr)
           (adrIPv4 (list @A @B @C @D)) )
        (T (quit "Bad IP address" (pack Adr))) ) ) )

(de adrIPv4 (Lst Port)

  (cons
     (sum >> (-24 -16 -8 0) (mapcar format Lst))
     (format Port) ) )

(de adrIPv6 (Lst Port)

  (cons
     (sum >>
        (-112 -96 -80 -64 -48 -32 -16 0)
        (mapcan
           '((X)
              (if X
                 (cons (hex X))
                 (need (- 9 (length Lst)) 0) ) )  # Handle '::'
           (cons (or (car Lst) "0") (cdr Lst)) ) )
     (format Port) ) )</lang>

Test: <lang PicoLisp>(for A

  (quote
     "127.0.0.1"
     "127.0.0.1:80"
     "::1"
     "[::1]:80"
     "2605:2700:0:3::4713:93e3"
     "[2605:2700:0:3::4713:93e3]:80" )
  (let I (ipAddress (chop A))
     (tab (-29 34 40 7)
        A
        (hex (car I))
        (format (car I))
        (cdr I) ) ) )</lang>

Output:

127.0.0.1                                              7F000001                              2130706433
127.0.0.1:80                                           7F000001                              2130706433     80
::1                                                           1                                       1
[::1]:80                                                      1                                       1     80
2605:2700:0:3::4713:93e3       260527000000000300000000471393E3  50537416338094019778974086937420469219
[2605:2700:0:3::4713:93e3]:80  260527000000000300000000471393E3  50537416338094019778974086937420469219     80

Python

Library: pyparse

The following uses pyparse to parse the IP address. It's an attempt at using pyparse to describe an IP address in an extended BNF syntax. Using a parser does seems a bit like using a sledgehammer to crack a nut. However it does make for an interesting alternative to using a regular expressions to parse IP addresses. Note - for example - that the parser specifically reports - as an exception - the location where the IP address is syntactically wrong. <lang python> import string from pyparsing import * # import antigravity

tests="""# 127.0.0.1 The "localhost" IPv4 address 127.0.0.1:80 The "localhost" IPv4 address, with a specified port (80) 0 Dud address f Dud address

1 The "localhost" IPv6 address

[::1]:80 The "localhost" IPv6 address, with a specified port (80) 0000 Dud address 0000:0000 Dud address 0000:0000:0000:0000:0000:0000:0000:0000 Good address 0000:0000:0000::0000:0000 Good Address 0000::0000::0000:0000 Dud address ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff Good address fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff Good address fff:ffff:0:ffff:ffff:ffff:ffff:ffff Good address 2605:2700:0:3::4713:93e3 Rosetta Code's primary server's public IPv6 address [2605:2700:0:3::4713:93e3]:80 Rosetta Code's primary server's public IPv6 address, with a specified port (80) 192.168.0.1 # private 2001:db8:85a3:0:0:8a2e:370:7334 # doc, IPv6 for 555-1234 2001:db8:85a3::8a2e:370:7334 # doc

# unspecified
ffff:192.168.0.1 # transition
ffff:71.19.147.227 # RC transition

[::ffff:71.19.147.227]:80 # RC transition +port [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 # doc +port 256.0.0.0 # invalid g::1 # invalid """

def print_args(args):

 print "print_args:", args

def join(args):

 args[0]="".join(args)
 del args[1:]

def replace(val):

 def lambda_replace(args):
   args[0]=val
   del args[1:]
 return lambda_replace

def atoi(args): args[0]=string.atoi(args[0]) def itohex2(args): args[0]="%02x"%args[0]

def hextoi(args): args[0]=string.atoi(args[0], 16) def itohex4(args): args[0]="%04x"%args[0]

def assert_in_range(lwb, upb):

 def range_check(args):
   return # turned range checking off until debugged
   if args[0] < lwb:
     raise ValueError,"value %d < %d"%(args[0], lwb)
   if args[0] > upb:
     raise ValueError,"value %d > %d"%(args[0], upb)
 return range_check

dot = Literal(".").suppress()("dot"); colon = Literal(":").suppress()("colon") octet = Word(nums).setParseAction(atoi,assert_in_range(0,255),itohex2)("octet");

port = Word(nums).setParseAction(atoi,assert_in_range(0,256*256-1))("port") ipv4 = (octet + (dot+octet)*3)("addr") ipv4.setParseAction(join) #,hextoi)

ipv4_port = ipv4+colon.suppress()+port("port")

a2f = "abcdef" hex = oneOf(" ".join(nums+a2f));

hexet = (hex*(0,4))("hexet") hexet.setParseAction(join, hextoi, itohex4)

max=8; stop=max+1

num_xxxxs = [None, hexet]; num_xxxxs.extend([hexet + (colon+hexet)*n for n in range(1,max)])

x0000s = [ Literal("::").setParseAction(replace("0000"*num_x0000s)) for num_x0000s in range(stop) ]

ipv6=num_xxxxs[-1]+x0000s[0] | num_xxxxs[-1]

  1. Build a table of rules for IPv6, in particular the double colon

for num_prefix in range(max-1, -1, -1):

 for num_x0000s in range(0,stop-num_prefix):
   x0000_etc = x0000s[num_x0000s]
   num_suffix=max-num_prefix-num_x0000s
   if num_prefix: 
     if num_suffix: pat = num_xxxxs[num_prefix]+x0000_etc+num_xxxxs[num_suffix]
     else:          pat = num_xxxxs[num_prefix]+x0000_etc
   elif num_suffix: pat =                       x0000_etc+num_xxxxs[num_suffix]
   else: pat=x0000_etc
   ipv6 = ipv6 | pat
 

ipv6.setParseAction(join) # ,hextoi) ipv6_port = Literal("[").suppress() + ipv6 + Literal("]").suppress()+colon+port("port")

ipv6_transitional = (Literal("::ffff:").setParseAction(replace("00000000000000000000ffff"))+ipv4).setParseAction(join) ipv6_transitional_port = Literal("[").suppress() + ipv6_transitional + Literal("]").suppress()+colon+port("port")

ip_fmt = (

          (ipv4_port|ipv4)("ipv4") |
          (ipv6_transitional_port|ipv6_transitional|ipv6_port|ipv6)("ipv6")
        ) + LineEnd()

class IPAddr(object):

 def __init__(self, string): self.addr = ip_fmt.parseString(string)[:]
 def __repr__(self): return `self.addr` # "".join(self.addr)

for test in tests.splitlines():

 if not test.startswith("#"):
   ip, desc = test.split(None,1)
   print ip,
   try: print "=>", IPAddr(ip),"OK!",
   except (ParseException,ValueError), details: print "Bad! IP address syntax error detected:",details,
   print "- Actually",desc</lang>

Output:

127.0.0.1 => ['7f000001'] OK! - Actually The "localhost" IPv4 address
127.0.0.1:80 => ['7f000001', 80] OK! - Actually The "localhost" IPv4 address, with a specified port (80)
0 => Bad! IP address syntax error detected: Expected "." (at char 1), (line:1, col:2) - Actually Dud address
f => Bad! IP address syntax error detected: Expected ":" (at char 1), (line:1, col:2) - Actually Dud address
::1 => ['00000000000000000000000000000001'] OK! - Actually The "localhost" IPv6 address
[::1]:80 => ['00000000000000000000000000000001', 80] OK! - Actually The "localhost" IPv6 address, with a specified port (80)
0000 => Bad! IP address syntax error detected: Expected "." (at char 4), (line:1, col:5) - Actually Dud address
0000:0000 => Bad! IP address syntax error detected: Expected ":" (at char 9), (line:1, col:10) - Actually Dud address
0000:0000:0000:0000:0000:0000:0000:0000 => ['00000000000000000000000000000000'] OK! - Actually Good address
0000:0000:0000::0000:0000 => ['00000000000000000000000000000000'] OK! - Actually Good Address
0000::0000::0000:0000 => Bad! IP address syntax error detected: Expected end of line (at char 10), (line:1, col:11) - Actually Dud address
ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => ['ffffffffffffffffffffffffffffffff'] OK! - Actually Good address
fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => ['0fffffffffffffffffffffffffffffff'] OK! - Actually Good address
fff:ffff:0:ffff:ffff:ffff:ffff:ffff => ['0fffffff0000ffffffffffffffffffff'] OK! - Actually Good address
2605:2700:0:3::4713:93e3 => ['260527000000000300000000471393e3'] OK! - Actually Rosetta Code's primary server's public IPv6 address
[2605:2700:0:3::4713:93e3]:80 => ['260527000000000300000000471393e3', 80] OK! - Actually Rosetta Code's primary server's public IPv6 address, 
with a specified port (80)
192.168.0.1 => ['c0a80001'] OK! - Actually # private
2001:db8:85a3:0:0:8a2e:370:7334 => ['20010db885a3000000008a2e03707334'] OK! - Actually # doc, IPv6 for 555-1234
2001:db8:85a3::8a2e:370:7334 => ['20010db885a3000000008a2e03707334'] OK! - Actually # doc
:: => ['00000000000000000000000000000000'] OK! - Actually # unspecified
::ffff:192.168.0.1 => ['00000000000000000000ffffc0a80001'] OK! - Actually # transition
::ffff:71.19.147.227 => ['00000000000000000000ffff471393e3'] OK! - Actually # RC transition
[::ffff:71.19.147.227]:80 => ['00000000000000000000ffff471393e3', 80] OK! - Actually # RC transition  +port        
[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 => ['20010db885a308d313198a2e03707348', 443] OK! - Actually # doc +port
256.0.0.0 => ['100000000'] OK! - Actually # invalid
g::1 => Bad! IP address syntax error detected:  (at char 4), (line:1, col:5) - Actually # invalid

Ruby

This example is in need of improvement:
  • This script parses "[::1]:0" like "::1"; it removes port number 0. It should either keep port 0, or disallow port 0 in string.
  • This script slices the hexadecimal address from the sockaddr string. It should instead require 'ipaddr' and call IPAddr#hton.

Ruby 1.9.2 has better IPv6 support than older versions. This script uses class Addrinfo from Ruby 1.9.2.

Works with: Ruby version 1.9.2

<lang ruby>require 'socket'

  1. Parse _string_ for an IP address and optional port number. Returns
  2. them in an Addrinfo object.

def parse_addr(string)

 # Split host and port number from string.
 case string
 when /\A\[([^\]]+)\]:([0-9]+)\z/      # string like "[::1]:80"
   address, port = $1, $2
 when /\A([^:]+):([0-9]+)\z/           # string like "127.0.0.1:80"
   address, port = $1, $2
 else                                  # string with no port number
   address, port = string, nil
 end
 # Pass address, port to Addrinfo.getaddrinfo. It will raise SocketError
 # if address or port is not valid.
 #  * :DGRAM is an arbitrary socket type that allows port numbers;
 #    :STREAM/:TCP and :DGRAM/:UDP use the same style of port numbers.
 #  * AI_NUMERICHOST requires IP address, not DNS name.
 #  * AI_NUMERICSERV requires numeric port, not /etc/services name.
 ary = Addrinfo.getaddrinfo(address, port, nil, :DGRAM, nil,
                            Socket::AI_NUMERICHOST | Socket::AI_NUMERICSERV)
 # An IP address is exactly one address.
 ary.size == 1 or raise SocketError, "expected 1 address, found #{ary.size}"
 ary.first

end

family_hash = Hash.new {|h, k| "family #{k}"} family_hash[Socket::AF_INET] = "ipv4" family_hash[Socket::AF_INET6] = "ipv6"

["127.0.0.1",

"127.0.0.1:80",
"::1",
"[::1]:80",
"2605:2700:0:3::4713:93e3",
"[2605:2700:0:3::4713:93e3]:80",
"fe80::1%lo0",
"1600 Pennsylvania Avenue NW"].each do |string|
 begin
   addr = parse_addr(string)
 rescue SocketError
   puts "#{string}: illegal address"
 else
   # Show family, address and port.
   family, address, port = addr.afamily, addr.ip_address, addr.ip_port
   puts string
   print "  #{family_hash[family]} address #{address}"
   print ", port #{port}" unless port == 0
   puts
   # Show address in hexadecimal. We must unpack it from sockaddr string.
   if addr.ipv4?
     # Expected format of IPv4 sockaddr:
     #   4 bytes to skip
     #   4 bytes for address, network byte order "N" => num
     num = addr.to_sockaddr.unpack("@4N").first
     puts "  hex #{"0x%08x" % num}"
   elsif addr.ipv6?
     # Expected format of IPv6 sockaddr:
     #   8 bytes to skip
     #   32 bytes for address, network byte order "N4" => ary[0..3]
     #   4 bytes for scope, host byte order "L" => ary[4]
     ary = addr.to_sockaddr.unpack("@8N4L")
     num = (ary[0] << 96) + (ary[1] << 64) + (ary[2] << 32) + ary[3]
     print "  hex #{"0x%032x" % num}"
     if addr.ipv6_linklocal?
       print ", scope #{ary[4]}"
     end
     puts
   end
 end

end</lang>

127.0.0.1
  ipv4 address 127.0.0.1
  hex 0x7f000001
127.0.0.1:80
  ipv4 address 127.0.0.1, port 80
  hex 0x7f000001
::1
  ipv6 address ::1
  hex 0x00000000000000000000000000000001
[::1]:80
  ipv6 address ::1, port 80
  hex 0x00000000000000000000000000000001
2605:2700:0:3::4713:93e3
  ipv6 address 2605:2700:0:3::4713:93e3
  hex 0x260527000000000300000000471393e3
[2605:2700:0:3::4713:93e3]:80
  ipv6 address 2605:2700:0:3::4713:93e3, port 80
  hex 0x260527000000000300000000471393e3
fe80::1%lo0
  ipv6 address fe80::1%lo0
  hex 0xfe800000000000000000000000000001, scope 3
1600 Pennsylvania Avenue NW: illegal address

With BSD, fe80::1%lo0 is a link-local address. With other systems, fe80::1%lo0 might be illegal address, because lo0 is not valid scope identifier.

Tcl

Library: Tcllib (Package: ip)

<lang tcl>package require Tcl 8.5 package require ip

proc parseIP {address} {

   set result {}
   set family [ip::version $address]
   set port -1
   if {$family == -1} {

if {[regexp {^\[(.*)\]:(\d+)$} $address -> address port]} { dict set result port $port set family [ip::version $address] if {$family != 6} { return -code error "bad address" } } elseif {[regexp {^(.*):(\d+)$} $address -> address port]} { dict set result port $port set family [ip::version $address] if {$family != 4} { return -code error "bad address" } } else { return -code error "bad address" }

   }
   # Only possible error in ports is to be too large an integer
   if {$port > 65535} {

return -code error "bad port"

   }
   dict set result family $family
   if {$family == 4} {

# IPv4 normalized form is dotted quad, but toInteger helps dict set result addr [format %x [ip::toInteger $address]]

   } else {

# IPv6 normalized form is colin-separated hex dict set result addr [string map {: ""} [ip::normalize $address]]

   }
   # Return the descriptor dictionary
   return $result

}</lang> Demonstration code: <lang tcl>foreach address {

   127.0.0.1
   127.0.0.1:80
   ::1
   [::1]:80
   2605:2700:0:3::4713:93e3
   [2605:2700:0:3::4713:93e3]:80
   ::ffff:192.168.0.1
   [::ffff:192.168.0.1]:22
   ::ffff:127.0.0.0.1
   a::b::1
   127.0.0.1:100000

} {

   if {[catch {

set parsed [parseIP $address]

   } msg]} {

puts "error ${msg}: \"$address\"" continue

   }
   dict with parsed {

puts -nonewline "family: IPv$family addr: $addr" if {[dict exists $parsed port]} { puts -nonewline " port: $port" } puts ""

   }

}</lang> Output:

family: IPv4 addr: 7f000001
family: IPv4 addr: 7f000001 port: 80
family: IPv6 addr: 00000000000000000000000000000001
family: IPv6 addr: 00000000000000000000000000000001 port: 80
family: IPv6 addr: 260527000000000300000000471393e3
family: IPv6 addr: 260527000000000300000000471393e3 port: 80
family: IPv6 addr: 00000000000000000000ffffc0a80001
family: IPv6 addr: 00000000000000000000ffffc0a80001 port: 22
error bad address: "::ffff:127.0.0.0.1"
error bad address: "a::b::1"
error bad port: "127.0.0.1:100000"