You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用C#将仅含UDP载荷的日志文件转换为pcap文件

我来帮你搞定这个问题!要把仅含UDP载荷的日志转成.pcap文件,核心是补全pcap格式要求的完整数据包结构——你的日志只有UDP载荷,而pcap需要包含链路层、网络层、传输层的头部信息才能被Wireshark这类工具识别。下面是具体的C#实现方案:

核心原理:PCAP格式的组成

PCAP文件分为两部分:

  • 全局文件头:描述pcap的版本、捕获链路类型等基础信息
  • 数据包记录:每个记录包含数据包的时间戳、长度,以及完整的数据包内容(以太网头 + IP头 + UDP头 + 载荷)

因为你的日志只有UDP载荷,所以我们需要手动构造缺失的头部信息。

C#实现步骤
  1. 定义PCAP和网络协议的结构体(用于二进制序列化)
  2. 实现校验和计算方法(IP和UDP校验和是可选但推荐的)
  3. 读取日志中的UDP载荷,为每个载荷补全所有头部
  4. 将构造好的内容写入PCAP文件
完整代码示例

首先,我们需要定义对应协议的结构体(注意字节序和内存布局):

using System;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;

// PCAP全局文件头
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PcapGlobalHeader
{
    public uint MagicNumber;     // 固定0xa1b2c3d4(小端字节序)
    public ushort VersionMajor;  // 版本号主版本:2
    public ushort VersionMinor;  // 版本号次版本:4
    public int ThisZone;         // 时区偏移,设0即可
    public uint SigFigs;         // 时间戳精度,设0即可
    public uint SnapLen;         // 最大捕获长度,设65535
    public uint Network;         // 链路类型,1=以太网
}

// PCAP单个数据包记录头
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct PcapPacketHeader
{
    public uint TsSec;           // 时间戳(秒,从1970-01-01开始)
    public uint TsUsec;          // 时间戳(微秒)
    public uint CaptureLength;   // 捕获到的数据包长度
    public uint OriginalLength;  // 原始数据包长度
}

// 以太网帧头
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct EthernetHeader
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] DestMac;       // 目的MAC地址
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
    public byte[] SrcMac;        // 源MAC地址
    public ushort EtherType;     // 上层协议类型,0x0800=IPv4
}

// IPv4头部
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IpHeader
{
    public byte VersionAndIhl;   // 版本(4位)+头部长度(4位),0x45=IPv4+20字节头
    public byte Tos;             // 服务类型,设0
    public ushort TotalLength;   // IP包总长度(IP头+UDP头+载荷)
    public ushort Id;            // IP标识,可自增
    public ushort FlagsOffset;   // 标志+片偏移,设0
    public byte Ttl;             // 生存时间,设64
    public byte Protocol;        // 上层协议,17=UDP
    public ushort Checksum;      // IP校验和
    public uint SrcIp;           // 源IP地址(uint格式)
    public uint DestIp;          // 目的IP地址(uint格式)
}

// UDP头部
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct UdpHeader
{
    public ushort SrcPort;       // 源端口
    public ushort DestPort;      // 目的端口
    public ushort Length;        // UDP包总长度(UDP头+载荷)
    public ushort Checksum;      // UDP校验和(可选)
}

// UDP校验和需要的IP伪头部
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IpPseudoHeader
{
    public uint SrcIp;
    public uint DestIp;
    public byte Zero;
    public byte Protocol;
    public ushort UdpLength;
}

接下来实现辅助工具方法:

// 结构体转字节数组
private static byte[] StructToBytes(object obj)
{
    int size = Marshal.SizeOf(obj);
    byte[] bytes = new byte[size];
    IntPtr ptr = Marshal.AllocHGlobal(size);
    Marshal.StructureToPtr(obj, ptr, false);
    Marshal.Copy(ptr, bytes, 0, size);
    Marshal.FreeHGlobal(ptr);
    return bytes;
}

// 计算校验和(用于IP和UDP)
private static ushort CalculateChecksum(byte[] data)
{
    uint sum = 0;
    int idx = 0;
    while (idx < data.Length)
    {
        sum += idx + 1 < data.Length ? BitConverter.ToUInt16(data, idx) : data[idx];
        idx += 2;
    }
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += sum >> 16;
    return (ushort)~sum;
}

// 计算UDP校验和(需要IP伪头部)
private static ushort CalculateUdpChecksum(IpHeader ipHeader, UdpHeader udpHeader, byte[] payload)
{
    var pseudoHeader = new IpPseudoHeader
    {
        SrcIp = ipHeader.SrcIp,
        DestIp = ipHeader.DestIp,
        Zero = 0,
        Protocol = ipHeader.Protocol,
        UdpLength = udpHeader.Length
    };

    byte[] pseudoBytes = StructToBytes(pseudoHeader);
    byte[] udpBytes = StructToBytes(udpHeader);
    
    var checksumBuffer = new byte[pseudoBytes.Length + udpBytes.Length + payload.Length];
    Buffer.BlockCopy(pseudoBytes, 0, checksumBuffer, 0, pseudoBytes.Length);
    Buffer.BlockCopy(udpBytes, 0, checksumBuffer, pseudoBytes.Length, udpBytes.Length);
    Buffer.BlockCopy(payload, 0, checksumBuffer, pseudoBytes.Length + udpBytes.Length, payload.Length);
    
    return CalculateChecksum(checksumBuffer);
}

// 十六进制字符串转字节数组(如果日志是十六进制格式)
private static byte[] HexToBytes(string hexStr)
{
    hexStr = hexStr.Replace(" ", "").Replace("-", "");
    var bytes = new byte[hexStr.Length / 2];
    for (int i = 0; i < hexStr.Length; i += 2)
        bytes[i/2] = Convert.ToByte(hexStr.Substring(i, 2), 16);
    return bytes;
}

最后是核心转换方法:

public static void ConvertUdpLogsToPcap(string logPath, string pcapPath,
    byte[] srcMac, byte[] destMac, uint srcIp, uint destIp,
    ushort srcPort, ushort destPort)
{
    // 初始化PCAP全局头
    var globalHeader = new PcapGlobalHeader
    {
        MagicNumber = 0xa1b2c3d4,
        VersionMajor = 2,
        VersionMinor = 4,
        ThisZone = 0,
        SigFigs = 0,
        SnapLen = 65535,
        Network = 1
    };

    using (var fs = new FileStream(pcapPath, FileMode.Create, FileAccess.Write))
    {
        // 写入全局头
        fs.Write(StructToBytes(globalHeader), 0, Marshal.SizeOf(typeof(PcapGlobalHeader)));

        // 读取日志文件(假设每行是一个UDP载荷的十六进制字符串)
        var lines = File.ReadAllLines(logPath);
        ushort ipId = 1;

        foreach (var line in lines)
        {
            var payload = HexToBytes(line.Trim());
            if (payload.Length == 0) continue;

            // 构造以太网头
            var ethHeader = new EthernetHeader
            {
                DestMac = destMac,
                SrcMac = srcMac,
                EtherType = (ushort)IPAddress.HostToNetworkOrder((short)0x0800) // 转成网络字节序
            };

            // 构造IP头
            int ipHeaderSize = Marshal.SizeOf(typeof(IpHeader));
            int udpHeaderSize = Marshal.SizeOf(typeof(UdpHeader));
            int totalIpLength = ipHeaderSize + udpHeaderSize + payload.Length;
            
            var ipHeader = new IpHeader
            {
                VersionAndIhl = 0x45,
                Tos = 0,
                TotalLength = (ushort)IPAddress.HostToNetworkOrder((short)totalIpLength),
                Id = (ushort)IPAddress.HostToNetworkOrder((short)ipId++),
                FlagsOffset = 0,
                Ttl = 64,
                Protocol = 17,
                SrcIp = srcIp,
                DestIp = destIp,
                Checksum = 0 // 先设0,计算校验和后再赋值
            };
            // 计算IP校验和
            ipHeader.Checksum = CalculateChecksum(StructToBytes(ipHeader));
            var ipHeaderBytes = StructToBytes(ipHeader);

            // 构造UDP头
            int udpTotalLength = udpHeaderSize + payload.Length;
            var udpHeader = new UdpHeader
            {
                SrcPort = (ushort)IPAddress.HostToNetworkOrder((short)srcPort),
                DestPort = (ushort)IPAddress.HostToNetworkOrder((short)destPort),
                Length = (ushort)IPAddress.HostToNetworkOrder((short)udpTotalLength),
                Checksum = 0
            };
            // 计算UDP校验和(可选,不想算的话保持0即可)
            udpHeader.Checksum = CalculateUdpChecksum(ipHeader, udpHeader, payload);
            var udpHeaderBytes = StructToBytes(udpHeader);

            // 拼接完整数据包
            var packetBytes = new byte[Marshal.SizeOf(typeof(EthernetHeader)) + ipHeaderBytes.Length + udpHeaderBytes.Length + payload.Length];
            int offset = 0;
            
            Buffer.BlockCopy(StructToBytes(ethHeader), 0, packetBytes, offset, Marshal.SizeOf(typeof(EthernetHeader)));
            offset += Marshal.SizeOf(typeof(EthernetHeader));
            
            Buffer.BlockCopy(ipHeaderBytes, 0, packetBytes, offset, ipHeaderBytes.Length);
            offset += ipHeaderBytes.Length;
            
            Buffer.BlockCopy(udpHeaderBytes, 0, packetBytes, offset, udpHeaderBytes.Length);
            offset += udpHeaderBytes.Length;
            
            Buffer.BlockCopy(payload, 0, packetBytes, offset, payload.Length);

            // 构造数据包记录头(用当前时间,若日志有时间戳可替换)
            var timeSinceEpoch = DateTime.Now - new DateTime(1970, 1, 1);
            var packetHeader = new PcapPacketHeader
            {
                TsSec = (uint)timeSinceEpoch.TotalSeconds,
                TsUsec = (uint)(timeSinceEpoch.Milliseconds * 1000),
                CaptureLength = (uint)packetBytes.Length,
                OriginalLength = (uint)packetBytes.Length
            };

            // 写入记录头和数据包
            fs.Write(StructToBytes(packetHeader), 0, Marshal.SizeOf(typeof(PcapPacketHeader)));
            fs.Write(packetBytes, 0, packetBytes.Length);
        }
    }
}
调用示例
// 配置参数:根据你的实际场景修改
byte[] srcMac = new byte[] { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66 };
byte[] destMac = new byte[] { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
uint srcIp = BitConverter.ToUInt32(IPAddress.Parse("192.168.1.100").GetAddressBytes(), 0);
uint destIp = BitConverter.ToUInt32(IPAddress.Parse("192.168.1.1").GetAddressBytes(), 0);
ushort srcPort = 12345;
ushort destPort = 53;

// 执行转换
ConvertUdpLogsToPcap("udp_payloads.log", "output.pcap", srcMac, destMac, srcIp, destIp, srcPort, destPort);
注意事项
  • 时间戳:示例中用了当前时间,如果你的日志包含每个UDP包的时间戳,记得替换成日志中的时间(转成Unix时间戳即可)
  • 头部参数:源/目的IP、端口、MAC地址需要你根据实际场景提供,如果日志里包含这些信息,要先解析出来再传入
  • 校验和:如果不想计算校验和,可以直接把IP和UDP的Checksum设为0,Wireshark等工具依然能解析数据包,只是会提示校验和错误
  • 日志格式:示例假设日志每行是十六进制字符串的载荷,如果你的日志是二进制格式,直接读取字节即可,不需要HexToBytes方法

内容的提问来源于stack exchange,提问作者user1645200

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:46:27