{"record":{"id":"25e9377e6ccd0c8b","repo":"slackhq/nebula","slug":"s-rule-v-local-cidr-did-not-parse-s","errorCode":null,"errorMessage":"%s rule #%v; local_cidr did not parse; %s","messagePattern":"(.+?) rule #(.+?); local_cidr did not parse; (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"firewall.go","lineNumber":397,"sourceCode":"\t\t\t}\n\t\tdefault:\n\t\t\treturn fmt.Errorf(\"%s rule #%v; proto was not understood; `%s`\", table, i, r.Proto)\n\t\t}\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; %s %s\", table, i, errPort, err)\n\t\t}\n\n\t\tif r.Cidr != \"\" && r.Cidr != \"any\" {\n\t\t\t_, err = netip.ParsePrefix(r.Cidr)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s rule #%v; cidr did not parse; %s\", table, i, err)\n\t\t\t}\n\t\t}\n\n\t\tif r.LocalCidr != \"\" && r.LocalCidr != \"any\" {\n\t\t\t_, err = netip.ParsePrefix(r.LocalCidr)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"%s rule #%v; local_cidr did not parse; %s\", table, i, err)\n\t\t\t}\n\t\t}\n\n\t\tif warning := r.sanity(); warning != nil {\n\t\t\tl.Warn(\"firewall rule sanity check\",\n\t\t\t\t\"table\", table,\n\t\t\t\t\"rule\", i,\n\t\t\t\t\"warning\", warning,\n\t\t\t)\n\t\t}\n\n\t\terr = fw.AddRule(inbound, proto, startPort, endPort, r.Groups, r.Host, r.Cidr, r.LocalCidr, r.CAName, r.CASha)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"%s rule #%v; `%s`\", table, i, err)\n\t\t}\n\t}\n\n\treturn nil","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/firewall.go#L379-L415","documentation":"Identical to the cidr check, AddFirewallRulesFromConfig validates each rule's local_cidr with netip.ParsePrefix unless it is empty or \"any\". local_cidr constrains the destination address inside the tunnel, so it must be a valid IP prefix.","triggerScenarios":"A rule with local_cidr like \"192.168.1.0\" (missing /prefix), \"0.0.0.0/0/0\", or other malformed prefix strings, and not \"any\".","commonSituations":"Copy-paste errors when configuring unsafe_routes/routed ranges, forgetting the prefix length, or confusing local_cidr with the host.cidr format.","solutions":["Provide a valid CIDR prefix for local_cidr (e.g. \"192.168.1.0/24\").","Use \"any\" if the rule should match any local destination.","Confirm the value is a prefix (IP + /mask), not a bare IP or hostname."],"exampleFix":"// before (config)\n- local_cidr: 192.168.1.0\n  port: 22\n  proto: tcp\n  group: admins\n// after\n- local_cidr: 192.168.1.0/24\n  port: 22\n  proto: tcp\n  group: admins","handlingStrategy":"validation","validationCode":"import \"netip\"\n\nfunc validateLocalCidr(rules []map[string]any) error {\n    for i, r := range rules {\n        v, _ := r[\"local_cidr\"].(string)\n        if v == \"\" || v == \"any\" { continue }\n        if _, err := netip.ParsePrefix(v); err != nil {\n            return fmt.Errorf(\"rule #%d: local_cidr %q invalid: %v\", i, v, err)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err := fw.AddFirewallRulesFromConfig(l, \"inbound\", rules); err != nil {\n    if strings.Contains(err.Error(), \"local_cidr did not parse\") {\n        log.Fatalf(\"local_cidr must be a CIDR prefix or 'any': %v\", err)\n    }\n    return err\n}","preventionTips":["Remember local_cidr is the tunneled destination prefix — always include /mask.","Don't confuse local_cidr with the host's tunl/unsafe_routes cidr syntax.","Validate both cidr and local_cidr with netip.ParsePrefix in CI config tests."],"tags":["firewall","configuration","cidr","networking"],"backgroundTag":"invalid-cidr-notation","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}