// sockets recursively finds all unix sockets under the path provided func (r *Registry) sockets(path string) ([]string, error) { var ( result []string statT syscall.Stat_t ) // TODO: use of fs.Stat (which is syscall.Stat) here makes this linux specific. if err := fs.Stat(path, &statT); err != nil { return nil, err } switch statT.Mode & syscall.S_IFMT { case syscall.S_IFDIR: files, err := fs.ReadDir(path) if err != nil { return nil, err } for _, file := range files { fpath := filepath.Join(path, file.Name()) s, err := r.sockets(fpath) if err != nil { log.Warningf("plugins: error loading path %s: %v", fpath, err) } result = append(result, s...) } case syscall.S_IFSOCK: result = append(result, path) } return result, nil }
func (p dir) Stat(path string, stat *syscall.Stat_t) error { if path == "/" { *stat = syscall.Stat_t{Mode: syscall.S_IFDIR} return nil } head, tail := split(path) fs, ok := p.entries[head] if !ok { return fmt.Errorf("Not found: %s", path) } return fs.Stat(tail, stat) }
// walk walks over all numerical (PID) /proc entries. It reads // /proc/PID/net/tcp{,6} for each namespace and sees if the ./fd/* files of each // process in that namespace are symlinks to sockets. Returns a map from socket // ID (inode) to PID. func (w pidWalker) walk(buf *bytes.Buffer) (map[uint64]*Proc, error) { var ( sockets = map[uint64]*Proc{} // map socket inode -> process namespaces = map[uint64][]*process.Process{} // map network namespace id -> processes statT syscall.Stat_t ) // We do two process traversals: One to group processes by namespace and // another one to obtain their connections. // // The first traversal is needed to allow obtaining the connections on a // per-namespace basis. This is done to minimize the race condition // between reading /net/tcp{,6} of each namespace and /proc/PID/fd/* for // the processes living in that namespace. w.walker.Walk(func(p, _ process.Process) { dirName := strconv.Itoa(p.PID) netNamespacePath := filepath.Join(procRoot, dirName, getNetNamespacePathSuffix()) if err := fs.Stat(netNamespacePath, &statT); err != nil { return } namespaceID := statT.Ino namespaces[namespaceID] = append(namespaces[namespaceID], &p) }) for _, procs := range namespaces { select { case <-w.tickc: w.walkNamespace(buf, sockets, procs) case <-w.stopc: break // abort } } metrics.SetGauge(namespaceKey, float32(len(namespaces))) return sockets, nil }
// walkNamespace does the work of walk for a single namespace func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error { if found, err := readProcessConnections(buf, namespaceProcs); err != nil || !found { return err } var statT syscall.Stat_t var fdBlockCount uint64 for i, p := range namespaceProcs { // Get the sockets for all the processes in the namespace dirName := strconv.Itoa(p.PID) fdBase := filepath.Join(procRoot, dirName, "fd") if fdBlockCount > w.fdBlockSize { // we surpassed the filedescriptor rate limit select { case <-w.tickc: case <-w.stopc: return nil // abort } fdBlockCount = 0 // read the connections again to // avoid the race between between /net/tcp{,6} and /proc/PID/fd/* if found, err := readProcessConnections(buf, namespaceProcs[i:]); err != nil || !found { return err } } fds, err := fs.ReadDirNames(fdBase) if err != nil { // Process is gone by now, or we don't have access. continue } var proc *Proc for _, fd := range fds { fdBlockCount++ // Direct use of syscall.Stat() to save garbage. err = fs.Stat(filepath.Join(fdBase, fd), &statT) if err != nil { continue } // We want sockets only. if statT.Mode&syscall.S_IFMT != syscall.S_IFSOCK { continue } // Initialize proc lazily to avoid creating unnecessary // garbage if proc == nil { proc = &Proc{ PID: uint(p.PID), Name: p.Name, } } sockets[statT.Ino] = proc } } return nil }