"""
Dynamic Host Configuration Protocol for IPv4
http://www.networksorcery.com/enp/protocol/dhcp.htm
http://www.networksorcery.com/enp/protocol/bootp/options.htm
"""
from construct import *
from ipv4 import IpAddress
from binascii import unhexlify
import six
dhcp_option = Struct("dhcp_option",
Enum(Byte("code"),
Pad = 0,
Subnet_Mask = 1,
Time_Offset = 2,
Router = 3,
Time_Server = 4,
Name_Server = 5,
Domain_Name_Server = 6,
Log_Server = 7,
Quote_Server = 8,
LPR_Server = 9,
Impress_Server = 10,
Resource_Location_Server = 11,
Host_Name = 12,
Boot_File_Size = 13,
Merit_Dump_File = 14,
Domain_Name = 15,
Swap_Server = 16,
Root_Path = 17,
Extensions_Path = 18,
IP_Forwarding_enabledisable = 19,
Nonlocal_Source_Routing_enabledisable = 20,
Policy_Filter = 21,
Maximum_Datagram_Reassembly_Size = 22,
Default_IP_TTL = 23,
Path_MTU_Aging_Timeout = 24,
Path_MTU_Plateau_Table = 25,
Interface_MTU = 26,
All_Subnets_are_Local = 27,
Broadcast_Address = 28,
Perform_Mask_Discovery = 29,
Mask_supplier = 30,
Perform_router_discovery = 31,
Router_solicitation_address = 32,
Static_routing_table = 33,
Trailer_encapsulation = 34,
ARP_cache_timeout = 35,
Ethernet_encapsulation = 36,
Default_TCP_TTL = 37,
TCP_keepalive_interval = 38,
TCP_keepalive_garbage = 39,
Network_Information_Service_domain = 40,
Network_Information_Servers = 41,
NTP_servers = 42,
Vendor_specific_information = 43,
NetBIOS_over_TCPIP_name_server = 44,
NetBIOS_over_TCPIP_Datagram_Distribution_Server = 45,
NetBIOS_over_TCPIP_Node_Type = 46,
NetBIOS_over_TCPIP_Scope = 47,
X_Window_System_Font_Server = 48,
X_Window_System_Display_Manager = 49,
Requested_IP_Address = 50,
IP_address_lease_time = 51,
Option_overload = 52,
DHCP_message_type = 53,
Server_identifier = 54,
Parameter_request_list = 55,
Message = 56,
Maximum_DHCP_message_size = 57,
Renew_time_value = 58,
Rebinding_time_value = 59,
Class_identifier = 60,
Client_identifier = 61,
NetWareIP_Domain_Name = 62,
NetWareIP_information = 63,
Network_Information_Service_Domain = 64,
Network_Information_Service_Servers = 65,
TFTP_server_name = 66,
Bootfile_name = 67,
Mobile_IP_Home_Agent = 68,
Simple_Mail_Transport_Protocol_Server = 69,
Post_Office_Protocol_Server = 70,
Network_News_Transport_Protocol_Server = 71,
Default_World_Wide_Web_Server = 72,
Default_Finger_Server = 73,
Default_Internet_Relay_Chat_Server = 74,
StreetTalk_Server = 75,
StreetTalk_Directory_Assistance_Server = 76,
User_Class_Information = 77,
SLP_Directory_Agent = 78,
SLP_Service_Scope = 79,
Rapid_Commit = 80,
Fully_Qualified_Domain_Name = 81,
Relay_Agent_Information = 82,
Internet_Storage_Name_Service = 83,
NDS_servers = 85,
NDS_tree_name = 86,
NDS_context = 87,
BCMCS_Controller_Domain_Name_list = 88,
BCMCS_Controller_IPv4_address_list = 89,
Authentication = 90,
Client_last_transaction_time = 91,
Associated_ip = 92,
Client_System_Architecture_Type = 93,
Client_Network_Interface_Identifier = 94,
Lightweight_Directory_Access_Protocol = 95,
Client_Machine_Identifier = 97,
Open_Group_User_Authentication = 98,
Autonomous_System_Number = 109,
NetInfo_Parent_Server_Address = 112,
NetInfo_Parent_Server_Tag = 113,
URL = 114,
Auto_Configure = 116,
Name_Service_Search = 117,
Subnet_Selection = 118,
DNS_domain_search_list = 119,
SIP_Servers_DHCP_Option = 120,
Classless_Static_Route_Option = 121,
CableLabs_Client_Configuration = 122,
GeoConf = 123,
),
Switch("value", lambda ctx: ctx.code,
{
# codes without any value
"Pad" : Pass,
},
# codes followed by length and value fields
default = Struct("value",
Byte("length"),
Field("data", lambda ctx: ctx.length),
)
)
)
dhcp_header = Struct("dhcp_header",
Enum(Byte("opcode"),
BootRequest = 1,
BootReply = 2,
),
Enum(Byte("hardware_type"),
Ethernet = 1,
Experimental_Ethernet = 2,
ProNET_Token_Ring = 4,
Chaos = 5,
IEEE_802 = 6,
ARCNET = 7,
Hyperchannel = 8,
Lanstar = 9,
),
Byte("hardware_address_length"),
Byte("hop_count"),
UBInt32("transaction_id"),
UBInt16("elapsed_time"),
BitStruct("flags",
Flag("boardcast"),
Padding(15),
),
IpAddress("client_addr"),
IpAddress("your_addr"),
IpAddress("server_addr"),
IpAddress("gateway_addr"),
IpAddress("client_addr"),
Bytes("client_hardware_addr", 16),
Bytes("server_host_name", 64),
Bytes("boot_filename", 128),
# BOOTP/DHCP options
# "The first four bytes contain the (decimal) values 99, 130, 83 and 99"
Const(Bytes("magic", 4), six.b("\x63\x82\x53\x63")),
Rename("options", OptionalGreedyRange(dhcp_option)),
)
if __name__ == "__main__":
test = unhexlify(six.b(
"01" "01" "08" "ff" "11223344" "1234" "0000"
"11223344" "aabbccdd" "11223444" "aabbccdd" "11223344"
"11223344556677889900aabbccddeeff"
"41414141414141414141414141414141" "41414141414141414141414141414141"
"41414141414141414141414141414141" "41414141414141414141414141414141"
"42424242424242424242424242424242" "42424242424242424242424242424242"
"42424242424242424242424242424242" "42424242424242424242424242424242"
"42424242424242424242424242424242" "42424242424242424242424242424242"
"42424242424242424242424242424242" "42424242424242424242424242424242"
"63825363"
"0104ffffff00"
"00"
"060811223344aabbccdd"
))
print (dhcp_header.parse(test))