215 lines
4.6 KiB
Go
215 lines
4.6 KiB
Go
|
//go:generate ../../../tools/readme_config_includer/generator
|
||
|
package port_name
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
_ "embed"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/influxdata/telegraf"
|
||
|
"github.com/influxdata/telegraf/plugins/processors"
|
||
|
)
|
||
|
|
||
|
//go:embed sample.conf
|
||
|
var sampleConfig string
|
||
|
|
||
|
type sMap map[string]map[int]string // "https" == services["tcp"][443]
|
||
|
|
||
|
var services sMap
|
||
|
|
||
|
type PortName struct {
|
||
|
SourceTag string `toml:"tag"`
|
||
|
SourceField string `toml:"field"`
|
||
|
Dest string `toml:"dest"`
|
||
|
DefaultProtocol string `toml:"default_protocol"`
|
||
|
ProtocolTag string `toml:"protocol_tag"`
|
||
|
ProtocolField string `toml:"protocol_field"`
|
||
|
|
||
|
Log telegraf.Logger `toml:"-"`
|
||
|
}
|
||
|
|
||
|
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 (*PortName) SampleConfig() string {
|
||
|
return sampleConfig
|
||
|
}
|
||
|
|
||
|
func (pn *PortName) Apply(metrics ...telegraf.Metric) []telegraf.Metric {
|
||
|
for _, m := range metrics {
|
||
|
var portProto string
|
||
|
var fromField bool
|
||
|
|
||
|
if len(pn.SourceTag) > 0 {
|
||
|
if tag, ok := m.GetTag(pn.SourceTag); ok {
|
||
|
portProto = tag
|
||
|
}
|
||
|
}
|
||
|
if len(pn.SourceField) > 0 {
|
||
|
if field, ok := m.GetField(pn.SourceField); ok {
|
||
|
switch v := field.(type) {
|
||
|
default:
|
||
|
pn.Log.Errorf("Unexpected type %t in source field; must be string or int", v)
|
||
|
continue
|
||
|
case int64:
|
||
|
portProto = strconv.FormatInt(v, 10)
|
||
|
case uint64:
|
||
|
portProto = strconv.FormatUint(v, 10)
|
||
|
case string:
|
||
|
portProto = v
|
||
|
}
|
||
|
fromField = true
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(portProto) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
portProtoSlice := strings.SplitN(portProto, "/", 2)
|
||
|
l := len(portProtoSlice)
|
||
|
|
||
|
if l == 0 {
|
||
|
// Empty tag
|
||
|
pn.Log.Errorf("empty port tag: %v", pn.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
|
||
|
pn.Log.Errorf("error converting port to integer: %v", val)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
|
||
|
proto := pn.DefaultProtocol
|
||
|
if l > 1 && len(portProtoSlice[1]) > 0 {
|
||
|
proto = portProtoSlice[1]
|
||
|
}
|
||
|
if len(pn.ProtocolTag) > 0 {
|
||
|
if tag, ok := m.GetTag(pn.ProtocolTag); ok {
|
||
|
proto = tag
|
||
|
}
|
||
|
}
|
||
|
if len(pn.ProtocolField) > 0 {
|
||
|
if field, ok := m.GetField(pn.ProtocolField); ok {
|
||
|
switch v := field.(type) {
|
||
|
default:
|
||
|
pn.Log.Errorf("Unexpected type %t in protocol field; must be string", v)
|
||
|
continue
|
||
|
case string:
|
||
|
proto = v
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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.
|
||
|
pn.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.
|
||
|
pn.Log.Debugf("port not found in services map: %v", port)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if fromField {
|
||
|
m.AddField(pn.Dest, service)
|
||
|
} else {
|
||
|
m.AddTag(pn.Dest, service)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return metrics
|
||
|
}
|
||
|
|
||
|
func (*PortName) Init() error {
|
||
|
services = make(sMap)
|
||
|
readServicesFile()
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func init() {
|
||
|
processors.Add("port_name", func() telegraf.Processor {
|
||
|
return &PortName{
|
||
|
SourceTag: "port",
|
||
|
SourceField: "port",
|
||
|
Dest: "service",
|
||
|
DefaultProtocol: "tcp",
|
||
|
ProtocolTag: "proto",
|
||
|
ProtocolField: "proto",
|
||
|
}
|
||
|
})
|
||
|
}
|