fatedier/frp · error

destinationIP is required

Error message

destinationIP is required

What it means

Returned by NewVirtualNetPlugin in the visitor plugin package when the virtual_net visitor options omit destinationIP. The plugin creates a TUN-backed route to a /32 (IPv4) or /128 (IPv6) host route derived from that IP, so it is mandatory at construction time.

Source

Thrown at pkg/plugin/visitor/virtual_net.go:62

	consecutiveErrors int // Tracks consecutive connection errors for exponential backoff

	ctx    context.Context
	cancel context.CancelFunc
}

func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.VisitorPluginOptions) (Plugin, error) {
	opts := options.(*v1.VirtualNetVisitorPluginOptions)

	p := &VirtualNetPlugin{
		pluginCtx: pluginCtx,
		routes:    make([]net.IPNet, 0),
	}

	p.ctx, p.cancel = context.WithCancel(pluginCtx.Ctx)

	if opts.DestinationIP == "" {
		return nil, errors.New("destinationIP is required")
	}

	// Parse DestinationIP and create a host route.
	ip := net.ParseIP(opts.DestinationIP)
	if ip == nil {
		return nil, fmt.Errorf("invalid destination IP address [%s]", opts.DestinationIP)
	}

	var mask net.IPMask
	if ip.To4() != nil {
		mask = net.CIDRMask(32, 32) // /32 for IPv4
	} else {
		mask = net.CIDRMask(128, 128) // /128 for IPv6
	}
	p.routes = append(p.routes, net.IPNet{IP: ip, Mask: mask})

	return p, nil
}

View on GitHub (pinned to 6c8a8d0a97)

Solutions

  1. Add destinationIP to the visitor block, e.g. destinationIP = "10.6.6.6" — the frpc TUN device routes to this address
  2. Ensure it is a valid single IP (no CIDR suffix); invalid IPs raise a separate 'invalid destination IP address' error
  3. Confirm server side has virtualNetConfig enabled and the visitor type is spelled virtual_net

Example fix

# before
[[visitors]]
name = "vt"
type = "virtual_net"
serverName = "p2p-server"
secretKey = "s"
bindAddr = "127.0.0.1"

# after
[[visitors]]
name = "vt"
type = "virtual_net"
serverName = "p2p-server"
secretKey = "s"
bindAddr = "127.0.0.1"
destinationIP = "10.6.6.6"
Defensive patterns

Strategy: validation

Validate before calling

for _, v := range visitorCfgs {
    if v.GetType() == "virtual_net" {
        vn := v.(*v1.VirtualNetVisitorPluginOptions) // or read via typed config
        if vn.DestinationIP == "" {
            return fmt.Errorf("visitor %s: virtual_net requires destinationIP", v.GetName())
        }
        if net.ParseIP(vn.DestinationIP) == nil {
            return fmt.Errorf("visitor %s: invalid destinationIP %q", v.GetName(), vn.DestinationIP)
        }
    }
}

Type guard

func validDestinationIP(ip string) bool {
    return net.ParseIP(ip) != nil // single IP, no CIDR suffix
}

Prevention

When it happens

Trigger: Declaring a [[visitors]] with type = "virtual_net" in frpc.toml without destinationIP; programmatic VisitorConfig for virtual_net with DestinationIP left empty string.

Common situations: Copying a virtual_net visitor example and editing only bindAddr/port; upgrading frp versions where the option was renamed or moved; enabling virtual net feature (server side virtualNetConfig) without completing the visitor config.

Related errors


AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15). Data as JSON: /api/errors/1627b3b6fb404df5. Report an issue: GitHub.