Version 3 of A Little CIDR Calculator

Updated 2003-06-09 16:06:48

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 [L1 ] 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.

 #!/bin/sh
 # Emacs: please open this file in -*-Tcl-*- mode
 #
 # Author: Mark Oakden http://wiki.tcl.tk/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+"$@"}
 #
 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.