Created
April 24, 2026 08:29
-
-
Save douxxtech/3a1ffdf1acf6b3c1dcf81aad55a4bfe6 to your computer and use it in GitHub Desktop.
A simple traceroute implementation in C using raw ICMP sockets.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| A simple traceroute implementation in C using raw ICMP sockets. | |
| Resolves hostnames, detects loops, and prints RTT for each hop. | |
| Linux only | requires cap_net_raw or root | gcc -o traceroute traceroute.c | |
| https://gist.github.com/douxxtech/3a1ffdf1acf6b3c1dcf81aad55a4bfe6 | |
| */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <netdb.h> | |
| #include <arpa/inet.h> | |
| #include <netinet/ip.h> | |
| #include <netinet/ip_icmp.h> | |
| #include <unistd.h> | |
| #include <time.h> | |
| #include <string.h> | |
| #define MAX_HOPS 100 | |
| #define TIMEOUT_SEC 1 | |
| #define PROBE_COUNT 3 | |
| #define BUF_SIZE 512 | |
| char *getIp(char *host) | |
| { | |
| // get the host entry for the hostname/IP (fetches the IP if not alr one) | |
| struct hostent *he = gethostbyname(host); | |
| if (he == NULL) | |
| { | |
| printf("Could not resolve host: %s\n", host); | |
| exit(1); | |
| } | |
| struct in_addr *addr = (struct in_addr *)he->h_addr; // he points to raw IP bytes | |
| char *ip = inet_ntoa(*addr); // convert the bytes to a string | |
| return ip; | |
| } | |
| char *getHostname(char *ip) | |
| { | |
| struct sockaddr_in sa; | |
| sa.sin_family = AF_INET; | |
| inet_aton(ip, &sa.sin_addr); | |
| static char hostname[256]; // static so it survives the return | |
| int resolved = getnameinfo((struct sockaddr *)&sa, sizeof(sa), hostname, sizeof(hostname), NULL, 0, 0); | |
| if (resolved == 0) | |
| return hostname; | |
| else | |
| return NULL; | |
| } | |
| void build_echo(char *buf, int seq) | |
| { | |
| struct icmphdr *icmp = (struct icmphdr *)buf; | |
| icmp->type = ICMP_ECHO; | |
| icmp->code = 0; | |
| icmp->un.echo.id = htons(getpid() & 0xFFFF); | |
| icmp->un.echo.sequence = htons(seq); | |
| icmp->checksum = 0; | |
| // simple checksum | |
| unsigned short *ptr = (unsigned short *)buf; | |
| unsigned int sum = 0; | |
| for (int i = 0; i < (int)(sizeof(struct icmphdr) / 2); i++) | |
| sum += ptr[i]; | |
| while (sum >> 16) | |
| sum = (sum & 0xFFFF) + (sum >> 16); | |
| icmp->checksum = ~sum; | |
| } | |
| double ping(int send_sock, int recv_sock, char *recv_buf, struct sockaddr_in *dest, struct sockaddr_in *from, socklen_t *from_len) | |
| { | |
| struct timespec start, end; | |
| char send_buf[sizeof(struct icmphdr)]; | |
| build_echo(send_buf, 0); | |
| clock_gettime(CLOCK_MONOTONIC, &start); | |
| sendto(send_sock, send_buf, sizeof(send_buf), 0, (struct sockaddr *)dest, sizeof(*dest)); | |
| int received = recvfrom(recv_sock, recv_buf, BUF_SIZE, 0, (struct sockaddr *)from, from_len); | |
| clock_gettime(CLOCK_MONOTONIC, &end); | |
| if (received < 0) | |
| return -1; | |
| return (end.tv_sec - start.tv_sec) * 1000.0 + (end.tv_nsec - start.tv_nsec) / 1e6; | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| if (argc < 2) | |
| { | |
| printf("An hostname/IP address is required.\n"); | |
| exit(1); | |
| } | |
| char *host = argv[1]; | |
| char *ip = getIp(host); | |
| if (strcmp(host, ip)) | |
| printf("\nTracing route to %s [%s]\nover a maximum of %d hops:\n\n", host, ip, MAX_HOPS); | |
| else | |
| { | |
| char *hostname = getHostname(ip); | |
| if (hostname != NULL && strcmp(ip, hostname)) | |
| printf("\nTracing route to %s [%s]\nover a maximum of %d hops:\n\n", hostname, ip, MAX_HOPS); | |
| else | |
| printf("\nTracing route to %s over a maximum of %d hops\n\n", ip, MAX_HOPS); | |
| } | |
| // Create sockets | |
| int send_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); | |
| int recv_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); | |
| if (send_sock < 0 || recv_sock < 0) | |
| { | |
| printf("Failed to create sockets. Try running as root or set the capability:\n"); | |
| printf("sudo setcap cap_net_raw+ep %s\n", argv[0]); | |
| exit(1); | |
| } | |
| struct timeval tv = {.tv_sec = TIMEOUT_SEC, .tv_usec = 0}; | |
| setsockopt(recv_sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); // Set timeout to not get stuck | |
| // setup the dest | |
| struct sockaddr_in dest; | |
| dest.sin_family = AF_INET; // IPv4 | |
| inet_aton(ip, &dest.sin_addr); // build the IP bytes | |
| double ms[PROBE_COUNT]; | |
| uint32_t seen_ips[MAX_HOPS] = {0}; | |
| int consecutive = 0; | |
| for (int ttl = 1; ttl <= MAX_HOPS; ttl++) | |
| { | |
| // Set the TTL to the current iter ttl | |
| setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); | |
| char buf[BUF_SIZE]; | |
| struct sockaddr_in from; | |
| socklen_t from_len = sizeof(from); | |
| for (int i = 0; i < PROBE_COUNT; i++) | |
| ms[i] = ping(send_sock, recv_sock, buf, &dest, &from, &from_len); | |
| // print stats | |
| printf("%3d ", ttl); | |
| int erm = 0; | |
| for (int i = 0; i < PROBE_COUNT; i++) | |
| { | |
| int final_ms = (int)ms[i]; | |
| if (final_ms < 0) | |
| { | |
| printf(" * "); | |
| erm++; | |
| } | |
| else | |
| { | |
| if (final_ms == 0) | |
| printf("%5s ms ", "<1"); | |
| else | |
| printf("%5d ms ", final_ms); | |
| } | |
| } | |
| if (erm == PROBE_COUNT) | |
| { | |
| printf("Request timed out.\n"); | |
| continue; | |
| } | |
| char *hop_ip = inet_ntoa(from.sin_addr); | |
| char hostname[256]; | |
| // loop detection, see notes.txt | |
| if (from.sin_addr.s_addr == seen_ips[ttl > 1 ? ttl - 2 : 0]) | |
| consecutive++; | |
| else | |
| consecutive = 0; | |
| seen_ips[ttl - 1] = from.sin_addr.s_addr; | |
| if (consecutive >= 2) | |
| { | |
| printf("%s\n", hop_ip); | |
| printf("\nLoop detected: %s repeating. Stopping.\n", hop_ip); | |
| break; | |
| } | |
| int resolved = getnameinfo((struct sockaddr *)&from, sizeof(from), hostname, sizeof(hostname), NULL, 0, 0); | |
| if (resolved == 0 && strcmp(hostname, hop_ip)) | |
| printf("%s [%s]\n", hostname, hop_ip); | |
| else | |
| printf("%s\n", hop_ip); | |
| // parse the buffer into an actual ICMP response | |
| struct iphdr *ip_hdr = (struct iphdr *)buf; | |
| struct icmphdr *icmp_hdr = (struct icmphdr *)(buf + ip_hdr->ihl * 4); | |
| if (icmp_hdr->type == ICMP_ECHOREPLY && from.sin_addr.s_addr == dest.sin_addr.s_addr) | |
| break; | |
| } | |
| // cleanup properly | |
| close(send_sock); | |
| close(recv_sock); | |
| printf("\nTraceroute completed.\n"); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment