1 名词解释
SLE: Sequence Left Edge of already acknowledged data when Selective Acknowledgments are used. 即已收到tcp数据的左边界。
SRE: Sequence Right Edge of already acknowledged data when Selective Acknowledgments are used. 即已收到tcp数据的右边界。
2 使用场景
2.1 丢包
SACK在数据丢包需要重传时起作用。比如,服务器已发送的数据为1~34454个包,但是,客户端只收到了“1~22774,28614~34454”这些序列的包,也就是说“22775~28613”这些包已经丢了。这个时候,客户端会向服务器请求发送回馈包,说我收到了seq为22774的包,同时也乱序收到了"SLE为28614,SRE为34454"的包。那么,服务器就知道,接着从seq=22775的包开始发送,发送到seq=28613的包的时候,就不用在发送seq=28614的包了,因为客户端已经收到了。
如果ACK中不带SLE和SRE会怎样呢?那服务器就会重发从"22775"开始之后的所有的包,包括其实客户端已经收到的"28614~34454"序号的包,那就浪费网络带宽了,不是么。
2.2 乱序
当出现 SLE、SRE 的 SACK 报文并不能判别一定发生了丢包,如果从2、3层诊断确实没有发生丢包,此时可以判断:
1、没有配置rps,多个收报软中断运行各个 cpu 核上,即同一 tcp 链路报文分别在不同的核上处理,各个核上处理报文速率有快慢,导致乱序。
2、配置了rps,但是配置可能出错,需要核对一下。
本质:如果相同五元组的TCP报文被随机分配到不同的 core上处理,此时就会出现乱序的结果。
2.2.1 硬件中断处理代码
static int netif_rx_internal(struct sk_buff *skb)
{
int ret;
net_timestamp_check(netdev_tstamp_prequeue, skb);
trace_netif_rx(skb);
if (static_key_false(&generic_xdp_needed)) {
int ret;
preempt_disable();
rcu_read_lock();
ret = do_xdp_generic(rcu_dereference(skb->dev->xdp_prog), skb);
rcu_read_unlock();
preempt_enable();
/* Consider XDP consuming the packet a success from
* the netdev point of view we do not want to count
* this as an error.
*/
if (ret != XDP_PASS)
return NET_RX_SUCCESS;
}
#ifdef CONFIG_RPS
if (static_key_false(&rps_needed)) {
struct rps_dev_flow voidflow, *rflow = &voidflow;
int cpu;
preempt_disable();
rcu_read_lock();
cpu = get_rps_cpu(skb->dev, skb, &rflow);
if (cpu < 0)
cpu = smp_processor_id();
ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
rcu_read_unlock();
preempt_enable();
} else
#endif
//没有rps
{
unsigned int qtail;
ret = enqueue_to_backlog(skb, get_cpu(), &qtail);
put_cpu();
}
return ret;
}
=====================================================================
/*
* enqueue_to_backlog is called to queue an skb to a per CPU backlog
* queue (may be a remote CPU queue).
*/
static int enqueue_to_backlog(struct sk_buff *skb, int cpu,
unsigned int *qtail)
{
struct softnet_data *sd;
unsigned long flags;
unsigned int qlen;
sd = &per_cpu(softnet_data, cpu);
local_irq_save(flags);
rps_lock(sd);
if (!netif_running(skb->dev))
goto drop;
qlen = skb_queue_len(&sd->input_pkt_queue);
//skb_flow_limit:查看rx队列中是否还有空间
if (qlen <= netdev_max_backlog && !skb_flow_limit(skb, qlen)) {
if (qlen) {
enqueue:
/*队列还有空间将数据包添加到input_pkt_queue队列中*/
__skb_queue_tail(&sd->input_pkt_queue, skb);
input_queue_tail_incr_save(sd, qtail);
rps_unlock(sd);
local_irq_restore(flags);
return NET_RX_SUCCESS;
}
/* Schedule NAPI for backlog device
* We can use non atomic operation since we own the queue lock
*/
if (!__test_and_set_bit(NAPI_STATE_SCHED, &sd->backlog.state)) {
if (!rps_ipi_queued(sd))
____napi_schedule(sd, &sd->backlog);//唤醒sotf_irq处理报文
}
goto enqueue;
}
drop:
sd->dropped++;
rps_unlock(sd);
local_irq_restore(flags);
atomic_long_inc(&skb->dev->rx_dropped);
kfree_skb(skb);
return NET_RX_DROP;
}
说明:网卡队列大小可以通过 /proc/sys/net/core/netdev_max_backlog 修改
sack报文如下:
这里还有一点关于 wireshark 的疑问:图中的SACK报文的ack=16590226,sack_left = 16616506,sack_right=16617966。很显然sack的片段并没有被覆盖,为啥会被标记成D-SACK ???