Parse an IP Address: Difference between revisions

From Rosetta Code
Content added Content deleted
(IPv4 127.0.0.1 maps to IPv6 ::ffff:127.0.0.1, not ::127.0.0.1.)
(Add Ruby. This might be the first example to parse an IPv6 link-local address and report its scope.)
Line 193: Line 193:
2605:2700:0:3::4713:93e3 260527000000000300000000471393E3 50537416338094019778974086937420469219
2605:2700:0:3::4713:93e3 260527000000000300000000471393E3 50537416338094019778974086937420469219
[2605:2700:0:3::4713:93e3]:80 260527000000000300000000471393E3 50537416338094019778974086937420469219 80</pre>
[2605:2700:0:3::4713:93e3]:80 260527000000000300000000471393E3 50537416338094019778974086937420469219 80</pre>

=={{header|Ruby}}==
Ruby 1.9.2 has better IPv6 support than older versions. This script uses class Addrinfo from Ruby 1.9.2.

{{works with|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? or addr.ipv6_mc_linklocal?
print ", scope #{ary[4]}"
end
puts
end
end
end</lang>

<pre>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</pre>

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.

Revision as of 20:17, 28 September 2011

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.

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

Ruby

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? or addr.ipv6_mc_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.