实际上,我们常用的网络编程都是在应用层的报文的收发操作,也就是大多数程序员接触到的流式套接字(SOCK_STREAM)和数据包式套接字(SOCK_DGRAM)。而这些数据包都是由系统提供的协议栈实现,用户只需要填充应用层报文即可,由系统完成底层报文头的填充并发送。然而在某些情况下需要执行更底层的操作,比如修改报文头、避开系统协议栈等。这个时候就需要使用其他的方式来实现。

一、 原始套接字

原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM、SOCK_DGRAM的套接字,它实现于系统核心。然而,原始套接字能做什么呢?首先来说,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。总体来说,SOCK_RAW可以处理普通的网络报文之外,还可以处理一些特殊协议报文以及操作IP层及其以上的数据。

原始套接字可以用来自行组装IP数据包,然后将数据包发送到其他终端。必须在管理员权限下才能使用原始套接字,这么做可防止普通用户往网络写出它们自行构造的IP数据报。

(1)原始套接字的创建:

int sockfd = socket (AF_INET, SOCK_RAW, protocol);

把第二个参数设置为SOCK_RAW,第三个参数(协议)通常不为0。其中protocol参数时形如IPPROTO_xxx的某个常值,定义在<netinet/in.h>头文件中,如IPPROTO_ICMP、IPPROTO_UDP、IPPROTO_TCP等。

可以在这个套接字上按以下方式开启IP_HDRINCL套接字选项(随数据包含的IP首部):

const int on =1;
if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
错误处理;

可以在这个套接字上调用bind函数,但是比较少见。bind函数仅仅设置本地地址,因为原始套接字不存在端口的概念。就输出而言,调用bind设置的是将用于从这个原始套接字发送的所有数据报源IP地址(只在IP_HDRINCL套接字选项未开启的前提下)。如果不调用bind,内核就把源IP地址设置为外出接口的主IP地址。

可以在这个原始套接字上调用connect函数,不过比较少见。connect函数仅仅设置外地地址,同样因为原始套接字不存在端口号的概念。就输出而言,调用connect之后我们可以把sendto调用改为write或send调用,因为目的地址已经指定了。

(2)原始套接字的输出

原始套接字的输出遵循以下规则:

  • 普通输出通过调用sendto或sendmsg并指定目的IP地址完成。如果套接字已经连接,那么也可以调用write、writev或send。
  • 如果IP_HDRINCL套接字选项未开启,那么由进程让内核发送的数据的起始位置指的是IP首部之后的第一个字节,因为内核将构造IP首部并把它置于来自进程的数据之前。内核把所有构造IPv4首部的协议字段设置成来之sock调用的第三个参数。
  • 如果IP_HDRINCL套接字选项已开启,那么由进程让内核发送的数据的起始位置指的是IP首部的第一个字节。进程调用输出函数写出的数据量必须包括IP首部的大小。整个IP首部都是由进程构造,不过(a)IP的标识字段可以设置为0,从而告知内核设置该值,(b)IPv4首部校验和字段总是由内核计算并存储,(c)IPv4的选项字段是可选的。
  • 内核会对超出外出接口MTU的原始分组进行分片。

(3)原始套接字的输入

首先要考虑内核将哪些接收到的IP数据报传递到原始套接字?这要遵循下面的规则:

  • 接收到的UDP或者TCP分组绝不传递到任何原始套接字,如果一个进程想要读取含有UDP分组或TCP分组的IP数据报,它就必须在数据链路层读取这些分组。
  • 大多数ICMP分组在内核处理完其中的ICMP消息后传递到原始套接字。
  • 所有IGMP分组在内核完成处理其中的IGMP消息后传递到原始套接字。
  • 内核把不认识其协议字段的所有IP数据报传递给原始套接字。内核对这些分组执行的唯一处理是针对某些IP首部字段的最小验证:IP版本、IPv4校验和、首部长度以及目的地址。
  • 如果某个数据报以片段形式到达,那么在它的所有片段均到达且重组出该数据报之前,不传递任何分组到原始套接字。

当内核有一个需要传递到原始套接字的IP数据报时,它将检查所有进程上的所有原始套接字,以寻找所有匹配的套接字。每个匹配的套接字将被传递送以该IP数据报的一个副本。内核对每个原始套接字均执行以下3个测试,只有这三个测试均为真,内核才把接收到的数据报发送给这个套接字。

  • 如果创建这个原始套接字时指定了非0的协议参数(socket的第三个参数),那么接收到的数据报协议字段必须匹配该值,否则该数据报不递送到这个套接字。
  • 如果这个套接字已由bind调用绑定了某个本地IP地址,那么接收到的数据报的目的IP地址必须匹配这个绑定地址,否则该数据报不递送到这个套接字。
  • 如果这个套接字已由connect调用指定了某个外地IP地址,那么接收到的数据报的源IP地址必须匹配这个已连接地址,否则该数据报不递送到这个套接字。

注意,如果一个原始套接字是以0值协议参数传递的,并且没有调用bind,也未对它调用connect,那么该套接字将接收可由内核传递到原始套接字的每个原始数据报的一个副本。

无论何时往一个原始IPv4套接字上递送一个接收到的数据报,传递到该套接字所在进程的都是包括IP首部在内的完整数据报,然后对于IPv6原始套接字,传递套接字的只是扣除了IPv6首部和所有扩展首部的净荷(payload)。

二 、链路层处理报文

如果需要从链路层处理报文,那么就需要采用更加底层的套接字。还是先看下套接字函数的原型:

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

这个函数中,domain表示协议簇,type表示套接字类型,而protocol表示的是处理的协议类型。在Linux下提供了多种底层套接字。下面分别进行简单介绍。

1 PF_INET协议簇

通过PF_INET可以构造原始套接字,如下所示:

int fd = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);

正如前面所讲的,它工作在IP层及其以上各层协议上(当然是在使用IP_HDRINCL选项之后才能操作IP层数据啦),但是这种套接字无法接收从本地发送出去的报文。而使用SOCK_PACKET类型的套接字,则可以操作链路层数据了:

int fd = socket (PF_INET, SOCK_PACKET, IPPROTO_TCP);

不过据说这种方式存在一定的缺陷,而且也不能保证后续版本的系统上一定支持这种方式,因此不推荐使用

2 PF_PACKET协议簇

PF_PACKET协议簇是用来取代SOCK_PACKET的一种编程接口。作为一种协议簇,它可以对应两种不同的套接字类型:SOCK_RAW和SOCK_DGRAM。当使用SOCK_RAW时,用户操作链路层数据,但是如果使用后者,则由系统处理链路层协议头。这种套接字支持四种协议(ETH_P_IP、ETH_P_ARP、ETH_P_RARP、ETH_P_ALL)(未确认)

int fd = socket (PF_PACKET, SOCK_RAW, IPPROTO_TCP);

int fd = socket (PF_PACKET, SOCK_DGRAM, IPPROTO_TCP);

3 NETLINK协议簇

这种方式是用户模式和kernel的IP网络配置之间的推荐接口。

综上所述,真正能够实现操作链路层数据的只有三种方式:

int fd = socket (PF_INET, SOCK_PACKET, IPPROTO_TCP);

int fd = socket (PF_PACKET, SOCK_RAW, IPPROTO_TCP);

int fd = socket (PF_PACKET, SOCK_DGRAM, IPPROTO_TCP);

UNIX网络编程——原始套接字SOCK_RAW的更多相关文章

  1. UNIX网络编程——原始套接字(dos攻击)

    原始套接字(SOCK_RAW).应用原始套接字,我们可以编写出由TCP和UDP套接字不能够实现的功能. 注意原始套接字只能够由有 root权限的人创建. 可以参考前面的博客<<UNIX网络 ...

  2. UNIX网络编程——原始套接字的魔力【续】

    如何从链路层直接发送数据帧 上一篇里面提到的是从链路层"收发"数据,该篇是从链路层发送数据帧. 上一节我们主要研究了如何从链路层直接接收数据帧,可以通过bind函数来将原始套接字绑 ...

  3. UNIX网络编程——原始套接字的魔力【下】

    可以接收链路层MAC帧的原始套接字 前面我们介绍过了通过原始套接字socket(AF_INET, SOCK_RAW, protocol)我们可以直接实现自行构造整个IP报文,然后对其收发.提醒一点,在 ...

  4. UNIX网络编程——原始套接字的魔力【上】

    基于原始套接字编程 在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发层面,数据的传输特性由TCP或UDP来保证: 也就是说,对于TCP或UDP的程序开发,焦点在Dat ...

  5. Linux网络编程——原始套接字实例:MAC 头部报文分析

    通过<Linux网络编程——原始套接字编程>得知,我们可以通过原始套接字以及 recvfrom( ) 可以获取链路层的数据包,那我们接收的链路层数据包到底长什么样的呢? 链路层封包格式 M ...

  6. LINUX 网络编程 原始套接字

    一 原始套接字 原始套接字(SOCK_RAW)是一种不同于SOCK_STREAM.SOCK_DGRAM的套接字,它实现于系统核心.然而,原始套接字能做什么呢?首先来说,普通的套接字无法处理ICMP.I ...

  7. unix网络编程——TCP套接字编程

    TCP客户端和服务端所需的基本套接字.服务器先启动,之后的某个时刻客户端启动并试图连接到服务器.之后客户端向服务器发送请求,服务器处理请求,并给客户端一个响应.该过程一直持续下去,直到客户端关闭,给服 ...

  8. Linux网络编程——原始套接字编程

    原始套接字编程和之前的 UDP 编程差不多,无非就是创建一个套接字后,通过这个套接字接收数据或者发送数据.区别在于,原始套接字可以自行组装数据包(伪装本地 IP,本地 MAC),可以接收本机网卡上所有 ...

  9. Linux网络编程——原始套接字能干什么?

    通常情况下程序员接所接触到的套接字(Socket)为两类: (1)流式套接字(SOCK_STREAM):一种面向连接的 Socket,针对于面向连接的TCP 服务应用: (2)数据报式套接字(SOCK ...

随机推荐

  1. [HDU]4694 Important Sisters(支配树)

    支配树模板 #include<cstdio> #include<cstring> #include<algorithm> using namespace std; ...

  2. [UOJ UR #2]树上GCD

    来自FallDream的博客,未经允许,请勿转载,谢谢. 传送门 看完题目,一般人都能想到 容斥稳了 .这样我们只要统计有多少点对满足gcd是i的倍数. 考虑长链剖分,每次合并的时候,假设我已经求出轻 ...

  3. [51nod1239欧拉函数之和]

    来自FallDream的博客,未经允许,请勿转载,谢谢 --------------------------------------------- 给定n,求$S(n)=\sum_{i=1}^{n}\ ...

  4. ubuntu Linux下C语言open函数打开或创建文件与read,write函数详细讲解

    open(打开文件) 相关函数 read,write,fcntl,close,link,stat,umask,unlink,fopen 表头文件 #include<sys/types.h> ...

  5. Linux下的crontab定时、执行任务命令详解 oracle 自动备份

    在LINUX中,周期执行的任务一般由cron这个守护进程来处理[ps -ef|grep cron].cron读取一个或多个配置文件,这些配置文件中包含了命令行及其调用时间.cron的配置文件称为&qu ...

  6. swift之属性

    知识点总结: 1.存储属性 struct Town{ let region = "South" //只读属性 var population = //读写属性 } 2.惰性存储属性 ...

  7. Mysql数据库连接报错!1130:host XXX is not allowed to connect to this mysql server

    我猜想是可能是连接的用户权限问题.结果这样子操作mysql库,可以解决此问题.在本机登入mysql后,更改 “mysql” 数据库里的 “user” 表里的 “host” 项,从”localhost” ...

  8. EF Core 2.0使用MsSql/Mysql实现DB First和Code First

    参考地址 EF官网 ASP.NET Core MVC 和 EF Core - 教程系列 环境 Visual Studio 2017 最新版本的.NET Core 2.0 SDK 最新版本的 Windo ...

  9. sourceTree+gerrit管理代码

    第一次接触gerrit,会对这种代码管理方式非常排斥,尤其是习惯了用sourceTree配合git进行代码管理的同学.不爽归不爽,代码还得写,我们的目标是让开发过程爽起来. 关于gerrit的知识,移 ...

  10. 【if...else】三角形判断

    给定三条边的长度,判断能否组成三角形,如果可以,判断三角形的形状. 输入要求 一组数据,每行三个实数,在(0,10]之间 输出要求 根据每行的数据判断,如果不能组成三角形,则输出"Not a ...