Skip to content

Instantly share code, notes, and snippets.

@atoonk
Created February 27, 2024 22:43
Show Gist options
  • Save atoonk/46f60156bfad71d4ac43b7460367121d to your computer and use it in GitHub Desktop.
Save atoonk/46f60156bfad71d4ac43b7460367121d to your computer and use it in GitHub Desktop.
af_packet_compare.go
// 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()
}
@atoonk
Copy link
Author

atoonk commented Feb 27, 2024

root@andre:~# go run main.go
Sending packets using MMap'd AF_PACKET socket
Duration: 17.528770731s Packets per second:570490
Sending packets regular AF_PACKET socket
Duration: 18.4959144s Packets per second:540659

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment