diff --git a/cmd/root.go b/cmd/root.go index b1f1260..a60338d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -254,6 +254,7 @@ func runMain(cmd *cobra.Command, args []string) { logger.Fatal("failed to load rules", zap.Error(err)) } rsConfig := &ruleset.BuiltinConfig{ + Logger: &rulesetLogger{}, GeoSiteFilename: config.Ruleset.GeoSite, GeoIpFilename: config.Ruleset.GeoIp, } @@ -371,14 +372,6 @@ func (l *engineLogger) UDPStreamAction(info ruleset.StreamInfo, action ruleset.A zap.Bool("noMatch", noMatch)) } -func (l *engineLogger) MatchError(info ruleset.StreamInfo, err error) { - logger.Error("match error", - zap.Int64("id", info.ID), - zap.String("src", info.SrcString()), - zap.String("dst", info.DstString()), - zap.Error(err)) -} - func (l *engineLogger) ModifyError(info ruleset.StreamInfo, err error) { logger.Error("modify error", zap.Int64("id", info.ID), @@ -408,6 +401,26 @@ func (l *engineLogger) AnalyzerErrorf(streamID int64, name string, format string zap.String("msg", fmt.Sprintf(format, args...))) } +type rulesetLogger struct{} + +func (l *rulesetLogger) Log(info ruleset.StreamInfo, name string) { + logger.Info("ruleset log", + zap.String("name", name), + zap.Int64("id", info.ID), + zap.String("src", info.SrcString()), + zap.String("dst", info.DstString()), + zap.Any("props", info.Props)) +} + +func (l *rulesetLogger) MatchError(info ruleset.StreamInfo, name string, err error) { + logger.Error("ruleset match error", + zap.String("name", name), + zap.Int64("id", info.ID), + zap.String("src", info.SrcString()), + zap.String("dst", info.DstString()), + zap.Error(err)) +} + func envOrDefaultString(key, def string) string { if v := os.Getenv(key); v != "" { return v diff --git a/engine/interface.go b/engine/interface.go index 6975834..1ad26e3 100644 --- a/engine/interface.go +++ b/engine/interface.go @@ -41,7 +41,6 @@ type Logger interface { UDPStreamPropUpdate(info ruleset.StreamInfo, close bool) UDPStreamAction(info ruleset.StreamInfo, action ruleset.Action, noMatch bool) - MatchError(info ruleset.StreamInfo, err error) ModifyError(info ruleset.StreamInfo, err error) AnalyzerDebugf(streamID int64, name string, format string, args ...interface{}) diff --git a/engine/tcp.go b/engine/tcp.go index b9fe156..1874055 100644 --- a/engine/tcp.go +++ b/engine/tcp.go @@ -148,10 +148,7 @@ func (s *tcpStream) ReassembledSG(sg reassembly.ScatterGather, ac reassembly.Ass s.virgin = false s.logger.TCPStreamPropUpdate(s.info, false) // Match properties against ruleset - result, err := s.ruleset.Match(s.info) - if err != nil { - s.logger.MatchError(s.info, err) - } + result := s.ruleset.Match(s.info) action := result.Action if action != ruleset.ActionMaybe && action != ruleset.ActionModify { verdict := actionToTCPVerdict(action) diff --git a/engine/udp.go b/engine/udp.go index 89f407b..fe1f07c 100644 --- a/engine/udp.go +++ b/engine/udp.go @@ -201,10 +201,7 @@ func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) { s.virgin = false s.logger.UDPStreamPropUpdate(s.info, false) // Match properties against ruleset - result, err := s.ruleset.Match(s.info) - if err != nil { - s.logger.MatchError(s.info, err) - } + result := s.ruleset.Match(s.info) action := result.Action if action == ruleset.ActionModify { // Call the modifier instance @@ -214,6 +211,7 @@ func (s *udpStream) Feed(udp *layers.UDP, rev bool, uc *udpContext) { s.logger.ModifyError(s.info, errInvalidModifier) action = ruleset.ActionMaybe } else { + var err error uc.Packet, err = udpMI.Process(udp.Payload) if err != nil { // Modifier error, fallback to maybe diff --git a/ruleset/expr.go b/ruleset/expr.go index 4512e1f..cd07d30 100644 --- a/ruleset/expr.go +++ b/ruleset/expr.go @@ -23,6 +23,7 @@ import ( type ExprRule struct { Name string `yaml:"name"` Action string `yaml:"action"` + Log bool `yaml:"log"` Modifier ModifierEntry `yaml:"modifier"` Expr string `yaml:"expr"` } @@ -45,7 +46,8 @@ func ExprRulesFromYAML(file string) ([]ExprRule, error) { // compiledExprRule is the internal, compiled representation of an expression rule. type compiledExprRule struct { Name string - Action Action + Action *Action // fallthrough if nil + Log bool ModInstance modifier.Instance Program *vm.Program } @@ -55,6 +57,7 @@ var _ Ruleset = (*exprRuleset)(nil) type exprRuleset struct { Rules []compiledExprRule Ans []analyzer.Analyzer + Logger Logger GeoMatcher *geo.GeoMatcher } @@ -62,25 +65,31 @@ func (r *exprRuleset) Analyzers(info StreamInfo) []analyzer.Analyzer { return r.Ans } -func (r *exprRuleset) Match(info StreamInfo) (MatchResult, error) { +func (r *exprRuleset) Match(info StreamInfo) MatchResult { env := streamInfoToExprEnv(info) for _, rule := range r.Rules { v, err := vm.Run(rule.Program, env) if err != nil { - return MatchResult{ - Action: ActionMaybe, - }, fmt.Errorf("rule %q failed to run: %w", rule.Name, err) + // Log the error and continue to the next rule. + r.Logger.MatchError(info, rule.Name, err) + continue } if vBool, ok := v.(bool); ok && vBool { - return MatchResult{ - Action: rule.Action, - ModInstance: rule.ModInstance, - }, nil + if rule.Log { + r.Logger.Log(info, rule.Name) + } + if rule.Action != nil { + return MatchResult{ + Action: *rule.Action, + ModInstance: rule.ModInstance, + } + } } } + // No match return MatchResult{ Action: ActionMaybe, - }, nil + } } // CompileExprRules compiles a list of expression rules into a ruleset. @@ -97,9 +106,16 @@ func CompileExprRules(rules []ExprRule, ans []analyzer.Analyzer, mods []modifier } // Compile all rules and build a map of analyzers that are used by the rules. for _, rule := range rules { - action, ok := actionStringToAction(rule.Action) - if !ok { - return nil, fmt.Errorf("rule %q has invalid action %q", rule.Name, rule.Action) + if rule.Action == "" && !rule.Log { + return nil, fmt.Errorf("rule %q must have at least one of action or log", rule.Name) + } + var action *Action + if rule.Action != "" { + a, ok := actionStringToAction(rule.Action) + if !ok { + return nil, fmt.Errorf("rule %q has invalid action %q", rule.Name, rule.Action) + } + action = &a } visitor := &idVisitor{Identifiers: make(map[string]bool)} patcher := &idPatcher{} @@ -145,9 +161,10 @@ func CompileExprRules(rules []ExprRule, ans []analyzer.Analyzer, mods []modifier cr := compiledExprRule{ Name: rule.Name, Action: action, + Log: rule.Log, Program: program, } - if action == ActionModify { + if action != nil && *action == ActionModify { mod, ok := fullModMap[rule.Modifier.Name] if !ok { return nil, fmt.Errorf("rule %q uses unknown modifier %q", rule.Name, rule.Modifier.Name) @@ -168,6 +185,7 @@ func CompileExprRules(rules []ExprRule, ans []analyzer.Analyzer, mods []modifier return &exprRuleset{ Rules: compiledRules, Ans: depAns, + Logger: config.Logger, GeoMatcher: geoMatcher, }, nil } @@ -284,6 +302,10 @@ func (p *idPatcher) Visit(node *ast.Node) { switch (*node).(type) { case *ast.CallNode: callNode := (*node).(*ast.CallNode) + if callNode.Func == nil { + // Ignore invalid call nodes + return + } switch callNode.Func.Name { case "cidr": cidrStringNode, ok := callNode.Arguments[1].(*ast.StringNode) diff --git a/ruleset/interface.go b/ruleset/interface.go index 7469d78..60af75d 100644 --- a/ruleset/interface.go +++ b/ruleset/interface.go @@ -90,10 +90,17 @@ type Ruleset interface { Analyzers(StreamInfo) []analyzer.Analyzer // Match matches a stream against the ruleset and returns the result. // It must be safe for concurrent use by multiple workers. - Match(StreamInfo) (MatchResult, error) + Match(StreamInfo) MatchResult +} + +// Logger is the logging interface for the ruleset. +type Logger interface { + Log(info StreamInfo, name string) + MatchError(info StreamInfo, name string, err error) } type BuiltinConfig struct { + Logger Logger GeoSiteFilename string GeoIpFilename string }