{"record":{"id":"3e5889131742a7a0","repo":"TechnitiumSoftware/DnsServer","slug":"external-to-internal-translation-entries-must-have-3e5889","errorCode":null,"errorMessage":"External to internal translation entries must have same prefix length: {strExternal} - {strInternal}","messagePattern":"External to internal translation entries must have same prefix length: (.+?) - (.+?)","errorType":"exception","errorClass":"InvalidDataException","httpStatus":null,"severity":"error","filePath":"Apps/SplitHorizonApp/AddressTranslation.cs","lineNumber":389,"sourceCode":"                JsonElement jsonExternalToInternalTranslation = jsonGroup.GetProperty(\"externalToInternalTranslation\");\n\n                Dictionary<IPAddress, IPAddress> externalToInternalIpTranslation = new Dictionary<IPAddress, IPAddress>();\n                Dictionary<IPAddress, IPAddress> internalToExternalIpTranslation = new Dictionary<IPAddress, IPAddress>();\n                List<KeyValuePair<NetworkAddress, NetworkAddress>> externalToInternalNetworkTranslation = new List<KeyValuePair<NetworkAddress, NetworkAddress>>();\n\n                foreach (JsonProperty jsonProperty in jsonExternalToInternalTranslation.EnumerateObject())\n                {\n                    string strExternal = jsonProperty.Name;\n                    string strInternal = jsonProperty.Value.GetString();\n\n                    NetworkAddress external = NetworkAddress.Parse(strExternal);\n                    NetworkAddress @internal = NetworkAddress.Parse(strInternal);\n\n                    if (external.AddressFamily != @internal.AddressFamily)\n                        throw new InvalidDataException(\"External to internal translation entries must have same address family: \" + strExternal + \" - \" + strInternal);\n\n                    if (external.PrefixLength != @internal.PrefixLength)\n                        throw new InvalidDataException(\"External to internal translation entries must have same prefix length: \" + strExternal + \" - \" + strInternal);\n\n                    if (\n                        ((external.AddressFamily == AddressFamily.InterNetwork) && (external.PrefixLength == 32)) ||\n                        ((external.AddressFamily == AddressFamily.InterNetworkV6) && (external.PrefixLength == 128))\n                       )\n                    {\n                        externalToInternalIpTranslation.TryAdd(external.Address, @internal.Address);\n\n                        if (_translateReverseLookups)\n                            internalToExternalIpTranslation.TryAdd(@internal.Address, external.Address);\n                    }\n                    else\n                    {\n                        externalToInternalNetworkTranslation.Add(new KeyValuePair<NetworkAddress, NetworkAddress>(external, @internal));\n                    }\n                }\n\n                _externalToInternalTranslation = externalToInternalIpTranslation;","sourceCodeStart":371,"sourceCodeEnd":407,"githubUrl":"https://github.com/TechnitiumSoftware/DnsServer/blob/d0484b6c1e7439cdc53d67d81e9c876cda2ad756/Apps/SplitHorizonApp/AddressTranslation.cs#L371-L407","documentation":"SplitHorizonApp's external-to-internal translation additionally requires both endpoints to share the same prefix length (e.g. /24 to /24, /32 to /32). This InvalidDataException fires when the external and internal NetworkAddress values have matching families but different PrefixLength values, since asymmetric prefixes would translate the wrong number of addresses.","triggerScenarios":"A translation entry where the external CIDR and internal CIDR differ in mask size, e.g. external '203.0.113.0/24' (256 addresses) mapped to internal '10.0.0.1/32' (single host).","commonSituations":"Configuring a /29 external block to a /24 internal subnet (or vice versa) thinking the translation auto-scales; forgetting that 1:1 NAT needs equal-size pools.","solutions":["Match the prefix length on both sides of each translation entry.","For single-host translation use /32 (IPv4) or /128 (IPv6) on both external and internal.","If pool sizes genuinely differ, split into multiple equal-prefix entries instead."],"exampleFix":"// before\n\"externalToInternalTranslation\": {\n  \"203.0.113.0/29\": \"10.0.0.1/32\"\n}\n\n// after\n\"externalToInternalTranslation\": {\n  \"203.0.113.0/29\": \"10.0.0.0/29\"\n}","handlingStrategy":"validation","validationCode":"static void AssertSamePrefixLength(string ext, string intl)\n{\n    var ep = int.Parse(ext.Split('/')[1]);\n    var ip = int.Parse(intl.Split('/')[1]);\n    if (ep != ip)\n        throw new InvalidDataException($\"Prefix length mismatch: {ext} - {intl}\");\n}\n\nforeach (var (ext, intl) in translationEntries)\n    AssertSamePrefixLength(ext, intl);","typeGuard":null,"tryCatchPattern":"try { /* load translation config */ }\ncatch (InvalidDataException ex) when (ex.Message.Contains(\"same prefix length\"))\n{ /* report the entry and abort config apply */ }","preventionTips":["Mirror prefix lengths on both sides of every translation entry.","Use /32 and /128 for single-host NAT on both endpoints.","Document that 1:1 translation requires equal-size pools."],"tags":["splithorizon","config","networking","nat","validation"],"backgroundTag":null,"analyzedSha":"d0484b6c1e7439cdc53d67d81e9c876cda2ad756","analyzedAt":"2026-08-13T22:57:35.508Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}