ip.tcl

Go to the documentation of this file.
00001 /*  ip.tcl - Copyright (C) 2004 Pat Thoyts <patthoyts@users.sourceforge.net>*/
00002 /* */
00003 /*  Internet address manipulation.*/
00004 /* */
00005 /*  RFC 3513: IPv6 addressing.*/
00006 /* */
00007 /*  -------------------------------------------------------------------------*/
00008 /*  See the file "license.terms" for information on usage and redistribution*/
00009 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00010 /*  -------------------------------------------------------------------------*/
00011 /* */
00012 /*  $Id: ip.tcl,v 1.12 2007/08/20 20:03:04 andreas_kupries Exp $*/
00013 
00014 /*  @mdgen EXCLUDE: ipMoreC.tcl*/
00015 
00016 package require Tcl 8.2;                /*  tcl minimum version*/
00017 
00018 namespace ip {
00019     variable version 1.1.2
00020     variable rcsid {$Id: ip.tcl,v 1.12 2007/08/20 20:03:04 andreas_kupries Exp $}
00021 
00022     namespace export is version normalize equal type contract mask
00023     /* catch {namespace ensemble create}*/
00024 
00025     variable IPv4Ranges
00026     if {![info exists IPv4Ranges]} {
00027         array  IPv4Ranges =  {
00028             0/8        private
00029             10/8       private
00030             127/8      private
00031             172.16/12  private
00032             192.168/16 private
00033             223/8      reserved
00034             224/3      reserved
00035         }
00036     }
00037 
00038     variable IPv6Ranges
00039     if {![info exists IPv6Ranges]} {
00040         /*  RFC 3513: 2.4*/
00041         /*  RFC 3056: 2*/
00042         array  IPv6Ranges =  {
00043             2002::/16 "6to4 unicast"
00044             fe80::/10 "link local"
00045             fec0::/10 "site local"
00046             ff00::/8  "multicast"
00047             ::/128    "unspecified"
00048             ::1/128   "localhost"
00049         }
00050     }
00051 }
00052 
00053 ret  ::ip::is (type class , type ip) {
00054     foreach {ip mask} [split $ip /] break
00055     switch -exact -- $class {
00056         ipv4 - IPv4 - 4 {
00057             return [IPv4? $ip]
00058         }
00059         ipv6 - IPv6 - 6 {
00060             return [IPv6? $ip]
00061         }
00062         default {
00063             return -code error "bad class \"$class\": must be ipv4 or ipv6"
00064         }
00065     }
00066 }
00067 
00068 ret  ::ip::version (type ip) {
00069     set version -1
00070     foreach {addr mask} [split $ip /] break
00071     if {[string first $addr :] < 0 && [IPv4? $addr]} {
00072         set version 4
00073     } elseif {[IPv6? $addr]} {
00074         set version 6
00075     }
00076     return $version
00077 }
00078         
00079 ret  ::ip::equal (type lhs , type rhs) {
00080     foreach {LHS LM} [SplitIp $lhs] break
00081     foreach {RHS RM} [SplitIp $rhs] break
00082     if {[set version [version $LHS]] != [version $RHS]} {
00083         return -code error "type mismatch:\
00084             cannot compare different address types"
00085     }
00086     if {$version == 4} {set fmt I} else {set fmt I4}
00087     set LHS [Mask$version [Normalize $LHS $version] $LM]
00088     set RHS [Mask$version [Normalize $RHS $version] $RM]
00089     binary scan $LHS $fmt LLL
00090     binary scan $RHS $fmt RRR
00091     foreach L $LLL R $RRR {
00092         if {$L != $R} {return 0}
00093     }
00094     return 1
00095 }
00096 
00097 ret  ::ip::normalize (type ip , optional Ip4inIp6 =0) {
00098     foreach {ip mask} [SplitIp $ip] break
00099     set version [version $ip]
00100     set s [ToString [Normalize $ip $version] $Ip4inIp6]
00101     if {($version == 6 && $mask != 128) || ($version == 4 && $mask != 32)} {
00102         append s /$mask
00103     }
00104     return $s
00105 }
00106 
00107 ret  ::ip::contract (type ip) {
00108     foreach {ip mask} [SplitIp $ip] break
00109     set version [version $ip]
00110     set s [ToString [Normalize $ip $version]]
00111     if {$version == 6} {
00112         set r ""
00113         foreach o [split $s :] { 
00114             append r [format %x: 0x$o] 
00115         }
00116         set r [string trimright $r :]
00117         regsub {(?:^|:)0(?::0)+(?::|$)} $r {::} r
00118     } else {
00119         set r [string trimright $s .0]
00120     }
00121     return $r
00122 }
00123 
00124 /*  Returns an IP address prefix.*/
00125 /*  For instance: */
00126 /*   prefix 192.168.1.4/16 => 192.168.0.0*/
00127 /*   prefix fec0::4/16     => fec0:0:0:0:0:0:0:0*/
00128 /*   prefix fec0::4/ffff:: => fec0:0:0:0:0:0:0:0*/
00129 /* */
00130 ret  ::ip::prefix (type ip) {
00131     foreach {addr mask} [SplitIp $ip] break
00132     set version [version $addr]
00133     set addr [Normalize $addr $version]
00134     return [ToString [Mask$version $addr $mask]]
00135 }
00136 
00137 /*  Return the address type. For IPv4 this is one of private, reserved */
00138 /*  or normal*/
00139 /*  For IPv6 it is one of site local, link local, multicast, unicast,*/
00140 /*  unspecified or loopback.*/
00141 ret  ::ip::type (type ip) {
00142     set version [version $ip]
00143     upvar [namespace current]::IPv${version}Ranges types
00144     set ip [prefix $ip]
00145     foreach prefix [array names types] {
00146         set mask [mask $prefix]
00147         if {[equal $ip/$mask $prefix]} {
00148             return $types($prefix)
00149         }
00150     }
00151     if {$version == 4} {
00152         return "normal"
00153     } else {
00154         return "unicast"
00155     }
00156 }
00157 
00158 ret  ::ip::mask (type ip) {
00159     foreach {addr mask} [split $ip /] break
00160     return $mask
00161 }
00162 
00163 /*  -------------------------------------------------------------------------*/
00164 
00165 /*  Returns true is the argument can be converted into an IPv4 address.*/
00166 /* */
00167 ret  ::ip::IPv4? (type ip) {
00168     if {[catch {Normalize4 $ip}]} {
00169         return 0
00170     }
00171     return 1
00172 }
00173 
00174 ret  ::ip::IPv6? (type ip) {
00175     set octets [split $ip :]
00176     if {[llength $octets] < 3 || [llength $octets] > 8} {
00177         return 0
00178     }
00179     set ndx 0
00180     foreach octet $octets {
00181         incr ndx
00182         if {[string length $octet] < 1} continue
00183         if {[regexp {^[a-fA-F\d]{1,4}$} $octet]} continue
00184         if {$ndx >= [llength $octets] && [IPv4? $octet]} continue
00185         if {$ndx == 2 && [lindex $octets 0] == 2002 && [IPv4? $octet]} continue
00186         #"Invalid IPv6 address \"$ip\""
00187         return 0
00188     }
00189     if {[regexp {^:[^:]} $ip]} {
00190         #"Invalid ipv6 address \"$ip\" (starts with :)"
00191         return 0
00192     }
00193     if {[regexp {[^:]:$} $ip]} {
00194         # "Invalid IPv6 address \"$ip\" (ends with :)"
00195         return 0
00196     }
00197     if {[regsub -all :: $ip "|" junk] > 1} {
00198         # "Invalid IPv6 address \"$ip\" (more than one :: pattern)"
00199         return 0
00200     }
00201     return 1
00202 }
00203 
00204 ret  ::ip::Mask4 (type ip , optional bits ={)} {
00205     if {[string length $bits] < 1} {  bits =  32 }
00206     binary scan $ip I ipx
00207     if {[string is integer $bits]} {
00208          mask =  [expr {(0xFFFFFFFF << (32 - $bits)) & 0xFFFFFFFF}]
00209     } else {
00210         binary scan [Normalize4 $bits] I mask
00211     }
00212     return [binary format I [expr {$ipx & $mask}]]
00213 }
00214 
00215 ret  ::ip::Mask6 (type ip , optional bits ={)} {
00216     if {[string length $bits] < 1} {  bits =  128 }
00217     if {[string is integer $bits]} {
00218          mask =  [binary format B128 [string repeat 1 $bits]]
00219     } else {
00220         binary scan [Normalize6 $bits] I4 mask
00221     }
00222     binary scan $ip I4 Addr
00223     binary scan $mask I4 Mask
00224     foreach A $Addr M $Mask {
00225         lappend r [expr {$A & $M}]
00226     }
00227     return [binary format I4 $r]
00228 }
00229 
00230         
00231 
00232 /*  A network address specification is an IPv4 address with an optional bitmask*/
00233 /*  Split an address specification into a IPv4 address and a network bitmask.*/
00234 /*  This doesn't validate the address portion.*/
00235 /*  If a spec with no mask is provided then the mask will be 32*/
00236 /*  (all bits significant).*/
00237 /*  Masks may be either integer number of significant bits or dotted-quad*/
00238 /*  notation.*/
00239 /* */
00240 ret  ::ip::SplitIp (type spec) {
00241     set slash [string last / $spec]
00242     if {$slash != -1} {
00243         incr slash -1
00244         set ip [string range $spec 0 $slash]
00245         incr slash 2
00246         set bits [string range $spec $slash end]
00247     } else {
00248         set ip $spec
00249         if {[string length $ip] > 0 && [version $ip] == 6} {
00250             set bits 128
00251         } else {
00252             set bits 32
00253         }
00254     }
00255     return [list $ip $bits]
00256 }
00257 
00258 /*  Given an IP string from the user, convert to a normalized internal rep.*/
00259 /*  For IPv4 this is currently a hex string (0xHHHHHHHH).*/
00260 /*  For IPv6 this is a binary string or 16 chars.*/
00261 ret  ::ip::Normalize (type ip , optional version =0) {
00262     if {$version < 0} {
00263         set version [version $ip]
00264         if {$version < 0} {
00265             return -code error "invalid address \"$ip\":\
00266                 value must be a valid IPv4 or IPv6 address"
00267         }
00268     }
00269     return [Normalize$version $ip]
00270 }
00271 
00272 ret  ::ip::Normalize4 (type ip) {
00273     set octets [split $ip .]
00274     if {[llength $octets] > 4} {
00275         return -code error "invalid ip address \"$ip\""
00276     } elseif {[llength $octets] < 4} {
00277         set octets [lrange [concat $octets 0 0 0] 0 3]
00278     }
00279     foreach oct $octets {
00280         if {$oct < 0 || $oct > 255} {
00281             return -code error "invalid ip address"
00282         }
00283     }
00284     return [binary format c4 $octets]
00285 }
00286 
00287 ret  ::ip::Normalize6 (type ip) {
00288     set octets [split $ip :]
00289     set ip4embed [string first . $ip]
00290     set len [llength $octets]
00291     if {$len < 0 || $len > 8} {
00292         return -code error "invalid address: this is not an IPv6 address"
00293     }
00294     set result ""
00295     for {set n 0} {$n < $len} {incr n} {
00296         set octet [lindex $octets $n]
00297         if {$octet == {}} {
00298             if {$n == 0 || $n == ($len - 1)} {
00299                 set octet \0\0
00300             } else {
00301                 set missing [expr {9 - $len}]
00302                 if {$ip4embed != -1} {incr missing -1}
00303                 set octet [string repeat \0\0 $missing]
00304             }
00305         } elseif {[string first . $octet] != -1} {
00306             set octet [Normalize4 $octet]
00307         } else {
00308             set m [expr {4 - [string length $octet]}]
00309             if {$m != 0} {
00310                 set octet [string repeat 0 $m]$octet
00311             }
00312             set octet [binary format H4 $octet]
00313         }
00314         append result $octet
00315     }
00316     if {[string length $result] != 16} {
00317         return -code error "invalid address: \"$ip\" is not an IPv6 address"
00318     }
00319     return $result
00320 }
00321 
00322 
00323 /*  This will convert a full ipv4/ipv6 in binary format into a normal*/
00324 /*  expanded string rep.*/
00325 ret  ::ip::ToString (type bin , optional Ip4inIp6 =0) {
00326     set len [string length $bin]
00327     set r ""
00328     if {$len == 4} {
00329         binary scan $bin c4 octets
00330         foreach octet $octets {
00331             lappend r [expr {$octet & 0xff}]
00332         }
00333         return [join $r .]
00334     } elseif {$len == 16} {
00335         if {$Ip4inIp6 == 0} {
00336             binary scan $bin H32 hex
00337             for {set n 0} {$n < 32} {incr n} {
00338                 append r [string range $hex $n [incr n 3]]:
00339             }
00340             return [string trimright $r :]
00341         } else {
00342             binary scan $bin H24c4 hex octets
00343             for {set n 0} {$n < 24} {incr n} {
00344                 append r [string range $hex $n [incr n 3]]:
00345             }
00346             foreach octet $octets {
00347                 append r [expr {$octet & 0xff}].
00348             }
00349             return [string trimright $r .]
00350         }
00351     } else {
00352         return -code error "invalid binary address:\
00353             argument is neither an IPv4 nor an IPv6 address"
00354     }
00355 }
00356 
00357 /*  -------------------------------------------------------------------------*/
00358 /*  Load extended command set.*/
00359 
00360 source [file join [file dirname [info script]] ipMore.tcl]
00361 
00362 /*  -------------------------------------------------------------------------*/
00363 
00364 package provide ip $::ip::version
00365 
00366 /*  -------------------------------------------------------------------------*/
00367 /*  Local Variables:*/
00368 /*    indent-tabs-mode: nil*/
00369 /*  End:*/
00370 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1