netbirdio/netbird · error

enable forwarding: %w

Error message

enable forwarding: %w

What it means

AddDNATRule enables IP forwarding through ipfwdstate.IPForwardingState before queueing NAT rules. RequestForwarding writes net.ipv4.ip_forward or per-interface net.ipv6.conf.*.forwarding sysctls on the first reference and increments a refcount. The wrapped 'enable forwarding' error means the sysctl write failed, so no DNAT rule is added and no refcount is held.

Source

Thrown at client/firewall/nftables/router_linux.go:1571

}

func (r *router) AddDNATRule(rule firewall.ForwardRule) (firewall.Rule, error) {
	ruleKey := rule.ID()
	if _, exists := r.rules[ruleKey+dnatSuffix]; exists {
		return rule, nil
	}

	protoNum, err := r.af.protoNum(rule.Protocol)
	if err != nil {
		return nil, fmt.Errorf("convert protocol to number: %w", err)
	}

	// Request forwarding before queueing rules: addDnatRedirect/addDnatMasq
	// buffer netlink messages on r.conn that the next caller's Flush would
	// commit if we returned without flushing them ourselves.
	v6 := r.af.tableFamily == nftables.TableFamilyIPv6
	if err := r.ipFwdState.RequestForwarding(v6); err != nil {
		return nil, fmt.Errorf("enable forwarding: %w", err)
	}

	if err := r.addDnatRedirect(rule, protoNum, ruleKey); err != nil {
		if rerr := r.ipFwdState.ReleaseForwarding(v6); rerr != nil {
			log.Warnf("rollback forwarding refcount: %v", rerr)
		}
		return nil, err
	}

	r.addDnatMasq(rule, protoNum, ruleKey)

	// Unlike iptables, there's no point in adding "out" rules in the forward chain here as our policy is ACCEPT.
	// To overcome DROP policies in other chains, we'd have to add rules to the chains there.
	// We also cannot just add "oif <iface> accept" there and filter in our own table as we don't know what is supposed to be allowed.
	// TODO: find chains with drop policies and add rules there

	if err := r.conn.Flush(); err != nil {
		if rerr := r.ipFwdState.ReleaseForwarding(v6); rerr != nil {

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Test manually: 'sudo sysctl -w net.ipv4.ip_forward=1'; if that fails, fix privileges or procfs first.
  2. Run the agent as root or grant CAP_NET_ADMIN (containers: --cap-add=NET_ADMIN --sysctl net.ipv4.ip_forward=1).
  3. Ensure /proc is mounted read-write and not masked by a read-only bind mount over /proc/sys.
  4. For IPv6 failures confirm IPv6 is enabled (check /proc/cmdline for ipv6.disable=1, read net.ipv6.conf.all.forwarding) or drop the v6 configuration.
  5. Note that the routing path downgrades v6 enable failures to warnings, but this DNAT path returns them: v6 DNAT requires a writable sysctl.

Example fix

# before: container without privileges
 docker run netbird/netbird ... # AddDNATRule -> "enable forwarding: ... permission denied"

# after
 docker run --cap-add=NET_ADMIN --sysctl net.ipv4.ip_forward=1 netbird/netbird ...
Defensive patterns

Strategy: validation

Validate before calling

// Verify the forwarding sysctl is writable before attempting DNAT setup
func forwardingSysctlWritable(v6 bool) error {
    path := "/proc/sys/net/ipv4/ip_forward"
    if v6 {
        path = "/proc/sys/net/ipv6/conf/all/forwarding"
    }
    f, err := os.OpenFile(path, os.O_WRONLY, 0)
    if err != nil {
        return fmt.Errorf("sysctl %s not writable: %w", path, err)
    }
    return f.Close()
}

Try / catch

if err := r.ipFwdState.RequestForwarding(v6); err != nil {
    if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EROFS) {
        return fmt.Errorf("cannot enable forwarding (privileged, writable /proc/sys required): %w", err)
    }
    return fmt.Errorf("enable forwarding: %w", err)
}

Prevention

When it happens

Trigger: Running without root or CAP_NET_ADMIN; /proc/sys mounted read-only (unprivileged container, immutable OS); IPv6-family request on a kernel with IPv6 disabled; /proc not mounted at all.

Common situations: Agent in Docker without --privileged or cap_add NET_ADMIN; hardened or immutable hosts; ipv6.disable=1 on the kernel command line while a v6 route or DNAT rule is configured; security software blocking sysctl writes.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/efe5dadd64fc2b2b. Report an issue: GitHub.