【工具】依赖已捕获的tcpdump/wireshark码流快速模拟测试场景

pond-flower / 2024-02-03 / 原文

初衷:

  以http为例,大多数场景下已有成熟的测试工具能够通过构造报文来模拟测试场景,如postman、jemter等,但对接的第三方可能具有某些特定或约定好的机制,针对此类机制构造用例往往要花不少时间

  而调测阶段通常可以通过tcpdump极快的获取到范例报文,因此如果可以直接根据范例报文来构造用例要轻松不少

思路:

  1、获取到范例报文(以下图为例)

       

  2、根据报文获取应用层码流信息(注意:仅需应用层码流信息)

  wireshak提供了便捷获取码流信息的方式

       

  选中应用层码流后右键->复制->... as a Hex Stream

  即可获得形如如下格式的十六进制码流:

474554202f20485454502f312e310d0a486f73743a203130332e38352e38342e3134313a38390d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b2078363429204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132302e302e302e30205361666172692f3533372e3336204564672f3132302e302e302e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f776562702c696d6167652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d302e370d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e392c656e3b713d302e382c656e2d47423b713d302e372c656e2d55533b713d302e360d0a436f6f6b69653a204a53455353494f4e49443d44453335413542373541323338444238344543333630464533413233304242380d0a49662d4d6f6469666965642d53696e63653a205361742c2032362041756720323032332030323a34323a313020474d540d0a0d0a

  3、接下来就是基础的网络基础知识(socket()->connect()->send()->recv())

  注意:send时需要提前将十六进制码流转换为字节码:

    (1)初始化ascii码表

    (2)根据第二步中获取的十六进制码流将其转换为字节码

示例源码如下:

CommonHeader.h

#ifndef TESTAPP_COMMONHEADER_H
#define TESTAPP_COMMONHEADER_H

#include <array>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>

#include <iostream>

#include <algorithm>
#include <numeric>

#include <climits>
#include <cstdarg>

using namespace std;

static constexpr int INVALID = -1;
static constexpr int RECV_BUFFER_SIZE = 10240;

static const int RET_FAILURE = -1;
static const int RET_SUCCESS = 0;

static const int BUFF_SIZE_1024 = 1024;

static void Print(const char *str, ...)
{
    char buff[BUFF_SIZE_1024] = {0};
    va_list values;
    va_start(values, str);
    vsnprintf(buff, BUFF_SIZE_1024 - 1, str, values);
    va_end(values);
    cout << buff << endl;
}

#endif

Util.h

#ifndef TESTAPP_UTIL_H
#define TESTAPP_UTIL_H

#include "CommonHeader.h"

class Util {
public:
    static Util &Instance() {
        static Util instance;
        return instance;
    }

    uint32_t HexToDec(u_char c) {
        if (c >= '0' && c <= '9') {
            return c - '0';
        }

        if (c >= 'a' && c <= 'f') {
            return c - 'a' + 10;
        }

        Print("Hex to decimal [%s] failed", c);
        return RET_FAILURE;
    }

    int TransalatePacket(string packet, string &output) {
        if (packet.length() % 2 != 0) {
            Print("Packet length [%lu] invalid", packet.length());
            return RET_FAILURE;
        }

        for (uint32_t i = 0; i < packet.length(); i += 2) {
            uint32_t high = HexToDec(packet[i]);
            if (high == RET_FAILURE) {
                return RET_FAILURE;
            }

            uint32_t low = HexToDec(packet[i + 1]);
            if (low == RET_FAILURE) {
                return RET_FAILURE;
            }

            auto it = m_asciiMap.find(high * 16 + low);
            if (it == m_asciiMap.end()) {
                Print("[%u] not exist in ascii map", high * 16 + low);
                return RET_FAILURE;
            }

            output += it->second;
        }

        return RET_SUCCESS;
    }

private:
    Util() {
        // 以空格字符作为基准生成ascii码表
        u_char space = ' ';
        for (uint32_t i = 0; i <= 255; ++i) {
            m_asciiMap[i] = space + (i - 32);
        }
    }

    unordered_map<uint32_t, char> m_asciiMap;
};

#endif

 

Client.h

#ifndef TESTAPP_CLIENT_H
#define TESTAPP_CLIENT_H

#include "CommonHeader.h"

class Client
{
public:
    Client(string peerIp, int peerPort);

    ~Client();

    int Connect();

    int Send(const string &msg, bool raw = true);

    int Recv();

private:
    int m_socket {INVALID};
    string m_peerIp;
    int m_peerPort { INVALID };
    char m_recvBuffer[RECV_BUFFER_SIZE] {};
};

#endif

Client.cpp

#include <cstdio>
#include <cstring>

#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "Util.h"
#include "Client.h"

Client::Client(string peerIp, int peerPort) :
        m_peerIp(peerIp),
        m_peerPort(peerPort) {
}

Client::~Client() {
    if (m_socket != INVALID) {
        close(m_socket);
    }
}

int Client::Connect() {
    if (m_socket != INVALID) {
        close(m_socket);
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(m_peerPort);
    addr.sin_addr.s_addr = inet_addr(m_peerIp.c_str());

    m_socket = socket(AF_INET, SOCK_STREAM, 0);

    if (connect(m_socket, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) != 0) {
        perror("connect");
        return RET_FAILURE;
    }

    return RET_SUCCESS;
}

int Client::Send(const string &msg, bool raw) {
    string output;
    if (raw) {
        Util::Instance().TransalatePacket(msg, output);
    } else {
        output = msg;
    }

    if (m_socket == INVALID) {
        return RET_FAILURE;
    }

    int ret = send(m_socket, output.c_str(), output.length(), 0);
    if (ret == RET_FAILURE) {
        perror("send");
        return RET_FAILURE;
    }

    Print("Send %d bytes to peer [%s:%d]", ret, m_peerIp.c_str(), m_peerPort);
    return RET_SUCCESS;
}

int Client::Recv() {
    if (m_socket == INVALID) {
        return RET_FAILURE;
    }

    memset(m_recvBuffer, 0, sizeof(m_recvBuffer));
    int ret = recv(m_socket, m_recvBuffer, sizeof(m_recvBuffer), 0);
    if (ret < 0) {
        perror("recv");
        return RET_FAILURE;
    }

    Print("Recv %d bytes from peer [%s:%d]", ret, m_peerIp.c_str(), m_peerPort);
    return RET_SUCCESS;
}

使用范例:

main.cpp

#include <csignal>
#include "Client.h"

using namespace std;

int main()
{
    Client client("127.0.0.1", 38000);
    if (client.Connect() == RET_FAILURE) {
        return RET_FAILURE;
    }

    string packet = "474554202f20485454502f312e310d0a486f73743a203130332e38352e38342e3134313a38390d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b2078363429204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132302e302e302e30205361666172692f3533372e3336204564672f3132302e302e302e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f776562702c696d6167652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d302e370d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e392c656e3b713d302e382c656e2d47423b713d302e372c656e2d55533b713d302e360d0a436f6f6b69653a204a53455353494f4e49443d44453335413542373541323338444238344543333630464533413233304242380d0a49662d4d6f6469666965642d53696e63653a205361742c2032362041756720323032332030323a34323a313020474d540d0a0d0a";
    client.Send(packet);

    client.Recv();

    sleep(10);
    return RET_SUCCESS;
}