DNS客户端实现

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>


#define LOGD printf
#define LOGE printf
#define LOGW printf

#define PUBLICS_DNS "114.114.114.114"
#define DNS_LEN (16 + 1)
#define BUFF_LEN256 256
#define BUFF_LEN1024 1024
#define DNS_MAX     25
#define DNS_PORT 53
#define DNS_CONF_FILE "/etc/resolv.conf"
#define BEGIN_TIMESTAMP() \
    struct timespec startTime, endTime; \
    char tmBuf[512] = {0}; \
    int32_t err; \
    uint64_t startTm = 0, endTm, diffTime; \
    errno = 0; \
    memset(&startTime, 0, sizeof(startTime)); \
    err = clock_gettime(CLOCK_REALTIME, &startTime); \
    if (err != 0) { \
        (void)strerror_r(errno, tmBuf, sizeof(tmBuf)); \
        LOGE("clock_gettime error, err:%d, reason:%s", err, tmBuf); \
    } else { \
        startTm = (uint64_t)(startTime.tv_sec * 1000) + (uint64_t)(startTime.tv_nsec / (1000 * 1000)); \
    }

#define END_TIMESTAMP() \
    errno = 0; \
    memset(&endTime, 0, sizeof(endTime)); \
    err = clock_gettime(CLOCK_REALTIME, &endTime); \
    if (err != 0) { \
        memset(tmBuf, 0, sizeof(tmBuf)); \
        (void)strerror_r(errno, tmBuf, sizeof(tmBuf)); \
        LOGE("clock_gettime error, err:%d, reason:%s", err, tmBuf); \
    } else { \
        endTm = (uint64_t)(endTime.tv_sec * 1000) + (uint64_t)(endTime.tv_nsec / (1000 * 1000)); \
        if (endTm >= startTm && startTm != 0) { \
            diffTime = endTm - startTm; \
            if (diffTime > 0) \
                LOGD("[%s] blocking time:%lums\n", __FUNCTION__, diffTime); \
        } \
    }

typedef struct {
    /*
     * 标识符:是一个12比特的数
     * 用于标识该查询.这个标识符会被复制到回答报文中,来匹配用户发出的请求和收到的回答
     */
    uint16_t id;

    /*
     * 标志:
     * 1比特“查询/回答”标志指出是查询报文(0)还是回答报文(1):
     * 被请求的权威DNS服务器会将1比特的”权威的“标志位被放置到回答报文中:
     * 递归查询时会设置1比特的”希望递归“标志位:
     * 若DNS服务器支持递归查询,会在回答报文中对1比特的”递归可用“标志位
     * bit15      -> 0:查询 1:应答
     * bit14-bit11-> 0:标准查询
     * bit9       -> 0:消息未截断
     * bit8       -> 0:迭代查询 1:递归查询 
     * bit7       -> 1:(应答报文中:递归可用标志位)
     * bit6       -> 0:resverd
     * bit4       -> 0:非权威数据不可接受的
     */
    uint16_t flags;

    /* 问题数 */
    uint16_t numq;

    /* 回答RR(Resource Record)数 */
    uint16_t numAnswerRR;

    /* 权威RR(Resource Record)数 */
    uint16_t numAuthRR;

    /* 附加RR(Resource Record)数 */
    uint16_t numAddRR;
} DnsHeader;

typedef struct {
    /*
     * A    : 01-IP地址
     * NS   : 02-名字服务器
     * CNMAE: 05-规范名称
     * PTR  : 12-指针记录
     * HINFO: 13-主机信息
     * MX   : 15-邮件交换记录
     * AXFR : 252-对区域转换请求
     * ANY  : 255-对所有记录
     */
    uint16_t type;

    /* 查询类:通常设为1.指的是互联网地址 */
    uint16_t classes;
} DnsQryFmt;


void PrintDataByHEX(const char *title, uint8_t *data, uint32_t len)
{
    if ((title == NULL) || (data == NULL) || (len == 0)) {
        LOGE("input param error!");
        return;
    }

    int i;
    uint8_t *tmpData = data;
    char *p = NULL;
    uint32_t dataLen = len;
    char buf[128 + 1] = {0};
    uint8_t tmpValue;

    while (dataLen > 0) {
        p = buf;
        for (i = 0; ((i < 16) && (dataLen > 0)); i++) { //每行最多打印16个数据
            /* 0xA9 -> 'A' */
            tmpValue = (*tmpData >> 4) & 0x0f;
            if (tmpValue <= 9) {
                *p++ = tmpValue + '0';
            } else {
                *p++ = tmpValue - 10 + 'a';
            }
            /* 0xA9 -> '9' */
            tmpValue = (*tmpData & 0x0f);
            if (tmpValue <= 9) {
                *p++ = tmpValue + '0';
            } else {
                *p++ = tmpValue - 10 + 'a';
            }
            *p++ = ' '; //每个数据间增加一个空格
            tmpData++;
            if (dataLen > 0) {
                dataLen--;
            }
        }
        LOGD("[%s][%d]:%s\n", title, len, buf);
        memset(buf, 0, sizeof(buf));
    }
}
static char* StrTrim(char *str)
{
    char *p = NULL;
    char *q = NULL;

    p = str;
    q = str + strlen(str) - 1;
    while(isspace(*p))
        p++;
    while(isspace(*q))
         q--;
    *(q + 1) = '\0';
    return p;
}

static void PacketQueryNameData(char *buf, uint32_t len)
{
    char *p = buf;
    int32_t i = 0;
    /*
     * 查询名是要查找的名字,它是一个或多个标识符的序列.
     * 每个标识符以首字节的计数值来说明随后标识符的字节长度,每个名字以最后字节为0结束,长度为0的标识符是根标识符.
     * 计数字节的值必须是0~63的数,因为标识符的最大长度仅为63,该字段无需以整32bit边界结束,即无需填充字节
     * www.baidu.com域名存储的结构如下:
     * 3 w w w 5 b a i d u 3 c o m 0
     */

    while (p < (buf  + len)) {
        if (*p == '.') {
            *(p - i - 1) = i;
            i = 0;
        } else {
            i++;
        }
        p++;
    }
    *(p - i - 1) = i;
}

void PrintErrnoInfo(const char *title, int32_t errnum)
{
    char tmpBuf[BUFF_LEN256] = {0};
    (void)strerror_r(errnum, tmpBuf, sizeof(tmpBuf));
    LOGE("%s->errno:%d reson:%s\n", title, errnum, tmpBuf);
}

static bool GetDnsServer(uint8_t *cnt, char *dns)
{
    char tmp[BUFF_LEN256];
    FILE *fp = NULL;

    fp = fopen(DNS_CONF_FILE, "r");
    if (fp == NULL) {
        LOGE("fopen %s failed\n", DNS_CONF_FILE);
        return false;
    }

    while (!feof(fp)) {
        bzero(tmp, sizeof(tmp));
        fgets(tmp, sizeof(tmp), fp);
        char *pos = strstr(tmp, "nameserver");
        if (pos == NULL) {
            continue;
        }
        strcpy(dns, StrTrim(pos + strlen("nameserver")));
        LOGD("dns%hhu:%s\n", *cnt, dns);

        if (*cnt < DNS_MAX) {
            (*cnt)++;
        }
        dns += DNS_LEN;
    }
    fclose(fp);
    return true;
}

/*
 * domain : AF_INET/AF_INET6
 * name   : 域名
 * usrDns : 1.指定的DNS 2.未指定DNS填NULL,默认使用系统的DNS
 * ip     : 输出域名的IP
 * ipLen  : ip缓冲区的长度
 */
int32_t UserGetHostByName(int32_t domain, const char *name, const char *usrDns, char *ip, uint32_t ipLen)
{
    int32_t cliFd = -1;
    char dns[DNS_MAX][DNS_LEN];
    uint8_t cnt = 0;
    char buf[BUFF_LEN1024];
    int32_t i = 0;

    if (usrDns != NULL) {
        cnt = 1;
        strcpy(dns[0], usrDns);
    } else if ((usrDns == NULL ) && !GetDnsServer(&cnt, dns[0])) {
        return -1;
    }

    struct sockaddr_in svrAddr;
    struct sockaddr_in6 svrAddr6;
    while (cnt-- > 0) {
        if (domain == AF_INET) {
            bzero(&svrAddr, sizeof(svrAddr));
            svrAddr.sin_family = AF_INET;
            svrAddr.sin_port = htons(DNS_PORT);
            LOGD("==>dns[%d]:%s\n", i, dns[i]);
            inet_pton(AF_INET, dns[i], &svrAddr.sin_addr);
            LOGD("svrAddr.sin_addr:%#x\n", svrAddr.sin_addr.s_addr);

        } else if (domain == AF_INET6) {
            bzero(&svrAddr6, sizeof(svrAddr6));
            inet_pton(AF_INET6, dns[i], &svrAddr6.sin6_addr);
        } else {
            LOGE("invalid domain:%d\n", domain);
            return -1;
        }
        i++;
        cliFd = socket(domain, SOCK_DGRAM, 0);
        if (cliFd <= 0) {
            LOGE("socket error:%d\n", cliFd);
            continue;
        }
        bzero(buf, sizeof(buf));
        /* DNS请求头报文 */
        DnsHeader *dnsHeader = (DnsHeader *)buf;
        dnsHeader->id = (uint16_t)1;
        dnsHeader->flags = htons(0x0100);
        dnsHeader->numq = htons(1);
        dnsHeader->numAnswerRR = 0;

        /* 查询名数据包 */
        strcpy(buf + sizeof(DnsHeader) + 1, name);
        PacketQueryNameData(buf + sizeof(DnsHeader) + 1, strlen(name));
        DnsQryFmt *dnsQry = (DnsQryFmt *)(buf + sizeof(DnsHeader) + 2 + strlen(name));
        dnsQry->classes = htons(1);
        dnsQry->type = htons(1);
        ssize_t rcvLen = -1;
        BEGIN_TIMESTAMP();
        if (domain == AF_INET) {
            errno = 0;
            struct timeval tmOut;
            tmOut.tv_sec = 2;
            tmOut.tv_usec = 0;
            if (setsockopt(cliFd, SOL_SOCKET, SO_RCVTIMEO, &tmOut, sizeof(tmOut)) != 0) {
                PrintErrnoInfo("setsockopt", errno);
                continue;
            }

           socklen_t sockLen = sizeof(struct sockaddr_in);
           if (sendto(cliFd, buf, sizeof(DnsHeader) + strlen(name) + 2 + sizeof(DnsQryFmt), 0, (struct sockaddr *)&svrAddr, sockLen) <= 0) {
               PrintErrnoInfo("sendto", errno);
               continue;
           }
           PrintDataByHEX("SENDTO", (uint8_t *)buf, sizeof(DnsHeader) + strlen(name) + 2 + sizeof(DnsQryFmt));

           rcvLen = recvfrom(cliFd, buf, sizeof(buf), 0, (struct sockaddr *)&svrAddr, &sockLen);
           if (rcvLen <= 0) {
               PrintErrnoInfo("recvfrom", errno);
               continue;
            }
            PrintDataByHEX("RECVFROM", (uint8_t *)buf, rcvLen);
        } else {
            /*  */
            LOGW("not support\n");
        }
        END_TIMESTAMP();
        LOGD("dnsHeader->numAnswerRR:%u\n", dnsHeader->numAnswerRR);
        if (dnsHeader->numAnswerRR == 0) {
            LOGE("dns query error\n");
            continue;
        }
        snprintf(ip, ipLen - 1,"%hhu.%hhu.%hhu.%hhu", (uint8_t)*(buf + rcvLen - 4), (uint8_t)*(buf + rcvLen - 3), (uint8_t)*(buf + rcvLen - 2), (uint8_t)*(buf + rcvLen - 1));
        LOGD("dns query success:%s=>%s\n", name, ip);
        close(cliFd);
        break;
    }
    return 0;
}

int32_t main(int32_t argcs, char *argv[])
{
    char ip[BUFF_LEN256] = {0};
    if (argcs == 2) {
        UserGetHostByName(AF_INET, "www.baidu.com", argv[1], ip, sizeof(ip));
    } else {
        UserGetHostByName(AF_INET, "www.baidu.com", NULL, ip, sizeof(ip));
    }

    return 0;
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值