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
|
## Go-libmemif
Package **libmemif** is a Golang adapter for the **libmemif library**
(`extras/libmemif` in the [VPP](https://wiki.fd.io/view/VPP) repository).
To differentiate between the adapter and the underlying C-written library,
labels `Go-libmemif` and `C-libmemif` are used in the documentation.
### Requirements
libmemif for Golang is build on the top of the original, C-written
libmemif library using `cgo`. It is therefore necessary to have C-libmemif
header files and the library itself installed in locations known
to the compiler.
For example, to install C-libmemif system-wide into the standard
locations, execute:
```
$ git clone https://gerrit.fd.io/r/vpp
$ cd vpp/extras/libmemif
$ ./bootstrap
$ ./configure
$ make install
```
### Build
Package **libmemif** is not part of the **GoVPP** core and as such it is
not included in the [make build](../../Makefile) target.
Instead, it has its own target in the [top-level Makefile](../../Makefile)
used to build the attached examples with the adapter:
```
$ make extras
```
### APIs
All **Go-libmemif** public APIs can be found in [adapter.go](adapter.go).
Please see the comments for a more detailed description.
Additionally, a list of all errors thrown by libmemif can be found
in [error.go](error.go).
### Usage
**libmemif** needs to be first initialized with `Init(appName)`.
This has to be done only once in the context of the entire process.
Make sure to call `Cleanup()` to release all the resources allocated
by **libmemif** before exiting your application. Consider calling
`Init()` followed by `Cleanup()` scheduled with `defer` in the `main()`
function.
Log messages are by default printed to stdout. Use `SetLogger()` to use
your own customized logger (can be changed before `Init()`).
Once **libmemif** is initialized, new memif interfaces can be created
with `CreateInterface(config, callbacks)`. See `MemifConfig` structure
definition to learn about possible memif configuration options.
If successful, `CreateInterface()` returns an instance of `Memif`
structure representing the underlying memif interface.
Callbacks are optional and can be shared across multiple memif instances.
Available callbacks are:
1. **OnConnect**: called when the connection is established.
By the time the callback is called, the Rx/Tx queues are initialized
and ready for data transmission. Interrupt channels are also
created and ready to be read from.
The user is expected to start polling for input packets via repeated
calls to `Memif.RxBurst(queueID, count)` or to initiate select
on the interrupt channels obtained with `Get*InterruptChan()`,
depending on the Rx mode. By default, all memif Rx queues are created
in the interrupt mode, but this can be changed per-queue with
`Memif.SetRxMode(queueID, mode)`.
2. **OnDisconnect**: called after the connection was closed. Immediately
after the user callback returns, Rx/Tx queues and interrupt channels
are also deallocated. The user defined callback should therefore ensure
that all the Rx/Tx operations are stopped before it returns.
**libmemif** was designed for a maximum possible performance. Packets
are sent and received in bulks, rather than one-by-one, using
`Memif.TxBurst(queueID, packets)` and `Memif.RxBurst(queueID, count)`,
respectively. Memif connection can consists of multiple queues in both
directions. A queue is one-directional wait-free ring buffer.
It is the unit of parallelism for data transmission. The maximum possible
lock-free granularity is therefore one go routine for one queue.
Interrupt channel for one specific Rx queue can be obtained with
`GetQueueInterruptChan(queueID)` as opposed to `GetInterruptChan()`
for all the Rx queues. There is only one interrupt signal sent for
an entire burst of packets, therefore an interrupt handling routine
should repeatedly call RxBurst() until an empty slice of packets
is returned. This way it is ensured that there are no packets left
on the queue unread when the interrupt signal is cleared.
Study the `ReadAndPrintPackets()` function in [raw-data example](examples/raw-data/raw-data.go).
For **libmemif** the packet is just an array of bytes. It does not care
what the actual content is. It is not required for a packet to follow
any network protocol in order to get transported from one end to another.
See the type declaration for `RawPacketData` and its use in `Memif.TxBurst()`
and `Memif.RxBurst()`.
In order to remove a memif interface, call `Memif.Close()`. If the memif
is in the connected state, the connection is first properly closed.
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
of the package with a detailed commentary.
The examples can be found in the subdirectory [examples](./examples).
#### Raw data (libmemif <-> libmemif)
*raw-data* is a basic example showing how to create a memif interface,
handle events through callbacks and perform Rx/Tx of raw data. Before
handling an actual packets it is important to understand the skeleton
of libmemif-based applications.
Since VPP expects proper packet data, it is not very useful to connect
*raw-data* example with VPP, even though it will work, since all
the received data will get dropped on the VPP side.
To create a connection of two raw-data instances, start two processes
concurrently in an arbitrary order:
- *master* memif:
```
$ cd extras/libmemif/examples/raw-data
$ ./raw-data
```
- *slave* memif:
```
$ cd extras/libmemif/examples/raw-data
$ ./raw-data --slave
```
Every 3 seconds both sides send 3 raw-data packets to the opposite end
through each of the 3 queues. The received packets are printed to stdout.
Stop an instance of *raw-data* with an interrupt signal (^C).
#### ICMP Responder
*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 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
```
*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
specific feature common to all interface types.
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).
|