Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add processor to look up service name by port #7540

Merged
merged 4 commits into from
May 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -10,6 +10,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
reimda marked this conversation as resolved.
Show resolved Hide resolved
```
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 {
reimda marked this conversation as resolved.
Show resolved Hide resolved
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 {
reimda marked this conversation as resolved.
Show resolved Hide resolved
// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log this as an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added logging here and in most other cases in Apply. It now logs an error if they provide the port tag but it's empty, or if it's not empty but won't parse as an int, or if they supply a protocol that isn't in the services file (meaning not tcp or udp). It logs a debug message if the port/protocol isn't in the services file.

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
reimda marked this conversation as resolved.
Show resolved Hide resolved
//
// 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