aboutsummaryrefslogtreecommitdiffstats
path: root/extras/libmemif/examples/gopacket/gopacket.go
blob: 2e3e04dc9b474e8c7b9d264a8905c13472817009 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
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"
	"git.fd.io/govpp.git/extras/libmemif"
	"github.com/google/gopacket"
	"github.com/google/gopacket/layers"
	"io"
	"net"
	"os"
	"os/signal"
)

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
}