linux socket参数详解,那些年,我们忽略的socket参数

5b5dd80b4eaeee191b73f10c1452cd3b.png

调试过网络程序的人大多使用过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开头定义在(includelinuxsocket.h),实际使用中并没有区别(有两个不同的名字只是因为是历史上的设计原因)。最常用的取值有AF_INET,AF_PACKET,AF_UNIX等。AF_UNIX用于主机内部进程间通信,本文暂且不谈。AF_INET与AF_PACKET的区别在于使用前者只能看到IP层以上的东西,而后者可以看到链路层的信息。

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

d6eb2c35684d602dd9897191c43160de.png

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

9f88eb01f8897777c9cde27acbc8bfbc.png

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_type为KEY的链表上:

6083c0c50c5a9107d8bdbd81f47398c1.png

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

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 Header和TCP Header则由内核组装完成。接收方向,应用程序通过recv也只能收到payload

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

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

使用上面的接口可以创建一个更原始的TCP套接字,当我们使用这个套接字发送数据时,需要提供Payload和TCP 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_DGRAM、SOCK_RAW或者SOCK_PACKET, protocol则表示支持的网络层的协议类型。

Protocol Handler

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

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

8b0e7853b382ab2c71f8afe4fd94bc97.png

注该patch将Protocl Handler在dev下增加了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_PACKET,handler是在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_DGRAM和SOCK_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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值