Python網(wǎng)絡(luò)編程5-實(shí)現(xiàn)DHCP Client

??DHCP(Dynamic Host Configuration Protocol,動(dòng)態(tài)主機(jī)配置協(xié)議),前身是BOOTP協(xié)議,是一個(gè)局域網(wǎng)的網(wǎng)絡(luò)協(xié)議,使用UDP協(xié)議工作,統(tǒng)一使用兩個(gè)IANA分配的端口:67(服務(wù)器端),68(客戶端)。主要作用是集中的管理、分配IP地址,使client動(dòng)態(tài)的獲得IP地址、Gateway地址、DNS服務(wù)器地址等信息。

一、DHCP報(bào)文格式

DHCP報(bào)文格式

1.1報(bào)文字段解釋

  • op:報(bào)文的操作類型。分為請(qǐng)求報(bào)文和響應(yīng)報(bào)文。1:請(qǐng)求報(bào)文,即client送給server的封包 2:應(yīng)答報(bào)文。
    • 請(qǐng)求報(bào)文:DHCP Discover、DHCP Request、DHCP Release、DHCP Inform和DHCP Decline。
    • 應(yīng)答報(bào)文:DHCP Offer、DHCP ACK和DHCP NAK。
  • htype:DHCP客戶端的MAC地址類型。MAC地址類型其實(shí)是指明網(wǎng)絡(luò)類型。
    • htype值為1時(shí)表示為最常見(jiàn)的以太網(wǎng)MAC地址類型。
  • hlen:DHCP客戶端的MAC地址長(zhǎng)度。
    • hlen值為6時(shí)表示為以太網(wǎng)。以太網(wǎng)MAC地址長(zhǎng)度為6個(gè)字節(jié)
  • hops:DHCP報(bào)文經(jīng)過(guò)的DHCP中繼的數(shù)目,默認(rèn)為0。
    • DHCP請(qǐng)求報(bào)文每經(jīng)過(guò)一個(gè)DHCP中繼,該字段就會(huì)增加1。沒(méi)有經(jīng)過(guò)DHCP中繼時(shí)值為0。(若數(shù)據(jù)包需經(jīng)過(guò)router傳送,每站加1,若在同一網(wǎng)內(nèi),為0。)
  • xid:相當(dāng)于請(qǐng)求標(biāo)識(shí)。用來(lái)標(biāo)識(shí)一次IP地址請(qǐng)求過(guò)程。
    • 客戶端通過(guò)DHCP Discover報(bào)文發(fā)起一次IP地址請(qǐng)求時(shí)選擇的隨機(jī)數(shù),在一次請(qǐng)求中所有報(bào)文的Xid都是一樣的。
  • secs:DHCP客戶端從獲取到IP地址或者續(xù)約過(guò)程開(kāi)始到現(xiàn)在所消耗的時(shí)間,以秒為單位。
    • 在沒(méi)有獲得IP地址前該字段始終為0。(DHCP客戶端開(kāi)始DHCP請(qǐng)求后所經(jīng)過(guò)的時(shí)間。目前尚未使用,固定為0。)
  • flags:標(biāo)志位,只使用第0比特位,用來(lái)標(biāo)識(shí)DHCP服務(wù)器應(yīng)答報(bào)文是采用單播還是廣播發(fā)送
    • 0 表示采用單播發(fā)送方式,
    • 1 表示采用廣播發(fā)送方式。
    • 其余位尚未使用。(即從0-15bits,最左1bit為1時(shí)表示server將以廣播方式傳送封包給client。)
  • ciaddr:DHCP客戶端的IP地址。
    • 僅在DHCP服務(wù)器發(fā)送的ACK報(bào)文中顯示,因?yàn)樵诘玫紻HCP服務(wù)器確認(rèn)前,DHCP客戶端是還沒(méi)有分配到IP地址的。
  • yiaddr:DHCP服務(wù)器分配給客戶端的IP地址。
    • 僅在DHCP服務(wù)器發(fā)送的Offer和ACK報(bào)文中顯示,其他報(bào)文中顯示為0。
  • siaddr:下一個(gè)為DHCP客戶端分配IP地址等信息的DHCP服務(wù)器IP地址。
    • 僅在DHCP Offer、DHCP ACK報(bào)文中顯示,其他報(bào)文中顯示為0。(用于bootstrap過(guò)程中的IP地址)
    • 一般來(lái)說(shuō)是服務(wù)器的ip地址,通常認(rèn)為option->srever_id字段為真正的服務(wù)器ip,siaddr有可能是多次路由跳轉(zhuǎn)中的某一個(gè)路由的ip。
  • giaddr:DHCP客戶端發(fā)出請(qǐng)求報(bào)文后經(jīng)過(guò)的第一個(gè)DHCP中繼的IP地址。
    • 如果沒(méi)有經(jīng)過(guò)DHCP中繼,則顯示為0。(轉(zhuǎn)發(fā)代理(網(wǎng)關(guān))IP地址)
  • chaddr:DHCP客戶端的MAC地址。
    • 在每個(gè)報(bào)文中都會(huì)顯示對(duì)應(yīng)DHCP客戶端的MAC地址。
  • sname:為DHCP客戶端分配IP地址的DHCP服務(wù)器名稱(DNS域名格式)。
    • 在Offer和ACK報(bào)文中顯示發(fā)送報(bào)文的DHCP服務(wù)器名稱,其他報(bào)文顯示為0。
  • file:DHCP服務(wù)器為DHCP客戶端指定的啟動(dòng)配置文件名稱及路徑信息。
    • 僅在DHCP Offer報(bào)文中顯示,其他報(bào)文中顯示為空。
  • options:可選項(xiàng)字段,長(zhǎng)度可變,格式為"代碼+長(zhǎng)度+數(shù)據(jù)"。

option字段
??DHCP報(bào)文中的Options字段可以用來(lái)存放普通協(xié)議中沒(méi)有定義的控制信息和參數(shù)。如果用戶在DHCP服務(wù)器端配置了Options字段,DHCP客戶端在申請(qǐng)IP地址的時(shí)候,會(huì)通過(guò)服務(wù)器端回應(yīng)的DHCP報(bào)文獲得Options字段中的配置信息。

DHCP option

Options字段由Type、Length和Value三部分組成。

1.2 工作原理

獲取IP地址過(guò)程

DHCP 獲取IP

  • 發(fā)現(xiàn)過(guò)程
    DHCP Client以廣播的方式發(fā)送一個(gè)DHCP Discover消息,多個(gè)DHCP Server接收到PC發(fā)送的DHCP Discover消息,都會(huì)對(duì)所收到的DHCP Discover消息做出回應(yīng)。
  • 提供階段
    DHCP Server從地址池中選一個(gè)IP地址通過(guò),DHCP Offer消息(單播)將這個(gè)IP地址發(fā)送給DHCP Client。
  • 請(qǐng)求階段
    DHCP Client會(huì)以廣播方式發(fā)送一個(gè)DHCP Request消息
    1.其意圖就是向路由器R上的DHCP Server提出請(qǐng)求,希望獲取到該DHCP Server發(fā)送給自己的DHCP Offer消息中所提供的那個(gè)IP地址。
    2.這個(gè)DHCP Request消息中攜帶有R上的DHCP Server的標(biāo)識(shí)(稱為Server Identifier),表示PC上的DHCP Client只愿意接受R上的DHCP Server所給出的Offer
    3.其他的DHCP Server收到并分析了該DHCP Request消息后,會(huì)明白PC拒絕了自己的Offer。于是,這些DHCP Server就會(huì)收回自己當(dāng)初給予PC的Offer
  • 確認(rèn)階段
    DHCP Server會(huì)向DHCP Client發(fā)送一個(gè)DHCP Ack消息。DHCP Server也可能會(huì)向PC上的DHCP Client發(fā)送一個(gè)DHCP Nak消息。如果PC接收到了DHCP Nak消息,就說(shuō)明這次獲取IP地址的嘗試失敗了。在這種情況下, PC只能重新回到發(fā)現(xiàn)階段來(lái)開(kāi)始新一輪的IP地址申請(qǐng)過(guò)程。

二、實(shí)驗(yàn)環(huán)境

??實(shí)驗(yàn)使用的linux 主機(jī)由兩個(gè)網(wǎng)絡(luò)接口,其中ens33使用DHCP獲取IP地址,ens37使用靜態(tài)IP地址;因此需要使用ens33來(lái)發(fā)送數(shù)據(jù)包。


實(shí)驗(yàn)環(huán)境

三、Python實(shí)現(xiàn)DHCP Client

3.1 Python腳本

??Change_MAC.py用于MAC地址與Bytes類型相互轉(zhuǎn)換。

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


import struct

def Change_Chaddr_To_MAC(chaddr): 
    '''轉(zhuǎn)換16字節(jié)chaddr為MAC地址,前6字節(jié)為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用于發(fā)送DHCP Discover報(bào)文;其中GET_MAC.py見(jiàn)ARP章節(jié)。

#!/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地址轉(zhuǎn)換為二進(jìn)制格式
        #chaddr一共16個(gè)字節(jié),MAC地址只有6個(gè)字節(jié),所以需要b'\x00'*10填充到16個(gè)字節(jié)
        #param_req_list為請(qǐng)求的參數(shù),沒(méi)有這個(gè)部分服務(wù)器只會(huì)回送IP地址,什么參數(shù)都不給
        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用于發(fā)送DHCP Request報(bào)文。

#!/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‘作為結(jié)束符,方便后續(xù)程序讀取
    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的報(bào)文交互

#!/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:#發(fā)現(xiàn)并且打印DHCP Discover
            print('發(fā)現(xiàn)DHCP Discover包,MAC地址為:',end='')
            MAC_Bytes = pkt.getlayer(BOOTP).fields['chaddr']
            MAC_ADDR = Change_Chaddr_To_MAC(MAC_Bytes)
            print('Request包中發(fā)現(xiàn)如下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:#發(fā)現(xiàn)并且打印DHCP OFFER
            options = {}
            MAC_Bytes = pkt.getlayer(BOOTP).fields['chaddr']
            MAC_ADDR = Change_Chaddr_To_MAC(MAC_Bytes)
            #把從OFFER得到的信息讀取并且寫(xiě)入options字典
            options['MAC'] = MAC_ADDR
            options['client_id'] = Change_MAC_To_Bytes(MAC_ADDR)
            print('發(fā)現(xiàn)DHCP OFFER包,請(qǐng)求者得到的IP為:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('OFFER包中發(fā)現(xiàn)如下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:#發(fā)現(xiàn)并且打印DHCP Request
            print('發(fā)現(xiàn)DHCP Request包,請(qǐng)求的IP為:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('Request包中發(fā)現(xiàn)如下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:#發(fā)現(xiàn)并且打印DHCP ACK
            print('發(fā)現(xiàn)DHCP ACK包,確認(rèn)的IP為:' + pkt.getlayer(BOOTP).fields['yiaddr'])
            print('ACK包中發(fā)現(xiàn)如下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))#執(zhí)行多線程,target是目標(biāo)程序,args是給目標(biāo)闖入的參數(shù)
    Send_Discover.start()
    sniff(prn=DHCP_Monitor_Control, filter="port 68 and port 67", store=0, iface=Global_IF, timeout = timeout)#用于捕獲DHCP交互的報(bào)文

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

3.2 執(zhí)行效果

執(zhí)行效果

Wireshark對(duì)遠(yuǎn)程linux主機(jī)抓包,結(jié)果如下
客戶端以廣播發(fā)送DHCP Discover包,其中報(bào)文操作類型為1(請(qǐng)求報(bào)文),DHCP客戶端的MAC地址設(shè)置為00:0c:29:03:a1:08,option53設(shè)置報(bào)文類型為Discover,option55(請(qǐng)求選項(xiàng)列表)中包含請(qǐng)求的參數(shù)。


DHCP Discover

服務(wù)器以單播向客戶端回復(fù)信息,其中報(bào)文操作類型為2(應(yīng)答報(bào)文),分配給客戶端的IP為192.168.160.146,option 53設(shè)置報(bào)文類型為offer,Option 54設(shè)置服務(wù)器標(biāo)識(shí)為192.168.160.254,其他option為客戶端請(qǐng)求列表的應(yīng)答。


DHCP Offer

客戶端以廣播發(fā)送Request報(bào)文,其中服務(wù)器標(biāo)識(shí)為192.168.160.146(表明是給這臺(tái)服務(wù)器的回復(fù)),確認(rèn)請(qǐng)求的IP為192.168.160.146.
DHCP Request

服務(wù)器單播向客戶端發(fā)送ACK報(bào)文,再次確認(rèn)給其分配的IP為192.168.160.146,服務(wù)器標(biāo)識(shí)為192.168.160.254.
DHCP ACK

值得注意的是,交互的四個(gè)報(bào)文中Transaction ID均為0x00000000,表明是同一次DHCP交互報(bào)文。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容