如何在Python子列表中填充未使用的IP地址?

3
我创建了一个Python脚本,旨在让我作为系统管理员的生活更加轻松。该脚本的目的是将Microsoft DHCP服务器转储文件转换为排序后的CSV文件。
我将在此处包含代码,并感谢所有种类的改进。
我的问题是,我的脚本创建了一个列表的列表(每个DHCP预留一个列表)。例如:
[
  # [DHCP SERVER, IP ADDRESS, MAC ADDRESS, HOSTNAME, DESCRIPTION]
  [server1,172.16.0.120,31872fcefa33,wks120.domain.net,Description of client]
  [server1,172.16.0.125,4791ca3d7279,wks125.domain.net,Description of client]
  [server1,172.16.0.132,6035a71c930c,wks132.domain.net,Description of client]
  ...
]

未使用的IP地址未列出。但我希望我的脚本能够自动添加所有未使用的IP地址之间的子列表,并给它们一个注释,例如“未注册”。
我不知道如何开始在谷歌上搜索如何完成这个任务,所以任何帮助都将不胜感激 :) 脚本内容:
#!/usr/bin/python
import sys, shlex
from operator import itemgetter

# Function: get_dhcp_reservations
#
# Extracts a list of ip reservations from a Microsoft DHCP server dump file
# then it stores the processed reservations them in a nested list

def get_dhcp_reservations(dmpFile):

  # Setup empty records list
  records = []

  # Open dump file for reading
  dmpFile = open(dmpFile,"r")

  # Iterate dump file line by line
  for line in dmpFile:

    # Only user lines with the word "reservedip" in it
    if "reservedip" in line:

      # Split the line into fields excluding quoted substrings
      field = shlex.split(line)

      # Create a list of only the required fields
      result = [field[2][1:9], field[7], field[8], field[9], field[10]]

      # Append each new record as a nested list
      records.append(result)

  # Return the rendered data
  return records


# Function: sort_reservations_by_ip
#
# Sorts all records by the IPv4 address field

def sort_reservations_by_ip(records):

  # Temporarily convert dotted IPv4 address to tuples for sorting
  for record in records:
    record[1] = ip2tuple(record[1])

  # Sort sublists by IP address
  records.sort(key=itemgetter(1)) 

  # Convert tuples back to dotted IPv4 addresses
  for record in records:
    record[1] = tuple2ip(record[1])

  return records


# Function: ip2tuple
#
# Split ip address into a tuple of 4 integers (for sorting)

def ip2tuple(address):
  return tuple(int(part) for part in address.split('.'))


# Function: tuple2ip
#
# Converts the tuple of 4 integers back to an dotted IPv4 address

def tuple2ip(address):

  result = ""

  for octet in address:
    result += str(octet)+"."

  return result[0:-1]


# Get DHCP reservations
records = get_dhcp_reservations(sys.argv[1])

# Sort reservations by IP address
records = sort_reservations_by_ip(records)

# Print column headings
print "DHCP Server,Reserved IP,MAC Address,Hostname,Description"

# Print in specified format records
for record in records:
  print record[0]+","+record[1]+",\""+record[2]+"\","+record[3]+","+record[4]

注意:我也尝试过使用Python socket.inet_ntoa 进行IPv4排序,但并未成功。

转储文件示例

根据请求,这是一些转储文件的内容:

[Ommited content]

# ======================================================================
#  Start Add ReservedIp to the Scope : 172.16.0.0, Server : server1.domain.net            
# ======================================================================


    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.76 0800278882ae "wks126devlin.domain.net" "Viana (VM)" "BOTH"
    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.118 001e37322202 "WKS18.domain.net" "Kristof (linux)" "BOTH"
    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.132 000d607205a5 "WKS32.domain.net" "Lab PC" "BOTH"
    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.156 338925b532ca "wks56.domain.net" "Test PC" "BOTH"
    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.155 001422a7d474 "WKS55.domain.net" "Liesbeth" "BOTH"
    Dhcp Server \\server1.domain.net Scope 172.16.0.0 Add reservedip 172.16.0.15 0800266cfe31 "xpsystst.domain.net" "Pascal (VM)" "BOTH"

[Ommited content]

你能给一个 dmpFile 内容的例子吗? - bernard paulus
他们都在 .16.0 服务器上吗? - arynaq
我正在尝试从一个子网解析预订,是的 :) - Pascal Van Acker
3个回答

2

我首先创建了一个空预订列表,然后用你提供的非空列表覆盖它:

#!/usr/bin/env python

reservations = [
    # [DHCP SERVER, IP ADDRESS, MAC ADDRESS, HOSTNAME, DESCRIPTION]
    ['server1','172.16.0.120','31872fcefa33','wks120.domain.net','Description of client'],
    ['server1','172.16.0.125','4791ca3d7279','wks125.domain.net','Description of client'],
    ['server1','172.16.0.132','6035a71c930c','wks132.domain.net','Description of client'],
]

def reservationlist(reservations, serverpattern, addresspattern, hostpattern,
        start, end):
    result = []
    for i in range(start, end + 1):
        result.append([
            serverpattern % i,
            addresspattern % i,
            '[no mac]',
            hostpattern % i,
            'Unregistered'])

    for reservation in reservations:
        index = int(reservation[1].split('.')[3]) - start
        result[index] = reservation

    return result

print reservationlist(
    reservations,
    'server%d',
    '172.16.0.%d',
    'wks%d.domain.net',
    120,
    132)

最终结果如下:
[['server1', '172.16.0.120', '31872fcefa33', 'wks120.domain.net', 'Description of client'],
['server121', '172.16.0.121', '[no mac]', 'wks121.domain.net', 'Unregistered'],
['server122', '172.16.0.122', '[no mac]', 'wks122.domain.net', 'Unregistered'],
['server123', '172.16.0.123', '[no mac]', 'wks123.domain.net', 'Unregistered'],
['server124', '172.16.0.124', '[no mac]', 'wks124.domain.net', 'Unregistered'],
['server1', '172.16.0.125', '4791ca3d7279', 'wks125.domain.net', 'Description of client'],
['server126', '172.16.0.126', '[no mac]', 'wks126.domain.net', 'Unregistered'],
['server127', '172.16.0.127', '[no mac]', 'wks127.domain.net', 'Unregistered'],
['server128', '172.16.0.128', '[no mac]', 'wks128.domain.net', 'Unregistered'],
['server129', '172.16.0.129', '[no mac]', 'wks129.domain.net', 'Unregistered'],
['server130', '172.16.0.130', '[no mac]', 'wks130.domain.net', 'Unregistered'],
['server131', '172.16.0.131', '[no mac]', 'wks131.domain.net', 'Unregistered'],
['server1', '172.16.0.132', '6035a71c930c', 'wks132.domain.net', 'Description of client']]

啊!我忍不住了。这个版本接受起始值和结束值的IP地址:

#!/usr/bin/env python

reservations = [
    # [DHCP SERVER, IP ADDRESS, MAC ADDRESS, HOSTNAME, DESCRIPTION]
    ['server1','172.16.0.120','31872fcefa33','wks120.domain.net','Description of client'],
    ['server1','172.16.0.125','4791ca3d7279','wks125.domain.net','Description of client'],
    ['server1','172.16.0.132','6035a71c930c','wks132.domain.net','Description of client'],
]

def addr_to_int(address):
    """Convert an IP address to a 32-bit int"""
    a, b, c, d = map(int, address.split('.'))
    return a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d

def int_to_addr(value):
    """Convert a 32-bit int into a tuple of its IPv4 byte values"""
    return value >> 24, value >> 16 & 255, value >> 8 & 255, value & 255

def reservationlist(reservations, serverpattern, addresspattern, hostpattern,
        start, end):

    reservationdict = dict((addr_to_int(item[1]), item)
            for item in reservations)
    startint = addr_to_int(start)
    endint = addr_to_int(end)
    for i in range(startint, endint + 1):
        try:
            item = reservationdict[i]
        except KeyError:
            addressbytes = int_to_addr(i)
            item = [
                serverpattern.format(*addressbytes),
                addresspattern.format(*addressbytes),
                '[no mac]',
                hostpattern.format(*addressbytes),
                'Unregistered']
        yield item

for entry in reservationlist(
    reservations,
    'server{3}',
    '172.16.{2}.{3}',
    'wks{3}.domain.net',
    '172.16.0.120',
    '172.16.1.132'):
    print entry

这个版本使用yield关键字将reservationlist()转换为生成器。它不会一次性在RAM中保存所有值,而是每次只发出一个值,直到循环结束。对于每次循环,它尝试从您的预订列表中获取实际值(使用dict进行快速访问)。如果无法获取,则使用string.format方法填充带有IPv4地址字节的字符串模板。

关于地址操作的简短说明

int_to_addr函数接受一个32位IP地址,例如:

AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD

并返回0-255范围内的4字节,例如:

AAAAAAAA, BBBBBBBB, CCCCCCCC, DDDDDDDD

在该函数中,>>表示“将值向右旋转那么多位”,而“& 255”表示“仅返回最后8位(128 + 64 + 32 + 16 + 8 + 4 + 2 + 1)”。
因此,如果我们传入上面的“AAAA ... DDDD”数字:
  • value >> 24 => AAAAAAAA
  • value >> 16 => AAAAAAAABBBBBBBB。该值& 255 => BBBBBBBB
  • value >> 8 => AAAAAAAABBBBBBBBCCCCCCCC。该值& 255 => CCCCCCCC
  • value & 255 => DDDDDDDD
这是将32位IPv4地址转换为4个字节列表的更或多或少标准方法。当您用点连接这些值时,您就可以得到正常的“A.B.C.D”地址格式。

谢谢您提供的解决方案,我正在努力理解它(我很不擅长阅读代码)。但从您的结果来看,它似乎是有效的。非常感谢! :) - Pascal Van Acker
我发现我忘了提到这个子网的子网掩码是255.255.252.0或22位网络位。因此第三个八位组也会递增。 - Pascal Van Acker
太棒了!非常感谢您的出色解释!我会投票支持您的答案,但这是我在这里的第一篇帖子 :) - Pascal Van Acker
很高兴能帮忙!欢迎来到SO. :-) - Kirk Strauser
user948652:谢谢,我在谷歌上寻找一个关键词。 @Kirk:再次感谢你的帮助,我已经更新了我的脚本,现在只使用字典来填充空白。我做的方式与你有些不同,因为我还没有完全理解你代码中“item”变量的用法。我最终将32位IP地址存储为字典键,列表作为它的值。这个方法非常好用 :) - Pascal Van Acker
显示剩余6条评论

1

这是一个计划:

  1. 将所有IP转换为int。即,对于IPv4,IP[0]*(256**3) + IP[1]*(256**2) + IP[2]*256 + IP[3]
  2. 将您获得的所有IP存储在字典中,以IP_as_int作为键。
  3. 迭代您子网中的所有IP(作为int,使用range()),并为每个IP获取您的字典。如果get()返回None,则打印当前IP的默认消息。否则,打印返回的列表。

因此,我们计划的第一步:

def ip_tuple2int(ip_tuple):
     return sum(ip_part * 256**(len(ip_tuple) - no) for no, ip_part in enumerate(ip_tuple, start=1))

我们需要一个函数来稍后打印这些内容。假设我们使用以下代码:
def ip_int2str(ip_int):
    l = []
    while ip_int > 0:
        l.insert(0, ip_int % 256)
        ip_int /= 256

    if len(l) > 4:
        return socket.inet_ntop(socket.AF_INET6, ''.join(chr(i) for i in l))
    else:
        return '.'.join(str(i) for i in l)

第二步:
d = {}
for rec in records:
    d[ip_tuple2int(ip2tuple(rec[1]))] = rec

在第三步中,我们需要网络掩码。我们假设它存储在nmask中,就像这样:nmask = ip_tuple2int(ip2tuple("255.255.254.0"))(是的,这个掩码很不寻常,但最好解决更一般的问题)。
min_ip = d.keys()[0] & nmask
max_ip = min_ip | nmask ^ 2**int(math.ceil(math.log(nmask, 2))) - 1
    # the right-hand of the '|' is just the bitwise inversion of nmask
    # because ~nmask gives a negative number in python

for ip_int in range(min_ip, max_ip + 1):
    row = d.get(ip_int)
    if row:
        print row
    else:
        print [None, ip_int2str(ip_int), None, None, None]

.

所以,这就是解决方案的结束。这里呈现的代码支持IPv4和IPv6:已经针对两种情况的一些输入进行了测试,使用以下ip2tuple()

def ip2tuple(ip_str):
    try:
        ip_bin = socket.inet_pton(socket.AF_INET, ip_str)
    except socket.error:
        ip_bin = socket.inet_pton(socket.AF_INET6, ip_str)

    return [ord(c) for c in ip_bin]

如果您想要接受IPv6,您的问题中的代码仍需要进行调整。

最后,只要地址类型的最高有效位被设置,此代码还支持任何网络掩码。

编辑:有关两个复杂行的更多信息:min_ip和max_ip

所以,我们有

min_ip = d.keys()[0] & nmask
max_ip = min_ip | nmask ^ 2**int(math.ceil(math.log(nmask, 2))) - 1

计算我们范围内的最小和最大IP地址。让我们来分解一下!

min_ip = d.keys()[0] & nmask

在这里,我们取一个任意的IP地址d.keys()[0],并将其与网络掩码进行AND运算:我们保留IP地址中不变的位,将组成可变部分的位清零。

max_ip = min_ip | nmask ^ 2**int(math.ceil(math.log(nmask, 2))) - 1

为了计算最大的IP地址,我们需要取子网掩码中常量部分(存储在min_ip中),并将IP地址变量部分的所有位都设置为1,然后进行“加”运算(二进制OR)。
这是通过计算与nmask相同大小的二进制1行来完成的2 ** int(math.ceil(math.log(nmask, 2))) - 1,并使用XOR运算符与nmask进行运算,以便nmask中所有设置为1的位变为0,网络掩码的低阶0变为1。
为什么要使用这种解决方案?因为即使它不太清晰,但它可以自动适应地址类型。它甚至可以支持4096位地址-甚至更多!

感谢您的评论。但我不太确定是否理解了您的意思。 我目前正在将IP转换为元组以进行排序,那么我应该如何将其转换为int类型?例如,我只获取字符串172.16.0.15,我应该使用子字符串来获取各个八位字节吗? - Pascal Van Acker
我觉得我会使用你的方法,但是稍微有些不同。我想我会直接将IP转换为整数,而不是一起使用元组,谢谢 :) - Pascal Van Acker
非常感谢,我最终采用了你的解决方案,但是简化了一下,上面的代码还是有点超出我的理解范围。 - Pascal Van Acker
请记住:整数可以被视为位字段。i * 256 等同于 i << 8i % 256 等同于 i & 0xff。例如:IP地址 0.0.4.1 可以表示为 0x00 00 04 01(十六进制表示法),在十进制中为1025。这里的大多数方法都是将字节打包或从整数中解包出来的。 - bernard paulus

0

使用numpy完成

import numpy as np

reservations = [
    # [DHCP SERVER, IP ADDRESS, MAC ADDRESS, HOSTNAME, DESCRIPTION]
    ['server1','172.16.0.120','31872fcefa33','wks120.domain.net',
    'Description of client'],
    ['server1','172.16.0.125','4791ca3d7279','wks125.domain.net',
    'Description of client'],
    ['server1','172.16.0.132','6035a71c930c','wks132.domain.net',
    'Description of client'],
]
occupied_ip = []
for i in reservations:
    occupied_ip.append(int(i[1][-3:]))
occupied_ip = np.array(occupied_ip)

iplist = np.arange(256)
idx = np.in1d(iplist,occupied_ip)    #Where are the two arrays equual?
idx = np.logical_not(idx)            #Where are they NOT equal
freeip = iplist[idx]

unreserved = []
for i in range(len(freeip)):
    unreserved.append(["server1", "172.16.0."+str(freeip[i]), "Unassigned MAC",
    "unknown domain"])
    print unreserved[i]

生成

....
    ['server1', '172.16.0.117', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.118', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.119', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.121', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.122', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.123', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.124', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.126', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.127', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.128', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.129', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.130', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.131', 'Unassigned MAC', 'unknown domain']
    ['server1', '172.16.0.133', 'Unassigned MAC', 'unknown domain']
...

哇,这看起来也很不错!我会研究一下的,谢谢你的发帖。 :) - Pascal Van Acker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接