# How frp Virtual Network (VirtualNet) Enables Full Layer-3 Connectivity

> Discover how frp's Virtual Network provides full Layer-3 connectivity. frp VirtualNet uses a TUN interface and IP routing to enable seamless LAN-like traffic for TCP, UDP, and ICMP.

- Repository: [fatedier/frp](https://github.com/fatedier/frp)
- Tags: deep-dive
- Published: 2026-02-26

---

**frp's Virtual Network creates a virtual Layer-3 network by combining a TUN interface on the server with bidirectional IP routing tables, allowing any IP traffic (TCP, UDP, ICMP) to traverse the frp tunnel as if both machines shared the same LAN.**

The frp (Fast Reverse Proxy) project by `fatedier/frp` traditionally provided port-forwarding capabilities, but the Virtual Network feature fundamentally extends this architecture. By implementing a virtual TUN device and intelligent routing logic in the source code, frp enables full IP-level connectivity between client and server machines without requiring modifications to application traffic.

## Core Architecture Components

The VirtualNet implementation relies on three tightly-coupled components working across the frp client (frpc) and server (frps):

### TUN Interface and VNet Controller

Located in [`pkg/vnet/controller.go`](https://github.com/fatedier/frp/blob/main/pkg/vnet/controller.go), the **VNet Controller** manages a virtual TUN network interface on the frps side. This controller reads raw IP packets from the TUN device, parses IPv4/IPv6 headers, and dispatches them to the appropriate tunnel connection. The controller maintains two distinct routing tables to handle traffic flow in both directions.

### Visitor Plugin (Client-Side Route Registration)

The visitor plugin in [`pkg/plugin/visitor/virtual_net.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/visitor/virtual_net.go) operates on the frpc side. When enabled, it registers **client routes** (destination IPs or CIDR blocks) with the VNet Controller using `RegisterClientRoute`. The plugin receives a pipe-connected `net.Conn` and implements exponential back-off reconnection logic when the tunnel disconnects. This establishes the outbound path from server to client.

### Client Plugin (Server-Side Connection Handling)

On the frps side, [`pkg/plugin/client/virtual_net.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/client/virtual_net.go) creates **server routes** for each incoming `virtual_net` proxy connection. Through `StartServerConnReadLoop`, the plugin registers the connection with the controller and continuously reads traffic from the tunnel, feeding it back into the TUN device for delivery to the target virtual IP.

## Bidirectional Packet Routing Mechanism

The controller implements a two-router system to achieve full-mesh connectivity:

- **`clientRouter`**: Routes **outbound** packets from the virtual network to client connections based on the packet's **destination IP** using `findConn(dst)`.
- **`serverRouter`**: Routes **inbound** packets to server connections based on the packet's **source IP** using `findConnBySrc(src)`.

When `handlePacket` processes a packet from the TUN interface in [`pkg/vnet/controller.go`](https://github.com/fatedier/frp/blob/main/pkg/vnet/controller.go), it first attempts to match the destination IP against the client router:

```go
if targetConn, err := c.clientRouter.findConn(dst); err == nil {
    WriteMessage(targetConn, buf)
    return
}

```

If no client route exists, it falls back to the server router to find a connection by source IP:

```go
if targetConn, err := c.serverRouter.findConnBySrc(src); err == nil {
    WriteMessage(targetConn, buf)
    return
}
log.Tracef("no route found for packet from %s to %s", src, dst)

```

This dual-router design ensures that traffic originating from either side of the tunnel can reach its destination, enabling protocols like ICMP (ping), UDP, and arbitrary TCP connections that traditional port-forwarding cannot support.

## Configuring frp Virtual Network

### Server Configuration (frps)

Enable the VirtualNet feature gate and configure a `virtual_net` plugin proxy:

```toml

# frps.toml

featureGates = { VirtualNet = true }

[[proxies]]
name = "vnet-server"
type = "stcp"
secretKey = "your-secret-key"

[proxies.plugin]
type = "virtual_net"

```

### Client Configuration (frpc)

Assign a virtual IP address and configure the visitor plugin:

```toml

# frpc.toml

featureGates = { VirtualNet = true }
serverAddr = "x.x.x.x"
serverPort = 7000

virtualNet.address = "100.86.0.2/24"

[[visitors]]
name = "vnet-visitor"
type = "stcp"
serverName = "vnet-server"
secretKey = "your-secret-key"
bindPort = -1

[visitors.plugin]
type = "virtual_net"
destinationIP = "100.86.0.1"

```

The `virtualNet.address` assigns an IP to the local TUN interface, while `destinationIP` registers the route to the remote peer. When the visitor connection closes, the plugin automatically uses exponential back-off to re-establish the route registration.

## Summary

- frp Virtual Network operates at **Layer-3 (IP)** using a TUN interface in [`pkg/vnet/controller.go`](https://github.com/fatedier/frp/blob/main/pkg/vnet/controller.go)
- **Bidirectional routing** is handled by separate `clientRouter` (destination-based) and `serverRouter` (source-based) tables
- The **visitor plugin** ([`pkg/plugin/visitor/virtual_net.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/visitor/virtual_net.go)) registers client-side routes with `RegisterClientRoute` and automatic reconnection logic
- The **client plugin** ([`pkg/plugin/client/virtual_net.go`](https://github.com/fatedier/frp/blob/main/pkg/plugin/client/virtual_net.go)) manages server-side connections through `StartServerConnReadLoop`
- Configuration requires enabling the `VirtualNet` feature gate and assigning virtual IPs in the same CIDR range
- Supports **any IP protocol** including ICMP, UDP, and TCP without application modification

## Frequently Asked Questions

### What is the difference between frp Virtual Network and standard TCP/UDP proxies?

Standard frp proxies forward specific ports at Layer-4, requiring each service to be mapped individually. Virtual Network operates at Layer-3, creating a virtual LAN where entire IP subnets are reachable. This allows ping (ICMP), dynamic port allocation, and protocols that don't work through traditional NAT traversal according to the `fatedier/frp` source code.

### Does Virtual Network require root or administrative privileges?

Yes. Creating and managing TUN interfaces requires elevated privileges on both the frps server and frpc client machines. The controller in [`pkg/vnet/controller.go`](https://github.com/fatedier/frp/blob/main/pkg/vnet/controller.go) must open the TUN device, which is a privileged operation on Linux, macOS, and Windows systems.

### Can multiple clients join the same virtual network?

Yes. Each client registers its unique virtual IP (e.g., `100.86.0.2/24`, `100.86.0.3/24`) with the server-side router. The `clientRouter` maintains separate entries for each destination IP, allowing full mesh connectivity between all joined clients and the server within the configured subnet.

### How does frp handle packet routing conflicts or missing routes?

When `handlePacket` cannot find a matching route in either the `clientRouter` (by destination) or `serverRouter` (by source), the packet is dropped with a trace log: `"no route found for packet from %s to %s"`. This prevents IP leakage while the exponential back-off mechanism in the visitor plugin ensures routes are re-established automatically after network disruptions.