网络程序设计教材第九章 原始套接口数据链路层Word格式.docx
- 文档编号:16739922
- 上传时间:2022-11-25
- 格式:DOCX
- 页数:24
- 大小:66.22KB
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx
《网络程序设计教材第九章 原始套接口数据链路层Word格式.docx》由会员分享,可在线阅读,更多相关《网络程序设计教材第九章 原始套接口数据链路层Word格式.docx(24页珍藏版)》请在冰豆网上搜索。
Connect函数仅设置目的地址。
对于输出而言,调用connect函数之后,由于目的地址已经指定,用户可以调用write或read,而不是sendto了。
在创建原始套接字时下列选项将影响原始套接字的行为:
IP_TTL选项:
使用该选项用户可以设置或者获取原始套接字的生存时间的值。
获取生存时间可以使用以下代码实现:
intttl;
getsockopt(ip_fd,IPPROTO_IP,IP_TTL,&
ttl,sizeof(int));
IP_TOS选项:
使用这个选项可以设置或者获取IP数据报头的服务类型字段。
需要注意的是函数getsockopt只能返回发送IP数据报的TOS域,而不是接收到的IP数据报的TOS域。
接收到的IP数据报的TOS域,可以使用上面代码,通过structip类型的指针来获取ip_tos域。
IP_OPTIONS选项:
使用该选项可以设置IP选项。
IP_HDRINCL选项:
如果原始套接字上设置了该选项,则IP协议将只为IP数据报头部作如下工作:
IP协议为IP数据报头计算校验和;
如果标识域被填写为0,则IP协议来设置标识域(标识域用于分段时使用);
如果IP地址被设置为INADDR_ANY,则IP协议将以实际发送的网络接口的IP地址来填写这个域。
设置了IP_HDRINCL选项的原始套接字上发送的每个数据报必须自己建立IP数据报头部。
IP_HDRINCL选项可以使用户程序自己来填充IP数据报的头部,这为用户程序扩大了自由空间,但同时,也为许多恶意的攻击程序创造了有利条件。
例如攻击程序可以故意伪造TCPSYN数据段,并将承载该TCP数据段的IP数据报中源地址域填写为不可到达的IP地址,收到这样IP数据报的TCP服务器将可能试图同那个地址建立连接,而它将得不到对方的确认。
这样,TCP服务器的性能将受到很大影响,当服务器在很短的时间间隔内收到大量这样伪造的TCP数据段,则服务器将失效。
原始套接字可以使应用程序发送和接收IP数据报,用户程序可以使用IP_HDRINCL选项来自己填写IP数据报的头部。
由于原始套接字为用户程序提供了很大的自由空间,系统只允许超级用户来创建和使用原始套接字。
另外,用户可以使用bind函数为原始套接字设置本端的IP地址,可以使用connect函数为原始套接字来设置对方的IP地址。
9.3原始套接口输出
原始套接口上的输入和输出函数是内核中最简单的一部分,原始套接口的输出遵循以下规则:
1.普通输出通过调用sendto或sendmsg并指定目的IP地址来完成。
如果套接口已经连接,也可以调用write、writev或send。
2.如果IP_HDRINCL选项未设置,则内核写的数据起始地址指IP头部之后的第一个字节。
因为这种情况下,内核将构造IP头部,并将它安在来自进程的数据之前。
内核将IPv4头部的协议字段设置成用户在调用socket函数时所给的第三个参数。
3.如果IP_HDRINCL选项已经设置,则内核写的数据起始地址指IP头部的第一个字节。
用户所提供的数据大小值必须包括头部的字节数。
此时进程构造除了以下两项以外的整个IP头部:
IPv4标识字段可以设置为0,要求内核设置该值;
IPv4头部的校验和由内核来计算和存储。
4.对于超出外出接口MTU的分组,内核将其分片。
这里需要说明的是IP_HDRINCL选项从未正式地说明过,特别是IP头部中的字节序的问题。
对于源自Berkeley的内核而言,ip_len和ip_off采用主机字节序,其它字段使用网络字节序。
而对Linux内核而言,所有字段均需使用网络字节序。
在4.3BSDReno引入IP_HDRINCL之前,应用程序在原始套接口上给出自己的IP头部的唯一途径是使用VanJacobson于1988年提供的一个内核补丁(用于支持Traceroute)。
此补丁要求应用程序创建一个协议为IPPROTO_RAW的原始IP套接口。
IPPROTO_RAW实际值为255(作为一个保留值,该值不能在IP头部的协议字段中出现)。
9.3.1原始套接口在IPv4和IPv6上的差异
IPv6的原始套接口与IPv4相比有以下几点不同:
●IPv6原始套接口发送或接收的协议头部内,所有字段均使用网络字节序。
●IPv6不存在类似于IPv4中IP_HDRINCL这样的套接口选项。
使用IPv6原始套接口无法读写整个IPv6分组(包括扩展头部),不过通过套接口选项及辅助数据,用户可以取得IPv6头部所有的字段和所有扩展头部。
对于IPv6头部中的版本字段和下一个头部字段是无法得到的。
有效负载长度字段或者作为某个输出函数的一个参数,或者作为来自某个输入函数的返回值总是可以得到,但是当需要特大有效负载选项时,真正的选项本身应用程序是得不到的。
应用进程也得不到片段的头部。
要读写整个IPv6数据报,仍必须使用数据链路访问。
●IPv6原始套接口的校验和处理有差别。
9.3.2IPV6_CHECKSUM套接口选项
对于ICMPv6原始套接口,总是由内核来计算并在ICMPv6头部内存储其校验和。
这一点与ICMPv4原始套接口有所不同。
ICMPv4原始套接口由应用程序来计算并存储校验和。
此外,虽然ICMPv4和ICMPv6都要求发送方计算校验和,ICMPv6在其校验和中还包括一个伪报头(pseudoheader)。
伪报头所包含的字段中有一个源IPv6地址字段,应用进程一般让内核来设置其值。
为了避免应用进程仅仅为了计算校验和而设置该地址,由内核来计算校验和更为方便。
对于其它IPv6原始套接口(即调用socket时第三个参数不为IPPROTO_ICMPV6的那些原始套接口),可以使用一个套接口选项来通知内核,是否为外出分组计算和存储校验和,以及验证接收分组的校验和。
缺省情况下,此选项是关闭的,如果赋予一个非负的值,则此选项打开。
具体如下:
intoffset=2;
if(setsockopt(sockfd,IPPROTO_IPV6,IPV6_CHECKSUM,&
offset,sizeof(offset))<
0)
perror(“itisanerror”);
上面语句不但设置套接口的校验和选项,而且通知内核该16位校验和的字节偏移量:
本例中为自应用数据起始位置起,偏移2个字节。
为了清除此选项,可以设置偏移量为-1。
当此选项设置时,内核将为输出分组计算并存储校验和,并对输入分组的校验和进行验证。
9.4原始套接口输入
对于原始套接口的输入,主要是选择哪些分组传递给原始套接口,它将遵循如下的规则:
1.接收到的TCP分组和UDP分组决不会传递给任何原始套接口,如果一个进程希望读取包含TCP或UDP分组的IP数据报,那么它们必须在数据链路层读入。
2.当内核处理完ICMP报文之后,绝大多数ICMP分组将传递给原始套接口。
对于源自Berkeley的实现,除了回射请求、时间戳请求和地址掩码请求将完全由内核处理以外,所有收到的ICMP分组都将传递给某个原始套接口。
3.当内核处理完IGMP消息之后,所有IGMP分组都将传递到某个原始套接口。
4.所有带有内核不能识别的协议字段的IP数据报都将传递给某个原始套接口。
内核对于这些分组唯一做的就是检验IP头部中某些字段:
IP版本、IPv4头部校验和、头部长度以及目的IP地址。
5.如果数据报以片段形式到达,则该分组将在所有片段到达并重组后才传给原始套接口。
当内核准备好一个待传递的数据报之后,内核将对所有进程的原始套接口进行检查,以寻找所有匹配的套接口。
每个匹配的套接口都将收到一个该IP数据报的拷贝。
以下是对每个原始套接口所做的三个测试,只有当这三个测试都为真时,数据报才会传递到该套接口。
1.如果在创建原始套接口时,所指定的protocol参数不为0,则接收到的数据报的协议字段应与该值匹配;
否则该数据报将不传递到该套接口。
2.如果此套接口上绑定了一个本地IP地址,那么接收到的数据报的目的IP地址应与该绑定地址相匹配;
3.如果此原始套接口通过调用connect指定了一个对方IP地址,那么接收到的数据报的源IP地址应与该已连接地址相匹配;
如果一个原始套接口以protocol参数为0的方式创建,并且未调用connect和bind函数,那么对于内核传递给原始套接口的每个原始数据报,该套接口都会收到一个拷贝。
此外,当一个接收到的数据报传递给IPv4原始套接口时,整个数据报(包括IP头部)都将传递给进程。
对于原始IPv6套接口,除了扩展头部外的整个数据报内容都传递给套接口。
在传递给应用进程的IPv4头部内,ip_len、ip_off和ip_id是主机字节序的,其它字段是网络字节序的。
在Linux系统中,所有字段均为网络字节序。
原始ICMPv6套接口接收绝大多数内核收到的ICMPv4消息。
但是ICMPv6是ICMPv4的一个超集,包括ARP和IGMP的功能。
因此,原始ICMPv6套接口有可能收到比原始ICMPv4套接口多的分组。
然而大多数应用程序只对所有的ICMP消息中的一个子集感兴趣。
为了减少内核通过原始ICMPv6套接口向应用进程传递的分组的数量,提供了一个由应用程序指定的过滤器。
过滤器使用structicmp6_filter数据类型声明,该数据类型由<
netinet/icmp6.h>
定义。
一个原始ICMPv6套接口的当前过滤器可用setsockopt和getsockopt函数来设置和获取,其中level参数为IPPROTO_ICMPV6,optname参数为ICMP6_FILTER。
下面是操作icmp6_filter结构的6个宏:
#include<
netinet/icmp6.h>
voidICMP6_FILTER_SETPASSALL(structicmp6_filter*filt);
voidICMP6_FILTER_SETBLOCKALL(structicmp6_filter*filt);
voidICMP6_FILTER_SETPASS(intmsgtype,structicmp6_filter*filt);
voidICMP6_FILTER_SETBLOCK(intmsgtype,structicmp6_filter*filt);
intICMP6_FILTER_WILLPASS(intmsgtype,conststructicmp6_filter*filt);
intICMP6_FILTER_WILLBLOCK(intmsgtype,conststructicmp6_filter*filt);
上述这些宏调用中的filt参数是一个指向某个icmp6_filter变量的指针,前四个宏修改此icmp6_filter变量,后两个宏检查它。
Msgtype参数值在0到255之间,指定ICMP消息类型。
SETPASSALL宏设定所有消息类型均可传递给应用进程。
SETBLOCKALL宏设定没有消息类型可传递。
作为缺省,当一个ICMPv6原始套接口创建时,所有ICMPv6消息类型可以传递给其应用进程。
SETPASS宏打开某个消息类型向该应用程序的传递,而SETBLOCK宏阻塞某个消息类型的传递。
如果给定消息类型可以由过滤器传递时,WILLPASS宏返回1,否则返回0。
如果给定消息类型被过滤器所阻塞时,WILLBLOCK宏返回1,否则返回0。
下面是一个应用程序的一部分,它只接收ICMPv6路由器通告。
Structicmp6_filtermyfilter;
Fd=socket(AF_INET6,SOCK_RAW,IPPROTO_ICMPV6);
ICMP6_FILTER_SETBLOCKALL(&
myfilter);
ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT,&
Setsockopt(fd,IPPROTO_ICMPV6,ICMP6_FILTER,&
myfilter,sizeof(myfilter));
上述语句首先阻塞所有消息类型的传递(缺省是传递所有消息类型),然后只传递路由器通告消息。
9.5ping程序
本节将开发一个同时支持IPv4和IPv6的ping程序。
此程序与目前公开运行的版本有两点不同。
首先,此程序忽略了目前运行的ping程序所支持的大量选项,只支持一个选项;
其次,本程序同时支持IPv4和IPv6,而目前运行的程序只支持IPv4。
Ping程序的操作很简单,向某些IP地址发送一个IP地址发送一个ICMP回射请求,接着该节点返回一个ICMP回射应答。
这两个ICMP消息在IPv4和IPv6下均得到支持。
图9-1为ICMP消息的格式。
在图9-1中,类型字段可以选取15个不同的值,每个值描述了一种特定类型的ICMP报文。
某些ICMP报文还使用代码字段的值来进一步描述不同的条件。
检查和字段覆盖整个ICMP报文,使用的算法和IP首部检查和算法相同,对于ICMP报文,检查和字段是必须具有的。
标识符字段将设为ping程序的进程ID,序列号随每个分组的发送而增加1。
除此之外,还将存入一个分组发送时间的8字节时间戳作为可选数据。
ICMP规定回射应答需返回标识符、序列号和所有可选数据。
存储时间戳使得用户可以在收到应答时计算RTT。
078151631
16位检验和
8位代码
8位类型
16位序列号
16位标识符
可选数据
图9-1ICMPv4及ICMPv6回射请求和回射应答消息格式
图9-2说明了组成ping程序的主要函数流程。
为SIGALRM建立信号处理程序
Sig_alrm()
Main()
Send_v4()
Readloop()
或
Proc_v4()
Recvfrom()
Send_v6()
Proc_v6()
每秒发送一个回射请求
无限接收循环
图9-2组成ping程序的主要函数流程
Ping程序分为两大部分:
一部分读取一个原始套接口上收到的所有消息,并输出ICMP回射应答,另一部分每隔一秒发送一个回射请求。
另一部分由SIGALRM信号每秒驱动一次。
有关该程序实现的代码如下:
#include"
unp.h"
#include<
netinet/in_systm.h>
netinet/ip.h>
netinet/ip_icmp.h>
#defineBUFSIZE1500
/*globals*/
charrecvbuf[BUFSIZE];
charsendbuf[BUFSIZE];
intdatalen;
/*#bytesofdata,followingICMPheader*/
char*host;
intnsent;
/*add1foreachsendto()*/
pid_tpid;
/*ourPID*/
intsockfd;
intverbose;
/*functionprototypes*/
voidproc_v4(char*,ssize_t,structtimeval*);
voidproc_v6(char*,ssize_t,structtimeval*);
voidsend_v4(void);
voidsend_v6(void);
voidreadloop(void);
voidsig_alrm(int);
voidtv_sub(structtimeval*,structtimeval*);
structproto{
void(*fproc)(char*,ssize_t,structtimeval*);
void(*fsend)(void);
structsockaddr*sasend;
/*sockaddr{}forsend,fromgetaddrinfo*/
structsockaddr*sarecv;
/*sockaddr{}forreceiving*/
socklen_tsalen;
/*lengthofsockaddr{}s*/
inticmpproto;
/*IPPROTO_xxxvalueforICMP*/
}*pr;
#ifdefIPV6
ip6.h"
/*shouldbe<
netinet/ip6.h>
*/
icmp6.h"
/*shouldbe<
#endif
structprotoproto_v4={proc_v4,send_v4,NULL,NULL,0,IPPROTO_ICMP};
structprotoproto_v6={proc_v6,send_v6,NULL,NULL,0,IPPROTO_ICMPV6};
intdatalen=56;
/*datathatgoeswithICMPechorequest*/
int
main(intargc,char**argv)
{
intc;
structaddrinfo*ai;
opterr=0;
/*don'
twantgetopt()writingtostderr*/
while((c=getopt(argc,argv,"
v"
))!
=-1){
switch(c){
case'
v'
:
verbose++;
break;
case'
?
'
err_quit("
unrecognizedoption:
%c"
c);
}
}
if(optind!
=argc-1)
err_quit("
usage:
ping[-v]<
hostname>
"
);
host=argv[optind];
pid=getpid();
Signal(SIGALRM,sig_alrm);
ai=Host_serv(host,NULL,0,0);
printf("
PING%s(%s):
%ddatabytes\n"
ai->
ai_canonname,
Sock_ntop_host(ai->
ai_addr,ai->
ai_addrlen),datalen);
/*4initializeaccordingtoprotocol*/
if(ai->
ai_family==AF_INET){
pr=&
proto_v4;
}elseif(ai->
ai_family==AF_INET6){
proto_v6;
if(IN6_IS_ADDR_V4MAPPED(&
(((structsockaddr_in6*)
ai->
ai_addr)->
sin6_addr)))
cannotpingIPv4-mappedIPv6address"
}else
unknownaddressfamily%d"
ai_family);
pr->
sasend=ai->
ai_addr;
sarecv=Calloc(1,ai->
ai_addrlen);
salen=ai->
ai_addrlen;
readloop();
exit(0);
}
void
readloop(void)
intsize;
charrecvbuf[BUFSIZE];
socklen_tlen;
ssize_tn;
structtimevaltval;
sockfd=Socket(pr->
sasend->
sa_family,SOCK_RAW,pr->
icmpproto);
setuid(getuid());
tneedspecialpermissionsanymore*/
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 网络程序设计教材第九章 原始套接口数据链路层 网络程序设计 教材 第九 原始 接口 数据链