1.原始套接字的用处

  使用原始套接字可以构造或读取网际层及其以上报文。

  具体来说,可以构造 ICMP, IGMP 协议报文,通过开启 IP_HDRINCL 套接字选项,进而自定义 IPv4首部。

2. 创建原始套接字

2.1 使用 SOCK_RAW 创建原始套接字

  1. sockfd = socket(AF_INET, SOCK_RAW, protocol);

  protocol 定义在 <netinet/in.h>,如 IPPROTO_xxx。

2.2 初始化原始套接字的常用步骤

2.2.1 IP_HDRINCL

  1. int on = 1;
  2. setsockopt(sockfd, IPPOTO_IP, IP_HDRINCL, &on, sizeof(on));

2.2.2 bind, connect

  原始套接字不存在端口的概念(TCP报文只是原始套接字报文的数据部分,而非首部),所以调用 bind, connect,分别设置 报文的源地址和目标地址。

  对应bind而言,没有启用 IP_HDRINCL 选项,则bind会设置套接字源地址,若没有进行bind,内核会 根据数据包出口设置源地址。

  对于connect,若进行connect,则设置套接字对端地址,从而可以用send/write而非sendto。

3. 原始套接字的输出

3.1 sendto 和 send

  如果套接字已经设置了对端地址,则可以使用send

3.2 内核对发送数据的处理

(1)没启动 IP_HDRINCL,内核构造IPv4首部,应用进程传来的数据会被当成 IPv4数据部分,内核会将自己构造的首部和应用传来的数据并接,其中IPv4协议号按照 socket 第三个参数即 IPPROTO_XXX 设置。

(2)开启了 IP_HDRINCL,IPv4首部由应用程序构造, 内核会将应用进程传来的数据当成包含IPv4首部和IPv4数据部分的完整IPv4报文,但 IPv4首部的校验和字段总是由内核计算并存储。

  即,根据IPROTO_XXX是否启用,传递给原始套接字的数据报可能不完整(不包含IP首部)。

3.3 内核会对超出 MTU 的分组进行分片。

3.4 内核总会对IPv4首部求校验并存储,而对于其他校验如ICMP,TCP,需要自己求的。

4. 原始套接字的输入

  原始套接字的输入来自于内核,内核会将哪些数据传给原始套接字呢?

(1)内核不会将TCP和UDP分组传给原始套接字,如果希望获得TCP和UDP分组,必须使用链路层套接字。

(2)大多数的ICMP消息和所有的IGMP消息在内核处理完其中信息后,会传递给原始套接字

(3)内核不认识的协议字段的所有IP数据报将传给原始套接字

(4)如果数据包以分片形式到达,则在数据包完整前,内核不会传给原始套接字

  当IP数据报通过上面的筛选,内核会找到匹配该数据包的原始套接字,并将数据包拷贝给该套接字,那么匹配的规则是什么呢?

(1)数据包的协议和套接字的协议匹配

(2)数据包的目的地址和套接字的源地址匹配

(3)数据包的源地址和套接字的目的地址匹配

(4)若套接字的协议和目的地址和源地址都是 void的,即协议设置为0,没有调用 connect 设置目的地址,没有调用bind设置 源地址。则该套接字会接受所有的IP数据包。

  从原始套接字,收到的数据报,一定是包含IP首部的完整数据包。

5.实际测试

5.1 ping程序

实际发现 connect 没有。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <sys/time.h>
  4. #include <arpa/inet.h>
  5. #include "wrap_common.h"
  6. #include <string.h>
  7. #include <sys/time.h>
  8. #include <sys/types.h> /* See NOTES */
  9. #include <sys/socket.h>
  10. #include <netinet/ip_icmp.h>
  11.  
  12. void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv);
  13.  
  14. uint16_t in_cksum(uint16_t *addr, int len)
  15. {
  16. int nleft = len;
  17. uint32_t sum = 0;
  18. uint16_t *w = addr;
  19. uint16_t answer = 0;
  20.  
  21. while (nleft > 1) {
  22. sum += *w++;
  23. nleft -= 2;
  24.  
  25. }
  26.  
  27. if (nleft == 1) {
  28. *(unsigned char *)(&answer) = *(unsigned char *)w ;
  29. sum += answer;
  30.  
  31. }
  32.  
  33. /* 4add back carry outs from top 16 bits to low 16 bits */
  34. sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
  35. sum += (sum >> 16); /* add carry */
  36. answer = ~sum; /* truncate to 16 bits */
  37. return(answer);
  38. }
  39.  
  40. struct icmp* get_icmp(int seq)
  41. {
  42. static struct icmp icmp_buf;
  43. static int datalen = 56;
  44. int len;
  45.  
  46. icmp_buf.icmp_type = ICMP_ECHO;
  47. icmp_buf.icmp_code = 0;
  48. icmp_buf.icmp_id = getpid();
  49. icmp_buf.icmp_seq = seq;
  50. memset(icmp_buf.icmp_data, 0xa5, datalen);
  51.  
  52. gettimeofday((struct timeval *)icmp_buf.icmp_data, NULL);
  53. len = 8 + datalen;
  54. icmp_buf.icmp_cksum = 0;
  55. icmp_buf.icmp_cksum = in_cksum((unsigned short *)&icmp_buf, len);
  56.  
  57. return &icmp_buf;
  58. }
  59.  
  60. int main(int argc, char **argv)
  61. {
  62. int sockfd;
  63. const struct sockaddr_in dst_addr = {0}, addr = {0}, src_addr = {0};
  64. char *dst_ip;
  65. struct icmp *icmp;
  66. struct timeval tval;
  67. int recv_len, i;
  68. char buf[128];
  69.  
  70. dst_ip = argv[1];
  71.  
  72. sockfd = Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  73.  
  74. Inet_pton(AF_INET, dst_ip, (void *)&dst_addr.sin_addr);
  75.  
  76. for (i = 0; ; i++) {
  77. icmp = get_icmp(i);
  78.  
  79. if (sendto(sockfd, icmp, 8+56,0, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in)) < 0)
  80. perror("sendto");
  81. if ((recv_len = recv(sockfd, &buf, sizeof(buf), 0)) < 0)
  82. perror("recv");
  83.  
  84. gettimeofday(&tval, NULL);
  85. proc_v4(buf, recv_len, &tval);
  86.  
  87. sleep(1);
  88. }
  89.  
  90. return 0;
  91. }
  92.  
  93. void tv_sub(struct timeval *out, struct timeval *in)
  94. {
  95. if ( (out->tv_usec -= in->tv_usec) < 0 ) { /* out -= in */
  96. --out->tv_sec;
  97. out->tv_usec += 1000000;
  98. }
  99. out->tv_sec -= in->tv_sec;
  100. }
  101.  
  102. void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
  103. {
  104. int hlen1, icmplen;
  105. double rtt;
  106. struct ip *ip;
  107. struct icmp *icmp;
  108. struct timeval *tvsend;
  109.  
  110. ip = (struct ip *) ptr; /* start of IP header */
  111. hlen1 = ip->ip_hl << 2; /* length of IP header */
  112. if (ip->ip_p != IPPROTO_ICMP)
  113. return; /* not ICMP */
  114.  
  115. icmp = (struct icmp *) (ptr + hlen1); /* start of ICMP header */
  116. if ( (icmplen = len - hlen1) < 8 )
  117. return; /* malformed packet */
  118.  
  119. if (icmp->icmp_type == ICMP_ECHOREPLY) {
  120.  
  121. if (icmp->icmp_id != getpid()) {
  122. printf("icmp id error : %d != %d \n", icmp->icmp_id, getpid());
  123. return; /* not a response to our ECHO_REQUEST */
  124.  
  125. }
  126.  
  127. if (icmplen < 16)
  128. return; /* not enough data to use */
  129.  
  130. tvsend = (struct timeval *) icmp->icmp_data;
  131. tv_sub(tvrecv, tvsend);
  132. rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
  133.  
  134. printf("from %s ", inet_ntoa(ip->ip_src));
  135. printf("%d bytes, seq=%u, ttl=%d, rtt=%.3f ms\n", icmplen, icmp->icmp_seq, ip->ip_ttl, rtt);
  136. }
  137. }

5.2 使用多进程,和多线程测试

发现只要一个套接字发包,所有套接字都会收到回复,所以在读包时应该将所有包读出。

  1. while ((recv_len = recv(sockfd, &buf, sizeof(buf), MSG_DONTWAIT)) >= 0) {
  2. gettimeofday(&tval, NULL);
  3. proc_v4(buf, recv_len, &tval);
  4. }

5.3 ping程序的recv必须即使处理,才知道接受包时的准确时间。所以应该用多路转接IO,或者专门用个线程来阻塞接受。

UNP——原始套接字的更多相关文章

  1. 原始套接字-自定义IP首部和TCP首部

    /* ===================================================================================== * * Filenam ...

  2. 浅谈原始套接字 SOCK_RAW 的内幕及其应用(port scan, packet sniffer, syn flood, icmp flood)

    一.SOCK_RAW 内幕 首先在讲SOCK_RAW 之前,先来看创建socket 的函数: int socket(int domain, int type, int protocol); domai ...

  3. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

  4. 005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)

    一.目的: 自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去. 若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的! 二.数据结构: TCP首部结构图: s ...

  5. 004.UDP--拼接UDP数据包,构造ip头和udp头通信(使用原始套接字)

    一.大致流程: 建立一个client端,一个server端,自己构建IP头和UDP头,写入数据(hello,world!)后通过原始套接字(SOCK_RAW)将包发出去. server端收到数据后,打 ...

  6. 002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)

    一.大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 二.数据结构: ICM ...

  7. linux原始套接字(4)-构造IP_UDP

    一.概述                                                    同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...

  8. linux原始套接字(3)-构造IP_TCP发送与接收

    一.概述                                                    tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...

  9. linux原始套接字(2)-icmp请求与接收

    一.概述                                                    上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请 ...

随机推荐

  1. 多测师_讲解python__004 函数

    # 函数:一个工具,随调随用# 降级代码冗余## 增加代码的复用性,提高开发效率,为了不成为cv战士## 提高程序扩展性## 函数有两个阶段:定义阶段,调用阶段.## 定义时:只检查函数体内代码语法, ...

  2. 云服务器、euleros系统自动断开连接解决方案

    我这里的云服务器,网上查的修改sshd.config文件并不有效 我提供另一种方法解决这个问题: vim /etc/profile 再最底部新增 export TMOUT=6000 #6000代表60 ...

  3. MeteoInfoLab脚本示例:格点数据散点图

    绘制格点数据的散点图,用scaterm函数. 脚本程序: f = addfile('D:/Temp/GrADS/model.ctl') ps = f['PS'][0,(10,60),(60,140)] ...

  4. CentOS 7操作系统目录结构介绍

    CentOS 7操作系统目录结构介绍 操作系统存在着大量的数据文件信息,相应文件信息会存在于系统相应目录中,为了更好的管理数据信息,会将系统进行一些目录规划,不同目录存放不同的资源. 根下目录结构说明 ...

  5. 经验分享:对于刚接触开发的大学生,怎么在Windows查看与关闭端口占用方法?

      前言:做开发有的时候会发现某一端口被占用了而导致不能正常启动服务,这个时候就需要把这个端口释放掉了,步骤如下 观察报错信息提示,了解是哪个端口号被占用,以8700为例 按win+r输入cmd打开控 ...

  6. 纯CSS+HTML自定义checkbox效果[转]

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  7. 数据库备份作业的T-SQL语句

    1.关于大容量数据导入导出的一些方法SQL SERVER提供多种工具用于各种数据源的数据导入导出,这些数据源包括本文文件.ODBC数据源.OLE DB数据源.ASCII文本文件和EXCEL电子表格.2 ...

  8. 使用ModelForm校验数据唯一性

    在设计模型类的时候,将指定字段设置unique=true属性,可以保证该字段在数据库中的唯一性. 使用ModelForm可以将指定模型类快速生成表单元素.在提交数据后,使用is_valid()校验时, ...

  9. vue响应式原理整理

    vue是数据响应性,这是很酷的一个地方.本文只为理清逻辑.详细请看官方文档 https://cn.vuejs.org/v2/guide/reactivity.html vue的data在处理数据时候, ...

  10. 跟我一起学.NetCore之MediatR好像有点火

    前言 随着微服务的流行,而DDD(领域驱动设计)也光速般兴起,CRQS(Command Query Responsibility Seperation--命令查询职责分离).领域事件名词是不是经常在耳 ...