利用原始套接字实现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报文。
- 利用原始套接字实现tracert路由追踪
- TraceRoute(tracert)源码(基于原始套接字实现)
- 路由追踪tracert命令
- 路由追踪tracert命令
- ip路由追踪命令(Tracert)
- 原始套接字实现
- 原始套接字实现
- 利用原始套接字抓取数据
- Windows下利用原始套接字实现的一个抓包程序Demo
- Windows下利用原始套接字实现的一个抓包程序Demo
- 利用原始套接字实现对流经本机IP包的捕获
- 用原始套接字实现网络监听
- 用原始套接字实现网络监听
- 原始套接字实现网络监听
- 原始套接字透析之实现Ping
- ICMP,原始套接字,ping实现
- Linux原始套接字实现分析---转
- Linux原始套接字实现分析
- httpClient接口调用
- html巩固练习之常用标签的使用
- Python nonlocal 与 global 关键字解析
- 动态规划(篇1)重叠子问题
- recyclerview layoutManager Tangram 的基础 —— vlayout(Android)
- 利用原始套接字实现tracert路由追踪
- imagesc demo
- java 基本数据类型 操作及相互转换
- OK335XD uboot移植(二)logo显示
- SAP在大中华区推出SAP数字化转型教育网络平台
- SQL中的case when then else end用法
- org.apache.log4j.Logger详解
- ubantu安装搜狗输入法
- 递推:Number Sequence(mod找规律)