Skip to content

Commit 922c3a7

Browse files
authored
Merge pull request firecracker-microvm#124 from sipsma/cnipr
Add DEL and CHECK support to tc-redirect plugin
2 parents d743636 + f027352 commit 922c3a7

File tree

10 files changed

+689
-298
lines changed

10 files changed

+689
-298
lines changed

cni/cmd/tc-redirect-tap/main.go

Lines changed: 181 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/containernetworking/cni/pkg/version"
2424
"github.com/containernetworking/plugins/pkg/ns"
2525
"github.com/containernetworking/plugins/pkg/utils/buildversion"
26+
"github.com/hashicorp/go-multierror"
2627
"github.com/pkg/errors"
2728

2829
"github.com/firecracker-microvm/firecracker-go-sdk/cni/internal"
@@ -42,17 +43,20 @@ func add(args *skel.CmdArgs) error {
4243
return err
4344
}
4445

45-
currentResult, err := getCurrentResult(args)
46-
if err != nil {
47-
return err
46+
if p.netNS == nil {
47+
return errors.Errorf("netns path %q does not exist", args.Netns)
4848
}
4949

50-
err = p.add(currentResult)
50+
if p.currentResult == nil {
51+
return &NoPreviousResultError{}
52+
}
53+
54+
err = p.add()
5155
if err != nil {
5256
return err
5357
}
5458

55-
return types.PrintResult(currentResult, currentResult.CNIVersion)
59+
return types.PrintResult(p.currentResult, p.currentResult.CNIVersion)
5660
}
5761

5862
func del(args *skel.CmdArgs) error {
@@ -61,6 +65,12 @@ func del(args *skel.CmdArgs) error {
6165
return err
6266
}
6367

68+
if p.netNS == nil {
69+
// the network namespace is already gone and everything we create
70+
// is inside the netns, so nothing to do here.
71+
return nil
72+
}
73+
6474
return p.del()
6575
}
6676

@@ -70,42 +80,42 @@ func check(args *skel.CmdArgs) error {
7080
return err
7181
}
7282

73-
return p.check()
74-
}
75-
76-
func getCurrentResult(args *skel.CmdArgs) (*current.Result, error) {
77-
// parse the previous CNI result (or throw an error if there wasn't one)
78-
cniConf := types.NetConf{}
79-
err := json.Unmarshal(args.StdinData, &cniConf)
80-
if err != nil {
81-
return nil, errors.Wrap(err, "failure checking for previous result output")
82-
}
83-
84-
err = version.ParsePrevResult(&cniConf)
85-
if err != nil {
86-
return nil, errors.Wrap(err, "failed to parse previous CNI result")
87-
}
88-
89-
if cniConf.PrevResult == nil {
90-
return nil, errors.New("no previous result was found, was this plugin chained with a previous one?")
83+
if p.netNS == nil {
84+
return errors.Errorf("netns path %q does not exist", args.Netns)
9185
}
9286

93-
currentResult, err := current.NewResultFromResult(cniConf.PrevResult)
94-
if err != nil {
95-
return nil, errors.Wrap(err, "failed to generate current result from previous CNI result")
87+
if p.currentResult == nil {
88+
return &NoPreviousResultError{}
9689
}
9790

98-
return currentResult, nil
91+
return p.check()
9992
}
10093

10194
func newPlugin(args *skel.CmdArgs) (*plugin, error) {
95+
if args.IfName == "" {
96+
return nil, errors.New("no device to redirect with was found, was IfName specified?")
97+
}
98+
10299
netNS, err := ns.GetNS(args.Netns)
103100
if err != nil {
104-
return nil, errors.Wrapf(err, "failed to open netns at path %q", args.Netns)
101+
// It's valid for the netns to no longer exist during DEL commands (in which case DEL is
102+
// a noop). Thus, we leave validating that netNS is not nil to command implementations.
103+
switch err.(type) {
104+
case *ns.NSPathNotExistErr:
105+
netNS = nil
106+
default:
107+
return nil, errors.Wrapf(err, "failed to open netns at path %q", args.Netns)
108+
}
105109
}
106110

107-
if args.IfName == "" {
108-
return nil, errors.New("no device to redirect with was found, was IfName specified?")
111+
currentResult, err := getCurrentResult(args)
112+
if err != nil {
113+
switch err.(type) {
114+
case *NoPreviousResultError:
115+
currentResult = nil
116+
default:
117+
return nil, errors.Wrapf(err, "failure parsing previous CNI result")
118+
}
109119
}
110120

111121
return &plugin{
@@ -123,9 +133,37 @@ func newPlugin(args *skel.CmdArgs) (*plugin, error) {
123133
redirectInterfaceName: args.IfName,
124134

125135
netNS: netNS,
136+
137+
currentResult: currentResult,
126138
}, nil
127139
}
128140

141+
func getCurrentResult(args *skel.CmdArgs) (*current.Result, error) {
142+
// parse the previous CNI result (or throw an error if there wasn't one)
143+
cniConf := types.NetConf{}
144+
err := json.Unmarshal(args.StdinData, &cniConf)
145+
if err != nil {
146+
return nil, errors.Wrap(err, "failure checking for previous result output")
147+
}
148+
149+
err = version.ParsePrevResult(&cniConf)
150+
if err != nil {
151+
return nil, errors.Wrap(err, "failed to parse previous CNI result")
152+
}
153+
154+
if cniConf.PrevResult == nil {
155+
return nil, &NoPreviousResultError{}
156+
}
157+
158+
currentResult, err := current.NewResultFromResult(cniConf.PrevResult)
159+
if err != nil {
160+
return nil, errors.Wrap(err,
161+
"failed to generate current result from previous CNI result")
162+
}
163+
164+
return currentResult, nil
165+
}
166+
129167
type plugin struct {
130168
internal.NetlinkOps
131169

@@ -152,18 +190,25 @@ type plugin struct {
152190
// netNS is the network namespace in which the redirectIface exists and thus in which
153191
// the tap device will be created too
154192
netNS ns.NetNS
193+
194+
// currentResult is the CNI result object, initialized to the previous CNI
195+
// result if there was any or to nil if there was no previous result provided
196+
currentResult *current.Result
155197
}
156198

157-
func (p plugin) add(currentResult *current.Result) error {
199+
func (p plugin) add() error {
158200
return p.netNS.Do(func(_ ns.NetNS) error {
159201
redirectLink, err := p.GetLink(p.redirectInterfaceName)
160202
if err != nil {
161-
return errors.Wrapf(err, "failed to find redirect interface %q", p.redirectInterfaceName)
203+
return errors.Wrapf(err,
204+
"failed to find redirect interface %q", p.redirectInterfaceName)
162205
}
163206

164-
redirectIPs := internal.InterfaceIPs(currentResult, redirectLink.Attrs().Name, p.netNS.Path())
207+
redirectIPs := internal.InterfaceIPs(
208+
p.currentResult, redirectLink.Attrs().Name, p.netNS.Path())
165209
if len(redirectIPs) != 1 {
166-
return errors.Errorf("expected to find 1 IP on redirect interface %q, but instead found %+v",
210+
return errors.Errorf(
211+
"expected to find 1 IP on redirect interface %q, but instead found %+v",
167212
redirectLink.Attrs().Name, redirectIPs)
168213
}
169214
redirectIP := redirectIPs[0]
@@ -194,7 +239,7 @@ func (p plugin) add(currentResult *current.Result) error {
194239
}
195240

196241
// Add the tap device to our results
197-
currentResult.Interfaces = append(currentResult.Interfaces, &current.Interface{
242+
p.currentResult.Interfaces = append(p.currentResult.Interfaces, &current.Interface{
198243
Name: tapLink.Attrs().Name,
199244
Sandbox: p.netNS.Path(),
200245
Mac: tapLink.Attrs().HardwareAddr.String(),
@@ -208,16 +253,16 @@ func (p plugin) add(currentResult *current.Result) error {
208253
// differentiate from the tap and associate it with the VM.
209254
//
210255
// See the `vmconf` package's docstring for the definition of this interface
211-
currentResult.Interfaces = append(currentResult.Interfaces, &current.Interface{
256+
p.currentResult.Interfaces = append(p.currentResult.Interfaces, &current.Interface{
212257
Name: tapLink.Attrs().Name,
213258
Sandbox: p.vmID,
214259
Mac: redirectLink.Attrs().HardwareAddr.String(),
215260
})
216-
vmIfaceIndex := len(currentResult.Interfaces) - 1
261+
vmIfaceIndex := len(p.currentResult.Interfaces) - 1
217262

218263
// Add the IP configuration that should be applied to the VM internally by
219264
// associating the IPConfig with the vmIface. We use the redirectIface's IP.
220-
currentResult.IPs = append(currentResult.IPs, &current.IPConfig{
265+
p.currentResult.IPs = append(p.currentResult.IPs, &current.IPConfig{
221266
Version: redirectIP.Version,
222267
Address: redirectIP.Address,
223268
Gateway: redirectIP.Gateway,
@@ -229,9 +274,105 @@ func (p plugin) add(currentResult *current.Result) error {
229274
}
230275

231276
func (p plugin) del() error {
232-
panic("del is currently unimplemented")
277+
return p.netNS.Do(func(_ ns.NetNS) error {
278+
var multiErr *multierror.Error
279+
280+
// try to remove the qdisc we added from the redirect interface
281+
redirectLink, err := p.GetLink(p.redirectInterfaceName)
282+
switch err.(type) {
283+
284+
case nil:
285+
// the link exists, so try removing the qdisc
286+
err := p.RemoveIngressQdisc(redirectLink)
287+
switch err.(type) {
288+
case nil, *internal.QdiscNotFoundError:
289+
// we removed successfully or there already wasn't a qdisc, nothing to do
290+
default:
291+
multiErr = multierror.Append(multiErr, errors.Wrapf(err,
292+
"failed to remove ingress qdisc from %q", redirectLink.Attrs().Name))
293+
}
294+
295+
case *internal.LinkNotFoundError:
296+
// if the link doesn't exist, there's nothing to do
297+
298+
default:
299+
multiErr = multierror.Append(multiErr,
300+
errors.Wrapf(err, "failure finding device %q", p.redirectInterfaceName))
301+
}
302+
303+
// if there was no previous result, we can't find the vm-tap pair, so we are done here
304+
if p.currentResult == nil {
305+
return multiErr.ErrorOrNil()
306+
}
307+
308+
// try to remove the tap device we added
309+
_, tapIface, err := internal.VMTapPair(p.currentResult, p.vmID)
310+
switch err.(type) {
311+
312+
case nil:
313+
err = p.RemoveLink(tapIface.Name)
314+
switch err.(type) {
315+
case nil, *internal.LinkNotFoundError:
316+
// we removed successfully or someone else beat us to removing it first
317+
default:
318+
multiErr = multierror.Append(multiErr, errors.Wrapf(err,
319+
"failure removing device %q", tapIface.Name))
320+
}
321+
322+
case *internal.LinkNotFoundError:
323+
// if the link doesn't exist, there's nothing to do
324+
325+
default:
326+
multiErr = multierror.Append(multiErr, err)
327+
}
328+
329+
return multiErr.ErrorOrNil()
330+
})
233331
}
234332

235333
func (p plugin) check() error {
236-
panic("check is currently unimplemented")
334+
return p.netNS.Do(func(_ ns.NetNS) error {
335+
_, tapIface, err := internal.VMTapPair(p.currentResult, p.vmID)
336+
if err != nil {
337+
return err
338+
}
339+
340+
tapLink, err := p.GetLink(tapIface.Name)
341+
if err != nil {
342+
return err
343+
}
344+
345+
redirectLink, err := p.GetLink(p.redirectInterfaceName)
346+
if err != nil {
347+
return err
348+
}
349+
350+
_, err = p.GetIngressQdisc(tapLink)
351+
if err != nil {
352+
return err
353+
}
354+
355+
_, err = p.GetIngressQdisc(redirectLink)
356+
if err != nil {
357+
return err
358+
}
359+
360+
_, err = p.GetRedirectFilter(tapLink, redirectLink)
361+
if err != nil {
362+
return err
363+
}
364+
365+
_, err = p.GetRedirectFilter(redirectLink, tapLink)
366+
if err != nil {
367+
return err
368+
}
369+
370+
return nil
371+
})
372+
}
373+
374+
type NoPreviousResultError struct{}
375+
376+
func (e NoPreviousResultError) Error() string {
377+
return "no previous result was found, was this plugin chained with a previous one?"
237378
}

0 commit comments

Comments
 (0)