diff --git a/arp/handler.go b/arp/handler.go index d6cb33c..b0f7b85 100644 --- a/arp/handler.go +++ b/arp/handler.go @@ -191,24 +191,25 @@ func (h *Handler) Encapsulate(carrierData []byte, offsetToIP, offsetToFrame int) return n, nil } for i := range h.queries { - if !h.queries[i].querysent { - h.queries[i].querysent = true - afrm, _ := NewFrame(b) - afrm.SetHardware(h.htype, uint8(len(h.ourHWAddr))) - afrm.SetProtocol(h.protoType, uint8(len(h.ourProtoAddr))) - afrm.SetOperation(OpRequest) - hwSender, protoSender := afrm.Sender() - copy(hwSender, h.ourHWAddr) - copy(protoSender, h.ourProtoAddr) - hwTarget, protoTarget := afrm.Target() - copy(protoTarget, h.queries[i].protoaddr) - for j := range hwTarget { - hwTarget[j] = 0 - } - broadcast := ethernet.BroadcastAddr() - trySetEthernetDst(carrierData[:offsetToFrame], broadcast[:]) - return n, nil + if h.queries[i].isInvalid() || h.queries[i].querysent { + continue } + h.queries[i].querysent = true + afrm, _ := NewFrame(b) + afrm.SetHardware(h.htype, uint8(len(h.ourHWAddr))) + afrm.SetProtocol(h.protoType, uint8(len(h.ourProtoAddr))) + afrm.SetOperation(OpRequest) + hwSender, protoSender := afrm.Sender() + copy(hwSender, h.ourHWAddr) + copy(protoSender, h.ourProtoAddr) + hwTarget, protoTarget := afrm.Target() + copy(protoTarget, h.queries[i].protoaddr) + for j := range hwTarget { + hwTarget[j] = 0 + } + broadcast := ethernet.BroadcastAddr() + trySetEthernetDst(carrierData[:offsetToFrame], broadcast[:]) + return n, nil } return 0, nil } diff --git a/x/xnet/stack-async.go b/x/xnet/stack-async.go index fd439aa..3847055 100644 --- a/x/xnet/stack-async.go +++ b/x/xnet/stack-async.go @@ -450,6 +450,17 @@ func (s *StackAsync) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, e return [6]byte(hwslice), nil } +// DiscardResolveHardwareAddress6 discards a pending ARP query for the given IP address. +func (s *StackAsync) DiscardResolveHardwareAddress6(ip netip.Addr) error { + s.mu.Lock() + defer s.mu.Unlock() + if !ip.Is4() { + return errors.New("unsupported or invalid IP address") + } + addr := ip.As4() + return s.arp.DiscardQuery(addr[:]) +} + type DHCPResults struct { DNSServers []netip.Addr Router netip.Addr diff --git a/x/xnet/xnet_bench_test.go b/x/xnet/xnet_bench_test.go new file mode 100644 index 0000000..7e6f9fb --- /dev/null +++ b/x/xnet/xnet_bench_test.go @@ -0,0 +1,173 @@ +package xnet + +import ( + "net/netip" + "testing" + + "github.com/soypat/lneto/tcp" +) + +func BenchmarkARPExchange(b *testing.B) { + const MTU = 1500 + c1, c2 := new(StackAsync), new(StackAsync) + queryAddr := netip.AddrFrom4([4]byte{192, 168, 1, 2}) + + err := c1.Reset(StackConfig{ + Hostname: "C1", + RandSeed: 1, + StaticAddress: netip.AddrFrom4([4]byte{192, 168, 1, 1}), + HardwareAddress: [6]byte{0xde, 0xad, 0xbe, 0xef, 0x00, 0x00}, + MTU: MTU, + }) + if err != nil { + b.Fatal(err) + } + err = c2.Reset(StackConfig{ + Hostname: "C2", + RandSeed: 2, + StaticAddress: queryAddr, + HardwareAddress: [6]byte{0xc0, 0xff, 0xee, 0xc0, 0xff, 0xee}, + MTU: MTU, + }) + if err != nil { + b.Fatal(err) + } + // Set gateways so ethernet frames are properly addressed. + c1.SetGateway6(c2.HardwareAddress()) + c2.SetGateway6(c1.HardwareAddress()) + + var buf [MTU]byte + + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = c1.StartResolveHardwareAddress6(queryAddr) + if err != nil { + b.Fatal(err) + } + n, err := c1.Encapsulate(buf[:], -1, 0) // Send Request. + if err != nil { + b.Fatal(err) + } else if n == 0 { + b.Fatal("expected send of data after first query") + } + err = c2.Demux(buf[:n], 0) // Receive request. + if err != nil { + b.Fatal(err) + } + n, err = c2.Encapsulate(buf[:], -1, 0) // Send response. + if err != nil { + b.Fatal(err) + } else if n == 0 { + b.Fatal("got no response to request") + } + err = c1.Demux(buf[:n], 0) // Receive response. + if err != nil { + b.Fatal(err) + } + _, err = c1.ResultResolveHardwareAddress6(queryAddr) + if err != nil { + b.Fatal("expected query result:", err) + } + // Discard query for next iteration. + c1.DiscardResolveHardwareAddress6(queryAddr) + } +} + +func BenchmarkTCPHandshake(b *testing.B) { + const MTU = 1500 + const svPort = 8080 + client, sv := new(StackAsync), new(StackAsync) + clconn, svconn := new(tcp.Conn), new(tcp.Conn) + + err := sv.Reset(StackConfig{ + Hostname: "Server", + RandSeed: 1, + StaticAddress: netip.AddrFrom4([4]byte{10, 0, 0, 1}), + MaxTCPConns: 1, + HardwareAddress: [6]byte{0xbe, 0xef, 0, 0, 0, 1}, + MTU: MTU, + }) + if err != nil { + b.Fatal(err) + } + err = client.Reset(StackConfig{ + Hostname: "Client", + RandSeed: 2, + StaticAddress: netip.AddrFrom4([4]byte{10, 0, 0, 2}), + MaxTCPConns: 1, + HardwareAddress: [6]byte{0xbe, 0xef, 0, 0, 0, 2}, + MTU: MTU, + }) + if err != nil { + b.Fatal(err) + } + sv.SetGateway6(client.HardwareAddress()) + client.SetGateway6(sv.HardwareAddress()) + + buf := make([]byte, MTU*4) + err = clconn.Configure(tcp.ConnConfig{ + RxBuf: buf[:MTU], + TxBuf: buf[MTU : MTU*2], + TxPacketQueueSize: 4, + }) + if err != nil { + b.Fatal(err) + } + err = svconn.Configure(tcp.ConnConfig{ + RxBuf: buf[2*MTU : 3*MTU], + TxBuf: buf[3*MTU : 4*MTU], + TxPacketQueueSize: 4, + }) + if err != nil { + b.Fatal(err) + } + + var pktbuf [MTU]byte + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Setup connections. + err = sv.ListenTCP(svconn, svPort) + if err != nil { + b.Fatal(err) + } + err = client.DialTCP(clconn, 1337, netip.AddrPortFrom(sv.Addr(), svPort)) + if err != nil { + b.Fatal(err) + } + + // SYN from client. + n, err := client.Encapsulate(pktbuf[:], -1, 0) + if err != nil { + b.Fatal(err) + } + err = sv.Demux(pktbuf[:n], 0) + if err != nil { + b.Fatal(err) + } + + // SYN-ACK from server. + n, err = sv.Encapsulate(pktbuf[:], -1, 0) + if err != nil { + b.Fatal(err) + } + err = client.Demux(pktbuf[:n], 0) + if err != nil { + b.Fatal(err) + } + + // ACK from client. + n, err = client.Encapsulate(pktbuf[:], -1, 0) + if err != nil { + b.Fatal(err) + } + err = sv.Demux(pktbuf[:n], 0) + if err != nil { + b.Fatal(err) + } + + // Abort connections for next iteration. + clconn.Abort() + svconn.Abort() + } +}