mirror of
https://github.com/fatedier/frp.git
synced 2025-04-23 09:41:27 +00:00
virtual-net: initial
This commit is contained in:
parent
773169e0c4
commit
22b852a935
@ -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.ProxyContext{
|
||||||
|
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())
|
||||||
@ -311,12 +330,13 @@ func (svr *Service) loopLoginUntilSuccess(maxInterval time.Duration, firstLoginE
|
|||||||
connEncrypted = false
|
connEncrypted = false
|
||||||
}
|
}
|
||||||
sessionCtx := &SessionContext{
|
sessionCtx := &SessionContext{
|
||||||
Common: svr.common,
|
Common: svr.common,
|
||||||
RunID: svr.runID,
|
RunID: svr.runID,
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
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,8 @@ func (sv *STCPVisitor) Run() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
go sv.internalConnWorker()
|
go sv.internalConnWorker()
|
||||||
|
|
||||||
|
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.VisitorContext{
|
||||||
|
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,8 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sv.plugin.Start()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,9 +159,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 +189,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
go.mod
5
go.mod
@ -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.9.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.30.0
|
||||||
golang.org/x/net v0.32.0
|
golang.org/x/net v0.32.0
|
||||||
golang.org/x/oauth2 v0.16.0
|
golang.org/x/oauth2 v0.16.0
|
||||||
golang.org/x/sync v0.10.0
|
golang.org/x/sync v0.10.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
|
||||||
@ -64,12 +67,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.28.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/tools v0.28.0 // indirect
|
golang.org/x/tools v0.28.0 // indirect
|
||||||
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // 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
|
||||||
|
16
go.sum
16
go.sum
@ -48,6 +48,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
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=
|
||||||
@ -117,6 +119,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=
|
||||||
@ -143,6 +147,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=
|
||||||
@ -201,9 +209,11 @@ 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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
@ -238,6 +248,10 @@ 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 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||||
@ -268,6 +282,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=
|
||||||
|
@ -58,9 +58,10 @@ type ClientCommonConfig struct {
|
|||||||
// set.
|
// set.
|
||||||
Start []string `json:"start,omitempty"`
|
Start []string `json:"start,omitempty"`
|
||||||
|
|
||||||
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"`
|
||||||
|
|
||||||
// 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 +205,8 @@ 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"`
|
||||||
|
Routes []string `json:"routes,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,10 @@ type TLS2RawPluginOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *TLS2RawPluginOptions) Complete() {}
|
func (o *TLS2RawPluginOptions) Complete() {}
|
||||||
|
|
||||||
|
type VirtualNetPluginOptions struct {
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
AllowedIPs []string `json:"allowedIPs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *VirtualNetPluginOptions) Complete() {}
|
@ -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() {}
|
@ -18,9 +18,7 @@ package plugin
|
|||||||
|
|
||||||
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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,9 +19,7 @@ package plugin
|
|||||||
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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ type HTTPProxy struct {
|
|||||||
s *http.Server
|
s *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPProxyPlugin(options v1.ClientPluginOptions) (Plugin, error) {
|
func NewHTTPProxyPlugin(_ ProxyContext, 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)
|
||||||
|
@ -20,9 +20,7 @@ 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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,7 @@ 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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
@ -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 ProxyContext 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(pxyCtx ProxyContext, 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, pxyCtx ProxyContext, 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(pxyCtx, 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,6 @@ 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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ package plugin
|
|||||||
|
|
||||||
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(_ ProxyContext, 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ package plugin
|
|||||||
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(_ ProxyContext, 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 {
|
||||||
|
@ -18,7 +18,6 @@ package plugin
|
|||||||
|
|
||||||
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(_ ProxyContext, 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 plugin
|
||||||
|
|
||||||
|
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 {
|
||||||
|
pxyCtx ProxyContext
|
||||||
|
opts *v1.VirtualNetPluginOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVirtualNetPlugin(pxyCtx ProxyContext, options v1.ClientPluginOptions) (Plugin, error) {
|
||||||
|
opts := options.(*v1.VirtualNetPluginOptions)
|
||||||
|
|
||||||
|
p := &VirtualNetPlugin{
|
||||||
|
pxyCtx: pxyCtx,
|
||||||
|
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.pxyCtx.VnetController == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the connection with the controller
|
||||||
|
routeName := p.pxyCtx.Name
|
||||||
|
err := p.pxyCtx.VnetController.RegisterServerConn(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.pxyCtx.VnetController != nil {
|
||||||
|
p.pxyCtx.VnetController.UnregisterServerConn(p.pxyCtx.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
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 VisitorContext 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(visitorCtx VisitorContext, 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, visitorCtx VisitorContext, options v1.VisitorPluginOptions) (p Plugin, err error) {
|
||||||
|
if fn, ok := creators[pluginName]; ok {
|
||||||
|
p, err = fn(visitorCtx, options)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("plugin [%s] is not registered", pluginName)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Plugin interface {
|
||||||
|
Name() string
|
||||||
|
Start()
|
||||||
|
Close() error
|
||||||
|
}
|
234
pkg/plugin/visitor/virtual_net.go
Normal file
234
pkg/plugin/visitor/virtual_net.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
// 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 {
|
||||||
|
visitorCtx VisitorContext
|
||||||
|
|
||||||
|
routes []net.IPNet
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
controllerConn net.Conn
|
||||||
|
closeSignal chan struct{}
|
||||||
|
|
||||||
|
pluginCtx context.Context
|
||||||
|
pluginCancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVirtualNetPlugin(visitorCtx VisitorContext, options v1.VisitorPluginOptions) (Plugin, error) {
|
||||||
|
opts := options.(*v1.VirtualNetVisitorPluginOptions)
|
||||||
|
|
||||||
|
p := &VirtualNetPlugin{
|
||||||
|
visitorCtx: visitorCtx,
|
||||||
|
routes: make([]net.IPNet, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pluginCtx, p.pluginCancel = context.WithCancel(visitorCtx.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.visitorCtx.Ctx)
|
||||||
|
if p.visitorCtx.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.visitorCtx.Name, routeStr)
|
||||||
|
|
||||||
|
go p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *VirtualNetPlugin) run() {
|
||||||
|
xl := xlog.FromContextSafe(p.pluginCtx)
|
||||||
|
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.pluginCtx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin run loop for visitor [%s] stopping (context cancelled before pipe creation).", p.visitorCtx.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.visitorCtx.Name)
|
||||||
|
err := p.visitorCtx.VnetController.RegisterClientRoute(p.visitorCtx.Name, p.routes, controllerConn)
|
||||||
|
if err != nil {
|
||||||
|
xl.Errorf("Failed to register client route for visitor [%s]: %v. Retrying after %v", p.visitorCtx.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.pluginCtx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin registration retry wait interrupted for visitor [%s]", p.visitorCtx.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.visitorCtx.Name)
|
||||||
|
|
||||||
|
// Pass the CloseNotifyConn to HandleConn.
|
||||||
|
// HandleConn is responsible for calling Close() on pluginNotifyConn.
|
||||||
|
p.visitorCtx.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.pluginCtx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin run loop stopping for visitor [%s] (context cancelled while waiting).", p.visitorCtx.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.visitorCtx.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.visitorCtx.Name)
|
||||||
|
select {
|
||||||
|
case <-time.After(reconnectDelay):
|
||||||
|
// Delay completed, loop will continue.
|
||||||
|
case <-p.pluginCtx.Done():
|
||||||
|
xl.Infof("VirtualNetPlugin reconnection delay interrupted for visitor [%s]", p.visitorCtx.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.visitorCtx.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.visitorCtx.Name)
|
||||||
|
p.controllerConn.Close()
|
||||||
|
p.controllerConn = nil
|
||||||
|
} else {
|
||||||
|
// xl.Debugf("No active controllerConn to cleanup for visitor [%s]", p.visitorCtx.Name)
|
||||||
|
}
|
||||||
|
// 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.visitorCtx.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.visitorCtx.Ctx) // Use base context for close logging
|
||||||
|
xl.Infof("Closing VirtualNetPlugin for visitor [%s]", p.visitorCtx.Name)
|
||||||
|
|
||||||
|
// 1. Signal the run loop goroutine to stop via context cancellation.
|
||||||
|
p.pluginCancel()
|
||||||
|
|
||||||
|
// 2. Unregister the route from the controller.
|
||||||
|
// This might implicitly cause the VnetController to close its end of the pipe (controllerConn).
|
||||||
|
if p.visitorCtx.VnetController != nil {
|
||||||
|
p.visitorCtx.VnetController.UnregisterClientRoute(p.visitorCtx.Name)
|
||||||
|
xl.Infof("Unregistered client route for visitor [%s]", p.visitorCtx.Name)
|
||||||
|
} else {
|
||||||
|
xl.Warnf("VnetController is nil during close for visitor [%s], cannot unregister route", p.visitorCtx.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.visitorCtx.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
94
pkg/vnet/README.md
Normal file
94
pkg/vnet/README.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Virtual Network Controller for FRP
|
||||||
|
|
||||||
|
This package implements a virtual network controller that enables VPN-like functionality between FRP clients and servers.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Virtual Network Controller manages TUN devices and routing logic to create secure tunnels between clients and servers. It supports both client-side routing (based on destination IP) and server-side routing (based on source IP).
|
||||||
|
|
||||||
|
## How it Works
|
||||||
|
|
||||||
|
1. **Client Side**: Routes packets based on destination IP addresses. When a packet is destined for a network that matches a registered route, it's forwarded through the appropriate work connection.
|
||||||
|
|
||||||
|
2. **Server Side**: Routes packets based on source IP addresses. When a packet is received over a connection, its source IP is registered with that connection. Future packets from the TUN device with that source IP will be routed back through the same connection.
|
||||||
|
|
||||||
|
3. **TUN Device**: A virtual network interface that captures and injects packets at the IP layer.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Client Implementation
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Set up VNet configuration
|
||||||
|
clientCfg := v1.VirtualNetConfig{
|
||||||
|
Address: "10.10.0.1/24",
|
||||||
|
Routes: []string{"10.20.0.0/24"}, // Routes to the server's network
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and initialize controller
|
||||||
|
controller := vnet.NewController(clientCfg)
|
||||||
|
if err := controller.Init(); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse route strings to IPNet objects
|
||||||
|
routes, err := vnet.ParseRoutes(clientCfg.Routes)
|
||||||
|
if err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register work connection with routes
|
||||||
|
if err := controller.RegisterClientRoute("server1", routes, workConn); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the controller (typically in a goroutine)
|
||||||
|
go func() {
|
||||||
|
if err := controller.Run(); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Implementation
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Set up VNet configuration
|
||||||
|
serverCfg := v1.VirtualNetConfig{
|
||||||
|
Address: "10.20.0.1/24",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and initialize controller
|
||||||
|
controller := vnet.NewController(serverCfg)
|
||||||
|
if err := controller.Init(); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register client work connection
|
||||||
|
if err := controller.RegisterServerConn("client1", workConn); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the controller (typically in a goroutine)
|
||||||
|
go func() {
|
||||||
|
if err := controller.Run(); err != nil {
|
||||||
|
// Handle error
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Setup
|
||||||
|
|
||||||
|
For proper routing, you'll need to:
|
||||||
|
|
||||||
|
1. Configure the TUN device with appropriate IP addresses
|
||||||
|
2. Add routes to your routing table for traffic that should go through the VPN
|
||||||
|
3. Enable IP forwarding if needed
|
||||||
|
|
||||||
|
See the examples directory for a complete implementation example.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- The VNet controller operates at the IP layer, so all traffic is routed based on IP addresses
|
||||||
|
- Encryption should be handled at the transport layer (e.g., by the FRP connection)
|
||||||
|
- Consider using firewall rules to restrict access to the TUN device
|
365
pkg/vnet/controller.go
Normal file
365
pkg/vnet/controller.go
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
// 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/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"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/golib/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxPacketSize = 1350 // Maximum TUN packet size
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
conn, _, _, err := createTun(c.addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.tun = conn
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Controller) Run() error {
|
||||||
|
conn := c.tun
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf := make([]byte, maxPacketSize)
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("vnet read from tun error:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Trace("vnet read from tun [%d]: %s", n, base64.StdEncoding.EncodeToString(buf[:n]))
|
||||||
|
|
||||||
|
var src, dst net.IP
|
||||||
|
if waterutil.IsIPv4(buf[:n]) {
|
||||||
|
header, err := ipv4.ParseHeader(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Infof("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
} else if waterutil.IsIPv6(buf[:n]) {
|
||||||
|
header, err := ipv6.ParseHeader(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
src = header.Src
|
||||||
|
dst = header.Dst
|
||||||
|
log.Infof("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
} else {
|
||||||
|
log.Warnf("unknown packet, discarded(%d)", n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. First try to route based on destination IP (client mode)
|
||||||
|
targetConn, err := c.clientRouter.findConn(dst)
|
||||||
|
if err == nil {
|
||||||
|
// Found matching destination route, sending data
|
||||||
|
if _, err := targetConn.Write(buf[:n]); err != nil {
|
||||||
|
log.Warn("write to client target conn error:", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. If client routing fails, try routing based on source IP (server mode)
|
||||||
|
targetConn, err = c.serverRouter.findConnBySrc(dst)
|
||||||
|
if err == nil {
|
||||||
|
// Found matching source route, sending data
|
||||||
|
if _, err := targetConn.Write(buf[:n]); err != nil {
|
||||||
|
log.Warn("write to server target conn error:", err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. No matching route found
|
||||||
|
log.Warnf("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(conn io.ReadWriteCloser) {
|
||||||
|
defer func() {
|
||||||
|
// TODO
|
||||||
|
// unregister client route
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read fixed size packet
|
||||||
|
buf := make([]byte, maxPacketSize)
|
||||||
|
// TODO: custom message format
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("client read error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if waterutil.IsIPv4(buf[:n]) {
|
||||||
|
header, err := ipv4.ParseHeader(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Infof("%s >> %s %d/%-4d %-4x %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.Len, header.TotalLen, header.ID, header.Flags)
|
||||||
|
} else if waterutil.IsIPv6(buf[:n]) {
|
||||||
|
header, err := ipv6.ParseHeader(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Infof("%s >> %s %d %d",
|
||||||
|
header.Src, header.Dst,
|
||||||
|
header.PayloadLen, header.TrafficClass)
|
||||||
|
} else {
|
||||||
|
log.Warnf("unknown packet, discarded(%d)", n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to TUN device
|
||||||
|
log.Trace("vnet write to tun (client) [%d]: %s", n, base64.StdEncoding.EncodeToString(buf[:n]))
|
||||||
|
_, err = c.tun.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("client write tun error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server connection read loop
|
||||||
|
func (c *Controller) readLoopServer(conn io.ReadWriteCloser) {
|
||||||
|
for {
|
||||||
|
// Read fixed size packet
|
||||||
|
buf := make([]byte, maxPacketSize)
|
||||||
|
// TODO: custom message format
|
||||||
|
n, err := conn.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("server read error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register source IP to connection mapping
|
||||||
|
if waterutil.IsIPv4(buf[:n]) || waterutil.IsIPv6(buf[:n]) {
|
||||||
|
var src net.IP
|
||||||
|
if waterutil.IsIPv4(buf[:n]) {
|
||||||
|
header, err := ipv4.ParseHeader(buf[:n])
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
} else if waterutil.IsIPv6(buf[:n]) {
|
||||||
|
header, err := ipv6.ParseHeader(buf[:n])
|
||||||
|
if err == nil {
|
||||||
|
src = header.Src
|
||||||
|
c.serverRouter.registerSrcIP(src, conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to TUN
|
||||||
|
log.Trace("vnet write to tun (server) [%d]: %s", n, base64.StdEncoding.EncodeToString(buf[:n]))
|
||||||
|
_, err = c.tun.Write(buf[:n])
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("server write tun error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterClientRoute Register client route (based on destination IP CIDR)
|
||||||
|
func (c *Controller) RegisterClientRoute(name string, routes []net.IPNet, conn io.ReadWriteCloser) error {
|
||||||
|
if err := c.clientRouter.addRoute(name, routes, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.readLoopClient(conn)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterServerConn Register server connection (dynamically associates with source IPs)
|
||||||
|
func (c *Controller) RegisterServerConn(name string, conn io.ReadWriteCloser) error {
|
||||||
|
if err := c.serverRouter.addConn(name, conn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go c.readLoopServer(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.ReadWriteCloser, 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.ReadWriteCloser // 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.ReadWriteCloser),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ReadWriteCloser, 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.ReadWriteCloser) {
|
||||||
|
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
|
||||||
|
}
|
122
pkg/vnet/examples/client.go.bak
Normal file
122
pkg/vnet/examples/client.go.bak
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Parse command-line flags
|
||||||
|
serverAddr := flag.String("server", "localhost:8000", "Server address (ip:port)")
|
||||||
|
clientTUNAddr := flag.String("tun", "10.10.0.1/24", "Client TUN device address")
|
||||||
|
serverTUNNet := flag.String("server-net", "10.20.0.0/24", "Server TUN network for routing")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
fmt.Printf("Connecting to server at %s\n", *serverAddr)
|
||||||
|
fmt.Printf("Client TUN address: %s\n", *clientTUNAddr)
|
||||||
|
fmt.Printf("Server network route: %s\n", *serverTUNNet)
|
||||||
|
|
||||||
|
// Set up vnet controller on the client
|
||||||
|
clientCfg := v1.VirtualNetConfig{
|
||||||
|
Address: *clientTUNAddr,
|
||||||
|
Routes: []string{*serverTUNNet},
|
||||||
|
}
|
||||||
|
clientController := vnet.NewController(clientCfg)
|
||||||
|
if err := clientController.Init(); err != nil {
|
||||||
|
fmt.Printf("Client init error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle shutdown gracefully
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
fmt.Println("\nShutting down client...")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Start the controller
|
||||||
|
go func() {
|
||||||
|
if err := clientController.Run(); err != nil {
|
||||||
|
fmt.Printf("Client run error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Main connection loop
|
||||||
|
for {
|
||||||
|
// Connect to the server
|
||||||
|
conn, err := net.Dial("tcp", *serverAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to connect to server %s: %v\n", *serverAddr, err)
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Connected to server at %s\n", conn.RemoteAddr())
|
||||||
|
|
||||||
|
// Wrap the connection
|
||||||
|
rwc := &readWriteCloserConn{conn}
|
||||||
|
|
||||||
|
// Parse routes from config
|
||||||
|
clientRoutes, err := vnet.ParseRoutes(clientCfg.Routes)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Parse client routes error: %v\n", err)
|
||||||
|
conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the route with the connection
|
||||||
|
if err := clientController.RegisterClientRoute("server", clientRoutes, rwc); err != nil {
|
||||||
|
fmt.Printf("Register client route error: %v\n", err)
|
||||||
|
conn.Close()
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("VPN tunnel established with server")
|
||||||
|
fmt.Printf("Client TUN: %s, routes: %s\n", *clientTUNAddr, *serverTUNNet)
|
||||||
|
|
||||||
|
time.Sleep(60 * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readWriteCloserConn wraps net.Conn as io.ReadWriteCloser
|
||||||
|
type readWriteCloserConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Read(p []byte) (n int, err error) {
|
||||||
|
return rwc.Conn.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Write(p []byte) (n int, err error) {
|
||||||
|
return rwc.Conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Close() error {
|
||||||
|
return rwc.Conn.Close()
|
||||||
|
}
|
114
pkg/vnet/examples/server.go.bak
Normal file
114
pkg/vnet/examples/server.go.bak
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// 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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||||
|
"github.com/fatedier/frp/pkg/vnet"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Parse command-line flags
|
||||||
|
listenAddr := flag.String("listen", ":8000", "Server listen address (ip:port)")
|
||||||
|
serverTUNAddr := flag.String("tun", "10.20.0.1/24", "Server TUN device address")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
// Listen for incoming connections
|
||||||
|
listener, err := net.Listen("tcp", *listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Failed to listen on %s: %v\n", *listenAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Server listening on %s\n", *listenAddr)
|
||||||
|
fmt.Printf("Server TUN address: %s\n", *serverTUNAddr)
|
||||||
|
|
||||||
|
// Set up vnet controller on the server
|
||||||
|
serverCfg := v1.VirtualNetConfig{
|
||||||
|
Address: *serverTUNAddr, // Server TUN device address
|
||||||
|
}
|
||||||
|
serverController := vnet.NewController(serverCfg)
|
||||||
|
if err := serverController.Init(); err != nil {
|
||||||
|
fmt.Printf("Server init error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the controller
|
||||||
|
go func() {
|
||||||
|
if err := serverController.Run(); err != nil {
|
||||||
|
fmt.Printf("Server run error: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Handle shutdown gracefully
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
fmt.Println("\nShutting down server...")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Println("Waiting for client connection...")
|
||||||
|
// Accept and handle connections
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error accepting connection: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("New client connected from %s\n", conn.RemoteAddr())
|
||||||
|
|
||||||
|
// Wrap the connection
|
||||||
|
rwc := &readWriteCloserConn{conn}
|
||||||
|
|
||||||
|
// Register the connection
|
||||||
|
if err := serverController.RegisterServerConn("client", rwc); err != nil {
|
||||||
|
fmt.Printf("Register server connection error: %v\n", err)
|
||||||
|
conn.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("VPN tunnel established with client")
|
||||||
|
fmt.Printf("Server TUN: %s\n", *serverTUNAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readWriteCloserConn wraps net.Conn as io.ReadWriteCloser
|
||||||
|
type readWriteCloserConn struct {
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Read(p []byte) (n int, err error) {
|
||||||
|
return rwc.Conn.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Write(p []byte) (n int, err error) {
|
||||||
|
return rwc.Conn.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwc *readWriteCloserConn) Close() error {
|
||||||
|
return rwc.Conn.Close()
|
||||||
|
}
|
75
pkg/vnet/tun.go
Normal file
75
pkg/vnet/tun.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// 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 (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"golang.zx2c4.com/wireguard/tun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tunDevice struct {
|
||||||
|
dev tun.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDevice) Read(p []byte) (int, error) {
|
||||||
|
// Wireguard's TUN implementation expects a 4-byte offset before the data
|
||||||
|
// Make a larger buffer with room for the offset
|
||||||
|
buf := make([]byte, len(p)+4)
|
||||||
|
|
||||||
|
// Create sizes array for wireguard to populate
|
||||||
|
sz := make([]int, 1)
|
||||||
|
|
||||||
|
// Call wireguard's Read with offset=4
|
||||||
|
n, err := d.dev.Read([][]byte{buf}, sz, 4)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the actual data (excluding the 4-byte offset) to the output buffer
|
||||||
|
dataSize := sz[0]
|
||||||
|
if dataSize > len(p) {
|
||||||
|
dataSize = len(p)
|
||||||
|
}
|
||||||
|
copy(p, buf[4:4+dataSize])
|
||||||
|
|
||||||
|
return dataSize, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDevice) Write(p []byte) (int, error) {
|
||||||
|
buf := make([]byte, len(p)+4)
|
||||||
|
copy(buf[4:], p)
|
||||||
|
return d.dev.Write([][]byte{buf}, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *tunDevice) Close() error {
|
||||||
|
return d.dev.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTunDevice(adviceName string, mtu int) (io.ReadWriteCloser, string, error) {
|
||||||
|
ifce, err := tun.CreateTUN(adviceName, mtu)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := ifce.Name()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
return &tunDevice{dev: ifce}, name, nil
|
||||||
|
}
|
84
pkg/vnet/tun_darwin.go
Normal file
84
pkg/vnet/tun_darwin.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 (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunName = "utun"
|
||||||
|
defaultMTU = 1350
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTun(addr string) (io.ReadWriteCloser, string, net.IP, error) {
|
||||||
|
ifce, name, err := createTunDevice(defaultTunName, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, ipNet, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate a peer IP for the point-to-point tunnel
|
||||||
|
peerIP := generatePeerIP(ip)
|
||||||
|
|
||||||
|
// Configure the interface with proper point-to-point addressing
|
||||||
|
cmd := fmt.Sprintf("ifconfig %s inet %s %s mtu %d up",
|
||||||
|
name, ip.String(), peerIP.String(), defaultMTU)
|
||||||
|
args := strings.Split(cmd, " ")
|
||||||
|
if err = exec.Command(args[0], args[1:]...).Run(); err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add default route for the tunnel subnet
|
||||||
|
routes := []net.IPNet{*ipNet}
|
||||||
|
if err = addRoutes(name, routes); err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ifce, name, ip, 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 {
|
||||||
|
cmd := fmt.Sprintf("route add -net %s -interface %s", route.String(), ifName)
|
||||||
|
args := strings.Split(cmd, " ")
|
||||||
|
if err := exec.Command(args[0], args[1:]...).Run(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
80
pkg/vnet/tun_linux.go
Normal file
80
pkg/vnet/tun_linux.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultTunName = "utun"
|
||||||
|
defaultMTU = 1350
|
||||||
|
)
|
||||||
|
|
||||||
|
func createTun(addr string) (io.ReadWriteCloser, string, net.IP, error) {
|
||||||
|
dev, name, err := createTunDevice(defaultTunName, defaultMTU)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ifce, err := net.InterfaceByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
link, err := netlink.LinkByName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, cidr, err := net.ParseCIDR(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
if err := netlink.AddrAdd(link, &netlink.Addr{
|
||||||
|
IPNet: &net.IPNet{
|
||||||
|
IP: ip,
|
||||||
|
Mask: cidr.Mask,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(link); err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = addRoutes(ifce, cidr); err != nil {
|
||||||
|
return nil, "", nil, err
|
||||||
|
}
|
||||||
|
return dev, name, ip, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRoutes(ifce *net.Interface, cidr *net.IPNet) error {
|
||||||
|
r := netlink.Route{
|
||||||
|
Dst: cidr,
|
||||||
|
}
|
||||||
|
if r.Gw == nil {
|
||||||
|
r.LinkIndex = ifce.Index
|
||||||
|
}
|
||||||
|
if err := netlink.RouteReplace(&r); err != nil {
|
||||||
|
return fmt.Errorf("add route %v %v: %v", r.Dst, r.Gw, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user