Parse an IP Address
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>
Perl 6
<lang perl6>grammar IP_Addr {
token TOP { ^ [ <IPv4> | <IPv6> ] $ }
token IPv4 { [ <d8> +% '.' ] <?{ $<d8> == 4 }> <port>? { @*by8 = @$<d8> } }
token IPv6 { | <ipv6> | '[' <ipv6> ']' <port> }
token ipv6 { | <h16> +% ':' <?{ $<h16> == 8 }> { @*by16 = @$<h16> }
| [ (<h16>) +% ':']? '::' (<h16>) +% ':' <?{ @$0 + @$1 <= 8 }> { @*by16 = @$0, '0' xx 8 - (@$0 + @$1), @$1 }
| '::ffff:' <IPv4> { @*by16 = '0' xx 5, 'ffff', by8to16 @*by8 } }
token d8 { (\d+) <?{ $0 < 256 }> } token d16 { (\d+) <?{ $0 < 65536 }> } token h16 { (<:hexdigit>+) <?{ @$0 <= 4 }> }
token port { ':' <d16> { $*port = +$<d16> } }
}
sub by8to16 (@m) { gather for @m -> $a,$b { take ($a * 256 + $b).fmt("%04x") } }
my @cases = <
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 2001:db8:85a3:0:0:8a2e:370:7334 2001:db8:85a3::8a2e:370:7334 [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 192.168.0.1 ::ffff:192.168.0.1 ::ffff:71.19.147.227 [::ffff:71.19.147.227]:80 :: 256.0.0.0 g::1 0000 0000:0000 0000:0000:0000:0000:0000:0000:0000:0000 0000:0000:0000::0000:0000 0000::0000::0000:0000 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff fff:ffff:0:ffff:ffff:ffff:ffff:ffff
>;
for @cases -> $addr {
my @*by8; my @*by16; my $*port;
IP_Addr.parse($addr);
say $addr; if @*by16 { say " IPv6: ", @*by16».map({:16(~$_)})».fmt("%04x").join; say " Port: ", $*port if $*port; } elsif @*by8 { say " IPv4: ", @*by8».fmt("%02x").join; say " Port: ", $*port if $*port; } else { say " BOGUS!"; } say ;
}</lang>
- Output:
127.0.0.1 IPv4: 7f000001 127.0.0.1:80 IPv4: 7f000001 Port: 80 ::1 IPv6: 00000000000000000000000000000001 [::1]:80 IPv6: 00000000000000000000000000000001 Port: 80 2605:2700:0:3::4713:93e3 IPv6: 260527000000000300000000471393e3 [2605:2700:0:3::4713:93e3]:80 IPv6: 260527000000000300000000471393e3 Port: 80 2001:db8:85a3:0:0:8a2e:370:7334 IPv6: 20010db885a3000000008a2e03707334 2001:db8:85a3::8a2e:370:7334 IPv6: 20010db885a3000000008a2e03707334 [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 IPv6: 20010db885a308d313198a2e03707348 Port: 443 192.168.0.1 IPv4: c0a80001 ::ffff:192.168.0.1 IPv6: 00000000000000000000ffffc0a80001 ::ffff:71.19.147.227 IPv6: 00000000000000000000ffff471393e3 [::ffff:71.19.147.227]:80 IPv6: 00000000000000000000ffff471393e3 Port: 80 :: BOGUS! 256.0.0.0 BOGUS! g::1 BOGUS! 0000 BOGUS! 0000:0000 BOGUS! 0000:0000:0000:0000:0000:0000:0000:0000 IPv6: 00000000000000000000000000000000 0000:0000:0000::0000:0000 IPv6: 00000000000000000000000000000000 0000::0000::0000:0000 IPv6: 00000000000000000000000000000000 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff IPv6: ffffffffffffffffffffffffffffffff ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff BOGUS! fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff IPv6: 0fffffffffffffffffffffffffffffff fff:ffff:0:ffff:ffff:ffff:ffff:ffff IPv6: 0fffffff0000ffffffffffffffffffff
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
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)
- 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, +port (80) 2001:db8:85a3:0:0:8a2e:370:7334 # doc, IPv6 for 555-1234 2001:db8:85a3::8a2e:370:7334 # doc [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 # doc +port 192.168.0.1 # private
- ffff:192.168.0.1 # private transitional
- ffff:71.19.147.227 # Rosetta Code's transitional
[::ffff:71.19.147.227]:80 # Rosetta Code's transitional +port
- # unspecified
256.0.0.0 # invalid, octet > 255 (currently not detected) g::1 # invalid 0000 Bad address 0000:0000 Bad address 0000:0000:0000:0000:0000:0000:0000:0000 Good address 0000:0000:0000::0000:0000 Good Address 0000::0000::0000:0000 Bad address ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff Good address ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff Bad address fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff Good address fff:ffff:0:ffff:ffff:ffff:ffff:ffff Good address """
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 # turn range checking off 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
a2f = "abcdef" hex = oneOf(" ".join(nums+a2f));
hexet = (hex*(0,4))("hexet") hexet.setParseAction(join, hextoi, itohex4)
max=8; stop=max+1
xXXXX_etc = [None, hexet]; xXXXX_etc.extend([hexet + (colon+hexet)*n for n in range(1,max)]) x0000_etc = [ Literal("::").setParseAction(replace("0000"*num_x0000s)) for num_x0000s in range(stop) ]
ipv6=xXXXX_etc[-1]+x0000_etc[0] | xXXXX_etc[-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 = x0000_etc[num_x0000s] num_suffix=max-num_prefix-num_x0000s if num_prefix: if num_suffix: pat = xXXXX_etc[num_prefix]+x0000+xXXXX_etc[num_suffix] else: pat = xXXXX_etc[num_prefix]+x0000 elif num_suffix: pat = x0000+xXXXX_etc[num_suffix] else: pat=x0000 ipv6 = ipv6 | pat
ipv6.setParseAction(join) # ,hextoi) ipv6_port = Literal("[").suppress() + ipv6 + Literal("]").suppress()+colon+port
ipv6_transitional = (Literal("::ffff:").setParseAction(replace("0"*20+"ffff"))+ipv4).setParseAction(join) ipv6_transitional_port = Literal("[").suppress() + ipv6_transitional + Literal("]").suppress()+colon+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.service = dict(zip(("address","port"), ip_fmt.parseString(string)[:])) def __getitem__(self, key): return self.service[key] def __contains__(self, key): return key in self.service def __repr__(self): return `self.service` # "".join(self.service) address=property(lambda self: self.service["address"]) port=property(lambda self: self.service["port"]) is_service=property(lambda self: "port" in self.service) version=property(lambda self: {False:4, True:6}[len(self.address)>8])
for test in tests.splitlines():
if not test.startswith("#"): ip_str, desc = test.split(None,1) print ip_str,"=>", try: ip=IPAddr(ip_str) print ip, "IP Version:",ip.version,"- Address is OK!", except (ParseException,ValueError), details: print "Bad! IP address syntax error detected:",details, print "- Actually:",desc</lang>
Output:
127.0.0.1 => {'address': '7f000001'} IP Version: 4 - Address is OK! - Actually: # The "localhost" IPv4 address 127.0.0.1:80 => {'port': 80, 'address': '7f000001'} IP Version: 4 - Address is OK! - Actually: # The "localhost" IPv4 address, with a specifie d port (80) ::1 => {'address': '00000000000000000000000000000001'} IP Version: 6 - Address is OK! - Actually: # The "localhost" IPv6 address [::1]:80 => {'port': 80, 'address': '00000000000000000000000000000001'} IP Version: 6 - Address is OK! - Actually: # The "localhost" IPv6 addr ess, with a specified port (80) 2605:2700:0:3::4713:93e3 => {'address': '260527000000000300000000471393e3'} IP Version: 6 - Address is OK! - Actually: # Rosetta Code's primar y server's public IPv6 address [2605:2700:0:3::4713:93e3]:80 => {'port': 80, 'address': '260527000000000300000000471393e3'} IP Version: 6 - Address is OK! - Actually: # Rose tta Code's primary server's public IPv6 address, +port (80) 2001:db8:85a3:0:0:8a2e:370:7334 => {'address': '20010db885a3000000008a2e03707334'} IP Version: 6 - Address is OK! - Actually: # doc, IPv6 for 555-1234 2001:db8:85a3::8a2e:370:7334 => {'address': '20010db885a3000000008a2e03707334'} IP Version: 6 - Address is OK! - Actually: # doc [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443 => {'port': 443, 'address': '20010db885a308d313198a2e03707348'} IP Version: 6 - Address is OK! - Ac tually: # doc +port 192.168.0.1 => {'address': 'c0a80001'} IP Version: 4 - Address is OK! - Actually: # private ::ffff:192.168.0.1 => {'address': '00000000000000000000ffffc0a80001'} IP Version: 6 - Address is OK! - Actually: # private transitional ::ffff:71.19.147.227 => {'address': '00000000000000000000ffff471393e3'} IP Version: 6 - Address is OK! - Actually: # Rosetta Code's transition al [::ffff:71.19.147.227]:80 => {'port': 80, 'address': '00000000000000000000ffff471393e3'} IP Version: 6 - Address is OK! - Actually: # Rosetta Code's transitional +port :: => {'address': '00000000000000000000000000000000'} IP Version: 6 - Address is OK! - Actually: # unspecified 256.0.0.0 => {'address': '100000000'} IP Version: 6 - Address is OK! - Actually: # invalid, octet > 255 (currently not detected) g::1 => Bad! IP address syntax error detected: (at char 4), (line:1, col:5) - Actually: # invalid 0000 => Bad! IP address syntax error detected: Expected "." (at char 4), (line:1, col:5) - Actually: Bad address 0000:0000 => Bad! IP address syntax error detected: Expected ":" (at char 9), (line:1, col:10) - Actually: Bad address 0000:0000:0000:0000:0000:0000:0000:0000 => {'address': '00000000000000000000000000000000'} IP Version: 6 - Address is OK! - Actually: Good add ress 0000:0000:0000::0000:0000 => {'address': '00000000000000000000000000000000'} IP Version: 6 - Address is 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: Bad address ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => {'address': 'ffffffffffffffffffffffffffffffff'} IP Version: 6 - Address is OK! - Actually: Good add ress ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff => Bad! IP address syntax error detected: Expected ":" (at char 18), (line:1, col:19) - Actually: Bad address fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff => {'address': '0fffffffffffffffffffffffffffffff'} IP Version: 6 - Address is OK! - Actually: Good addr ess fff:ffff:0:ffff:ffff:ffff:ffff:ffff => {'address': '0fffffff0000ffffffffffffffffffff'} IP Version: 6 - Address is OK! - Actually: Good address
REXX
One of REXX's strongest features is its ability for parsing, it has PARSE instruction for this purpose.
version 1
This version of the REXX program doesn't have any error checking in it for readability. <lang rexx> /*REXX program to parse an IP address into --> IPv4 or IPv6 format, port*/
say center('input IP address' ,30),
center('hex IP address' ,32), center('decimal IP address' ,39) 'space port'
say left(,30,"-"),
left(,32,"-"), left(,39,"-") left(,5,'-'), left(,5,'-')
call IP_parse 127.0.0.1 /*this IP doesn't need quotes. */ call IP_parse '127.0.0.1:80' call IP_parse '::1' call IP_parse '[::1]:80' call IP_parse '2605:2700:0:3::4713:93e3' call IP_parse '[2605:2700:0:3::4713:93e3]:80' exit
/*-------------------------------------IP_parse subroutine--------------*/ IP_parse: procedure; parse arg a .; hexA=; numeric digits 50 digs=0123456789; lowHex='abcdef'; uppHex=lowHex; upper uppHex hexdigs=digs||lowHex||uppHex; dot=pos('.',a)\==0
if dot then do; aSpace=4
parse var a a.1 '.' a.2 "." a.3 '.' a.4 ":" port do j=1 for 4; _=a.j hexA=hexA||d2x(_,2) end end else do; aSpace=6 parse var a pureA ']:' port b=reverse(space(translate(pureA,,'[]'),0)) parse var b a.1 ':' a.2 ":" a.3 ':' a.4 ":" a.5 ':' a.6 ":" a.7 ':' a.8 do j=1 for 8; _=reverse(word(a.j 0,1)) hexA=right(_,4,0)hexA end end
say left(a,30) right(hexA,32) right(x2d(hexA),39) ' IPv'aSpace right(port,5) return </lang> Output (all input data is within the REXX program):
input IP address hex IP address decimal IP address space port ------------------------------ -------------------------------- --------------------------------------- ----- ----- 127.0.0.1 7F000001 2130706433 IPv4 127.0.0.1:80 7F000001 2130706433 IPv4 80 ::1 00000000000000000000000000000001 1 IPv6 [::1]:80 00000000000000000000000000000001 1 IPv6 80 2605:2700:0:3::4713:93e3 000026052700000000030000471393e3 771139775666717831100069657678819 IPv6 [2605:2700:0:3::4713:93e3]:80 000026052700000000030000471393e3 771139775666717831100069657678819 IPv6 80
version 2
This version of the REXX program has some error checking.
It also has code to allow the displaying of hex digits in
lowercase, uppercase, or the case that it was specified.
<lang rexx>
/*REXX program to parse an IP address into ──> IPv4 or IPv6 format, port*/
say center('input IP address' ,30),
center('hex IP address' ,32), center('decimal IP address' ,39) 'space port'
say left(,30,"-"),
left(,32,"-"), left(,39,"-") left(,5,'-'), left(,5,'-')
call IP_parse 127.0.0.1 /*this IP doesn't need quotes. */ call IP_parse '127.0.0.1:80' call IP_parse '::1' call IP_parse '[::1]:80' call IP_parse '2605:2700:0:3::4713:93e3' call IP_parse '[2605:2700:0:3::4713:93e3]:80' exit
/*─────────────────────────────────────IP_parse subroutine──────────────*/ IP_parse: procedure; parse arg a .; hexA=; numeric digits 50 digs=0123456789; lowHex='abcdef'; uppHex=lowHex; upper uppHex hexdigs=digs||lowHex||uppHex; dot=pos('.',a)\==0
if dot then do; aSpace=4
parse var a a.1 '.' a.2 "." a.3 '.' a.4 ":" port do j=1 for 4; _=a.j c=verify(_,digs) if c\==0 then call er j,"digit:" substr(_,c,1) if _>255 then call er j,'is > 255:' _ hexA=hexA||d2x(_,2) end end else do; aSpace=6 parse var a pureA ']:' port b=reverse(space(translate(pureA,,'[]'),0)) parse var b a.1 ':' a.2 ":" a.3 ':' a.4 ":" a.5 ':' a.6 ":" a.7 ':' a.8 do j=1 for 8; _=reverse(word(a.j 0,1)) c=verify(_,hexdigs) if c\==0 then call er 9-j,"hex char:" substr(_,c,1) if x2d(_)>x2d('ffff') then call er 9-j,"is > ffff:" _ hexA=right(_,4,0)hexA end end
if port\== then do
c=verify(port,digs) if c\==0 then call er ,'port number digit:' substr(port,c,1) if port>65535 then call er ,'port number is >65535:' port end
/*uppercase hex to be displayed?*/ upper hexA /*pick one of these*/ /*lowercase hex to be displayed?*/ hexA=translate(hexA,lowHex,uppHex) /*hex to be displayed as is? */ nop /*leave hexA allone*/
say left(a,30) right(hexA,32) right(x2d(hexA),39) ' IPv'aSpace right(port,5) return
/*─────────────────────────────────────ER subroutine────────────────────*/ er: parse arg partJ,txt; say; say '***error!***'; say
say 'invalid IP address='a if partJ\== then say 'invalid IP part' partJ arg(2) else say 'invalid IP' arg(2) say; exit 13
</lang>
Ruby
Ruby 1.9.2 has better IPv6 support than older versions. This script uses class Addrinfo from Ruby 1.9.2.
<lang ruby>require 'socket'
- Parse _string_ for an IP address and optional port number. Returns
- 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
<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"