netbirdio/netbird · error

get net: %w

Error message

get net: %w

What it means

Returned by Client.Dial when Engine.GetNet fails: either the WireGuard interface is not initialized yet (engine mid-teardown or not fully up) or the interface has no netstack ("failed to get netstack"), which is the expected condition when the client was created with NoUserspace=true (kernel mode has no userspace stack to dial from).

Source

Thrown at client/embed/embed.go:357

	c.mu.Lock()
	defer c.mu.Unlock()
	if c.config == nil {
		return profilemanager.Config{}, ErrConfigNotInitialized
	}
	return *c.config, nil
}

// Dial dials a network address in the netbird network.
// Not applicable if the userspace networking mode is disabled.
func (c *Client) Dial(ctx context.Context, network, address string) (net.Conn, error) {
	engine, err := c.getEngine()
	if err != nil {
		return nil, err
	}

	nsnet, err := engine.GetNet()
	if err != nil {
		return nil, fmt.Errorf("get net: %w", err)
	}

	return nsnet.DialContext(ctx, network, address)
}

// DialContext dials a network address in the netbird network with context
func (c *Client) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
	return c.Dial(ctx, network, address)
}

// ListenTCP listens on the given address in the netbird network.
// Not applicable if the userspace networking mode is disabled.
func (c *Client) ListenTCP(address string) (net.Listener, error) {
	nsnet, addr, err := c.getNet()
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. If you need Dial/NewHTTPClient, create the client with NoUserspace=false (the default userspace/netstack mode).
  2. Guard calls with the client lifecycle: only dial between successful Start and Stop, and stop outstanding requests before Stop.
  3. Retry briefly if the error is a transient not-yet-initialized window.

Example fix

// before
client, err := embed.New(embed.Options{NoUserspace: true, ...})
conn, err := client.Dial(ctx, "tcp", "10.10.0.5:8080")

// after
client, err := embed.New(embed.Options{NoUserspace: false, ...})
conn, err := client.Dial(ctx, "tcp", "10.10.0.5:8080")
Defensive patterns

Strategy: validation

Validate before calling

if opts.NoUserspace {
    return errors.New("dial APIs require userspace mode; set NoUserspace=false")
}

Try / catch

conn, err := client.Dial(ctx, network, addr)
if err != nil {
    if errors.Is(err, embed.ErrClientNotStarted) || errors.Is(err, embed.ErrEngineNotStarted) || strings.Contains(err.Error(), "get net") {
        // lifecycle/mode problem: not retried away by looping
    }
}

Prevention

When it happens

Trigger: Calling Dial after Start succeeded but with NoUserspace=true in Options; calling Dial concurrently with Stop so the interface is already torn down; calling Dial in the window before the interface is fully initialized.

Common situations: An embedding app that toggles NoUserspace for performance finds Dial/DialContext/NewHTTPClient all failing; races between an HTTP client built via NewHTTPClient and client shutdown; dialing right after Start returns on a slow engine.

Related errors


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