diff --git a/client/proxy/xtcp.go b/client/proxy/xtcp.go
index 31f9ac89..6f904eef 100644
--- a/client/proxy/xtcp.go
+++ b/client/proxy/xtcp.go
@@ -64,7 +64,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}, "")
 	if err != nil {
 		xl.Warnf("nathole prepare error: %v", err)
 		return
diff --git a/client/visitor/xtcp.go b/client/visitor/xtcp.go
index a1efd72b..e505c3b5 100644
--- a/client/visitor/xtcp.go
+++ b/client/visitor/xtcp.go
@@ -275,7 +275,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.cfg.UDPListen)
 	if err != nil {
 		xl.Warnf("nathole prepare error: %v", err)
 		return
diff --git a/pkg/config/v1/visitor.go b/pkg/config/v1/visitor.go
index 51fe88a6..89f33334 100644
--- a/pkg/config/v1/visitor.go
+++ b/pkg/config/v1/visitor.go
@@ -157,6 +157,11 @@ type XTCPVisitorConfig struct {
 	MinRetryInterval  int    `json:"minRetryInterval,omitempty"`
 	FallbackTo        string `json:"fallbackTo,omitempty"`
 	FallbackTimeoutMs int    `json:"fallbackTimeoutMs,omitempty"`
+
+	// Specify the listen addr and port for the UDP NAT hole punching, the format is "addr:port" like "0.0.0.0:7000".
+	// It is useful when your router's NAT hole punching doesn't work well, so you can explicitly config a UDP port
+	// forwarding rule on the router to work around.
+	UDPListen string `json:"udpListen,omitempty"`
 }
 
 func (c *XTCPVisitorConfig) Complete(g *ClientCommonConfig) {
diff --git a/pkg/nathole/nathole.go b/pkg/nathole/nathole.go
index bdd0ee83..45adb585 100644
--- a/pkg/nathole/nathole.go
+++ b/pkg/nathole/nathole.go
@@ -108,9 +108,9 @@ func PreCheck(
 }
 
 // Prepare is used to do some preparation work before penetration.
-func Prepare(stunServers []string) (*PrepareResult, error) {
+func Prepare(stunServers []string, udpListen string) (*PrepareResult, error) {
 	// discover for Nat type
-	addrs, localAddr, err := Discover(stunServers, "")
+	addrs, localAddr, err := Discover(stunServers, udpListen)
 	if err != nil {
 		return nil, fmt.Errorf("discover error: %v", err)
 	}