Documentation ¶
Overview ¶
Package discover discovers Linux kernel namespaces of types cgroup, ipc, mount, net, pid, time, user, and uts. This package discovers namespaces not only when processes have joined them, but also when namespaces have been bind-mounted or are only referenced anymore by process file descriptors or within a hierarchy (PID and user only).
Please note that our manual has some nice UML diagrams depicting Linux' namespaces model from 10,000m.
In case of PID and user namespaces, lxkns additionally discovers their hierarchies, except when running on a really ancient kernel before 4.9. Furthermore, for user namespaces the owning user ID and the owned namespaces will be discovered too. Time namespaces require a kernel 5.6 or later.
And finally, lxkns relates namespaces to the “leading” (or “root”) processes joined to them; this relationship is basically derived for convenience from the process tree hierarchy. The kernel itself doesn't define any special relationship between namespaces and processes except for the “attachment” of processes joining namespaces.
The namespace discovery process can be controlled in several aspects, according to the range of discovery of namespace types and places to search namespaces for, according to the needs of API users of the lxkns package.
Discovery ¶
A namespace discovery is just a single call to function discover.Namespaces. It accepts option setters, such as discover.StandardDiscovery, discover.WithMounts, et cetera.
import ( "github.com/thediveo/lxkns/discover" ) func main() { allns := discover.Namespaces(discover.StandardDiscovery()) ... }
Please also have a look at our manual's Discovering Namespaces UML diagrams.
Basics of the lxkns Information Model ¶
Not totally unexpectedly, the lxkns discovery information model at its most basic level comprises ... namespaces. Again, the manual has some nice namespace representation UML diagram.
In the previous code snippet, the information model returned is stored in the “allns” variable for further processing. The result organizes the namespaces found by type. For instance, the following code snippet prints all namespaces, sorted first by type and then by namespace identifier:
// Iterate over all 7 types of Linux-kernel namespaces, then over all // namespaces of a given type... for nsidx := range allns.Namespaces { for _, ns := range allns.SortedNamespaces(discover.NamespaceTypeIndex(nsidx)) { println(ns.Type().Name(), ns.ID().Ino) } }
Because namespaces have no order defined, the discovery results “list” the namespaces in per-type maps, indexed by namespace identifiers. For convenience, SortedNamespaces() returns the namespaces of a specific type in a slice instead of a map, sorted numerically by the namespace identifiers (that is, sorting by inode numbers, ignoring dev IDs at this time).
Technically, these namespace identifiers are tuples consisting of 64bit unsigned inode numbers and (~64bit) device ID numbers, and come from the special “nsfs” namespace filesystem integrated with the Linux kernel. And before someone tries: nope, the nsfs cannot be mounted; and it even does not appear in the kernel's list of namespaces.
Unprivileged Discovery and How To Not Panic ¶
While it is possible to discover namespaces without root privileges, this won't return the full set of namespaces in a Linux host. The reason is that while an unprivileged discovery is allowed to see some basic information about all processes in the system, it is not allowed to query the namespaces such privileged processes are joined too. In addition, an unprivileged discovery may turn up namespaces (for instance, when bind-mounted) for which the identifier is discovered, but further information, such as the parent or child namespaces for PID and user namespaces, is undiscoverable.
Users of the lxkns information model thus must be prepared to handle incomplete information yielded by unprivileged discover.Namespaces calls. In particular, applications must be prepared to handle:
- more than a single "initial" namespace per type of namespace,
- PID and user namespaces without a parent namespace,
- namespaces without owning user namespaces,
- processes not related to any namespace.
In consequence, always check interface values and pointers for nil values like a pro. You can find many examples in the sources for the "lsuns", "lspidns", and "pidtree" CLI tools (inside the cmd sub-package).
In-Capabilities ¶
It is possible to run full discoveries without being root, when given the discovery process the following effective capabilities:
- CAP_SYS_PTRACE – no joking here, that's what needed for reading namespace references from /proc/[PID]/ns/*
- CAP_SYS_CHROOT – for mount namespace switching
- CAP_SYS_ADMIN – for mount namespace switching
- CAP_DAC_READ_SEARCH – for reading details of bind-mounted namespaces
Considering that especially CAP_SYS_PTRACE being essential there's probably not much difference to “just be root” in the end, unless you want show off your “capabilities capabilities”.
Namespace Hierarchies ¶
PID and user namespaces form separate and independent namespaces hierarchies. This parent-child hierarchy is exposed through the model.Hierarchy interface of the discovered namespaces.
Please note that lxkns represents namespaces often using the model.Namespace interface when the specific type of namespace doesn't matter. In case of PID and user-type namespaces a model.Namespace can be “converted” into an interface value of type model.Hierarchy using a type assertion, in order to access the particular namespace hierarchy.
// If it's a PID or user namespace, then we can turn a "Namespace" // into an "Hierarchy" in order to access hierarchy information. if hns, ok := ns.(model.Hierarchy); ok { if hns.Parent() != nil { ... } for _, childns := range hns.Children() { ... } }
Ownership ¶
User namespaces play the central role in controlling the access of processes to other namespaces as well as the capabilities process gain when allowed to join user namespaces. A comprehensive discussion of the rules and their ramifications is beyond this package documentation. For starters, please refer to the man page for user_namespaces(7).
The controlling role of user namespaces show up in the discovery information model as owner-owneds relationships: user namespaces own non-user namespaces. And non-user namespaces are owned by user namespaces, the “ownings”. In case you are now scratching your head “why the Gopher” the owned namespaces are referred to as “ownings”: welcome to the wonderful Gopher world of “er”-ers, where interface method naming conventions create wonderful identifier art.
If a namespace interface value represents a user-type namespace, then it can be “converted” into an model.Ownership interface value using a type assertion. This interface discloses which namespaces are owned by a particular user namespace. Please note that this does not include child user namespaces, use [Hierarchy.Children] instead.
// Get the user namespace -owned-> namespaces relationships. if owns, ok := ns.(model.Ownership); ok { for _, ownedns := range owns.Ownings() { ... } }
In the opposite direction, the owner of a namespace can be directly queried via the model.Namespace interface (again, only for non-user namespaces):
// Get the namespace -owned by-> user namespace relationship. ownerns := ns.Owner()
When asking a user namespace for its owner, the parent user namespace is returned in accordance with the Linux ioctl()s for discovering the ownership of namespaces.
Namespaces and Processes ¶
The lxkns discovery information model also relates processes to namespaces, and vice versa. After all, processes are probably the main source for discovering namespaces.
For this reason, the discovery results (in “allns” in case of the above discovery code example) not only list the namespaces found, but also a snapshot of the process tree at discovery time (please relax now, as this is a snapshot of the “tree”, not of all the processes themselves).
// Get the init(1) process representation. initprocess := allns.Processes[model.PIDType(1)] for _, childprocess := range initprocess.Children() { ... }
Please note that the process tree information is for convenience; it's not a replacement for the famous gopsutil package in many use cases. However, the process tree information show which namespaces are used by (or “joined by”) which particular processes.
// Show all namespaces joined by a specific process, such as init(1). for nsidx := model.MountNS; nsidx < model.NamespaceTypesCount; nsidx++ { println(initprocess.Namespaces[nsidx].String()) }
It's also possible, given a specific namespace, to find the processes joined to this namespace. However, the lxkns information model optimizes this relationship information on the assumption that in many situations not the list of all processes joined to a namespace is needed, but actually only the so-called “leader” process or processes.
A leader process of namespace X is the process topmost in the process tree hierarchy of processes joined to namespace X. It is perfectly valid for a namespace to have more than one leader process joined to it. An example is a container with its own processes joined to the container namespaces, and an additional “visiting” process also joined to one or several namespaces of this container. The lxkns information then is able to correctly handle and represent such system states.
// Show the leader processes joined to the initial user namespace. for _, leaders := range initprocess.Namespaces[model.UserNS].Leaders() { ... }
Architecture ¶
Please find more details about the lxkns information model in the architectural section of the lxkns manual.
Index ¶
- func NewPIDMap(result *Result) model.PIDMapper
- func NewlyProcfsPathIsBetter(newly, known model.NamespaceRef, processes model.ProcessTable) bool
- func PIDfromPath(path string) model.PIDType
- func SortChildNamespaces(nslist []model.Hierarchy) []model.Hierarchy
- func SortNamespaces(nslist []model.Namespace) []model.Namespace
- func SortedNamespaces(nsmap model.NamespaceMap) []model.Namespace
- type BindmountedNamespaceInfo
- type DiscoverOpts
- type DiscoveryOption
- func FromBindmounts() DiscoveryOption
- func FromFds() DiscoveryOption
- func FromProcs() DiscoveryOption
- func FromTasks() DiscoveryOption
- func NotFromBindmounts() DiscoveryOption
- func NotFromFds() DiscoveryOption
- func NotFromProcs() DiscoveryOption
- func NotFromTasks() DiscoveryOption
- func SameAs(r *Result) DiscoveryOption
- func WithAffinityAndScheduling() DiscoveryOption
- func WithContainerizer(c containerizer.Containerizer) DiscoveryOption
- func WithFullDiscovery() DiscoveryOption
- func WithHierarchy() DiscoveryOption
- func WithLabel(key, value string) DiscoveryOption
- func WithLabels(labels map[string]string) DiscoveryOption
- func WithMounts() DiscoveryOption
- func WithNamespaceTypes(t species.NamespaceType) DiscoveryOption
- func WithOwnership() DiscoveryOption
- func WithPIDMapper() DiscoveryOption
- func WithSocketProcesses() DiscoveryOption
- func WithStandardDiscovery() DiscoveryOption
- func WithoutAffinityAndScheduling() DiscoveryOption
- func WithoutHierarchy() DiscoveryOption
- func WithoutMounts() DiscoveryOption
- func WithoutOwnership() DiscoveryOption
- func WithoutSocketProcesses() DiscoveryOption
- type NamespacedMountPathMap
- type Result
- type SocketProcesses
- type UidUsernameMap
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewPIDMap ¶
NewPIDMap returns a new PID map (model.PIDMapper) based on the specified discovery results and further information gathered from the /proc filesystem.
func NewlyProcfsPathIsBetter ¶ added in v0.23.0
func NewlyProcfsPathIsBetter(newly, known model.NamespaceRef, processes model.ProcessTable) bool
NewlyProcfsPathIsBetter returns true if the passed reference path for a "newly" found namespace reference is "better" than an already known one for that particular namespace. "Better" here is defined as the newly reference belonging to a process process that is older than the process with the already known namespace reference AND all (newly and known) reference elements except for the first one match each other. In all other cases, NewlyProcfsPathIsBetter returns false; in particular, when one of the namespace references isn't from a process.
func PIDfromPath ¶ added in v0.23.0
PIDfromPath returns the PID embedded in a /proc/$PID/... path, or 0 if the path doesn't contain a /proc/$PID.
func SortChildNamespaces ¶
SortChildNamespaces returns a sorted copy of a list of hierarchical namespaces. The namespaces are sorted by their namespace ids in ascending order. Please note that the list itself is flat, but this function can only be used on hierarchical namespaces (PID, user).
func SortNamespaces ¶
SortNamespaces returns a sorted copy of a list of namespaces. The namespaces are sorted by their namespace ids in ascending order.
func SortedNamespaces ¶
func SortedNamespaces(nsmap model.NamespaceMap) []model.Namespace
SortedNamespaces returns the namespaces from a map sorted by their namespace identifiers (inode numbers).
Types ¶
type BindmountedNamespaceInfo ¶
type BindmountedNamespaceInfo struct { ID species.NamespaceID Type species.NamespaceType Ref model.NamespaceRef OwnernsID species.NamespaceID }
BindmountedNamespaceInfo describes a bind-mounted namespace in some (other) mount namespace, including the owning user namespace ID, so we can later correctly set up the ownership relations in the discovery results.
type DiscoverOpts ¶
type DiscoverOpts struct { // The types of namespaces discovered: this is an OR'ed combination of Linux // kernel namespace constants, such as CLONE_NEWNS, CLONE_NEWNET, et cetera. // If zero, defaults to discovering all namespaces. NamespaceTypes species.NamespaceType `json:"-"` ScanProcs bool `json:"from-procs"` // Scan processes for attached namespaces. ScanTasks bool `json:"from-tasks"` // Scan all tasks for attached namespaces. ScanFds bool `json:"from-fds"` // Scan open file descriptors for namespaces. ScanBindmounts bool `json:"from-bindmounts"` // Scan bind-mounts for namespaces. DiscoverHierarchy bool `json:"with-hierarchy"` // Discover the hierarchy of PID and user namespaces. DiscoverOwnership bool `json:"with-ownership"` // Discover the ownership of non-user namespaces. DiscoverFreezerState bool `json:"with-freezer"` // Discover the cgroup freezer state of processes. DiscoverMounts bool `json:"with-mounts"` // Discover mount point hierarchy with mount paths and visibility. DiscoverSocketProcesses bool `json:"with-socket-processes"` // Discover the processes related to specific socket inode numbers. DiscoverAffinityScheduling bool `json:"with-affinity-scheduling"` // Disover CPU affinity and scheduling of leader processes. Labels map[string]string `json:"labels"` // Pass options (in form of labels) to decorators Containerizer containerizer.Containerizer `json:"-"` // Discover containers using containerizer. // contains filtered or unexported fields }
DiscoverOpts provides information about the extent of a Linux-kernel namespace discovery.
This information is JSON-marshallable, with the exception of the containerizer.Containerizer interface.
type DiscoveryOption ¶
type DiscoveryOption func(*DiscoverOpts)
DiscoveryOption represents a function able to set a particular discovery option state in DiscoverOpts.
func FromBindmounts ¶
func FromBindmounts() DiscoveryOption
FromBindmounts opts to find bind-mounted namespaces.
func FromFds ¶
func FromFds() DiscoveryOption
FromFds opts to find namespaces from the open file descriptors of processes.
func FromProcs ¶
func FromProcs() DiscoveryOption
FromProcs opts to find namespaces attached to processes.
func FromTasks ¶ added in v0.26.0
func FromTasks() DiscoveryOption
FromTasks opts to find namespaces attached to tasks (as opposed to processes). FromTask implies FromProcs.
func NotFromBindmounts ¶
func NotFromBindmounts() DiscoveryOption
NotFromBindmounts opts out from searching for bind-mounted namespaces.
func NotFromFds ¶
func NotFromFds() DiscoveryOption
NotFromFds opts out looking at the open file descriptors of processes when searching for namespaces.
func NotFromProcs ¶
func NotFromProcs() DiscoveryOption
NotFromProcs opts out of looking at processes when searching for namespaces.
func NotFromTasks ¶ added in v0.26.0
func NotFromTasks() DiscoveryOption
NotFromTasks opts out of looking at tasks when searching for namespaces. This does not include NotFromProcs, so a full opt-out should specify both.
func SameAs ¶
func SameAs(r *Result) DiscoveryOption
SameAs reuses the discovery options used for a previous discovery.
func WithAffinityAndScheduling ¶ added in v0.35.0
func WithAffinityAndScheduling() DiscoveryOption
func WithContainerizer ¶
func WithContainerizer(c containerizer.Containerizer) DiscoveryOption
WithContainerizer opts for discovery of containers related to namespaces, using the specified Containerizer. Depending on your system configuration you might want to additionally use WithPIDMapper in order to support containers in containers.
func WithFullDiscovery ¶
func WithFullDiscovery() DiscoveryOption
WithFullDiscovery opts in to all discovery features that lxkns has to offer. Please note that API users still need to set an optional containerizer.Containerizer explicitly using WithContainerizer.
func WithHierarchy ¶
func WithHierarchy() DiscoveryOption
WithHierarchy opts to query the namespace hierarchy of PID and user namespaces.
func WithLabel ¶
func WithLabel(key, value string) DiscoveryOption
WithLabel adds a key-value pair to the discovery options.
func WithLabels ¶
func WithLabels(labels map[string]string) DiscoveryOption
WithLabels adds a map of key-value pair to the discovery options.
func WithMounts ¶
func WithMounts() DiscoveryOption
WithMounts opts to find mount points and determine their visibility.
func WithNamespaceTypes ¶
func WithNamespaceTypes(t species.NamespaceType) DiscoveryOption
WithNamespaceTypes sets the types of namespaces to discover, where multiple types need to be OR'ed together. Setting 0 will discover all available types.
func WithOwnership ¶
func WithOwnership() DiscoveryOption
WithOwnership opts to find the ownership relations between user namespaces and all other namespaces.
func WithPIDMapper ¶
func WithPIDMapper() DiscoveryOption
WithPIDMapper opts in to discover the PID mapping between PID namespaces. In order to correctly map container PIDs of containers inside another container, API users need to enable this option. It defaults to off due to the additional system load it causes when scanning all processes for their PID namespace-related information and building the full translation map.
func WithSocketProcesses ¶ added in v0.34.0
func WithSocketProcesses() DiscoveryOption
WithSocketProcesses opts to find the relationship between socket inode numbers and process PIDs.
func WithStandardDiscovery ¶
func WithStandardDiscovery() DiscoveryOption
WithStandardDiscovery opts for a "standard" discovery, scanning not only processes, but also open file descriptors and bind-mounts, as well as the namespace hierarchy and ownership, and freezer states. All types of namespaces will be discovered. Please note that time namespaces can only be discovered on newer kernels with support for them.
Tasks will not be scanned, except for the task group leader that represents the process.
Please note that mount point discovery (including visibility calculation) is not automatically opted in; it has to be opted in individually.
func WithoutAffinityAndScheduling ¶ added in v0.35.0
func WithoutAffinityAndScheduling() DiscoveryOption
func WithoutHierarchy ¶
func WithoutHierarchy() DiscoveryOption
WithoutHierarchy opts out of querying the namespace hierarchy of PID and user namespaces.
func WithoutMounts ¶
func WithoutMounts() DiscoveryOption
WithoutMounts opts out of finding mount points and determining their visibility.
func WithoutOwnership ¶
func WithoutOwnership() DiscoveryOption
WithoutOwnership opts out of looking for the ownership relations between user namespaces and all other namespaces.
func WithoutSocketProcesses ¶ added in v0.34.0
func WithoutSocketProcesses() DiscoveryOption
WithoutSocketProcesses opts out of finding the relationship between socket inode numbers and process PIDs.
type NamespacedMountPathMap ¶
type NamespacedMountPathMap map[species.NamespaceID]mounts.MountPathMap
NamespacedMountPathMap maps mount namespaces identified by their namespace ID to their corresponding mount path maps.
type Result ¶
type Result struct { Options DiscoverOpts // options used during discovery. Namespaces model.AllNamespaces // all discovered namespaces, subject to filtering according to Options. InitialNamespaces model.NamespacesSet // the 7 initial namespaces. UserNSRoots []model.Namespace // the topmost user namespace(s) in the hierarchy. PIDNSRoots []model.Namespace // the topmost PID namespace(s) in the hierarchy. Processes model.ProcessTable // processes checked for namespaces. PIDMap model.PIDMapper `json:"-"` // optional PID translator. Mounts NamespacedMountPathMap // per mount-namespace mount paths and mount points. Containers model.Containers // all alive containers found SocketProcessMap SocketProcesses // optional socket inode number to process(es) mapping }
Result stores the results of a tour through Linux processes and kernel namespaces.
func Namespaces ¶
func Namespaces(options ...DiscoveryOption) *Result
Namespaces returns the Linux kernel namespaces found, based on discovery options specified in the call. It is allowed to pass nil discovery options to allow more concise code without the need for lots of “if”s. The discovery results also specify the initial namespaces, as well the process table/tree on which the discovery bases at least in part.
func (*Result) SortedNamespaces ¶
func (dr *Result) SortedNamespaces(nsidx model.NamespaceTypeIndex) []model.Namespace
SortedNamespaces returns a sorted list of discovered namespaces of the specified type. The namespaces are sorted by their identifier, which is an inode number (on the special "nsfs" filesystem), ignoring a namespace's device ID.
type SocketProcesses ¶ added in v0.34.0
SocketProcesses maps socket inode numbers to processes that have open file descriptors for specific sockets.
As it turned out over time, there are multiple lxkns API users that otherwise repeatedly scan the open file descriptors of processes for sockets in order to gather their inode numbers, so in the sense of DRY we offer this information with a single scan we need to do anyway in case discovering network namespaces from sockets.
type UidUsernameMap ¶
UidUsernameMap maps user identifiers (uids) to their corresponding user names, if any.
func DiscoverUserNames ¶
func DiscoverUserNames(namespaces model.AllNamespaces) UidUsernameMap
DiscoverUserNames returns the mapping from user identifiers (uids, found as owners of user namespaces) to their corresponding user names, if any. The namespaces information is required so that the information can be discovered from the initial mount namespace of the host.