/************************************************************************* * * Copyright (C) 2018 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 "tlog.h" #include "util.h" #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 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; }; 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; fast_ping_result ping_callback; char host[PING_MAX_HOSTLEN]; int fd; unsigned int seq; 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_struct { int run; pthread_t tid; pthread_mutex_t lock; unsigned short ident; int epoll_fd; int fd_icmp; struct ping_host_struct icmp_host; int fd_icmp6; struct ping_host_struct icmp6_host; pthread_mutex_t map_lock; DECLARE_HASHTABLE(addrmap, 6); }; static struct fast_ping_struct ping; static atomic_t ping_sid = ATOMIC_INIT(0); uint16_t _fast_ping_checksum(uint16_t *header, size_t len) { uint32_t sum = 0; int i; for (i = 0; i < len / sizeof(uint16_t); i++) { sum += ntohs(header[i]); } return htons(~((sum >> 16) + (sum & 0xffff))); } 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(getpid()), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { perror("WARNING: failed to install socket filter\n"); } } 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(getpid()), 0, 1); if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { perror("WARNING: failed to install socket filter\n"); } } static struct addrinfo *_fast_ping_getaddr(const char *host, const char *port, int type, int protocol) { struct addrinfo hints; struct addrinfo *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = type; hints.ai_protocol = protocol; if (getaddrinfo(host, port, &hints, &result) != 0) { tlog(TLOG_ERROR, "get addr info failed. %s\n", strerror(errno)); 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) { atomic_inc(&ping_host->ref); } static void _fast_ping_close_host_sock(struct ping_host_struct *ping_host) { if (ping_host->fd < 0) { return; } struct epoll_event *event; 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_host_put(struct ping_host_struct *ping_host) { if (!atomic_dec_and_test(&ping_host->ref)) { 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; ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_END, &ping_host->addr, ping_host->addr_len, ping_host->seq, &tv, ping_host->userptr); } tlog(TLOG_DEBUG, "ping %p end", ping_host); // memset(ping_host, 0, sizeof(*ping_host)); 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; ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_END, &ping_host->addr, ping_host->addr_len, ping_host->seq, &tv, ping_host->userptr); } _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 = getpid(); icmp6->icmp6_seq = htons(ping_host->seq); gettimeofday(&packet->msg.tv, 0); 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, (struct sockaddr *)&ping_host->addr, ping_host->addr_len); if (len < 0 || len != sizeof(struct fast_ping_packet)) { int err = errno; if (errno == ENETUNREACH) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", gethost_by_addr(ping_host_name, (struct sockaddr *)&ping_host->addr, ping_host->addr_len), 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; 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, 0); 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, (struct sockaddr *)&ping_host->addr, ping_host->addr_len); if (len < 0 || len != sizeof(struct fast_ping_packet)) { int err = errno; if (errno == ENETUNREACH) { goto errout; } char ping_host_name[PING_MAX_HOSTLEN]; tlog(TLOG_ERROR, "sendto %s, id %d, %s", gethost_by_addr(ping_host_name, (struct sockaddr *)&ping_host->addr, ping_host->addr_len), 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; int fd = -1; _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); ping_host->seq++; if (connect(fd, (struct sockaddr *)&ping_host->addr, ping_host->addr_len) != 0) { if (errno != EINPROGRESS) { char ping_host_name[PING_MAX_HOSTLEN]; if (errno == ENETUNREACH) { goto errout; } tlog(TLOG_ERROR, "connect %s, id %d, %s", gethost_by_addr(ping_host_name, (struct sockaddr *)&ping_host->addr, ping_host->addr_len), ping_host->sid, strerror(errno)); goto errout; } } ping_host->fd = fd; memset(&event, 0, sizeof(event)); event.events = EPOLLIN | EPOLLOUT; 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; 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); } ping_host->send = 1; gettimeofday(&ping_host->last, 0); if (ret != 0) { return ret; } 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); switch (type) { case FAST_PING_ICMP: fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (fd < 0) { tlog(TLOG_ERROR, "create icmp socket failed.\n"); goto errout; } _fast_ping_install_filter_v4(fd); icmp_host = &ping.icmp_host; break; case FAST_PING_ICMP6: fd = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (fd < 0) { tlog(TLOG_ERROR, "create icmp socket failed.\n"); goto errout; } _fast_ping_install_filter_v6(fd); icmp_host = &ping.icmp6_host; break; default: return -1; } setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const char *)&buffsize, optlen); setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const char *)&buffsize, optlen); 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; } icmp_host->fd = fd; icmp_host->type = type; return fd; errout: close(fd); 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; } 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, struct timeval *tv, 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 time=%.3f\n", host, seqno, rtt); } else if (result == PING_RESULT_TIMEOUT) { tlog(TLOG_INFO, "from %15s: seq=%d timeout\n", host, seqno); } else if (result == PING_RESULT_END) { fast_ping_stop(ping_host); } } struct ping_host_struct *fast_ping_start(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; int domain = -1; int icmp_proto = 0; uint32_t addrkey; char ip_str[PING_MAX_HOSTLEN]; char port_str[MAX_IP_LEN]; char *service = NULL; int port = -1; FAST_PING_TYPE type; int socktype = 0; unsigned int seed; if (parse_ip(host, ip_str, &port) != 0) { goto errout; } if (port > 0) { icmp_proto = 0; socktype = SOCK_STREAM; snprintf(port_str, MAX_IP_LEN, "%d", port); service = port_str; type = FAST_PING_TCP; gai = _fast_ping_getaddr(ip_str, service, socktype, icmp_proto); if (gai == NULL) { goto errout; } } else { socktype = SOCK_RAW; domain = _fast_ping_getdomain(ip_str); if (domain < 0) { return NULL; } switch (domain) { case AF_INET: icmp_proto = IPPROTO_ICMP; type = FAST_PING_ICMP; break; case AF_INET6: icmp_proto = IPPROTO_ICMPV6; type = FAST_PING_ICMP6; break; default: return NULL; break; } if (_fast_ping_create_icmp(type) < 0) { goto errout; } gai = _fast_ping_getaddr(ip_str, service, socktype, icmp_proto); if (gai == NULL) { goto errout; } } ping_host = malloc(sizeof(*ping_host)); if (ping_host == NULL) { goto errout; } memset(ping_host, 0, sizeof(*ping_host)); strncpy(ping_host->host, host, PING_MAX_HOSTLEN); ping_host->fd = -1; ping_host->timeout = timeout; ping_host->count = count; ping_host->type = 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); freeaddrinfo(gai); tlog(TLOG_DEBUG, "ping %s, id = %d", host, ping_host->sid); addrkey = jhash(&ping_host->addr, ping_host->addr_len, 0); addrkey = jhash(&ping_host->sid, sizeof(ping_host->sid), addrkey); pthread_mutex_lock(&ping.map_lock); _fast_ping_host_get(ping_host); hash_add(ping.addrmap, &ping_host->addr_node, addrkey); pthread_mutex_unlock(&ping.map_lock); _fast_ping_host_get(ping_host); _fast_ping_host_get(ping_host); if (_fast_ping_sendping(ping_host) != 0) { goto errout_remove; } ping_host->run = 1; _fast_ping_host_put(ping_host); return ping_host; errout: if (gai) { freeaddrinfo(gai); } if (ping_host) { free(ping_host); } return NULL; errout_remove: fast_ping_stop(ping_host); _fast_ping_host_put(ping_host); return NULL; } int fast_ping_stop(struct ping_host_struct *ping_host) { 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, u_char *packet_data, int data_len) { int icmp_len; struct fast_ping_packet *packet = (struct fast_ping_packet *)packet_data; struct icmp6_hdr *icmp6 = &packet->icmp6; if (icmp6->icmp6_type != ICMP6_ECHO_REPLY) { tlog(TLOG_DEBUG, "icmp6 type faild, %d:%d", icmp6->icmp6_type, ICMP6_ECHO_REPLY); return NULL; } icmp_len = data_len; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } 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, u_char *packet_data, int data_len) { struct ip *ip = (struct ip *)packet_data; struct fast_ping_packet *packet; struct icmp *icmp; int hlen; int icmp_len; if (ip->ip_p != IPPROTO_ICMP) { tlog(TLOG_ERROR, "ip type faild, %d:%d", ip->ip_p, IPPROTO_ICMP); return NULL; } hlen = ip->ip_hl << 2; packet = (struct fast_ping_packet *)(packet_data + hlen); icmp = &packet->icmp; icmp_len = data_len - hlen; if (icmp_len < 16) { tlog(TLOG_ERROR, "length is invalid, %d", icmp_len); return NULL; } if (icmp->icmp_type != ICMP_ECHOREPLY) { tlog(TLOG_DEBUG, "icmp type faild, %d:%d", icmp->icmp_type, ICMP_ECHOREPLY); return NULL; } if (icmp->icmp_id != ping.ident) { tlog(TLOG_ERROR, "ident failed, %d:%d", icmp->icmp_id, ping.ident); return NULL; } return packet; } struct fast_ping_packet *_fast_ping_recv_packet(struct ping_host_struct *ping_host, 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, inpacket, len); if (packet == NULL) { goto errout; } } else if (ping_host->type == FAST_PING_ICMP) { packet = _fast_ping_icmp_packet(ping_host, 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; u_char inpacket[ICMP_INPACKET_SIZE]; struct sockaddr_storage from; struct ping_host_struct *recv_ping_host; struct fast_ping_packet *packet = NULL; socklen_t from_len = sizeof(from); uint32_t addrkey; struct timeval tvresult = *now; struct timeval *tvsend = NULL; unsigned int sid; unsigned int seq; unsigned int cookie; len = recvfrom(ping_host->fd, inpacket, sizeof(inpacket), 0, (struct sockaddr *)&from, (socklen_t *)&from_len); if (len < 0) { tlog(TLOG_ERROR, "recvfrom failed, %s\n", strerror(errno)); goto errout; } packet = _fast_ping_recv_packet(ping_host, inpacket, len, now); if (packet == NULL) { char name[PING_MAX_HOSTLEN]; tlog(TLOG_DEBUG, "recv ping packet from %s failed.", gethost_by_addr(name, (struct sockaddr *)&from, from_len)); goto errout; } addrkey = jhash(&from, from_len, 0); tvsend = &packet->msg.tv; sid = packet->msg.sid; seq = packet->msg.seq; cookie = packet->msg.cookie; addrkey = jhash(&sid, sizeof(sid), addrkey); pthread_mutex_lock(&ping.map_lock); hash_for_each_possible(ping.addrmap, recv_ping_host, addr_node, addrkey) { if (recv_ping_host->addr_len == from_len && memcmp(&recv_ping_host->addr, &from, from_len) == 0 && recv_ping_host->sid == sid && recv_ping_host->cookie == cookie) { 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); return -1; } tv_sub(&tvresult, tvsend); if (recv_ping_host->ping_callback) { recv_ping_host->ping_callback(recv_ping_host, recv_ping_host->host, PING_RESULT_RESPONSE, &recv_ping_host->addr, recv_ping_host->addr_len, recv_ping_host->seq, &tvresult, recv_ping_host->userptr); } recv_ping_host->send = 0; if (recv_ping_host->count == 1) { _fast_ping_host_remove(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) { ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_RESPONSE, &ping_host->addr, ping_host->addr_len, ping_host->seq, &tvresult, ping_host->userptr); } ping_host->send = 0; 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(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; 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; int i; 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; int i = 0; struct timeval now; struct timeval interval; int64_t millisecond; gettimeofday(&now, 0); 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) { ping_host->ping_callback(ping_host, ping_host->host, PING_RESULT_TIMEOUT, &ping_host->addr, ping_host->addr_len, ping_host->seq, &interval, ping_host->userptr); 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_work(void *arg) { struct epoll_event events[PING_MAX_EVENTS + 1]; int num; int i; unsigned long now = {0}; struct timeval tvnow = {0}; int sleep = 100; int sleep_time = 0; unsigned long expect_time = 0; sleep_time = sleep; now = get_tick_count() - sleep; expect_time = now + sleep; while (ping.run) { now = get_tick_count(); if (now >= expect_time) { _fast_ping_period_run(); sleep_time = sleep - (now - expect_time); if (sleep_time < 0) { sleep_time = 0; } expect_time += sleep; } num = epoll_wait(ping.epoll_fd, events, PING_MAX_EVENTS, sleep_time); if (num < 0) { usleep(100000); continue; } if (num == 0) { continue; } gettimeofday(&tvnow, 0); for (i = 0; i < num; i++) { struct epoll_event *event = &events[i]; 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; } int fast_ping_init(void) { pthread_attr_t attr; int epollfd = -1; int ret; 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, 0); pthread_mutex_init(&ping.lock, 0); hash_init(ping.addrmap); ping.epoll_fd = epollfd; ping.ident = getpid(); 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(errno)); goto errout; } return 0; errout: if (ping.tid > 0) { void *retval = NULL; ping.run = 0; pthread_join(ping.tid, &retval); } if (epollfd) { close(epollfd); } pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); return -1; } void fast_ping_exit(void) { if (ping.tid > 0) { void *ret = NULL; ping.run = 0; pthread_join(ping.tid, &ret); } 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; } _fast_ping_remove_all(); pthread_mutex_destroy(&ping.lock); pthread_mutex_destroy(&ping.map_lock); }