
set MaskList {	" 0: 0.0.0.0"\
	    	" 1: 128.0.0.0"\
	    	" 2: 192.0.0.0"\
	    	" 3: 224.0.0.0"\
	    	" 4: 240.0.0.0"\
	    	" 5: 248.0.0.0"\
	    	" 6: 252.0.0.0"\
	    	" 7: 254.0.0.0"\
	    	" 8: 255.0.0.0"\
	    	" 9: 255.128.0.0"\
	    	"10: 255.192.0.0"\
	    	"11: 255.224.0.0"\
	    	"12: 255.240.0.0"\
	    	"13: 255.248.0.0"\
	    	"14: 255.252.0.0"\
	    	"15: 255.254.0.0"\
	    	"16: 255.255.0.0"\
	    	"17: 255.255.128.0"\
	    	"18: 255.255.192.0"\
	    	"19: 255.255.224.0"\
	    	"20: 255.255.240.0"\
	    	"21: 255.255.248.0"\
	    	"22: 255.255.252.0"\
	    	"23: 255.255.254.0"\
	    	"24: 255.255.255.0"\
	    	"25: 255.255.255.128"\
	    	"26: 255.255.255.192"\
	    	"27: 255.255.255.224"\
	    	"28: 255.255.255.240"\
	    	"29: 255.255.255.248"\
	    	"30: 255.255.255.252"\
	    	"31: 255.255.255.254"\
	    	"32: 255.255.255.255"\
}

set matchdq "(\[0-9\]+\\.\[0-9\]+\\.\[0-9\]+\\.\[0-9\]+)"

proc LookUpMask {maskbits} {
  # return the dotted-quad representation of a number-of-bits style netmask
  global MaskList
  global matchdq

  foreach maskexpr $MaskList {
    if {[regexp "$maskbits: +$matchdq" $maskexpr junk mask]} {
      return $mask
    }
  }

  error "No mask found to match maskbits = $maskbits."
}


proc LookUpMaskBits {netmask} {
  # return the number-of-bits representation of a dotted-quad style netmask
  global MaskList

  foreach maskexpr $MaskList {
    if {[regexp "(\[0-9\]+): +$netmask" $maskexpr junk maskbits]} {
      return $maskbits
    }
  }

  error "No maskbits found to match mask = $netmask"
}


proc MaskNetwork {address} {
  # return the network address portion of an address/mask pair
  # the mask may be dotted-quad or number-of-bits
  # there must be no embedded spaces
  global matchdq

  set matchparts "(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)"

  if {[regexp "$matchdq/(\[0-9\]+)\$" $address junk ipaddr maskbits]} {
    # CIDR format - convert.
    set address "$ipaddr/[LookUpMask $maskbits]"
  }

  if {![regexp "$matchdq/$matchdq" $address junk ipaddr netmask]} {
    error "Cannot parse \"$address\""
  }

  regexp $matchparts $ipaddr junk ippart1 ippart2 ippart3 ippart4
  regexp $matchparts $netmask junk maskpart1 maskpart2 maskpart3 maskpart4

  if {$ippart1 > 255 || $ippart2 > 255 || $ippart3 > 255 || $ippart4 > 255 || 
      $maskpart1 > 255 || $maskpart2 > 255 || $maskpart3 > 255 || $maskpart4 > 255} {
    error "Address \"$address\" is invalid."
  }

  return "[expr $ippart1 & $maskpart1].[expr $ippart2 & $maskpart2].[expr $ippart3 & $maskpart3].[expr $ippart4 & $maskpart4]"
}


proc MaskHost {address} {
  # return the host portion of an address/mask pair
  # the mask may be dotted-quad or number-of-bits
  # there must be no embedded spaces
  global matchdq

  set matchparts "(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)"

  if {[regexp "$matchdq/(\[0-9\]+)\$" $address junk ipaddr maskbits]} {
    # CIDR format - convert.
    set address "$ipaddr/[LookUpMask $maskbits]"
  }

  if {![regexp "$matchdq/$matchdq" $address junk ipaddr netmask]} {
    error "Cannot parse \"$address\""
  }

  regexp $matchparts $ipaddr junk ippart1 ippart2 ippart3 ippart4
  regexp $matchparts $netmask junk maskpart1 maskpart2 maskpart3 maskpart4

  if {$ippart1 > 255 || $ippart2 > 255 || $ippart3 > 255 || $ippart4 > 255 || 
      $maskpart1 > 255 || $maskpart2 > 255 || $maskpart3 > 255 || $maskpart4 > 255} {
    error "Address \"$address\" is invalid."
  }

  set maskpart1 [expr 255 - $maskpart1]
  set maskpart2 [expr 255 - $maskpart2]
  set maskpart3 [expr 255 - $maskpart3]
  set maskpart4 [expr 255 - $maskpart4]

  return "[expr $ippart1 & $maskpart1].[expr $ippart2 & $maskpart2].[expr $ippart3 & $maskpart3].[expr $ippart4 & $maskpart4]"
}


proc BroadcastAddress {address} {
  # return the broadcast address for an address/mask pair
  # the mask may be dotted-quad or number-of-bits
  # there must be no embedded spaces
  global matchdq

  set matchparts "(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)\\.(\[0-9\]+)"

  if {[regexp "$matchdq/(\[0-9\]+)\$" $address junk ipaddr maskbits]} {
    # CIDR format - convert.
    set address "$ipaddr/[LookUpMask $maskbits]"
  }

  if {![regexp "$matchdq/$matchdq" $address junk ipaddr netmask]} {
    error "Cannot parse \"$address\""
  }

  regexp $matchparts $ipaddr junk ippart1 ippart2 ippart3 ippart4
  regexp $matchparts $netmask junk maskpart1 maskpart2 maskpart3 maskpart4

  if {$ippart1 > 255 || $ippart2 > 255 || $ippart3 > 255 || $ippart4 > 255 || 
      $maskpart1 > 255 || $maskpart2 > 255 || $maskpart3 > 255 || $maskpart4 > 255} {
    error "Address \"$address\" is invalid."
  }

  set baddrpart1 [expr $ippart1 & $maskpart1]
  set maskpart1 [expr 255 - $maskpart1]
  set baddrpart1 [expr $baddrpart1 | [expr 255 & $maskpart1]]

  set baddrpart2 [expr $ippart2 & $maskpart2]
  set maskpart2 [expr 255 - $maskpart2]
  set baddrpart2 [expr $baddrpart2 | [expr 255 & $maskpart2]]

  set baddrpart3 [expr $ippart3 & $maskpart3]
  set maskpart3 [expr 255 - $maskpart3]
  set baddrpart3 [expr $baddrpart3 | [expr 255 & $maskpart3]]

  set baddrpart4 [expr $ippart4 & $maskpart4]
  set maskpart4 [expr 255 - $maskpart4]
  set baddrpart4 [expr $baddrpart4 | [expr 255 & $maskpart4]]

  return "$baddrpart1.$baddrpart2.$baddrpart3.$baddrpart4"
}


proc IsAddressPrivate {address} {
  # return true if the address is a Private Network address per RFC1597
  global matchdq

  if {[regexp "$matchdq/\[0-9\]+" $address junk]} {
    # strip off existing mask information
    set address [MaskNetwork $address]
  }

  if {![regexp "$matchdq" $address junk]} {
    error "Cannot parse \"$address\""
  }

  if { [MaskNetwork "$address/8"]  == "10.0.0.0" ||
       [MaskNetwork "$address/12"] == "172.16.0.0" ||
       [MaskNetwork "$address/16"] == "192.168.0.0" } {
    return 1
  } else {
    return 0
  }
}

