mirror of
https://github.com/soypat/lneto.git
synced 2026-09-04 22:09:05 +00:00
Add DNS support to pcap (#100)
* begin adding dns pcap functionality * improve question formatting * work on capturing answers as well * finalize DNS printing * increase subfield limits * add FlagContainer type * prettier DNS printing
This commit is contained in:
@@ -346,6 +346,8 @@ func (r *Resource) Reset() {
|
|||||||
r.data = r.data[:0]
|
r.data = r.data[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resource) Header() ResourceHeader { return r.header }
|
||||||
|
|
||||||
func (r *Resource) RawData() []byte {
|
func (r *Resource) RawData() []byte {
|
||||||
length := r.header.Length
|
length := r.header.Length
|
||||||
if int(length) > len(r.data) {
|
if int(length) > len(r.data) {
|
||||||
|
|||||||
@@ -85,8 +85,9 @@ func run() error {
|
|||||||
}
|
}
|
||||||
defer sv.Close()
|
defer sv.Close()
|
||||||
var cap pcap.PacketBreakdown
|
var cap pcap.PacketBreakdown
|
||||||
|
cap.SubfieldLimit = 30 // For big DNS or DHCP.
|
||||||
pf := pcap.Formatter{
|
pf := pcap.Formatter{
|
||||||
FilterClasses: []pcap.FieldClass{pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassSize, pcap.FieldClassFlags},
|
FilterClasses: []pcap.FieldClass{pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassSize, pcap.FieldClassFlags, pcap.FieldClassDNSName},
|
||||||
}
|
}
|
||||||
var pfbuf []byte
|
var pfbuf []byte
|
||||||
sv.OnTransfer(func(channel int, pkt []byte) {
|
sv.OnTransfer(func(channel int, pkt []byte) {
|
||||||
|
|||||||
@@ -136,9 +136,11 @@ func run() (err error) {
|
|||||||
lastAction := time.Now()
|
lastAction := time.Now()
|
||||||
buf := make([]byte, math.MaxUint16) // Generic-receive Offload (GRO) can aggregate packets.
|
buf := make([]byte, math.MaxUint16) // Generic-receive Offload (GRO) can aggregate packets.
|
||||||
var cap pcap.PacketBreakdown
|
var cap pcap.PacketBreakdown
|
||||||
|
cap.SubfieldLimit = 30 // For chunky DNS.
|
||||||
var frames []pcap.Frame
|
var frames []pcap.Frame
|
||||||
pf := pcap.Formatter{
|
pf := pcap.Formatter{
|
||||||
FilterClasses: []pcap.FieldClass{pcap.FieldClassFlags, pcap.FieldClassOperation, pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassAddress, pcap.FieldClassTimestamp},
|
FilterClasses: []pcap.FieldClass{pcap.FieldClassFlags, pcap.FieldClassOperation, pcap.FieldClassDst, pcap.FieldClassSrc, pcap.FieldClassAddress, pcap.FieldClassTimestamp, pcap.FieldClassDNSName},
|
||||||
|
SubfieldLimit: 30,
|
||||||
}
|
}
|
||||||
var pfbuf []byte
|
var pfbuf []byte
|
||||||
logFrames := func(context string, pkt []byte) error {
|
logFrames := func(context string, pkt []byte) error {
|
||||||
|
|||||||
+193
-11
@@ -384,14 +384,14 @@ func (pc *PacketBreakdown) CaptureICMPv4(dst []Frame, pkt []byte, bitOffset int)
|
|||||||
}
|
}
|
||||||
|
|
||||||
finfo := reclaimFrame(&dst, "ICMP", bitOffset, baseICMPv4Fields[:])
|
finfo := reclaimFrame(&dst, "ICMP", bitOffset, baseICMPv4Fields[:])
|
||||||
|
tp := ifrm.Type()
|
||||||
// Add type-specific fields.
|
// Add type-specific fields.
|
||||||
switch ifrm.Type() {
|
switch tp {
|
||||||
case icmpv4.TypeEcho, icmpv4.TypeEchoReply:
|
case icmpv4.TypeEcho, icmpv4.TypeEchoReply:
|
||||||
finfo.Fields = append(finfo.Fields, icmpv4EchoFields[:]...)
|
finfo.Fields = append(finfo.Fields, icmpv4EchoFields[:]...)
|
||||||
if len(icmpData) > 8 {
|
if len(icmpData) > 8 {
|
||||||
finfo.Fields = append(finfo.Fields, FrameField{
|
finfo.Fields = append(finfo.Fields, FrameField{
|
||||||
Name: "Data",
|
Name: tp.String(),
|
||||||
Class: FieldClassPayload,
|
Class: FieldClassPayload,
|
||||||
FrameBitOffset: 8 * octet,
|
FrameBitOffset: 8 * octet,
|
||||||
BitLength: (len(icmpData) - 8) * octet,
|
BitLength: (len(icmpData) - 8) * octet,
|
||||||
@@ -439,26 +439,202 @@ func (pc *PacketBreakdown) CaptureDNS(dst []Frame, pkt []byte, bitOffset int) ([
|
|||||||
}
|
}
|
||||||
dnsData := pkt[bitOffset/8:]
|
dnsData := pkt[bitOffset/8:]
|
||||||
pc.dmsg.LimitResourceDecoding(4, 4, 4, 4)
|
pc.dmsg.LimitResourceDecoding(4, 4, 4, 4)
|
||||||
off, incomplete, err := pc.dmsg.Decode(dnsData)
|
_, incomplete, err := pc.dmsg.Decode(dnsData)
|
||||||
if err != nil && !incomplete {
|
if err != nil && !incomplete {
|
||||||
return dst, err
|
return dst, err
|
||||||
}
|
}
|
||||||
debuglog("pcap:dns-decode")
|
debuglog("pcap:dns-decode")
|
||||||
finfo := reclaimFrame(&dst, "DNS", bitOffset, nil)
|
hdr, _ := dns.NewFrame(dnsData)
|
||||||
|
finfo := reclaimFrame(&dst, "DNS", bitOffset, baseDNSFields[:])
|
||||||
if incomplete {
|
if incomplete {
|
||||||
finfo.Errors = append(finfo.Errors, ErrLimitExceeded)
|
finfo.Errors = append(finfo.Errors, ErrLimitExceeded)
|
||||||
}
|
}
|
||||||
field := internal.SliceReclaim(&finfo.Fields)
|
if pc.SubfieldLimit <= 0 {
|
||||||
*field = FrameField{
|
debuglog("pcap:dns-done")
|
||||||
Name: "Data",
|
return dst, nil
|
||||||
FrameBitOffset: 0,
|
|
||||||
BitLength: int(off) * octet,
|
|
||||||
SubFields: field.SubFields[:0], // Reuse subfields.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wireOff := dns.SizeHeader
|
||||||
|
|
||||||
|
// Questions section: walk all QDCount wire records to keep wireOff correct,
|
||||||
|
// but only emit SubFields for the decoded ones.
|
||||||
|
nq := int(hdr.QDCount())
|
||||||
|
if nq > 0 {
|
||||||
|
sectionStart := wireOff
|
||||||
|
qfield := internal.SliceReclaim(&finfo.Fields)
|
||||||
|
*qfield = FrameField{Name: "Questions", Class: fieldClassDNSResource, SubFields: qfield.SubFields[:0], Flags: FlagContainer}
|
||||||
|
decoded := pc.dmsg.Questions
|
||||||
|
for i := range nq {
|
||||||
|
nameStart := wireOff
|
||||||
|
wireOff, err = dnsSkipName(dnsData, wireOff)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nameEnd := wireOff
|
||||||
|
wireOff += 4 // Type(2) + Class(2)
|
||||||
|
if i < len(decoded) && len(qfield.SubFields)+3 <= pc.SubfieldLimit {
|
||||||
|
qfield.SubFields = append(qfield.SubFields, FrameField{
|
||||||
|
Name: "Name",
|
||||||
|
Class: FieldClassDNSName,
|
||||||
|
FrameBitOffset: nameStart * octet,
|
||||||
|
BitLength: (nameEnd - nameStart) * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Type",
|
||||||
|
Class: FieldClassOperation,
|
||||||
|
FrameBitOffset: nameEnd * octet,
|
||||||
|
BitLength: 2 * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Class",
|
||||||
|
Class: FieldClassOperation,
|
||||||
|
FrameBitOffset: (nameEnd + 2) * octet,
|
||||||
|
BitLength: 2 * octet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qfield.FrameBitOffset = sectionStart * octet
|
||||||
|
qfield.BitLength = (wireOff - sectionStart) * octet
|
||||||
|
}
|
||||||
|
|
||||||
|
wireOff = pc.appendDNSResources(finfo, "Answers", dnsData, pc.dmsg.Answers, int(hdr.ANCount()), wireOff)
|
||||||
|
wireOff = pc.appendDNSResources(finfo, "Authorities", dnsData, pc.dmsg.Authorities, int(hdr.NSCount()), wireOff)
|
||||||
|
wireOff = pc.appendDNSResources(finfo, "Additionals", dnsData, pc.dmsg.Additionals, int(hdr.ARCount()), wireOff)
|
||||||
|
_ = wireOff
|
||||||
|
|
||||||
debuglog("pcap:dns-done")
|
debuglog("pcap:dns-done")
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// appendDNSResources adds a section FrameField with per-record SubFields to finfo.
|
||||||
|
// It walks all `total` wire records to keep wireOff accurate, but only emits SubFields
|
||||||
|
// for the decoded slice entries while nFields < pc.SubfieldLimit.
|
||||||
|
func (pc *PacketBreakdown) appendDNSResources(finfo *Frame, name string, dnsData []byte, decoded []dns.Resource, total, wireOff int) int {
|
||||||
|
if total == 0 {
|
||||||
|
return wireOff
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
sectionStart := wireOff
|
||||||
|
rfield := internal.SliceReclaim(&finfo.Fields)
|
||||||
|
*rfield = FrameField{Name: name, Class: fieldClassDNSResource, SubFields: rfield.SubFields[:0], Flags: FlagContainer}
|
||||||
|
for i := range total {
|
||||||
|
nameStart := wireOff
|
||||||
|
wireOff, err = dnsSkipName(dnsData, wireOff)
|
||||||
|
if err != nil || wireOff+10 > len(dnsData) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nameEnd := wireOff
|
||||||
|
dataLen := int(binary.BigEndian.Uint16(dnsData[nameEnd+8:]))
|
||||||
|
wireOff += 10 + dataLen // Type(2)+Class(2)+TTL(4)+Length(2)+Data
|
||||||
|
if wireOff > len(dnsData) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if i < len(decoded) && len(rfield.SubFields)+6 <= pc.SubfieldLimit {
|
||||||
|
rfield.SubFields = append(rfield.SubFields, FrameField{
|
||||||
|
Name: "Name",
|
||||||
|
Class: FieldClassDNSName,
|
||||||
|
FrameBitOffset: nameStart * octet,
|
||||||
|
BitLength: (nameEnd - nameStart) * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Type",
|
||||||
|
Class: FieldClassOperation,
|
||||||
|
FrameBitOffset: nameEnd * octet,
|
||||||
|
BitLength: 2 * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Class",
|
||||||
|
Class: FieldClassOperation,
|
||||||
|
FrameBitOffset: (nameEnd + 2) * octet,
|
||||||
|
BitLength: 2 * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "TTL",
|
||||||
|
Class: FieldClassID,
|
||||||
|
FrameBitOffset: (nameEnd + 4) * octet,
|
||||||
|
BitLength: 4 * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Length",
|
||||||
|
Class: FieldClassSize,
|
||||||
|
FrameBitOffset: (nameEnd + 8) * octet,
|
||||||
|
BitLength: 2 * octet,
|
||||||
|
}, FrameField{
|
||||||
|
Name: "Data",
|
||||||
|
Class: FieldClassAddress,
|
||||||
|
FrameBitOffset: (nameEnd + 10) * octet,
|
||||||
|
BitLength: dataLen * octet,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rfield.FrameBitOffset = sectionStart * octet
|
||||||
|
rfield.BitLength = (wireOff - sectionStart) * octet
|
||||||
|
if err != nil {
|
||||||
|
finfo.Errors = append(finfo.Errors, err)
|
||||||
|
}
|
||||||
|
return wireOff
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsAppendDottedName walks a DNS name in wire format starting at off within
|
||||||
|
// dnsMsg, follows compression pointers, and appends the dotted representation
|
||||||
|
// (e.g. "example.com") to dst. Returns dst unchanged on error.
|
||||||
|
func dnsAppendDottedName(dst, dnsMsg []byte, off int) []byte {
|
||||||
|
// TODO: somehow move this to dns package. dns.Name.Append ? dns.Name is the wire format...
|
||||||
|
first := true
|
||||||
|
for ptr := 0; off < len(dnsMsg) && ptr <= 10; {
|
||||||
|
c := dnsMsg[off]
|
||||||
|
off++
|
||||||
|
switch c & 0xc0 {
|
||||||
|
case 0x00:
|
||||||
|
if c == 0 {
|
||||||
|
return dst // null terminator: done
|
||||||
|
}
|
||||||
|
end := off + int(c)
|
||||||
|
if end > len(dnsMsg) {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if !first {
|
||||||
|
dst = append(dst, '.')
|
||||||
|
}
|
||||||
|
dst = append(dst, dnsMsg[off:end]...)
|
||||||
|
off = end
|
||||||
|
first = false
|
||||||
|
case 0xc0:
|
||||||
|
if off >= len(dnsMsg) {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
off = int(c&0x3f)<<8 | int(dnsMsg[off])
|
||||||
|
ptr++ // guard against pointer loops
|
||||||
|
default:
|
||||||
|
return dst // reserved label type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// dnsSkipName advances off past a DNS name in wire format without allocating.
|
||||||
|
// Compression pointers are followed and consume 2 bytes, terminating the name.
|
||||||
|
func dnsSkipName(b []byte, off int) (int, error) {
|
||||||
|
for {
|
||||||
|
if off >= len(b) {
|
||||||
|
return off, lneto.ErrTruncatedFrame
|
||||||
|
}
|
||||||
|
c := b[off]
|
||||||
|
off++
|
||||||
|
switch c & 0xc0 {
|
||||||
|
case 0x00:
|
||||||
|
if c == 0 {
|
||||||
|
return off, nil // null terminator
|
||||||
|
}
|
||||||
|
off += int(c) // skip label bytes
|
||||||
|
if off > len(b) {
|
||||||
|
return off, lneto.ErrTruncatedFrame
|
||||||
|
}
|
||||||
|
case 0xc0:
|
||||||
|
if off >= len(b) {
|
||||||
|
return off, lneto.ErrTruncatedFrame
|
||||||
|
}
|
||||||
|
return off + 1, nil // compression pointer: 2 bytes total, always terminal
|
||||||
|
default:
|
||||||
|
return off, lneto.ErrInvalidField // reserved label type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
func (pc *PacketBreakdown) CaptureNTP(dst []Frame, pkt []byte, bitOffset int) ([]Frame, error) {
|
||||||
if bitOffset%8 != 0 {
|
if bitOffset%8 != 0 {
|
||||||
return dst, errNotByteAligned
|
return dst, errNotByteAligned
|
||||||
@@ -656,6 +832,9 @@ type Flags uint32
|
|||||||
const (
|
const (
|
||||||
FlagRightAligned Flags = 1 << iota
|
FlagRightAligned Flags = 1 << iota
|
||||||
FlagLegacy
|
FlagLegacy
|
||||||
|
// FlagContainer is used for [FrameField]s whose SubFields represent
|
||||||
|
// the entirety of the FrameField's data. i.e: DNS Questions/Answers.
|
||||||
|
FlagContainer
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ff Flags) IsLegacy() bool { return ff&FlagLegacy != 0 }
|
func (ff Flags) IsLegacy() bool { return ff&FlagLegacy != 0 }
|
||||||
@@ -855,10 +1034,13 @@ const (
|
|||||||
FieldClassBinaryText // binary-text
|
FieldClassBinaryText // binary-text
|
||||||
FieldClassOperation // op
|
FieldClassOperation // op
|
||||||
FieldClassTimestamp // timestamp
|
FieldClassTimestamp // timestamp
|
||||||
|
FieldClassDNSName // dns name
|
||||||
)
|
)
|
||||||
|
|
||||||
const octet = 8
|
const octet = 8
|
||||||
|
|
||||||
|
const fieldClassDNSResource = fieldClassUndefined
|
||||||
|
|
||||||
var baseEthernetFields = [...]FrameField{
|
var baseEthernetFields = [...]FrameField{
|
||||||
{
|
{
|
||||||
Class: FieldClassDst,
|
Class: FieldClassDst,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
"github.com/soypat/lneto/dhcpv4"
|
"github.com/soypat/lneto/dhcpv4"
|
||||||
|
"github.com/soypat/lneto/dns"
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
"github.com/soypat/lneto/http/httpraw"
|
"github.com/soypat/lneto/http/httpraw"
|
||||||
"github.com/soypat/lneto/internal/ltesto"
|
"github.com/soypat/lneto/internal/ltesto"
|
||||||
@@ -486,9 +487,91 @@ func ExampleFormatter_dhcp() {
|
|||||||
// (Hostname string)="myhost"
|
// (Hostname string)="myhost"
|
||||||
}
|
}
|
||||||
|
|
||||||
func writeOpt(dst []byte, opt dhcpv4.OptNum, data ...byte) int {
|
func ExampleFormatter_dns() {
|
||||||
dst[0] = byte(opt)
|
const (
|
||||||
dst[1] = byte(len(data))
|
ethSize = 14
|
||||||
copy(dst[2:], data)
|
ipv4Size = 20
|
||||||
return 2 + len(data)
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-4
@@ -105,12 +105,12 @@ func (f *Formatter) FormatFrame(dst []byte, frm Frame, pkt []byte) (_ []byte, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Formatter) filterField(field FrameField) bool {
|
func (f *Formatter) filterField(field FrameField) bool {
|
||||||
return f.FilterClasses != nil && !slices.Contains(f.FilterClasses, field.Class) ||
|
return f.FilterClasses != nil && field.Flags&FlagContainer == 0 && !slices.Contains(f.FilterClasses, field.Class) ||
|
||||||
(field.Flags.IsLegacy() && !f.DisableLegacyFilter)
|
(field.Flags.IsLegacy() && !f.DisableLegacyFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Formatter) FormatField(dst []byte, pktStartOff int, field FrameField, pkt []byte) (_ []byte, err error) {
|
func (f *Formatter) FormatField(dst []byte, pktStartOff int, field FrameField, pkt []byte) (_ []byte, err error) {
|
||||||
printOnlySubfields := field.Class == FieldClassOptions && len(field.SubFields) > 0
|
printOnlySubfields := (field.Flags&FlagContainer != 0 || field.Class == FieldClassOptions) && len(field.SubFields) > 0
|
||||||
if !printOnlySubfields {
|
if !printOnlySubfields {
|
||||||
dst, err = f.formatField(dst, pktStartOff, field, pkt)
|
dst, err = f.formatField(dst, pktStartOff, field, pkt)
|
||||||
} else {
|
} else {
|
||||||
@@ -119,7 +119,10 @@ func (f *Formatter) FormatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
if f.SubfieldLimit > 0 && len(field.SubFields) > 0 {
|
if f.SubfieldLimit > 0 && len(field.SubFields) > 0 {
|
||||||
sep := f.subfieldSep()
|
sep := f.subfieldSep()
|
||||||
lim := min(len(field.SubFields), f.SubfieldLimit)
|
lim := min(len(field.SubFields), f.SubfieldLimit)
|
||||||
for i := 0; err == nil && i < lim && !f.filterField(field.SubFields[i]); i++ {
|
for i := 0; err == nil && i < lim; i++ {
|
||||||
|
if f.filterField(field.SubFields[i]) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
dst = append(dst, sep...)
|
dst = append(dst, sep...)
|
||||||
// Notice we only format subfields one level low
|
// Notice we only format subfields one level low
|
||||||
dst, err = f.formatField(dst, pktStartOff, field.SubFields[i], pkt)
|
dst, err = f.formatField(dst, pktStartOff, field.SubFields[i], pkt)
|
||||||
@@ -143,9 +146,13 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
dst = append(dst, ')')
|
dst = append(dst, ')')
|
||||||
}
|
}
|
||||||
if field.BitLength == 0 {
|
if field.BitLength == 0 {
|
||||||
|
// We do not print empty nor container fields.
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
dst = append(dst, '=')
|
dst = append(dst, '=')
|
||||||
|
if field.Flags&FlagContainer != 0 {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
f.mubuf.Lock()
|
f.mubuf.Lock()
|
||||||
defer f.mubuf.Unlock()
|
defer f.mubuf.Unlock()
|
||||||
f.buf, err = appendField(f.buf[:0], pkt, field.FrameBitOffset+pktStartOff, field.BitLength, field.Flags.IsRightAligned())
|
f.buf, err = appendField(f.buf[:0], pkt, field.FrameBitOffset+pktStartOff, field.BitLength, field.Flags.IsRightAligned())
|
||||||
@@ -177,6 +184,12 @@ func (f *Formatter) formatField(dst []byte, pktStartOff int, field FrameField, p
|
|||||||
dst = strconv.AppendQuote(dst, unsafe.String(&f.buf[0], len(f.buf)))
|
dst = strconv.AppendQuote(dst, unsafe.String(&f.buf[0], len(f.buf)))
|
||||||
}
|
}
|
||||||
debuglog("pcap:fmtfield:text-done")
|
debuglog("pcap:fmtfield:text-done")
|
||||||
|
case FieldClassDNSName:
|
||||||
|
// Walk the DNS message from pktStartOff to resolve the name, following
|
||||||
|
// compression pointers that reference earlier bytes in the DNS message.
|
||||||
|
dnsMsg := pkt[pktStartOff/8:]
|
||||||
|
nameOff := field.FrameBitOffset / 8
|
||||||
|
dst = dnsAppendDottedName(dst, dnsMsg, nameOff)
|
||||||
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
|
case FieldClassDst, FieldClassSrc, FieldClassSize, FieldClassAddress, FieldClassOperation:
|
||||||
// IP, MAC addresses and ports.
|
// IP, MAC addresses and ports.
|
||||||
if field.BitLength <= 16 {
|
if field.BitLength <= 16 {
|
||||||
@@ -221,7 +234,7 @@ func (f *Formatter) fieldSep() string {
|
|||||||
func (f *Formatter) subfieldSep() string {
|
func (f *Formatter) subfieldSep() string {
|
||||||
sep := f.SubfieldSep
|
sep := f.SubfieldSep
|
||||||
if sep == "" {
|
if sep == "" {
|
||||||
sep = "_" // default sub-field separator
|
sep = "," // default sub-field separator
|
||||||
}
|
}
|
||||||
return sep
|
return sep
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:generate stringer -type=Type,CodeDestinationUnreachable,CodeRedirect -linecomment -output stringers.go
|
||||||
|
|
||||||
const (
|
const (
|
||||||
sizeHeader = 8
|
sizeHeader = 8
|
||||||
)
|
)
|
||||||
@@ -33,7 +35,7 @@ const (
|
|||||||
type CodeTimeExceeded uint8
|
type CodeTimeExceeded uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CodeExceededInTransit CodeTimeExceeded = iota // TTL exceeded in transit
|
CodeExceededInTransit CodeTimeExceeded = iota // TTL exceeded
|
||||||
CodeFragmentReassembly // fragment reassembly time exceeded
|
CodeFragmentReassembly // fragment reassembly time exceeded
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
// Code generated by "stringer -type=Type,CodeDestinationUnreachable,CodeRedirect -linecomment -output stringers.go"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package icmpv4
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[TypeEchoReply-0]
|
||||||
|
_ = x[TypeEcho-8]
|
||||||
|
_ = x[TypeDestinationUnreachable-3]
|
||||||
|
_ = x[TypeSourceQuench-4]
|
||||||
|
_ = x[TypeRedirect-5]
|
||||||
|
_ = x[TypeTimeExceeded-11]
|
||||||
|
_ = x[TypeParameterProblem-12]
|
||||||
|
_ = x[TypeTimestamp-13]
|
||||||
|
_ = x[TypeTimestampReply-14]
|
||||||
|
_ = x[TypeInfoRequest-15]
|
||||||
|
_ = x[TypeInfoRequestReply-16]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_Type_name_0 = "echo reply"
|
||||||
|
_Type_name_1 = "destination unreachablesource quenchredirect"
|
||||||
|
_Type_name_2 = "echo"
|
||||||
|
_Type_name_3 = "time exceededparameter problemtimestamptimestamp replyinformation requestinformation request reply"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_Type_index_1 = [...]uint8{0, 23, 36, 44}
|
||||||
|
_Type_index_3 = [...]uint8{0, 13, 30, 39, 54, 73, 98}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i Type) String() string {
|
||||||
|
switch {
|
||||||
|
case i == 0:
|
||||||
|
return _Type_name_0
|
||||||
|
case 3 <= i && i <= 5:
|
||||||
|
i -= 3
|
||||||
|
return _Type_name_1[_Type_index_1[i]:_Type_index_1[i+1]]
|
||||||
|
case i == 8:
|
||||||
|
return _Type_name_2
|
||||||
|
case 11 <= i && i <= 16:
|
||||||
|
i -= 11
|
||||||
|
return _Type_name_3[_Type_index_3[i]:_Type_index_3[i+1]]
|
||||||
|
default:
|
||||||
|
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[CodeNetUnreachable-0]
|
||||||
|
_ = x[CodeHostUnreachable-1]
|
||||||
|
_ = x[CodeProtoUnreachable-2]
|
||||||
|
_ = x[CodePortUnreachable-3]
|
||||||
|
_ = x[CodeFragNeededAndDFSet-4]
|
||||||
|
_ = x[CodeSourceRouteFailed-5]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CodeDestinationUnreachable_name = "net unreachablehost unreachableprotocol unreachableport unreachablefragmentation needed and DF setsource route failed"
|
||||||
|
|
||||||
|
var _CodeDestinationUnreachable_index = [...]uint8{0, 15, 31, 51, 67, 98, 117}
|
||||||
|
|
||||||
|
func (i CodeDestinationUnreachable) String() string {
|
||||||
|
if i >= CodeDestinationUnreachable(len(_CodeDestinationUnreachable_index)-1) {
|
||||||
|
return "CodeDestinationUnreachable(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _CodeDestinationUnreachable_name[_CodeDestinationUnreachable_index[i]:_CodeDestinationUnreachable_index[i+1]]
|
||||||
|
}
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[CodeRedirectForNetwork-0]
|
||||||
|
_ = x[CodeRedirectForHost-1]
|
||||||
|
_ = x[CodeRedirectForToSAndNetwork-2]
|
||||||
|
_ = x[CodeRedirectToSAndHost-3]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _CodeRedirect_name = "redirect for networkredirect for hostredirect for ToS+networkredirect for ToS+host"
|
||||||
|
|
||||||
|
var _CodeRedirect_index = [...]uint8{0, 20, 37, 61, 82}
|
||||||
|
|
||||||
|
func (i CodeRedirect) String() string {
|
||||||
|
if i >= CodeRedirect(len(_CodeRedirect_index)-1) {
|
||||||
|
return "CodeRedirect(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _CodeRedirect_name[_CodeRedirect_index[i]:_CodeRedirect_index[i+1]]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user