Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/networks/usernet/recoincile.go
2655 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package usernet
5
6
import (
7
"context"
8
"encoding/json"
9
"errors"
10
"fmt"
11
"os"
12
"os/exec"
13
"path"
14
"path/filepath"
15
"strings"
16
"time"
17
18
"github.com/sirupsen/logrus"
19
20
"github.com/lima-vm/lima/v2/pkg/executil"
21
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
22
"github.com/lima-vm/lima/v2/pkg/lockutil"
23
"github.com/lima-vm/lima/v2/pkg/osutil"
24
"github.com/lima-vm/lima/v2/pkg/store"
25
)
26
27
// Start starts a instance a usernet network with the given name.
28
// The name parameter must point to a valid network configuration name under <LIMA_HOME>/_config/networks.yaml with `mode: user-v2`.
29
func Start(ctx context.Context, name string) error {
30
logrus.Debugf("Make sure usernet network is started")
31
networksDir, err := dirnames.LimaNetworksDir()
32
if err != nil {
33
return err
34
}
35
// usernet files contents are stored under {LIMA_HOME}/_networks/user-v2/<pid, fdsock, endpointsock, logs>
36
usernetDir := path.Join(networksDir, name)
37
if err := os.MkdirAll(usernetDir, 0o755); err != nil {
38
return err
39
}
40
41
pidFile, err := PIDFile(name)
42
if err != nil {
43
return err
44
}
45
pid, _ := store.ReadPIDFile(pidFile)
46
if pid == 0 {
47
qemuSock, err := Sock(name, QEMUSock)
48
if err != nil {
49
return err
50
}
51
52
fdSock, err := Sock(name, FDSock)
53
if err != nil {
54
return err
55
}
56
57
endpointSock, err := Sock(name, EndpointSock)
58
if err != nil {
59
return err
60
}
61
62
subnet, err := SubnetCIDR(name)
63
if err != nil {
64
return err
65
}
66
67
leases, err := readLeases(name)
68
if err != nil {
69
return err
70
}
71
72
err = lockutil.WithDirLock(usernetDir, func() error {
73
self, err := os.Executable()
74
if err != nil {
75
return err
76
}
77
leasesString := mapToCliString(leases)
78
args := []string{
79
"usernet", "-p", pidFile,
80
"-e", endpointSock,
81
"--listen-qemu", qemuSock,
82
"--listen", fdSock,
83
"--subnet", subnet.String(),
84
}
85
if leasesString != "" {
86
args = append(args, "--leases", leasesString)
87
}
88
cmd := exec.CommandContext(ctx, self, args...)
89
cmd.SysProcAttr = executil.BackgroundSysProcAttr
90
91
stdoutPath := filepath.Join(usernetDir, fmt.Sprintf("%s.%s.%s.log", "usernet", name, "stdout"))
92
stderrPath := filepath.Join(usernetDir, fmt.Sprintf("%s.%s.%s.log", "usernet", name, "stderr"))
93
if err := os.RemoveAll(stdoutPath); err != nil {
94
return err
95
}
96
if err := os.RemoveAll(stderrPath); err != nil {
97
return err
98
}
99
100
cmd.Stdout, err = os.Create(stdoutPath)
101
if err != nil {
102
return err
103
}
104
cmd.Stderr, err = os.Create(stderrPath)
105
if err != nil {
106
return err
107
}
108
109
logrus.Debugf("Starting usernet network: %v", cmd.Args)
110
if err := cmd.Start(); err != nil {
111
return fmt.Errorf("failed to run %v: %w (Hint: check %s/usernet.*.log)", cmd.Args, err, usernetDir)
112
}
113
return nil
114
})
115
if err != nil {
116
return err
117
}
118
for {
119
if _, err := os.Stat(fdSock); !errors.Is(err, os.ErrNotExist) {
120
break
121
}
122
time.Sleep(500 * time.Millisecond)
123
}
124
}
125
return nil
126
}
127
128
// Stop stops running instance a usernet network with the given name.
129
// The name parameter must point to a valid network configuration name under <LIMA_HOME>/_config/networks.yaml with `mode: user-v2`.
130
func Stop(ctx context.Context, name string) error {
131
logrus.Debugf("Make sure usernet network is stopped")
132
pidFile, err := PIDFile(name)
133
if err != nil {
134
return err
135
}
136
pid, _ := store.ReadPIDFile(pidFile)
137
138
if pid != 0 {
139
logrus.Debugf("Stopping usernet daemon")
140
141
err = writeLeases(ctx, name)
142
if err != nil {
143
return err
144
}
145
146
if err := osutil.SysKill(pid, osutil.SigInt); err != nil {
147
logrus.Error(err)
148
return fmt.Errorf("failed to kill process with pid %d: %w", pid, err)
149
}
150
}
151
152
// wait for daemons to terminate (up to 5s) before stopping, otherwise the sockets may not get deleted which
153
// will cause subsequent start commands to fail.
154
startWaiting := time.Now()
155
for {
156
if pid, _ := store.ReadPIDFile(pidFile); pid == 0 {
157
break
158
}
159
if time.Since(startWaiting) > 5*time.Second {
160
logrus.Infof("usernet network still running after 5 seconds. Attempting to forcibly kill")
161
if err := osutil.SysKill(pid, osutil.SigKill); err != nil {
162
logrus.Error(err)
163
}
164
break
165
}
166
time.Sleep(500 * time.Millisecond)
167
}
168
return nil
169
}
170
171
func mapToCliString(m map[string]string) string {
172
var strArr []string
173
for key, value := range m {
174
strArr = append(strArr, fmt.Sprintf("%s=%s", key, value))
175
}
176
return strings.Join(strArr, ",")
177
}
178
179
func readLeases(name string) (map[string]string, error) {
180
leasesFile, err := Leases(name)
181
if err != nil {
182
return nil, err
183
}
184
var leases map[string]string
185
if _, err := os.Stat(leasesFile); errors.Is(err, os.ErrNotExist) {
186
return leases, nil
187
}
188
file, err := os.Open(leasesFile)
189
if err != nil {
190
return nil, err
191
}
192
decoder := json.NewDecoder(file)
193
err = decoder.Decode(&leases)
194
return leases, err
195
}
196
197
func writeLeases(ctx context.Context, nwName string) error {
198
client := NewClientByName(nwName)
199
leases, err := client.Leases(ctx)
200
if err != nil {
201
return err
202
}
203
leasesFile, err := Leases(nwName)
204
if err != nil {
205
return err
206
}
207
file, err := os.Create(leasesFile)
208
if err != nil {
209
return err
210
}
211
encoder := json.NewEncoder(file)
212
err = encoder.Encode(leases)
213
if err != nil {
214
return err
215
}
216
return nil
217
}
218
219