mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
Add fuzz tests, rename fuzz tests and fix panic found by fuzzing (#72)
* begin adding better fuzz test * finish adding working fuzzer * implement mutateipv4 * fuzzer finds a panic in ICMP client on receive empty payload * rename fuzz tests to reflect fuzz methodology
This commit is contained in:
@@ -5,11 +5,13 @@ import (
|
|||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
||||||
"github.com/soypat/lneto"
|
"github.com/soypat/lneto"
|
||||||
|
"github.com/soypat/lneto/arp"
|
||||||
"github.com/soypat/lneto/ethernet"
|
"github.com/soypat/lneto/ethernet"
|
||||||
"github.com/soypat/lneto/internal"
|
"github.com/soypat/lneto/internal"
|
||||||
"github.com/soypat/lneto/ipv4"
|
"github.com/soypat/lneto/ipv4"
|
||||||
"github.com/soypat/lneto/ipv4/icmpv4"
|
"github.com/soypat/lneto/ipv4/icmpv4"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
|
"github.com/soypat/lneto/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -231,3 +233,418 @@ func (gen *PacketGen) AppendIPv4ICMPEcho(dst []byte, cfg ICMPEchoConfig) []byte
|
|||||||
func sizeWord(l int) uint8 {
|
func sizeWord(l int) uint8 {
|
||||||
return uint8((l + 3) / 4)
|
return uint8((l + 3) / 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PacketMut mutates existing packet bytes in-place for fuzz testing.
|
||||||
|
// All Mutate methods use bitmapMut to select which fields to mutate:
|
||||||
|
// each candidate field consumes 1 bit from LSB. Bit=1 means mutate using seed.
|
||||||
|
// Addresses and ports are never mutated. CRCs are recomputed after mutation.
|
||||||
|
// Methods return remaining seed and bitmapMut for chaining across layers.
|
||||||
|
type PacketMut struct{}
|
||||||
|
|
||||||
|
// MutateEthernet mutates an Ethernet+IPv4+transport packet top-to-bottom.
|
||||||
|
// Dispatches to [PacketMut.MutateIPv4] for the IP layer and transport.
|
||||||
|
func (pm PacketMut) MutateEthernet(pkt []byte, seed, bitmapMut int64) int {
|
||||||
|
efrm, err := ethernet.NewFrame(pkt)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
fields := 0
|
||||||
|
etype := efrm.EtherTypeOrSize()
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, func(v uint16) { efrm.SetEtherType(ethernet.Type(v)) }, uint16(etype))
|
||||||
|
fields++
|
||||||
|
var n int
|
||||||
|
switch etype {
|
||||||
|
case ethernet.TypeIPv4:
|
||||||
|
n, _, _ = pm.MutateIPv4(efrm.Payload(), seed, bitmapMut)
|
||||||
|
case ethernet.TypeARP:
|
||||||
|
n, _, _ = pm.MutateARP(efrm.Payload(), seed, bitmapMut)
|
||||||
|
}
|
||||||
|
return fields + n
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutateIPv4 mutates IPv4 header fields (IHL, ToS, TotalLength, TTL, Protocol)
|
||||||
|
// and optionally injects IP options, fixes IP CRC, then dispatches to the
|
||||||
|
// appropriate transport mutator.
|
||||||
|
func (pm PacketMut) MutateIPv4(ipBuf []byte, seed, bitmapMut int64) (fields int, seedOut, bitmapOut int64) {
|
||||||
|
ifrm, err := ipv4.NewFrame(ipBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
v, ihl := ifrm.VersionAndIHL()
|
||||||
|
if v != 4 || ihl < 5 {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { ifrm.SetVersionAndIHL(4, v&0xf) }, ihl)
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { ifrm.SetToS(ipv4.ToS(v)) }, uint8(ifrm.ToS()))
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, ifrm.SetTotalLength, ifrm.TotalLength())
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, ifrm.SetTTL, ifrm.TTL())
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { ifrm.SetProtocol(lneto.IPProto(v)) }, uint8(ifrm.Protocol()))
|
||||||
|
fields = 5
|
||||||
|
|
||||||
|
// IP option injection: 2 bits consumed (inject + variant selector).
|
||||||
|
if bitmapMut&1 != 0 && ifrm.HeaderLength() >= 20 && ifrm.HeaderLength() <= len(ipBuf) {
|
||||||
|
opts := ifrm.Options()
|
||||||
|
if len(opts) > 0 {
|
||||||
|
seed = mutateIPOptions(opts, seed, bitmapMut>>1)
|
||||||
|
fields++
|
||||||
|
bitmapMut >>= 1 // extra bit for variant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitmapMut >>= 1
|
||||||
|
fields++
|
||||||
|
|
||||||
|
ifrm.SetCRC(0)
|
||||||
|
ifrm.SetCRC(ifrm.CalculateHeaderCRC())
|
||||||
|
|
||||||
|
tl := ifrm.TotalLength()
|
||||||
|
if int(tl) > len(ipBuf) {
|
||||||
|
tl = uint16(len(ipBuf))
|
||||||
|
}
|
||||||
|
_, ihl = ifrm.VersionAndIHL()
|
||||||
|
hl := uint16(ihl) * 4
|
||||||
|
if hl > tl {
|
||||||
|
return fields, seed, bitmapMut
|
||||||
|
}
|
||||||
|
transportBuf := ipBuf[hl:tl]
|
||||||
|
|
||||||
|
var n int
|
||||||
|
switch ifrm.Protocol() {
|
||||||
|
case lneto.IPProtoTCP:
|
||||||
|
n, seed, bitmapMut = pm.MutateTCP(transportBuf, ifrm, seed, bitmapMut)
|
||||||
|
case lneto.IPProtoUDP:
|
||||||
|
n, seed, bitmapMut = pm.MutateUDP(transportBuf, ifrm, seed, bitmapMut)
|
||||||
|
case lneto.IPProtoICMP:
|
||||||
|
n, seed, bitmapMut = pm.MutateICMP(transportBuf, seed, bitmapMut)
|
||||||
|
}
|
||||||
|
return fields + n, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutateIPOptions writes adversarial IP option bytes into the options region.
|
||||||
|
// Strategies selected by seed bits:
|
||||||
|
// - 0: All NOPs (padding that shouldn't affect parsing but inflates IHL)
|
||||||
|
// - 1: Record Route with claimed length exceeding option space
|
||||||
|
// - 2: Garbage bytes (random option kinds with random lengths)
|
||||||
|
// - 3: Option with length=0 (infinite loop in naive parsers)
|
||||||
|
// - 4: Option with length=1 (length includes kind but not length byte itself)
|
||||||
|
// - 5: Nested contradictions: valid kind, absurd length crossing into transport
|
||||||
|
// - 6: Single option claiming entire IP packet as option data
|
||||||
|
// - 7: Fill with End-of-Options (0x00) — parser should stop immediately
|
||||||
|
func mutateIPOptions(opts []byte, seed int64, variant int64) int64 {
|
||||||
|
strategy := variant & 0x7
|
||||||
|
switch strategy {
|
||||||
|
case 0: // All NOPs.
|
||||||
|
for i := range opts {
|
||||||
|
opts[i] = 1 // NOP
|
||||||
|
}
|
||||||
|
case 1: // Record Route (type 7) with oversize length.
|
||||||
|
if len(opts) >= 3 {
|
||||||
|
opts[0] = 7 // Record Route
|
||||||
|
opts[1] = byte(len(opts)) + 40 // length way past option space
|
||||||
|
opts[2] = 4 // pointer
|
||||||
|
for i := 3; i < len(opts); i++ {
|
||||||
|
opts[i] = byte(seed >> uint(i%8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 2: // Garbage: random kind + random length pairs.
|
||||||
|
for i := 0; i < len(opts); {
|
||||||
|
opts[i] = byte(seed)
|
||||||
|
seed >>= 3
|
||||||
|
if i+1 < len(opts) {
|
||||||
|
opts[i+1] = byte(seed) // random length field
|
||||||
|
seed >>= 4
|
||||||
|
}
|
||||||
|
i += 2
|
||||||
|
}
|
||||||
|
case 3: // Option with length=0 (infinite loop trap).
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = 68 // Timestamp option kind
|
||||||
|
opts[1] = 0 // length=0: parser that loops on length will hang
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 4: // Option with length=1 (only covers kind byte).
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = 7 // Record Route
|
||||||
|
opts[1] = 1 // length=1: doesn't even cover the length byte
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = 1 // NOP fill
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 5: // Valid kind, length extends into transport header.
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = 68 // Timestamp
|
||||||
|
opts[1] = byte(len(opts) + 20) // extends 20 bytes into transport
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = byte(seed)
|
||||||
|
seed >>= 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 6: // Single option claiming huge data.
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = 130 // Security option kind
|
||||||
|
opts[1] = 255 // max possible length
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = 0xCC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 7: // All End-of-Options.
|
||||||
|
for i := range opts {
|
||||||
|
opts[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutateTCP mutates TCP fields: Seq, Ack, Flags, WindowSize, DataOffset and
|
||||||
|
// optionally injects adversarial TCP options.
|
||||||
|
// ifrm is needed for pseudo-header CRC recalculation.
|
||||||
|
func (pm PacketMut) MutateTCP(transportBuf []byte, ifrm ipv4.Frame, seed, bitmapMut int64) (fields int, seedOut, bitmapOut int64) {
|
||||||
|
tfrm, err := tcp.NewFrame(transportBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
off, flags := tfrm.OffsetAndFlags()
|
||||||
|
|
||||||
|
seed, bitmapMut = mutate32(seed, bitmapMut, func(v uint32) { tfrm.SetSeq(tcp.Value(v)) }, uint32(tfrm.Seq()))
|
||||||
|
seed, bitmapMut = mutate32(seed, bitmapMut, func(v uint32) { tfrm.SetAck(tcp.Value(v)) }, uint32(tfrm.Ack()))
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { tfrm.SetOffsetAndFlags(off, tcp.Flags(v).Mask()) }, uint8(flags))
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, tfrm.SetWindowSize, tfrm.WindowSize())
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { tfrm.SetOffsetAndFlags(v&0xf, flags) }, off)
|
||||||
|
fields = 5
|
||||||
|
|
||||||
|
// TCP option injection: 2 bits consumed (inject + variant selector).
|
||||||
|
if bitmapMut&1 != 0 && tfrm.HeaderLength() >= 20 && tfrm.HeaderLength() <= len(transportBuf) {
|
||||||
|
opts := tfrm.Options()
|
||||||
|
if len(opts) > 0 {
|
||||||
|
seed = mutateTCPOptions(opts, seed, bitmapMut>>1)
|
||||||
|
fields++
|
||||||
|
bitmapMut >>= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitmapMut >>= 1
|
||||||
|
fields++
|
||||||
|
|
||||||
|
tfrm.SetCRC(0)
|
||||||
|
var crc lneto.CRC791
|
||||||
|
ifrm.CRCWriteTCPPseudo(&crc)
|
||||||
|
tfrm.SetCRC(crc.PayloadSum16(transportBuf))
|
||||||
|
return fields, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutateTCPOptions writes adversarial TCP option bytes into the options region.
|
||||||
|
// Strategies selected by seed bits:
|
||||||
|
// - 0: MSS with extreme value (1 or 65535)
|
||||||
|
// - 1: Window Scale with huge shift (>14, RFC max is 14)
|
||||||
|
// - 2: Option with length=0 (infinite loop trap)
|
||||||
|
// - 3: Option length exceeds remaining space (truncated option)
|
||||||
|
// - 4: SACK blocks with impossible ranges (garbage SACK data)
|
||||||
|
// - 5: Duplicate MSS options (which one wins?)
|
||||||
|
// - 6: Valid-looking options followed by garbage past End marker
|
||||||
|
// - 7: All NOPs (max padding, no real options)
|
||||||
|
func mutateTCPOptions(opts []byte, seed int64, variant int64) int64 {
|
||||||
|
strategy := variant & 0x7
|
||||||
|
switch strategy {
|
||||||
|
case 0: // MSS extreme values.
|
||||||
|
if len(opts) >= 4 {
|
||||||
|
opts[0] = byte(tcp.OptMaxSegmentSize) // kind=2
|
||||||
|
opts[1] = 4 // length=4
|
||||||
|
if seed&1 != 0 {
|
||||||
|
opts[2], opts[3] = 0xFF, 0xFF // MSS=65535
|
||||||
|
} else {
|
||||||
|
opts[2], opts[3] = 0x00, 0x01 // MSS=1
|
||||||
|
}
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
for i := 4; i < len(opts); i++ {
|
||||||
|
opts[i] = 0 // End
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 1: // Window Scale with illegal shift.
|
||||||
|
if len(opts) >= 3 {
|
||||||
|
opts[0] = byte(tcp.OptWindowScale) // kind=3
|
||||||
|
opts[1] = 3 // length=3
|
||||||
|
opts[2] = byte(seed&0x1F) | 0x10 // shift 16-31, RFC max=14
|
||||||
|
seed >>= 5
|
||||||
|
for i := 3; i < len(opts); i++ {
|
||||||
|
opts[i] = 1 // NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 2: // Option with length=0.
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = byte(tcp.OptMaxSegmentSize)
|
||||||
|
opts[1] = 0 // length=0: naive parser loops forever
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3: // Option length exceeds remaining space.
|
||||||
|
if len(opts) >= 2 {
|
||||||
|
opts[0] = byte(tcp.OptSACK)
|
||||||
|
opts[1] = byte(len(opts) + 10) // extends past option region
|
||||||
|
for i := 2; i < len(opts); i++ {
|
||||||
|
opts[i] = byte(seed)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 4: // SACK with garbage block data.
|
||||||
|
if len(opts) >= 10 {
|
||||||
|
opts[0] = byte(tcp.OptSACK) // kind=5
|
||||||
|
sackLen := len(opts)
|
||||||
|
if sackLen > 34 {
|
||||||
|
sackLen = 34 // max 4 SACK blocks
|
||||||
|
}
|
||||||
|
opts[1] = byte(sackLen)
|
||||||
|
for i := 2; i < sackLen; i++ {
|
||||||
|
opts[i] = byte(seed)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
for i := sackLen; i < len(opts); i++ {
|
||||||
|
opts[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 5: // Duplicate MSS options (parser picks first? last? panics?).
|
||||||
|
for i := 0; i+4 <= len(opts); i += 4 {
|
||||||
|
opts[i] = byte(tcp.OptMaxSegmentSize)
|
||||||
|
opts[i+1] = 4
|
||||||
|
mss := uint16(seed & 0xFFFF)
|
||||||
|
opts[i+2] = byte(mss >> 8)
|
||||||
|
opts[i+3] = byte(mss)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
case 6: // Valid option then garbage after End marker.
|
||||||
|
if len(opts) >= 6 {
|
||||||
|
opts[0] = byte(tcp.OptWindowScale)
|
||||||
|
opts[1] = 3
|
||||||
|
opts[2] = 7 // valid shift
|
||||||
|
opts[3] = 0 // End-of-Options
|
||||||
|
// Garbage after End — should be ignored but tests parser bounds.
|
||||||
|
for i := 4; i < len(opts); i++ {
|
||||||
|
opts[i] = byte(seed) | 0x80 // high-bit kinds (undefined)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 7: // All NOPs — maximum padding, option parser iterates through each.
|
||||||
|
for i := range opts {
|
||||||
|
opts[i] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return seed
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutateUDP mutates the UDP Length field.
|
||||||
|
// ifrm is needed for pseudo-header CRC recalculation.
|
||||||
|
func (pm PacketMut) MutateUDP(transportBuf []byte, ifrm ipv4.Frame, seed, bitmapMut int64) (fields int, seedOut, bitmapOut int64) {
|
||||||
|
ufrm, err := udp.NewFrame(transportBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, ufrm.SetLength, ufrm.Length())
|
||||||
|
|
||||||
|
ufrm.SetCRC(0)
|
||||||
|
var crc lneto.CRC791
|
||||||
|
ifrm.CRCWriteUDPPseudo(&crc, ufrm.Length())
|
||||||
|
ufrm.SetCRC(crc.PayloadSum16(transportBuf))
|
||||||
|
return 1, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutateICMP mutates ICMP Type and Code fields.
|
||||||
|
func (pm PacketMut) MutateICMP(transportBuf []byte, seed, bitmapMut int64) (fields int, seedOut, bitmapOut int64) {
|
||||||
|
frm, err := icmpv4.NewFrame(transportBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { frm.SetType(icmpv4.Type(v)) }, uint8(frm.Type()))
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, frm.SetCode, frm.Code())
|
||||||
|
|
||||||
|
frm.SetCRC(0)
|
||||||
|
var crc lneto.CRC791
|
||||||
|
frm.SetCRC(crc.PayloadSum16(transportBuf))
|
||||||
|
return 2, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutateARP mutates ARP frame fields: Operation, hardware type/length,
|
||||||
|
// protocol type/length, and sender/target addresses.
|
||||||
|
func (pm PacketMut) MutateARP(arpBuf []byte, seed, bitmapMut int64) (fields int, seedOut, bitmapOut int64) {
|
||||||
|
afrm, err := arp.NewFrame(arpBuf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field: Operation (Request/Reply/garbage).
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, func(v uint16) { afrm.SetOperation(arp.Operation(v)) }, uint16(afrm.Operation()))
|
||||||
|
|
||||||
|
htype, hlen := afrm.Hardware()
|
||||||
|
ptype, plen := afrm.Protocol()
|
||||||
|
|
||||||
|
// Mutate sender/target protocol addresses BEFORE length fields,
|
||||||
|
// since length mutations shift where Sender/Target point.
|
||||||
|
_, senderProto := afrm.Sender()
|
||||||
|
if len(senderProto) > 0 && bitmapMut&1 != 0 {
|
||||||
|
for i := range senderProto {
|
||||||
|
senderProto[i] = byte(seed)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitmapMut >>= 1
|
||||||
|
|
||||||
|
_, targetProto := afrm.Target()
|
||||||
|
if len(targetProto) > 0 && bitmapMut&1 != 0 {
|
||||||
|
for i := range targetProto {
|
||||||
|
targetProto[i] = byte(seed)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bitmapMut >>= 1
|
||||||
|
|
||||||
|
// Field: Hardware type (wrong htype → mismatch).
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, func(v uint16) { afrm.SetHardware(v, hlen) }, htype)
|
||||||
|
// Field: Hardware length (wrong hlen shifts all subsequent field offsets).
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { afrm.SetHardware(htype, v) }, hlen)
|
||||||
|
// Field: Protocol type (wrong proto → mismatch).
|
||||||
|
seed, bitmapMut = mutate16(seed, bitmapMut, func(v uint16) { afrm.SetProtocol(ethernet.Type(v), plen) }, uint16(ptype))
|
||||||
|
// Field: Protocol length (wrong plen shifts target field offsets).
|
||||||
|
seed, bitmapMut = mutate8(seed, bitmapMut, func(v uint8) { afrm.SetProtocol(ptype, v) }, plen)
|
||||||
|
fields = 7
|
||||||
|
|
||||||
|
return fields, seed, bitmapMut
|
||||||
|
}
|
||||||
|
|
||||||
|
func mutate8(seed, bitmapMut int64, set func(uint8), cur uint8) (int64, int64) {
|
||||||
|
if bitmapMut&1 != 0 {
|
||||||
|
v := uint8(seed) ^ cur
|
||||||
|
if v == cur {
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
set(v)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
return seed, bitmapMut >> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func mutate16(seed, bitmapMut int64, set func(uint16), cur uint16) (int64, int64) {
|
||||||
|
if bitmapMut&1 != 0 {
|
||||||
|
v := uint16(seed) ^ cur
|
||||||
|
if v == cur {
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
set(v)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
|
||||||
|
}
|
||||||
|
return seed, bitmapMut >> 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func mutate32(seed, bitmapMut int64, set func(uint32), cur uint32) (int64, int64) {
|
||||||
|
if bitmapMut&1 != 0 {
|
||||||
|
v := uint32(seed) ^ cur
|
||||||
|
if v == cur {
|
||||||
|
v++
|
||||||
|
}
|
||||||
|
set(v)
|
||||||
|
seed = internal.Prand64(seed)
|
||||||
|
}
|
||||||
|
return seed, bitmapMut >> 1
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ func Prand32[T ~uint32](seed T) T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prand32 generates a pseudo random number from a seed.
|
// Prand32 generates a pseudo random number from a seed.
|
||||||
func Prand64[T ~uint64](seed T) T {
|
func Prand64[T ~uint64 | ~int64](seed T) T {
|
||||||
seed ^= seed << 13
|
seed ^= seed << 13
|
||||||
seed ^= seed >> 7
|
seed ^= seed >> 7
|
||||||
seed ^= seed << 17
|
seed ^= seed << 17
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ func (client *Client) Demux(carrierData []byte, frameOffset int) error {
|
|||||||
// We received a ping request; not handled client-side.
|
// We received a ping request; not handled client-side.
|
||||||
efrm := FrameEcho{Frame: ifrm}
|
efrm := FrameEcho{Frame: ifrm}
|
||||||
data := efrm.Data()
|
data := efrm.Data()
|
||||||
|
if len(data) == 0 {
|
||||||
|
return lneto.ErrPacketDrop
|
||||||
|
}
|
||||||
n, werr := client.responseRing.Write(data)
|
n, werr := client.responseRing.Write(data)
|
||||||
if werr != nil {
|
if werr != nil {
|
||||||
err = werr
|
err = werr
|
||||||
@@ -127,6 +130,9 @@ func (client *Client) Demux(carrierData []byte, frameOffset int) error {
|
|||||||
case TypeEchoReply:
|
case TypeEchoReply:
|
||||||
efrm := FrameEcho{Frame: ifrm}
|
efrm := FrameEcho{Frame: ifrm}
|
||||||
data := efrm.Data()
|
data := efrm.Data()
|
||||||
|
if len(data) == 0 {
|
||||||
|
return lneto.ErrPacketDrop
|
||||||
|
}
|
||||||
hash := client.magichash(data, len(data)) & keyHashBits
|
hash := client.magichash(data, len(data)) & keyHashBits
|
||||||
idx := client.pingidx(hash)
|
idx := client.pingidx(hash)
|
||||||
if idx < 0 || (ipEnabled && client.outgoingEcho[idx].raddr != raddr) {
|
if idx < 0 || (ipEnabled && client.outgoingEcho[idx].raddr != raddr) {
|
||||||
@@ -232,7 +238,7 @@ func (client *Client) magichash(pattern []byte, size int) (hash uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) PingStart(remoteAddr [4]byte, pattern []byte, size uint16) (key uint32, err error) {
|
func (client *Client) PingStart(remoteAddr [4]byte, pattern []byte, size uint16) (key uint32, err error) {
|
||||||
if int(size) < len(pattern) {
|
if int(size) < len(pattern) || len(pattern) == 0 {
|
||||||
return 0, lneto.ErrInvalidConfig
|
return 0, lneto.ErrInvalidConfig
|
||||||
} else if remoteAddr == [4]byte{} {
|
} else if remoteAddr == [4]byte{} {
|
||||||
return 0, lneto.ErrZeroDestination
|
return 0, lneto.ErrZeroDestination
|
||||||
|
|||||||
+60
-5
@@ -1,7 +1,9 @@
|
|||||||
package udp
|
package udp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -55,29 +57,39 @@ func (conn *Conn) Configure(cfg ConnConfig) error {
|
|||||||
func (conn *Conn) Abort() {
|
func (conn *Conn) Abort() {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
conn.h.Abort()
|
|
||||||
conn.abort()
|
conn.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (conn *Conn) abort() {
|
func (conn *Conn) abort() {
|
||||||
|
conn.h.Abort()
|
||||||
conn.rdead = time.Time{}
|
conn.rdead = time.Time{}
|
||||||
conn.wdead = time.Time{}
|
conn.wdead = time.Time{}
|
||||||
conn.remoteAddr = conn.remoteAddr[:0]
|
conn.remoteAddr = conn.remoteAddr[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errStillOpen = errors.New("close udp conn before opening")
|
||||||
|
|
||||||
// Open sets the local port and remote address for the connection.
|
// Open sets the local port and remote address for the connection.
|
||||||
func (conn *Conn) Open(localPort, remotePort uint16, remoteAddr []byte) error {
|
func (conn *Conn) Open(localPort uint16, remoteAddr netip.AddrPort) error {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
conn.abort()
|
if conn.h.IsOpen() {
|
||||||
err := conn.h.SetPorts(localPort, remotePort)
|
return errStillOpen
|
||||||
|
}
|
||||||
|
err := conn.h.SetPorts(localPort, remoteAddr.Port())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
conn.remoteAddr = append(conn.remoteAddr[:0], remoteAddr...)
|
conn.remoteAddr = append(conn.remoteAddr[:0], remoteAddr.Addr().AsSlice()...)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (conn *Conn) IsOpen() bool {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.IsOpen()
|
||||||
|
}
|
||||||
|
|
||||||
// LocalPort returns the local port set by [Conn.Open].
|
// LocalPort returns the local port set by [Conn.Open].
|
||||||
func (conn *Conn) LocalPort() uint16 {
|
func (conn *Conn) LocalPort() uint16 {
|
||||||
conn.mu.Lock()
|
conn.mu.Lock()
|
||||||
@@ -229,3 +241,46 @@ func (conn *Conn) deadlineExceeded(deadline *time.Time) bool {
|
|||||||
defer conn.mu.Unlock()
|
defer conn.mu.Unlock()
|
||||||
return !deadline.IsZero() && time.Since(*deadline) > 0
|
return !deadline.IsZero() && time.Since(*deadline) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferedInput returns the number of unread bytes in the receive buffer.
|
||||||
|
func (conn *Conn) BufferedInput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.BufferedInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferedUnsent returns the number of written but unsent bytes in the transmit buffer.
|
||||||
|
func (conn *Conn) BufferedOutput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.BufferedOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeInput returns the total size of the receive ring buffer.
|
||||||
|
func (conn *Conn) SizeInput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.SizeInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SizeOutput returns the total size of the transmit ring buffer.
|
||||||
|
func (conn *Conn) SizeOutput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.SizeOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeOutput returns the number of free bytes in the transmit buffer.
|
||||||
|
// This tells the user how many bytes can be written with Write method before write failing.
|
||||||
|
func (conn *Conn) FreeOutput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.FreeOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeInput returns the number of free bytes in the receive buffer.
|
||||||
|
func (conn *Conn) FreeInput() int {
|
||||||
|
conn.mu.Lock()
|
||||||
|
defer conn.mu.Unlock()
|
||||||
|
return conn.h.FreeInput()
|
||||||
|
}
|
||||||
|
|||||||
+2
-1
@@ -2,6 +2,7 @@ package udp
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"net/netip"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/soypat/lneto/internal"
|
"github.com/soypat/lneto/internal"
|
||||||
@@ -29,7 +30,7 @@ func newTestConn(t *testing.T) *Conn {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
err = conn.Open(1234, 8080, []byte{10, 0, 0, 1})
|
err = conn.Open(1234, netip.AddrPortFrom(netip.AddrFrom4([4]byte{10, 0, 0, 1}), 8080))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,6 +164,11 @@ func (h *Handler) Read(b []byte) (int, error) {
|
|||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsOpen returns true if the handler can send/receive data.
|
||||||
|
func (h *Handler) IsOpen() bool {
|
||||||
|
return !h.closeCalled && h.lport > 0
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the connection. Calls to [Handler.Send] and [Handler.Recv] will
|
// Close closes the connection. Calls to [Handler.Send] and [Handler.Recv] will
|
||||||
// return [net.ErrClosed] after Close is called.
|
// return [net.ErrClosed] after Close is called.
|
||||||
func (h *Handler) Close() {
|
func (h *Handler) Close() {
|
||||||
@@ -212,3 +217,14 @@ func (h *Handler) SizeInput() int {
|
|||||||
func (h *Handler) SizeOutput() int {
|
func (h *Handler) SizeOutput() int {
|
||||||
return h.txRing.Size()
|
return h.txRing.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FreeOutput returns the number of free bytes in the transmit buffer.
|
||||||
|
// This tells the user how many bytes can be written with Write method before write failing.
|
||||||
|
func (h *Handler) FreeOutput() int {
|
||||||
|
return h.txRing.Free()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreeInput returns the number of free bytes in the receive buffer.
|
||||||
|
func (h *Handler) FreeInput() int {
|
||||||
|
return h.rxRing.Free()
|
||||||
|
}
|
||||||
|
|||||||
+35
-5
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/soypat/lneto/ipv4/icmpv4"
|
"github.com/soypat/lneto/ipv4/icmpv4"
|
||||||
"github.com/soypat/lneto/ntp"
|
"github.com/soypat/lneto/ntp"
|
||||||
"github.com/soypat/lneto/tcp"
|
"github.com/soypat/lneto/tcp"
|
||||||
|
"github.com/soypat/lneto/udp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -32,7 +33,7 @@ type StackAsync struct {
|
|||||||
ip internet.StackIP
|
ip internet.StackIP
|
||||||
arp arp.Handler
|
arp arp.Handler
|
||||||
icmp icmpv4.Client
|
icmp icmpv4.Client
|
||||||
udps internet.StackPorts
|
udps internet.StackPortsMACFiltered
|
||||||
tcps internet.StackPortsMACFiltered
|
tcps internet.StackPortsMACFiltered
|
||||||
|
|
||||||
dhcpUDP internet.StackUDPPort
|
dhcpUDP internet.StackUDPPort
|
||||||
@@ -353,6 +354,35 @@ func (s *StackAsync) EnableICMP(enabled bool) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StackAsync) DialUDP(conn *udp.Conn, localPort uint16, addrp netip.AddrPort) (err error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
var mac []byte
|
||||||
|
if s.subnet.Contains(addrp.Addr()) {
|
||||||
|
mac = make([]byte, 6)
|
||||||
|
ip := addrp.Addr().As4()
|
||||||
|
hw, err := s.arp.QueryResult(ip[:])
|
||||||
|
if err == nil {
|
||||||
|
// MAC already contained in results.
|
||||||
|
copy(mac, hw)
|
||||||
|
} else {
|
||||||
|
// StartQuery starts an ARP query for addresses in this network.
|
||||||
|
// On finishing query MAC is set and thus the StackPort will allow encapsulating
|
||||||
|
// data on that connection.
|
||||||
|
err = s.arp.StartQuery(mac, ip[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = conn.Open(localPort, addrp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.udps.Register(conn, mac)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort) (err error) {
|
func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrPort) (err error) {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
@@ -423,7 +453,7 @@ func (s *StackAsync) RegisterUDP(node lneto.StackNode, remoteAddr []byte, remote
|
|||||||
}
|
}
|
||||||
s.userUDPs = s.userUDPs[:idx+1]
|
s.userUDPs = s.userUDPs[:idx+1]
|
||||||
s.userUDPs[idx].SetStackNode(node, remoteAddr, remotePort)
|
s.userUDPs[idx].SetStackNode(node, remoteAddr, remotePort)
|
||||||
return s.udps.Register(&s.userUDPs[idx])
|
return s.udps.Register(&s.userUDPs[idx], nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errNoDNSServer = errors.New("no DNS server- did DHCP complete? You can set a predetermined DNS server in Stack configuration")
|
var errNoDNSServer = errors.New("no DNS server- did DHCP complete? You can set a predetermined DNS server in Stack configuration")
|
||||||
@@ -461,7 +491,7 @@ func (s *StackAsync) StartLookupIP(host string) error {
|
|||||||
}
|
}
|
||||||
*(*[4]byte)(s.addrBuf[:4]) = s.dnssv.As4()
|
*(*[4]byte)(s.addrBuf[:4]) = s.dnssv.As4()
|
||||||
s.dnsUDP.SetStackNode(&s.dns, s.addrBuf[:4], dns.ServerPort)
|
s.dnsUDP.SetStackNode(&s.dns, s.addrBuf[:4], dns.ServerPort)
|
||||||
err = s.udps.Register(&s.dnsUDP)
|
err = s.udps.Register(&s.dnsUDP, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -511,7 +541,7 @@ func (s *StackAsync) StartDHCPv4Request(request [4]byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.dhcpUDP.SetStackNode(&s.dhcp, nil, dhcpv4.DefaultServerPort)
|
s.dhcpUDP.SetStackNode(&s.dhcp, nil, dhcpv4.DefaultServerPort)
|
||||||
err = s.udps.Register(&s.dhcpUDP)
|
err = s.udps.Register(&s.dhcpUDP, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -525,7 +555,7 @@ func (s *StackAsync) StartNTP(addr netip.Addr) error {
|
|||||||
|
|
||||||
*(*[4]byte)(s.addrBuf[:4]) = addr.As4()
|
*(*[4]byte)(s.addrBuf[:4]) = addr.As4()
|
||||||
s.ntpUDP.SetStackNode(&s.ntp, s.addrBuf[:4], ntp.ServerPort)
|
s.ntpUDP.SetStackNode(&s.ntp, s.addrBuf[:4], ntp.ServerPort)
|
||||||
err := s.udps.Register(&s.ntpUDP)
|
err := s.udps.Register(&s.ntpUDP, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-6
@@ -99,12 +99,7 @@ func (s StackGo) SocketNetip(ctx context.Context, network string, family, sotype
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
raddr4 := raddr.Addr().As4()
|
err = s.blk.async.DialUDP(&conn, laddr.Port(), raddr)
|
||||||
err = conn.Open(laddr.Port(), raddr.Port(), raddr4[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = s.blk.async.RegisterUDP(&conn, raddr4[:], raddr.Port())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user