mirror of
https://github.com/soypat/lneto.git
synced 2026-08-26 17:39:29 +00:00
passive MAC learning and ARP cache revamp (#96)
* begin working on arp cache * fix little things * rework arp cache priority * fix test * keep working on ARP * push changes before thinking about ip prefix issue * add passive peer MAC setting * patch egress mac with correct ethernet CRCs * consolidate subnet learning in subnetTable type * add tests and fix subnet table bug
This commit is contained in:
+31
-16
@@ -39,7 +39,7 @@ type StackAsync struct {
|
||||
dhcpUDP internet.StackUDPPort
|
||||
dhcp dhcpv4.Client
|
||||
dhcpResults DHCPResults
|
||||
subnet netip.Prefix // Local subnet for ARP resolution.
|
||||
arpt subnetTable
|
||||
|
||||
dnsUDP internet.StackUDPPort
|
||||
dns dns.Client
|
||||
@@ -83,6 +83,9 @@ type StackConfig struct {
|
||||
// ICMPQueueLimit sets maximum number of input/output packets queued for processing.
|
||||
// If set to zero ICMP cannot be enabled on the stack.
|
||||
ICMPQueueLimit int
|
||||
// PassivePeers limits how many subnet peers the stack passively learns MAC addresses for.
|
||||
// Passively learned entries skip ARP round-trips on the first DialTCP/DialUDP to that peer.
|
||||
PassivePeers int
|
||||
}
|
||||
|
||||
func (s *StackAsync) Hostname() string {
|
||||
@@ -94,7 +97,11 @@ func (s *StackAsync) IngressEthernet(ethernetFrame []byte) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.totalrecv += uint64(len(ethernetFrame))
|
||||
return s.link.Demux(ethernetFrame, 0)
|
||||
err := s.link.Demux(ethernetFrame, 0)
|
||||
if err == nil {
|
||||
s.arpt.learnFromIngressEthernet(ethernetFrame)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EgressEthernet writes the next ethernet frame to send into dstEthernetFrame from the stack.
|
||||
@@ -137,7 +144,7 @@ func (s *StackAsync) MTU() int {
|
||||
}
|
||||
|
||||
func (s *StackAsync) Reset(cfg StackConfig) error {
|
||||
if cfg.RandSeed == 0 || cfg.Hostname == "" {
|
||||
if cfg.RandSeed == 0 || cfg.Hostname == "" || cfg.PassivePeers > 255 {
|
||||
return lneto.ErrInvalidConfig
|
||||
}
|
||||
mac := cfg.HardwareAddress
|
||||
@@ -165,12 +172,18 @@ func (s *StackAsync) Reset(cfg StackConfig) error {
|
||||
return err
|
||||
}
|
||||
s.link.SetAcceptMulticast(cfg.AcceptMulticast)
|
||||
if cfg.PassivePeers == 0 {
|
||||
s.link.OnEncapsulate(nil)
|
||||
} else {
|
||||
s.link.OnEncapsulate(s.arpt.patchEgressMAC)
|
||||
}
|
||||
const ipNodes = 3 // 3 IP protocols possible: UDP, TCP, ICMP.
|
||||
err = s.ip.Reset(addr, ipNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.ip.SetAcceptMulticast(cfg.AcceptMulticast)
|
||||
s.arpt.passivePeers = uint8(cfg.PassivePeers)
|
||||
err = s.resetARP()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -241,14 +254,16 @@ func (s *StackAsync) resetARP() error {
|
||||
err := s.arp.Reset(arp.HandlerConfig{
|
||||
HardwareAddr: mac[:],
|
||||
ProtocolAddr: addr.AsSlice(),
|
||||
MaxQueries: 3,
|
||||
MaxPending: 3,
|
||||
MaxQueries: 5,
|
||||
MaxPending: 5,
|
||||
HardwareType: 1,
|
||||
ProtocolType: proto,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.arpt.reset(10, s.arpt.passivePeers)
|
||||
s.arp.SetOnResolveCallback(s.arpt.onResolve)
|
||||
err = s.link.Register(&s.arp)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -308,7 +323,7 @@ func (s *StackAsync) Addr() netip.Addr {
|
||||
func (s *StackAsync) SetSubnet(subnetMask netip.Prefix) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.subnet = subnetMask
|
||||
s.arpt.subnet = subnetMask
|
||||
}
|
||||
|
||||
func (s *StackAsync) SetHardwareAddress(hw [6]byte) error {
|
||||
@@ -358,10 +373,10 @@ func (s *StackAsync) DialUDP(conn *udp.Conn, localPort uint16, addrp netip.AddrP
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var mac []byte
|
||||
if s.subnet.Contains(addrp.Addr()) {
|
||||
if s.arpt.subnet.Contains(addrp.Addr()) {
|
||||
mac = make([]byte, 6)
|
||||
ip := addrp.Addr().As4()
|
||||
hw, err := s.arp.QueryResult(ip[:])
|
||||
hw, err := s.arp.CacheLookup(ip[:])
|
||||
if err == nil {
|
||||
// MAC already contained in results.
|
||||
copy(mac, hw)
|
||||
@@ -369,7 +384,7 @@ func (s *StackAsync) DialUDP(conn *udp.Conn, localPort uint16, addrp netip.AddrP
|
||||
// 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[:])
|
||||
err = s.arpt.startQuery(mac, ip[:], &s.arp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -387,9 +402,9 @@ func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrP
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
var mac []byte
|
||||
if s.subnet.Contains(addrp.Addr()) {
|
||||
if s.arpt.subnet.Contains(addrp.Addr()) {
|
||||
ip := addrp.Addr().As4()
|
||||
hw, err := s.arp.QueryResult(ip[:])
|
||||
hw, err := s.arp.CacheLookup(ip[:])
|
||||
mac = make([]byte, 6)
|
||||
if err == nil {
|
||||
// Query exists, use pre-existing result.
|
||||
@@ -398,7 +413,7 @@ func (s *StackAsync) DialTCP(conn *tcp.Conn, localPort uint16, addrp netip.AddrP
|
||||
// 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[:])
|
||||
err = s.arpt.startQuery(mac, ip[:], &s.arp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -576,7 +591,7 @@ func (s *StackAsync) StartResolveHardwareAddress6(ip netip.Addr) error {
|
||||
return lneto.ErrUnsupported
|
||||
}
|
||||
addr := ip.As4()
|
||||
return s.arp.StartQuery(nil, addr[:])
|
||||
return s.arp.StartQuery(addr[:], false)
|
||||
}
|
||||
|
||||
// ResultResolveHardwareAddress6
|
||||
@@ -587,7 +602,7 @@ func (s *StackAsync) ResultResolveHardwareAddress6(ip netip.Addr) (hw [6]byte, e
|
||||
return hw, lneto.ErrUnsupported
|
||||
}
|
||||
addr := ip.As4()
|
||||
hwslice, err := s.arp.QueryResult(addr[:])
|
||||
hwslice, err := s.arp.CacheLookup(addr[:])
|
||||
if err != nil {
|
||||
return hw, err
|
||||
} else if len(hwslice) != 6 {
|
||||
@@ -604,7 +619,7 @@ func (s *StackAsync) DiscardResolveHardwareAddress6(ip netip.Addr) error {
|
||||
return lneto.ErrUnsupported
|
||||
}
|
||||
addr := ip.As4()
|
||||
return s.arp.DiscardQuery(addr[:])
|
||||
return s.arp.CacheRemove(addr[:])
|
||||
}
|
||||
|
||||
type DHCPResults struct {
|
||||
@@ -648,7 +663,7 @@ func (stack *StackAsync) AssimilateDHCPResults(results *DHCPResults) error {
|
||||
stack.mu.Lock()
|
||||
defer stack.mu.Unlock()
|
||||
if results.Subnet.IsValid() {
|
||||
stack.subnet = results.Subnet
|
||||
stack.arpt.subnet = results.Subnet
|
||||
}
|
||||
if results.AssignedAddr.IsValid() {
|
||||
err := stack.setIPAddr(results.AssignedAddr)
|
||||
|
||||
@@ -140,7 +140,7 @@ func (s StackBlocking) DoResolveHardwareAddress6(addr netip.Addr, timeout time.D
|
||||
err = errDeadlineExceed // Ensure that if iterations done error is returned.
|
||||
}
|
||||
ip4 := addr.As4()
|
||||
s.async.arp.DiscardQuery(ip4[:])
|
||||
s.async.arp.CacheRemove(ip4[:])
|
||||
return hw, err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package xnet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
|
||||
"github.com/soypat/lneto/arp"
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/internal"
|
||||
)
|
||||
|
||||
// subnetTable manages both passively learned peer MAC/IP tuples and in-flight async ARP resolves.
|
||||
//
|
||||
// Layout of resolves slice:
|
||||
//
|
||||
// [0 : passivePeers] — owned MAC+IP, permanently retained (learned passively from ingress)
|
||||
// [passivePeers : len] — externally-owned MAC, evicted by age (pending ARP queries)
|
||||
type subnetTable struct {
|
||||
subnet netip.Prefix
|
||||
resolves []struct {
|
||||
mac []byte // externally owned for pending entries; owned for passive entries.
|
||||
ip []byte // always owned by this struct.
|
||||
age uint16
|
||||
}
|
||||
passivePeers uint8
|
||||
}
|
||||
|
||||
func (a *subnetTable) reset(arpentries int, passivePeers uint8) {
|
||||
a.passivePeers = passivePeers
|
||||
if a.resolves == nil {
|
||||
internal.SliceReuse(&a.resolves, arpentries+int(passivePeers))
|
||||
a.resolves = a.resolves[:cap(a.resolves)]
|
||||
}
|
||||
}
|
||||
|
||||
func (a *subnetTable) learnFromIngressEthernet(ethernetFrame []byte) {
|
||||
if len(ethernetFrame) > 14+20 &&
|
||||
binary.BigEndian.Uint16(ethernetFrame[12:14]) == uint16(ethernet.TypeIPv4) {
|
||||
src, _, _, _, _ := internal.GetIPAddr(ethernetFrame[14:])
|
||||
a.learnPassive(src, ethernetFrame[6:12])
|
||||
}
|
||||
}
|
||||
|
||||
// learnPassive stores or updates a passively observed MAC/IP tuple in the reserved slots.
|
||||
// It is a no-op if passivePeers is zero, src is not in the local subnet, or all slots are taken.
|
||||
func (a *subnetTable) learnPassive(src, mac []byte) {
|
||||
if a.passivePeers == 0 {
|
||||
return
|
||||
}
|
||||
addr, _ := netip.AddrFromSlice(src)
|
||||
if !a.subnet.Contains(addr) {
|
||||
return
|
||||
}
|
||||
for i := range a.passivePeers {
|
||||
v := &a.resolves[i]
|
||||
if internal.BytesEqual(v.ip, src) {
|
||||
copy(v.mac, mac) // update in case MAC changed (e.g. NIC swap)
|
||||
return
|
||||
}
|
||||
if len(v.ip) == 0 {
|
||||
v.ip = append(v.ip, src...)
|
||||
v.mac = append(v.mac, mac...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startQuery copies the MAC into mac immediately if the IP was passively learned,
|
||||
// otherwise issues an ARP query via h and registers mac as the externally-owned destination.
|
||||
func (a *subnetTable) startQuery(mac, ip []byte, h *arp.Handler) error {
|
||||
for i := range a.passivePeers {
|
||||
v := &a.resolves[i]
|
||||
if internal.BytesEqual(v.ip, ip) {
|
||||
copy(mac, v.mac)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if err := h.StartQuery(ip, true); err != nil {
|
||||
return err
|
||||
}
|
||||
n := int(a.passivePeers)
|
||||
oldest := n
|
||||
for i := n; i < len(a.resolves); i++ {
|
||||
v := &a.resolves[i]
|
||||
if len(v.mac) == 0 {
|
||||
oldest = i
|
||||
break
|
||||
} else if v.age > a.resolves[oldest].age {
|
||||
oldest = i
|
||||
}
|
||||
}
|
||||
for i := n; i < len(a.resolves); i++ {
|
||||
a.resolves[i].age++
|
||||
}
|
||||
v := &a.resolves[oldest]
|
||||
v.mac = mac
|
||||
v.ip = append(v.ip[:0], ip...)
|
||||
v.age = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// onResolve is the arp.Handler resolve callback; called when an ARP response arrives.
|
||||
func (a *subnetTable) onResolve(mac, ip []byte) {
|
||||
for i := int(a.passivePeers); i < len(a.resolves); i++ {
|
||||
v := &a.resolves[i]
|
||||
if internal.BytesEqual(ip, v.ip) {
|
||||
copy(v.mac, mac)
|
||||
v.mac = nil
|
||||
v.ip = v.ip[:0]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// patchEgressMAC is registered as the OnEncapsulate callback on StackEthernet.
|
||||
// It runs after the payload is written but before CRC is appended, so the CRC
|
||||
// covers the corrected destination MAC.
|
||||
func (a *subnetTable) patchEgressMAC(frame []byte) {
|
||||
if a.passivePeers == 0 || len(frame) < 14+20 ||
|
||||
binary.BigEndian.Uint16(frame[12:14]) != uint16(ethernet.TypeIPv4) {
|
||||
return
|
||||
}
|
||||
efrm, _ := ethernet.NewFrame(frame)
|
||||
if efrm.IsBroadcast() {
|
||||
return // broadcast stays broadcast (e.g. DHCP discover).
|
||||
}
|
||||
// Server-side connections have no registered MAC; fill from passively learned entries.
|
||||
_, dstIP, _, _, err := internal.GetIPAddr(frame[14:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for i := range a.passivePeers {
|
||||
v := &a.resolves[i]
|
||||
if internal.BytesEqual(v.ip, dstIP) {
|
||||
*efrm.DestinationHardwareAddr() = [6]byte(v.mac)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ func TestARPLocal(t *testing.T) {
|
||||
tst := testerFrom(t, mtu)
|
||||
_ = tst
|
||||
tst.ARPExchangeOnly(s1, s2)
|
||||
hwaddr, err := s1.arp.QueryResult(addr2.Addr().AsSlice())
|
||||
hwaddr, err := s1.arp.CacheLookup(addr2.Addr().AsSlice())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else if !bytes.Equal(hwaddr[:], hw2[:]) {
|
||||
|
||||
@@ -283,6 +283,7 @@ func testStackSeeded(t *testing.T, seed1, seed2 int64) {
|
||||
MTU: mtu,
|
||||
HardwareAddress: [6]byte{0x1, 0, 0, 0, 0, v1},
|
||||
AcceptMulticast: v1%2 == 0,
|
||||
PassivePeers: 1,
|
||||
}
|
||||
err := s1.Reset(cfg1)
|
||||
if err != nil {
|
||||
@@ -298,6 +299,7 @@ func testStackSeeded(t *testing.T, seed1, seed2 int64) {
|
||||
MTU: mtu,
|
||||
HardwareAddress: [6]byte{0x2, 0, 0, 0, 0, v2},
|
||||
AcceptMulticast: v2%2 == 0,
|
||||
PassivePeers: 1,
|
||||
}
|
||||
err = s2.Reset(cfg2)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
package xnet
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/soypat/lneto/ethernet"
|
||||
"github.com/soypat/lneto/tcp"
|
||||
)
|
||||
|
||||
// TestSubnetTable_PatchEgressMAC_WhenGatewayMAC captures the bug where patchEgressMAC
|
||||
// returns early when the Ethernet dst is a gateway MAC (not broadcast), so it never
|
||||
// patches the destination to the passively-learned client MAC.
|
||||
func TestSubnetTable_PatchEgressMAC_WhenGatewayMAC(t *testing.T) {
|
||||
clientIP := [4]byte{10, 0, 0, 1}
|
||||
clientMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}
|
||||
serverIP := [4]byte{10, 0, 0, 2}
|
||||
serverMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}
|
||||
gatewayMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF} // separate from client
|
||||
|
||||
var st subnetTable
|
||||
st.reset(4, 2)
|
||||
st.subnet = netip.MustParsePrefix("10.0.0.0/24")
|
||||
|
||||
// Learn client MAC from a simulated ingress frame (client→server SYN).
|
||||
ingressFrame := makeMinimalIPv4Frame(serverMAC, clientMAC, clientIP, serverIP)
|
||||
st.learnFromIngressEthernet(ingressFrame)
|
||||
|
||||
// Simulate egress SYN-ACK: stack uses gateway MAC as Ethernet dst (the bug).
|
||||
egressFrame := makeMinimalIPv4Frame(gatewayMAC, serverMAC, serverIP, clientIP)
|
||||
st.patchEgressMAC(egressFrame)
|
||||
|
||||
gotDst := [6]byte(egressFrame[0:6])
|
||||
if gotDst != clientMAC {
|
||||
t.Errorf("patchEgressMAC did not fix Ethernet dst:\n got %x (gateway MAC)\n want %x (client MAC)", gotDst, clientMAC)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStackAsync_ListenerSynAckAddressedToClient mirrors the ESP32 hotspot scenario:
|
||||
// server's gateway is a router (not the client), so the SYN-ACK must use the
|
||||
// passively-learned client MAC, not the router/gateway MAC.
|
||||
func TestStackAsync_ListenerSynAckAddressedToClient(t *testing.T) {
|
||||
const mtu = ethernet.MaxMTU
|
||||
const svPort = 80
|
||||
|
||||
clientMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01}
|
||||
serverMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02}
|
||||
routerMAC := [6]byte{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF} // third party — not client
|
||||
|
||||
// Server: gateway = router (not client), but passively learns client MAC from SYN.
|
||||
var sv StackAsync
|
||||
err := sv.Reset(StackConfig{
|
||||
Hostname: "Server1",
|
||||
RandSeed: 1234,
|
||||
StaticAddress: netip.AddrFrom4([4]byte{10, 0, 0, 2}),
|
||||
MaxActiveTCPPorts: 1,
|
||||
HardwareAddress: serverMAC,
|
||||
MTU: mtu,
|
||||
PassivePeers: 2,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sv.SetGateway6(routerMAC)
|
||||
sv.SetSubnet(netip.MustParsePrefix("10.0.0.0/24"))
|
||||
|
||||
pool, err := NewTCPPool(TCPPoolConfig{
|
||||
PoolSize: 1,
|
||||
QueueSize: 4,
|
||||
TxBufSize: mtu,
|
||||
RxBufSize: mtu,
|
||||
EstablishedTimeout: 10e9,
|
||||
ClosingTimeout: 10e9,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var listener tcp.Listener
|
||||
if err = listener.Reset(svPort, pool); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = sv.RegisterListener(&listener); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Client: gateway = server MAC (direct L2 path, as in a hotspot WLAN).
|
||||
var client StackAsync
|
||||
err = client.Reset(StackConfig{
|
||||
Hostname: "Client1",
|
||||
RandSeed: 5678,
|
||||
StaticAddress: netip.AddrFrom4([4]byte{10, 0, 0, 1}),
|
||||
MaxActiveTCPPorts: 1,
|
||||
HardwareAddress: clientMAC,
|
||||
MTU: mtu,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client.SetGateway6(serverMAC)
|
||||
|
||||
var clConn tcp.Conn
|
||||
if err = clConn.Configure(tcp.ConnConfig{
|
||||
RxBuf: make([]byte, mtu), TxBuf: make([]byte, mtu),
|
||||
TxPacketQueueSize: 4,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = client.DialTCP(&clConn, 54321, netip.AddrPortFrom(sv.Addr(), svPort)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := make([]byte, mtu+ethernet.MaxOverheadSize)
|
||||
|
||||
// Step 1: client egresses SYN.
|
||||
n, err := client.EgressEthernet(buf)
|
||||
if err != nil || n == 0 {
|
||||
t.Fatalf("client egress SYN: n=%d err=%v", n, err)
|
||||
}
|
||||
synDst := [6]byte(buf[0:6])
|
||||
if synDst != serverMAC {
|
||||
t.Fatalf("SYN Ethernet dst wrong: got %x, want server %x", synDst, serverMAC)
|
||||
}
|
||||
|
||||
// Step 2: server ingresses SYN — passively learns client MAC.
|
||||
if err = sv.IngressEthernet(buf[:n]); err != nil {
|
||||
t.Fatalf("server ingress SYN: %v", err)
|
||||
}
|
||||
|
||||
// Step 3: server egresses SYN-ACK — must be addressed to client, not router.
|
||||
clear(buf)
|
||||
n, err = sv.EgressEthernet(buf)
|
||||
if err != nil || n == 0 {
|
||||
t.Fatalf("server egress SYN-ACK: n=%d err=%v", n, err)
|
||||
}
|
||||
|
||||
synackDst := [6]byte(buf[0:6])
|
||||
if synackDst != clientMAC {
|
||||
t.Errorf("SYN-ACK Ethernet dst wrong:\n got %x\n want %x (client MAC)\n note: %x is router MAC", synackDst, clientMAC, routerMAC)
|
||||
}
|
||||
}
|
||||
|
||||
// makeMinimalIPv4Frame builds a 35-byte Ethernet+IPv4 frame (no payload, 1 padding byte).
|
||||
// This is the minimum size that passes both learnFromIngressEthernet (>34) and patchEgressMAC (>=34) checks.
|
||||
func makeMinimalIPv4Frame(dstMAC, srcMAC [6]byte, srcIP, dstIP [4]byte) []byte {
|
||||
frame := make([]byte, 35)
|
||||
copy(frame[0:6], dstMAC[:])
|
||||
copy(frame[6:12], srcMAC[:])
|
||||
binary.BigEndian.PutUint16(frame[12:14], uint16(ethernet.TypeIPv4))
|
||||
frame[14] = 0x45 // IPv4, IHL=5
|
||||
frame[22] = 64 // TTL
|
||||
copy(frame[26:30], srcIP[:])
|
||||
copy(frame[30:34], dstIP[:])
|
||||
return frame
|
||||
}
|
||||
Reference in New Issue
Block a user