{"record":{"id":"ffbdb2568290ef5e","repo":"netbirdio/netbird","slug":"add-route-rule-w","errorCode":null,"errorMessage":"add route rule: %w","messagePattern":"add route rule: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"client/internal/acl/manager.go","lineNumber":218,"sourceCode":"\t\t\t}\n\t\t\tdelete(d.peerRulesPairs, pairID)\n\t\t}\n\t}\n\td.peerRulesPairs = newRulePairs\n}\n\nfunc (d *DefaultManager) applyRouteACLs(rules []*mgmProto.RouteFirewallRule, dynamicResolver bool) error {\n\tnewRouteRules := make(map[id.RuleID]struct{}, len(rules))\n\tvar merr *multierror.Error\n\n\t// Apply new rules - firewall manager will return existing rule ID if already present\n\tfor _, rule := range rules {\n\t\tid, err := d.applyRouteACL(rule, dynamicResolver)\n\t\tif err != nil {\n\t\t\tif errors.Is(err, ErrSourceRangesEmpty) {\n\t\t\t\tlog.Debugf(\"skipping empty sources rule with destination %s: %v\", rule.Destination, err)\n\t\t\t} else {\n\t\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"add route rule: %w\", err))\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tnewRouteRules[id] = struct{}{}\n\t}\n\n\t// Clean up old firewall rules\n\tfor id := range d.routeRules {\n\t\tif _, exists := newRouteRules[id]; !exists {\n\t\t\tif err := d.firewall.DeleteRouteRule(id); err != nil {\n\t\t\t\tmerr = multierror.Append(merr, fmt.Errorf(\"delete route rule: %w\", err))\n\t\t\t}\n\t\t\t// implicitly deleted from the map\n\t\t}\n\t}\n\n\td.routeRules = newRouteRules\n\treturn nberrors.FormatErrorOrNil(merr)","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/internal/acl/manager.go#L200-L236","documentation":"Appended per-rule in applyRouteACLs when applyRouteACL fails for a route-firewall (distribution policy) rule. It wraps every failure inside that helper: parse source range, determine destination, invalid protocol/action, and `add route rule` from firewall.AddRouteFiltering itself. ErrSourceRangesEmpty is deliberately excluded (skipped at debug), so seeing this error means a real parsing or backend problem. Failed rules are not inserted into newRouteRules, so the route ACL is not enforced for that traffic.","triggerScenarios":"Management sending a RouteFirewallRule with any malformed field (bad CIDR in SourceRanges or Destination, protocol enum the agent does not know), or the firewall backend failing AddRouteFiltering (nftables/iptables error, full ruleset).","commonSituations":"Network routes (access control policies on routing groups) configured after a management upgrade while agents lag behind; API-authored policies with invalid CIDRs; hosts where the firewall backend is degraded.","solutions":["Unwrap the %w chain in the engine logs - the inner message names the exact field (source range, destination, protocol) or the backend","Fix the offending policy field in management (valid CIDR, known protocol)","Update the agent so its RuleProtocol/RuleAction enums cover what management emits","Verify firewall backend health with `nft list ruleset` / `iptables-save` and agent capabilities"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// management-side guard for route firewall rules\nfunc validRouteRule(r *mgmProto.RouteFirewallRule) error {\n    if len(r.SourceRanges) == 0 {\n        return errors.New(\"no source ranges\")\n    }\n    for _, s := range r.SourceRanges {\n        if _, err := netip.ParsePrefix(s); err != nil {\n            return fmt.Errorf(\"source %q: %w\", s, err)\n        }\n    }\n    if !r.IsDynamic {\n        if _, err := netip.ParsePrefix(r.Destination); err != nil {\n            return fmt.Errorf(\"destination %q: %w\", r.Destination, err)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := d.applyRouteACLs(rules, dyn); err != nil {\n    var merr *multierror.Error\n    if errors.As(err, &merr) {\n        for _, e := range merr.Errors {\n            switch {\n            case strings.Contains(e.Error(), \"parse source range\"),\n                strings.Contains(e.Error(), \"determine destination\"),\n                strings.Contains(e.Error(), \"invalid protocol\"):\n                log.Errorf(\"management sent malformed rule - fix policy: %v\", e)\n            case strings.Contains(e.Error(), \"add route rule\"):\n                log.Errorf(\"firewall backend rejected rule: %v\", e)\n            case strings.Contains(e.Error(), \"delete route rule\"):\n                log.Warnf(\"reconcile divergence: %v\", e)\n            }\n        }\n    }\n}","preventionTips":["Run the validRouteRule guard above in management before dispatching network maps","Keep agent >= management version so enum and format expectations match","Remember ErrSourceRangesEmpty rules are silently skipped by design - do not add sources later expecting old rules to apply"],"tags":["go","netbird","acl","routing","firewall","policy"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}