Skip to content

Commit 321aff3

Browse files
authored
Merge pull request #117 from sipsma/cniaddpr
Add tc-redirect-tap plugin with support for ADD command.
2 parents ea85a54 + 3aa6f8c commit 321aff3

File tree

11 files changed

+1498
-1
lines changed

11 files changed

+1498
-1
lines changed

cni/Makefile

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
# Set this to pass additional commandline flags to the go compiler, e.g. "make test EXTRAGOARGS=-v"
15+
EXTRAGOARGS?=
16+
17+
SOURCES:=$(shell find . -name '*.go' ! -name '*_test.go')
18+
GOMOD := $(shell go env GOMOD)
19+
GOSUM := $(GOMOD:.mod=.sum)
20+
21+
22+
.PHONY: all
23+
all: tc-redirect-tap
24+
25+
tc-redirect-tap: $(SOURCES) $(GOMOD) $(GOSUM)
26+
go build -o tc-redirect-tap $(CURDIR)/cmd/tc-redirect-tap
27+
28+
.PHONY: install
29+
install:
30+
31+
.PHONY: test
32+
test:
33+
go test ./... $(EXTRAGOARGS)
34+
35+
.PHONY: clean
36+
clean:
37+
- rm -f tc-redirect-tap

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

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package main
15+
16+
import (
17+
"encoding/json"
18+
"os"
19+
20+
"github.com/containernetworking/cni/pkg/skel"
21+
"github.com/containernetworking/cni/pkg/types"
22+
"github.com/containernetworking/cni/pkg/types/current"
23+
"github.com/containernetworking/cni/pkg/version"
24+
"github.com/containernetworking/plugins/pkg/ns"
25+
"github.com/containernetworking/plugins/pkg/utils/buildversion"
26+
"github.com/pkg/errors"
27+
28+
"github.com/firecracker-microvm/firecracker-go-sdk/cni/internal"
29+
)
30+
31+
func main() {
32+
skel.PluginMain(add, check, del,
33+
// support CNI versions that support plugin chaining
34+
version.PluginSupports("0.3.0", "0.3.1", version.Current()),
35+
buildversion.BuildString("tc-redirect-tap"),
36+
)
37+
}
38+
39+
func add(args *skel.CmdArgs) error {
40+
p, err := newPlugin(args)
41+
if err != nil {
42+
return err
43+
}
44+
45+
currentResult, err := getCurrentResult(args)
46+
if err != nil {
47+
return err
48+
}
49+
50+
err = p.add(currentResult)
51+
if err != nil {
52+
return err
53+
}
54+
55+
return types.PrintResult(currentResult, currentResult.CNIVersion)
56+
}
57+
58+
func del(args *skel.CmdArgs) error {
59+
p, err := newPlugin(args)
60+
if err != nil {
61+
return err
62+
}
63+
64+
return p.del()
65+
}
66+
67+
func check(args *skel.CmdArgs) error {
68+
p, err := newPlugin(args)
69+
if err != nil {
70+
return err
71+
}
72+
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?")
91+
}
92+
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")
96+
}
97+
98+
return currentResult, nil
99+
}
100+
101+
func newPlugin(args *skel.CmdArgs) (*plugin, error) {
102+
netNS, err := ns.GetNS(args.Netns)
103+
if err != nil {
104+
return nil, errors.Wrapf(err, "failed to open netns at path %q", args.Netns)
105+
}
106+
107+
if args.IfName == "" {
108+
return nil, errors.New("no device to redirect with was found, was IfName specified?")
109+
}
110+
111+
return &plugin{
112+
NetlinkOps: internal.DefaultNetlinkOps(),
113+
114+
// TODO(sipsma) support customizing tap name through args
115+
116+
// TODO(sipsma) support customizing tap uid/gid through args
117+
tapUID: os.Geteuid(),
118+
tapGID: os.Getegid(),
119+
120+
// given the use case of supporting VMs, we call the "containerID" a "vmID"
121+
vmID: args.ContainerID,
122+
123+
redirectInterfaceName: args.IfName,
124+
125+
netNS: netNS,
126+
}, nil
127+
}
128+
129+
type plugin struct {
130+
internal.NetlinkOps
131+
132+
// vmID is the sandbox ID used to specify the interface that should be created
133+
// and configured for the VM internally (the CNI spec allows the sandbox ID to
134+
// be a hypervisor/VM ID in addition to a network namespace path)
135+
vmID string
136+
137+
// tapName is the name that the VM's tap device will be created with. If it's
138+
// unset, it will be with a name decided automatically by the kernel
139+
tapName string
140+
141+
// tapUID is the uid of the user-owner of the tap device
142+
tapUID int
143+
144+
// tapGID is the gid of the group-owner of the tap device
145+
tapGID int
146+
147+
// redirectInterfaceName is the name of the device that the tap device will have a
148+
// u32 redirect filter pair with. It's provided by the client via the CNI runtime
149+
// config "IfName" parameter
150+
redirectInterfaceName string
151+
152+
// netNS is the network namespace in which the redirectIface exists and thus in which
153+
// the tap device will be created too
154+
netNS ns.NetNS
155+
}
156+
157+
func (p plugin) add(currentResult *current.Result) error {
158+
return p.netNS.Do(func(_ ns.NetNS) error {
159+
redirectLink, err := p.GetLink(p.redirectInterfaceName)
160+
if err != nil {
161+
return errors.Wrapf(err, "failed to find redirect interface %q", p.redirectInterfaceName)
162+
}
163+
164+
redirectIPs := internal.InterfaceIPs(currentResult, redirectLink.Attrs().Name, p.netNS.Path())
165+
if len(redirectIPs) != 1 {
166+
return errors.Errorf("expected to find 1 IP on redirect interface %q, but instead found %+v",
167+
redirectLink.Attrs().Name, redirectIPs)
168+
}
169+
redirectIP := redirectIPs[0]
170+
171+
tapLink, err := p.CreateTap(p.tapName, redirectLink.Attrs().MTU, p.tapUID, p.tapGID)
172+
if err != nil {
173+
return err
174+
}
175+
176+
err = p.AddIngressQdisc(tapLink)
177+
if err != nil {
178+
return err
179+
}
180+
181+
err = p.AddIngressQdisc(redirectLink)
182+
if err != nil {
183+
return err
184+
}
185+
186+
err = p.AddRedirectFilter(tapLink, redirectLink)
187+
if err != nil {
188+
return err
189+
}
190+
191+
err = p.AddRedirectFilter(redirectLink, tapLink)
192+
if err != nil {
193+
return err
194+
}
195+
196+
// Add the tap device to our results
197+
currentResult.Interfaces = append(currentResult.Interfaces, &current.Interface{
198+
Name: tapLink.Attrs().Name,
199+
Sandbox: p.netNS.Path(),
200+
Mac: tapLink.Attrs().HardwareAddr.String(),
201+
})
202+
203+
// Add the pseudo vm interface to our results. It specifies the configuration
204+
// that should be applied to the VM's internal interface once it is spun up.
205+
// It is not yet a real device. It is given the same name as the tap device in
206+
// order to associate it as the internal interface corresponding to the external
207+
// tap device. However, it's given the vmID as the sandbox ID in order to
208+
// differentiate from the tap and associate it with the VM.
209+
//
210+
// See the `vmconf` package's docstring for the definition of this interface
211+
currentResult.Interfaces = append(currentResult.Interfaces, &current.Interface{
212+
Name: tapLink.Attrs().Name,
213+
Sandbox: p.vmID,
214+
Mac: redirectLink.Attrs().HardwareAddr.String(),
215+
})
216+
vmIfaceIndex := len(currentResult.Interfaces) - 1
217+
218+
// Add the IP configuration that should be applied to the VM internally by
219+
// associating the IPConfig with the vmIface. We use the redirectIface's IP.
220+
currentResult.IPs = append(currentResult.IPs, &current.IPConfig{
221+
Version: redirectIP.Version,
222+
Address: redirectIP.Address,
223+
Gateway: redirectIP.Gateway,
224+
Interface: &vmIfaceIndex,
225+
})
226+
227+
return nil
228+
})
229+
}
230+
231+
func (p plugin) del() error {
232+
panic("del is currently unimplemented")
233+
}
234+
235+
func (p plugin) check() error {
236+
panic("check is currently unimplemented")
237+
}

0 commit comments

Comments
 (0)