From 8de68f184f1e3e18d61126e6112b67b1dae4e09a Mon Sep 17 00:00:00 2001 From: Prajwal S N Date: Tue, 18 Jul 2023 18:17:09 +0530 Subject: [PATCH] feat(crit): add `crit x sk` for sockets Signed-off-by: Prajwal S N --- crit/cli/cli.go | 8 +-- crit/crit.go | 1 + crit/explore.go | 118 +++++++++++++++++++++++++++++++++++++++++ crit/utils.go | 114 ++++++++++++++++++++++++++++++++++++--- test/crit/crit-test.sh | 1 + 5 files changed, 232 insertions(+), 10 deletions(-) diff --git a/crit/cli/cli.go b/crit/cli/cli.go index 0fbc380d0..6781c8a57 100644 --- a/crit/cli/cli.go +++ b/crit/cli/cli.go @@ -220,9 +220,9 @@ var infoCmd = &cobra.Command{ // The `crit x` command var xCmd = &cobra.Command{ - Use: "x DIR {ps|fd|mem|rss}", + Use: "x DIR {ps|fd|mem|rss|sk}", Short: "Explore the image directory", - Long: "Explore the image directory with one of (ps, fd, mem, rss) options", + Long: "Explore the image directory with one of (ps, fd, mem, rss, sk) options", // Exactly two arguments are required: // * Path of the input directory // * Explore type @@ -248,8 +248,10 @@ var xCmd = &cobra.Command{ xData, err = c.ExploreMems() case "rss": xData, err = c.ExploreRss() + case "sk": + xData, err = c.ExploreSk() default: - err = errors.New("invalid explore type (supported: {ps|fd|mem|rss})") + err = errors.New("invalid explore type (supported: {ps|fd|mem|rss|sk})") } if err != nil { log.Fatal(fmt.Errorf("error exploring directory: %w", err)) diff --git a/crit/crit.go b/crit/crit.go index b8cdf6ff5..a68c7c49c 100644 --- a/crit/crit.go +++ b/crit/crit.go @@ -25,6 +25,7 @@ type Critter interface { ExploreFds() ([]*Fd, error) ExploreMems() ([]*MemMap, error) ExploreRss() ([]*RssMap, error) + ExploreSk() ([]*Sk, error) } // crit implements the Critter interface. It contains: diff --git a/crit/explore.go b/crit/explore.go index cd234e585..5afcb75b7 100644 --- a/crit/explore.go +++ b/crit/explore.go @@ -373,3 +373,121 @@ func (c *crit) ExploreRss() ([]*RssMap, error) { return rssMaps, nil } + +// Sk represents the sockets associated with a single process +type Sk struct { + PId uint32 `json:"pId"` + Sockets []*Socket `json:"sockets"` +} + +// Socket represents a single socket +type Socket struct { + Fd uint32 `json:"fd"` + Type string `json:"type"` + Family string `json:"family,omitempty"` + Protocol string `json:"protocol,omitempty"` + State string `json:"state,omitempty"` + SourceAddr string `json:"sourceAddr,omitempty"` + SourcePort uint32 `json:"sourcePort,omitempty"` + DestAddr string `json:"destAddr,omitempty"` + DestPort uint32 `json:"destPort,omitempty"` + SendBuf string `json:"sendBuf,omitempty"` + RecvBuf string `json:"recvBuf,omitempty"` +} + +// ExploreSk searches the process tree for sockets +// and returns a list of PIDs with the associated sockets +func (c *crit) ExploreSk() ([]*Sk, error) { + psTreeImg, err := getImg(filepath.Join(c.inputDirPath, "pstree.img"), &pstree.PstreeEntry{}) + if err != nil { + return nil, err + } + + sks := make([]*Sk, 0) + for _, entry := range psTreeImg.Entries { + process := entry.Message.(*pstree.PstreeEntry) + pID := process.GetPid() + // Get file with object IDs + idsImg, err := getImg(filepath.Join(c.inputDirPath, fmt.Sprintf("ids-%d.img", pID)), &criu_core.TaskKobjIdsEntry{}) + if err != nil { + return nil, err + } + filesID := idsImg.Entries[0].Message.(*criu_core.TaskKobjIdsEntry).GetFilesId() + // Get open file descriptors + fdInfoImg, err := getImg(filepath.Join(c.inputDirPath, fmt.Sprintf("fdinfo-%d.img", filesID)), &fdinfo.FdinfoEntry{}) + if err != nil { + return nil, err + } + skEntry := Sk{PId: pID} + for _, fdInfoEntry := range fdInfoImg.Entries { + fdInfo := fdInfoEntry.Message.(*fdinfo.FdinfoEntry) + socket := Socket{ + Fd: fdInfo.GetFd(), + Type: fdInfo.GetType().String(), + } + switch fdInfo.GetType() { + case fdinfo.FdTypes_INETSK: + file, err := getFile(c.inputDirPath, fdInfo.GetId()) + if err != nil { + return nil, err + } + if isk := file.GetIsk(); isk != nil { + socket.State = getSkState(isk.GetState()) + socket.Family = getSkFamily(isk.GetFamily()) + socket.Protocol = getSkProtocol(isk.GetType()) + socket.SourceAddr = processIP(isk.GetSrcAddr()) + socket.SourcePort = isk.GetSrcPort() + socket.DestAddr = processIP(isk.GetDstAddr()) + socket.DestPort = isk.GetDstPort() + socket.SendBuf = countBytes(int64(isk.GetOpts().GetSoSndbuf())) + socket.RecvBuf = countBytes(int64(isk.GetOpts().GetSoRcvbuf())) + } + case fdinfo.FdTypes_UNIXSK: + file, err := getFile(c.inputDirPath, fdInfo.GetId()) + if err != nil { + return nil, err + } + if usk := file.GetUsk(); usk != nil { + socket.State = getSkState(usk.GetState()) + socket.Protocol = getSkProtocol(usk.GetType()) + socket.SendBuf = countBytes(int64(usk.GetOpts().GetSoSndbuf())) + socket.RecvBuf = countBytes(int64(usk.GetOpts().GetSoRcvbuf())) + } + case fdinfo.FdTypes_PACKETSK: + file, err := getFile(c.inputDirPath, fdInfo.GetId()) + if err != nil { + return nil, err + } + if psk := file.GetPsk(); psk != nil { + socket.Protocol = getSkProtocol(psk.GetProtocol()) + socket.SendBuf = countBytes(int64(psk.GetOpts().GetSoSndbuf())) + socket.RecvBuf = countBytes(int64(psk.GetOpts().GetSoRcvbuf())) + } + case fdinfo.FdTypes_NETLINKSK: + file, err := getFile(c.inputDirPath, fdInfo.GetId()) + if err != nil { + return nil, err + } + if nlsk := file.GetNlsk(); nlsk != nil { + socket.State = getSkState(nlsk.GetState()) + socket.Protocol = getSkProtocol(nlsk.GetProtocol()) + socket.SendBuf = countBytes(int64(nlsk.GetOpts().GetSoSndbuf())) + socket.RecvBuf = countBytes(int64(nlsk.GetOpts().GetSoRcvbuf())) + } + default: + continue + } + + skEntry.Sockets = append(skEntry.Sockets, &socket) + } + + // Omit if the process has no associated sockets + if len(skEntry.Sockets) == 0 { + continue + } + + sks = append(sks, &skEntry) + } + + return sks, nil +} diff --git a/crit/utils.go b/crit/utils.go index 5fa817a43..e547aab1b 100644 --- a/crit/utils.go +++ b/crit/utils.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "github.com/checkpoint-restore/go-criu/v6/crit/images/fdinfo" "github.com/checkpoint-restore/go-criu/v6/crit/images/pipe" @@ -110,27 +111,37 @@ var ( filesImg, regImg, pipeImg, unixSkImg *CriuImage ) -// Helper to get file path for exploring file descriptors -func getFilePath(dir string, fID uint32, fType fdinfo.FdTypes) (string, error) { - var filePath string +// Helper to fetch a file if it exists in files.img +func getFile(dir string, fID uint32) (*fdinfo.FileEntry, error) { var err error - // Get open files if filesImg == nil { filesImg, err = getImg(filepath.Join(dir, "files.img"), &fdinfo.FileEntry{}) if err != nil { - return "", err + return nil, err } } - // Check if file entry is present var file *fdinfo.FileEntry for _, entry := range filesImg.Entries { file = entry.Message.(*fdinfo.FileEntry) if file.GetId() == fID { - break + return file, nil } } + return nil, nil +} + +// Helper to get file path for exploring file descriptors +func getFilePath(dir string, fID uint32, fType fdinfo.FdTypes) (string, error) { + var filePath string + var err error + // Fetch the file, if it exists in file.img + file, err := getFile(dir, fID) + if err != nil { + return "", err + } + switch fType { case fdinfo.FdTypes_REG: filePath, err = getRegFilePath(dir, file, fID) @@ -232,3 +243,92 @@ func getUnixSkFilePath(dir string, file *fdinfo.FileEntry, fID uint32) (string, return "unix[?]", nil } + +// Helper to convert slice of uint32 into IP address string +func processIP(parts []uint32) string { + // Create string slice from integer parts + partStrings := make([]string, 0, len(parts)) + for _, part := range parts { + partStrings = append(partStrings, strconv.FormatUint(uint64(part), 10)) + } + // IPv4 + if len(parts) == 4 { + return strings.Join(partStrings, ".") + } + // IPv6 + if len(parts) > 4 && len(parts) <= 8 { + return strings.Join(partStrings, ":") + } + return "unknown" +} + +// Helper to identify socket state +func getSkState(state uint32) string { + switch state { + case 0: + return "ESTABLISHED" + case 1: + return "SYN_SENT" + case 2: + return "SYN_RECV" + case 3: + return "FIN_WAIT1" + case 4: + return "FIN_WAIT2" + case 5: + return "TIME_WAIT" + case 6: + return "CLOSE" + case 7: + return "CLOSE_WAIT" + case 8: + return "LAST_ACK" + case 9: + return "LISTEN" + case 10: + return "CLOSING" + default: + // State cannot be empty, hence we use unknown + return "UNKNOWN" + } +} + +// Helper to identify socket family +func getSkFamily(family uint32) string { + switch family { + case 0: + return "UNIX" + case 1: + return "BRIDGE" + case 2: + return "KEY" + case 3: + return "PACKET" + default: + return "" + } +} + +// Helper to identify socket protocol +func getSkProtocol(protocol uint32) string { + switch protocol { + case 0: + return "IP" + case 1: + return "IGMP" + case 2: + return "TCP" + case 3: + return "UDP" + case 4: + return "IPV6" + case 5: + return "GRE" + case 6: + return "AH" + case 7: + return "RAW" + default: + return "" + } +} diff --git a/test/crit/crit-test.sh b/test/crit/crit-test.sh index 7fddde690..09c64ef94 100755 --- a/test/crit/crit-test.sh +++ b/test/crit/crit-test.sh @@ -76,6 +76,7 @@ function command_test { $CRIT x "$TEST_IMG_DIR" fd || exit 1 $CRIT x "$TEST_IMG_DIR" mem || exit 1 $CRIT x "$TEST_IMG_DIR" rss || exit 1 + $CRIT x "$TEST_IMG_DIR" sk || exit 1 } echo "Generating image list..."