nbEasyTshark 05-08 实时获取网卡数据

05 获取地理信息

——非复吕蒙之三日,但见楚庄之飞鸣。

wireshark中是没有获取地理信息功能的,这算是一个新future

我们使用开源的数据库ip2region来做。

项目地址:https://github.com/lionsoul2014/ip2region

我们能拿到一个ip2region.xdb文件,如果要使用这类信息库文件,首先得加载它

xdb_search_t searcher("ip2region/ip2region.xdb");
searcher.init_content();

写个小demo测试一下

#include "../third_library/ip2region/xdb_search.h"
#include <string>
#include <iostream>

int main() {
    xdb_search_t searcher("../third_library/ip2region/ip2region.xdb");
    searcher.init_content();

    std::string ip = "211.21.92.114";
    std::string location = searcher.search(ip);

    std::cout << ip << ": " << location << std::endl;

    return 0;
}

compile:

g++ -std=c++14 .\02_ip2region.cpp

..\third_library\ip2region\xdb_search.cc -o ip2region_test.exe -lws2_32

输出:

211.21.92.114: 中国|0|台湾省|0|中华电信

好使~

region的格式是固定的:国家 | 区域 | 省份 | 城市 | ISP

归属地格式化

既然要用到第三方库,那么地理信息相关的功能应封装成一个工具类。ip2region_util;

之后我们的packet信息输出时需要增加源ip和目的ip的归属信息。

效果如图:

06 面向对象封装,添加日志功能

TsharkManager封装

为了方便维护,将与tshark有关的操作都封装成TsharkManager类。

在visual studio中,右键项目->添加->类,添加TsharkManager。

到目前为止,我们有关tshark的操作都封装到TsharkManager类中

我们之前做了哪些操作呢?

  • 我们可以读取离线文件的数据,逐行解析
  • 我们可以将读取的数据保存在数据结构中,在需要的时候取用

所以我们可以封装出以下类:

class TsharkManager
{
public:
    TsharkManager(std::string workDir);
    ~TsharkManager();

    bool analysisFile(std::string filePath);

    void printAllPacket();

    bool getPacketHexData(uint32_t frameNumber, std::vector<unsigned char>& data);

private:
    bool parseLine(std::string line, std::shared_ptr<Packet> packet);

private:
    std::string tsharkPath;
    std::string currentFilePath;

    IP2RegionUtil ip2regionUtil;

    std::unordered_map<uint32_t, std::shared_ptr<Packet>> allPackets;
};

日志框架loguru

当程序具有一定规模后,便需要引入日志模块。这样做有如下好处:

  1. 可以将日志按照严重程度分级,常见的日志分级方式如下:
  • debug: 较为verbose的信息
  • info: 往往是某个模块重要功能的详细信息
  • notice/warning:需要注意的信息
  • error:错误!
  1. c++中的std::cout在多线程环境下容易混乱
  2. 缺乏时间戳和文件信息,调试时不直观
  3. 性能问题

以上,比起自己造一个日志轮子,我们选择loguru作为第三方日志库。

GitHub – emilk/loguru: A lightweight C++ logging library

初始化:

    loguru::init(argc, argv);
    loguru::add_file("nb_easy_tshark.log", loguru::Append, loguru::Verbosity_MAX);

各级别打印:

 LOG_F(ERROR, "Failed to open file: %s", packet_file.c_str());
 LOG_F(WARNING, "Failed to open file: %s", packet_file.c_str());
 LOG_F(INFO, "Failed to open file: %s", packet_file.c_str());

07 获取编程网卡的枚举

现在我们知道tshark -D命令可以获取当前系统中的网卡枚举。

对比一下 Wireshark上的网卡名吧。

若是对标wireshark,实际上我们想要的是网卡名中最后()里的内容。

所以我们这里需要做一些字符串操作

  1. 过滤掉我们不需要的网卡,如ciscodump,etwdump…
  2. 获取tshark -D网卡的打印,而后得到id,name,和remark
    /* get name and remark
     * e.g: 6. \Device\NPF_{FFCB4D95-E737-4DCA-B016-522C9BA641B5} (WLAN)
     * id:6
     * name:\Device\NPF_{FFCB4D95-E737-4DCA-B016-522C9BA641B5}
     * remark:WLAN
     */

最终测试结果如下:

bingo~

08 网卡数据包采集分析

我们之前的分析都是基于离线数据包的分析,而现在我们需要和wireshark一样,可以实时分析从网卡上抓到的数据包的数据。

tshark -i WLAN

tashark 会抓取网卡上流经的数据报文,并加以打印。

既然我们要对数据包进行实时的分析,那么我们也就需要扩展TsharkManager的类函数

public:
    bool startCapture(std::string adapterName);
    bool stopCapture();

使用多线程抓包

新建一个线程用于实时抓包.

  • 数据面:实时抓包
  • 管理面:控制相关
bool TsharkManager::startCapture(std::string adapterName) {
    LOG_F(INFO, "start to capture, adapterName %s", adapterName.c_str());
    stopFlag = false;
    captureWorkerThread =  std::make_shared<std::thread>(&TsharkManager::captureWorkerThreadEntry,
        this, "\"" + adapterName + "\"");
    return true;
}

开始抓包

我们之前实现了分析离线数据包的函数 analysisFile, 现在只需要稍微改动一下就能实现实施抓取数据包的功能:

  1. tshark的参数,由-r改为-i adapter. 表示采集网卡数据
  2. 增加一些参数:
    1. -w:指定保存数据表
    2. -f: 指定保存为pcap格式
  3. 在while循环中增加了stopFlag。

停止抓包

bool TsharkManager::stopCapture() {
    LOG_F(INFO, "now stop capture pcap");
    stopFlag = true;
    captureWorkerThread->join();

    return true;
}

而后修改main函数适配即可。

实时抓包后打印:

无数据时如何退出?

如果网卡一直没有数据包,那么循环里的fgets会一直阻塞不返回,所以主线程去join它的时候会一直等待,程序会卡死,除非我们强制杀死进程。

如何解决这个问题:

  1. 可以使用非阻塞io和select/poll,发现有数据到来时才使用fget去尝试读取
volatile int running = 1;

void* data_thread_func(void* arg) {
    FILE* pipe = (FILE*)arg;
    int fd = fileno(pipe);
    
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    
    char buffer[1024];
    fd_set readfds;
    struct timeval timeout;
    
    while (running) {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        
        timeout.tv_sec = 0;
        timeout.tv_usec = 100000;
        
        int result = select(fd + 1, &readfds, NULL, NULL, &timeout);
        
        if (result > 0 && FD_ISSET(fd, &readfds)) {
            if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
                printf("Received: %s", buffer);
            }
        } else if (result == 0) {
            continue;
        } else {
            perror("select");
            break;
        }
    }
    
    fclose(pipe);
    return NULL;
}
  1. 既然无流量时fgets会阻塞,那么首先确认下fgets什么时候会正常返回?
  • 读到换行符\n,此时换行符也会被包含在返回的字符串中。
  • 读到指定的字符数n-1,并自动在末尾加上空字符\0
  • 到达文件末尾EOF.

因为没有数据,所以前两种都不满足,我们可以直接使用kill tshark进程的方式,杀死后pipe就会断开,fgets会到达EOF。

kill tshark

要杀死tshark,就需要

  1. 找到tshark的pid
  2. 封装kill的函数(win/linux 双平台)
    static FILE* PopenEx(std::string command, PID_T* pidOut = nullptr);
    static int Kill(PID_T pid);

而后在停止抓包的时候,提前杀死tshark进程即可。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇