一个 IPv4 网段聚合的实现:
- 效果: 将多个相同掩码长度的网段,聚合为尽量少的网段,这里,尽量避免了歧义,所以是 100% 正反向匹配;
- 注意: 如果是不同掩码长度的网段聚合,可以先分类或将网段展开为同掩码长度的多个子网。
from socket import inet_ntoa, inet_aton
from struct import pack, unpack
class IPv4RouteAgg(object):
def __init__(self, mask: int, nets=None):
if nets is None:
nets = []
self.mask_len = mask
self.mask_bits = IPv4Tools.mask_int_fmt(mask)
self.nets = [IPv4Tools.ip_int_fmt(net) & self.mask_bits for net in nets]
def add_net(self, net):
self.nets.append(IPv4Tools.ip_int_fmt(net))
def get_agg(self):
same_part = self._get_same_part()
diff_len = self.mask_len - same_part[0]
if diff_len > 18:
raise ValueError("nets diff part is too long (more than 18), please classify before agg.")
net_group = NetGroup(diff_len=self.mask_len - same_part[0], start=same_part[1] >> 32 - self.mask_len)
for net in self.nets:
net_group.match(net >> 32 - self.mask_len)
nets = []
filled_ngs = net_group.return_filled()
for ng in filled_ngs:
nets.append(f"{IPv4Tools.ipv4_fmt(ng.start << 32 - self.mask_len)}/{self.mask_len - ng.diff_len}")
return nets
def _get_same_part(self):
for shorter_len in range(self.mask_len, 0, -1):
is_same_and_prefix = self._same_on_this_mask(shorter_len)
if is_same_and_prefix[0]:
return shorter_len, is_same_and_prefix[1]
def _same_on_this_mask(self, mask_len):
mask_bit = IPv4Tools.mask_int_fmt(mask_len)
same_prefix = self.nets[0] & mask_bit
for net in self.nets[1:]:
if same_prefix ^ (net & mask_bit):
return False, same_prefix
return True, same_prefix
class NetGroup(object):
def __init__(self, diff_len, start):
self.diff_len = diff_len
self.start = start
self.end = start + 2 ** diff_len - 1 if diff_len > 0 else self.start
self._fill = False
if diff_len == 0:
self.data = start
return
self.data = [NetGroup(diff_len - 1, start), NetGroup(diff_len - 1, start + 2 ** (diff_len - 1))]
@property
def fill(self):
if isinstance(self.data, int):
return self._fill
return all([self.data[0].fill, self.data[1].fill])
def match(self, value):
if self.start <= value <= self.end:
if isinstance(self.data, int):
self._fill = True
elif not self.data[0].match(value):
self.data[1].match(value)
return True
else:
return False
def return_filled(self):
if self.fill:
return [self]
if isinstance(self.data, int):
return []
nps = []
nps.extend(self.data[0].return_filled())
nps.extend(self.data[1].return_filled())
return nps
def __repr__(self):
return f"{self.start}" if self.start == self.end else f"{self.start}-{self.end}"
class IPv4Tools:
@staticmethod
def mask_int_fmt(mask_len):
return 0xffffffff ^ (1 << 32 - mask_len) - 1
@staticmethod
def mask_len_fmt(mask_int):
return 32 - len(bin(0xffffffff ^ mask_int)[2:])
@staticmethod
def ip_int_fmt(ip_in_str: str):
return unpack("!I", inet_aton(ip_in_str))[0]
@staticmethod
def ipv4_fmt(ip_in_int: int):
return inet_ntoa(pack(">I", ip_in_int))
if __name__ == '__main__':
ra = IPv4RouteAgg(nets=["192.168.0.0", "192.168.1.0", "192.168.2.0", "192.168.3.0", "192.168.4.0", "192.0.100.0"], mask=24)
for i in range(23, 200):
ra.add_net(f"192.168.{i}.0")
print(ra.get_agg())