热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

inetsocket与packetsocket

调试过网络程序的人大多使用过tcpdump,那你知道tcpdump是如何工作的吗?tcpdump这类工具也被称为Sniffer,它可以在不影响应用程序正常报文的情况下,将流经网卡的

《inet socket 与 packet socket》

调试过网络程序的人大多使用过tcpdump,那你知道tcpdump是如何工作的吗?
tcpdump这类工具也被称为Sniffer,它可以在不影响应用程序正常报文的情况下,将流经网卡的报文复制一份给Sniffer,然后经过加工过滤,最后呈现给用户。

本文不分析tcpdump的具体实现,而只是借tcpdump来揭示一些网络编程中一个大多数人都容易忽略的一个主题:Socket参数对用户接收报文的影响…

相信所有接触过Socket编程的人都应该认识下面这个API

#include
sockfd = socket(int socket_family, int socket_type, int protocol);

没错,它基本是socket编程的第一步,创建一个套接字。他有三个参数,不过又有多少人真的去了解这些参数的意义呢? 对于TCP或者UDP应用的开发者来说,他们可以很容易地从互联网上找(抄)到这样的例子:

/* 创建TCP socket*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* 创建UDP socket*/
sockfd = socket(AF_INET, SOCK_DGRAM, 0)

为什么第一个参数要使用AF_INET,为什么第二个参数要使用SOCK_STREAM或者SOCK_DGRAM,为什么第三个参数要填0 ?

socket_family

第一个参数表示创建的socket所属的地址簇或者协议簇,取值以AF或者PF开头定义在(include\linux\socket.h),实际使用中并没有区别(有两个不同的名字只是因为是历史上的设计原因)。最常用的取值有AF_INET,AF_PACKET,AF_UNIX等。AF_UNIX用于主机内部进程间通信,本文暂且不谈。AF_INETAF_PACKET的区别在于使用前者只能看到IP层以上的东西,而后者可以看到链路层的信息

什么意思呢? 为了说明这个问题,我们需要知道网络报文的分类。如下图所示:Ethernet II帧是应用最为广泛的帧类型(当然也有像PPP这样的其他链路帧类型)。Ethernet II帧内部,又可大致分为IP报文和其他报文。我们熟悉的TCP或者UDP报文都属于IP报文。

《inet socket 与 packet socket》

AF_INET是与IP报文对应的,而AF_PACKET则是与Ethernet II报文对应的。AF_INET创建的套接字称为inet socket,而AF_PACKET创建的套接字称为packet socket

《inet socket 与 packet socket》

socket_type & protocol

第一个参数family会影响第二个参数socket_type和第三个参数protocol取值范围

第二个参数socket_type表示套接字类型。它的取值不多,常见的就以下三种

enum sock_type {
SOCK_STREAM = 1, /* stream (connection) socket */
SOCK_DGRAM = 2, /* datagram (conn.less) socket */
SOCK_RAW = 3, /* raw socket */
};

第三个参数protocol表示套接字上报文的协议。

对于AF_INET地址簇,protocol的取值范围是如 IPPROTO_TCP IPPROTO_UDP IPPROTO_ICMP 这样的IP报文协议类型,或者IPPROTO_IP = 0 这个特殊值
对于AF_PACKET地址簇,protocol的取值范围是 ETH_P_IP ETH_P_ARP这样的以太帧协议类型。

inet socket的协议开关表

每一个inet socket只能收发一种IP协议类型的报文,这是在套接字创建的时候就决定的(protocol参数),比如TCP套接字是不能收发UDP报文的,反之也是一样。并且,protocol的值还受到socket_type的限制,不匹配的取值会导致套接字创建操作会返回失败。

/* 错误取值,返回失败 */
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP);

内核通过协议开关表记录了哪些哪些取值是有效的,inet在初始化时会将支持的协议注册在协议开关表中的以socket_typeKEY的链表上:

《inet socket 与 packet socket》

而在创建套接字时,inet_create会在协议开关表中根据socket_typeprotocol进行匹配

list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}

IPPROTO_IP的值为0, 在用户使用0作为创建套接字的第三个参数时,会匹配到该链表上的第一个协议,这正是创建TCP或者UDP套接字时,第三个参数可以为0的原因, 0表示由内核自动选择。··

/* 创建TCP socket*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
/* 创建UDP socket*/
sockfd = socket(AF_INET, SOCK_DGRAM, 0)

raw inet socket

对于inet socket来说,一个TCP报文可以这样分解:

packet = IP Header + TCP Header + Payload

如果我们是使用SOCK_STREAM创建的TCP套接字,应用程序在通过send发送数据时,只需要提供Payload就行了,而IP HeaderTCP Header则由内核组装完成。接收方向,应用程序通过recv也只能收到payload

RAW套接字则为应用提供了更底层的控制能力

int s = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);

使用上面的接口可以创建一个更原始的TCP套接字,当我们使用这个套接字发送数据时,需要提供PayloadTCP Header,而IP Header依然由内核协议栈自动组装。

如果希望手动组装IP Header,有两个方法:

第一种是protocol使用IPPROTO_RAW

int s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);

第二种是置位IP_HDRINCL的套接字选项。

int s = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);
int One= 1;
const int *val = &one;
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) <0)
{
printf ("Error setting IP_HDRINCL. Error number : %d . Error message : %s \n" , errno , strerror(errno));
exit(0);
}

以上两种方法都是告诉内核,IP Header也由应用程序自己提供。

packet socket

inet socket的控制范围是IP报文,而packet socket的控制范围扩大到了以太层报文

对于inet socket, 第二个参数socket_type只能选择SOCK_DGRAMSOCK_RAW或者SOCK_PACKET, protocol则表示支持的网络层的协议类型。

Protocol Handler

对以太帧来说,不同的网络层协议类型(比如IP ARP PPPoE)有不同的接收处理函数。在内核中,这就是Protocol Handler

内核中的Protocl Handler是这样组织的

《inet socket 与 packet socket》

该patch将Protocl Handlerdev下增加了ptype_all链表和ptype_base链表

无论网卡是否采用NAPI,内核最终都会调用到__netfi_receive_skb接收报文,这个函数会遍历ptype_all链表上已注册的handler,然后再遍历ptype_base特定协议链上的所有已注册的handler

handler的注册是通过dev_add_pack完成的,如果没有指定协议(ETH_P_ALL),该handler就会注册在ptype_all上(tcpdump默认就会注册在这里),否则根据协议注册在ptype_base的某条链表上。

在报文接收过程中,同一个skb会被deliver_skb到多个handler(至少将ptype_all链表上的handler走一遍)。

内核启动时,inet会注册一个handler,它支持IP协议,所有AF_INET套接字实际上是共用这样一个handler,对应的接收函数是ip_rcv,区分是哪一个套接字的报文是之后的工作。

/* net/ipv4/af_inet.c */
static struct packet_type ip_packet_type __read_mostly = {
.type = cpu_to_be16(ETH_P_IP),
.func = ip_rcv,
};
static int __init inet_init(void)
{
// code omitted
dev_add_pack(&ip_packet_type);
// code omitted
}

而对于AF_PACKEThandler是在packet_create中单独注册的,也就是说,每个AF_PACKET套接字拥有独立的handler

static int packet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
// code omitted
po->prot_hook.func = packet_rcv;
// code omitted
register_prot_hook(sk); // 这里面去 dev_add_pack
}

单独的handler,使得在接收函数packet_rcv的时候,就已经可以知道这是属于哪一个套接字的数据了。

raw packet socket

对于AF_PACKET来说,一个报文可以这样分解:

packet = Ethernet Header + Payload

SOCK_DGRAMSOCK_RAW的区别就在于,在接收方向,使用SOCK_DGRAM套接字的应用程序收到的报文已经去除了Ethernet Header,而SOCK_RAW套接字则会保留。

packet socket 与 tcpdump

回到本文最初的问题,tcpdump是如何完成嗅探工作的呢? 没错!它正是使用的packet socket

  • tcpdump作为sniffer,它不能影响正常的报文收发,因此它需要单独的protocol handler,这样内核接收的报文会复制一份后,交给tcpdump
  • tcpdump不止能抓取IP报文, 它还可以抓起链路层信息或者其他一些非IP报文。

REF

difference-between-pf-inet-sockets-and-pf-packet
data-link-access-and-zero-copy
raw-socket-in-linux
raw-sockets-c-code-linux


推荐阅读
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 一、Hadoop来历Hadoop的思想来源于Google在做搜索引擎的时候出现一个很大的问题就是这么多网页我如何才能以最快的速度来搜索到,由于这个问题Google发明 ... [详细]
  • Python实现变声器功能(萝莉音御姐音)的方法及步骤
    本文介绍了使用Python实现变声器功能(萝莉音御姐音)的方法及步骤。首先登录百度AL开发平台,选择语音合成,创建应用并填写应用信息,获取Appid、API Key和Secret Key。然后安装pythonsdk,可以通过pip install baidu-aip或python setup.py install进行安装。最后,书写代码实现变声器功能,使用AipSpeech库进行语音合成,可以设置音量等参数。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 使用圣杯布局模式实现网站首页的内容布局
    本文介绍了使用圣杯布局模式实现网站首页的内容布局的方法,包括HTML部分代码和实例。同时还提供了公司新闻、最新产品、关于我们、联系我们等页面的布局示例。商品展示区包括了车里子和农家生态土鸡蛋等产品的价格信息。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • 本文整理了315道Python基础题目及答案,帮助读者检验学习成果。文章介绍了学习Python的途径、Python与其他编程语言的对比、解释型和编译型编程语言的简述、Python解释器的种类和特点、位和字节的关系、以及至少5个PEP8规范。对于想要检验自己学习成果的读者,这些题目将是一个不错的选择。请注意,答案在视频中,本文不提供答案。 ... [详细]
  • .NetCoreWebApi生成Swagger接口文档的使用方法
    本文介绍了使用.NetCoreWebApi生成Swagger接口文档的方法,并详细说明了Swagger的定义和功能。通过使用Swagger,可以实现接口和服务的可视化,方便测试人员进行接口测试。同时,还提供了Github链接和具体的步骤,包括创建WebApi工程、引入swagger的包、配置XML文档文件和跨域处理。通过本文,读者可以了解到如何使用Swagger生成接口文档,并加深对Swagger的理解。 ... [详细]
  • 本文介绍了5个基本Linux命令行工具的现代化替代品,包括du、top和ncdu。这些替代品在功能上进行了改进,提高了可用性,并且适用于现代化系统。其中,ncdu是du的替代品,它提供了与du类似的结果,但在一个基于curses的交互式界面中,重点关注占用磁盘空间较多的目录。 ... [详细]
author-avatar
跌蕩起伏的2012_900
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有