/************************************************************************* * * Copyright (C) 2018-2020 Ruilin Peng (Nick) . * * smartdns is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * smartdns is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "fast_ping.h" #include "atomic.h" #include "hashtable.h" #include "list.h" #include "tlog.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PING_MAX_EVENTS 128 #define PING_MAX_HOSTLEN 128 #define ICMP_PACKET_SIZE (1024 * 64) #define ICMP_INPACKET_SIZE 1024 #define IPV4_ADDR_LEN 4 #define IPV6_ADDR_LEN 16 #define SOCKET_PRIORITY (6) #ifndef ICMP_FILTER #define ICMP_FILTER 1 struct icmp_filter { uint32_t data; }; #endif struct ping_dns_head { unsigned short id; unsigned short flag; unsigned short qdcount; unsigned short ancount; unsigned short aucount; unsigned short adcount; char qd_name; unsigned short q_qtype; unsigned short q_qclass; } __attribute__((packed)); typedef enum FAST_PING_TYPE { FAST_PING_ICMP = 1, FAST_PING_ICMP6 = 2, FAST_PING_TCP, FAST_PING_UDP, FAST_PING_UDP6, FAST_PING_END, } FAST_PING_TYPE; struct fast_ping_packet_msg { struct timeval tv; unsigned int sid; unsigned int seq; unsigned int cookie; }; struct fast_ping_packet { union { struct icmp icmp; struct icmp6_hdr icmp6; }; unsigned int ttl; struct fast_ping_packet_msg msg; }; struct ping_host_struct { atomic_t ref; atomic_t notified; struct hlist_node addr_node; struct list_head action_list; FAST_PING_TYPE type; void *userptr; int error; fast_ping_result ping_callback; char host[PING_MAX_HOSTLEN]; int fd; unsigned int seq; int ttl; struct timeval last; int interval; int timeout; int count; int send; int run; unsigned int cookie; unsigned int sid; unsigned short port; unsigned short ss_family; union { struct sockaddr addr; struct sockaddr_in6 in6; struct sockaddr_in in; }; socklen_t addr_len; struct fast_ping_packet packet; }; struct fast_ping_notify_event { struct list_head list; struct ping_host_struct *ping_host; FAST_PING_RESULT ping_result; unsigned int seq; int ttl; struct timeval tvresult; }; struct fast_ping_struct { atomic_t run; pthread_t tid; pthread_mutex_t lock; unsigned short ident; int epoll_fd; int no_unprivileged_ping; int fd_icmp; struct ping_host_struct icmp_host; int fd_icmp6; struct ping_host_struct icmp6_host; int fd_udp; struct ping_host_struct udp_host; int fd_udp6; struct ping_host_struct udp6_host; int event_fd; pthread_t notify_tid; pthread_cond_t notify_cond; pthread_mutex_t notify_lock; struct list_head notify_event_list; pthread_mutex_t map_lock; DECLARE_HASHTABLE(addrmap, 6); }; static struct fast_ping_struct ping; static atomic_t ping_sid = ATOMIC_INIT(0); static int bool_print_log = 1; static void _fast_ping_host_put(struct ping_host_struct *ping_host); static void _fast_ping_wakup_thread(void) { uint64_t u = 1; int unused __attribute__((unused)); unused = write(ping.event_fd, &u, sizeof(u)); } static uint16_t _fast_ping_checksum(uint16_t *header, size_t len) { uint32_t sum = 0; unsigned int i = 0; for (i = 0; i < len / sizeof(uint16_t); i++) { sum += ntohs(header[i]); } return htons(~((sum >> 16) + (sum & 0xffff))); } static void _fast_ping_install_filter_v6(int sock) { struct icmp6_filter icmp6_filter; ICMP6_FILTER_SETBLOCKALL(&icmp6_filter); ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &icmp6_filter); setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &icmp6_filter, sizeof(struct icmp6_filter)); static int once; static struct sock_filter insns[] = { BPF_STMT(BPF_LD | BPF_H | BPF_ABS, 4), /* Load icmp echo ident */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1), /* Ours? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* Yes, it passes. */ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, 0), /* Load icmp type */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP6_ECHO_REPLY, 1, 0), /* Echo? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* No. It passes. This must not happen. */ BPF_STMT(BPF_RET | BPF_K, 0), /* Echo with wrong ident. Reject. */ }; static struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (once) { return; } once = 1; /* Patch bpflet for current identifier. */ insns[1] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { perror("WARNING: failed to install socket filter\n"); } } static void _fast_ping_install_filter_v4(int sock) { static int once; static struct sock_filter insns[] = { BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. F..g BSD... Look into ping6. */ BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, 0xAAAA, 0, 1), /* Ours? */ BPF_STMT(BPF_RET | BPF_K, ~0U), /* Yes, it passes. */ BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */ BPF_STMT(BPF_RET | BPF_K, 0xFFFFFFF), /* No. It passes. */ BPF_STMT(BPF_RET | BPF_K, 0) /* Echo with wrong ident. Reject. */ }; static struct sock_fprog filter = {sizeof insns / sizeof(insns[0]), insns}; if (once) { return; } once = 1; /* Patch bpflet for current identifier. */ insns[2] = (struct sock_filter)BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(ping.ident), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { perror("WARNING: failed to install socket filter\n"); } } static int _fast_ping_sockaddr_ip_cmp(struct sockaddr *first_addr, socklen_t first_addr_len, struct sockaddr *second_addr, socklen_t second_addr_len) { if (first_addr_len != second_addr_len) { return -1; } if (first_addr->sa_family != second_addr->sa_family) { return -1; } switch (first_addr->sa_family) { case AF_INET: { struct sockaddr_in *first_addr_in = (struct sockaddr_in *)first_addr; struct sockaddr_in *second_addr_in = (struct sockaddr_in *)second_addr; if (memcmp(&first_addr_in->sin_addr.s_addr, &second_addr_in->sin_addr.s_addr, IPV4_ADDR_LEN) != 0) { return -1; } } break; case AF_INET6: { struct sockaddr_in6 *first_addr_in6 = (struct sockaddr_in6 *)first_addr; struct sockaddr_in6 *second_addr_in6 = (struct sockaddr_in6 *)second_addr; if (memcmp(&first_addr_in6->sin6_addr.s6_addr, &second_addr_in6->sin6_addr.s6_addr, IPV4_ADDR_LEN) != 0) { return -1; } } break; default: return -1; } return 0; } static uint32_t _fast_ping_hash_key(unsigned int sid, struct sockaddr *addr) { uint32_t key = 0; void *sin_addr = NULL; unsigned int sin_addr_len = 0; switch (addr->sa_family) { case AF_INET: { struct sockaddr_in *addr_in = NULL; addr_in = (struct sockaddr_in *)addr; sin_addr = &addr_in->sin_addr.s_addr; sin_addr_len = IPV4_ADDR_LEN; } break; case AF_INET6: { struct sockaddr_in6 *addr_in6 = NULL; addr_in6 = (struct sockaddr_in6 *)addr; if (IN6_IS_ADDR_V4MAPPED(&addr_in6->sin6_addr)) { sin_addr = addr_in6->sin6_addr.s6_addr + 12; sin_addr_len = IPV4_ADDR_LEN; } else { sin_addr = addr_in6->sin6_addr.s6_addr; sin_addr_len = IPV6_ADDR_LEN; } } break; default: goto errout; break; } if (sin_addr == NULL) { return -1; } key = jhash(sin_addr, sin_addr_len, 0); key = jhash(&sid, sizeof(sid), key); return key; errout: return -1; } static struct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; int errcode = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; errcode = getaddrinfo(host, port, &hints, &result); if (errcode != 0) { tlog(TLOG_ERROR, "get addr info failed. host:%s, port: %s, error %s\n", host, port, gai_strerror(errcode)); goto errout; } return result; errout: if (result) { freeaddrinfo(result); } return NULL; } static int _fast_ping_getdomain(const char *host) { struct addrinfo hints; struct addrinfo *result = NULL; int domain = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; if (getaddrinfo(host, NULL, &hints, &result) != 0) { tlog(TLOG_ERROR, "get addr info failed. %s\n", strerror(errno)); goto errout; } domain = result->ai_family; freeaddrinfo(result); return domain; errout: if (result) { freeaddrinfo(result); } return -1; } static void _fast_ping_host_get(struct ping_host_struct *ping_host) { if (atomic_inc_return(&ping_host->ref) <= 0) { tlog(TLOG_ERROR, "BUG: ping host ref is invalid, host: %s", ping_host->host); abort(); } } static void _fast_ping_close_host_sock(struct ping_host_struct *ping_host) { if (ping_host->fd < 0) { return; } struct epoll_event *event = NULL; event = (struct epoll_event *)1; epoll_ctl(ping.epoll_fd, EPOLL_CTL_DEL, ping_host->fd, event); close(ping_host->fd); ping_host->fd = -1; } static void _fast_ping_release_notify_event(struct fast_ping_notify_event *ping_notify_event) { pthread_mutex_lock(&ping.notify_lock); list_del_init(&ping_notify_event->list); pthread_mutex_unlock(&ping.notify_lock); if (ping_notify_event->ping_host) { _fast_ping_host_put(ping_notify_event->ping_host); ping_notify_event->ping_host = NULL; } free(ping_notify_event); } static int _fast_ping_send_notify_event(struct ping_host_struct *ping_host, FAST_PING_RESULT ping_result, unsigned int seq, int ttl, struct timeval *tvresult) { struct fast_ping_notify_event *notify_event = NULL; notify_event = malloc(sizeof(struct fast_ping_notify_event)); if (notify_event == NULL) { goto errout; } memset(notify_event, 0, sizeof(struct fast_ping_notify_event)); INIT_LIST_HEAD(¬ify_event->list); notify_event->seq = seq; notify_event->ttl = ttl; notify_event->ping_result = ping_result; notify_event->tvresult = *tvresult; pthread_mutex_lock(&ping.notify_lock); if (list_empty(&ping.notify_event_list)) { pthread_cond_signal(&ping.notify_cond); } list_add_tail(¬ify_event->list, &ping.notify_event_list); notify_event->ping_host = ping_host; _fast_ping_host_get(ping_host); pthread_mutex_unlock(&ping.notify_lock); return 0; errout: if (notify_event) { _fast_ping_release_notify_event(notify_event); } return -1; } static void _fast_ping_host_put(struct ping_host_struct *ping_host) { int ref_cnt = atomic_dec_and_test(&ping_host->ref); if (!ref_cnt) { if (ref_cnt < 0) { tlog(TLOG_ERROR, "invalid refcount of ping_host %s", ping_host->host); abort(); } return; } _fast_ping_close_host_sock(ping_host); pthread_mutex_lock(&ping.map_lock); hash_del(&ping_host->addr_node); pthread_mutex_unlock(&ping.map_lock); if (atomic_inc_return(&ping_host->notified) == 1) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; _fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv); } tlog(TLOG_DEBUG, "ping %s end, id %d", ping_host->host, ping_host->sid); ping_host->type = FAST_PING_END; free(ping_host); } static void _fast_ping_host_remove(struct ping_host_struct *ping_host) { _fast_ping_close_host_sock(ping_host); pthread_mutex_lock(&ping.map_lock); if (!hash_hashed(&ping_host->addr_node)) { pthread_mutex_unlock(&ping.map_lock); return; } hash_del(&ping_host->addr_node); pthread_mutex_unlock(&ping.map_lock); if (atomic_inc_return(&ping_host->notified) == 1) { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 0; _fast_ping_send_notify_event(ping_host, PING_RESULT_END, ping_host->seq, ping_host->ttl, &tv); } _fast_ping_host_put(ping_host); } static int _fast_ping_sendping_v6(struct ping_host_struct *ping_host) { struct fast_ping_packet *packet = &ping_host->packet; struct icmp6_hdr *icmp6 = &packet->icmp6; int len = 0; ping_host->seq++; memset(icmp6, 0, sizeof(*icmp6)); icmp6->icmp6_type = ICMP6_ECHO_REQUEST; icmp6->icmp6_code = 0; icmp6->icmp6_cksum = 0; icmp6->icmp6_id = ping.ident; icmp6->icmp6_seq = htons(ping_host->seq); gettimeofday(&packet->msg.tv, NULL); gettimeofday(&ping_host->last, NULL); packet->msg.sid = ping_host->sid; packet->msg.cookie = ping_host->cookie; packet->msg.seq = ping_host->seq; icmp6->icmp6_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet)); len = sendto(ping.fd_icmp6, &ping_host->packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr, ping_host->addr_len); if (len < 0 || len != sizeof(struct fast_ping_packet)) { int err = errno; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EHOSTUNREACH) { goto errout; } if (errno == EACCES) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", gethost_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } static int _fast_ping_sendping_v4(struct ping_host_struct *ping_host) { struct fast_ping_packet *packet = &ping_host->packet; struct icmp *icmp = &packet->icmp; int len = 0; ping_host->seq++; memset(icmp, 0, sizeof(*icmp)); icmp->icmp_type = ICMP_ECHO; icmp->icmp_code = 0; icmp->icmp_cksum = 0; icmp->icmp_id = ping.ident; icmp->icmp_seq = htons(ping_host->seq); gettimeofday(&packet->msg.tv, NULL); gettimeofday(&ping_host->last, NULL); packet->msg.sid = ping_host->sid; packet->msg.seq = ping_host->seq; packet->msg.cookie = ping_host->cookie; icmp->icmp_cksum = _fast_ping_checksum((void *)packet, sizeof(struct fast_ping_packet)); len = sendto(ping.fd_icmp, packet, sizeof(struct fast_ping_packet), 0, &ping_host->addr, ping_host->addr_len); if (len < 0 || len != sizeof(struct fast_ping_packet)) { int err = errno; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", gethost_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } static int _fast_ping_sendping_udp(struct ping_host_struct *ping_host) { struct ping_dns_head dns_head; int len = 0; int flag = 0; int fd = 0; flag |= (0 << 15) & 0x8000; flag |= (2 << 11) & 0x7800; flag |= (0 << 10) & 0x0400; flag |= (0 << 9) & 0x0200; flag |= (0 << 8) & 0x0100; flag |= (0 << 7) & 0x0080; flag |= (0 << 0) & 0x000F; if (ping_host->type == FAST_PING_UDP) { fd = ping.fd_udp; } else if (ping_host->type == FAST_PING_UDP6) { fd = ping.fd_udp6; } else { return -1; } ping_host->seq++; memset(&dns_head, 0, sizeof(dns_head)); dns_head.id = htons(ping_host->sid); dns_head.flag = flag; dns_head.qdcount = htons(1); dns_head.qd_name = 0; dns_head.q_qtype = htons(2); /* DNS_T_NS */ dns_head.q_qclass = htons(1); gettimeofday(&ping_host->last, NULL); len = sendto(fd, &dns_head, sizeof(dns_head), 0, &ping_host->addr, ping_host->addr_len); if (len < 0 || len != sizeof(dns_head)) { int err = errno; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", gethost_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(err)); goto errout; } return 0; errout: return -1; } static int _fast_ping_sendping_tcp(struct ping_host_struct *ping_host) { struct epoll_event event; int flags = 0; int fd = -1; int yes = 1; const int priority = SOCKET_PRIORITY; const int ip_tos = IP_TOS; _fast_ping_close_host_sock(ping_host); fd = socket(ping_host->ss_family, SOCK_STREAM, 0); if (fd < 0) { goto errout; } flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)); setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); set_sock_keepalive(fd, 0, 0, 0); /* Set the socket lingering so we will RST connections instead of wasting * bandwidth with the four-step close */ set_sock_lingertime(fd, 0); ping_host->seq++; if (connect(fd, &ping_host->addr, ping_host->addr_len) != 0) { if (errno != EINPROGRESS) { char ping_host_name[PING_MAX_HOSTLEN]; if (errno == ENETUNREACH || errno == EINVAL || errno == EADDRNOTAVAIL || errno == EHOSTUNREACH) { goto errout; } if (errno == EACCES) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_ERROR, "connect %s, id %d, %s", gethost_by_addr(ping_host_name, sizeof(ping_host_name), (struct sockaddr *)&ping_host->addr), ping_host->sid, strerror(errno)); goto errout; } } gettimeofday(&ping_host->last, NULL); ping_host->fd = fd; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT | EPOLLERR; event.data.ptr = ping_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { ping_host->fd = -1; goto errout; } return 0; errout: if (fd > 0) { close(fd); ping_host->fd = -1; } return -1; } static int _fast_ping_sendping(struct ping_host_struct *ping_host) { int ret = -1; gettimeofday(&ping_host->last, NULL); if (ping_host->type == FAST_PING_ICMP) { ret = _fast_ping_sendping_v4(ping_host); } else if (ping_host->type == FAST_PING_ICMP6) { ret = _fast_ping_sendping_v6(ping_host); } else if (ping_host->type == FAST_PING_TCP) { ret = _fast_ping_sendping_tcp(ping_host); } else if (ping_host->type == FAST_PING_UDP || ping_host->type == FAST_PING_UDP6) { ret = _fast_ping_sendping_udp(ping_host); } ping_host->send = 1; if (ret != 0) { ping_host->error = errno; return ret; } else { ping_host->error = 0; } return 0; } static int _fast_ping_create_icmp_sock(FAST_PING_TYPE type) { int fd = -1; struct ping_host_struct *icmp_host = NULL; struct epoll_event event; int buffsize = 64 * 1024; socklen_t optlen = sizeof(buffsize); const int val = 255; const int on = 1; const int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY); switch (type) { case FAST_PING_ICMP: if (ping.no_unprivileged_ping == 0) { fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); } else { fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (fd > 0) { _fast_ping_install_filter_v4(fd); } } if (fd < 0) { if (errno == EACCES || errno == EAFNOSUPPORT) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_ERROR, "create icmp socket failed, %s\n", strerror(errno)); goto errout; } icmp_host = &ping.icmp_host; break; case FAST_PING_ICMP6: if (ping.no_unprivileged_ping == 0) { fd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6); } else { fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (fd > 0) { _fast_ping_install_filter_v6(fd); } } if (fd < 0) { if (errno == EACCES || errno == EAFNOSUPPORT) { if (bool_print_log == 0) { goto errout; } bool_print_log = 0; } tlog(TLOG_ERROR, "create icmp socket failed, %s\n", strerror(errno)); goto errout; } setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); icmp_host = &ping.icmp6_host; break; default: return -1; } struct icmp_filter filt; filt.data = ~((1 << ICMP_SOURCE_QUENCH) | (1 << ICMP_DEST_UNREACH) | (1 << ICMP_TIME_EXCEEDED) | (1 << ICMP_PARAMETERPROB) | (1 << ICMP_REDIRECT) | (1 << ICMP_ECHOREPLY)); setsockopt(fd, SOL_RAW, ICMP_FILTER, &filt, sizeof filt); setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char *)&buffsize, optlen); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char *)&buffsize, optlen); setsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); icmp_host->fd = fd; icmp_host->type = type; memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = icmp_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { goto errout; } return fd; errout: close(fd); if (icmp_host) { icmp_host->fd = -1; icmp_host->type = 0; } return -1; } static int _fast_ping_create_icmp(FAST_PING_TYPE type) { int fd = 0; int *set_fd = NULL; pthread_mutex_lock(&ping.lock); switch (type) { case FAST_PING_ICMP: set_fd = &ping.fd_icmp; break; case FAST_PING_ICMP6: set_fd = &ping.fd_icmp6; break; default: goto errout; break; } if (*set_fd > 0) { goto out; } fd = _fast_ping_create_icmp_sock(type); if (fd < 0) { goto errout; } *set_fd = fd; out: pthread_mutex_unlock(&ping.lock); return *set_fd; errout: if (fd > 0) { close(fd); } pthread_mutex_unlock(&ping.lock); return -1; } static int _fast_ping_create_udp_sock(FAST_PING_TYPE type) { int fd = -1; struct ping_host_struct *udp_host = NULL; struct epoll_event event; const int val = 255; const int on = 1; const int ip_tos = (IPTOS_LOWDELAY | IPTOS_RELIABILITY); switch (type) { case FAST_PING_UDP: fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create udp socket failed, %s\n", strerror(errno)); goto errout; } udp_host = &ping.udp_host; udp_host->type = FAST_PING_UDP; break; case FAST_PING_UDP6: fd = socket(AF_INET6, SOCK_DGRAM, 0); if (fd < 0) { tlog(TLOG_ERROR, "create udp socket failed, %s\n", strerror(errno)); goto errout; } udp_host = &ping.udp6_host; udp_host->type = FAST_PING_UDP6; setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_2292HOPLIMIT, &on, sizeof(on)); setsockopt(fd, IPPROTO_IPV6, IPV6_HOPLIMIT, &on, sizeof(on)); break; default: return -1; } setsockopt(fd, SOL_IP, IP_TTL, &val, sizeof(val)); setsockopt(fd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)); setsockopt(fd, IPPROTO_IP, IP_TOS, &ip_tos, sizeof(ip_tos)); memset(&event, 0, sizeof(event)); event.events = EPOLLIN; event.data.ptr = udp_host; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fd, &event) != 0) { goto errout; } udp_host->fd = fd; return fd; errout: close(fd); return -1; } static int _fast_ping_create_udp(FAST_PING_TYPE type) { int fd = 0; int *set_fd = NULL; pthread_mutex_lock(&ping.lock); switch (type) { case FAST_PING_UDP: set_fd = &ping.fd_udp; break; case FAST_PING_UDP6: set_fd = &ping.fd_udp6; break; default: goto errout; break; } if (*set_fd > 0) { goto out; } fd = _fast_ping_create_udp_sock(type); if (fd < 0) { goto errout; } *set_fd = fd; out: pthread_mutex_unlock(&ping.lock); return *set_fd; errout: if (fd > 0) { close(fd); } pthread_mutex_unlock(&ping.lock); return -1; } static void _fast_ping_print_result(struct ping_host_struct *ping_host, const char *host, FAST_PING_RESULT result, struct sockaddr *addr, socklen_t addr_len, int seqno, int ttl, struct timeval *tv, int error, void *userptr) { if (result == PING_RESULT_RESPONSE) { double rtt = tv->tv_sec * 1000.0 + tv->tv_usec / 1000.0; tlog(TLOG_INFO, "from %15s: seq=%d ttl=%d time=%.3f\n", host, seqno, ttl, rtt); } else if (result == PING_RESULT_TIMEOUT) { tlog(TLOG_INFO, "from %15s: seq=%d timeout\n", host, seqno); } else if (result == PING_RESULT_ERROR) { tlog(TLOG_DEBUG, "from %15s: error is %s\n", host, strerror(error)); } else if (result == PING_RESULT_END) { fast_ping_stop(ping_host); } } static int _fast_ping_get_addr_by_icmp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; int domain = -1; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char *service = NULL; socktype = SOCK_RAW; domain = _fast_ping_getdomain(ip_str); if (domain < 0) { goto errout; } switch (domain) { case AF_INET: sockproto = IPPROTO_ICMP; ping_type = FAST_PING_ICMP; break; case AF_INET6: sockproto = IPPROTO_ICMPV6; ping_type = FAST_PING_ICMP6; break; default: goto errout; break; } if (_fast_ping_create_icmp(ping_type) < 0) { goto errout; } gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; *out_ping_type = ping_type; return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } static int _fast_ping_get_addr_by_tcp(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char *service = NULL; char port_str[MAX_IP_LEN]; if (port <= 0) { port = 80; } sockproto = 0; socktype = SOCK_STREAM; snprintf(port_str, MAX_IP_LEN, "%d", port); service = port_str; ping_type = FAST_PING_TCP; gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; *out_ping_type = ping_type; return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } static int _fast_ping_get_addr_by_dns(const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { struct addrinfo *gai = NULL; int socktype = 0; FAST_PING_TYPE ping_type = 0; int sockproto = 0; char port_str[MAX_IP_LEN]; int domain = -1; char *service = NULL; if (port <= 0) { port = 53; } domain = _fast_ping_getdomain(ip_str); if (domain < 0) { goto errout; } switch (domain) { case AF_INET: ping_type = FAST_PING_UDP; break; case AF_INET6: ping_type = FAST_PING_UDP6; break; default: goto errout; break; } sockproto = 0; socktype = SOCK_DGRAM; snprintf(port_str, MAX_IP_LEN, "%d", port); service = port_str; if (_fast_ping_create_udp(ping_type) < 0) { goto errout; } gai = _fast_ping_getaddr(ip_str, service, socktype, sockproto); if (gai == NULL) { goto errout; } *out_gai = gai; *out_ping_type = ping_type; return 0; errout: if (gai) { freeaddrinfo(gai); } return -1; } static int _fast_ping_get_addr_by_type(PING_TYPE type, const char *ip_str, int port, struct addrinfo **out_gai, FAST_PING_TYPE *out_ping_type) { switch (type) { case PING_TYPE_ICMP: return _fast_ping_get_addr_by_icmp(ip_str, port, out_gai, out_ping_type); break; case PING_TYPE_TCP: return _fast_ping_get_addr_by_tcp(ip_str, port, out_gai, out_ping_type); break; case PING_TYPE_DNS: return _fast_ping_get_addr_by_dns(ip_str, port, out_gai, out_ping_type); break; default: break; } return -1; } struct ping_host_struct *fast_ping_start(PING_TYPE type, const char *host, int count, int interval, int timeout, fast_ping_result ping_callback, void *userptr) { struct ping_host_struct *ping_host = NULL; struct addrinfo *gai = NULL; uint32_t addrkey = 0; char ip_str[PING_MAX_HOSTLEN]; int port = -1; FAST_PING_TYPE ping_type = FAST_PING_END; unsigned int seed = 0; int ret = 0; if (parse_ip(host, ip_str, &port) != 0) { goto errout; } ret = _fast_ping_get_addr_by_type(type, ip_str, port, &gai, &ping_type); if (ret != 0) { goto errout; } ping_host = malloc(sizeof(*ping_host)); if (ping_host == NULL) { goto errout; } memset(ping_host, 0, sizeof(*ping_host)); safe_strncpy(ping_host->host, host, PING_MAX_HOSTLEN); ping_host->fd = -1; ping_host->timeout = timeout; ping_host->count = count; ping_host->type = ping_type; ping_host->userptr = userptr; atomic_set(&ping_host->ref, 0); atomic_set(&ping_host->notified, 0); ping_host->sid = atomic_inc_return(&ping_sid); seed = ping_host->sid; ping_host->cookie = rand_r(&seed); ping_host->run = 0; if (ping_callback) { ping_host->ping_callback = ping_callback; } else { ping_host->ping_callback = _fast_ping_print_result; } ping_host->interval = (timeout > interval) ? timeout : interval; ping_host->addr_len = gai->ai_addrlen; ping_host->port = port; ping_host->ss_family = gai->ai_family; if (gai->ai_addrlen > sizeof(struct sockaddr_in6)) { goto errout; } memcpy(&ping_host->addr, gai->ai_addr, gai->ai_addrlen); tlog(TLOG_DEBUG, "ping %s, id = %d", host, ping_host->sid); addrkey = _fast_ping_hash_key(ping_host->sid, &ping_host->addr); _fast_ping_host_get(ping_host); _fast_ping_host_get(ping_host); // for ping race condition, get reference count twice if (_fast_ping_sendping(ping_host) != 0) { goto errout_remove; } pthread_mutex_lock(&ping.map_lock); _fast_ping_host_get(ping_host); if (hash_empty(ping.addrmap)) { _fast_ping_wakup_thread(); } hash_add(ping.addrmap, &ping_host->addr_node, addrkey); ping_host->run = 1; pthread_mutex_unlock(&ping.map_lock); freeaddrinfo(gai); _fast_ping_host_put(ping_host); return ping_host; errout_remove: ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_ERROR, &ping_host->addr, ping_host->addr_len, ping_host->seq, ping_host->ttl, NULL, ping_host->error, ping_host->userptr); fast_ping_stop(ping_host); _fast_ping_host_put(ping_host); ping_host = NULL; errout: if (gai) { freeaddrinfo(gai); } if (ping_host) { free(ping_host); } return NULL; } int fast_ping_stop(struct ping_host_struct *ping_host) { if (ping_host == NULL) { return 0; } atomic_inc_return(&ping_host->notified); _fast_ping_host_remove(ping_host); _fast_ping_host_put(ping_host); return 0; } static void tv_sub(struct timeval *out, struct timeval *in) { if ((out->tv_usec -= in->tv_usec) < 0) { /* out -= in */ --out->tv_sec; out->tv_usec += 1000000; } out->tv_sec -= in->tv_sec; } static struct fast_ping_packet *_fast_ping_icmp6_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len) { int icmp_len = 0; struct fast_ping_packet *packet = (struct fast_ping_packet *)packet_data; struct icmp6_hdr *icmp6 = &packet->icmp6; struct cmsghdr *c = NULL; int hops = 0; for (c = CMSG_FIRSTHDR(msg); c; c = CMSG_NXTHDR(msg, c)) { if (c->cmsg_level != IPPROTO_IPV6) { continue; } switch (c->cmsg_type) { case IPV6_HOPLIMIT: #ifdef IPV6_2292HOPLIMIT case IPV6_2292HOPLIMIT: #endif if (c->cmsg_len < CMSG_LEN(sizeof(int))) { continue; } memcpy(&hops, CMSG_DATA(c), sizeof(hops)); } } packet->ttl = hops; if (icmp6->icmp6_type != ICMP6_ECHO_REPLY) { errno = ENETUNREACH; return NULL; } icmp_len = data_len; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } if (ping.no_unprivileged_ping) { if (icmp6->icmp6_id != ping.ident) { tlog(TLOG_ERROR, "ident failed, %d:%d", icmp6->icmp6_id, ping.ident); return NULL; } } return packet; } static struct fast_ping_packet *_fast_ping_icmp_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *packet_data, int data_len) { struct ip *ip = (struct ip *)packet_data; struct fast_ping_packet *packet = NULL; struct icmp *icmp = NULL; int hlen = 0; int icmp_len = 0; hlen = ip->ip_hl << 2; packet = (struct fast_ping_packet *)(packet_data + hlen); icmp = &packet->icmp; icmp_len = data_len - hlen; packet->ttl = ip->ip_ttl; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } if (icmp->icmp_type != ICMP_ECHOREPLY) { errno = ENETUNREACH; return NULL; } if (ping.no_unprivileged_ping) { if (ip->ip_p != IPPROTO_ICMP) { tlog(TLOG_ERROR, "ip type faild, %d:%d", ip->ip_p, IPPROTO_ICMP); return NULL; } if (icmp->icmp_id != ping.ident) { tlog(TLOG_ERROR, "ident failed, %d:%d", icmp->icmp_id, ping.ident); return NULL; } } return packet; } static struct fast_ping_packet *_fast_ping_recv_packet(struct ping_host_struct *ping_host, struct msghdr *msg, u_char *inpacket, int len, struct timeval *tvrecv) { struct fast_ping_packet *packet = NULL; if (ping_host->type == FAST_PING_ICMP6) { packet = _fast_ping_icmp6_packet(ping_host, msg, inpacket, len); if (packet == NULL) { goto errout; } } else if (ping_host->type == FAST_PING_ICMP) { packet = _fast_ping_icmp_packet(ping_host, msg, inpacket, len); if (packet == NULL) { goto errout; } } else { tlog(TLOG_ERROR, "ping host type is invalid, %d", ping_host->type); goto errout; } return packet; errout: return NULL; } static int _fast_ping_process_icmp(struct ping_host_struct *ping_host, struct timeval *now) { int len = 0; u_char inpacket[ICMP_INPACKET_SIZE]; struct sockaddr_storage from; struct ping_host_struct *recv_ping_host = NULL; struct fast_ping_packet *packet = NULL; socklen_t from_len = sizeof(from); uint32_t addrkey = 0; struct timeval tvresult = *now; struct timeval *tvsend = NULL; unsigned int sid = 0; unsigned int seq = 0; unsigned int cookie = 0; struct msghdr msg; struct iovec iov; char ans_data[4096]; memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT); if (len < 0) { tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); goto errout; } from_len = msg.msg_namelen; packet = _fast_ping_recv_packet(ping_host, &msg, inpacket, len, now); if (packet == NULL) { char name[PING_MAX_HOSTLEN]; if (errno == ENETUNREACH) { goto errout; } tlog(TLOG_DEBUG, "recv ping packet from %s failed.", gethost_by_addr(name, sizeof(name), (struct sockaddr *)&from)); goto errout; } tvsend = &packet->msg.tv; sid = packet->msg.sid; seq = packet->msg.seq; cookie = packet->msg.cookie; addrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from, from_len) == 0 && recv_ping_host->sid == sid && recv_ping_host->cookie == cookie) { _fast_ping_host_get(recv_ping_host); break; } } pthread_mutex_unlock(&ping.map_lock); if (recv_ping_host == NULL) { return -1; } if (recv_ping_host->seq != seq) { tlog(TLOG_ERROR, "seq num mismatch, expect %u, real %u", recv_ping_host->seq, seq); _fast_ping_host_put(recv_ping_host); return -1; } recv_ping_host->ttl = packet->ttl; tv_sub(&tvresult, tvsend); if (recv_ping_host->ping_callback) { _fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl, &tvresult); } recv_ping_host->send = 0; if (recv_ping_host->count == 1) { _fast_ping_host_remove(recv_ping_host); } _fast_ping_host_put(recv_ping_host); return 0; errout: return -1; } static int _fast_ping_process_tcp(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now) { struct timeval tvresult = *now; struct timeval *tvsend = &ping_host->last; int connect_error = 0; socklen_t len = sizeof(connect_error); if (event->events & EPOLLIN || event->events & EPOLLERR) { if (getsockopt(ping_host->fd, SOL_SOCKET, SO_ERROR, (char *)&connect_error, &len) != 0) { goto errout; } if (connect_error != 0 && connect_error != ECONNREFUSED) { goto errout; } } tv_sub(&tvresult, tvsend); if (ping_host->ping_callback) { _fast_ping_send_notify_event(ping_host, PING_RESULT_RESPONSE, ping_host->seq, ping_host->ttl, &tvresult); } ping_host->send = 0; _fast_ping_close_host_sock(ping_host); if (ping_host->count == 1) { _fast_ping_host_remove(ping_host); } return 0; errout: _fast_ping_host_remove(ping_host); return -1; } static int _fast_ping_process_udp(struct ping_host_struct *ping_host, struct timeval *now) { ssize_t len = 0; u_char inpacket[ICMP_INPACKET_SIZE]; struct sockaddr_storage from; struct ping_host_struct *recv_ping_host = NULL; struct ping_dns_head *dns_head = NULL; socklen_t from_len = sizeof(from); uint32_t addrkey = 0; struct timeval tvresult = *now; struct timeval *tvsend = NULL; unsigned int sid = 0; struct msghdr msg; struct iovec iov; char ans_data[4096]; struct cmsghdr *cmsg = NULL; int ttl = 0; memset(&msg, 0, sizeof(msg)); iov.iov_base = (char *)inpacket; iov.iov_len = sizeof(inpacket); msg.msg_name = &from; msg.msg_namelen = sizeof(from); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = ans_data; msg.msg_controllen = sizeof(ans_data); len = recvmsg(ping_host->fd, &msg, MSG_DONTWAIT); if (len < 0) { tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); goto errout; } for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_TTL) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } else if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_HOPLIMIT) { if (cmsg->cmsg_len >= sizeof(int)) { int *ttlPtr = (int *)CMSG_DATA(cmsg); ttl = *ttlPtr; } } } from_len = msg.msg_namelen; dns_head = (struct ping_dns_head *)inpacket; if (len < (ssize_t)sizeof(*dns_head)) { goto errout; } sid = ntohs(dns_head->id); addrkey = _fast_ping_hash_key(sid, (struct sockaddr *)&from); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (_fast_ping_sockaddr_ip_cmp(&recv_ping_host->addr, recv_ping_host->addr_len, (struct sockaddr *)&from, from_len) == 0 && recv_ping_host->sid == sid) { _fast_ping_host_get(recv_ping_host); break; } } pthread_mutex_unlock(&ping.map_lock); if (recv_ping_host == NULL) { return -1; } recv_ping_host->ttl = ttl; tvsend = &recv_ping_host->last; tv_sub(&tvresult, tvsend); if (recv_ping_host->ping_callback) { _fast_ping_send_notify_event(recv_ping_host, PING_RESULT_RESPONSE, recv_ping_host->seq, recv_ping_host->ttl, &tvresult); } recv_ping_host->send = 0; if (recv_ping_host->count == 1) { _fast_ping_host_remove(recv_ping_host); } _fast_ping_host_put(recv_ping_host); return 0; errout: return -1; } static int _fast_ping_process(struct ping_host_struct *ping_host, struct epoll_event *event, struct timeval *now) { int ret = -1; switch (ping_host->type) { case FAST_PING_ICMP6: case FAST_PING_ICMP: ret = _fast_ping_process_icmp(ping_host, now); break; case FAST_PING_TCP: ret = _fast_ping_process_tcp(ping_host, event, now); break; case FAST_PING_UDP6: case FAST_PING_UDP: ret = _fast_ping_process_udp(ping_host, now); break; default: tlog(TLOG_ERROR, "BUG: type error : %p, %d, %s, %d", ping_host, ping_host->sid, ping_host->host, ping_host->fd); abort(); break; } return ret; } static void _fast_ping_remove_all(void) { struct ping_host_struct *ping_host = NULL; struct ping_host_struct *ping_host_tmp = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; LIST_HEAD(remove_list); pthread_mutex_lock(&ping.map_lock); hash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node) { list_add_tail(&ping_host->action_list, &remove_list); } pthread_mutex_unlock(&ping.map_lock); list_for_each_entry_safe(ping_host, ping_host_tmp, &remove_list, action_list) { _fast_ping_host_remove(ping_host); } } static void _fast_ping_period_run(void) { struct ping_host_struct *ping_host = NULL; struct ping_host_struct *ping_host_tmp = NULL; struct hlist_node *tmp = NULL; unsigned long i = 0; struct timeval now; struct timezone tz; struct timeval interval; int64_t millisecond = 0; gettimeofday(&now, &tz); LIST_HEAD(action); pthread_mutex_lock(&ping.map_lock); hash_for_each_safe(ping.addrmap, i, tmp, ping_host, addr_node) { if (ping_host->run == 0) { continue; } interval = now; tv_sub(&interval, &ping_host->last); millisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000; if (millisecond >= ping_host->timeout && ping_host->send == 1) { list_add_tail(&ping_host->action_list, &action); _fast_ping_host_get(ping_host); continue; } if (millisecond < ping_host->interval) { continue; } list_add_tail(&ping_host->action_list, &action); _fast_ping_host_get(ping_host); } pthread_mutex_unlock(&ping.map_lock); list_for_each_entry_safe(ping_host, ping_host_tmp, &action, action_list) { interval = now; tv_sub(&interval, &ping_host->last); millisecond = interval.tv_sec * 1000 + interval.tv_usec / 1000; if (millisecond >= ping_host->timeout && ping_host->send == 1) { _fast_ping_send_notify_event(ping_host, PING_RESULT_TIMEOUT, ping_host->seq, ping_host->ttl, &interval); ping_host->send = 0; } if (millisecond < ping_host->interval) { list_del(&ping_host->action_list); _fast_ping_host_put(ping_host); continue; } if (ping_host->count > 0) { if (ping_host->count == 1) { _fast_ping_host_remove(ping_host); list_del(&ping_host->action_list); _fast_ping_host_put(ping_host); continue; } ping_host->count--; } _fast_ping_sendping(ping_host); list_del(&ping_host->action_list); _fast_ping_host_put(ping_host); } } static void _fast_ping_process_notify_event(struct fast_ping_notify_event *ping_notify_event) { struct ping_host_struct *ping_host = ping_notify_event->ping_host; if (ping_host == NULL) { return; } ping_host->ping_callback(ping_host, ping_host->host, ping_notify_event->ping_result, &ping_host->addr, ping_host->addr_len, ping_notify_event->seq, ping_notify_event->ttl, &ping_notify_event->tvresult, ping_host->error, ping_host->userptr); } static void *_fast_ping_notify_worker(void *arg) { struct fast_ping_notify_event *ping_notify_event = NULL; while (atomic_read(&ping.run)) { pthread_mutex_lock(&ping.notify_lock); if (list_empty(&ping.notify_event_list)) { pthread_cond_wait(&ping.notify_cond, &ping.notify_lock); } ping_notify_event = list_first_entry_or_null(&ping.notify_event_list, struct fast_ping_notify_event, list); if (ping_notify_event) { list_del_init(&ping_notify_event->list); } pthread_mutex_unlock(&ping.notify_lock); if (ping_notify_event == NULL) { continue; } _fast_ping_process_notify_event(ping_notify_event); _fast_ping_release_notify_event(ping_notify_event); } return NULL; } static void _fast_ping_remove_all_notify_event(void) { struct fast_ping_notify_event *notify_event = NULL; struct fast_ping_notify_event *tmp = NULL; list_for_each_entry_safe(notify_event, tmp, &ping.notify_event_list, list) { _fast_ping_process_notify_event(notify_event); _fast_ping_release_notify_event(notify_event); } } static void *_fast_ping_work(void *arg) { struct epoll_event events[PING_MAX_EVENTS + 1]; int num = 0; int i = 0; unsigned long now = {0}; unsigned long last = {0}; struct timeval tvnow = {0}; int sleep = 100; int sleep_time = 0; unsigned long expect_time = 0; setpriority(PRIO_PROCESS, 0, -5); sleep_time = sleep; now = get_tick_count() - sleep; last = now; expect_time = now + sleep; while (atomic_read(&ping.run)) { now = get_tick_count(); if (sleep_time > 0) { sleep_time -= now - last; if (sleep_time <= 0) { sleep_time = 0; } } last = now; if (now >= expect_time) { _fast_ping_period_run(); sleep_time = sleep - (now - expect_time); if (sleep_time < 0) { sleep_time = 0; expect_time = now; } expect_time += sleep; } pthread_mutex_lock(&ping.map_lock); if (hash_empty(ping.addrmap)) { sleep_time = -1; } pthread_mutex_unlock(&ping.map_lock); num = epoll_wait(ping.epoll_fd, events, PING_MAX_EVENTS, sleep_time); if (num < 0) { usleep(100000); continue; } if (sleep_time == -1) { expect_time = get_tick_count(); } if (num == 0) { continue; } gettimeofday(&tvnow, NULL); for (i = 0; i < num; i++) { struct epoll_event *event = &events[i]; /* read event */ if (event->data.fd == ping.event_fd) { uint64_t value; int unused __attribute__((unused)); unused = read(ping.event_fd, &value, sizeof(uint64_t)); continue; } struct ping_host_struct *ping_host = (struct ping_host_struct *)event->data.ptr; _fast_ping_process(ping_host, event, &tvnow); } } close(ping.epoll_fd); ping.epoll_fd = -1; return NULL; } static int _fast_ping_init_wakeup_event(void) { int fdevent = -1; fdevent = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); if (fdevent < 0) { tlog(TLOG_ERROR, "create eventfd failed, %s\n", strerror(errno)); goto errout; } struct epoll_event event; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLERR; event.data.fd = fdevent; if (epoll_ctl(ping.epoll_fd, EPOLL_CTL_ADD, fdevent, &event) != 0) { tlog(TLOG_ERROR, "set eventfd failed, %s\n", strerror(errno)); goto errout; } ping.event_fd = fdevent; return 0; errout: return -1; } int fast_ping_init(void) { pthread_attr_t attr; int epollfd = -1; int ret = 0; bool_print_log = 1; if (ping.epoll_fd > 0) { return -1; } memset(&ping, 0, sizeof(ping)); pthread_attr_init(&attr); epollfd = epoll_create1(EPOLL_CLOEXEC); if (epollfd < 0) { tlog(TLOG_ERROR, "create epoll failed, %s\n", strerror(errno)); goto errout; } pthread_mutex_init(&ping.map_lock, NULL); pthread_mutex_init(&ping.lock, NULL); pthread_mutex_init(&ping.notify_lock, NULL); pthread_cond_init(&ping.notify_cond, NULL); INIT_LIST_HEAD(&ping.notify_event_list); hash_init(ping.addrmap); ping.no_unprivileged_ping = !has_unprivileged_ping(); ping.ident = (getpid() & 0XFFFF); atomic_set(&ping.run, 1); ret = pthread_create(&ping.tid, &attr, _fast_ping_work, NULL); if (ret != 0) { tlog(TLOG_ERROR, "create ping work thread failed, %s\n", strerror(ret)); goto errout; } ret = pthread_create(&ping.notify_tid, &attr, _fast_ping_notify_worker, NULL); if (ret != 0) { tlog(TLOG_ERROR, "create ping notifyer work thread failed, %s\n", strerror(ret)); goto errout; } ping.epoll_fd = epollfd; ret = _fast_ping_init_wakeup_event(); if (ret != 0) { tlog(TLOG_ERROR, "init wakeup event failed, %s\n", strerror(errno)); goto errout; } return 0; errout: if (ping.notify_tid) { void *retval = NULL; atomic_set(&ping.run, 0); pthread_cond_signal(&ping.notify_cond); pthread_join(ping.notify_tid, &retval); ping.notify_tid = 0; } if (ping.tid) { void *retval = NULL; atomic_set(&ping.run, 0); _fast_ping_wakup_thread(); pthread_join(ping.tid, &retval); ping.tid = 0; } if (epollfd > 0) { close(epollfd); } if (ping.event_fd) { close(ping.event_fd); ping.event_fd = -1; } pthread_cond_destroy(&ping.notify_cond); pthread_mutex_destroy(&ping.notify_lock); pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); memset(&ping, 0, sizeof(ping)); return -1; } static void _fast_ping_close_fds(void) { if (ping.fd_icmp > 0) { close(ping.fd_icmp); ping.fd_icmp = -1; } if (ping.fd_icmp6 > 0) { close(ping.fd_icmp6); ping.fd_icmp6 = -1; } if (ping.fd_udp > 0) { close(ping.fd_udp); ping.fd_udp = -1; } if (ping.fd_udp6 > 0) { close(ping.fd_udp6); ping.fd_udp6 = -1; } } void fast_ping_exit(void) { if (ping.notify_tid) { void *retval = NULL; atomic_set(&ping.run, 0); pthread_cond_signal(&ping.notify_cond); pthread_join(ping.notify_tid, &retval); ping.notify_tid = 0; } if (ping.tid) { void *ret = NULL; atomic_set(&ping.run, 0); _fast_ping_wakup_thread(); pthread_join(ping.tid, &ret); ping.tid = 0; } if (ping.event_fd > 0) { close(ping.event_fd); ping.event_fd = -1; } _fast_ping_close_fds(); _fast_ping_remove_all(); _fast_ping_remove_all_notify_event(); pthread_cond_destroy(&ping.notify_cond); pthread_mutex_destroy(&ping.notify_lock); pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); }