当前位置: 首页 > news >正文

公司网站建设实施方案wordpress图像大小

公司网站建设实施方案,wordpress图像大小,百度指数移动版,网站程序语言目前大多操作系统都为程序提供访问数据链路层的功能#xff0c;此功能可提供以下能力#xff1a; 1.能监视由数据链路层接收的分组#xff0c;使得tcpdump之类的程序能运行#xff0c;而无需专门的硬件设备来监视分组。如果结合使用网络接口进入混杂模式#xff08;promis…目前大多操作系统都为程序提供访问数据链路层的功能此功能可提供以下能力 1.能监视由数据链路层接收的分组使得tcpdump之类的程序能运行而无需专门的硬件设备来监视分组。如果结合使用网络接口进入混杂模式promiscuous mode的能力那么应用甚至能监视本地电缆上流通的所有分组而不仅仅是以程序运行所在主机为目的地的分组。 网络接口进入混杂模式的能力在日益普及的交换式网络即使用交换机连接多个设备的计算机网络在交换式网络中交换机充当着网络通信的中心交换机的每个接口都直接与一个主机相连中用处不大因为交换机仅仅把传给目的主机的单播、多播或广播分组传到目的主机的物理网络接口上。为了监视流经其他交换机端口的分组连接到我们主机的交换机的端口必须配置成接收其他分组的这称为监视器模式monitor mode或端口镜像port mirroring。有些你可能认为没有交换器的存储转发能力的设备实际也具有这种能力如双速率10/100 Mbit/s集线器也可近似看成一个双端口的交换机一个端口上连接100Mbit/s系统另一个端口上连接10Mbit/s系统。 2.能够作为应用进程而非内核的一部分运行某些程序如RARP服务器的大多数Unix版本是普通的应用进程它们从数据链路读入RARP请求又往数据链路写出RARP应答RARP请求和应答都不是IP数据报。 Unix上访问数据链路层的3个常用方法是BSD的分组过滤器BPF、SVR 4的数据链路提供者接口DLPI、Linux的SOCK_PACKET接口。我们先介绍这3个数据链路访问接口然后讲解libpcap这个公开可得的分组捕获函数库该函数库在这3个系统上都能工作因此使用此函数库能使我们编写独立于操作系统提供的实际数据链路访问接口的程序。 4.4 BSD及源自Berkeley的许多其他实现都支持BSD分组过滤器BSD Packet FilterBPFBPF的实现在TCPv2中有讲解。 发送一个分组之前或在接收一个分组之后会调用BPF TCPv2中给出了某个以太网接口驱动程序中调用BPF的例子。在分组接收后尽早调用BPF以及在发送分组前尽晚调用BPF的原因是为了提供精确的时间戳。 尽管往数据链路中安置一个用于捕获所有分组的代码并不困难BPF强大在它的过滤能力打开一个BPF设备的应用进程可以装载各自的过滤器这个过滤器随后由BPF应用于每个分组有些过滤器比较简单如只接收UDP或TCP分组但更复杂的过滤器可以检查分组首部某些字段是否为特定值如以下过滤器 tcp and port 80 and tcp[13:1] 0x7 ! 0只收集去往或来自端口80的设置了SYN、FIN、RST标志的TCP分节其中表达式tcp[13:1]指代从TCP首部开始位置起字节偏移量为13那个位置开始的1字节值。 BPF实现一个基于注册的过滤机器该过滤机器对每个收到的数据包应用特定于应用程序的过滤。虽然可以用这个伪机器的机器语言编写过滤程序但最简单的接口是使用pcap_compile函数把类似上面的ASCII字符串编译成BPF伪机器的机器语言。 BPF使用以下3个技术降低开销 1.BPF过滤在内核中进行以此把BPF到应用进程的数据复制量减少到最小。如果不在内核中过滤需要从内核空间到用户空间的复制分组这种复制开销高昂如果每个分组都这么复制BPF可能跟不上快速的数据链路。 2.由BPF传递到应用进程的只是每个分组的一段定长部分这个长度称为捕获长度capture length也称为快照长度snapshot length简写为snaplen。大多应用进程只需要分组首部而不需要分组数据这个技术减少了由BPF复制到应用进程的数据量例如tcpdump默认把该值设置为96字节能容纳一个14字节的以太网首部、一个40字节的IPv6首部、一个20字节的TCP首部以及22字节的数据如果需要显示来自其他协议如DNS或NFS的额外信息用户就得在运行tcpdump时增大该值。 3.BPF为每个应用进程分别缓冲数据只有当缓冲区已满或读超时时该缓冲区中的数据才复制到应用进程该超时值可由应用进程指定例如tcpdump把它设置为1000msRARP守护进程把它设置为0因为RARP分组极少且RARP服务器需要一接收请求就发送应答。如此缓冲的目的在于减少系统调用的次数。尽管从BPF复制到应用进程的仍然是相同数量的分组但每次系统调用都有一定的开销因而减少系统调用次数就能降低开销。 尽管我们在图29-1中只显示了一个缓冲区BPF其实为每个应用进程维护两个缓冲区在其中一个缓冲区中的数据被复制到应用进程期间另一个缓冲区被用于装填数据这就是标准的双缓冲技术。 我们在图29-1中只显示了BPF的分组接收包括由数据链路从下方网络接收的分组和由数据链路从上方IP接收的分组。应用进程也可以写往BPF使分组通过数据链路往外向上或向下发送出去但大多数应用进程仅仅读BPF。没有理由通过写往BPF发送IP数据报因为IP_HDRINCL套接字选项允许我们写出任何期望的IP数据报包括IP首部。写往BPF的唯一理由是为了自行发送不是IP数据报的网络分组如RARP守护进程就如此发送不是IP数据报的RARP应答。 为了访问BPF我们必须打开一个当前关闭着的BPF设备例如我们可以尝试打开/dev/bpf0如果返回EBUSY错误就尝试打开/etc/bpf1一旦打开一个BPF设备我们可以使用一些ioctl命令设置该设备的特征包括装载过滤器、设置读超时、设置缓冲区大小、将一个数据链路即网络接口连接到BPF设备、启用混杂模式等然后就使用read和write函数执行IO。 SVR 4通过数据链路提供者接口Datalink Provider InterfaceDLPI提供数据链路访问DLPI是一个由ATT设计的独立于协议的访问数据链路层所提供服务的接口其访问通过发送和接收流消息STREAMS message实施。 DLPI有两种打开风格一种是应用进程先打开一个设备然后通过DLPI的DL_ATTACH_REQ请求要使用的网络接口另一种是直接打开某个网络接口设备如le0。为了提升效率需要压入2个流模块STREAMS module在内核中进行分组过滤的pfmod模块和为应用进程缓冲数据的bufmod模块 从概念上来说这两个模块类似BPF开销降低的技术pfmod在内核中使用伪机器支持过滤bufmod通过支持快照长度和读取超时来减少数据量和系统调用次数。 然而一个有趣的区别在于BPF和pfmod过滤器支持的伪机器类型。BPF过滤器是一个有向无环控制流图而pfmod则使用布尔表达式树。前者自然地映射为寄存器型机器代码而后者自然地映射为堆栈型机器代码[McCanne and Jacobson 1993]。该论文表明BPF使用的CFG实现通常比布尔表达式树快3到20倍具体取决于过滤器的复杂性。 另外BPF总是在复制分组前作出过滤决策以避免复制过滤器将会丢弃的数据包。根据DLPI的实现数据包可能会被复制给pfmod然后可能会被pfmod丢弃。 Linux先后有两个从数据链路层接收分组的方法。较旧的方法是创建类型为SOCK_PACKET的套接字此方法更普适但缺乏灵活性较新的方法创建协议族为PF_PACKET的套接字这个方法引入了更过的过滤和性能特性。我们需要有足够的权限才能创建这两种套接字且调用socket的第三个参数必须是指定以太网帧的某个非0值。创建PF_PACKET套接字时调用socket的第二个参数既可以是SOCK_DGRAM表示扣除链路层首部的帧也可以是SOCK_RAW表示完整的链路层帧。SOCK_PACKET套接字只返回完整的链路层帧。以下方式可以从数据链路接收所有帧 fd socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); // 较新方法 fd socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); // 较旧方法如果只想捕获IPv4帧 fd socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); // 较新方法 fd socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP)); // 较旧方法用作socket调用的第三个参数的常值还有ETH_P_ARP、ETH_P_IPV6等它们告知数据链路应该把接收到的哪些类型的帧传递给所创建的套接字。如果数据链路支持混杂模式如以太网如果需要的话可以将设备改为混杂模式。对于PF_PACKET套接字把一个网络接口改为混杂模式可通过PACKET_ADD_MEMBERSHIP套接字选项完成此时setsockopt函数的第四个参数的类型为packet_mreq在此结构中指定网络接口以及PACKET_MR_PROMISC行为对于SOCK_PACKET套接字改为混杂模式需要使用SIOCGIFFLAGS标志调用ioctl以获取标志然后将IFF_PROMISC加入获取到的标志再以SIOCSIFFLAGS调用ioctl存储标志不幸的是若采用此方法多个程序同时设置混杂模式时可能会互相干扰且有缺陷的程序可能在退出后还保持着混杂模式。 Lunix的数据链路访问方法与BPF和DLPI存在如下差别 1.Linux方法不提供内核缓冲且只有较新的方法才提供内核过滤需要用SO_ATTACK_FILTER套接字选项安装尽管这些套接字有普通的套接字接收缓冲区但多个帧不能缓冲在一起由单个读操作一次性地传递给应用进程。这样会增加从内核到应用进程复制的数据的开销。 2.Linux较旧的方法不提供针对设备的过滤较新的方法可通过调用bind与某个设备关联。如果调用socket时指定了ETH_P_IP那么来自任何设备如以太网、PPP链路、环回设备的所有IPv4分组都被传递到所创建的套接字。recvfrom函数将返回一个通用套接字地址结构其中的sa_data成员含有设备名字如eth0应用进程必须自行丢弃来自不关注的设备的数据。这里仍然会有太多数据返回到应用进程从而妨碍对于高速网络的监视。 libpcap是访问操作系统所提供的分组捕获机制的分组捕获函数库它是与实现无关的。目前它只支持分组的读入当然只需往该函数库中增加一些代码行就可以让调用者往数据链路写出分组。libnet函数库不仅支持往数据链路写分组还能构造任意协议的分组。 libpcap目前支持源自Berkeley内核中的BPF、Solaris 2.x和HP-UX中的DLPI、SunOS 4.1.x中的NIT网络接口层Network Interface Layer、Linux的SOCK_PACKET套接字和PF_PACKET套接字以及若干其他操作系统。tcpdump就使用该函数库。libpcap由大约25个函数组成我们稍后给出使用其中常用函数的一个例子所有库函数均以pcap_前缀打头。 libpcap函数库可从http://www.tcpdump.org公开获取。 libnet函数库可构造任意协议的分组并将其输出到网络中的接口它以与实现无关的方式提供原始套接字访问方式和数据链路访问方式。 libnet隐藏了构造IP、UDP、TCP首部的许多细节并提供简单且便于移植的数据链路和原始套接字写出访问接口。稍后给出一些libnet库函数的使用例子。libnet的所有库函数均以libnet_前缀打头。 现开发一个程序它向一个名字服务器发送含有某个DNS查询的UDP数据报然后使用分组捕获函数库读入应答确定这个名字服务器是否计算UDP校验和。对于IPv4UDP校验和的计算是可选的如今大多系统默认开启校验和但较老系统如SunOS 4.1.x默认禁止校验和。当今所有系统特别是运行名字服务器的系统都总是应该开启UDP校验和否则DNS服务器收到的受损数据报可能破坏DNS服务器的数据库存入错误的信息。 开启和禁止UDP校验和通常是基于系统范围设置的。 我们将自行构造UDP数据报DNS查询并把它写到一个原始套接字这个查询使用普通的UDP套接字就可发送但我们想展示如何使用IP_HDRINCL套接字选项构造一个完整的IP数据报。 并且我们无法在从普通UDP套接字读入时获取UDP校验和UDP或TCP分组也不会传到原始套接字因此我们必须使用分组捕获机制获取含有名字服务器的应答的完整UDP数据报。 我们会检查所获取UDP首部中的校验和字段如果其值为0那么该名字服务器没有开启UDP校验和。 我们把自行构造的UDP数据报写出到原始套接字然后使用libpcap读回其应答。UDP模块也接收到这个来自名字服务器的应答并将响应以一个ICMP端口不可达错误因为UDP模块根本不知道我们自行构造的UDP数据报选用的端口号。名字服务器将忽略这个ICMP错误。使用TCP编写一个这样的测试程序比较困难尽管我们可以很容易地把构造的TCP分节写出到网络但对于此分节的应答我们的TCP模块响应以一个RST结果是连三路握手都完成不了。 绕过这个难题的方法之一是以发送主机所在子网上某个未使用的IP地址为源地址发送TCP分节且事先在发送主机上为这个未使用IP地址增加一个ARP表项使得发送主机能回答这个未使用地址的ARP请求但不把这个未使用IP地址作为别名地址配置在发送主机上这将导致发送主机上的IP协议栈丢弃所接收的目的地址为未使用地址的分组前提是发送主机不用作路由器。 以下是构成udpcksum程序的函数 以下是udpcksum.h头文件 #include unp.h #include pcap.h#include netinet/in_systm.h /* required for ip.h */ #include netinet/in.h #include netinet/ip.h #include netinet/ip_var.h #include netinet/udp.h #include netinet/udp_var.h #include net/if.h #include netinet/if_ether.h#define TTL_OUT 64 /* outgoing TTL *//* declare global variables */ extern struct sockaddr *dest, *local; extern socklen_t destlen, locallen; extern int datalink; extern char *device; extern pcap_t *pd; extern int rawfd; extern int snaplen; extern int verbose; extern int zerosum;/* function prototypes */ void cleanup(int); char *next_pcap(int *); void open_output(void); void open_pcap(void); void send_dns_query(void); void test_udp(void); void udp_write(char *, int); struct udpiphdr *udp_read(void);以下是udpcksum的main函数 #include udpcksum.h/* define global variables */ struct sockaddr *desc, *local; struct sockaddr_in locallookup; socklen_t destlen, locallen;int datalink; /* from pcap_datalink(), in net/bpf.h */ char *device; /* pcap device */ pcap_t *pd; /* packet capture struct pointer */ int rawfd; /* raw socket to write on */ int snaplen 200; /* amount of data to capture */ int verbose; int zerosum; /* send UDP query with no checksum */static void usage(const char *);int main(int argc, char *argv[]) {int c, lopt 0;char *ptr, localname[1024], *localport;struct addrinfo *aip;opterr 0; /* dont want getopt() writing to stderr */// getopt函数可以接受数字选项如此处的0while ((c getopt(argc, argv, 0i:l:v)) ! -1) {switch (c) {// -0选项要求不设置UDP校验和就发送UDP查询以便查看服务器对它的处理是否不同于设置了校验和的数据报case 0:zerosum 1;break;// -i选项用于指定接收服务器的应答的接口如果接口未指定分组捕获函数库将会选择一个// 但函数库选定的接口在多宿主机上可能不是即将接收DNS应答的接口// 从分组捕获设备读入与从普通套接字读入的差别之一就体现在此// 使用套接字我们可以使用通配地址从而接收到达任意接口的分组// 但使用分组捕获设备就只能在单个接口上接收到达的分组// Linux的SOCK_PACKET方法没有把它的数据链路捕获限定在单个设备// 尽管如此libpcap却基于其默认设置或我们的-i选项提供限定接口形式的过滤case i:device optarg; /* pcap device */break;// -l选项用于指定源IP地址和源端口号本选项的参数中端口号或服务名是最后一个点号之后的部分// 源IP地址是组后一个点号之前的部分case l: /* local IP address and port #: a.b.c.d.p */if ((ptr strrchr(optarg, .)) NULL) {usage(invalid -l option);}*ptr 0; /* null replaces final period */localport ptr; /* service name or port number */strncpy(localname, optarg, sizeof(localname));lopt 1;break;case v:verbose 1;break;case ?:usage(unrecognized option);}}// 剩余命令行参数必须恰好是两个运行DNS服务器的目的主机名或目的IP和服务名或端口号if (optind ! argc - 2) {usage(missing host and/or serv);}/* convert destination name and service */// 调用我们的host_serv将目的主机名或目的IP和服务名或端口号转换成套接字地址结构aip Host_serv(argv[optind], argv[optind 1], AF_INET, SOCK_DGRAM);dest aip-ai_addr; /* dont freeaddrinfo() */destlen aip-ai_addrlen;/** Need local IP address for source IP address for UDP datagrams.* Cant specify 0 adn let IP choose, as we need to know it for* the pseudoheader to calculate the UDP checksum.* If -l option supplied, then use those valuse; otherwise,* connect a UDP socket to the destination to determine the* right source address.*/// 我们自定构造UDP首部因此我们在写出该UDP数据报前必须知道源IP地址我们不能让IP模块为其选择值// 因为源IP地址是UDP伪首部的一部分计算UDP校验和时会使用伪首部// 如果有-l选项将本地地址和端口转换为套接字地址结构if (lopt) {/* convert local name and service */aip Host_serv(localname, localport, AF_INET, SOCK_DGRAM);local aip-ai_addr; /* dont freeaddrinfo() */locallen aip-ai_addrlen;// 否则通过把一个UDP套接字连接到目的地确定内核选定的本地IP地址和临时端口号} else {int s;s Socket(AF_INET, SOCK_DGRAM, 0);Connect(s, dest, destlen);/* kernel chooses correct local address for dest */locallen sizeof(locallookup);local (struct sockaddr *)locallookup;Getsockname(s, local, locallen);if (locallookup.sin_addr.s_addr htonl(INADDR_ANY)) {err_quit(Cant determine local address - use -l\n);}close(s);}// 调用open_output创建一个原始套接字并开启IP_HDRINCL套接字选项// 我们于是可以往这个套接字写出包括IP首部在内的完整IP数据报// open_output函数还有一个使用libnet实现的版本open_output(); /* open output, either raw socket or libnet */// 调用open_pcap打开分组捕获设备open_pcap(); /* open packet capture device */// 创建原始套接字和打开分组捕获设备都需要超级用户特权但具体取决于实现// 如对于BPF管理员可设置/dev/bpf设备的访问权限// 既然已经完成特权操作我们此处放弃这个特权假定这个特权是通过设置用户id而获取的// 具有超级用户特权的进程调用setuid把它的实际用户ID、有效用户ID、保存的设置用户ID都设为当前的实际用户IDsetuid(getuid()); /* dont need superuser privileges anymore */// 防止用户在程序运行完前强行终止它Signal(SIGTERM, cleanup);Signal(SIGINT, cleanup);Signal(SIGHUP, cleanup);// test_udp函数发送一个DNS查询并读入服务器的应答test_udp();// cleanup函数显示来自分组捕获函数库的统计结果后终止进程cleanup(0); }open_pcap函数由main函数调用以打开分组捕获设备 #include udpcksum.h#define CMD udp and src host %s and src port %dvoid open_pcap(void) {uint32_t localnet, netmask;char cmd[MAXLINE], errbuf[PCAP_ERRBUF_SIZE], str1[INET_ADDRSTRLEN], str2[INET_ADDRSTRLEN];struct bpf_program fcode;// 如果没有指定分组捕获设备通过-i命令行选项就调用pcap_lookupdev选择一个设备// pcap_lookupdev函数以SIOCGIFCONF为参数调用ioctl找到索引号最小的UP状态的接口设备除环回接口外if (device NULL) {// 许多pcap库函数在出错时填写一个出错消息串// 传给pcap_lookupdev函数的唯一参数就是一个用于填写出错消息的字符数组if ((device pcap_lookupdev(errbuf)) NULL) {err_quit(pcap_lookup: %s, errbuf);}}printf(device %s\n, device);/* hardcode: promisc0, to_ms500 */// 调用pcap_open_live打开这个设备函数名中的live表明所打开的是一个真实设备// 而不是一个含有先前保存的分组的文件// device参数是设备名snaplen参数是每个分组保存的字节数第三个参数为是否设置混杂模式// 第四个参数为以毫秒为单位的超时值第五个参数是指向用于返回出错字符串的字符数组指针// 如果设置了混杂模式网络接口就被投入混杂模式导致它接收电缆上流经的所有分组// 对于tcpdump混杂模式是通常的模式但对于我们的例子来自DNS服务器的应答会被发送到本主机因此无需设置混杂模式// 超时参数指读超时如果每收到一个分组就让设备把该分组返送到应用进程会引起从内核到应用进程的大量个体分组复制// 因此效率比较低libpcap仅当设备的读缓冲区被填满或读超时发生时才返送分组// 如果超时值被设为0则每个分组一经接收就被返送if ((pd pcap_open_live(device, snaplen, 0, 500, errbuf)) NULL) {err_quit(pcap_open_live: %s, errbuf);}// pcap_lookupnet函数返回分组捕获设备的网络地址和子网掩码// 我们接下来调用pcap_compile时必须指定这个子网掩码// 因为分组过滤器需要用子网掩码判断一个IP地址是否为一个子网定向广播地址if (pcap_lookupnet(device, localnet, netmask, errbuf) 0) {err_quit(pcap_lookupnet: %s, errbuf);}if (verbose) {printf(localnet %s, netmask %s\n, Inet_ntop(AF_INET, localnet, str1, sizeof(str1)),Inet_ntop(AF_INET, netmask, str2, sizeof(str2)));}snprintf(cmd, sizeof(cmd), CMD, Sock_ntop_host(dest, destlen), ntohs(sock_get_port(dest, destlen)));if (verbose) {printf(cmd %s\n, cmd);}// pcap_compile函数把我们在cmd字符数组中构造的过滤器字符串编译成一个过滤器程序// 将其存放在fcode中这个过滤器将选择我们希望接收的分组if (pcap_compile(pd, fcode, cmd, 0, netmask) 0) {err_quit(pcap_compile: %s, pcap_geterr(pd));}// pcap_setfilter函数把我们刚编译出来的过滤器程序装载到分组捕获设备if (pcap_setfilter(pd, fcode) 0) {err_quit(pcap_setfilter: %s, pcap_geterr(pd));}// pcap_datalink函数返回分组捕获设备的数据链路类型接收分组时我们根据该值确定数据链路首部大小if ((datalink pcap_datalink(pd)) 0) {err_quit(pcap_datalink: %s, pcap_geterr(pd));}if (verbose) {printf(datalink %d\n, datalink);} }test_udp函数发送一个DNS查询并读入服务器的应答 void test_udp(void) {// 我们希望这两个自动变量从信号处理函数siglongjmp到本函数前后值保持不变// 加上volatile限定词可以防止编译器优化导致跳回后nsent当做初始值0使用因为从定义到使用看起来没有修改过它的值volatile int nsent 0, timeout 3;struct udpiphdr *ui;Signal(SIGALRM, sig_alrm);// 首次调用sigsetjmp时它返回0从siglongjmp函数跳回时它返回1// sigsetjmp函数的第二个参数非0时会将当前的信号屏蔽字保存在jmpbuf参数中// 从而从siglongjmp函数跳回时恢复信号屏蔽字// 进入信号处理函数时会将该信号信号加入屏蔽字从而跳回来时恢复信号屏蔽字if (sigsetjmp(jmpbuf, 1)) {// 进入此处说明是从SIGALRM信号处理函数中调用siglongjmp跳转回来的// 即我们发送了一个请求但没有收到应答从而超时导致进入SIGALRM信号处理函数然后跳转回来// 如果3次请求都超时则终止进程if (nsent 3) {err_quit(no response);}// 否则显示一条消息并倍增超时值通过指数回退增加printf(timeout\n);// timeout的初始值为3表示首次超时值为3秒然后依次是6秒、12秒timeout * 2; /* exponential backoff: 3, 6, 12 */}// 我们像这样使用sigsetjmp和siglongjmp函数而非简单地判断读函数是否错误返回EINTR// 是因为分组捕获函数库的读函数由我们的udp_read函数调用在read函数返回EINTR时重启读操作// 而我们不想为了返回EINTR错误而修改库函数唯一的解决方法是捕获SIGALRM信号并执行一个非本地的长跳转// 从而让控制流返回到本函数而非库函数中// 信号处理函数建立后和sigsetjmp首次调用前SIGALRM信号也有可能被递交因此此时再打开该标志// 即使程序本身不会导致产生SIGALRM信号它也可能通过其他方式产生如使用kill命令canjump 1; /* siglongjmp is now OK */// send_dns_query函数向DNS服务器发送一个DNS查询send_dns_query();nsent;// udp_read函数用于读入DNS服务器的应答读应答前先调用alarm防止读操作永远阻塞// 超时时内核将产生SIGALRM信号而我们的信号处理函数会调用siglongjmpalarm(timeout); ui udp_read();canjump 0;alarm(0);if (ui-ui_sum 0) {printf(UDP checksums off\n);} else {printf(UDP checksums on\n);}if (verbose) {printf(received UDP checksum %x\n, ntohs(ui-ui_sum));} }以下是我们的SIGALRM的信号处理函数sig_alrm以下内容与test_udp函数放在同一文件 #include udpcksum.h #include setjmp.hstatic sigjmp_buf jmpbuf; static int canjump;void sig_alrm(int signo) {// canjmp是test_udp函数中初始化跳转缓冲区后设置的并在读入应答后清除if (canjmp 0) {return;}siglongjmp(jmpbuf, 1); }以下send_dns_query函数构造一个DNS查询并通过原始套接字把该UDP数据报发送给名字服务器 void send_dns_query(void) {size_t nbytes;char *buf, *ptr;// 分配缓冲区它足以存放20字节IP首部、8字节UDP首部、100字节用户数据buf Malloc(sizeof(struct udpiphdr) 100);// ptr指向用户数据的第一个字节ptr buf sizeof(struct udpiphdr); /* leave room for IP/UDP headers */// DNS标识字段设为1234*((uint16_t *)ptr) htons(1234); /* identification */ptr 2;// DNS标志字段*((uint16_t *)ptr) htons(0x0100); /* flags: recursion desired */ptr 2;// DNS问题数字段为1表示DNS查询中包含1个问题*((uint16_t *)ptr) htons(1); /* # questions */ptr 2;// 把回答的RR数、权威RR数、额外RR数都设为0*((uint16_t *)ptr) 0; /* # answer RRs */ptr 2;*((uint16_t *)ptr) 0; /* # authority RRs */ptr 2;*((uint16_t *)ptr) 0; /* # additional RRs */ptr 2;// 查询a.root-servers.net的IP地址// \001是1个8进制字节表示此标签长度为1个字节其他8进制字节同理memcpy(ptr, \001a\012root-servers\003net\000, 20);ptr 20;// DNS查询类型为A查询*((uint16_t *)ptr) htons(1); /* query type A */ptr 2;*((uint16_t *)ptr) htons(1); /* query class 1 (IP addr) */ptr 2;// 这个消息由36字节的用户数据构成8个2字节字段和1个20字节域名nbytes (ptr - buf) - sizeof(struct udpiphdr);// 调用我们的udp_write构造UDP和IP首部并把构造完的IP数据报写到原始套接字udp_write(buf, nbytes);if (verbose) {printf(sent: %s bytes of data\n, nbytes);} }以下是open_output函数 // 存放原始套接字描述符的全局变量 int rawfd; /* raw socket to write on */void open_output(void) {int on 1;/** Need a raw socket to write our own IP datagrams to.* Process must have superuser privileges to create this socket.* Also must set IP_HDRINCL so we can write our own IP headers.*/rawfd Socket(dest-sa_family, SOCK_RAW, 0);// 开启IP_HDRINCL套接字选项该选项允许我们往套接字写出包括IP首部在内的完整IP数据报Setsockopt(rawfd, IPPROTO_IP, IP_HDRINCL, on, sizeof(on)); }以下udp_write函数构造IP和UDP首部并把结果数据报写出到原始套接字以下内容与open_output放在同一文件中 void udp_write(char *buf, int userlen) {struct udpiphdr *ui;struct ip *ip;/* fill in and checksum UDP header */// ip指向IP首部的开始位置ui也指向相同位置但udpiphdr结构是IP和UDP首部的组合ip (struct ip *)buf;ui (struct udpiphdr *)buf;// 显式清0首部区域以免可能留在缓冲区中的剩余数据影响校验和的计算// 此处的早先版本显式清零udpiphdr结构中的每个成员但该结构有一些实现相关的细节不同系统之间会有差异// 在显式构造首部时这是一个典型的移植性问题bzero(ui, sizeof(*ui));// ui_len是UDP首部字节数8字节加上UDP用户数据字节数此值就是UDP首部中的长度字段值ui-ui_len htons((uint16_t)(sizeof(struct udphdr) userlen));/* then add 28 for IP datagram length */// userlen是整个IP数据报的长度包括IP首部// 其值为UDP首部之后的UDP用户数据字节数加上28字节20字节IP首部8字节UDP首部userlen sizeof(struct udpiphdr);// UDP校验和计算不仅涵盖UDP首部和UDP数据还涉及来自IP首部的若干字段这些来自IP首部的字段构成伪首部// 校验和计算涵盖伪首部能提供如下额外验证如果校验和正确则数据报确实已递送到正确的主机和正确的协议处理代码// 从此处开始到ui_ulen的赋值为值都是构成伪首部的字段ui-ui_pr IPPROTO_UDP;ui-ui_src.s_addr ((struct sockaddr_in *)local)-sin_addr.s_addr;ui-ui_dst.s_addr ((struct sockaddr_in *)dest)-sin_addr.s_addr;ui-ui_sport ((struct sockaddr_in *)local)-sin_port;ui-ui_dport ((struct sockaddr_in *)dest)-sin_port;ui-ui_ulen ui-ui_len;// 如果计算校验和即没有设置-0命令行参数if (zerosum 0) { #if 1 /* change to if 0 for Solaris 2.x, x 6 */// 如果计算出的校验和为0就改为存入0xffff在一的补数ones complement中这两个值是同义的// UDP通过设置校验和为0值指示发送者没有存放UDP校验和// 在第二十八章中我们没有检查计算出的校验和是否为0因为ICMPv4校验和是必需的其值为0不指示没有校验和if ((ui-ui_sum in_cksum((u_int16_t *)ui, userlen)) 0) {ui-ui_sum 0xffff;} // Solaris 2.xx6对于通过设置了IP_HDRINCL套接字选项的原始套接字发送的TCP分节或UDP数据报而言 // 在校验和字段上有一个缺陷这些校验和由内核计算但进程必须把ui_sum成员设置为TCP或UDP的长度 #elseui-ui_sum ui-ui_len; #endif}/* fill in rest of IP header *//* ip_output() calculates stores IP header checksum */// 既然开启了IP_HDRINCL套接字选项我们就要手动填写IP首部中的大多数字段ip-ip_v IPVERSION;ip-ip_hl sizeof(struct ip) 2;ip-ip_tos 0; // ip_len成员需要根据所用系统决定按主机字节序设置还是网络字节序设置这是使用原始套接字时的一个移植性问题 #if defined(linux) || defined(__OpenBSD__)ip-ip_len htons(userlen); /* network byte order */ #elseip-ip_len userlen; /* host byte order */ #endif// 把IP首部的标识字段设为0以告知IP模块去设置这个字段主机每发送一份IP数据报标识字段的值就会加1// 如果IP数据报需要进行分片发送则每个分片的IP首部标识字段都是一致的// IP模块还会计算IP首部校验和ip-ip_id 0; /* let IP set this *//* frag offset, MF and DF flags */// MF是More Fragments的简称值为1代表后面还有分片的数据报值为0代表当前数据报已是最后一个分片// DF是Dont Fragment的简称表示不能对IP数据报进行分片ip-ip_off 0; ip-ip_ttl TTL_OUT;Sendto(rawfd, buf, userlen, 0, dest, destlen); }以下是udp_read函数它从分组捕获设备读入下一个分组 struct udpiphdr *udp_read(void) {int len;char *ptr;struct ether_header *eptr;for (; ; ) {// 调用我们的next_pcap函数从分组捕获设备获取下一个分组ptr next_pcap(len);// 既然数据链路首部依照实际设备类型存在差异我们根据pcap_datalink函数返回的datalink变量选择分支switch (datalink) {case DLT_NULL: /* loopback header 4 bytes */return udp_check(ptr 4, len - 4);// 虽然名字里有10MB限定词这个数据链路类型也用于100 Mbit/s以太网case DLT_EN10MB:eptr (struct ether_header *)ptr;if (ntohs(eptr-ether_type) ! ETHERTYPE_IP) {err_quit(Ethernet type %x not IP, ntohs(eptr-ether_type));}return udp_check(ptr 14, len - 14);// SLIPSerial Line Internet Protocol链路利用串行端口发送和接收IP数据包case DLT_SLIP: /* SLIP header 24 bytes */return udp_check(ptr 24, len - 24);case DLT_PPP: /* PPP header 24 bytes */return udp_check(ptr 24, len - 24);default:err_quit(unsupported datalink (%d), datalink);}} }以上函数中所示的针对SLIP和PPP的24字节偏移量适用于BSD/OS 2.1版本。 以下是next_pcap函数它返回来自分组捕获设备的下一个分组 char *next_pcap(int *len) {char *ptr;struct pcap_pkthdr hdr;/* keep looking until packet ready */// 库函数pcap_next或者返回下一个分组或者因超时返回NULL// 我们在一个循环中调用pcap_next直到返回一个分组或者被SIGALRM信号中断从而在信号处理函数中跳回test_udp函数// pcap_next函数的返回值是指向所返回分组的一个指针它的第二个参数指向的pcap_pkthdr结构也在返回时被填写while ((ptr (char *)pcap_next(pd, hdr)) NULL);// 捕获到的数据长度通过len参数指针返回给调用者本函数的返回值则是指向所捕获分组的指针*len hdr.caplen; /* capture length */// 函数返回值指向的数据链路首部对于以太网帧是14字节的以太网首部对于环回接口是4字节的伪链路首部return ptr; }pcap_next函数返回分组时填写的pcap_pkthdr结构 ts成员是分组捕获设备读入该分组的时间而不是该分组真正递送到进程的时间。caplen成员是实际捕获的数据量我们的snaplen变量设为200后又将其作为pcap_open_live函数的第二个参数分组捕获机制旨在捕获每个分组的各个首部而非捕获其中所有数据。len成员是该分组在电缆上出现的完整长度caplen总是小于len。 由上图pcap_next函数内部实现中pcap_read函数依赖于分组捕获设备的类型如BPF实现调用read、DLPI实现调用getmsg、Linux调用recvfrom。 以下cleanup函数由main函数在程序即将终止时调用同时也用于键盘输入的中断本程序的信号的信号处理函数 void cleanup(int signo) {struct pcap_stat stat;putc(\n, stdout);if (verbose) {// 调用pcap_stats获取分组捕获统计信息if (pcap_stats(pd, stat) 0) {err_quit(pcap_stats: %s\n, pcap_geterr(pd));}// 由过滤器接收的分组总数printf(%d packets received by filter\n, stat.ps_recv);// 由内核丢弃的分组总数丢弃原因为分组到来时没有足够的缓冲区空间存放它printf(%d packets dropped by kernel\n, stat.ps_drop);}exit(0); }以下是udp_check函数它验证IP和UDP首部中的多个字段我们需要执行这些验证工作因为由分组捕获设备传递给我们的分组绕过了IP层这一点不同于原始套接字 struct udpiphdr *udp_check(char *ptr, int len) {int hlen;struct ip *ip;struct udpiphdr *ui;// 分组长度必须至少包括IP和UDP首部if (len sizeof(struct ip) sizeof(struct udphdr)) {err_quit(len %d, len);}/* minimal verification of IP header */ip (struct ip *)ptr;// 验证IP版本if (ip-ip_v ! IPVERSION) {err_quit(ip_v %d, ip-ip_v);}hlen ip-ip_hl 2;// 验证IP首部长度if (hlen sizeof(struct ip)) {err_quit(ip_hl %d, ip-ip_hl);}if (len hlen sizeof(struct udphdr)) {err_quit(len %d, hlen %d, len, hlen);}// 验证IP首部校验和if ((ip-ip_sum in_cksum((uint16_t *)ip, hlen)) ! 0) {err_quit(ip checksum error);}// 如果协议字段表明这是一个UDP数据报就返回指向IP/UDP组合首部结构的指针if (ip-ip_p IPPROTO_UDP) {ui (struct udpiphdr *)ip;return ui;// 否则就终止程序因为我们在pcap_setfilter函数中指定了不返回其他类型的分组} else {err_quit(not a UDP packet);} }首先使用-0命令行选项运行udpcksum程序以验证名字服务器对于不带校验和的到达数据报也给出响应同时还指定-v命令行选项显示详细信息 之后我们针对一个未开启UDP校验和的本地名字服务器我们的freebsd4主机运行udpcksum不开启UDP校验和的名字服务器越来越少了 以下是open_output和send_dns_query这两个函数用libnet取代原始套接字实现的版本libnet替我们关心许多细节问题包括校验和和IP首部字节序的可移植性。以下是使用libnet的open_output函数 // libnet使用一个不透明数据类型libnet_t作为调用者和函数库的连接 static libnet_t *l; /* libnet descriptor */void open_output(void) {char errbuf[LIBNET_ERRBUF_SIZE];/* Initialize libnet with an IPv4 raw socket */// libnet_init函数返回一个libnet_t指针调用者把它传递给以后的libnet函数以指示所期望的libnet实例// 从这个意义上来说它类似于套接字和pcap_t类型的pcap描述符// 第一个参数为LIBNET_RAW4会请求libnet_init函数打开一个IPv4原始套接字// 如果发生错误libnet_init函数将在它的errbuf参数中返回出错信息并返回空指针l libnet_init(LIBNET_RAW4, NULL, errbuf);if (l NULL) {err_quit(Cant initialize libnet: %s, errbuf);} }以下是使用libnet的send_dns_query函数可将它与使用原始套接字的send_dns_query和udp_write函数相比较 void send_dns_query(void) {char qbuf[24], *ptr;u_int16_t one;int packet_size LIBNET_UDP_H LIBNET_DNSV4_H 24;static libnet_ptag_t ip_tag, udp_tag, dns_tag;/* build query portion of DNS packet */// 构造DNS分组的查询问题部分ptr qbuf;memcpy(ptr, \001a\012root-servers\003net\000, 20);ptr 20;ont htons(1);memcpy(ptr, one, 2); /* query type A */ptr 2;memcpy(ptr, one, 2); /* query class 1 (IP addr) *//* build DNS packet */// libnet_build_dnsv4函数接受用户参数用来构造DNS首部dns_tag libnet_build_dnsv4(1234, /* identification */0x0100, /* flags: recursion desired */1, /* # questions */0, /* # answer RRs */0, /* # authority RRs */0, /* # additional RRs */qbuf, /* query */24, /* length of query */l, dns_tag);/* build UDP header */// lib_build_udp函数接受用户参数用来构造UDP首部udp_tag libnet_build_udp(((struct sockaddr_in *)local)-sin_port, /* soure port */((struct sockaddr_in *)dest)-sin_port, /* dest port */packet_size, /* length */0, /* checksum libnet将自动计算校验和并存入该字段 */NULL, /* payload */0, /* payload length */l, udp_tag);/* Since we specified the checksum as 0, libnet will automatically *//* calculate the UDP checksum. Turn it off if the user doesnt want it. */// 如果用户请求不计算UDP校验和必须显式禁止UDP校验和计算if (zerosum) {if (libnet_toggle_checksum(1, udp_tag, LIBNET_OFF) 0) {err_quit(turning off checksums: %s\n, libnet_geterror(1));}}/* build IP header */// libnet_build_ipv4函数接受用户参数用来构造IPv4首部// libnet会自动留意ip_len字段是否为网络字节序这是通过使用libnet令移植性得以改善的一个例子ip_tag libnet_build_ipv4(packet_size LIBNET_IPV4_H, /* len */0, /* tos */0, /* IP ID */0, /* fragment */TTL_OUT, /* ttl */IPPROTO_UDP, /* protocol */0, /* checksum */((struct sockaddr_in *)local)-sin_addr.s_addr, /* source */((struct sockaddr_in *)dest)-sin_addr.s_addr, /* dest */NULL, /* payload */0, /* payload length */l, ip_tag);// libnet_write函数把组装成的数据报写出到网络if (libnet_write(l) 0) {err_quit(libnet_write: %s\n, libnet_geterror(1));}if (verbose) {printf(sent: %d bytes of data\n, packet_size);} }send_dns_query函数的libnet版本只有67行而原始套接字版本send_dns_query和udp_write函数的组合却有96行且含有至少两个移植性小问题。 原始套接字使我们有能力读写内核不理解的IP数据报数据链路层访问则把这个能力进一步扩展成读写任何类型的数据链路帧而不仅仅是IP数据报。tcpdump也许是直接访问数据链路层的最常用程序。 不同操作系统有不同的数据链路层访问方法如源自Berkeley的BPF、SVR 4的DLPI、Linux的SOCK_PACKET如果我们使用公开可得的分组捕获函数库libpcap我们就可以忽略所有这些区别编写出可移植的代码。 不同系统上编写原始数据报可能各不相同公开可得的libnet函数库隐藏了这些差异所提供的输出接口既可在原始套接字输出也可在数据链路上直接输出。
http://www.dnsts.com.cn/news/60446.html

相关文章:

  • 内销网站要怎么做四川建设厅个人证书查询
  • 宁波网站推广多少钱一个做网站制作课程总结
  • 加强政协网站建设适合小企业的erp软件
  • 网站建设和网络营销区别杭州婚恋网站建设
  • 邱启良 深圳网站建设教务管理系统是应用软件吗
  • 河北建设厅查询官方网站互联壹佰做企业网站
  • 网站导航栏设计代码怀化废品回收市场在哪
  • 大连成品网站建设代运营公司排名前十
  • 我是做颗粒在什么网站上南宁中考招生信息网
  • 重庆网站设计开发灵动网站建设
  • 网站建设 团队介绍在沈阳做一个展示网站多少钱
  • 国航网站建设cdr做图时怎么找到网站的
  • wordpress360网站卫士安阳到濮阳
  • 双阳区住房和城乡建设局网站能绑定域名的免费网站
  • 网站需要的技术望京SOHO网站建设
  • 无锡市无锡市住房和城乡建设局网站浙江建设信息港 官网
  • 秦皇岛陵县网站建设百度seo排名优化公司哪家好
  • 企业内部网站模板免费下载个人简历表格
  • 电子商务网站建设基本组成新乡电子商务网站建设
  • 网站群维护方案怎样宣传自己的品牌
  • 有什么做海报网站ps案例教程网站
  • 做网站ps图片都是多大河北建设厅网站怎么搜索文件
  • 网站的首页页面布局怎么做久就建筑网
  • 那家建设网站p2p公司最好炒股软件下载
  • 专业做网站咨询什么是门户网站?
  • 做影视网站推荐哪个服务器大连网站快速排名
  • wordpress怎么设置语言设置中文做网络优化哪家公司比较好
  • 响应式网站建设的好处商标查询系统官网
  • 手机网站图片 触摸 放大代码 jsseo外链发布
  • 网站建设的产品类型是什么陕西金顶建设公司网站