用C语言手撕一个DNS客户端

今天的故事开始于一个看似简单的问题

每次用sqlplus / as sysdba登录Oracle
需要整整 12 
 
对于工程师和客户来说,这种延迟不仅让人心烦
而且在高并发场景下
还可能会累积成显著的性能瓶颈
一起来看看我们是如何解决这个小问题的吧
20:44:44.190970 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 9 <0.000021>20:44:44.191027 connect(9, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("<主DNS服务器IP>")}, 16) = 0 <0.000023><<===创建一个UDP socket,连接到主DNS服务器,端口是53(dns默认端口)20:44:44.191102 poll([{fd=9, events=POLLOUT}], 1, 0) = 1 ([{fd=9, revents=POLLOUT}]) <0.000019>20:44:44.191163 sendto(9, "p2331��1������vnovulorcu6410icbc-ax"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 <0.000037>20:44:44.191236 poll([{fd=9, events=POLLIN}], 1, 1000) = 0 (Timeout) <1.001080>                                            <<===利用epoll向服务器发送dns报文,并等待返回,1秒钟后超时
20:44:45.192402 socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 10 <0.000048>20:44:45.192548 connect(10, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("<备用DNS服务器IP>")}, 16) = 0 <0.000047>20:44:45.192686 poll([{fd=10, events=POLLOUT}], 1, 0) = 1 ([{fd=10, revents=POLLOUT}]) <0.000044>20:44:45.192831 sendto(10, "p2331��1������vnovulorcu6410icbc-ax"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 <0.000058>20:44:45.192971 poll([{fd=10, events=POLLIN}], 1, 1000) = 0 (Timeout) <1.000812><<===创建第二个UDP socket,连接到第备用DNS服务器,发送DNS报文,并等待返回
20:44:46.193875 poll([{fd=9, events=POLLOUT}], 1, 0) = 1 ([{fd=9, revents=POLLOUT}]) <0.000032>20:44:46.193977 sendto(9, "p2331��1������vnovulorcu6410icbc-ax"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 <0.000061>20:44:46.194092 poll([{fd=9, events=POLLIN}], 1, 1000) = 0 (Timeout) <1.001073><<===重新向主DNS服务器发送报文,1秒钟后又超时
20:44:47.195234 poll([{fd=10, events=POLLOUT}], 1, 0) = 1 ([{fd=10, revents=POLLOUT}]) <0.000032>20:44:47.195336 sendto(10, "p2331��1������vnovulorcu6410icbc-ax"..., 38, MSG_NOSIGNAL, NULL, 0) = 38 <0.000062>20:44:47.195452 poll([{fd=10, events=POLLIN}], 1, 1000) = 0 (Timeout) <1.001104><<===重新向备用DNS服务器发送报文,1秒钟后又超时
20:44:48.196677 close(9)                = 0 <0.000077>20:44:48.196868 close(10)               = 0 <0.000051>
<<===重试结束关闭上面两个socket
到目前为止,大概用了4秒
然后又重复上面的过程三次,一共12秒
20:45:00.248651 open("/etc/hosts", O_RDONLY|O_CLOEXEC) = 9 <0.000041>20:45:00.248803 fstat(9, {st_mode=S_IFREG|0644, st_size=184, ...}) = 0 <0.000030>20:45:00.248918 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fffc17f5000 <0.000030>20:45:00.249025 read(9, "127.0.0.1   localhost localhost."..., 4096) = 184 <0.000049>20:45:00.249182 read(9, "", 4096)       = 0 <0.000030>20:45:00.249324 close(9)                = 0 <0.000031>20:45:00.249425 munmap(0x7fffc17f5000, 4096) = 0 <0.000042>20:45:00.249631 write(10, "��4P6�����3s3������������!���376377377"..., 1104) = 1104 <0.000047>20:45:00.249769 read(11, "��62146�����1034�23���23AUTH_VERSION_S"..., 8208) = 1676 <0.015974>20:45:00.266029 open("/u01/oracle/product/db12cr2/rdbms/mesg/oraus.msb", O_RDONLY) = 9 <0.000048><<===从DNS拿不到结果,只好从/etc/host里找了,最后勉强登录完成.

 

PART 1

找出问题原因

 

 

/ as sysdba是bequeath的登录方式,就是通过domain socket那个sock文件完成的登录,常理来说,大可不必去访问DNS取IP,Oracle可能是想把IP记在SQLNET/session里,为一些其它功能作数据准备吧?比如白名单,黑名单之类的功能。
这个我们不论,但是从strace里可以看到好玩的东西。一般我们取主机IP用的是linux的库函数getaddrinfo(),但是用ltrace跟踪了一下,发现Oracle根本没有用getaddrinfo这个函数,而是自己实现了DNS客户端的功能。
 

 

PART 2

我也要写个DNS客户端

跟客户沟通,等客户验证完成,已经是当天夜里23:30了,躺在床上毫无睡意的专家,脑子里萦绕着,“我也要写个DNS客户端,我也要写个DNS客户端…”。于是——

 

 

1

头文件

 

定义两个函数原型:

* dns_client_commit: 手写DNS报文,查询IP
* simple_convert_domain_ip: 利用getaddrinfo获取IP

DNS服务器,就用联通的114.114.114.114,端口默认的53

 

源码清单: 3.1 dnsClient.h

#ifndef __DNSCLIENT_H__#define __DNSCLIENT_H__#define DNS_SERVER_PORT    53#define DNS_SERVER_IP    "114.114.114.114"
/*通过自己组DNS报文,向dns服务器发送UDP消息,获取IP地址。*/int dns_client_commit(const char *domain);
/*通过getaddrinfo函数从DNS获取IP地址*/void simple_convert_domain_ip(const char *domain);#endif

 

2

函数实现

要点:DNS报文中的域名,要变成标准格式的。比如,www.163.com这个域名,标准格式是:
03 77 77 77 03 31 36 33 03 63 6f 6d
就是: 3 www 3 163 3 com

这个函数原型定义在resolv.h中,编译时需要引用resolv库。
代码清单: 3.2 dnsClient.
#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <time.h>#include <arpa/nameser.h>#include <sys/errno.h>#include <resolv.h>#include <string.h>#include <netdb.h>#include "dnsClient.h"/*利用res_mkquery函数,生成一个DNS报文 */int create_dns_request(const char *domain, unsigned char *request, int rlen){    int len = strlen(domain);    int request_size = res_mkquery(ns_o_query, domain, ns_c_in, ns_t_a, NULL, 0, NULL, request, rlen);    if (request_size < 0)    {        perror("res_mkquery");        return -1;    }    return request_size;}/*打印dns报文,用于调试 */void print_dns_request(unsigned char *request, int request_size){    printf("DNS Request:n");    int i;    for (i = 0; i < request_size; i++)    {        printf("%02x ", request[i]);        if ((i + 1) % 16 == 0)        {            printf("n");        }    }    printf("n");}/* 利用ns_initparse函数解析dns回复的报文消息*/void parse_dns_response(const unsigned char *response, int response_size){    printf("DBS Response:n");    ns_msg handle;    if (ns_initparse(response, response_size, &handle) < 0)    {        fprintf(stderr, "ns_initparse errorn");        return;    }    int answer_count = ns_msg_count(handle, ns_s_an);    int i;    for (i = 0; i < answer_count; i++)    {        ns_rr rr;        if (ns_parserr(&handle, ns_s_an, i, &rr) < 0)        {            fprintf(stderr, "ns_parserr errorn");            return;        }        // ns_rr_type(rr) == ns_t_a && ns_rr_class(rr) == ns_c_in &&        if (ns_rr_rdlen(rr) == 4)        {            struct in_addr ip_address;            memcpy(&ip_address, ns_rr_rdata(rr), sizeof(struct in_addr));            printf("%sn", inet_ntoa(ip_address));        }    }}/*向dns服务器发送报文 */int dns_client_commit(const char *domain){    struct sockaddr_in servaddr;    unsigned char request[1024] = {0};    int length = create_dns_request(domain, (unsigned char *)request, 1024);    // print_dns_request(request, length);    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(DNS_SERVER_PORT);    servaddr.sin_addr.s_addr = inet_addr(DNS_SERVER_IP);    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);    if (sockfd < 0)    {        perror("socket");        return -1;    }    int ret = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));    if (ret != 0)    {        perror("connect");        return -1;    }    int slen = sendto(sockfd, request, length, 0, NULL, 0);    if (slen < 0)    {        printf("error:%dn", errno);        perror("sendto");        return -2;    }    char response[1024] = {0};    struct sockaddr_in addr;    size_t addr_len = sizeof(struct sockaddr_in);    int n = recvfrom(sockfd, response, sizeof(response), 0, (struct sockaddr *)&addr, (socklen_t *)&addr_len);    parse_dns_response((unsigned char *)response, n);    return n;}/*利用getaddrinfo函数获取IP */void simple_convert_domain_ip(const char *domain){    struct addrinfo hints, *res, *p;    int status;    char ip[INET6_ADDRSTRLEN];    memset(&hints, 0, sizeof(hints));    hints.ai_family = AF_UNSPEC;    hints.ai_socktype = SOCK_STREAM;    if ((status = getaddrinfo(domain, NULL, &hints, &res)) != 0)    {        perror("getaddrinfo");        return;    }    printf("nIP address for %s:n", domain);    for (p = res; p != NULL; p = p->ai_next)    {        void *addr;        char *ip_version;        if (p->ai_family == AF_INET)        {            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;            addr = &(ipv4->sin_addr);            ip_version = "IPv4";        }        else        {            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;            addr = &(ipv6->sin6_addr);            ip_version = "IPv6";        }        inet_ntop(p->ai_family, addr, ip, sizeof(ip));        printf("%s: %sn", ip_version, ip);    }    freeaddrinfo(res);    return;}

3

测试函数

 
代码清单:3.3 test.c
#include <stdio.h>#include <stdlib.h>#include "dnsClient.h"
int main(int argc, char *argv[]){    if (argc < 2)        return -1;    dns_client_commit(argv[1]);    simple_convert_domain_ip(argv[1]);    exit(EXIT_SUCCESS);}

4

Makefile

代码清单:3.4 Makefile
TARGET=./testLIBS=-lresolv
all:  gcc -o $(TARGET) dnsClient.c test.c $(LIBS)
clean:  rm -f $(TARGET)

5

编译测试

make./test www.ce-service.com.cn
 
DBS Response:47.104.176.125
IP address for www.ce-service.com.cn:IPv4: 47.104.176.12

原创文章,作者:速盾高防cdn,如若转载,请注明出处:https://www.sudun.com/ask/93408.html

(0)
速盾高防cdn's avatar速盾高防cdn
上一篇 2024年7月8日 下午12:36
下一篇 2024年7月8日 下午12:38

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注