hs-test: configure VPP from test context
[vpp.git] / extras / hs-test / netconfig.go
1 package main
2
3 import (
4         "errors"
5         "fmt"
6         "os/exec"
7         "strings"
8
9         "go.fd.io/govpp/binapi/ethernet_types"
10         "go.fd.io/govpp/binapi/interface_types"
11         "go.fd.io/govpp/binapi/ip_types"
12 )
13
14 type (
15         AddressWithPrefix = ip_types.AddressWithPrefix
16         MacAddress        = ethernet_types.MacAddress
17
18         NetConfig struct {
19                 Configure   func() error
20                 Unconfigure func()
21         }
22
23         NetTopology []NetConfig
24
25         NetConfigBase struct {
26                 name     string
27                 category string // what else to call this when `type` is reserved?
28         }
29
30         NetworkInterfaceVeth struct {
31                 NetConfigBase
32                 index                interface_types.InterfaceIndex
33                 peerNetworkNamespace string
34                 peerName             string
35                 peerIp4Address       string
36                 ip4Address           ip_types.AddressWithPrefix
37                 hwAddress            ethernet_types.MacAddress
38         }
39
40         NetworkInterfaceTap struct {
41                 NetConfigBase
42                 index      interface_types.InterfaceIndex
43                 ip4Address string
44         }
45
46         NetworkNamespace struct {
47                 NetConfigBase
48         }
49
50         NetworkBridge struct {
51                 NetConfigBase
52                 networkNamespace string
53                 interfaces       []string
54         }
55 )
56
57 const (
58         NetNs  string = "netns"
59         Veth   string = "veth"
60         Tap    string = "tap"
61         Bridge string = "bridge"
62 )
63
64 func (b NetConfigBase) Name() string {
65         return b.name
66 }
67
68 func (b NetConfigBase) Type() string {
69         return b.category
70 }
71
72 func (iface NetworkInterfaceVeth) Configure() error {
73         err := AddVethPair(iface.name, iface.peerName)
74         if err != nil {
75                 return err
76         }
77
78         if iface.peerNetworkNamespace != "" {
79                 err := LinkSetNetns(iface.peerName, iface.peerNetworkNamespace)
80                 if err != nil {
81                         return err
82                 }
83         }
84
85         if iface.peerIp4Address != "" {
86                 err = AddAddress(iface.peerName, iface.peerIp4Address, iface.peerNetworkNamespace)
87                 if err != nil {
88                         return fmt.Errorf("failed to add configure address for %s: %v", iface.peerName, err)
89                 }
90         }
91         return nil
92 }
93
94 func (iface NetworkInterfaceVeth) Unconfigure() {
95         DelLink(iface.name)
96 }
97
98 func (iface NetworkInterfaceVeth) Address() string {
99         return strings.Split(iface.ip4Address.String(), "/")[0]
100 }
101
102 func (iface NetworkInterfaceTap) Configure() error {
103         err := AddTap(iface.name, iface.ip4Address)
104         if err != nil {
105                 return err
106         }
107         return nil
108 }
109
110 func (iface NetworkInterfaceTap) Unconfigure() {
111         DelLink(iface.name)
112 }
113
114 func (ns NetworkNamespace) Configure() error {
115         return addDelNetns(ns.name, true)
116 }
117
118 func (ns NetworkNamespace) Unconfigure() {
119         addDelNetns(ns.name, false)
120 }
121
122 func (b NetworkBridge) Configure() error {
123         return AddBridge(b.name, b.interfaces, b.networkNamespace)
124 }
125
126 func (b NetworkBridge) Unconfigure() {
127         DelBridge(b.name, b.networkNamespace)
128 }
129
130 func (t *NetTopology) Configure() error {
131         for _, c := range *t {
132                 err := c.Configure()
133                 if err != nil {
134                         return err
135                 }
136         }
137         return nil
138 }
139
140 func (t *NetTopology) Unconfigure() {
141         for _, c := range *t {
142                 c.Unconfigure()
143         }
144 }
145
146 func newConfigFn(cfg NetDevConfig) func() error {
147         t := cfg["type"]
148         if t == "netns" {
149                 return func() error { return AddNetns(cfg["name"].(string)) }
150         } else if t == "veth" {
151                 return func() error {
152                         var peerNs string
153                         peer := cfg["peer"].(NetDevConfig)
154                         peerName := peer["name"].(string)
155                         err := AddVethPair(cfg["name"].(string), peerName)
156                         if err != nil {
157                                 return err
158                         }
159
160                         if peer["netns"] != nil {
161                                 peerNs = peer["netns"].(string)
162                                 if peerNs != "" {
163                                         err := LinkSetNetns(peerName, peerNs)
164                                         if err != nil {
165                                                 return err
166                                         }
167                                 }
168                         }
169                         if peer["ip4"] != nil {
170                                 err = AddAddress(peerName, peer["ip4"].(string), peerNs)
171                                 if err != nil {
172                                         return fmt.Errorf("failed to add configure address for %s: %v", peerName, err)
173                                 }
174                         }
175                         return nil
176                 }
177         } else if t == "bridge" {
178                 return func() error { return configureBridge(cfg) }
179         } else if t == "tap" {
180                 return func() error { return configureTap(cfg) }
181         }
182         return nil
183 }
184
185 func newUnconfigFn(cfg NetDevConfig) func() {
186         t := cfg["type"]
187         name := cfg["name"].(string)
188
189         if t == "tap" {
190                 return func() { DelLink(name) }
191         } else if t == "netns" {
192                 return func() { DelNetns(name) }
193         } else if t == "veth" {
194                 return func() { DelLink(name) }
195         } else if t == "bridge" {
196                 return func() { DelBridge(name, cfg["netns"].(string)) }
197         }
198         return nil
199 }
200
201 func NewNetConfig(cfg NetDevConfig) NetConfig {
202         var nc NetConfig
203
204         nc.Configure = newConfigFn(cfg)
205         nc.Unconfigure = newUnconfigFn(cfg)
206
207         return nc
208 }
209
210 func NewNetNamespace(cfg NetDevConfig) (NetworkNamespace, error) {
211         var networkNamespace NetworkNamespace
212         networkNamespace.name = cfg["name"].(string)
213         networkNamespace.category = "netns"
214         return networkNamespace, nil
215 }
216
217 func NewBridge(cfg NetDevConfig) (NetworkBridge, error) {
218         var bridge NetworkBridge
219         bridge.name = cfg["name"].(string)
220         bridge.category = "bridge"
221         for _, v := range cfg["interfaces"].([]interface{}) {
222                 bridge.interfaces = append(bridge.interfaces, v.(string))
223         }
224         bridge.networkNamespace = cfg["netns"].(string)
225         return bridge, nil
226 }
227
228 func NewVeth(cfg NetDevConfig) (NetworkInterfaceVeth, error) {
229         var veth NetworkInterfaceVeth
230         var err error
231         veth.name = cfg["name"].(string)
232         veth.category = "veth"
233
234         if cfg["preset-hw-address"] != nil {
235                 veth.hwAddress, err = ethernet_types.ParseMacAddress(cfg["preset-hw-address"].(string))
236                 if err != nil {
237                         return NetworkInterfaceVeth{}, err
238                 }
239         }
240
241         peer := cfg["peer"].(NetDevConfig)
242
243         veth.peerName = peer["name"].(string)
244
245         if peer["netns"] != nil {
246                 veth.peerNetworkNamespace = peer["netns"].(string)
247         }
248
249         if peer["ip4"] != nil {
250                 veth.peerIp4Address = peer["ip4"].(string)
251         }
252
253         return veth, nil
254 }
255
256 func NewTap(cfg NetDevConfig) (NetworkInterfaceTap, error) {
257         var tap NetworkInterfaceTap
258         tap.name = cfg["name"].(string)
259         tap.category = "tap"
260         tap.ip4Address = cfg["ip4"].(string)
261         return tap, nil
262 }
263
264 func DelBridge(brName, ns string) error {
265         err := SetDevDown(brName, ns)
266         if err != err {
267                 return err
268         }
269
270         err = addDelBridge(brName, ns, false)
271         if err != nil {
272                 return err
273         }
274
275         return nil
276 }
277
278 func configureBridge(dev NetDevConfig) error {
279         var ifs []string
280         for _, v := range dev["interfaces"].([]interface{}) {
281                 ifs = append(ifs, v.(string))
282         }
283         return AddBridge(dev["name"].(string), ifs, dev["netns"].(string))
284 }
285
286 func configureTap(dev NetDevConfig) error {
287         return AddTap(dev["name"].(string), dev["ip4"].(string))
288 }
289
290 func SetDevUp(dev, ns string) error {
291         return setDevUpDown(dev, ns, true)
292 }
293
294 func SetDevDown(dev, ns string) error {
295         return setDevUpDown(dev, ns, false)
296 }
297
298 func AddTap(ifName, ifAddress string) error {
299         cmd := exec.Command("ip", "tuntap", "add", ifName, "mode", "tap")
300         o, err := cmd.CombinedOutput()
301         if err != nil {
302                 s := fmt.Sprintf("error creating tap %s: %v: %s", ifName, err, string(o))
303                 return errors.New(s)
304         }
305
306         cmd = exec.Command("ip", "addr", "add", ifAddress, "dev", ifName)
307         err = cmd.Run()
308         if err != nil {
309                 DelLink(ifName)
310                 s := fmt.Sprintf("error setting addr for tap %s: %v", ifName, err)
311                 return errors.New(s)
312         }
313
314         err = SetDevUp(ifName, "")
315         if err != nil {
316                 DelLink(ifName)
317                 return err
318         }
319         return nil
320 }
321
322 func DelLink(ifName string) {
323         cmd := exec.Command("ip", "link", "del", ifName)
324         cmd.Run()
325 }
326
327 func setDevUpDown(dev, ns string, isUp bool) error {
328         var op string
329         if isUp {
330                 op = "up"
331         } else {
332                 op = "down"
333         }
334         c := []string{"ip", "link", "set", "dev", dev, op}
335         cmd := appendNetns(c, ns)
336         err := cmd.Run()
337         if err != nil {
338                 s := fmt.Sprintf("error bringing %s device %s!", dev, op)
339                 return errors.New(s)
340         }
341         return nil
342 }
343
344 func AddVethPair(ifName, peerName string) error {
345         cmd := exec.Command("ip", "link", "add", ifName, "type", "veth", "peer", "name", peerName)
346         err := cmd.Run()
347         if err != nil {
348                 return fmt.Errorf("creating veth pair failed: %v", err)
349         }
350         err = SetDevUp(ifName, "")
351         if err != nil {
352                 return fmt.Errorf("set link up failed: %v", err)
353         }
354         return nil
355 }
356
357 func addDelNetns(name string, isAdd bool) error {
358         var op string
359         if isAdd {
360                 op = "add"
361         } else {
362                 op = "del"
363         }
364         cmd := exec.Command("ip", "netns", op, name)
365         _, err := cmd.CombinedOutput()
366         if err != nil {
367                 return errors.New("add/del netns failed")
368         }
369         return nil
370 }
371
372 func AddNetns(nsName string) error {
373         return addDelNetns(nsName, true)
374 }
375
376 func DelNetns(nsName string) error {
377         return addDelNetns(nsName, false)
378 }
379
380 func LinkSetNetns(ifName, ns string) error {
381         cmd := exec.Command("ip", "link", "set", "dev", ifName, "up", "netns", ns)
382         err := cmd.Run()
383         if err != nil {
384                 return fmt.Errorf("error setting device '%s' to netns '%s: %v", ifName, ns, err)
385         }
386         return nil
387 }
388
389 func NewCommand(s []string, ns string) *exec.Cmd {
390         return appendNetns(s, ns)
391 }
392
393 func appendNetns(s []string, ns string) *exec.Cmd {
394         var cmd *exec.Cmd
395         if ns == "" {
396                 // use default namespace
397                 cmd = exec.Command(s[0], s[1:]...)
398         } else {
399                 var args = []string{"netns", "exec", ns}
400                 args = append(args, s[:]...)
401                 cmd = exec.Command("ip", args...)
402         }
403         return cmd
404 }
405
406 func addDelBridge(brName, ns string, isAdd bool) error {
407         var op string
408         if isAdd {
409                 op = "addbr"
410         } else {
411                 op = "delbr"
412         }
413         var c = []string{"brctl", op, brName}
414         cmd := appendNetns(c, ns)
415         err := cmd.Run()
416         if err != nil {
417                 s := fmt.Sprintf("%s %s failed!", op, brName)
418                 return errors.New(s)
419         }
420         return nil
421 }
422
423 func AddBridge(brName string, ifs []string, ns string) error {
424         err := addDelBridge(brName, ns, true)
425         if err != nil {
426                 return err
427         }
428
429         for _, v := range ifs {
430                 c := []string{"brctl", "addif", brName, v}
431                 cmd := appendNetns(c, ns)
432                 err = cmd.Run()
433                 if err != nil {
434                         s := fmt.Sprintf("error adding %s to bridge %s: %v", v, brName, err)
435                         return errors.New(s)
436                 }
437         }
438         err = SetDevUp(brName, ns)
439         if err != nil {
440                 return err
441         }
442         return nil
443 }