PT 2004-Jun-07 I think it would be useful to have a module in tcllib to do some manipulations of internet addresses. If we can think up a sensible API then we can provide something to help cope with both IPv4 and IPv6 addresses. For instance, how do I check that an address is within a certain range? Is 192.168.0.4 within 192.168.0.0./24 or within 192.16./16 or even within 192.168.0.0./255.255.255.0?
Here are a few helper functions I've used elsewhere. It's likely there are faster/neater implementations :) Assume at some point we have done
namespace eval ip4 {}
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
proc ::ip4::ip2x {ip} { set octets [lrange [concat [split $ip .] 0 0 0] 0 3] foreach oct $octets { if {$oct < 0 || $oct > 255} { return -code error "invalid ip address" } } eval [linsert $octets 0 format 0x%02x%02x%02x%02x] }
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}]} { 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 }