From 3f1edad4e6ba0a7876750aea55507fae14d8badf Mon Sep 17 00:00:00 2001 From: Milan Lenco Date: Wed, 11 Oct 2017 16:40:58 +0200 Subject: ODPM 266: Go-libmemif + 2 examples. Change-Id: Icdb9b9eb2314eff6c96afe7996fcf2728291de4a Signed-off-by: Milan Lenco --- .../examples/icmp-responder/icmp-responder.go | 402 +++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 extras/libmemif/examples/icmp-responder/icmp-responder.go (limited to 'extras/libmemif/examples/icmp-responder/icmp-responder.go') diff --git a/extras/libmemif/examples/icmp-responder/icmp-responder.go b/extras/libmemif/examples/icmp-responder/icmp-responder.go new file mode 100644 index 0000000..f9867f7 --- /dev/null +++ b/extras/libmemif/examples/icmp-responder/icmp-responder.go @@ -0,0 +1,402 @@ +// icmp-responder is a simple example showing how to answer APR and ICMP echo +// requests through a memif interface. Package "google/gopacket" is 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 +// +// To start the example, simply type: +// root$ ./icmp-responder +// +// icmp-responder 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$ ./icmp-responder --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" + "net" + "os" + "os/signal" + "sync" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + + "git.fd.io/govpp.git/extras/libmemif" +) + +const ( + // Socket through which the opposite memifs will establish the connection. + Socket = "/tmp/icmp-responder-example" + + // Secret used to authenticate the memif connection. + Secret = "secret" + + // ConnectionID is an identifier used to match opposite memifs. + ConnectionID = 1 + + // IPAddress assigned to the memif interface. + IPAddress = "192.168.1.1" + + // MAC address assigned to the memif interface. + MAC = "aa:aa:aa:aa:aa:aa" + + // NumQueues is the (configured!) number of queues for both Rx & Tx. + // The actual number agreed during connection establishment may be smaller! + NumQueues uint8 = 3 +) + +// For management of go routines. +var wg sync.WaitGroup +var stopCh chan struct{} + +// Parsed addresses. +var hwAddr net.HardwareAddr +var ipAddr net.IP + +// ErrUnhandledPacket is thrown and printed when an unexpected packet is received. +var ErrUnhandledPacket = errors.New("received an unhandled packet") + +// OnConnect is called when a memif connection gets established. +func OnConnect(memif *libmemif.Memif) (err error) { + details, err := memif.GetDetails() + if err != nil { + fmt.Printf("libmemif.GetDetails() error: %v\n", err) + } + 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). + // Use Memif.GetDetails to get the number of queues. + var i uint8 + for i = 0; i < uint8(len(details.RxQueues)); i++ { + wg.Add(1) + go IcmpResponder(memif, i) + } + return nil +} + +// 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) + wg.Wait() + return nil +} + +// IcmpResponder answers to ICMP pings with ICMP pongs. +func IcmpResponder(memif *libmemif.Memif, queueID uint8) { + defer wg.Done() + + // Get channel which fires every time there are packets to read on the queue. + interruptCh, err := memif.GetQueueInterruptChan(queueID) + if err != nil { + // Example of libmemif error handling code: + switch err { + case libmemif.ErrQueueID: + fmt.Printf("libmemif.Memif.GetQueueInterruptChan() complains about invalid queue id!?") + // Here you would put all the errors that need to be handled individually... + default: + fmt.Printf("libmemif.Memif.GetQueueInterruptChan() error: %v\n", err) + } + return + } + + for { + select { + case <-interruptCh: + // Read all packets from the queue but at most 10 at once. + // Since there is only one interrupt signal sent for an entire burst + // of packets, an interrupt handling routine should repeatedly call + // RxBurst() until the function returns an empty slice of packets. + // This way it is ensured that there are no packets left + // on the queue unread when the interrupt signal is cleared. + for { + packets, err := memif.RxBurst(queueID, 10) + if err != nil { + fmt.Printf("libmemif.Memif.RxBurst() error: %v\n", err) + // Skip this burst, continue with the next one 3secs later... + break + } + if len(packets) == 0 { + // No more packets to read until the next interrupt. + break + } + // Generate response for each supported request. + responses := []libmemif.RawPacketData{} + for _, packet := range packets { + fmt.Println("Received new packet:") + DumpPacket(packet) + response, err := GeneratePacketResponse(packet) + if err == nil { + fmt.Println("Sending response:") + DumpPacket(response) + responses = append(responses, response) + } else { + fmt.Printf("Failed to generate response: %v\n", err) + } + } + // Send pongs / ARP responses. We may not be able to do it in one + // burst if the ring is (almost) full or the internal buffer cannot + // contain it. + sent := 0 + for { + count, err := memif.TxBurst(queueID, responses[sent:]) + if err != nil { + fmt.Printf("libmemif.Memif.TxBurst() error: %v\n", err) + break + } else { + fmt.Printf("libmemif.Memif.TxBurst() has sent %d packets.\n", count) + sent += int(count) + if sent == len(responses) { + break + } + } + } + } + case <-stopCh: + return + } + } +} + +// DumpPacket prints a human-readable description of the packet. +func DumpPacket(packetData libmemif.RawPacketData) { + packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) + fmt.Println(packet.Dump()) +} + +// GeneratePacketResponse returns an appropriate answer to an ARP request +// or an ICMP echo request. +func GeneratePacketResponse(packetData libmemif.RawPacketData) (response libmemif.RawPacketData, err error) { + packet := gopacket.NewPacket(packetData, layers.LayerTypeEthernet, gopacket.Default) + + ethLayer := packet.Layer(layers.LayerTypeEthernet) + if ethLayer == nil { + fmt.Println("Missing ETH layer.") + return nil, ErrUnhandledPacket + } + eth, _ := ethLayer.(*layers.Ethernet) + + if eth.EthernetType == layers.EthernetTypeARP { + // Handle ARP request. + arpLayer := packet.Layer(layers.LayerTypeARP) + if arpLayer == nil { + fmt.Println("Missing ARP layer.") + return nil, ErrUnhandledPacket + } + arp, _ := arpLayer.(*layers.ARP) + 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, + } + // Set up buffer and options for serialization. + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + err := gopacket.SerializeLayers(buf, opts, ðResp, &arpResp) + if err != nil { + fmt.Println("SerializeLayers error: ", err) + } + return buf.Bytes(), nil + } + + if eth.EthernetType == layers.EthernetTypeIPv4 { + // Respond to ICMP request. + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + fmt.Println("Missing IPv4 layer.") + return nil, ErrUnhandledPacket + } + ipv4, _ := ipLayer.(*layers.IPv4) + if ipv4.Protocol != layers.IPProtocolICMPv4 { + fmt.Println("Not ICMPv4 protocol.") + return nil, ErrUnhandledPacket + } + icmpLayer := packet.Layer(layers.LayerTypeICMPv4) + if icmpLayer == nil { + fmt.Println("Missing ICMPv4 layer.") + return nil, ErrUnhandledPacket + } + icmp, _ := icmpLayer.(*layers.ICMPv4) + 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, + } + + // Set up buffer and options for serialization. + buf := gopacket.NewSerializeBuffer() + opts := gopacket.SerializeOptions{ + FixLengths: true, + ComputeChecksums: true, + } + err := gopacket.SerializeLayers(buf, opts, ðResp, &ipv4Resp, &icmpResp, + gopacket.Payload(icmp.Payload)) + if err != nil { + fmt.Println("SerializeLayers error: ", err) + } + return buf.Bytes(), nil + } + + return nil, ErrUnhandledPacket +} + +func main() { + var err error + fmt.Println("Starting 'icmp-responder' example...") + + hwAddr, err = net.ParseMAC(MAC) + if err != nil { + fmt.Println("Failed to parse the MAC address: %v", err) + return + } + + ip := net.ParseIP(IPAddress) + if ip != nil { + ipAddr = ip.To4() + } + if ipAddr == nil { + fmt.Println("Failed to parse the IP address: %v", err) + 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 := "ICMP-Responder" + 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: ConnectionID, + SocketFilename: Socket, + Secret: Secret, + IsMaster: isMaster, + Mode: libmemif.IfModeEthernet, + }, + MemifShmSpecs: libmemif.MemifShmSpecs{ + NumRxQueues: NumQueues, + NumTxQueues: NumQueues, + 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 +} -- cgit 1.2.3-korg