这篇文章主要解析常用的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_LD
和BPF_LDX
将k值存入A或者X中,使用BPF_JUMP
将A中的值与k或者X进行比较,实现指令跳转,可以跳转到下一步的过滤指令,或者跳转到BPF_RET
进行截取包长度的限定,如果截取包的长度为0,则代表未匹配。