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

目录

一、综述

二、INET的初始化

2.1 INET接口注册

2.2 抽象实体的建立

2.3 代码细节分析

2.3.1 socket参数

三、其他协议

3.1 PF_PACKET

3.3 PF_UNIX

四、参考


一、综述

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

二、INET的初始化

2.1 INET接口注册

[net/ipv4/af_inet.c]

static int __init inet_init(void)
{    
	rc = proto_register(&tcp_prot, 1);
	rc = proto_register(&udp_prot, 1);
	rc = proto_register(&raw_prot, 1);
	rc = 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 = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
		INIT_LIST_HEAD(r);

	for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
		inet_register_protosw(q);

	dev_add_pack(&ip_packet_type);

}

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

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{}的对应关系由这个所谓“协议切换表”统一了起来

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

2.2 抽象实体的建立

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

一个典型的socket API原型:

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

对应系统调用:

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 = sock_alloc();
    sock->type = type;

    rcu_read_lock();
    pf = rcu_dereference(net_families[family]);
    rcu_read_unlock();

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

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

socket系统调用首先会分配一个socket结构体:

[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结构是不同的,因为sock是代表具体协议层的,这里利用接口pf->create(inet_create)对具体协议(inet)进行创建。

[inet/ipv4/af_inet.c]

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

    sock->state = SS_UNCONNECTED;

    sock->ops = answer->ops;
    answer_prot = answer->prot;
    answer_flags = answer->flags;
    rcu_read_unlock();

    WARN_ON(!answer_prot->slab);

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

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

    inet->nodefrag = 0;

    sock_init_data(sock, sk);

    sk->sk_destruct       = inet_sock_destruct;
    sk->sk_protocol       = protocol;
    sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
}

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

到此为止,前篇建立的结构就完全建立了,这里为了方便,再贴一次:

2.3 代码细节分析

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

 inet = inet_sk(sk);

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

	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,主机序
	__be32			inet_saddr;

2.3.1 socket参数

  • 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 = 0;
        /* Check the non-wild match. */
        if (protocol == answer->protocol) { //ping在这匹配,如果指定IPPROTO_TCP,IPPROTO_UDP,对应的tcp/udp也在这匹配,是全匹配,所以叫non-wild
            if (protocol != IPPROTO_IP)  //不能使用SOCK_RAW和IPPROTO_IP的组合
                break;
        } else {
            /* Check for the two wild cases. */
            if (IPPROTO_IP == protocol) {   //如果TCP,UDP protocol字段指定0,IPPROTO_IP,在这匹配,通过下一句将protocol改成精确的值
                protocol = answer->protocol;
                break;
            }
            if (IPPROTO_IP == answer->protocol)  //原始套接字基本上都到这,如果原始套接字指定TCP,UDP,ICMP,走non-wild
                break;
        }
        err = -EPROTONOSUPPORT;
    }

RAW在socket

[inet/ipv4/af_inet.c]

    if (SOCK_RAW == sock->type) { //如果是raw,主机序源端口为协议号
        inet->inet_num = protocol;
        if (IPPROTO_RAW == protocol)
            inet->hdrincl = 1;   //hdrincl 表示app自行添加IP头
    }

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

    if (sk->sk_prot->init) {
        err = 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 = proto_register(&packet_proto, 0);

	if (rc != 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 = pkt_sk(sk);

[net/netlink/af_netlink.c]

netlink_create->netlink_sock{}

nlk = nlk_sk(sock->sk);

3.3 PF_UNIX

[net/unix/af_unix.c]

unix_create->unix_sock{}

u = unix_sk(sk);

四、参考

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

猜你喜欢

转载自blog.csdn.net/whenloce/article/details/84069407