diff --git a/checkpointctl.go b/checkpointctl.go index cd64d07c..1ffa511b 100644 --- a/checkpointctl.go +++ b/checkpointctl.go @@ -23,6 +23,7 @@ var ( psTreeCmd bool psTreeEnv bool files bool + sockets bool showAll bool ) @@ -127,6 +128,12 @@ func setupInspect() *cobra.Command { false, "Display the open file descriptors for processes in the container checkpoint", ) + flags.BoolVar( + &sockets, + "sockets", + false, + "Display the open sockets for processes in the container checkpoint", + ) flags.BoolVar( &showAll, "all", @@ -150,6 +157,7 @@ func inspect(cmd *cobra.Command, args []string) error { psTreeCmd = true psTreeEnv = true files = true + sockets = true } requiredFiles := []string{metadata.SpecDumpFile, metadata.ConfigDumpFile} @@ -170,12 +178,24 @@ func inspect(cmd *cobra.Command, args []string) error { psTree = true requiredFiles = append( requiredFiles, + // Unpack files.img, fs-*.img, ids-*.img, fdinfo-*.img filepath.Join(metadata.CheckpointDirectory, "files.img"), - // fs-*.img filepath.Join(metadata.CheckpointDirectory, "fs-"), - // ids-*.img filepath.Join(metadata.CheckpointDirectory, "ids-"), - // fdinfo-*.img + filepath.Join(metadata.CheckpointDirectory, "fdinfo-"), + ) + } + + if sockets { + // Enable displaying process tree, even if it is not passed. + // This is necessary to attach the sockets to the processes + // that opened them and display this in the tree. + psTree = true + requiredFiles = append( + requiredFiles, + // Unpack files.img, ids-*.img, fdinfo-*.img + filepath.Join(metadata.CheckpointDirectory, "files.img"), + filepath.Join(metadata.CheckpointDirectory, "ids-"), filepath.Join(metadata.CheckpointDirectory, "fdinfo-"), ) } @@ -195,8 +215,8 @@ func inspect(cmd *cobra.Command, args []string) error { if psTree { requiredFiles = append( requiredFiles, + // Unpack pstree.img, core-*.img filepath.Join(metadata.CheckpointDirectory, "pstree.img"), - // core-*.img filepath.Join(metadata.CheckpointDirectory, "core-"), ) } diff --git a/test/checkpointctl.bats b/test/checkpointctl.bats index d77cd3a5..a83a11ed 100644 --- a/test/checkpointctl.bats +++ b/test/checkpointctl.bats @@ -376,6 +376,32 @@ function teardown() { [[ ${lines[0]} == *"failed to get file descriptors"* ]] } +@test "Run checkpointctl inspect with tar file and --sockets" { + cp data/config.dump \ + data/spec.dump "$TEST_TMP_DIR1" + mkdir "$TEST_TMP_DIR1"/checkpoint + cp test-imgs/pstree.img \ + test-imgs/core-*.img \ + test-imgs/files.img \ + test-imgs/ids-*.img \ + test-imgs/fdinfo-*.img "$TEST_TMP_DIR1"/checkpoint + ( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . ) + checkpointctl inspect "$TEST_TMP_DIR2"/test.tar --sockets + [ "$status" -eq 0 ] +} + +@test "Run checkpointctl inspect with tar file and --sockets and missing files.img" { + cp data/config.dump \ + data/spec.dump "$TEST_TMP_DIR1" + mkdir "$TEST_TMP_DIR1"/checkpoint + cp test-imgs/pstree.img \ + test-imgs/core-*.img "$TEST_TMP_DIR1"/checkpoint + ( cd "$TEST_TMP_DIR1" && tar cf "$TEST_TMP_DIR2"/test.tar . ) + checkpointctl inspect "$TEST_TMP_DIR2"/test.tar --sockets + [ "$status" -eq 1 ] + [[ ${lines[0]} == *"failed to get sockets"* ]] +} + @test "Run checkpointctl inspect with tar file and --ps-tree and valid PID" { cp data/config.dump \ data/spec.dump "$TEST_TMP_DIR1" diff --git a/tree.go b/tree.go index 8fdc8c02..25a46c7a 100644 --- a/tree.go +++ b/tree.go @@ -58,6 +58,16 @@ func renderTreeView(tasks []task) error { addFdsToTree(tree, fds) } + if sockets { + c := crit.New(nil, nil, filepath.Join(task.outputDir, "checkpoint"), false, false) + fds, err := c.ExploreSk() + if err != nil { + return fmt.Errorf("failed to get sockets: %w", err) + } + + addSkToTree(tree, fds) + } + if mounts { addMountsToTree(tree, info.specDump) } @@ -181,6 +191,54 @@ func addFdsToTree(tree treeprint.Tree, fds []*crit.Fd) { } } +func addSkToTree(tree treeprint.Tree, sks []*crit.Sk) { + var node treeprint.Tree + for _, sk := range sks { + node = tree.FindByMeta(sk.PId) + // If FindByMeta returns nil, then the node with + // the PID has been pruned while building the tree. + // Hence, skip all associated sockets. + if node == nil { + continue + } + + nodeSubtree := node.AddBranch("Open sockets") + var data string + var protocol string + for _, socket := range sk.Sockets { + protocol = socket.Protocol + switch socket.FdType { + case "UNIXSK": + // UNIX sockets do not have a protocol assigned. + // Hence, the meta value for the socket is just + // the socket type. + protocol = fmt.Sprintf("UNIX (%s)", socket.Type) + data = socket.SrcAddr + if len(data) == 0 { + // Use an abstract socket address + data = "@" + } + case "INETSK": + if protocol == "TCP" { + protocol = fmt.Sprintf("%s (%s)", socket.Protocol, socket.State) + } + data = fmt.Sprintf( + "%s:%d -> %s:%d (↑ %s ↓ %s)", + socket.SrcAddr, socket.SrcPort, + socket.DestAddr, socket.DestPort, + socket.SendBuf, socket.RecvBuf, + ) + case "PACKETSK": + data = fmt.Sprintf("↑ %s ↓ %s", socket.SendBuf, socket.RecvBuf) + case "NETLINKSK": + data = fmt.Sprintf("↑ %s ↓ %s", socket.SendBuf, socket.RecvBuf) + } + + nodeSubtree.AddMetaBranch(protocol, data) + } + } +} + // Recursively updates the Comm field of the psTree struct with the command line arguments // from process memory pages func updatePsTreeCommToCmdline(checkpointOutputDir string, psTree *crit.PsTree) error {