[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 {} 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. [PT] ---- [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''' ''address'' Returns the protocol version of the address (4 or 6) or 0 if the address is neither IPv4 or IPv6. '''::ip::is''' ''class address'' Returns 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 address'' Compare 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''' ''address'' Convert 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''' ''address'' Convert a normalized internet address into a more compact form suitable for displaying to users. '''::ip::prefix''' ''address'' Returns 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''' ''address'' If 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? These would all be appropriate predicates to provide in such a library. ---- 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 bitboundary 128 would return 1 64,192 would return 2 32,96,160 etc. would return 3 16,48,80 etc. would return 4 and so on And 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/30 And 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 } ---- [[[Category Internet]]]