Files
Mellaris/engine/worker.go
T
hayzam 7a3f6e945d Improves flow handling and adds runtime stats APIs
Refactors TCP and UDP flow managers to enhance analyzer selection and flow binding accuracy, including O(1) UDP stream rebinding by 5-tuple.
Introduces runtime stats tracking for engine and ruleset operations, exposing new APIs for granular performance and error metrics.
Optimizes GeoMatcher with result caching and supports efficient geosite set matching, reducing redundant computation in ruleset expressions.
2026-05-13 06:10:38 +05:30

243 lines
5.9 KiB
Go

package engine
import (
"context"
"net"
"git.difuse.io/Difuse/Mellaris/io"
"git.difuse.io/Difuse/Mellaris/ruleset"
"github.com/bwmarrin/snowflake"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
)
type workerPacket struct {
Packet io.Packet
StreamID uint32
Data []byte
SrcMAC net.HardwareAddr
DstMAC net.HardwareAddr
Gen int64
}
type workerResult struct {
Packet io.Packet
StreamID uint32
Verdict io.Verdict
ModifiedPacket []byte
Gen int64
}
type worker struct {
id int
packetChan chan *workerPacket
resultChan chan workerResult
logger Logger
macResolver *sourceMACResolver
tcpFlowMgr *tcpFlowManager
udpSM *udpStreamManager
modSerializeBuffer gopacket.SerializeBuffer
}
type workerConfig struct {
ID int
ChanSize int
Logger Logger
Ruleset ruleset.Ruleset
MACResolver *sourceMACResolver
TCPMaxBufferedPagesTotal int // unused, kept for config compat
TCPMaxBufferedPagesPerConn int // unused, kept for config compat
UDPMaxStreams int
AnalyzerSelectionMode AnalyzerSelectionMode
ResultChan chan workerResult
Stats *statsCounters
}
func (c *workerConfig) fillDefaults() {
if c.ChanSize <= 0 {
c.ChanSize = 64
}
if c.UDPMaxStreams <= 0 {
c.UDPMaxStreams = 4096
}
}
func newWorker(config workerConfig) (*worker, error) {
config.fillDefaults()
sfNode, err := snowflake.NewNode(int64(config.ID))
if err != nil {
return nil, err
}
selector := newAnalyzerSelector(config.AnalyzerSelectionMode, config.Stats)
tcpMgr := newTCPFlowManager(config.ID, config.Logger, config.MACResolver, sfNode, selector)
if config.Ruleset != nil {
tcpMgr.updateRuleset(config.Ruleset, 0)
}
udpSF := &udpStreamFactory{
WorkerID: config.ID,
Logger: config.Logger,
Node: sfNode,
Ruleset: config.Ruleset,
Selector: selector,
Stats: config.Stats,
}
udpSM, err := newUDPStreamManager(udpSF, config.UDPMaxStreams, config.Stats)
if err != nil {
return nil, err
}
return &worker{
id: config.ID,
packetChan: make(chan *workerPacket, config.ChanSize),
resultChan: config.ResultChan,
logger: config.Logger,
macResolver: config.MACResolver,
tcpFlowMgr: tcpMgr,
udpSM: udpSM,
modSerializeBuffer: gopacket.NewSerializeBuffer(),
}, nil
}
func (w *worker) Feed(p *workerPacket) bool {
select {
case w.packetChan <- p:
return true
default:
return false
}
}
func (w *worker) FeedBlocking(p *workerPacket) {
w.packetChan <- p
}
func (w *worker) Run(ctx context.Context) {
w.logger.WorkerStart(w.id)
defer w.logger.WorkerStop(w.id)
for {
select {
case <-ctx.Done():
return
case wp := <-w.packetChan:
if wp == nil {
return
}
v, b := w.handle(wp)
w.resultChan <- workerResult{
Packet: wp.Packet,
StreamID: wp.StreamID,
Verdict: v,
ModifiedPacket: b,
Gen: wp.Gen,
}
}
}
}
func (w *worker) UpdateRuleset(r ruleset.Ruleset) error {
w.tcpFlowMgr.updateRuleset(r, 0)
return w.udpSM.factory.UpdateRuleset(r)
}
func (w *worker) handle(wp *workerPacket) (io.Verdict, []byte) {
data := wp.Data
if len(data) == 0 {
return io.VerdictAccept, nil
}
ipVersion := data[0] >> 4
if ipVersion == 4 {
l3, transport, ok := ParseL3(data)
if !ok {
return io.VerdictAccept, nil
}
switch l3.Protocol {
case 6: // TCP
tcp, payload, ok := ParseTCP(transport)
if !ok {
return io.VerdictAccept, nil
}
verdict := w.tcpFlowMgr.handle(
wp.StreamID, l3, tcp, payload,
wp.SrcMAC, wp.DstMAC,
)
return verdict, nil
case 17: // UDP
udp, payload, ok := ParseUDP(transport)
if !ok {
return io.VerdictAccept, nil
}
v, modPayload := w.handleUDP(
wp.StreamID, l3, udp, payload,
wp.SrcMAC, wp.DstMAC,
)
if v == io.VerdictAcceptModify && modPayload != nil {
return w.serializeModifiedUDP(data, l3, udp, transport, modPayload)
}
return v, nil
default:
return io.VerdictAccept, nil
}
}
// Ethernet frame path (for custom PacketIO)
if ipVersion == 6 {
// TODO: IPv6 support with raw parsing
return io.VerdictAccept, nil
}
return io.VerdictAccept, nil
}
func (w *worker) handleUDP(streamID uint32, l3 L3Info, udp UDPInfo, payload []byte, srcMAC, dstMAC net.HardwareAddr) (io.Verdict, []byte) {
ipSrc := net.IP(l3.SrcIP[:])
ipDst := net.IP(l3.DstIP[:])
ipFlow := gopacket.NewFlow(layers.EndpointIPv4, ipSrc.To4(), ipDst.To4())
if len(srcMAC) == 0 && w.macResolver != nil {
srcMAC = w.macResolver.Resolve(ipSrc)
}
uc := &udpContext{
Verdict: udpVerdictAccept,
SrcMAC: srcMAC,
DstMAC: dstMAC,
}
// Temporarily set payload on a UDP layer so existing UDP handling works.
w.udpSM.MatchWithContext(streamID, ipFlow, &layers.UDP{
BaseLayer: layers.BaseLayer{Payload: payload},
SrcPort: layers.UDPPort(udp.SrcPort),
DstPort: layers.UDPPort(udp.DstPort),
}, uc)
return io.Verdict(uc.Verdict), uc.Packet
}
func (w *worker) serializeModifiedUDP(fullData []byte, l3 L3Info, udp UDPInfo, transport []byte, modPayload []byte) (io.Verdict, []byte) {
ipPkt := gopacket.NewPacket(fullData, layers.LayerTypeIPv4, gopacket.DecodeOptions{Lazy: true, NoCopy: true})
netLayer := ipPkt.NetworkLayer()
trLayer := ipPkt.TransportLayer()
if netLayer == nil || trLayer == nil {
return io.VerdictAccept, nil
}
udpLayer, ok := trLayer.(*layers.UDP)
if !ok {
return io.VerdictAccept, nil
}
udpLayer.Payload = modPayload
_ = udpLayer.SetNetworkLayerForChecksum(netLayer)
_ = w.modSerializeBuffer.Clear()
err := gopacket.SerializePacket(w.modSerializeBuffer,
gopacket.SerializeOptions{FixLengths: true, ComputeChecksums: true}, ipPkt)
if err != nil {
return io.VerdictAccept, nil
}
return io.VerdictAcceptModify, w.modSerializeBuffer.Bytes()
}