BPF高階 - 使用BPF過濾固定特徵報文

這篇文章主要介紹如何使用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));
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章