CONNTRACK的tcp序列号调整扩展功能

当报文的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选项 */
    }
}

你可能感兴趣的:(c++,c,ubuntu,linux)