mirror of
https://github.com/fatedier/frp.git
synced 2025-06-17 17:18:21 +00:00
Merge 5d8710561958ecbbbf4d0c07c1e34b40781d6976 into 07946e9752a5f829d1bb5cdf6cfbaeef906f278f
This commit is contained in:
commit
5f5af5796d
@ -28,6 +28,7 @@ import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/nathole/upnp"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
)
|
||||
@ -53,6 +54,23 @@ func NewXTCPProxy(baseProxy *BaseProxy, cfg v1.ProxyConfigurer) Proxy {
|
||||
}
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) makeRouterToNatThisHole(remoteGetAddrs []string, localIps []string, localAddr net.Addr) {
|
||||
|
||||
xl := pxy.xl
|
||||
if !pxy.cfg.AllowToUseUPNP {
|
||||
xl.Tracef("makeRouterToNatThisHole: upnp disabled")
|
||||
return
|
||||
}
|
||||
|
||||
description := pxy.cfg.UPNPPortMappingDescription
|
||||
if description == "" {
|
||||
description = upnp.DEFAULT_UPNP_PROGRAM_DESCRIPTION
|
||||
}
|
||||
|
||||
upnp.AskForMapping(xl, remoteGetAddrs, localIps, localAddr, description)
|
||||
|
||||
}
|
||||
|
||||
func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkConn) {
|
||||
xl := pxy.xl
|
||||
defer conn.Close()
|
||||
@ -64,7 +82,7 @@ func (pxy *XTCPProxy) InWorkConn(conn net.Conn, startWorkConnMsg *msg.StartWorkC
|
||||
}
|
||||
|
||||
xl.Tracef("nathole prepare start")
|
||||
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer})
|
||||
prepareResult, err := nathole.Prepare([]string{pxy.clientCfg.NatHoleSTUNServer}, pxy.makeRouterToNatThisHole)
|
||||
if err != nil {
|
||||
xl.Warnf("nathole prepare error: %v", err)
|
||||
return
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
v1 "github.com/fatedier/frp/pkg/config/v1"
|
||||
"github.com/fatedier/frp/pkg/msg"
|
||||
"github.com/fatedier/frp/pkg/nathole"
|
||||
"github.com/fatedier/frp/pkg/nathole/upnp"
|
||||
"github.com/fatedier/frp/pkg/transport"
|
||||
netpkg "github.com/fatedier/frp/pkg/util/net"
|
||||
"github.com/fatedier/frp/pkg/util/util"
|
||||
@ -261,6 +262,23 @@ func (sv *XTCPVisitor) getTunnelConn() (net.Conn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (sv *XTCPVisitor) makeRouterToNatThisHole(remoteGetAddrs []string, localIps []string, localAddr net.Addr) {
|
||||
|
||||
xl := xlog.FromContextSafe(sv.ctx)
|
||||
if !sv.cfg.AllowToUseUPNP {
|
||||
xl.Tracef("makeRouterToNatThisHole: upnp disabled")
|
||||
return
|
||||
}
|
||||
|
||||
description := sv.cfg.UPNPPortMappingDescription
|
||||
if description == "" {
|
||||
description = upnp.DEFAULT_UPNP_PROGRAM_DESCRIPTION
|
||||
}
|
||||
|
||||
upnp.AskForMapping(xl, remoteGetAddrs, localIps, localAddr, description)
|
||||
|
||||
}
|
||||
|
||||
// 0. PreCheck
|
||||
// 1. Prepare
|
||||
// 2. ExchangeInfo
|
||||
@ -275,7 +293,7 @@ func (sv *XTCPVisitor) makeNatHole() {
|
||||
}
|
||||
|
||||
xl.Tracef("nathole prepare start")
|
||||
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer})
|
||||
prepareResult, err := nathole.Prepare([]string{sv.clientCfg.NatHoleSTUNServer}, sv.makeRouterToNatThisHole)
|
||||
if err != nil {
|
||||
xl.Warnf("nathole prepare error: %v", err)
|
||||
return
|
||||
|
@ -336,6 +336,9 @@ localPort = 22
|
||||
# If not empty, only visitors from specified users can connect.
|
||||
# Otherwise, visitors from same user can connect. '*' means allow all users.
|
||||
allowUsers = ["user1", "user2"]
|
||||
# allow to use upnp to map this port
|
||||
allowToUseUPNP = false
|
||||
upnpPortMappingDescription = "helper-port-mapping"
|
||||
|
||||
# frpc role visitor -> frps -> frpc role server
|
||||
[[visitors]]
|
||||
@ -368,3 +371,5 @@ maxRetriesAnHour = 8
|
||||
minRetryInterval = 90
|
||||
# fallbackTo = "stcp_visitor"
|
||||
# fallbackTimeoutMs = 500
|
||||
allowToUseUPNP = false
|
||||
upnpPortMappingDescription = "helper-port-mapping"
|
||||
|
3
go.mod
3
go.mod
@ -46,7 +46,9 @@ require (
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
|
||||
github.com/huin/goupnp v1.3.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackpal/gateway v1.0.14 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
|
||||
github.com/klauspost/reedsolomon v1.12.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@ -60,6 +62,7 @@ require (
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.11.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/templexxx/cpu v0.1.0 // indirect
|
||||
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -66,9 +66,13 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jackpal/gateway v1.0.14 h1:6ZfIuFvnvWrS59hHbvZGR/R33ojV2LASBODomt7zlJU=
|
||||
github.com/jackpal/gateway v1.0.14/go.mod h1:6c8LjW+FVESFmwxaXySkt7fU98Yv806ADS3OY6Cvh2U=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
||||
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/reedsolomon v1.12.0 h1:I5FEp3xSwVCcEh3F5A7dofEfhXdF/bWhQWPH+XwBFno=
|
||||
@ -127,6 +131,7 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@ -194,6 +199,7 @@ golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
|
@ -134,3 +134,8 @@ type HTTPHeader struct {
|
||||
Name string `json:"name"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type XTCPConfigUPNPMixin struct {
|
||||
AllowToUseUPNP bool `json:"allowToUseUPNP,omitempty"`
|
||||
UPNPPortMappingDescription string `json:"upnpPortMappingDescription,omitempty"`
|
||||
}
|
||||
|
@ -411,6 +411,8 @@ type XTCPProxyConfig struct {
|
||||
|
||||
Secretkey string `json:"secretKey,omitempty"`
|
||||
AllowUsers []string `json:"allowUsers,omitempty"`
|
||||
|
||||
XTCPConfigUPNPMixin
|
||||
}
|
||||
|
||||
func (c *XTCPProxyConfig) MarshalToMsg(m *msg.NewProxy) {
|
||||
|
@ -153,6 +153,8 @@ type XTCPVisitorConfig struct {
|
||||
MinRetryInterval int `json:"minRetryInterval,omitempty"`
|
||||
FallbackTo string `json:"fallbackTo,omitempty"`
|
||||
FallbackTimeoutMs int `json:"fallbackTimeoutMs,omitempty"`
|
||||
|
||||
XTCPConfigUPNPMixin
|
||||
}
|
||||
|
||||
func (c *XTCPVisitorConfig) Complete(g *ClientCommonConfig) {
|
||||
|
@ -107,8 +107,12 @@ func PreCheck(
|
||||
return nil
|
||||
}
|
||||
|
||||
type OnGetMyRemoteAddress func(remoteGetAddrs []string, localIps []string, localAddr net.Addr)
|
||||
|
||||
// Prepare is used to do some preparation work before penetration.
|
||||
func Prepare(stunServers []string) (*PrepareResult, error) {
|
||||
func Prepare(stunServers []string,
|
||||
callback OnGetMyRemoteAddress,
|
||||
) (*PrepareResult, error) {
|
||||
// discover for Nat type
|
||||
addrs, localAddr, err := Discover(stunServers, "")
|
||||
if err != nil {
|
||||
@ -119,6 +123,11 @@ func Prepare(stunServers []string) (*PrepareResult, error) {
|
||||
}
|
||||
|
||||
localIPs, _ := ListLocalIPsForNatHole(10)
|
||||
|
||||
if callback != nil {
|
||||
callback(addrs, localIPs, localAddr)
|
||||
}
|
||||
|
||||
natFeature, err := ClassifyNATFeature(addrs, localIPs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("classify nat feature error: %v", err)
|
||||
|
173
pkg/nathole/upnp/upnp.go
Normal file
173
pkg/nathole/upnp/upnp.go
Normal file
@ -0,0 +1,173 @@
|
||||
package upnp
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"errors"
|
||||
"github.com/fatedier/frp/pkg/util/xlog"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||
"github.com/jackpal/gateway"
|
||||
)
|
||||
|
||||
const DEFAULT_UPNP_PROGRAM_DESCRIPTION = "helper-port-mapping"
|
||||
|
||||
type RouterClient interface {
|
||||
AddPortMapping(
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewEnabled bool,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) (err error)
|
||||
|
||||
GetExternalIPAddress() (
|
||||
NewExternalIPAddress string,
|
||||
err error,
|
||||
)
|
||||
}
|
||||
|
||||
func PickRouterClient(ctx context.Context) (RouterClient, error) {
|
||||
tasks, _ := errgroup.WithContext(ctx)
|
||||
// Request each type of client in parallel, and return what is found.
|
||||
var ip1Clients []*internetgateway2.WANIPConnection1
|
||||
tasks.Go(func() error {
|
||||
var err error
|
||||
ip1Clients, _, err = internetgateway2.NewWANIPConnection1Clients()
|
||||
return err
|
||||
})
|
||||
var ip2Clients []*internetgateway2.WANIPConnection2
|
||||
tasks.Go(func() error {
|
||||
var err error
|
||||
ip2Clients, _, err = internetgateway2.NewWANIPConnection2Clients()
|
||||
return err
|
||||
})
|
||||
var ppp1Clients []*internetgateway2.WANPPPConnection1
|
||||
tasks.Go(func() error {
|
||||
var err error
|
||||
ppp1Clients, _, err = internetgateway2.NewWANPPPConnection1Clients()
|
||||
return err
|
||||
})
|
||||
|
||||
if err := tasks.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Trivial handling for where we find exactly one device to talk to, you
|
||||
// might want to provide more flexible handling than this if multiple
|
||||
// devices are found.
|
||||
switch {
|
||||
case len(ip2Clients) == 1:
|
||||
return ip2Clients[0], nil
|
||||
case len(ip1Clients) == 1:
|
||||
return ip1Clients[0], nil
|
||||
case len(ppp1Clients) == 1:
|
||||
return ppp1Clients[0], nil
|
||||
default:
|
||||
return nil, errors.New("multiple or no services found")
|
||||
}
|
||||
}
|
||||
|
||||
func UPNP_ForwardPort(ctx context.Context,
|
||||
NewRemoteHost string,
|
||||
NewExternalPort uint16,
|
||||
NewProtocol string,
|
||||
NewInternalPort uint16,
|
||||
NewInternalClient string,
|
||||
NewPortMappingDescription string,
|
||||
NewLeaseDuration uint32,
|
||||
) error {
|
||||
client, err := PickRouterClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return client.AddPortMapping(
|
||||
NewRemoteHost,
|
||||
// External port number to expose to Internet:
|
||||
NewExternalPort,
|
||||
// Forward TCP (this could be "UDP" if we wanted that instead).
|
||||
NewProtocol,
|
||||
// Internal port number on the LAN to forward to.
|
||||
// Some routers might not support this being different to the external
|
||||
// port number.
|
||||
NewInternalPort,
|
||||
// Internal address on the LAN we want to forward to.
|
||||
NewInternalClient,
|
||||
// Enabled:
|
||||
true,
|
||||
// Informational description for the client requesting the port forwarding.
|
||||
NewPortMappingDescription,
|
||||
// How long should the port forward last for in seconds.
|
||||
// If you want to keep it open for longer and potentially across router
|
||||
// resets, you might want to periodically request before this elapses.
|
||||
NewLeaseDuration,
|
||||
)
|
||||
}
|
||||
|
||||
func AskForMapping(xl *xlog.Logger, remoteGetAddrs []string, localIps []string, localAddr net.Addr, description string) {
|
||||
|
||||
xl.Tracef("makeRouterToNatThisHole: %v, localIps %v, localAddr=%v", remoteGetAddrs, localIps, localAddr.String())
|
||||
|
||||
targetAddr := remoteGetAddrs[0]
|
||||
remoteAddrPort, err := netip.ParseAddrPort(targetAddr)
|
||||
if err != nil {
|
||||
xl.Errorf("netip.ParseAddrPort error: %v. parse: %v", err, targetAddr)
|
||||
return
|
||||
}
|
||||
|
||||
localAddrStr := localAddr.String()
|
||||
localAddrPort, err := netip.ParseAddrPort(localAddrStr)
|
||||
if err != nil {
|
||||
xl.Errorf("netip.ParseAddrPort local error: %v. parse: %v", err, localAddrStr)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
targetForwardTo := ""
|
||||
if len(localIps) == 1 {
|
||||
targetForwardTo = localIps[0]
|
||||
} else {
|
||||
targetForwardToIp, err := gateway.DiscoverInterface()
|
||||
if err != nil {
|
||||
xl.Warnf("load Default interface error:%v", err)
|
||||
} else {
|
||||
targetForwardTo = targetForwardToIp.String()
|
||||
}
|
||||
}
|
||||
|
||||
if targetForwardTo == "" && len(localIps) > 1 {
|
||||
targetForwardTo = localIps[0]
|
||||
}
|
||||
|
||||
ctx, _ := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
||||
|
||||
xl.Infof("UPNP_ForwardPort: remoteAddrPort=%v, localAddrPort=%v, targetForwardToLocal=%v", remoteAddrPort, localAddrPort, targetForwardTo)
|
||||
err = UPNP_ForwardPort(
|
||||
ctx,
|
||||
/*NewRemoteHost*/ remoteAddrPort.Addr().String(),
|
||||
/*NewExternalPort*/ remoteAddrPort.Port(),
|
||||
/*NewProtocol*/ "UDP",
|
||||
|
||||
/*NewInternalPort*/
|
||||
localAddrPort.Port(),
|
||||
/*NewInternalClient*/ targetForwardTo,
|
||||
/*NewPortMappingDescription*/ description,
|
||||
/*NewLeaseDuration*/ 360,
|
||||
)
|
||||
if err != nil {
|
||||
xl.Warnf("UPNP_ForwardPort error: %v.", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
xl.Tracef("UPNP_ForwardPort done")
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user