【工具】依赖已捕获的tcpdump/wireshark码流快速模拟测试场景
初衷:
以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 <fstream> #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; string g_peerIp; int g_peerPort; vector<function<void(Client &)>> g_taskList; vector<function<void(Client &)>> g_loopTask; bool g_loopFlag = false; int g_loopInterval = 1; template<typename Func> int ReadFileLineByLine(const string &path, Func &func) { ifstream in(path); if (!in.is_open()) { return RET_FAILURE; } string line; while (getline(in, line)) { if (func(line) == RET_FAILURE) { return RET_FAILURE; } } return RET_SUCCESS; } int ReadConfigIni(const string path) { auto func = [&](string &line) { // 跳过空行、换行符、注释 if (line.empty() || line[0] == '#' || line[0] == '\r') { return RET_SUCCESS; } uint16_t pos = line.find(' '); if (pos == string::npos) { return RET_FAILURE; } uint16_t valStart = pos + 1; while (valStart < line.size() && line[valStart] == ' ') { valStart++; } if (valStart == line.size()) { return RET_FAILURE; } vector<function<void(Client &)>> &taskList = g_loopFlag ? g_loopTask : g_taskList; string paramName = line.substr(0, pos); if (paramName == "PeerAddr") { string peerAddr = line.substr(valStart, line.length() - valStart - 1); int sepPos = peerAddr.find(':'); g_peerIp = peerAddr.substr(0, sepPos); g_peerPort = stoi(peerAddr.substr(sepPos + 1, peerAddr.length() - sepPos - 1)); } else if (paramName == "Send") { string msg = line.substr(valStart, line.length() - valStart - 1); taskList.push_back([msg](Client &client) { client.Send(msg); }); } else if (paramName == "Recv") { taskList.push_back([](Client &client) { client.Recv(); }); } else if (paramName == "Sleep") { int time = stoi(line.substr(valStart, line.length() - valStart - 1).c_str()); taskList.push_back([time](Client &) { sleep(time); }); } else if (paramName == "LoopStart") { g_loopFlag = true; g_loopInterval = stoi(line.substr(valStart, line.length() - valStart - 1).c_str()); } else if (paramName == "LoopEnd") { g_loopFlag = false; auto func = [](Client &client) { while (true) { for (auto &task: g_loopTask) { task(client); } sleep(g_loopInterval); } }; g_taskList.push_back(func); } return RET_SUCCESS; }; return ReadFileLineByLine(path, func); } int main() { ReadConfigIni("/home/config.ini"); Client client(g_peerIp, g_peerPort); if (client.Connect() == RET_FAILURE) { return RET_FAILURE; } for (auto &task: g_taskList) { task(client); } return RET_SUCCESS; }
config.ini
PeerAddr 192.168.18.129:38000 Sleep 1 Send 474554202f20485454502f312e310d0a486f73743a203130332e38352e38342e3134313a38390d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b2078363429204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132302e302e302e30205361666172692f3533372e3336204564672f3132302e302e302e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f776562702c696d6167652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d302e370d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e392c656e3b713d302e382c656e2d47423b713d302e372c656e2d55533b713d302e360d0a436f6f6b69653a204a53455353494f4e49443d44453335413542373541323338444238344543333630464533413233304242380d0a49662d4d6f6469666965642d53696e63653a205361742c2032362041756720323032332030323a34323a313020474d540d0a0d0a Recv LoopStart 1 Send 474554202f20485454502f312e310d0a486f73743a203130332e38352e38342e3134313a38390d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e30202857696e646f7773204e542031302e303b2057696e36343b2078363429204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f3132302e302e302e30205361666172692f3533372e3336204564672f3132302e302e302e300d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f776562702c696d6167652f61706e672c2a2f2a3b713d302e382c6170706c69636174696f6e2f7369676e65642d65786368616e67653b763d62333b713d302e370d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174650d0a4163636570742d4c616e67756167653a207a682d434e2c7a683b713d302e392c656e3b713d302e382c656e2d47423b713d302e372c656e2d55533b713d302e360d0a436f6f6b69653a204a53455353494f4e49443d44453335413542373541323338444238344543333630464533413233304242380d0a49662d4d6f6469666965642d53696e63653a205361742c2032362041756720323032332030323a34323a313020474d540d0a0d0a Recv msg Sleep 3 LoopEnd