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. 多测师讲解python _函数的传递_高级讲师肖sir

    题目:   要求1.通过函数来实现       2.引用函数传递方法        3.引用返回值   有一个登录系统:账号admin  密码123456 验证码abc123    账号.密码.验证码 ...

  2. Java工程师应该掌握的知识,按重要程度排出六个阶段如下

      第一阶段:计算机组成原理.数据结构和算法.网络通信原理.操作系统原理: 第二阶段:Java基础.JVM内存模型和GC算法.JVM性能调优.JDK工具.设计模式: 第三阶段:Spring系列.Myb ...

  3. 扫描仪扫描文件处理-imagemagick常用参数

    -resize 宽x高(缩放,不变形) -extent 宽x高(放大,不变形)之前设置:-gravity center(重心居中) -brightness-contrast 亮度x对比度(设置亮度对比 ...

  4. lumen-ioc容器测试 (5)

    lumen-ioc容器测试 (1) lumen-ioc容器测试 (2) lumen-ioc容器测试 (3) lumen-ioc容器测试 (4) lumen-ioc容器测试 (5) lumen-ioc容 ...

  5. PyTorch常用参数初始化方法详解

    1. 均匀分布 torch.nn.init.uniform_(tensor, a=0, b=1) 从均匀分布U(a, b)中采样,初始化张量. 参数: tensor - 需要填充的张量 a - 均匀分 ...

  6. JavaSE学习笔记01注释、标识符与基本类型

    1. HelloWorld 编写代码 public class Hello{ public static void main(String[] args){ System.out.println(&q ...

  7. 高精度算法求n阶阶乘

    1 #include "stdio.h" 2 #include "String.h" 3 #define MAX 10000 4 int f[MAX]; 5 v ...

  8. 关于隐私保护的英文论文的阅读—— How to read English thesis

    首先 开始我读论文时 也是恨不得吃透每个单词 但是后来转念一想 没必要每个单词都弄懂 因为 一些程度副词 修饰性的形容词等 这些只能增强语气罢了 对文章主题的理解并没有天大的帮助 而读文章应该首先把握 ...

  9. 第三章 MySQL的多实例

    一.MySQL服务构成 1.MySQL程序结构 1.连接层 2.sql层 3.存储引擎层 2.MySQL逻辑结构 1.库 2.表:元数据+真实数据行 3.元数据:列+其它属性(行数+占用空间大小+权限 ...

  10. Libevent库基础(2)

    带缓冲区的事件 bufferevent #include <event2/bufferevent.h> read/write 两个缓冲. 借助 队列. 创建.销毁bufferevent: ...