youtubebeat/vendor/github.com/elastic/beats/packetbeat/flows/worker.go

379 lines
9.3 KiB
Go
Raw Normal View History

2018-11-18 11:08:38 +01:00
// 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 flows
import (
"encoding/base64"
"encoding/binary"
"errors"
"net"
"time"
"github.com/elastic/beats/libbeat/beat"
"github.com/elastic/beats/libbeat/common"
"github.com/elastic/beats/packetbeat/procs"
"github.com/elastic/beats/packetbeat/protos/applayer"
)
type flowsProcessor struct {
spool spool
table *flowMetaTable
counters *counterReg
timeout time.Duration
}
var (
ErrInvalidTimeout = errors.New("timeout must not <= 1s")
ErrInvalidPeriod = errors.New("report period must be -1 or >= 1s")
)
func newFlowsWorker(
pub Reporter,
table *flowMetaTable,
counters *counterReg,
timeout, period time.Duration,
) (*worker, error) {
oneSecond := 1 * time.Second
if timeout < oneSecond {
return nil, ErrInvalidTimeout
}
if 0 < period && period < oneSecond {
return nil, ErrInvalidPeriod
}
tickDuration := timeout
ticksTimeout := 1
ticksPeriod := -1
if period > 0 {
tickDuration = time.Duration(gcd(int64(timeout), int64(period)))
if tickDuration < oneSecond {
tickDuration = oneSecond
}
ticksTimeout = int(timeout / tickDuration)
if ticksTimeout == 0 {
ticksTimeout = 1
}
ticksPeriod = int(period / tickDuration)
if ticksPeriod == 0 {
ticksPeriod = 1
}
}
debugf("new flows worker. timeout=%v, period=%v, tick=%v, ticksTO=%v, ticksP=%v",
timeout, period, tickDuration, ticksTimeout, ticksPeriod)
defaultBatchSize := 1024
processor := &flowsProcessor{
table: table,
counters: counters,
timeout: timeout,
}
processor.spool.init(pub, defaultBatchSize)
return makeWorker(processor, tickDuration, ticksTimeout, ticksPeriod, 10)
}
func makeWorker(
processor *flowsProcessor,
tickDuration time.Duration,
ticksTimeout, ticksPeriod int,
align int64,
) (*worker, error) {
return newWorker(func(w *worker) {
defer processor.execute(w, false, true, true)
if align > 0 {
// round time to nearest 10 seconds for alignment
aligned := time.Unix(((time.Now().Unix()+(align-1))/align)*align, 0)
waitStart := aligned.Sub(time.Now())
debugf("worker wait start(%v): %v", aligned, waitStart)
if cont := w.sleep(waitStart); !cont {
return
}
}
nTimeout := ticksTimeout
nPeriod := ticksPeriod
reportPeriodically := ticksPeriod > 0
debugf("start flows worker loop")
w.periodically(tickDuration, func() error {
nTimeout--
nPeriod--
debugf("worker tick, nTimeout=%v, nPeriod=%v", nTimeout, nPeriod)
handleTimeout := nTimeout == 0
handleReports := reportPeriodically && nPeriod == 0
if handleTimeout {
nTimeout = ticksTimeout
}
if nPeriod <= 0 {
nPeriod = ticksPeriod
}
processor.execute(w, handleTimeout, handleReports, false)
return nil
})
}), nil
}
func (fw *flowsProcessor) execute(w *worker, checkTimeout, handleReports, lastReport bool) {
if !checkTimeout && !handleReports {
return
}
debugf("exec tick, timeout=%v, report=%v", checkTimeout, handleReports)
// get counter names snapshot if reports must be generated
fw.counters.mutex.Lock()
intNames := fw.counters.ints.getNames()
uintNames := fw.counters.uints.getNames()
floatNames := fw.counters.floats.getNames()
fw.counters.mutex.Unlock()
fw.table.Lock()
defer fw.table.Unlock()
ts := time.Now()
// TODO: create snapshot inside flows/tables, so deletion of timedout flows
// and reporting flows stats can be done more concurrent to packet
// processing.
for table := fw.table.tables.head; table != nil; table = table.next {
var next *biFlow
for flow := table.flows.head; flow != nil; flow = next {
next = flow.next
debugf("handle flow: %v, %v", flow.id.flowIDMeta, flow.id.flowID)
reportFlow := handleReports
isOver := lastReport
if checkTimeout {
if ts.Sub(flow.ts) > fw.timeout {
debugf("kill flow")
reportFlow = true
flow.kill() // mark flow as killed
isOver = true
table.remove(flow)
}
}
if reportFlow {
debugf("report flow")
fw.report(w, ts, flow, isOver, intNames, uintNames, floatNames)
}
}
}
fw.spool.flush()
}
func (fw *flowsProcessor) report(
w *worker,
ts time.Time,
flow *biFlow,
isOver bool,
intNames, uintNames, floatNames []string,
) {
event := createEvent(ts, flow, isOver, intNames, uintNames, floatNames)
debugf("add event: %v", event)
fw.spool.publish(event)
}
func createEvent(
ts time.Time, f *biFlow,
isOver bool,
intNames, uintNames, floatNames []string,
) beat.Event {
timestamp := ts
fields := common.MapStr{
"start_time": common.Time(f.createTS),
"last_time": common.Time(f.ts),
"type": "flow",
"flow_id": common.NetString(f.id.Serialize()),
"final": isOver,
}
source := common.MapStr{}
dest := common.MapStr{}
tuple := common.IPPortTuple{}
var proto applayer.Transport
// add ethernet layer meta data
if src, dst, ok := f.id.EthAddr(); ok {
source["mac"] = net.HardwareAddr(src).String()
dest["mac"] = net.HardwareAddr(dst).String()
}
// add vlan
if vlan := f.id.OutterVLan(); vlan != nil {
fields["outer_vlan"] = binary.LittleEndian.Uint16(vlan)
}
if vlan := f.id.VLan(); vlan != nil {
fields["vlan"] = binary.LittleEndian.Uint16(vlan)
}
// add icmp
if icmp := f.id.ICMPv4(); icmp != nil {
fields["icmp_id"] = binary.LittleEndian.Uint16(icmp)
} else if icmp := f.id.ICMPv6(); icmp != nil {
fields["icmp_id"] = binary.LittleEndian.Uint16(icmp)
}
// ipv4 layer meta data
if src, dst, ok := f.id.OutterIPv4Addr(); ok {
srcIP, dstIP := net.IP(src), net.IP(dst)
source["outer_ip"] = srcIP.String()
dest["outer_ip"] = dstIP.String()
tuple.SrcIP = srcIP
tuple.DstIP = dstIP
tuple.IPLength = 4
}
if src, dst, ok := f.id.IPv4Addr(); ok {
srcIP, dstIP := net.IP(src), net.IP(dst)
source["ip"] = srcIP.String()
dest["ip"] = dstIP.String()
// Save IPs for process matching if an outer layer was not present
if tuple.IPLength == 0 {
tuple.SrcIP = srcIP
tuple.DstIP = dstIP
tuple.IPLength = 4
}
}
// ipv6 layer meta data
if src, dst, ok := f.id.OutterIPv6Addr(); ok {
srcIP, dstIP := net.IP(src), net.IP(dst)
source["outer_ipv6"] = srcIP.String()
dest["outer_ipv6"] = dstIP.String()
tuple.SrcIP = srcIP
tuple.DstIP = dstIP
tuple.IPLength = 6
}
if src, dst, ok := f.id.IPv6Addr(); ok {
srcIP, dstIP := net.IP(src), net.IP(dst)
source["ipv6"] = net.IP(src).String()
dest["ipv6"] = net.IP(dst).String()
// Save IPs for process matching if an outer layer was not present
if tuple.IPLength == 0 {
tuple.SrcIP = srcIP
tuple.DstIP = dstIP
tuple.IPLength = 6
}
}
// udp layer meta data
if src, dst, ok := f.id.UDPAddr(); ok {
tuple.SrcPort = binary.LittleEndian.Uint16(src)
tuple.DstPort = binary.LittleEndian.Uint16(dst)
source["port"], dest["port"] = tuple.SrcPort, tuple.DstPort
fields["transport"] = "udp"
proto = applayer.TransportUDP
}
// tcp layer meta data
if src, dst, ok := f.id.TCPAddr(); ok {
tuple.SrcPort = binary.LittleEndian.Uint16(src)
tuple.DstPort = binary.LittleEndian.Uint16(dst)
source["port"], dest["port"] = tuple.SrcPort, tuple.DstPort
fields["transport"] = "tcp"
proto = applayer.TransportTCP
}
if id := f.id.ConnectionID(); id != nil {
fields["connection_id"] = base64.StdEncoding.EncodeToString(id)
}
if f.stats[0] != nil {
source["stats"] = encodeStats(f.stats[0], intNames, uintNames, floatNames)
}
if f.stats[1] != nil {
dest["stats"] = encodeStats(f.stats[1], intNames, uintNames, floatNames)
}
fields["source"] = source
fields["dest"] = dest
// Set process information if it's available
if tuple.IPLength != 0 && tuple.SrcPort != 0 {
if cmdline := procs.ProcWatcher.FindProcessesTuple(&tuple, proto); cmdline != nil {
src, dst := common.MakeEndpointPair(tuple.BaseTuple, cmdline)
for key, value := range map[string]string{
"client_proc": src.Name,
"client_cmdline": src.Cmdline,
"proc": dst.Name,
"cmdline": dst.Cmdline,
} {
if len(value) != 0 {
fields[key] = value
}
}
}
}
return beat.Event{
Timestamp: timestamp,
Fields: fields,
}
}
func encodeStats(
stats *flowStats,
ints, uints, floats []string,
) map[string]interface{} {
report := make(map[string]interface{})
i := 0
for _, mask := range stats.intFlags {
for m := mask; m != 0; m >>= 1 {
if (m & 1) == 1 {
report[ints[i]] = stats.ints[i]
}
i++
}
}
i = 0
for _, mask := range stats.uintFlags {
for m := mask; m != 0; m >>= 1 {
if (m & 1) == 1 {
report[uints[i]] = stats.uints[i]
}
i++
}
}
i = 0
for _, mask := range stats.floatFlags {
for m := mask; m != 0; m >>= 1 {
if (m & 1) == 1 {
report[floats[i]] = stats.floats[i]
}
i++
}
}
return report
}