Skip to content

Commit

Permalink
Add processor to look up service name by port (influxdata#7540)
Browse files Browse the repository at this point in the history
  • Loading branch information
reimda authored and jaecktec committed Jun 8, 2020
1 parent f507a9f commit ed02caa
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ test-windows:
go test -short ./plugins/inputs/win_services/...
go test -short ./plugins/inputs/procstat/...
go test -short ./plugins/inputs/ntpq/...
go test -short ./plugins/processors/port_name/...

.PHONY: vet
vet:
Expand Down
1 change: 1 addition & 0 deletions plugins/processors/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/processors/override"
_ "github.com/influxdata/telegraf/plugins/processors/parser"
_ "github.com/influxdata/telegraf/plugins/processors/pivot"
_ "github.com/influxdata/telegraf/plugins/processors/port_name"
_ "github.com/influxdata/telegraf/plugins/processors/printer"
_ "github.com/influxdata/telegraf/plugins/processors/regex"
_ "github.com/influxdata/telegraf/plugins/processors/rename"
Expand Down
26 changes: 26 additions & 0 deletions plugins/processors/port_name/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Port Name Lookup Processor Plugin

Use the `port_name` processor to convert a tag containing a well-known port number to the registered service name.

Tag can contain a number ("80") or number and protocol separated by slash ("443/tcp"). If protocol is not provided it defaults to tcp but can be changed with the default_protocol setting.

### Configuration

```toml
[[processors.port_name]]
## Name of tag holding the port number
# tag = "port"

## Name of output tag where service name will be added
# dest = "service"

## Default tcp or udp
# default_protocol = "tcp"
```

### Example

```diff
- measurement,port=80 field=123 1560540094000000000
+ measurement,port=80,service=http field=123 1560540094000000000
```
174 changes: 174 additions & 0 deletions plugins/processors/port_name/port_name.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package portname

import (
"bufio"
"io"
"os"
"strconv"
"strings"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/processors"
)

var sampleConfig = `
[[processors.port_name]]
## Name of tag holding the port number
# tag = "port"
## Name of output tag where service name will be added
# dest = "service"
## Default tcp or udp
# default_protocol = "tcp"
`

type sMap map[string]map[int]string // "https" == services["tcp"][443]

var services sMap

type PortName struct {
SourceTag string `toml:"tag"`
DestTag string `toml:"dest"`
DefaultProtocol string `toml:"default_protocol"`

Log telegraf.Logger `toml:"-"`
}

func (d *PortName) SampleConfig() string {
return sampleConfig
}

func (d *PortName) Description() string {
return "Given a tag of a TCP or UDP port number, add a tag of the service name looked up in the system services file"
}

func readServicesFile() {
file, err := os.Open(servicesPath())
if err != nil {
return
}
defer file.Close()

services = readServices(file)
}

// Read the services file into a map.
//
// This function takes a similar approach to parsing as the go
// standard library (see src/net/port_unix.go in golang source) but
// maps protocol and port number to service name, not protocol and
// service to port number.
func readServices(r io.Reader) sMap {
services = make(sMap)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// "http 80/tcp www www-http # World Wide Web HTTP"
if i := strings.Index(line, "#"); i >= 0 {
line = line[:i]
}
f := strings.Fields(line)
if len(f) < 2 {
continue
}
service := f[0] // "http"
portProto := f[1] // "80/tcp"
portProtoSlice := strings.SplitN(portProto, "/", 2)
if len(portProtoSlice) < 2 {
continue
}
port, err := strconv.Atoi(portProtoSlice[0]) // "80"
if err != nil || port <= 0 {
continue
}
proto := portProtoSlice[1] // "tcp"
proto = strings.ToLower(proto)

protoMap, ok := services[proto]
if !ok {
protoMap = make(map[int]string)
services[proto] = protoMap
}
protoMap[port] = service
}
return services
}

func (d *PortName) Apply(metrics ...telegraf.Metric) []telegraf.Metric {
for _, m := range metrics {
portProto, ok := m.GetTag(d.SourceTag)
if !ok {
// Nonexistent tag
continue
}
portProtoSlice := strings.SplitN(portProto, "/", 2)
l := len(portProtoSlice)

if l == 0 {
// Empty tag
d.Log.Errorf("empty port tag: %v", d.SourceTag)
continue
}

var port int
if l > 0 {
var err error
val := portProtoSlice[0]
port, err = strconv.Atoi(val)
if err != nil {
// Can't convert port to string
d.Log.Errorf("error converting port to integer: %v", val)
continue
}
}

proto := d.DefaultProtocol
if l > 1 && len(portProtoSlice[1]) > 0 {
proto = portProtoSlice[1]
}
proto = strings.ToLower(proto)

protoMap, ok := services[proto]
if !ok {
// Unknown protocol
//
// Protocol is normally tcp or udp. The services file
// normally has entries for both, so our map does too. If
// not, it's very likely the source tag or the services
// file doesn't make sense.
d.Log.Errorf("protocol not found in services map: %v", proto)
continue
}

service, ok := protoMap[port]
if !ok {
// Unknown port
//
// Not all ports are named so this isn't an error, but
// it's helpful to know when debugging.
d.Log.Debugf("port not found in services map: %v", port)
continue
}

m.AddTag(d.DestTag, service)
}

return metrics
}

func (h *PortName) Init() error {
services = make(sMap)
readServicesFile()
return nil
}

func init() {
processors.Add("port_name", func() telegraf.Processor {
return &PortName{
SourceTag: "port",
DestTag: "service",
DefaultProtocol: "tcp",
}
})
}
Loading

0 comments on commit ed02caa

Please sign in to comment.