系统环境:
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