{"record":{"id":"1ed19587e79acb90","repo":"netbirdio/netbird","slug":"output-dnat-not-supported-without-native-firewall","errorCode":null,"errorMessage":"output DNAT not supported without native firewall","messagePattern":"output DNAT not supported without native firewall","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"client/firewall/uspfilter/nat.go","lineNumber":573,"sourceCode":"// RemoveInboundDNAT removes an inbound DNAT rule.\nfunc (m *Manager) RemoveInboundDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\tvar layerType gopacket.LayerType\n\tswitch protocol {\n\tcase firewall.ProtocolTCP:\n\t\tlayerType = layers.LayerTypeTCP\n\tcase firewall.ProtocolUDP:\n\t\tlayerType = layers.LayerTypeUDP\n\tdefault:\n\t\treturn fmt.Errorf(\"unsupported protocol: %s\", protocol)\n\t}\n\n\treturn m.removePortRedirection(localAddr, layerType, originalPort, translatedPort)\n}\n\n// AddOutputDNAT delegates to the native firewall if available.\nfunc (m *Manager) AddOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\tif m.nativeFirewall == nil {\n\t\treturn fmt.Errorf(\"output DNAT not supported without native firewall\")\n\t}\n\treturn m.nativeFirewall.AddOutputDNAT(localAddr, protocol, originalPort, translatedPort)\n}\n\n// RemoveOutputDNAT delegates to the native firewall if available.\nfunc (m *Manager) RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error {\n\tif m.nativeFirewall == nil {\n\t\treturn nil\n\t}\n\treturn m.nativeFirewall.RemoveOutputDNAT(localAddr, protocol, originalPort, translatedPort)\n}\n\n// translateInboundPortDNAT applies port-specific DNAT translation to inbound packets.\nfunc (m *Manager) translateInboundPortDNAT(packetData []byte, d *decoder, srcIP, dstIP netip.Addr) bool {\n\tif !m.portDNATEnabled.Load() {\n\t\treturn false\n\t}\n","sourceCodeStart":555,"sourceCodeEnd":591,"githubUrl":"https://github.com/netbirdio/netbird/blob/93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c/client/firewall/uspfilter/nat.go#L555-L591","documentation":"AddOutputDNAT on the uspfilter Manager only works when a native (kernel) firewall manager was injected at construction time. uspfilter filters traffic in userspace and cannot rewrite the destination of locally originated connections before they leave the host, so output DNAT is delegated to iptables/nftables/pf. When the Manager was built without a native firewall (netstack mode, embedded/WASM clients, platforms where the native manager failed or is unsupported), the call fails fast instead of silently doing nothing.","triggerScenarios":"Running the agent in netstack/embedded mode (no TUN, no kernel firewall) and invoking AddOutputDNAT; constructing the uspfilter Manager on a platform whose native firewall initialization failed and fell through with a nil nativeFirewall; calling the API before the native manager was attached.","commonSituations":"Enabling a feature that requires outbound port redirection (e.g. local SSH-over-relay or DNS redirect setups) on iOS, in the embedded client, or in a container without NET_ADMIN/nftables; misconfigured deployment expecting kernel NAT in a pure userspace deployment.","solutions":["Do not call AddOutputDNAT on deployments without a kernel firewall; feature-detect before using it","Run the agent where a native firewall backend is available (Linux with nftables/iptables and NET_ADMIN, macOS pf, Windows WFP) and verify that backend initialized at startup","If you control Manager construction, pass a native firewall manager or expose a capability flag (HasOutputDNAT) so callers can branch","Treat the error as a hard signal: do not fall back to pretending redirection happened"],"exampleFix":"// before\nif err := m.AddOutputDNAT(addr, proto, 80, 8080); err != nil {\n    return err\n}\n\n// after\nif err := m.AddOutputDNAT(addr, proto, 80, 8080); err != nil {\n    if strings.Contains(err.Error(), \"without native firewall\") {\n        log.Warn(\"output DNAT unavailable in this mode; skipping\")\n        return nil\n    }\n    return err\n}","handlingStrategy":"validation","validationCode":"// surface capability at your boundary before calling the daemon/firewall API\nif runtime.GOOS == \"js\" || embeddedMode {\n    return errors.New(\"output DNAT requires a native firewall backend\")\n}\nif err := m.AddOutputDNAT(addr, proto, orig, translated); err != nil { ... }","typeGuard":"// if you control Manager construction, expose the capability\nfunc (m *Manager) SupportsOutputDNAT() bool { return m.nativeFirewall != nil }","tryCatchPattern":"if err := m.AddOutputDNAT(addr, proto, orig, translated); err != nil {\n    if strings.Contains(err.Error(), \"without native firewall\") {\n        log.Warnf(\"output DNAT unsupported here; feature disabled\")\n        return nil\n    }\n    return err\n}","preventionTips":["Feature-detect native firewall support before enabling features that need output DNAT","Do not assume uspfilter provides NAT: it is a userspace filter, DNAT is delegated","Fail loud at startup when a required feature is unavailable in the chosen mode instead of at first use"],"tags":["go","netbird","firewall","nat","userspace","netstack"],"backgroundTag":null,"analyzedSha":"93e97f4bf1ad715072dcb3fb6cdb1763431b5a9c","analyzedAt":"2026-08-16T03:09:19.136Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}