实验 3 基于原始套接字的网络程序设计
姓名:
学号:
班级:
一、 实验环境:
Windows 系统,VS2013 编译器
二、实验内容:
1.设计思路
(1)编写 traceroute 程序。要求实现基于 ICMP 的 traceroute 探测程序。
traceroute 程序可以使用多种协议实现,其原理是借助 ICMP 的超时差错报
文来反馈路径信息。具体而言,以 ICMP 实现为例,在 IP 首部有一个 TTL 字段,
记录该数据报在网络上的存活时间(经过的路由器条数)。每当分组经过一个路
由器,其存活时间 TTL 会减 1,当其 TTL 减为 1 时,路由器会取消分组,并传送
一个 ICMP 超时差错报文给发送端,而当请求包到达目的主机时,目的方会返回
一个正常的 ICMP 响应。这样,通过有策略地构造 TTL 值递增的探测报文,就能
借助路由器反馈的 ICMP 超时差错和 ICMP ECHO 响应来收集从探测源到探测目标
路径上的路由器信息。
为了满足 traceroute 的基本功能,需要构建一个 traceroute 的探测框架,
具有 ICMP ECHO 请求的构造和发送功能,能够接收 ICMP 协议承载的差错报文和
ECHO 响应,并能够对不同类型的反馈做出正确的解析。基于此,该框架设置最
大的 TTL 探测值,对用户输入的目标域名进行地址转换,将 TTL 值从 1 开始逐渐
递增,针对每个 TTL 值进行三次探测,接收路由器返回的 ICMP 超时差错应答,
获得探测包往返的时间延迟,之道获得目的地址的 ICMP ECHO 响应或达到最大跳
数为止。
(2)对 FTP 协议进行解析,分析出用户名和密码
需要通过 ioctlsocket()来进行设置接收经过网卡的所有的数据包。
2.程序清单(要求有详细的注释)
(1)traceroute 程序
#include
#include
#include
using namespace std;
#pragma comment(lib, "Ws2_32.lib")
//IP报头
typedef struct
{
unsigned char hdr_len : 4;
//4位头部长度
unsigned char version : 4;
//4位版本号
unsigned char tos;
//8位服务类型
unsigned short total_len;
//16位总长度
unsigned short identifier;
//16位标识符
unsigned short frag_and_flags;
//3位标志加13位片偏移
unsigned char ttl;
//8位生存时间
unsigned char protocol;
//8位上层协议号
unsigned short checksum;
//16位校验和
unsigned long sourceIP;
//32位源IP地址
unsigned long destIP;
//32位目的IP地址
} IP_HEADER;
//ICMP报头
typedef struct
{
BYTE type;
//8位类型字段
BYTE code;
//8位代码字段
USHORT cksum;
//16位校验和
USHORT id;
//16位标识符
USHORT seq;
//16位序列号
} ICMP_HEADER;
//报文解码结构
typedef struct
{
USHORT usSeqNo;
//序列号
DWORD dwRoundTripTime;
//往返时间
in_addr dwIPaddr;
//返回报文的IP地址
}DECODE_RESULT;
//计算网际校验和函数
USHORT checksum(USHORT *pBuf, int iSize)
{
unsigned long cksum = 0;
while (iSize>1)
{
}
cksum += *pBuf++;
iSize -= sizeof(USHORT);
if (iSize)
{
}
cksum += *(UCHAR *)pBuf;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (USHORT)(~cksum);
}
//对数据包进行解码
BOOL DecodeIcmpResponse(char * pBuf, int iPacketSize, DECODE_RESULT &DecodeResult, BYTE
ICMP_ECHO_REPLY, BYTE ICMP_TIMEOUT)
{
//检查数据报大小的合法性
IP_HEADER* pIpHdr = (IP_HEADER*)pBuf;
int iIpHdrLen = pIpHdr->hdr_len * 4;
if (iPacketSize < (int)(iIpHdrLen + sizeof(ICMP_HEADER)))
return FALSE;
//根据ICMP报文类型提取ID字段和序列号字段
ICMP_HEADER *pIcmpHdr = (ICMP_HEADER *)(pBuf + iIpHdrLen);
USHORT usID, usSquNo;
if (pIcmpHdr->type == ICMP_ECHO_REPLY)
//ICMP回显应答报文
{
}
usID = pIcmpHdr->id;
//报文ID
usSquNo = pIcmpHdr->seq;
//报文序列号
else if (pIcmpHdr->type == ICMP_TIMEOUT)//ICMP超时差错报文
{
char * pInnerIpHdr = pBuf + iIpHdrLen + sizeof(ICMP_HEADER);
载荷中的IP头
int iInnerIPHdrLen = ((IP_HEADER *)pInnerIpHdr)->hdr_len * 4;
载荷中的IP头长
//
//
ICMP_HEADER * pInnerIcmpHdr = (ICMP_HEADER *)(pInnerIpHdr + iInnerIPHdrLen);//
载荷中的ICMP头
usID = pInnerIcmpHdr->id;
//报文ID
usSquNo = pInnerIcmpHdr->seq;
//序列号
}
else
{
}
return false;
//检查ID和序列号以确定收到期待数据报
if (usID != (USHORT)GetCurrentProcessId() || usSquNo != DecodeResult.usSeqNo)
{
}
return false;
//记录IP地址并计算往返时间
DecodeResult.dwIPaddr.s_addr = pIpHdr->sourceIP;
DecodeResult.dwRoundTripTime = GetTickCount() - DecodeResult.dwRoundTripTime;
//处理正确收到的ICMP数据报
if (pIcmpHdr->type == ICMP_ECHO_REPLY || pIcmpHdr->type == ICMP_TIMEOUT)
{
}
//输出往返时间信息
if (DecodeResult.dwRoundTripTime)
cout << "
" << DecodeResult.dwRoundTripTime << "ms" ;
else
cout << "
" << "<1ms" ;
return true;
}
int main()
{
//初始化Windows sockets网络环境
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
char IpAddress[255];
cout << "请输入一个IP地址或域名:";
cin >> IpAddress;
//得到IP地址
u_long ulDestIP = inet_addr(IpAddress);
//转换不成功时按域名解析
if (ulDestIP == INADDR_NONE)
{
hostent * pHostent = gethostbyname(IpAddress);
if (pHostent)
ulDestIP = (*(in_addr*)pHostent->h_addr).s_addr;
cout << "输入的IP地址或域名无效!" << endl;
WSACleanup();
system("pause");
return 0;
{
}
else
{
}
}
cout << "到达IP:" << IpAddress << " 最多通过30个沃点\n" << endl;
//填充目地端socket地址
sockaddr_in destSockAddr;
ZeroMemory(&destSockAddr, sizeof(sockaddr_in));
destSockAddr.sin_family = AF_INET;
destSockAddr.sin_addr.s_addr = ulDestIP;
//创建原始套接字
SOCKET sockRaw = WSASocket(AF_INET, SOCK_RAW, IPPROTO_ICMP, NULL, 0,
WSA_FLAG_OVERLAPPED);
//超时时间
int iTimeout = 3000;
//接收超时
setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIMEO, (char *)&iTimeout, sizeof(iTimeout));
//发送超时
setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char *)&iTimeout, sizeof(iTimeout));
//构造ICMP回显请求消息,并以TTL递增的顺序发送报文
//ICMP类型字段
const BYTE ICMP_ECHO_REQUEST = 8;
//请求回显
const BYTE ICMP_ECHO_REPLY = 0;
//回显应答
const BYTE ICMP_TIMEOUT = 11;
//传输超时
//其他常量定义
const int DEF_ICMP_DATA_SIZE = 32;
//ICMP报文默认数据字段长度
const int MAX_ICMP_PACKET_SIZE = 1024;//ICMP报文最大长度(包括报头)
const DWORD DEF_ICMP_TIMEOUT = 6000;
//回显应答超时时间
const int DEF_MAX_HOP = 30;
//最大跳站数
//填充ICMP报文中每次发送时不变的字段
char IcmpSendBuf[sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE];//发送缓冲区
memset(IcmpSendBuf, 0, sizeof(IcmpSendBuf));
//初始化发送缓冲区
char IcmpRecvBuf[MAX_ICMP_PACKET_SIZE];
//接收缓冲区
memset(IcmpRecvBuf, 0, sizeof(IcmpRecvBuf));
//初始化接收缓冲区
ICMP_HEADER * pIcmpHeader = (ICMP_HEADER*)IcmpSendBuf;
pIcmpHeader->type = ICMP_ECHO_REQUEST;
//类型为请求回显
pIcmpHeader->code = 0;
//代码字段为0
pIcmpHeader->id = (USHORT)GetCurrentProcessId();
//ID字段为当前进程号
memset(IcmpSendBuf + sizeof(ICMP_HEADER), 'E', DEF_ICMP_DATA_SIZE);//数据字段
USHORT usSeqNo = 0;
//ICMP报文序列号
int iTTL = 1;
//TTL初始值为1
BOOL bReachDestHost = FALSE;
//循环退出标志
int iMaxHot = DEF_MAX_HOP;
//循环的最大次数
DECODE_RESULT DecodeResult;
//传递给报文解码函数的结构化参数
while (!bReachDestHost&&iMaxHot--)
{
//设置IP报头的TTL字段
setsockopt(sockRaw, IPPROTO_IP, IP_TTL, (char *)&iTTL, sizeof(iTTL));
cout << iTTL << flush;
//输出当前序号
//填充ICMP报文中每次发送变化的字段
((ICMP_HEADER *)IcmpSendBuf)->cksum = 0;
//校验和先置为0
((ICMP_HEADER *)IcmpSendBuf)->seq = htons(usSeqNo++);
//填充序列号
((ICMP_HEADER *)IcmpSendBuf)->cksum = checksum((USHORT *)IcmpSendBuf,
sizeof(ICMP_HEADER)+DEF_ICMP_DATA_SIZE);//计算校验和
//记录序列号和当前时间
DecodeResult.usSeqNo = ((ICMP_HEADER*)IcmpSendBuf)->seq;
//当前序号
DecodeResult.dwRoundTripTime = GetTickCount();
//当前时间
//发送TCP回显请求信息
sendto(sockRaw, IcmpSendBuf, sizeof(IcmpSendBuf), 0, (sockaddr*)&destSockAddr,
sizeof(destSockAddr));
//接收ICMP差错报文并进行解析处理
sockaddr_in from;
//对端socket地址
int iFromLen = sizeof(from);
//地址结构大小
int iReadDataLen;
//接收数据长度
while (1)
{
//接收数据
iReadDataLen = recvfrom(sockRaw, IcmpRecvBuf, MAX_ICMP_PACKET_SIZE, 0,
(sockaddr*)&from, &iFromLen);
if (iReadDataLen != SOCKET_ERROR)//有数据到达
{