如何在python子列表中填写未使用的ip地址?

时间:2012-11-27 17:24:02

标签: python sorting ip dhcp

我创建了一个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]

3 个答案:

答案 0 :(得分: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”地址格式。

答案 1 :(得分:1)

这是一个计划:

  1. 转换int中的所有IP。也就是说,IP[0]*(256**3) + IP[1]*(256**2) + IP[2]*256 + IP[3]代表IPv4。
  2. 将您获得的所有IP存储在dict中,并将IP_as_int作为密钥
  3. 遍历子网的所有ips(作为整数,使用range()),并get()为每个IP设置dict。如果get()返回None,则打印默认消息以及当前IP。否则,打印返回的列表。
  4. 所以,我们计划的第一步:

    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],并将它与网络掩码一起使用:我们保留as-is是ip的位是常量,并且将构成变量部分的位置为零。 / p>

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

    计算最大ip,我们采用子网的ips的常量部分,存储在min_ip中,并且我们“添加”(二进制OR)ips的可变部分,所有位都设置为1。 这是通过计算与nmask 2**int(math.ceil(math.log(nmask, 2))) - 1大小相同的二进制行和使用nmask的XOR来完成的,这样nmask中设置为1的所有位都变为0,网络掩码的所有低位0都变为1

    为何选择此解决方案?因为即使它不太清楚,它也会自动适应地址类型。它甚至可能支持4096位地址 - 还有更多!

答案 2 :(得分: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']
...