Version 1 of A Little CIDR Calculator

Updated 2003-05-15 10:43:34

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.0
 # 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 {
         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
     if { [regsub -all {[.0-9]} $IP {}] != "" } {
         return 0
     }
     # exactly three dots
     if { [regsub -all {[0-9]} $IP {}] != "..." } {
         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
     }
     if { [string length [regsub -all {[0-9]} $CIDR {}]] != 0 } {
         return 0
     }
     scan $CIDR %d $CIDR
     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 40 -height 6 -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 (0x$hexNetmask)\n"
     .g.t insert end "  network: $networkAddress\n"
     .g.t insert end "broadcast: $broadcastAddress\n"
     .g.t insert end "  Num IPs: $numIPs\n"
     .g.t insert end " First IP: $firstIP\n  Last IP: $lastIP\n"
 }

 buildGUI
 # That's all, folks.