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]
4

3 回答 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 中,而是一次发出一个值,直到循环完成。对于每次通过循环,它都会尝试从您的预订列表中获取实际值(使用 adict进行快速访问)。如果不能,则使用该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=> AAAAAAA
  • value >> 16=> AAAAAAABBBBBBBB。该值 & 255 => BBBBBBBB
  • value >> 8=> AAAAAAABBBBBBBBCCCCCCCC。该值 & 255 => CCCCCCCC
  • value & 255=> DDDDDDDD

这是将 32 位 IPv4 地址转换为 4 字节列表的或多或少的标准方法。当您将这些值与一个点连接在一起时,您将获得正常的“ABCD”地址格式。

于 2012-11-27T18:00:53.287 回答
1

这是一个计划:

  1. 将您的所有 IP 转换为int. 也就是说,IP[0]*(256**3) + IP[1]*(256**2) + IP[2]*256 + IP[3]对于 IPv4。
  2. 将您获得的所有 IP 存储在一个字典中,以 IP_as_int 作为键
  3. 遍历子网的所有 ip(作为整数,range()用于此),以及get()每个 IP 的 dict。如果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],并将其与网络掩码进行“与”运算:我们按原样保存 ip 中不变的位,并将构成可变部分的位归零。

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

为了计算最大 ip,我们将子网的 ips 的常数部分存储在 min_ip 中,然后我们“添加”(二进制或)ips 的可变部分,所有位都设置为 1。这是通过计算 a与 nmask大小相同的 1 的二进制行2**int(math.ceil(math.log(nmask, 2))) - 1并与 nmask 进行异或运算,以便 nmask 中设置为 1 的所有位变为 0,并且网络掩码的所有低位 0 变为 1。

为什么这个解决方案?因为即使它不太清楚,它也会自动适应地址类型。它甚至可能支持 4096 位地址——甚至更多!

于 2012-11-27T18:00:16.813 回答
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']
...
于 2012-11-27T18:22:58.873 回答