這篇文章主要介紹如何使用BPF過濾固定特徵報文
參考文章:https://www.freebsd.org/cgi/man.cgi?query=bpf&sektion=4&manpath=FreeBSD | 4.7-RELEASE
前兩篇文章分別介紹了BPF在Android中的運用實例,以及BPF規則指令的解析,相信大家對BPF及其規則都有了大致的瞭解。現在我們來看看如何運用BPF來過濾固定特徵的報文,從這個過程中加深對BPF規則的瞭解和運用。
IP過濾
/**
* 構建源ip和目標ip過濾的socket filter,並綁定在創建的socket上
* @param socket_fd 已創建的socket fd
* @param src_ip 需要過濾的源ip
* @param dest_ip 需要過濾的目標ip
*/
void attachSocketFilter(int socket_fd, uint32_t src_ip, uint32_t dest_ip) {
uint32_t ip_offset = sizeof(ether_header);
std::vector<sock_filter> filter_code;
// 源ip過濾
uint32_t saddr_offset = ip_offset + offsetof(iphdr, saddr);
filter_code.push_back(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, saddr_offset));
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, src_ip, 3, 4));
// 目標ip過濾
uint32_t daddr_offset = ip_offset + offsetof(iphdr, daddr);
filter_code.push_back(BPF_STMT(BPF_LD | BPF_W | BPF_ABS, saddr_offset));
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, src_ip, 0, 1));
// Fail or Success
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0xffff));
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0));
struct sock_fprog filter = {
(uint16_t) filter_code.size(), filter_code.data()
};
if (setsockopt(socket_fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
printf("Attach socket filter failed: %s", strerror(errno));
}
}
傳輸層協議過濾
/**
* 構建傳輸層協議過濾的socket filter,並綁定在創建的socket上
* @param socket_fd 已創建的socket fd
* @param protocol 傳輸層協議 IPPROTO_UDP | IPPROTO_TCP | IPPROTO_ICMP
*/
void attachSocketFilter(int socket_fd, uint32_t protocol) {
uint32_t ip_offset = sizeof(ether_header);
std::vector<sock_filter> filter_code;
// 傳輸層協議過濾
uint32_t protocol_offset = ip_offset + offsetof(iphdr, protocol);
filter_code.push_back(BPF_STMT(BPF_LD | BPF_B | BPF_ABS, protocol_offset ));
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, protocol, 0, 1));
// Fail or Success
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0xffff));
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0));
struct sock_fprog filter = {
(uint16_t) filter_code.size(), filter_code.data()
};
if (setsockopt(socket_fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
printf("Attach socket filter failed: %s", strerror(errno));
}
}
端口過濾
/**
* 構建源端口和目標端口過濾的socket filter,並綁定在創建的socket上
* @param socket_fd 已創建的socket fd
* @param src_port 需要過濾的源端口
* @param dest_port 需要過濾的目標端口
*/
void attachSocketFilter(int socket_fd, uint32_t src_port, uint32_t dest_port) {
uint32_t ip_offset = sizeof(ether_header);
std::vector<sock_filter> filter_code;
... // 需要對傳輸層協議進行過濾,假設我們這裏已經對protocol過濾,且目標是tcp
// 源端口過濾
uint32_t sport_offset = ip_offset + offsetof(tcphdr, source);
filter_code.push_back(BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset));
filter_code.push_back(BPF_STMT(BPF_LD | BPF_H | BPF_IND, sport_offset));
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, src_port, 3, 4));
// 目標端口過濾
uint32_t dport_offset = ip_offset + offsetof(tcphdr, dest);
filter_code.push_back(BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset));
filter_code.push_back(BPF_STMT(BPF_LD | BPF_H | BPF_IND, dport_offset ));
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, dest_port, 0, 1));
// Fail or Success
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0xffff));
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0));
struct sock_fprog filter = {
(uint16_t) filter_code.size(), filter_code.data()
};
if (setsockopt(socket_fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
printf("Attach socket filter failed: %s", strerror(errno));
}
}
數據內容過濾
/**
* 構建數據過濾的socket filter,並綁定在創建的socket上
* @param socket_fd 已創建的socket fd
* @param idents 需要過濾的數據段
* @param ident_offset_in_data 需要過濾的數據在包數據中的偏移
* @param ident_len 需要過濾的數據長度
*/
void attachSocketFilter(int socket_fd, uint8_t *idents, uint32_t ident_offset_in_data
, uint_32 ident_len) {
uint32_t ip_offset = sizeof(ether_header);
... // 需要對傳輸層協議進行過濾,假設我們這裏已經對protocol過濾,且目標是tcp
// 計算到數據的偏移
// 計算方式:ether_header + iphdr + tcphdr這三個的長度和
// 計算ip頭長度並存入X寄存器中
filter_code.push_back(BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset));
// tcp頭長度定義在ack_seq之後的一個字節中的前四位,由於和其他內容共享一個字節,
// 所以不好直接計算其offset,這裏通過ack_seq來計算
uint32_t tcplen_indirect_offset = ip_offset + offsetof(tcphdr, ack_seq) + 4;
// 將tcphdr長度的真實偏移存入A中
filter_code.push_back(BPF_STMT(BPF_LD | BPF_B | BPF_IND, tcplen_indirect_offset));
// 移位計算tcphdr長度,與4*(P[k:1]&0xf)如出一轍
filter_code.push_back(BPF_STMT(BPF_ALU | BPF_RSH | BPF_K, 4));
filter_code.push_back(BPF_STMT(BPF_ALU | BPF_LSH | BPF_K, 2));
// 將iphdr長度與tcphdr長度相加存入A中
filter_code.push_back(BPF_STMT(BPF_ALU | BPF_ADD | BPF_X));
// ident的非直接偏移,除去iphdr和tcphdr的長度後的偏移
uint32_t ident_indirect_offset = ip_offset + ident_offset_in_data;
// 將A中值(iphdr+tcphdr)存入X中,方便之後將A用於累加計算
filter_code.push_back(BPF_STMT(BPF_MISC | BPF_TXA));
for (uint32_t i = 0; i < ident_len; i++) {
uint32_t ident= idents[i];
// 將ident對應包中的偏移存入A中
filter_code.push_back(BPF_STMT(BPF_LD | BPF_B | BPF_IND, ident_indirect_offset + (i + 1)));
// 對比ident偏移處字節和ident,true則繼續,否則跳到jf指令((ident_len - i - 1) * 2 + 1);
filter_code.push_back(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ident, 0, (ident_len - i - 1) * 2 + 1));
}
// Fail or Success
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0xffff));
filter_code.push_back(BPF_STMT(BPF_RET | BPF_K, 0));
struct sock_fprog filter = {
(uint16_t) filter_code.size(), filter_code.data()
};
if (setsockopt(socket_fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
printf("Attach socket filter failed: %s", strerror(errno));
}
}