Skip to content

Commit 59381dc

Browse files
committed
vmnet: Add vmnet package
The package manages the vmnet-helper[1] child process, providing connection to the vment network without running the guest as root. We will use vment-helper for the vfkit driver, which does not have a way to use shared network, when guests can access other guest in the network. We can use it later with the qemu driver as alternative to socket_vment. [1] https://github.com/nirs/vmnet-helper
1 parent 6b13687 commit 59381dc

File tree

1 file changed

+213
-0
lines changed

1 file changed

+213
-0
lines changed

pkg/drivers/vmnet/vmnet.go

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
//go:build darwin
2+
3+
/*
4+
Copyright 2024 The Kubernetes Authors All rights reserved.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
// package vmnet provides the helper process connecting virtual machines to the
20+
// vmnet network.
21+
package vmnet
22+
23+
import (
24+
"encoding/json"
25+
"errors"
26+
"fmt"
27+
"os"
28+
"os/exec"
29+
"path/filepath"
30+
"syscall"
31+
32+
pkgdrivers "k8s.io/minikube/pkg/drivers"
33+
34+
"github.com/docker/machine/libmachine/log"
35+
"github.com/docker/machine/libmachine/state"
36+
)
37+
38+
const (
39+
pidfileName = "vmnet-helper.pid"
40+
logfileName = "vmnet-helper.log"
41+
executablePath = "/opt/vmnet-helper/bin/vmnet-helper"
42+
)
43+
44+
// Helper manages the vmnet-helper process.
45+
type Helper struct {
46+
// The pidfile and log are stored here.
47+
MachineDir string
48+
49+
// InterfaceID is a random UUID for the vmnet interface. Using the same UUID
50+
// will obtain the same MAC address from vmnet.
51+
InterfaceID string
52+
53+
// Set when vmnet interface is started.
54+
macAddress string
55+
}
56+
57+
type interfaceInfo struct {
58+
MACAddress string `json:"vmnet_mac_address"`
59+
}
60+
61+
// HelperAvailable tells if vmnet-helper executable is installed and configured
62+
// correctly.
63+
func HelperAvailable() bool {
64+
version, err := exec.Command("sudo", "--non-interactive", executablePath, "--version").Output()
65+
if err != nil {
66+
log.Debugf("Failed to run vmnet-helper: %w", err)
67+
return false
68+
}
69+
log.Debugf("Using vmnet-helper version %q", version)
70+
return true
71+
}
72+
73+
// Start the vmnet-helper child process, creating the vmnet interface for the
74+
// machine. sock is a connected unix datagram socket to pass the helper child
75+
// process.
76+
func (h *Helper) Start(sock *os.File) error {
77+
cmd := exec.Command(
78+
"sudo",
79+
"--non-interactive",
80+
"--close-from", fmt.Sprintf("%d", sock.Fd()+1),
81+
executablePath,
82+
"--fd", fmt.Sprintf("%d", sock.Fd()),
83+
"--interface-id", h.InterfaceID,
84+
)
85+
86+
cmd.ExtraFiles = []*os.File{sock}
87+
88+
// Create vmnet-helper in a new process group so it is not harmed when
89+
// terminating the minikube process group.
90+
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
91+
92+
logfile, err := h.openLogfile()
93+
if err != nil {
94+
return fmt.Errorf("failed to open helper logfile: %w", err)
95+
}
96+
defer logfile.Close()
97+
cmd.Stderr = logfile
98+
99+
stdout, err := cmd.StdoutPipe()
100+
if err != nil {
101+
return fmt.Errorf("failed to create helper stdout pipe: %w", err)
102+
}
103+
defer stdout.Close()
104+
105+
if err := cmd.Start(); err != nil {
106+
return fmt.Errorf("failed to start vmnet-helper: %w", err)
107+
}
108+
109+
log.Infof("Started vmnet-helper (pid=%v)", cmd.Process.Pid)
110+
111+
if err := pkgdrivers.WritePidfile(h.pidfilePath(), cmd.Process.Pid); err != nil {
112+
return fmt.Errorf("failed to write vmnet-helper pidfile: %w", err)
113+
}
114+
115+
var info interfaceInfo
116+
if err := json.NewDecoder(stdout).Decode(&info); err != nil {
117+
return fmt.Errorf("failed to decode vmnet interface info: %w", err)
118+
}
119+
120+
log.Infof("Got mac address %q", info.MACAddress)
121+
h.macAddress = info.MACAddress
122+
123+
return nil
124+
}
125+
126+
// GetMACAddress reutuns the mac address assigned by vmnet framework.
127+
func (h *Helper) GetMACAddress() string {
128+
return h.macAddress
129+
}
130+
131+
// Stop terminate sudo, which will terminate vmnet-helper.
132+
func (h *Helper) Stop() error {
133+
log.Info("Stop vmnet-helper")
134+
return pkgdrivers.SignalPidfile(h.pidfilePath(), syscall.SIGTERM)
135+
}
136+
137+
// Kill both sudo and vmnet-helper by killing the process group.
138+
func (h *Helper) Kill() error {
139+
pidfile := h.pidfilePath()
140+
pid, err := pkgdrivers.ReadPidfile(pidfile)
141+
if err != nil {
142+
if !errors.Is(err, os.ErrNotExist) {
143+
return err
144+
}
145+
// Already stopped.
146+
os.Remove(pidfile)
147+
return nil
148+
}
149+
log.Infof("Kill vmnet-helper process group (pgid=%v)", pid)
150+
if err := syscall.Kill(-pid, syscall.SIGKILL); err != nil {
151+
if err != syscall.ESRCH {
152+
return err
153+
}
154+
// Process done.
155+
os.Remove(pidfile)
156+
return nil
157+
}
158+
return nil
159+
}
160+
161+
// GetState returns the vmnet-helper child process state.
162+
func (h *Helper) GetState() (state.State, error) {
163+
pidfile := h.pidfilePath()
164+
pid, err := pkgdrivers.ReadPidfile(pidfile)
165+
if err != nil {
166+
if !errors.Is(err, os.ErrNotExist) {
167+
return state.Error, err
168+
}
169+
return state.Stopped, nil
170+
}
171+
if err := pkgdrivers.CheckPid(pid); err != nil {
172+
// No pid, remove pidfile
173+
os.Remove(pidfile)
174+
return state.Stopped, nil
175+
}
176+
return state.Running, nil
177+
}
178+
179+
func (h *Helper) openLogfile() (*os.File, error) {
180+
logfile := filepath.Join(h.MachineDir, logfileName)
181+
return os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o600)
182+
}
183+
184+
func (h *Helper) pidfilePath() string {
185+
return filepath.Join(h.MachineDir, pidfileName)
186+
}
187+
188+
// Apple recommend receive buffer size to be 4 times the size of the send buffer
189+
// size, but send buffer size is not used to allocate a buffer in datagram
190+
// sockets, it only limits the maximum packet size. Must be larger than TSO
191+
// packets size (65550 bytes).
192+
const sendBufferSize = 65 * 1024
193+
194+
// The receive buffer size determine how many packets can be queued by the
195+
// peer. Using bigger receive buffer size make ENOBUFS error less likely for the
196+
// peer and improves throughput.
197+
const recvBufferSize = 4 * 1024 * 1024
198+
199+
// Socketpair returns a pair of connected unix datagram sockets that can be used
200+
// to connect the helper and a vm. Pass one socket to the helper child process
201+
// and the other to the vm child process.
202+
func Socketpair() (*os.File, *os.File, error) {
203+
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_DGRAM, 0)
204+
if err != nil {
205+
return nil, nil, err
206+
}
207+
// Setting buffer size is an optimization - don't fail on errors.
208+
for _, fd := range fds {
209+
_ = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, sendBufferSize)
210+
_ = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, recvBufferSize)
211+
}
212+
return os.NewFile(uintptr(fds[0]), "sock1"), os.NewFile(uintptr(fds[1]), "sock2"), nil
213+
}

0 commit comments

Comments
 (0)