ip.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016 package require Tcl 8.2;
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
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
00041
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
00125
00126
00127
00128
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
00138
00139
00140
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
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
00233
00234
00235
00236
00237
00238
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
00259
00260
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
00324
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
00359
00360 source [file join [file dirname [info script]] ipMore.tcl]
00361
00362
00363
00364 package provide ip $::ip::version
00365
00366
00367
00368
00369
00370