不论是18版,还是37版,一开始都会从TCP的控制块中取出SACK选项的起始地址。

SACK选项的起始地址是保存在tcp_skb_cb结构的sacked项中的,那么这是在什么时候做的呢?

SACK块并不是总是合法的,非法的SACK块可能会引起处理错误,所以还需要进行SACK块的合法性检查。

本文主要内容:TCP首部中SACK选项的解析和地址的获取,SACK块的合法性检查。

Author:zhangskd @ csdn

SACK选项的地址

TCP_SKB_CB(skb)->sacked is initialized to offset corresponding to the start of the SACK option in the

TCP header for the segment received.

处理时机为:

tcp_rcv_established(),进入慢速路径时调用

| --> tcp_validate_incoming()

| --> tcp_fast_parse_options()

| --> tcp_parse_options()

在慢速路径中,有可能只带有TIMESTAMP选项,因此先用tcp_fast_parse_options()快速解析。

/* Fast parse options. This hopes to only see timestamps.
* If it is wrong it falls back on tcp_parse_options().
*/
static int tcp_fast_parse_options(struct sk_buff *skb, struct tcphdr *th, struct tcp_sock *tp, u8 **hvpp)
{
/* In the spirit of fast parsing, compare doff directly to constant values.
* Because equality is used, short doff can be ignored here.
*/
if (th->doff == (sizeof(*th) / 4)) { /* 没有带选项 */
tp->rx_opt.saw_tstamp = 0;
return 0; } else if (tp->rx_opt.tstamp_ok &&
th->doff == ((sizeof(*th) + TCPOLEN_TSTAMP_ALIGNED) / 4)) { /* 只带有时间戳选项 */
if (tcp_parse_aligned_timestamp(tp, th))
return 1;
} /* 如果以上的快速解析失败,则进行全面解析 */
tcp_parse_options(skb, &tp->rx_opt, hvpp, 1); return 1;
}
static int tcp_parse_aligned_timestamp(struct tcp_sock *tp, struct tcphdr *th)
{
__be32 *ptr = (__be32 *) (th + 1); /* 指向选项部分 */ /* 如果选项部分的前4个字节分别为:0x 01 01 08 0A */
if (*ptr == htonl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
| (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP)) { tp->rx_opt.saw_tstamp = 1;
++ptr; tp->rx_opt.rcv_tsval = ntohl(*ptr); /* 提取接收包的时间戳*/
++ptr; tp->rx_opt.rcv_tsecr = ntohl(*ptr); /* 提取接收包的回显值*/
return 1;
} return 0;
}

在慢速路径中,如果tcp_fast_parse_options()失败,则调用tcp_parse_options()全面解析TCP选项。

/* Look for tcp options. Normally only called on SYN and SYNACK packets.
* But, this can also be called on packets in the established flow when the fast version
* below fails.
*/
void tcp_parse_options(struct sk_buff *skb, struct tcp_options_received *opt_rx, u8 **hvpp, int estab)
{
unsigned char *ptr;
struct tcphdr *th = tcp_hdr(skb);
int length = (th->doff * 4) - sizeof(struct tcphdr); /* 选项总长度 */ ptr = (unsigned char *) (th + 1); /* 选项起始地址 */
opt_rx->saw_tstamp = 0; /* 此ACK有没有带时间戳接下来才知道 */ while (length > 0) {
int opcode = *ptr++; /* 选项kind */
int opsize; switch (opcode) {
case TCPOPT_EOL: /* 结束选项,不常见到 */
return; case TCPOPT_NOP: /* 填充选项 */
length--; /* 此选项只占一个字节 */
continue; default:
opsize = *ptr++; /* 此选项长度 */ if (opsize < 2) /* "silly options" */
return; /* 选项长度过小 */ if (opsize > length)
return; /* don't parse partial options */ switch (opcode) {
...
case TCPOPT_SACK_PERM:
if (opsize == TCPOLEN_SACK_PERM && th->syn &&
!estab && sysctl_tcp_sack) { opt_rx->sack_ok = 1; /* SYN包中显示支持SACK */
tcp_sack_reset(opt_rx); /* 清空dsack和num_sacks */
}
break; case TCPOPT_SACK:
if ((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
!((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
opt_rx->sack_ok) { /*保存SACK选项的起始地址偏移*/
TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *) th;
}
break;
...
}
}
}
}
/* TCP options */
#define TCPOPT_NOP 1 /* Padding */
#define TCPOPT_EOL 0 /* End of options */
#define TCPOPT_MSS 2 /* Segment size negotiating */
#define TCPOPT_WINDOW 3 /* Window Scaling */
#define TCPOPT_SACK_PERM 4 /* SACK Permitted */
#define TCPOPT_SACK 5 /* SACK Block */
#define TCPOPT_TIMESTAMP 8 /* Better RTT estimations/PAWS */ static inline void tcp_sack_reset(struct tcp_options_received *rx_opt)
{
rx_opt->dsack = 0;
rx_opt->num_sacks = 0;
} /* This is the max number of SACKS that we'll generate and process.
* It's safe to increase this, although since:
* size = TCPOLEN_SACK_BASE_ALIGNED(4) + n * TCPOLEN_SACK_PERBLOCK(8)
* only four options will fit in a standard TCP header
*/
#define TCP_NUM_SACKS 4 /* SACK块数最多为4 */

SACK块合法性检查

检查SACK块或者DSACK块是否合法。

2.6.24之前的版本没有检查SACK块的合法性,而某些非法的SACK块可能会触发空指针的引用。

在3.1版本之前有一个小bug,处理DSACK时会产生问题,修复非常简单:

@if (! after(end_seq, tp->snd_una)),把非去掉。

符合以下任一条件的SACK块是合法的:

1. sack块和dsack块:snd_una < start_seq < end_seq <= snd_nxt

2. dsack块:undo_marker <= start_seq < end_seq <= snd_una

3. dsack块:start_seq < undo_marker < end_seq <= snd_una 且 end_seq - start_seq <= max_window

/* SACK block range validation checks that the received SACK block fits to the
* expected sequence limits, i.e., it is between SND.UNA and SND.NXT.
*/
static int tcp_is_sackblock_valid(struct tcp_sock *tp, int is_dsack, u32 start_seq, u32 end_seq)
{
/* Too far in future, or reversed (interpretation is ambiguous)
* end_seq超过了snd_nxt,或者start_seq >= end_seq,那么不合法
*/
if (after(end_seq, tp->snd_nxt) || ! before(start_seq, end_seq))
return 0; /* Nasty start_seq wrap-around check (see comments above) */
* start_seq超过了snd_nxt
*/
if (! before(start_seq, tp->snd_nxt))
return 0; /* In outstanding window? This is valid exit for D-SACKs too.
* start_seq == snd_una is non-sensical (see comments above)
*/
if (after(start_seq, tp->snd_una))
return 1; /* 合法 */ if (! is_dsack || ! tp->undo_marker)
return 0; /* Then it's D-SACK, and must reside below snd_una completely.
* 注意在3.1以前这里是:! after(end_seq, tp->snd_una),是一个bug
*/
if (after(end_seq, tp->snd_una))
return 0; if (! before(start_seq, tp->undo_marker))
return 1; /* dsack块合法 */ /* Too old,DSACK块太旧了*/
if (! after(end_seq, tp->undo_marker))
return 0; /* Undo_marker boundary crossing */
return !before(start_seq, end_seq - tp->max_window);
}

TCP的核心系列 — SACK和DSACK的实现(三)的更多相关文章

  1. TCP的核心系列 — SACK和DSACK的实现(一)

    TCP的实现中,SACK和DSACK是比较重要的一部分. SACK和DSACK的处理部分由Ilpo Järvinen (ilpo.jarvinen@helsinki.fi) 维护. tcp_ack() ...

  2. TCP的核心系列 — SACK和DSACK的实现(二)

    和18版本相比,37版本的SACK和DSACK的实现做了很多改进,最明显的就是需要遍历的次数少了, 减少了CPU的消耗.37版的性能提升了,代码有大幅度的改动,逻辑也更加复杂了. 本文主要内容:37版 ...

  3. TCP的核心系列 — SACK和DSACK的实现(七)

    我们发送重传包时,重传包也可能丢失,如果没有检查重传包是否丢失的机制,那么只能依靠超时来恢复了. 37版本把检查重传包是否丢失的部分独立出来,这就是tcp_mark_lost_retrans(). 在 ...

  4. TCP的核心系列 — SACK和DSACK的实现(六)

    上篇文章中我们主要说明如何skip到一个SACK块对应的开始段,如何walk这个SACK块包含的段,而没有涉及到 如何标志一个段的记分牌.37版本把给一个段打标志的内容独立出来,这就是tcp_sack ...

  5. TCP的核心系列 — SACK和DSACK的实现(五)

    18版本对于每个SACK块,都是从重传队列头开始遍历.37版本则可以选择性的遍历重传队列的某一部分,忽略 SACK块间的间隙.或者已经cache过的部分.这主要是通过tcp_sacktag_skip( ...

  6. TCP的核心系列 — SACK和DSACK的实现(四)

    和18版本不同,37版本把DSACK的检测部分独立出来,可读性更好. 37版本在DSACK的处理中也做了一些优化,对DSACK的两种情况分别进行处理. 本文主要内容:DSACK的检测.DSACK的处理 ...

  7. TCP的核心系列 — ACK的处理(二)

    本文主要内容:tcp_ack()中的一些细节,如发送窗口的更新.持续定时器等. 内核版本:3.2.12 Author:zhangskd @ csdn 发送窗口的更新 什么时候需要更新发送窗口呢? (1 ...

  8. TCP的核心系列 — ACK的处理(一)

    TCP发送数据包后,会收到对端的ACK.通过处理ACK,TCP可以进行拥塞控制和流控制,所以 ACK的处理是TCP的一个重要内容.tcp_ack()用于处理接收到的ACK. 本文主要内容:TCP接收A ...

  9. TCP的核心系列 — 重传队列的更新和时延的采样(二)

    在tcp_clean_rtx_queue()中,并非对每个ACK都进行时延采样.是否进行时延采样,跟这个ACK是否为 重复的ACK.这个ACK是否确认了重传包,以及是否使用时间戳选项都有关系. 本文主 ...

随机推荐

  1. springMVC源码分析--SimpleControllerHandlerAdapter(三)

    上一篇博客springMVC源码分析--HandlerAdapter(一)中我们主要介绍了一下HandlerAdapter接口相关的内容,实现类及其在DispatcherServlet中执行的顺序,接 ...

  2. 快速索引 (对View的自定义)

    快速索引 (对View的自定义) 快速索引应用场景: 微信好友列表, 联系人通讯录, 应用管理, 文件管理等. 快速索引7步曲: *1. A-Z索引的绘制. * 2. 处理Touch事件. * 3. ...

  3. springMVC源码分析--DispatcherServlet请求获取及处理

    在之前的博客springMVC源码分析--容器初始化(二)DispatcherServlet中我们介绍过DispatcherServlet,是在容器初始化过程中出现的,我们之前也说过Dispatche ...

  4. Android Studio安装Genymotion插件

    Android Studio安装Genymotion插件 Eclipse就不介绍了,谷歌都已经放弃Eclipse了,你还在坚持什么. 安装Genymotion 官网:https://www.genym ...

  5. 【ShaderToy】水彩画

    写在前面 好久没有更新shadertoy系列了,我万万没想到有童鞋还惦记着它...之前说过希望可以一周更新一篇,现在看来是不怎么可能了,一个月更新一篇的希望比较大(不要再相信我了...) 我把之前实现 ...

  6. 在Linux环境下实现一个非常好的bash脚本框架

    为了方便我日常工作中的编译环境,免去我敲命令行所浪费的时间,我个人写了一个非常有用而又简单的脚本框架,该框架即可以完成的工程源码编译,也可以清除,拷贝等等操作,具体需要开发者自己来实现细节,我的框架思 ...

  7. Android Demo---如何敲出圆角的Button+圆角头像

    经常玩儿App的小伙伴都知道,APP上面有很多按钮都是圆角的,圆形给人感觉饱满,富有张力,不知道设计圆角按钮的小伙伴是不是和小编有着相同的想法`(*∩_∩*)′,听小编公司开发IOS的小伙伴说,他们里 ...

  8. shell入门之变量测试

    格式:test 测试条件 字符串测试: 注意空格: test str1 == str2 测试字符串是否相等 test str1 != str2 测试字符串是否不相等 test str1 测试字符串是否 ...

  9. Android初级教程获取手机系统联系人信息

    在手机内部,对联系人信息存在对应的数据库.我们创建的而联系人信息都存在这张表中.如下是对数据库的截图,我已经对表和应该注意的地方做了红笔标注: 好了,现在可以根据数据库里面的数据来写代码了. 代码如下 ...

  10. (七十九)MapKit的基本使用

    MapKit是苹果公司开发的用于显示地图和实现定位.导航的地图框架. MapKit View可以通过storyboard.xib创建,也可以通过代码直接创建. 需要注意的是,通过storyboard和 ...