| #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; |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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); |
| |
| |
| 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); |
| } |
| |
| |
| 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); |
| } |
| } |
| |
| |
| 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)); |
| |
| default_ctx.options.sock_state_cb = resolv_sock_state_cb; |
| default_ctx.options.sock_state_cb_data = &default_ctx; |
| default_ctx.options.timeout = 3000; |
| default_ctx.options.tries = 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; |
| } |
| |
| 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; |
| } |