diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | extras/libmemif/README.md | 70 | ||||
-rw-r--r-- | extras/libmemif/adapter.go | 54 | ||||
-rw-r--r-- | extras/libmemif/examples/gopacket/gopacket.go | 362 | ||||
-rw-r--r-- | extras/libmemif/examples/icmp-responder/icmp-responder.go | 7 | ||||
-rw-r--r-- | extras/libmemif/packethandle.go | 150 |
6 files changed, 612 insertions, 32 deletions
@@ -14,6 +14,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 39a6d58..854aa96 100644 --- a/extras/libmemif/README.md +++ b/extras/libmemif/README.md @@ -17,6 +17,8 @@ locations, execute: ``` $ git clone https://gerrit.fd.io/r/vpp $ cd vpp/extras/libmemif +$ ./bootstrap +$ ./configure $ make install ``` @@ -101,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 @@ -144,9 +154,10 @@ used to decode and construct packets. The appropriate VPP configuration for the opposite memif is: ``` -vpp$ create memif id 1 socket /tmp/icmp-responder-example slave secret secret -vpp$ set int state memif0/1 up -vpp$ set int ip address memif0/1 192.168.1.2/24 +vpp$ create memif socket id 1 filename /tmp/icmp-responder-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: @@ -182,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/adapter.go b/extras/libmemif/adapter.go index 6058c94..a74c5cf 100644 --- a/extras/libmemif/adapter.go +++ b/extras/libmemif/adapter.go @@ -55,11 +55,10 @@ typedef struct uint8_t num_s2m_rings; uint8_t num_m2s_rings; uint16_t buffer_size; - memif_log2_ring_size_t log2_ring_size; + uint8_t log2_ring_size; uint8_t is_master; - memif_interface_id_t interface_id; + uint32_t interface_id; char *interface_name; - char *instance_name; memif_interface_mode_t mode; } govpp_memif_conn_args_t; @@ -124,11 +123,6 @@ govpp_memif_create (memif_conn_handle_t *conn, govpp_memif_conn_args_t *go_args, strncpy ((char *)args.interface_name, go_args->interface_name, sizeof(args.interface_name) - 1); } - if (go_args->instance_name != NULL) - { - strncpy ((char *)args.instance_name, go_args->instance_name, - sizeof (args.instance_name) - 1); - } args.mode = go_args->mode; return memif_create(conn, &args, govpp_on_connect_callback, @@ -202,10 +196,10 @@ govpp_get_tx_queue_details (govpp_memif_details_t *md, int index) // Copy packet data into the selected buffer. static void -govpp_copy_packet_data(memif_buffer_t *buffers, int index, void *data, uint32_t size) +govpp_copy_packet_data(memif_buffer_t *buffers, int index, void *data, uint16_t size) { - buffers[index].data_len = (size > buffers[index].buffer_len ? buffers[index].buffer_len : size); - memcpy(buffers[index].data, data, (size_t)buffers[index].data_len); + buffers[index].len = (size > buffers[index].len ? buffers[index].len : size); + memcpy(buffers[index].data, data, (size_t)buffers[index].len); } // Get packet data from the selected buffer. @@ -213,7 +207,7 @@ govpp_copy_packet_data(memif_buffer_t *buffers, int index, void *data, uint32_t static void * govpp_get_packet_data(memif_buffer_t *buffers, int index, int *size) { - *size = (int)buffers[index].data_len; + *size = (int)buffers[index].len; return buffers[index].data; } @@ -350,6 +344,7 @@ type Memif struct { queueIntCh []chan struct{} // per RX queue interrupt channel // Rx/Tx queues + ringSize int // number of items in each ring stopQPollFd int // event file descriptor used to stop pollRxQueue-s wg sync.WaitGroup // wait group for all pollRxQueue-s rxQueueBufs []CPacketBuffers // an array of C-libmemif packet buffers for each RX queue @@ -444,11 +439,11 @@ func Init(appName string) error { // Initialize C-libmemif. var errCode int if appName == "" { - errCode = int(C.memif_init(nil, nil)) + errCode = int(C.memif_init(nil, nil, nil, nil)) } else { appName := C.CString(appName) defer C.free(unsafe.Pointer(appName)) - errCode = int(C.memif_init(nil, appName)) + errCode = int(C.memif_init(nil, appName, nil, nil)) } err := getMemifError(errCode) if err != nil { @@ -517,11 +512,17 @@ func CreateInterface(config *MemifConfig, callbacks *MemifCallbacks) (memif *Mem log.WithField("ifName", config.IfName).Debug("Creating a new memif interface") + log2RingSize := config.Log2RingSize + if log2RingSize == 0 { + log2RingSize = 10 + } + // Create memif-wrapper for Go-libmemif. memif = &Memif{ MemifMeta: config.MemifMeta, callbacks: &MemifCallbacks{}, ifIndex: context.nextMemifIndex, + ringSize: 1 << log2RingSize, } // Initialize memif callbacks. @@ -547,17 +548,12 @@ func CreateInterface(config *MemifConfig, callbacks *MemifCallbacks) (memif *Mem defer C.free(unsafe.Pointer(args.socket_filename)) } // - interface ID - args.interface_id = C.memif_interface_id_t(config.ConnID) + args.interface_id = C.uint32_t(config.ConnID) // - interface name if config.IfName != "" { args.interface_name = C.CString(config.IfName) defer C.free(unsafe.Pointer(args.interface_name)) } - // - instance name - if config.InstanceName != "" { - args.instance_name = C.CString(config.InstanceName) - defer C.free(unsafe.Pointer(args.instance_name)) - } // - mode switch config.Mode { case IfModeEthernet: @@ -587,7 +583,7 @@ func CreateInterface(config *MemifConfig, callbacks *MemifCallbacks) (memif *Mem // - buffer size args.buffer_size = C.uint16_t(config.BufferSize) // - log_2(ring size) - args.log2_ring_size = C.memif_log2_ring_size_t(config.Log2RingSize) + args.log2_ring_size = C.uint8_t(config.Log2RingSize) // Create memif in C-libmemif. errCode := C.govpp_memif_create(&memif.cHandle, args, unsafe.Pointer(uintptr(memif.ifIndex))) @@ -757,7 +753,7 @@ func (memif *Memif) TxBurst(queueID uint8, packets []RawPacketData) (count uint1 // Allocate ring slots. cQueueID := C.uint16_t(queueID) errCode := C.memif_buffer_alloc(memif.cHandle, cQueueID, pb.buffers, C.uint16_t(bufCount), - &allocated, C.uint16_t(bufSize)) + &allocated, C.uint32_t(bufSize)) err = getMemifError(int(errCode)) if err == ErrNoBufRing { // Not enough ring slots, <count> will be less than bufCount. @@ -770,7 +766,7 @@ func (memif *Memif) TxBurst(queueID uint8, packets []RawPacketData) (count uint1 // Copy packet data into the buffers. for i := 0; i < int(allocated); i++ { packetData := unsafe.Pointer(&packets[i][0]) - C.govpp_copy_packet_data(pb.buffers, C.int(i), packetData, C.uint32_t(len(packets[i]))) + C.govpp_copy_packet_data(pb.buffers, C.int(i), packetData, C.uint16_t(len(packets[i]))) } errCode = C.memif_tx_burst(memif.cHandle, cQueueID, pb.buffers, allocated, &sentCount) @@ -793,7 +789,6 @@ func (memif *Memif) TxBurst(queueID uint8, packets []RawPacketData) (count uint1 // Rx queue. func (memif *Memif) RxBurst(queueID uint8, count uint16) (packets []RawPacketData, err error) { var recvCount C.uint16_t - var freed C.uint16_t if count == 0 { return packets, nil @@ -836,7 +831,9 @@ func (memif *Memif) RxBurst(queueID uint8, count uint16) (packets []RawPacketDat packets = append(packets, C.GoBytes(packetData, packetSize)) } - errCode = C.memif_buffer_free(memif.cHandle, cQueueID, pb.buffers, recvCount, &freed) + if recvCount > 0 { + errCode = C.memif_refill_queue(memif.cHandle, cQueueID, recvCount, 0) + } err = getMemifError(int(errCode)) if err != nil { // Throw away packets to avoid duplicities. @@ -895,6 +892,13 @@ func (memif *Memif) initQueues() error { // Initialize Rx/Tx packet buffers. for i = 0; i < len(details.RxQueues); i++ { memif.rxQueueBufs = append(memif.rxQueueBufs, CPacketBuffers{}) + if !memif.IsMaster { + errCode := C.memif_refill_queue(memif.cHandle, C.uint16_t(i), C.uint16_t(memif.ringSize-1), 0) + err = getMemifError(int(errCode)) + if err != nil { + log.Warn(err.Error()) + } + } } for i = 0; i < len(details.TxQueues); i++ { memif.txQueueBufs = append(memif.txQueueBufs, CPacketBuffers{}) 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/examples/icmp-responder/icmp-responder.go b/extras/libmemif/examples/icmp-responder/icmp-responder.go index f9867f7..5e9f2e0 100644 --- a/extras/libmemif/examples/icmp-responder/icmp-responder.go +++ b/extras/libmemif/examples/icmp-responder/icmp-responder.go @@ -3,9 +3,10 @@ // and construct packets. // // The appropriate VPP configuration for the opposite memif is: -// vpp$ create memif id 1 socket /tmp/icmp-responder-example slave secret secret -// vpp$ set int state memif0/1 up -// vpp$ set int ip address memif0/1 192.168.1.2/24 +// vpp$ create memif socket id 1 filename /tmp/icmp-responder-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$ ./icmp-responder 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 +} |