Skip to content

Commit 981371a

Browse files
committed
uffd: anchor sockets at short runtime root, not under data dir
Unix domain socket paths cap at 108 bytes (sun_path). Putting the per- fork socket under <DataDir>/templates/<25-char-cuid>/uffd/<25-char- cuid>.uffd blew that limit on CI runners where t.TempDir() returns long prefixes, surfacing as "bind: invalid argument" in TestFirecrackerForkFromTemplate. Sockets are ephemeral, so anchoring them at /tmp/h-uffd/<templateID>/ keeps the path well under the limit regardless of how deep DataDir is. The tracker now also rm -rfs the per-template socket dir on the last release so we don't leak stale entries.
1 parent 551e1f2 commit 981371a

2 files changed

Lines changed: 33 additions & 10 deletions

File tree

lib/instances/uffd.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"os"
78
"path/filepath"
89
"sync"
910

@@ -35,8 +36,9 @@ type uffdTracker struct {
3536
}
3637

3738
type uffdEntry struct {
38-
server *uffd.Server
39-
forks map[string]struct{}
39+
server *uffd.Server
40+
socketDir string
41+
forks map[string]struct{}
4042
}
4143

4244
func newUffdTracker() *uffdTracker {
@@ -65,7 +67,7 @@ func (t *uffdTracker) acquireUffdForFork(ctx context.Context, templateID, memFil
6567
t.mu.Unlock()
6668
return "", fmt.Errorf("uffd: start server for template %s: %w", templateID, err)
6769
}
68-
entry = &uffdEntry{server: srv, forks: map[string]struct{}{}}
70+
entry = &uffdEntry{server: srv, socketDir: socketDir, forks: map[string]struct{}{}}
6971
t.entries[templateID] = entry
7072
}
7173
t.mu.Unlock()
@@ -101,6 +103,7 @@ func (t *uffdTracker) releaseUffdForFork(templateID, forkID string) error {
101103
}
102104
delete(entry.forks, forkID)
103105
srv := entry.server
106+
socketDir := entry.socketDir
104107
empty := len(entry.forks) == 0
105108
if empty {
106109
delete(t.entries, templateID)
@@ -115,6 +118,9 @@ func (t *uffdTracker) releaseUffdForFork(templateID, forkID string) error {
115118
if err := srv.Close(); err != nil && firstErr == nil {
116119
firstErr = err
117120
}
121+
if socketDir != "" {
122+
_ = os.RemoveAll(socketDir)
123+
}
118124
}
119125
return firstErr
120126
}
@@ -130,8 +136,12 @@ func (t *uffdTracker) maybeCloseEmpty(templateID string) {
130136
}
131137
delete(t.entries, templateID)
132138
srv := entry.server
139+
socketDir := entry.socketDir
133140
t.mu.Unlock()
134141
_ = srv.Close()
142+
if socketDir != "" {
143+
_ = os.RemoveAll(socketDir)
144+
}
135145
}
136146

137147
// closeAll tears down every server. Called by the manager during
@@ -150,6 +160,9 @@ func (t *uffdTracker) closeAll() error {
150160
if err := entry.server.Close(); err != nil && firstErr == nil {
151161
firstErr = err
152162
}
163+
if entry.socketDir != "" {
164+
_ = os.RemoveAll(entry.socketDir)
165+
}
153166
}
154167
return firstErr
155168
}

lib/paths/paths.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,26 @@ func (p *Paths) SnapshotGuestDir(snapshotID string) string {
260260
return filepath.Join(p.SnapshotDir(snapshotID), "guest")
261261
}
262262

263-
// TemplateUffdDir returns the directory that holds the per-fork
264-
// userfaultfd page-server sockets for a template instance. Sockets live
265-
// in a dedicated subdirectory of the template's instance dir, keyed by
266-
// the source (template) instance id rather than each fork's id, because
267-
// Unix domain socket paths are tightly length-limited and one server
268-
// fans out to many forks.
263+
// TemplateUffdDir returns the directory that holds a template's per-fork
264+
// userfaultfd page-server sockets. Sockets cannot live under the data dir
265+
// because Unix domain socket paths are limited to 108 bytes (sun_path); a
266+
// deep DataDir + the cuid2 template+fork ids easily blow that. Anchoring
267+
// at a short, fixed runtime root keeps the absolute path well under the
268+
// limit (~30 chars for typical ids) regardless of where the data dir lives.
269+
//
270+
// Sockets are ephemeral — losing them on reboot is fine; firecracker
271+
// reconnects on restore.
269272
func (p *Paths) TemplateUffdDir(templateInstanceID string) string {
270-
return filepath.Join(p.InstanceDir(templateInstanceID), "uffd")
273+
return filepath.Join(uffdRuntimeRoot(), templateInstanceID)
271274
}
272275

276+
// uffdRuntimeRoot returns the short root used to hold uffd sockets. It is
277+
// not configurable on purpose: the only requirement is that it stay short
278+
// and writable, and /tmp meets both. Tests get isolation from the cuid
279+
// segment in TemplateUffdDir.
280+
func uffdRuntimeRoot() string {
281+
return filepath.Join("/tmp", "h-uffd")
282+
}
273283

274284
// Device path methods
275285

0 commit comments

Comments
 (0)