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

linux网络子系统分析(二)——协议栈分层框架的建立

目录一、综述二、INET的初始化2.1INET接口注册2.2抽象实体的建立2.3代码细节分析2.3.1socket参数三、其他协议3.1PF_PACKET3.2P

目录

一、综述

二、INET的初始化

2.1 INET接口注册

2.2 抽象实体的建立

2.3 代码细节分析

2.3.1 socket参数

三、其他协议

3.1 PF_PACKET

3.2 PF_NETLINK

3.3 PF_UNIX

四、参考




一、综述

在上一篇中,主要分析了linux网络协议栈层次划分,列举了层与层之间的主要接口,本文结合实际代码和数据结构进行进一步说明这些层次是如何建立的,主要基于INET。


二、INET的初始化


2.1 INET接口注册

[net/ipv4/af_inet.c]

static int __init inet_init(void)
{ rc &#61; proto_register(&tcp_prot, 1);rc &#61; proto_register(&udp_prot, 1);rc &#61; proto_register(&raw_prot, 1);rc &#61; proto_register(&ping_prot, 1);(void)sock_register(&inet_family_ops);if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) <0)pr_crit("%s: Cannot add ICMP protocol\n", __func__);if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) <0)pr_crit("%s: Cannot add UDP protocol\n", __func__);if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) <0)pr_crit("%s: Cannot add TCP protocol\n", __func__);/* Register the socket-side information for inet_create. */for (r &#61; &inetsw[0]; r <&inetsw[SOCK_MAX]; &#43;&#43;r)INIT_LIST_HEAD(r);for (q &#61; inetsw_array; q <&inetsw_array[INETSW_ARRAY_LEN]; &#43;&#43;q)inet_register_protosw(q);dev_add_pack(&ip_packet_type);}

删除了无关的部分&#xff0c;可以看到在发送层面调用了proto_register/inet_register_protosw&#xff0c;这里具体看一下inet_protosw{}的结构&#xff1a;

struct inet_protosw {struct list_head list;/* These two fields form the lookup key. */unsigned short type; /* This is the 2nd argument to socket(2). */unsigned short protocol; /* This is the L4 protocol number. */struct proto *prot;const struct proto_ops *ops;unsigned char flags; /* See INET_PROTOSW_* below. */
};

可以看到type<->protocol<->proto_ops{}<->proto{}的对应关系由这个所谓“协议切换表”统一了起来&#xff1a;

static struct inet_protosw inetsw_array[] &#61;
{{.type &#61; SOCK_STREAM,.protocol &#61; IPPROTO_TCP,.prot &#61; &tcp_prot,.ops &#61; &inet_stream_ops,.flags &#61; INET_PROTOSW_PERMANENT |INET_PROTOSW_ICSK,},{.type &#61; SOCK_DGRAM,.protocol &#61; IPPROTO_UDP,.prot &#61; &udp_prot,.ops &#61; &inet_dgram_ops,.flags &#61; INET_PROTOSW_PERMANENT,},{.type &#61; SOCK_DGRAM,.protocol &#61; IPPROTO_ICMP,.prot &#61; &ping_prot,.ops &#61; &inet_sockraw_ops,.flags &#61; INET_PROTOSW_REUSE,},{.type &#61; SOCK_RAW,.protocol &#61; IPPROTO_IP, /* wild card */.prot &#61; &raw_prot,.ops &#61; &inet_sockraw_ops,.flags &#61; INET_PROTOSW_REUSE,}
};

接收层面&#xff0c;inet_add_protocol/dev_add_pack。基本上一个收发的分层框架接口就建立起来了。


2.2 抽象实体的建立

各层的接口注册完毕后&#xff0c;接下来看一看接口是如何与抽象结构绑定的&#xff0c;这个过程是进行socket系统调用产生的&#xff0c;socket系统调用的作用应该在socket layer分配一个抽象socket结构&#xff0c;为网络协议层分配一个sock结构&#xff0c;并将抽象层的接口与抽象的实体进行绑定

一个典型的socket API原型&#xff1a;


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

为了保证分析的完整性&#xff0c;还是将inet socket相关参数做一下简单说明&#xff0c;对于address family是INET的socket来说(man 7 ip),报文都是基于IP&#xff0c;所以有TCP, UDP, RAW三种&#xff1a;


  •  tcp_socket &#61; socket(AF_INET, SOCK_STREAM, 0); 
  •  udp_socket &#61; socket(AF_INET, SOCK_DGRAM, 0);  
  •  raw_socket &#61; socket(AF_INET, SOCK_RAW, protocol); 

 对于TCP(SOCK_STREAM)来说&#xff0c;第三个参数只能是0或者IPPROTO_TCP,对于UDP(SOCK_DGRAM)来说&#xff0c;第三个参数只能是0或者IPPROTO_UDP&#xff0c;对于inet raw报文来说,有以下特点&#xff1a;


  1. 其构造的报文不包括二层头&#xff0c;这点与AF_PACKET区别。
  2. 如果不指定IP_HDRINCL(socket),由内核构造ipv4头。
  3. 如果协议指定IPPROTO_RAW&#xff0c;默认使能IP_HDRINCL&#xff0c;但是这样的参数在收放向上无法用来接收所有的IP报文

对应系统调用&#xff1a;

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)

接下来分析socket系统调用执行流程

[net/socket.c]


int __sock_create(struct net *net, int family, int type, int protocol,
             struct socket **res, int kern)
{
    sock &#61; sock_alloc();
    sock->type &#61; type;

    rcu_read_lock();
    pf &#61; rcu_dereference(net_families[family]);
    rcu_read_unlock();

    err &#61; pf->create(net, sock, protocol, kern);
    if (err <0)
        goto out_module_put;
}


代码只摘录了部分&#xff0c;只说明重要的部分&#xff0c;我们来看socket具体流程

socket系统调用首先会分配一个socket结构体&#xff1a;

[include/linux/net.h]

struct socket {socket_state state;short type;unsigned long flags;struct socket_wq __rcu *wq;struct file *file;struct sock *sk;const struct proto_ops *ops;
};

我们知道不同的family address对应的sock结构是不同的&#xff0c;因为sock是代表具体协议层的&#xff0c;这里利用接口pf->create&#xff08;inet_create&#xff09;对具体协议(inet)进行创建。

[inet/ipv4/af_inet.c]


static int inet_create(struct net *net, struct socket *sock, int protocol,  int kern)
{

    sock->state &#61; SS_UNCONNECTED;

    sock->ops &#61; answer->ops;
    answer_prot &#61; answer->prot;
    answer_flags &#61; answer->flags;
    rcu_read_unlock();

    WARN_ON(!answer_prot->slab);

    err &#61; -ENOBUFS;
    sk &#61; sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
    err &#61; 0;
    if (INET_PROTOSW_REUSE & answer_flags)
        sk->sk_reuse &#61; SK_CAN_REUSE;

    inet &#61; inet_sk(sk);
    inet->is_icsk &#61; (INET_PROTOSW_ICSK & answer_flags) !&#61; 0;

    inet->nodefrag &#61; 0;

    sock_init_data(sock, sk);

    sk->sk_destruct       &#61; inet_sock_destruct;
    sk->sk_protocol       &#61; protocol;
    sk->sk_backlog_rcv &#61; sk->sk_prot->backlog_rcv;
}


先看对socket绑定接口的操作:根据type<->proto_ops<->proto关系&#xff0c;找到协议切换的结构&#xff0c;此时socket可以与proto_ops进行绑定&#xff0c;随后sk_alloc分配sock&#xff0c;过程中将sock与proto绑定

到此为止&#xff0c;前篇建立的结构就完全建立了&#xff0c;这里为了方便&#xff0c;再贴一次&#xff1a;


2.3 代码细节分析

sk_alloc完成sock{}分配和初始化&#xff0c;sock{}是网络层的表示&#xff0c;但由于协议多样&#xff0c;linux将sock{}抽象成一个基类&#xff0c;具体的协议要通过对sock{}的继承得到&#xff0c;对具体的协议(如inet&#xff0c;继承的结构是inet_sock{})是这样操作的&#xff0c;在协议注册指定继承者inet_sock{}的大小,这样在调用sk_alloc分配sock时实际上分配的大小是针对inet_sock{}的&#xff0c;这样在使用时可以像下面这样&#xff1a;

 inet &#61; inet_sk(sk);

sock这个结构本身还是比较复杂的&#xff0c;我们来看一些关键的部分&#xff0c;既然它是网络协议层的表示&#xff0c;那么基本的sip,dip,sport,dport是少不了的&#xff0c;这些位于sock{}的sock_common{}中&#xff1a;

struct sock_common __sk_common;#define sk_num __sk_common.skc_num
#define sk_dport __sk_common.skc_dport
#define sk_addrpair __sk_common.skc_addrpair
#define sk_daddr __sk_common.skc_daddr
#define sk_rcv_saddr __sk_common.skc_rcv_saddr
#define sk_family __sk_common.skc_family
#define sk_state __sk_common.skc_state
#define sk_reuse __sk_common.skc_reuse
#define sk_reuseport __sk_common.skc_reuseport#define sk_prot __sk_common.skc_prot

inet

struct inet_sock {struct sock sk;#define inet_daddr sk.__sk_common.skc_daddr
#define inet_rcv_saddr sk.__sk_common.skc_rcv_saddr
#define inet_dport sk.__sk_common.skc_dport //dport
#define inet_num sk.__sk_common.skc_num //sport&#xff0c;主机序__be32 inet_saddr;

2.3.1 socket参数

这里再给出实际应用的几个例子&#xff0c;看看再socket建立的时候是怎么匹配的&#xff1a;


  • ping        socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
  • TCP        socket(AF_INET, SOCK_STREAM, IPPROTO_IP); 
  • UDP/ifconfig    socket(AF_NET, SOCK_DGRAM, IPPROTO_IP ); 

[inet/ipv4/af_inet.c]


    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

        err &#61; 0;
        /* Check the non-wild match. */
        if (protocol &#61;&#61; answer->protocol) { //如果指定TCP指定IPPROTO_TCP&#xff0c;UDP指定IPPROTO_UDP,对应的tcp/udp在这匹配&#xff0c;是全匹配&#xff0c;所以叫non-wild
            if (protocol !&#61; IPPROTO_IP)  //不能使用SOCK_RAW&#xff0c;IPPROTO_IP的组合
                break;
        } else {
            /* Check for the two wild cases. */
            if (IPPROTO_IP &#61;&#61; protocol) {   //如果TCP,UDP protocol字段指定0&#xff0c;IPPROTO_IP&#xff0c;在这匹配&#xff0c;通过下一句将protocol改成实际的协议的值
                protocol &#61; answer->protocol;
                break;
            }
            if (IPPROTO_IP &#61;&#61; answer->protocol)  //原始套接字基本上都到这&#xff0c;如果原始套接字指定了IPPROTO_ICMP,走non-wild
                break;
        }
        err &#61; -EPROTONOSUPPORT;
    }


RAW在socket中的特殊处理

[inet/ipv4/af_inet.c]


    if (SOCK_RAW &#61;&#61; sock->type) { //如果是raw&#xff0c;主机序源端口为协议号
        inet->inet_num &#61; protocol;
        if (IPPROTO_RAW &#61;&#61; protocol)  //上面说过了IPPROTO_RAW默认指定IP_HDRINCL&#xff0c;表示app自行添加IP头
            inet->hdrincl &#61; 1;   
    }

    if (inet->inet_num) {
        inet->inet_sport &#61; htons(inet->inet_num);  //原始套接字在socket阶段就要指定端口&#xff0c;然后通过hash保存sk
        /* Add to protocol hash chains. */
        err &#61; sk->sk_prot->hash(sk);
        if (err) {
            sk_common_release(sk);
            goto out;
        }
    }

    if (sk->sk_prot->init) {
        err &#61; sk->sk_prot->init(sk);
    }



三、其他协议


  • PF_UNIX
  • PF_NETLINK
  • PF_PACKET

3.1 PF_PACKET

[net/packet/af_packet.c]

static int __init packet_init(void)
{int rc &#61; proto_register(&packet_proto, 0);if (rc !&#61; 0)goto out;sock_register(&packet_family_ops);register_pernet_subsys(&packet_net_ops);register_netdevice_notifier(&packet_netdev_notifier);
out:return rc;
}

packet_create->packet_sock{}

po &#61; pkt_sk(sk);

[net/netlink/af_netlink.c]

netlink_create->netlink_sock{}

nlk &#61; nlk_sk(sock->sk);

3.3 PF_UNIX

[net/unix/af_unix.c]

unix_create->unix_sock{}

u &#61; unix_sk(sk);

四、参考

【1】深入浅出Linux TCP/IP协议栈  罗钰 编著

 

 

 

 


推荐阅读
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 深入解析ESFramework中的AgileTcp组件
    本文详细介绍了ESFramework框架中AgileTcp组件的设计与实现。AgileTcp是ESFramework提供的ITcp接口的高效实现,旨在优化TCP通信的性能和结构清晰度。 ... [详细]
  • Go语言开发中的常见陷阱与解决方案
    本文探讨了在使用Go语言开发过程中遇到的一些典型问题,包括Map遍历的不确定性、切片操作的潜在风险以及并发处理时的常见错误。通过具体案例分析,提供有效的解决策略。 ... [详细]
  • Nginx 反向代理与负载均衡实验
    本实验旨在通过配置 Nginx 实现反向代理和负载均衡,确保从北京本地代理服务器访问上海的 Web 服务器时,能够依次显示红、黄、绿三种颜色页面以验证负载均衡效果。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文详细介绍了 Java 中 org.geotools.data.shapefile.ShapefileDataStore 类的 getCurrentTypeName() 方法,并提供了多个代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • Linux环境下进程间通信:深入解析信号机制
    本文详细探讨了Linux系统中信号的生命周期,从信号生成到处理函数执行完毕的全过程,并介绍了信号编程中的注意事项和常见应用实例。通过分析信号在进程中的注册、注销及处理过程,帮助读者理解如何高效利用信号进行进程间通信。 ... [详细]
  • Google排名优化-面向Google(Search Engine Friendly)的URL设计 ... [详细]
  • 本文详细探讨了Java命令行参数的概念、使用方法及在实际编程中的应用,包括如何通过命令行传递参数给Java程序,以及如何在Java程序中解析这些参数。 ... [详细]
  • iTOP4412开发板QtE5.7源码编译指南
    本文详细介绍了如何在iTOP4412开发板上编译QtE5.7源码,包括所需文件的位置、编译器设置、触摸库编译以及QtE5.7的完整编译流程。 ... [详细]
author-avatar
phpyang
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有