From 3f26790b479f8a0cab00093b83491e9f47648b56 Mon Sep 17 00:00:00 2001 From: Tomas Slusny Date: Thu, 28 Jun 2018 12:23:28 +0200 Subject: 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 --- Makefile | 1 + extras/libmemif/README.md | 61 ++++- extras/libmemif/examples/gopacket/gopacket.go | 362 ++++++++++++++++++++++++++ extras/libmemif/packethandle.go | 150 +++++++++++ 4 files changed, 573 insertions(+), 1 deletion(-) create mode 100644 extras/libmemif/examples/gopacket/gopacket.go create mode 100644 extras/libmemif/packethandle.go diff --git a/Makefile b/Makefile index 4eed58d..214f632 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ install: extras: @cd extras/libmemif/examples/raw-data && go build -v @cd extras/libmemif/examples/icmp-responder && go build -v + @cd extras/libmemif/examples/gopacket && go build -v clean: @rm -f cmd/binapi-generator/binapi-generator diff --git a/extras/libmemif/README.md b/extras/libmemif/README.md index 3b692b1..854aa96 100644 --- a/extras/libmemif/README.md +++ b/extras/libmemif/README.md @@ -103,6 +103,14 @@ Do not touch memif after it was closed, let garbage collector to remove the `Memif` instance. In the end, `Cleanup()` will also ensure that all active memif interfaces are closed before the cleanup finalizes. +To use libmemif with `google/gopacket`, simply call `Memif.NewPacketHandle()` +to create `google/gopacket/PacketDataSource` from memif queue. After this you +can use gopacket API to read from `MemifPacketHandle` as normal. You can pass +optional `rxCount` when creating the packet handle and then when reading data, +handle will try to read more packets at once and cache them for next iteration. +Handle also includes convenience method `MemifPacketHandle.WritePacketData()` +that is simply calling 1 `Memif.TxBurst()` for provided data. + ### Examples **Go-libmemif** ships with two simple examples demonstrating the usage @@ -185,4 +193,55 @@ vpp$ sh ip arp It was actually converted to an ARP request. This is a VPP specific feature common to all interface types. -Stop the example with an interrupt signal (^C). \ No newline at end of file +Stop the example with an interrupt signal (^C). + +#### GoPacket ICMP Responder + +*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 + specific feature common to all interface types. + +Stop the example with an interrupt signal (^C). 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, ðResp, &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, ðResp, &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 +} diff --git a/extras/libmemif/packethandle.go b/extras/libmemif/packethandle.go new file mode 100644 index 0000000..83bf90a --- /dev/null +++ b/extras/libmemif/packethandle.go @@ -0,0 +1,150 @@ +// Copyright (c) 2017 Cisco and/or its affiliates. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package libmemif + +import ( + "github.com/google/gopacket" + "time" + "sync" + "io" +) + +type memoizedPacket struct { + data RawPacketData + ci gopacket.CaptureInfo +} + +type MemifPacketHandle struct { + memif *Memif + queueId uint8 + rxCount uint16 + + // Used for caching packets when larger rxburst is called + packetQueue []*memoizedPacket + + // Used for synchronization of read/write calls + readMu sync.Mutex + writeMu sync.Mutex + closeMu sync.Mutex + stop bool +} + +// Create new GoPacket packet handle from libmemif queue. rxCount determines how many packets will be read +// at once, minimum value is 1 +func (memif *Memif) NewPacketHandle(queueId uint8, rxCount uint16) *MemifPacketHandle { + if rxCount == 0 { + rxCount = 1 + } + + return &MemifPacketHandle{ + memif: memif, + queueId: queueId, + rxCount: rxCount, + } +} + +// Reads packet data from memif in bursts, based on previously configured rxCount parameterer. Then caches the +// resulting packets and returns them 1 by 1 from this method until queue is empty then tries to call new rx burst +// to read more data. If no data is returned, io.EOF error is thrown and caller should stop reading. +func (handle *MemifPacketHandle) ReadPacketData() (data []byte, ci gopacket.CaptureInfo, err error) { + handle.readMu.Lock() + defer handle.readMu.Unlock() + + if handle.stop { + err = io.EOF + return + } + + queueLen := len(handle.packetQueue) + + if queueLen == 0 { + packets, burstErr := handle.memif.RxBurst(handle.queueId, handle.rxCount) + packetsLen := len(packets) + + if burstErr != nil { + err = burstErr + return + } + + if packetsLen == 0 { + err = io.EOF + return + } + + handle.packetQueue = make([]*memoizedPacket, packetsLen) + + for i, packet := range packets { + packetLen := len(packet) + + handle.packetQueue[i] = &memoizedPacket{ + data: []byte(packet), + ci: gopacket.CaptureInfo{ + Timestamp: time.Now(), + CaptureLength: packetLen, + Length: packetLen, + }, + } + } + } + + packet := handle.packetQueue[0] + handle.packetQueue = handle.packetQueue[1:] + data = packet.data + ci = packet.ci + + return +} + +// Writes packet data to memif in burst of 1 packet. In case no packet is sent, this method throws io.EOF error and +// called should stop trying to write packets. +func (handle *MemifPacketHandle) WritePacketData(data []byte) (err error) { + handle.writeMu.Lock() + defer handle.writeMu.Unlock() + + if handle.stop { + err = io.EOF + return + } + + count, err := handle.memif.TxBurst(handle.queueId, []RawPacketData{data}) + + if err != nil { + return + } + + if count == 0 { + err = io.EOF + } + + return +} + +// Waits for all read and write operations to finish and then prevents more from occurring. Handle can be closed only +// once and then can never be opened again. +func (handle *MemifPacketHandle) Close() { + handle.closeMu.Lock() + defer handle.closeMu.Unlock() + + // wait for packet reader to stop + handle.readMu.Lock() + defer handle.readMu.Unlock() + + // wait for packet writer to stop + handle.writeMu.Lock() + defer handle.writeMu.Unlock() + + // stop reading and writing + handle.stop = true +} -- cgit 1.2.3-korg