Что значит внешние IP-подсети? По-научному это подсети, содержащие IP-адреса глобально доступных узлов, или, как их называют в простонародье, подсети «белых IP-адресов».
Получить искомый перечень можно разными способами:
Взять список всех публичных автономных систем (ASN) и извлечь из него искомые IP-подсети.
Вычесть из всего адресного пространства (0.0.0.0 – 255.255.255.255) список специальных, глобально недоступных IP-адресов.
Я решил использовать второй способ, как более простой. Дело в том, что специальные адреса определяются стандартами, которые практически неизменны, а список и содержимое автономных систем изменяются с течением времени. Дело осталось за малым — пробежаться по стандартам и выписать искомые адреса.
Первым попавшимся мне стандартом оказался RFC: 3704 (BCP: 84) Ingress Filtering for Multihomed Networks. В нём указано, что интернет-провайдеры не должны маршрутизировать в Интернет «марсианские IP-адреса», включающий в себя адреса:
0.0.0.0/8
10.0.0.0/8
127.0.0.0/8
172.16.0.0/12
192.168.0.0/16
224.0.0.0/4
240.0.0.0/4
Хорошо, первый блок специальных адресов получен, но есть ощущение, что это далеко не всё. Название следующего стандарта RFC: 3330 Special-Use IPv4 Addresses обнадёживало, что на нём поиски закончатся. Однако, после прочтения мне удалось пополнить список совсем немного:
169.254.0.0/16
192.0.2.0/24
192.88.99.0/24
198.18.0.0/15
И тут мне стало грустно. Прочитав два стандарта, я понял, что они содержат лишь небольшие порции зарезервированных адресов, и ни один не является исчерпывающим. Например, ни в первом, ни во втором не было диапазона 100.64.0.0/10 (Carrier grade NAT), который также относится к специальным глобально недоступным адресам.
Я решил, что нужно искать не стандарты, а ресурс, где были бы приведены сразу все немаршрутизируемые IP-адреса. Благо, такой ресурс нашёлся. IANA выпускает и поддерживает полный перечень IP-адресов специального назначения (Рисунок 1).

Это именно то, что нужно. Здесь интересующие меня адреса отмечены, как «Globally Reachable: FALSE».
Но, как обычно, не обошлось без нескольких ложек дёгтя.
Сеть 192.0.0.0/24 является глобально недоступной с двумя исключениями: 192.0.0.9/32 (Port Control Protocol Anycast) и 192.0.0.10/32 (Traversal Using Relays around NAT Anycast).
Список не содержит подсеть 224.0.0.0/4 зарезервированную (RFC: 1112 Host Extensions for IP Multicasting) под многоадресный трафик.
Соответственно, передо мной встал вопрос, что делать с этими адресами. С одной стороны, RFC указывает, что 192.0.0.9 и 192.0.0.10 являются глобально доступными, с другой стороны, они являются служебными и, соответственно, ни один реальный сервер в Интернете не должен иметь их в качестве адреса сетевого интерфейса. Такая же проблема и с многоадресным трафиком. Он может ограничиваться только локальной сетью, а может и выходить за её пределы.
Осознавая тот факт, что красивого универсального решения тут быть не может, я решил:
Не делать исключений для 192.0.0.9 и 192.0.0.10 и всю подсеть 192.0.0.0/24 считать глобально недоступной.
Подсеть многоадресной рассылки 224.0.0.0/4 также считать глобально недоступной.
Источник данных я нашёл, войну с перфекционизмом провёл, теперь нужно заняться расчётами. Поигравшись полчаса, я понял, что строить искомый перечень вручную — путь в никуда. Во-первых, очень трудоёмко, во-вторых, могут появиться ошибки. Следовательно, нужно автоматизировать.
Для этого я написал Python3 библиотеку ipaddressspace, опирающуюся на стандартную ipadrress, и позволяющую проводить математические операции над пространствами IP-адресов с учётом особенностей алгебры множеств.
ipaddressspace.py
#!/usr/bin/env python3
"""IPv4 address space maniplation toolkit.
Features:
1. Converting IP address ranges to and from IP networks
2. Addition and subtraction of IP address ranges and address spaces
#Example 1. Show start and end IP addresses for an IP network
r = IPv4Range(ipaddress.IPv4Network("192.137.234.16/31"))
print(r)
#Output:
#192.137.234.16 - 192.137.234.17
#Example 2. Show IP networks covering a range of IP addresses
r = IPv4Range(ipaddress.IPv4Address("1.0.0.1"),
ipaddress.IPv4Address("1.0.0.8"))
for net in r.networks():
print(net)
#Output:
#1.0.0.1/32
#1.0.0.2/31
#1.0.0.4/30
#1.0.0.8/32
#Example 3. Addition of address spaces
s1 = IPv4AddressSpace()
s1.append(ipaddress.IPv4Network("1.0.0.0/27"))
s1 += IPv4Range(ipaddress.IPv4Address("1.0.0.33"),
ipaddress.IPv4Address("1.0.0.255"))
print(s1)
#Output:
#1.0.0.0 - 1.0.0.31
#1.0.0.33 - 1.0.0.255
s1 += ipaddress.IPv4Address("1.0.0.32")
print(s1)
#Output
#1.0.0.0 - 1.0.0.255
# Example 4. Calculation of the difference between address spaces
s2 = IPv4AddressSpace(ipaddress.IPv4Network("0.0.0.0/0"))
s2.remove(ipaddress.IPv4Network("192.168.0.0/16"))
print(s2)
#Output:
#0.0.0.0 - 192.167.255.255
#192.169.0.0 - 255.255.255.255
s3 = IPv4AddressSpace(ipaddress.IPv4Network("0.0.0.0/0"))
s3.remove(ipaddress.IPv4Network("10.0.0.0/8"))
print(s3)
#Output:
#0.0.0.0 - 9.255.255.255
#11.0.0.0 - 255.255.255.255
s4 = s2-s3
print(s4)
#Output:
#10.0.0.0 - 10.255.255.255
#Example 5. Show IP networks spanning the IP address space
s5 = IPv4AddressSpace(ipaddress.IPv4Network("0.0.0.0/0"))
s5 -= ipaddress.IPv4Network("10.0.0.0/8")
s5 -= ipaddress.IPv4Network("64.0.0.0/2")
print(s5)
#Output
#0.0.0.0 - 9.255.255.255
#11.0.0.0 - 63.255.255.255
#128.0.0.0 - 255.255.255.255
for net in s5.networks():
print(net)
#Output
#0.0.0.0/5
#8.0.0.0/7
#11.0.0.0/8
#12.0.0.0/6
#16.0.0.0/4
#32.0.0.0/3
#128.0.0.0/1
Licensed under the MIT License (https://opensource.org/license/mit).
(c) 2025, imbasoft.ru
"""
import ipaddress
class IPv4Range:
"""A range of IPv4 addresses defined by a start IP and an end IP address.
The object is immutable after creation, changing attributes is not allowed.
Attributes:
IP_ADDRESS_BITS_COUNT (int): a constant that specifies the number
of bits in an IPv4 address
IP_ADDRESS_MAX_VALUE (int): a constant that specifies int form of
max IPv4 address (255.255.255.255)
start_ip (ipaddress.IPv4Address) - start address of range. Must be
always <= end_ip. Can't be None.
end_ip (ipaddress.IPv4Address) - end address of range. Must be
always => start_ip. Can't be None.
"""
IP_ADDRESS_BITS_COUNT = 32
IP_ADDRESS_MAX_VALUE = (1 << IP_ADDRESS_BITS_COUNT) - 1
def __init__(self, *args):
"""
Object constrution overloaded with args.
Raises:
ValueError: if the arguments are wrong: invalid types, start_ip >
end_ip and etc.
"""
# one argument can be only ipaddress.IPv4Network
if 1 == len(args):
ip_subnet = args[0]
if not isinstance(ip_subnet, ipaddress.IPv4Network):
raise ValueError(
"Single argument must be an IP address.IPv4Network"
)
self.__start_ip = ip_subnet.network_address
self.__end_ip = ip_subnet.broadcast_address
# two arguments: first is start IP address, second last IP address
elif 2 == len(args):
if not isinstance(args[0], ipaddress.IPv4Address):
raise ValueError(
"First paramater must be an ipaddress.IPv4Address"
)
if not isinstance(args[1], ipaddress.IPv4Address):
raise ValueError(
"Second paramater must be an ipaddress.IPv4Address"
)
if args[0] > args[1]:
raise ValueError(
"First paramater must be less than or equal to Second"
)
self.__start_ip = args[0]
self.__end_ip = args[1]
else:
raise ValueError("Incorrect number of parameters")
@property
def start_ip(self):
"""ipaddress.IPv4Address: Start of IP range."""
return self.__start_ip
@property
def end_ip(self):
"""ipaddress.IPv4Address: End of IP range."""
return self.__end_ip
def __eq__(self, param):
"""Compare object with IPv4Range, IPv4Network, IPv4Address.
Args:
param (IPv4Range | IPv4Network | IPv4Address):
object to compare with
Raises:
ValueError: if the number or type of parameters is incorrect
"""
if isinstance(param, IPv4Range):
if self.start_ip == param.start_ip and self.end_ip == param.end_ip:
return True
elif isinstance(param, ipaddress.IPv4Address):
if self.start_ip == param and self.end_ip == param:
return True
elif isinstance(param, ipaddress.IPv4Network):
if (
self.start_ip == param.network_address
and self.end_ip == param.broadcast_address
):
return True
else:
raise ValueError(
"Only IPv4Range, IPv4Address, IPv4Network "
"types are allowed to compare"
)
return False
def __str__(self):
"""Print form of object."""
return f"{self.start_ip} - {self.end_ip}"
def __iter__(self):
"""Unpack start_ip and end_ip from object."""
yield self.__start_ip
yield self.__end_ip
def _get_non_zero_bits_positions(self, param):
"""Return non-zero bits positions in parameter.
Args:
param (int): the value in which to find the positions
of non-zero bits.
Return:
(int, int): A tuple, where the first value is the position of
the high-order non-zero bit, and the second is
the position of the low-order non-zero bit.
Examples:
_get_non_zero_bits_positions(0b110) -> (3,2)
"""
low_pos = 0
hi_pos = 0
index = 1
while param != 0:
remainder = param % 2
if remainder != 0:
if 0 == low_pos:
low_pos = index
hi_pos = index
hi_pos = max (hi_pos, index)
param //= 2
index += 1
return (hi_pos, low_pos)
def networks(self):
"""Return a list of IPv4 networks that cover a range of IP addresses.
The list contains a minimum number of the largest IP networks.
Return:
[ipaddress.IPv4Network] - list of IP networks
"""
start_addr = int(self.__start_ip)
end_addr = int(self.__end_ip)
result_list = []
# this is need becouse network must contain last address
# of ip range
end_addr += 1
# start work
current_addr = start_addr
go_next = True
while go_next:
low_bit_pos = self._get_non_zero_bits_positions(current_addr)[-1]
if 0 == low_bit_pos:
low_bit_pos = IPv4Range.IP_ADDRESS_BITS_COUNT + 1
# IPv4Range.__IPAdressBitsCount + 1 needed becouse range() must
# contain IPv4Range.__IPAdressBitsCount as last index
mask_max = IPv4Range.IP_ADDRESS_BITS_COUNT + 1
# A smaller mask covers a larger range of IP addresses.
# The mask cannot be larger than the last non-zero bit in
# the IP address.
mask_min = IPv4Range.IP_ADDRESS_BITS_COUNT + 1 - low_bit_pos
# Looking for an IP mask that most fully covers the specified
# IP range. Starting with a largest available mask (/0, /1, ... )
# and ends with a small one (..., /31, /32)
# If the mask is too big, skip it and go to a smaller one.
for current_mask in range(mask_min, mask_max):
mask_add = IPv4Range.IP_ADDRESS_MAX_VALUE >> current_mask
max_addr = current_addr + mask_add
if (max_addr + 1) == end_addr:
go_next = False
break
if max_addr < end_addr:
break
network_str = (
str(ipaddress.IPv4Address(current_addr)) + "/"
+ str(current_mask)
)
current_addr = max_addr + 1
result_list.append(ipaddress.IPv4Network(network_str))
return result_list
class IPv4AddressSpace:
"""List of IPv4Ranges and manipulation methods.
Attributes:
__ip_ranges ([IPv4Range]): an internal list of IP address ranges
describing the IP address space.
"""
def __init__(self, param=None):
"""Object creation is overloaded with various types of parameters.
Args:
param (ipaddress.IPv4Network | IPv4Range | IPv4AddressSpace):
used to create IP ranges
Raises:
ValueError: for unsupported paramter types
"""
# empty constructor - default
if param is None:
self.clear()
else:
if isinstance(param, ipaddress.IPv4Network):
self.__ip_ranges = [IPv4Range(param)]
elif isinstance(param, IPv4Range):
self.__ip_ranges = [param]
elif isinstance(param, IPv4AddressSpace):
self.__ip_ranges = param.ip_ranges
else:
raise ValueError("Unsupported parameter type")
@property
def ip_ranges(self):
"""[IPv4Range]: Address space.
All IPv4Range's in address space sorted by IP address in ascending
order. There are no overlaps or duplicates here.
Use addition or subtraction to manipulate address space.
"""
return self.__ip_ranges
def _set_ip_ranges(self, param):
"""Private address space setter."""
if isinstance(param, list):
# empty list
if not param:
self.clear()
else:
last_int_ip = 0
for item in param:
# type checking
if not isinstance(item, IPv4Range):
raise ValueError("Invalid item in list")
# IPv4Range's must not overlap and must be
# sorted in ascending order.
if (last_int_ip > int(item.start_ip)
or last_int_ip > int(item.end_ip)):
raise ValueError("IPv4Range not ordered")
# Add 1 because the next range should not overlap
# the previous one.
last_int_ip = int(item.end_ip) + 1
# check passed. assign list
self.__ip_ranges = param
else:
raise ValueError("Unsupported parameter type")
def clear(self):
"""Clear internal list of IPv4Range's."""
self.__ip_ranges = []
def is_empty(self):
"""Check for elements in the IPv4Range list.
Return:
(bool): True if the elements exists, or false if it does not.
"""
if self.__ip_ranges is None:
return True
if 0 == len(self.__ip_ranges):
return True
return False
def __iter__(self):
"""Return iterator of internal IPv4Range list."""
return iter(self.__ip_ranges)
def __str__(self):
"""Show IPv4Range's in printable form."""
result = ""
for ip_range in self.__ip_ranges:
if result:
result += "\n"
result += f"{ip_range}"
return result
def _sum(self, param):
"""Private function for summing address spaces.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. The function returns a list with the minimum number
of largest IPv4Range's. After the function executes, the number of
IPv4Range in the internal list may differ from the returned value.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to add to the internal
list of IPv4Ranges
Returns:
[IPv4Range]: The result of adding IPv4Range's
Raises:
ValueError: if an unsupported parameter type is used
"""
if isinstance(param, ipaddress.IPv4Address):
added_address_space = IPv4AddressSpace(IPv4Range(param, param))
elif isinstance(param, ipaddress.IPv4Network):
added_address_space = IPv4AddressSpace(param)
elif isinstance(param, IPv4Range):
added_address_space = IPv4AddressSpace(param)
elif isinstance(param, IPv4AddressSpace):
added_address_space = param
else:
raise ValueError("Unsupported parameter type")
combinet_list_of_ranges = (self.ip_ranges
+ added_address_space.ip_ranges)
if not combinet_list_of_ranges:
return []
intervals = [
(int(start_ip), int(end_ip))
for start_ip, end_ip in combinet_list_of_ranges
]
# Sort by StartIP and ignorig EndIP
intervals.sort(key=lambda x: x[0])
result_ip_ranges = []
prev_int_start_ip, prev_int_end_ip = intervals[0]
for current_start, current_end in intervals[1:]:
# Two ranges intersect or are adjacent,
# so expand their endpoint.
if prev_int_end_ip + 1 >= current_start:
prev_int_end_ip = max(prev_int_end_ip, current_end)
else:
result_ip_ranges.append(
IPv4Range(
ipaddress.IPv4Address(prev_int_start_ip),
ipaddress.IPv4Address(prev_int_end_ip),
)
)
prev_int_start_ip = current_start
prev_int_end_ip = current_end
result_ip_ranges.append(
IPv4Range(
ipaddress.IPv4Address(prev_int_start_ip),
ipaddress.IPv4Address(prev_int_end_ip),
)
)
return result_ip_ranges
def _subtract(self, param):
"""Private function for subtracting address spaces.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. The function returns a list with the minimum number
of largest IPv4Range's. After the function executes, the number of
IPv4Range in the internal list may differ from the returned value.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to subtract from the
internal list of IPv4Range's
Returns:
[IPv4Range]: The result of subtracting address spaces. May be None
if there are no IP addresses left.
Raises:
ValueError: if an unsupported parameter type is used
"""
if isinstance(param, ipaddress.IPv4Address):
sub_address_space = IPv4AddressSpace(IPv4Range(param, param))
elif isinstance(param, ipaddress.IPv4Network):
sub_address_space = IPv4AddressSpace(param)
elif isinstance(param, IPv4Range):
sub_address_space = IPv4AddressSpace(param)
elif isinstance(param, IPv4AddressSpace):
sub_address_space = param
else:
raise ValueError("Unsupported parameter type")
if self.is_empty():
return []
base = self.__ip_ranges
# Convert internal IPRanges to a list of tuples (events) where:
# - first member is IP converted to int
# - second member is type of IP:
# -- 1 = start of the base range
# -- -1 = end of the base range
# -- 2 = start of the subtractive range
# -- -2 = end of the subtractive range
events = []
for start_ip, end_ip in base:
start = int(start_ip)
end = int(end_ip)
events.append((start, 1))
events.append((end + 1, -1))
for start_ip, end_ip in sub_address_space:
start = int(start_ip)
end = int(end_ip)
events.append((start, -2))
events.append((end + 1, 2))
# Sort events by IP and type. Smallest IP first.
# If IPs are equal then count type order:
# substractive end, base start, base end, substractive start
events.sort(key=lambda x: (x[0], -x[1]))
# Possible events order after sort:
# base_start => base_end | sub_start | sub_end
# base_end => base_start | sub_start | sub_end
# sub_start => sub_end | base_start | base_end
# sub_end => sub_start | base_start | base_end
# First event can be: base_start | sub_start
# Valid subtraction results:
# 1. base start | base end
# 2. base start | (sub_start - 1)
# 3. (sub end+1) | base_end
# We must find these transitions of events
# When we find the start of a range, the next event will
# always be the end of the range. Since the range is contiguous
# and has no gaps
result = []
active = 0
start = None
for current_int_ip, delta in events:
if active > 0 and start is not None:
# save found range
# -1 becouse all ends incrimented by one
result.append((start, current_int_ip - 1))
active += delta
if active > 0 and start is None:
start = current_int_ip
elif active <= 0:
start = None
# Create IPRanges list
return [
IPv4Range(ipaddress.IPv4Address(s), ipaddress.IPv4Address(e))
for s, e in result
if s <= e
]
def __add__(self, param):
"""Addition of address spaces.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. Returns an IPv4AddressSpace containing the sum
of the internal list of IPv4Range's with parameters. The result
consists of the minimum number of the largest IPv4Range's.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to subtract to the internal
list of IPv4Range's
Returns:
[IPv4Range]: The result of adding IPv4Range's
Raises:
ValueError: if an unsupported parameter type is used
"""
result_ip_address_space = IPv4AddressSpace()
result_ip_address_space._set_ip_ranges(self._sum(param))
return result_ip_address_space
def append(self, param):
"""Add new address space to the internal list of IPv4Range's.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. After the function is executed, the internal
IPv4Range list will contain the sum of itself with the parameter.
The number of elements in the internal IPv4Range list may also change.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to add to the internal
list of IPv4Range's
Raises:
ValueError: if an unsupported parameter type is used
"""
self._set_ip_ranges(self._sum(param))
def __sub__(self, param):
"""Subtraction of address spaces.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. The function returns a list with the minimum number
of largest IPv4Range's. After the function executes, the number of
IPv4Range in the internal list may differ from the returned value.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to subtract from the
internal list of IPv4Range's
Returns:
[IPv4Range]: The result of subtracting address spaces. May be None
if there are no IP addresses left.
Raises:
ValueError: if an unsupported parameter type is used
"""
result_ip_address_space = IPv4AddressSpace()
result_ip_address_space._set_ip_ranges(self._subtract(param))
return result_ip_address_space
def remove(self, param):
"""Subtraction of address spaces.
The function is overloaded with various parameter types.
Parameters of any type are converted internally into the
IPv4AddressSpace. After executing the function, the internal IPv4Range
list will contain the difference between itself and the parameter.
The number of elements in the internal IPv4Range list may also change
and become zero.
Args:
param (ipaddress.IPv4Address | ipaddress.IPv4Network,
IPv4Range | IPv4AddressSpace): data to subtract from the
internal list of IPv4Range's
Raises:
ValueError: if an unsupported parameter type is used
"""
self._set_ip_ranges(self._subtract(param))
def networks(self):
"""Get list of IPv4 networks that span the address space.
Return:
[ipaddress.IPv4Network]: The resulting list. May be empty.
Raises:
ValueError: if an unsupported parameter type is used
"""
network_list = []
for r in self.__ip_ranges:
network_list.extend(r.networks())
return network_list
@staticmethod
def load_from_file(filename):
"""Return IPv4AddressSpace loaded from file.
File format:
# - comment
Each line can contain:
IPv4 adress. Ex. 1.2.3.4
IPv4 subnet. Ex. 1.2.3.4/8 or 1.2.3.4/255.0.0.0
IPv4 range. Ex. 1.2.3.4-1.2.3.5
Args:
param (filename): name of file to load.
Return:
IPv4AddressSpace: loaded from file
Examples:
ia = IPv4AddressSpace.LoadFromFile("file.txt")
Raises:
ValueError: if error parsing file
FileNotFoundError: if file i/o errors
OSError: if file i/o errors
"""
ip_address_space = IPv4AddressSpace()
line_from_file = ""
with open(filename, "r", encoding="utf-8") as file:
line_succesfuly_decoded = True
while True:
if not line_succesfuly_decoded:
raise ValueError(f"Error parsing line {line_from_file}")
# read line from file
line_from_file = file.readline()
# stop if line empty
if not line_from_file:
break
# removing comments
spleated_list_from_line = line_from_file.split("#")
uncommented_str = spleated_list_from_line[0]
uncommented_str = uncommented_str.strip()
if not uncommented_str: # drop full commented lines
continue
# pasrsing line
# is line IPv4Address?
try:
ip_addr = ipaddress.IPv4Address(uncommented_str)
ip_address_space += ip_addr
continue
except ValueError:
pass
# is line IPv4Network?
try:
ip_network = ipaddress.IPv4Network(uncommented_str,False)
ip_address_space += ip_network
continue
except ValueError:
pass
# is line IPv4Range?
try:
split_result = uncommented_str.split("-")
if 2 != len(split_result):
raise ValueError(
"String can't be splited by '-' into 2 parts"
)
start_ip = ipaddress.IPv4Address(split_result[0].strip())
end_ip = ipaddress.IPv4Address(split_result[1].strip())
ip_address_space += IPv4Range(start_ip, end_ip)
continue
except ValueError:
pass
line_succesfuly_decoded = False
return ip_address_space
Если библиотеку можно использовать где угодно, хоть на Web-сайте, то мне здесь и сейчас необходима небольшая консольная утилита, позволяющая выполнить необходимые расчёты. В результате я написал ipcalc.py.
ipcalc.py
import sys
import ipaddressspace
# skip sys.argv[0] — scrpit name.
args = sys.argv[1:]
print("IPv4 address space calculator. (c) imbasoft.ru. Freeware. MIT License")
if 0 == len(args):
print("Usage: ipcalc.py file1 +/- file2 +/- file3 ... [--network]")
print("Example: ipcalc.py iaspace1.txt + iaspace2.txt - iaspace3.txt")
exit(0)
file_expected = True
network_output = False
result_ip_address_space = ipaddressspace.IPv4AddressSpace()
last_operator = "+"
for arg in args:
if arg in ('+', '-'):
if file_expected:
print("Error parsing command line. File expected")
exit(1)
file_expected = True
last_operator = arg
continue
if "--network" == arg:
network_output = True
continue
else:
if not file_expected:
print("Error parsing command line. Operator expected")
exit(1)
ia_from_file = None
try:
ia_from_file=ipaddressspace.IPv4AddressSpace.load_from_file(arg)
except:
print(f"Error parsing file: {arg}")
exit(1)
if "+" == last_operator:
result_ip_address_space += ia_from_file
if "-" == last_operator:
result_ip_address_space -= ia_from_file
file_expected = False
print("Result:")
if (network_output):
for net in result_ip_address_space.networks():
print(net)
else:
print(result_ip_address_space)
Работает она просто. В командной строке указываются файлы и арифметические операторы. Файлы содержат адресные пространства, а арифметические операторы указывают, что необходимо сделать: сложить адресные пространства между собой или вычесть. По умолчанию ответ выдаётся в виде диапазонов IP-адресов (например, 1.2.3.4-1.2.3.10), а если указать ключ «--network», то в виде CIDR-подсетей (например, 10.20.30.0/24).
Для решения моей задачи я сделал два файла:
1.FullSpace.txt, задающий всё доступное адресное пространство.
FullSpace.txt
0.0.0.0/0
2.SpecialIPs.txt, содержащий перечень зарезервированных IP-адресов:
SpecialIPs.txt
# Based on https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
0.0.0.0/8
0.0.0.0/32
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/24
192.0.0.0/24
192.0.0.0/29
192.0.0.8/32
# The IP address is considered globally unreachable.
192.0.0.9/32
# The IP address is considered globally unreachable.
192.0.0.10/32
192.0.0.170/32
192.0.0.171/32
192.0.2.0/24
192.88.99.2/32
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
240.0.0.0/4
255.255.255.255/32
# Manually added multicast subnet
224.0.0.4/4
Запустив всё это вместе:
python3 ipcalc.py FullSpace.txt - SpecialIPs.txt
Я получил перечень белых IP-диапазонов:
1.0.0.0 - 9.255.255.255
11.0.0.0 - 100.63.255.255
100.128.0.0 - 126.255.255.255
128.0.0.0 - 169.253.255.255
169.255.0.0 - 172.15.255.255
172.32.0.0 - 191.255.255.255
192.0.1.0 - 192.0.1.255
192.0.3.0 - 192.88.99.1
192.88.99.3 - 192.167.255.255
192.169.0.0 - 198.17.255.255
198.20.0.0 - 198.51.99.255
198.51.101.0 - 203.0.112.255
203.0.114.0 - 223.255.255.255
Добавив ключ «--network»
python3 ipcalc.py FullSpace.txt - SpecialIPs.txt --network
Получил перечень «белых IP-подсетей»:
1.0.0.0/8
2.0.0.0/7
4.0.0.0/6
8.0.0.0/7
11.0.0.0/8
12.0.0.0/6
16.0.0.0/4
32.0.0.0/3
64.0.0.0/3
96.0.0.0/6
100.0.0.0/10
100.128.0.0/9
101.0.0.0/8
102.0.0.0/7
104.0.0.0/5
112.0.0.0/5
120.0.0.0/6
124.0.0.0/7
126.0.0.0/8
128.0.0.0/3
160.0.0.0/5
168.0.0.0/8
169.0.0.0/9
169.128.0.0/10
169.192.0.0/11
169.224.0.0/12
169.240.0.0/13
169.248.0.0/14
169.252.0.0/15
169.255.0.0/16
170.0.0.0/7
172.0.0.0/12
172.32.0.0/11
172.64.0.0/10
172.128.0.0/9
173.0.0.0/8
174.0.0.0/7
176.0.0.0/4
192.0.1.0/24
192.0.3.0/24
192.0.4.0/22
192.0.8.0/21
192.0.16.0/20
192.0.32.0/19
192.0.64.0/18
192.0.128.0/17
192.1.0.0/16
192.2.0.0/15
192.4.0.0/14
192.8.0.0/13
192.16.0.0/12
192.32.0.0/11
192.64.0.0/12
192.80.0.0/13
192.88.0.0/18
192.88.64.0/19
192.88.96.0/23
192.88.98.0/24
192.88.99.0/31
192.88.99.3/32
192.88.99.4/30
192.88.99.8/29
192.88.99.16/28
192.88.99.32/27
192.88.99.64/26
192.88.99.128/25
192.88.100.0/22
192.88.104.0/21
192.88.112.0/20
192.88.128.0/17
192.89.0.0/16
192.90.0.0/15
192.92.0.0/14
192.96.0.0/11
192.128.0.0/11
192.160.0.0/13
192.169.0.0/16
192.170.0.0/15
192.172.0.0/14
192.176.0.0/12
192.192.0.0/10
193.0.0.0/8
194.0.0.0/7
196.0.0.0/7
198.0.0.0/12
198.16.0.0/15
198.20.0.0/14
198.24.0.0/13
198.32.0.0/12
198.48.0.0/15
198.50.0.0/16
198.51.0.0/18
198.51.64.0/19
198.51.96.0/22
198.51.101.0/24
198.51.102.0/23
198.51.104.0/21
198.51.112.0/20
198.51.128.0/17
198.52.0.0/14
198.56.0.0/13
198.64.0.0/10
198.128.0.0/9
199.0.0.0/8
200.0.0.0/7
202.0.0.0/8
203.0.0.0/18
203.0.64.0/19
203.0.96.0/20
203.0.112.0/24
203.0.114.0/23
203.0.116.0/22
203.0.120.0/21
203.0.128.0/17
203.1.0.0/16
203.2.0.0/15
203.4.0.0/14
203.8.0.0/13
203.16.0.0/12
203.32.0.0/11
203.64.0.0/10
203.128.0.0/9
204.0.0.0/6
208.0.0.0/4
© 2026 ООО «МТ ФИНАНС»

