DHCP4
DHCP4协议说明
DHCP是应用层的协议,DHCP报文是承载UDP上的高层协议报文,采用67(DHCP服务器)和68(DHCP客户端)两个端口号。
DHCP的全称是Dynamic Host Configuration Protocol,它的主要功能是实现自动设置IP地址、统一管理IP地址分管。
其工作原理:
DHCP报文格式如下:
各个参数的说明如下:
字段 | 长度(字节) | 含义 |
---|---|---|
Op | 1 | 表示报文的类型: 1:客户端请求报文 2:服务器响应报文 |
Htype | 1 | 表示硬件地址的类型。对于以太网,该类型的值为“1”。 |
Hlen | 1 | 表示硬件地址的长度,单位是字节。对于以太网,该值为6。 |
Hops | 1 | 跳数。客户端设置为0,也能被一个代理服务器设置。 |
Xid | 4 | 事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,客户端用它对请求和应答进行匹配。 该ID由客户端设置并由服务器返回,为32位整数。 |
Secs | 2 | 由客户端填充,表示从客户端开始获得IP地址或IP地址续借后所使用了的秒数。 |
Flags | 2 | 此字段在BOOTP中保留未用,在DHCP中表示标志字段。 Flags字段格式: 0 15 +---------------------------------+ ` |
Ciaddr | 4 | 客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充。 |
Yiaddr | 4 | “你自己的”或客户端的IP地址。 |
Siaddr | 4 | 表明DHCP协议流程的下一个阶段要使用的服务器的IP地址。 |
Giaddr | 4 | 该字段表示第一个DHCP中继的IP地址(注意:不是地址池中定义的网关)。 当客户端发出DHCP请求时,如果服务器和客户端不在同一个网络中,那么第一个DHCP中继在转发这个DHCP请求报文时会把自己的IP地址填入此字段。 服务器会根据此字段来判断出网段地址,从而选择为用户分配地址的地址池。 服务器还会根据此地址将响应报文发送给此DHCP中继,再由DHCP中继将此报文转发给客户端。 若在到达DHCP服务器前经过了不止一个DHCP中继,那么第一个DHCP中继后的中继不会改变此字段,只是把Hops的数目加1。 |
Chaddr | 16 | 该字段表示客户端的MAC地址,此字段与前面的“Hardware Type”和“Hardware Length”保持一致。当客户端发出DHCP请求时,将自己的硬件地址填入此字段。对于以太网,当“Hardware Type”和“Hardware Length”分别为“1”和“6”时,此字段必须填入6字节的以太网MAC地址。 |
Sname | 64 | 该字段表示客户端获取配置信息的服务器名字。此字段由DHCP Server填写,是可选的。 如果填写,必须是一个以0结尾的字符串。 |
File | 128 | 该字段表示客户端的启动配置文件名。此字段由DHCP Server填写,是可选的。 如果填写,必须是一个以0结尾的字符串。 |
Options | 可变 | 该字段表示DHCP的选项字段,至少为312字节,格式为“代码+长度+数据”。 DHCP通过此字段包含了服务器分配给终端的配置信息,如网关IP地址,DNS服务器的IP地址,客户端可以使用IP地址的有效租期等信息。 |
对应到代码中的结构体:
#pragma pack(1)
///
/// EFI_DHCP4_PACKET defines the format of DHCPv4 packets. See RFC 2131 for more information.
///
typedef struct {UINT8 OpCode;UINT8 HwType;UINT8 HwAddrLen;UINT8 Hops;UINT32 Xid;UINT16 Seconds;UINT16 Reserved;EFI_IPv4_ADDRESS ClientAddr; ///< Client IP address from client.EFI_IPv4_ADDRESS YourAddr; ///< Client IP address from server.EFI_IPv4_ADDRESS ServerAddr; ///< IP address of next server in bootstrap.EFI_IPv4_ADDRESS GatewayAddr; ///< Relay agent IP address.UINT8 ClientHwAddr[16]; ///< Client hardware address.CHAR8 ServerName[64];CHAR8 BootFileName[128];
} EFI_DHCP4_HEADER;
#pragma pack()
DHCP4代码综述
DHCP4也是一个通用的网络协议,其实现在NetworkPkg\Dhcp4Dxe\Dhcp4Dxe.inf,这里首先需要看下它的入口:
EFI_STATUS
EFIAPI
Dhcp4DriverEntryPoint (IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE *SystemTable)
{return EfiLibInstallDriverBindingComponentName2 (ImageHandle,SystemTable,&gDhcp4DriverBinding,ImageHandle,&gDhcp4ComponentName,&gDhcp4ComponentName2);
}
仅仅是安装了gDhcp4DriverBinding
:
EFI_DRIVER_BINDING_PROTOCOL gDhcp4DriverBinding = {Dhcp4DriverBindingSupported,Dhcp4DriverBindingStart,Dhcp4DriverBindingStop,0xa,NULL,NULL
};
DHCP4在UEFI网络协议栈中的关系图:
它跟DNS4的结构基本一致。
Dhcp4DriverBindingSupported
DHCP4依赖于UDP4:
EFI_STATUS
EFIAPI
Dhcp4DriverBindingSupported (IN EFI_DRIVER_BINDING_PROTOCOL *This,IN EFI_HANDLE ControllerHandle,IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL)
{Status = gBS->OpenProtocol (ControllerHandle,&gEfiUdp4ServiceBindingProtocolGuid,NULL,This->DriverBindingHandle,ControllerHandle,EFI_OPEN_PROTOCOL_TEST_PROTOCOL);
}
Dhcp4DriverBindingStart
Start函数的流程大致如下:
- 创建
DHCP_SERVICE
,后面会进一步介绍。 - 通过UDP4来接收数据,对应的函数是
UdpIoRecvDatagram()
,该函数会循环执行:
EFI_STATUS
EFIAPI
UdpIoRecvDatagram (IN UDP_IO *UdpIo,IN UDP_IO_CALLBACK CallBack,IN VOID *Context,IN UINT32 HeadLen)
{RxToken = UdpIoCreateRxToken (UdpIo, CallBack, Context, HeadLen);UdpIo->RecvRequest = RxToken;if (UdpIo->UdpVersion == UDP_IO_UDP4_VERSION) {Status = UdpIo->Protocol.Udp4->Receive (UdpIo->Protocol.Udp4, &RxToken->Token.Udp4);}
}
因为Start函数中有如下的代码:
//// Start the receiving//Status = UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0);
所以UdpIoRecvDatagram()
中创建的Token对应的回调函数是DhcpInput()
,而该函数的实现中有:
VOID
EFIAPI
DhcpInput (NET_BUF *UdpPacket,UDP_END_POINT *EndPoint,EFI_STATUS IoStatus,VOID *Context)
{// 前面是数据处理,处理完之后就跳转到RESTRT,或者就走这里的异常,但是也是调用了UdpIoRecvDatagram()if (EFI_ERROR (Status)) {NetbufFree (UdpPacket);UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0);DhcpEndSession (DhcpSb, Status);return;}RESTART:Status = UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0);
}
所以在注释中才有“Start the receiving”的说法。
- 安装
gEfiDhcp4ServiceBindingProtocolGuid
。
DHCP_SERVICE
DHCP_SERVICE
在Start函数中创建:
EFI_STATUS
EFIAPI
Dhcp4DriverBindingStart (IN EFI_DRIVER_BINDING_PROTOCOL *This,IN EFI_HANDLE ControllerHandle,IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL)
{Status = Dhcp4CreateService (ControllerHandle, This->DriverBindingHandle, &DhcpSb);
}
其结构体位于NetworkPkg\Dhcp4Dxe\Dhcp4Impl.h:
//
// DHCP driver is specical in that it is a singleton. Although it
// has a service binding, there can be only one active child.
//
struct _DHCP_SERVICE {UINT32 Signature;EFI_SERVICE_BINDING_PROTOCOL ServiceBinding;INTN ServiceState; // CONFIGED, UNCONFIGED, and DESTROYEFI_HANDLE Controller;EFI_HANDLE Image;LIST_ENTRY Children;UINTN NumChildren;INTN DhcpState;EFI_STATUS IoStatus; // the result of last user operationUINT32 Xid;IP4_ADDR ClientAddr; // lease IP or configured client addressIP4_ADDR Netmask;IP4_ADDR ServerAddr;EFI_DHCP4_PACKET *LastOffer; // The last received offerEFI_DHCP4_PACKET *Selected;DHCP_PARAMETER *Para;UINT32 Lease;UINT32 T1;UINT32 T2;INTN ExtraRefresh; // This refresh is reqested by userUDP_IO *UdpIo; // Udp child receiving all DHCP messageUDP_IO *LeaseIoPort; // Udp child with lease IPEFI_DHCP4_PACKET *LastPacket; // The last sent packet for retransmissionEFI_MAC_ADDRESS Mac;UINT8 HwType;UINT8 HwLen;UINT8 ClientAddressSendOut[16];DHCP_PROTOCOL *ActiveChild;EFI_DHCP4_CONFIG_DATA ActiveConfig;UINT32 UserOptionLen;//// Timer event and various timer//EFI_EVENT Timer;UINT32 PacketToLive; // Retransmission timer for our packetsUINT32 LastTimeout; // Record the init value of PacketToLive every timeINTN CurRetry;INTN MaxRetries;UINT32 LeaseLife;
};
这里首先需要注意开头的注释,虽然可以由多个这样的服务数据,但是DHCP4只是一个单例(singleton),也就是有效的只有一个。
重要参数说明如下:
ServiceState
:对应的值:
//
// The state of the DHCP service. It starts as UNCONFIGED. If
// and active child configures the service successfully, it
// goes to CONFIGED. If the active child configures NULL, it
// goes back to UNCONFIGED. It becomes DESTROY if it is (partly)
// destroyed.
//
#define DHCP_UNCONFIGED 0
#define DHCP_CONFIGED 1
#define DHCP_DESTROY 2
关于它的使用情况已经在上述注释总说明。
DhcpState
:它表示的是通信过程中的DHCP状态,对应的值:
typedef enum {////// The EFI DHCPv4 Protocol driver is stopped.///Dhcp4Stopped = 0x0,////// The EFI DHCPv4 Protocol driver is inactive.///Dhcp4Init = 0x1,////// The EFI DHCPv4 Protocol driver is collecting DHCP offer packets from DHCP servers.///Dhcp4Selecting = 0x2,////// The EFI DHCPv4 Protocol driver has sent the request to the DHCP server and is waiting for a response.///Dhcp4Requesting = 0x3,////// The DHCP configuration has completed.///Dhcp4Bound = 0x4,////// The DHCP configuration is being renewed and another request has/// been sent out, but it has not received a response from the server yet.///Dhcp4Renewing = 0x5,////// The DHCP configuration has timed out and the EFI DHCPv4/// Protocol driver is trying to extend the lease time.///Dhcp4Rebinding = 0x6,////// The EFI DHCPv4 Protocol driver was initialized with a previously/// allocated or known IP address.///Dhcp4InitReboot = 0x7,////// The EFI DHCPv4 Protocol driver is seeking to reuse the previously/// allocated IP address by sending a request to the DHCP server.///Dhcp4Rebooting = 0x8
} EFI_DHCP4_STATE;
IoStatus
:表示的是DHCP通信操作导致的状态。Xid
:一个随机数,在DHCP4协议说明中已经有介绍。ClientAddr
、Netmask
、ServerAddr
:ClientAddr
是通过DHCP租用到的IP地址,对应代码:
EFI_STATUS
DhcpLeaseAcquired (IN OUT DHCP_SERVICE *DhcpSb)
{DhcpSb->ClientAddr = EFI_NTOHL (DhcpSb->Selected->Dhcp4.Header.YourAddr);if (DhcpSb->Para != NULL) {DhcpSb->Netmask = DhcpSb->Para->NetMask;DhcpSb->ServerAddr = DhcpSb->Para->ServerId;}
}
另外两个IP也在这里设置。
LastOffer
:最后一次收到的包。Selected
:从中获取IP的那个包。Para
:DHCP参数,其结构体如下:
///
/// The options that matters to DHCP driver itself. The user of
/// DHCP clients may be interested in other options, such as
/// classless route, who can parse the DHCP offer to get them.
///
typedef struct {IP4_ADDR NetMask; // DHCP4_TAG_NETMASKIP4_ADDR Router; // DHCP4_TAG_ROUTER, only the first router is used//// DHCP specific options//UINT8 DhcpType; // DHCP4_TAG_MSG_TYPEUINT8 Overload; // DHCP4_TAG_OVERLOADIP4_ADDR ServerId; // DHCP4_TAG_SERVER_IDUINT32 Lease; // DHCP4_TAG_LEASEUINT32 T1; // DHCP4_TAG_T1UINT32 T2; // DHCP4_TAG_T2
} DHCP_PARAMETER;
Lease
、T1
、T2
等参数也在这里有体现。
ExtraRefresh
:更新租用期时会使用到,对应到函数:
/**Extends the lease time by sending a request packet.The RenewRebind() function is used to manually extend the lease time when theEFI DHCPv4 Protocol driver is in the Dhcp4Bound state and the lease time hasnot expired yet. This function will send a request packet to the previouslyfound server (or to any server when RebindRequest is TRUE) and transfer thestate into the Dhcp4Renewing state (or Dhcp4Rebinding when RebindingRequest isTRUE). When a response is received, the state is returned to Dhcp4Bound.If no response is received before the try count is exceeded (the RequestTryCountfield that is specified in EFI_DHCP4_CONFIG_DATA) but before the lease time thatwas issued by the previous server expires, the driver will return to the Dhcp4Boundstate and the previous configuration is restored. The outgoing and incoming packetscan be captured by the EFI_DHCP4_CALLBACK function.@param[in] This Pointer to the EFI_DHCP4_PROTOCOL instance.@param[in] RebindRequest If TRUE, this function broadcasts the request packets and entersthe Dhcp4Rebinding state. Otherwise, it sends a unicastrequest packet and enters the Dhcp4Renewing state.@param[in] CompletionEvent If not NULL, this event is signaled when the renew/rebind phasecompletes or some error occurs.EFI_DHCP4_PROTOCOL.GetModeData() can be called tocheck the completion status. If NULL,EFI_DHCP4_PROTOCOL.RenewRebind() will busy-waituntil the DHCP process finishes.@retval EFI_SUCCESS The EFI DHCPv4 Protocol driver is now in theDhcp4Renewing state or is back to the Dhcp4Bound state.@retval EFI_NOT_STARTED The EFI DHCPv4 Protocol driver is in the Dhcp4Stoppedstate. EFI_DHCP4_PROTOCOL.Configure() needs tobe called.@retval EFI_INVALID_PARAMETER This is NULL.@retval EFI_TIMEOUT There was no response from the server when the try count wasexceeded.@retval EFI_ACCESS_DENIED The driver is not in the Dhcp4Bound state.@retval EFI_DEVICE_ERROR An unexpected system or network error occurred.**/
EFI_STATUS
EFIAPI
EfiDhcp4RenewRebind (IN EFI_DHCP4_PROTOCOL *This,IN BOOLEAN RebindRequest,IN EFI_EVENT CompletionEvent OPTIONAL)
{DhcpSb->ExtraRefresh = TRUE;
}
UdpIo
:UDP4通信接口,这里包装了一层,因为有v4和v6两个版本,我们只关注v4版本。LeaseIoPort
:也是UDP4通信接口,不过对应到获取租用IP的接口。LastPacket
:最后发送的包。Mac
、HwType
、HwLen
:网卡参数,对应到EFI_SIMPLE_NETWORK_MODE
中的值。ClientAddressSendOut
:对应DHCP包头部中的ClientHwAddr
。ActiveChild
、ActiveConfig
:前面已经说过DHCP使用单例,所以对应的DHCP_PROTOCOL
也只有一个是有效的,这个就执行有效的DHCP_PROTOCOL
,以及对应的参数。UserOptionLen
:DHCP选项的长度。Timer
:一个定时器,在创建服务时创建:
//// Create various resources, UdpIo, Timer, and get Mac address//Status = gBS->CreateEvent (EVT_NOTIFY_SIGNAL | EVT_TIMER,TPL_CALLBACK,DhcpOnTimerTick,DhcpSb,&DhcpSb->Timer);
从对应的回调函数中可以知道它的作用:
/**Each DHCP service has three timer. Two of them are count down timer.One for the packet retransmission. The other is to collect the offers.The third timer increments the lease life which is compared to T1, T2,and lease to determine the time to renew and rebind the lease.DhcpOnTimerTick will be called once every second.@param[in] Event The timer event@param[in] Context The context, which is the DHCP service instance.**/
VOID
EFIAPI
DhcpOnTimerTick (IN EFI_EVENT Event,IN VOID *Context)
它每秒执行一次:
Status = gBS->SetTimer (DhcpSb->Timer, TimerPeriodic, TICKS_PER_SECOND);
PacketToLive
、LastTimeout
、CurRetry
、MaxRetries
:与定时器相关的一些值,还与重发等机制相关。LeaseLife
:租用时间。