Учитывая два файла (IP-адреса и информация о подсети), создайте файл, который связывает каждый IP-адрес с подсетью.

Я боролся в течение нескольких дней с правильным способом решения этого решения, и я ищу некоторую помощь.

У меня есть два файла, и мне нужно создать третий, который показывает взаимосвязь.

  1. Файл IP-адреса — ip.csv
  2. Файл подсети — subnet.csv

Мне нужно указать, в какой подсети находится каждый IP, и создать третий файл

Файл ip.csv будет содержать около 1,5 миллиона IP-адресов, а файл subnet.csv — около 140 000 подсетей.

Образец файла ip.csv:

IP,Type
10.78.175.167,IPv4
10.20.3.56,IPv4

Образец файла subnet.csv:

Subnet,Netmask
10.176.122.136/30,255.255.255.252
10.20.3.0/24,255.255.254.0

Формат файла, который мне нужно создать:

Subnet,IP
10.20.3.0/24,10.20.3.56

Я попытался использовать вещи с этих страниц:

Это код, который я пробовал. Он работает с небольшими наборами, но у меня возникают проблемы с его запуском с полным набором файлов.

#!/usr/local/bin/python2.7
import csv
import ipaddress
import iptools
import re
import SubnetTree
import sys
from socket import inet_aton

testdir = '/home/test/testdir/'
iprelfile = testdir + 'relationship.csv'
testipsub = testdir + 'subnet.csv'
testipaddr = testdir + 'ip.csv'

o1 = open (iprelfile, "a")

# Subnet file
IPR = set()
o1.write('Subnet,IP\n')
with open(testipsub, 'rb') as master:
    reader = csv.reader(master)
    for row in reader:
        if 'Subnet' not in row[0]:
            # Convert string to unicode to be parsed with ipaddress module
            b = unicode(row[1])
            # Using ipaddress module to create list containing every IP in subnet
            n2 = ipaddress.ip_network(b)
            b1 = (list(n2.hosts()))
            # IP address file
            with open(testipaddr, 'rb') as ipaddy:
                readera = csv.reader(ipaddy)
                for rowa in readera:
                    if 'IP' not in rowa[0]:
                        bb = rowa[0]
                        for ij in b1:
                            # Convert to string for comparison
                            f = str(ij)
                            # If the IP address is in subnet range
                            if f == bb:
                                IPR.update([row[0] + ',' + bb + '\n'])


for ip in IPR:
    o1.write(ip + '\n')

# Closing the file
o1.close()

person DDI Guy    schedule 04.02.2017    source источник
comment
Какие проблемы? Ошибки или низкая скорость?   -  person Alex    schedule 04.02.2017
comment
Вы должны открывать два первых файла CSV одновременно, а не открывать второй для каждой строки первого файла.   -  person OneCricketeer    schedule 04.02.2017
comment
Я довольно новичок в Python. Можете ли вы помочь мне поменять мой код, чтобы я одновременно открывал файлы?   -  person DDI Guy    schedule 04.02.2017
comment
Это просто очень медленно, без ошибок. Если я делаю небольшой образец, он работает нормально.   -  person DDI Guy    schedule 04.02.2017
comment
возможно, сначала прочитайте все подсети, а затем прочитайте IP-адреса и проверьте каждую подсеть. Вы можете использовать словарь (или двоичное дерево) для поиска подсетей, которые вы должны проверить.   -  person furas    schedule 04.02.2017
comment
@furas Я новичок в Python. Я проверил, что могу прочитать файл подсети и довольно быстро получить все данные. Я думаю, где я потерялся, делая ваше предложение. Можете ли вы помочь указать мне правильное направление?   -  person DDI Guy    schedule 04.02.2017
comment
Есть ли случаи, когда подсети перекрываются/включают друг друга? Если есть ожидаемый результат, если IP принадлежит нескольким подсетям?   -  person niemmi    schedule 04.02.2017
comment
@niemmi Я не могу придумать случай, когда IP-адрес был бы в нескольких подсетях. На всякий случай, наверное, было бы неплохо встроить. Но каждый IP должен попадать только в одну подсеть.   -  person DDI Guy    schedule 04.02.2017


Ответы (1)


Вы можете прочитать все подсети в память и отсортировать их по сетевому адресу. Это позволит вам использовать bisect для бинарного поиска, чтобы найти подсеть для каждого IP. Это работает только в том случае, если подсети не перекрывают друг друга, если они это делают, вам, вероятно, потребуется использовать дерево сегментов.

import bisect
import csv
import ipaddress

def sanitize(ip):
    parts = ip.split('/', 1)
    parts[0] = '.'.join(str(int(x)) for x in parts[0].split('.'))

    return '/'.join(parts)

with open('subnet.csv') as subnet_f:
    reader = csv.reader(subnet_f)
    next(reader)    # Skip column names

    # Create list of subnets sorted by network address and
    # list of network addresses in the same order
    subnets = sorted((ipaddress.IPv4Network(sanitize(row[0])) for row in reader),
                     key=lambda x: x.network_address)
    network_addrs = [subnet.network_address for subnet in subnets]

with open('ip.csv') as ip_f, open('output.csv', 'w', newline='') as out_f:
    reader = csv.reader(ip_f)
    next(reader)

    writer = csv.writer(out_f)
    writer.writerow(['Subnet', 'IP'])

    for row in reader:
        ip = ipaddress.IPv4Address(sanitize(row[0]))
        index = bisect.bisect(network_addrs, ip) - 1

        if index < 0 or subnets[index].broadcast_address < ip:
            continue    # IP not in range of any networks
        writer.writerow([subnets[index], ip])

Выход:

Subnet,IP
10.20.3.0/24,10.20.3.56

Временная сложность выше составляет O(n log m), где n — количество IP-адресов, а m — количество сетей. Обратите внимание, что он работает только с Python 3, так как ipaddress не включен в Python 2.7. Если вам нужно использовать Python 2.7, доступны backports.

Обновление. Первая цель эффективного решения — найти способ эффективной обработки каждого отдельного IP-адреса. Зацикливание на всех подсетях ужасно дорого, поэтому это не сработает. Гораздо лучше создать отсортированный список первых IP-адресов в каждой подсети. Для заданных данных это будет выглядеть так:

[IPv4Address('10.20.3.0'), IPv4Address('10.176.122.136')]

Это позволит нам выполнить бинарный поиск, чтобы найти индекс IP-адреса, равный или меньший, чем индивидуальный IP-адрес. Например, когда мы ищем IP 10.20.3.56, мы используем bisect.bisect, чтобы предоставить нам первый индекс, больший, чем IP, и уменьшаем его на единицу:

>>> l = [IPv4Address('10.20.3.0'), IPv4Address('10.176.122.136')]
>>> index = bisect.bisect(l, IPv4Address('10.20.3.56'))
>>> index
1
>>> l[index - 1]
IPv4Address('10.20.3.0')

Поскольку мы сохранили сети в другом списке, который находится в том же порядке, мы можем использовать индекс для получения данной подсети. После того, как у нас есть подсеть, нам все равно нужно проверить, что отдельный IP-адрес равен или ниже последнего IP-адреса в подсети. Если отдельный IP-адрес находится в подсети, запишите строку в результат, если нет, перейдите к следующему IP-адресу.

person niemmi    schedule 04.02.2017
comment
Python3 и может запускать скрипт для большинства данных. Проблема, с которой я сталкиваюсь, заключается в том, что она не работает, если есть адрес IPv6. У меня также возникают проблемы, если у кого-то есть ведущий ноль. Например, если есть IP-адрес 192.168.010.199 вместо 192.168.10.199. Кроме того, что, если в исходном файле более двух столбцов? - person DDI Guy; 04.02.2017
comment
@DDIGuy Обновлен ответ для обработки более длинных строк, проведена обработка ввода для удаления ведущих нулей и добавлено более длинное объяснение. - person niemmi; 04.02.2017
comment
Спасибо за объяснение. любые предложения о том, что делать, если у меня есть сочетание IPv4 и IPv6? Должен ли я выполнить это один раз для IPv4, а затем снова для IPv6? Или есть простой способ добавить его сюда. - person DDI Guy; 04.02.2017
comment
@DDIGuy: Вероятно, самый простой способ — иметь два сценария, один для IPv4, а другой для IPv6. Не должно быть так сложно изменить, чтобы пропустить IPv6, а другой - вместо этого использовать классы, связанные с IPv6, и пропустить IPv4. - person niemmi; 04.02.2017
comment
Спасибо за помощь. Завтра попробую и отпишусь о результатах. - person DDI Guy; 04.02.2017
comment
Спасибо за помощь. Я очень ценю это. Я на ~90% пути к завершению этого. Как вы предложили, я использую IPv4 только с кодом, который вы мне дали. Я помещаю все это в одну функцию и просто вызываю эту функцию. Я хочу получить функцию для IPv6, созданную следующей. Как добавить строку в каждую распечатываемую строку? Используя приведенный выше код, вывод дает мне два столбца, и это здорово. Я хочу добавить еще 3 столбца в каждую напечатанную строку. В каждой строке будет одна и та же информация. Я хочу добавить New, Test Keyword и 05.02.2017, что является значением переменной даты, которая у меня есть. - person DDI Guy; 05.02.2017
comment
поэтому результат будет выглядеть так: 10.20.3.0/24,10.20.3.56,New,Test Keyword, добавлено 05.02.2017 Я пытался добавить его в строку записи, которую вы добавили, но я получаю ошибки, TypeError: string индексы должны быть целыми числами. Я пробовал несколько вещей, таких как создание переменной и ее вызов, но не работал. Еще раз, спасибо за помощь. - person DDI Guy; 05.02.2017
comment
подведем итог... (1) нужно добавить строку в каждую напечатанную строку (2) нужно сделать то же самое для IPv6. Могу ли я использовать bisect для этого? (3) Мне просто нужно изучить каждую часть, чтобы понять, что она делает. Например, функция очистки и лямбда. - person DDI Guy; 05.02.2017
comment
@DDIGuy Просто добавьте столбцы в список, который вы передаете писателю: writer.writerow([subnets[index], ip, 'New', ...]). Работает точно так же для IPv6, вы также можете использовать bisect. Единственное, что вам нужно добавить, это фильтрацию по IPv и использовать IPv6Address и IPv6Network при обработке IPv6-адресов. - person niemmi; 05.02.2017