BPF进阶 - BPF常用命令

这篇文章主要解析常用的BPF命令
参考文章:https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD | 4.7-RELEASE

上篇文章 BPF初探 - Android中BPF运用实例解析 介绍了Android源码中对BPF的运用,其中配置的BPF过滤规则看起来很简洁,但类似汇编代码的规则配置让初学者看起来非常难受,下面就对常用的指令进行解释。

基础知识

  在指令解析之前,为了能够更好的理解,我们需要先贯彻以下几点知识:
  
  1. 以太头长度定长,一共14个字节

/** if_ether.h*/
#define ETH_ALEN 6
struct ethhdr {
    unsigned char h_dest[ETH_ALEN];          // 6字节dest mac
    unsigned char h_source[ETH_ALEN];        // 6字节src mac
    __be16 h_proto;                          // 2字节protocol
} 

  2. ip头不定长,需要根据ip头中第一个字节后4位(BIG_ENDIAN)来确定

/** ip.h*/
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,
            version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,                     // IPv4|IPv6
            ihl:4;                         // ip头长度
#else
#error  "Please fix "
#endif
    __u8    tos;                           // 服务类型,代表包的优先级
    __be16  tot_len;                       // 整个包分节长度
    __be16  id;                            // 标识 
    __be16  frag_off;                      // 片偏移
    __u8    ttl;                           // 生存时间
    __u8    protocol;                      // 应用协议
    __sum16 check;                         // 校验和
    __be32  saddr;                         // 源地址
    __be32  daddr;                         // 目标地址
    /*The options start here. */           // 上面一共20字节,如果有可选选项,则会导致ip头长度超过20
};

  综上两点,我们可以得出,到ip头的偏移就是ether头的长度,到udp/tcp/icmp头的偏移需要动态计算ip头的长度。
 
BPF常用指令解析

  BPF一共有8种类型的指令,分别是 BPF_LD, BPF_LDX, BPF_ST, BPF_STX, BPF_ALU, BPF_JMP, BPF_RET, BPF_MISC

  BPF_LD  将值复制到累加器(A)

/**BPF_LD */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS, k)   A <- P[k:4]     // 将k字节偏移处往后4个字节存入A中
BPF_STMT(BPF_LD | BPF_H | BPF_ABS, k)   A <- P[k:2]     // 将k字节偏移处往后2个字节存入A中
BPF_STMT(BPF_LD | BPF_B | BPF_ABS, k)   A <- P[k:1]     // 将k字节偏移处往后1个字节存入A中
BPF_STMT(BPF_LD | BPF_W | BPF_IND, k)   A <- P[X+k:4]   // 将(X寄存器值与k的和)偏移处往后4个字节存入A中
BPF_STMT(BPF_LD | BPF_H | BPF_IND, k)   A <- P[X+k:2]   // 将(X寄存器值与k的和)偏移处往后2个字节存入A中
BPF_STMT(BPF_LD | BPF_B | BPF_IND, k)   A <- P[X+k:1]   // 将(X寄存器值与k的和)偏移处往后1个字节存入A中
BPF_STMT(BPF_LD | BPF_W | BPF_LEN)      A <- len        // 将包长度存存入A中
BPF_STMT(BPF_LD | BPF_IMM, k)           A <- k          // 将k值存入A中
BPF_STMT(BPF_LD | BPF_MEM, k)           A <- M[k]       // 将k地址内存的值存入A中

  BPF_LDX 将值复制到寄存器(X)

/**BPF_LDX */
BPF_STMT(BPF_LDX | BPF_W | BPF_IMM, k)  X <- k              // 将k值存入X中
BPF_STMT(BPF_LDX | BPF_W | BPF_MEM, k)  X <- M[k]           // 将k地址内存的值存入X中
BPF_STMT(BPF_LDX | BPF_W | BPF_LEN, k)  X <- len            // 将包长度存入X中

BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, k)  X <- 4*(P[k:1]&0xf) // 用于计算ip头的长度 --->
                                        // ---> 将偏移k处一个字节后4位转换成十进制乘以4

  BPF_ST  将A累加器中的值存入存储器中

/**BPF_ST */
BPF_STMT(BPF_ST, k)                     M[k] <- X       // 将A中的值存入存储器中

  BPF_STX 将X寄存器中的值存入存储器中

/**BPF_STX */
BPF_STMT(BPF_ST, k)                     M[k] <- X       // 将X中的值存入存储器中

  BPF_ALU 将A累加器中的值进行不同方式的计算并存入A中

/**BPF_ALU */
BPF_STMT(BPF_ALU | BPF_ADD | BPF_K, k)  A <- A + k      // A + k 后存入A中
BPF_STMT(BPF_ALU | BPF_SUB | BPF_K, k)  A <- A - k      // ..
BPF_STMT(BPF_ALU | BPF_MUL | BPF_K, k)  A <- A * k      
BPF_STMT(BPF_ALU | BPF_DIV | BPF_K, k)  A <- A / k
BPF_STMT(BPF_ALU | BPF_AND | BPF_K, k)  A <- A & k
BPF_STMT(BPF_ALU | BPF_OR | BPF_K, k)   A <- A | k
BPF_STMT(BPF_ALU | BPF_LSH | BPF_K, k)  A <- A << k
BPF_STMT(BPF_ALU | BPF_RSH | BPF_K, k)  A <- A >> k
BPF_STMT(BPF_ALU | BPF_ADD | BPF_X)     A <- A + X
BPF_STMT(BPF_ALU | BPF_SUB | BPF_X)     A <- A - X
BPF_STMT(BPF_ALU | BPF_MUL | BPF_X)     A <- A * X
BPF_STMT(BPF_ALU | BPF_DIV | BPF_X)     A <- A / X
BPF_STMT(BPF_ALU | BPF_AND | BPF_X)     A <- A & X
BPF_STMT(BPF_ALU | BPF_OR | BPF_X)      A <- A | X
BPF_STMT(BPF_ALU | BPF_LSH | BPF_X)     A <- A << X
BPF_STMT(BPF_ALU | BPF_RSH | BPF_X)     A <- A >> X
BPF_STMT(BPF_ALU | BPF_NEG)             A <- -A

  BPF_JMP 条件跳转,根据条件跳转到不同偏移的命令

/**BPF_JMP */
BPF_JUMP(BPF_JMP | BPF_JA, k)           pc += k         // 永远跳转到这条命令后偏移k的命令
// 如果A>k,则跳转到偏移jt的命令,否则跳转到偏移为jf的命令  
BPF_JUMP(BPF_JMP | BPF_JGT | BPF_K, k)  pc += (A > k) ? jt : jf 
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, k)  pc += (A >= k) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, k)  pc += (A == k) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, k) pc += (A & k) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JGT | BPF_X)     pc += (A > X) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JGE | BPF_X)     pc += (A >= X) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_X)     pc += (A == X) ? jt : jf
BPF_JUMP(BPF_JMP | BPF_JSET | BPF_X)    pc += (A & X) ? jt : jf

  BPF_RET 结束指令,设定接收的包的长度

/** BPF_RET*/
BPF_STMT(BPF_RET | BPF_A),                          // 接收长度为A累加器值的包
BPF_STMT(BPF_RET | BPF_K, k)                        // 接收长度为k的包

  BPF_MISC 将A中的值存入X中,或将X中的值存入A中

/** BPF_MISC*/
BPF_STMT(BPF_MISC | BPF_TAX)                X <- A      // 将A中的值存入X中
BPF_STMT(BPF_MISC | BPF_TXA)                A <- X      // 将X中的值存入A中

使用BPF_LDBPF_LDX将k值存入A或者X中,使用BPF_JUMP将A中的值与k或者X进行比较,实现指令跳转,可以跳转到下一步的过滤指令,或者跳转到BPF_RET进行截取包长度的限定,如果截取包的长度为0,则代表未匹配。

你可能感兴趣的:(Linux)