执行:
./traceroute 183.220.98.129
编译:
复制代码到新建 traceroute.c 文件之中
gcc -D_DEFAULT_SOURCE -o traceroute traceroute.c
效果:
./traceroute 183.220.98.129
traceroute to 183.220.98.129 (183.220.98.129), 30 hops max
1 192.168.1.1 192.168.1.1 192.168.1.1
2 100.72.0.1 100.72.0.1 100.72.0.1
3 183.222.60.97 183.222.60.97 183.222.60.97
4 223.86.15.65 223.86.15.65 223.86.15.65
5 223.87.26.174 223.87.26.174 223.87.26.174
6 * * *
7 183.220.98.129 183.220.98.129 183.220.98.129
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include <sys/time.h>
// 必须在包含系统头文件前定义特性宏
#define _DEFAULT_SOURCE // 替代废弃的 _BSD_SOURCE
#include <netinet/ip_icmp.h>
#define MAX_HOPS 30
#define PROBES_PER_HOP 3
#define TIMEOUT_SEC 3
// ICMP 校验和计算
unsigned short checksum(void *b, int len) {
unsigned short *buf = (unsigned short *)b;
unsigned int sum = 0;
for (sum = 0; len > 1; len -= 2) {
sum += *buf++;
}
if (len == 1) {
sum += *(unsigned char *)buf;
}
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return (unsigned short)~sum;
}
// 解析 ICMP 响应
void parse_reply(char *buf, int len, struct sockaddr_in *from) {
struct iphdr *ip = (struct iphdr *)buf;
int iphdr_len = ip->ihl * 4;
struct icmphdr *icmp = (struct icmphdr *)(buf + iphdr_len);
if (icmp->type == ICMP_TIME_EXCEEDED || icmp->type == ICMP_ECHOREPLY) {
char *orig_pkt = buf + iphdr_len + sizeof(struct icmphdr);
struct iphdr *orig_ip = (struct iphdr *)orig_pkt;
int orig_iphdr_len = orig_ip->ihl * 4;
struct icmphdr *orig_icmp = (struct icmphdr *)(orig_pkt + orig_iphdr_len);
// 检查是否为我们的探测包
if (orig_icmp->un.echo.id == htons(getpid())) {
printf("%-15s ", inet_ntoa(from->sin_addr));
fflush(stdout);
}
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <hostname>\n", argv[0]);
return 1;
}
// 创建原始套接字
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
perror("socket");
return 1;
}
// 设置接收超时
struct timeval tv = {TIMEOUT_SEC, 0};
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// 解析目标地址
struct sockaddr_in dest;
memset(&dest, 0, sizeof(dest));
dest.sin_family = AF_INET;
struct hostent *host = gethostbyname(argv[1]);
if (!host) {
herror("gethostbyname");
close(sock);
return 1;
}
memcpy(&dest.sin_addr, host->h_addr, host->h_length);
printf("traceroute to %s (%s), %d hops max\n", argv[1], inet_ntoa(dest.sin_addr), MAX_HOPS);
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
memset(&from, 0, sizeof(from));
// 主循环:递增 TTL
for (int ttl = 1; ttl <= MAX_HOPS; ttl++) {
printf("%2d ", ttl);
// 设置 TTL
setsockopt(sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl));
// 发送多个探测包
for (int probe = 0; probe < PROBES_PER_HOP; probe++) {
// 构造 ICMP Echo 请求
struct icmphdr icmp;
memset(&icmp, 0, sizeof(icmp));
icmp.type = ICMP_ECHO;
icmp.code = 0;
icmp.un.echo.id = htons(getpid());
icmp.un.echo.sequence = htons(ttl * PROBES_PER_HOP + probe);
icmp.checksum = checksum(&icmp, sizeof(icmp));
// 发送包
if (sendto(sock, &icmp, sizeof(icmp), 0, (struct sockaddr *)&dest, sizeof(dest)) <= 0) {
perror("sendto");
continue;
}
// 接收响应
char buf[1024];
int ret = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&from, &fromlen);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf(" * ");
} else {
perror("recvfrom");
}
} else {
parse_reply(buf, ret, &from);
}
usleep(100000); // 避免 flood
}
printf("\n");
// 到达目标则退出
if (from.sin_addr.s_addr == dest.sin_addr.s_addr) {
break;
}
}
close(sock);
return 0;
}