Строим карту подсетей на Python
Я натурально боюсь сетевых инженеров - люди, помнящие наизусть таблицу маршрутизации, пулы подсетей, IP-адреса кластерных нод, параметры шифрования туннелей и всего сопутствующего, достойны того, чтобы их уважали и побаивались. В моей голове такая паутина данных надолго не задерживается и я рад, что обязанность по обслуживанию сетей делегируется отдельной группе специалистов.
Однако и на моей улице случился праздник - возникла необходимость разобраться с виртуальной сетью, состоящей из огромного количества подсетей различного размера. На вход мне подали несколько файлов в формате JSON, на выходе нужна была внятная картина происходящего. Из инструментов у меня были только Python, Stack Overflow и базовые знания по сетям.
Парсинг JSON
Взглянув на исходные данные, я понял, что ничего особенно страшного в них нет - только подсети и их id:
[
{
"subnet": "10.0.0.0/16",
"id": "GBYQGY"
},
{
"subnet": "2.0.0.0/24",
"id": "MHI6OJ"
},
{
"subnet": "10.0.42.0/24",
"id": "3NLBMW"
},
...
{
"subnet": "11.5.183.0/30",
"id": "TWWMFH"
}
]Для парсинга JSON буду использовать библиотеку json:
import json def parseJSON(json_file): f = open(json_file, "r") data = json.loads(f.read()) f.close() return data
Теперь можно получить список сетей в виде списка словарей и взглянуть на него по-человечески:
import re
# Получим список сетей из JSON-файла
subnets = parseJSON('subnets.json')
# Отсортируем адреса подсетей по возрастанию
subnets = sorted(subnets, key = lambda i: [int(m) for m in re.findall("\d+", i['subnet'])])
# Выведем список на экран
for subnet in subnets:
print("Subnet:", subnet['subnet'].ljust(18), "ID:", subnet['id'])Обратите внимание, что для сортировки подсетей используется библиотека re. (Спасибо Jean-François Fabre)
Subnet: 2.0.0.0/16 ID: K298OT Subnet: 2.0.5.0/24 ID: MOXKAC Subnet: 10.0.0.0/16 ID: ZIDR8I Subnet: 10.0.0.0/18 ID: B8FQRF Subnet: 10.0.0.0/24 ID: PP6BUY Subnet: 10.0.1.0/24 ID: GLYTC9 Subnet: 172.0.0.0/18 ID: CFY9HZ Subnet: 172.0.3.0/24 ID: E8KPII Subnet: 172.77.63.0/24 ID: ZJZFFY Subnet: 172.77.65.0/24 ID: GOGRCB
Разбираемся с сетями
Что мы знаем про подсети? У них есть префикс и маска. Маска определяет размер подсети (сколько в ней уместится IP-адресов). Благодаря ей мы можем разделить одну большую сеть на кучу маленьких. Более подробно можно почитать на Википедии.
Что же касается моего случая, здесь необходимо найти вхождение одних подсетей в другие, чтобы избежать коллизий. То есть я буду определять, является ли одна сеть суперсетью для другой.
Для работы с IP-адресами буду использовать библиотеку ipaddress:
import ipaddress
subnets = parseJSON('subnets.json')
for i in range(len(subnets)):
sn1 = ipaddress.ip_network(subnets[i]['subnet'])
for subnet in subnets[i+1:]:
sn2 = ipaddress.ip_network(subnet['subnet'])
if sn1.supernet_of(sn2):
print("Subnet", str(sn1).ljust(12), "is supernet of", sn2)Subnet 2.0.0.0/16 is supernet of 2.0.5.0/24 Subnet 10.0.0.0/16 is supernet of 10.0.0.0/18 Subnet 10.0.0.0/16 is supernet of 10.0.0.0/24 Subnet 10.0.0.0/16 is supernet of 10.0.1.0/24 Subnet 10.0.0.0/18 is supernet of 10.0.0.0/24 Subnet 10.0.0.0/18 is supernet of 10.0.1.0/24 Subnet 172.0.0.0/18 is supernet of 172.0.3.0/24
Мы видим, что подсети вполне себе пересекаются.
Кстати, ещё один способ проверить пересечение сетей - использовать метод overlaps:
for i in range(len(subnets)):
sn1 = ipaddress.ip_network(subnets[i]['subnet'])
for subnet in subnets[i+1:]:
sn2 = ipaddress.ip_network(subnet['subnet'])
if sn1.overlaps(sn2):
print("Subnets", sn1, "and", sn2, "are overlapsed") Subnets 2.0.0.0/16 and 2.0.5.0/24 are overlapsed Subnets 10.0.0.0/16 and 10.0.0.0/18 are overlapsed Subnets 10.0.0.0/16 and 10.0.0.0/24 are overlapsed Subnets 10.0.0.0/16 and 10.0.1.0/24 are overlapsed Subnets 10.0.0.0/18 and 10.0.0.0/24 are overlapsed Subnets 10.0.0.0/18 and 10.0.1.0/24 are overlapsed Subnets 172.0.0.0/18 and 172.0.3.0/24 are overlapsed
Строим карту
У нас есть список подсетей и умение определять их пересечение. Остаётся на основе этого построить карту.
Карта будет отображаться в виде дерева и для этого буду использовать библиотеку anytree:
from anytree import Node, RenderTree
subnets = parseJSON('subnets.json')
nodes = []
# Сразу добавляем единый корень будущему дереву, поскольку наши данные его не создадут
node_root = Node("subnet", id="id")
nodes.append(node_root)
# Добавляем подсети в список нод, сразу указывая родителя по умолчанию
for subnet in subnets:
node = Node(subnet['subnet'], id=subnet['id'], parent=nodes[0])
nodes.append(node)
# Находим родителей на основе факта пересечения сетей
for node in nodes:
if node.is_leaf:
sn1 = ipaddress.ip_network(node.name)
for i in range(1, len(nodes)):
sn2 = ipaddress.ip_network(nodes[i].name)
if sn1 != sn2 and sn1.supernet_of(sn2):
nodes[i].parent = node
# Строим дерево
for pre, fill, node in RenderTree(node_root):
print("%s%s" % (pre, node.name))Перед запуском необходимо установить anytree следующей командой:
pip3 install anytree
subnet ├── 2.0.0.0/16 │ └── 2.0.5.0/24 ├── 10.0.0.0/16 │ └── 10.0.0.0/18 │ ├── 10.0.0.0/24 │ └── 10.0.1.0/24 ├── 172.0.0.0/18 │ └── 172.0.3.0/24 ├── 172.77.63.0/24 └── 172.77.65.0/24
Также мы можем воспользоваться graphviz для визуализации карты в виде изображения. Для этого сначала необходимо установить graphviz, затем воспользоваться DotExporter:
from anytree.exporter import DotExporter
DotExporter(node_root).to_picture("network_map.png")Выводы
Карта подсетей в первом приближении построена. Опыт работы с Python получен. Дальше можно оптимизировать и улучшать функционал скрипта.