??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)文格式

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字段中的配置信息。

Options字段由Type、Length和Value三部分組成。
1.2 工作原理
獲取IP地址過(guò)程

-
發(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ù)包。

三、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í)行效果

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ù)。

服務(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)答。

客戶端以廣播發(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.

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

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