forked from firecracker-microvm/firecracker-go-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
vmconf.go
219 lines (190 loc) · 8.16 KB
/
vmconf.go
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
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
/*
Package vmconf defines an interface for converting particular CNI invocation
results to networking configuration usable by a VM. It expects the CNI result
to have the following properties:
- The results should contain an interface for a tap device, which will be used
as the VM's tap device.
- The results should contain an interface with the same name as the tap device
but with sandbox ID set to the containerID provided during CNI invocation.
This should be a "pseudo-interface", not one that has actually been created.
It represents the configuration that should be applied to the VM internally.
The CNI "containerID" is, in this case, used more as a "vmID" to represent
the VM's internal network interface.
- If the CNI results specify an IP associated with this interface, that IP
should be used to statically configure the VM's internal network interface.
*/
package vmconf
import (
"fmt"
"strings"
"github.com/containernetworking/cni/pkg/types"
current "github.com/containernetworking/cni/pkg/types/100"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/verloop/firecracker-go-sdk/cni/internal"
)
// StaticNetworkConf holds the configuration needed to configure a VM's networking
// stack. It is generally parsed from a CNI result object via the StaticNetworkConfFrom
// function.
//
// Fields beginning with "VM" are references to entities that need to be setup to
// exist *within* the VM once the VM is started.
type StaticNetworkConf struct {
// TapName is the name of the tap device that the VM should use as its
// network interface
TapName string
// NetNSPath is the path to the bind-mounted network namespace in which the VM's
// tap device was created and thus where the VM should execute.
NetNSPath string
// VMIfName (optional) is interface name to configure. If left blank, config
// is applied to the first (default) interface.
VMIfName string
// VMMacAddr is the mac address that callers should configure their VM to use internally.
VMMacAddr string
// VMMTU is the MTU that callers should configure their VM to use internally.
VMMTU int
// VMIPConfig is the ip configuration that callers should configure their VM's internal
// primary interface to use.
VMIPConfig *current.IPConfig
// VMRoutes are the routes that callers should configure their VM's internal route table
// to have
VMRoutes []*types.Route
// VMNameservers are the nameservers that callers should configure their VM to use internally
VMNameservers []string
// VMDomain is the resolver domain that callers should configure VM to use internally.
VMDomain string
// VMSearchDomans are the resolver search domains that callers should configure their VM to
// use internally
VMSearchDomains []string
// VMResolverOptions are the resolve options that callers should configure their VM to use
// internally
VMResolverOptions []string
}
// IPBootParam provides a string that can be used as the argument to "ip=" in a Linux kernel boot
// parameters in order to boot a machine with network settings matching those in a StaticNetworkConf
// object.
//
// See "ip=" section of kernel docs here for more details:
// https://www.kernel.org/doc/Documentation/filesystems/nfs/nfsroot.txt
//
// Due to the limitation of "ip=", not all configuration specified in StaticNetworkConf can be
// applied automatically. In particular:
// - The MacAddr and MTU cannot be applied
// - The only routes created will match what's specified in VMIPConfig; VMRoutes will be ignored.
// - Only up to two namesevers can be supplied. If VMNameservers is has more than 2 entries, only
// the first two in the slice will be applied in the VM.
// - VMDomain, VMSearchDomains and VMResolverOptions will be ignored
// - Nameserver settings are also only set in /proc/net/pnp. Most applications will thus require
// /etc/resolv.conf to be a symlink to /proc/net/pnp in order to resolve names as expected.
func (c StaticNetworkConf) IPBootParam() string {
// See "ip=" section of kernel linked above for details on each field listed below.
// client-ip is really just the ip that will be assigned to the primary interface
clientIP := c.VMIPConfig.Address.IP.String()
// don't set nfs server IP
const serverIP = ""
// default gateway for the network; used to generate a corresponding route table entry
defaultGateway := c.VMIPConfig.Gateway.String()
// subnet mask used to generate a corresponding route table entry for the primary interface
// (must be provided in dotted decimal notation)
subnetMask := fmt.Sprintf("%d.%d.%d.%d",
c.VMIPConfig.Address.Mask[0],
c.VMIPConfig.Address.Mask[1],
c.VMIPConfig.Address.Mask[2],
c.VMIPConfig.Address.Mask[3],
)
// the "hostname" field actually just configures a hostname value for DHCP requests, thus no need to set it
const dhcpHostname = ""
// If blank, use the only network device present in the VM
device := c.VMIfName
// Don't do any autoconfiguration (i.e. DHCP, BOOTP, RARP)
const autoconfiguration = "off"
// up to two nameservers (if any were provided)
var nameservers [2]string
copy(nameservers[:], c.VMNameservers)
// TODO(sipsma) should we support configuring an NTP server?
const ntpServer = ""
return strings.Join([]string{
clientIP,
serverIP,
defaultGateway,
subnetMask,
dhcpHostname,
device,
autoconfiguration,
nameservers[0],
nameservers[1],
ntpServer,
}, ":")
}
// StaticNetworkConfFrom takes the result of a CNI invocation that conforms to the specification
// in this package's docstring and converts it to a StaticNetworkConf object that the caller
// can use to configure their VM with.
func StaticNetworkConfFrom(result types.Result, containerID string) (*StaticNetworkConf, error) {
currentResult, err := current.NewResultFromResult(result)
if err != nil {
return nil, fmt.Errorf("failed to parse cni result: %w", err)
}
// As specified in the vmconf package docstring, we are looking for an interface who's
// sandbox ID is the "containerID" (really "vmID" in this case) that CNI was invoked
// with. That interface holds the configuration that should be applied to the VM's
// internal network device.
vmIfaceSandbox := containerID
vmIface, tapIface, err := internal.VMTapPair(currentResult, vmIfaceSandbox)
if err != nil {
return nil, err
}
// find the IP associated with the VM iface
vmIPs := internal.InterfaceIPs(currentResult, vmIface.Name, vmIface.Sandbox)
if len(vmIPs) != 1 {
return nil, fmt.Errorf("expected to find 1 IP for vm interface %q, but instead found %+v",
vmIface.Name, vmIPs)
}
vmIP := vmIPs[0]
netNS, err := ns.GetNS(tapIface.Sandbox)
if err != nil {
return nil, fmt.Errorf("failed to find netns at path %q: %w", tapIface.Sandbox, err)
}
tapMTU, err := mtuOf(tapIface.Name, netNS, internal.DefaultNetlinkOps())
if err != nil {
return nil, err
}
return &StaticNetworkConf{
TapName: tapIface.Name,
NetNSPath: tapIface.Sandbox,
VMMacAddr: vmIface.Mac,
VMMTU: tapMTU,
VMIPConfig: vmIP,
VMRoutes: currentResult.Routes,
VMNameservers: currentResult.DNS.Nameservers,
VMDomain: currentResult.DNS.Domain,
VMSearchDomains: currentResult.DNS.Search,
VMResolverOptions: currentResult.DNS.Options,
}, nil
}
func mtuOf(ifaceName string, netNS ns.NetNS, netlinkOps internal.NetlinkOps) (int, error) {
var mtu int
err := netNS.Do(func(_ ns.NetNS) error {
link, err := netlinkOps.GetLink(ifaceName)
if err != nil {
return fmt.Errorf("failed to find device %q in netns %q: %w",
ifaceName, netNS.Path(), err)
}
mtu = link.Attrs().MTU
return nil
})
if err != nil {
return 0, fmt.Errorf("failed to find MTU: %w", err)
}
return mtu, nil
}