网络通信编程原始套接字.ppt
1,第四章,原始套接字,2,内容提要,1.使用原始套接字2.套接字选项3.ICMP编程4.使用IP头包含选项5.网络嗅探器实例,3,1.使用原始套接字,利用原始套接字(Raw Socket),可访问底层传输协议。,原始套接字(Raw Socket)与标准套接字区别,4,使用原始套接字可以做什么?实现一些实用工具(ping,traceroute)。可对IP头,TCP头,UDP头,ICMP头等进行操作。原始套接字使用SOCK_RAW套接字类型来创建的,目前只有Winsock2提供了对它的支持。无论Microsoft Windows CE 还是老版本的Windows 95(无Winsock 2升级)均不能利用原始套接字。,5,创建原始套接字,原始套接字类型在IP头中使用预定义的协议(如ICMP)在IP头中使用自定义的协议(使用IP头包含选项),6,创建原始套接字 使用socket()或WSASocket()创建原始套接字。例:创建原始套接字使用预定义协议:SOCKET s;S=socket(AF_INET,SOCK_RAM,IPPROTO_ICMP);/ORS=WSAsocket(AF_INEF,SOCK_RAM,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED);,7,使用自定义协议使用IGMP、UDP、IP或者原始IP,只需分别设置IPPROTO_IGMP、IPPROTO_UDP、IPPROTO_IP或IPPROTO_RAW即可。注意:在Windows NT 4、Windows 98以及Windows 95(安装Winsock 2)操作系统中,创建原始套接字时,只能使用ICMP。协议标志IPPROTO_UDP、IPPROTO_IP以及IPPROTO_RAW均要求使用套接字选项IP_HDRINCL,而该选项在上述平台下都是不支持的。Windows 2000提供了对IP_HDRINCL选项的支持,所以能够处理IP头(IPPROTO_RAW)、TCP头(IPPROTO_TCP)以及UDP头(IPPROTO_UDP)。无论是否设置IP_HDRINCL选项,原始套接字上接收到的数据都会包含IP头。,8,使用原始套接字可以对底层传输机制加以控制,所以有些人将其用于不法用途,,9,套接字选项操作函数getsocketopt()获取套接字选项信息。int getsocketopt(SOCKET s,/套接字描述符 int level,/选项级别 int optname,/选项名称 char*optval,/选项值 int*optlen/选项长度);setsocketopt()设置套接字选项。int setsocketopt(SOCKET s,int level,int optname,const char*optval,int optlen);,2.套接字选项,10,选项级别:协议的层次对应选项级别应用层:SOL_SOCKET传输层:IPPROTO_TCP、IPPROTO_UDP网络层:IPPROTO_IP不同级别属性不同,同一级别的不同协议的属性不同,因此必须指定level参数例:Int nTime=2*1000setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,(char*)&nTime,sizeof(nTime),11,SOL_SOCKET选项级别SO_ACCEPTCONN,如果已调用Listen函数,将套接字置为监听模式,这个选项就会返回TRUE。SOCK_DGRAM类型的套接字不支持这一选项。,SO_BROADCAST,12,SO_CONNECT_TIME,该选项最常见的用法是和AcceptEx函数一道使用。,SO_DONTROUTE,假定创建了一个UDP套接字,并将其与接口A绑定到一起,然后发送一个数据包,目的地址是网络中使用接口B的另一个网络上的某台机器。此时,若采用默认设置,该数据包会经过一个路由过程,使其能通过接口B传入目标网络。但将这里的布尔值设为TRUE,再来调用setsockopt,便可禁止路由,数据包会从绑定的接口上直接发送出去。以后可调用getsockopt,判断是否允许了路由。注意:在默认情况下,路由是允许的。,13,SO_REUSEADDR,监听套接字例外。两个独立的套接字不可与同一个本地地址绑定到一起,以等待进入的连接通知。,SO_EXCLUSIVEADDRUSE,例如,假定两个单独的进程都与同一个本地地址绑定到了一起(假定已设置了SO_REUSEADDR),那么两个套接字中到底由哪一个负责接收进入连接请求通知呢?这一点并未定义!如果应用程序的要求非常苛刻(用于关键性任务的程序),这一点显然是非常不幸的。SO_EXCLUSIVEADDRUSE选项的作用便是将一个本地地址牢牢锁定在与它绑定的那个套接字上。,14,SO_RCVBUF,改缓冲区的大小,一个可能的原因是需要根据应用程序的行为,对缓冲区大小进行相应地调整。,SO_SNDBUF,15,SO_RCVTIMEO,SO_SNDTIMEO,主要用于设置阻塞套接字的收发超时时间,超时时间实际上指明了阻塞时间的长短。当调用接收(发送)函数时,如果在SO _ RCVTIMEO(SO_ SNDTIMEO)指定的时间内没有数据到来,那么函数调用也会结束并且返回错误10060(WSAETIMEDOUT)。,16,IPPROTO_IP级别IPPROTO_IP这一级的套接字选项与IP协议存在密切联系,比如可用这些选项修改IP头的特定字段以及向IP多播组增添一个套接字等等。,IP_OPTIONS,通过该选项可在I P头内设置各种IP选项,可能出现的选项包括:记录路由:每个路由器都将自己的IP地址添加到IP头内。时间标志(时间戳):每个路由器都增添自己的IP地址及时间。,17,源路由选择:源主机指定一条通过互联网的路径。注意:主机和路由器并不支持所有这些选项。设置一个IP选项时,传至setsockopt调用内部的数据会遵照下图所示的数据结构。IP选项头最长可达40字节。,IP选项头格式,18,IP_HDRINCL,若将IP_HDRINCL选项设为TRUE,发送函数会将IP头包括在发送的数据前面,并会促使接收函数将IP头作为数据的一部分。在调用一个Winsock发送函数的时候,必须在数据前面包括完整的IP头,并对IP头的每个字段进行正确的填写。仅仅用于原始套接字该选项仅适用于Windows 2000操作系统。,19,IP_TTL,TTL字段需要在IP头内进行设置。数据报利用TTL字段对数据报能够通过的最大路由器数量加以限制,避免由于路由循环,造成数据报永无休止地在网络中“游荡”。数据报每经过一个路由器,都会由路由器解析出它的TTL值,并将其减去1。若发现这个值变成了0,数据报便会被“无情”地丢弃。注意,Windows CE不支持这个选项。,20,3.ICMP编程,内容提要ICMP协议PING程序分析TRACEROUTE程序分析,为了提高 IP 数据报交付成功的机会,在网际层使用了因特网控制报文协议 ICMP(Internet Control Message Protocol)。ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告。ICMP 不是高层协议,而是 IP 层的协议。ICMP 报文作为 IP 层数据报的数据,加上数据报的首部,组成 IP 数据报发送出去。,ICMP协议,ICMP的位置及封装,IP,ICMP,IGMP,ARP,RARP,Network layer,ICMP message,ICMP message,ICMP 本身是一个网络层协议ICMP报文首先要封装成IP数据报,然后再传送给下一层,ICMP 报文的格式,首 部,ICMP 报文,0,数 据 部 分,检验和,类型,代码,(这 4 个字节取决于 ICMP 报文的类型),8,16,31,IP 数据报,前 4 个字节都是一样的,ICMP 的数据部分(长度取决于类型),ICMP报文中各字段的作用,类型:8比特长字段,定义了报文的类型。代码:8比特长字段,指明了发送此特定报文类型的原因。检验和:16比特长字段,进行差错检验。首部的其余部分:对每一种报文类型都是特定的。数据部分:在差错报文中:所携带的信息可找出引起差错的原始分组;在查询报文中:携带了基于查询类型的额外信息。,报文类型,ICMP 报文,差错报告,查询,报告路由器或主机(目的站)在处理一个IP数据报时可能遇到的一些问题,帮助主机或网络管理员从一个路由器或另一个主机得到特定的信息它是成对出现的,ICMP 差错报告报文的数据字段的内容,首部,IP 数据报,ICMP 的前 8 字节,装入 ICMP 报文的 IP 数据报,IP 数据报首部,ICMP 差错报告报文,8字节,收到的 IP 数据报,IP 数据报首部,8字节,ICMP 差错报告报文,IP 数据报的数据字段,(1)回送请求和回答:由主机/路由器向一特定主机发出ICMP询问,特定主机收到后必须给出ICMP回答报文。,代码:0,检验和,可选数据(由请求报文发送,由回答报文重复),类型:8 or 0,标识符,序号,8:回送请求0:回送回答,ICMP查询报文,说明:,主机或路由器可以发送回送请求报文,收到回送请求报文的主机或路由器发送出回送回答报文。回送请求和回送回答报文可由网络管理员来使用,用来检查IP协议的工作情况。用回送请求和回送回答报文可测试一个主机的可达性,通常是调用ping命令来这样做的。格式中的标识符和序号字段在协议中没有正式定义,可以由发送站任意使用。可供选择的数据字段包含一个报文,它必须由回答的结点在回送回答报文中完全一样的重复。,(2)时间戳请求和回答,代码:0,检验和,类型:13 or 14,标识符,序号,13:请求 14:回答,发送时间戳,接收时间戳,传送时间戳,说明:,发送时间戳:发送方填写接收时间戳:应答主机收到请求报文时填写传送时间戳:应答主机发送应答报文时填写,(3)地址掩码请求和回答,代码:0,检验和,类型:17 or 18,标识符,序号,17:请求 18:回答,地址掩码,主机可能知道他的完整IP地址,但却不知道地址中的哪一部分定义网络地址和子网地址,哪一部分对应于主机标识符。这样,主机就需要知道掩码。要得到掩码,主机应发送地址掩码请求报文给局域网上的路由器。若主机知道该路由器的地址,它就将这个请求直接发给该路由器;若不知道,则广播此报文。路由器收到地址掩码请求报文就响应地址掩码回答报文,向主机提供所需的掩码。在请求报文中,地址掩码字段填入全0,回答报文中,这个字段就包含真正的掩码。,说明:,检验和,发送端的检验和计算:先将首部的检验和字段置为0。将分组划分为K部分,每部分都是16比特长。用反码算术运算将所有这些部分相加。将最终结果取反码就得出检验和。再将其填入检验和字段。,接收端的检验和计算:将收到的分组划分为K部分,每部分都是16比特长。用反码算术运算将所有这些部分相加。将得到的结果取反码。若结果为0,则接收此分组,否组就拒绝此分组。,在ICMP中,检验和的计算覆盖了整个报文(首部和数据)。,PING程序分析,使用原始套接字实现回送请求和回答(PING程序的设计与实现),PING 用来测试两个主机之间的连通性。PING 使用了 ICMP 回送请求与回送回答报文。PING 是应用层直接使用网络层 ICMP 的例子,它没有通过运输层的 TCP 或UDP。,C:Documents and Settingswy ping,代码:0,检验和,可选数据(由请求报文发送,由回答报文重复),类型:8 or 0,标识符,序号,8:回送请求0:回送回答,PING 使用了 ICMP 回送请求与回送回答报文,原始套接字的创建,函数格式:/创建原始套节字 SOCKET sRaw=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);IPPROTO_ICMP指定使用ICMP,PING 程序执行步骤,1、创建协议类型为IPPROTO_ICMP的原始套接字,设置套接字的属性。2、创建并初始化ICMP封包。3、调用sendto函数向远程主机发送ICMP请求。4、调用recvfrom函数接收ICMP响应。,/Ping.h/声明IP头数据结构typedef struct _IPHeader/20字节的IP头 UCHAR iphVerLen;/版本号和头长度(各占4位)UCHAR ipTOS;/服务类型 USHORT ipLength;/封包总长度,即整个IP报的长度 USHORT ipID;/封包标识,惟一标识发送的每一个数据报 USHORT ipFlags;/标志和片偏移 UCHAR ipTTL;/生存时间,就是TTL UCHAR ipProtocol;/协议,可能是TCP、UDP、ICMP等 USHORT ipChecksum;/校验和 ULONG ipSource;/源IP地址 ULONG ipDestination;/目标IP地址 IPHeader,*PIPHeader;,PING 程序代码,/ICMP头数据结构typedef struct icmp_hdr unsigned char icmp_type;/消息类型 unsigned char icmp_code;/代码 unsigned short icmp_checksum;/校验和 unsigned short icmp_id;/用来惟一标识此请求的ID号,通常设置为进程ID unsigned short icmp_sequence;/序号 unsigned long icmp_timestamp;/数据传输时间 ICMP_HDR,*PICMP_HDR;/ICMP回送请求的数据结构typedef struct _EchoRequestICMP_HDR icmphdr;char cData32;ECHOREQUEST,*PECHOREQUEST;,/ICMP回送应答的数据结构#define REQ_DATASIZE 32typedef struct _EchoReplyIPHeader iphdr;ECHOREQUEST echoRequest;ECHOREPLAY,*PECHOREPLAY;/校验和的计算/以16位的字为单位将缓冲区的内容相加,如果缓冲区长度为奇数,/则再加上一个字节。它们的和存入一个32位的双字中USHORT checksum(USHORT*buff,int size);,/Ping.cpp#include#include#pragma comment(lib,WS2_32)/链接到WS2_32.lib#include#include ping.h,USHORT checksum(USHORT*buff,int size)u_long cksum=0;while(size1)/将数据以字为单位累加到cksum 中 cksum=cksum+*buff;buff=buff+1;size=size-sizeof(USHORT);/等价于size=size-2;if(size=1)/共有奇数个字节将最后一个字节扩展为字,再累加 USHORT u=0;u=(USHORT)(*(UCHAR*)buff);cksum=cksum+u;/校验位计算/高16位和低16位相加cksum=(cksum 16)+(cksum,位右移运算符 右边的值依次被移出了,左边的位置依次放0,校验位计算(1)将32位的chsum高16位和低16位相加(2)自加当前数的高16位(3)取反并转换为16位,int main()WSADATA wsaData;WORD version=MAKEWORD(2,2);int ret=WSAStartup(version,/创建ICMP封包(回送请求)ECHOREQUEST echoReq;/填写ICMP封包数据=8;/请求一个ICMP回显=0;=(USHORT)GetCurrentProcessId();=0;=0;/填充数据部分,可以为任意memset(/Sets buffers to a specified character.,/开始发送和接收ICMP封包USHORT nSeq=0;SOCKADDR_IN from;int nLen=sizeof(from);while(TRUE)static int nCount=0;int nRet;if(nCount+=4)break;=0;/GetTickCount()系统开始后,已经经过的毫秒数=GetTickCount();=nSeq+;=checksum(USHORT*),/接收回送应答包ECHOREPLAY echoReply;nRet=recvfrom(sRaw,(char*),/下面开始解析接收到的封包if(nRet sizeof(ECHOREPLAY)printf(Too few bytes from%s n,inet_ntoa(from.sin_addr);/接收到的数据中包含IP头,IP头大小为20个字节if!=0)/回显printf(nonecho type%d recvd n,);return-1;if!=GetCurrentProcessId()printf(someone elses packet!n);return-1;,printf(%d bytes Reply from%s:,nRet,inet_ntoa(from.sin_addr);printf(icmp_seq=%d.,);int nTick=GetTickCount();printf(time:%d ms,nTick-);printf(TTL=%d,);printf(n);Sleep(1000);WSACleanup();/释放Winsock库return 0;,64 bytes Reply from 192.168.0.2:icmp_seq=0.time:0 ms TTL=6464 bytes Reply from 192.168.0.2:icmp_seq=1.time:0 ms TTL=6464 bytes Reply from 192.168.0.2:icmp_seq=2.time:0 ms TTL=6464 bytes Reply from 192.168.0.2:icmp_seq=3.time:0 ms TTL=64,PING 程序运行结果,51,Traceroute(追踪路由)利用Traceroute可侦测出到达网络内特定主机,中途需经过哪些路由器(IP地址)。利用Ping,使用IP选项头内的记录路由选项,侦测中途经过的路由器IP地址,但Ping最多只支持9跳。,TRACEROUTE程序分析,52,53,若网络较大,穿过的路由器不止9个,应换用Traceroute。实现Traceroute程序的方法 基本思想:多次发送数据包,TTL递增,TTL 为0时返回一条 ICMP报文。例:路由跟踪程序(采用发送UDP数据包)创建两个套接字:sRaw 用于接收ICMP数据包sSend用于发送TTL不断增加的UDP数据报,Traceroute程序主要代码,typedef struct icmp_hdr unsigned char icmp_type;/消息类型 unsigned char icmp_code;/代码 unsigned short icmp_checksum;/校验和/下面是回显头 unsigned short icmp_id;/用来惟一标识此请求的ID号,通常设置为进程ID unsigned short icmp_sequence;/序列号 unsigned long icmp_timestamp;/时间戳 ICMP_HDR,*PICMP_HDR;,BOOL SetTTL(SOCKET s,int nValue)int ret=setsockopt(s,IPPROTO_IP,IP_TTL,(char*),void main()char*szDestIp=“202.199.25.20”;/目的地址char recvBuf1024=0;/创建用于接收ICMP封包的原始套节字,绑定到本地端口SOCKET sRaw=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);sockaddr_in in;in.sin_family=AF_INET;in.sin_port=0;=INADDR_ANY;if(bind(sRaw,(sockaddr*),/创建用于发送UDP封包的套节字SOCKET sSend=socket(AF_INET,SOCK_DGRAM,0);SOCKADDR_IN destAddr;destAddr.sin_family=AF_INET;destAddr.sin_port=htons(22);=inet_addr(szDestIp);int nTTL=1;int nRet;ICMP_HDR*pICMPHdr;int nTick;SOCKADDR_IN recvAddr;do/设置UDP封包的TTL值SetTTL(sSend,nTTL);nTick=GetTickCount();/发送这个UDP封包nRet=sendto(sSend,hello,5,0,(sockaddr*),if(nRet=SOCKET_ERROR)printf(sendto()failed n);break;/等待接收路由器返回的ICMP报文int nLen=sizeof(recvAddr);nRet=recvfrom(sRaw,recvBuf,1024,0,(sockaddr*),/解析接收到的ICMP数据pICMPHdr=(ICMP_HDR*),60,4.使用IP头包含选项,创建原始套接字后使用IP_HDRINCL套接字选项可对IP头进行操作,同时也能操作TCP或UDP头(或封装在IP内的其他任何协议)。使用IP_HDRINCL选项时,必须针对每一次发送调用,向IP头内自行填充内容。同时还需填写封装在其中的其他协议头。相关协议报文格式,固定部分,可变部分,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,数 据 部 分,首 部,传送,IP 数据报,发送在前,IP 数据报格式,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,数 据 部 分,首 部,传送,IP 数据报,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,数 据 部 分,首 部,传送,IP 数据报,固定部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,标志(flag)占 3 位,目前只有前两位有意义。标志字段的最低位是 MF(More Fragment)。MF 1 表示后面“还有分片”。MF 0 表示最后一个分片。标志字段中间的一位是 DF(Dont Fragment)。只有当 DF 0 时才允许分片。,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,生存时间(8 位)记为 TTL(Time To Live),这是为了限制数据报在网络中的生存时间,用“跳数”作为 TTL 的单位。数据报每经过一个路由器,其 TTL 值就减 1。,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,运输层,网络层,首部,TCP,UDP,ICMP,IGMP,OSPF,数 据 部 分,IP 数据报,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,首部,0,4,8,16,19,24,31,版 本,标志,生 存 时 间,协 议,标 识,区 分 服 务,总 长 度,片 偏 移,填 充,首 部 检 验 和,源 地 址,目 的 地 址,可 选 字 段(长 度 可 变),位,首部长度,数 据 部 分,固定部分,可变部分,IP 数据报首部的可变部分,IP 首部的可变部分就是一个选项字段,用来支持排错、测量以及安全等措施,内容很丰富。选项字段的长度可变,从 1 个字节到 40 个字节不等,取决于所选择的项目。增加首部的可变部分是为了增加 IP 数据报的功能,但这同时也使得 IP 数据报的首部长度成为可变的。这就增加了每一个路由器处理数据报的开销。,typedef struct _IPHeader/20字节的IP头 UCHAR iphVerLen;/版本号和头长度(各占4位)UCHAR ipTOS;/服务类型 USHORT ipLength;/整个IP报文长度 USHORT ipID;/封包标识 USHORT ipFlags;/标志、片偏移量 UCHAR ipTTL;/生存时间TTL UCHAR ipProtocol;/协议(TCP、UDP、ICMP等)USHORT ipChecksum;/校验和 ULONG ipSource;/源IP地址 ULONG ipDestination;/目标IP地址 IPHeader,*PIPHeader;,77,网络编程中IP头对应结构体声明,78,UDP头简单。长度仅为8个字节,而且只包含了四个字段,格式如图所示。,由于UDP是一种不能保证数据可靠传输的协议,所以校验和的计算是可选的。UDP校验和除覆盖了UDP头之外,还同时覆盖了实际的数据,此外还包括IP头的一部分。,typedef struct _UDPHeaderUSHORTsourcePort;/源端口号USHORTdestinationPort;/目的端口号USHORTlen;/封包长度USHORTchecksum;/校验和 UDPHeader,*PUDPHeader;,79,80,计算UDP校验和的附加字段叫作“伪首部”。UDP校验和基于如下几个域:32位源IP地址(IP头)32位目标IP地址(IP头)8位字段(全零)8位协议16位UDP长度16位源端口号16位目标端口号16位UDP长度16位UDP校验和UDP数据,81,发送原始UDP封包的步骤首先以IPPOTO_UDP为协议类型创建一个原始套接字,打开原始套接字上的IP_HDRINCL选项;然后构建UDP封包(先设置IP头,再设置UDP头,最后设置数据);初始化完整的UDP封包之后,调用sendto函数即可将它发送。例:发送原始UDP封包,发送原始UDP封包,int main()/输入参数信息char szDestIp=10.16.115.88;/=填写目的IP地址char szSourceIp=127.0.0.1;/=填写您自己的IP地址USHORT nDestPort=4567;USHORT nSourcePort=8888;char szMsg=This is a test rn;int nMsgLen=strlen(szMsg);/创建原始套节字SOCKET sRaw=socket(AF_INET,SOCK_RAW,IPPROTO_UDP);/有效IP头包含选项BOOL bIncl=TRUE;setsockopt(sRaw,IPPROTO_IP,IP_HDRINCL,(char*),/IP头IPHeader*pIphdr=(IPHeader*)buff;pIphdr-iphVerLen=(4ipLength=htons(sizeof(IPHeader)+sizeof(UDPHeader)+nMsgLen);pIphdr-ipTTL=128;pIphdr-ipProtocol=IPPROTO_UDP;pIphdr-ipSource=inet_addr(szSourceIp);pIphdr-ipDestination=inet_addr(szDestIp);pIphdr-ipChecksum=checksum(USHORT*)pIphdr,sizeof(IPHeader);/UDP头UDPHeader*pUdphdr=(UDPHeader*),ComputeUdpPseudoHeaderChecksum(pIphdr,pUdphdr,pData,nMsgLen);/设置目的地址SOCKADDR_IN destAddr=0;destAddr.sin_family=AF_INET;destAddr.sin_port=htons(nDestPort);=inet_addr(szDestIp);/发送原始UDP封包int nRet;for(int i=0;i5;i+)nRet=sendto(sRaw,buff,sizeof(IPHeader)+sizeof(UDPHeader)+nMsgLen,0,(sockaddr*),85,5.网络嗅探器实例,嗅探器设计原理网卡正常工作模式:套接字程序只接收与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧丢弃。网络嗅探器的目的:从网卡接收所有经过它的数据包,这些数据包既可以是发给它的也可以是发往别处的。达到上述目的就不能让网卡按通常的正常模式工作,而必须将其设置为混杂模式。设置混杂模式只能在原始套接字进行,不能在流套接字和数据报套接字进行。,TCP首部,20 字节的固定首部,目 的 端 口,数据偏移,检 验 和,选 项(长 度 可 变),源 端 口,序 号,紧 急 指 针,窗 口,确 认 号,保 留,FIN,32 位,SYN,RST,PSH,ACK,URG,位 0 8 16 24 31,填 充,TCP 数据部分,TCP 首部,TCP 报文段,IP 数据部分,IP 首部,发送在前,typedef struct _TCPHeader/20字节的TCP头USHORTsourcePort;/16位源端口号USHORTdestinationPort;/16位目的端口号ULONGsequenceNumber;/32位序列号ULONGacknowledgeNumber;/32位确认号UCHARdataoffset;/高4位首部长度/6位保留字UCHARflags;/6位标志位 USHORTwindows;/16位窗口大小USHORTchecksum;/16位校验和USHORTurgentPointer;/16位紧急数据偏移量 TCPHeader,*PTCPHeader;,88,嗅探步骤:创建原始套接字并绑定到明确的本地地址,不能使用INADDR_ANY。调用ioctlsocket()设置混杂模式 DWORD dwValue=1;ioctlsocket(sRaw,SIO_RCVALL,&dwValue)调用recv()函数获取数据包。解析收到的数据包。例:网络嗅探器例程,void main()/创建原始套节字SOCKET sRaw=socket(AF_INET,SOCK_RAW,IPPROTO_IP);/获取本地IP地址char szHostName56;SOCKADDR_IN addr_in;struct hostent*pHost;gethostname(szHostName,56);if(pHost=gethostbyname(char*)szHostName)=NULL)return;/在调用ioctl之前,套节字必须绑定addr_in.sin_family=AF_INET;addr_in.sin_port=htons(0);,pHost-h_addr_list0,pHost-h_length);printf(Binding to interface:%s n,inet_ntoa(addr_in.sin_addr);if(bind(sRaw,(PSOCKADDR),/开始接收封包char buff1024;int nRet;while(TRUE)nRet=recv(sRaw,buff,1024,0);if(nRet 0)DecodeIPPacket(buff);closesocket(sRaw);,void DecodeTCPPacket(char*pData)TCPHeader*pTCPHdr=(TCPHeader*)pData;printf(Port:%d-%d n,ntohs(pTCPHdr-sourcePort),ntohs(pTCPHdr-destinationPort);/下面还可以根据目的端口号进一步解析应用层协议switch(ntohs(pTCPHdr-destinationPort)case 21:break;case 80:case 8080:break;,void DecodeIPPacket(char*pData)IPHeader*pIP