libev中解析DNS

C/C++
301
0
0
2024-01-09
标签   DNS

系统环境:

gcc 8.3

GLIBC 2.28

前置环境

1. 编译libev

1. git clone https://github.com/enki/libev.git
2. cd libev 
3. ./configure --enable-static=true --prefix=/root/newev   #指定个安装目录,使用静态库
4. make && make install

2. 安装c-ares

sudo apt-get install libc-ares2 libc-ares-dev

例程

1. 项目使用cmake,编写CMakeList.txt

project(libev_dns)

# 设置 C 编译器
set(CMAKE_C_COMPILER gcc)
link_directories(/libev/test/libcork/build/lib)

# 设置生成的可执行文件的输出路径

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)

include_directories(../)
include_directories(.)

link_directories(../lib)
include_directories(../include)
include_directories(/usr/include)
add_executable(dns_query dns_main.cpp common.cpp)

# 连接
target_link_libraries(dns_query ev cares)

2. dns_main.cpp

#include <iostream>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ares.h>
#include "ev.h"
#include "common.h"

#define IO_COUNT 5
struct resolv_ctx
{
    struct ev_io ios[IO_COUNT];
    ares_channel channel;
    struct ares_options options;
};

struct resolv_query
{
    int requests[2];
    size_t response_count;
    struct sockaddr **responses;
    void (*client_cb)(struct sockaddr *, void *);
    void (*free_cb)(void *);
    uint16_t port;
    void *data;
};


// 全局变量
static struct ev_loop *default_loop;
static struct resolv_ctx default_ctx;

// dns状态更新时
void resolv_sock_state_cb(void *data, ares_socket_t socket_fd, int readable, int writable)
{
    struct resolv_ctx *ctx = (struct resolv_ctx *)data;
    int events = (readable ? EV_READ : 0) | (writable ? EV_WRITE : 0);

    int i = 0;
    int ffi = -1;
    for (; i < IO_COUNT; ++i)
    {
        if (ctx->ios[i].fd == socket_fd)
        {
            break;
        }
        if (ffi < 0 && ctx->ios[i].fd == -1)
        {
            ffi = i;
        }
    }

    if (i < IO_COUNT)
    {
        ev_io_stop(default_loop, &ctx->ios[i]);
    }
    else if (ffi > -1)
    {
        i = ffi;
    }
    else
    {
        i = 0;
        ev_io_stop(default_loop, &ctx->ios[i]);
    }
    if (events)
    {
        ev_io_set(&ctx->ios[i], socket_fd, events);
        ev_io_start(default_loop, &ctx->ios[i]);
    }
    else
    {
        ev_io_set(&ctx->ios[i], -1, 0);
    }
}

// ev io 事件回调函数
static void resolv_sock_cb(EV_P_ ev_io *w, int revents)
{
    ares_socket_t rfd = ARES_SOCKET_BAD, wfd = ARES_SOCKET_BAD;

    if (revents & EV_READ)
        rfd = w->fd;
    if (revents & EV_WRITE)
        wfd = w->fd;
    ares_process_fd(default_ctx.channel, rfd, wfd);
}

// 查询完成后的通知函数
void process_client_callback(struct resolv_query *query)
{

    struct sockaddr *best_address = nullptr;

    for (int i = 0; i < query->response_count; ++i)
    {
        if (query->responses[i]->sa_family == AF_INET)
        {
            best_address = query->responses[i];
            break;
        }
        best_address = query->responses[i];
    }

    query->client_cb(best_address, query->data);

    // clear context
    for (int i = 0; i < query->response_count; i++)
        ss_free(query->responses[i]);
    ss_free(query->responses);
    if (query->free_cb != NULL)
        query->free_cb(query->data);
    else
        ss_free(query->data);
    ss_free(query);
}

// 获取ipv4结果
void dns_query_v4_cb(void *arg, int status, int timeouts, struct hostent *he)
{
    struct resolv_query *query = (struct resolv_query *)arg;
    if (he == nullptr || status != ARES_SUCCESS)
    {
        printf("failed to lookup v4 address %s, status:%d\n", ares_strerror(status), status);
    }
    else
    {
        int i = 0;
        int n = 0;
        printf("found address name v4 address %s\n", he->h_name);
        n = 0;
        while (he->h_addr_list[n])
        {
            n++;
        }
        if (n > 0)
        {

            struct sockaddr **new_response = (struct sockaddr **)
                ss_realloc(query->responses, (query->response_count + n) * sizeof(struct sockaddr *));

            if (new_response == nullptr)
            {
                perror("failed to allocate memory for additional DNS responses\n");
            }
            else
            {
                query->responses = new_response;
                for (int i = 0; i < n; ++i)
                {
                    struct sockaddr_in *sa = (struct sockaddr_in *)ss_malloc(sizeof(struct sockaddr_in));
                    memset(sa, 0, sizeof(struct sockaddr_in));
                    sa->sin_family = AF_INET;
                    sa->sin_port = query->port;
                    memcpy(&sa->sin_addr, he->h_addr_list[i], he->h_length);
                    query->responses[query->response_count] = (struct sockaddr *)sa;
                    if (query->responses[query->response_count] == nullptr)
                    {
                        perror("failed to allocate memory for DNS query result address\n");
                    }
                    else
                    {
                        query->response_count++;
                    }
                }
            }
        }
    }
    query->requests[0] = 0;
    if (query->requests[0] == 0 && query->requests[1] == 0)
    {
        process_client_callback(query);
    }
}

// 获取ipv6结果
void dns_query_v6_cb(void *arg, int status, int timeouts, struct hostent *he)
{
    int i = 0;
    int n = 0;
    struct resolv_query *query = (struct resolv_query *)arg;
    if (!he || status != ARES_SUCCESS)
    {
        printf("failed to lookup v6 address %s\n", ares_strerror(status));
    }
    else
    {
        printf("found address name v6 address %s\n", he->h_name);
        n = 0;
        while (he->h_addr_list[n])
        {
            n++;
        }
        if (n > 0)
        {
            struct sockaddr **new_responses = (struct sockaddr **)ss_realloc(query->requests, (query->response_count + n) * sizeof(struct sockaddr *));

            if (new_responses == nullptr)
            {
                perror("failed to allocate memory for additional DNS responses");
            }
            else
            {
                query->responses = new_responses;
                if (query->responses[query->response_count] == nullptr)
                {
                    perror("failed to allocate memory for DNS query result address");
                }
                else
                {
                    query->response_count++;
                }
            }
        }
    }
    query->requests[1] = 0;
    if (query->requests[0] == 0 && query->requests[1] == 0)
    {
        process_client_callback(query);
    }
}

// 初始化
int resolv_init(struct ev_loop *loop, char *nameserver)
{
    default_loop = loop;
    int status = ares_library_init(ARES_LIB_INIT_ALL);
    if (status != ARES_SUCCESS)
    {
        return -1;
    }

    memset(&default_ctx, 0, sizeof(struct resolv_ctx));
    // ev_io 绑定
    default_ctx.options.sock_state_cb = resolv_sock_state_cb;
    default_ctx.options.sock_state_cb_data = &default_ctx;
    default_ctx.options.timeout = 3000; // 30s 超时
    default_ctx.options.tries = 2;      // 重试2次

    status = ares_init_options(&default_ctx.channel, &default_ctx.options, ARES_OPT_NOROTATE | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES | ARES_OPT_SOCK_STATE_CB);

    if (status != ARES_SUCCESS)
    {
        perror("failed to initialize c-ares");
        return -1;
    }
    if (nameserver != nullptr)
    {
        status = ares_set_servers_ports_csv(default_ctx.channel, nameserver);
    }

    if (status != ARES_SUCCESS)
    {
        perror("failed to set nameservers");
        return -1;
    }
	// 绑定io事件
    for (int i = 0; i < IO_COUNT; ++i)
    {
        ev_io_init(&default_ctx.ios[i], resolv_sock_cb, -1, 0);
    }
    return 0;
}


// 查询入口
void resolv_start(const char *hostname, uint16_t port, void (*client_cb)(struct sockaddr *, void *), void (*free_cb)(void *), void *data)
{
    struct resolv_query *query = (resolv_query *)ss_malloc(sizeof(struct resolv_query));
    memset(query, 0, sizeof(struct resolv_query));

    query->port = port;
    query->client_cb = client_cb;
    query->response_count = 0;
    query->responses = nullptr;
    query->data = data;
    query->free_cb = free_cb;

    query->requests[0] = AF_INET;
    query->requests[1] = AF_INET6;
    ares_gethostbyname(default_ctx.channel, hostname, AF_INET, dns_query_v4_cb, query);
    ares_gethostbyname(default_ctx.channel, hostname, AF_INET6, dns_query_v6_cb, query);
}

// 退出
void resolv_shutdown(struct ev_loop *loop)
{
    for (int i = 0; i < IO_COUNT; i++)
        ev_io_stop(loop, &default_ctx.ios[i]);

    ares_cancel(default_ctx.channel);
    ares_destroy(default_ctx.channel);
    ares_library_cleanup();

    printf("resolv_shutdown");
}

// 获取查询结果
void resolv_cb(struct sockaddr *addr, void *data)
{
    char ip[INET6_ADDRSTRLEN];

    if (addr->sa_family == AF_INET)
    {
        const sockaddr_in *sa_in = reinterpret_cast<const sockaddr_in *>(addr);
        inet_ntop(AF_INET, &(sa_in->sin_addr), ip, INET_ADDRSTRLEN);
        std::cout << "ipv4:" << ip << std::endl;
    }
    else if (addr->sa_family == AF_INET6)
    {
        const sockaddr_in6 *sa_in6 = reinterpret_cast<const sockaddr_in6 *>(addr);
        inet_ntop(AF_INET6, &(sa_in6->sin6_addr), ip, INET6_ADDRSTRLEN);
        std::cout << "ipv6:" << ip << std::endl;
    }
}

// 
void resolv_free_cb(void *data)
{
}

int main(int argc, char *argv[])
{
    struct ev_loop *loop = EV_DEFAULT;
    resolv_init(loop, "8.8.8.8");
    if (argc == 2)
    {
        resolv_start(argv[1], 80, resolv_cb, resolv_free_cb, nullptr);
    }
    else
    {
        resolv_start("qq.com", 80, resolv_cb, resolv_free_cb, nullptr);
    }
    ev_run(loop, 0);
    resolv_shutdown(loop);
    return 0;
}

3. 辅助函数 common.cpp

void *ss_malloc(size_t size)
{
    void *tmp = malloc(size);
    if (tmp == NULL)
        exit(-1);
    return tmp;
}

void *ss_realloc(void *ptr, size_t new_size)
{
    void *n = realloc(ptr, new_size);
    if (n == NULL)
    {
        free(ptr);
        ptr = NULL;
        exit(EXIT_FAILURE);
    }
    return n;
}

void ss_free(void *ptr)
{
    free(ptr);
    ptr = NULL;
}

4. 编译测试

1. make 
2. ./bin/dns_query badiu.com
failed to lookup v6 address DNS server returned answer with no data
found address name v4 address baidu.com
ipv4:39.156.66.10