【マスクとアドレスと論理式】pythonでのbit演算でIPv4を計算する

何コレ?

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import socket
import struct
import re

type1 = re.compile(ur'([\d\.]+)\/(\d+)') # 192.168.0.1/24
type2 = re.compile(ur'([\d\.]+)\s+([\d\.]+)') # 192.168.0.1/ 255.255.255.0

acl_serach_max = 8192
acl_search_ip_max = 4

def aton(ipstr):
	return struct.unpack(r'!l', socket.inet_aton(ipstr))[0]

def ntoa(ip):
	return socket.inet_ntoa(struct.pack(r'!l', ip))

def net_addr(ip_addr, subnet):
	return (ip_addr & subnet)

def bcast_addr(ip_addr, subnet):
	return ip_addr | ~subnet

def bit_mask(bit):
	if 32 <= bit:
		bin = r'0'
	elif bit <= 0:
		bin = r'1' * 32
	else:
		bin = r'1' * (32 - bit)
	return ~int(bin, 2)

def check_subnet(ip_addr, subnet, ip):
	if ((ip & subnet) ^ (ip_addr & subnet)) == 0:
		return True
	else:
		return False

def check_wildcard(ip_addr, wildcard, ip):
	if ((ip & ~wildcard) ^ (ip_addr & ~wildcard)) == 0:
		return True
	else:
		return False

def count_wildcard(ip_start, ip_end, wildcard):
	if not bin(wildcard).lstrip(r'0b').find(r'0') == -1:
		i = 0
		ip_addrs = []
		for ip in xrange(ip_start, ip_end+1):
			if check_wildcard(ip_start, wildcard, ip):
				ip_addrs.append(ntoa(ip))
			i += 1
			if i > acl_serach_max or len(ip_addrs) >= acl_search_ip_max:
				ip_addrs.append(r"...")
				break
		return ip_addrs
	else:
		return (ip_end - ip_start) + 1

def parse(ip, mask):
	if 0 < mask:
		# net_addr
		wildcard_start = ip & ~mask
		# bcast_addr
		wildcard_end = ip | mask
		print r'ACL',
		print ntoa(ip), r'->', ntoa(wildcard_start), r'-', ntoa(wildcard_end), count_wildcard(wildcard_start, wildcard_end, mask)
	else:
		if check_subnet(aton(r'10.0.0.0'), aton(r'255.0.0.0'), ip):
			print r'PrivateA',
		elif check_subnet(aton(r'172.16.0.0'), aton(r'255.240.0.0'), ip):
			print r'PrivateB',
		elif check_subnet(aton(r'192.168.0.0'), aton(r'255.255.0.0'), ip):
			print r'PrivateC',
		elif check_subnet(aton(r'169.254.0.0'), aton(r'255.255.0.0'), ip):
			print r'APIPA',
		elif check_subnet(aton(r'224.0.0.0'), aton(r'240.0.0.0'), ip):
			print r'Multicast',
		else:
			print r'Global',
		net = net_addr(ip, mask)
		bcast = bcast_addr(ip, mask)
		print ntoa(ip), r'->', ntoa(net), r'/', ntoa(mask), ntoa(bcast), (bcast - net) + 1

def entry():
	print r'[[ IP Range 1.0.0 ]]'
	for line in iter(sys.stdin.readline, r''):
		try:
			line = line.strip()
			rr = type1.findall(line)
			for sIp, sMask in rr:
				ip = aton(sIp)
				mask = bit_mask(int(sMask))
				parse(ip, mask)
			line  = type1.sub(r'', line)
			rr = type2.findall(line)
			for sIp, sMask in rr:
				ip = aton(sIp)
				mask = aton(sMask)
				parse(ip, mask)
		except socket.error, e:
			print 'Syntax Error:', e
		except (ee, e):
			print 'Error:', ee, e

if __name__ == '__main__':
	reload(sys)
	sys.setdefaultencoding(r'utf-8')
	entry()

簡単に言えばIPアドレスのレンジを計算してくれる計算機だ。
もちろんpythonで書いた。
いやー、持ち込み何でも可っていうのはイイネ!

どんな計算機?

ネットワークアドレス、サブネットマスク、ブロードキャスト、IPの数がスパッとわかる。

[[ IP Range 1.0.0 ]]
192.168.10.245/24[エンター]
PrivateC 192.168.10.245 -> 192.168.10.0 / 255.255.255.0 192.168.10.255 256
172.16.32.254/12[エンター]
PrivateB 172.16.32.254 -> 172.16.0.0 / 255.240.0.0 172.31.255.255 1048576

もちろん複数行や表記ゆれ、CIDRにも対応

10.0.0.10/16 192.168.7.5 255.255.255.248 172.23.12.5/24[エンター]
PrivateA 10.0.0.10 -> 10.0.0.0 / 255.255.0.0 10.0.255.255 65536
PrivateB 172.23.12.5 -> 172.23.12.0 / 255.255.255.0 172.23.12.255 256
PrivateC 192.168.7.5 -> 192.168.7.0 / 255.255.255.248 192.168.7.7 8

しかもACLワイルドカードマスクまで計算してくれるスグレもの

192.168.0.0 0.0.0.255 192.168.32.14 0.0.255.255[エンター]
ACL 192.168.0.0 -> 192.168.0.0 - 192.168.0.255 256
ACL 192.168.32.14 -> 192.168.0.0 - 192.168.255.255 65536

マスクとアドレスと論理式

そもそもアドレスだのマスクだのパソコンの都合がいい値を無理に表示して192.168.1.1だの255.255.255.0だのしてる訳であってIPv6の省略記法なんてすぐには覚えれそうに無イネ!

それでもアドレスとサブネットマスクからネットワークアドレスを求めてみる

まずは2進数で計算だ。そんな簡単な事を言うがHaskhellとかSchemeをやってそうな関数型言語を扱う高等民族とかとは別の至高なプログラマバイナリアンにしか直ぐには計算できない芸当であろう。

    10101100.00010000.00011100.11101011 # 172.16.28.235(IPアドレス)
AND 11111111.11111111.11111111.11111000 # 255.255.255.248(サブネット)
---------------------------------------
    10101100.00010000.00011100.11101000 # 172.16.28.232(ネットワーク)

    network_address = ip_address & subnetmask # プログラムで表すと…

ANDで計算すればサクっとできる!そんな簡単な事を言うが(略)

ブロードキャストは?
    10101100.00010000.00011100.11101011 # 172.16.28.235(IPアドレス)
OR  00000000.00000000.00000000.00000111 # 0.0.0.7(サブネットのインバース)
---------------------------------------
    10101100.00010000.00011100.11101111 # 172.16.28.239(ブロードキャスト)
    
    broadcast_address = ip_address | ~subnetmask # プログラムで表すと…

サブネットマスクをNOTでインバース(0と1を反転したもの)とし、IPアドレスとORしたものがブロードキャストだ。
しかし馬鹿な俺は最初にプログラムで書いたのが…

network_address = ip_address & subnetmask
broadcast_address = network_address + ~subnetmask

まだ俺は至高民族バイナリアンにはなれそうにないです。

式と範囲とワイルドカード

ワイルドカードマスクってなんぞや?簡単に言わせればワイルドカードマスクはサブネットマスクのインバースだ。(嘘)よってワイルドカードマスクの範囲はワイルドカードマスクを反転したものをサブネットとしてネットワークアドレスから始まりでブロードキャストアドレスで終わる簡単な物だ。(嘘)

172.16.28.235 0.0.0.7[エンター]
ACL 172.16.28.235 -> 172.16.28.232 - 172.16.28.239 8
172.16.28.235 0.0.3.255[エンター]
ACL 172.16.28.235 -> 172.16.28.0 - 172.16.31.255 1024

だがちょっと待って欲しい。ワイルドカードをもっと知るためにはサブネットマスクについてもっと知っておく必要がある。

10000000.00000000.00000000.00000000 # /1
                ...
11111111.00000000.00000000.00000000 # /8
                ...
11111111.11111111.11111111.00000000 # /24
                ...
11111111.11111111.11111111.11111000 # /29 255.254.255.248

左から右に1が増えていく…

サブネットマスクの最小クラスは/1である。そこから左から右に向かって埋まっていく。よって32ビット符号付き整数で表すとサブネットマスクは必ずマイナス値である。その符号の部分がサブネットマスクで表した時の128.000.000.000の128の部分になる。ここで重要な事が一つある。上の表を見て気づいたかもしれないが、サブネットマスクは1で埋まったところに0が紛れることは絶対に起きない。

11111111.11111111.11111111.11111 0 10 # 255.255.255.250 このような値にもならない。
                                ↑
11111111.1111111 0 .11111111.11111000
                ↑絶対に0が紛れるようなことは起きない。

サブネットマスクでは以上のようなケースは絶対に起きないということだ。しかしワイルドカードマスクは違う、もう入らないなんてもんじゃない。超入る。とにかくワイルドカードマスクのヤバさをもっと知るべきだと思います。(この部分を書きたいためにこの記事書きました。)

ワイのワイルドカードワイバーンや!

ということは以下のようなワイルドカードマスクも設定できるのである。

00000000.00000000.00000001.00001000 # 0.0.1.4
                            ↑ 1と1の間に0が挟まれている

以上のワイルドカードマスクを見て、もし(192.168.1.0 0.0.1.4)とかいう設定をACLでみしまったときアナタはどうするだろう。192.168.0.0〜192.168.1.4とかいうアナタは痛い目にみるかもしれない。正解は(192.168.0.0, 192.168.0.4, 192.168.1.0, 192.168.1.4)の4つだけである。

ACLのやっている処理を探る
    11000000.10101000.00000001.00000000 # 192.168.1.0(IPアドレス)
AND 11111111.11111111.11111110.11110111 # 0.0.1.4(ワイルドカードのインバース)
---------------------------------------
    11000000.10101000.00000000.00000000 # 192.168.0.0

まずACLの範囲はACLに設定されたIPアドレスワイルドカードのインバースにANDを施したアドレスを求める。(実質、指定したIPアドレスのネットワークアドレスを求めたモノと同じ)

    11000000.10101000.00000001.00000011 # 192.168.1.3(192.168.0.0〜192.168.1.4の間にありそうなIP)
AND 11111111.11111111.11111110.11110111 # 0.0.1.4(ワイルドカードのインバース)
---------------------------------------
    11000000.10101000.00000000.00000011 # 192.168.0.3

次にチェックされるIPアドレスに同じようにワイルドカードのインバースをANDで施したアドレスを求め、上で求めたアドレスと比較し合致するか合致しないかを判定するのだ。(実質的に、上のアドレスとネットワークアドレスが違ってしまった!これでは別ネットワークと判断される)

わかりやすく…?
最初の処理でしているのは…
11000000.10101000.00000001.00000000 # 192.168.1.0(IPアドレス)
00000000.00000000.00000001.00001000 # 0.0.1.4(ワイルドカード)
11000000.10101000.0000000*.0000*000 # *の部分だけ無視することができる
そしてチェックするIPと比べてみると…
11000000.10101000.00000001.00000011 # 192.168.1.3
11000000.10101000.0000000*.0000*000 # 
                                 ↑*以外の部分が違っている!!

2つのビットの比較はコンピュータではXORでサクッと調べることが出来る。0が帰ってきたら正解だ。

((ip & subnet) ^ (ip_addr & subnet)) # サブネットマスクによる同一ネットワークのチェック
((ip & ~wildcard) ^ (ip_addr & ~wildcard)) # ワイルドカードによるチェック
偶数だけ、奇数だけのACLの種明かし

そんな狂気じみたACLを設定して視認性が〜とかなる前にこれを応用したのが奇数だけ偶数だけのACLの設定方法である。

11000000.10101000.00000001.00000000 # 192.168.1.0(IPアドレス、最後が偶数でビットは0)
00000000.00000000.00000000.11111110 # 0.0.0.254(ワイルドカード)
11000000.10101000.00000001.*******0 # チェックするIPの最後のビットが0の時だけ判定される
                              ↑*の部分の判定は行われない。

最後のビットが0かどうかが判断され、また最後のビットが0の値は偶数だ。

11000000.10101000.00000001.00000001 # 192.168.1.1(IPアドレス、最後が偶数でビットは0)
00000000.00000000.00000000.11111110 # 0.0.0.254(ワイルドカード)
11000000.10101000.00000001.*******1 # チェックするIPの最後のビットが1の時だけ判定される

また最後のビットが1の値はなのは偶数だけである。以上でワイルドカードサブネットマスクのインバースでなくフラグとしてのマスクであることを理解してくれれば嬉しい。

最後に

この記事は現在CCNAも取ってない状態で記述されました(´・ω・`)