netbird/client/internal/netflow/manager.go

232 lines
5.4 KiB
Go
Raw Normal View History

package netflow
import (
"context"
2025-03-07 13:56:00 +01:00
"errors"
2025-02-28 19:16:29 +01:00
"fmt"
"runtime"
"sync"
"time"
log "github.com/sirupsen/logrus"
"google.golang.org/protobuf/types/known/timestamppb"
2025-02-28 19:16:29 +01:00
"github.com/netbirdio/netbird/client/internal/netflow/conntrack"
"github.com/netbirdio/netbird/client/internal/netflow/logger"
2025-03-04 16:43:07 +01:00
nftypes "github.com/netbirdio/netbird/client/internal/netflow/types"
"github.com/netbirdio/netbird/flow/client"
"github.com/netbirdio/netbird/flow/proto"
)
2025-02-28 19:16:29 +01:00
// Manager handles netflow tracking and logging
type Manager struct {
mux sync.Mutex
2025-03-04 16:43:07 +01:00
logger nftypes.FlowLogger
flowConfig *nftypes.FlowConfig
conntrack nftypes.ConnTracker
ctx context.Context
receiverClient *client.GRPCClient
publicKey []byte
}
2025-02-28 19:16:29 +01:00
// NewManager creates a new netflow manager
2025-03-04 16:43:07 +01:00
func NewManager(ctx context.Context, iface nftypes.IFaceMapper, publicKey []byte) *Manager {
2025-02-28 19:16:29 +01:00
flowLogger := logger.New(ctx)
2025-03-04 16:43:07 +01:00
var ct nftypes.ConnTracker
2025-02-28 19:16:29 +01:00
if runtime.GOOS == "linux" && iface != nil && !iface.IsUserspaceBind() {
ct = conntrack.New(flowLogger, iface)
}
return &Manager{
2025-02-28 19:16:29 +01:00
logger: flowLogger,
conntrack: ct,
ctx: ctx,
publicKey: publicKey,
}
}
2025-02-28 19:16:29 +01:00
// Update applies new flow configuration settings
2025-03-07 13:56:00 +01:00
// needsNewClient checks if a new client needs to be created
func (m *Manager) needsNewClient(previous *nftypes.FlowConfig) bool {
current := m.flowConfig
return previous == nil ||
!previous.Enabled ||
previous.TokenPayload != current.TokenPayload ||
previous.TokenSignature != current.TokenSignature ||
previous.URL != current.URL
}
2025-03-07 13:56:00 +01:00
// enableFlow starts components for flow tracking
func (m *Manager) enableFlow(previous *nftypes.FlowConfig) error {
// first make sender ready so events don't pile up
if m.needsNewClient(previous) {
if m.receiverClient != nil {
if err := m.receiverClient.Close(); err != nil {
log.Warnf("error closing previous flow client: %s", err)
2025-02-28 19:16:29 +01:00
}
}
2025-03-07 13:56:00 +01:00
flowClient, err := client.NewClient(m.flowConfig.URL, m.flowConfig.TokenPayload, m.flowConfig.TokenSignature, m.flowConfig.Interval)
if err != nil {
return fmt.Errorf("create client: %w", err)
}
2025-03-07 13:56:00 +01:00
log.Infof("flow client configured to connect to %s", m.flowConfig.URL)
m.receiverClient = flowClient
go m.receiveACKs(flowClient)
go m.startSender()
}
2025-03-07 13:56:00 +01:00
m.logger.Enable()
if m.conntrack != nil {
if err := m.conntrack.Start(m.flowConfig.Counters); err != nil {
return fmt.Errorf("start conntrack: %w", err)
}
}
return nil
}
// disableFlow stops components for flow tracking
func (m *Manager) disableFlow() error {
2025-02-28 19:16:29 +01:00
if m.conntrack != nil {
m.conntrack.Stop()
}
2025-03-07 13:56:00 +01:00
m.logger.Disable()
2025-03-07 13:56:00 +01:00
if m.receiverClient != nil {
return m.receiverClient.Close()
}
return nil
}
2025-03-07 13:56:00 +01:00
// Update applies new flow configuration settings
func (m *Manager) Update(update *nftypes.FlowConfig) error {
if update == nil {
return nil
}
m.mux.Lock()
defer m.mux.Unlock()
previous := m.flowConfig
m.flowConfig = update
if update.Enabled {
return m.enableFlow(previous)
}
return m.disableFlow()
}
2025-02-28 19:16:29 +01:00
// Close cleans up all resources
func (m *Manager) Close() {
2025-02-28 19:16:29 +01:00
m.mux.Lock()
defer m.mux.Unlock()
if m.conntrack != nil {
m.conntrack.Close()
}
2025-03-07 13:56:00 +01:00
if m.receiverClient != nil {
if err := m.receiverClient.Close(); err != nil {
log.Warnf("failed to close receiver client: %s", err)
}
}
m.logger.Close()
}
2025-02-28 19:16:29 +01:00
// GetLogger returns the flow logger
2025-03-04 16:43:07 +01:00
func (m *Manager) GetLogger() nftypes.FlowLogger {
return m.logger
}
func (m *Manager) startSender() {
ticker := time.NewTicker(m.flowConfig.Interval)
defer ticker.Stop()
2025-03-07 13:56:00 +01:00
for {
select {
case <-m.ctx.Done():
return
case <-ticker.C:
events := m.logger.GetEvents()
for _, event := range events {
2025-03-07 13:56:00 +01:00
if err := m.send(event); err != nil {
log.Errorf("failed to send flow event to server: %s", err)
continue
}
2025-03-07 13:56:00 +01:00
log.Tracef("sent flow event: %s", event.ID)
}
}
}
}
2025-03-07 13:56:00 +01:00
func (m *Manager) receiveACKs(client *client.GRPCClient) {
err := client.Receive(m.ctx, m.flowConfig.Interval, func(ack *proto.FlowEventAck) error {
log.Tracef("received flow event ack: %s", ack.EventId)
m.logger.DeleteEvents([]string{ack.EventId})
return nil
})
2025-03-07 13:56:00 +01:00
if err != nil && !errors.Is(err, context.Canceled) {
log.Errorf("failed to receive flow event ack: %s", err)
}
}
2025-03-04 16:43:07 +01:00
func (m *Manager) send(event *nftypes.Event) error {
2025-03-07 13:56:00 +01:00
m.mux.Lock()
client := m.receiverClient
m.mux.Unlock()
if client == nil {
return nil
}
2025-03-07 13:56:00 +01:00
return client.Send(toProtoEvent(m.publicKey, event))
}
2025-03-04 16:43:07 +01:00
func toProtoEvent(publicKey []byte, event *nftypes.Event) *proto.FlowEvent {
protoEvent := &proto.FlowEvent{
EventId: event.ID,
Timestamp: timestamppb.New(event.Timestamp),
PublicKey: publicKey,
2025-03-04 16:43:07 +01:00
FlowFields: &proto.FlowFields{
FlowId: event.FlowID[:],
RuleId: event.RuleID,
Type: proto.Type(event.Type),
Direction: proto.Direction(event.Direction),
Protocol: uint32(event.Protocol),
SourceIp: event.SourceIP.AsSlice(),
DestIp: event.DestIP.AsSlice(),
2025-03-04 16:46:03 +01:00
RxPackets: event.RxPackets,
TxPackets: event.TxPackets,
RxBytes: event.RxBytes,
TxBytes: event.TxBytes,
},
}
2025-03-07 13:56:00 +01:00
2025-03-04 16:43:07 +01:00
if event.Protocol == nftypes.ICMP {
protoEvent.FlowFields.ConnectionInfo = &proto.FlowFields_IcmpInfo{
IcmpInfo: &proto.ICMPInfo{
IcmpType: uint32(event.ICMPType),
IcmpCode: uint32(event.ICMPCode),
},
}
return protoEvent
}
2025-03-04 16:43:07 +01:00
protoEvent.FlowFields.ConnectionInfo = &proto.FlowFields_PortInfo{
PortInfo: &proto.PortInfo{
SourcePort: uint32(event.SourcePort),
DestPort: uint32(event.DestPort),
},
}
return protoEvent
}