{"record":{"id":"1627b3b6fb404df5","repo":"fatedier/frp","slug":"destinationip-is-required","errorCode":null,"errorMessage":"destinationIP is required","messagePattern":"destinationIP is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/plugin/visitor/virtual_net.go","lineNumber":62,"sourceCode":"\n\tconsecutiveErrors int // Tracks consecutive connection errors for exponential backoff\n\n\tctx    context.Context\n\tcancel context.CancelFunc\n}\n\nfunc NewVirtualNetPlugin(pluginCtx PluginContext, options v1.VisitorPluginOptions) (Plugin, error) {\n\topts := options.(*v1.VirtualNetVisitorPluginOptions)\n\n\tp := &VirtualNetPlugin{\n\t\tpluginCtx: pluginCtx,\n\t\troutes:    make([]net.IPNet, 0),\n\t}\n\n\tp.ctx, p.cancel = context.WithCancel(pluginCtx.Ctx)\n\n\tif opts.DestinationIP == \"\" {\n\t\treturn nil, errors.New(\"destinationIP is required\")\n\t}\n\n\t// Parse DestinationIP and create a host route.\n\tip := net.ParseIP(opts.DestinationIP)\n\tif ip == nil {\n\t\treturn nil, fmt.Errorf(\"invalid destination IP address [%s]\", opts.DestinationIP)\n\t}\n\n\tvar mask net.IPMask\n\tif ip.To4() != nil {\n\t\tmask = net.CIDRMask(32, 32) // /32 for IPv4\n\t} else {\n\t\tmask = net.CIDRMask(128, 128) // /128 for IPv6\n\t}\n\tp.routes = append(p.routes, net.IPNet{IP: ip, Mask: mask})\n\n\treturn p, nil\n}","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/fatedier/frp/blob/6c8a8d0a97d03b44e9528d30b30c70cb9d61b405/pkg/plugin/visitor/virtual_net.go#L44-L80","documentation":"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.","triggerScenarios":"Declaring a [[visitors]] with type = \"virtual_net\" in frpc.toml without destinationIP; programmatic VisitorConfig for virtual_net with DestinationIP left empty string.","commonSituations":"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.","solutions":["Add destinationIP to the visitor block, e.g. destinationIP = \"10.6.6.6\" — the frpc TUN device routes to this address","Ensure it is a valid single IP (no CIDR suffix); invalid IPs raise a separate 'invalid destination IP address' error","Confirm server side has virtualNetConfig enabled and the visitor type is spelled virtual_net"],"exampleFix":"# before\n[[visitors]]\nname = \"vt\"\ntype = \"virtual_net\"\nserverName = \"p2p-server\"\nsecretKey = \"s\"\nbindAddr = \"127.0.0.1\"\n\n# after\n[[visitors]]\nname = \"vt\"\ntype = \"virtual_net\"\nserverName = \"p2p-server\"\nsecretKey = \"s\"\nbindAddr = \"127.0.0.1\"\ndestinationIP = \"10.6.6.6\"","handlingStrategy":"validation","validationCode":"for _, v := range visitorCfgs {\n    if v.GetType() == \"virtual_net\" {\n        vn := v.(*v1.VirtualNetVisitorPluginOptions) // or read via typed config\n        if vn.DestinationIP == \"\" {\n            return fmt.Errorf(\"visitor %s: virtual_net requires destinationIP\", v.GetName())\n        }\n        if net.ParseIP(vn.DestinationIP) == nil {\n            return fmt.Errorf(\"visitor %s: invalid destinationIP %q\", v.GetName(), vn.DestinationIP)\n        }\n    }\n}","typeGuard":"func validDestinationIP(ip string) bool {\n    return net.ParseIP(ip) != nil // single IP, no CIDR suffix\n}","tryCatchPattern":null,"preventionTips":["frpc verify catches a missing destinationIP before the visitor is created","Reserve a dedicated overlay subnet for virtual_net and pick destinationIPs from it deliberately"],"tags":["frp","go","visitor","virtual-net","config"],"backgroundTag":null,"analyzedSha":"6c8a8d0a97d03b44e9528d30b30c70cb9d61b405","analyzedAt":"2026-08-15T06:53:27.215Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}