github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/sd/hostsocket/net.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package hostsocket 4 5 import ( 6 "bufio" 7 "bytes" 8 "context" 9 "errors" 10 "fmt" 11 "log/slog" 12 "os" 13 "os/exec" 14 "path/filepath" 15 "strings" 16 "time" 17 18 "github.com/netdata/go.d.plugin/agent/discovery/sd/model" 19 "github.com/netdata/go.d.plugin/logger" 20 21 "github.com/ilyam8/hashstructure" 22 ) 23 24 type netSocketTargetGroup struct { 25 provider string 26 source string 27 targets []model.Target 28 } 29 30 func (g *netSocketTargetGroup) Provider() string { return g.provider } 31 func (g *netSocketTargetGroup) Source() string { return g.source } 32 func (g *netSocketTargetGroup) Targets() []model.Target { return g.targets } 33 34 type NetSocketTarget struct { 35 model.Base 36 37 hash uint64 38 39 Protocol string 40 Address string 41 Port string 42 Comm string 43 Cmdline string 44 } 45 46 func (t *NetSocketTarget) TUID() string { return t.tuid() } 47 func (t *NetSocketTarget) Hash() uint64 { return t.hash } 48 func (t *NetSocketTarget) tuid() string { 49 return fmt.Sprintf("%s_%s_%d", strings.ToLower(t.Protocol), t.Port, t.hash) 50 } 51 52 func NewNetSocketDiscoverer(cfg NetworkSocketConfig) (*NetDiscoverer, error) { 53 tags, err := model.ParseTags(cfg.Tags) 54 if err != nil { 55 return nil, fmt.Errorf("parse tags: %v", err) 56 } 57 58 dir := os.Getenv("NETDATA_PLUGINS_DIR") 59 if dir == "" { 60 dir, _ = os.Getwd() 61 } 62 63 d := &NetDiscoverer{ 64 Logger: logger.New().With( 65 slog.String("component", "discovery sd hostsocket"), 66 ), 67 interval: time.Second * 60, 68 ll: &localListenersExec{ 69 binPath: filepath.Join(dir, "local-listeners"), 70 timeout: time.Second * 5, 71 }, 72 } 73 d.Tags().Merge(tags) 74 75 return d, nil 76 } 77 78 type ( 79 NetDiscoverer struct { 80 *logger.Logger 81 model.Base 82 83 interval time.Duration 84 ll localListeners 85 } 86 localListeners interface { 87 discover(ctx context.Context) ([]byte, error) 88 } 89 ) 90 91 func (d *NetDiscoverer) Discover(ctx context.Context, in chan<- []model.TargetGroup) { 92 if err := d.discoverLocalListeners(ctx, in); err != nil { 93 d.Error(err) 94 return 95 } 96 97 tk := time.NewTicker(d.interval) 98 defer tk.Stop() 99 100 for { 101 select { 102 case <-ctx.Done(): 103 return 104 case <-tk.C: 105 if err := d.discoverLocalListeners(ctx, in); err != nil { 106 d.Error(err) 107 return 108 } 109 } 110 } 111 } 112 113 func (d *NetDiscoverer) discoverLocalListeners(ctx context.Context, in chan<- []model.TargetGroup) error { 114 bs, err := d.ll.discover(ctx) 115 if err != nil { 116 if errors.Is(err, context.Canceled) { 117 return nil 118 } 119 return err 120 } 121 122 tggs, err := d.parseLocalListeners(bs) 123 if err != nil { 124 return err 125 } 126 127 select { 128 case <-ctx.Done(): 129 case in <- tggs: 130 } 131 return nil 132 } 133 134 func (d *NetDiscoverer) parseLocalListeners(bs []byte) ([]model.TargetGroup, error) { 135 var tgts []model.Target 136 137 sc := bufio.NewScanner(bytes.NewReader(bs)) 138 for sc.Scan() { 139 text := strings.TrimSpace(sc.Text()) 140 if text == "" { 141 continue 142 } 143 144 // Protocol|Address|Port|Cmdline 145 parts := strings.SplitN(text, "|", 4) 146 if len(parts) != 4 { 147 return nil, fmt.Errorf("unexpected data: '%s'", text) 148 } 149 150 tgt := NetSocketTarget{ 151 Protocol: parts[0], 152 Address: parts[1], 153 Port: parts[2], 154 Comm: extractComm(parts[3]), 155 Cmdline: parts[3], 156 } 157 158 hash, err := calcHash(tgt) 159 if err != nil { 160 continue 161 } 162 163 tgt.hash = hash 164 tgt.Tags().Merge(d.Tags()) 165 166 tgts = append(tgts, &tgt) 167 } 168 169 tgg := &netSocketTargetGroup{ 170 provider: "hostsocket", 171 source: "net", 172 targets: tgts, 173 } 174 175 return []model.TargetGroup{tgg}, nil 176 } 177 178 type localListenersExec struct { 179 binPath string 180 timeout time.Duration 181 } 182 183 func (e *localListenersExec) discover(ctx context.Context) ([]byte, error) { 184 execCtx, cancel := context.WithTimeout(ctx, e.timeout) 185 defer cancel() 186 187 cmd := exec.CommandContext(execCtx, e.binPath, "tcp") // TODO: tcp6? 188 189 bs, err := cmd.Output() 190 if err != nil { 191 return nil, fmt.Errorf("error on '%s': %v", cmd, err) 192 } 193 194 return bs, nil 195 } 196 197 func extractComm(s string) string { 198 i := strings.IndexByte(s, ' ') 199 if i <= 0 { 200 return "" 201 } 202 _, comm := filepath.Split(s[:i]) 203 return comm 204 } 205 206 func calcHash(obj any) (uint64, error) { 207 return hashstructure.Hash(obj, nil) 208 }