tailscale/tailscale · error

add table: %w

Error message

add table: %w

What it means

createTableIfNotExist actually tried to create the table (c.AddTable + Flush) and the kernel rejected it. Classic errnos: EPERM (no CAP_NET_ADMIN), EOPNOTSUPP/ENOTSUP (kernel lacks nftables), EEXIST (another writer created the table between the probe and the flush). This is the definitive 'cannot create nftables table' signal.

Source

Thrown at util/linuxfw/nftables_runner.go:421

		}
	}
	return nil, nil
}

// createTableIfNotExist creates a nftables table via connection c if it does
// not exist within the given family.
func createTableIfNotExist(c *nftables.Conn, family nftables.TableFamily, name string) (*nftables.Table, error) {
	if t, err := getTableIfExists(c, family, name); err != nil {
		return nil, fmt.Errorf("get table: %w", err)
	} else if t != nil {
		return t, nil
	}
	t := c.AddTable(&nftables.Table{
		Family: family,
		Name:   name,
	})
	if err := c.Flush(); err != nil {
		return nil, fmt.Errorf("add table: %w", err)
	}
	return t, nil
}

type errorChainNotFound struct {
	chainName string
	tableName string
}

func (e errorChainNotFound) Error() string {
	return fmt.Sprintf("chain %s not found in table %s", e.chainName, e.tableName)
}

// getChainFromTable returns the chain with the given name from the given table.
// Note that a chain name is unique within a table.
func getChainFromTable(c *nftables.Conn, table *nftables.Table, name string) (*nftables.Chain, error) {
	if table == nil {
		return nil, fmt.Errorf("could not get chain %q: table not initialized", name)

View on GitHub (pinned to 6e0912f979)

Solutions

  1. Grant CAP_NET_ADMIN (root) to the process.
  2. Enable nftables in the kernel; verify with 'nft list tables' or 'ls /proc/net/netfilter/nfnetlink_queue' style probes.
  3. On EEXIST races, re-run the ensure — it is idempotent and the probe will now find the table.
  4. Fall back to the iptables runner if the kernel permanently lacks nftables.

Example fix

// before
conn := nftables.New()
t, err := createTableIfNotExist(conn, nftables.TableFamilyIPv4, "nat")

// after
if err != nil {
	switch {
	case errors.Is(err, unix.EPERM):
		return fmt.Errorf("need CAP_NET_ADMIN: %w", err)
	case errors.Is(err, unix.EOPNOTSUPP):
		return fmt.Errorf("kernel lacks nftables support: %w", err)
	case errors.Is(err, unix.EEXIST):
		t, err = createTableIfNotExist(conn, nftables.TableFamilyIPv4, "nat") // re-probe
	}
}
Defensive patterns

Strategy: retry

Type guard

func isCreateRace(err error) bool { return errors.Is(err, unix.EEXIST) }
func isUnsupported(err error) bool { return errors.Is(err, unix.EOPNOTSUPP) }

Try / catch

t, err := createTableIfNotExist(conn, family, name)
if errors.Is(err, unix.EEXIST) {
	t, err = createTableIfNotExist(conn, family, name) // probe now finds it
}
if errors.Is(err, unix.EOPNOTSUPP) {
	// switch to iptables runner permanently
}

Prevention

When it happens

Trigger: First firewall setup on a host: AddBase, AddDNATRule, EnsureSNATForDst, ClampMSSToPMTU, or svc-chain ensure creating nat/filter tables when the kernel refuses the batch.

Common situations: Missing capabilities in containers (most common); old or stripped kernels without CONFIG_NF_TABLES; racing creators; netlink batch overflow with huge rule sets.

Related errors


AI-assisted analysis of tailscale/tailscale@6e0912f979 (2026-08-18). Data as JSON: /api/errors/463fcbd3367053e6. Report an issue: GitHub.