mirror of
https://github.com/openp2p-cn/openp2p.git
synced 2025-09-27 04:56:03 +08:00
188 lines
5.2 KiB
Go
188 lines
5.2 KiB
Go
package openp2p
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"math/rand"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
reuse "github.com/openp2p-cn/go-reuseport"
|
|
)
|
|
|
|
func natTCP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort 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", localPort), fmt.Sprintf("%s:%d", serverHost, serverPort), time.Second*5)
|
|
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
|
if err != nil {
|
|
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
|
|
return
|
|
}
|
|
defer conn.Close()
|
|
_, 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(time.Second * 5))
|
|
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) {
|
|
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, 0, 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{}
|
|
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
|
|
|
return natRsp.IP, natRsp.Port, nil
|
|
}
|
|
|
|
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, hasIPvr int, hasUPNPorNATPMP int, err error) {
|
|
// the random local port may be used by other.
|
|
localPort := int(rand.Uint32()%15000 + 50000)
|
|
echoPort := P2PNetworkInstance(nil).config.TCPPort
|
|
ip1, port1, err := natTest(host, udp1, localPort)
|
|
if err != nil {
|
|
return "", 0, 0, 0, err
|
|
}
|
|
hasIPv4, hasUPNPorNATPMP := publicIPTest(ip1, echoPort)
|
|
gLog.Printf(LvINFO, "local port:%d, nat port:%d, hasIPv4:%d, UPNP:%d", localPort, port1, hasIPv4, hasUPNPorNATPMP)
|
|
_, 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, hasIPv4, hasUPNPorNATPMP, err
|
|
}
|
|
natType := NATSymmetric
|
|
if port1 == port2 {
|
|
natType = NATCone
|
|
}
|
|
return ip1, natType, hasIPv4, hasUPNPorNATPMP, nil
|
|
}
|
|
|
|
func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATPMP int) {
|
|
var echoConn *net.UDPConn
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
go func() {
|
|
var err error
|
|
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
|
if err != nil {
|
|
gLog.Println(LvERROR, "echo server listen error:", err)
|
|
return
|
|
}
|
|
buf := make([]byte, 1600)
|
|
// close outside for breaking the ReadFromUDP
|
|
// wait 5s for echo testing
|
|
wg.Done()
|
|
echoConn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
|
n, addr, err := echoConn.ReadFromUDP(buf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
echoConn.WriteToUDP(buf[0:n], addr)
|
|
}()
|
|
wg.Wait() // wait echo udp
|
|
defer echoConn.Close()
|
|
// testing for public ip
|
|
for i := 0; i < 2; i++ {
|
|
if i == 1 {
|
|
// test upnp or nat-pmp
|
|
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
|
|
}
|
|
log.Println("PublicIP:", ext)
|
|
|
|
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30)
|
|
if err != nil {
|
|
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
|
break
|
|
} else {
|
|
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800)
|
|
}
|
|
}
|
|
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", publicIP, echoPort))
|
|
if err != nil {
|
|
break
|
|
}
|
|
conn.WriteTo([]byte("echo"), dst)
|
|
buf := make([]byte, 1600)
|
|
|
|
// wait for echo testing
|
|
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
|
_, _, err = conn.ReadFromUDP(buf)
|
|
if err == nil {
|
|
if i == 1 {
|
|
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
|
hasUPNPorNATPMP = 1
|
|
} else {
|
|
gLog.Println(LvDEBUG, "public ip:YES")
|
|
hasPublicIP = 1
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return
|
|
}
|