mirror of
https://github.com/openp2p-cn/openp2p.git
synced 2025-09-26 20:51:15 +08:00
194 lines
5.3 KiB
Go
194 lines
5.3 KiB
Go
package openp2p
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
reuse "github.com/openp2p-cn/go-reuseport"
|
|
)
|
|
|
|
func natTCP(serverHost string, serverPort int) (publicIP string, publicPort int, localPort int) {
|
|
// dialer := &net.Dialer{
|
|
// LocalAddr: &net.TCPAddr{
|
|
// IP: net.ParseIP("0.0.0.0"),
|
|
// Port: localPort,
|
|
// },
|
|
// }
|
|
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", 0), fmt.Sprintf("%s:%d", serverHost, serverPort), NatTestTimeout)
|
|
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
|
// log.Println(LvINFO, conn.LocalAddr())
|
|
if err != nil {
|
|
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
localPort, _ = strconv.Atoi(strings.Split(conn.LocalAddr().String(), ":")[1])
|
|
_, wrerr := conn.Write([]byte("1"))
|
|
if wrerr != nil {
|
|
fmt.Printf("Write error: %s\n", wrerr)
|
|
return
|
|
}
|
|
b := make([]byte, 1000)
|
|
conn.SetReadDeadline(time.Now().Add(NatTestTimeout))
|
|
n, rderr := conn.Read(b)
|
|
if rderr != nil {
|
|
fmt.Printf("Read error: %s\n", rderr)
|
|
return
|
|
}
|
|
arr := strings.Split(string(b[:n]), ":")
|
|
if len(arr) < 2 {
|
|
return
|
|
}
|
|
publicIP = arr[0]
|
|
port, _ := strconv.ParseInt(arr[1], 10, 32)
|
|
publicPort = int(port)
|
|
return
|
|
|
|
}
|
|
func natTest(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
|
|
gLog.Println(LvDEBUG, "natTest start")
|
|
defer gLog.Println(LvDEBUG, "natTest end")
|
|
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
|
if err != nil {
|
|
gLog.Println(LvERROR, "natTest listen udp error:", err)
|
|
return "", 0, err
|
|
}
|
|
defer conn.Close()
|
|
|
|
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
// The connection can write data to the desired address.
|
|
msg, err := newMessage(MsgNATDetect, MsgNAT, nil)
|
|
_, err = conn.WriteTo(msg, dst)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
deadline := time.Now().Add(NatTestTimeout)
|
|
err = conn.SetReadDeadline(deadline)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
buffer := make([]byte, 1024)
|
|
nRead, _, err := conn.ReadFrom(buffer)
|
|
if err != nil {
|
|
gLog.Println(LvERROR, "NAT detect error:", err)
|
|
return "", 0, err
|
|
}
|
|
natRsp := NatDetectRsp{}
|
|
json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
|
|
|
return natRsp.IP, natRsp.Port, nil
|
|
}
|
|
|
|
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, err error) {
|
|
// the random local port may be used by other.
|
|
localPort := int(rand.Uint32()%15000 + 50000)
|
|
|
|
ip1, port1, err := natTest(host, udp1, localPort)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
_, port2, err := natTest(host, udp2, localPort) // 2rd nat test not need testing publicip
|
|
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
natType := NATSymmetric
|
|
if port1 == port2 {
|
|
natType = NATCone
|
|
}
|
|
return ip1, natType, nil
|
|
}
|
|
|
|
func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATPMP int) {
|
|
if publicIP == "" || echoPort == 0 {
|
|
return
|
|
}
|
|
var echoConn *net.UDPConn
|
|
gLog.Println(LvDEBUG, "echo server start")
|
|
var err error
|
|
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
|
if err != nil { // listen error
|
|
gLog.Println(LvERROR, "echo server listen error:", err)
|
|
return
|
|
}
|
|
defer echoConn.Close()
|
|
// testing for public ip
|
|
for i := 0; i < 2; i++ {
|
|
if i == 1 {
|
|
// test upnp or nat-pmp
|
|
gLog.Println(LvDEBUG, "upnp test start")
|
|
nat, err := Discover()
|
|
if err != nil || nat == nil {
|
|
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
|
break
|
|
}
|
|
ext, err := nat.GetExternalAddress()
|
|
if err != nil {
|
|
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
|
|
break
|
|
}
|
|
gLog.Println(LvINFO, "PublicIP:", ext)
|
|
|
|
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30) // 30 seconds fot upnp testing
|
|
if err != nil {
|
|
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
|
break
|
|
} else {
|
|
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800) // 7 days for tcp connection
|
|
}
|
|
}
|
|
gLog.Printf(LvDEBUG, "public ip test start %s:%d", publicIP, echoPort)
|
|
conn, err := net.ListenUDP("udp", nil)
|
|
if err != nil {
|
|
break
|
|
}
|
|
defer conn.Close()
|
|
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort))
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
// The connection can write data to the desired address.
|
|
msg, _ := newMessage(MsgNATDetect, MsgPublicIP, NatDetectReq{EchoPort: echoPort})
|
|
_, err = conn.WriteTo(msg, dst)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
buf := make([]byte, 1600)
|
|
|
|
// wait for echo testing
|
|
echoConn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
|
nRead, _, err := echoConn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
|
|
continue
|
|
}
|
|
natRsp := NatDetectRsp{}
|
|
err = json.Unmarshal(buf[openP2PHeaderSize:nRead], &natRsp)
|
|
if err != nil {
|
|
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
|
|
continue
|
|
}
|
|
if natRsp.Port == echoPort {
|
|
if i == 1 {
|
|
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
|
hasUPNPorNATPMP = 1
|
|
} else {
|
|
gLog.Println(LvDEBUG, "public ip:YES")
|
|
hasPublicIP = 1
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|