Строим карту подсетей на 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 получен. Дальше можно оптимизировать и улучшать функционал скрипта.