DNS应用
根据DNS报文格式,可以通过自组DNS请求报文,判断当前设备是否已连接外网。DNS报文格式链接: DNS报文格式.
1. 结构体构造
DNS请求报文为header + 正文,其中正文为name+type+class。所以我们需要构造的DNS请求报文格式为header+name+type+class,header可用一个结构体表示,长度为16 * 6位,type+class可用一个结构体表示,长度为16 * 2位。
具体如下:
typedef struct
{
u16 id;
u16 flags;
u16 ques;
u16 answer;
u16 auth;
u16 addi;
}dns_header; // DNS请求头
typedef struct
{
u16 type;
u16 class;
}dns_query; // DNS请求正文
2. name处理
在DNS报文中,需要注意域名的编码。请求的域名中没有“.”,域名中的“.”被编码为元信息,指示接下来的多少字节是有效信息。
看个例子,我要请求www.google.com.hk。
报文中的name为 03 77 77 77 06 67 6f 6f 67 6c 65 03 63 6f 6d 02 68 6b 00,即为03 w w w 06 g o o g l e 03 c o m 02 h k 00
其中加粗的就是元信息,最后一个字符为0。
3. 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define TRUE 0
#define FALSE 1
#define MAX_SIZE 1024
/*
* DNS header :
*
* | ID | flag |
* | Questions | Answer |
* | Authority RRs | Additional RRs |
* | Name |
* | Type | Class |
*
* size : 2 + 2 + 2 + 2 + 2 + 1 + NameLen + 1 + 2 + 2
*/
typedef unsigned char uchar;
typedef unsigned short u16;
typedef struct
{
u16 id;
u16 flags;
u16 ques;
u16 answer;
u16 auth;
u16 addi;
}dns_header;
typedef struct
{
u16 type;
u16 class;
}dns_query;
static int socket_init(void)
{
return socket(AF_INET, SOCK_DGRAM, 0);
}
int get_host_by_name(const char *name)
{
if (!name || 0 == strlen(name))
return FALSE;
int fd = socket_init();
if (fd < 0)
{
printf("socket_init error, socket creat fail \n");
return FALSE;
}
int i = 0;
char *tmpPtr = NULL;
char sendBuff[MAX_SIZE];
dns_header *dnsheader_p = (dns_header *)sendBuff;
dns_query * dnsquery_p = NULL;
// DNS header 构造
memset(sendBuff, 0, MAX_SIZE);
dnsheader_p->id = (u16)1;
dnsheader_p->flags = htons(0x0100);
dnsheader_p->ques = htons(1);
dnsheader_p->answer = htons(0);
dnsheader_p->auth = htons(0);
dnsheader_p->addi = htons(0);
// DNS name 填充
strcpy(sendBuff + sizeof(dns_header) + 1, name);
tmpPtr = sendBuff + sizeof(dns_header) + 1;
// www.baidu.com -> 3www5baidu3com0
while (tmpPtr < (sendBuff + sizeof(dns_header) + 1 + strlen(name)))
{
if (*tmpPtr != '.')
{
i++;
}
else
{
*(tmpPtr - i - 1) = i;
i = 0;
}
tmpPtr++;
}
*(tmpPtr - i - 1) = i;
// DNS query 构造
dnsquery_p = (dns_query *)(sendBuff + sizeof(dns_header) + 2 + strlen(name));
dnsquery_p->type = htons(1);
dnsquery_p->class = htons(1); // 1 name -> ipv4 28 name -> ipv6
struct timeval tv;
struct sockaddr_in servaddr;
tv.tv_sec = 5;
tv.tv_usec = 5;
// 发送DNS请求包
memset(&servaddr, 0, sizeof(struct sockaddr_in));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(53);
if (inet_pton(AF_INET, "114.114.114.114", &servaddr.sin_addr) < 0)
{
printf("inet_pton error, get sin_addr fail \n");
close(fd);
return FALSE;
}
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval));
if (sendto(fd, sendBuff, sizeof(dns_header) + 1 + strlen(name) + 1 + sizeof(dns_query),
0, (const struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) < 0)
{
printf("send to error, send buff fail \n");
close(fd);
return FALSE;
}
// 接收DNS响应包
int len, size;
size = sizeof(struct sockaddr_in);
if ((len = recvfrom(fd, sendBuff, MAX_SIZE, 0, (struct sockaddr *)&servaddr, (socklen_t *)&size)) < 0)
{
if (errno == EWOULDBLOCK)
{
printf("recvfrom error, timeout \n");
}
else
{
printf("recvfrom error, unknow fail\n");
}
close(fd);
return FALSE;
}
if (0 == dnsheader_p->answer)
{
printf("ack error, no answer \n");
close(fd);
return FALSE;
}
tmpPtr = sendBuff + len - 4;
printf("parsing %s success ! it’s addr is %u.%u.%u.%u \n", name,
(uchar)*tmpPtr, (uchar)*(tmpPtr+1),
(uchar)*(tmpPtr+2), (uchar)*(tmpPtr+3));
close(fd);
return TRUE;
}
void usage(void)
{
printf("usage :\n\n\t./test name\n\n\teg. ./test www.zhihu.com\n\n");
exit(-1);
}
int main(int argc, char *argv[])
{
if (argc != 2)
usage();
get_host_by_name(argv[1]);
return 0;
}
程序执行结果如下: