Thursday, July 23, 2009

netmask 255.255.255.0 -- why doesn't this work?

So "Ulrich" was having trouble bringing networking up, and he showed me his network parameters:
ip address: 10.53.10.121 
default gateway: 10.53.0.1
netmask: 255.255.255.0
Yow! That will never work, because the netmask won't let him get to the default gateway!

What does all this stuff mean, how could anyone tell, and more importantly what is the correct netmask to use?

Here's a little theory, then I'll give you the lazy man's way. The IP address is a sequence of 32 bits, represented usually as four 8-bit bytes rendered in decimal and separated by dots. When I want to send something to another "node", I compare its IP address with mine in conjunction with the netmask. If it matches, I just send it on my local LAN. If it doesn't match, I use the default gateway. (You can have other gateways, but for now we're just talking about the default gateway.)

Let's say that 10.53.10.121 wants to talk to 10.53.10.64, with the netmask 255.255.255.0 shown above. The comparison looks like this:
address bits
10.53.10.12100001010001101010000101001111001
10.53.10.6400001010001101010000101001000000
difference?--------------------------XXX--X
netmask
255.255.255.0
11111111111111111111111100000000
after mask?--------------------------------
So for 10.53.10.121↔10.53.10.64, the addresses, masked, result in no difference. So we will just send on the local LAN.

But if 10.53.10.121 wants to talk to 10.56.21.92, it looks like this:
address bits
10.53.10.12100001010001101010000101001111001
10.56.21.9200001010001110000001010101011100
difference?------------XX-X---XXXXX--X--X-X
netmask
255.255.255.0
11111111111111111111111100000000
after mask?------------XX-X---XXXXX--------
Even after masking, these don't match. So communicate with 10.56.21.92 we have to use the default gateway. But wait, is that possible? The default gateway is 10.53.0.1, so repeating the conversion and comparison yields:
address bits
10.53.10.12100001010001101010000101001111001
10.53.0.100001010001101010000000000000001
difference?--------------------X-X--XXXX---
netmask
255.255.255.0
11111111111111111111111100000000
after mask?--------------------X-X---------
Even after masking, these still don't match. So we can't send to the default gateway just by sending to it on the local LAN. Instead we have to use a gateway--but waitaminute, this is the only gateway we've got!

What we must do is loosen up the netmask. If we change it to, say, 255.255.240.0, the result will instead look like this:
address bits
10.53.10.12100001010001101010000101001111001
10.53.0.100001010001101010000000000000001
difference?--------------------X-X--XXXX---
netmask
255.255.240.0
11111111111111111111000000000000
after mask?--------------------------------
Behold! After masking, the addresses match, so the IP routing software knows it can send to this gateway.

In other words, the way to figure out a netmask that will work is to do a bit-compare between the IP addresses of the interface and of the default gateway, and see how long a netmask you need in order to mask off the differences. The optimal netmask may be shorter (255.255.224.0 or 255.255.192.0 for example), but this procedure ought to give you one that at least lets you get to the default gateway.

The lazy man's answer

Just run this little script, lines composed while riding the train, umm, like this:
% ./netmask.py 10.53.10.121 10.53.0.1
best case netmask is fffff000 or 255.255.240.0
% cat netmask.py
#!/usr/bin/python -tt
# vim:et:sw=4
'''Given a list of IPv4 addresses, prints the longest netmask
that accommodates them all. Addresses can be in dotted quad
format (e.g. 192.168.0.1, all numbers <=255) or hex (7-8 digits,
optionally preceded by '0x' -- case is ignored).
USE AT YOUR OWN RISK. NO WARRANTY, EXPRESS OR IMPLIED.'''

import re
import string
import sys

def main(argv):
# addresses can be supplied in hex format or dotted-quad
hexpat = re.compile('(?:0x)?([0-9a-f]{7,8})$', re.I)
dqpat = re.compile('(\d+)\.(\d+)\.(\d+)\.(\d+)$')
FULL_MASK = 0xffffffffL # 32 bits
is_first = True
got_error = False # hope for the best
for an_addr in argv:
an_addr = an_addr.strip()

hexmatch = hexpat.match(an_addr) # Hex format?
if hexmatch:
# Yes! That was easy.
the_addr = string.atol(hexmatch.groups()[0], 16)
else:
dqmatch = dqpat.match(an_addr)
if dqmatch: # Better be dotted-quad
the_addr = 0
for a_comp in dqmatch.groups():
if int(a_comp) > 255:
# That's an illegal number
print '*** In "' + an_addr + '":', a_comp, '> 255'
got_error = True
the_addr = (the_addr << 8) + int(a_comp)
else:
print "*** couldn't" ' decode "' + an_addr + '"'
got_error = True
if got_error:
continue
if is_first:
# Initialize...
to_match = the_addr
the_mask = FULL_MASK
is_first = False
else:
# It's the (2nd or more) time through. to_match has the
# bits in common so far (possibly just the 1st address).
# the_mask holds the longest netmask that "works" so far.
# On entry: to_match & the_mask == to_match
# When loop is done: to_match & the_mask = the_addr & the_mask
while ((to_match & the_mask) != (the_addr & the_mask)):
the_mask = (the_mask << 1) & FULL_MASK
# Now re-establish entry condition
to_match = to_match & the_mask

# OK, now we've read all the addresses
if got_error:
# Don't process anything, just leave
print 'Not doing anything due to errors above'
sys.exit(1)
if is_first:
# Nothing to do
print '??? No IP addresses supplied.'
sys.exit(0)
# All good!
print 'best case netmask is', ('%08lx' % the_mask),
dqmask = `the_mask >> 24` + '.' + `(the_mask >> 16) & 0xff` + '.' + \
`(the_mask >> 8) & 0xff` + '.' + `the_mask & 0xff`
print 'or', dqmask.replace('L','')
sys.exit(0)


if __name__ == '__main__':
main(sys.argv[1:])
%
That's it! You can snarf'n'barf the shaded part above and save it to a file on your Linux/UNIX™/Mac™ computer. You might need to adjust the first line (it works on Mac OS X 10.4 Tiger). Don't forget to set execute permissions on the script.

Enjoy!

No comments: