model

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2023 License: Apache-2.0 Imports: 11 Imported by: 25

Documentation

Overview

Package model defines the core of lxkns information model: Linux kernel namespaces and processes, and how they relate to each other; with the additional missing link between processes and user-land containers. Please also refer to the description of the Information Model in the lxkns online manual for further information and UML diagrams besides this API documentation.

Linux namespaces partition certain OS resources and thus come in different types. At the moment, there are namespaces for partitioning cgroups, IPC, mounts, networks, PIDs, (monotonic) time, users, and UTS-related information (hostname, ...).

Namespaces have unique identifiers, yet these are not names, but inode numbers (ignoring here the lost cause of device numbers on purpose).

Two types of namespaces are hierarchical: PID and user namespaces; all other types of namespaces are “flat” without any hierarchy defined within namespaces of the same type.

All namespaces are additionally owned by one user namespace or another. In case of user namespaces this ownership actually is the parent-child namespace relationship instead.

Namespaces may exist with processes, but also without any processes. The latter requires references to such a namespace in form of either bind mounts or parent-children relationships.

The lxkns information model shows which processes are currently “attached” to a specific namespace, if any. However, to reduce noise, the information model only references the “top-most” processes attached to a namespace, and leadership is simply based on the process tree. These “top-most” processes are also dubbed “leaders”, and there's even a most senior leader process, the “ealdorman”, based on its starting time since the Boot Epoch.

All other processes also attached to a specific namespace can then be found by following the process parent-child relationships, starting from the leader processes.

The lxkns information model thus also contains the parent-child relationships between processes. In addition, lxkns also models how individual processes are attached to namespaces, so it's easy to quickly navigate forth and back between namespaces and processes. Each process is always attached to exactly one namespace of each type. However, earlier kernels lack time namespace support, so be prepared that references to time namespaces will be nil on these kernels.

Moreover, leader processes are related to (userland) containers, where applicable. Containers are also organized according to their managing container engine. Of course, depending on host configuration, multiple container engines might be present at the same time. A typical example is a Docker engine (daemon) together with a containerd engine.

Index

Constants

This section is empty.

Variables

TypeIndexLexicalOrder contains NamespaceTypeIndex type indices in lexical order.

TypesByIndex maps model.Allnamespaces array indices to their corresponding Linux' kernel namespace clone() syscall constants.

Functions

This section is empty.

Types

type AllNamespaces

type AllNamespaces [NamespaceTypesCount]NamespaceMap

AllNamespaces contains separate model.NamespaceMaps for all types of Linux kernel namespaces. This type allows package functions to work on multiple namespace types simultaneously in order to optimize traversal of the /proc filesystem, bind-mounts, et cetera. model.AllNamespaces thus stores “all” namespaces that could be discovered in the system, subject to discovery filtering.

func NewAllNamespaces

func NewAllNamespaces() *AllNamespaces

NewAllNamespaces returns a fully initialized model.AllNamespaces object, ready to be filled with funny namespaces, such as “Kevin” and “Chantal”.

type Container

type Container struct {
	// Identifier of this container; depending on the particular container
	// engine this might be a unique container instance ID (for instance,
	// as in the case of a Docker-managed container).
	ID string `json:"id"`
	// Container name, which might be the same as the ID (for instance, in case
	// of containerd-managed containers), but might also be different (such as
	// in the case of Docker-managed containers).
	Name string `json:"name"`
	// Type of container in form of a unique identifier, such as "docker.com",
	// "containerd.io", et cetera.
	Type string `json:"type"`
	// Optional flavor of container, or the same as the Type.
	Flavor string `json:"flavor"`
	// PID of the initial (or "ealdorman") container process. This is always
	// non-zero, as Containerizers must never return any dead (non-alive)
	// containers. After finishing the discovery process this is the container's
	// PID in the initial PID namespace, even for containerized container
	// engines.
	PID PIDType `json:"pid"`
	// true, if the process(es) inside this container has (have) been either
	// paused and are in the process of pausing; otherwise false.
	Paused bool `json:"paused"`
	// Meta data in form of labels assigned to this container.
	Labels Labels `json:"labels"`

	// Group(s) this container belongs to.
	Groups []*Group `json:"-"`

	// Managing container engine instance.
	Engine *ContainerEngine `json:"-"`
	// Initial container process (ealdorman) details object.
	Process *Process `json:"-"`
}

Container is a deliberately limited and simplified view on "alive" containers (where alive containers always have at least an initial process, so are never process-less). This is all we need in the context of Linux-kernel namespaces.

func (*Container) Group

func (c *Container) Group(Type string) *Group

Group returns the group with specified Type, or nil.

type ContainerEngine

type ContainerEngine struct {
	// Container engine instance identifier/data, such as a UUID, et cetera.
	ID string `json:"id"`
	// Identifier of the type of container engine, such as "docker.com",
	// "containerd.io", et cetera.
	Type string `json:"type"`
	// Container engine version information.
	Version string `json:"version"`
	// Container engine API path (in initial mount namespace).
	API string `json:"api"`
	// Container engine PID, if known. Otherwise, zero.
	PID PIDType `json:"pid"`

	// Containers discovered from this container engine.
	Containers []*Container `json:"-"`
}

ContainerEngine describes a single specific instance of a container engine.

func (*ContainerEngine) AddContainer

func (e *ContainerEngine) AddContainer(c *Container)

AddContainer adds a container to the list of discovered containers belonging to this particular engine instance. At the same time, it also links the container to this engine.

type Containers

type Containers []*Container

Containers is a slice of Container, offering some convenience functions, such as finding a container by name.

func (Containers) FirstWithName

func (cs Containers) FirstWithName(name string) *Container

FirstWithName returns the first container with the specified name, or nil if none could be found.

func (Containers) FirstWithNameType

func (cs Containers) FirstWithNameType(name string, typ string) *Container

FirstWithNameType returns the first container with the specified name and of the specified type (or flavor), or nil if none could be found.

func (Containers) WithEngineType

func (cs Containers) WithEngineType(enginetype string) (tcs Containers)

WithEngineType returns only containers matching the specific engine type.

type Group

type Group struct {
	// Name of group of containers.
	Name string `json:"name"`
	// Type of container group in form of a unique identifier.
	Type string `json:"type"`
	// Optional flavor of container, or the same as the Type.
	Flavor string `json:"flavor"`
	// Containers in this group.
	Containers []*Container `json:"-"`
	// Labels store additional discovery-related group meta information.
	Labels Labels `json:"labels"`
}

Group groups a set of containers by a particular criterium as identified by the Type/Flavor of this group.

func (*Group) AddContainer

func (g *Group) AddContainer(c *Container)

AddContainer adds the specified container to this group, updating also the container's group memberships.

type Hierarchy

type Hierarchy interface {
	// Parent returns the parent user or PID namespace of this user or PID
	// namespace. If there is no parent namespace or the parent namespace in
	// inaccessible, then Parent returns nil.
	Parent() Hierarchy
	// Children returns a list of child PID or user namespaces for this PID or
	// user namespace.
	Children() []Hierarchy
}

Hierarchy informs about the parent-child relationships of PID and user namespaces.

type Labels

type Labels map[string]string

Labels are labels as key=value pairs assigned to a container. Both keys and values are strings.

type Namespace

type Namespace interface {
	// ID returns the unique identifier of this Linux-kernel namespace. This
	// identifier is basically a tuple consisting of an inode number from the
	// special "nsfs" namespace filesystem inside the Linux kernel, together
	// with the device ID of that nsfs filesystem. IDs cannot be set as only the
	// Linux allocates and manages them.
	ID() species.NamespaceID
	// Type returns the type of namespace in form of one of the NamespaceType,
	// such as species.CLONE_NEWNS, species.CLONE_NEWCGROUP, et cetera.
	Type() species.NamespaceType
	// Owner returns the user namespace "owning" this namespace. For user
	// namespaces, Owner always returns nil; use Hierarchy.Parent() instead, as
	// the owner of a user namespace is its parent user namespace.
	Owner() Ownership
	// Ref returns a filesystem path (or sequence of paths, see NamespaceRef for
	// details) suitable for referencing this namespace. A zero ref indicates
	// that there is no reference path available: this is the case for "hidden"
	// PID and user namespaces sandwiched in between PID or user namespaces
	// where reference paths are available, because these other namespaces have
	// processes joined to them, or are either bind-mounted or fd-referenced.
	// Hidden PID namespaces can appear only when there is no process in any of
	// their child namespaces and the child PID namespace(s) is bind-mounted or
	// fd-references (the parent PID namespace is then kept alive because the
	// child PID namespaces are kept alive).
	Ref() NamespaceRef
	// Leaders returns an unsorted list of Process-es which are joined to this
	// namespace and which are the topmost processes in the process tree still
	// joined to this namespace.
	Leaders() []*Process
	// LeaderPIDs returns the list of leader PIDs. This is a convenience method
	// for those use cases where just a list of leader process PIDs is needed,
	// but not the leader Process objects themselves.
	LeaderPIDs() []PIDType // "leader" process PIDs only.
	// Ealdorman returns the most senior leader process. The "most senior"
	// process is the one which was created at the earliest, based on the start
	// times from /proc/[PID]/stat. Me thinks, me has read too many Bernard
	// Cornwell books. Wyrd bið ful aræd.
	Ealdorman() *Process
	// LooseThreads returns those [Task] objects that are attached to this
	// namespace but whose [Process] objects are attached to a different
	// namespace of this type.
	LooseThreads() []*Task
	// LooseThreadIDs returns the list of IDs of "loose" threads (tasks). This
	// is a convenience method for such situations where only the task IDs are
	// needed, but no further details.
	LooseThreadIDs() []PIDType
	// String describes this namespace with type, id, joined leader processes,
	// and optionally information about owner, children, parent.
	String() string
}

Namespace represents a Linux kernel namespace in terms of its unique identifier, type, owning user namespace, joined (leader) processes, and some more.

type NamespaceMap

type NamespaceMap map[species.NamespaceID]Namespace

NamespaceMap indexes a bunch of model.Namespaces by their identifiers. Usually, namespace indices will contain only namespaces of the same type.

type NamespaceRef

type NamespaceRef []string

NamespaceRef is a filesystem reference to a namespace. It can be a single path, such as "/proc/1/ns/net" or "/proc/666/fd/6" in case the namespace can be referenced from any mount namespace (as long as there's a procfs mounted in the usual place). For bind-mounted namespaces this is either a single-path optimized reference or a multi-path reference. In this latter case the first path is to be interpreted in the context of PID 1 and references a mount namespace. All following paths, except for the last path in case of non-mount namespaces, are then to be taken relative in that mount namespace referenced by the previous element.

If this does sound moonstruck, then it most probably is. But didn't we said “in every nook and cranny”?

func (NamespaceRef) String

func (r NamespaceRef) String() string

String returns the textual representation of a NamespaceRef in form of a series of namespace reference paths, separated by some fancy unicode glyph.

type NamespaceStringer

type NamespaceStringer interface {
	fmt.Stringer
	// TypeIDString describes this instance of a Linux kernel namespace just by
	// its type and identifier, and nothing else.
	TypeIDString() string
}

NamespaceStringer describes a namespace either in its descriptive form when using the well-known fmt.Stringer.String method, or in a terse format when going for model.NamespaceStringer.TypeIDString, which only describes the type and identifier of a namespace.

type NamespaceTypeIndex

type NamespaceTypeIndex int

NamespaceTypeIndex is an array index type for Linux kernel namespace types. It is used with the model.AllNamespaces type, which is an array of namespace maps, one map “id->namespace object” for each type of Linux kernel namespace. NamespaceTypeIndex must not be confused with the Linux' kernel namespace clone() syscall constants as the latter are typed as species.NamespaceType instead.

const (
	MountNS  NamespaceTypeIndex = iota // array index for mount namespaces map
	CgroupNS                           // array index for cgroup namespaces map
	UTSNS                              // array index for UTS namespaces map
	IPCNS                              // array index for IPC namespaces map
	UserNS                             // array index for user namespaces map
	PIDNS                              // array index for PID namespaces map
	NetNS                              // array index for net namespaces map
	TimeNS                             // array index for time namespaces map

	NamespaceTypesCount // number of namespace types
)

Set of indices into AllNamespaces arrays, one for each type of Linux kernel namespace.

func TypeIndex

func TypeIndex(nstype species.NamespaceType) NamespaceTypeIndex

TypeIndex returns the model.AllNamespaces array index corresponding with the specified Linux' kernel clone() syscall namespace constant. For instance, for species.CLONE_NEWNET the index model.NetNS is then returned.

type NamespacedPID

type NamespacedPID struct {
	PIDNS Namespace // PID namespace ID for PID.
	PID   PIDType   // PID within PID namespace (of ID).
}

NamespacedPID is a PID valid only in the context of its PID namespace.

type NamespacedPIDs

type NamespacedPIDs []NamespacedPID

NamespacedPIDs is a list of PIDs for the same single process, but in different PID namespaces. The order of the list is undefined.

func (NamespacedPIDs) PIDs

func (ns NamespacedPIDs) PIDs() []PIDType

PIDs just returns the different PIDs assigned to a single process in different PID namespaces, without the namespaces. This is a convenience function for those simple use cases where just the PID list is wanted, but no further PID namespace details.

type NamespacesSet

type NamespacesSet [NamespaceTypesCount]Namespace

NamespacesSet contains a model.Namespace reference of each type exactly once. For instance, it represents the set of 7 namespaces a process will always be joined (“attached”, ...) to. Processes cannot be not attached to each type of Linux kernel namespace.

type Ownership

type Ownership interface {
	// UID returns the user ID of the process that created this user namespace.
	UID() int
	// Ownings returns all namespaces owned by this user namespace, with the
	// exception of user namespaces. "Owned" user namespaces are actually child
	// user namespaces, so they are returned through Hierarchy.Children()
	// instead.
	Ownings() AllNamespaces
}

Ownership informs about the owning user ID, as well as the namespaces owned by a specific user namespace. Only user namespaces provide and implement Ownership.

type PIDMapper

type PIDMapper interface {
	// Translate translates a PID "pid" in PID namespace "from" to the
	// corresponding PID in PID namespace "to". Returns 0, if PID "pid" either
	// does not exist in namespace "from", or PID namespace "to" isn't either a
	// parent or child of PID namespace "from".
	Translate(pid PIDType, from Namespace, to Namespace) PIDType
	// NamespacedPIDs returns for a specific namespaced PID the list of all PIDs
	// the corresponding process has been given in different PID namespaces.
	// Returns nil if the PID doesn't exist in the specified PID namespace. The
	// list is ordered from the topmost PID namespace down to the leaf PID
	// namespace to which a process actually is joined to.
	NamespacedPIDs(pid PIDType, from Namespace) NamespacedPIDs
}

PIDMapper translates PIDs (model.PIDType) from one PID namespace to another.

type PIDType

type PIDType int32

PIDType expresses things more clearly.

  • No, that's not a "PidType" since “PID” is an acronym, but neither an abbreviation, nor an ordinary word (yet/still) in itself.

type ProTaskCommon added in v0.24.0

type ProTaskCommon struct {
	Name       string        `json:"name"`      // (limited) name, from the comm status field.
	Namespaces NamespacesSet `json:"-"`         // the 8 namespaces joined by this process.
	Starttime  uint64        `json:"starttime"` // time of process start, since the Kernel boot epoch.
	CpuCgroup  string        `json:"cpucgroup"` // (relative) path of CPU control group for this process.
	// (relative) path of freezer control group for this process. Please note
	// that for a cgroup v2 unified and non-hybrid hierarchy this path will
	// always be the same as for CpuCgroup.
	FridgeCgroup string `json:"fridgecgroup"`
	FridgeFrozen bool   `json:"fridgefrozen"` // effective freezer state.
}

ProTaskCommon defines the fields we're interested in that are common to both Process and Task objects.

type Process

type Process struct {
	ProTaskCommon
	PID       PIDType    `json:"pid"`             // this process' identifier.
	PPID      PIDType    `json:"ppid"`            // parent's process identifier.
	Parent    *Process   `json:"-"`               // our parent's process description.
	Children  []*Process `json:"-"`               // child processes.
	Cmdline   []string   `json:"cmdline"`         // command line of process.
	Tasks     []*Task    `json:"tasks,omitempty"` // tasks of this process, including the main task.
	Container *Container `json:"-"`               // associated container; only for the leader.
}

Process represents our very limited view and even more limited interest in a specific Linux process. Well, the limitation comes from what we need for namespace discovery to be useful.

func NewProcess

func NewProcess(PID PIDType, withtasks bool) (proc *Process)

NewProcess returns a model.Process object describing certain properties of the Linux process with the specified PID. In particular, the parent PID and the name of the process, as well as the command line. If withtasks is true, it will additionally discover all tasks of the process.

func NewProcessInProcfs

func NewProcessInProcfs(PID PIDType, withtasks bool, procroot string) (proc *Process)

NewProcessInProcfs implements model.NewProcess and additionally allows for testing on fake /proc "filesystems".

func (*Process) Basename

func (p *Process) Basename() (basename string)

Basename returns the process executable name with the directory stripped off, similar to what basename(1) does when applied to the “$0” argument. However, in case the basename would be empty, then the process name is returned instead as fallback.

func (*Process) String

func (p *Process) String() string

String praises a Process object with a text hymn.

func (*Process) Valid

func (p *Process) Valid() bool

Valid checks for the same process to still be present in the OS process table and then returns true, otherwise false. The validity check bases on the start time of the process, so stale PIDs can be detected even if they get reused after some time.

type ProcessListByPID

type ProcessListByPID []*Process

ProcessListByPID is a type alias for sorting slices of *model.Process by their PIDs in numerically ascending order.

func (ProcessListByPID) Len

func (l ProcessListByPID) Len() int

func (ProcessListByPID) Less

func (l ProcessListByPID) Less(i, j int) bool

func (ProcessListByPID) Swap

func (l ProcessListByPID) Swap(i, j int)

type ProcessTable

type ProcessTable map[PIDType]*Process

ProcessTable maps PIDs to their model.Process descriptions, allowing for quick lookups.

func NewProcessTable

func NewProcessTable(freezer bool) (pt ProcessTable)

NewProcessTable returns the currently available processes (as usual, without tasks/threads). The process table is in fact a map, indexed by PIDs. When the freezer parameter is true then additionally the cgroup freezer states will also be discovered; as this might require switching into the initial mount namespace and this is possible in Go only when re-executing as a child, the caller must explicitly request this additional discovery.

func NewProcessTableFromProcfs

func NewProcessTableFromProcfs(freezer bool, withtasks bool, procroot string) (pt ProcessTable)

NewProcessTableFromProcfs implements model.NewProcessTable and allows for testing on fake /proc "filesystems".

func NewProcessTableWithTasks added in v0.26.0

func NewProcessTableWithTasks(freezer bool) (pt ProcessTable)

NewProcessTableWithTasks returns not only the currently available tasks, but optionally also the tasks/threads.

func (ProcessTable) ByName

func (t ProcessTable) ByName(name string) (procs []*Process)

ByName returns all processes with the specified name.

func (ProcessTable) ProcessesByPIDs

func (t ProcessTable) ProcessesByPIDs(pid ...PIDType) []*Process

ProcessesByPIDs returns the model.Process objects corresponding to the specified PIDs. It skips PIDs for which no Process object is known and only returns Process objects for known PIDs. If you need error handling, then you'll better roll your own function.

type Task added in v0.24.0

type Task struct {
	ProTaskCommon
	TID     PIDType  `json:"tid"` // our task's identifier.
	Process *Process `json:"-"`   // our main task ~process.
}

Task represents our very, very limited view and interest in a particular Linux task (including the main task that represents the whole process).

func (*Task) MainTask added in v0.24.0

func (t *Task) MainTask() bool

MainTask returns true if the given Task is the process main task.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL