利用原始套接字实现tracert路由追踪

来源:互联网 发布:ncre vb 编辑:程序博客网 时间:2024/06/06 02:28

在windows的命令行下,使用tracert 域名/IP地址 可以记录本机到目的主机所经过的路由器的IP地址。这个功能使用原始套接字也可以实现。

我们通过不断地向目的主机发送ICMP-ECHORequest包,并且将包的TTL一开始设为1,这样一到达网关路由器后,路由器就检测到这个包超时了(TTL=0了),于是就会丢弃次包,并返回一个ICMP超时报文,在ICMP超时报文中,包含了路由器的IP地址信息,于是解析这个信息并打印就可以了。

接着再发送一个ICMP-ECHORequest报文,这次将TTL设为2,这样的话将会抵达第二个路由器,第二个路由器发现TTL=0,丢弃后返回ICMP超时报文,解析即可。

同理,循环不断地将TTL值加1,发送ICMP-ECHO报文Request直到收到目的主机的ICMP-ECHOREPLY报文,说明已经到达目的主机,退出循环。

#include "stdafx.h"#pragma pack(4)#define WIN32_LEAN_AND_MEAN#include <winsock2.h>#include <ws2tcpip.h>#include <stdio.h>#include <stdlib.h>#pragma comment(lib,"ws2_32.lib")#define ICMP_ECHOREPLY      0#define ICMP_DESTUNREACH    3#define ICMP_SRCQUENCH      4#define ICMP_REDIRECT       5#define ICMP_ECHO           8#define ICMP_TIMEOUT       11#define ICMP_PARMERR       12#define MAX_HOPS           30#define ICMP_MIN 8    // Minimum 8 byte icmp packet (just header)typedef struct iphdr{unsigned int   h_len : 4;        // Length of the headerunsigned int   version : 4;      // Version of IPunsigned char  tos;            // Type of serviceunsigned short total_len;      // Total length of the packetunsigned short ident;          // Unique identifierunsigned short frag_and_flags; // Flagsunsigned char  ttl;            // Time to liveunsigned char  proto;          // Protocol (TCP, UDP etc)unsigned short checksum;       // IP checksumunsigned int   sourceIP;       // Source IPunsigned int   destIP;         // Destination IP} IpHeader;typedef struct _ihdr{BYTE   i_type;               // ICMP message typeBYTE   i_code;               // Sub codeUSHORT i_cksum;USHORT i_id;                 // Unique idUSHORT i_seq;                // Sequence number// This is not the std header, but we reserve space for time//ULONG timestamp;} IcmpHeader;#define DEF_PACKET_SIZE         32#define MAX_PACKET            1024void usage(char *progname){printf("usage: %s host-name [max-hops]\n", progname);ExitProcess(-1);}int set_ttl(SOCKET s, int nTimeToLive){int     nRet;nRet = setsockopt(s, IPPROTO_IP, IP_TTL, (LPSTR)&nTimeToLive, sizeof(int));if (nRet == SOCKET_ERROR){printf("setsockopt(IP_TTL) failed: %d\n",WSAGetLastError());return 0;}return 1;}int decode_resp(char *buf, int bytes, SOCKADDR_IN *from, int ttl){IpHeader *iphdr = NULL;IcmpHeader *icmphdr = NULL;unsigned short  iphdrlen;struct hostent *lpHostent = NULL;struct in_addr inaddr = from->sin_addr;//from是从recv函数里返回过来的iphdr = (IpHeader *)buf;// Number of 32-bit words * 4 = bytesiphdrlen = iphdr->h_len * 4;//首部长度的单位是32位字if (bytes < iphdrlen + ICMP_MIN)//8printf("Too few bytes from %s\n", inet_ntoa(from->sin_addr));icmphdr = (IcmpHeader*)(buf + iphdrlen);//指向icmp头部分switch (icmphdr->i_type)//检测ICMP报文类型{case ICMP_ECHOREPLY:     // Response from destination//(如果是ICMP_ECHOREPLY报文,说明不是因为TTL=0被丢弃的,说明到达了目的主机)lpHostent = gethostbyaddr((const char *)&from->sin_addr, AF_INET, sizeof(struct in_addr));//获取主机名if (lpHostent != NULL)printf("%2d  %s (%s)\n", ttl, lpHostent->h_name, inet_ntoa(inaddr));//打印主机地址return 1;break;case ICMP_TIMEOUT:      // Response from router along the way//(如果是ICMP_TIMEOUT报文的话,说明被路由器超时丢弃了,所以返回值为0,告诉主循环还没有完成)printf("%2d  %s\n", ttl, inet_ntoa(inaddr));return 0;break;case ICMP_DESTUNREACH:  // Can't reach the destination at allprintf("%2d  %s  reports: Host is unreachable\n", ttl,inet_ntoa(inaddr));return 1;break;default:printf("non-echo type %d recvd\n", icmphdr->i_type);return 1;break;}return 0;}USHORT checksum(USHORT *buffer, int size){unsigned long cksum = 0;while (size > 1){cksum += *buffer++;size -= sizeof(USHORT);}if (size)cksum += *(UCHAR*)buffer;cksum = (cksum >> 16) + (cksum & 0xffff);cksum += (cksum >> 16);return (USHORT)(~cksum);}void fill_icmp_data(char * icmp_data, int datasize){IcmpHeader *icmp_hdr;char       *datapart;icmp_hdr = (IcmpHeader*)icmp_data;icmp_hdr->i_type = ICMP_ECHO;//icmp_echo_requesticmp_hdr->i_code = 0;//icmp_hdr->i_id = (USHORT)GetCurrentProcessId();icmp_hdr->i_cksum = 0;icmp_hdr->i_seq = 0;datapart = icmp_data + sizeof(IcmpHeader);//将指针指向数据部分以便能填充数据部分// Place some junk in the buffer. Don't care about the data...memset(datapart, 'E', datasize - sizeof(IcmpHeader));}int main(int argc, char **argv){WSADATA      wsd;SOCKET       sockRaw;HOSTENT     *hp = NULL;SOCKADDR_IN  dest,from;int  ret, datasize,fromlen = sizeof(from),timeout,done = 0,maxhops,ttl = 1;char  *icmp_data, *recvbuf;BOOL bOpt;USHORT seq_no = 0;if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0){printf("WSAStartup() failed: %ld\n", GetLastError());return -1;}if (argc < 2){usage(argv[0]);}maxhops = 30;//When the af parameter is AF_INET or AF_INET6 and the type is SOCK_RAW, //the value specified for the protocol is set in the protocol field of the IPv6 or IPv4 packet header.sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0, WSA_FLAG_OVERLAPPED);if (sockRaw == INVALID_SOCKET){printf("WSASocket() failed: %d\n", WSAGetLastError());ExitProcess(-1);}timeout = 1000;ret = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));if (ret == SOCKET_ERROR){printf("setsockopt(SO_RCVTIMEO) failed: %d\n", WSAGetLastError());return -1;}timeout = 1000;ret = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout));if (ret == SOCKET_ERROR){printf("setsockopt(SO_SNDTIMEO) failed: %d\n", WSAGetLastError());return -1;}ZeroMemory(&dest, sizeof(dest));dest.sin_family = AF_INET;if ((dest.sin_addr.s_addr = inet_addr(argv[1])) == INADDR_NONE)//如果inet_addr()转出来的是一个无效的网络地址,说明输入的是域名//需要gethostbyname才能获得目的IP{hp = gethostbyname(argv[1]);//那么就用gethostbyname()取得网络地址if (hp)memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length);else{printf("Unable to resolve %s\n", argv[1]);ExitProcess(-1);}}datasize = DEF_PACKET_SIZE;//32datasize += sizeof(IcmpHeader);icmp_data = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);//分配堆内存recvbuf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET);if ((!icmp_data) || (!recvbuf)){printf("HeapAlloc() failed %ld\n", GetLastError());return -1;}memset(icmp_data, 0, MAX_PACKET);fill_icmp_data(icmp_data, datasize);printf("\nTracing route to %s over a maximum of %d hops:\n\n", argv[1], maxhops);for (ttl = 1; ((ttl < maxhops) && (!done)); ttl++){int bwrote;set_ttl(sockRaw, ttl);((IcmpHeader*)icmp_data)->i_cksum = 0;//((IcmpHeader*)icmp_data)->timestamp = GetTickCount();((IcmpHeader*)icmp_data)->i_seq = seq_no++;((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);bwrote = sendto(sockRaw, icmp_data, datasize, 0, (SOCKADDR *)&dest, sizeof(dest));if (bwrote == SOCKET_ERROR){if (WSAGetLastError() == WSAETIMEDOUT){printf("%2d  Send request timed out.\n", ttl);continue;}printf("sendto() failed: %d\n", WSAGetLastError());return -1;}ret = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen);if (ret == SOCKET_ERROR){if (WSAGetLastError() == WSAETIMEDOUT){printf("%2d  Receive Request timed out.\n", ttl);continue;}printf("recvfrom() failed: %d\n", WSAGetLastError());return -1;}done = decode_resp(recvbuf, ret, &from, ttl);Sleep(1000);}HeapFree(GetProcessHeap(), 0, recvbuf);HeapFree(GetProcessHeap(), 0, icmp_data);system("tracert www.nwpu.edu.cn");//与系统自带的tracert命令进行比较system("pause");return 0;}

一开始的时候直接运行程序得到了如下结果(上面的信息是我的程序的信息,下面的是windows自带的tracert打印出来的信息):


我的程序除了目的主机的ICMP-ECHOREPLY报文收到了以外,其它的ICMP-ECHO请求全部超时了(是socket超时,不是返回超时报文),感觉就是被路由器丢弃了,并且没有返回ICMP-TIMEOUT报文。用了很多办法都没有解决,后来死马当作活马医的心态在控制面板中关闭了windows放火墙,居然就对了,运行结果如下:


与tracert命令的结果一样,说明追踪的结果是对的。

可能是windows的防火墙会自动检测和过滤一些无意义的报文,增加自身操作系统的稳定性。以后网络编程的东西要是结果不对,都可以试一试关闭防火墙。

至于头两个路由器为什么一直都没反应,我猜测是学校的路由器的设置和其它因特网中路由器的设置不一样,会自动丢弃超时报文而不返回ICMP-TIMEOUT报文。



0 0