Skip to content

Commit 7457f68

Browse files
authored
Merge pull request #133 from rancher-sandbox/vde
Add networking options for vde_vmnet support
2 parents 64a63b4 + 3ec2ee1 commit 7457f68

File tree

13 files changed

+121
-22
lines changed

13 files changed

+121
-22
lines changed

cmd/limactl/start.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ func loadOrCreateInstance(clicontext *cli.Context) (*store.Instance, error) {
114114
} else {
115115
logrus.Info("Terminal is not available, proceeding without opening an editor")
116116
}
117-
y, err := limayaml.Load(yBytes)
117+
// limayaml.Load() needs to pass the store file path to limayaml.FillDefault() to calculate default MAC addresses
118+
filePath := filepath.Join(instDir, filenames.LimaYAML)
119+
y, err := limayaml.Load(yBytes, filePath)
118120
if err != nil {
119121
return nil, err
120122
}
@@ -131,7 +133,7 @@ func loadOrCreateInstance(clicontext *cli.Context) (*store.Instance, error) {
131133
if err := os.MkdirAll(instDir, 0700); err != nil {
132134
return nil, err
133135
}
134-
if err := os.WriteFile(filepath.Join(instDir, filenames.LimaYAML), yBytes, 0644); err != nil {
136+
if err := os.WriteFile(filePath, yBytes, 0644); err != nil {
135137
return nil, err
136138
}
137139
return store.Inspect(instName)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: 2
2+
ethernets:
3+
{{- range $nw := .Networks}}
4+
{{$nw.Name}}:
5+
match:
6+
macaddress: '{{$nw.MACAddress}}'
7+
dhcp4: true
8+
set-name: {{$nw.Name}}
9+
{{- end }}

pkg/cidata/cidata.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import (
1515
"github.com/AkihiroSuda/lima/pkg/iso9660util"
1616
"github.com/AkihiroSuda/lima/pkg/limayaml"
1717
"github.com/AkihiroSuda/lima/pkg/localpathutil"
18+
"github.com/AkihiroSuda/lima/pkg/qemu"
1819
"github.com/AkihiroSuda/lima/pkg/sshutil"
20+
"github.com/AkihiroSuda/lima/pkg/store/filenames"
1921
"github.com/opencontainers/go-digest"
2022
"github.com/pkg/errors"
2123
"github.com/sirupsen/logrus"
@@ -32,8 +34,8 @@ var (
3234
}
3335
)
3436

35-
func GenerateISO9660(isoPath, name string, y *limayaml.LimaYAML) error {
36-
if err := limayaml.ValidateRaw(*y); err != nil {
37+
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML) error {
38+
if err := limayaml.Validate(*y); err != nil {
3739
return err
3840
}
3941
u, err := user.Current()
@@ -70,6 +72,11 @@ func GenerateISO9660(isoPath, name string, y *limayaml.LimaYAML) error {
7072
args.Mounts = append(args.Mounts, expanded)
7173
}
7274

75+
args.Networks = append(args.Networks, Network{MACAddress: qemu.SlirpMACAddress, Name: "eth0"})
76+
for _, vde := range y.Network.VDE {
77+
args.Networks = append(args.Networks, Network{MACAddress: vde.MACAddress, Name: vde.Name})
78+
}
79+
7380
if err := ValidateTemplateArgs(args); err != nil {
7481
return err
7582
}
@@ -147,7 +154,7 @@ func GenerateISO9660(isoPath, name string, y *limayaml.LimaYAML) error {
147154
})
148155
}
149156

150-
return iso9660util.Write(isoPath, "cidata", layout)
157+
return iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout)
151158
}
152159

153160
func GuestAgentBinary(arch string) (io.ReadCloser, error) {

pkg/cidata/template.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@ type Containerd struct {
2323
System bool
2424
User bool
2525
}
26+
type Network struct {
27+
MACAddress string
28+
Name string
29+
}
2630
type TemplateArgs struct {
2731
Name string // instance name
2832
User string // user name
2933
UID int
3034
SSHPubKeys []string
3135
Mounts []string // abs path, accessible by the User
3236
Containerd Containerd
37+
Networks []Network
3338
}
3439

3540
func ValidateTemplateArgs(args TemplateArgs) error {

pkg/limayaml/default.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,20 @@ memory: "4GiB"
3131
# Default: "100GiB"
3232
disk: "100GiB"
3333

34+
network:
35+
# The instance can get routable IP addresses from the vmnet framework using
36+
# https://github.com/AkihiroSuda/vde_vmnet. Both vde_switch and vde_vmnet
37+
# daemons must be running before the instance is started. The interface type
38+
# (host, shared, or bridged) is configured in vde_vmnet and not lima.
39+
vde:
40+
# url points to the vde_switch socket directory
41+
# - url: "/var/run/vde.ctl"
42+
# # MAC address of the instance; lima will pick one based on the instance name,
43+
# # so DHCP assigned ip addresses should remain constant over instance restarts.
44+
# macAddress: ""
45+
# # Interface name, defaults to "vde0", "vde1", etc.
46+
# name: ""
47+
3448
# Expose host directories to the guest
3549
# Default: none
3650
mounts:

pkg/limayaml/defaults.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package limayaml
22

33
import (
4+
"crypto/sha256"
45
"fmt"
6+
"net"
57
"runtime"
8+
"strconv"
69

710
"github.com/AkihiroSuda/lima/pkg/guestagent/api"
811
)
912

10-
func FillDefault(y *LimaYAML) {
13+
func FillDefault(y *LimaYAML, filePath string) {
1114
y.Arch = resolveArch(y.Arch)
1215
for i := range y.Images {
1316
img := &y.Images[i]
@@ -55,6 +58,21 @@ func FillDefault(y *LimaYAML) {
5558
FillPortForwardDefaults(&y.PortForwards[i])
5659
// After defaults processing the singular HostPort and GuestPort values should not be used again.
5760
}
61+
for i := range y.Network.VDE {
62+
vde := &y.Network.VDE[i]
63+
if vde.MACAddress == "" {
64+
// every interface in every limayaml file must get its own unique MAC address
65+
uniqueID := fmt.Sprintf("%s#%d", filePath, i)
66+
sha := sha256.Sum256([]byte(uniqueID))
67+
// According to https://gitlab.com/wireshark/wireshark/-/blob/master/manuf
68+
// no well-known MAC addresses start with 0x22.
69+
hw := append(net.HardwareAddr{0x22}, sha[0:5]...)
70+
vde.MACAddress = hw.String()
71+
}
72+
if vde.Name == "" {
73+
vde.Name = "vde" + strconv.Itoa(i)
74+
}
75+
}
5876
}
5977

6078
func FillPortForwardDefaults(rule *PortForward) {

pkg/limayaml/limayaml.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type LimaYAML struct {
2020
Containerd Containerd `yaml:"containerd,omitempty"`
2121
Probes []Probe `yaml:"probes,omitempty"`
2222
PortForwards []PortForward `yaml:"portForwards,omitempty"`
23+
Network Network `yaml:"network,omitempty"`
2324
}
2425

2526
type Arch = string
@@ -105,3 +106,12 @@ type PortForward struct {
105106
Proto Proto `yaml:"proto,omitempty"`
106107
Ignore bool `yaml:"ignore,omitempty"`
107108
}
109+
110+
type Network struct {
111+
VDE []VDE `yaml:"vde,omitempty"`
112+
}
113+
type VDE struct {
114+
URL string `yaml:"url,omitempty"`
115+
MACAddress string `yaml:"macAddress,omitempty"`
116+
Name string `yaml:"name,omitempty"`
117+
}

pkg/limayaml/load.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import (
77
// Load loads the yaml and fulfills unspecified fields with the default values.
88
//
99
// Load does not validate. Use Validate for validation.
10-
func Load(b []byte) (*LimaYAML, error) {
10+
func Load(b []byte, filePath string) (*LimaYAML, error) {
1111
var y LimaYAML
1212
if err := yaml.Unmarshal(b, &y); err != nil {
1313
return nil, err
1414
}
15-
FillDefault(&y)
15+
FillDefault(&y, filePath)
1616
return &y, nil
1717
}

pkg/limayaml/template_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
func TestDefaultTemplateYAML(t *testing.T) {
10-
_, err := Load(DefaultTemplate)
10+
_, err := Load(DefaultTemplate, "does-not-exist")
1111
assert.NilError(t, err)
1212
// Do not call Validate(y) here, as it fails when `~/lima` is missing
1313
}

pkg/limayaml/validate.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package limayaml
22

33
import (
44
"fmt"
5+
"net"
56
"os"
67
"os/user"
78
"path/filepath"
@@ -13,11 +14,6 @@ import (
1314
)
1415

1516
func Validate(y LimaYAML) error {
16-
FillDefault(&y)
17-
return ValidateRaw(y)
18-
}
19-
20-
func ValidateRaw(y LimaYAML) error {
2117
switch y.Arch {
2218
case X8664, AARCH64:
2319
default:
@@ -154,7 +150,7 @@ func ValidateRaw(y LimaYAML) error {
154150
if rule.HostPortRange[0] > rule.HostPortRange[1] {
155151
return errors.Errorf("field `%s.hostPortRange[1]` must be greater than or equal to field `%s.hostPortRange[0]`", field, field)
156152
}
157-
if rule.GuestPortRange[1] - rule.GuestPortRange[0] != rule.HostPortRange[1] - rule.HostPortRange[0] {
153+
if rule.GuestPortRange[1]-rule.GuestPortRange[0] != rule.HostPortRange[1]-rule.HostPortRange[0] {
158154
return errors.Errorf("field `%s.hostPortRange` must specify the same number of ports as field `%s.guestPortRange`", field, field)
159155
}
160156
if rule.Proto != TCP {
@@ -163,6 +159,38 @@ func ValidateRaw(y LimaYAML) error {
163159
// Not validating that the various GuestPortRanges and HostPortRanges are not overlapping. Rules will be
164160
// processed sequentially and the first matching rule for a guest port determines forwarding behavior.
165161
}
162+
for i, vde := range y.Network.VDE {
163+
field := fmt.Sprintf("network.vde[%d]", i)
164+
if vde.URL == "" {
165+
return errors.Errorf("field `%s.url` must not be empty", field)
166+
}
167+
// The field is called VDE.URL in anticipation of QEMU upgrading VDE2 to VDEplug4,
168+
// but right now the only valid value is a path to the vde_switch socket directory.
169+
fi, err := os.Stat(vde.URL)
170+
if err != nil {
171+
return errors.Wrapf(err, "field `%s.url` %q failed stat", field, vde.URL)
172+
}
173+
if !fi.IsDir() {
174+
return errors.Wrapf(err, "field `%s.url` %q is not a directory", field, vde.URL)
175+
}
176+
ctlSocket := filepath.Join(vde.URL, "ctl")
177+
fi, err = os.Stat(ctlSocket)
178+
if err != nil {
179+
return errors.Wrapf(err, "field `%s.url` control socket %q failed stat", field, ctlSocket)
180+
}
181+
if fi.Mode()&os.ModeSocket == 0 {
182+
return errors.Errorf("field `%s.url` file %q is not a UNIX socket", field, ctlSocket)
183+
}
184+
if vde.MACAddress != "" {
185+
hw, err := net.ParseMAC(vde.MACAddress)
186+
if err != nil {
187+
return errors.Wrap(err, "field `vmnet.mac` invalid")
188+
}
189+
if len(hw) != 6 {
190+
return errors.Errorf("field `%s.macAddress` must be a 48 bit (6 bytes) MAC address; actual length of %q is %d bytes", field, vde.MACAddress, len(hw))
191+
}
192+
}
193+
}
166194
return nil
167195
}
168196

0 commit comments

Comments
 (0)