Merge 5d8710561958ecbbbf4d0c07c1e34b40781d6976 into 07946e9752a5f829d1bb5cdf6cfbaeef906f278f

This commit is contained in:
李林哲 2024-04-11 13:41:04 +08:00 committed by GitHub
commit 5f5af5796d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 244 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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"`
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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
View 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")
}