netbirdio/netbird · error

add outbound MSS clamp rule: %w

Error message

add outbound MSS clamp rule: %w

What it means

addMSSClampingRules() appends the TCPMSS rule to the NETBIRD-RT-MSSCLAMP mangle chain: outgoing TCP SYN packets on the NetBird interface get --set-mss = mtu - (40 for IPv4 / 60 for IPv6). Failure is logged, not propagated, so the clamp chain exists but stays empty. Note mss is computed with uint16 arithmetic, so an MTU below the header size would underflow to a huge value.

Source

Thrown at client/firewall/iptables/router_linux.go:587

	// Add jump rule from FORWARD chain in mangle table to our custom chain
	jumpRule := []string{
		"-j", chainRTMSSCLAMP,
	}
	if err := r.iptablesClient.Insert(tableMangle, chainFORWARD, 1, jumpRule...); err != nil {
		return fmt.Errorf("add jump to MSS clamp chain: %w", err)
	}
	r.rules[jumpMSSClamp] = jumpRule

	ruleOut := []string{
		"-o", r.wgIface.Name(),
		"-p", "tcp",
		"--tcp-flags", "SYN,RST", "SYN",
		"-j", "TCPMSS",
		"--set-mss", fmt.Sprintf("%d", mss),
	}
	if err := r.iptablesClient.Append(tableMangle, chainRTMSSCLAMP, ruleOut...); err != nil {
		return fmt.Errorf("add outbound MSS clamp rule: %w", err)
	}
	r.rules["mss-clamp-out"] = ruleOut

	return nil
}

func (r *router) insertEstablishedRule(chain string) error {
	establishedRule := getConntrackEstablished()

	err := r.iptablesClient.Insert(tableFilter, chain, 1, establishedRule...)
	if err != nil {
		return fmt.Errorf("failed to insert established rule: %v", err)
	}

	ruleKey := "established-" + chain
	r.rules[ruleKey] = establishedRule

	return nil

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Reproduce manually with the MSS value from the log to confirm whether xt_tcpmss is the problem
  2. `modprobe xt_tcpmss`
  3. Check the configured MTU (management network config / interface): it must exceed 40 (IPv4) or 60 (IPv6) by a sane margin
  4. Verify the clamp chain received the rule: `sudo iptables -t mangle -S NETBIRD-RT-MSSCLAMP`
  5. Restart the agent after fixing modules/MTU so addMSSClampingRules runs again

Example fix

// before: uint16 underflow possible when mtu < header size
mss := r.mtu - overhead

// after: guard the subtraction before building the rule
if int(r.mtu) <= int(overhead) {
    return fmt.Errorf("mtu %d too small for MSS clamp (needs > %d)", r.mtu, overhead)
}
mss := r.mtu - overhead
Defensive patterns

Strategy: validation

Validate before calling

func validMSS(mtu uint16, v6 bool) (uint16, error) {
    overhead := uint16(40)
    if v6 {
        overhead = 60
    }
    if mtu <= overhead {
        return 0, fmt.Errorf("mtu %d must exceed header size %d", mtu, overhead)
    }
    return mtu - overhead, nil
}

Try / catch

Log-only at the call site; on catch, check whether xt_tcpmss exists and whether the MSS value was sane, then retry once after loading the module.

Prevention

When it happens

Trigger: `iptables -t mangle -A NETBIRD-RT-MSSCLAMP -o wt0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss <mss>` failing when xt_tcpmss is missing, the NetBird interface name is stale, or iptables rejects the computed MSS value; also triggered by misconfigured tiny MTUs making the subtraction underflow.

Common situations: Kernels without xt_tcpmss (or it built as an unloaded module); overlay MTU set unusually low (bad management-side MTU config); IPv6 peers (v6=true, 60-byte header) on a host with an MTU below 60 (extreme misconfig); silent failure noticed only as PMTU blackholes for routed TCP.

Related errors


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