From: German Service Network Date: Mon, 2 Feb 2026 06:14:13 +0000 (+0100) Subject: Change from pthread to select loop X-Git-Url: https://git.gsnw.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=HEAD;p=ftraceroute.git Change from pthread to select loop --- diff --git a/README.md b/README.md index 5c12cf7..66e1c95 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ftraceroute is a small traceroute program that sends ICMP echo probes to network hosts. We try to use as little memory as possible. -Current maintainer: German-Service-Network +Current maintainer: German-Service-Network Website: https://www.gsnw.de/ ## Installation diff --git a/src/ftraceroute.c b/src/ftraceroute.c index 19cea7b..bd7255e 100644 --- a/src/ftraceroute.c +++ b/src/ftraceroute.c @@ -24,10 +24,11 @@ #include #include #include +#include #include #include #include -#include +#include #ifdef HAVE_CONFIG_H #include "config.h" @@ -36,19 +37,7 @@ #include "ftraceroute.h" #include "options.h" -double version = 0.1; - -/* Mutex for synchronized output to stdout */ -pthread_mutex_t log_mutex = PTHREAD_MUTEX_INITIALIZER; - -/* Struct to pass arguments to the thread */ -typedef struct { - char *host; - int max_hops; - int probes; - int timeout_ms; - uint16_t thread_id; /* Unique ID for ICMP correlation */ -} trace_args_t; +double version = 0.2; int main(int argc, char **argv) { @@ -106,268 +95,292 @@ int main(int argc, char **argv) { if (probes <= 0) probes = DEFAULT_PROBES; int num_hosts = argc - optind; - pthread_t *threads = malloc(sizeof(pthread_t) * num_hosts); - trace_args_t *t_args = malloc(sizeof(trace_args_t) * num_hosts); + trace_session_t *sessions = calloc(num_hosts, sizeof(trace_session_t)); + // Initialization of all sessions for (int i = 0; i < num_hosts; i++) { - t_args[i].host = argv[optind + i]; - t_args[i].max_hops = max_hops; - t_args[i].probes = probes; - t_args[i].timeout_ms = timeout_ms; - // Generate a unique ID for this thread (mix PID and index) to separate ICMP replies - t_args[i].thread_id = (getpid() & 0xFF00) | ((i + 1) & 0x00FF); - - if (pthread_create(&threads[i], NULL, run_trace, &t_args[i]) != 0) { - perror("pthread_create"); - } + session_init(&sessions[i], argv[optind + i], max_hops, probes, timeout_ms, i); } - // Wait for all threads to finish - for (int i = 0; i < num_hosts; i++) { - pthread_join(threads[i], NULL); + // Main loop (event loop) + while (1) { + int active_count = 0; + int max_fd = -1; + fd_set readfds; + FD_ZERO(&readfds); + + // 1. Collect FDs for select + for (int i = 0; i < num_hosts; i++) { + trace_session_t *s = &sessions[i]; + if (s->state == STATE_FINISHED || s->state == STATE_ERROR) continue; + active_count++; + + // While waiting for a response, we add the socket to the read set. + if (s->state == STATE_AWAIT_REPLY && s->sock >= 0) { + FD_SET(s->sock, &readfds); + if (s->sock > max_fd) max_fd = s->sock; + } + } + if (active_count == 0) break; + + // 2. Select with a short timeout (10 ms) so that we can check send/timeouts. + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 10000; + select(max_fd + 1, &readfds, NULL, NULL, &tv); + + // 3. Processing for all sessions + for (int i = 0; i < num_hosts; i++) { + trace_session_t *s = &sessions[i]; + if (s->state == STATE_FINISHED || s->state == STATE_ERROR) continue; + + // A) Read (if data is available) + if (s->state == STATE_AWAIT_REPLY && s->sock >= 0 && FD_ISSET(s->sock, &readfds)) { + process_read(s); + } + + // B) Timeout check (if still waiting) + if (s->state == STATE_AWAIT_REPLY) { + process_timeout_check(s); + } + + // C) Preparing a new hop + if (s->state == STATE_PREPARE_HOP) { + s->current_ttl++; + if (s->current_ttl > s->max_hops) { + fprintf(stderr, "[%s] Target not reached (max_hops=%d).\n", s->host_arg, s->max_hops); + s->state = STATE_FINISHED; + session_close(s); + } else { + // Initialize output buffer + s->line_off = 0; + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "[%s] %2d ", s->host_arg, s->current_ttl); + + // Set socket TTL + if (s->dst.ss_family == AF_INET) { + setsockopt(s->sock, IPPROTO_IP, IP_TTL, &s->current_ttl, sizeof(s->current_ttl)); + } else { + setsockopt(s->sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->current_ttl, sizeof(s->current_ttl)); + } + + s->current_probe = 0; + s->printed_addr = false; + s->state = STATE_SEND_PROBE; + } + } + + // D) Send (when ready) + if (s->state == STATE_SEND_PROBE) { + process_send(s); + } + } } - free(threads); - free(t_args); + free(sessions); return 0; } -void *run_trace(void *arg) { - trace_args_t *args = (trace_args_t *)arg; - // Resolve Host (IPv4 or IPv6) - struct sockaddr_storage dst_store; - if (resolve_host(args->host, &dst_store) < 0) return NULL; +/* + * Functions + */ + +void session_init(trace_session_t *s, char *host, int mh, int pp, int tm, int idx) { + s->host_arg = host; + s->max_hops = mh; + s->probes_per_hop = pp; + s->timeout_ms = tm; + s->current_ttl = 0; + s->reached_dest = false; + s->ident = (getpid() & 0xFFFF) + idx; // Unique ID per session + s->seq_base = 0; + + if (resolve_host(host, &s->dst) < 0) { + fprintf(stderr, "[%s] Could not resolve host.\n", host); + s->state = STATE_ERROR; + s->sock = -1; + return; + } + + s->dst_len = (s->dst.ss_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + s->proto = (s->dst.ss_family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6; - struct sockaddr *dst = (struct sockaddr *)&dst_store; - socklen_t dst_len = (dst->sa_family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); char dst_ip[INET6_ADDRSTRLEN]; - getnameinfo(dst, dst_len, NULL, 0, dst_ip, sizeof(dst_ip), NI_NUMERICHOST); - - pthread_mutex_lock(&log_mutex); - printf("[%s] Start traceroute to %s (%s)\n", args->host, args->host, dst_ip); - pthread_mutex_unlock(&log_mutex); - - // Choose Protocol based on family - int proto = (dst->sa_family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6; - - int sock = socket(dst->sa_family, SOCK_RAW, proto); - if (sock < 0) { - // Output error protected by mutex - pthread_mutex_lock(&log_mutex); - fprintf(stderr, "[%s] ", args->host); - perror("socket creation failed"); - pthread_mutex_unlock(&log_mutex); - return NULL; + getnameinfo((struct sockaddr *)&s->dst, s->dst_len, NULL, 0, dst_ip, sizeof(dst_ip), NI_NUMERICHOST); + printf("[%s] Start traceroute to %s (%s)\n", host, host, dst_ip); + + + + s->sock = socket(s->dst.ss_family, SOCK_RAW, s->proto); + if (s->sock < 0) { + perror("socket"); + s->state = STATE_ERROR; + return; } - // Set recv timeout - struct timeval tv; - tv.tv_sec = args->timeout_ms / 1000; - tv.tv_usec = (args->timeout_ms % 1000) * 1000; - if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { - perror("setsockopt(SO_RCVTIMEO)"); + // IMPORTANT: Non-blocking for single-threaded event loop + int flags = fcntl(s->sock, F_GETFL, 0); + fcntl(s->sock, F_SETFL, flags | O_NONBLOCK); + + s->state = STATE_PREPARE_HOP; +} + +void session_close(trace_session_t *s) { + if (s->sock >= 0) { + close(s->sock); + s->sock = -1; } +} - // Use the unique thread_id assigned in main instead of just getpid() - unsigned short ident = args->thread_id; - unsigned short seq_base = 0; - bool reached = false; - - for (int ttl = 1; ttl <= args->max_hops && !reached; ++ttl) { - if (dst->sa_family == AF_INET) { - if (setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) { - perror("setsockopt(IP_TTL)"); - close(sock); - return NULL; - } +void process_send(trace_session_t *s) { + if (s->current_probe >= s->probes_per_hop) { + flush_line(s); + if (s->reached_dest) { + s->state = STATE_FINISHED; + session_close(s); } else { - if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)) < 0) { - perror("setsockopt(IPV6_UNICAST_HOPS)"); - close(sock); - return NULL; - } + s->state = STATE_PREPARE_HOP; } + return; + } - // Create buffer for entire line to prevent “mixed output” - char line_buf[2048]; - size_t off = 0; - off += snprintf(line_buf + off, sizeof(line_buf) - off, "[%s] %2d ", args->host, ttl); - - bool printed_addr = false; - - for (int p = 0; p < args->probes; ++p) { - unsigned char packet[PACKET_SIZE]; - memset(packet, 0, sizeof(packet)); - size_t pkt_len = 0; + unsigned char packet[PACKET_SIZE]; + memset(packet, 0, sizeof(packet)); + size_t pkt_len = 0; + struct timeval *stamp_ptr = NULL; + + if (s->dst.ss_family == AF_INET) { + struct icmphdr *icmp = (struct icmphdr *)packet; + icmp->type = ICMP_ECHO; + icmp->code = 0; + icmp->un.echo.id = htons(s->ident); + icmp->un.echo.sequence = htons(s->seq_base++); + + stamp_ptr = (struct timeval *)(packet + sizeof(struct icmphdr)); + pkt_len = sizeof(struct icmphdr) + sizeof(struct timeval); + gettimeofday(stamp_ptr, NULL); + icmp->checksum = 0; + icmp->checksum = checksum(packet, (int)pkt_len); + } else { + struct icmp6_hdr *icmp6 = (struct icmp6_hdr *)packet; + icmp6->icmp6_type = ICMP6_ECHO_REQUEST; + icmp6->icmp6_code = 0; + icmp6->icmp6_id = htons(s->ident); + icmp6->icmp6_seq = htons(s->seq_base++); + + stamp_ptr = (struct timeval *)(packet + sizeof(struct icmp6_hdr)); + pkt_len = sizeof(struct icmp6_hdr) + sizeof(struct timeval); + gettimeofday(stamp_ptr, NULL); + } - struct timeval *stamp_ptr = NULL; + gettimeofday(&s->t_send, NULL); + if (sendto(s->sock, packet, pkt_len, 0, (struct sockaddr *)&s->dst, s->dst_len) < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "* "); + s->current_probe++; + return; + } + } + s->state = STATE_AWAIT_REPLY; +} - // Build Packet - if (dst->sa_family == AF_INET) { - struct icmphdr *icmp = (struct icmphdr *)packet; - icmp->type = ICMP_ECHO; - icmp->code = 0; - icmp->un.echo.id = htons(ident); - icmp->un.echo.sequence = htons(seq_base++); - - stamp_ptr = (struct timeval *)(packet + sizeof(struct icmphdr)); - pkt_len = sizeof(struct icmphdr) + sizeof(struct timeval); - - gettimeofday(stamp_ptr, NULL); - icmp->checksum = 0; - icmp->checksum = checksum(packet, (int)pkt_len); - } else { - // IPv6 ICMP Header construction - struct icmp6_hdr *icmp6 = (struct icmp6_hdr *)packet; - icmp6->icmp6_type = ICMP6_ECHO_REQUEST; - icmp6->icmp6_code = 0; - icmp6->icmp6_id = htons(ident); - icmp6->icmp6_seq = htons(seq_base++); - - stamp_ptr = (struct timeval *)(packet + sizeof(struct icmp6_hdr)); - pkt_len = sizeof(struct icmp6_hdr) + sizeof(struct timeval); - - gettimeofday(stamp_ptr, NULL); - // NOTE: Kernel calculates Checksum for ICMPv6 RAW sockets! - icmp6->icmp6_cksum = 0; - } +void process_timeout_check(trace_session_t *s) { + struct timeval now; + gettimeofday(&now, NULL); + double elapsed = ms_between(s->t_send, now); + if (elapsed > s->timeout_ms) { + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "* "); + s->current_probe++; + s->state = STATE_SEND_PROBE; + } +} - struct timeval t_send; - gettimeofday(&t_send, NULL); - if (sendto(sock, packet, pkt_len, 0, dst, dst_len) < 0) { - perror("sendto"); - if (off < sizeof(line_buf)) { - off += snprintf(line_buf + off, sizeof(line_buf) - off, "* "); +void process_read(trace_session_t *s) { + unsigned char recvbuf[PACKET_SIZE]; + struct sockaddr_storage reply_addr; + socklen_t rlen = sizeof(reply_addr); + struct timeval t_rcv; + + ssize_t n = recvfrom(s->sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&reply_addr, &rlen); + if (n < 0) return; + + gettimeofday(&t_rcv, NULL); + bool match = false; + bool is_final = false; + + if (s->dst.ss_family == AF_INET) { + struct iphdr *ip = (struct iphdr *)recvbuf; + int iphdrlen = ip->ihl * 4; + if (n >= iphdrlen + 8) { + struct icmphdr *icmph = (struct icmphdr *)(recvbuf + iphdrlen); + if (icmph->type == ICMP_ECHOREPLY) { + if (ntohs(icmph->un.echo.id) == s->ident && ntohs(icmph->un.echo.sequence) == (unsigned short)(s->seq_base - 1)) { + match = true; is_final = true; } - continue; - } - // Receive Loop - unsigned char recvbuf[PACKET_SIZE]; - for (;;) { - struct sockaddr_storage reply_addr; - socklen_t rlen = sizeof(reply_addr); - struct timeval t_rcv; - - ssize_t n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&reply_addr, &rlen); - - if (n < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) { - if (off < sizeof(line_buf)) { - off += snprintf(line_buf + off, sizeof(line_buf) - off, "* "); - } - break; - } else { - perror("recvfrom"); - break; + } else if (icmph->type == ICMP_TIME_EXCEEDED) { + unsigned char *inner = (unsigned char *)icmph + 8; + struct iphdr *ip2 = (struct iphdr *)inner; + int ip2len = ip2->ihl * 4; + if (n >= iphdrlen + 8 + ip2len + 8) { + struct icmphdr *icmp2 = (struct icmphdr *)(inner + ip2len); + if (ntohs(icmp2->un.echo.id) == s->ident && ntohs(icmp2->un.echo.sequence) == (unsigned short)(s->seq_base - 1)) { + match = true; } } - gettimeofday(&t_rcv, NULL); - - bool match = false; - bool final_reply = false; - - // --- Parsing Response --- - if (dst->sa_family == AF_INET) { - // IPv4: RAW socket delivers IP header + payload - struct iphdr *ip = (struct iphdr *)recvbuf; - int iphdrlen = ip->ihl * 4; - if (n < iphdrlen + 8) continue; - - struct icmphdr *icmph = (struct icmphdr *)(recvbuf + iphdrlen); - - if (icmph->type == ICMP_ECHOREPLY) { - if (ntohs(icmph->un.echo.id) == ident && ntohs(icmph->un.echo.sequence) == (unsigned short)(seq_base - 1)) { - match = true; - final_reply = true; - } - } else if (icmph->type == ICMP_TIME_EXCEEDED) { - // Inner IP packet - unsigned char *inner = (unsigned char *)icmph + 8; - struct iphdr *ip2 = (struct iphdr *)inner; - int ip2len = ip2->ihl * 4; - if (n >= iphdrlen + 8 + ip2len + 8) { - struct icmphdr *icmp2 = (struct icmphdr *)(inner + ip2len); - if (ntohs(icmp2->un.echo.id) == ident && ntohs(icmp2->un.echo.sequence) == (unsigned short)(seq_base - 1)) { - match = true; - } - } - } - } else { - // IPv6: RAW socket normally delivers ONLY the ICMP header (without IPv6 header) - // Check whether there is enough data for ICMPv6 headers - if (n < (ssize_t)sizeof(struct icmp6_hdr)) continue; - - struct icmp6_hdr *icmp6h = (struct icmp6_hdr *)recvbuf; - - if (icmp6h->icmp6_type == ICMP6_ECHO_REPLY) { - if (ntohs(icmp6h->icmp6_id) == ident && ntohs(icmp6h->icmp6_seq) == (unsigned short)(seq_base - 1)) { - match = true; - final_reply = true; - } - } else if (icmp6h->icmp6_type == ICMP6_TIME_EXCEEDED) { - // Payload structure: [ICMP6 Header (8 bytes)] + [Original IPv6 Header (40 bytes)] + [Original ICMP6 Header Start] - // Skip 8 (ICMP header) + 40 (inner IPv6 header) to get to the inner ICMP header. - size_t offset_to_inner_icmp = sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr); - - if (n >= (ssize_t)(offset_to_inner_icmp + sizeof(struct icmp6_hdr))) { - struct icmp6_hdr *inner_icmp = (struct icmp6_hdr *)(recvbuf + offset_to_inner_icmp); - if (ntohs(inner_icmp->icmp6_id) == ident && ntohs(inner_icmp->icmp6_seq) == (unsigned short)(seq_base - 1)) { - match = true; - } - } - } + } + } + } else { + // IPv6 + if (n >= (ssize_t)sizeof(struct icmp6_hdr)) { + struct icmp6_hdr *icmp6h = (struct icmp6_hdr *)recvbuf; + if (icmp6h->icmp6_type == ICMP6_ECHO_REPLY) { + if (ntohs(icmp6h->icmp6_id) == s->ident && ntohs(icmp6h->icmp6_seq) == (unsigned short)(s->seq_base - 1)) { + match = true; + is_final = true; } - - if (!match) continue; - - if (!printed_addr) { - char host_name[NI_MAXHOST] = {0}; - char ip_txt[INET6_ADDRSTRLEN] = {0}; - - addr_to_host((struct sockaddr *)&reply_addr, rlen, host_name, sizeof(host_name), ip_txt, sizeof(ip_txt)); - - // Write address to buffer (only for the first hit per hop) - int written = 0; - if (host_name[0] == '\0') { - written = snprintf(line_buf + off, sizeof(line_buf) - off, "%s ", ip_txt); - } else { - written = snprintf(line_buf + off, sizeof(line_buf) - off, "%s (%s) ", host_name, ip_txt); + } else if (icmp6h->icmp6_type == ICMP6_TIME_EXCEEDED) { + size_t offset = sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr); + if (n >= (ssize_t)(offset + sizeof(struct icmp6_hdr))) { + struct icmp6_hdr *inner = (struct icmp6_hdr *)(recvbuf + offset); + if (ntohs(inner->icmp6_id) == s->ident && ntohs(inner->icmp6_seq) == (unsigned short)(s->seq_base - 1)) { + match = true; } - if (written > 0) off += written; - printed_addr = true; } - - double rtt_ms = ms_between(t_send, t_rcv); - if (off < sizeof(line_buf)) { - off += snprintf(line_buf + off, sizeof(line_buf) - off, "%.3f ms ", rtt_ms); - } - - if (final_reply) reached = true; - break; // Next probe - } - } - - // Now output the entire line atomically - pthread_mutex_lock(&log_mutex); - printf("%s\n", line_buf); - pthread_mutex_unlock(&log_mutex); - if (reached) break; + } + } } - if (!reached) { - fprintf(stderr, "[%s] Ziel nicht erreicht (max_hops=%d).\n", args->host, args->max_hops); - } + if (match) { + // Address buffering (once per hop) + if (!s->printed_addr) { + char host_name[NI_MAXHOST] = {0}; + char ip_txt[INET6_ADDRSTRLEN] = {0}; + addr_to_host((struct sockaddr *)&reply_addr, rlen, host_name, sizeof(host_name), ip_txt, sizeof(ip_txt)); - close(sock); - return NULL; + if (host_name[0] == '\0') { + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "%s ", ip_txt); + } else { + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "%s (%s) ", host_name, ip_txt); + } + s->printed_addr = true; + } + + double rtt = ms_between(s->t_send, t_rcv); + s->line_off += snprintf(s->line_buf + s->line_off, sizeof(s->line_buf) - s->line_off, "%.3f ms ", rtt); + + if (is_final) s->reached_dest = true; + + // Test successful -> Next test + s->current_probe++; + s->state = STATE_SEND_PROBE; + } } -/* - * Functions - */ +void flush_line(trace_session_t *s) { + printf("%s\n", s->line_buf); +} unsigned short checksum(void *b, int len) { unsigned short *buf = b; diff --git a/src/ftraceroute.h b/src/ftraceroute.h index bf6f6dd..b8b7dc5 100644 --- a/src/ftraceroute.h +++ b/src/ftraceroute.h @@ -12,9 +12,52 @@ #include #include +#include #include -void *run_trace(void *arg); +/* State definition for a session */ +typedef enum { + STATE_PREPARE_HOP, // Ready for next hop (increase TTL, initialize buffer) + STATE_SEND_PROBE, // Ready to send a sample + STATE_AWAIT_REPLY, // Waiting for response (select) + STATE_FINISHED, // Goal achieved or Max Hops through + STATE_ERROR // Error case (socket defective, etc.) +} session_state_t; + +/* Structure that holds the state of a single trace operation */ +typedef struct { + char *host_arg; // Hostname from arguments + struct sockaddr_storage dst; // destination address + socklen_t dst_len; + int sock; // Socket File Descriptor + int proto; // IPPROTO_ICMP or IPPROTO_ICMPV6 + uint16_t ident; // Unique ID for ICMP + uint16_t seq_base; // sequence counter + int max_hops; + int probes_per_hop; + int timeout_ms; + + // Current progress + int current_ttl; + int current_probe; + bool reached_dest; + session_state_t state; + + // Timing for current rehearsal + struct timeval t_send; + + // Output buffering (for atomic line output) + char line_buf[2048]; + int line_off; + bool printed_addr; // Has the address for this hop already been buffered? +} trace_session_t; + +void session_init(trace_session_t *s, char *host, int mh, int pp, int tm, int idx); +void session_close(trace_session_t *s); +void process_send(trace_session_t *s); +void process_timeout_check(trace_session_t *s); +void process_read(trace_session_t *s); +void flush_line(trace_session_t *s); unsigned short checksum(void *b, int len); double ms_between(struct timeval a, struct timeval b); int resolve_host(const char *host, struct sockaddr_storage *out);