mirror of
https://github.com/soypat/lneto.git
synced 2026-08-21 15:09:05 +00:00
Tcp rst handling (#40)
* claude suggests a way forward * add timing to capture printer * add pcap.Flags * fix ICMP CRC calculation and add test * bugfix: still send data on half-close state(close-wait) * fix pcap test
This commit is contained in:
@@ -3,12 +3,19 @@ package xnet
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/soypat/lneto/internet/pcap"
|
||||
)
|
||||
|
||||
type CapturePrinterConfig struct {
|
||||
NamespaceWidth int
|
||||
// TimePrecision if non-zero is used to print timestamp
|
||||
// at which the packet was received. By default the amount of
|
||||
// seconds since configuration is printed.
|
||||
TimePrecision int
|
||||
// Now returns the current time.
|
||||
Now func() time.Time
|
||||
}
|
||||
|
||||
// CapturePrinter prints internet packets using the [pcap.PacketBreakdown] and [pcap.Formatter] types.
|
||||
@@ -20,9 +27,18 @@ type CapturePrinter struct {
|
||||
fmtPcapBuf []byte
|
||||
// minimum length of namespace on print.
|
||||
namespaceminwidth int
|
||||
|
||||
timeprec int
|
||||
origin time.Time
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
func (stack *CapturePrinter) Configure(writer io.Writer, cfg CapturePrinterConfig) error {
|
||||
stack.timeprec = cfg.TimePrecision
|
||||
stack.now = cfg.Now
|
||||
if stack.printTimestamps() {
|
||||
stack.origin = cfg.Now()
|
||||
}
|
||||
stack.namespaceminwidth = cfg.NamespaceWidth
|
||||
stack.write = writer.Write
|
||||
return nil
|
||||
@@ -36,9 +52,19 @@ func (stack *CapturePrinter) Formatter() *pcap.Formatter {
|
||||
|
||||
func (stack *CapturePrinter) PrintPacket(prefix string, pkt []byte) {
|
||||
fmtbuf := stack.fmtPcapBuf[:0]
|
||||
useTimestamps := stack.printTimestamps()
|
||||
var captime time.Time
|
||||
if useTimestamps {
|
||||
captime = stack.now()
|
||||
}
|
||||
var err error
|
||||
stack.frms, err = stack.cap.CaptureEthernet(stack.frms[:0], pkt, 0)
|
||||
if err == nil {
|
||||
if useTimestamps {
|
||||
diff := captime.Sub(stack.origin)
|
||||
fmtbuf = strconv.AppendFloat(fmtbuf, diff.Seconds(), 'f', stack.timeprec, 32)
|
||||
fmtbuf = append(fmtbuf, ' ')
|
||||
}
|
||||
fmtbuf = append(fmtbuf, prefix...)
|
||||
// Ensure minimum width of packet length display for less jitter in log viewline.
|
||||
prevlen := len(prefix)
|
||||
@@ -60,3 +86,7 @@ func (stack *CapturePrinter) PrintPacket(prefix string, pkt []byte) {
|
||||
stack.write(fmtbuf)
|
||||
stack.fmtPcapBuf = fmtbuf[:0] // Reuse buffer if allocated at larger size.
|
||||
}
|
||||
|
||||
func (stack *CapturePrinter) printTimestamps() bool {
|
||||
return stack.timeprec > 0 && stack.now != nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/soypat/lneto/arp"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal/ltesto"
|
||||
"github.com/soypat/lneto/internet/pcap"
|
||||
"github.com/soypat/lneto/ipv4"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
@@ -909,3 +910,82 @@ func TestTCPConn_BufferNotClearedOnPassiveClose(t *testing.T) {
|
||||
t.Fatalf("read wrong data: got %q, want %q", readBuf[:n], sendData)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStackAsync_ICMPEchoChecksum(t *testing.T) {
|
||||
const MTU = 1500
|
||||
stackAddr := netip.AddrFrom4([4]byte{192, 168, 1, 99})
|
||||
stackMAC := [6]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}
|
||||
routerAddr := [4]byte{192, 168, 1, 1}
|
||||
routerMAC := [6]byte{0x00, 0x11, 0x22, 0x33, 0x44, 0x55}
|
||||
|
||||
stack := new(StackAsync)
|
||||
err := stack.Reset(StackConfig{
|
||||
Hostname: "ICMPTest",
|
||||
RandSeed: 42,
|
||||
StaticAddress: stackAddr,
|
||||
HardwareAddress: stackMAC,
|
||||
MTU: MTU,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gen := ltesto.PacketGen{
|
||||
SrcMAC: routerMAC,
|
||||
DstMAC: stackMAC,
|
||||
SrcIPv4: routerAddr,
|
||||
DstIPv4: stackAddr.As4(),
|
||||
}
|
||||
icmpPayload := []byte("abcdefghijklmnopqrstuvwxyz012345") // 32 bytes, typical ping payload.
|
||||
|
||||
// Test 1: Valid ICMP echo request should be accepted.
|
||||
pkt := gen.AppendIPv4ICMPEcho(nil, ltesto.ICMPEchoConfig{
|
||||
Identifier: 0x1234,
|
||||
SequenceNumber: 1,
|
||||
Payload: icmpPayload,
|
||||
})
|
||||
err = stack.Demux(pkt, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("valid ICMP echo rejected: %v", err)
|
||||
}
|
||||
|
||||
// Test 2: Valid ICMP with trailing FCS bytes (simulates real PIO hardware capture).
|
||||
// This is a regression test for the bug where recvicmp checksummed ifrm.RawData()
|
||||
// instead of ifrm.Payload(), causing the 4 trailing FCS bytes to corrupt the checksum.
|
||||
pkt = gen.AppendIPv4ICMPEcho(nil, ltesto.ICMPEchoConfig{
|
||||
Identifier: 0x1234,
|
||||
SequenceNumber: 2,
|
||||
Payload: icmpPayload,
|
||||
})
|
||||
pkt = append(pkt, 0xDE, 0xAD, 0xBE, 0xEF) // Simulate Ethernet FCS.
|
||||
err = stack.Demux(pkt, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("valid ICMP with trailing FCS rejected: %v", err)
|
||||
}
|
||||
|
||||
// Test 3: Corrupted ICMP checksum should be rejected.
|
||||
pkt = gen.AppendIPv4ICMPEcho(nil, ltesto.ICMPEchoConfig{
|
||||
Identifier: 0x1234,
|
||||
SequenceNumber: 3,
|
||||
Payload: icmpPayload,
|
||||
})
|
||||
pkt[len(pkt)-1] ^= 0xFF // Flip bits in last payload byte to corrupt ICMP checksum.
|
||||
err = stack.Demux(pkt, 0)
|
||||
if err == nil {
|
||||
t.Fatal("corrupted ICMP accepted, expected CRC error")
|
||||
}
|
||||
|
||||
// Test 4: Corrupted ICMP with trailing FCS should also be rejected.
|
||||
pkt = gen.AppendIPv4ICMPEcho(nil, ltesto.ICMPEchoConfig{
|
||||
Identifier: 0x1234,
|
||||
SequenceNumber: 4,
|
||||
Payload: icmpPayload,
|
||||
})
|
||||
pkt[len(pkt)-1] ^= 0xFF // Corrupt ICMP payload.
|
||||
pkt = append(pkt, 0xDE, 0xAD, 0xBE, 0xEF) // Simulate Ethernet FCS.
|
||||
err = stack.Demux(pkt, 0)
|
||||
if err == nil {
|
||||
t.Fatal("corrupted ICMP with FCS accepted, expected CRC error")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user