A Little CIDR Calculator

This was a small project I set myself to learn a bit about the use of the binary command in Tcl which allows the manipulation and conversion to different bases/formats of binary data.

It takes an IP address and CIDR number (e.g. 192.168.100.1/24) and shows the network and broadcast addresses, number of usable IP addresses and first and last usable IP addresses in the block.

jmn Nice work. Can we assume this and other contributions by you to the wiki come under either a Tcl-style license (i.e BSD) or are public domain?

MNO Everything I put here can be considered under a Tcl-style (BSD-like) license or GPL at the choice of anybody who wants the code.

Version 1.1 is now more PocketPC (Windows/CE) friendly, and works with (some versions of) Tcl older than 8.4.1.

 #!/bin/sh
 # Emacs: please open this file in -*-Tcl-*- mode
 #
 # Author: Mark Oakden https://wiki.tcl-lang.org/MNO
 # Version: 1.1
 #
 # Note: this is almost certainly riddled with byte order
 # and 32-bit assumptions.
 #
 # Changes since 1.0:-
 #  changed usage of regsub to accomodate earlier tcl/tk versions than 8.4.1
 #  changed layout to accomodate PocketPC better
 #
 # the next but one line restarts with tclsh...
 # DO NOT REMOVE THIS BACKSLASH -> \
     exec tclsh "$0" ${1+"[email protected]"}
 #
 package require Tk
 #
 # a couple of defaults
 #
 set IP 192.168.100.1
 set CIDR 24
 # IPtoHex assumes IP has already been validated
 proc IPtoHex { IP } {
     binary scan [binary format c4 [split $IP .]] H8 Hex
     return $Hex
 }
 proc hexToIP { Hex } {
     binary scan [binary format H8 $Hex] c4 IPtmp
     foreach num $IPtmp {
         # binary scan "c" format gives signed int - the following
         # [expr]-ology converts to unsigned (from [binary] manpage)
         lappend IP [expr ($num + 0x100) % 0x100]
     }
     set IP [join $IP .]
     return $IP
 }
 proc CIDRtoHexNetmask { CIDR } {
     set zeros [expr 32 - $CIDR]
     set ones $CIDR
     set binaryCIDR [string repeat 1 $ones]
     append binaryCIDR [string repeat 0 $zeros]
     binary scan [binary format B32 $binaryCIDR] H8 HexNetmask
     return $HexNetmask
 }
 proc IPisValid { IP } {
     # must contain only dots and digits
     # this originally read:-
     #if { [regsub -all {[.0-9]} $IP {}] != "" } {
     #        return 0
     #}
     regsub -all {[.0-9]} $IP {} tmpStr
     if { $tmpStr != "" } {
         return 0
     }
     # however this appears to be a 8.4.1-ism which doesn't work with 
     # earlier versions (e.g. the 8.4a2 version that the PocketPC tcltk
     # version is based on.
     #
     # exactly three dots
     regsub -all {[0-9]} $IP {} tmpStr
     if { $tmpStr != "..." } {
         return 0
     }
     # each numerical component is between 0 and 255
     foreach b [split $IP .] {
         if { [string length $b] == 0 } {
             return 0
         }
         set ob $b
         scan $b %d b ;# allow for leading zeros which tcl thinks are octal
         if { $b < 0 | $b > 255 } {
             return 0
         }
     }
     return 1
 }

 proc CIDRisValid { CIDR } {
     if { [string length $CIDR] == 0 } {
         return 0
     }
     regsub -all {[0-9]} $CIDR {} tmpStr
     if { [string length $tmpStr] != 0 } {
         return 0
     }
     scan $CIDR %d CIDR
     # 4 is arbitrary restriction on my part, but no-one uses CIDR to
     # amalgamate multiple class A addresses! CIDR of 31 and 32 are
     # non-useful also (/31 would leave just two IP addresses in the
     # subnet, one of which would be the network address, the other
     # the broadcast address - i.e. no usable IPs)
     if { $CIDR < 4 | $CIDR > 30 } {
          return 0
     }
     return 1
 }
 
 # IP and netmask in Hex, returns hex
 proc networkAddress { hexIP hexNetmask } {
     set compNetmask [expr 0x$hexNetmask ^ 0xffffffff]
     set tmpNetAddr [expr ( 0x$hexIP | $compNetmask ) ^ $compNetmask]
     binary scan [binary format I $tmpNetAddr] H8 networkAddress
     return $networkAddress
 }

 # IP and netmask in Hex, returns hex
 proc broadcastAddress { hexIP hexNetmask } {
     set tmpBrdAddr [expr 0x$hexIP | ( 0x$hexNetmask ^ 0xffffffff )]
     binary scan [binary format I $tmpBrdAddr] H8 broadcastAddress
     return $broadcastAddress
 }

 #
 proc buildGUI {} {
     pack [frame .f] -expand 1 -fill x
     label .f.l1 -text "IP Address/CIDR:"
     label .f.l2 -text "/"
     entry .f.ip -textvariable ::IP -width 15
     entry .f.cidr -textvariable ::CIDR -width 2
     bind .f.ip <Return> {calculate $::IP $::CIDR}
     bind .f.cidr <Return> {calculate $::IP $::CIDR}
     button .f.go -text "Go" -command {calculate $::IP $::CIDR}
     pack .f.l1 .f.ip .f.l2 .f.cidr -side left
     pack .f.go -side left -fill x -expand true
     pack [frame .g] -expand 1 -fill x
     text .g.t -width 32 -height 7 -font {Courier 8}
     pack .g.t -expand 1 -fill x
 }
 
 proc calculate { IP CIDR } {
     if { ! [IPisValid $IP] } {
          error "IP is not valid"
     }
     if { ! [CIDRisValid $CIDR] } {
          error "CIDR is not valid"
     }
     set hexIP [IPtoHex $IP]
     set hexNetmask [CIDRtoHexNetmask $CIDR]
     set netmask [hexToIP $hexNetmask]
     set hexNetworkAddress [networkAddress $hexIP $hexNetmask]
     set networkAddress [hexToIP $hexNetworkAddress]
     set hexBroadcastAddress [broadcastAddress $hexIP $hexNetmask]
     set broadcastAddress [hexToIP $hexBroadcastAddress]
     set numIPs [expr 0x$hexBroadcastAddress - 0x$hexNetworkAddress - 1]
     binary scan [binary format I [expr 0x$hexNetworkAddress + 1]] \
          H8 firstIP
     set firstIP [hexToIP $firstIP]
     binary scan [binary format I [expr 0x$hexBroadcastAddress - 1]]\
          H8 lastIP
     set lastIP [hexToIP $lastIP]
     .g.t delete 1.0 end
     .g.t insert end "netmask: $netmask\n"
     .g.t insert end "  (hex): (0x$hexNetmask)\n"
     .g.t insert end "network: $networkAddress\n"
     .g.t insert end " b'cast: $broadcastAddress\n"
     .g.t insert end "  # IPs: $numIPs\n"
     .g.t insert end " 1st IP: $firstIP\nlast IP: $lastIP\n"
 }
 
 buildGUI
 # That's all, folks.

RS 2008-01-16 - ... or the ip package in Tcllib, or this:

 proc ip2int ip {
     set res 0
     foreach i [split $ip .] {set res [expr {wide($res<<8 | $i)}]}
     set res
 }
 proc bits n {
     set res 0
     foreach i [split [string repeat 1 $n][string repeat 0 [expr {32-$n}]] ""] {
        set res [expr {$res<<1 | $i}]
     }
     set res
 }
 proc maskmatch {ip1 width ip2} {
     expr {([ip2int $ip1] & [bits $width]) == ([ip2int $ip2] & [bits $width])}
 }
 proc maskmatch2 {mask ip} {
     foreach {ip0 width} [split $mask /] break
     if {$width eq ""} {return [string equal $mask $ip]}
     maskmatch $ip0 $width $ip
 }
 % maskmatch2 10.10.1.32/27  10.10.1.44
 1
 % maskmatch2 10.10.1.32/27  10.10.1.90
 0

dkf contributed this simpler version on the chat:

 proc onNet {cidrAddr addr} {
    scan $cidrAddr {%d.%d.%d.%d/%d} a b c d bits
    set addr2 [format {%d.%d.%d.%d} $a $b $c $d]
    set mask [expr {0xffffffff & (0xffffffff << (32-$bits))}]
    expr {($mask & [ip2int $addr]) == ($mask & [ip2int $addr2])}
 }

kostix 05-05-2008: with ip from Tcllib it can be implemented this way:

 proc onNet {net ip} {
   set mask [ip::mask $net]
   if {$mask != ""} {
     set pfx [ip::prefix $ip/$mask]
   } else {
     set pfx $ip
   }
   string equal [ip::prefix $net] $pfx
 }

where $net is something like 192.168/16, 10.8.0.0/255.255.255 and so on.

See also IP Calculator GUI for a slightly different approach.