Created
February 27, 2024 22:43
-
-
Save atoonk/46f60156bfad71d4ac43b7460367121d to your computer and use it in GitHub Desktop.
af_packet_compare.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// POC for sending packets using tpacket and afpacket | |
// tpacket is a high-performance packet sending mechanism using MMap'd AF_PACKET | |
// afpacket is a regular packet sending mechanism | |
package main | |
import ( | |
"fmt" | |
"log" | |
"net" | |
"syscall" | |
"time" | |
"github.com/google/gopacket" | |
"github.com/google/gopacket/afpacket" | |
"github.com/google/gopacket/layers" | |
) | |
const ( | |
iface = "veth0" | |
packetCount = 10 * 1000 * 1000 | |
) | |
func main() { | |
// create a packet to send | |
outgoingPacket := createPacket(64) | |
// first we send the packet using tpacket from | |
// "github.com/google/gopacket/afpacket | |
// Package afpacket provides Go bindings for MMap'd AF_PACKET socket reading. | |
fmt.Println("Sending packets using MMap'd AF_PACKET socket") | |
start := time.Now() | |
err := sendTpacket(packetCount, outgoingPacket) | |
if err != nil { | |
log.Fatalf("failed to send tpacket: %w", err) | |
} | |
elapsed := time.Since(start) | |
printPps(packetCount, elapsed) | |
// Now do the same with a regular AF_PACKET | |
fmt.Println("Sending packets regular AF_PACKET socket") | |
start = time.Now() | |
err = sendAfpacket(packetCount, outgoingPacket) | |
if err != nil { | |
log.Fatalf("failed to send afpacket: %w", err) | |
} | |
elapsed = time.Since(start) | |
printPps(packetCount, elapsed) | |
} | |
func sendAfpacket(packetCount int, outgoingPacket []byte) error { | |
// Create an afpacket TPacket. Adjust options for performance as needed. | |
// Now do the same with a regular AF_PACKET | |
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(syscall.ETH_P_IP))) | |
if err != nil { | |
return fmt.Errorf("failed to create socket: %w", err) | |
} | |
defer syscall.Close(fd) | |
ifi, err := net.InterfaceByName(iface) | |
if err != nil { | |
return fmt.Errorf("failed to get interface %s: %w", iface, err) | |
} | |
addr := &syscall.SockaddrLinklayer{ | |
Protocol: htons(syscall.ETH_P_IP), | |
Ifindex: ifi.Index, | |
} | |
for i := 0; i < packetCount; i++ { | |
if err := syscall.Sendto(fd, outgoingPacket, 0, addr); err != nil { | |
return fmt.Errorf("failed to send packet: %w", err) | |
} | |
} | |
return nil | |
} | |
func sendTpacket(packetCount int, outgoingPacket []byte) error { | |
// Create an afpacket TPacket. Adjust options for performance as needed. | |
tPacket, err := afpacket.NewTPacket(afpacket.OptInterface(iface)) | |
if err != nil { | |
return fmt.Errorf("failed to create tpacket: %w", err) | |
} | |
defer tPacket.Close() | |
for i := 0; i < packetCount; i++ { | |
if err := tPacket.WritePacketData(outgoingPacket); err != nil { | |
return fmt.Errorf("failed to write packet: %w", err) | |
} | |
} | |
return nil | |
} | |
func printPps(packetCount int, elapsed time.Duration) { | |
pps := float64(packetCount) / elapsed.Seconds() | |
fmt.Printf("Duration: %s Packets per second:%d\n", elapsed, int(pps)) | |
} | |
// htons converts a uint16 from host- to network byte order. | |
func htons(i uint16) uint16 { | |
return (i<<8)&0xff00 | i>>8 | |
} | |
// createPacket returns the packet to send as bytes | |
func createPacket(payloadSize int) []byte { | |
// Create a simple Ethernet + IP + UDP packet. | |
buffer := gopacket.NewSerializeBuffer() | |
var layersToSerialize []gopacket.SerializableLayer | |
ethLayer := &layers.Ethernet{ | |
SrcMAC: net.HardwareAddr{0x00, 0x20, 0x00, 0x00, 0x00, 0x00}, | |
DstMAC: net.HardwareAddr{0x00, 0x21, 0x00, 0x00, 0x00, 0x00}, | |
EthernetType: layers.EthernetTypeIPv4, | |
} | |
layersToSerialize = append(layersToSerialize, ethLayer) | |
// Set IP layer | |
ipLayer := &layers.IPv4{ | |
Version: 4, | |
TTL: 64, | |
SrcIP: net.IPv4(192, 168, 1, 1), // Source IP of the network | |
DstIP: net.IPv4(192, 168, 1, 2), // Destination IP of the network | |
Protocol: layers.IPProtocolUDP, | |
} | |
layersToSerialize = append(layersToSerialize, ipLayer) | |
udpLayer := &layers.UDP{ | |
SrcPort: 12345, | |
DstPort: 12345, | |
} | |
udpLayer.SetNetworkLayerForChecksum(ipLayer) // Important for checksum calculation | |
layersToSerialize = append(layersToSerialize, udpLayer) | |
payload := make([]byte, payloadSize) | |
layersToSerialize = append(layersToSerialize, gopacket.Payload(payload)) | |
// Serialize the packet layers into the buffer | |
if err := gopacket.SerializeLayers(buffer, gopacket.SerializeOptions{ComputeChecksums: true, FixLengths: true}, layersToSerialize...); err != nil { | |
fmt.Printf("error serializing packet: %w\n", err) | |
} | |
return buffer.Bytes() | |
} |
Author
atoonk
commented
Feb 27, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment