Skip to content

Commit 3ec2ee1

Browse files
committed
Support multiple VDE interfaces; let the user override the default name
Signed-off-by: Jan Dubois <[email protected]>
1 parent fd713d1 commit 3ec2ee1

File tree

12 files changed

+84
-74
lines changed

12 files changed

+84
-74
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: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
version: 2
22
ethernets:
3-
{{- range $i, $addr := .MACAddresses}}
4-
eth{{$i}}:
3+
{{- range $nw := .Networks}}
4+
{{$nw.Name}}:
55
match:
6-
macaddress: '{{$addr}}'
6+
macaddress: '{{$nw.MACAddress}}'
77
dhcp4: true
8-
set-name: eth{{$i}}
8+
set-name: {{$nw.Name}}
99
{{- end }}

pkg/cidata/cidata.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ var (
3535
)
3636

3737
func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML) error {
38-
if err := limayaml.ValidateRaw(*y); err != nil {
38+
if err := limayaml.Validate(*y); err != nil {
3939
return err
4040
}
4141
u, err := user.Current()
@@ -72,9 +72,9 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML) error {
7272
args.Mounts = append(args.Mounts, expanded)
7373
}
7474

75-
args.MACAddresses = append(args.MACAddresses, qemu.SlirpMACAddress)
76-
if y.Network.VDE.URL != "" {
77-
args.MACAddresses = append(args.MACAddresses, qemu.MACAddress(instDir, y))
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})
7878
}
7979

8080
if err := ValidateTemplateArgs(args); err != nil {

pkg/cidata/template.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +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 {
27-
Name string // instance name
28-
User string // user name
29-
UID int
30-
SSHPubKeys []string
31-
Mounts []string // abs path, accessible by the User
32-
Containerd Containerd
33-
MACAddresses []string
31+
Name string // instance name
32+
User string // user name
33+
UID int
34+
SSHPubKeys []string
35+
Mounts []string // abs path, accessible by the User
36+
Containerd Containerd
37+
Networks []Network
3438
}
3539

3640
func ValidateTemplateArgs(args TemplateArgs) error {

pkg/limayaml/default.yaml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,18 @@ memory: "4GiB"
3232
disk: "100GiB"
3333

3434
network:
35-
# The instance can get a routable IP address from the vmnet framework using
35+
# The instance can get routable IP addresses from the vmnet framework using
3636
# https://github.com/AkihiroSuda/vde_vmnet. Both vde_switch and vde_vmnet
3737
# daemons must be running before the instance is started. The interface type
3838
# (host, shared, or bridged) is configured in vde_vmnet and not lima.
3939
vde:
40-
# vde_switch socket directory, normally /var/run/vde.ctl
41-
# Default: "" (vmnet is not being used)
42-
url: ""
43-
# MAC address of the instance; lima will pick one based on the instance name,
44-
# so DHCP assigned ip addresses should remain constant over instance restarts.
45-
macAddress: ""
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: ""
4647

4748
# Expose host directories to the guest
4849
# Default: none

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ type PortForward struct {
108108
}
109109

110110
type Network struct {
111-
VDE VDE `yaml:"vde,omitempty"`
111+
VDE []VDE `yaml:"vde,omitempty"`
112112
}
113113
type VDE struct {
114114
URL string `yaml:"url,omitempty"`
115115
MACAddress string `yaml:"macAddress,omitempty"`
116+
Name string `yaml:"name,omitempty"`
116117
}

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: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ import (
1414
)
1515

1616
func Validate(y LimaYAML) error {
17-
FillDefault(&y)
18-
return ValidateRaw(y)
19-
}
20-
21-
func ValidateRaw(y LimaYAML) error {
2217
switch y.Arch {
2318
case X8664, AARCH64:
2419
default:
@@ -155,7 +150,7 @@ func ValidateRaw(y LimaYAML) error {
155150
if rule.HostPortRange[0] > rule.HostPortRange[1] {
156151
return errors.Errorf("field `%s.hostPortRange[1]` must be greater than or equal to field `%s.hostPortRange[0]`", field, field)
157152
}
158-
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] {
159154
return errors.Errorf("field `%s.hostPortRange` must specify the same number of ports as field `%s.guestPortRange`", field, field)
160155
}
161156
if rule.Proto != TCP {
@@ -164,33 +159,36 @@ func ValidateRaw(y LimaYAML) error {
164159
// Not validating that the various GuestPortRanges and HostPortRanges are not overlapping. Rules will be
165160
// processed sequentially and the first matching rule for a guest port determines forwarding behavior.
166161
}
167-
if y.Network.VDE.URL != "" {
168-
fieldRef := "field `network.vde.url`"
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+
}
169167
// The field is called VDE.URL in anticipation of QEMU upgrading VDE2 to VDEplug4,
170168
// but right now the only valid value is a path to the vde_switch socket directory.
171-
fi, err := os.Stat(y.Network.VDE.URL)
169+
fi, err := os.Stat(vde.URL)
172170
if err != nil {
173-
return errors.Wrapf(err, "%s %q failed stat", fieldRef, y.Network.VDE.URL)
171+
return errors.Wrapf(err, "field `%s.url` %q failed stat", field, vde.URL)
174172
}
175173
if !fi.IsDir() {
176-
return errors.Wrapf(err, "%s %q is not a directory", fieldRef, y.Network.VDE.URL)
174+
return errors.Wrapf(err, "field `%s.url` %q is not a directory", field, vde.URL)
177175
}
178-
ctlSocket := filepath.Join(y.Network.VDE.URL, "ctl")
176+
ctlSocket := filepath.Join(vde.URL, "ctl")
179177
fi, err = os.Stat(ctlSocket)
180178
if err != nil {
181-
return errors.Wrapf(err, "%s control socket %q failed stat", fieldRef, ctlSocket)
182-
}
183-
if fi.Mode() & os.ModeSocket == 0 {
184-
return errors.Errorf("%s file %q is not a UNIX socket", fieldRef, ctlSocket)
179+
return errors.Wrapf(err, "field `%s.url` control socket %q failed stat", field, ctlSocket)
185180
}
186-
}
187-
if y.Network.VDE.MACAddress != "" {
188-
hw, err := net.ParseMAC(y.Network.VDE.MACAddress)
189-
if err != nil {
190-
return errors.Wrap(err,"field `vmnet.mac` invalid")
181+
if fi.Mode()&os.ModeSocket == 0 {
182+
return errors.Errorf("field `%s.url` file %q is not a UNIX socket", field, ctlSocket)
191183
}
192-
if len(hw) != 6 {
193-
return errors.Errorf("field `vmnet.mac` must be a 48 bit (6 bytes) MAC address; actual length of %q is %d bytes", y.Network.VDE.MACAddress, len(hw))
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+
}
194192
}
195193
}
196194
return nil

0 commit comments

Comments
 (0)