Skip to content

Commit bb15eea

Browse files
committed
Move all cgroup adoption helpers to its own pkg
1 parent 16c404f commit bb15eea

File tree

5 files changed

+142
-111
lines changed

5 files changed

+142
-111
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ FROM base AS gowinres
183183
ARG GOWINRES_VERSION=v0.3.1
184184
RUN --mount=type=cache,target=/root/.cache/go-build \
185185
--mount=type=cache,target=/go/pkg/mod \
186-
GOBIN=/build/ GO111MODULE=on go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
186+
GOBIN=/build/ GO111MODULE=on GOINSECURE=proxy.golang.org go install "github.com/tc-hib/go-winres@${GOWINRES_VERSION}" \
187187
&& /build/go-winres --help
188188

189189
# containerd

api/server/router/container/container_routes.go

Lines changed: 3 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import (
88
"net/http"
99
"runtime"
1010
"strconv"
11-
"strings"
12-
"os"
1311

1412
"github.com/containerd/containerd/platforms"
1513
"github.com/docker/docker/api/server/httpstatus"
1614
"github.com/docker/docker/api/server/httputils"
1715
"github.com/docker/docker/api/types"
18-
"github.com/docker/docker/pkg/ctxkey"
16+
"github.com/docker/docker/pkg/cgroups"
1917
"github.com/docker/docker/api/types/backend"
2018
"github.com/docker/docker/api/types/container"
2119
"github.com/docker/docker/api/types/filters"
@@ -477,65 +475,6 @@ func (s *containerRouter) postContainerUpdate(ctx context.Context, w http.Respon
477475
return httputils.WriteJSON(w, http.StatusOK, resp)
478476
}
479477

480-
// deriveParentFromProc finds the deepest ".slice" in the caller's cgroup path
481-
// and returns it as a systemd slice path, e.g. "user.slice/user-1000.slice".
482-
func deriveParentFromProc(mode string, pc *ctxkey.PeerCred) (string, error) {
483-
if pc == nil || pc.PID == 0 {
484-
return "", fmt.Errorf("no peer credentials")
485-
}
486-
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pc.PID))
487-
if err != nil {
488-
return "", fmt.Errorf("read cgroup: %w", err)
489-
}
490-
// On cgroup v2, there is one line like: "0::/user.slice/user-1000.slice/session-6.scope"
491-
// On cgroup v1, systemd is "name=systemd:/user.slice/user-1000.slice/session-6.scope"
492-
lines := strings.Split(string(data), "\n")
493-
var path string
494-
for _, ln := range lines {
495-
if ln == "" {
496-
continue
497-
}
498-
parts := strings.SplitN(ln, ":", 3)
499-
if len(parts) < 3 {
500-
continue
501-
}
502-
controller, cgPath := parts[1], parts[2]
503-
if controller == "" || controller == "name=systemd" || controller == "" /* v2 */ {
504-
path = cgPath
505-
// prefer v2 line if present; break on first match
506-
if strings.HasPrefix(ln, "0::") {
507-
break
508-
}
509-
}
510-
}
511-
if path == "" {
512-
return "", fmt.Errorf("no cgroup path found")
513-
}
514-
// Extract slice segments ending with ".slice"
515-
segs := strings.Split(strings.TrimPrefix(path, "/"), "/")
516-
var slices []string
517-
for _, s := range segs {
518-
if strings.HasSuffix(s, ".slice") {
519-
slices = append(slices, s)
520-
}
521-
}
522-
if len(slices) == 0 {
523-
// Fallback to uid mapping
524-
return fmt.Sprintf("user.slice/user-%d.slice", pc.UID), nil
525-
}
526-
// Build "slice path" up to the deepest slice (exclude scopes/services)
527-
// e.g., user.slice/user-1000.slice
528-
var b strings.Builder
529-
for i, s := range slices {
530-
if i > 0 {
531-
b.WriteString("/")
532-
}
533-
b.WriteString(s)
534-
}
535-
return b.String(), nil
536-
}
537-
538-
539478
func (s *containerRouter) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
540479
if err := httputils.ParseForm(r); err != nil {
541480
return err
@@ -559,14 +498,14 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
559498
logrus.Info("initialized empty HostConfig")
560499
}
561500

562-
if cred, ok := r.Context().Value(ctxkey.PeerCredKey).(*ctxkey.PeerCred); ok && cred != nil {
501+
if cred, ok := r.Context().Value(cgroups.PeerCredKey).(*cgroups.PeerCred); ok && cred != nil {
563502
logrus.WithFields(logrus.Fields{
564503
"pid": cred.PID,
565504
"uid": cred.UID,
566505
"gid": cred.GID,
567506
}).Debug("retrieved peer credentials from context")
568507

569-
if parent, err := deriveParentFromProc("syetemd", cred); err == nil {
508+
if parent, err := cgroups.DeriveParentFromProc(cred); err == nil {
570509
hostConfig.CgroupParent = parent
571510
logrus.WithFields(logrus.Fields{
572511
"pid": cred.PID,

cmd/dockerd/daemon.go

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"strings"
1414
"sync"
1515
"time"
16-
"syscall"
17-
"golang.org/x/sys/unix"
1816

1917

2018
containerddefaults "github.com/containerd/containerd/defaults"
@@ -24,7 +22,7 @@ import (
2422
"github.com/docker/docker/api/server/middleware"
2523
"github.com/docker/docker/api/server/router"
2624
"github.com/docker/docker/api/server/router/build"
27-
"github.com/docker/docker/pkg/ctxkey"
25+
"github.com/docker/docker/pkg/cgroups"
2826
checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
2927
"github.com/docker/docker/api/server/router/container"
3028
distributionrouter "github.com/docker/docker/api/server/router/distribution"
@@ -85,37 +83,6 @@ func NewDaemonCli() *DaemonCli {
8583
}
8684
}
8785

88-
func getPeerCred(c net.Conn) (*ctxkey.PeerCred, error) {
89-
sc, ok := c.(syscall.Conn)
90-
if !ok {
91-
return nil, fmt.Errorf("not a syscall.Conn")
92-
}
93-
94-
raw, err := sc.SyscallConn()
95-
if err != nil {
96-
return nil, fmt.Errorf("SyscallConn: %w", err)
97-
}
98-
99-
var cred *ctxkey.PeerCred
100-
var ctrlErr error
101-
102-
// Control runs a function with the underlying FD.
103-
if err := raw.Control(func(fd uintptr) {
104-
ucred, err := unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
105-
if err != nil {
106-
ctrlErr = err
107-
return
108-
}
109-
cred = &ctxkey.PeerCred{PID: int(ucred.Pid), UID: int(ucred.Uid), GID: int(ucred.Gid)}
110-
}); err != nil {
111-
return nil, fmt.Errorf("raw.Control: %w", err)
112-
}
113-
if ctrlErr != nil {
114-
return nil, fmt.Errorf("getsockopt SO_PEERCRED: %w", ctrlErr)
115-
}
116-
return cred, nil
117-
}
118-
11986
func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
12087
if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
12188
return err
@@ -221,14 +188,14 @@ func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
221188
httpServer := &http.Server{
222189
ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
223190
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
224-
if cred, err := getPeerCred(c); err == nil && cred != nil {
191+
if cred, err := cgroups.GetPeerCred(c); err == nil && cred != nil {
225192
logrus.WithFields(logrus.Fields{
226193
"pid": cred.PID,
227194
"uid": cred.UID,
228195
"gid": cred.GID,
229196
"remoteAddr": c.RemoteAddr().String(),
230197
}).Info("accepted new connection with peer credentials")
231-
return context.WithValue(ctx, ctxkey.PeerCredKey, cred)
198+
return context.WithValue(ctx, cgroups.PeerCredKey, cred)
232199
} else if err != nil {
233200
logrus.WithError(err).Error("getPeerCred error")
234201
}

pkg/cgroups/cgroups.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package cgroups
2+
3+
import (
4+
"os"
5+
"fmt"
6+
"strings"
7+
"net"
8+
"syscall"
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
// PeerCredKey is used as the context key for storing peer credentials.
13+
var PeerCredKey = &struct{}{}
14+
15+
type PeerCred struct {
16+
PID int
17+
UID int
18+
GID int
19+
}
20+
21+
func GetPeerCred(c net.Conn) (*PeerCred, error) {
22+
sc, ok := c.(syscall.Conn)
23+
if !ok {
24+
return nil, fmt.Errorf("not a syscall.Conn")
25+
}
26+
27+
raw, err := sc.SyscallConn()
28+
if err != nil {
29+
return nil, fmt.Errorf("SyscallConn: %w", err)
30+
}
31+
32+
var cred *PeerCred
33+
var ctrlErr error
34+
35+
// Control runs a function with the underlying FD.
36+
if err := raw.Control(func(fd uintptr) {
37+
ucred, err := unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
38+
if err != nil {
39+
ctrlErr = err
40+
return
41+
}
42+
cred = &PeerCred{PID: int(ucred.Pid), UID: int(ucred.Uid), GID: int(ucred.Gid)}
43+
}); err != nil {
44+
return nil, fmt.Errorf("raw.Control: %w", err)
45+
}
46+
if ctrlErr != nil {
47+
return nil, fmt.Errorf("getsockopt SO_PEERCRED: %w", ctrlErr)
48+
}
49+
return cred, nil
50+
}
51+
52+
func DeriveParentFromProcCgroupfs(pc *PeerCred) (string, error) {
53+
if pc == nil || pc.PID == 0 {
54+
return "", fmt.Errorf("no peer credentials")
55+
}
56+
57+
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pc.PID))
58+
if err != nil {
59+
return "", fmt.Errorf("read cgroup: %w", err)
60+
}
61+
62+
var path string
63+
for _, ln := range strings.Split(string(data), "\n") {
64+
if ln == "" { continue }
65+
parts := strings.SplitN(ln, ":", 3)
66+
if len(parts) < 3 { continue }
67+
if strings.HasPrefix(ln, "0::") {
68+
path = parts[2]
69+
break
70+
}
71+
if parts[1] == "cpu" { path = parts[2] }
72+
}
73+
if path == "" {
74+
return "", fmt.Errorf("no cgroup path found")
75+
}
76+
return path, nil
77+
}
78+
79+
// deriveParentFromProc finds the deepest ".slice" in the caller's cgroup path
80+
// and returns it as a systemd slice path, e.g. "user.slice/user-1000.slice".
81+
func DeriveParentFromProc(pc *PeerCred) (string, error) {
82+
if pc == nil || pc.PID == 0 {
83+
return "", fmt.Errorf("no peer credentials")
84+
}
85+
data, err := os.ReadFile(fmt.Sprintf("/proc/%d/cgroup", pc.PID))
86+
if err != nil {
87+
return "", fmt.Errorf("read cgroup: %w", err)
88+
}
89+
// On cgroup v2, there is one line like: "0::/user.slice/user-1000.slice/session-6.scope"
90+
// On cgroup v1, systemd is "name=systemd:/user.slice/user-1000.slice/session-6.scope"
91+
lines := strings.Split(string(data), "\n")
92+
var path string
93+
for _, ln := range lines {
94+
if ln == "" {
95+
continue
96+
}
97+
parts := strings.SplitN(ln, ":", 3)
98+
if len(parts) < 3 {
99+
continue
100+
}
101+
controller, cgPath := parts[1], parts[2]
102+
if controller == "" || controller == "name=systemd" || controller == "" /* v2 */ {
103+
path = cgPath
104+
// prefer v2 line if present; break on first match
105+
if strings.HasPrefix(ln, "0::") {
106+
break
107+
}
108+
}
109+
}
110+
if path == "" {
111+
return "", fmt.Errorf("no cgroup path found")
112+
}
113+
// Extract slice segments ending with ".slice"
114+
segs := strings.Split(strings.TrimPrefix(path, "/"), "/")
115+
var slices []string
116+
for _, s := range segs {
117+
if strings.HasSuffix(s, ".slice") {
118+
slices = append(slices, s)
119+
}
120+
}
121+
if len(slices) == 0 {
122+
// Fallback to uid mapping
123+
return fmt.Sprintf("user.slice/user-%d.slice", pc.UID), nil
124+
}
125+
// Build "slice path" up to the deepest slice (exclude scopes/services)
126+
// e.g., user.slice/user-1000.slice
127+
var b strings.Builder
128+
for i, s := range slices {
129+
if i > 0 {
130+
b.WriteString("/")
131+
}
132+
b.WriteString(s)
133+
}
134+
return b.String(), nil
135+
}

pkg/ctxkey/ctxkey.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)