youtubebeat/vendor/github.com/elastic/beats/packetbeat/procs/procs.go

270 lines
7.5 KiB
Go

// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package procs
import (
"net"
"strings"
"time"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/libbeat/logp"
"github.com/elastic/beats/packetbeat/protos/applayer"
"github.com/elastic/gosigar"
)
// This controls how often process info for a running process is reloaded
// A big value means less unnecessary refreshes at a higher risk of missing
// a PID being recycled by the OS
const processCacheExpiration = time.Second * 30
type portProcMapping struct {
port uint16
pid int
proc *process
}
type process struct {
name string
commandLine string
// To control cache expiration
expiration time.Time
}
// Allow the OS-dependant implementation to be replaced by a mock for testing
type processWatcherImpl interface {
// GetLocalPortToPIDMapping returns the list of local port numbers and the PID
// that owns them.
GetLocalPortToPIDMapping(transport applayer.Transport) (ports map[uint16]int, err error)
// GetProcessCommandLine returns the command line for a given process.
GetProcessCommandLine(pid int) string
// GetLocalIPs returns the list of local addresses.
GetLocalIPs() ([]net.IP, error)
}
type ProcessesWatcher struct {
portProcMap map[applayer.Transport]map[uint16]portProcMapping
localAddrs []net.IP
processCache map[int]*process
// config
enabled bool
procConfig []ProcConfig
impl processWatcherImpl
}
var ProcWatcher ProcessesWatcher
func (proc *ProcessesWatcher) Init(config ProcsConfig) error {
return proc.initWithImpl(config, proc)
}
func (proc *ProcessesWatcher) initWithImpl(config ProcsConfig, impl processWatcherImpl) error {
proc.impl = impl
proc.portProcMap = map[applayer.Transport]map[uint16]portProcMapping{
applayer.TransportUDP: make(map[uint16]portProcMapping),
applayer.TransportTCP: make(map[uint16]portProcMapping),
}
proc.processCache = make(map[int]*process)
proc.enabled = config.Enabled
if proc.enabled {
logp.Info("Process watcher enabled")
} else {
logp.Info("Process watcher disabled")
}
// Read the local IP addresses
var err error
proc.localAddrs, err = impl.GetLocalIPs()
if err != nil {
logp.Err("Error getting local IP addresses: %s", err)
proc.localAddrs = []net.IP{}
}
proc.procConfig = config.Monitored
return nil
}
// FindProcessesTupleTCP looks up local process information for the source and
// destination addresses of TCP tuple
func (proc *ProcessesWatcher) FindProcessesTupleTCP(tuple *common.IPPortTuple) (procTuple *common.CmdlineTuple) {
return proc.FindProcessesTuple(tuple, applayer.TransportTCP)
}
// FindProcessesTupleUDP looks up local process information for the source and
// destination addresses of UDP tuple
func (proc *ProcessesWatcher) FindProcessesTupleUDP(tuple *common.IPPortTuple) (procTuple *common.CmdlineTuple) {
return proc.FindProcessesTuple(tuple, applayer.TransportUDP)
}
// FindProcessesTuple looks up local process information for the source and
// destination addresses of a tuple for the given transport protocol
func (proc *ProcessesWatcher) FindProcessesTuple(tuple *common.IPPortTuple, transport applayer.Transport) (procTuple *common.CmdlineTuple) {
procTuple = &common.CmdlineTuple{}
if !proc.enabled {
return
}
if proc.isLocalIP(tuple.SrcIP) {
if p := proc.findProc(tuple.SrcPort, transport); p != nil {
procTuple.Src = []byte(p.name)
procTuple.SrcCommand = []byte(p.commandLine)
logp.Debug("procs", "Found process '%s' (%s) for port %d/%s", p.commandLine, p.name, tuple.SrcPort, transport)
}
}
if proc.isLocalIP(tuple.DstIP) {
if p := proc.findProc(tuple.DstPort, transport); p != nil {
procTuple.Dst = []byte(p.name)
procTuple.DstCommand = []byte(p.commandLine)
logp.Debug("procs", "Found process '%s' (%s) for port %d/%s", p.commandLine, p.name, tuple.DstPort, transport)
}
}
return
}
func (proc *ProcessesWatcher) findProc(port uint16, transport applayer.Transport) *process {
defer logp.Recover("FindProc exception")
procMap, ok := proc.portProcMap[transport]
if !ok {
return nil
}
p, exists := procMap[port]
if exists {
return p.proc
}
proc.updateMap(transport)
p, exists = procMap[port]
if exists {
return p.proc
}
return nil
}
func (proc *ProcessesWatcher) updateMap(transport applayer.Transport) {
if logp.HasSelector("procsdetailed") {
start := time.Now()
defer func() {
logp.Debug("procsdetailed", "updateMap() took %v", time.Now().Sub(start))
}()
}
ports, err := proc.impl.GetLocalPortToPIDMapping(transport)
if err != nil {
logp.Err("unable to list local ports: %v", err)
}
proc.expireProcessCache()
for port, pid := range ports {
proc.updateMappingEntry(transport, port, pid)
}
}
func (proc *ProcessesWatcher) updateMappingEntry(transport applayer.Transport, port uint16, pid int) {
prev, ok := proc.portProcMap[transport][port]
if ok && prev.pid == pid {
// This port->pid mapping already exists
return
}
p := proc.getProcessInfo(pid)
if p == nil {
return
}
// Simply overwrite old entries for now.
// We never expire entries from this map. Since there are 65k possible
// ports, the size of the dict can be max 1.5 MB, which we consider
// reasonable.
proc.portProcMap[transport][port] = portProcMapping{port: port, pid: pid, proc: p}
logp.Debug("procsdetailed", "updateMappingEntry(): port=%d/%s pid=%d process='%s' name=%s",
port, transport, pid, p.commandLine, p.name)
}
func (proc *ProcessesWatcher) isLocalIP(ip net.IP) bool {
if ip.IsLoopback() {
return true
}
for _, addr := range proc.localAddrs {
if ip.Equal(addr) {
return true
}
}
return false
}
func (proc *ProcessesWatcher) getProcessInfo(pid int) *process {
if p, ok := proc.processCache[pid]; ok {
return p
}
// Not in cache, resolve process info
p := &process{
commandLine: proc.impl.GetProcessCommandLine(pid),
expiration: time.Now().Add(processCacheExpiration),
}
// see if the command-line matches any 'grep' pattern
for _, match := range proc.procConfig {
if strings.Contains(p.commandLine, match.CmdlineGrep) {
p.name = match.Process
break
}
}
proc.processCache[pid] = p
return p
}
func (proc *ProcessesWatcher) expireProcessCache() {
now := time.Now()
for pid, info := range proc.processCache {
if now.After(info.expiration) {
delete(proc.processCache, pid)
}
}
}
// GetProcessCommandLine returns the command line for a given process.
func (proc *ProcessesWatcher) GetProcessCommandLine(pid int) (cmdLine string) {
var procArgs gosigar.ProcArgs
if err := procArgs.Get(pid); err == nil {
cmdLine = strings.Join(procArgs.List, " ")
} else {
// Save PID without command-line to avoid continued errors for this process
logp.Err("Unable to get command-line for pid %d: %v", pid, err)
}
return cmdLine
}
// GetLocalIPs returns the list of local addresses.
func (proc *ProcessesWatcher) GetLocalIPs() ([]net.IP, error) {
return common.LocalIPAddrs()
}