Frame Processing
Frame Processing
Section titled “Frame Processing”The internal/frame/ module provides low-level Ethernet frame processing for TAP device networking. This module is essential for Layer 2 mesh networking, enabling virtual LAN functionality between peers.
Overview
Section titled “Overview”The frame processing system provides:
- Ethernet Frame Parsing - Parse raw Ethernet frames from TAP devices
- Frame Building - Construct Ethernet frames for transmission
- ARP Handling - Process ARP requests/replies for address resolution
- MAC Table Management - Learn and lookup MAC addresses for forwarding decisions
Architecture
Section titled “Architecture”Module Components
Section titled “Module Components”| Component | File | Purpose |
|---|---|---|
EthernetFrame | ethernet.go | Ethernet frame parsing and building |
ARPPacket | arp.go | ARP packet handling and ARP interceptor |
MACTable | mac_table.go | Thread-safe MAC address learning and lookup |
Ethernet Frame Processing
Section titled “Ethernet Frame Processing”Frame Structure
Section titled “Frame Structure”The Ethernet frame follows the IEEE 802.3 standard:
Implementation details (internal/frame/ethernet.go):
// EthernetHeader represents an Ethernet frame headertype EthernetHeader struct { DstMAC net.HardwareAddr // Destination MAC address (6 bytes) SrcMAC net.HardwareAddr // Source MAC address (6 bytes) EtherType EtherType // EtherType field (2 bytes)}
// EthernetFrame represents a complete Ethernet frametype EthernetFrame struct { Header EthernetHeader Payload []byte Raw []byte // Original raw frame (for forwarding)}Size Constants
Section titled “Size Constants”| Constant | Value | Description |
|---|---|---|
EthernetHeaderSize | 14 bytes | Header size without payload |
MinEthernetFrame | 60 bytes | Minimum frame size (padded if smaller) |
MaxEthernetFrame | 1522 bytes | Maximum with VLAN tag |
MaxPayload | 1500 bytes | Standard MTU |
Supported EtherTypes
Section titled “Supported EtherTypes”| EtherType | Value | Description |
|---|---|---|
EtherTypeIPv4 | 0x0800 | IPv4 packets |
EtherTypeARP | 0x0806 | ARP packets |
EtherTypeIPv6 | 0x86DD | IPv6 packets |
EtherTypeVLAN | 0x8100 | 802.1Q VLAN-tagged frames |
Parsing Frames
Section titled “Parsing Frames”// Parse a raw Ethernet frame from TAP deviceframe, err := frame.ParseEthernetFrame(rawBytes)if err != nil { // Handle: ErrFrameTooShort, etc.}
// Access frame fieldsdestMAC := frame.Header.DstMACsrcMAC := frame.Header.SrcMACetherType := frame.Header.EtherTypepayload := frame.Payload
// Check frame typeif frame.IsIPv4() { // Process IPv4 packet} else if frame.IsARP() { // Process ARP packet}
// Check destination typeif frame.IsBroadcast() { // Flood to all peers} else if frame.IsMulticast() { // Send to multicast group} else { // Unicast - lookup MAC table}Building Frames
Section titled “Building Frames”// Build an Ethernet frameframeBytes, err := frame.BuildEthernetFrame( dstMAC, // Destination MAC srcMAC, // Source MAC frame.EtherTypeIPv4, // EtherType ipPayload, // IP packet payload)if err != nil { // Handle: ErrInvalidMAC, ErrPayloadTooLarge}
// Write to TAP device_, err = tapDevice.Write(frameBytes)Frame Processing Flow
Section titled “Frame Processing Flow”ARP Processing
Section titled “ARP Processing”ARP Packet Structure
Section titled “ARP Packet Structure”Implementation details (internal/frame/arp.go):
// ARPPacket represents an ARP packettype ARPPacket struct { HardwareType uint16 // Ethernet = 1 ProtocolType uint16 // IPv4 = 0x0800 HardwareAddrLen uint8 // 6 for Ethernet ProtocolAddrLen uint8 // 4 for IPv4 Operation uint16 // 1 = request, 2 = reply SenderHardwareAddr net.HardwareAddr SenderProtocolAddr netip.Addr TargetHardwareAddr net.HardwareAddr TargetProtocolAddr netip.Addr}ARP Operations
Section titled “ARP Operations”| Operation | Value | Description |
|---|---|---|
ARPRequest | 1 | Who has IP X? Tell IP Y |
ARPReply | 2 | IP X is at MAC Z |
ARP Interceptor
Section titled “ARP Interceptor”The ARPInterceptor handles ARP traffic for virtual network interfaces:
// Create ARP interceptor for a virtual interfaceinterceptor := frame.NewARPInterceptor( localMAC, // Our virtual MAC address localIP, // Our virtual IP address macTable, // MAC table for learning)
// Process incoming frameresponse := interceptor.HandleFrame(frameData)if response != nil { // Write ARP reply to TAP device tapDevice.Write(response)}ARP Request/Reply Flow
Section titled “ARP Request/Reply Flow”Building ARP Packets
Section titled “Building ARP Packets”// Build ARP requestarpRequest, _ := frame.BuildARPRequest( senderMAC, // Our MAC senderIP, // Our IP targetIP, // IP we're looking for)
// Build ARP replyarpReply, _ := frame.BuildARPReply( senderMAC, // Our MAC senderIP, // Our IP targetMAC, // Requester's MAC targetIP, // Requester's IP)
// Build complete Ethernet frame with ARP packetethFrame, _ := frame.BuildARPRequestFrame(senderMAC, senderIP, targetIP)// Automatically uses broadcast destination for requestsMAC Address Table
Section titled “MAC Address Table”Purpose
Section titled “Purpose”The MAC table maintains a mapping between:
- MAC addresses seen on the virtual network
- Peer IDs that own those MAC addresses
- Optional virtual IP addresses
This enables the mesh node to forward unicast frames directly to the correct peer.
MAC Entry Structure
Section titled “MAC Entry Structure”type MACEntry struct { MAC net.HardwareAddr // MAC address PeerID string // Peer that owns this MAC VirtualIP netip.Addr // Associated IP (if known) LastSeen time.Time // Last traffic timestamp LearnedAt time.Time // When first learned Static bool // Static entries don't expire}MAC Table Operations
Section titled “MAC Table Operations”Implementation details (internal/frame/mac_table.go):
| Operation | Description |
|---|---|
Learn(mac, peerID) | Add or update a MAC entry |
LearnWithIP(mac, peerID, ip) | Learn with associated IP |
LearnStatic(mac, peerID, ip) | Add non-expiring entry |
Lookup(mac) | Get peer ID for MAC |
LookupEntry(mac) | Get full entry for MAC |
LookupByIP(ip) | Find MAC by IP address |
GetPeerMACs(peerID) | Get all MACs for a peer |
Remove(mac) | Remove specific entry |
RemovePeer(peerID) | Remove all entries for peer |
Expire() | Remove stale entries |
Learning Process
Section titled “Learning Process”Entry Expiration
Section titled “Entry Expiration”Entries automatically expire after the configured MaxAge (default: 5 minutes):
// Configure MAC tablecfg := frame.MACTableConfig{ MaxAge: 5 * time.Minute,}table := frame.NewMACTable(cfg)
// Start background expiry workerstopCh := make(chan struct{})table.StartExpiryWorker(time.Minute, stopCh)
// Manual expirationexpired := table.Expire() // Returns count of expired entriesThread Safety
Section titled “Thread Safety”The MAC table is fully thread-safe:
- Uses
sync.RWMutexfor concurrent access - Read operations use
RLock()for parallelism - Write operations use
Lock()for exclusivity
Integration with Mesh Node
Section titled “Integration with Mesh Node”Frame Processing in MeshNode
Section titled “Frame Processing in MeshNode”The MeshNode (internal/mesh/node.go) integrates the frame module:
// MeshNode uses frame processing for TAP networkingtype MeshNode struct { // ... macTable *frame.MACTable arpHandler *frame.ARPInterceptor // ...}
// Initializationfunc (n *MeshNode) initializeDevice() error { // Create MAC table n.macTable = frame.NewMACTable(frame.MACTableConfig{ MaxAge: 5 * time.Minute, })
// Create ARP handler for TAP mode if n.config.Device.Type == "tap" && n.localMAC != nil { n.arpHandler = frame.NewARPInterceptor( n.localMAC, n.localIP, n.macTable, ) } // ...}TAP Frame Handling
Section titled “TAP Frame Handling”// handleTAPFrame processes frames from TAP devicefunc (n *MeshNode) handleTAPFrame(frameData []byte) { // Parse Ethernet frame ethFrame, err := frame.ParseEthernetFrame(frameData) if err != nil { return }
// Learn source MAC n.macTable.Learn(ethFrame.Header.SrcMAC, n.localPeerID)
// Handle ARP if ethFrame.Header.EtherType == frame.EtherTypeARP { response := n.arpHandler.HandleFrame(frameData) if response != nil { n.device.Write(response) } return }
// Route based on destination if frame.IsBroadcast(ethFrame.Header.DstMAC) { n.broadcast.Broadcast(frameData, 8) } else if frame.IsMulticast(ethFrame.Header.DstMAC) { n.broadcast.Multicast(groupID, frameData, 8) } else { // Unicast - lookup and send entry, found := n.macTable.LookupEntry(ethFrame.Header.DstMAC) if found { n.sendToP2P(entry.PeerID, frameData) } else { n.broadcast.Broadcast(frameData, 8) // Unknown - flood } }}Complete Frame Flow
Section titled “Complete Frame Flow”Extension Points
Section titled “Extension Points”Custom Frame Handlers
Section titled “Custom Frame Handlers”You can extend the frame processing by implementing custom handlers:
// Custom frame handler interface (conceptual)type FrameHandler interface { HandleFrame(frame *frame.EthernetFrame) (response []byte, handled bool)}
// Example: Custom protocol handlertype MyProtocolHandler struct { etherType frame.EtherType}
func (h *MyProtocolHandler) HandleFrame(f *frame.EthernetFrame) ([]byte, bool) { if f.Header.EtherType != h.etherType { return nil, false } // Process custom protocol return response, true}Custom MAC Table Filters
Section titled “Custom MAC Table Filters”Implement custom lookups using the Find method:
// Find all MACs for a specific IP subnetsubnetMACs := macTable.Find(func(e *frame.MACEntry) bool { if !e.VirtualIP.IsValid() { return false } prefix := netip.MustParsePrefix("10.100.0.0/24") return prefix.Contains(e.VirtualIP)})
// Find recently active MACsrecentMACs := macTable.Find(func(e *frame.MACEntry) bool { return time.Since(e.LastSeen) < time.Minute})Adding New EtherTypes
Section titled “Adding New EtherTypes”To handle additional protocols:
// Add new EtherType constantconst ( EtherTypeMyProtocol frame.EtherType = 0x88B5 // Example)
// Check for custom protocol in handlerif ethFrame.Header.EtherType == EtherTypeMyProtocol { handleMyProtocol(ethFrame.Payload)}Error Handling
Section titled “Error Handling”Common Errors
Section titled “Common Errors”| Error | Cause | Solution |
|---|---|---|
ErrFrameTooShort | Frame < 14 bytes | Check TAP device reads |
ErrFrameTooLong | Frame > 1522 bytes | Check MTU settings |
ErrInvalidMAC | MAC not 6 bytes | Validate input addresses |
ErrPayloadTooLarge | Payload > 1500 bytes | Fragment or reduce MTU |
ErrARPTooShort | ARP packet < 28 bytes | Check frame parsing |
ErrARPInvalidType | Non-Ethernet/IPv4 ARP | Only Ethernet/IPv4 supported |
Error Handling Pattern
Section titled “Error Handling Pattern”frame, err := frame.ParseEthernetFrame(data)if err != nil { switch { case errors.Is(err, frame.ErrFrameTooShort): // Log and skip malformed frame slog.Debug("frame too short", "len", len(data)) case errors.Is(err, frame.ErrFrameTooLong): // Possible MTU issue slog.Warn("frame too long", "len", len(data)) default: slog.Error("frame parse error", "error", err) } return}Performance Considerations
Section titled “Performance Considerations”Memory Efficiency
Section titled “Memory Efficiency”- Frames use
Rawfield to preserve original bytes for forwarding - MAC table uses pre-allocated maps
- Entry expiration prevents unbounded growth
Throughput Optimization
Section titled “Throughput Optimization”- Minimize copying: Use
frame.Rawfor forwarding instead of rebuilding - Batch operations: Process multiple frames before flushing
- Tune MAC table expiry: Balance memory vs. lookup misses
Benchmarks
Section titled “Benchmarks”Typical performance characteristics:
| Operation | Approximate Time |
|---|---|
| Parse Ethernet frame | ~50 ns |
| Build Ethernet frame | ~100 ns |
| MAC table lookup | ~20 ns |
| MAC table learn | ~50 ns |
| ARP packet parse | ~80 ns |
Debugging
Section titled “Debugging”Logging Frame Processing
Section titled “Logging Frame Processing”Enable debug logging to trace frame handling:
slog.Debug("processing frame", "src_mac", ethFrame.Header.SrcMAC, "dst_mac", ethFrame.Header.DstMAC, "ether_type", ethFrame.Header.EtherType, "payload_len", len(ethFrame.Payload),)MAC Table Inspection
Section titled “MAC Table Inspection”// Get all entries for debuggingentries := macTable.All()for _, e := range entries { slog.Info("mac entry", "mac", e.MAC, "peer_id", e.PeerID, "ip", e.VirtualIP, "age", time.Since(e.LearnedAt), )}Common Issues
Section titled “Common Issues”| Symptom | Possible Cause | Debug Steps |
|---|---|---|
| Frames not reaching peers | MAC not learned | Check macTable.Lookup() |
| ARP not resolving | ARP interceptor not handling | Verify localIP matches |
| High broadcast traffic | MAC table missing entries | Check expiry settings |
| Duplicate frames | Loop in forwarding | Verify source MAC learning |
API Reference
Section titled “API Reference”EthernetFrame Methods
Section titled “EthernetFrame Methods”| Method | Returns | Description |
|---|---|---|
IsBroadcast() | bool | Check if destination is broadcast |
IsMulticast() | bool | Check if destination is multicast |
IsUnicast() | bool | Check if destination is unicast |
IsIPv4() | bool | Check if payload is IPv4 |
IsIPv6() | bool | Check if payload is IPv6 |
IsARP() | bool | Check if payload is ARP |
IsIP() | bool | Check if payload is IP (v4 or v6) |
Clone() | *EthernetFrame | Deep copy the frame |
MarshalBinary() | ([]byte, error) | Serialize to bytes |
ExtractIPAddresses() | (src, dst IP, error) | Get IP addresses from payload |
String() | string | Human-readable representation |
MACTable Methods
Section titled “MACTable Methods”| Method | Returns | Description |
|---|---|---|
Learn(mac, peerID) | void | Learn/update MAC entry |
LearnWithIP(mac, peerID, ip) | void | Learn with IP association |
LearnStatic(mac, peerID, ip) | void | Add non-expiring entry |
Lookup(mac) | (peerID, bool) | Get peer ID for MAC |
LookupEntry(mac) | (*MACEntry, bool) | Get full entry |
LookupByIP(ip) | (MAC, bool) | Reverse lookup by IP |
GetPeerMACs(peerID) | []MAC | Get all MACs for peer |
Remove(mac) | void | Remove specific entry |
RemovePeer(peerID) | void | Remove all peer entries |
Expire() | int | Remove stale entries |
Clear() | void | Remove all entries |
Count() | int | Number of entries |
All() | []*MACEntry | All current entries |
ARPInterceptor Methods
Section titled “ARPInterceptor Methods”| Method | Returns | Description |
|---|---|---|
HandleFrame(data) | []byte | Process frame, return response |
HandlePacket(data) | ([]byte, error) | Process ARP packet only |
LocalMAC() | HardwareAddr | Get local MAC address |
LocalIP() | netip.Addr | Get local IP address |