IP地址是一串数据,不便记忆。一般用户在使用TCP/IP协议进行通信时也不使用IP地址,而是使用英文和点号组成的字符串,两者的转换通过DNS(Domain Name System)来完成。
DNS也有v4和v6版本,这里只介绍v4版本。其报文格式如下:
各个参数的说明如下:
字段 | 长度(bit) | 描述 |
---|---|---|
Header | 96 | 头部字段,是必须存在的,它定义了报文是请求还是应答,也定义了其他段是否需要存在,以及是标准查询还是其他。 |
Question | 变长 | 大多数查询中,Question段包含着问题(question),比如,指定问什么。 这个段包含QDCOUNT(头部字段中的QDCOUNT字段,通常值是1)个问题。 |
Anser | 变长 | 分别指应答,授权,附加字段。 都共用相同的格式:多个资源记录,资源记录的个数由报文头段中对应的几个数值确定。 |
Authority | 变长 | |
Additianal | 变长 |
其中的头部有12个字节,内容如下:
各个参数的说明如下:
字段 | 长度(bit) | 描述 |
---|---|---|
ID | 16 | 标识字段,客户通过标识字段来确定DNS响应是否与查询请求匹配。 |
QR | 1 | 操作类型: 0:查询报文 1:响应报文 |
OPCODE | 4 | 查询类型: 0:标准查询 1:反向查询 2:服务器状态查询 3~15:保留未用反向查询是客户端请求服务器根据回答生成导致此回答的问题,这个查询类型的使用并不多。 |
AA | 1 | 若置位,则表示该域名解析服务器是授权回答该域的。 |
TC | 1 | 若置位,则表示报文被截断。使用UDP传输时,应答的总长度超过512字节时,只返回报文的前512个字节内容。 |
RD | 1 | 客户端希望域名解析服务器采取的解析方式: 0:表示希望域名解析服务器采取迭代解析 1:表示希望域名解析服务器采取递归解析 |
RA | 1 | 域名解析服务器采取的解析方式: 0:表示域名解析服务器采取迭代解析 1:表示域名解析服务器采取递归解析 |
Z | 3 | 全部置0,保留未用。 |
RCODE | 4 | 响应类型: 0:无差错 1:查询格式错 2:服务器失效 3:域名不存在 4:查询没有被执行 5:查询被拒绝 6-15: 保留未用 |
QDCOUNT | 16 | 无符号16位整数表示报文请求段中的问题记录数。 |
ANCOUNT | 16 | 无符号16位整数表示报文回答段中的回答记录数。 |
NSCOUNT | 16 | 无符号16位整数表示报文授权段中的授权记录数。 |
ARCOUNT | 16 | 无符号16位整数表示报文附加段中的附加记录数。 |
对应代码中的结构体:
union _DNS_FLAGS {
struct {
UINT16 RCode : 4;
UINT16 Zero : 3;
UINT16 RA : 1;
UINT16 RD : 1;
UINT16 TC : 1;
UINT16 AA : 1;
UINT16 OpCode : 4;
UINT16 QR : 1;
} Bits;
UINT16 Uint16;
};
typedef struct {
UINT16 Identification;
DNS_FLAGS Flags;
UINT16 QuestionsNum;
UINT16 AnswersNum;
UINT16 AuthorityNum;
UINT16 AditionalNum;
} DNS_HEADER;
后面变长部分的内容,后面如何使用到再介绍。
UDP4也是一个通用的网络协议,其实现在NetworkPkg\DnsDxe\DnsDxe.inf,这里首先需要看下它的入口:
EFI_STATUS
EFIAPI
DnsDriverEntryPoint (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
{
//
// Install the Dns4 Driver Binding Protocol.
//
Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gDns4DriverBinding,
ImageHandle,
&gDnsComponentName,
&gDnsComponentName2
);
//
// Install the Dns6 Driver Binding Protocol.
//
Status = EfiLibInstallDriverBindingComponentName2 (
ImageHandle,
SystemTable,
&gDns6DriverBinding,
NULL,
&gDnsComponentName,
&gDnsComponentName2
);
//
// Create the driver data structures.
//
mDriverData = AllocateZeroPool (sizeof (DNS_DRIVER_DATA));
//
// Create the timer event to update DNS cache list.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
DnsOnTimerUpdate,
NULL,
&mDriverData->Timer
);
Status = gBS->SetTimer (mDriverData->Timer, TimerPeriodic, TICKS_PER_SECOND);
InitializeListHead (&mDriverData->Dns4CacheList);
InitializeListHead (&mDriverData->Dns4ServerList);
InitializeListHead (&mDriverData->Dns6CacheList);
InitializeListHead (&mDriverData->Dns6ServerList);
}
从这里可以看到会安装v4和v6版本的EFI_DRIVER_BINDING_PROTOCOL
接口,不过这里只关注v4版本的:
EFI_DRIVER_BINDING_PROTOCOL gDns4DriverBinding = {
Dns4DriverBindingSupported,
Dns4DriverBindingStart,
Dns4DriverBindingStop,
DNS_VERSION,
NULL,
NULL
};
除此之外,这里还为DNS_DRIVER_DATA
分配了内存,并初始化了部分内容,最重要的是有一个定时器,是用来更新DNS缓存列表的,其结构体如下:
struct _DNS_DRIVER_DATA {
EFI_EVENT Timer; /// Ticking timer for DNS cache update.
LIST_ENTRY Dns4CacheList;
LIST_ENTRY Dns4ServerList;
LIST_ENTRY Dns6CacheList;
LIST_ENTRY Dns6ServerList;
};
DNS4在UEFI网络协议栈中的关系图:
DNS4依赖于UDP4:
EFI_STATUS
EFIAPI
Dns4DriverBindingSupported (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
//
// Test for the Udp4ServiceBinding Protocol.
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiUdp4ServiceBindingProtocolGuid,
NULL,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_TEST_PROTOCOL
}
Start函数的流程大致如下:
DNS_SERVICE
。gEfiDns4ServiceBindingProtocolGuid
。同其它驱动一样,重点也是结构体,这里就是DNS_SERVICE
。
DNS_SERVICE
在Start函数中创建:
EFI_STATUS
EFIAPI
Dns4DriverBindingStart (
IN EFI_DRIVER_BINDING_PROTOCOL *This,
IN EFI_HANDLE ControllerHandle,
IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
Status = DnsCreateService (ControllerHandle, This->DriverBindingHandle, IP_VERSION_4, &DnsSb);
Status = gBS->SetTimer (DnsSb->Timer, TimerPeriodic, TICKS_PER_SECOND);
其结构体定义如下:
struct _DNS_SERVICE {
UINT32 Signature;
EFI_SERVICE_BINDING_PROTOCOL ServiceBinding;
UINT16 Dns4ChildrenNum;
LIST_ENTRY Dns4ChildrenList;
UINT16 Dns6ChildrenNum;
LIST_ENTRY Dns6ChildrenList;
EFI_HANDLE ControllerHandle;
EFI_HANDLE ImageHandle;
EFI_EVENT TimerToGetMap;
EFI_EVENT Timer; /// Ticking timer for packet retransmission.
UINT8 IpVersion;
UDP_IO *ConnectUdp;
};
其中比较重要的成员有:
mDns4ServiceBinding
:EFI_SERVICE_BINDING_PROTOCOL mDns4ServiceBinding = {
Dns4ServiceBindingCreateChild,
Dns4ServiceBindingDestroyChild
};
Dns4ChildrenNum
、Dns4ChildrenList
:对应DNS4子项,在Dns4ServiceBindingCreateChild()
中创建。TimerToGetMap
:其说明如下: //
// Create the timer used to time out the procedure which is used to
// get the default IP address.
//
Status = gBS->CreateEvent (
EVT_TIMER,
TPL_CALLBACK,
NULL,
NULL,
&DnsSb->TimerToGetMap
);
Timer
:其说明如下: //
// Create the timer to retransmit packets.
//
Status = gBS->CreateEvent (
EVT_NOTIFY_SIGNAL | EVT_TIMER,
TPL_CALLBACK,
DnsOnTimerRetransmit,
DnsSb,
&DnsSb->Timer
);
IpVersion
:对应DNS4来说就是IP_VERSION_4
。ConnectUdp
:对应结构体UDP_IO
:struct _UDP_IO {
UINT32 Signature;
LIST_ENTRY Link;
INTN RefCnt;
UINT8 UdpVersion;
//
// Handle used to create/destroy UDP child
//
EFI_HANDLE Controller;
EFI_HANDLE Image;
EFI_HANDLE UdpHandle;
EFI_SIMPLE_NETWORK_MODE SnpMode;
LIST_ENTRY SentDatagram; ///< A list of UDP_TX_TOKEN.
UDP_RX_TOKEN *RecvRequest;
union {
EFI_UDP4_PROTOCOL *Udp4;
EFI_UDP6_PROTOCOL *Udp6;
} Protocol;
union {
EFI_UDP4_CONFIG_DATA Udp4;
EFI_UDP6_CONFIG_DATA Udp6;
} Config;
};
基本上都是与UDP(这里只关注UDP4版本),其中的Token在DoDnsQuery()
中创建:
EFI_STATUS
DoDnsQuery (
IN DNS_INSTANCE *Instance,
IN NET_BUF *Packet
)
{
if (Instance->UdpIo->RecvRequest == NULL) {
Status = UdpIoRecvDatagram (Instance->UdpIo, DnsOnPacketReceived, Instance, 0);
if (EFI_ERROR (Status)) {
return Status;
}
}
它在发送请求时使用。
该Protocol的结构体如下:
///
/// The EFI_DNS4_Protocol provides the function to get the host name and address
/// mapping, also provides pass through interface to retrieve arbitrary information
/// from DNS.
///
struct _EFI_DNS4_PROTOCOL {
EFI_DNS4_GET_MODE_DATA GetModeData;
EFI_DNS4_CONFIGURE Configure;
EFI_DNS4_HOST_NAME_TO_IP HostNameToIp;
EFI_DNS4_IP_TO_HOST_NAME IpToHostName;
EFI_DNS4_GENERAL_LOOKUP GeneralLookUp;
EFI_DNS4_UPDATE_DNS_CACHE UpdateDnsCache;
EFI_DNS4_POLL Poll;
EFI_DNS4_CANCEL Cancel;
};
本函数是DNS4模块中最重要的部分,其实现也比较简单:
EFI_STATUS
DoDnsQuery (
IN DNS_INSTANCE *Instance,
IN NET_BUF *Packet
)
{
//
// Ready to receive the DNS response.
//
if (Instance->UdpIo->RecvRequest == NULL) {
Status = UdpIoRecvDatagram (Instance->UdpIo, DnsOnPacketReceived, Instance, 0);
if (EFI_ERROR (Status)) {
return Status;
}
}
//
// Transmit the DNS packet.
//
NET_GET_REF (Packet);
Status = UdpIoSendDatagram (Instance->UdpIo, Packet, NULL, NULL, DnsOnPacketSent, Instance);
}
它最终调用的就是UDP4接口:
EFI_STATUS
EFIAPI
UdpIoSendDatagram (
IN UDP_IO *UdpIo,
IN NET_BUF *Packet,
IN UDP_END_POINT *EndPoint OPTIONAL,
IN EFI_IP_ADDRESS *Gateway OPTIONAL,
IN UDP_IO_CALLBACK CallBack,
IN VOID *Context
)
{
TxToken = UdpIoCreateTxToken (UdpIo, Packet, EndPoint, Gateway, CallBack, Context);
//
// Insert the tx token into SendDatagram list before transmitting it. Remove
// it from the list if the returned status is not EFI_SUCCESS.
//
InsertHeadList (&UdpIo->SentDatagram, &TxToken->Link);
if (UdpIo->UdpVersion == UDP_IO_UDP4_VERSION) {
Status = UdpIo->Protocol.Udp4->Transmit (UdpIo->Protocol.Udp4, &TxToken->Token.Udp4);
}
最终的调用流程:
这就与EFI_DNS4_PROTOCOL
联系起来了。