From e3dc41abc334694afc045c0394b1a8f5444c30f6 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 30 Oct 2024 17:44:00 +0100 Subject: [PATCH] feat: Add CGroup v2 support Signed-off-by: Steffen Vogel --- cmd/gontc/gontc.go | 25 +- default.nix | 2 +- go.mod | 2 + go.sum | 5 + pkg/base_node.go | 27 +- pkg/cgroup.go | 95 ++++++ pkg/cgroup_test.go | 108 ++++++ pkg/cmd.go | 61 ++-- pkg/cmd_test.go | 4 +- pkg/exec.go | 8 +- pkg/namespace.go | 6 +- pkg/network.go | 21 +- pkg/options/systemd/cgroup.go | 568 ++++++++++++++++++++++++++++++++ pkg/options/systemd/kill.go | 76 +++++ pkg/options/systemd/property.go | 22 ++ pkg/options/systemd/scope.go | 79 +++++ pkg/options/systemd/variants.go | 108 ++++++ pkg/teardown.go | 22 +- 18 files changed, 1193 insertions(+), 46 deletions(-) create mode 100644 pkg/cgroup.go create mode 100644 pkg/cgroup_test.go create mode 100644 pkg/options/systemd/cgroup.go create mode 100644 pkg/options/systemd/kill.go create mode 100644 pkg/options/systemd/property.go create mode 100644 pkg/options/systemd/scope.go create mode 100644 pkg/options/systemd/variants.go diff --git a/cmd/gontc/gontc.go b/cmd/gontc/gontc.go index ed99788..933f657 100644 --- a/cmd/gontc/gontc.go +++ b/cmd/gontc/gontc.go @@ -5,16 +5,19 @@ package main import ( + "context" "errors" "flag" "fmt" "os" "path/filepath" "strings" + "time" "cunicu.li/gont/v2/internal" "cunicu.li/gont/v2/internal/utils" g "cunicu.li/gont/v2/pkg" + "github.com/coreos/go-systemd/v22/dbus" "golang.org/x/exp/slices" ) @@ -174,13 +177,21 @@ func list(args []string) { } func clean(args []string) error { + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 10*time.Second) + + c, err := dbus.NewWithContext(ctx) + if err != nil { + return fmt.Errorf("failed to connect to D-Bus: %w", err) + } + if len(args) > 1 { network := args[1] - if err := g.TeardownNetwork(network); err != nil { + if err := g.TeardownNetwork(ctx, c, network); err != nil { return fmt.Errorf("failed to teardown network '%s': %w", network, err) } } else { - return g.TeardownAllNetworks() + return g.TeardownAllNetworks(ctx, c) } return nil @@ -202,6 +213,16 @@ func exec(network, node string, args []string) error { return err } + cgroupName := fmt.Sprintf("gont-run-%d", os.Getpid()) + cgroup, err := g.NewCGroup(nil, "scope", cgroupName) + if err != nil { + return fmt.Errorf("failed to create CGroup scope: %w", err) + } + + if err := cgroup.Start(); err != nil { + return fmt.Errorf("failed to start CGroup scope: %w", err) + } + return g.Exec(network, node, args) } diff --git a/default.nix b/default.nix index 85b49bb..505ab39 100644 --- a/default.nix +++ b/default.nix @@ -4,7 +4,7 @@ buildGoModule { name = "gont"; src = ./.; - vendorHash = "sha256-IXTpMzTrWRH10vB6hRsMf7ilT5tUG/EPJbYLO+8d9Ik="; + vendorHash = "sha256-EAwP8nNyS6lnLi/OBxxdZzePIiy30l6uFr1Z8SPAllA="; buildInputs = [ libpcap ]; doCheck = false; } diff --git a/go.mod b/go.mod index 518bc48..388df27 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,12 @@ module cunicu.li/gont/v2 go 1.23.0 require ( + github.com/coreos/go-systemd/v22 v22.5.0 github.com/davecgh/go-spew v1.1.1 github.com/fxamacker/cbor/v2 v2.7.0 github.com/go-delve/delve v1.21.0 github.com/go-ping/ping v1.1.0 + github.com/godbus/dbus/v5 v5.1.0 github.com/google/nftables v0.2.0 github.com/gopacket/gopacket v1.3.0 github.com/vishvananda/netlink v1.3.0 diff --git a/go.sum b/go.sum index e23e506..988d4dd 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkE github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cosiner/argv v0.1.0/go.mod h1:EusR6TucWKX+zFgtdUsKT2Cvg45K5rtpCcWz4hK06d8= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= @@ -67,6 +69,9 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/pkg/base_node.go b/pkg/base_node.go index d81dc59..8511b86 100644 --- a/pkg/base_node.go +++ b/pkg/base_node.go @@ -24,6 +24,7 @@ type BaseNodeOption interface { type BaseNode struct { *Namespace + *CGroup isHostNode bool network *Network @@ -46,9 +47,7 @@ type BaseNode struct { logger *zap.Logger } -func (n *Network) AddNode(name string, opts ...Option) (*BaseNode, error) { - var err error - +func (n *Network) AddNode(name string, opts ...Option) (node *BaseNode, err error) { basePath := filepath.Join(n.VarPath, "nodes", name) for _, path := range []string{"ns", "files"} { path = filepath.Join(basePath, path) @@ -57,13 +56,22 @@ func (n *Network) AddNode(name string, opts ...Option) (*BaseNode, error) { } } - node := &BaseNode{ + node = &BaseNode{ name: name, network: n, BasePath: basePath, logger: zap.L().Named("node").With(zap.String("node", name)), } + cgroupName := fmt.Sprintf("gont-%s-%s", n.Name, name) + if node.CGroup, err = NewCGroup(n.sdConn, "slice", cgroupName, opts...); err != nil { + return nil, fmt.Errorf("failed to create CGroup slice: %w", err) + } + + if err := node.CGroup.Start(); err != nil { + return nil, fmt.Errorf("failed to start CGroup slice: %w", err) + } + node.logger.Info("Adding new node") for _, opt := range opts { @@ -121,6 +129,7 @@ func (n *Network) AddNode(name string, opts ...Option) (*BaseNode, error) { } } + // Create Netlink connection handle if it does not exist yet if node.nlHandle == nil { node.nlHandle, err = nl.NewHandleAt(node.NsHandle) if err != nil { @@ -309,7 +318,15 @@ func (n *BaseNode) Teardown() error { return err } - return os.RemoveAll(n.BasePath) + if err := os.RemoveAll(n.BasePath); err != nil { + return err + } + + if err := n.CGroup.Stop(); err != nil { + return fmt.Errorf("failed to stop Cgroup slice: %w", err) + } + + return nil } // WriteProcFS write a value to a path within the ProcFS by entering the namespace of this node. diff --git a/pkg/cgroup.go b/pkg/cgroup.go new file mode 100644 index 0000000..8c8537d --- /dev/null +++ b/pkg/cgroup.go @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package gont + +import ( + "context" + "fmt" + + "github.com/coreos/go-systemd/v22/dbus" +) + +type CGroupOption interface { + ApplyCGroup(s *CGroup) +} + +type CGroup struct { + Name string + Type string + Properties []dbus.Property + + sdConn *dbus.Conn +} + +func NewCGroup(c *dbus.Conn, typ, name string, opts ...Option) (g *CGroup, err error) { + g = &CGroup{ + Name: name, + Type: typ, + sdConn: c, + } + + // Create D-Bus connection if not existing + if g.sdConn == nil { + if g.sdConn, err = dbus.NewWithContext(context.Background()); err != nil { + return nil, fmt.Errorf("failed to connect to D-Bus: %w", err) + } + } + + for _, opt := range opts { + if opt, ok := opt.(CGroupOption); ok { + opt.ApplyCGroup(g) + } + } + + return g, nil +} + +func (g *CGroup) Unit() string { + return g.Name + "." + g.Type +} + +// Start creates the CGroup +func (g *CGroup) Start() error { + if _, err := g.sdConn.StartTransientUnitContext(context.Background(), g.Unit(), "replace", g.Properties, nil); err != nil { + return fmt.Errorf("failed to create slice: %w", err) + } + + return nil +} + +// Stop stops the CGroup and kills all contained processes +func (g *CGroup) Stop() error { + ch := make(chan string) + if _, err := g.sdConn.StopUnitContext(context.Background(), g.Unit(), "fail", ch); err != nil { + return fmt.Errorf("failed to remove slice: %w", err) + } + + if state := <-ch; state != "done" { + return fmt.Errorf("failed to wait for CGroup shutdown: state is %s", state) + } + + return nil +} + +// Freeze suspends execution of all processes in the control group. +func (g *CGroup) Freeze() error { + return g.sdConn.FreezeUnit(context.Background(), g.Unit()) +} + +// Thaw resumes execution of all processes in the control group. +func (g *CGroup) Thaw() error { + return g.sdConn.ThawUnit(context.Background(), g.Unit()) +} + +// SetProperties sets transient systemd CGroup properties of the unit. +// See: https://systemd.io/TRANSIENT-SETTINGS/ +func (g *CGroup) SetProperties(opts ...CGroupOption) error { + so := &CGroup{} + for _, opt := range opts { + opt.ApplyCGroup(g) + opt.ApplyCGroup(so) + } + + return g.sdConn.SetUnitPropertiesContext(context.Background(), g.Unit(), true, so.Properties...) +} diff --git a/pkg/cgroup_test.go b/pkg/cgroup_test.go new file mode 100644 index 0000000..93932f7 --- /dev/null +++ b/pkg/cgroup_test.go @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2023 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package gont_test + +import ( + "fmt" + "testing" + "time" + + g "cunicu.li/gont/v2/pkg" + sdo "cunicu.li/gont/v2/pkg/options/systemd" + "github.com/stretchr/testify/require" +) + +func TestCGroup(t *testing.T) { + n, err := g.NewNetwork("", globalNetworkOptions...) + require.NoError(t, err, "Failed to create network") + defer n.Close() + + h, err := n.AddHost("h1") + require.NoError(t, err) + + cmd := h.Command("cat", "/proc/self/cgroup") + out, err := cmd.CombinedOutput() + require.NoError(t, err) + + expectedCgroup := fmt.Sprintf("0::/gont.slice/gont-%s.slice/gont-%s-%s.slice/gont-run-%d.scope\n", n.Name, n.Name, h.Name(), cmd.ProcessState.Pid()) + require.Equal(t, expectedCgroup, string(out)) +} + +func TestCGroupPropertyNetwork(t *testing.T) { + n, err := g.NewNetwork("", g.Customize(globalNetworkOptions, sdo.MemoryMax(5<<20))...) + require.NoError(t, err, "Failed to create network") + defer n.Close() + + h, err := n.AddHost("h1") + require.NoError(t, err) + + cmd := h.Command("bash", "-c", "systemctl show "+n.Unit()+" | grep ^MemoryMax=") + out, err := cmd.CombinedOutput() + require.NoError(t, err) + + outExpected := fmt.Sprintf("MemoryMax=%d\n", 5<<20) + require.Equal(t, outExpected, string(out)) +} + +func TestCGroupPropertyHost(t *testing.T) { + n, err := g.NewNetwork("", globalNetworkOptions...) + require.NoError(t, err, "Failed to create network") + defer n.Close() + + h, err := n.AddHost("h1", sdo.MemoryMax(5<<20)) + require.NoError(t, err) + + cmd := h.Command("bash", "-c", "systemctl show "+h.Unit()+" | grep ^MemoryMax=") + out, err := cmd.CombinedOutput() + require.NoError(t, err) + + outExpected := fmt.Sprintf("MemoryMax=%d\n", 5<<20) + require.Equal(t, outExpected, string(out)) +} + +func TestCGroupPropertyCommand(t *testing.T) { + n, err := g.NewNetwork("", globalNetworkOptions...) + require.NoError(t, err, "Failed to create network") + defer n.Close() + + h, err := n.AddHost("h1") + require.NoError(t, err) + + cmd := h.Command("bash", "-c", "systemctl show gont-run-$$.scope | grep ^MemoryMax=", sdo.MemoryMax(5<<20)) + out, err := cmd.CombinedOutput() + require.NoError(t, err) + + outExpected := fmt.Sprintf("MemoryMax=%d\n", 5<<20) + require.Equal(t, outExpected, string(out)) +} + +func TestCGroupTeardown(t *testing.T) { + n, err := g.NewNetwork("", globalNetworkOptions...) + require.NoError(t, err, "Failed to create network") + + h, err := n.AddHost("h1", sdo.MemoryMax(5<<20)) + require.NoError(t, err) + + cmd := h.Command("sleep", 3600) + err = cmd.Start() + require.NoError(t, err) + + exited := make(chan bool) + go func() { + cmd.Wait() + close(exited) + }() + + time.Sleep(10 * time.Millisecond) + + err = n.Close() + require.NoError(t, err) + + select { + case <-exited: + + case <-time.After(10 * time.Millisecond): + require.Fail(t, "Process did not terminate") + } +} diff --git a/pkg/cmd.go b/pkg/cmd.go index 02e56fb..db02876 100644 --- a/pkg/cmd.go +++ b/pkg/cmd.go @@ -8,12 +8,13 @@ import ( "context" "fmt" "io" - "log" "os" "os/exec" "strconv" "syscall" + sdbus "github.com/coreos/go-systemd/v22/dbus" + "github.com/godbus/dbus/v5" "github.com/gopacket/gopacket/pcapgo" "go.uber.org/zap" "go.uber.org/zap/zapio" @@ -33,6 +34,7 @@ type CmdOption interface { } type Cmd struct { + *CGroup *exec.Cmd // Options @@ -56,12 +58,16 @@ func (n *BaseNode) Command(name string, args ...any) *Cmd { node: n, } + c.CGroup, _ = NewCGroup(c.node.sdConn, "scope", "") + strArgs := []string{} for _, arg := range args { switch arg := arg.(type) { case ExecCmdOption: case CmdOption: arg.ApplyCmd(c) + case CGroupOption: + arg.ApplyCGroup(c.CGroup) default: if strArg, ok := stringifyArg(arg); ok { strArgs = append(strArgs, strArg) @@ -133,7 +139,7 @@ func (n *BaseNode) Command(name string, args ...any) *Cmd { return c } -func (c *Cmd) Start() error { +func (c *Cmd) Start() (err error) { // Add some IPC pipes to capture decryption secrets for envName, secretsType := range map[string]uint32{ "SSLKEYLOGFILE": pcapgo.DSB_SECRETS_TYPE_TLS, @@ -169,23 +175,19 @@ func (c *Cmd) Start() error { c.Stderr = io.MultiWriter(c.StderrWriters...) } - if d := c.debugger(); d != nil { - // We need to start the process in a stopped state - // so we can attach the delve debugger before execution - // commences in order to allow for breakpoints early - // in the execution. - if err := c.stoppedStart(); err != nil { - return err - } + // We need to start the process in a stopped state for two reasons + // 1. Attaching the Delve debugger before execution + // commences in order to allow for breakpoints early + // in the execution. + // 2. Attaching the process into the new Systemd Scope Unit + if err := c.stoppedStart(); err != nil { + return err + } - var err error + if d := c.debugger(); d != nil { if c.debuggerInstance, err = d.start(c.Cmd); err != nil { return err } - } else { - if err := c.Cmd.Start(); err != nil { - return err - } } // Add PID as field to logger after the process has been started @@ -195,6 +197,25 @@ func (c *Cmd) Start() error { )) } + // Start CGroup scope and attach process to it + c.CGroup.Name = fmt.Sprintf("gont-run-%d", c.Process.Pid) + c.CGroup.Properties = append(c.CGroup.Properties, + sdbus.Property{ + Name: "Slice", + Value: dbus.MakeVariant(c.node.Unit()), + }, + sdbus.PropPids(uint32(c.Process.Pid)), //nolint:gosec + ) + + if err := c.CGroup.Start(); err != nil { + return fmt.Errorf("failed to start CGroup scope: %w", err) + } + + // Signal child that that it is ready to proceed + if err := c.Process.Signal(syscall.SIGCONT); err != nil { + return fmt.Errorf("failed to send continuation signal to child: %w", err) + } + return nil } @@ -338,7 +359,7 @@ func (c *Cmd) stoppedStart() error { pgid, err := syscall.Getpgid(c.Process.Pid) if err != nil { - log.Panic(err) + return fmt.Errorf("failed to get pgid: %w", err) } for { @@ -348,10 +369,10 @@ func (c *Cmd) stoppedStart() error { return err } - c.logger.Debug("Stopped", - zap.String("signal", ws.Signal().String()), - zap.String("stop_signal", ws.StopSignal().String()), - zap.Int("trap_cause", ws.TrapCause())) + // c.logger.Debug("Stopped", + // zap.String("signal", ws.Signal().String()), + // zap.String("stop_signal", ws.StopSignal().String()), + // zap.Int("trap_cause", ws.TrapCause())) if ws.Exited() { return fmt.Errorf("process exited prematurely") diff --git a/pkg/cmd_test.go b/pkg/cmd_test.go index 76342df..d6ac952 100644 --- a/pkg/cmd_test.go +++ b/pkg/cmd_test.go @@ -161,11 +161,11 @@ func TestIProute2Files(t *testing.T) { defer n.Close() beep, err := n.AddHost("beep") - require.Nil(t, err) + require.NoError(t, err) cmd := beep.Command("ip", "addr") out, err := cmd.CombinedOutput() - assert.Nil(t, err) + assert.NoError(t, err) t.Logf("Output: %s", out) } diff --git a/pkg/exec.go b/pkg/exec.go index 4e5d7df..e7748b0 100644 --- a/pkg/exec.go +++ b/pkg/exec.go @@ -45,13 +45,7 @@ func init() { panic(err) } - // Enter new namespaces - if err := Unshare(network, node); err != nil { - panic(err) - } - - // Run program - if err := execvpe.Execvpe(os.Args[0], os.Args, os.Environ()); err != nil { + if err := Exec(network, node, os.Args); err != nil { panic(err) } diff --git a/pkg/namespace.go b/pkg/namespace.go index 6cabfb6..c5c422b 100644 --- a/pkg/namespace.go +++ b/pkg/namespace.go @@ -64,7 +64,11 @@ func NewNamespace(name string) (*Namespace, error) { } // Restore original netns namespace - return ns, unix.Setns(curNetNs, syscall.CLONE_NEWNET) + if err = unix.Setns(curNetNs, syscall.CLONE_NEWNET); err != nil { + return nil, err + } + + return ns, nil } func (ns *Namespace) Close() error { diff --git a/pkg/network.go b/pkg/network.go index 11d16ad..7297048 100644 --- a/pkg/network.go +++ b/pkg/network.go @@ -25,6 +25,8 @@ type NetworkOption interface { } type Network struct { + *CGroup + Name string nodes map[string]Node @@ -34,7 +36,7 @@ type Network struct { HostNode *Host VarPath string - TmpPath string + TmpPath string // For Go builds (see RunGo()) // Options Persistent bool @@ -88,7 +90,7 @@ func NewNetwork(name string, opts ...Option) (n *Network, err error) { varPath := filepath.Join(baseVarDir, name) tmpPath := filepath.Join(baseTmpDir, name) - n := &Network{ + n = &Network{ Name: name, VarPath: varPath, TmpPath: tmpPath, @@ -107,6 +109,11 @@ func NewNetwork(name string, opts ...Option) (n *Network, err error) { } } + cgroupName := fmt.Sprintf("gont-%s", name) + if n.CGroup, err = NewCGroup(nil, "slice", cgroupName, opts...); err != nil { + return nil, fmt.Errorf("failed to create CGroup slice: %w", err) + } + if stat, err := os.Stat(varPath); err == nil && stat.IsDir() { return nil, syscall.EEXIST } @@ -131,6 +138,10 @@ func NewNetwork(name string, opts ...Option) (n *Network, err error) { return nil, fmt.Errorf("failed to generate configuration files: %w", err) } + if err := n.CGroup.Start(); err != nil { + return nil, fmt.Errorf("failed to start CGroup slice: %w", err) + } + n.logger.Info("Created new network") return n, nil @@ -226,11 +237,15 @@ func (n *Network) Teardown() error { } if n.TmpPath != "" { - if err := os.RemoveAll(n.VarPath); err != nil { + if err := os.RemoveAll(n.TmpPath); err != nil { return fmt.Errorf("failed to delete network tmp dir: %w", err) } } + if err := n.CGroup.Stop(); err != nil { + return fmt.Errorf("failed to stop CGroup slice: %w", err) + } + return nil } diff --git a/pkg/options/systemd/cgroup.go b/pkg/options/systemd/cgroup.go new file mode 100644 index 0000000..83a0909 --- /dev/null +++ b/pkg/options/systemd/cgroup.go @@ -0,0 +1,568 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package cgroup + +import ( + "net" + "time" + + sdbus "github.com/coreos/go-systemd/v22/dbus" + "github.com/godbus/dbus/v5" +) + +// CPU Accounting and Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CPUAccounting= +func CPUAccounting(enable bool) Property { + return Property(sdbus.Property{ + Name: "CPUAccounting", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CPUWeight=weight +func CPUWeight(weight uint64) Property { + return Property(sdbus.Property{ + Name: "CPUWeight", + Value: dbus.MakeVariant(weight), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CPUWeight=weight +func StartupCPUWeight(weight uint64) Property { + return Property(sdbus.Property{ + Name: "StartupCPUWeight", + Value: dbus.MakeVariant(weight), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CPUQuota= +func CPUQuota(quota float32) Property { + return Property(sdbus.Property{ + Name: "CPUQuotaPerSecUSec", + Value: dbus.MakeVariant(uint64(quota * 1e6)), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CPUQuotaPeriodSec= +func CPUQuotaPeriod(period time.Duration) Property { + return Property(sdbus.Property{ + Name: "CPUQuotaPerSecUSec", + Value: dbus.MakeVariant(uint64(period.Microseconds())), //nolint:gosec + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#AllowedCPUs= +func AllowedCPUs(mask uint64) Property { + return Property(sdbus.Property{ + Name: "AllowedCPUs", + Value: makeCpuSet(mask), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#AllowedCPUs= +func StartupAllowedCPUs(mask uint64) Property { + return Property(sdbus.Property{ + Name: "StartupAllowedCPUs", + Value: makeCpuSet(mask), + }) +} + +// Memory Accounting and Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryAccounting= +func MemoryAccounting(enable bool) Property { + return Property(sdbus.Property{ + Name: "MemoryAccounting", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMin=bytes,%20MemoryLow=bytes +func MemoryMin(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemoryMin", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMin=bytes,%20MemoryLow=bytes +func MemoryLow(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemoryLow", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMin=bytes,%20MemoryLow=bytes +func StartupMemoryLow(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "StartupMemoryLow", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMin=bytes,%20MemoryLow=bytes +func DefaultStartupMemoryLow(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "DefaultStartupMemoryLow", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryHigh=bytes +func MemoryHigh(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemoryHigh", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryHigh=bytes +func StartupMemoryHigh(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "StartupMemoryHigh", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMax=bytes +func MemoryMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemoryMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryMax=bytes +func StartupMemoryMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "StartupMemoryMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemorySwapMax=bytes +func MemorySwapMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemorySwapMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemorySwapMax=bytes +func StartupMemorySwapMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "StartupMemorySwapMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryZSwapMax=bytes +func MemoryZSwapMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "MemoryZSwapMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryZSwapMax=bytes +func StartupMemoryZSwapMax(bytes uint64) Property { + return Property(sdbus.Property{ + Name: "StartupMemoryZSwapMax", + Value: dbus.MakeVariant(bytes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryZSwapWriteback= +func MemoryZSwapWriteback(enable bool) Property { + return Property(sdbus.Property{ + Name: "MemoryZSwapWriteback", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#AllowedMemoryNodes= +func AllowedMemoryNodes(mask uint64) Property { + return Property(sdbus.Property{ + Name: "AllowedMemoryNodes", + Value: makeCpuSet(mask), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#AllowedMemoryNodes= +func StartupAllowedMemoryNodes(mask uint64) Property { + return Property(sdbus.Property{ + Name: "StartupAllowedMemoryNodes", + Value: makeCpuSet(mask), + }) +} + +// Process Accounting and Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#TasksAccounting= +func TasksAccounting(enable bool) Property { + return Property(sdbus.Property{ + Name: "TasksAccounting", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#TasksMax=N +func TasksMax(max uint64) Property { + return Property(sdbus.Property{ + Name: "TasksMax", + Value: dbus.MakeVariant(max), + }) +} + +// IO Accounting and Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOAccounting= +func IOAccounting(enable bool) Property { + return Property(sdbus.Property{ + Name: "IOAccounting", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOWeight=weight +func IOWeight(weight uint64) Property { + return Property(sdbus.Property{ + Name: "IOWeight", + Value: dbus.MakeVariant(weight), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOWeight=weight +func StartupIOWeight(weight uint64) Property { + return Property(sdbus.Property{ + Name: "StartupIOWeight", + Value: dbus.MakeVariant(weight), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IODeviceWeight=device%20weight +// TODO: Implement +// func IODeviceWeight(device string, weight uint64) Property { +// return Property(dbus.Property{ +// Name: "IODeviceWeight", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOReadBandwidthMax=device%20bytes +// TODO: Implement +// func IOReadBandwidthMax(device string, bytes uint64) Property { +// return Property(sdbus.Property{ +// Name: "IOReadBandwidthMax", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOReadBandwidthMax=device%20bytes +// TODO: Implement +// func IOWriteBandwidthMax(device string, bytes uint64) Property { +// return Property(dbus.Property{ +// Name: "IOWriteBandwidthMax", +// Value: dbus.MakeVariant(), +// } +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOReadIOPSMax=device%20IOPS +// TODO: Implement +// func IOReadIOPSMax(device string, iops uint64) Property { +// return Property(sdbus.Property{ +// Name: "IOReadIOPSMax", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IOReadIOPSMax=device%20IOPS +// TODO: Implement +// func IOWriteIOPSMax(device string, iops uint64) Property { +// return Property(sdbus.Property{ +// Name: "IOWriteIOPSMax", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IODeviceLatencyTargetSec=device%20target +// TODO: Implement +// func IODeviceLatencyTarget(device string, target time.Duration) Property { +// return Property(sdbus.Property{ +// Name: "IODeviceLatencyTargetSec", +// Value: dbus.MakeVariant(), +// }) +// } + +// Network Accounting and Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IPAccounting= +func IPAccounting(enable bool) Property { + return Property(sdbus.Property{ + Name: "IPAccounting", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IPAddressAllow=ADDRESS%5B/PREFIXLENGTH%5D%E2%80%A6 +func IPAddressAllow(prefixes ...net.IPNet) Property { + return Property(sdbus.Property{ + Name: "IPAddressAllow", + Value: makePrefixList(prefixes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IPAddressAllow=ADDRESS%5B/PREFIXLENGTH%5D%E2%80%A6 +func IPAddressDeny(prefixes ...net.IPNet) Property { + return Property(sdbus.Property{ + Name: "IPAddressDeny", + Value: makePrefixList(prefixes), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#SocketBindAllow=bind-rule +func SocketBindAllow(policies ...BindPolicy) Property { + val, err := makeBindPolicies(policies) + if err != nil { + panic(err) + } + + return Property(sdbus.Property{ + Name: "SocketBindAllow", + Value: val, + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#SocketBindAllow=bind-rule +func SocketBindDeny(policies ...BindPolicy) Property { + val, err := makeBindPolicies(policies) + if err != nil { + panic(err) + } + + return Property(sdbus.Property{ + Name: "SocketBindDeny", + Value: val, + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#RestrictNetworkInterfaces= +func RestrictNetworkInterfaces(allow bool, intfs ...string) Property { + return Property(sdbus.Property{ + Name: "RestrictNetworkInterfaces", + Value: makeNetworkInterfaceAllowList(allow, intfs), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#NFTSet=family:table:set +// TODO: Implement +// type NFTSetDefinition struct { +// Source string +// Family string +// Table string +// Set string +// } +// func NFTSet(sets []NFTSetDefinition) Property { +// return Property(sdbus.Property{ +// Name: "NFTSet", +// Value: dbus.MakeVariant(), +// }) +// } + +// BPF Programs + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IPIngressFilterPath=BPF_FS_PROGRAM_PATH +func IPIngressFilterPath(paths ...string) Property { + return Property(sdbus.Property{ + Name: "IPIngressFilterPath", + Value: dbus.MakeVariant(paths), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#IPIngressFilterPath=BPF_FS_PROGRAM_PATH +func IPEgressFilterPath(paths ...string) Property { + return Property(sdbus.Property{ + Name: "IPEgressFilterPath", + Value: dbus.MakeVariant(paths), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#BPFProgram=type:program-path +type BPFProgramAttachType string + +const ( + BPFAttachTypeIngress BPFProgramAttachType = "ingress" + BPFAttachTypeEgress BPFProgramAttachType = "egress" + BPFAttachTypeSockCreate BPFProgramAttachType = "sock_create" + BPFAttachTypeSockOps BPFProgramAttachType = "sock_ops" + BPFAttachTypeDevice BPFProgramAttachType = "device" + BPFAttachTypeBind4 BPFProgramAttachType = "bind4" + BPFAttachTypeBind6 BPFProgramAttachType = "bind6" + BPFAttachTypeConnect4 BPFProgramAttachType = "connect4" + BPFAttachTypeConnect6 BPFProgramAttachType = "connect6" + BPFAttachTypePostBind4 BPFProgramAttachType = "post_bind4" + BPFAttachTypePostBind6 BPFProgramAttachType = "post_bind6" + BPFAttachTypeSendmsg4 BPFProgramAttachType = "sendmsg4" + BPFAttachTypeSendmsg6 BPFProgramAttachType = "sendmsg6" + BPFAttachTypeSysctl BPFProgramAttachType = "sysctl" + BPFAttachTypeRecvmsg4 BPFProgramAttachType = "recvmsg4" + BPFAttachTypeRecvmsg6 BPFProgramAttachType = "recvmsg6" + BPFAttachTypeGetsockopt BPFProgramAttachType = "getsockopt" + BPFAttachTypeSetsockopt BPFProgramAttachType = "setsockopt" +) + +type BPFProgramType struct { + AttachType BPFProgramAttachType `dbus:"-"` + Path string `dbus:"-"` +} + +func BPFProgram(programs ...BPFProgramType) Property { + return Property(sdbus.Property{ + Name: "BPFProgram", + Value: dbus.MakeVariant(programs), + }) +} + +// Device Access + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#DeviceAllow= +// TODO: Implement +// func DeviceAllow() Property { +// return Property(sdbus.Property{ +// Name: "DeviceAllow", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#DevicePolicy=auto%7Cclosed%7Cstrict +type DevicePolicyType string + +const ( + DevicePolicyAuto DevicePolicyType = "auto" + DevicePolicyClosed DevicePolicyType = "closed" + DevicePolicyStrict DevicePolicyType = "strict" +) + +func DevicePolicy(policy DevicePolicyType) Property { + return Property(sdbus.Property{ + Name: "DevicePolicy", + Value: dbus.MakeVariant(policy), + }) +} + +// Control Group Management + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Slice= +// TODO: Implement +func Slice(slice string) Property { + return Property(sdbus.Property{ + Name: "Slice", + Value: dbus.MakeVariant(slice), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#Delegate= +func Delegate(enable bool) Property { + return Property(sdbus.Property{ + Name: "Delegate", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#DelegateSubgroup= +func DelegateSubgroup(group string) Property { + return Property(sdbus.Property{ + Name: "DelegateSubgroup", + Value: dbus.MakeVariant(group), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#DisableControllers= +func DisableControllers(controllers ...string) Property { + return Property(sdbus.Property{ + Name: "DisableControllers", + Value: dbus.MakeVariant(controllers), + }) +} + +// Memory Pressure Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill +// TODO: Implement +// func ManagedOOMSwap() Property { +// return Property(sdbus.Property{ +// Name: "ManagedOOMSwap", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#ManagedOOMSwap=auto%7Ckill +// TODO: Implement +// func ManagedOOMMemoryPressure() Property { +// return Property(sdbus.Property{ +// Name: "ManagedOOMMemoryPressure", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#ManagedOOMMemoryPressureLimit= +// TODO: Implement +// func ManagedOOMMemoryPressureLimit() Property { +// return Property(sdbus.Property{ +// Name: "ManagedOOMMemoryPressureLimit", +// Value: dbus.MakeVariant(), +// }) +// } + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#ManagedOOMPreference=none%7Cavoid%7Comit +type ManagedOOMPreferenceMode string + +const ( + ManagedOOMPreferenceNone ManagedOOMPreferenceMode = "none" + ManagedOOMPreferenceAvoid ManagedOOMPreferenceMode = "avoid" + ManagedOOMPreferenceOmit ManagedOOMPreferenceMode = "omit" +) + +func ManagedOOMPreference(mode ManagedOOMPreferenceMode) Property { + return Property(sdbus.Property{ + Name: "ManagedOOMPreference", + Value: dbus.MakeVariant(mode), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureWatch= +type MemoryPressureWatchMode string + +const ( + MemoryPressureWatchOff MemoryPressureWatchMode = "off" + MemoryPressureWatchOn MemoryPressureWatchMode = "on" + MemoryPressureWatchSkip MemoryPressureWatchMode = "skip" +) + +func MemoryPressureWatch(mode MemoryPressureWatchMode) Property { + return Property(sdbus.Property{ + Name: "MemoryPressureWatch", + Value: dbus.MakeVariant(mode), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#MemoryPressureThresholdSec= +func MemoryPressureThreshold(d time.Duration) Property { + return Property(sdbus.Property{ + Name: "MemoryPressureThresholdSec", + Value: dbus.MakeVariant(uint64(d.Microseconds())), //nolint:gosec + }) +} + +// Coredump Control + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html#CoredumpReceive= +func CoredumpReceive(enable bool) Property { + return Property(sdbus.Property{ + Name: "CoredumpReceive", + Value: dbus.MakeVariant(enable), + }) +} diff --git a/pkg/options/systemd/kill.go b/pkg/options/systemd/kill.go new file mode 100644 index 0000000..6ab66c9 --- /dev/null +++ b/pkg/options/systemd/kill.go @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package cgroup + +import ( + sdbus "github.com/coreos/go-systemd/v22/dbus" + "github.com/godbus/dbus/v5" +) + +// See: https://github.com/systemd/systemd/blob/main/src/core/dbus-kill.c + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillMode= +type KillModeType string + +const ( + KillModeControlGroup KillModeType = "control-group" + KillModeProcess KillModeType = "process" + KillModeMixed KillModeType = "mixed" + KillModeNonde KillModeType = "none" +) + +func KillMode(typ KillModeType) Property { + return Property(sdbus.Property{ + Name: "KillMode", + Value: dbus.MakeVariant(typ), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#KillSignal= +func KillSignal(signal int) Property { + return Property(sdbus.Property{ + Name: "KillSignal", + Value: dbus.MakeVariant(signal), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#RestartKillSignal= +func RestartKillSignal(signal int) Property { + return Property(sdbus.Property{ + Name: "RestartKillSignal", + Value: dbus.MakeVariant(signal), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#SendSIGHUP= +func SendSIGHUP(enable bool) Property { + return Property(sdbus.Property{ + Name: "SendSIGHUP", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#SendSIGKILL= +func SendSIGKILL(enable bool) Property { + return Property(sdbus.Property{ + Name: "SendSIGKILL", + Value: dbus.MakeVariant(enable), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#FinalKillSignal= +func FinalKillSignal(signal int) Property { + return Property(sdbus.Property{ + Name: "FinalKillSignal", + Value: dbus.MakeVariant(signal), + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.kill.html#WatchdogSignal= +func WatchdogSignal(signal int) Property { + return Property(sdbus.Property{ + Name: "WatchdogSignal", + Value: dbus.MakeVariant(signal), + }) +} diff --git a/pkg/options/systemd/property.go b/pkg/options/systemd/property.go new file mode 100644 index 0000000..a9805ba --- /dev/null +++ b/pkg/options/systemd/property.go @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package cgroup + +import ( + g "cunicu.li/gont/v2/pkg" + sdbus "github.com/coreos/go-systemd/v22/dbus" +) + +// Property is configuring a systemd CGroup resource control property +// +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.resource-control.html +// See implementation: +// - https://github.com/systemd/systemd/blob/e127c66985b9e338fcb79900a88a24848d5d31fb/src/core/dbus-cgroup.c +// - https://github.com/systemd/systemd/blob/e127c66985b9e338fcb79900a88a24848d5d31fb/src/core/dbus-scope.c +// - https://github.com/systemd/systemd/blob/e127c66985b9e338fcb79900a88a24848d5d31fb/src/core/dbus-slice.c +type Property sdbus.Property + +func (p Property) ApplyCGroup(g *g.CGroup) { + g.Properties = append(g.Properties, sdbus.Property(p)) +} diff --git a/pkg/options/systemd/scope.go b/pkg/options/systemd/scope.go new file mode 100644 index 0000000..a132816 --- /dev/null +++ b/pkg/options/systemd/scope.go @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package cgroup + +import ( + "time" + + sdbus "github.com/coreos/go-systemd/v22/dbus" + "github.com/godbus/dbus/v5" +) + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#RuntimeMaxSec= +func RuntimeMax(period time.Duration) Property { + return Property(sdbus.Property{ + Name: "RuntimeMaxUSec", + Value: dbus.MakeVariant(uint64(period.Microseconds())), //nolint:gosec + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#TimeoutStopSec= +func TimeoutStop(period time.Duration) Property { + return Property(sdbus.Property{ + Name: "TimeoutStopUSec", + Value: dbus.MakeVariant(uint64(period.Microseconds())), //nolint:gosec + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#RuntimeRandomizedExtraSec= +func RuntimeRandomizedExtra(period time.Duration) Property { + return Property(sdbus.Property{ + Name: "RuntimeRandomizedExtraUSec", + Value: dbus.MakeVariant(uint64(period.Microseconds())), //nolint:gosec + }) +} + +// See: https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html#OOMPolicy= +type OOMPolicyType string + +const ( + OOMPolicyContinue OOMPolicyType = "continue" + OOMPolicyStop OOMPolicyType = "stop" + OOMPolicyKill OOMPolicyType = "kill" +) + +func OOMPolicy(policy OOMPolicyType) Property { + return Property(sdbus.Property{ + Name: "OOMPolicy", + Value: dbus.MakeVariant(policy), + }) +} + +func PIDs(pids ...uint32) Property { + return Property(sdbus.Property{ + Name: "PIDs", + Value: dbus.MakeVariant(pids), + }) +} + +// func PIDFDs(fds []int) Property { +// return Property(sdbus.Property{ +// Name: "PIDs", +// Value: dbus.MakeVariant(), //nolint:gosec +// }) +// } + +func User(user string) Property { + return Property(sdbus.Property{ + Name: "User", + Value: dbus.MakeVariant(user), + }) +} + +func Group(group string) Property { + return Property(sdbus.Property{ + Name: "Group", + Value: dbus.MakeVariant(group), + }) +} diff --git a/pkg/options/systemd/variants.go b/pkg/options/systemd/variants.go new file mode 100644 index 0000000..667a786 --- /dev/null +++ b/pkg/options/systemd/variants.go @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2024 Steffen Vogel +// SPDX-License-Identifier: Apache-2.0 + +package cgroup + +import ( + "encoding/binary" + "fmt" + "net" + + "github.com/godbus/dbus/v5" + "golang.org/x/sys/unix" +) + +func makeCpuSet(mask uint64) dbus.Variant { + buf := binary.LittleEndian.AppendUint64(nil, mask) + return dbus.MakeVariant(buf) +} + +func makePrefixList(prefixes []net.IPNet) dbus.Variant { + type prefix struct { + Family int `dbus:"-"` + Prefix []byte `dbus:"-"` + Len uint32 `dbus:"-"` + } + + ps := []prefix{} + for _, pfx := range prefixes { + l, _ := pfx.Mask.Size() + + p := prefix{ + Len: uint32(l), //nolint:gosec + } + + if b := pfx.IP.To4(); b == nil { + p.Family = unix.AF_INET6 + p.Prefix = b + } else { + p.Family = unix.AF_INET + p.Prefix = b + } + + ps = append(ps, p) + } + + return dbus.MakeVariant(ps) +} + +type BindPolicy struct { + // Network family. Must be one of "tcp4", "tcp6", "udp4", "udp6" + Network string + PortMin uint16 + PortMax uint16 +} + +func makeBindPolicies(policies []BindPolicy) (dbus.Variant, error) { + type policy struct { + Family int `dbus:"-"` + IPProto int `dbus:"-"` + PortMin uint16 `dbus:"-"` + NrPorts uint16 `dbus:"-"` + } + + ps := []policy{} + for _, pol := range policies { + if pol.PortMin > pol.PortMax { + return dbus.MakeVariant(nil), fmt.Errorf("invalid ports: %d > %d", pol.PortMin, pol.PortMax) + } + + p := policy{ + PortMin: pol.PortMin, + NrPorts: pol.PortMax - pol.PortMin + 1, + } + + switch pol.Network { + case "tcp4": + p.Family = unix.AF_INET + p.IPProto = unix.IPPROTO_TCP + case "tcp6": + p.Family = unix.AF_INET6 + p.IPProto = unix.IPPROTO_TCP + case "udp4": + p.Family = unix.AF_INET + p.IPProto = unix.IPPROTO_UDP + case "udp6": + p.Family = unix.AF_INET6 + p.IPProto = unix.IPPROTO_UDP + default: + return dbus.MakeVariant(nil), fmt.Errorf("unsupported network: %s", pol.Network) + } + + ps = append(ps, p) + } + + return dbus.MakeVariant(ps), nil +} + +func makeNetworkInterfaceAllowList(allow bool, intfs []string) dbus.Variant { + type allowList struct { + Allow bool `dbus:"-"` + InterfaceNames []string `dbus:"-"` + } + + return dbus.MakeVariant(allowList{ + Allow: allow, + InterfaceNames: intfs, + }) +} diff --git a/pkg/teardown.go b/pkg/teardown.go index f88ef9d..91b550e 100644 --- a/pkg/teardown.go +++ b/pkg/teardown.go @@ -4,6 +4,7 @@ package gont import ( + "context" "errors" "fmt" "math/rand" @@ -13,6 +14,7 @@ import ( "sort" "cunicu.li/gont/v2/internal/utils" + "github.com/coreos/go-systemd/v22/dbus" "github.com/vishvananda/netns" "golang.org/x/sys/unix" ) @@ -76,9 +78,9 @@ func GenerateNetworkName() string { return fmt.Sprintf("%s%d", random, rand.Intn(128)+1) //nolint:gosec } -func TeardownAllNetworks() error { +func TeardownAllNetworks(ctx context.Context, c *dbus.Conn) error { for _, name := range NetworkNames() { - if err := TeardownNetwork(name); err != nil { + if err := TeardownNetwork(ctx, c, name); err != nil { return fmt.Errorf("failed to teardown network '%s': %w", name, err) } } @@ -86,7 +88,7 @@ func TeardownAllNetworks() error { return nil } -func TeardownNetwork(network string) error { +func TeardownNetwork(ctx context.Context, c *dbus.Conn, network string) error { networkVarPath := filepath.Join(baseVarDir, network) networkTmpPath := filepath.Join(baseTmpDir, network) nodesVarPath := filepath.Join(networkVarPath, "nodes") @@ -102,7 +104,7 @@ func TeardownNetwork(network string) error { } node := fi.Name() - if err := TeardownNode(network, node); err != nil { + if err := TeardownNode(ctx, c, network, node); err != nil { return fmt.Errorf("failed to teardown node '%s': %w", node, err) } } @@ -115,10 +117,15 @@ func TeardownNetwork(network string) error { return fmt.Errorf("failed to delete network dir: %w", err) } + sliceName := fmt.Sprintf("gont-%s.slice", network) + if _, err := c.StopUnitContext(ctx, sliceName, "fail", nil); err != nil { + return fmt.Errorf("failed to stop CGroup slice: %w", err) + } + return nil } -func TeardownNode(network, node string) error { +func TeardownNode(ctx context.Context, c *dbus.Conn, network, node string) error { nodePath := filepath.Join(baseVarDir, network, "nodes", node) nsMount := filepath.Join(nodePath, "ns", "net") @@ -140,5 +147,10 @@ func TeardownNode(network, node string) error { return fmt.Errorf("failed to delete node dir: %w", err) } + sliceName := fmt.Sprintf("gont-%s-%s.slice", network, node) + if _, err := c.StopUnitContext(ctx, sliceName, "fail", nil); err != nil { + return fmt.Errorf("failed to stop CGroup slice: %w", err) + } + return nil }