TCP/IP 协议栈在 Linux 内核中的运行时序分析

TCP/IP 协议栈在 Linux 内核中的运行时序分析

1.要求:

  • 在深入理解Linux内核任务调度(中断处理、softirg、tasklet、wq、内核线程等)机制的基础上,分析梳理send和recv过程中TCP/IP协议栈相关的运行任务实体及相互协作的时序分析。
  • 编译、部署、运行、测评、原理、源代码分析、跟踪调试等
  • 应该包括时序图

2.网络体系结构概述

  2.1协议简介

  TCP/IP协议有四层,分为应用层、传输层、网络层、网络接口层(包括链路层和物理层),

   

在实际应用中,链路层最常用的一种高速介质就是以太网,网络层常用协议是IP(Internet Protocol),此外,为了辅助网络层,还提供了一些满足其他需求的协议,比如ICMP(Internet Control Message Protocol)和ARP(Address Resolution Protocol)传输层常用的协议是TCP(Transmission Control Protocol)和UDP(User Datagram Protocol),应用层包含一些常见的协议,如HTTP协议,SMTP协议,FTP协议等。

2.2网络架构

在Linux网络协议栈的架构以及实现Internet模型中,最上面是用户空间中实现的应用层,中间为内核空间实现的网络子系统,底部为物理设备,提供了对网络的连接能力

网络协议栈顶部是系统调用接口,为用户空间中的应用程序提供了一种访问内核网络子系统的接口,下面是一个协议无关层,它提供了一种通用方法来使用传输层协议,然后是传输层具体协议,传输层下面是网络层,然后是邻居子系统,在邻居子系统存在的目标才是当前可以直接访问的,在下面是网络设备接口,提供了与各个设备驱动程序通信的通用接口,最底层是设备驱动程序

3.Socket套接字

独立于具体协议的网络编程接口,在TCP/IP协议模型中,位于应用层和传输层之间,BSD socket(伯克利套接字)是通过标准的UNIX文件描述符和其他程序通迅的方法,1目前已被广泛移植到各个平台

3.1套接字分类

  • 流式套接字(SOCK_STREAM)  

         提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。

  • 数据报套接字(SOCK_DGRAM)

        提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。

  • 原始套接字(SOCK_RAW)

   可以对较低层次协议,如IP、ICMP直接访问

3.2 Socket执行次序

 

 socket结构

struct socket
{
     socket_state  state; // socket state
      
     short   type ; // socket type
      
     unsigned long  flags; // socket flags
      
     struct fasync_struct  *fasync_list;
      
     wait_queue_head_t wait;
      
     struct file *file;
      
     struct sock *sock;  // socket在网络层的表示;
      
     const struct proto_ops *ops;
           
}

  Socket API函数分析

  1.bind函数

  int bind(int sockfd,struct sockaddr * my_addr,int addrlen)

  功能:为套接字指明一个本地端点地址TCP/IP协议使用sockaddr_in结构,包含IP地址和端口号,服务器使用它来指明熟知的端口号,然后等待连接

  参数说明:

  Sockfd:套接字描述符,指明创建连接的套接字

  my_addr:本地地址,IP地址和端口号

  addrlen :地址长度

  

  2.listen函数

  int listen(int sockfd,int input_queue_size)

  功能: 面向连接的服务器使用它将一个套接字置为被动模式,并准备接收传入连接。用于服务器,指明某个套接字连接是被动的 

  参数说明: Sockfd:套接字描述符,指明创建连接的套接字 input_queue_size:该套接字使用的队列长度,指定在请求队列中允许的最大请求数 举例:listen(sockfd,20)

  3.accept函数

  int accept(int sockfd, struct sockaddr *addr, int *addrlen);

  功能:获取传入连接请求,返回新的连接的套接字描述符。为每个新的连接请求创建了一个新的套接字,服务器只对新的连接使用该套接字,原来的监听套接字接受其他的连接请求。新的连接上传输数据使用新的套接字,使用完毕,服务器将关闭这个套接字。

  参数说明:

  Sockfd:套接字描述符,指明正在监听的套接字

  addr:提出连接请求的主机地址

  addrlen:地址长度

  4.connection函数

  int connect(int sockfd,struct sockaddr *server_addr,int sockaddr_len)

  功能: 同远程服务器建立主动连接,成功时返回0,若连接失败返回-1。

  参数说明:

  Sockfd:套接字描述符,指明创建连接的套接字

  Server_addr:指明远程端点:IP地址和端口号

  sockaddr_len :地址长度

  5.send函数

  int send(int sockfd, const void * data, int data_len, unsigned int flags)

  功能: 在TCP连接上发送数据,返回成功传送数据的长度,出错时返回-1。send会将外发数据复制到OS内核中

  参数说明:

  sockfd:套接字描述符

  data:指向要发送数据的指针

  data_len:数据长度

  flags:一直为0

  6.recv函数

  int recv(int sockfd, void *buf, int buf_len,unsigned int flags);

  功能: 从TCP接收数据,返回实际接收的数据长度,出错时返回-1。服务器使用其接收客户请求,客户使用它接受服务器的应答。如果没有数据,将阻塞,如果收到的数据大于缓存的大小,多余的数据将丢弃。

  参数说明:

  Sockfd:套接字描述符

  Buf:指向内存块的指针

  Buf_len:内存块大小,以字节为单位

  flags:一般为0

  7.close函数

  close(int sockfd);

  功能: 撤销套接字。如果只有一个进程使用,立即终止连接并撤销该套接字,如果多个进程共享该套接字,将引用数减一,如果引用数降到零,则撤销它。

  参数说明:

  Sockfd:套接字描述符

4.内核初始化加载TCP/IP协议栈

  Linux内核初始化过程之所以复杂,是因为它同时支持静态加载和动态加载模块,动态加载内核模块提高了系统灵活性,但因此要考虑更多方面,设备驱动程序可以静态地编译到内核中,也可以作为一个内核模块动态地装载和卸载,系统启动初始化时,一旦进入start_kernel()说明低级初始化已完成,接下来是对各种设备和子系统地初始化

  4.1Linux内核初始化过程中加载TCP/IP协议栈

  调用顺序如图所示:

首先进入start_kernel函数,

调用rest_init()函数,

调用kernel_thread(kernel_init,NUL,CLONE_FS)函数,

并调用kernel_init()函数,

进入调用kernel_init_freeable()函数,

进入调用do_basic_setup()函数,进入调用do_initcalls()函数

5.send发送过程

5.1、应用层

创建一个socket API创建一个socket,最终调用sock_create()方法,该方法返回被创建好地那个socket的file descriptor,如图:

对于TCP,应用调用connnect()函数,使得客户端和服务器通过该socket建立一个连接,然后可以调用send函数发出一个message给接收端,send会接着调用sock_sendmsg函数

int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
    struct kiocb iocb;
    struct sock_iocb siocb;
    int ret;
 
    init_sync_kiocb(&iocb, NULL);
    iocb.private = &siocb;
 
    ret = __sock_sendmsg(&iocb, sock, msg, size);
 
    /* iocb queued, will get completion event */
    if (-EIOCBQUEUED == ret)
        ret = wait_on_sync_kiocb(&iocb);
 
    return ret;
}

sock_sendmsg()在初始化异步IO控制块后,调用_sock_sendmsg()

static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,
       struct msghdr *msg, size_t size)
{
    int err = security_socket_sendmsg(sock, msg, size);
    return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
}
 
static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,
        struct msghdr *msg, size_t size)
{
    struct sock_iocb *si = kiocb_to_siocb(iocb);
    si->sock = sock;
    si->scm = NULL;
    si->msg = msg;
    si->size = size;
 
    /* 调用Socket层的操作函数,如果是SOCK_STREAM,则proto_ops为inet_stream_ops,
     * 函数指针指向inet_sendmsg()。
     */
    return sock->ops->sendmsg(iocb, sock, msg, size);
} 

  _sock_sendmsg()调用security_socket_sendmsg()和sock_send_nosec()函数,接着调用inet_sendmsg()

  5.2传输层

  SOCK_STREAM套接口的socket层操作函数集实例为inet_stream_ops,其中发送函数为inet_sendmsg()。

const struct proto_ops inet_stream_ops = {
    .family = PF_INET,
    .owner = THIS_MODULE,
    ...
    .sendmsg = inet_sendmsg,
    ...
};

  inet_sendmsg()主要调用TCP层的发送函数tcp_sendmsg()来处理。

int inet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size)
{
    struct sock *sk = sock->sk;
    sock_rps_record_flow(sk);
 
    /* We may need to bnd the socket.
     * 如果连接还没有分配本地端口,且允许自动绑定,那么给连接绑定一个本地端口。
     * tcp_prot的no_autobaind为true,所以TCP是不允许自动绑定端口的。
     */
    if (! inet_sk(sk)->inet_num && ! sk->sk_prot->no_autobind && inet_autobind(s))
        return -EAGAIN;
 
    /* 如果传输层使用的是TCP,则sk_prot为tcp_prot,sendmsg指向tcp_sendmsg() */
    return sk->sk_prot->sendmsg(iocb, sk, msg, size);
}
 
/* Automatically bind an unbound socket. */
static int inet_autobind(struct sock *sk)
{
    struct inet_sock *inet;
 
    /* We may need to bind the socket. */
    lock_sock(sk);
 
    /* 如果还没有分配本地端口 */
    if (! inet->inet_num) {
 
        /* SOCK_STREAM套接口的TCP操作函数集为tcp_prot,其中端口绑定函数为
         * inet_csk_get_port()。
         */
        if (sk->sk_prot->get_port(sk, 0)) {
            release_sock(sk);
            return -EAGAIN;
        }
        inet->inet_sport = htons(inet->inet_num);
    }
 
    release_sock(sk);
    return 0;
}

  

int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{
	int ret;

	lock_sock(sk);

	ret = tcp_sendmsg_locked(sk, msg, size);
	release_sock(sk);

	return ret;
}

  tcp_sendmsg中接着调用tcp_sendmsg_locked()函数

int tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct ubuf_info *uarg = NULL;
	struct sk_buff *skb;
	struct sockcm_cookie sockc;
	int flags, err, copied = 0;
	int mss_now = 0, size_goal, copied_syn = 0;
	int process_backlog = 0;
	bool zc = false;
	long timeo;

	flags = msg->msg_flags;

	if (flags & MSG_ZEROCOPY && size && sock_flag(sk, SOCK_ZEROCOPY)) {
		skb = tcp_write_queue_tail(sk);
		uarg = sock_zerocopy_realloc(sk, size, skb_zcopy(skb));
		if (!uarg) {
			err = -ENOBUFS;
			goto out_err;
		}

		zc = sk->sk_route_caps & NETIF_F_SG;
		if (!zc)
			uarg->zerocopy = 0;
	}

	if (unlikely(flags & MSG_FASTOPEN || inet_sk(sk)->defer_connect) &&
	    !tp->repair) {
		err = tcp_sendmsg_fastopen(sk, msg, &copied_syn, size, uarg);
		if (err == -EINPROGRESS && copied_syn > 0)
			goto out;
		else if (err)
			goto out_err;
	}

	timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);

	tcp_rate_check_app_limited(sk);  /* is sending application-limited? */

	/* Wait for a connection to finish. One exception is TCP Fast Open
	 * (passive side) where data is allowed to be sent before a connection
	 * is fully established.
	 */
	if (((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) &&
	    !tcp_passive_fastopen(sk)) {
		err = sk_stream_wait_connect(sk, &timeo);
		if (err != 0)
			goto do_error;
	}

	if (unlikely(tp->repair)) {
		if (tp->repair_queue == TCP_RECV_QUEUE) {
			copied = tcp_send_rcvq(sk, msg, size);
			goto out_nopush;
		}

		err = -EINVAL;
		if (tp->repair_queue == TCP_NO_QUEUE)
			goto out_err;

		/* 'common' sending to sendq */
	}

	sockcm_init(&sockc, sk);
	if (msg->msg_controllen) {
		err = sock_cmsg_send(sk, msg, &sockc);
		if (unlikely(err)) {
			err = -EINVAL;
			goto out_err;
		}
	}

	/* This should be in poll */
	sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);

	/* Ok commence sending. */
	copied = 0;

restart:
	mss_now = tcp_send_mss(sk, &size_goal, flags);

	err = -EPIPE;
	if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
		goto do_error;

	while (msg_data_left(msg)) {
		int copy = 0;

		skb = tcp_write_queue_tail(sk);
		if (skb)
			copy = size_goal - skb->len;

		if (copy <= 0 || !tcp_skb_can_collapse_to(skb)) {
			bool first_skb;

new_segment:
			if (!sk_stream_memory_free(sk))
				goto wait_for_sndbuf;

			if (unlikely(process_backlog >= 16)) {
				process_backlog = 0;
				if (sk_flush_backlog(sk))
					goto restart;
			}
			first_skb = tcp_rtx_and_write_queues_empty(sk);
			skb = sk_stream_alloc_skb(sk, 0, sk->sk_allocation,
						  first_skb);
			if (!skb)
				goto wait_for_memory;

			process_backlog++;
			skb->ip_summed = CHECKSUM_PARTIAL;

			skb_entail(sk, skb);
			copy = size_goal;

			/* All packets are restored as if they have
			 * already been sent. skb_mstamp_ns isn't set to
			 * avoid wrong rtt estimation.
			 */
			if (tp->repair)
				TCP_SKB_CB(skb)->sacked |= TCPCB_REPAIRED;
		}

		/* Try to append data to the end of skb. */
		if (copy > msg_data_left(msg))
			copy = msg_data_left(msg);

		/* Where to copy to? */
		if (skb_availroom(skb) > 0 && !zc) {
			/* We have some space in skb head. Superb! */
			copy = min_t(int, copy, skb_availroom(skb));
			err = skb_add_data_nocache(sk, skb, &msg->msg_iter, copy);
			if (err)
				goto do_fault;
		} else if (!zc) {
			bool merge = true;
			int i = skb_shinfo(skb)->nr_frags;
			struct page_frag *pfrag = sk_page_frag(sk);

			if (!sk_page_frag_refill(sk, pfrag))
				goto wait_for_memory;

			if (!skb_can_coalesce(skb, i, pfrag->page,
					      pfrag->offset)) {
				if (i >= sysctl_max_skb_frags) {
					tcp_mark_push(tp, skb);
					goto new_segment;
				}
				merge = false;
			}

			copy = min_t(int, copy, pfrag->size - pfrag->offset);

			if (!sk_wmem_schedule(sk, copy))
				goto wait_for_memory;

			err = skb_copy_to_page_nocache(sk, &msg->msg_iter, skb,
						       pfrag->page,
						       pfrag->offset,
						       copy);
			if (err)
				goto do_error;

			/* Update the skb. */
			if (merge) {
				skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
			} else {
				skb_fill_page_desc(skb, i, pfrag->page,
						   pfrag->offset, copy);
				page_ref_inc(pfrag->page);
			}
			pfrag->offset += copy;
		} else {
			err = skb_zerocopy_iter_stream(sk, skb, msg, copy, uarg);
			if (err == -EMSGSIZE || err == -EEXIST) {
				tcp_mark_push(tp, skb);
				goto new_segment;
			}
			if (err < 0)
				goto do_error;
			copy = err;
		}

		if (!copied)
			TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_PSH;

		WRITE_ONCE(tp->write_seq, tp->write_seq + copy);
		TCP_SKB_CB(skb)->end_seq += copy;
		tcp_skb_pcount_set(skb, 0);

		copied += copy;
		if (!msg_data_left(msg)) {
			if (unlikely(flags & MSG_EOR))
				TCP_SKB_CB(skb)->eor = 1;
			goto out;
		}

		if (skb->len < size_goal || (flags & MSG_OOB) || unlikely(tp->repair))
			continue;

		if (forced_push(tp)) {
			tcp_mark_push(tp, skb);
			__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
		} else if (skb == tcp_send_head(sk))
			tcp_push_one(sk, mss_now);
		continue;

wait_for_sndbuf:
		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
		if (copied)
			tcp_push(sk, flags & ~MSG_MORE, mss_now,
				 TCP_NAGLE_PUSH, size_goal);

		err = sk_stream_wait_memory(sk, &timeo);
		if (err != 0)
			goto do_error;

		mss_now = tcp_send_mss(sk, &size_goal, flags);
	}

out:
	if (copied) {
		tcp_tx_timestamp(sk, sockc.tsflags);
		tcp_push(sk, flags, mss_now, tp->nonagle, size_goal);
	}
out_nopush:
	sock_zerocopy_put(uarg);
	return copied + copied_syn;

do_error:
	skb = tcp_write_queue_tail(sk);
do_fault:
	tcp_remove_empty_skb(sk, skb);

	if (copied + copied_syn)
		goto out;
out_err:
	sock_zerocopy_put_abort(uarg, true);
	err = sk_stream_error(sk, flags, err);
	/* make sure we wake any epoll edge trigger waiter */
	if (unlikely(tcp_rtx_and_write_queues_empty(sk) && err == -EAGAIN)) {
		sk->sk_write_space(sk);
		tcp_chrono_stop(sk, TCP_CHRONO_SNDBUF_LIMITED);
	}
	return err;
}

  主要工作是把用户层数据填充到skb中,调用tcp_push()来发送,tcp_push函数调用tcp_write_xmit()函数,接着调用_tcp_transmit_skb发送函数,所有的SKB都经过该函数进行发送,最后进入到ip_queue_xmit到玩过曾,使用tcp_write_timer函数进行定时,重传

static void tcp_push(struct sock *sk, int flags, int mss_now,
             int nonagle, int size_goal)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct sk_buff *skb;

    skb = tcp_write_queue_tail(sk);
    if (!skb)
        return;
    if (!(flags & MSG_MORE) || forced_push(tp))
        tcp_mark_push(tp, skb);

    tcp_mark_urg(tp, flags);

    if (tcp_should_autocork(sk, skb, size_goal)) {

        /* avoid atomic op if TSQ_THROTTLED bit is already set */
        if (!test_bit(TSQ_THROTTLED, &sk->sk_tsq_flags)) {
            NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAUTOCORKING);
            set_bit(TSQ_THROTTLED, &sk->sk_tsq_flags);
        }
        /* It is possible TX completion already happened
         * before we set TSQ_THROTTLED.
         */
        if (refcount_read(&sk->sk_wmem_alloc) > skb->truesize)
            return;
    }

    if (flags & MSG_MORE)
        nonagle = TCP_NAGLE_CORK;

    __tcp_push_pending_frames(sk, mss_now, nonagle);
}

  

static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,
                  int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{

    skb_push(skb, tcp_header_size);
    skb_reset_transport_header(skb);
    ......
    /* 构建TCP头部和校验和 */
    th = (struct tcphdr *)skb->data;
    th->source        = inet->inet_sport;
    th->dest        = inet->inet_dport;
    th->seq            = htonl(tcb->seq);
    th->ack_seq        = htonl(rcv_nxt);

    tcp_options_write((__be32 *)(th + 1), tp, &opts);
    skb_shinfo(skb)->gso_type = sk->sk_gso_type;
    if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) {
        th->window      = htons(tcp_select_window(sk));
        tcp_ecn_send(sk, skb, th, tcp_header_size);
    } else {
        /* RFC1323: The window in SYN & SYN/ACK segments
         * is never scaled.
         */
        th->window    = htons(min(tp->rcv_wnd, 65535U));
    }
    ......
    icsk->icsk_af_ops->send_check(sk, skb);

    if (likely(tcb->tcp_flags & TCPHDR_ACK))
        tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);

    if (skb->len != tcp_header_size) {
        tcp_event_data_sent(tp, sk);
        tp->data_segs_out += tcp_skb_pcount(skb);
        tp->bytes_sent += skb->len - tcp_header_size;
    }

    if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq)
        TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS,
                  tcp_skb_pcount(skb));

    tp->segs_out += tcp_skb_pcount(skb);
    /* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */
    skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb);
    skb_shinfo(skb)->gso_size = tcp_skb_mss(skb);

    /* Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns) */

    /* Cleanup our debris for IP stacks */
    memset(skb->cb, 0, max(sizeof(struct inet_skb_parm),
                   sizeof(struct inet6_skb_parm)));

    err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl);
    ......
}

 gdb调试

 

 5.3网络层

ip_queue_xmit(skb)会检查skb->dst路由信息,会选择路由。在ip_fragement函数中,检查IP_DF标志位,如果IP数据包禁止分片,会调用icmp_send()向发送放发送不可达ICMP报文,并丢弃该报文,设置IP状态为分片失败,释放skb,返回错误

int ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{
    struct sock *sk = skb->sk;
    struct inet_sock *inet = inet_sk(sk);
    struct ip_options_rcu *inet_opt;
    struct flowi4 *fl4;
    struct rtable *rt;
    struct iphdr *iph;
    int res;
    /* 判断数据包是否有路由,如果已经有了,就直接跳到packet_routed */
    rcu_read_lock();
    inet_opt = rcu_dereference(inet->inet_opt);
    fl4 = &fl->u.ip4;
    rt = skb_rtable(skb);
    if (rt != NULL)
        goto packet_routed;
    /* 从套接字获得合法的路由(需要检查是否过期)*/
    rt = (struct rtable *)__sk_dst_check(sk, 0);
    if (rt == NULL) {
        __be32 daddr;
        daddr = inet->inet_daddr;
        /* 如果有IP 严格路由选项,则使用选项中的地址作为目的地址进行路由查询*/
        if (inet_opt && inet_opt->opt.srr)
            daddr = inet_opt->opt.faddr;
        /* 进行路由查找*/
        rt = ip_route_output_ports(sock_net(sk), fl4, sk,
                     daddr, inet->inet_saddr,
                     inet->inet_dport,
                     inet->inet_sport,
                     sk->sk_protocol,
                     RT_CONN_FLAGS(sk),
                     sk->sk_bound_dev_if);
        if (IS_ERR(rt))
            goto no_route;
        /* 根据路由的接口的特性设置套接字特性*/
        sk_setup_caps(sk, &rt->dst);
    }
    /* 给数据包设置路由*/
    skb_dst_set_noref(skb, &rt->dst);
packet_routed:
    /* 如果有IP严格路由选项*/
    if (inet_opt && inet_opt->opt.is_strictroute && fl4->daddr != rt->rt_gateway)
        goto no_route;
    /* 分配IP首部和选项空间*/
    skb_push(skb, sizeof(struct iphdr) + (inet_opt ? inet_opt->opt.optlen : 0));
    /* 设置IP首部位置*/
    skb_reset_network_header(skb);
    /* 得到数据包IP首部的指针*/
    iph = ip_hdr(skb);
    /* 构建IP首部*/
    *((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
    /* 如不能分片,则在IP首部设置IP_DF标志*/
    if (ip_dont_fragment(sk, &rt->dst) && !skb->local_df)
        iph->frag_off = htons(IP_DF);
    else
        iph->frag_off = 0;
    iph->ttl      = ip_select_ttl(inet, &rt->dst);
    iph->protocol = sk->sk_protocol;
    iph->saddr    = fl4->saddr;
    iph->daddr    = fl4->daddr;
    /* Transport layer set skb->h.foo itself. */
    /* 构建IP选项*/
    if (inet_opt && inet_opt->opt.optlen) {
        iph->ihl += inet_opt->opt.optlen >> 2;
        ip_options_build(skb, &inet_opt->opt, inet->inet_daddr, rt, 0);
    }
    /* 选择合适的IP identifier */
    ip_select_ident_more(iph, &rt->dst, sk,
               (skb_shinfo(skb)->gso_segs ?: 1) - 1);
    /* 根据套接字选项,设置数据包的优先级和标记*/
    skb->priority = sk->sk_priority;
    skb->mark = sk->sk_mark;
    /* 发送数据包 */
    res = ip_local_out(skb);
    rcu_read_unlock();
    return res;
no_route:
    rcu_read_unlock();
    IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
    kfree_skb(skb);
    return -EHOSTUNREACH;
}

 gdb调试

 5.4链路层

数据链路层在不可靠武力介质上提供可靠传输,该层作用包括:物理地址寻址、数据成帧、流量控制、数据检错、重发等,从dev_queue_xmit()开始

gdb调试

6.recv调用过程

6.1数据链路层

当链路层收到帧是,会产生一个中断,通知内核接收到新的数据帧,进入软中断,调用net_rx_action函数,获取所有的包,进入netif_receive_skb处理,调用第三层协议接收函数处理该skb包,进入第三层网络层来处理

6.2网络层

ip_rcv函数收到该skb包,进入网络层处理,到达ip_rcv_finish函数调用ip_router_input函数,进入路由处理,来决定是转发还是丢弃,如果转发到本机,就调用ip_local_deliver函数,调用ip_local_deliver函数来根据协议号,调用下一层接口;如果需要转发,就进入转发流程,调用dst_input函数

/*
 * IP receive entry point
 */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
       struct net_device *orig_dev)
{
    struct net *net = dev_net(dev);

    skb = ip_rcv_core(skb, net);
    if (skb == NULL)
        return NET_RX_DROP;

    return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
               net, NULL, skb, dev, NULL,
               ip_rcv_finish);
}

 

static int ip_rcv_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    struct net_device *dev = skb->dev;
    int ret;

    /* if ingress device is enslaved to an L3 master device pass the
     * skb to its handler for processing
     */
    skb = l3mdev_ip_rcv(skb);
    if (!skb)
        return NET_RX_SUCCESS;

    ret = ip_rcv_finish_core(net, sk, skb, dev);
    if (ret != NET_RX_DROP)
        ret = dst_input(skb);
    return ret;
}

  

 

int ip_local_deliver(struct sk_buff *skb)
{
    /*
     *    Reassemble IP fragments.
     */
    struct net *net = dev_net(skb->dev);

    if (ip_is_fragment(ip_hdr(skb))) {
        if (ip_defrag(net, skb, IP_DEFRAG_LOCAL_DELIVER))
            return 0;
    }

    return NF_HOOK(NFPROTO_IPV4, NF_INET_LOCAL_IN,
               net, NULL, skb, skb->dev, NULL,
               ip_local_deliver_finish);
}

  

 

void ip_protocol_deliver_rcu(struct net *net, struct sk_buff *skb, int protocol)
{
    const struct net_protocol *ipprot;
    int raw, ret;

resubmit:
    raw = raw_local_deliver(skb, protocol);

    ipprot = rcu_dereference(inet_protos[protocol]);
    if (ipprot) {
        if (!ipprot->no_policy) {
            if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                kfree_skb(skb);
                return;
            }
            nf_reset_ct(skb);
        }
        ret = INDIRECT_CALL_2(ipprot->handler, tcp_v4_rcv, udp_rcv,
                      skb);
        if (ret < 0) {
            protocol = -ret;
            goto resubmit;
        }
        __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
    } else {
        if (!raw) {
            if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
                icmp_send(skb, ICMP_DEST_UNREACH,
                      ICMP_PROT_UNREACH, 0);
            }
            kfree_skb(skb);
        } else {
            __IP_INC_STATS(net, IPSTATS_MIB_INDELIVERS);
            consume_skb(skb);
        }
    }
}

  

 6.3传输层

tcp_v4_rcv函数为TCP总入口,数据包从IP层传递上来,进入该函数,tcp_v4_rcv函数只要做以下几个工作:(1) 设置TCP_CB (2) 查找控制块 (3)根据控制块状态做不同处理,包括TCP_TIME_WAIT状态处理,TCP_NEW_SYN_RECV状态处理,TCP_LISTEN状态处理 (4) 接收TCP段;之后,调用的也就是__sys_recvfrom,整个函数的调用路径与send非常类似。整个函数实际调用的是sock->ops->recvmsg(sock, msg, msg_data_left(msg), flags),tcp三次握手也是在接收函数中实现的,根据收到的数据判断当前状态来是否建立连接

int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock,
        int flags, int *addr_len)
{
    ......
    if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) &&
        (sk->sk_state == TCP_ESTABLISHED))
        sk_busy_loop(sk, nonblock);
​
    lock_sock(sk);
    .....
        if (unlikely(tp->repair)) {
        err = -EPERM;
        if (!(flags & MSG_PEEK))
            goto out;
​
        if (tp->repair_queue == TCP_SEND_QUEUE)
            goto recv_sndq;
​
        err = -EINVAL;
        if (tp->repair_queue == TCP_NO_QUEUE)
            goto out;
    ......
        last = skb_peek_tail(&sk->sk_receive_queue);
        skb_queue_walk(&sk->sk_receive_queue, skb) {
            last = skb;
    ......
            if (!(flags & MSG_TRUNC)) {
            err = skb_copy_datagram_msg(skb, offset, msg, used);
            if (err) {
                /* Exception. Bailout! */
                if (!copied)
                    copied = -EFAULT;
                break;
            }
        }
​
        *seq += used;
        copied += used;
        len -= used;
​
        tcp_rcv_space_adjust(sk);

  .维护三个队列,prequeue、backlog、receive_queue,分别为预处理队列,后备队列和接收队列,在连接建立后,若没有数据到来,接收队列为空,进程会在sk_busy_loop函数内循环等待,知道接收队列不为空,并调用函数skb_copy_datagram_msg将接收到的数据拷贝到用户态,实际调用的是__skb_datagram_iter

gdb调试

6.4应用层

对于recv函数,与send类似,自然也是recvfrom的特殊情况,调用的也就是__sys_recvfrom,整个函数的调用路径与send非常类似:

int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
           struct sockaddr __user *addr, int __user *addr_len)
{
    ......
    err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
    if (unlikely(err))
        return err;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    .....
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    /* Save some cycles and don't copy the address if not needed */
    msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
    /* We assume all kernel code knows the size of sockaddr_storage */
    msg.msg_namelen = 0;
    msg.msg_iocb = NULL;
    msg.msg_flags = 0;
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    err = sock_recvmsg(sock, &msg, flags);
​
    if (err >= 0 && addr != NULL) {
        err2 = move_addr_to_user(&address,
                     msg.msg_namelen, addr, addr_len);
    .....
}

  应用调用 read 或者 recv 时,该调用会被映射为/net/socket.c 中的 sys_recv 系统调用,并被转化为 sys_recvfrom 调用,然后调用 sock_recvmsg 函数。对于 INET 类型的 socket,/net/ipv4/af_inet.c 中的 inet_recvmsg 方法会被调用,它会调用相关协议的数据接收方法。TCP 会调用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到buffer。

 

int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags,
           struct sockaddr __user *addr, int __user *addr_len)
{
    struct socket *sock;
    struct iovec iov;
    struct msghdr msg;
    struct sockaddr_storage address;
    int err, err2;
    int fput_needed;

    err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter);
    if (unlikely(err))
        return err;
    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (!sock)
        goto out;

    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    /* Save some cycles and don't copy the address if not needed */
    msg.msg_name = addr ? (struct sockaddr *)&address : NULL;
    /* We assume all kernel code knows the size of sockaddr_storage */
    msg.msg_namelen = 0;
    msg.msg_iocb = NULL;
    msg.msg_flags = 0;
    if (sock->file->f_flags & O_NONBLOCK)
        flags |= MSG_DONTWAIT;
    err = sock_recvmsg(sock, &msg, flags);

    if (err >= 0 && addr != NULL) {
        err2 = move_addr_to_user(&address,
                     msg.msg_namelen, addr, addr_len);
        if (err2 < 0)
            err = err2;
    }

    fput_light(sock->file, fput_needed);
out:
    return err;
}

  

int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags)
{
    int err = security_socket_recvmsg(sock, msg, msg_data_left(msg), flags);

    return err ?: sock_recvmsg_nosec(sock, msg, flags);
}
EXPORT_SYMBOL(sock_recvmsg);

  

int inet_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
         int flags)
{
    struct sock *sk = sock->sk;
    int addr_len = 0;
    int err;

    if (likely(!(flags & MSG_ERRQUEUE)))
        sock_rps_record_flow(sk);

    err = INDIRECT_CALL_2(sk->sk_prot->recvmsg, tcp_recvmsg, udp_recvmsg,
                  sk, msg, size, flags & MSG_DONTWAIT,
                  flags & ~MSG_DONTWAIT, &addr_len);
    if (err >= 0)
        msg->msg_namelen = addr_len;
    return err;
}

  7.时序图

posted on 2021-01-30 20:26  跃入人海  阅读(168)  评论(0)    收藏  举报

导航