namespaces.go 4.52 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// namespaces provides Linux namespace introspection for CRIU checkpoint.
package checkpoint

import (
	"fmt"
	"os"
	"strings"

	"golang.org/x/sys/unix"
)

// NamespaceType represents a Linux namespace type
type NamespaceType string

const (
	NamespaceNet    NamespaceType = "net"
	NamespacePID    NamespaceType = "pid"
	NamespaceMnt    NamespaceType = "mnt"
	NamespaceUTS    NamespaceType = "uts"
	NamespaceIPC    NamespaceType = "ipc"
	NamespaceUser   NamespaceType = "user"
	NamespaceCgroup NamespaceType = "cgroup"
)

// NamespaceInfo holds namespace identification information
type NamespaceInfo struct {
	Type       NamespaceType
	Inode      uint64
	Path       string
	IsExternal bool // Whether NS is external (shared with pause container)
}

// GetNamespaceInode returns the inode number for a namespace
func GetNamespaceInode(pid int, nsType NamespaceType, hostProc string) (uint64, error) {
	if hostProc == "" {
		hostProc = "/proc"
	}

	nsPath := fmt.Sprintf("%s/%d/ns/%s", hostProc, pid, nsType)
	var stat unix.Stat_t
	if err := unix.Stat(nsPath, &stat); err != nil {
		return 0, fmt.Errorf("failed to stat namespace %s: %w", nsPath, err)
	}

	return stat.Ino, nil
}

// GetNamespaceInfo returns detailed namespace information
func GetNamespaceInfo(pid int, nsType NamespaceType, hostProc string) (*NamespaceInfo, error) {
	if hostProc == "" {
		hostProc = "/proc"
	}

	nsPath := fmt.Sprintf("%s/%d/ns/%s", hostProc, pid, nsType)

	// Get inode
	var stat unix.Stat_t
	if err := unix.Stat(nsPath, &stat); err != nil {
		return nil, fmt.Errorf("failed to stat namespace %s: %w", nsPath, err)
	}

	// Read the symlink to get the namespace identifier
	link, err := os.Readlink(nsPath)
	if err != nil {
		return nil, fmt.Errorf("failed to readlink %s: %w", nsPath, err)
	}

	// Check if this is different from init's namespace (PID 1)
	initNsPath := fmt.Sprintf("%s/1/ns/%s", hostProc, nsType)
	var initStat unix.Stat_t
	isExternal := false
	if err := unix.Stat(initNsPath, &initStat); err == nil {
		// If the inode is different from init's, it's an external namespace
		isExternal = stat.Ino != initStat.Ino
	}

	return &NamespaceInfo{
		Type:       nsType,
		Inode:      stat.Ino,
		Path:       link,
		IsExternal: isExternal,
	}, nil
}

// GetAllNamespaces returns information about all namespaces for a process
func GetAllNamespaces(pid int, hostProc string) (map[NamespaceType]*NamespaceInfo, error) {
	nsTypes := []NamespaceType{
		NamespaceNet,
		NamespacePID,
		NamespaceMnt,
		NamespaceUTS,
		NamespaceIPC,
		NamespaceUser,
		NamespaceCgroup,
	}

	namespaces := make(map[NamespaceType]*NamespaceInfo)
	for _, nsType := range nsTypes {
		info, err := GetNamespaceInfo(pid, nsType, hostProc)
		if err != nil {
			// Some namespaces might not exist, skip them
			continue
		}
		namespaces[nsType] = info
	}

	return namespaces, nil
}

// IsNetNamespaceExternal checks if the network namespace is external
// (i.e., shared with the pause container in Kubernetes)
func IsNetNamespaceExternal(pid int, hostProc string) (bool, uint64, error) {
	info, err := GetNamespaceInfo(pid, NamespaceNet, hostProc)
	if err != nil {
		return false, 0, err
	}
	return info.IsExternal, info.Inode, nil
}

// IsPIDNamespaceExternal checks if the PID namespace is external
func IsPIDNamespaceExternal(pid int, hostProc string) (bool, uint64, error) {
	info, err := GetNamespaceInfo(pid, NamespacePID, hostProc)
	if err != nil {
		return false, 0, err
	}
	return info.IsExternal, info.Inode, nil
}

// OpenNamespaceFD opens a file descriptor to a namespace
// The caller is responsible for closing the returned file
func OpenNamespaceFD(pid int, nsType NamespaceType, hostProc string) (*os.File, error) {
	if hostProc == "" {
		hostProc = "/proc"
	}

	nsPath := fmt.Sprintf("%s/%d/ns/%s", hostProc, pid, nsType)
	return os.Open(nsPath)
}

// FormatExternalNamespace formats namespace info for CRIU's External option
// Format: <type>[<inode>]:<key>
func FormatExternalNamespace(nsType NamespaceType, inode uint64) string {
	key := formatNamespaceKey(nsType)
	return fmt.Sprintf("%s[%d]:%s", nsType, inode, key)
}

// formatNamespaceKey creates the CRIU key for external namespaces
// Format: extRoot<Type>NS (e.g., extRootNetNS, extRootPidNS)
func formatNamespaceKey(nsType NamespaceType) string {
	// Capitalize first letter of namespace type
	nsName := string(nsType)
	if len(nsName) > 0 {
		nsName = strings.ToUpper(nsName[:1]) + nsName[1:]
	}
	return "extRoot" + nsName + "NS"
}

// GetNamespaceKey returns the CRIU key for a namespace type
func GetNamespaceKey(nsType NamespaceType) string {
	return formatNamespaceKey(nsType)
}