| 1 | #!/usr/local/bin/ruby |
|---|
| 2 | # cidr2: CIDR style IPv4 address and netmask calculator |
|---|
| 3 | # $Id$ |
|---|
| 4 | |
|---|
| 5 | require 'readline' |
|---|
| 6 | |
|---|
| 7 | # |
|---|
| 8 | # Constants |
|---|
| 9 | # |
|---|
| 10 | |
|---|
| 11 | BITLENGTH = 32 |
|---|
| 12 | ALLONE = (2 ** BITLENGTH - 1) |
|---|
| 13 | DEFAULT_IP = "192.168.0.0" |
|---|
| 14 | |
|---|
| 15 | PROMPT = { |
|---|
| 16 | :global => 'cidr> ', |
|---|
| 17 | :expect_subnetmask => '(subnet)> ', |
|---|
| 18 | } |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | # |
|---|
| 22 | # Global Variables |
|---|
| 23 | # |
|---|
| 24 | |
|---|
| 25 | $flag_full = false |
|---|
| 26 | $flag_verbose = 0 |
|---|
| 27 | $flag_dump = false |
|---|
| 28 | $address = [] |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | ########################################################################### |
|---|
| 33 | # Procedures |
|---|
| 34 | ########################################################################### |
|---|
| 35 | |
|---|
| 36 | # |
|---|
| 37 | # show usage |
|---|
| 38 | # |
|---|
| 39 | def usage |
|---|
| 40 | print <<-"EOD".gsub(/^ /, '') |
|---|
| 41 | cidr2: CIDR style IPv4 address and netmask calculator |
|---|
| 42 | |
|---|
| 43 | Usage: #{File.basename($0)} [-d [-f]] [IP_adress]/<prefix_length> |
|---|
| 44 | Options: |
|---|
| 45 | -d switch to fping mode (dump IP addresses) |
|---|
| 46 | -f sub-option of -d. dump with broadcast/network address. |
|---|
| 47 | |
|---|
| 48 | Examples: |
|---|
| 49 | [Command line mode] |
|---|
| 50 | Basic usage |
|---|
| 51 | % cidr2 192.168.255.240/28 |
|---|
| 52 | % cidr2 192.168.255.240/255.255.255.240 |
|---|
| 53 | |
|---|
| 54 | IP address is ommitable (implies "192.168.0.0"). |
|---|
| 55 | % cidr2 /28 |
|---|
| 56 | |
|---|
| 57 | Cisco IOS style netmask (wildcard mask) capable. |
|---|
| 58 | % cidr2 192.168.255.240/0.0.0.15 |
|---|
| 59 | |
|---|
| 60 | The slash is ommitable when you specifiy IP address and mask both. |
|---|
| 61 | % cidr2 192.168.255.240 0.0.0.15 |
|---|
| 62 | |
|---|
| 63 | It parse arguments generously. All of below are acceptable: |
|---|
| 64 | % cidr2 192.168.255.240 /28 |
|---|
| 65 | % cidr2 blahblahblah 192.168.255.240 foobarbaz /28 |
|---|
| 66 | % cidr2 access-list 10 deny 192.168.255.240 0.0.0.15 |
|---|
| 67 | |
|---|
| 68 | [Interactive mode] |
|---|
| 69 | Basic usage |
|---|
| 70 | % cidr2 |
|---|
| 71 | cidr2> 172.31.255.252 /30 |
|---|
| 72 | |
|---|
| 73 | It memorize last passed IP address. |
|---|
| 74 | cidr2> /29 (=> process as "172.31.255.252/29") |
|---|
| 75 | cidr2> /255.255.255.248 (=> same as above) |
|---|
| 76 | cidr2> /0.0.0.7 (=> same as above) |
|---|
| 77 | cidr2> /16 (=> process as "172.31.255.252/16") |
|---|
| 78 | |
|---|
| 79 | It parse arguments generously. |
|---|
| 80 | cidr2> IP address 192.168.0.0 is mine! don't use in your network! |
|---|
| 81 | (subnet)> /24 (=> ask subnet mask if you don't specified) |
|---|
| 82 | EOD |
|---|
| 83 | |
|---|
| 84 | exit(1) |
|---|
| 85 | end |
|---|
| 86 | |
|---|
| 87 | # |
|---|
| 88 | # IP address <=> String conversion |
|---|
| 89 | # |
|---|
| 90 | def str2ipaddr(str, delim = '.') |
|---|
| 91 | ipaddr = 0 |
|---|
| 92 | str.split(delim).each {|o| ipaddr *= 256; ipaddr += o.to_i} |
|---|
| 93 | |
|---|
| 94 | return ipaddr |
|---|
| 95 | end |
|---|
| 96 | |
|---|
| 97 | def ipaddr2str(ipaddr, delim = '.') |
|---|
| 98 | octet = [] |
|---|
| 99 | for i in 0..3 |
|---|
| 100 | addr = ipaddr |
|---|
| 101 | addr = addr >> (i * 8) |
|---|
| 102 | addr = addr & 255 |
|---|
| 103 | |
|---|
| 104 | octet.unshift(addr) |
|---|
| 105 | end |
|---|
| 106 | |
|---|
| 107 | str = octet.join(delim) |
|---|
| 108 | return str |
|---|
| 109 | end |
|---|
| 110 | |
|---|
| 111 | # |
|---|
| 112 | # Token class |
|---|
| 113 | # |
|---|
| 114 | class Token |
|---|
| 115 | def initialize(func) |
|---|
| 116 | @buf = [] |
|---|
| 117 | @func = func |
|---|
| 118 | end |
|---|
| 119 | def get(mode) |
|---|
| 120 | if @buf.size == 0 then |
|---|
| 121 | line = @func.call(mode) |
|---|
| 122 | if line.nil? then |
|---|
| 123 | return nil |
|---|
| 124 | end |
|---|
| 125 | |
|---|
| 126 | @buf = line.chomp.strip.split(/\s+/) |
|---|
| 127 | @buf.push "" |
|---|
| 128 | end |
|---|
| 129 | return @buf.shift |
|---|
| 130 | end |
|---|
| 131 | def push(token) |
|---|
| 132 | @buf.unshift(token) |
|---|
| 133 | self |
|---|
| 134 | end |
|---|
| 135 | end |
|---|
| 136 | |
|---|
| 137 | # |
|---|
| 138 | # Parser |
|---|
| 139 | # |
|---|
| 140 | def parseinput(mode, funcinput) |
|---|
| 141 | buf = funcinput.get(mode) |
|---|
| 142 | |
|---|
| 143 | case mode |
|---|
| 144 | when :global |
|---|
| 145 | case buf |
|---|
| 146 | when /^(\d+\.\d+\.\d+\.\d+)?\/([0-9a-fA-Fx.]+)$/ |
|---|
| 147 | ipaddr = $1 |
|---|
| 148 | mask = $2 |
|---|
| 149 | result(ipaddr, mask) |
|---|
| 150 | when /^(\d+\.\d+\.\d+\.\d+)$/ |
|---|
| 151 | ipaddr = $1 |
|---|
| 152 | result(ipaddr, parseinput(:expect_subnetmask, funcinput)) |
|---|
| 153 | when /^\/([0-9a-fA-Fx.]+)$/ |
|---|
| 154 | mask = $1 |
|---|
| 155 | result(nil, mask) |
|---|
| 156 | when nil |
|---|
| 157 | return nil |
|---|
| 158 | end |
|---|
| 159 | return(parseinput(:global, funcinput)) |
|---|
| 160 | |
|---|
| 161 | when :expect_subnetmask |
|---|
| 162 | case buf |
|---|
| 163 | when /^\/?(\d+\.\d+\.\d+\.\d+)$/ |
|---|
| 164 | # dotted decimal |
|---|
| 165 | return $1 |
|---|
| 166 | when /^\/?(\d+)$/ |
|---|
| 167 | # length |
|---|
| 168 | return $1 |
|---|
| 169 | when /^\/?(0x[0-9a-fA-F]+)$/ |
|---|
| 170 | # hex |
|---|
| 171 | return $1 |
|---|
| 172 | when /^\./, /^end$/i |
|---|
| 173 | return nil |
|---|
| 174 | when nil |
|---|
| 175 | return nil |
|---|
| 176 | end |
|---|
| 177 | return(parseinput(:expect_subnetmask, funcinput)) |
|---|
| 178 | end |
|---|
| 179 | end |
|---|
| 180 | |
|---|
| 181 | # |
|---|
| 182 | # show result |
|---|
| 183 | # |
|---|
| 184 | def result(ipaddr, prefixlen) |
|---|
| 185 | if ipaddr.nil? then |
|---|
| 186 | if $recent_ip then |
|---|
| 187 | ipaddr = $recent_ip |
|---|
| 188 | else |
|---|
| 189 | ipaddr = DEFAULT_IP.dup |
|---|
| 190 | end |
|---|
| 191 | end |
|---|
| 192 | $recent_ip = ipaddr |
|---|
| 193 | return if prefixlen.nil? |
|---|
| 194 | |
|---|
| 195 | case prefixlen |
|---|
| 196 | when /^0x/ |
|---|
| 197 | # hex |
|---|
| 198 | prefixlen = prefixlen.hex |
|---|
| 199 | l = 0 |
|---|
| 200 | for i in 0...BITLENGTH |
|---|
| 201 | if (prefixlen[i] === 1) then |
|---|
| 202 | l = i |
|---|
| 203 | break |
|---|
| 204 | end |
|---|
| 205 | end |
|---|
| 206 | |
|---|
| 207 | prefixlen = BITLENGTH - l |
|---|
| 208 | when /\./ |
|---|
| 209 | # dotted decimal (inverse or in-order mask) |
|---|
| 210 | prefixlen = str2ipaddr(prefixlen) |
|---|
| 211 | if (prefixlen[BITLENGTH - 1] === 0) then |
|---|
| 212 | # make inverse mask into in-order mask |
|---|
| 213 | prefixlen = ALLONE ^ prefixlen |
|---|
| 214 | end |
|---|
| 215 | |
|---|
| 216 | l = 0 |
|---|
| 217 | for i in 0...BITLENGTH |
|---|
| 218 | if (prefixlen[i] === 1) then |
|---|
| 219 | l = i |
|---|
| 220 | break |
|---|
| 221 | end |
|---|
| 222 | end |
|---|
| 223 | |
|---|
| 224 | prefixlen = BITLENGTH - l |
|---|
| 225 | else |
|---|
| 226 | # numeric (length) |
|---|
| 227 | prefixlen = prefixlen.to_i |
|---|
| 228 | end |
|---|
| 229 | |
|---|
| 230 | |
|---|
| 231 | mask = (ALLONE & ~(2 ** (BITLENGTH - prefixlen) - 1)) |
|---|
| 232 | size = 2 ** (BITLENGTH - prefixlen) |
|---|
| 233 | |
|---|
| 234 | ipaddr = str2ipaddr(ipaddr) |
|---|
| 235 | networkaddr = ipaddr & mask |
|---|
| 236 | broadcastaddr = networkaddr + size - 1 |
|---|
| 237 | |
|---|
| 238 | |
|---|
| 239 | if (!$flag_dump) then |
|---|
| 240 | print "IP address : ", ipaddr2str(ipaddr), "\n" |
|---|
| 241 | print "Netmask : ", ipaddr2str(mask), |
|---|
| 242 | " (", ipaddr2str(ALLONE ^ mask), ")", "\n" |
|---|
| 243 | print "Prefix length : /", prefixlen, "\n" |
|---|
| 244 | print "Network addr. : ", ipaddr2str(networkaddr), "\n" |
|---|
| 245 | print "Broadcast : ", ipaddr2str(broadcastaddr), "\n" |
|---|
| 246 | print "# of IP addr. : ", size, " (#{size - 2})", "\n\n" |
|---|
| 247 | end |
|---|
| 248 | |
|---|
| 249 | if ($flag_dump) then |
|---|
| 250 | for i in networkaddr..broadcastaddr |
|---|
| 251 | next if !$flag_full && i === networkaddr |
|---|
| 252 | next if !$flag_full && i === broadcastaddr |
|---|
| 253 | |
|---|
| 254 | print ipaddr2str(i), "\n" |
|---|
| 255 | end |
|---|
| 256 | end |
|---|
| 257 | end |
|---|
| 258 | |
|---|
| 259 | ########################################################################### |
|---|
| 260 | # Main |
|---|
| 261 | ########################################################################### |
|---|
| 262 | |
|---|
| 263 | commandline = [] |
|---|
| 264 | ARGV.each do |arg| |
|---|
| 265 | case arg |
|---|
| 266 | when /^-d/ |
|---|
| 267 | $flag_dump = true |
|---|
| 268 | when /^-f/ |
|---|
| 269 | $flag_full = true |
|---|
| 270 | when /^-h/ |
|---|
| 271 | usage; exit |
|---|
| 272 | when /^-v/ |
|---|
| 273 | $flag_verbose += 1 |
|---|
| 274 | else |
|---|
| 275 | commandline.push arg |
|---|
| 276 | end |
|---|
| 277 | end |
|---|
| 278 | |
|---|
| 279 | if commandline.size > 0 then |
|---|
| 280 | # commandline mode |
|---|
| 281 | parseinput(:global, Token.new(lambda {|dummy| commandline.shift })) |
|---|
| 282 | else |
|---|
| 283 | # interactive mode |
|---|
| 284 | parseinput(:global, |
|---|
| 285 | Token.new(lambda {|mode| |
|---|
| 286 | Readline.readline(PROMPT[mode], true) })) |
|---|
| 287 | end |
|---|