mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
578 lines
18 KiB
Go
578 lines
18 KiB
Go
package pcap
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/soypat/lneto"
|
|
"github.com/soypat/lneto/dhcp/dhcpv4"
|
|
"github.com/soypat/lneto/dns"
|
|
"github.com/soypat/lneto/ethernet"
|
|
"github.com/soypat/lneto/http/httpraw"
|
|
"github.com/soypat/lneto/internal/ltesto"
|
|
"github.com/soypat/lneto/ipv4"
|
|
"github.com/soypat/lneto/ipv6"
|
|
"github.com/soypat/lneto/tcp"
|
|
"github.com/soypat/lneto/udp"
|
|
)
|
|
|
|
const httpProtocol = "HTTP/1.1"
|
|
|
|
func makeHttpPayload(body string) ([]byte, error) {
|
|
var hdr httpraw.Header
|
|
hdr.SetProtocol(httpProtocol)
|
|
hdr.SetStatus("200", "OK")
|
|
hdr.Set("Cookie", "ABC=123")
|
|
|
|
payload, err := hdr.AppendResponse(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(payload, body...), nil
|
|
}
|
|
|
|
func TestCap(t *testing.T) {
|
|
const mtu = ethernet.MaxMTU
|
|
const httpBody = "{200,ok}"
|
|
var buf [mtu]byte
|
|
var gen ltesto.PacketGen
|
|
|
|
payload, err := makeHttpPayload(httpBody)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rng := rand.New(rand.NewSource(1))
|
|
gen.RandomizeAddrs(rng)
|
|
pkt := gen.AppendRandomIPv4TCPPacket(buf[:0], rng, tcp.Segment{
|
|
SEQ: 100,
|
|
ACK: 200,
|
|
DATALEN: tcp.Size(len(payload)),
|
|
WND: 1024,
|
|
Flags: tcp.FlagFIN, //tcp.FlagSYN | tcp.FlagACK | tcp.FlagPSH,
|
|
})
|
|
|
|
copy(pkt[len(pkt)-len(payload):], payload)
|
|
// padding should not be included in the captured payload
|
|
pkt = append(pkt, "padding"...)
|
|
|
|
var pbreak PacketBreakdown
|
|
frames, err := pbreak.CaptureEthernet(nil, pkt, 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Ethernet+IPv4+TCP+HTTP = 4 frames
|
|
if len(frames) != 4 {
|
|
t.Errorf("want 4 frames, got %d", len(frames))
|
|
}
|
|
getClass := func(frame Frame, class FieldClass) uint64 {
|
|
idx, err := frame.FieldByClass(class)
|
|
if err != nil {
|
|
return 0xffff_ffff_ffff_ffff
|
|
}
|
|
v, _ := frame.FieldAsUint(idx, pkt)
|
|
return v
|
|
}
|
|
getName := func(frame Frame, name string) uint64 {
|
|
for i := range frame.Fields {
|
|
if frame.Fields[i].Name == name {
|
|
v, _ := frame.FieldAsUint(i, pkt)
|
|
return v
|
|
}
|
|
}
|
|
return math.MaxUint64
|
|
}
|
|
getNameData := func(frame Frame, name string) []byte {
|
|
for i := range frame.Fields {
|
|
if frame.Fields[i].Name == name {
|
|
v, _ := frame.AppendField(nil, i, pkt)
|
|
return v
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
efrm, _ := ethernet.NewFrame(pkt)
|
|
pefrm := frames[0]
|
|
pifrm := frames[1]
|
|
ptfrm := frames[2]
|
|
phfrm := frames[3]
|
|
gotEproto := ethernet.Type(getClass(pefrm, FieldClassProto))
|
|
if gotEproto != efrm.EtherTypeOrSize() {
|
|
t.Errorf("want %s ethernet type, got %s", efrm.EtherTypeOrSize().String(), gotEproto.String())
|
|
}
|
|
|
|
ifrm, _ := ipv4.NewFrame(efrm.Payload())
|
|
gotIproto := lneto.IPProto(getClass(pifrm, FieldClassProto))
|
|
if gotIproto != ifrm.Protocol() {
|
|
t.Errorf("want %s IP proto, got %s", ifrm.Protocol().String(), gotIproto.String())
|
|
}
|
|
gotToS := ipv4.ToS(getName(pifrm, "Type of Service"))
|
|
wantToS := ifrm.ToS()
|
|
if gotToS != wantToS {
|
|
t.Errorf("want %x IP ToS, got %x", wantToS, gotToS)
|
|
}
|
|
gotIflags := ipv4.Flags(getClass(pifrm, FieldClassFlags))
|
|
if gotIflags != ifrm.Flags() {
|
|
t.Errorf("want %x IP flags, got %x", ifrm.Flags(), gotIflags)
|
|
}
|
|
gotVersion := getClass(pifrm, FieldClassVersion)
|
|
wantVersion, _ := ifrm.VersionAndIHL()
|
|
if gotVersion != uint64(wantVersion) {
|
|
t.Errorf("want %d IP version, got %d", wantVersion, gotVersion)
|
|
}
|
|
|
|
tfrm, _ := tcp.NewFrame(ifrm.Payload())
|
|
gotTCPFlags := tcp.Flags(getClass(ptfrm, FieldClassFlags))
|
|
wantHeaderLen, wantTCPflags := tfrm.OffsetAndFlags()
|
|
if gotTCPFlags != wantTCPflags {
|
|
t.Errorf("want %s TCP flags, got %s", wantTCPflags.String(), gotTCPFlags.String())
|
|
}
|
|
wanDstPort := gen.DstTCP
|
|
wantSrcPort := gen.SrcTCP
|
|
gotSrcPort := uint16(getClass(ptfrm, FieldClassSrc))
|
|
gotDstPort := uint16(getClass(ptfrm, FieldClassDst))
|
|
if wantSrcPort != gotSrcPort {
|
|
t.Errorf("want %d TCP src port, got %d", wantSrcPort, gotSrcPort)
|
|
}
|
|
if wanDstPort != gotDstPort {
|
|
t.Errorf("want %d TCP dst port, got %d", wanDstPort, gotDstPort)
|
|
}
|
|
gotHeaderLen := getClass(ptfrm, FieldClassSize)
|
|
if gotHeaderLen != uint64(wantHeaderLen) {
|
|
t.Errorf("want %d TCP header length, got %d", wantHeaderLen, gotHeaderLen)
|
|
}
|
|
gotHttpHeader := string(getNameData(phfrm, "HTTP Header"))
|
|
if !strings.HasPrefix(gotHttpHeader, httpProtocol) {
|
|
t.Errorf("want HTTP header starting with %q, got %q", httpProtocol, gotHttpHeader)
|
|
}
|
|
gotBody := getNameData(phfrm, "HTTP Body")
|
|
if string(gotBody) != httpBody {
|
|
t.Errorf("want %q HTTP body, got %q", httpBody, gotBody)
|
|
}
|
|
}
|
|
|
|
// TestRightAlignedFields tests extraction of fields that span byte boundaries
|
|
// with right-aligned output, such as IPv6 Traffic Class and Flow Label.
|
|
func TestRightAlignedFields(t *testing.T) {
|
|
// Build a minimal IPv6 packet with known Traffic Class and Flow Label values.
|
|
// IPv6 header: 40 bytes minimum.
|
|
// Byte 0-3: Version (4 bits) + Traffic Class (8 bits) + Flow Label (20 bits)
|
|
const (
|
|
wantVersion = 6
|
|
wantTrafficClass = 0xAB // 8 bits at bit offset 4
|
|
wantFlowLabel = 0x000C_DEF0 // 20 bits at bit offset 12 (we'll use 0xCDEF0 masked to 20 bits = 0xDEF0)
|
|
)
|
|
// Actually flow label is 20 bits, so max is 0xFFFFF. Use 0xDEF01 masked.
|
|
const wantFlow20 = 0xDEF01 & 0xFFFFF // 0xDEF01
|
|
|
|
var pkt [14 + 40 + 20]byte // Ethernet + IPv6 header + TCP header
|
|
// Set up Ethernet frame.
|
|
efrm, _ := ethernet.NewFrame(pkt[:])
|
|
efrm.SetEtherType(ethernet.TypeIPv6)
|
|
|
|
// Set up IPv6 header manually.
|
|
i6frm, _ := ipv6.NewFrame(efrm.Payload())
|
|
i6frm.SetVersionTrafficAndFlow(wantVersion, ipv6.ToS(wantTrafficClass), wantFlow20)
|
|
i6frm.SetPayloadLength(20) // TCP header size
|
|
i6frm.SetNextHeader(lneto.IPProtoTCP)
|
|
i6frm.SetHopLimit(64)
|
|
|
|
// Set up minimal TCP header.
|
|
tfrm, _ := tcp.NewFrame(i6frm.Payload())
|
|
tfrm.SetOffsetAndFlags(5, 0) // 5 words = 20 bytes, no flags
|
|
|
|
// Verify our setup is correct.
|
|
gotVer, gotToS, gotFlow := i6frm.VersionTrafficAndFlow()
|
|
if gotVer != wantVersion {
|
|
t.Fatalf("setup: version mismatch: got %d, want %d", gotVer, wantVersion)
|
|
}
|
|
if uint8(gotToS) != wantTrafficClass {
|
|
t.Fatalf("setup: traffic class mismatch: got 0x%02x, want 0x%02x", gotToS, wantTrafficClass)
|
|
}
|
|
if gotFlow != wantFlow20 {
|
|
t.Fatalf("setup: flow label mismatch: got 0x%05x, want 0x%05x", gotFlow, wantFlow20)
|
|
}
|
|
|
|
// Capture the packet.
|
|
var pbreak PacketBreakdown
|
|
frames, err := pbreak.CaptureEthernet(nil, pkt[:], 0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(frames) < 2 {
|
|
t.Fatalf("expected at least 2 frames (Ethernet + IPv6), got %d", len(frames))
|
|
}
|
|
|
|
// Find IPv6 frame.
|
|
var ipv6Frame *Frame
|
|
for i := range frames {
|
|
if frames[i].Protocol == "IPv6" {
|
|
ipv6Frame = &frames[i]
|
|
break
|
|
}
|
|
}
|
|
if ipv6Frame == nil {
|
|
t.Fatal("IPv6 frame not found")
|
|
}
|
|
|
|
// Helper to get field by name.
|
|
getByName := func(name string) (uint64, error) {
|
|
for i, f := range ipv6Frame.Fields {
|
|
if f.Name == name {
|
|
return ipv6Frame.FieldAsUint(i, pkt[:])
|
|
}
|
|
}
|
|
return 0, nil
|
|
}
|
|
|
|
// Test Traffic Class (Type of Service) - 8 bits at bit offset 4, right-aligned.
|
|
gotTrafficClass, err := getByName("Type of Service")
|
|
if err != nil {
|
|
t.Fatalf("failed to get Traffic Class: %v", err)
|
|
}
|
|
if uint8(gotTrafficClass) != wantTrafficClass {
|
|
t.Errorf("Traffic Class: got 0x%02x, want 0x%02x", gotTrafficClass, wantTrafficClass)
|
|
}
|
|
|
|
// Test Flow Label - 20 bits at bit offset 12, right-aligned.
|
|
gotFlowLabel, err := getByName("Flow Label")
|
|
if err != nil {
|
|
t.Fatalf("failed to get Flow Label: %v", err)
|
|
}
|
|
if uint32(gotFlowLabel) != wantFlow20 {
|
|
t.Errorf("Flow Label: got 0x%05x, want 0x%05x", gotFlowLabel, wantFlow20)
|
|
}
|
|
|
|
// Test Version - 4 bits at bit offset 0, not right-aligned.
|
|
gotVersion, err := getByName("")
|
|
if err != nil {
|
|
t.Fatalf("failed to get Version: %v", err)
|
|
}
|
|
if len(ipv6Frame.Fields) > 0 && ipv6Frame.Fields[0].Class == FieldClassVersion {
|
|
gotVersion, _ = ipv6Frame.FieldAsUint(0, pkt[:])
|
|
}
|
|
if uint8(gotVersion) != wantVersion {
|
|
t.Errorf("Version: got %d, want %d", gotVersion, wantVersion)
|
|
}
|
|
}
|
|
|
|
// TestAppendFieldRightAligned directly tests the appendField function
|
|
// with right-aligned fields that have trailing bits.
|
|
func TestAppendFieldRightAligned(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
pkt []byte
|
|
fieldBitStart int
|
|
bitlen int
|
|
rightAligned bool
|
|
wantData []byte
|
|
}{
|
|
{
|
|
// IPv6 Traffic Class: bits 4-11 (8 bits spanning bytes 0-1)
|
|
name: "IPv6 Traffic Class 0xAB",
|
|
pkt: []byte{0x6A, 0xB0, 0x00, 0x00}, // Version=6, TC=0xAB, Flow=0
|
|
fieldBitStart: 4,
|
|
bitlen: 8,
|
|
rightAligned: true,
|
|
wantData: []byte{0xAB},
|
|
},
|
|
{
|
|
// IPv6 Traffic Class with different value
|
|
name: "IPv6 Traffic Class 0xFF",
|
|
pkt: []byte{0x6F, 0xF0, 0x00, 0x00}, // Version=6, TC=0xFF, Flow=0
|
|
fieldBitStart: 4,
|
|
bitlen: 8,
|
|
rightAligned: true,
|
|
wantData: []byte{0xFF},
|
|
},
|
|
{
|
|
// IPv6 Traffic Class at minimum
|
|
name: "IPv6 Traffic Class 0x00",
|
|
pkt: []byte{0x60, 0x00, 0x00, 0x00}, // Version=6, TC=0x00, Flow=0
|
|
fieldBitStart: 4,
|
|
bitlen: 8,
|
|
rightAligned: true,
|
|
wantData: []byte{0x00},
|
|
},
|
|
{
|
|
// IPv6 Flow Label: bits 12-31 (20 bits spanning bytes 1-3)
|
|
name: "IPv6 Flow Label 0xDEF01",
|
|
pkt: []byte{0x60, 0x0D, 0xEF, 0x01}, // Version=6, TC=0, Flow=0xDEF01
|
|
fieldBitStart: 12,
|
|
bitlen: 20,
|
|
rightAligned: true,
|
|
wantData: []byte{0x0D, 0xEF, 0x01}, // 20 bits right-aligned in 3 bytes
|
|
},
|
|
{
|
|
// Flow Label max value
|
|
name: "IPv6 Flow Label 0xFFFFF",
|
|
pkt: []byte{0x60, 0xFF, 0xFF, 0xFF}, // Version=6, TC=0, Flow=0xFFFFF
|
|
fieldBitStart: 12,
|
|
bitlen: 20,
|
|
rightAligned: true,
|
|
wantData: []byte{0x0F, 0xFF, 0xFF}, // 20 bits right-aligned in 3 bytes
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, err := appendField(nil, tc.pkt, tc.fieldBitStart, tc.bitlen, tc.rightAligned)
|
|
if err != nil {
|
|
t.Fatalf("appendField error: %v", err)
|
|
}
|
|
if len(got) != len(tc.wantData) {
|
|
t.Fatalf("length mismatch: got %d bytes, want %d bytes", len(got), len(tc.wantData))
|
|
}
|
|
for i := range got {
|
|
if got[i] != tc.wantData[i] {
|
|
t.Errorf("byte %d: got 0x%02x, want 0x%02x", i, got[i], tc.wantData[i])
|
|
}
|
|
}
|
|
// Also verify as uint64 for single/double byte cases.
|
|
if len(tc.wantData) <= 8 {
|
|
gotVal, err := fieldAsUint(tc.pkt, tc.fieldBitStart, tc.bitlen, tc.rightAligned)
|
|
if err != nil {
|
|
t.Fatalf("fieldAsUint error: %v", err)
|
|
}
|
|
var wantVal uint64
|
|
for _, b := range tc.wantData {
|
|
wantVal = wantVal<<8 | uint64(b)
|
|
}
|
|
if gotVal != wantVal {
|
|
t.Errorf("as uint: got 0x%x, want 0x%x", gotVal, wantVal)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestFieldAsUintRightAligned tests fieldAsUint with the same buffer
|
|
// used in the appendField fix, ensuring consistency.
|
|
func TestFieldAsUintRightAligned(t *testing.T) {
|
|
// Build IPv6 first 4 bytes with known values.
|
|
// Format: VVVV TTTT TTTT FFFF FFFF FFFF FFFF FFFF
|
|
// V=version (4 bits), T=traffic class (8 bits), F=flow label (20 bits)
|
|
var buf [4]byte
|
|
const version = 6
|
|
const trafficClass = 0xAB
|
|
const flowLabel = 0xCDEF0
|
|
|
|
// Encode: version in bits 0-3, traffic class in bits 4-11, flow label in bits 12-31
|
|
val := uint32(version)<<28 | uint32(trafficClass)<<20 | flowLabel
|
|
binary.BigEndian.PutUint32(buf[:], val)
|
|
|
|
// Verify encoding.
|
|
t.Logf("Encoded bytes: %02x %02x %02x %02x", buf[0], buf[1], buf[2], buf[3])
|
|
|
|
// Test version extraction (bits 0-3, 4 bits, left-aligned)
|
|
gotVersion, err := fieldAsUint(buf[:], 0, 4, false)
|
|
if err != nil {
|
|
t.Fatalf("version extraction failed: %v", err)
|
|
}
|
|
if gotVersion != version {
|
|
t.Errorf("version: got %d, want %d", gotVersion, version)
|
|
}
|
|
|
|
// Test traffic class extraction (bits 4-11, 8 bits, right-aligned)
|
|
gotTC, err := fieldAsUint(buf[:], 4, 8, true)
|
|
if err != nil {
|
|
t.Fatalf("traffic class extraction failed: %v", err)
|
|
}
|
|
if gotTC != trafficClass {
|
|
t.Errorf("traffic class: got 0x%02x, want 0x%02x", gotTC, trafficClass)
|
|
}
|
|
|
|
// Test flow label extraction (bits 12-31, 20 bits, right-aligned)
|
|
gotFlow, err := fieldAsUint(buf[:], 12, 20, true)
|
|
if err != nil {
|
|
t.Fatalf("flow label extraction failed: %v", err)
|
|
}
|
|
if gotFlow != flowLabel {
|
|
t.Errorf("flow label: got 0x%05x, want 0x%05x", gotFlow, flowLabel)
|
|
}
|
|
}
|
|
|
|
func ExampleFormatter_dhcp() {
|
|
// Build an Ethernet + IPv4 + UDP + DHCPv4 packet using dhcpv4.Client.
|
|
const (
|
|
ethSize = 14
|
|
ipv4Size = 20
|
|
udpSize = 8
|
|
)
|
|
pkt := make([]byte, 600) // Need 240 (DHCP header) + 255 (min options) + 42 (Eth+IP+UDP)
|
|
|
|
// Ethernet frame.
|
|
efrm, _ := ethernet.NewFrame(pkt)
|
|
*efrm.DestinationHardwareAddr() = [6]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff} // Broadcast
|
|
*efrm.SourceHardwareAddr() = [6]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe}
|
|
efrm.SetEtherType(ethernet.TypeIPv4)
|
|
|
|
// IPv4 frame (will be filled by DHCP client).
|
|
ifrm, _ := ipv4.NewFrame(pkt[ethSize:])
|
|
ifrm.SetVersionAndIHL(4, 5)
|
|
ifrm.SetID(0x1234)
|
|
ifrm.SetFlags(0x4000) // Don't Fragment
|
|
ifrm.SetTTL(64)
|
|
ifrm.SetProtocol(lneto.IPProtoUDP)
|
|
|
|
// UDP frame (ports set, length will be updated).
|
|
ufrm, _ := udp.NewFrame(pkt[ethSize+ipv4Size:])
|
|
ufrm.SetSourcePort(dhcpv4.DefaultClientPort) // 68
|
|
ufrm.SetDestinationPort(dhcpv4.DefaultServerPort) // 67
|
|
|
|
// Use dhcpv4.Client to generate DHCP Discover.
|
|
var cl dhcpv4.Client
|
|
err := cl.BeginRequest(0xdeadbeef, dhcpv4.RequestConfig{
|
|
RequestedAddr: [4]byte{192, 168, 1, 100},
|
|
ClientHardwareAddr: [6]byte{0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe},
|
|
Hostname: "myhost",
|
|
ClientID: "lneto-test",
|
|
})
|
|
if err != nil {
|
|
fmt.Println("begin request error:", err)
|
|
return
|
|
}
|
|
|
|
// Encapsulate DHCP into the packet at correct offset.
|
|
// offsetToIP=14 (after Ethernet), offsetToFrame=42 (after Ethernet+IPv4+UDP)
|
|
dhcpLen, err := cl.Encapsulate(pkt, ethSize, ethSize+ipv4Size+udpSize)
|
|
if err != nil {
|
|
fmt.Println("encapsulate error:", err)
|
|
return
|
|
}
|
|
|
|
// Update lengths now that we know DHCP size.
|
|
totalLen := ipv4Size + udpSize + dhcpLen
|
|
ifrm.SetTotalLength(uint16(totalLen))
|
|
ufrm.SetLength(uint16(udpSize + dhcpLen))
|
|
// The CRC field is already zero here.
|
|
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
|
|
|
|
pkt = pkt[:ethSize+totalLen]
|
|
|
|
// Capture and format the packet.
|
|
var cap PacketBreakdown
|
|
cap.SubfieldLimit = 10
|
|
frames, err := cap.CaptureEthernet(nil, pkt, 0)
|
|
if err != nil {
|
|
fmt.Println("capture error:", err)
|
|
return
|
|
}
|
|
|
|
var fmtr Formatter
|
|
fmtr.SubfieldLimit = cap.SubfieldLimit
|
|
fmtr.FrameSep = "\n"
|
|
fmtr.FieldSep = "; "
|
|
fmtr.SubfieldSep = "\n\t"
|
|
out, err := fmtr.FormatFrames(nil, frames, pkt)
|
|
if err != nil {
|
|
fmt.Println("format error:", err)
|
|
return
|
|
}
|
|
fmt.Println(string(out))
|
|
// Output:
|
|
// Ethernet len=14; destination=ff:ff:ff:ff:ff:ff; source=de:ad:be:ef:ca:fe; protocol=0x0800
|
|
// IPv4 len=20; version=0x04; (Header Length)=5; (Type of Service)=0x00; (Total Length)=312; identification=0x6043; flags=0x4000; (Time to live)=0x40; protocol=0x11; checksum=0xd972; source=0.0.0.0; destination=255.255.255.255
|
|
// UDP len=8; (Source port)=68; (Destination port)=67; size=292; checksum=0x0000
|
|
// DHCPv4 len=240; op=1; (Hardware Address Type)=0x01; (Hardware Address Length)=6; Hops=0x00; (Transaction ID)=0xdeadbeef; (Start Time)=0x0001; Flags=0x0000; (Client Address)=0.0.0.0; (Offered Address)=0.0.0.0; (Server Next Address)=255.255.255.255; (Relay Agent Address)=0.0.0.0; (Client Hardware Address)=de:ad:be:ef:ca:fe; options
|
|
// (DHCP message type.)=1
|
|
// (Parameter request list)=0x0102031a1c060f2a
|
|
// (DHCP maximum message size)=558
|
|
// (Requested IP address)=192.168.1.100
|
|
// (Client identifier)="lneto-test"
|
|
// (Hostname string)="myhost"
|
|
}
|
|
|
|
func ExampleFormatter_dns() {
|
|
const (
|
|
ethSize = 14
|
|
ipv4Size = 20
|
|
udpSize = 8
|
|
)
|
|
|
|
// Build a DNS query for "example.com" A record.
|
|
var msg dns.Message
|
|
msg.Questions = []dns.Question{
|
|
{Name: dns.MustNewName("example.com"), Type: dns.TypeA, Class: dns.ClassINET},
|
|
{Name: dns.MustNewName("temu.com"), Type: dns.TypeAAAA, Class: dns.ClassANY},
|
|
}
|
|
msg.Answers = []dns.Resource{
|
|
dns.NewResource(dns.MustNewName("abc.com"), dns.TypeALL, dns.ClassANY, 64, []byte{10, 0, 11, 1}),
|
|
dns.NewResource(dns.MustNewName("123.com"), dns.TypeA, dns.ClassINET, 64, []byte{20, 0, 22, 2}),
|
|
}
|
|
|
|
dnsPayload, err := msg.AppendTo(nil, 0x1234, dns.NewClientHeaderFlags(dns.OpCodeQuery, true))
|
|
if err != nil {
|
|
fmt.Println("dns encode error:", err)
|
|
return
|
|
}
|
|
|
|
pkt := make([]byte, ethSize+ipv4Size+udpSize+len(dnsPayload))
|
|
|
|
efrm, _ := ethernet.NewFrame(pkt)
|
|
*efrm.DestinationHardwareAddr() = [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
|
|
*efrm.SourceHardwareAddr() = [6]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x02}
|
|
efrm.SetEtherType(ethernet.TypeIPv4)
|
|
|
|
ifrm, _ := ipv4.NewFrame(pkt[ethSize:])
|
|
ifrm.SetVersionAndIHL(4, 5)
|
|
ifrm.SetTTL(64)
|
|
ifrm.SetProtocol(lneto.IPProtoUDP)
|
|
ifrm.SetTotalLength(uint16(ipv4Size + udpSize + len(dnsPayload)))
|
|
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
|
|
|
|
ufrm, _ := udp.NewFrame(pkt[ethSize+ipv4Size:])
|
|
ufrm.SetSourcePort(58200)
|
|
ufrm.SetDestinationPort(dns.ServerPort)
|
|
ufrm.SetLength(uint16(udpSize + len(dnsPayload)))
|
|
|
|
copy(pkt[ethSize+ipv4Size+udpSize:], dnsPayload)
|
|
|
|
var cap PacketBreakdown
|
|
cap.SubfieldLimit = 20
|
|
frames, err := cap.CaptureEthernet(nil, pkt, 0)
|
|
if err != nil {
|
|
fmt.Println("capture error:", err)
|
|
return
|
|
}
|
|
|
|
var fmtr Formatter
|
|
fmtr.SubfieldLimit = cap.SubfieldLimit
|
|
fmtr.FrameSep = "\n"
|
|
fmtr.FieldSep = "; "
|
|
fmtr.SubfieldSep = "\n\t"
|
|
out, err := fmtr.FormatFrames(nil, frames, pkt)
|
|
if err != nil {
|
|
fmt.Println("format error:", err)
|
|
return
|
|
}
|
|
fmt.Println(string(out))
|
|
// Output:
|
|
// Ethernet len=14; destination=00:00:00:00:00:01; source=00:00:00:00:00:02; protocol=0x0800
|
|
// IPv4 len=20; version=0x04; (Header Length)=5; (Type of Service)=0x00; (Total Length)=117; identification=0x0000; flags=0x0000; (Time to live)=0x40; protocol=0x11; checksum=0x7a79; source=0.0.0.0; destination=0.0.0.0
|
|
// UDP len=8; (Source port)=58200; (Destination port)=53; size=97; checksum=0x0000
|
|
// DNS len=89; identification=0x1234; flags=0x0100; Questions=2; Answers=2; Authorities=0; Additionals=0; Questions
|
|
// Name=example.com
|
|
// Type=1
|
|
// Class=1
|
|
// Name=temu.com
|
|
// Type=28
|
|
// Class=255; Answers
|
|
// Name=abc.com
|
|
// Type=255
|
|
// Class=255
|
|
// TTL=0x00000040
|
|
// Length=4
|
|
// Data=10.0.11.1
|
|
// Name=123.com
|
|
// Type=1
|
|
// Class=1
|
|
// TTL=0x00000040
|
|
// Length=4
|
|
// Data=20.0.22.2
|
|
}
|