summaryrefslogtreecommitdiffstats
path: root/extras/libmemif/examples/gopacket/gopacket.go
diff options
context:
space:
mode:
authorTomas Slusny <slusnucky@gmail.com>2018-06-28 12:23:28 +0200
committerTomas Slusny <slusnucky@gmail.com>2018-07-02 12:20:29 +0200
commit3f26790b479f8a0cab00093b83491e9f47648b56 (patch)
treeed48b06e769bfb1bbacaad42ec1d73d079214811 /extras/libmemif/examples/gopacket/gopacket.go
parent7c044e96ae66f2403e4d82005943a0f6d99bb6a7 (diff)
Add gopacket adapter for libmemif
Add simple PacketHandle adapter for gopacket PacketDataSource + add writePacketData convenience method to it. Change-Id: I2280db33076c497c9b63fd107b6d77ecf85dd23b Signed-off-by: Tomas Slusny <slusnucky@gmail.com>
Diffstat (limited to 'extras/libmemif/examples/gopacket/gopacket.go')
-rw-r--r--extras/libmemif/examples/gopacket/gopacket.go362
1 files changed, 362 insertions, 0 deletions
diff --git a/extras/libmemif/examples/gopacket/gopacket.go b/extras/libmemif/examples/gopacket/gopacket.go
new file mode 100644
index 0000000..ee452a2
--- /dev/null
+++ b/extras/libmemif/examples/gopacket/gopacket.go
@@ -0,0 +1,362 @@
+// gopacket is a simple example showing how to answer APR and ICMP echo
+// requests through a memif interface. This example is mostly identical
+// to icmp-responder example, but it is using MemifPacketHandle API to
+// read and write packets using gopacket API.
+//
+// The appropriate VPP configuration for the opposite memif is:
+// vpp$ create memif socket id 1 filename /tmp/gopacket-example
+// vpp$ create interface memif id 1 socket-id 1 slave secret secret no-zero-copy
+// vpp$ set int state memif1/1 up
+// vpp$ set int ip address memif1/1 192.168.1.2/24
+//
+// To start the example, simply type:
+// root$ ./gopacket
+//
+// gopacket needs to be run as root so that it can access the socket
+// created by VPP.
+//
+// Normally, the memif interface is in the master mode. Pass CLI flag "--slave"
+// to create memif in the slave mode:
+// root$ ./gopacket --slave
+//
+// Don't forget to put the opposite memif into the master mode in that case.
+//
+// To verify the connection, run:
+// vpp$ ping 192.168.1.1
+// 64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=.6974 ms
+// 64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=.6310 ms
+// 64 bytes from 192.168.1.1: icmp_seq=4 ttl=255 time=1.0350 ms
+// 64 bytes from 192.168.1.1: icmp_seq=5 ttl=255 time=.5359 ms
+//
+// Statistics: 5 sent, 4 received, 20% packet loss
+// vpp$ sh ip arp
+// Time IP4 Flags Ethernet Interface
+// 68.5648 192.168.1.1 D aa:aa:aa:aa:aa:aa memif0/1
+//
+// Note: it is expected that the first ping is shown as lost. It was actually
+// converted to an ARP request. This is a VPP feature common to all interface
+// types.
+//
+// Stop the example with an interrupt signal.
+package main
+
+import (
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/signal"
+ "github.com/google/gopacket"
+ "github.com/google/gopacket/layers"
+ "git.fd.io/govpp.git/extras/libmemif"
+)
+
+var (
+ // Used to signalize interrupt goroutines to stop
+ stopCh chan struct{}
+
+ // MAC address assigned to the memif interface.
+ hwAddr net.HardwareAddr
+
+ // IPAddress assigned to the memif interface.
+ ipAddr net.IP
+
+ // ErrUnhandledPacket is thrown and printed when an unexpected packet is received.
+ ErrUnhandledPacket = errors.New("received an unhandled packet")
+)
+
+// OnConnect is called when a memif connection gets established.
+func OnConnect(memif *libmemif.Memif) (err error) {
+ // Use Memif.GetDetails to get the number of queues.
+ details, err := memif.GetDetails()
+ if err != nil {
+ fmt.Printf("libmemif.GetDetails() error: %v\n", err)
+ return
+ }
+
+ fmt.Printf("memif %s has been connected: %+v\n", memif.IfName, details)
+ stopCh = make(chan struct{})
+
+ // Start a separate go routine for each RX queue.
+ // (memif queue is a unit of parallelism for Rx/Tx).
+ // Beware: the number of queues created may be lower than what was requested
+ // in MemifConfiguration (the master makes the final decision).
+ for _, queue := range details.RxQueues {
+ ch, err := memif.GetQueueInterruptChan(queue.QueueID)
+ if err != nil {
+ fmt.Printf("libmemif.Memif.GetQueueInterruptChan() error %v\n", err)
+ continue
+ }
+
+ go CreateInterruptCallback(memif.NewPacketHandle(queue.QueueID, 10), ch, OnInterrupt)
+ }
+
+ return
+}
+
+// OnDisconnect is called when a memif connection is lost.
+func OnDisconnect(memif *libmemif.Memif) (err error) {
+ fmt.Printf("memif %s has been disconnected\n", memif.IfName)
+ // Stop all packet producers and consumers.
+ close(stopCh)
+ return nil
+}
+
+// OnInterrupt is called when interrupted
+func OnInterrupt(handle *libmemif.MemifPacketHandle) {
+ source := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
+ var responses []gopacket.Packet
+
+ // Process ICMP pings
+ for packet := range source.Packets() {
+ fmt.Println("Received new packet:")
+ fmt.Println(packet.Dump())
+
+ response, err := GeneratePacketResponse(packet)
+ if err != nil {
+ fmt.Printf("Failed to generate response: %v\n", err)
+ continue
+ }
+
+ fmt.Println("Sending response:")
+ fmt.Println(response.Dump())
+ responses = append(responses, response)
+ }
+
+ // Answer with ICMP pongs
+ for i, response := range responses {
+ err := handle.WritePacketData(response.Data())
+
+ switch err {
+ case io.EOF:
+ return
+ case nil:
+ fmt.Printf("Succesfully sent packet #%v %v\n", i, len(response.Data()))
+ default:
+ fmt.Printf("Got error while sending packet #%v %v\n", i, err)
+ }
+ }
+}
+
+// Creates user-friendly memif interrupt callback
+func CreateInterruptCallback(handle *libmemif.MemifPacketHandle, interruptCh <-chan struct{}, callback func(handle *libmemif.MemifPacketHandle)) {
+ for {
+ select {
+ case <-interruptCh:
+ callback(handle)
+ case <-stopCh:
+ handle.Close()
+ return
+ }
+ }
+}
+
+// GeneratePacketResponse returns an appropriate answer to an ARP request
+// or an ICMP echo request.
+func GeneratePacketResponse(packet gopacket.Packet) (response gopacket.Packet, err error) {
+ ethLayer := packet.Layer(layers.LayerTypeEthernet)
+ eth, ok := ethLayer.(*layers.Ethernet)
+ if !ok {
+ fmt.Println("Missing ETH layer.")
+ return nil, ErrUnhandledPacket
+ }
+
+ // Set up buffer and options for serialization.
+ buf := gopacket.NewSerializeBuffer()
+ opts := gopacket.SerializeOptions{
+ FixLengths: true,
+ ComputeChecksums: true,
+ }
+
+ switch eth.EthernetType {
+ case layers.EthernetTypeARP:
+ // Handle ARP request.
+ arpLayer := packet.Layer(layers.LayerTypeARP)
+ arp, ok := arpLayer.(*layers.ARP)
+ if !ok {
+ fmt.Println("Missing ARP layer.")
+ return nil, ErrUnhandledPacket
+ }
+
+ if arp.Operation != layers.ARPRequest {
+ fmt.Println("Not ARP request.")
+ return nil, ErrUnhandledPacket
+ }
+
+ fmt.Println("Received an ARP request.")
+
+ // Build packet layers.
+ ethResp := layers.Ethernet{
+ SrcMAC: hwAddr,
+ DstMAC: eth.SrcMAC,
+ EthernetType: layers.EthernetTypeARP,
+ }
+
+ arpResp := layers.ARP{
+ AddrType: layers.LinkTypeEthernet,
+ Protocol: layers.EthernetTypeIPv4,
+ HwAddressSize: 6,
+ ProtAddressSize: 4,
+ Operation: layers.ARPReply,
+ SourceHwAddress: []byte(hwAddr),
+ SourceProtAddress: []byte(ipAddr),
+ DstHwAddress: arp.SourceHwAddress,
+ DstProtAddress: arp.SourceProtAddress,
+ }
+
+ if err := gopacket.SerializeLayers(buf, opts, &ethResp, &arpResp); err != nil {
+ fmt.Println("SerializeLayers error: ", err)
+ return nil, ErrUnhandledPacket
+ }
+ case layers.EthernetTypeIPv4:
+ // Respond to ICMP request.
+ ipLayer := packet.Layer(layers.LayerTypeIPv4)
+ ipv4, ok := ipLayer.(*layers.IPv4)
+ if !ok {
+ fmt.Println("Missing IPv4 layer.")
+ return nil, ErrUnhandledPacket
+ }
+
+ if ipv4.Protocol != layers.IPProtocolICMPv4 {
+ fmt.Println("Not ICMPv4 protocol.")
+ return nil, ErrUnhandledPacket
+ }
+
+ icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
+ icmp, ok := icmpLayer.(*layers.ICMPv4)
+ if !ok {
+ fmt.Println("Missing ICMPv4 layer.")
+ return nil, ErrUnhandledPacket
+ }
+
+ if icmp.TypeCode.Type() != layers.ICMPv4TypeEchoRequest {
+ fmt.Println("Not ICMPv4 echo request.")
+ return nil, ErrUnhandledPacket
+ }
+
+ fmt.Println("Received an ICMPv4 echo request.")
+
+ // Build packet layers.
+ ethResp := layers.Ethernet{
+ SrcMAC: hwAddr,
+ DstMAC: eth.SrcMAC,
+ EthernetType: layers.EthernetTypeIPv4,
+ }
+
+ ipv4Resp := layers.IPv4{
+ Version: 4,
+ IHL: 5,
+ TOS: 0,
+ Id: 0,
+ Flags: 0,
+ FragOffset: 0,
+ TTL: 255,
+ Protocol: layers.IPProtocolICMPv4,
+ SrcIP: ipAddr,
+ DstIP: ipv4.SrcIP,
+ }
+
+ icmpResp := layers.ICMPv4{
+ TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoReply, 0),
+ Id: icmp.Id,
+ Seq: icmp.Seq,
+ }
+
+ if err := gopacket.SerializeLayers(buf, opts, &ethResp, &ipv4Resp, &icmpResp, gopacket.Payload(icmp.Payload)); err != nil {
+ fmt.Println("SerializeLayers error: ", err)
+ return nil, ErrUnhandledPacket
+ }
+ default:
+ return nil, ErrUnhandledPacket
+ }
+
+ return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default), nil
+}
+
+func main() {
+ fmt.Println("Starting 'gopacket' example...")
+ var err error
+
+ // Parse MAC address associated with memif interface
+ hwAddr, err = net.ParseMAC("aa:aa:aa:aa:aa:aa")
+ if err != nil {
+ fmt.Printf("Failed to parse the MAC address: %v/n", err)
+ return
+ }
+
+ // Parse IP address associated with memif interface
+ ip := net.ParseIP("192.168.1.1")
+ if ip != nil {
+ ipAddr = ip.To4()
+ }
+ if ipAddr == nil {
+ fmt.Println("Failed to parse the IP address")
+ return
+ }
+
+ // If run with the "--slave" option, create memif in the slave mode.
+ var isMaster = true
+ var appSuffix string
+ if len(os.Args) > 1 && (os.Args[1] == "--slave" || os.Args[1] == "-slave") {
+ isMaster = false
+ appSuffix = "-slave"
+ }
+
+ // Initialize libmemif first.
+ appName := "gopacket" + appSuffix
+ fmt.Println("Initializing libmemif as ", appName)
+ err = libmemif.Init(appName)
+ if err != nil {
+ fmt.Printf("libmemif.Init() error: %v\n", err)
+ return
+ }
+
+ // Schedule automatic cleanup.
+ defer libmemif.Cleanup()
+
+ // Prepare callbacks to use with the memif.
+ // The same callbacks could be used with multiple memifs.
+ // The first input argument (*libmemif.Memif) can be used to tell which
+ // memif the callback was triggered for.
+ memifCallbacks := &libmemif.MemifCallbacks{
+ OnConnect: OnConnect,
+ OnDisconnect: OnDisconnect,
+ }
+
+ // Prepare memif1 configuration.
+ memifConfig := &libmemif.MemifConfig{
+ MemifMeta: libmemif.MemifMeta{
+ IfName: "memif1",
+ ConnID: 1, // ConnectionID is an identifier used to match opposite memifs.
+ SocketFilename: "/tmp/gopacket-example", // Socket through which the opposite memifs will establish the connection.
+ Secret: "secret", // Secret used to authenticate the memif connection.
+ IsMaster: isMaster,
+ Mode: libmemif.IfModeEthernet,
+ },
+ MemifShmSpecs: libmemif.MemifShmSpecs{
+ NumRxQueues: 3, // NumQueues is the (configured!) number of queues for both Rx & Tx.
+ NumTxQueues: 3, // The actual number agreed during connection establishment may be smaller!
+ BufferSize: 2048,
+ Log2RingSize: 10,
+ },
+ }
+
+ fmt.Printf("Callbacks: %+v\n", memifCallbacks)
+ fmt.Printf("Config: %+v\n", memifConfig)
+
+ // Create memif1 interface.
+ memif, err := libmemif.CreateInterface(memifConfig, memifCallbacks)
+ if err != nil {
+ fmt.Printf("libmemif.CreateInterface() error: %v\n", err)
+ return
+ }
+
+ // Schedule automatic cleanup of the interface.
+ defer memif.Close()
+
+ // Wait until an interrupt signal is received.
+ sigChan := make(chan os.Signal, 1)
+ signal.Notify(sigChan, os.Interrupt)
+ <-sigChan
+}