网络程序设计教材第九章 原始套接口数据链路层Word格式.docx

上传人:b****4 文档编号:16739922 上传时间:2022-11-25 格式:DOCX 页数:24 大小:66.22KB
下载 相关 举报
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx_第1页
第1页 / 共24页
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx_第2页
第2页 / 共24页
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx_第3页
第3页 / 共24页
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx_第4页
第4页 / 共24页
网络程序设计教材第九章 原始套接口数据链路层Word格式.docx_第5页
第5页 / 共24页
点击查看更多>>
下载资源
资源描述

网络程序设计教材第九章 原始套接口数据链路层Word格式.docx

《网络程序设计教材第九章 原始套接口数据链路层Word格式.docx》由会员分享,可在线阅读,更多相关《网络程序设计教材第九章 原始套接口数据链路层Word格式.docx(24页珍藏版)》请在冰豆网上搜索。

网络程序设计教材第九章 原始套接口数据链路层Word格式.docx

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模板 > 其它模板

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1