Python网络编程5-实现DHCP Client

2021-08-15  本文已影响0人  净坛使者_猪悟能

  DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,统一使用两个IANA分配的端口:67(服务器端),68(客户端)。主要作用是集中的管理、分配IP地址,使client动态的获得IP地址、Gateway地址、DNS服务器地址等信息。

一、DHCP报文格式

DHCP报文格式

1.1报文字段解释

option字段
  DHCP报文中的Options字段可以用来存放普通协议中没有定义的控制信息和参数。如果用户在DHCP服务器端配置了Options字段,DHCP客户端在申请IP地址的时候,会通过服务器端回应的DHCP报文获得Options字段中的配置信息。

DHCP option
Options字段由Type、Length和Value三部分组成。

1.2 工作原理

获取IP地址过程

DHCP 获取IP

二、实验环境

  实验使用的linux 主机由两个网络接口,其中ens33使用DHCP获取IP地址,ens37使用静态IP地址;因此需要使用ens33来发送数据包。


实验环境

三、Python实现DHCP Client

3.1 Python脚本

  Change_MAC.py用于MAC地址与Bytes类型相互转换。

#!/usr/bin/python3.4
# -*- coding=utf-8 -*-


import struct

def Change_Chaddr_To_MAC(chaddr): 
    '''转换16字节chaddr为MAC地址,前6字节为MAC'''
    MAC_ADDR_INT_List = struct.unpack('>16B', chaddr)[:6]
    MAC_ADDR_List = []
    for MAC_ADDR_INT in MAC_ADDR_INT_List:
        if MAC_ADDR_INT < 16:
            MAC_ADDR_List.append('0' + str(hex(MAC_ADDR_INT))[2:])
        else:
            MAC_ADDR_List.append(str(hex(MAC_ADDR_INT))[2:])
    MAC_ADDR = MAC_ADDR_List[0] + ':' + MAC_ADDR_List[1] + ':' + MAC_ADDR_List[2] + ':' + MAC_ADDR_List[3] + ':' + MAC_ADDR_List[4] + ':' + MAC_ADDR_List[5]
    return MAC_ADDR
 
 def Str_to_Int(string)
    if ord(string[0]) > 90:
        int1 = ord(string[0]) - 87
    else:
        int1 = ord(string[0]) - 48

    if ord(string[1]) > 90:
        int2 = ord(string[1]) - 87
    else:
        int2 = ord(string[1]) - 48
    int_final = int1 * 16 + int2
    return int_final

def Change_MAC_To_Bytes(MAC):
    section1 = Str_to_Int(MAC.split(':')[0])
    section2 = Str_to_Int(MAC.split(':')[1])
    section3 = Str_to_Int(MAC.split(':')[2])
    section4 = Str_to_Int(MAC.split(':')[3])
    section5 = Str_to_Int(MAC.split(':')[4])
    section6 = Str_to_Int(MAC.split(':')[5])
    Bytes_MAC = struct.pack('!6B', section1, section2, section3, section4, section5, section6)
    return Bytes_MAC
 

  DHCP_Discover.py用于发送DHCP Discover报文;其中GET_MAC.py见ARP章节。

#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
from GET_MAC import get_mac_address
from Change_MAC import Change_MAC_To_Bytes
import time

def DHCP_Discover_Sendonly(ifname, MAC, wait_time = 1):
    if wait_time != 0:
        time.sleep(wait_time)
        Bytes_MAC = Change_MAC_To_Bytes(MAC)#把MAC地址转换为二进制格式
        #chaddr一共16个字节,MAC地址只有6个字节,所以需要b'\x00'*10填充到16个字节
        #param_req_list为请求的参数,没有这个部分服务器只会回送IP地址,什么参数都不给
        discover = Ether(dst='ff:ff:ff:ff:ff:ff', src=MAC, type=0x0800) \
                   / IP(src='0.0.0.0', dst='255.255.255.255') \
                   / UDP(dport=67,sport=68) \
                   / BOOTP(op=1, chaddr=Bytes_MAC + b'\x00'*10) \
                   / DHCP(options=[('message-type','discover'), ('param_req_list', b'\x01\x06\x0f,\x03!\x96+'), ('end')])
        sendp(discover, iface = ifname, verbose=False)
    else:
        Bytes_MAC = Change_MAC_To_Bytes(MAC)
        discover = Ether(dst='ff:ff:ff:ff:ff:ff', src=MAC, type=0x0800) \
                   / IP(src='0.0.0.0', dst='255.255.255.255') \
                   / UDP(dport=67,sport=68) \
                   / BOOTP(op=1, chaddr=Bytes_MAC + b'\x00'*10) \
                   / DHCP(options=[('message-type','discover'), ('param_req_list', b'\x01\x06\x0f,\x03!\x96+'), ('end')])
        sendp(discover, iface = ifname, verbose=False)  

  DHCP_Request.py用于发送DHCP Request报文。

#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
import time

def DHCP_Request_Sendonly(ifname, options, wait_time = 1):
    request = Ether(dst='ff:ff:ff:ff:ff:ff',src=options['MAC'],type=0x0800)\
              /IP(src='0.0.0.0', dst='255.255.255.255')\
              /UDP(dport=67,sport=68)\
              /BOOTP(op=1,chaddr=options['client_id'] + b'\x00'*10,siaddr=options['Server_IP'],)\
              /DHCP(options=[('message-type','request'),
                     ('server_id', options['Server_IP']),
                     ('requested_addr', options['requested_addr']),
                     ('client_id', b'\x01' + options['client_id']),
                     ('param_req_list', b'\x01\x06\x0f,\x03!\x96+'), ('end')])#’end‘作为结束符,方便后续程序读取
    if wait_time != 0:
        time.sleep(wait_time)
        sendp(request, iface = ifname, verbose=False)
    else:
        sendp(request, iface = ifname, verbose=False)   

  DHCP_FULL.py用于完成DHCP Client与DHCP Server的报文交互

#!/usr/bin/python3.4
# -*- coding=utf-8 -*-

from kamene.all import *
import multiprocessing
from Change_MAC import Change_MAC_To_Bytes
from GET_MAC import get_mac_address
from Change_MAC import Change_Chaddr_To_MAC
from DHCP_Discover import DHCP_Discover_Sendonly
from DHCP_Request import DHCP_Request_Sendonly

def DHCP_Monitor_Control(pkt):
    try:
        if pkt.getlayer(DHCP).fields['options'][0][1]== 1:#发现并且打印DHCP Discover
            print('发现DHCP Discover包,MAC地址为:',end='')
            MAC_Bytes = pkt.getlayer(BOOTP).fields['chaddr']
            MAC_ADDR = Change_Chaddr_To_MAC(MAC_Bytes)
            print('Request包中发现如下Options:')
            for option in pkt.getlayer(DHCP).fields['options']:
                if option == 'end':
                    break
                print('%-15s ==> %s' %(str(option[0]),str(option[1])))          
        elif pkt.getlayer(DHCP).fields['options'][0][1]== 2:#发现并且打印DHCP OFFER
            options = {}
            MAC_Bytes = pkt.getlayer(BOOTP).fields['chaddr']
            MAC_ADDR = Change_Chaddr_To_MAC(MAC_Bytes)
            #把从OFFER得到的信息读取并且写入options字典
            options['MAC'] = MAC_ADDR
            options['client_id'] = Change_MAC_To_Bytes(MAC_ADDR)
            print('发现DHCP OFFER包,请求者得到的IP为:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('OFFER包中发现如下Options:')
            for option in pkt.getlayer(DHCP).fields['options']:
                if option == 'end':
                    break
                print('%-15s ==> %s' %(str(option[0]),str(option[1])))
            options['requested_addr'] = pkt.getlayer(BOOTP).fields['yiaddr']
            for i in pkt.getlayer(DHCP).fields['options']:
                if i[0] == 'server_id' :
                    options['Server_IP'] = i[1]
            Send_Request = multiprocessing.Process(target=DHCP_Request_Sendonly, args=(Global_IF,options))
            Send_Request.start()
        elif pkt.getlayer(DHCP).fields['options'][0][1]== 3:#发现并且打印DHCP Request
            print('发现DHCP Request包,请求的IP为:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('Request包中发现如下Options:')
            for option in pkt.getlayer(DHCP).fields['options']:
                if option == 'end':
                    break
                print('%-15s ==> %s' %(str(option[0]),str(option[1])))
        elif pkt.getlayer(DHCP).fields['options'][0][1]== 5:#发现并且打印DHCP ACK
            print('发现DHCP ACK包,确认的IP为:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('ACK包中发现如下Options:')
            for option in pkt.getlayer(DHCP).fields['options']:
                if option == 'end':
                    break
                print('%-15s ==> %s' %(str(option[0]),str(option[1])))
    except Exception as e:   
        print(e)
        pass

def DHCP_FULL(ifname, MAC, timeout = 10):
    global Global_IF
    Global_IF = ifname
    Send_Discover = multiprocessing.Process(target=DHCP_Discover_Sendonly, args=(Global_IF,MAC))#执行多线程,target是目标程序,args是给目标闯入的参数
    Send_Discover.start()
    sniff(prn=DHCP_Monitor_Control, filter="port 68 and port 67", store=0, iface=Global_IF, timeout = timeout)#用于捕获DHCP交互的报文

if __name__ == '__main__':
    ifname = 'ens33'
    DHCP_FULL('ens33', get_mac_address(ifname))

3.2 执行效果

执行效果

Wireshark对远程linux主机抓包,结果如下
客户端以广播发送DHCP Discover包,其中报文操作类型为1(请求报文),DHCP客户端的MAC地址设置为00:0c:29:03:a1:08,option53设置报文类型为Discover,option55(请求选项列表)中包含请求的参数。


DHCP Discover

服务器以单播向客户端回复信息,其中报文操作类型为2(应答报文),分配给客户端的IP为192.168.160.146,option 53设置报文类型为offer,Option 54设置服务器标识为192.168.160.254,其他option为客户端请求列表的应答。


DHCP Offer
客户端以广播发送Request报文,其中服务器标识为192.168.160.146(表明是给这台服务器的回复),确认请求的IP为192.168.160.146.
DHCP Request
服务器单播向客户端发送ACK报文,再次确认给其分配的IP为192.168.160.146,服务器标识为192.168.160.254.
DHCP ACK

值得注意的是,交互的四个报文中Transaction ID均为0x00000000,表明是同一次DHCP交互报文。

上一篇下一篇

猜你喜欢

热点阅读