TCP/UDP丢包

一、丢包
这个丢包不是网卡级别的丢包,在每个网卡中也会显示丢失的包的数据。这个一般是由于网卡在中断处理中需要通过skbuff来存储新来的包。此时是直接通过内存管理接口申请结构,此时这个地方并没有办法做限制,因为此时的中断处理程序并不理解上层的协议,更不用说进程或者是socket这些逻辑概念。所以当网卡收到数据之后就分配一个包结构,此时分配失败就认为是丢掉一个包,计入网卡的报文统计中。
TCP和UDP是传输层协议,所以它们的丢包一般是主动丢弃,并且它参考了更加详细的控制信息。这里我们最为关注的有两个,对于udp来说,一般是由于一个UDP能够占有的系统资源达到限量;不能让一个服务耗光系统中所有的资源;对于TCP来说,它除了有socket限制之外,对于侦听的套接口,它同样还需要有一个运行多少个连接存在的问题,不可能让一个套接口三次握手过多的连接而用户态不执行accept。
二、UDP丢包
UDP丢包主要存在于接收端无法及时处理对方发送的数据,这些数据以报文的形式在系统中暂时存储,但是如果这些未接受的报文太多,操作系统就会将新到来的报文丢掉,从而避免一个套接口对整个系统资源耗光。
这个逻辑和思路都比较简单,也是因为UDP本身是一个相对比较简单的传输控制协议。这里大致看一下相关代码
__udp4_lib_rcv--->>>udp_queue_rcv_skb
    if ((rc = sock_queue_rcv_skb(sk,skb)) < 0) {
        /* Note that an ENOMEM error is charged twice */
        if (rc == -ENOMEM)
            UDP_INC_STATS_BH(UDP_MIB_RCVBUFERRORS, up->pcflag);
        goto drop;
    }
而接收函数中处理为
int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
{
    int err = 0;
    int skb_len;

    /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
       number of warnings when compiling with -W --ANK
     */
    if (atomic_read(&sk->sk_rmem_alloc) + skb->truesize >=
        (unsigned)sk->sk_rcvbuf) {
        err = -ENOMEM;
        goto out;
    }
这里如果达到一个套接口的限量,则返回错误,上层记录到UDP丢包状态中,这个状态可以通过/proc/net/snmp文件查看,例如我的系统
Udp: InDatagrams NoPorts InErrors OutDatagrams RcvbufErrors SndbufErrors
Udp: 672 0 0 670 0 0
这个功能在2.6.17--2.6.21之间的一个版本添加,小于2.6.17的版本一定没有。
三、TCP丢包
1、三次握手时丢包
这个主要是由用户的listen的backlog参数决定的一个信息。其中的backlog表示可以有多少个连接完成三次握手而不执行accept,如果大于该值,则三次握手不能完成,这是一个准确值。相对来说还有个大概值,这个值也是根据backlog参数计算得到,只是按照2的幂数取整了,例如backlog为5,该值可能为8.它用来控制一个套接口可以同时最多接收多少个连接请求,这个请求准确的说是第一次握手,这个数值其实是和accept的限量是独立的。极端情况下,以listen参数为5说明,第一次握手可以有8个完成,而三次握手可以有5个。
①、listen之backlog参数处理
inet_listen -->>>sk->sk_max_ack_backlog = backlog;这里的数值是对于完成三次握手而没有被accept的连接的限制。
inet_csk_listen_start--->>reqsk_queue_alloc
    for (lopt->max_qlen_log = 3;
         (1 << lopt->max_qlen_log) < nr_table_entries;
         lopt->max_qlen_log++);
该数值限制的是第一次握手的回应数量。
②、第一次握手处理
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
    /* TW buckets are converted to open requests without
     * limitations, they conserve resources and peer is
     * evidently real one.
     */
    if (inet_csk_reqsk_queue_is_full(sk) && !isn) {这里判断listen中可以完成第一次握手的数量,如果大于限量,丢掉报文
#ifdef CONFIG_SYN_COOKIES
        if (sysctl_tcp_syncookies) {
            want_cookie = 1;
        } else
#endif
        goto drop;
    }

    /* Accept backlog is full. If we have already queued enough
     * of warm entries in syn queue, drop request. It is better than
     * clogging syn queue with openreqs with exponentially increasing
     * timeout.
     */
    if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)这里的young表示系统中还没有被重传的sync回应套接口
        goto drop;
……
drop:
    return 0;
这里的连接丢掉并没有记录任何信息,所以我们并不知道系统拒绝了多少三次握手的第一次请求。
③、第三次握手回应时丢包
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
                  struct request_sock *req,
                  struct dst_entry *dst)
{
    struct inet_request_sock *ireq;
    struct inet_sock *newinet;
    struct tcp_sock *newtp;
    struct sock *newsk;
#ifdef CONFIG_TCP_MD5SIG
    struct tcp_md5sig_key *key;
#endif

    if (sk_acceptq_is_full(sk))
        goto exit_overflow;
……
exit_overflow:
    NET_INC_STATS_BH(LINUX_MIB_LISTENOVERFLOWS);
exit:
    NET_INC_STATS_BH(LINUX_MIB_LISTENDROPS);
此时该信息有记录,可以通过/proc/net/snmp查看该信息。
2、缓冲区满时丢包
tcp_data_queue
        if (eaten <= 0) {
queue_and_out:
            if (eaten < 0 &&
                (atomic_read(&sk->sk_rmem_alloc) > sk->sk_rcvbuf ||  同样是检测了一个套接口的接收缓存数量,超过限量尝试整理出存储空间,如果失败丢弃报文
                 !sk_stream_rmem_schedule(sk, skb))) {
                if (tcp_prune_queue(sk) < 0 ||
                    !sk_stream_rmem_schedule(sk, skb))
                    goto drop;
            }
            sk_stream_set_owner_r(skb, sk);
            __skb_queue_tail(&sk->sk_receive_queue, skb);
        }
……
drop:
        __kfree_skb(skb);
        return;
这里并没有记录,所以超过缓冲区的丢包不会记录。但是TCP有自己的流量控制和重传机制,所以丢包并不是问题。
3、限量的设置
这些限制可以通过set_sockopt接口来修改。
    {
        .ctl_name    = NET_CORE_WMEM_DEFAULT,
        .procname    = "wmem_default",
        .data        = &sysctl_wmem_default,
        .maxlen        = sizeof(int),
        .mode        = 0644,
        .proc_handler    = &proc_dointvec
    },
    {
        .ctl_name    = NET_CORE_RMEM_DEFAULT,
        .procname    = "rmem_default",
        .data        = &sysctl_rmem_default,
        .maxlen        = sizeof(int),
        .mode        = 0644,
        .proc_handler    = &proc_dointvec
    },
proc文件系统中也可以修改这些接口
[tsecer@Harry template]$ cat /proc/sys/net/core/rmem_default 
114688
[tsecer@Harry template]$ cat /proc/sys/net/core/wmem_default 
114688
[tsecer@Harry template]$

posted on 2019-03-07 09:12  tsecer  阅读(1437)  评论(0编辑  收藏  举报

导航