Tracert是windows系统提供的一个工具,使用该程序可以让我们看到IP数据报从一台主机到另一台主机所经过的路由器。Linux系统也提供了类似的工具,叫traceroute,功能和Tracert一样。
在介绍Tracert的原理之前,需要先弄清楚IP首部TTL字段的含义,IP报文每经过一个路由器,路由器都会将该IP报文首部的TTL字段减1,
当路由器收到一份IP数据报的TTL是0或1时,路由器此时不会转发该数据报,而会丢弃该数据报,并且给IP数据报首部中的源地址发送一份ICMP超时报文。
IP首部的定义见:http://blog.csdn.net/china_jeffery/article/details/78984477
Tracert就是利用了路由器会丢弃TTL为1或0的数据报且返回ICMP超时报文的特性,来实现侦测路由的功能。Tracert程序先发送TTL值为1的IP数据报,处理这份数据报的第一个路由器将TTL减1,丢弃该数据报并返回ICMP超时报文,这样程序就得到了第一个路由器的地址,以此方式,递增IP数据报TTL的值,直到数据报最终到达目标主机。
那么怎么判断数据报到达了最终的目标主机呢?
我们不能单纯的通过未收到路由器返回的ICMP差错报文的方式来判断数据报到达目的地了,因为有可能我们由于接收ICMP差错报文超时等原因导致我们收不到ICMP差错报文(这也是为什么我们后面会介绍每一个TTL跃点会发送3次或多次请求的原因)。windows平台的tracert与linux平台的traceroute的实现原理稍有不同,判断数据报到达目标主机的方式也有不同。
tracert是通过发送ping包,因为windows系统内核都实现了ping功能,所以如果目的主机收到了ping请求就会回复相应的ping包,tracert就是通过这种方式来判断数据报是否到达了目标主机。而traceroute是通过发送UDP包(UDP端口选择一个不可能使用的UDP端口,比如大于30000的端口),因为目的主机没有监听该端口,所以不会响应接收到的该UDP请求,因此当UDP包到达时,目标主机会返回“端口不可达”的错误,traceroute就是通过该错误来判断UDP包到达了目的主机。
从实现方式来看,traceroute通过UDP的方式来实现更加稳定可靠,因为大多数主机的防火墙会组织ICMP报文,而不会阻止UDP报文。
下图使用wireshark抓取的tracert 192.168.3.76
命令的数据包,从图中可以看到tracert是通过发送ping包来实现的,以及每个ping包的TTL递增过程:
路由器在丢弃TTL为0或1的数据报时,会发送一个一份ICMP差错报文,该ICMP的差错报文的type为11, code为0.
ICMP报文的类型定义见:http://blog.csdn.net/china_jeffery/article/details/79045630
type为11的报文格式如下(code有0和1两种,格式一样):
该示例和之前的Ping程序的示例有所不同,该示例设置了IP_HDRINCL
选项来自己构造IP头部。
程序的启动参数使用webrtc的"rtc_base/flags.h"
实现,具体使用方法见:WebRTC-命令行参数解析。
代码中的其他某些功能,如断言、时间戳等基于webrtc的rtc_base
实现,这些功能也可以很方便的自己实现。 rtc_base
的使用参考:http://blog.csdn.net/china_jeffery/article/details/78887619
#include
#include
#include
#include
#include
#include "rtc_base/networkprotocolheader.h"
#include "rtc_base/checks.h"
#include "rtc_base/flags.h"
#include "rtc_base/timeutils.h"
DECLARE_bool(h); // 帮助
DECLARE_int(m); // 最大跃点数
DECLARE_int(w); // 等待每次回复的超时时间(毫秒)
DECLARE_int(s); // 发送ICMP包超时时间(毫秒)
DECLARE_int(n); // 每个跃点发送的请求数
DEFINE_bool(h, false, "帮助");
DEFINE_int(m, 30, "最大跃点数");
DEFINE_int(w, 3000, "等待每次回复的超时时间(毫秒)");
DEFINE_int(s, 3000, "发送ICMP包超时时间(毫秒)");
DEFINE_int(n, 3, "每个跃点发送的请求数");
const int kPingDataSize = 36;
__u32 kLocalIP = 0;
__u32 GetLocalIPv4Address() {
return inet_addr("192.168.42.26");
char hostname[MAX_PATH] = { 0 };
gethostname(hostname, MAX_PATH);
struct hostent FAR* lpHostEnt = gethostbyname(hostname);
if (lpHostEnt == NULL) {
return htonl(0x7f000001); //127.0.0.1
}
LPSTR lpAddr = lpHostEnt->h_addr_list[0];
struct in_addr addr;
memcpy(&addr, lpAddr, 4);
return addr.s_addr;
}
std::string IPv4ToString(__u32 ip) {
in_addr addr;
addr.s_addr = ip;
char *p= inet_ntoa(addr);
if (p)
return p;
return "";
}
std::string GetPrintString(const char* fmt, ...) {
char buf[100];
va_list arglist;
va_start(arglist, fmt);
StringCchVPrintfA(buf, 100, fmt, arglist);
va_end(arglist);
return buf;
}
bool print_ip(__u32* ips, int count, __u32 dest_ip) {
bool has_ip = false;
bool trace_end = false;
for (int i = 0; i < count; i++) {
if (ips[i] != 0) {
printf(" %s", IPv4ToString(ips[i]).c_str());
has_ip = true;
trace_end = (ips[i] == dest_ip);
}
}
if (!has_ip)
printf(" timeout");
printf("\n");
if (trace_end)
printf("Trace Complete\n");
return trace_end;
}
void FillRequestIPPacket(__u8* ip_packet, __u16 ip_packet_size, __u16 seq, __u8 ttl, __u32 dest_addr) {
RTC_DCHECK(ip_packet);
iphdr* p_iphdr = reinterpret_cast(ip_packet);
memset(p_iphdr, 0, sizeof(iphdr));
p_iphdr->version = 4;
p_iphdr->ihl = sizeof(iphdr)/4; // no option
p_iphdr->tos = 0;
p_iphdr->frag_off = 0;
p_iphdr->id = (__u16)rtc::Time32();
p_iphdr->ttl = ttl;
p_iphdr->protocol = IPPROTO_ICMP;
p_iphdr->tot_len = ip_packet_size;
p_iphdr->daddr = dest_addr;
p_iphdr->saddr = kLocalIP;
p_iphdr->check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_iphdr), p_iphdr->ihl*4);
ping_hdr* p_ping_hdr = reinterpret_cast(ip_packet + p_iphdr->ihl*4);
p_ping_hdr->common_hdr.type = 8;
p_ping_hdr->common_hdr.code = 0;
p_ping_hdr->id = (__u16)GetCurrentProcessId();
p_ping_hdr->seq = seq;
// fill some junk in the buffer.
if (kPingDataSize > 0)
memset((void*)((__u8*)p_ping_hdr+sizeof(ping_hdr)), 'E', kPingDataSize);
p_ping_hdr->common_hdr.check = 0;
p_ping_hdr->common_hdr.check = rtc::GetCheckSum(reinterpret_cast<__u16*>(p_ping_hdr), ip_packet_size - p_iphdr->ihl*4);
}
// return source ip address
bool DecodeIPPacket(const __u8* ip_packet, __u16 ip_packet_size, __u32 send_time, __u32* src_addr) {
const iphdr* ip_hdr = reinterpret_cast<const iphdr*>(ip_packet);
__u32 use_time = rtc::Time32() - send_time;
__u16 ip_hdr_len = ip_hdr->ihl * 4; // bytes
const icmp_common_hdr *icmp_hdr = reinterpret_cast<const icmp_common_hdr*>(ip_packet + ip_hdr_len);
if (icmp_hdr->type == 0 && icmp_hdr->code == 0) { // 回显应答
const ping_hdr *p_ping_hdr = reinterpret_cast<const ping_hdr*>(icmp_hdr);
if (p_ping_hdr->id != (__u16)GetCurrentProcessId()) {
printf("other process ping response packet, pid=%d\n", GetCurrentProcessId());
return false;
}
printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());
*src_addr = ip_hdr->saddr;
return true;
}
else if (icmp_hdr->type == 11 && icmp_hdr->code == 0) { // cause by ttl == 0
printf("%-10s", GetPrintString("<%d ms", use_time == 0 ? 1 : use_time).c_str());
*src_addr = ip_hdr->saddr;
return true;
}
else {
printf("unexpected response, type=%d, code=%d\n", icmp_hdr->type, icmp_hdr->code);
return false;
}
}
#define SAFE_RELEASE \
if (req_ip_packet) { \
delete[] req_ip_packet; \
req_ip_packet = NULL;\
}\
if (rsp_ip_packet) { \
delete[] rsp_ip_packet; \
rsp_ip_packet = NULL;\
}\
if (s != INVALID_SOCKET) {\
closesocket(s);\
s = INVALID_SOCKET;\
}\
WSACleanup();
int main(int argc, char**argv) {
rtc::FlagList::SetFlagsFromCommandLine(&argc, argv, true);
if (FLAG_h) {
rtc::FlagList::Print(NULL, false);
return 1;
}
char *hostname = argv[argc - 1];
if (!hostname || strlen(hostname) == 0) {
printf("Invalid host name\n");
return 1;
}
printf("Trace %s\n", hostname);
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
sockaddr_in from;
int from_len = sizeof(sockaddr_in);
kLocalIP = GetLocalIPv4Address();
sockaddr_in dest;
memset(&dest, 0, sizeof(sockaddr_in));
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr(hostname);
// resolve host name
if (dest.sin_addr.s_addr == INADDR_NONE) {
unsigned long begin_time = rtc::Time32();
struct addrinfo* result = nullptr;
struct addrinfo hints = { 0 };
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_ADDRCONFIG;
int ret = getaddrinfo(hostname, nullptr, &hints, &result);
if (ret != 0) {
printf("Resolve host name failed, error code = %d\n", ret);
return 1;
}
unsigned long end_time = rtc::Time32();
struct addrinfo* cursor = result;
printf("------------------------------\n");
printf("Resolve [time < %d ms]: \n", end_time - begin_time);
bool flag = false;
for (; cursor; cursor = cursor->ai_next) {
sockaddr_in *paddr_in = reinterpret_cast(cursor->ai_addr);
printf("%s\n", inet_ntoa(paddr_in->sin_addr));
if (!flag) {
dest.sin_addr = paddr_in->sin_addr;
flag = true;
}
}
freeaddrinfo(result);
printf("-------------------------------\n");
}
printf("Tracing %s [%d max hops]: \n", inet_ntoa(dest.sin_addr), FLAG_m);
SOCKET s = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (s == INVALID_SOCKET) {
printf("create socket failed, error code = %d\n", WSAGetLastError());
WSACleanup();
return 1;
}
int err = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, reinterpret_cast<const char*>(&FLAG_s), sizeof(FLAG_s));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_SNDTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
err = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&FLAG_w), sizeof(FLAG_w));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for SO_RCVTIMEO failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
int opt = 1;
err = setsockopt(s, IPPROTO_IP, IP_HDRINCL, reinterpret_cast<const char*>(&opt), sizeof(opt));
RTC_DCHECK(err != SOCKET_ERROR);
if (err == SOCKET_ERROR) {
printf("setsockopt for IP_HDRINCL failed, error code = %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
// ip packet for ping request.
__u16 req_ip_packet_size = sizeof(iphdr) + sizeof(ping_hdr) + kPingDataSize;
__u8 *req_ip_packet = new __u8[req_ip_packet_size];
memset(req_ip_packet, 0, req_ip_packet_size);
// ip packet for icmp response or ping echo.
__u16 rsp_ip_packet_size = // ICMP差错报文的大小
sizeof(iphdr)
+ sizeof(icmp_common_hdr) // ICMP(type=11,code=0或1)差错报文
+ 4 // unused
+ sizeof(iphdr) + 8;
//取ping包大小和ICMP差错报文大小的最大值,保证无论返回哪种报文缓冲区都够用,
//也可以直接分配一个足够大的缓冲区,如1024
//
rsp_ip_packet_size = std::max(rsp_ip_packet_size, req_ip_packet_size);
__u8 *rsp_ip_packet = new __u8[rsp_ip_packet_size];
memset(rsp_ip_packet, 0, rsp_ip_packet_size);
RTC_DCHECK(rsp_ip_packet);
int ttl = 1;
int seq = 0;
__u32 *ips = new __u32[FLAG_n];
for(int hop = 1; hop <= FLAG_m; hop++) {
printf(" %-4d", hop);
for (int i = 0; i < FLAG_n; i++) {
ips[i] = 0;
seq++;
FillRequestIPPacket(req_ip_packet, req_ip_packet_size, seq, ttl, dest.sin_addr.s_addr);
__u32 send_time = rtc::Time32();
int sent = sendto(s,
reinterpret_cast<const char*>(req_ip_packet),
req_ip_packet_size,
0,
reinterpret_cast<const sockaddr*>(&dest),
sizeof(sockaddr));
if (sent == SOCKET_ERROR) {
printf("%-10s", "*");
if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
continue;
}
int bread = recvfrom(s,
reinterpret_cast<char*>(rsp_ip_packet),
rsp_ip_packet_size,
0,
reinterpret_cast(&from),
&from_len);
if (bread == SOCKET_ERROR) {
int gle = WSAGetLastError();
printf("%-10s", "*");
if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
continue;
}
__u32 dest_ip = 0;
DecodeIPPacket(reinterpret_cast<const __u8*>(rsp_ip_packet), rsp_ip_packet_size, send_time, &dest_ip);
ips[i] = dest_ip;
if (i == FLAG_n - 1)
if (print_ip(ips, FLAG_n, dest.sin_addr.s_addr)) {
SAFE_RELEASE;
return 0;
}
}
ttl++;
}
SAFE_RELEASE;
return 0;
}
完整的示例代码见:https://gitee.com/china_jeffery/webrtc项目中的webrtc/src/msvc/test/tracert工程.