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——原始套接字的更多相关文章

  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. 关于.netMVC 出现@ViewBag 出现错误(波浪红线)的解决方法

    解决vs2015.vs2013解决mvc5 viewbag问题 1.关闭vs2015或者vs2013 打开我的电脑或者文件夹 2.打开我的电脑 在地址栏输入 %UserProfile%\AppData ...

  2. PHP之Trait详解 转

    php从以前到现在一直都是单继承的语言,无法同时从两个基类中继承属性和方法,为了解决这个问题,php出了Trait这个特性 用法:通过在类中使用use 关键字,声明要组合的Trait名称,具体的Tra ...

  3. 第十二章 Linux三剑客之老三—grep

    一.Linux grep 命令用于查找文件里符合条件的字符串. Linux系统中的grep命令是一种功能强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.grep全称是Global ...

  4. 浏览器页面左上角出现undefined

    浏览器页面左上角出现undefined, js文档中: let list; list += html代码; 解决办法: let list = html代码;

  5. 【Azure云服务 Cloud Service】Cloud Service的实例(VM)中的服务描述Software Protection 与 Windows Defender, 如何设置Windows Defender Antivirus服务

    1)Software Protection 与 Windows Defender是两个独立的服务.在Windows 服务中他们的描述分别为 Software Protection Enables th ...

  6. Helium文档8-WebUI自动化-wait_until等待元素出现

    前言 wait_until等待某个条件为真才继续往下执行.默认的超时时间为10s,每0.5查询一次,这俩参数选填.可以设置超时时间和轮询间隔. 可以作为添加后校验元素是否存在的场景 入参介绍 def ...

  7. 智能DNS的实现

    网络路径远,导致用户访问延迟 各个运营商之间的带宽有阀口. GSLB 就近的返回服务器的地址 CDN网络 内容分发网络 Content Delivery Network CND服务商 阿里 腾讯 蓝汛 ...

  8. git学习(十) idea git reset 操作

    git reset 是回滚操作,在 idea 中使用如下: Reset Type 有三种: Mixed 默认方式,只保留源码,回退 commit 和 index 信息 Soft 回退到某个版本,只回退 ...

  9. Qlik Sense学习笔记之插件开发

    date: 2019-05-06 13:18:45 updated: 2019-08-09 15:18:45 Qlik Sense学习笔记之插件开发 1.开发前的基础工作 1.1 新建插件 dev-h ...

  10. vue组件使用name属性来生成递归组件

    先来个简单的数据 1 lists = [{ 2 id: 1, 3 title: '第一层', 4 children: [{ 5 id: 3, 6 title: '第二层', 7 children: [ ...