一、丢包
这个丢包不是网卡级别的丢包,在每个网卡中也会显示丢失的包的数据。这个一般是由于网卡在中断处理中需要通过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]$