UNP——原始套接字
1.原始套接字的用处
使用原始套接字可以构造或读取网际层及其以上报文。
具体来说,可以构造 ICMP, IGMP 协议报文,通过开启 IP_HDRINCL 套接字选项,进而自定义 IPv4首部。
2. 创建原始套接字
2.1 使用 SOCK_RAW 创建原始套接字
- sockfd = socket(AF_INET, SOCK_RAW, protocol);
protocol 定义在 <netinet/in.h>,如 IPPROTO_xxx。
2.2 初始化原始套接字的常用步骤
2.2.1 IP_HDRINCL
- int on = 1;
- 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 没有。
- #include <stdlib.h>
- #include <stdio.h>
- #include <sys/time.h>
- #include <arpa/inet.h>
- #include "wrap_common.h"
- #include <string.h>
- #include <sys/time.h>
- #include <sys/types.h> /* See NOTES */
- #include <sys/socket.h>
- #include <netinet/ip_icmp.h>
- void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv);
- uint16_t in_cksum(uint16_t *addr, int len)
- {
- int nleft = len;
- uint32_t sum = 0;
- uint16_t *w = addr;
- uint16_t answer = 0;
- while (nleft > 1) {
- sum += *w++;
- nleft -= 2;
- }
- if (nleft == 1) {
- *(unsigned char *)(&answer) = *(unsigned char *)w ;
- sum += answer;
- }
- /* 4add back carry outs from top 16 bits to low 16 bits */
- sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
- sum += (sum >> 16); /* add carry */
- answer = ~sum; /* truncate to 16 bits */
- return(answer);
- }
- struct icmp* get_icmp(int seq)
- {
- static struct icmp icmp_buf;
- static int datalen = 56;
- int len;
- icmp_buf.icmp_type = ICMP_ECHO;
- icmp_buf.icmp_code = 0;
- icmp_buf.icmp_id = getpid();
- icmp_buf.icmp_seq = seq;
- memset(icmp_buf.icmp_data, 0xa5, datalen);
- gettimeofday((struct timeval *)icmp_buf.icmp_data, NULL);
- len = 8 + datalen;
- icmp_buf.icmp_cksum = 0;
- icmp_buf.icmp_cksum = in_cksum((unsigned short *)&icmp_buf, len);
- return &icmp_buf;
- }
- int main(int argc, char **argv)
- {
- int sockfd;
- const struct sockaddr_in dst_addr = {0}, addr = {0}, src_addr = {0};
- char *dst_ip;
- struct icmp *icmp;
- struct timeval tval;
- int recv_len, i;
- char buf[128];
- dst_ip = argv[1];
- sockfd = Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
- Inet_pton(AF_INET, dst_ip, (void *)&dst_addr.sin_addr);
- for (i = 0; ; i++) {
- icmp = get_icmp(i);
- if (sendto(sockfd, icmp, 8+56,0, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in)) < 0)
- perror("sendto");
- if ((recv_len = recv(sockfd, &buf, sizeof(buf), 0)) < 0)
- perror("recv");
- gettimeofday(&tval, NULL);
- proc_v4(buf, recv_len, &tval);
- sleep(1);
- }
- return 0;
- }
- void tv_sub(struct timeval *out, struct timeval *in)
- {
- if ( (out->tv_usec -= in->tv_usec) < 0 ) { /* out -= in */
- --out->tv_sec;
- out->tv_usec += 1000000;
- }
- out->tv_sec -= in->tv_sec;
- }
- void proc_v4(char *ptr, ssize_t len, struct timeval *tvrecv)
- {
- int hlen1, icmplen;
- double rtt;
- struct ip *ip;
- struct icmp *icmp;
- struct timeval *tvsend;
- ip = (struct ip *) ptr; /* start of IP header */
- hlen1 = ip->ip_hl << 2; /* length of IP header */
- if (ip->ip_p != IPPROTO_ICMP)
- return; /* not ICMP */
- icmp = (struct icmp *) (ptr + hlen1); /* start of ICMP header */
- if ( (icmplen = len - hlen1) < 8 )
- return; /* malformed packet */
- if (icmp->icmp_type == ICMP_ECHOREPLY) {
- if (icmp->icmp_id != getpid()) {
- printf("icmp id error : %d != %d \n", icmp->icmp_id, getpid());
- return; /* not a response to our ECHO_REQUEST */
- }
- if (icmplen < 16)
- return; /* not enough data to use */
- tvsend = (struct timeval *) icmp->icmp_data;
- tv_sub(tvrecv, tvsend);
- rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
- printf("from %s ", inet_ntoa(ip->ip_src));
- printf("%d bytes, seq=%u, ttl=%d, rtt=%.3f ms\n", icmplen, icmp->icmp_seq, ip->ip_ttl, rtt);
- }
- }
5.2 使用多进程,和多线程测试
发现只要一个套接字发包,所有套接字都会收到回复,所以在读包时应该将所有包读出。
- while ((recv_len = recv(sockfd, &buf, sizeof(buf), MSG_DONTWAIT)) >= 0) {
- gettimeofday(&tval, NULL);
- proc_v4(buf, recv_len, &tval);
- }
5.3 ping程序的recv必须即使处理,才知道接受包时的准确时间。所以应该用多路转接IO,或者专门用个线程来阻塞接受。
UNP——原始套接字的更多相关文章
- 原始套接字-自定义IP首部和TCP首部
/* ===================================================================================== * * Filenam ...
- 浅谈原始套接字 SOCK_RAW 的内幕及其应用(port scan, packet sniffer, syn flood, icmp flood)
一.SOCK_RAW 内幕 首先在讲SOCK_RAW 之前,先来看创建socket 的函数: int socket(int domain, int type, int protocol); domai ...
- Linux Socket 原始套接字编程
对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...
- 005.TCP--拼接TCP头部IP头部,实现TCP三次握手的第一步(Linux,原始套接字)
一.目的: 自己拼接IP头,TCP头,计算效验和,将生成的报文用原始套接字发送出去. 若使用tcpdump能监听有对方服务器的包回应,则证明TCP报文是正确的! 二.数据结构: TCP首部结构图: s ...
- 004.UDP--拼接UDP数据包,构造ip头和udp头通信(使用原始套接字)
一.大致流程: 建立一个client端,一个server端,自己构建IP头和UDP头,写入数据(hello,world!)后通过原始套接字(SOCK_RAW)将包发出去. server端收到数据后,打 ...
- 002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)
一.大致流程: 将ICMP头和时间数据设置好后,通过创建好的原始套接字socket发出去.目的主机计算效验和后会将数据原样返回,用当前时间和返回的数据结算时间差,计算出rtt. 二.数据结构: ICM ...
- linux原始套接字(4)-构造IP_UDP
一.概述 同上一篇tcp一样,udp也是封装在ip报文里面.创建UDP的原始套接字如下: (soc ...
- linux原始套接字(3)-构造IP_TCP发送与接收
一.概述 tcp报文封装在ip报文中,创建tcp的原始套接字如下: sockfd = socket ...
- linux原始套接字(2)-icmp请求与接收
一.概述 上一篇arp请求使用的是链路层的原始套接字.icmp封装在ip数据报里面,所以icmp请 ...
随机推荐
- 多测师_讲解python__004 函数
# 函数:一个工具,随调随用# 降级代码冗余## 增加代码的复用性,提高开发效率,为了不成为cv战士## 提高程序扩展性## 函数有两个阶段:定义阶段,调用阶段.## 定义时:只检查函数体内代码语法, ...
- 云服务器、euleros系统自动断开连接解决方案
我这里的云服务器,网上查的修改sshd.config文件并不有效 我提供另一种方法解决这个问题: vim /etc/profile 再最底部新增 export TMOUT=6000 #6000代表60 ...
- MeteoInfoLab脚本示例:格点数据散点图
绘制格点数据的散点图,用scaterm函数. 脚本程序: f = addfile('D:/Temp/GrADS/model.ctl') ps = f['PS'][0,(10,60),(60,140)] ...
- CentOS 7操作系统目录结构介绍
CentOS 7操作系统目录结构介绍 操作系统存在着大量的数据文件信息,相应文件信息会存在于系统相应目录中,为了更好的管理数据信息,会将系统进行一些目录规划,不同目录存放不同的资源. 根下目录结构说明 ...
- 经验分享:对于刚接触开发的大学生,怎么在Windows查看与关闭端口占用方法?
前言:做开发有的时候会发现某一端口被占用了而导致不能正常启动服务,这个时候就需要把这个端口释放掉了,步骤如下 观察报错信息提示,了解是哪个端口号被占用,以8700为例 按win+r输入cmd打开控 ...
- 纯CSS+HTML自定义checkbox效果[转]
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...
- 数据库备份作业的T-SQL语句
1.关于大容量数据导入导出的一些方法SQL SERVER提供多种工具用于各种数据源的数据导入导出,这些数据源包括本文文件.ODBC数据源.OLE DB数据源.ASCII文本文件和EXCEL电子表格.2 ...
- 使用ModelForm校验数据唯一性
在设计模型类的时候,将指定字段设置unique=true属性,可以保证该字段在数据库中的唯一性. 使用ModelForm可以将指定模型类快速生成表单元素.在提交数据后,使用is_valid()校验时, ...
- vue响应式原理整理
vue是数据响应性,这是很酷的一个地方.本文只为理清逻辑.详细请看官方文档 https://cn.vuejs.org/v2/guide/reactivity.html vue的data在处理数据时候, ...
- 跟我一起学.NetCore之MediatR好像有点火
前言 随着微服务的流行,而DDD(领域驱动设计)也光速般兴起,CRQS(Command Query Responsibility Seperation--命令查询职责分离).领域事件名词是不是经常在耳 ...