来自:http://abcdxyzk.github.io/blog/2013/09/06/kernel-net-sack/

static int
tcp_sacktag_write_queue(struct sock *sk, const struct sk_buff *ack_skb,
u32 prior_snd_una, struct tcp_sacktag_state *state)
{
struct tcp_sock *tp = tcp_sk(sk);
//ptr指向的就是SACK选项的起始位置
const unsigned char *ptr = (skb_transport_header(ack_skb) +
TCP_SKB_CB(ack_skb)->sacked);
//sp_wire在ptr的基础上前移两个字节,跳过选项的kind和len,指向SACK信息块的开始
struct tcp_sack_block_wire *sp_wire = (struct tcp_sack_block_wire *)(ptr+2);
//用于保存最后解析出来的SACK信息块,因为一个skb中最多可以携带4个,所以数组长度为4
struct tcp_sack_block sp[TCP_NUM_SACKS];
struct tcp_sack_block *cache;
struct sk_buff *skb;
//每个SACK信息块为8字节,所以num_sacks记录的是该skb中携带的SACK信息块的个数
int num_sacks = min(TCP_NUM_SACKS, (ptr[1] - TCPOLEN_SACK_BASE) >> 3);
int used_sacks;
bool found_dup_sack = false;
int i, j;
int first_sack_index; state->flag = 0;
state->reord = tp->packets_out;
//如果之前还没有SACK确认过的段,那么复位highest_sack域为发送队列的头部,即当前发送队列中
//最大的序号(该数据可能还尚未发送),
if (!tp->sacked_out) {
if (WARN_ON(tp->fackets_out))
tp->fackets_out = 0;
tcp_highest_sack_reset(sk);//因为当sacked_out为0,则说明没有通过sack确认的段,此时highest_sack自然就指向写队列的头。
//--->tcp_sk(sk)->highest_sack = tcp_write_queue_head(sk);
} } /*
D-SACK的判断是通过RFC2883中所描述的进行的。如果是下面两种情况,则说明收到了一个D-SACK。
1 如果SACK的第一个段所ack的区域被当前skb的ack所确认的段覆盖了一部分,则说明我们收到了一个d-sack,
而代码中也就是sack第一个段的起始序列号小于snd_una
2、如果sack的第二个段完全包含了第二个段,则说明我们收到了重复的sack
*/
found_dup_sack = tcp_check_dsack(sk, ack_skb, sp_wire,
num_sacks, prior_snd_una);
if (found_dup_sack)//检查是否有DSACK信息块,如果有,设置FLAG_DSACKING_ACK标记,表示输入段中有DSACK
state->flag |= FLAG_DSACKING_ACK; /* Eliminate too old ACKs, but take into
* account more or less fresh ones, they can
* contain valid SACK info.为接收方通告过的最大接收窗口。
* 如果SACK信息是很早以前的,直接丢弃
*/
if (before(TCP_SKB_CB(ack_skb)->ack_seq, prior_snd_una - tp->max_window))
return 0;
//当前根本就没有需要确认的段,所以也没有必要继续处理 if (!tp->packets_out)
goto out;
//后面会对sp[]数组按照SACK信息块中seq的大小做升序排列,first_sack_index
//记录了排序后原本输入段携带的第一个SACK信息块在sp[]中的位置,如果第一个
//SACK块无效,那么最终first_sack_index为-1
used_sacks = 0;
first_sack_index = 0;
for (i = 0; i < num_sacks; i++) {
//如果有DSACK,那么它一定在第一个位置。综合前面的判断设置dup_sack
bool dup_sack = !i && found_dup_sack; sp[used_sacks].start_seq = get_unaligned_be32(&sp_wire[i].start_seq);
sp[used_sacks].end_seq = get_unaligned_be32(&sp_wire[i].end_seq);
//如果SACK信息块无效,分别进行相关统计
if (!tcp_is_sackblock_valid(tp, dup_sack,
sp[used_sacks].start_seq,
sp[used_sacks].end_seq)) {
int mib_idx; if (dup_sack) {
if (!tp->undo_marker)
mib_idx = LINUX_MIB_TCPDSACKIGNOREDNOUNDO;
else
mib_idx = LINUX_MIB_TCPDSACKIGNOREDOLD;
} else {
/* Don't count olds caused by ACK reordering */
if ((TCP_SKB_CB(ack_skb)->ack_seq != tp->snd_una) &&
!after(sp[used_sacks].end_seq, tp->snd_una))
continue;
mib_idx = LINUX_MIB_TCPSACKDISCARD;
} NET_INC_STATS(sock_net(sk), mib_idx);
if (i == 0)//第一个SACK是无效的SACK,所以设置first_sack_index为-1
first_sack_index = -1;
continue;
} /* Ignore very old stuff early */
if (!after(sp[used_sacks].end_seq, prior_snd_una))
continue; used_sacks++;
} /* order SACK blocks to allow in order walk of the retrans queue
对实际使用的SACK块,按起始序列号,从小到大进行冒泡排序。*/
for (i = used_sacks - 1; i > 0; i--) {
for (j = 0; j < i; j++) {
if (after(sp[j].start_seq, sp[j + 1].start_seq)) {
swap(sp[j], sp[j + 1]); /* Track where the first SACK lock goes to */
if (j == first_sack_index)///注意first_sack_index会跟踪原始的第一个SACK信息块所在位置
first_sack_index = j + 1;
}
}
} skb = tcp_write_queue_head(sk);
state->fack_count = 0;
i = 0;
/*一旦收到对端的SACK信息,那么说明发生了丢包或者乱序,
而且这种状况可能往往无法 立即恢复,这意味着发送方会连续多次收到SACK信息,
而且这些SACK信息很可能是重复的。为了减少对发送队列的遍历次数,
这里发送方在用SACK信息块更新发送队列时采 用了cache机制。本质上也很简单,
就是将上一次的SACK信息块保存下来,在本次处理过程中,如果发现上一次已
经处理过了该范围的SACK,那么就可以跳过不处理。
cache //信息块就保存在tp->recv_sack_cache[]中
*/处理重传队列中的skb

重传队列中的skb有三种类型,分别是SACKED(S), RETRANS 和LOST(L),而每种类型所处理的数据包的个数分别保存在sacked_out, retrans_out 和lost_out中。

而处于重传队列的skb也就是会处于下面6中状态:

Valid combinations are: b
 * Tag  InFlight    Description
 * 0    1        - orig segment is in flight.inFlight也就是表示还在网络中的段的个数;没有ack的包
 * S    0        - nothing flies, orig reached receiver.
 * L    0        - nothing flies, orig lost by net.
 * R    2        - both orig and retransmit are in flight.
 * L|R    1        - orig is lost, retransmit is in flight.
 * S|R  1        - orig reached receiver, retrans is still in flight.
 * (L|S|R is logically valid, it could occur when L|R is sacked,
 *  but it is equivalent to plain S and code short-curcuits it to S.
 *  L|S is logically invalid, it would mean -1 packet in flight 8))
 *
重传队列中的skb的状态变迁是通过下面这几种事件来触发的:
These 6 states form finite state machine, controlled by the following events:
 * 1. New ACK (+SACK) arrives. (tcp_sacktag_write_queue())
 * 2. Retransmission. (tcp_retransmit_skb(), tcp_xmit_retransmit_queue())
 * 3. Loss detection event of two flavors:
 *    A. Scoreboard estimator decided the packet is lost.
 *       A'. Reno "three dupacks" marks head of queue lost.
 *       A''. Its FACK modification, head until snd.fack is lost.
 *    B. SACK arrives sacking SND.NXT at the moment, when the
 *       segment was retransmitted.
 * 4. D-SACK added new rule: D-SACK changes any tag to S.
 *
tcp socket的high_seq域,这个域是我们进入拥塞控制的时候最大的发送序列号,也就是snd_nxt.

if (!tp->sacked_out) {/* 如果之前没有SACK块 */
/* It's already past, so skip checking against it */
cache = tp->recv_sack_cache + ARRAY_SIZE(tp->recv_sack_cache);
} else {
cache = tp->recv_sack_cache;
/* Skip empty blocks in at head of the cache 上次的SACK 信息块保存在了数组的末尾n 个位置,
所以这里跳过开头的无效SACK信息*/
while (tcp_sack_cache_ok(tp, cache) && !cache->start_seq &&
!cache->end_seq)
cache++;
}
//下面的逻辑首先是检查cache和SACK信息块是否有交集,如果有,跳过当前SACK块,提高效率
while (i < used_sacks) {
u32 start_seq = sp[i].start_seq;
u32 end_seq = sp[i].end_seq;
bool dup_sack = (found_dup_sack && (i == first_sack_index));
struct tcp_sack_block *next_dup = NULL; if (found_dup_sack && ((i + 1) == first_sack_index))
next_dup = &sp[i + 1]; /* Skip too early cached blocks blocks如果cache的区间为[100, 200), 而当前SACK信息块的区间为[300, 400),
直接跳过这些cache块 */
while (tcp_sack_cache_ok(tp, cache) &&
!before(start_seq, cache->end_seq))
cache++; /* Can skip some work by looking recv_sack_cache?
cache和SACK块一定是有交集的*/
if (tcp_sack_cache_ok(tp, cache) && !dup_sack &&
after(end_seq, cache->start_seq)) {
//1. cache[100, 300), SACK[40, 440)
/* Head todo? */
if (before(start_seq, cache->start_seq)) {
skb = tcp_sacktag_skip(skb, sk, state,
start_seq);//前移发送队列,使得遍历指针知道seq为100的的地方
skb = tcp_sacktag_walk(skb, sk, next_dup,
state,//用[40, 100)即[start_seq, cache->start_seq)标记发送队列
start_seq,
cache->start_seq,
dup_sack);
} /* Rest of the block already fully processed? cache[100, 300), SACK[50, 200)SACK信息块的后半段已经全部被cache包含,
这部分已经标记过了,没必要重新标记,所以继续处理下一个SACK信息块 */
if (!after(end_seq, cache->end_seq))
goto advance_sp;
/*next_dup不为NULL的唯一一种情况就是输入段的DSACK信息块表示的确认范围被
后面的SACK信息块完全包裹,比如DSACK为[100, 200), 后面SACK为[50, 300),
只有这种情况,排序后,DSACK块才能排在SACK之后,这样next_dup才不为NULL
有可能DSACK和cache也有一部分重合
*///果start_seq < next_dup->start_seq < cache->start_seq,那么next_dup落在
//* (start_seq, cache->start_seq) 内的部分已经被上面的处理过了:)现在处理的next_dup的剩余部分
skb = tcp_maybe_skipping_dsack(skb, sk, next_dup,
state,
cache->end_seq); /* ...tail remains todo... */
if (tcp_highest_sack_seq(tp) == cache->end_seq) {
/* ...but better entrypoint exists! */
skb = tcp_highest_sack(sk);
if (!skb)/* 如果已经到了snd_nxt了,那么直接退出SACK块的遍历 */
break;
state->fack_count = tp->fackets_out;
cache++;
goto walk;
}
///跳过发送队列中那些序号小于cache->end_seq的skb,它们已经被标记过了
skb = tcp_sacktag_skip(skb, sk, state, cache->end_seq);
/* Check overlap against next cached too (past this one already) */
cache++;
continue;
}
//这个SACK信息块和cache块没有重叠,并且其start_seq一定大于所有的cache块的end_seq
if (!before(start_seq, tcp_highest_sack_seq(tp))) {
skb = tcp_highest_sack(sk);
if (!skb)
break;
state->fack_count = tp->fackets_out;
}
//跳过发送队列中那些序号小于start_seq的段
skb = tcp_sacktag_skip(skb, sk, state, start_seq); walk://更新序号位于[start_seq,end_seq)之间的skb的记分牌
skb = tcp_sacktag_walk(skb, sk, next_dup, state,
start_seq, end_seq, dup_sack); advance_sp:
i++;
}
、、

用上次缓存的tp->recv_sack_cache块来避免重复工作,提高处理效率。

主要思想就是,处理sack块时,和cache块作比较,如果它们有交集,说明交集部分已经处理过了,

不用再重复处理。

//更新cache,cache的大小同样是4个,并且每次收到SACK,上一次的cache内容都会清除,
//然后将本次接收的SACK块排序后的结果保存在cache中
/* Clear the head of the cache sack blocks so we can skip it next time */
for (i = 0; i < ARRAY_SIZE(tp->recv_sack_cache) - used_sacks; i++) {
tp->recv_sack_cache[i].start_seq = 0;
tp->recv_sack_cache[i].end_seq = 0;
}
for (j = 0; j < used_sacks; j++)
tp->recv_sack_cache[i++] = sp[j];
//更新乱序信息
if ((state->reord < tp->fackets_out) &&
((inet_csk(sk)->icsk_ca_state != TCP_CA_Loss) || tp->undo_marker))
tcp_update_reordering(sk, tp->fackets_out - state->reord, 0);
//更新计数器
tcp_verify_left_out(tp);
out: #if FASTRETRANS_DEBUG > 0
WARN_ON((int)tp->sacked_out < 0);
WARN_ON((int)tp->lost_out < 0);
WARN_ON((int)tp->retrans_out < 0);
WARN_ON((int)tcp_packets_in_flight(tp) < 0);
#endif
return state->flag;
}

tcp_sacktag_walk是遍历重传队列,找到对应需要设置的段,然后设置tcp_cb的sacked域为TCPCB_SACKED_ACKED,这里要注意,还有一种情况就是sack确认了多个skb,这个时候我们就需要合并这些skb,然后再处理

/*从参数skb指向的位置开始向后遍历发送队列,
更新序号位于[start_seq,end_seq)之间的skb的记分牌*/
static struct sk_buff *tcp_sacktag_walk(struct sk_buff *skb, struct sock *sk,
struct tcp_sack_block *next_dup,
struct tcp_sacktag_state *state,
u32 start_seq, u32 end_seq,
bool dup_sack_in)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *tmp; tcp_for_write_queue_from(skb, sk) {
int in_sack = 0;
bool dup_sack = dup_sack_in;
//已经遍历到了tp->sk_send_head,后面的skb还没有发送,结束处理
if (skb == tcp_send_head(sk))
break;
//因为发送队列中skb的序号是递增的,而当前skb的seq大于等于
//SACK块的末尾序号,说明该skb和SACK信息块已经没有交集了,
//而且发送队列后续skb也不会和该SACK信息块有交集了,结束处理
/* queue is in-order => we can short-circuit the walk early */
if (!before(TCP_SKB_CB(skb)->seq, end_seq))
break;
/*//假设用序号dseq和dend表示DSACK信息块的序号,
DSACK有两种情形:
1)dend<=snd_una,即DSACK是由已经ACK的数据段触发的;
2)dend>sud_una并且DSACK信息块的序号被后面的SACK信息块包含;
//情形1根本就无需更新记分牌,所以如果next_dup不为NULL,那么一定
//是情形2。此时,由于DSACK被SACK包围,所以一定是先处理SACK
//然后再处理DSACK,此时为了避免重复遍历发送队列,所以需要同时处理这 两个SACK信息块。
*/
/*下一个是DSACK块,需要先处理DSACK块。如果dend>=skb->seq,
//说明DSACK块和当前skb可能有交集*/
if (next_dup &&
before(TCP_SKB_CB(skb)->seq, next_dup->end_seq)) {
//传入的序号范围是DSACK的序号范围
in_sack = tcp_match_skb_to_sack(sk, skb,
next_dup->start_seq,
next_dup->end_seq);
//1)in_sack==0:压根就没有DSACK;
//2)DSACK无法确认skb;
//3)in_sack<0:DSACK导致了skb分片,但是分片失败了
if (in_sack > 0)
dup_sack = true;
} /* skb reference here is a bit tricky to get right, since
* shifting can eat and free both this skb and the next,
* so not even _safe variant of the loop is enough.
*/
if (in_sack <= 0) {
//对于情形1,没得说,这里用SACK检测。情形2和3也一样是因为SACK是DSACK的超集
tmp = tcp_shift_skb_data(sk, skb, state,
start_seq, end_seq, dup_sack);
if (tmp) {
if (tmp != skb) {
skb = tmp;
continue;
} in_sack = 0;
} else {
in_sack = tcp_match_skb_to_sack(sk, skb,
start_seq,
end_seq);
}
}
//结果小于0,说明skb分割失败,处理结束
if (unlikely(in_sack < 0))
break;
//如果最终检测到skb数据可以被确认,则标记该skb
if (in_sack) {
TCP_SKB_CB(skb)->sacked =
/* Mark the given newly-SACKed range as such, adjusting counters and hints.
用来设置对应的tag,这里所要设置的也就是tcp_cb的sacked域
值 为:
#define TCPCB_SACKED_ACKED      0x01 /* SKB ACK'd by a SACK block    
#define TCPCB_SACKED_RETRANS    0x02  /* SKB retransmitted       
#define TCPCB_LOST              0x04  /* SKB is lost         
#define TCPCB_TAGBITS           0x07  /* All tag bits         
#define TCPCB_EVER_RETRANS      0x80  /* Ever retransmitted frame
#define TCPCB_RETRANS     (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)
*/
tcp_sacktag_one(sk,
state,
TCP_SKB_CB(skb)->sacked,
TCP_SKB_CB(skb)->seq,
TCP_SKB_CB(skb)->end_seq,
dup_sack,
tcp_skb_pcount(skb),
&skb->skb_mstamp); if (!before(TCP_SKB_CB(skb)->seq,
tcp_highest_sack_seq(tp)))
tcp_advance_highest_sack(sk, skb);
} state->fack_count += tcp_skb_pcount(skb);
}
return skb;
} /* Avoid all extra work that is being done by sacktag while walking in
* a normal way 参数skb指向发送队列中的某个skb,该函数从skb开始向后遍历发送队列,
直到遍历到sk->sk_send_head(即下一个要发送的skb,
可能为NULL)或者skb的末尾序号
(假设序号范围为[seq1, end1))end1大于等于参数skip_to_seq为止,
返回终止时的skb指针。
该函数的作用就是跳过那些序号小于SACK信息块确认范围的skb。
*/
static struct sk_buff *tcp_sacktag_skip(struct sk_buff *skb, struct sock *sk,
struct tcp_sacktag_state *state,
u32 skip_to_seq)
{
tcp_for_write_queue_from(skb, sk) {
if (skb == tcp_send_head(sk))
break; if (after(TCP_SKB_CB(skb)->end_seq, skip_to_seq))
break; state->fack_count += tcp_skb_pcount(skb);
}
return skb;
}

note:SACK 选项的接收方在收到一个包后可能会被要求做大量处理。这种 N:1 的比例使 SACK 发送方可以打击非常强大的平台上的接收方。

然后攻击者发送一个充满 SACK 选项的包,目的是使另一方主机扫描整个队列以处理每个选项。一个指向队列末尾包的选项可能会迫使接收方的 TCP 协议栈遍历整个队列以判断此选项指向哪一个包。显然,攻击客户机可以根据自己的喜好发送任何 SACK 选项,但不太容易看出的一个问题是,它可以轻易控制被攻击方的重传队列的长度。SACK 选项的创建者可以控制接收者对接收到的每个选项执行的工作量,因为它也可以控制队列的大小。

最新文章

  1. 我的Hcharts的页面应用
  2. dagger2 备注
  3. mybatis 3的TypeHandler解析(null值的处理)
  4. iOS iOS9.0 的CoreLocation定位
  5. javaSE第二十五天
  6. jQuery实现的瀑布流效果, 向下滚动即时加载内容
  7. RabbitMQ 原文译02--工作队列
  8. 整理sed实战修改多行配置技巧
  9. Windows系统下的adb 配置
  10. SQLLoader8(加载的数据中有换行符处理方法)
  11. Flink Program Guide (1) -- 基本API概念(Basic API Concepts -- For Java)
  12. bootStrap事例代码
  13. 第四节:dingo/API 最新版 V2.0 之 Responses (连载)
  14. 【原】无脑操作:TypeScript环境搭建
  15. AWS 实战
  16. mysql 开发进阶篇系列 10 锁问题 (相同索引键值或同一行或间隙锁的冲突)
  17. centos6.7不联网的情况下安装配置本地yum源
  18. BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)
  19. jedis spring集成
  20. -27979 LoadRunner 错误27979 找不到请求表单 Action.c(73): Error -27979: Requested form not found

热门文章

  1. linux块设备驱动---程序设计(转)
  2. 多测师讲解自动化测试 _RF连接数据库_高级讲师肖sir
  3. 4-20mA模拟量采集
  4. linux(centos8):jmeter5.3并发测试实例(参数在范围内随机取值)
  5. centos8平台用NetworkManager/nmcli管理网络
  6. jquery ui,拖拽,dragsort
  7. js获取页面高度
  8. java安全编码指南之:ThreadPool的使用
  9. Java安全之Commons Collections3分析
  10. eclipse 开发常见问题集锦