当报文的tcp负载长度发生变化的时候(记住这里强调的是tcp负载的长度),就会影响发送方向(被修改的报文是谁发出的,那么这里的发送发现就指该方向)的发送序列号和应答方向报文的应答序列号。比较常见的是ALG功能,syn代理功能需要序列号调整。tcp序列号发生变化后,与序列号相关的sack选项也需要进行调整。
seqadj扩展功能
连接跟踪使用seqadj扩展功能实现序列号调整,在连接跟踪上添加一个序列号调整控制块nf_conn_seqadj:
/**
* struct nf_ct_seqadj - sequence number adjustment information
* @correction_pos: position of the last TCP sequence number modification 上次序列号修改的位置
* @offset_before: sequence number offset before last modification 序列号偏移,在上一次修改之后
* @offset_after: sequence number offset after last modification 序列号偏移,在最后一次修改之后
*/
struct nf_ct_seqadj {
u32 correction_pos;/* 最后一次修改tcp负载长度的报文原始的序列号 */
s32 offset_before; /* 最后一次的上一次修改报文的tcp负载累积长度 */
s32 offset_after; /* 最后一次修改tcp报文后的累积长度 */
};
struct nf_conn_seqadj {
struct nf_ct_seqadj seq[IP_CT_DIR_MAX];
};
使用下面的图来解释一下nf_ct_seqadj成员的意思:
1.连接跟踪创建时如果存在helper扩展功能或者启动了synproxy都会为ct添加seqadj扩展控制块。helper添加seqadj扩展功能时会进行初始化,将成员初始化为0。假设第一个syn报文的序列号为5000,那么其seqadj扩展控制块的内容如上图左边的值,即全为0(上图只显示了一个方向,另外一个方向类似)。
2.当第一次发生tcp负载长度变化时的报文的序列号是10000,假设报文tcp负载变长了10个字节,此时会设置nf_ct_seqadj控制块:
correction_pos=10000
offset_before=0
offset_after=10
3.当第二次发生tcp负载长度变化时的报文的序列号是20000,假设本报文的tcp负载长度变短了5个字节,此时会设置nf_ct_seqadj控制块:
correction_pos=20000
offset_before=10
offset_after=5
初始化seqadj控制块
在nf_nat_setup_info函数构建nat信息时,如果连接跟踪信息发生变化,并且ct存在help,那么会为该ct添加一个seqadj控制块
/* 根据提供的nat类型以及范围进行nat五元组修改 */
unsigned int
nf_nat_setup_info(struct nf_conn *ct,
const struct nf_nat_range *range,
enum nf_nat_manip_type maniptype)
{
...
nf_ct_invert_tuplepr(&curr_tuple,
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
/* 根据请求方向的五元组获取nat后的请求方向的五元组 */
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
/* 新的请求方向的五元组与原来的五元组不一样,则需要改变应答方向的五元组 */
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
struct nf_conntrack_tuple reply;
/* Alter conntrack table so will recognize replies. */
/* 根据新的五元组得到应答方向的新的五元组 */
nf_ct_invert_tuplepr(&reply, &new_tuple);
/* 替换应答方向的五元组 */
nf_conntrack_alter_reply(ct, &reply);
/* Non-atomic: we own this at the moment. */
if (maniptype == NF_NAT_MANIP_SRC)
ct->status |= IPS_SRC_NAT;
else
ct->status |= IPS_DST_NAT;
/* 判断该连接是否存在help,如果存在则必须添加seq-adj扩展功能 */
if (nfct_help(ct) && !nfct_seqadj(ct))
if (!nfct_seqadj_ext_add(ct))
return NF_DROP;
}
...
return NF_ACCEPT;
}
static inline struct nf_conn_seqadj *nfct_seqadj_ext_add(struct nf_conn *ct)
{
return nf_ct_ext_add(ct, NF_CT_EXT_SEQADJ, GFP_ATOMIC);
}
void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp)
{
unsigned int newlen, newoff, oldlen, alloc;
struct nf_ct_ext *old, *new;
struct nf_ct_ext_type *t;
/* Conntrack must not be confirmed to avoid races on reallocation. */
WARN_ON(nf_ct_is_confirmed(ct));
old = ct->ext;
if (old) {
if (__nf_ct_ext_exist(old, id))
return NULL;
oldlen = old->len;
} else {
oldlen = sizeof(*new);
}
rcu_read_lock();
t = rcu_dereference(nf_ct_ext_types[id]);
if (!t) {
rcu_read_unlock();
return NULL;
}
newoff = ALIGN(oldlen, t->align);
newlen = newoff + t->len;
rcu_read_unlock();
alloc = max(newlen, NF_CT_EXT_PREALLOC);
kmemleak_not_leak(old);
new = __krealloc(old, alloc, gfp);
if (!new)
return NULL;
if (!old) {
memset(new->offset, 0, sizeof(new->offset));
ct->ext = new;
} else if (new != old) {
kfree_rcu(old, rcu);
rcu_assign_pointer(ct->ext, new);
}
new->offset[id] = newoff;
new->len = newlen;
//初始化为0
memset((void *)new + newoff, 0, newlen - newoff);
return (void *)new + newoff;
}
tcp负载长度发生变化
在ftp传输PORT命令或者PASV的应答时会进行alg处理,如果使能了nat则会修改PORT命令或者PASV的应答的内容,导致tcp负载发生变化。
/* Generic function for mangling variable-length address changes inside
* NATed TCP connections (like the PORT XXX,XXX,XXX,XXX,XXX,XXX
* command in FTP).
*
* Takes care about all the nasty sequence number changes, checksumming,
* skb enlargement, ...
*
* */
bool __nf_nat_mangle_tcp_packet(struct sk_buff *skb,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo,
unsigned int protoff,
unsigned int match_offset,
unsigned int match_len,
const char *rep_buffer,
unsigned int rep_len, bool adjust)
{
const struct nf_nat_l3proto *l3proto;
struct tcphdr *tcph;
int oldlen, datalen;
if (!skb_make_writable(skb, skb->len))
return false;
if (rep_len > match_len &&
rep_len - match_len > skb_tailroom(skb) &&
!enlarge_skb(skb, rep_len - match_len))
return false;
SKB_LINEAR_ASSERT(skb);
tcph = (void *)skb->data + protoff;
oldlen = skb->len - protoff;
/* 修改报文内容,对于ftp来说修改的是PORT/PASV命令的参数,所以需要传入参数的起始地址match_offset和和参数的长度match_len
* 以及新的参数的起始地址rep_buffer和长度rep_len */
mangle_contents(skb, protoff + tcph->doff*4,
match_offset, match_len, rep_buffer, rep_len);
datalen = skb->len - protoff;
/* 重新计算校验码 */
l3proto = __nf_nat_l3proto_find(nf_ct_l3num(ct));
l3proto->csum_recalc(skb, IPPROTO_TCP, tcph, &tcph->check,
datalen, oldlen);
/* 长度发生改变,需要调整序列号,match_len为原始的PORT命令内容长度,rep_len为修改后的长度 */
/* 记住这里还没有变更tcp头的序列号,所以tcph->seq还是原始的值 */
if (adjust && rep_len != match_len)
nf_ct_seqadj_set(ct, ctinfo, tcph->seq,
(int)rep_len - (int)match_len);//变化长度,变长为正,变短为负
return true;
}
int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
__be32 seq, s32 off)
{
struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);/* 获取序列号调整控制块 */
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct nf_ct_seqadj *this_way;
if (off == 0)
return 0;
if (unlikely(!seqadj)) {
WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n");
return 0;
}
set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
spin_lock_bh(&ct->lock);
this_way = &seqadj->seq[dir];/* 获取本方向的序列号信息,刚开始的时候是三者都为0 */
if (this_way->offset_before == this_way->offset_after ||//只有初始化的时候会相等,说明是tcp负载长度第一次发生变化
before(this_way->correction_pos, ntohl(seq))) {/* 新的序列号大于上一次的调整,后续调整 */
this_way->correction_pos = ntohl(seq);/* 设置本次tcp负载发生变化的报文原始序列号 */
this_way->offset_before = this_way->offset_after;/* 将上一次修改后的序列号长度变化值覆盖掉上上次的 */
this_way->offset_after += off;/* 更新本次修改后的累加序列号变化长度 */
}
spin_unlock_bh(&ct->lock);
return 0;
}
在confirm函数中进行序列号的调整
confirm的优先级为NF_IP_PRI_CONNTRACK_CONFIRM,最低的优先级,也就是说最后执行的函数。
static unsigned int ipv4_confirm(void *priv,
struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ctinfo == IP_CT_RELATED_REPLY)
goto out;
/* adjust seqs for loopback traffic only in outgoing direction */
// 调整序列号,为什么只调整outgoing direction?没想明白。
if (test_bit(IPS_SEQ_ADJUST_BIT, &ct->status) &&
!nf_is_loopback_packet(skb)) {
if (!nf_ct_seq_adjust(skb, ct, ctinfo, ip_hdrlen(skb))) {
NF_CT_STAT_INC_ATOMIC(nf_ct_net(ct), drop);
return NF_DROP;
}
}
out:
/* We've seen it coming out the other side: confirm it */
return nf_conntrack_confirm(skb);
}
/* TCP sequence number adjustment. Returns 1 on success, 0 on failure */
int nf_ct_seq_adjust(struct sk_buff *skb,
struct nf_conn *ct, enum ip_conntrack_info ctinfo,
unsigned int protoff)
{
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
struct tcphdr *tcph;
__be32 newseq, newack;
s32 seqoff, ackoff;
struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
struct nf_ct_seqadj *this_way, *other_way;
int res = 1;
this_way = &seqadj->seq[dir];
other_way = &seqadj->seq[!dir];
if (!skb_make_writable(skb, protoff + sizeof(*tcph)))
return 0;
tcph = (void *)skb->data + protoff;
spin_lock_bh(&ct->lock);
//这个判断非常关键,一共有7中情况,见下面详细分析
//这里采用的是after,没有等于号,也就是说tcph->seq==this_way->correction_pos
//使用seqoff = this_way->offset_before,这符合实际。
if (after(ntohl(tcph->seq), this_way->correction_pos))/* 新报文序列号是在最新修改之后,所以使用after,否则使用before */
seqoff = this_way->offset_after;
else
seqoff = this_way->offset_before;
newseq = htonl(ntohl(tcph->seq) + seqoff);/* 新的序列号等于原始序列号加上偏移 */
/* 修正tcp校验码 */
inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
pr_debug("Adjusting sequence number from %u->%u\n",
ntohl(tcph->seq), ntohl(newseq));
tcph->seq = newseq;
if (!tcph->ack)/* 没有设置应答标志的话,直接退出 */
goto out;
/* 调整确认序列号,确认序列号应该与反方向的发送序列号的偏移进行匹配,
* 判断该报文是在上次修改之前的报文,还是之后的报文。
* other_way->correction_pos记录的是发送方向发生改变的报文的原始序列号。而tcph->ack_seq是接收方对修改序列号
* 后的报文的应答,这里减去other_way->offset_before后即为对原始发送序列号报文的应答序列号。
*/
if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
other_way->correction_pos))
ackoff = other_way->offset_after;
else
ackoff = other_way->offset_before;
newack = htonl(ntohl(tcph->ack_seq) - ackoff);
inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
false);
pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
ntohl(newack));
tcph->ack_seq = newack;
//进行sack选项的调整。
res = nf_ct_sack_adjust(skb, protoff, tcph, ct, ctinfo);
out:
spin_unlock_bh(&ct->lock);
return res;
}
情况1:
当前还没有发生过tcp负载长度变化,after(ntohl(tcph->seq), this_way->correction_pos)为真,seqoff = this_way->offset_after;也就是为0,不会实际修改tcph->seq。
情况2:
当前已经发生过一次tcp负载长度变化,after(ntohl(tcph->seq), this_way->correction_pos)为真,seqoff = this_way->offset_after;也就是为10,修改tcph->seq=tcph->seq+10。
情况3:
当前已经发生过一次tcp负载长度变化,但是当前的报文的序列号为6000,小于this_way->correction_pos。这种情况就是报文迷路了导致的,after(ntohl(tcph->seq), this_way->correction_pos)为假,seqoff = this_way->offset_before;也就是为0,不会实际修改tcph->seq。
情况4:
当前已经发生过两次tcp负载长度变化,after(ntohl(tcph->seq), this_way->correction_pos)为真,seqoff = this_way->offset_after;也就是为5,修改tcph->seq=tcph->seq+5。
情况5:
当前已经发生过两次tcp负载长度变化,但是当前的报文的序列号为16000,小于this_way->correction_pos。这种情况就是报文迷路了导致的,after(ntohl(tcph->seq), this_way->correction_pos)为假,seqoff = this_way->offset_before;也就是为10,会实际修改tcph->seq=tcph->seq+10。
情况6:
当前已经发生过两次tcp负载长度变化,但是当前的报文的序列号为6000,小于this_way->correction_pos。这种情况就是报文严重迷路了导致的,after(ntohl(tcph->seq), this_way->correction_pos)为假,seqoff = this_way->offset_before;也就是为10,会实际修改tcph->seq=tcph->seq+10。这种修改是错误的,因为序列号6000的报文不需要修改序列号。也就是说netfilter不能正确处理两次tcp负载长度变化之前的迷路报文。
应答序列号的调整
应答序列号表示接受方期望收到的下一个字节序列号。它等于接收方收到的报文的发送序列号加上报文的长度。接收方还可以合并应答,也就是说应答报文不一定和接收的报文对应起来。 而且发送方在接收应答序列号时,只关注比当前已经应答过的序列号大的序列号即可。
正常情况下一对一应答报文
情况1:
当前发送方向还没有发生过tcp负载长度变化,接收方收到的tcph->seq=6000,假设报文长度为100,那么应答序列号为tcph->ack_seq=6100。after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为0,不会修改tcph->ack_seq。
情况2:
当前发送方向已经发生过一次tcp负载长度变化,接收方收到的tcph->seq=10000,假设报文长度为100。假设本次应答报文就是应答了序列号为10000的报文,由于长度边长了10,所以应答序列号为tcph->ack_seq=10110。tcph->ack_seq-other_way->offset_before=10110-0=10110大于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为10,修改tcph->ack_seq=tcph->ack_seq-10=10100。这个值与发送方的正常的期望应答序列号一致,没有问题。
情况3:
当前发送方向已经发生过一次tcp负载长度变化,接收方收到的tcph->seq=16010(netfilter给增加了10个字节),假设报文长度为100。假设本次应答报文就是应答了序列号为16000的报文,应答序列号为tcph->ack_seq=16110。tcph->ack_seq-other_way->offset_before=16110-0=16110大于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为10,修改tcph->ack_seq=tcph->ack_seq-10=16100。这个值与发送方的正常的期望应答序列号一致,没有问题。
情况4:
当前发送方向已经发生过两次tcp负载长度变化,假设本次应答序列号为20000的报文,假设报文长度为100。那么接收方收到的报文的序列号为20010(netfilter给增加一个10个字节),由于本次报文的长度在netfilter中减少了5个字节,所以实际接收方收到的报文长度为95个字节,应答序列号为tcph->ack_seq=20105。tcph->ack_seq-other_way->offset_before=20105 - 5=20100大于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为5,修改tcph->ack_seq=tcph->ack_seq-5=26105-5=26100。这个值与发送方的正常的期望应答序列号一致,没有问题。
情况5:
当前发送方向已经发生过两次tcp负载长度变化,接收方收到的tcph->seq=26005(netfilter给增加了5个字节序列号),假设报文长度为100。假设本次应答报文就是应答了序列号为26000的报文,应答序列号为tcph->ack_seq=26105。tcph->ack_seq-other_way->offset_before=26105 - 10=26095大于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为5,修改tcph->ack_seq=tcph->ack_seq-5=26105-5=26100。这个值与发送方的正常的期望应答序列号一致,没有问题。
从前面五种情况来说,netfilter不会导致应答序列号超过发送方发送的最后一个字节+1。这是符合我们的预期的。前面没有说明合并应答的情况,实际也是满足要求的。
正常情况下非一对一应答,应答相比于发送有延迟
情况1:
当前发送方向已经发生过一次tcp负载长度变化,发送方情况如上图所示,此时应答方最大应答到序列号为6000。假设本次应答接下来1000个字节。那么应答序列号tcph->ack_seq=7000。tcph->ack_seq-other_way->offset_before=7000 - 0=7000小于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为假,seqoff = this_way->offset_before为0,修改tcph->ack_seq不会发生变化,没有问题。
情况2:
当前发送方向已经发生过一次tcp负载长度变化,发送方情况如上图所示,此时应答方最大应答到序列号为6000。假设发送方发送的序列号已经到了15000了。本次应答接下来5000个字节。那么应答序列号tcph->ack_seq=11000。该应答序列号已经包含了对发生长度变化的10000序列号报文的应答。因为该报文在netfilter中增加了10个字节,所以实际应答应该要比序列号小10个字节,即真实到发送端的应答序列号为11000-10=10090。tcph->ack_seq-other_way->offset_before=11000 - 0=11000大于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为真,seqoff = this_way->offset_after为10,修改tcph->ack_seq=tcph->ack_seq-10=10090,与预期相符,没有问题。
情况3:
当前发送方向已经发生过两次tcp负载长度变化,发送方情况如上图所示,此时应答方最大应答到序列号为6000。假设发送方发送的序列号已经到了25000了。本次应答接下来2000个字节。那么应答序列号tcph->ack_seq=8000。该应答序列号没有包含了对发生长度变化的10000序列号报文的应答,所以实际应答应该要比序列号不变,即真实到发送端的应答序列号为8000。tcph->ack_seq-other_way->offset_before=8000- 5=7095小于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为假,seqoff = this_way->offset_before为10,修改tcph->ack_seq=tcph->ack_seq-10=7095,与预期不相符,有点问题,发生了报文的部分应答问题。
情况4:
当前发送方向已经发生过两次tcp负载长度变化,发送方情况如上图所示,此时应答方最大应答到序列号为6000。假设发送方发送的序列号已经到了25000了。本次应答接下来5000个字节。那么应答序列号tcph->ack_seq=11000。该应答序列号已经包含了对发生长度变化的10000序列号报文的应答。因为该报文在netfilter中增加了10个字节,所以实际应答应该要比序列号小10个字节,即真实到发送到的应答序列号为11000-10=10090。tcph->ack_seq-other_way->offset_before=11000 - 0=11000小于other_way->correction_pos。所以after(ntohl(tcph->ack_seq) - other_way->offset_before,other_way->correction_pos) 为假,seqoff = this_way->offset_before为10,修改tcph->ack_seq=tcph->ack_seq-10=10090,与预期相符,没有问题。
迷路的应答报文
迷路应答报文与延迟应答类似,只不过发送方收到应答报文时,对应的字节已经被应答过了,属于重复应答。
sack选项的序列号调整
sack选项简介:
kind=4是选择性确认(Selective Acknowledgment,SACK)选项。
TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP模块只重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改
/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。
kind=5是SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。
sack序列号调整实现分析:
/* TCP SACK sequence number adjustment */
/* tcp协议还需要调整sack选项 */
static unsigned int nf_ct_sack_adjust(struct sk_buff *skb,
unsigned int protoff,
struct tcphdr *tcph,
struct nf_conn *ct,
enum ip_conntrack_info ctinfo)
{
unsigned int dir, optoff, optend;
struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
optoff = protoff + sizeof(struct tcphdr);
optend = protoff + tcph->doff * 4;
if (!skb_make_writable(skb, optend))
return 0;
dir = CTINFO2DIR(ctinfo);
while (optoff < optend) {
/* Usually: option, length. */
unsigned char *op = skb->data + optoff;
switch (op[0]) {
case TCPOPT_EOL:
return 1;
case TCPOPT_NOP:
optoff++;
continue;
default:
/* no partial options */
if (optoff + 1 == optend ||//没有数据
optoff + op[1] > optend ||//长度异常
op[1] < 2)//长度异常
return 0;
if (op[0] == TCPOPT_SACK &&//sack选项
op[1] >= 2+TCPOLEN_SACK_PERBLOCK &&//长度不许大于10个字节,即至少有一个sack块。
((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0)//长度减去2必须是TCPOLEN_SACK_PERBLOCK的整数倍。
nf_ct_sack_block_adjust(skb, tcph, optoff + 2,
optoff+op[1],
&seqadj->seq[!dir]);/* sack处理的是应答序列号,所以是反方向的序列号控制块 */
optoff += op[1];
}
}
return 1;
}
sack序列号调整与应答序列号调整类似:
/* Adjust one found SACK option including checksum correction */
static void nf_ct_sack_block_adjust(struct sk_buff *skb,
struct tcphdr *tcph,
unsigned int sackoff,
unsigned int sackend,
struct nf_ct_seqadj *seq)
{
while (sackoff < sackend) {
struct tcp_sack_block_wire *sack;
__be32 new_start_seq, new_end_seq;
sack = (void *)skb->data + sackoff;//获取sack块
/* 起始序列号 */
if (after(ntohl(sack->start_seq) - seq->offset_before,
seq->correction_pos))
new_start_seq = htonl(ntohl(sack->start_seq) -
seq->offset_after);
else
new_start_seq = htonl(ntohl(sack->start_seq) -
seq->offset_before);
//结束序列号
if (after(ntohl(sack->end_seq) - seq->offset_before,
seq->correction_pos))
new_end_seq = htonl(ntohl(sack->end_seq) -
seq->offset_after);
else
new_end_seq = htonl(ntohl(sack->end_seq) -
seq->offset_before);
pr_debug("sack_adjust: start_seq: %u->%u, end_seq: %u->%u\n",
ntohl(sack->start_seq), ntohl(new_start_seq),
ntohl(sack->end_seq), ntohl(new_end_seq));
/* 增量校验和 */
inet_proto_csum_replace4(&tcph->check, skb,
sack->start_seq, new_start_seq, false);
inet_proto_csum_replace4(&tcph->check, skb,
sack->end_seq, new_end_seq, false);
/* 修改 */
sack->start_seq = new_start_seq;
sack->end_seq = new_end_seq;
sackoff += sizeof(*sack);/* 调到下一个sack选项 */
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。