zoukankan      html  css  js  c++  java
  • kernel笔记——网络收发包流程

    本文将介绍网络连接建立的过程、收发包流程,以及其中应用层、tcp层、ip层、设备层和驱动层各层发挥的作用。

    应用层

    对于使用socket进行网络连接的服务器端程序,我们会先调用socket函数创建一个套接字:

    fd = socket(AF_INET, SOCK_STREAM, 0);

    以上指定了连接协议,socket调用返回一个文件句柄,与socket文件对应的inode不在磁盘上,而是存在于内存。

    之后我们指定监听的端口、允许与哪些ip建立连接,并调用bind完成端口绑定:

    server_addr.sin_family = AF_INET;
    server_addr.sin_port   = htons(PORT);
    server_addr.sin_addr.s_addr = INADDR_ANY;
    bind(fd, (struct sockaddr_in *)&server_addr, sizeof(struct sockaddr_in));

    端口作为进程的标识,客户端根据服务器ip和端口号就能找到相应进程。

    接着我们调用listen函数,对端口进行监听:

    listen(fd, backlog);

    backlog值指定了监听队列的长度,以下内核参数限制了backlog可设定的最大值:

    linux # sysctl -a | grep somaxconn
    net.core.somaxconn = 128

    监听端口在listen调用后变为LISTEN状态:

    linux # netstat -antp | grep 9999
    Proto  Recv-Q Send-Q Local Address  Foreign Address  State  PID/Program name
    tcp         0      0  0.0.0.0:9999        0.0.0.0:* LISTEN       8709/server

    相应地,客户端调用connect进行连接,tcp三次握手在connect调用返回之前完成:

    如果服务器端向客户端发送SYN+ACK后,客户端不返回ACK,则服务器保持半连接(SYN_RECV)状态:

    linux # netstat -np | grep SYN_RECV
    tcp      0        0     0.0.0.0:9999  127.0.0.0.1:5334   SYN_RECV  - 

    若队列中的连接均处于半连接状态,服务器将不能处理正常的请求,syn泛洪攻击(syn flood)就是利用这个特点完成DoS(拒绝服务攻击)。

    当连接数超过队列长度backlog时,超出的连接也保持为半连接状态,直到数量达到内核参数tcp_max_syn_backlog值,超出该值的连接请求将被丢弃:

    linux # sysctl -a | grep tcp_max_syn
    net.ipv4.tcp_max_syn_backlog = 1024

    accept调用用于处理新到来的连接:

    new_fd = accept(fd, (struct sockaddr*)&client_addr, &sin_size);

    其返回一个文件描述符,后续我们可以对该文件描述符调用write、read等操作函数,原监听端口仍处于LISTEN状态:

    linux # netstat -antp | grep 9999
    tcp     0    0    0.0.0.0:9999       0.0.0.0:*      LISTEN  8709/server
    tcp     0    0  127.0.0.1:9999 127.0.0.1:52274 ESTABLISHED  -

    以上为网络连接建立过程中,应用层所做的工作,server端完成了socket创建、端口绑定、端口监听、连接和收发包任务,而client端相对简单,只需包含连接和收发包。

    tcp层

    内核代码中,tcp_sendmsg是tcp发包的主入口函数,该函数中struct sk_buff结构用于描述一个数据包。

    对于超过MTU(maximum transmission unit, 最大传输单元)的数据包,tcp层会对数据包进行拆分,若开启了网口的tcp segmentation offload功能,则拆分工作由网卡完成:

    linux # ethtool -k ether
    Offload parameters for eth1:
    rx-checksumming: on
    tx-checksumming: on
    scatter-gather: on
    tcp segmentation offload: on 

    以下内核参数是内核为tcp socket预留的用于发送数据包的缓冲区大小,单位为byte:

    linux # sysctl -a | grep tcp_wmem
    net.ipv4.tcp_wmem = 4096 16384 131072

    默认的用于包发送的缓冲区大小为16M。

    除了用于缓冲收发数据包,对于每个socket,内核还要分配一些数据结构用于保持连接状态,内核对tcp层可使用的内存大小进行了限制:

    linux # sysctl -a | grep tcp_mem
    net.ipv4.tcp_mem = 196608 262144 393216

    以上值以页为单位,分别对应最小值、压力值和最大值,并在系统启动、tcp栈初始化时根据内存总量设定。通过proc提供的接口,我们可以查到tcp已用的内存页数:

    linux # cat /proc/net/sockstat
    sockets : used 91
    TCP : inuse 8 orphan 0 tw 11 alloc 13 mem 2

    ip层

    内核代码中,ip_queue_xmit函数是ip层的主入口函数,注意ip层与tcp层操作的都是同一块内存(sk_buff结构),期间并没有发生数据包相关的内存拷贝。

    ip层主要完成查找路由的任务,其根据路由表配置,决定数据包发往哪个网口,另外,该层实现netfilter的功能。

    网络设备层

    dev_queue_xmit是网络设备层的主入口函数,该层为每个网口维护一条数据包队列,由ip层下发的数据包放入对应网口的队列中。在该层中,数据包不是直接交给网卡,而是先缓冲起来,再通过软中断(NET_TX_SOFTIRQ)调用qdisc_run函数,该函数将数据包进一步交由网卡处理。我们执行ifconfig时,txqueuelen指示了网络设备层中,网口队列的长度。

    驱动层

    使用不同驱动的网卡,相应的驱动层代码就不一样,这里以e1000网卡为例。e1000_xmit_frame是该层的主入口函数,该层利用环形队列进行数据包管理,由两个指针负责维护环形队列。执行ethtool命令,我们可以查询网口驱动层环形队列长度:

    linux # ethtool -g eth1
    Ring parameters for ether
    Pre-set maximums:
    RX : 511
    RX Mini : 0
    RX Jumbo : 0
    TX : 511
    Current hardware settings:
    RX : 200
    RX Mini : 0
    RX Jumbo : 0
    TX : 511

    以上RX与TX分别指示收包队列与发包队列长度,单位为包个数。

    网卡接收到数据包时将产生中断,以通知cpu数据包到来的消息,而网卡接收包又非常繁忙,如果每次收发包都向cpu发送硬中断,那cpu将忙于处理网卡中断。

    相应的优化方案是NAPI(New API)模式,其关闭网卡硬中断,使网卡不发送中断,而非使cpu不接收网卡中断。在e1000驱动代码中,由e1000_clean函数实现NAPI模式。

    不像写文件的过程,磁盘设备层完成内存数据到磁盘拷贝后,会将消息层层上报,这里的网卡驱动层发包后不会往上层发送通知消息。

    收包过程

    以上为网络发包所需经过的层次结构,以及各层的大体功能,下面我们简单看下收包过程。

    网卡接收到数据包后,通知上层,该过程不会发生拷贝,数据包丢给ip层。

    内核代码中,ip_rcv是ip层收包的主入口函数,该函数由软中断调用。存放数据包的sk_buff结构包含有目的地ip和端口信息,此时ip层进行检查,如果目的地ip不是本机,则将包丢弃,如果配置了netfilter,则按照配置规则对包进行转发。

    tcp_v4_rcv是tcp层收包的接收入口,其调用__inet_lookup_skb函数查到数据包需要往哪个socket传送,之后将数据包放入tcp层收包队列中,如果应用层有read之类的函数调用,队列中的包将被取出。

    tcp层收包使用的内存同样有限制:

    linux # sysctl -a | grep rmem
    net.ipv4.tcp_rmem = 4096 16384 131072
  • 相关阅读:
    Hdu 5396 Expression (区间Dp)
    Lightoj 1174
    codeforces 570 D. Tree Requests (dfs)
    codeforces 570 E. Pig and Palindromes (DP)
    Hdu 5385 The path
    Hdu 5384 Danganronpa (AC自动机模板)
    Hdu 5372 Segment Game (树状数组)
    Hdu 5379 Mahjong tree (dfs + 组合数)
    Hdu 5371 Hotaru's problem (manacher+枚举)
    Face The Right Way---hdu3276(开关问题)
  • 原文地址:https://www.cnblogs.com/felixzh/p/9039707.html
Copyright © 2011-2022 走看看