namespace eval ip4 {}Please feel free to comment and edit. I could especially use some equivalent functions to deal with IPv6 addresses.There are some similar functions on A Little CIDR Calculator - I shall attempt to collect the fastest versions together here. PTJJM 2009/02/06: Not sure if this is the right place for this; however, I did not see a better one.Is an IPv6 address valid (i.e. does it use one of the allowed forms)? Here is my first stab at it and it might not cover all cases. I do not think this can be boiled down to a reasonable regex that still covers all the cases. Please feel free to improve it.
proc isIpV4Address { string } {
#
# NOTE: Stolen from http://wiki.tcl.tk/989, Michael A. Cleverly
#
set octet {(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])}
set pattern "^[join [list $octet $octet $octet $octet] {\.}]\$"
return [regexp -- $pattern $string]
}
proc isIpV6Address { string } {
#
# NOTE: 2001:0db8:0000:0000:0000:0000:1428:57ab
# 2001:0db8:0000:0000:0000::1428:57ab
# 2001:0db8:0:0:0:0:1428:57ab
# 2001:0db8:0:0::1428:57ab
# 2001:0db8::1428:57ab
# 2001:db8::1428:57ab
# 2001:0db8:0000:0000:0000:0000:<IPv4>
# ::1
# ::
#
if {$string eq "::"} then {
return true
}
if {[string range $string 0 1] == "::"} then {
set string [string range $string 1 end]
}
if {[string range $string end-1 end] == "::"} then {
set string [string range $string 0 end-1]
}
set octets [split $string :]
set llength [llength $octets]
if {$llength > 0 && $llength <= 8} then {
set last [expr {$llength - 1}]
for {set index 0} {$index < $llength} {incr index} {
set octet [lindex $octets $index]
set length [string length $octet]
if {$length == 0} then {
if {![info exists null]} then {
set null $index; continue
} else {
return false
}
}
if {$length <= 4 && [string is xdigit -strict $octet]} then {
continue
}
if {$llength <= 7 && $index == $last && [isIpV4Address $octet]} then {
continue
}
return false
}
return true
}
return false
}PT 23-July-2004: I have added an ip package to tcllib now. This can parse and compare IPv4 and IPv6 addresses. At the moment I consider the programming interface incomplete - I'm sure people can come up with ways to improve it.::ip::version addressReturns the protocol version of the address (4 or 6) or 0 if the address is neither IPv4 or IPv6.::ip::is class addressReturns true if the address is a member of the given protocol class. The class parameter may be either ipv4 or ipv6 This is effectively a boolean equivalent of the version command. The class argument may be shortened to 4 or 6.::ip::equal address addressCompare two address specifications for equivalence. The arguments are normalized and the address prefix determined (if a mask is supplied). The normalized addresses are then compared bit-by-bit and the procedure returns true if they match.::ip::normalize addressConvert an IPv4 or IPv6 address into a fully expanded version. There are various shorthand ways to write internet addresses, missing out redundant parts or digts.. This procedure is the opposite of contract.::ip::contract addressConvert a normalized internet address into a more compact form suitable for displaying to users.::ip::prefix addressReturns the address prefix generated by masking the address part with the mask if provided. If there is no mask then it is equivalent to calling normalize::ip::type address::ip::mask addressIf the address supplied includes a mask then this is returned otherwise returns an empty string.Examples
% ip::version ::1 6 % ip::version 127.0.0.1 4 % ip::normalize 127/8 127.0.0.0/8 % ip::contract 192.168.0.0 192.168 % ip::normalize fec0::1 fec0:0000:0000:0000:0000:0000:0000:0001 % ip::contract fec0:0000:0000:0000:0000:0000:0000:0001 fec0::1 % ip::equal 192.168.0.4/16 192.168.0.0/16 1 % ip::equal fec0::1/10 fec0::fe01/10 1
Older stuff ......ip2x Convert an IPv4 address in dotted quad notation into a hexadecimal representation. This will extend truncated ip4 addresses with zeros. eg: ip2x 192.168.0.4 -> 0xc0a80004 or ip2x 127 -> 0x7f000000 This is a little faster using [binary] than using [format]
proc ::ip4::ip2x {ip {validate 0}} {
set octets [split $ip .]
if {[llength $octets] != 4} {
set octets [lrange [concat $octets 0 0 0] 0 3]
}
if {$validate} {
foreach oct $octets {
if {$oct < 0 || $oct > 255} {
return -code error "invalid ip address"
}
}
}
binary scan [binary format c4 $octets] H8 x
return 0x$x
}x2ip Turn the hex representation of an IPv4 address into dotted quad notation. proc ::ip4::x2ip {hex} {
set r {}
set bin [binary format I [expr {$hex}]]
binary scan $bin c4 octets
foreach octet $octets {
lappend r [expr {$octet & 0xFF}]
}
return [join $r .]
}ipmask Returns an IPv4 address masked with subnet bits as a hexadecimal representation. For instance: [ipmask 192.168.0.4 24] -> 0xc0a80000 This makes it easy to compare addresses as described in the introduction. Is 192.168.0.4 within 192.168/16? [expr {[ipmask 192.168.0.4 16] == [ipmask 192.168 16]}] proc ::ip4::ipmask {ip {bits {}}} {
if {[string length $bits] < 1} { set bits 32 }
set ipx [ip2x $ip]
if {[string is integer $bits]} {
set mask [expr {(0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF}]
} else {
set mask [ip2x $bits]
}
return [format 0x%08x [expr {$ipx & $mask}]]
}is_ip4_addr Use the ip4x conversion proc to check that the given address is really an IPv4 address. proc ::ip4::is_ip4_addr {ip} {
if {[catch {ip2x $ip true}]} {
return 0
}
return 1
}splitspec Split an address specification into a ipadd and mask part. This doesn't validate the address portion. If a spec with no mask is provided then the mask will be 32 (all bits significant). proc ::ip4::splitspec {spec} {
set bits 32
set domain $spec
set slash [string last / $spec]
if {$slash != -1} {
incr slash -1
set domain [string range $spec 0 $slash]
incr slash 2
set bits [string range $spec $slash end]
}
return [list $domain $bits]
}Examples
proc IpaddrInDomain {addr domainspec} {
foreach {network bits} [ip4::splitspec $domainspec] {}
set net [ip4::ipmask $network $bits]
set ipx [ip4::ipmask $addr $bits]
if {$ipx == $net} {
return 1
}
return 0
}escargo 7 Jun 2004 - From my networking experience, I can think of some functions that would be useful in handling IP addresses.
- Is an address a host address?
- Is an address a broadcast address?
- Are a host address and a netmask consistent?
For a widget that allows validation of dotted decimal IP addresses take a look at mentry.
SMJ 17 Nov 2005 - Some tidbits I had to make and wanted to share, I'm sure someone can make them better.. :)Returns Cisco wildcard mask from netmask
proc getwc {netmask} {
return [string map {255 0 254 1 252 3 248 7 240 15 224 31 192 63 128 127 0 255} $netmask]
}Returns the subnet from IP and netmask proc getsn {ip netmask} {
set ipsplit [split $ip .]
set nmsplit [split $netmask .]
for {set x 0} {$x<4} {incr x} {
lappend subnet [expr [lindex $ipsplit $x] & [lindex $nmsplit $x]]
}
return [join $subnet .]
}Returns Cisco wildcard mask from bits in a 192.0.2.0/24 notation proc getwc_from_route {route_with_bit} {
set ipsplit [split $route_with_bit /]
set bits [expr 32 - [lindex $ipsplit 1] ]
set wc [expr (1<<$bits) - 1]
set wc [expr ($wc & 0xffffffff)]
set first_octet [expr (($wc & 0xff000000)>>24)]
set second_octet [expr (($wc & 0xff0000)>>16)]
set third_octet [expr (($wc & 0xff00)>>8)]
set fourth_octet [expr $wc & 0xff]
set retval ""
append retval $first_octet "." $second_octet "." $third_octet "." $fourth_octet
return $retval
}Returns number of bits the octet needs to fit into a bitboundary128 would return 1 64,192 would return 2 32,96,160 etc. would return 3 16,48,80 etc. would return 4 and so onAnd here's the code
proc getbits {octet} {
set retval 0
set bits 0
set val 0
while { $retval != $octet } {
set val [expr $val + (128>>$bits)]
set retval [ expr $octet & $val ]
incr bits
}
return $bits
}Returns the input with an added bitmask according to the shortest but longest matching subnetmask while adhering to the bitboundaries :)192.168.123.0 would return 192.168.123.0/24 172.16.12.0 would return 172.16.12.0/22 (because of the .12. octet) 10.10.12.12 would return 10.10.12.12/30And here's the code (uses the getbits procedure above)
proc getclass {ip} {
set ipsplit [split $ip .]
set retval $ip
#In which octet is the least significant bit located?
if { [lindex $ipsplit 3] > 0 } {
#it's located in the fourth octet
set bits 24
} elseif { [lindex $ipsplit 2] > 0 } {
#it's located in the third octet
set bits 16
} elseif { [lindex $ipsplit 1] > 0 } {
#it's located in the second octet
set bits 8
} else {
#it's located in the first octet
set bits 0
}
#now add the additional bits
switch $bits {
24 { set bits [expr [getbits [lindex $ipsplit 3]] + $bits] }
16 { set bits [expr [getbits [lindex $ipsplit 2]] + $bits] }
8 { set bits [expr [getbits [lindex $ipsplit 1]] + $bits] }
0 { set bits getbits [lindex $ipsplit 0] }
}
append retval "/" $bits
return $retval
}
