Skip to content

Commit 07e6823

Browse files
committed
Deprecate network.vde and create networks fields in lima.yaml
Signed-off-by: Jan Dubois <[email protected]>
1 parent 4558b5a commit 07e6823

File tree

10 files changed

+176
-129
lines changed

10 files changed

+176
-129
lines changed

pkg/cidata/cidata.TEMPLATE.d/network-config

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
version: 2
22
ethernets:
33
{{- range $nw := .Networks}}
4-
{{$nw.Name}}:
4+
{{$nw.Interface}}:
55
match:
66
macaddress: '{{$nw.MACAddress}}'
77
dhcp4: true
8-
set-name: {{$nw.Name}}
9-
{{- if and (eq $nw.Name $.SlirpNICName) (gt (len $.DNSAddresses) 0) }}
8+
set-name: {{$nw.Interface}}
9+
{{- if and (eq $nw.Interface $.SlirpNICName) (gt (len $.DNSAddresses) 0) }}
1010
nameservers:
1111
addresses:
1212
{{- range $ns := $.DNSAddresses }}

pkg/cidata/cidata.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML) error {
7777
}
7878

7979
slirpMACAddress := limayaml.MACAddress(instDir)
80-
args.Networks = append(args.Networks, Network{MACAddress: slirpMACAddress, Name: qemuconst.SlirpNICName})
81-
for _, vde := range y.Network.VDE {
82-
args.Networks = append(args.Networks, Network{MACAddress: vde.MACAddress, Name: vde.Name})
80+
args.Networks = append(args.Networks, Network{MACAddress: slirpMACAddress, Interface: qemuconst.SlirpNICName})
81+
for _, nw := range y.Networks {
82+
args.Networks = append(args.Networks, Network{MACAddress: nw.MACAddress, Interface: nw.Interface})
8383
}
8484

8585
if len(y.DNS) > 0 {

pkg/cidata/template.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Containerd struct {
2525
}
2626
type Network struct {
2727
MACAddress string
28-
Name string
28+
Interface string
2929
}
3030
type TemplateArgs struct {
3131
Name string // instance name

pkg/limayaml/defaults.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,27 @@ func FillDefault(y *LimaYAML, filePath string) {
7272
FillPortForwardDefaults(&y.PortForwards[i])
7373
// After defaults processing the singular HostPort and GuestPort values should not be used again.
7474
}
75-
for i := range y.Network.VDE {
76-
vde := &y.Network.VDE[i]
77-
if vde.MACAddress == "" {
75+
76+
if len(y.Network.VDEDeprecated) > 0 && len(y.Networks) == 0 {
77+
for _, vde := range y.Network.VDEDeprecated {
78+
network := Network{
79+
Interface: vde.Name,
80+
MACAddress: vde.MACAddress,
81+
SwitchPort: vde.SwitchPort,
82+
VNL: vde.VNL,
83+
}
84+
y.Networks = append(y.Networks, network)
85+
}
86+
y.Network.migrated = true
87+
}
88+
for i := range y.Networks {
89+
nw := &y.Networks[i]
90+
if nw.MACAddress == "" {
7891
// every interface in every limayaml file must get its own unique MAC address
79-
vde.MACAddress = MACAddress(fmt.Sprintf("%s#%d", filePath, i))
92+
nw.MACAddress = MACAddress(fmt.Sprintf("%s#%d", filePath, i))
8093
}
81-
if vde.Name == "" {
82-
vde.Name = "vde" + strconv.Itoa(i)
94+
if nw.Interface == "" {
95+
nw.Interface = "lima" + strconv.Itoa(i)
8396
}
8497
}
8598
}

pkg/limayaml/limayaml.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ 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"`
24-
Env map[string]*string `yaml:"env,omitempty"` // EXPERIMENAL, see default.yaml
23+
Networks []Network `yaml:"networks,omitempty"`
24+
Network NetworkDeprecated `yaml:"network,omitempty"` // DEPRECATED, use `networks` instead
25+
Env map[string]*string `yaml:"env,omitempty"` // EXPERIMENAL, see default.yaml
2526
DNS []net.IP `yaml:"dns,omitempty"`
2627
}
2728

@@ -110,11 +111,28 @@ type PortForward struct {
110111
}
111112

112113
type Network struct {
113-
VDE []VDE `yaml:"vde,omitempty"`
114-
}
115-
type VDE struct {
114+
// `Lima` and `VNL` are mutually exclusive; exactly one is required
115+
Lima string `yaml:"lima,omitempty"`
116116
// VNL is a Virtual Network Locator (https://github.com/rd235/vdeplug4/commit/089984200f447abb0e825eb45548b781ba1ebccd).
117117
// On macOS, only VDE2-compatible form (optionally with vde:// prefix) is supported.
118+
VNL string `yaml:"vnl,omitempty"`
119+
SwitchPort uint16 `yaml:"switchPort,omitempty"` // VDE Switch port, not TCP/UDP port (only used by VDE networking)
120+
MACAddress string `yaml:"macAddress,omitempty"`
121+
Interface string `yaml:"interface,omitempty"`
122+
}
123+
124+
// DEPRECATED types below
125+
126+
// Types have been renamed to turn all references to the old names into compiler errors,
127+
// and to avoid accidental usage in new code.
128+
129+
type NetworkDeprecated struct {
130+
VDEDeprecated []VDEDeprecated `yaml:"vde,omitempty"`
131+
// migrate will be true when `network.VDE` has been copied to `networks` by FillDefaults()
132+
migrated bool
133+
}
134+
135+
type VDEDeprecated struct {
118136
VNL string `yaml:"vnl,omitempty"`
119137
SwitchPort uint16 `yaml:"switchPort,omitempty"` // VDE Switch port, not TCP/UDP port
120138
MACAddress string `yaml:"macAddress,omitempty"`

pkg/limayaml/validate.go

Lines changed: 67 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -160,86 +160,96 @@ func Validate(y LimaYAML) error {
160160
// processed sequentially and the first matching rule for a guest port determines forwarding behavior.
161161
}
162162

163-
if err := validateNetwork(y.Network); err != nil {
163+
if err := validateNetwork(y); err != nil {
164164
return err
165165
}
166166

167167
return nil
168168
}
169169

170-
func validateNetwork(yNetwork Network) error {
171-
networkName := make(map[string]int)
172-
for i, vde := range yNetwork.VDE {
173-
field := fmt.Sprintf("network.vde[%d]", i)
174-
if vde.VNL == "" {
175-
return fmt.Errorf("field `%s.vnl` must not be empty", field)
170+
func validateNetwork(y LimaYAML) error {
171+
if len(y.Network.VDEDeprecated) > 0 {
172+
if y.Network.migrated {
173+
logrus.Warnf("field `network.VDE` is deprecated; please use `networks` instead")
174+
} else {
175+
return fmt.Errorf("you cannot use deprecated field `network.VDE` together with replacement field `networks`")
176176
}
177-
// The field is called VDE.VNL in anticipation of QEMU upgrading VDE2 to VDEplug4,
178-
// but right now the only valid value on macOS is a path to the vde_switch socket directory,
179-
// optionally with vde:// prefix.
180-
// TODO: use networks.LimaSchema after solving circular dependency
181-
if strings.HasPrefix(vde.VNL, "lima://") {
182-
// TODO: validate network names? Problem is we can't use "networks" or "store" packages here...
183-
//name := strings.TrimPrefix(vde.VNL, networks.LimaScheme)
184-
//if _, ok := networkConfig.Networks[name]; !ok {
185-
// return fmt.Errorf("field `%s.vnl` references undefined network %q", field, name)
186-
//}
187-
} else if !strings.Contains(vde.VNL, "://") || strings.HasPrefix(vde.VNL, "vde://") {
188-
vdeSwitch := strings.TrimPrefix(vde.VNL, "vde://")
189-
if fi, err := os.Stat(vdeSwitch); err != nil {
190-
// negligible when the instance is stopped
191-
logrus.WithError(err).Debugf("field `%s.vnl` %q failed stat", field, vdeSwitch)
192-
} else {
193-
if fi.IsDir() {
194-
/* Switch mode (vdeSwitch is dir, port != 65535) */
195-
ctlSocket := filepath.Join(vdeSwitch, "ctl")
196-
// ErrNotExist during os.Stat(ctlSocket) can be ignored. ctlSocket does not need to exist until actually starting the VM
197-
if fi, err = os.Stat(ctlSocket); err == nil {
177+
}
178+
interfaceName := make(map[string]int)
179+
for i, nw := range y.Networks {
180+
field := fmt.Sprintf("networks[%d]", i)
181+
if nw.Lima != "" {
182+
if nw.VNL != "" {
183+
return fmt.Errorf("field `%s.lima` and field `%s.vnl` are mutually exclusive", field, field)
184+
}
185+
if nw.SwitchPort != 0 {
186+
return fmt.Errorf("field `%s.switchPort` cannot be used with field `%s.lima`", field, field)
187+
}
188+
// TODO: validate network name (we can't use networks and store packages right now)
189+
} else {
190+
if nw.VNL == "" {
191+
return fmt.Errorf("field `%s.lima` or field `%s.vnl` must be set", field, field)
192+
}
193+
// The field is called VDE.VNL in anticipation of QEMU upgrading VDE2 to VDEplug4,
194+
// but right now the only valid value on macOS is a path to the vde_switch socket directory,
195+
// optionally with vde:// prefix.
196+
if !strings.Contains(nw.VNL, "://") || strings.HasPrefix(nw.VNL, "vde://") {
197+
vdeSwitch := strings.TrimPrefix(nw.VNL, "vde://")
198+
if fi, err := os.Stat(vdeSwitch); err != nil {
199+
// negligible when the instance is stopped
200+
logrus.WithError(err).Debugf("field `%s.vnl` %q failed stat", field, vdeSwitch)
201+
} else {
202+
if fi.IsDir() {
203+
/* Switch mode (vdeSwitch is dir, port != 65535) */
204+
ctlSocket := filepath.Join(vdeSwitch, "ctl")
205+
// ErrNotExist during os.Stat(ctlSocket) can be ignored. ctlSocket does not need to exist until actually starting the VM
206+
if fi, err = os.Stat(ctlSocket); err == nil {
207+
if fi.Mode()&os.ModeSocket == 0 {
208+
return fmt.Errorf("field `%s.vnl` file %q is not a UNIX socket", field, ctlSocket)
209+
}
210+
}
211+
if nw.SwitchPort == 65535 {
212+
return fmt.Errorf("field `%s.vnl` points to a non-PTP switch, so the port number must not be 65535", field)
213+
}
214+
} else {
215+
/* PTP mode (vdeSwitch is socket, port == 65535) */
198216
if fi.Mode()&os.ModeSocket == 0 {
199-
return fmt.Errorf("field `%s.vnl` file %q is not a UNIX socket", field, ctlSocket)
217+
return fmt.Errorf("field `%s.vnl` %q is not a directory nor a UNIX socket", field, vdeSwitch)
218+
}
219+
if nw.SwitchPort != 65535 {
220+
return fmt.Errorf("field `%s.vnl` points to a PTP (switchless) socket %q, so the port number has to be 65535 (got %d)",
221+
field, vdeSwitch, nw.SwitchPort)
200222
}
201-
}
202-
if vde.SwitchPort == 65535 {
203-
return fmt.Errorf("field `%s.vnl` points to a non-PTP switch, so the port number must not be 65535", field)
204-
}
205-
} else {
206-
/* PTP mode (vdeSwitch is socket, port == 65535) */
207-
if fi.Mode()&os.ModeSocket == 0 {
208-
return fmt.Errorf("field `%s.vnl` %q is not a directory nor a UNIX socket", field, vdeSwitch)
209-
}
210-
if vde.SwitchPort != 65535 {
211-
return fmt.Errorf("field `%s.vnl` points to a PTP (switchless) socket %q, so the port number has to be 65535 (got %d)",
212-
field, vdeSwitch, vde.SwitchPort)
213223
}
214224
}
225+
} else if runtime.GOOS != "linux" {
226+
logrus.Warnf("field `%s.vnl` is unlikely to work for %s (unless libvdeplug4 has been ported to %s and is installed)",
227+
field, runtime.GOOS, runtime.GOOS)
215228
}
216-
} else if runtime.GOOS != "linux" {
217-
logrus.Warnf("field `%s.vnl` is unlikely to work for %s (unless libvdeplug4 has been ported to %s and is installed)",
218-
field, runtime.GOOS, runtime.GOOS)
219229
}
220-
if vde.MACAddress != "" {
221-
hw, err := net.ParseMAC(vde.MACAddress)
230+
if nw.MACAddress != "" {
231+
hw, err := net.ParseMAC(nw.MACAddress)
222232
if err != nil {
223233
return fmt.Errorf("field `vmnet.mac` invalid: %w", err)
224234
}
225235
if len(hw) != 6 {
226-
return fmt.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))
236+
return fmt.Errorf("field `%s.macAddress` must be a 48 bit (6 bytes) MAC address; actual length of %q is %d bytes", field, nw.MACAddress, len(hw))
227237
}
228238
}
229-
// FillDefault() will make sure that vde.Name is not the empty string
230-
if len(vde.Name) >= 16 {
231-
return fmt.Errorf("field `%s.name` must be less than 16 bytes, but is %d bytes: %q", field, len(vde.Name), vde.Name)
239+
// FillDefault() will make sure that nw.Interface is not the empty string
240+
if len(nw.Interface) >= 16 {
241+
return fmt.Errorf("field `%s.interface` must be less than 16 bytes, but is %d bytes: %q", field, len(nw.Interface), nw.Interface)
232242
}
233-
if strings.ContainsAny(vde.Name, " \t\n/") {
234-
return fmt.Errorf("field `%s.name` must not contain whitespace or slashes", field)
243+
if strings.ContainsAny(nw.Interface, " \t\n/") {
244+
return fmt.Errorf("field `%s.interface` must not contain whitespace or slashes", field)
235245
}
236-
if vde.Name == qemuconst.SlirpNICName {
237-
return fmt.Errorf("field `%s.name` must not be set to %q because it is reserved for slirp", field, qemuconst.SlirpNICName)
246+
if nw.Interface == qemuconst.SlirpNICName {
247+
return fmt.Errorf("field `%s.interface` must not be set to %q because it is reserved for slirp", field, qemuconst.SlirpNICName)
238248
}
239-
if prev, ok := networkName[vde.Name]; ok {
240-
return fmt.Errorf("field `%s.name` value %q has already been used by field `network.vde[%d].name`", field, vde.Name, prev)
249+
if prev, ok := interfaceName[nw.Interface]; ok {
250+
return fmt.Errorf("field `%s.interface` value %q has already been used by field `network.vde[%d].name`", field, nw.Interface, prev)
241251
}
242-
networkName[vde.Name] = i
252+
interfaceName[nw.Interface] = i
243253
}
244254
return nil
245255
}

pkg/networks/config.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"gopkg.in/yaml.v2"
88
"os"
99
"path/filepath"
10-
"strings"
1110
"sync"
1211

1312
"github.com/lima-vm/lima/pkg/store"
@@ -66,16 +65,12 @@ func Config() (NetworksConfig, error) {
6665
}
6766

6867
func VDESock(name string) (string, error) {
69-
if strings.HasPrefix(name, LimaScheme) {
70-
load()
71-
if cache.err != nil {
72-
return "", cache.err
73-
}
74-
name = strings.TrimPrefix(name, LimaScheme)
75-
if err := cache.config.Check(name); err != nil {
76-
return "", err
77-
}
78-
return cache.config.VDESock(name), nil
68+
load()
69+
if cache.err != nil {
70+
return "", cache.err
71+
}
72+
if err := cache.config.Check(name); err != nil {
73+
return "", err
7974
}
80-
return name, nil
75+
return cache.config.VDESock(name), nil
8176
}

pkg/networks/reconcile.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ func Reconcile(ctx context.Context, newInst string) error {
3434
if instance.Status != store.StatusRunning && instName != newInst {
3535
continue
3636
}
37-
for _, vde := range instance.Network.VDE {
38-
if strings.HasPrefix(vde.VNL, LimaScheme) {
39-
name := strings.TrimPrefix(vde.VNL, LimaScheme)
40-
if _, ok := config.Networks[name]; !ok {
41-
logrus.Errorf("network %q (used by instance %q) is missing from networks.yaml", name, instName)
42-
continue
43-
}
44-
activeNetwork[name] = true
37+
for _, nw := range instance.Networks {
38+
if nw.Lima == "" {
39+
continue
4540
}
41+
if _, ok := config.Networks[nw.Lima]; !ok {
42+
logrus.Errorf("network %q (used by instance %q) is missing from networks.yaml", nw.Lima, instName)
43+
continue
44+
}
45+
activeNetwork[nw.Lima] = true
4646
}
4747
}
4848
for name := range config.Networks {

pkg/qemu/qemu.go

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -260,36 +260,47 @@ func Cmdline(cfg Config) (string, []string, error) {
260260
args = append(args, "-netdev", fmt.Sprintf("user,id=net0,net=%s,dhcpstart=%s,hostfwd=tcp:127.0.0.1:%d-:22",
261261
qemuconst.SlirpNetwork, qemuconst.SlirpIPAddress, y.SSH.LocalPort))
262262
args = append(args, "-device", "virtio-net-pci,netdev=net0,mac="+limayaml.MACAddress(cfg.InstanceDir))
263-
if len(y.Network.VDE) > 0 && !strings.Contains(string(features.NetdevHelp), "vde") {
263+
usingVDE := false
264+
for _, nw := range y.Networks {
265+
if nw.VNL != "" {
266+
usingVDE = true
267+
}
268+
}
269+
if usingVDE && !strings.Contains(string(features.NetdevHelp), "vde") {
264270
return "", nil, fmt.Errorf("netdev \"vde\" is not supported by %s ( Hint: recompile QEMU with `configure --enable-vde` )", exe)
265271
}
266-
for i, vde := range y.Network.VDE {
267-
// Replace internal lima:// network name with proper socket path
268-
vdeSock, err := networks.VDESock(vde.VNL)
269-
if err != nil {
270-
return "", nil, err
271-
}
272-
// VDE4 accepts VNL like vde:///var/run/vde.ctl as well as file path like /var/run/vde.ctl .
273-
// VDE2 only accepts the latter form.
274-
// VDE2 supports macOS but VDE4 does not yet, so we trim vde:// prefix here for VDE2 compatibility.
275-
vdeSock = strings.TrimPrefix(vdeSock, "vde://")
276-
if !strings.Contains(vdeSock, "://") {
277-
if _, err := os.Stat(vdeSock); err != nil {
278-
return "", nil, fmt.Errorf("cannot use VNL %q: %w", vde.VNL, err)
279-
}
280-
// vdeSock is a directory, unless vde.SwitchPort == 65535 (PTP)
281-
actualSocket := filepath.Join(vdeSock, "ctl")
282-
if vde.SwitchPort == 65535 { // PTP
283-
actualSocket = vdeSock
272+
for i, nw := range y.Networks {
273+
var vdeSock string
274+
if nw.Lima != "" {
275+
vdeSock, err = networks.VDESock(nw.Lima)
276+
if err != nil {
277+
return "", nil, err
284278
}
285-
if st, err := os.Stat(actualSocket); err != nil {
286-
return "", nil, fmt.Errorf("cannot use VNL %q: failed to stat %q: %w", vde.VNL, actualSocket, err)
287-
} else if st.Mode()&fs.ModeSocket == 0 {
288-
return "", nil, fmt.Errorf("cannot use VNL %q: %q is not a socket: %w", vde.VNL, actualSocket, err)
279+
// TODO: should we also validate that the socket exists, or do we rely on the
280+
// networks reconciler to throw an error when the network cannot start?
281+
} else {
282+
// VDE4 accepts VNL like vde:///var/run/vde.ctl as well as file path like /var/run/vde.ctl .
283+
// VDE2 only accepts the latter form.
284+
// VDE2 supports macOS but VDE4 does not yet, so we trim vde:// prefix here for VDE2 compatibility.
285+
vdeSock = strings.TrimPrefix(nw.VNL, "vde://")
286+
if !strings.Contains(vdeSock, "://") {
287+
if _, err := os.Stat(vdeSock); err != nil {
288+
return "", nil, fmt.Errorf("cannot use VNL %q: %w", nw.VNL, err)
289+
}
290+
// vdeSock is a directory, unless vde.SwitchPort == 65535 (PTP)
291+
actualSocket := filepath.Join(vdeSock, "ctl")
292+
if nw.SwitchPort == 65535 { // PTP
293+
actualSocket = vdeSock
294+
}
295+
if st, err := os.Stat(actualSocket); err != nil {
296+
return "", nil, fmt.Errorf("cannot use VNL %q: failed to stat %q: %w", nw.VNL, actualSocket, err)
297+
} else if st.Mode()&fs.ModeSocket == 0 {
298+
return "", nil, fmt.Errorf("cannot use VNL %q: %q is not a socket: %w", nw.VNL, actualSocket, err)
299+
}
289300
}
290301
}
291302
args = append(args, "-netdev", fmt.Sprintf("vde,id=net%d,sock=%s", i+1, vdeSock))
292-
args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, vde.MACAddress))
303+
args = append(args, "-device", fmt.Sprintf("virtio-net-pci,netdev=net%d,mac=%s", i+1, nw.MACAddress))
293304
}
294305

295306
// virtio-rng-pci accelerates starting up the OS, according to https://wiki.gentoo.org/wiki/QEMU/Options

0 commit comments

Comments
 (0)