kernel在mangle表中解析tcp协议,进行tcpoptstrip操作

(1)首先,了解kernel中iphdr/updhdr/tcphdr结构

struct iphdr:

// include/uapi/linux/ip.h
struct iphdr {
#if defined(__LITTLE_ENDAIN_BITFIELD)
    __u8 ihl:4,
        version:4;
#elif defined(__BIG_ENDAIN_BITFIELD)
    __u8 version:4,
        ihl:4;
#else
#error    "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;
    __be16    tot_len;
    __be16    id;
    __be16    frag_off;
    __u8      ttl;
    __u8      protocol;
    __sum16   check;
    __be32    saddr;
    __be32    daddr;
};

struct udphdr:

// include/uapi/linux/udp.h
struct udphdr {
    __be16    source;
    __be16    dest;
    __be16    len;
    __sum16   check;
};

struct tcphdr:

// include/uapi/linux/tcp.h
struct tcphdr {
    __be16    source;
    __be16    dest;
    __be32    seq;
    __be32    ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16    resl:4,
            doff:4,
            fin:1,
            syn:1,
            rst:1,
            psh:1,
            ack:1,
            urg:1,
            ece:1,
            cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16    doff:4,
            resl:4,
            cwr:1,
            ece:1,
            urg:1,
            ack:1,
            psh:1,
            rst:1,
            syn:1,
            fin:1;
#else
#error    "Adjust your <asm/byteorder.h> defines"
#endif
    __be16    window;
    __sum16    check;
    __be16    urg_ptr;
};

其次,看一下iphtr的结构图

|----|----|------|----------------|---------
|ver |ihl | tos  |     tot_len    |
|----------------|----------------|
|       id       |    frag_off    |
|---------|------|----------------|
|   ttl|  |protoc|      check     | 20 Byte
|---------|------|----------------|
|             saddr               |
|---------------------------------|
|             daddr               |
|----------------|----------------|---------
|             options             | 40 Bytes
|---------------------------------| Max

iphdr->version
    版本(4位),目前的协议版本号是4,因此IP有时也称作IPv4。

iphdr->ihl
    首部长度(4位):首部长度指的是IP层头部占32 bit字的数目(也就是IP层头部包含多少个4字节 -- 32位),包括任何选项。由于它是一个4比特字段,因此首部最长为60个字节。普通IP数据报(没有任何选择项)字段的值是5 <==> 5 * 32 / 8 = 5 * 4 = 20 Bytes

iphdr->tos

    服务类型字段(8位): 服务类型(TOS)字段包括一个3 bit的优先权子字段(现在已被忽略),4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS子字段分别代表:最小时延、最大吞吐量、最高可靠性和最
小费用。4 bit中只能设置其中1 bit。如果所有4 bit均为0,那么就意味着是一般服务。

iphdr->tot_len
    总长度字段(16位)是指整个IP数据报的长度,以字节为单位。利用首部长度字段和总长度字段,就可以知道 IP数据报中数据内容的起始位置和长度。由于该字段长16比特,所以IP数据报最长可达65535字节
     总长度字段是IP首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为46字节,但是IP数据可能会更短。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。

iphdr->id
    标识字段(16位)唯一地标识主机发送的每一份数据报。通常每发送一份报文它的值就会加1。

iphdr->frag_off (16位)
    frag_off域的低13位 -- 分段偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。由于该域有13个位,所以,每个数据报最多有8192个分段。因此,最大的数据报长度为65,536字节,比iphdr->tot_len域还要大1。

    iphdr->frag_off的高3位
    (1) 比特0是保留的,必须为0;
    (2) 比特1是“更多分片”(MF -- More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1。
    (3) 比特2是“不分片”(DF -- Don't Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。

      
   |---|-------------|
   |DM0|   offset    |
   |---|-------------|
   15 1312          0
       

iphdr->ttl
    TTL(time-to-live) -- 8位,生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃,并发送ICMP报文通知源主机。
    TTL(Time to live)域是一个用于限制分组生存期的计数器。这里的计数时间单位为秒,因此最大的生存期为255秒。在每一跳上该计数器必须被递减,而且,当数据报在一台路由器上排队时间较长时,该计数器必须被多倍递减。在实践中,它只是跳计数器,当它递减到0的时候,分组被丢弃,路由器给源主机发送一个警告分组。此项特性可以避免数据报长时间地逗留在网络中,有时候当路由表被破坏之后,这种事情是有可能发生的。

iphdr->protocol
    协议字段(8位): 根据它可以识别是哪个协议向IP传送数据。
    当网络层组装完成一个完整的数据报之后,它需要知道该如何对它进行处理。协议(Protocol)域指明了该将它交给哪个传输进程。TCP是一种可能,但是UDP或者其他的协议也是可能的。

iphdr->check
    首部检验和字段(16位)是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。 ICMP、IGMP、UDP和TCP在它们各自的首部中均含有同时覆盖首部和数据检验和码。
    为了计算一份数据报的IP检验和,首先把检验和字段置为0。然后,对首部中每个16 bit进行二进制反码求和(整个首部看成是由一串16 bit的字组成),结果存在检验和字段中。当收到一份IP数据报后,同样对首部中每个16 bit进行二进制反码的求和。由于接收方在计算过程中包含了发送方存在首部中的检验和,因此,如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该为全1。如果结果不是全1(即检验和错误),那么IP就丢弃收到的数据报。但是不生成差错报文,由上层去发现丢失的数据报并进行重传。

iphdr->saddr
    32位源IP地址
iphdr->daddr
    32位目的IP地址

再次,看一下tcphdr的结构图

|----------------|----------------|---------
|    source      |     dest       |
|----------------|----------------|
|               seq               |
|----------------|----------------|
|             ack_seq             | 20 Bytes
|----|----|------|----------------|
|doff|res1|      |    window      |
|----|----|------|----------------|
|     check      |    urg_ptr     |
|----------------|----------------|---------
|             options             | 40 Bytes
|---------------------------------| max

 tcphdr->source: 16位源端口
 tcphdr->dest: 16位目的端口
 tcphdr->seq: 表示此次发送的数据在整个报文段中的起始字节数。此序号用来标识从tcp发送端向tcp接收端发送的数据字节流,seq表示在这个报文段中的第一个数据字节。序号是32 bit的无符号数。为了安全起见,它的初始值是一个随机生成的数,它到达32位最大值后,又从零开始。
tcphdr->ack_seq: 指定的时下一个期望接收的字节,而不是已经正确接收到的最后一个字节。
tcphdr->doff: Tcp头长度,指明了在tcp头部包含多少个32位的字。此信息时必须的,因为options域的长度是可变的,所以整个Tcp头部的长度也是变化的。这个域实际上指明了数据部分在段内部的其起始地址(以32位字作为单位进行计量),因为这个数值正好是按字为单位的TCP头部的长度,所以,二者的效果是等同的

再次,由于要对tcp opt的结构进行strip,了解一下tcp opt个结构

Tcp options字段

由于doff占有4bit位,因此最大值为2^4-1 = 15,所有Head最大长度为15*4 = 60字节;而头部除了固定长度的20 bytes,剩下最大可以表示40 Bytes. Tcp options字段的一般数据结构如下:
 

Kind/Type(1字节)Length(1字节)Info(n字节)


选项第一个字段kind说明选项的类型。有的Tcp选项没有后面两个字段,仅包含1字节的kind字段。第二字段Length(如果有的话)指定该选项的总长度,该长度包含kind和length字段占据的两个字节。第三个字段info(如果有的话)是选项的具体信息。

 

常见的Tcp选项
Kind/TypeLengthNameReferenc描述&用途
0(TCPOPT_EOL)1EOLRFC793End of Option List 选项列表结束
1(TCPOPT_NOP)1NOPRFC793No Operation 空操作(填充标志,为了字节对齐)
2(TCPOPT_MSS)4(TCPOLEN_MSS)MSSRFC793Maximum Segment Size 最大segment长度
3(TCPOPT_WINDOW)3(TCPOLEN_WINDOW)WSOPTRFC793窗口扩大系数(Windows Scaling Factor)
4(TCPOPT_SACK_PERM)2(TCPOLEN_SACK_PERM)SACK-PermittedRFC2018表明支持SACK
5(TCPOPT_SACK)可变SACKRFC2018SACK Block(收到乱序数据)
8(TCPOPT_TIMESTAMP)10(TCPOLEN_TIMESTAMP)TSPOTRFC1323Timestamps
19(TCPOPT_MD5SIG)18(TCPOLEN_MD5SIG)TCP-MD5RFC2385MD5认证
284UTORFC5482User Timeout(超过一定闲置时间拆除连接)
29可变TCP-AORFC5925认证(可选用各种算法)
253/254(TCPOPT_EXP)可变ExperimentalRFC4727保留,用于科研实验

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以下为mangle表中进行tcpopt相关的函数

static unsigned int
tcpoptstrip_tg4(struct sk_buff *skb, const struct xt_action_param *par)
{
    return tcpopstrip_mangle_packet(skb, par, ip_hdrlen(skb),
                sizeof(struct iphdr) + sizeof(struct tcphdr));
}

///

static inline unsigned int
ip_hdrlen(const struct sk_buff *skb)
{
    return ip_hdr(skb)->inl * 4;
}

static inline struct iphdr *ip_hdr(const sk_buff *skb)
{
    return (struct iphdr *)skb_network_header(skb);
}

static inline unsigned char *skb_network_header(const struct sk_buff *skb)
{
    return skb->head + skb->network_header;
}

///


static unsigned int
tcpoptstrip_mangle_packet(struct sk_buff *skb,
                const struct xt_action_param *par,
                unsigned int tcphoff, unsigned int minlen)
{
    const struct xt_tcpoptstrip_target_info *info = par->targetinfo;
    unsigned int optl, i, j;
    struct tcphdr *tcph;
    u_int16_t n, o;
    u_int8_t *opt;
    int len, tcp_hdrlen;

    /* This is a fragment, no TCP header is available */
    if (par->fragoff != 0)
        return XT_CONTINUE;

    if (!skb_make_writable(skb, skb->len))
        return NF_DROP;

    len = skb->len - tcphoff;
    if (len < (int)sizeof(struct tcphdr))
        return NF_DROP;

    tcph = (struct tcphdr *)(skb_network_header(skb) + tcpoff);
    tcp_hdrlen = tcph->doff * 4;

    if (len < tcp_hrdlen)
        return NF_DROP;

    opt = (u_int8_t *)tcph;

    /*
     * Walk through all TCP options - if we find some option to remove,
     * set all octets to %TCPOPT_NOP and adjust checksum
     */
    for (i = sizeof(struct tcphdr); i < tcp_hdrlen -1; i += optl) {
        optl = optlen(opt, i);
        if (i + optl > tcp_hdrlen)
            break;

        if (!tcpoptstrip_test_bit(info->strip_bmap, opt[i]))
            continue;

        for (j = o; j < optl; ++j) {
            o = opt[i+j];
            n = TCPOPT_NOP;
            if ((i+j) % 2 == 0) {
                o <<= 8;
                n <<= 8;
            }
            inet_proto_csum_replace2(&tcph->check, skb, htons(o),
                            htons(n), false);
        }
        memset(opt + i, TCPOPT_NOP, optl);
    }

    return XT_CONTINUE;
}

static inline unsigned int
optlen(u_int8_t *opt, unsigned int offset)
{
    /* Beware zero-length options: make finite progress */
    if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0)
        return 1;
    else
        return opt[offset+1];        // 根据tcp options的格式,获取该options的长度
}

#define tcpoptstrip_test_bit(bmap, idx) \
    (((1U << idx & 31) & bmap[(idx) >> 5]) != 0)

static inline void inet_proto_csum_replace2(__sum16 *sum, struct sk_buff *skb,
                        __be16 from, __be16 to, bool pseudohdr)
{
    inet_proto_csum_replace4(sum, skb, (__force __be32)from,
                        (__force __be32)to, pseudohdr);
}

void inet_proto_csum_replace4(__sum16 *sum, struct sk_buff *skb,
                        __be32 from, __be32 to, bool pseudohdr)
{
    if (skb->ip_summed != CHECKSUM_PARTIAL) {
        csum_replace4(sum, from, to);
        if (skb->ip_summed == CHECKSUM_COMPLETE && pseudohdr)
            skb->cusm = ~csum_add(csum_sub(~(skb->csum),
                            (__force __wsum)from),
                    (__force __wsum)to);
    } else if (pseudohdr)
        *sum = ~csum_fold(csum_add(csum_sub(cusum_unfold(*sum),
                            (__force __wsum)from),
                        (__force __wsum)to));
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值