Update libmemif
[govpp.git] / extras / libmemif / examples / gopacket / gopacket.go
1 // gopacket is a simple example showing how to answer APR and ICMP echo
2 // requests through a memif interface. This example is mostly identical
3 // to icmp-responder example, but it is using MemifPacketHandle API to
4 // read and write packets using gopacket API.
5 //
6 // The appropriate VPP configuration for the opposite memif is:
7 //   vpp$ create memif socket id 1 filename /tmp/gopacket-example
8 //   vpp$ create interface memif id 1 socket-id 1 slave secret secret no-zero-copy
9 //   vpp$ set int state memif1/1 up
10 //   vpp$ set int ip address memif1/1 192.168.1.2/24
11 //
12 // To start the example, simply type:
13 //   root$ ./gopacket
14 //
15 // gopacket needs to be run as root so that it can access the socket
16 // created by VPP.
17 //
18 // Normally, the memif interface is in the master mode. Pass CLI flag "--slave"
19 // to create memif in the slave mode:
20 //   root$ ./gopacket --slave
21 //
22 // Don't forget to put the opposite memif into the master mode in that case.
23 //
24 // To verify the connection, run:
25 //   vpp$ ping 192.168.1.1
26 //   64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=.6974 ms
27 //   64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=.6310 ms
28 //   64 bytes from 192.168.1.1: icmp_seq=4 ttl=255 time=1.0350 ms
29 //   64 bytes from 192.168.1.1: icmp_seq=5 ttl=255 time=.5359 ms
30 //
31 //   Statistics: 5 sent, 4 received, 20% packet loss
32 //   vpp$ sh ip arp
33 //   Time           IP4       Flags      Ethernet              Interface
34 //   68.5648   192.168.1.1     D    aa:aa:aa:aa:aa:aa memif0/1
35 //
36 // Note: it is expected that the first ping is shown as lost. It was actually
37 // converted to an ARP request. This is a VPP feature common to all interface
38 // types.
39 //
40 // Stop the example with an interrupt signal.
41 package main
42
43 import (
44         "errors"
45         "fmt"
46         "git.fd.io/govpp.git/extras/libmemif"
47         "github.com/google/gopacket"
48         "github.com/google/gopacket/layers"
49         "io"
50         "net"
51         "os"
52         "os/signal"
53 )
54
55 var (
56         // Used to signalize interrupt goroutines to stop
57         stopCh chan struct{}
58
59         // MAC address assigned to the memif interface.
60         hwAddr net.HardwareAddr
61
62         // IPAddress assigned to the memif interface.
63         ipAddr net.IP
64
65         // ErrUnhandledPacket is thrown and printed when an unexpected packet is received.
66         ErrUnhandledPacket = errors.New("received an unhandled packet")
67 )
68
69 // OnConnect is called when a memif connection gets established.
70 func OnConnect(memif *libmemif.Memif) (err error) {
71         // Use Memif.GetDetails to get the number of queues.
72         details, err := memif.GetDetails()
73         if err != nil {
74                 fmt.Printf("libmemif.GetDetails() error: %v\n", err)
75                 return
76         }
77
78         fmt.Printf("memif %s has been connected: %+v\n", memif.IfName, details)
79         stopCh = make(chan struct{})
80
81         // Start a separate go routine for each RX queue.
82         // (memif queue is a unit of parallelism for Rx/Tx).
83         // Beware: the number of queues created may be lower than what was requested
84         // in MemifConfiguration (the master makes the final decision).
85         for _, queue := range details.RxQueues {
86                 ch, err := memif.GetQueueInterruptChan(queue.QueueID)
87                 if err != nil {
88                         fmt.Printf("libmemif.Memif.GetQueueInterruptChan() error %v\n", err)
89                         continue
90                 }
91
92                 go CreateInterruptCallback(memif.NewPacketHandle(queue.QueueID, 10), ch, OnInterrupt)
93         }
94
95         return
96 }
97
98 // OnDisconnect is called when a memif connection is lost.
99 func OnDisconnect(memif *libmemif.Memif) (err error) {
100         fmt.Printf("memif %s has been disconnected\n", memif.IfName)
101         // Stop all packet producers and consumers.
102         close(stopCh)
103         return nil
104 }
105
106 // OnInterrupt is called when interrupted
107 func OnInterrupt(handle *libmemif.MemifPacketHandle) {
108         source := gopacket.NewPacketSource(handle, layers.LayerTypeEthernet)
109         var responses []gopacket.Packet
110
111         // Process ICMP pings
112         for packet := range source.Packets() {
113                 fmt.Println("Received new packet:")
114                 fmt.Println(packet.Dump())
115
116                 response, err := GeneratePacketResponse(packet)
117                 if err != nil {
118                         fmt.Printf("Failed to generate response: %v\n", err)
119                         continue
120                 }
121
122                 fmt.Println("Sending response:")
123                 fmt.Println(response.Dump())
124                 responses = append(responses, response)
125         }
126
127         // Answer with ICMP pongs
128         for i, response := range responses {
129                 err := handle.WritePacketData(response.Data())
130
131                 switch err {
132                 case io.EOF:
133                         return
134                 case nil:
135                         fmt.Printf("Succesfully sent packet #%v %v\n", i, len(response.Data()))
136                 default:
137                         fmt.Printf("Got error while sending packet #%v %v\n", i, err)
138                 }
139         }
140 }
141
142 // Creates user-friendly memif interrupt callback
143 func CreateInterruptCallback(handle *libmemif.MemifPacketHandle, interruptCh <-chan struct{}, callback func(handle *libmemif.MemifPacketHandle)) {
144         for {
145                 select {
146                 case <-interruptCh:
147                         callback(handle)
148                 case <-stopCh:
149                         handle.Close()
150                         return
151                 }
152         }
153 }
154
155 // GeneratePacketResponse returns an appropriate answer to an ARP request
156 // or an ICMP echo request.
157 func GeneratePacketResponse(packet gopacket.Packet) (response gopacket.Packet, err error) {
158         ethLayer := packet.Layer(layers.LayerTypeEthernet)
159         eth, ok := ethLayer.(*layers.Ethernet)
160         if !ok {
161                 fmt.Println("Missing ETH layer.")
162                 return nil, ErrUnhandledPacket
163         }
164
165         // Set up buffer and options for serialization.
166         buf := gopacket.NewSerializeBuffer()
167         opts := gopacket.SerializeOptions{
168                 FixLengths:       true,
169                 ComputeChecksums: true,
170         }
171
172         switch eth.EthernetType {
173         case layers.EthernetTypeARP:
174                 // Handle ARP request.
175                 arpLayer := packet.Layer(layers.LayerTypeARP)
176                 arp, ok := arpLayer.(*layers.ARP)
177                 if !ok {
178                         fmt.Println("Missing ARP layer.")
179                         return nil, ErrUnhandledPacket
180                 }
181
182                 if arp.Operation != layers.ARPRequest {
183                         fmt.Println("Not ARP request.")
184                         return nil, ErrUnhandledPacket
185                 }
186
187                 fmt.Println("Received an ARP request.")
188
189                 // Build packet layers.
190                 ethResp := layers.Ethernet{
191                         SrcMAC:       hwAddr,
192                         DstMAC:       eth.SrcMAC,
193                         EthernetType: layers.EthernetTypeARP,
194                 }
195
196                 arpResp := layers.ARP{
197                         AddrType:          layers.LinkTypeEthernet,
198                         Protocol:          layers.EthernetTypeIPv4,
199                         HwAddressSize:     6,
200                         ProtAddressSize:   4,
201                         Operation:         layers.ARPReply,
202                         SourceHwAddress:   []byte(hwAddr),
203                         SourceProtAddress: []byte(ipAddr),
204                         DstHwAddress:      arp.SourceHwAddress,
205                         DstProtAddress:    arp.SourceProtAddress,
206                 }
207
208                 if err := gopacket.SerializeLayers(buf, opts, &ethResp, &arpResp); err != nil {
209                         fmt.Println("SerializeLayers error: ", err)
210                         return nil, ErrUnhandledPacket
211                 }
212         case layers.EthernetTypeIPv4:
213                 // Respond to ICMP request.
214                 ipLayer := packet.Layer(layers.LayerTypeIPv4)
215                 ipv4, ok := ipLayer.(*layers.IPv4)
216                 if !ok {
217                         fmt.Println("Missing IPv4 layer.")
218                         return nil, ErrUnhandledPacket
219                 }
220
221                 if ipv4.Protocol != layers.IPProtocolICMPv4 {
222                         fmt.Println("Not ICMPv4 protocol.")
223                         return nil, ErrUnhandledPacket
224                 }
225
226                 icmpLayer := packet.Layer(layers.LayerTypeICMPv4)
227                 icmp, ok := icmpLayer.(*layers.ICMPv4)
228                 if !ok {
229                         fmt.Println("Missing ICMPv4 layer.")
230                         return nil, ErrUnhandledPacket
231                 }
232
233                 if icmp.TypeCode.Type() != layers.ICMPv4TypeEchoRequest {
234                         fmt.Println("Not ICMPv4 echo request.")
235                         return nil, ErrUnhandledPacket
236                 }
237
238                 fmt.Println("Received an ICMPv4 echo request.")
239
240                 // Build packet layers.
241                 ethResp := layers.Ethernet{
242                         SrcMAC:       hwAddr,
243                         DstMAC:       eth.SrcMAC,
244                         EthernetType: layers.EthernetTypeIPv4,
245                 }
246
247                 ipv4Resp := layers.IPv4{
248                         Version:    4,
249                         IHL:        5,
250                         TOS:        0,
251                         Id:         0,
252                         Flags:      0,
253                         FragOffset: 0,
254                         TTL:        255,
255                         Protocol:   layers.IPProtocolICMPv4,
256                         SrcIP:      ipAddr,
257                         DstIP:      ipv4.SrcIP,
258                 }
259
260                 icmpResp := layers.ICMPv4{
261                         TypeCode: layers.CreateICMPv4TypeCode(layers.ICMPv4TypeEchoReply, 0),
262                         Id:       icmp.Id,
263                         Seq:      icmp.Seq,
264                 }
265
266                 if err := gopacket.SerializeLayers(buf, opts, &ethResp, &ipv4Resp, &icmpResp, gopacket.Payload(icmp.Payload)); err != nil {
267                         fmt.Println("SerializeLayers error: ", err)
268                         return nil, ErrUnhandledPacket
269                 }
270         default:
271                 return nil, ErrUnhandledPacket
272         }
273
274         return gopacket.NewPacket(buf.Bytes(), layers.LayerTypeEthernet, gopacket.Default), nil
275 }
276
277 func main() {
278         fmt.Println("Starting 'gopacket' example...")
279         var err error
280
281         // Parse MAC address associated with memif interface
282         hwAddr, err = net.ParseMAC("aa:aa:aa:aa:aa:aa")
283         if err != nil {
284                 fmt.Printf("Failed to parse the MAC address: %v/n", err)
285                 return
286         }
287
288         // Parse IP address associated with memif interface
289         ip := net.ParseIP("192.168.1.1")
290         if ip != nil {
291                 ipAddr = ip.To4()
292         }
293         if ipAddr == nil {
294                 fmt.Println("Failed to parse the IP address")
295                 return
296         }
297
298         // If run with the "--slave" option, create memif in the slave mode.
299         var isMaster = true
300         var appSuffix string
301         if len(os.Args) > 1 && (os.Args[1] == "--slave" || os.Args[1] == "-slave") {
302                 isMaster = false
303                 appSuffix = "-slave"
304         }
305
306         // Initialize libmemif first.
307         appName := "gopacket" + appSuffix
308         fmt.Println("Initializing libmemif as ", appName)
309         err = libmemif.Init(appName)
310         if err != nil {
311                 fmt.Printf("libmemif.Init() error: %v\n", err)
312                 return
313         }
314
315         // Schedule automatic cleanup.
316         defer libmemif.Cleanup()
317
318         // Prepare callbacks to use with the memif.
319         // The same callbacks could be used with multiple memifs.
320         // The first input argument (*libmemif.Memif) can be used to tell which
321         // memif the callback was triggered for.
322         memifCallbacks := &libmemif.MemifCallbacks{
323                 OnConnect:    OnConnect,
324                 OnDisconnect: OnDisconnect,
325         }
326
327         // Prepare memif1 configuration.
328         memifConfig := &libmemif.MemifConfig{
329                 MemifMeta: libmemif.MemifMeta{
330                         IfName:         "memif1",
331                         ConnID:         1,                       // ConnectionID is an identifier used to match opposite memifs.
332                         SocketFilename: "/tmp/gopacket-example", // Socket through which the opposite memifs will establish the connection.
333                         Secret:         "secret",                // Secret used to authenticate the memif connection.
334                         IsMaster:       isMaster,
335                         Mode:           libmemif.IfModeEthernet,
336                 },
337                 MemifShmSpecs: libmemif.MemifShmSpecs{
338                         NumRxQueues:  3, // NumQueues is the (configured!) number of queues for both Rx & Tx.
339                         NumTxQueues:  3, // The actual number agreed during connection establishment may be smaller!
340                         BufferSize:   2048,
341                         Log2RingSize: 10,
342                 },
343         }
344
345         fmt.Printf("Callbacks: %+v\n", memifCallbacks)
346         fmt.Printf("Config: %+v\n", memifConfig)
347
348         // Create memif1 interface.
349         memif, err := libmemif.CreateInterface(memifConfig, memifCallbacks)
350         if err != nil {
351                 fmt.Printf("libmemif.CreateInterface() error: %v\n", err)
352                 return
353         }
354
355         // Schedule automatic cleanup of the interface.
356         defer memif.Close()
357
358         // Wait until an interrupt signal is received.
359         sigChan := make(chan os.Signal, 1)
360         signal.Notify(sigChan, os.Interrupt)
361         <-sigChan
362 }