mirror of
https://github.com/fatedier/frp.git
synced 2025-04-23 07:01:27 +00:00
commit
c5a8f6ef4a
45
README.md
45
README.md
@ -18,11 +18,6 @@ frp is an open source project with its ongoing development made possible entirel
|
|||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
|
||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||||
@ -97,6 +92,11 @@ frp also offers a P2P connect mode.
|
|||||||
* [Client Plugins](#client-plugins)
|
* [Client Plugins](#client-plugins)
|
||||||
* [Server Manage Plugins](#server-manage-plugins)
|
* [Server Manage Plugins](#server-manage-plugins)
|
||||||
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
* [SSH Tunnel Gateway](#ssh-tunnel-gateway)
|
||||||
|
* [Virtual Network (VirtualNet)](#virtual-network-virtualnet)
|
||||||
|
* [Feature Gates](#feature-gates)
|
||||||
|
* [Available Feature Gates](#available-feature-gates)
|
||||||
|
* [Enabling Feature Gates](#enabling-feature-gates)
|
||||||
|
* [Feature Lifecycle](#feature-lifecycle)
|
||||||
* [Related Projects](#related-projects)
|
* [Related Projects](#related-projects)
|
||||||
* [Contributing](#contributing)
|
* [Contributing](#contributing)
|
||||||
* [Donation](#donation)
|
* [Donation](#donation)
|
||||||
@ -1260,6 +1260,41 @@ frpc tcp --proxy_name "test-tcp" --local_ip 127.0.0.1 --local_port 8080 --remote
|
|||||||
|
|
||||||
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
Please refer to this [document](/doc/ssh_tunnel_gateway.md) for more information.
|
||||||
|
|
||||||
|
### Virtual Network (VirtualNet)
|
||||||
|
|
||||||
|
*Alpha feature added in v0.62.0*
|
||||||
|
|
||||||
|
The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.
|
||||||
|
|
||||||
|
For detailed information about configuration and usage, please refer to the [VirtualNet documentation](/doc/virtual_net.md).
|
||||||
|
|
||||||
|
## Feature Gates
|
||||||
|
|
||||||
|
frp supports feature gates to enable or disable experimental features. This allows users to try out new features before they're considered stable.
|
||||||
|
|
||||||
|
### Available Feature Gates
|
||||||
|
|
||||||
|
| Name | Stage | Default | Description |
|
||||||
|
|------|-------|---------|-------------|
|
||||||
|
| VirtualNet | ALPHA | false | Virtual network capabilities for frp |
|
||||||
|
|
||||||
|
### Enabling Feature Gates
|
||||||
|
|
||||||
|
To enable an experimental feature, add the feature gate to your configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
featureGates = {
|
||||||
|
VirtualNet = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feature Lifecycle
|
||||||
|
|
||||||
|
Features typically go through three stages:
|
||||||
|
1. **ALPHA**: Disabled by default, may be unstable
|
||||||
|
2. **BETA**: May be enabled by default, more stable but still evolving
|
||||||
|
3. **GA (Generally Available)**: Enabled by default, ready for production use
|
||||||
|
|
||||||
## Related Projects
|
## Related Projects
|
||||||
|
|
||||||
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
|
* [gofrp/plugin](https://github.com/gofrp/plugin) - A repository for frp plugins that contains a variety of plugins implemented based on the frp extension mechanism, meeting the customization needs of different scenarios.
|
||||||
|
@ -20,11 +20,6 @@ frp 是一个完全开源的项目,我们的开发工作完全依靠赞助者
|
|||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_jetbrains.jpg">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
<p align="center">
|
|
||||||
<a href="https://workos.com/?utm_campaign=github_repo&utm_medium=referral&utm_content=frp&utm_source=github" target="_blank">
|
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_workos.png">
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
<a href="https://github.com/daytonaio/daytona" target="_blank">
|
||||||
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
<img width="420px" src="https://raw.githubusercontent.com/fatedier/frp/dev/doc/pic/sponsor_daytona.png">
|
||||||
|
11
Release.md
11
Release.md
@ -1,7 +1,8 @@
|
|||||||
|
### Notes
|
||||||
|
|
||||||
|
* **Feature Gates Introduced:** This version introduces a new experimental mechanism called Feature Gates. This allows users to enable or disable specific experimental features before they become generally available. Feature gates can be configured in the `featureGates` map within the configuration file.
|
||||||
|
* **VirtualNet Feature Gate:** The first available feature gate is `VirtualNet`, which enables the experimental Virtual Network functionality (currently in Alpha stage).
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Support metadatas and annotations in frpc proxy commands.
|
* **Virtual Network (VirtualNet):** Introduce experimental virtual network capabilities (Alpha). This allows creating a TUN device managed by frp, enabling Layer 3 connectivity between different clients within the frp network. Requires root/admin privileges and is currently supported on Linux and macOS. Configuration is done via the `virtualNet` section and the `virtual_net` plugin. Enable with feature gate `VirtualNet`. **Note: As an Alpha feature, configuration details may change in future releases.**
|
||||||
|
|
||||||
### Fixes
|
|
||||||
|
|
||||||
* Properly release resources in service.Close() to prevent resource leaks when used as a library.
|
|
@ -29,6 +29,7 @@ import (
|
|||||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/wait"
|
"github.com/fatedier/frp/pkg/util/wait"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SessionContext struct {
|
type SessionContext struct {
|
||||||
@ -46,6 +47,8 @@ type SessionContext struct {
|
|||||||
AuthSetter auth.Setter
|
AuthSetter auth.Setter
|
||||||
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
// Connector is used to create new connections, which could be real TCP connections or virtual streams.
|
||||||
Connector Connector
|
Connector Connector
|
||||||
|
// Virtual net controller
|
||||||
|
VnetController *vnet.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
type Control struct {
|
type Control struct {
|
||||||
@ -99,8 +102,9 @@ func NewControl(ctx context.Context, sessionCtx *SessionContext) (*Control, erro
|
|||||||
ctl.registerMsgHandlers()
|
ctl.registerMsgHandlers()
|
||||||
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
ctl.msgTransporter = transport.NewMessageTransporter(ctl.msgDispatcher.SendChannel())
|
||||||
|
|
||||||
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter)
|
ctl.pm = proxy.NewManager(ctl.ctx, sessionCtx.Common, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common, ctl.connectServer, ctl.msgTransporter)
|
ctl.vm = visitor.NewManager(ctl.ctx, sessionCtx.RunID, sessionCtx.Common,
|
||||||
|
ctl.connectServer, ctl.msgTransporter, sessionCtx.VnetController)
|
||||||
return ctl, nil
|
return ctl, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/limit"
|
"github.com/fatedier/frp/pkg/util/limit"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
var proxyFactoryRegistry = map[reflect.Type]func(*BaseProxy, v1.ProxyConfigurer) Proxy{}
|
||||||
@ -58,6 +59,7 @@ func NewProxy(
|
|||||||
pxyConf v1.ProxyConfigurer,
|
pxyConf v1.ProxyConfigurer,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) (pxy Proxy) {
|
) (pxy Proxy) {
|
||||||
var limiter *rate.Limiter
|
var limiter *rate.Limiter
|
||||||
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
limitBytes := pxyConf.GetBaseConfig().Transport.BandwidthLimit.Bytes()
|
||||||
@ -70,6 +72,7 @@ func NewProxy(
|
|||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
limiter: limiter,
|
limiter: limiter,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
xl: xlog.FromContextSafe(ctx),
|
xl: xlog.FromContextSafe(ctx),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
}
|
}
|
||||||
@ -85,6 +88,7 @@ type BaseProxy struct {
|
|||||||
baseCfg *v1.ProxyBaseConfig
|
baseCfg *v1.ProxyBaseConfig
|
||||||
clientCfg *v1.ClientCommonConfig
|
clientCfg *v1.ClientCommonConfig
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
vnetController *vnet.Controller
|
||||||
limiter *rate.Limiter
|
limiter *rate.Limiter
|
||||||
// proxyPlugin is used to handle connections instead of dialing to local service.
|
// proxyPlugin is used to handle connections instead of dialing to local service.
|
||||||
// It's only validate for TCP protocol now.
|
// It's only validate for TCP protocol now.
|
||||||
@ -98,7 +102,10 @@ type BaseProxy struct {
|
|||||||
|
|
||||||
func (pxy *BaseProxy) Run() error {
|
func (pxy *BaseProxy) Run() error {
|
||||||
if pxy.baseCfg.Plugin.Type != "" {
|
if pxy.baseCfg.Plugin.Type != "" {
|
||||||
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, pxy.baseCfg.Plugin.ClientPluginOptions)
|
p, err := plugin.Create(pxy.baseCfg.Plugin.Type, plugin.PluginContext{
|
||||||
|
Name: pxy.baseCfg.Name,
|
||||||
|
VnetController: pxy.vnetController,
|
||||||
|
}, pxy.baseCfg.Plugin.ClientPluginOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -157,22 +164,22 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check if we need to send proxy protocol info
|
// check if we need to send proxy protocol info
|
||||||
var extraInfo plugin.ExtraInfo
|
var connInfo plugin.ConnectionInfo
|
||||||
if m.SrcAddr != "" && m.SrcPort != 0 {
|
if m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
if m.DstAddr == "" {
|
if m.DstAddr == "" {
|
||||||
m.DstAddr = "127.0.0.1"
|
m.DstAddr = "127.0.0.1"
|
||||||
}
|
}
|
||||||
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
|
srcAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.SrcAddr, strconv.Itoa(int(m.SrcPort))))
|
||||||
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
|
dstAddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort(m.DstAddr, strconv.Itoa(int(m.DstPort))))
|
||||||
extraInfo.SrcAddr = srcAddr
|
connInfo.SrcAddr = srcAddr
|
||||||
extraInfo.DstAddr = dstAddr
|
connInfo.DstAddr = dstAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
if baseCfg.Transport.ProxyProtocolVersion != "" && m.SrcAddr != "" && m.SrcPort != 0 {
|
||||||
h := &pp.Header{
|
h := &pp.Header{
|
||||||
Command: pp.PROXY,
|
Command: pp.PROXY,
|
||||||
SourceAddr: extraInfo.SrcAddr,
|
SourceAddr: connInfo.SrcAddr,
|
||||||
DestinationAddr: extraInfo.DstAddr,
|
DestinationAddr: connInfo.DstAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(m.SrcAddr, ".") {
|
if strings.Contains(m.SrcAddr, ".") {
|
||||||
@ -186,13 +193,15 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
} else if baseCfg.Transport.ProxyProtocolVersion == "v2" {
|
||||||
h.Version = 2
|
h.Version = 2
|
||||||
}
|
}
|
||||||
extraInfo.ProxyProtocolHeader = h
|
connInfo.ProxyProtocolHeader = h
|
||||||
}
|
}
|
||||||
|
connInfo.Conn = remote
|
||||||
|
connInfo.UnderlyingConn = workConn
|
||||||
|
|
||||||
if pxy.proxyPlugin != nil {
|
if pxy.proxyPlugin != nil {
|
||||||
// if plugin is set, let plugin handle connection first
|
// if plugin is set, let plugin handle connection first
|
||||||
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
xl.Debugf("handle by plugin: %s", pxy.proxyPlugin.Name())
|
||||||
pxy.proxyPlugin.Handle(pxy.ctx, remote, workConn, &extraInfo)
|
pxy.proxyPlugin.Handle(pxy.ctx, &connInfo)
|
||||||
xl.Debugf("handle by plugin finished")
|
xl.Debugf("handle by plugin finished")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -210,8 +219,8 @@ func (pxy *BaseProxy) HandleTCPWorkConnection(workConn net.Conn, m *msg.StartWor
|
|||||||
xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
xl.Debugf("join connections, localConn(l[%s] r[%s]) workConn(l[%s] r[%s])", localConn.LocalAddr().String(),
|
||||||
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
localConn.RemoteAddr().String(), workConn.LocalAddr().String(), workConn.RemoteAddr().String())
|
||||||
|
|
||||||
if extraInfo.ProxyProtocolHeader != nil {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := extraInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
workConn.Close()
|
workConn.Close()
|
||||||
xl.Errorf("write proxy protocol header to local conn error: %v", err)
|
xl.Errorf("write proxy protocol header to local conn error: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -28,12 +28,14 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
proxies map[string]*Wrapper
|
proxies map[string]*Wrapper
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
inWorkConnCallback func(*v1.ProxyBaseConfig, net.Conn, *msg.StartWorkConn) bool
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
closed bool
|
closed bool
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -47,10 +49,12 @@ func NewManager(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
proxies: make(map[string]*Wrapper),
|
proxies: make(map[string]*Wrapper),
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
closed: false,
|
closed: false,
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
@ -159,7 +163,7 @@ func (pm *Manager) UpdateAll(proxyCfgs []v1.ProxyConfigurer) {
|
|||||||
for _, cfg := range proxyCfgs {
|
for _, cfg := range proxyCfgs {
|
||||||
name := cfg.GetBaseConfig().Name
|
name := cfg.GetBaseConfig().Name
|
||||||
if _, ok := pm.proxies[name]; !ok {
|
if _, ok := pm.proxies[name]; !ok {
|
||||||
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter)
|
pxy := NewWrapper(pm.ctx, cfg, pm.clientCfg, pm.HandleEvent, pm.msgTransporter, pm.vnetController)
|
||||||
if pm.inWorkConnCallback != nil {
|
if pm.inWorkConnCallback != nil {
|
||||||
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
pxy.SetInWorkConnCallback(pm.inWorkConnCallback)
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,6 +74,8 @@ type Wrapper struct {
|
|||||||
handler event.Handler
|
handler event.Handler
|
||||||
|
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
// vnet controller
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
health uint32
|
health uint32
|
||||||
lastSendStartMsg time.Time
|
lastSendStartMsg time.Time
|
||||||
@ -91,6 +94,7 @@ func NewWrapper(
|
|||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
eventHandler event.Handler,
|
eventHandler event.Handler,
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Wrapper {
|
) *Wrapper {
|
||||||
baseInfo := cfg.GetBaseConfig()
|
baseInfo := cfg.GetBaseConfig()
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(baseInfo.Name)
|
||||||
@ -105,6 +109,7 @@ func NewWrapper(
|
|||||||
healthNotifyCh: make(chan struct{}),
|
healthNotifyCh: make(chan struct{}),
|
||||||
handler: eventHandler,
|
handler: eventHandler,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
xl: xl,
|
xl: xl,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: xlog.NewContext(ctx, xl),
|
||||||
}
|
}
|
||||||
@ -117,7 +122,7 @@ func NewWrapper(
|
|||||||
xl.Tracef("enable health check monitor")
|
xl.Tracef("enable health check monitor")
|
||||||
}
|
}
|
||||||
|
|
||||||
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter)
|
pw.pxy = NewProxy(pw.ctx, pw.Cfg, clientCfg, pw.msgTransporter, pw.vnetController)
|
||||||
return pw
|
return pw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
"github.com/fatedier/frp/pkg/util/wait"
|
"github.com/fatedier/frp/pkg/util/wait"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -110,6 +111,8 @@ type Service struct {
|
|||||||
// web server for admin UI and apis
|
// web server for admin UI and apis
|
||||||
webServer *httppkg.Server
|
webServer *httppkg.Server
|
||||||
|
|
||||||
|
vnetController *vnet.Controller
|
||||||
|
|
||||||
cfgMu sync.RWMutex
|
cfgMu sync.RWMutex
|
||||||
common *v1.ClientCommonConfig
|
common *v1.ClientCommonConfig
|
||||||
proxyCfgs []v1.ProxyConfigurer
|
proxyCfgs []v1.ProxyConfigurer
|
||||||
@ -156,6 +159,9 @@ func NewService(options ServiceOptions) (*Service, error) {
|
|||||||
if webServer != nil {
|
if webServer != nil {
|
||||||
webServer.RouteRegister(s.registerRouteHandlers)
|
webServer.RouteRegister(s.registerRouteHandlers)
|
||||||
}
|
}
|
||||||
|
if options.Common.VirtualNet.Address != "" {
|
||||||
|
s.vnetController = vnet.NewController(options.Common.VirtualNet)
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,6 +175,19 @@ func (svr *Service) Run(ctx context.Context) error {
|
|||||||
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
netpkg.SetDefaultDNSAddress(svr.common.DNSServer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if svr.vnetController != nil {
|
||||||
|
if err := svr.vnetController.Init(); err != nil {
|
||||||
|
log.Errorf("init virtual network controller error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
log.Infof("virtual network controller start...")
|
||||||
|
if err := svr.vnetController.Run(); err != nil {
|
||||||
|
log.Warnf("virtual network controller exit with error: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
if svr.webServer != nil {
|
if svr.webServer != nil {
|
||||||
go func() {
|
go func() {
|
||||||
log.Infof("admin server listen on %s", svr.webServer.Address())
|
log.Infof("admin server listen on %s", svr.webServer.Address())
|
||||||
@ -317,6 +336,7 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
ConnEncrypted: connEncrypted,
|
ConnEncrypted: connEncrypted,
|
||||||
AuthSetter: svr.authSetter,
|
AuthSetter: svr.authSetter,
|
||||||
Connector: connector,
|
Connector: connector,
|
||||||
|
VnetController: svr.vnetController,
|
||||||
}
|
}
|
||||||
ctl, err := NewControl(svr.ctx, sessionCtx)
|
ctl, err := NewControl(svr.ctx, sessionCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,6 +44,10 @@ func (sv *STCPVisitor) Run() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go sv.internalConnWorker()
|
go sv.internalConnWorker()
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,9 +20,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
plugin "github.com/fatedier/frp/pkg/plugin/visitor"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper wraps some functions for visitor to use.
|
// Helper wraps some functions for visitor to use.
|
||||||
@ -34,6 +36,8 @@ type Helper interface {
|
|||||||
// MsgTransporter returns the message transporter that is used to send and receive messages
|
// MsgTransporter returns the message transporter that is used to send and receive messages
|
||||||
// to the frp server through the controller.
|
// to the frp server through the controller.
|
||||||
MsgTransporter() transport.MessageTransporter
|
MsgTransporter() transport.MessageTransporter
|
||||||
|
// VNetController returns the vnet controller that is used to manage the virtual network.
|
||||||
|
VNetController() *vnet.Controller
|
||||||
// RunID returns the run id of current controller.
|
// RunID returns the run id of current controller.
|
||||||
RunID() string
|
RunID() string
|
||||||
}
|
}
|
||||||
@ -50,14 +54,34 @@ func NewVisitor(
|
|||||||
cfg v1.VisitorConfigurer,
|
cfg v1.VisitorConfigurer,
|
||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
helper Helper,
|
helper Helper,
|
||||||
) (visitor Visitor) {
|
) (Visitor, error) {
|
||||||
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
xl := xlog.FromContextSafe(ctx).Spawn().AppendPrefix(cfg.GetBaseConfig().Name)
|
||||||
|
ctx = xlog.NewContext(ctx, xl)
|
||||||
|
var visitor Visitor
|
||||||
baseVisitor := BaseVisitor{
|
baseVisitor := BaseVisitor{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
helper: helper,
|
helper: helper,
|
||||||
ctx: xlog.NewContext(ctx, xl),
|
ctx: ctx,
|
||||||
internalLn: netpkg.NewInternalListener(),
|
internalLn: netpkg.NewInternalListener(),
|
||||||
}
|
}
|
||||||
|
if cfg.GetBaseConfig().Plugin.Type != "" {
|
||||||
|
p, err := plugin.Create(
|
||||||
|
cfg.GetBaseConfig().Plugin.Type,
|
||||||
|
plugin.PluginContext{
|
||||||
|
Name: cfg.GetBaseConfig().Name,
|
||||||
|
Ctx: ctx,
|
||||||
|
VnetController: helper.VNetController(),
|
||||||
|
HandleConn: func(conn net.Conn) {
|
||||||
|
_ = baseVisitor.AcceptConn(conn)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg.GetBaseConfig().Plugin.VisitorPluginOptions,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
baseVisitor.plugin = p
|
||||||
|
}
|
||||||
switch cfg := cfg.(type) {
|
switch cfg := cfg.(type) {
|
||||||
case *v1.STCPVisitorConfig:
|
case *v1.STCPVisitorConfig:
|
||||||
visitor = &STCPVisitor{
|
visitor = &STCPVisitor{
|
||||||
@ -77,7 +101,7 @@ func NewVisitor(
|
|||||||
checkCloseCh: make(chan struct{}),
|
checkCloseCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return visitor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type BaseVisitor struct {
|
type BaseVisitor struct {
|
||||||
@ -85,6 +109,7 @@ type BaseVisitor struct {
|
|||||||
helper Helper
|
helper Helper
|
||||||
l net.Listener
|
l net.Listener
|
||||||
internalLn *netpkg.InternalListener
|
internalLn *netpkg.InternalListener
|
||||||
|
plugin plugin.Plugin
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@ -101,4 +126,7 @@ func (v *BaseVisitor) Close() {
|
|||||||
if v.internalLn != nil {
|
if v.internalLn != nil {
|
||||||
v.internalLn.Close()
|
v.internalLn.Close()
|
||||||
}
|
}
|
||||||
|
if v.plugin != nil {
|
||||||
|
v.plugin.Close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/transport"
|
"github.com/fatedier/frp/pkg/transport"
|
||||||
"github.com/fatedier/frp/pkg/util/xlog"
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -50,6 +51,7 @@ func NewManager(
|
|||||||
clientCfg *v1.ClientCommonConfig,
|
clientCfg *v1.ClientCommonConfig,
|
||||||
connectServer func() (net.Conn, error),
|
connectServer func() (net.Conn, error),
|
||||||
msgTransporter transport.MessageTransporter,
|
msgTransporter transport.MessageTransporter,
|
||||||
|
vnetController *vnet.Controller,
|
||||||
) *Manager {
|
) *Manager {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
clientCfg: clientCfg,
|
clientCfg: clientCfg,
|
||||||
@ -62,6 +64,7 @@ func NewManager(
|
|||||||
m.helper = &visitorHelperImpl{
|
m.helper = &visitorHelperImpl{
|
||||||
connectServerFn: connectServer,
|
connectServerFn: connectServer,
|
||||||
msgTransporter: msgTransporter,
|
msgTransporter: msgTransporter,
|
||||||
|
vnetController: vnetController,
|
||||||
transferConnFn: m.TransferConn,
|
transferConnFn: m.TransferConn,
|
||||||
runID: runID,
|
runID: runID,
|
||||||
}
|
}
|
||||||
@ -112,7 +115,11 @@ func (vm *Manager) Close() {
|
|||||||
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
func (vm *Manager) startVisitor(cfg v1.VisitorConfigurer) (err error) {
|
||||||
xl := xlog.FromContextSafe(vm.ctx)
|
xl := xlog.FromContextSafe(vm.ctx)
|
||||||
name := cfg.GetBaseConfig().Name
|
name := cfg.GetBaseConfig().Name
|
||||||
visitor := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
visitor, err := NewVisitor(vm.ctx, cfg, vm.clientCfg, vm.helper)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("new visitor error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
err = visitor.Run()
|
err = visitor.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warnf("start error: %v", err)
|
xl.Warnf("start error: %v", err)
|
||||||
@ -187,6 +194,7 @@ func (vm *Manager) TransferConn(name string, conn net.Conn) error {
|
|||||||
type visitorHelperImpl struct {
|
type visitorHelperImpl struct {
|
||||||
connectServerFn func() (net.Conn, error)
|
connectServerFn func() (net.Conn, error)
|
||||||
msgTransporter transport.MessageTransporter
|
msgTransporter transport.MessageTransporter
|
||||||
|
vnetController *vnet.Controller
|
||||||
transferConnFn func(name string, conn net.Conn) error
|
transferConnFn func(name string, conn net.Conn) error
|
||||||
runID string
|
runID string
|
||||||
}
|
}
|
||||||
@ -203,6 +211,10 @@ func (v *visitorHelperImpl) MsgTransporter() transport.MessageTransporter {
|
|||||||
return v.msgTransporter
|
return v.msgTransporter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *visitorHelperImpl) VNetController() *vnet.Controller {
|
||||||
|
return v.vnetController
|
||||||
|
}
|
||||||
|
|
||||||
func (v *visitorHelperImpl) RunID() string {
|
func (v *visitorHelperImpl) RunID() string {
|
||||||
return v.runID
|
return v.runID
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,10 @@ func (sv *XTCPVisitor) Run() (err error) {
|
|||||||
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
sv.retryLimiter = rate.NewLimiter(rate.Every(time.Hour/time.Duration(sv.cfg.MaxRetriesAnHour)), sv.cfg.MaxRetriesAnHour)
|
||||||
go sv.keepTunnelOpenWorker()
|
go sv.keepTunnelOpenWorker()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sv.plugin != nil {
|
||||||
|
sv.plugin.Start()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,9 +161,9 @@ func (sv *XTCPVisitor) keepTunnelOpenWorker() {
|
|||||||
|
|
||||||
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
||||||
xl := xlog.FromContextSafe(sv.ctx)
|
xl := xlog.FromContextSafe(sv.ctx)
|
||||||
isConnTrasfered := false
|
isConnTransfered := false
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isConnTrasfered {
|
if !isConnTransfered {
|
||||||
userConn.Close()
|
userConn.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -187,7 +191,7 @@ func (sv *XTCPVisitor) handleConn(userConn net.Conn) {
|
|||||||
xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
xl.Errorf("transfer connection to visitor %s error: %v", sv.cfg.FallbackTo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isConnTrasfered = true
|
isConnTransfered = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/config"
|
"github.com/fatedier/frp/pkg/config"
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
"github.com/fatedier/frp/pkg/config/v1/validation"
|
"github.com/fatedier/frp/pkg/config/v1/validation"
|
||||||
|
"github.com/fatedier/frp/pkg/featuregate"
|
||||||
"github.com/fatedier/frp/pkg/util/log"
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
"github.com/fatedier/frp/pkg/util/version"
|
"github.com/fatedier/frp/pkg/util/version"
|
||||||
)
|
)
|
||||||
@ -120,6 +121,12 @@ func runClient(cfgFilePath string) error {
|
|||||||
"please use yaml/json/toml format instead!\n")
|
"please use yaml/json/toml format instead!\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.FeatureGates) > 0 {
|
||||||
|
if err := featuregate.SetFromMap(cfg.FeatureGates); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
|
warning, err := validation.ValidateAllClientConfig(cfg, proxyCfgs, visitorCfgs)
|
||||||
if warning != nil {
|
if warning != nil {
|
||||||
fmt.Printf("WARNING: %v\n", warning)
|
fmt.Printf("WARNING: %v\n", warning)
|
||||||
|
@ -129,6 +129,15 @@ transport.tls.enable = true
|
|||||||
# It affects the udp and sudp proxy.
|
# It affects the udp and sudp proxy.
|
||||||
udpPacketSize = 1500
|
udpPacketSize = 1500
|
||||||
|
|
||||||
|
# Feature gates allows you to enable or disable experimental features
|
||||||
|
# Format is a map of feature names to boolean values
|
||||||
|
# You can enable specific features:
|
||||||
|
#featureGates = { VirtualNet = true }
|
||||||
|
|
||||||
|
# VirtualNet settings for experimental virtual network capabilities
|
||||||
|
# The virtual network feature requires enabling the VirtualNet feature gate above
|
||||||
|
# virtualNet.address = "100.86.1.1/24"
|
||||||
|
|
||||||
# Additional metadatas for client.
|
# Additional metadatas for client.
|
||||||
metadatas.var1 = "abc"
|
metadatas.var1 = "abc"
|
||||||
metadatas.var2 = "123"
|
metadatas.var2 = "123"
|
||||||
@ -358,6 +367,13 @@ localPort = 22
|
|||||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||||
allowUsers = ["user1", "user2"]
|
allowUsers = ["user1", "user2"]
|
||||||
|
|
||||||
|
[[proxies]]
|
||||||
|
name = "vnet-server"
|
||||||
|
type = "stcp"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
|
||||||
# frpc role visitor -> frps -> frpc role server
|
# frpc role visitor -> frps -> frpc role server
|
||||||
[[visitors]]
|
[[visitors]]
|
||||||
name = "secret_tcp_visitor"
|
name = "secret_tcp_visitor"
|
||||||
@ -389,3 +405,13 @@ maxRetriesAnHour = 8
|
|||||||
minRetryInterval = 90
|
minRetryInterval = 90
|
||||||
# fallbackTo = "stcp_visitor"
|
# fallbackTo = "stcp_visitor"
|
||||||
# fallbackTimeoutMs = 500
|
# fallbackTimeoutMs = 500
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
75
doc/virtual_net.md
Normal file
75
doc/virtual_net.md
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Virtual Network (VirtualNet)
|
||||||
|
|
||||||
|
*Alpha feature added in v0.62.0*
|
||||||
|
|
||||||
|
The VirtualNet feature enables frp to create and manage virtual network connections between clients and visitors through a TUN interface. This allows for IP-level routing between machines, extending frp beyond simple port forwarding to support full network connectivity.
|
||||||
|
|
||||||
|
> **Note**: VirtualNet is an Alpha stage feature and is currently unstable. Its configuration methods and functionality may be adjusted and changed at any time in subsequent versions. Do not use this feature in production environments; it is only recommended for testing and evaluation purposes.
|
||||||
|
|
||||||
|
## Enabling VirtualNet
|
||||||
|
|
||||||
|
Since VirtualNet is currently an alpha feature, you need to enable it with feature gates in your configuration:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Configuration
|
||||||
|
|
||||||
|
To use the virtual network capabilities:
|
||||||
|
|
||||||
|
1. First, configure your frpc with a virtual network address:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml
|
||||||
|
serverAddr = "x.x.x.x"
|
||||||
|
serverPort = 7000
|
||||||
|
featureGates = { VirtualNet = true }
|
||||||
|
|
||||||
|
# Configure the virtual network interface
|
||||||
|
virtualNet.address = "100.86.0.1/24"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. For client proxies, use the `virtual_net` plugin:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml (server side)
|
||||||
|
[[proxies]]
|
||||||
|
name = "vnet-server"
|
||||||
|
type = "stcp"
|
||||||
|
secretKey = "your-secret-key"
|
||||||
|
[proxies.plugin]
|
||||||
|
type = "virtual_net"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. For visitor connections, configure the `virtual_net` visitor plugin:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# frpc.toml (client side)
|
||||||
|
serverAddr = "x.x.x.x"
|
||||||
|
serverPort = 7000
|
||||||
|
featureGates = {
|
||||||
|
VirtualNet = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Configure the virtual network interface
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements and Limitations
|
||||||
|
|
||||||
|
- **Permissions**: Creating a TUN interface requires elevated permissions (root/admin)
|
||||||
|
- **Platform Support**: Currently supported on Linux and macOS
|
||||||
|
- **Default Status**: As an alpha feature, VirtualNet is disabled by default
|
||||||
|
- **Configuration**: A valid IP/CIDR must be provided for each endpoint in the virtual network
|
25
go.mod
25
go.mod
@ -4,7 +4,7 @@ go 1.23.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0
|
github.com/coreos/go-oidc/v3 v3.14.1
|
||||||
github.com/fatedier/golib v0.5.1
|
github.com/fatedier/golib v0.5.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
@ -19,16 +19,19 @@ require (
|
|||||||
github.com/quic-go/quic-go v0.48.2
|
github.com/quic-go/quic-go v0.48.2
|
||||||
github.com/rodaine/table v1.2.0
|
github.com/rodaine/table v1.2.0
|
||||||
github.com/samber/lo v1.47.0
|
github.com/samber/lo v1.47.0
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/tidwall/gjson v1.17.1
|
github.com/tidwall/gjson v1.17.1
|
||||||
|
github.com/vishvananda/netlink v1.3.0
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.13
|
github.com/xtaci/kcp-go/v5 v5.6.13
|
||||||
golang.org/x/crypto v0.30.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/net v0.32.0
|
golang.org/x/net v0.39.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/oauth2 v0.28.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.13.0
|
||||||
golang.org/x/time v0.5.0
|
golang.org/x/time v0.5.0
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
|
||||||
gopkg.in/ini.v1 v1.67.0
|
gopkg.in/ini.v1 v1.67.0
|
||||||
k8s.io/apimachinery v0.28.8
|
k8s.io/apimachinery v0.28.8
|
||||||
k8s.io/client-go v0.28.8
|
k8s.io/client-go v0.28.8
|
||||||
@ -39,10 +42,9 @@ require (
|
|||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 // indirect
|
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 // indirect
|
||||||
@ -64,13 +66,14 @@ require (
|
|||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d // indirect
|
||||||
golang.org/x/mod v0.22.0 // indirect
|
golang.org/x/mod v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.28.0 // indirect
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
65
go.sum
65
go.sum
@ -11,8 +11,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0 h1:tDnXHnLyiTVyT/2zLDGj09pFPkhND8Gl8lnTRhoEaJU=
|
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
|
||||||
github.com/coreos/go-oidc/v3 v3.10.0/go.mod h1:5j11xcw0D3+SGxn6Z/WFADsgcWVMyNAlSQupk0KK3ac=
|
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -25,8 +25,8 @@ github.com/fatedier/golib v0.5.1 h1:hcKAnaw5mdI/1KWRGejxR+i1Hn/NvbY5UsMKDr7o13M=
|
|||||||
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
github.com/fatedier/golib v0.5.1/go.mod h1:W6kIYkIFxHsTzbgqg5piCxIiDo4LzwgTY6R5W8l9NFQ=
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d h1:ynk1ra0RUqDWQfvFi5KtMiSobkVQ3cNc0ODb8CfIETo=
|
||||||
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/fatedier/yamux v0.0.0-20230628132301-7aca4898904d/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
@ -42,17 +42,14 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
|||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
|
||||||
|
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 h1:US08AXzP0bLurpzFUV3Poa9ZijrRdd1zAIOVtoHEiS8=
|
github.com/google/pprof v0.0.0-20241206021119-61a79c692802 h1:US08AXzP0bLurpzFUV3Poa9ZijrRdd1zAIOVtoHEiS8=
|
||||||
@ -117,6 +114,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
|||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8=
|
||||||
|
github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E=
|
||||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
@ -129,8 +128,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
github.com/templexxx/cpu v0.1.1 h1:isxHaxBXpYFWnk2DReuKkigaZyrjs2+9ypIdGP4h+HI=
|
||||||
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
github.com/templexxx/cpu v0.1.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk=
|
||||||
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
github.com/templexxx/xorsimd v0.4.3 h1:9AQTFHd7Bhk3dIT7Al2XeBX5DWOvsUPZCuhyAtNbHjU=
|
||||||
@ -143,6 +143,10 @@ github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
|||||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
|
github.com/vishvananda/netlink v1.3.0 h1:X7l42GfcV4S6E4vHTsw48qbrV+9PVojNfIhZcwQdrZk=
|
||||||
|
github.com/vishvananda/netlink v1.3.0/go.mod h1:i6NetklAujEcC6fK0JPjT8qSwWyO0HLn4UKG+hGqeJs=
|
||||||
|
github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
|
||||||
|
github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
|
github.com/xtaci/kcp-go/v5 v5.6.13 h1:FEjtz9+D4p8t2x4WjciGt/jsIuhlWjjgPCCWjrVR4Hk=
|
||||||
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
github.com/xtaci/kcp-go/v5 v5.6.13/go.mod h1:75S1AKYYzNUSXIv30h+jPKJYZUwqpfvLshu63nCNSOM=
|
||||||
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=
|
||||||
@ -156,8 +160,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||||
golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
|
||||||
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
@ -181,18 +185,18 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||||
golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -201,29 +205,30 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -238,10 +243,12 @@ golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8=
|
|||||||
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
|
||||||
|
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
|
||||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -254,8 +261,6 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
|||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -268,6 +273,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
|
||||||
|
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=
|
k8s.io/apimachinery v0.28.8 h1:hi/nrxHwk4QLV+W/SHve1bypTE59HCDorLY1stBIxKQ=
|
||||||
|
@ -61,6 +61,11 @@ type ClientCommonConfig struct {
|
|||||||
Log LogConfig `json:"log,omitempty"`
|
Log LogConfig `json:"log,omitempty"`
|
||||||
WebServer WebServerConfig `json:"webServer,omitempty"`
|
WebServer WebServerConfig `json:"webServer,omitempty"`
|
||||||
Transport ClientTransportConfig `json:"transport,omitempty"`
|
Transport ClientTransportConfig `json:"transport,omitempty"`
|
||||||
|
VirtualNet VirtualNetConfig `json:"virtualNet,omitempty"`
|
||||||
|
|
||||||
|
// FeatureGates specifies a set of feature gates to enable or disable.
|
||||||
|
// This can be used to enable alpha/beta features or disable default features.
|
||||||
|
FeatureGates map[string]bool `json:"featureGates,omitempty"`
|
||||||
|
|
||||||
// UDPPacketSize specifies the udp packet size
|
// UDPPacketSize specifies the udp packet size
|
||||||
// By default, this value is 1500
|
// By default, this value is 1500
|
||||||
@ -204,3 +209,7 @@ type AuthOIDCClientConfig struct {
|
|||||||
// this field will be transfer to map[string][]string in OIDC token generator.
|
// this field will be transfer to map[string][]string in OIDC token generator.
|
||||||
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
AdditionalEndpointParams map[string]string `json:"additionalEndpointParams,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type VirtualNetConfig struct {
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
}
|
||||||
|
@ -26,6 +26,32 @@ import (
|
|||||||
"github.com/fatedier/frp/pkg/util/util"
|
"github.com/fatedier/frp/pkg/util/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PluginHTTP2HTTPS = "http2https"
|
||||||
|
PluginHTTPProxy = "http_proxy"
|
||||||
|
PluginHTTPS2HTTP = "https2http"
|
||||||
|
PluginHTTPS2HTTPS = "https2https"
|
||||||
|
PluginHTTP2HTTP = "http2http"
|
||||||
|
PluginSocks5 = "socks5"
|
||||||
|
PluginStaticFile = "static_file"
|
||||||
|
PluginUnixDomainSocket = "unix_domain_socket"
|
||||||
|
PluginTLS2Raw = "tls2raw"
|
||||||
|
PluginVirtualNet = "virtual_net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
|
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
||||||
|
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
||||||
|
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
||||||
|
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
||||||
|
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
||||||
|
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
||||||
|
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
|
||||||
|
PluginVirtualNet: reflect.TypeOf(VirtualNetPluginOptions{}),
|
||||||
|
}
|
||||||
|
|
||||||
type ClientPluginOptions interface {
|
type ClientPluginOptions interface {
|
||||||
Complete()
|
Complete()
|
||||||
}
|
}
|
||||||
@ -74,30 +100,6 @@ func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(c.ClientPluginOptions)
|
return json.Marshal(c.ClientPluginOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
PluginHTTP2HTTPS = "http2https"
|
|
||||||
PluginHTTPProxy = "http_proxy"
|
|
||||||
PluginHTTPS2HTTP = "https2http"
|
|
||||||
PluginHTTPS2HTTPS = "https2https"
|
|
||||||
PluginHTTP2HTTP = "http2http"
|
|
||||||
PluginSocks5 = "socks5"
|
|
||||||
PluginStaticFile = "static_file"
|
|
||||||
PluginUnixDomainSocket = "unix_domain_socket"
|
|
||||||
PluginTLS2Raw = "tls2raw"
|
|
||||||
)
|
|
||||||
|
|
||||||
var clientPluginOptionsTypeMap = map[string]reflect.Type{
|
|
||||||
PluginHTTP2HTTPS: reflect.TypeOf(HTTP2HTTPSPluginOptions{}),
|
|
||||||
PluginHTTPProxy: reflect.TypeOf(HTTPProxyPluginOptions{}),
|
|
||||||
PluginHTTPS2HTTP: reflect.TypeOf(HTTPS2HTTPPluginOptions{}),
|
|
||||||
PluginHTTPS2HTTPS: reflect.TypeOf(HTTPS2HTTPSPluginOptions{}),
|
|
||||||
PluginHTTP2HTTP: reflect.TypeOf(HTTP2HTTPPluginOptions{}),
|
|
||||||
PluginSocks5: reflect.TypeOf(Socks5PluginOptions{}),
|
|
||||||
PluginStaticFile: reflect.TypeOf(StaticFilePluginOptions{}),
|
|
||||||
PluginUnixDomainSocket: reflect.TypeOf(UnixDomainSocketPluginOptions{}),
|
|
||||||
PluginTLS2Raw: reflect.TypeOf(TLS2RawPluginOptions{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
type HTTP2HTTPSPluginOptions struct {
|
type HTTP2HTTPSPluginOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
LocalAddr string `json:"localAddr,omitempty"`
|
LocalAddr string `json:"localAddr,omitempty"`
|
||||||
@ -185,3 +187,9 @@ type TLS2RawPluginOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *TLS2RawPluginOptions) Complete() {}
|
func (o *TLS2RawPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type VirtualNetPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *VirtualNetPluginOptions) Complete() {}
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/featuregate"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
||||||
@ -30,6 +31,13 @@ func ValidateClientCommonConfig(c *v1.ClientCommonConfig) (Warning, error) {
|
|||||||
warnings Warning
|
warnings Warning
|
||||||
errs error
|
errs error
|
||||||
)
|
)
|
||||||
|
// validate feature gates
|
||||||
|
if c.VirtualNet.Address != "" {
|
||||||
|
if !featuregate.Enabled(featuregate.VirtualNet) {
|
||||||
|
return warnings, fmt.Errorf("VirtualNet feature is not enabled; enable it by setting the appropriate feature gate flag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !slices.Contains(SupportedAuthMethods, c.Auth.Method) {
|
if !slices.Contains(SupportedAuthMethods, c.Auth.Method) {
|
||||||
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
errs = AppendError(errs, fmt.Errorf("invalid auth method, optional values are %v", SupportedAuthMethods))
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,9 @@ type VisitorBaseConfig struct {
|
|||||||
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
|
// It can be less than 0, it means don't bind to the port and only receive connections redirected from
|
||||||
// other visitors. (This is not supported for SUDP now)
|
// other visitors. (This is not supported for SUDP now)
|
||||||
BindPort int `json:"bindPort,omitempty"`
|
BindPort int `json:"bindPort,omitempty"`
|
||||||
|
|
||||||
|
// Plugin specifies what plugin should be used.
|
||||||
|
Plugin TypedVisitorPluginOptions `json:"plugin,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
|
func (c *VisitorBaseConfig) GetBaseConfig() *VisitorBaseConfig {
|
||||||
|
86
pkg/config/v1/visitor_plugin.go
Normal file
86
pkg/config/v1/visitor_plugin.go
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VisitorPluginVirtualNet = "virtual_net"
|
||||||
|
)
|
||||||
|
|
||||||
|
var visitorPluginOptionsTypeMap = map[string]reflect.Type{
|
||||||
|
VisitorPluginVirtualNet: reflect.TypeOf(VirtualNetVisitorPluginOptions{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
type VisitorPluginOptions interface {
|
||||||
|
Complete()
|
||||||
|
}
|
||||||
|
|
||||||
|
type TypedVisitorPluginOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
VisitorPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorPluginOptions) UnmarshalJSON(b []byte) error {
|
||||||
|
if len(b) == 4 && string(b) == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
typeStruct := struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}{}
|
||||||
|
if err := json.Unmarshal(b, &typeStruct); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Type = typeStruct.Type
|
||||||
|
if c.Type == "" {
|
||||||
|
return errors.New("visitor plugin type is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
v, ok := visitorPluginOptionsTypeMap[typeStruct.Type]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown visitor plugin type: %s", typeStruct.Type)
|
||||||
|
}
|
||||||
|
options := reflect.New(v).Interface().(VisitorPluginOptions)
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(bytes.NewBuffer(b))
|
||||||
|
if DisallowUnknownFields {
|
||||||
|
decoder.DisallowUnknownFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := decoder.Decode(options); err != nil {
|
||||||
|
return fmt.Errorf("unmarshal VisitorPluginOptions error: %v", err)
|
||||||
|
}
|
||||||
|
c.VisitorPluginOptions = options
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *TypedVisitorPluginOptions) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.VisitorPluginOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualNetVisitorPluginOptions struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
DestinationIP string `json:"destinationIP"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *VirtualNetVisitorPluginOptions) Complete() {}
|
219
pkg/featuregate/feature_gate.go
Normal file
219
pkg/featuregate/feature_gate.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package featuregate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Feature represents a feature gate name
|
||||||
|
type Feature string
|
||||||
|
|
||||||
|
// FeatureStage represents the maturity level of a feature
|
||||||
|
type FeatureStage string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Alpha means the feature is experimental and disabled by default
|
||||||
|
Alpha FeatureStage = "ALPHA"
|
||||||
|
// Beta means the feature is more stable but still might change and is disabled by default
|
||||||
|
Beta FeatureStage = "BETA"
|
||||||
|
// GA means the feature is generally available and enabled by default
|
||||||
|
GA FeatureStage = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// FeatureSpec describes a feature and its properties
|
||||||
|
type FeatureSpec struct {
|
||||||
|
// Default is the default enablement state for the feature
|
||||||
|
Default bool
|
||||||
|
// LockToDefault indicates the feature cannot be changed from its default
|
||||||
|
LockToDefault bool
|
||||||
|
// Stage indicates the maturity level of the feature
|
||||||
|
Stage FeatureStage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all available features here
|
||||||
|
var (
|
||||||
|
VirtualNet = Feature("VirtualNet")
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultFeatures defines default features with their specifications
|
||||||
|
var defaultFeatures = map[Feature]FeatureSpec{
|
||||||
|
// Actual features
|
||||||
|
VirtualNet: {Default: false, Stage: Alpha},
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeatureGate indicates whether a given feature is enabled or not
|
||||||
|
type FeatureGate interface {
|
||||||
|
// Enabled returns true if the key is enabled
|
||||||
|
Enabled(key Feature) bool
|
||||||
|
// KnownFeatures returns a slice of strings describing the known features
|
||||||
|
KnownFeatures() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutableFeatureGate allows for dynamic feature gate configuration
|
||||||
|
type MutableFeatureGate interface {
|
||||||
|
FeatureGate
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map[string]bool
|
||||||
|
SetFromMap(m map[string]bool) error
|
||||||
|
// Add adds features to the feature gate
|
||||||
|
Add(features map[Feature]FeatureSpec) error
|
||||||
|
// String returns a string representing the feature gate configuration
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// featureGate implements the FeatureGate and MutableFeatureGate interfaces
|
||||||
|
type featureGate struct {
|
||||||
|
// lock guards writes to known, enabled, and reads/writes of closed
|
||||||
|
lock sync.Mutex
|
||||||
|
// known holds a map[Feature]FeatureSpec
|
||||||
|
known atomic.Value
|
||||||
|
// enabled holds a map[Feature]bool
|
||||||
|
enabled atomic.Value
|
||||||
|
// closed is set to true once the feature gates are considered immutable
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFeatureGate creates a new feature gate with the default features
|
||||||
|
func NewFeatureGate() MutableFeatureGate {
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range defaultFeatures {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &featureGate{}
|
||||||
|
f.known.Store(known)
|
||||||
|
f.enabled.Store(map[Feature]bool{})
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map[string]bool
|
||||||
|
func (f *featureGate) SetFromMap(m map[string]bool) error {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
// Copy existing state
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
enabled := map[Feature]bool{}
|
||||||
|
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||||
|
enabled[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the new settings
|
||||||
|
for k, v := range m {
|
||||||
|
k := Feature(k)
|
||||||
|
featureSpec, ok := known[k]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unrecognized feature gate: %s", k)
|
||||||
|
}
|
||||||
|
if featureSpec.LockToDefault && featureSpec.Default != v {
|
||||||
|
return fmt.Errorf("cannot set feature gate %v to %v, feature is locked to %v", k, v, featureSpec.Default)
|
||||||
|
}
|
||||||
|
enabled[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist the changes
|
||||||
|
f.known.Store(known)
|
||||||
|
f.enabled.Store(enabled)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds features to the feature gate
|
||||||
|
func (f *featureGate) Add(features map[Feature]FeatureSpec) error {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
if f.closed {
|
||||||
|
return fmt.Errorf("cannot add feature gates after the feature gate is closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy existing state
|
||||||
|
known := map[Feature]FeatureSpec{}
|
||||||
|
for k, v := range f.known.Load().(map[Feature]FeatureSpec) {
|
||||||
|
known[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new features
|
||||||
|
for name, spec := range features {
|
||||||
|
if existingSpec, found := known[name]; found {
|
||||||
|
if existingSpec == spec {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Errorf("feature gate %q with different spec already exists: %v", name, existingSpec)
|
||||||
|
}
|
||||||
|
known[name] = spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist changes
|
||||||
|
f.known.Store(known)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string containing all enabled feature gates, formatted as "key1=value1,key2=value2,..."
|
||||||
|
func (f *featureGate) String() string {
|
||||||
|
pairs := []string{}
|
||||||
|
for k, v := range f.enabled.Load().(map[Feature]bool) {
|
||||||
|
pairs = append(pairs, fmt.Sprintf("%s=%t", k, v))
|
||||||
|
}
|
||||||
|
sort.Strings(pairs)
|
||||||
|
return strings.Join(pairs, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enabled returns true if the key is enabled
|
||||||
|
func (f *featureGate) Enabled(key Feature) bool {
|
||||||
|
if v, ok := f.enabled.Load().(map[Feature]bool)[key]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if v, ok := f.known.Load().(map[Feature]FeatureSpec)[key]; ok {
|
||||||
|
return v.Default
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// KnownFeatures returns a slice of strings describing the FeatureGate's known features
|
||||||
|
// GA features are hidden from the list
|
||||||
|
func (f *featureGate) KnownFeatures() []string {
|
||||||
|
knownFeatures := f.known.Load().(map[Feature]FeatureSpec)
|
||||||
|
known := make([]string, 0, len(knownFeatures))
|
||||||
|
for k, v := range knownFeatures {
|
||||||
|
if v.Stage == GA {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
known = append(known, fmt.Sprintf("%s=true|false (%s - default=%t)", k, v.Stage, v.Default))
|
||||||
|
}
|
||||||
|
sort.Strings(known)
|
||||||
|
return known
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default feature gates instance
|
||||||
|
var DefaultFeatureGates = NewFeatureGate()
|
||||||
|
|
||||||
|
// Enabled checks if a feature is enabled in the default feature gates
|
||||||
|
func Enabled(name Feature) bool {
|
||||||
|
return DefaultFeatureGates.Enabled(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFromMap sets feature gate values from a map in the default feature gates
|
||||||
|
func SetFromMap(featureMap map[string]bool) error {
|
||||||
|
return DefaultFeatureGates.SetFromMap(featureMap)
|
||||||
|
}
|
@ -14,13 +14,11 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
|
||||||
@ -42,7 +40,7 @@ type HTTP2HTTPPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTP2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
opts := options.(*v1.HTTP2HTTPPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@ -80,8 +78,8 @@ func NewHTTP2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *HTTP2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,14 +14,12 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
|
|
||||||
@ -43,7 +41,7 @@ type HTTP2HTTPSPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTP2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTP2HTTPSPluginOptions)
|
opts := options.(*v1.HTTP2HTTPSPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@ -89,8 +87,8 @@ func NewHTTP2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *HTTP2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -45,7 +45,7 @@ type HTTPProxy struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPProxyPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPProxyPluginOptions)
|
opts := options.(*v1.HTTPProxyPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
|
|
||||||
@ -69,8 +69,8 @@ func (hp *HTTPProxy) Name() string {
|
|||||||
return v1.PluginHTTPProxy
|
return v1.PluginHTTPProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hp *HTTPProxy) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (hp *HTTPProxy) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
|
|
||||||
sc, rd := libnet.NewSharedConn(wrapConn)
|
sc, rd := libnet.NewSharedConn(wrapConn)
|
||||||
firstBytes := make([]byte, 7)
|
firstBytes := make([]byte, 7)
|
||||||
|
@ -14,15 +14,13 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
@ -48,7 +46,7 @@ type HTTPS2HTTPPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPPluginOptions)
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
|
|
||||||
@ -106,10 +104,10 @@ func NewHTTPS2HTTPPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
func (p *HTTPS2HTTPPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
if extra.SrcAddr != nil {
|
if connInfo.SrcAddr != nil {
|
||||||
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
wrapConn.SetRemoteAddr(connInfo.SrcAddr)
|
||||||
}
|
}
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
@ -14,15 +14,13 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
stdlog "log"
|
stdlog "log"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
@ -48,7 +46,7 @@ type HTTPS2HTTPSPlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPS2HTTPSPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
opts := options.(*v1.HTTPS2HTTPSPluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@ -112,10 +110,10 @@ func NewHTTPS2HTTPSPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo) {
|
func (p *HTTPS2HTTPSPlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
if extra.SrcAddr != nil {
|
if connInfo.SrcAddr != nil {
|
||||||
wrapConn.SetRemoteAddr(extra.SrcAddr)
|
wrapConn.SetRemoteAddr(connInfo.SrcAddr)
|
||||||
}
|
}
|
||||||
_ = p.l.PutConn(wrapConn)
|
_ = p.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -25,13 +25,18 @@ import (
|
|||||||
pp "github.com/pires/go-proxyproto"
|
pp "github.com/pires/go-proxyproto"
|
||||||
|
|
||||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PluginContext struct {
|
||||||
|
Name string
|
||||||
|
VnetController *vnet.Controller
|
||||||
|
}
|
||||||
|
|
||||||
// Creators is used for create plugins to handle connections.
|
// Creators is used for create plugins to handle connections.
|
||||||
var creators = make(map[string]CreatorFn)
|
var creators = make(map[string]CreatorFn)
|
||||||
|
|
||||||
// params has prefix "plugin_"
|
type CreatorFn func(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error)
|
||||||
type CreatorFn func(options v1.ClientPluginOptions) (Plugin, error)
|
|
||||||
|
|
||||||
func Register(name string, fn CreatorFn) {
|
func Register(name string, fn CreatorFn) {
|
||||||
if _, exist := creators[name]; exist {
|
if _, exist := creators[name]; exist {
|
||||||
@ -40,16 +45,19 @@ func Register(name string, fn CreatorFn) {
|
|||||||
creators[name] = fn
|
creators[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
func Create(name string, options v1.ClientPluginOptions) (p Plugin, err error) {
|
func Create(pluginName string, pluginCtx PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||||
if fn, ok := creators[name]; ok {
|
if fn, ok := creators[pluginName]; ok {
|
||||||
p, err = fn(options)
|
p, err = fn(pluginCtx, options)
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("plugin [%s] is not registered", name)
|
err = fmt.Errorf("plugin [%s] is not registered", pluginName)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExtraInfo struct {
|
type ConnectionInfo struct {
|
||||||
|
Conn io.ReadWriteCloser
|
||||||
|
UnderlyingConn net.Conn
|
||||||
|
|
||||||
ProxyProtocolHeader *pp.Header
|
ProxyProtocolHeader *pp.Header
|
||||||
SrcAddr net.Addr
|
SrcAddr net.Addr
|
||||||
DstAddr net.Addr
|
DstAddr net.Addr
|
||||||
@ -58,7 +66,7 @@ type ExtraInfo struct {
|
|||||||
type Plugin interface {
|
type Plugin interface {
|
||||||
Name() string
|
Name() string
|
||||||
|
|
||||||
Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, extra *ExtraInfo)
|
Handle(ctx context.Context, connInfo *ConnectionInfo)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,13 +14,12 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
|
|
||||||
gosocks5 "github.com/armon/go-socks5"
|
gosocks5 "github.com/armon/go-socks5"
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ type Socks5Plugin struct {
|
|||||||
Server *gosocks5.Server
|
Server *gosocks5.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
func NewSocks5Plugin(_ PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||||
opts := options.(*v1.Socks5PluginOptions)
|
opts := options.(*v1.Socks5PluginOptions)
|
||||||
|
|
||||||
cfg := &gosocks5.Config{
|
cfg := &gosocks5.Config{
|
||||||
@ -51,9 +50,9 @@ func NewSocks5Plugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *Socks5Plugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *Socks5Plugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
defer conn.Close()
|
defer connInfo.Conn.Close()
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = sp.Server.ServeConn(wrapConn)
|
_ = sp.Server.ServeConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,10 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -40,7 +38,7 @@ type StaticFilePlugin struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewStaticFilePlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.StaticFilePluginOptions)
|
opts := options.(*v1.StaticFilePluginOptions)
|
||||||
|
|
||||||
listener := NewProxyListener()
|
listener := NewProxyListener()
|
||||||
@ -70,8 +68,8 @@ func NewStaticFilePlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return sp, nil
|
return sp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sp *StaticFilePlugin) Handle(_ context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (sp *StaticFilePlugin) Handle(_ context.Context, connInfo *ConnectionInfo) {
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
_ = sp.l.PutConn(wrapConn)
|
_ = sp.l.PutConn(wrapConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,12 +14,11 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
@ -40,7 +39,7 @@ type TLS2RawPlugin struct {
|
|||||||
tlsConfig *tls.Config
|
tlsConfig *tls.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTLS2RawPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewTLS2RawPlugin(_ PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
opts := options.(*v1.TLS2RawPluginOptions)
|
opts := options.(*v1.TLS2RawPluginOptions)
|
||||||
|
|
||||||
p := &TLS2RawPlugin{
|
p := &TLS2RawPlugin{
|
||||||
@ -55,10 +54,10 @@ func NewTLS2RawPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
|||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *TLS2RawPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, realConn net.Conn, _ *ExtraInfo) {
|
func (p *TLS2RawPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
wrapConn := netpkg.WrapReadWriteCloserToConn(conn, realConn)
|
wrapConn := netpkg.WrapReadWriteCloserToConn(connInfo.Conn, connInfo.UnderlyingConn)
|
||||||
tlsConn := tls.Server(wrapConn, p.tlsConfig)
|
tlsConn := tls.Server(wrapConn, p.tlsConfig)
|
||||||
|
|
||||||
if err := tlsConn.Handshake(); err != nil {
|
if err := tlsConn.Handshake(); err != nil {
|
||||||
|
@ -14,11 +14,10 @@
|
|||||||
|
|
||||||
//go:build !frps
|
//go:build !frps
|
||||||
|
|
||||||
package plugin
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
libio "github.com/fatedier/golib/io"
|
libio "github.com/fatedier/golib/io"
|
||||||
@ -35,7 +34,7 @@ type UnixDomainSocketPlugin struct {
|
|||||||
UnixAddr *net.UnixAddr
|
UnixAddr *net.UnixAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err error) {
|
func NewUnixDomainSocketPlugin(_ PluginContext, options v1.ClientPluginOptions) (p Plugin, err error) {
|
||||||
opts := options.(*v1.UnixDomainSocketPluginOptions)
|
opts := options.(*v1.UnixDomainSocketPluginOptions)
|
||||||
|
|
||||||
unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath)
|
unixAddr, errRet := net.ResolveUnixAddr("unix", opts.UnixPath)
|
||||||
@ -50,20 +49,20 @@ func NewUnixDomainSocketPlugin(options v1.ClientPluginOptions) (p Plugin, err er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, conn io.ReadWriteCloser, _ net.Conn, extra *ExtraInfo) {
|
func (uds *UnixDomainSocketPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
||||||
xl := xlog.FromContextSafe(ctx)
|
xl := xlog.FromContextSafe(ctx)
|
||||||
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
localConn, err := net.DialUnix("unix", nil, uds.UnixAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
xl.Warnf("dial to uds %s error: %v", uds.UnixAddr, err)
|
xl.Warnf("dial to uds %s error: %v", uds.UnixAddr, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if extra.ProxyProtocolHeader != nil {
|
if connInfo.ProxyProtocolHeader != nil {
|
||||||
if _, err := extra.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
if _, err := connInfo.ProxyProtocolHeader.WriteTo(localConn); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
libio.Join(localConn, conn)
|
libio.Join(localConn, connInfo.Conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uds *UnixDomainSocketPlugin) Name() string {
|
func (uds *UnixDomainSocketPlugin) Name() string {
|
||||||
|
71
pkg/plugin/client/virtual_net.go
Normal file
71
pkg/plugin/client/virtual_net.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(v1.PluginVirtualNet, NewVirtualNetPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualNetPlugin struct {
|
||||||
|
pluginCtx PluginContext
|
||||||
|
opts *v1.VirtualNetPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVirtualNetPlugin(pluginCtx PluginContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
|
opts := options.(*v1.VirtualNetPluginOptions)
|
||||||
|
|
||||||
|
p := &VirtualNetPlugin{
|
||||||
|
pluginCtx: pluginCtx,
|
||||||
|
opts: opts,
|
||||||
|
}
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) Handle(ctx context.Context, connInfo *ConnectionInfo) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
|
||||||
|
// Verify if virtual network controller is available
|
||||||
|
if p.pluginCtx.VnetController == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the connection with the controller
|
||||||
|
routeName := p.pluginCtx.Name
|
||||||
|
err := p.pluginCtx.VnetController.RegisterServerConn(ctx, routeName, connInfo.Conn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("virtual net failed to register server connection: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) Name() string {
|
||||||
|
return v1.PluginVirtualNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) Close() error {
|
||||||
|
if p.pluginCtx.VnetController != nil {
|
||||||
|
p.pluginCtx.VnetController.UnregisterServerConn(p.pluginCtx.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package plugin
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fatedier/frp/pkg/msg"
|
"github.com/fatedier/frp/pkg/msg"
|
||||||
|
58
pkg/plugin/visitor/plugin.go
Normal file
58
pkg/plugin/visitor/plugin.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PluginContext struct {
|
||||||
|
Name string
|
||||||
|
Ctx context.Context
|
||||||
|
VnetController *vnet.Controller
|
||||||
|
HandleConn func(net.Conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creators is used for create plugins to handle connections.
|
||||||
|
var creators = make(map[string]CreatorFn)
|
||||||
|
|
||||||
|
type CreatorFn func(pluginCtx PluginContext, options v1.VisitorPluginOptions) (Plugin, error)
|
||||||
|
|
||||||
|
func Register(name string, fn CreatorFn) {
|
||||||
|
if _, exist := creators[name]; exist {
|
||||||
|
panic(fmt.Sprintf("plugin [%s] is already registered", name))
|
||||||
|
}
|
||||||
|
creators[name] = fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func Create(pluginName string, pluginCtx PluginContext, options v1.VisitorPluginOptions) (p Plugin, err error) {
|
||||||
|
if fn, ok := creators[pluginName]; ok {
|
||||||
|
p, err = fn(pluginCtx, options)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("plugin [%s] is not registered", pluginName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
Start()
|
||||||
|
Close() error
|
||||||
|
}
|
232
pkg/plugin/visitor/virtual_net.go
Normal file
232
pkg/plugin/visitor/virtual_net.go
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !frps
|
||||||
|
|
||||||
|
package visitor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
netutil "github.com/fatedier/frp/pkg/util/net"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Register(v1.VisitorPluginVirtualNet, NewVirtualNetPlugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VirtualNetPlugin struct {
|
||||||
|
pluginCtx PluginContext
|
||||||
|
|
||||||
|
routes []net.IPNet
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
controllerConn net.Conn
|
||||||
|
closeSignal chan struct{}
|
||||||
|
|
||||||
|
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 as a single IP 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) Name() string {
|
||||||
|
return v1.VisitorPluginVirtualNet
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) Start() {
|
||||||
|
xl := xlog.FromContextSafe(p.pluginCtx.Ctx)
|
||||||
|
if p.pluginCtx.VnetController == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
routeStr := "unknown"
|
||||||
|
if len(p.routes) > 0 {
|
||||||
|
routeStr = p.routes[0].String()
|
||||||
|
}
|
||||||
|
xl.Infof("Starting VirtualNetPlugin for visitor [%s], attempting to register routes for %s", p.pluginCtx.Name, routeStr)
|
||||||
|
|
||||||
|
go p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) run() {
|
||||||
|
xl := xlog.FromContextSafe(p.ctx)
|
||||||
|
reconnectDelay := 10 * time.Second
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Create a signal channel for this connection attempt
|
||||||
|
currentCloseSignal := make(chan struct{})
|
||||||
|
|
||||||
|
// Store the signal channel under lock
|
||||||
|
p.mu.Lock()
|
||||||
|
p.closeSignal = currentCloseSignal
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin run loop for visitor [%s] stopping (context cancelled before pipe creation).", p.pluginCtx.Name)
|
||||||
|
// Ensure controllerConn from previous loop is cleaned up if necessary
|
||||||
|
p.cleanupControllerConn(xl)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerConn, pluginConn := net.Pipe()
|
||||||
|
|
||||||
|
// Store controllerConn under lock for cleanup purposes
|
||||||
|
p.mu.Lock()
|
||||||
|
p.controllerConn = controllerConn
|
||||||
|
p.mu.Unlock()
|
||||||
|
|
||||||
|
// Wrap pluginConn using CloseNotifyConn
|
||||||
|
pluginNotifyConn := netutil.WrapCloseNotifyConn(pluginConn, func() {
|
||||||
|
close(currentCloseSignal) // Signal the run loop
|
||||||
|
})
|
||||||
|
|
||||||
|
xl.Infof("Attempting to register client route for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
err := p.pluginCtx.VnetController.RegisterClientRoute(p.ctx, p.pluginCtx.Name, p.routes, controllerConn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("Failed to register client route for visitor [%s]: %v. Retrying after %v", p.pluginCtx.Name, err, reconnectDelay)
|
||||||
|
p.cleanupPipePair(xl, controllerConn, pluginConn) // Close both ends on registration failure
|
||||||
|
|
||||||
|
// Wait before retrying registration, unless context is cancelled
|
||||||
|
select {
|
||||||
|
case <-time.After(reconnectDelay):
|
||||||
|
continue // Retry the loop
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin registration retry wait interrupted for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
return // Exit loop if context is cancelled during wait
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Infof("Successfully registered client route for visitor [%s]. Starting connection handler with CloseNotifyConn.", p.pluginCtx.Name)
|
||||||
|
|
||||||
|
// Pass the CloseNotifyConn to HandleConn.
|
||||||
|
// HandleConn is responsible for calling Close() on pluginNotifyConn.
|
||||||
|
p.pluginCtx.HandleConn(pluginNotifyConn)
|
||||||
|
|
||||||
|
// Wait for either the plugin context to be cancelled or the wrapper's Close() to be called via the signal channel.
|
||||||
|
select {
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin run loop stopping for visitor [%s] (context cancelled while waiting).", p.pluginCtx.Name)
|
||||||
|
// Context cancelled, ensure controller side is closed if HandleConn didn't close its side yet.
|
||||||
|
p.cleanupControllerConn(xl)
|
||||||
|
return
|
||||||
|
case <-currentCloseSignal:
|
||||||
|
xl.Infof("Detected connection closed via CloseNotifyConn for visitor [%s].", p.pluginCtx.Name)
|
||||||
|
// HandleConn closed the plugin side (pluginNotifyConn). The closeFn was called, closing currentCloseSignal.
|
||||||
|
// We still need to close the controller side.
|
||||||
|
p.cleanupControllerConn(xl)
|
||||||
|
|
||||||
|
// Add a delay before attempting to reconnect, respecting context cancellation.
|
||||||
|
xl.Infof("Waiting %v before attempting reconnection for visitor [%s]...", reconnectDelay, p.pluginCtx.Name)
|
||||||
|
select {
|
||||||
|
case <-time.After(reconnectDelay):
|
||||||
|
// Delay completed, loop will continue.
|
||||||
|
case <-p.ctx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin reconnection delay interrupted for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
return // Exit loop if context is cancelled during wait
|
||||||
|
}
|
||||||
|
// Loop will continue to reconnect.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop will restart, context check at the beginning of the loop is sufficient.
|
||||||
|
xl.Infof("Re-establishing virtual connection for visitor [%s]...", p.pluginCtx.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupControllerConn closes the current controllerConn (if it exists) under lock.
|
||||||
|
func (p *VirtualNetPlugin) cleanupControllerConn(xl *xlog.Logger) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
if p.controllerConn != nil {
|
||||||
|
xl.Debugf("Cleaning up controllerConn for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
p.controllerConn.Close()
|
||||||
|
p.controllerConn = nil
|
||||||
|
}
|
||||||
|
// Also clear the closeSignal reference for the completed/cancelled connection attempt
|
||||||
|
p.closeSignal = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupPipePair closes both ends of a pipe, used typically when registration fails.
|
||||||
|
func (p *VirtualNetPlugin) cleanupPipePair(xl *xlog.Logger, controllerConn, pluginConn net.Conn) {
|
||||||
|
xl.Debugf("Cleaning up pipe pair for visitor [%s] after registration failure", p.pluginCtx.Name)
|
||||||
|
controllerConn.Close()
|
||||||
|
pluginConn.Close()
|
||||||
|
p.mu.Lock()
|
||||||
|
p.controllerConn = nil // Ensure field is nil if it was briefly set
|
||||||
|
p.closeSignal = nil // Ensure field is nil if it was briefly set
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close initiates the plugin shutdown.
|
||||||
|
func (p *VirtualNetPlugin) Close() error {
|
||||||
|
xl := xlog.FromContextSafe(p.pluginCtx.Ctx) // Use base context for close logging
|
||||||
|
xl.Infof("Closing VirtualNetPlugin for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
|
||||||
|
// 1. Signal the run loop goroutine to stop via context cancellation.
|
||||||
|
p.cancel()
|
||||||
|
|
||||||
|
// 2. Unregister the route from the controller.
|
||||||
|
// This might implicitly cause the VnetController to close its end of the pipe (controllerConn).
|
||||||
|
if p.pluginCtx.VnetController != nil {
|
||||||
|
p.pluginCtx.VnetController.UnregisterClientRoute(p.pluginCtx.Name)
|
||||||
|
xl.Infof("Unregistered client route for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
} else {
|
||||||
|
xl.Warnf("VnetController is nil during close for visitor [%s], cannot unregister route", p.pluginCtx.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Explicitly close the controller side of the pipe managed by this plugin.
|
||||||
|
// This ensures the pipe is broken even if the run loop is stuck or HandleConn hasn't closed its end.
|
||||||
|
p.cleanupControllerConn(xl)
|
||||||
|
xl.Infof("Finished cleaning up connections during close for visitor [%s]", p.pluginCtx.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package version
|
package version
|
||||||
|
|
||||||
var version = "0.61.2"
|
var version = "0.62.0"
|
||||||
|
|
||||||
func Full() string {
|
func Full() string {
|
||||||
return version
|
return version
|
||||||
|
360
pkg/vnet/controller.go
Normal file
360
pkg/vnet/controller.go
Normal file
@ -0,0 +1,360 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
"github.com/songgao/water/waterutil"
|
||||||
|
"golang.org/x/net/ipv4"
|
||||||
|
"golang.org/x/net/ipv6"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/util/log"
|
||||||
|
"github.com/fatedier/frp/pkg/util/xlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPacketSize = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
addr string
|
||||||
|
|
||||||
|
tun io.ReadWriteCloser
|
||||||
|
clientRouter *clientRouter // Route based on destination IP (client mode)
|
||||||
|
serverRouter *serverRouter // Route based on source IP (server mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewController(cfg v1.VirtualNetConfig) *Controller {
|
||||||
|
return &Controller{
|
||||||
|
addr: cfg.Address,
|
||||||
|
clientRouter: newClientRouter(),
|
||||||
|
serverRouter: newServerRouter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Init() error {
|
||||||
|
tunDevice, err := OpenTun(context.Background(), c.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.tun = tunDevice
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Run() error {
|
||||||
|
conn := c.tun
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := pool.GetBuf(maxPacketSize)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
log.Warnf("vnet read from tun error: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.handlePacket(buf[:n])
|
||||||
|
pool.PutBuf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handlePacket processes a single packet. The caller is responsible for managing the buffer.
|
||||||
|
func (c *Controller) handlePacket(buf []byte) {
|
||||||
|
log.Tracef("vnet read from tun [%d]: %s", len(buf), base64.StdEncoding.EncodeToString(buf))
|
||||||
|
|
||||||
|
var src, dst net.IP
|
||||||
|
switch {
|
||||||
|
case waterutil.IsIPv4(buf):
|
||||||
|
header, err := ipv4.ParseHeader(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parse ipv4 header error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Tracef("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
case waterutil.IsIPv6(buf):
|
||||||
|
header, err := ipv6.ParseHeader(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("parse ipv6 header error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Tracef("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
default:
|
||||||
|
log.Tracef("unknown packet, discarded(%d)", len(buf))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConn, err := c.clientRouter.findConn(dst)
|
||||||
|
if err == nil {
|
||||||
|
if err := WriteMessage(targetConn, buf); err != nil {
|
||||||
|
log.Warnf("write to client target conn error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
targetConn, err = c.serverRouter.findConnBySrc(dst)
|
||||||
|
if err == nil {
|
||||||
|
if err := WriteMessage(targetConn, buf); err != nil {
|
||||||
|
log.Warnf("write to server target conn error: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Tracef("no route found for packet from %s to %s", src, dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Stop() error {
|
||||||
|
return c.tun.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client connection read loop
|
||||||
|
func (c *Controller) readLoopClient(ctx context.Context, conn io.ReadWriteCloser) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
for {
|
||||||
|
data, err := ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("client read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case waterutil.IsIPv4(data):
|
||||||
|
header, err := ipv4.ParseHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("parse ipv4 header error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Tracef("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
case waterutil.IsIPv6(data):
|
||||||
|
header, err := ipv6.ParseHeader(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("parse ipv6 header error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
xl.Tracef("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
default:
|
||||||
|
xl.Tracef("unknown packet, discarded(%d)", len(data))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("vnet write to tun (client) [%d]: %s", len(data), base64.StdEncoding.EncodeToString(data))
|
||||||
|
_, err = c.tun.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("client write tun error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server connection read loop
|
||||||
|
func (c *Controller) readLoopServer(ctx context.Context, conn io.ReadWriteCloser) {
|
||||||
|
xl := xlog.FromContextSafe(ctx)
|
||||||
|
for {
|
||||||
|
data, err := ReadMessage(conn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("server read error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register source IP to connection mapping
|
||||||
|
if waterutil.IsIPv4(data) || waterutil.IsIPv6(data) {
|
||||||
|
var src net.IP
|
||||||
|
if waterutil.IsIPv4(data) {
|
||||||
|
header, err := ipv4.ParseHeader(data)
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
header, err := ipv6.ParseHeader(data)
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xl.Tracef("vnet write to tun (server) [%d]: %s", len(data), base64.StdEncoding.EncodeToString(data))
|
||||||
|
_, err = c.tun.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
xl.Warnf("server write tun error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterClientRoute Register client route (based on destination IP CIDR)
|
||||||
|
func (c *Controller) RegisterClientRoute(ctx context.Context, name string, routes []net.IPNet, conn io.ReadWriteCloser) error {
|
||||||
|
if err := c.clientRouter.addRoute(name, routes, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.readLoopClient(ctx, conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServerConn Register server connection (dynamically associates with source IPs)
|
||||||
|
func (c *Controller) RegisterServerConn(ctx context.Context, name string, conn io.ReadWriteCloser) error {
|
||||||
|
if err := c.serverRouter.addConn(name, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.readLoopServer(ctx, conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterServerConn Remove server connection from routing table
|
||||||
|
func (c *Controller) UnregisterServerConn(name string) {
|
||||||
|
c.serverRouter.delConn(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnregisterClientRoute Remove client route from routing table
|
||||||
|
func (c *Controller) UnregisterClientRoute(name string) {
|
||||||
|
c.clientRouter.delRoute(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseRoutes Convert route strings to IPNet objects
|
||||||
|
func ParseRoutes(routeStrings []string) ([]net.IPNet, error) {
|
||||||
|
routes := make([]net.IPNet, 0, len(routeStrings))
|
||||||
|
for _, r := range routeStrings {
|
||||||
|
_, ipNet, err := net.ParseCIDR(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse route %s error: %v", r, err)
|
||||||
|
}
|
||||||
|
routes = append(routes, *ipNet)
|
||||||
|
}
|
||||||
|
return routes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client router (based on destination IP routing)
|
||||||
|
type clientRouter struct {
|
||||||
|
routes map[string]*routeElement
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClientRouter() *clientRouter {
|
||||||
|
return &clientRouter{
|
||||||
|
routes: make(map[string]*routeElement),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) addRoute(name string, routes []net.IPNet, conn io.ReadWriteCloser) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.routes[name] = &routeElement{
|
||||||
|
name: name,
|
||||||
|
routes: routes,
|
||||||
|
conn: conn,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) findConn(dst net.IP) (io.Writer, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
for _, re := range r.routes {
|
||||||
|
for _, route := range re.routes {
|
||||||
|
if route.Contains(dst) {
|
||||||
|
return re.conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("no route found for destination %s", dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *clientRouter) delRoute(name string) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
delete(r.routes, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server router (based on source IP routing)
|
||||||
|
type serverRouter struct {
|
||||||
|
namedConns map[string]io.ReadWriteCloser // Name to connection mapping
|
||||||
|
srcIPConns map[string]io.Writer // Source IP string to connection mapping
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newServerRouter() *serverRouter {
|
||||||
|
return &serverRouter{
|
||||||
|
namedConns: make(map[string]io.ReadWriteCloser),
|
||||||
|
srcIPConns: make(map[string]io.Writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) addConn(name string, conn io.ReadWriteCloser) error {
|
||||||
|
r.mu.Lock()
|
||||||
|
original, ok := r.namedConns[name]
|
||||||
|
r.namedConns[name] = conn
|
||||||
|
r.mu.Unlock()
|
||||||
|
if ok {
|
||||||
|
// Close the original connection if it exists
|
||||||
|
_ = original.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) findConnBySrc(src net.IP) (io.Writer, error) {
|
||||||
|
r.mu.RLock()
|
||||||
|
defer r.mu.RUnlock()
|
||||||
|
conn, exists := r.srcIPConns[src.String()]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("no route found for source %s", src)
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) registerSrcIP(src net.IP, conn io.Writer) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
r.srcIPConns[src.String()] = conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *serverRouter) delConn(name string) {
|
||||||
|
r.mu.Lock()
|
||||||
|
defer r.mu.Unlock()
|
||||||
|
delete(r.namedConns, name)
|
||||||
|
// Note: We don't delete mappings from srcIPConns because we don't know which source IPs are associated with this connection
|
||||||
|
// This might cause dangling references, but they will be overwritten on new connections or restart
|
||||||
|
}
|
||||||
|
|
||||||
|
type routeElement struct {
|
||||||
|
name string
|
||||||
|
routes []net.IPNet
|
||||||
|
conn io.ReadWriteCloser
|
||||||
|
}
|
81
pkg/vnet/message.go
Normal file
81
pkg/vnet/message.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum message size
|
||||||
|
const (
|
||||||
|
maxMessageSize = 1024 * 1024 // 1MB
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format: [length(4 bytes)][data(length bytes)]
|
||||||
|
|
||||||
|
// ReadMessage reads a framed message from the reader
|
||||||
|
func ReadMessage(r io.Reader) ([]byte, error) {
|
||||||
|
// Read length (4 bytes)
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(r, binary.LittleEndian, &length)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read message length error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check length to prevent DoS
|
||||||
|
if length == 0 {
|
||||||
|
return nil, fmt.Errorf("message length is 0")
|
||||||
|
}
|
||||||
|
if length > maxMessageSize {
|
||||||
|
return nil, fmt.Errorf("message too large: %d > %d", length, maxMessageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read message data
|
||||||
|
data := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(r, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read message data error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMessage writes a framed message to the writer
|
||||||
|
func WriteMessage(w io.Writer, data []byte) error {
|
||||||
|
// Get data length
|
||||||
|
length := uint32(len(data))
|
||||||
|
if length == 0 {
|
||||||
|
return fmt.Errorf("message data length is 0")
|
||||||
|
}
|
||||||
|
if length > maxMessageSize {
|
||||||
|
return fmt.Errorf("message too large: %d > %d", length, maxMessageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write length
|
||||||
|
err := binary.Write(w, binary.LittleEndian, length)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write message length error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write message data
|
||||||
|
_, err = w.Write(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write message data error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
77
pkg/vnet/tun.go
Normal file
77
pkg/vnet/tun.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/fatedier/golib/pool"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
offset = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type TunDevice interface {
|
||||||
|
io.ReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func OpenTun(ctx context.Context, addr string) (TunDevice, error) {
|
||||||
|
td, err := openTun(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tunDeviceWrapper{dev: td}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type tunDeviceWrapper struct {
|
||||||
|
dev tun.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Read(p []byte) (int, error) {
|
||||||
|
buf := pool.GetBuf(len(p) + offset)
|
||||||
|
defer pool.PutBuf(buf)
|
||||||
|
|
||||||
|
sz := make([]int, 1)
|
||||||
|
|
||||||
|
n, err := d.dev.Read([][]byte{buf}, sz, offset)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSize := sz[0]
|
||||||
|
if dataSize > len(p) {
|
||||||
|
dataSize = len(p)
|
||||||
|
}
|
||||||
|
copy(p, buf[offset:offset+dataSize])
|
||||||
|
return dataSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Write(p []byte) (int, error) {
|
||||||
|
buf := pool.GetBuf(len(p) + offset)
|
||||||
|
defer pool.PutBuf(buf)
|
||||||
|
|
||||||
|
copy(buf[offset:], p)
|
||||||
|
return d.dev.Write([][]byte{buf}, offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDeviceWrapper) Close() error {
|
||||||
|
return d.dev.Close()
|
||||||
|
}
|
85
pkg/vnet/tun_darwin.go
Normal file
85
pkg/vnet/tun_darwin.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunName = "utun"
|
||||||
|
defaultMTU = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
||||||
|
dev, err := tun.CreateTUN(defaultTunName, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := dev.Name()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate a peer IP for the point-to-point tunnel
|
||||||
|
peerIP := generatePeerIP(ip)
|
||||||
|
|
||||||
|
// Configure the interface with proper point-to-point addressing
|
||||||
|
if err = exec.Command("ifconfig", name, "inet", ip.String(), peerIP.String(), "mtu", fmt.Sprint(defaultMTU), "up").Run(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default route for the tunnel subnet
|
||||||
|
routes := []net.IPNet{*ipNet}
|
||||||
|
if err = addRoutes(name, routes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generatePeerIP creates a peer IP for the point-to-point tunnel
|
||||||
|
// by incrementing the last octet of the IP
|
||||||
|
func generatePeerIP(ip net.IP) net.IP {
|
||||||
|
// Make a copy to avoid modifying the original
|
||||||
|
peerIP := make(net.IP, len(ip))
|
||||||
|
copy(peerIP, ip)
|
||||||
|
|
||||||
|
// Increment the last octet
|
||||||
|
peerIP[len(peerIP)-1]++
|
||||||
|
|
||||||
|
return peerIP
|
||||||
|
}
|
||||||
|
|
||||||
|
// addRoutes configures system routes for the TUN interface
|
||||||
|
func addRoutes(ifName string, routes []net.IPNet) error {
|
||||||
|
for _, route := range routes {
|
||||||
|
routeStr := route.String()
|
||||||
|
if err := exec.Command("route", "add", "-net", routeStr, "-interface", ifName).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
84
pkg/vnet/tun_linux.go
Normal file
84
pkg/vnet/tun_linux.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunName = "utun"
|
||||||
|
defaultMTU = 1420
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, addr string) (tun.Device, error) {
|
||||||
|
dev, err := tun.CreateTUN(defaultTunName, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := dev.Name()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifn, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, cidr, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := netlink.AddrAdd(link, &netlink.Addr{
|
||||||
|
IPNet: &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: cidr.Mask,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(link); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = addRoutes(ifn, cidr); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoutes(ifn *net.Interface, cidr *net.IPNet) error {
|
||||||
|
r := netlink.Route{
|
||||||
|
Dst: cidr,
|
||||||
|
LinkIndex: ifn.Index,
|
||||||
|
}
|
||||||
|
if err := netlink.RouteReplace(&r); err != nil {
|
||||||
|
return fmt.Errorf("add route to %v error: %v", r.Dst, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
29
pkg/vnet/tun_unsupported.go
Normal file
29
pkg/vnet/tun_unsupported.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2025 The frp Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !darwin && !linux
|
||||||
|
|
||||||
|
package vnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTun(_ context.Context, _ string) (tun.Device, error) {
|
||||||
|
return nil, fmt.Errorf("virtual net is not supported on this platform (%s/%s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user