Skip to content

Commit

Permalink
nathole: upnp support
Browse files Browse the repository at this point in the history
see also:

#1823
#3703
  • Loading branch information
slayercat committed Mar 28, 2024
1 parent 86f90f4 commit 98e4a7e
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 3 deletions.
20 changes: 19 additions & 1 deletion client/proxy/xtcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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()
Expand All @@ -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
Expand Down
20 changes: 19 additions & 1 deletion client/visitor/xtcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions conf/frpc_full_example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down Expand Up @@ -368,3 +371,5 @@ maxRetriesAnHour = 8
minRetryInterval = 90
# fallbackTo = "stcp_visitor"
# fallbackTimeoutMs = 500
allowToUseUPNP = false
upnpPortMappingDescription = "helper-port-mapping"
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ require (
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.9 // 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
Expand All @@ -61,6 +63,7 @@ require (
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // 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
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
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=
Expand Down Expand Up @@ -129,6 +133,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/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down Expand Up @@ -197,6 +202,7 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/v1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
2 changes: 2 additions & 0 deletions pkg/config/v1/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/v1/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 10 additions & 1 deletion pkg/nathole/nathole.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
173 changes: 173 additions & 0 deletions pkg/nathole/upnp/upnp.go
Original file line number Diff line number Diff line change
@@ -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[1]
} 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")

}

0 comments on commit 98e4a7e

Please sign in to comment.