package openp2p import ( "encoding/json" "fmt" "math/rand" "net" "strconv" "strings" "time" upnp "openp2p/pkg/upnp" reuse "github.com/openp2p-cn/go-reuseport" ) func natDetectTCP(serverHost string, serverPort int, lp int) (publicIP string, publicPort int, localPort int, err error) { gLog.dev("natDetectTCP start") defer gLog.dev("natDetectTCP end") conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("0.0.0.0:%d", lp), fmt.Sprintf("%s:%d", serverHost, serverPort), NatDetectTimeout) if err != nil { err = fmt.Errorf("dial tcp4 %s:%d error: %w", serverHost, serverPort, err) return } defer conn.Close() localAddr := conn.LocalAddr().(*net.TCPAddr) localPort = localAddr.Port if _, err = conn.Write([]byte("1")); err != nil { err = fmt.Errorf("write error: %w", err) return } b := make([]byte, 1000) conn.SetReadDeadline(time.Now().Add(NatDetectTimeout)) n, err := conn.Read(b) if err != nil { err = fmt.Errorf("read error: %w", err) return } response := strings.Split(string(b[:n]), ":") if len(response) < 2 { err = fmt.Errorf("invalid response format: %s", string(b[:n])) return } publicIP = response[0] port, err := strconv.Atoi(response[1]) if err != nil { err = fmt.Errorf("invalid port format: %w", err) return } publicPort = port return } func natDetectUDP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) { gLog.dev("natDetectUDP start") defer gLog.dev("natDetectUDP end") conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort)) if err != nil { gLog.e("natDetectUDP listen udp error:%s", 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(NatDetectTimeout) err = conn.SetReadDeadline(deadline) if err != nil { return "", 0, err } buffer := make([]byte, 1024) nRead, _, err := conn.ReadFrom(buffer) if err != nil { gLog.e("NAT detect error:%s", err) return "", 0, err } natRsp := NatDetectRsp{} json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp) return natRsp.IP, natRsp.Port, nil } func getNATType(host string, detectPort1 int, detectPort2 int) (publicIP string, NATType int, err error) { setUPNP(gConf.Network.PublicIPPort) // the random local port may be used by other. localPort := int(rand.Uint32()%15000 + 50000) ip1, port1, err := natDetectUDP(host, detectPort1, localPort) if err != nil { // udp block try tcp gLog.w("udp block, try tcp nat detect") if ip1, port1, _, err = natDetectTCP(host, detectPort1, localPort); err != nil { return "", 0, err } } _, port2, err := natDetectUDP(host, detectPort2, localPort) // 2rd nat test not need testing publicip if err != nil { gLog.w("udp block, try tcp nat detect") if _, port2, _, err = natDetectTCP(host, detectPort2, localPort); err != nil { return "", 0, err } } gLog.d("local port:%d nat port:%d", localPort, port2) 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.d("echo server start") var err error echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort}) if err != nil { // listen error gLog.e("echo server listen error:%s", err) return } defer echoConn.Close() // testing for public ip for i := 0; i < 2; i++ { if i == 1 { // test upnp or nat-pmp gLog.d("upnp test start") // 7 days for udp connection // 7 days for tcp connection setUPNP(echoPort) } gLog.d("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.ServerIP, 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.d("publicIPTest echoConn read timeout:%s", err) continue } natRsp := NatDetectRsp{} err = json.Unmarshal(buf[openP2PHeaderSize:nRead], &natRsp) if err != nil { gLog.d("publicIPTest Unmarshal error:%s", err) continue } if natRsp.Port == echoPort { if i == 1 { gLog.d("UPNP or NAT-PMP:YES") hasUPNPorNATPMP = 1 } else { gLog.d("public ip:YES") hasPublicIP = 1 } break } } return } func setUPNP(echoPort int) { nat := upnp.Any() // Initialize the NAT interface if nat == nil { gLog.d("NAT interface is not available") return } ext, err := nat.ExternalIP() if err != nil { gLog.d("could not perform UPNP external address:%s", err) return } gLog.i("PublicIP:%v", ext) externalPort, err := nat.AddMapping("udp", echoPort, echoPort, "openp2p", 604800) if err != nil { gLog.d("could not add udp UPNP port mapping %d", externalPort) return } else { nat.AddMapping("tcp", echoPort, echoPort, "openp2p", 604800) } }