From c0bad61eb61fc04924e722d0f5ab5ac9408b38ec Mon Sep 17 00:00:00 2001 From: TenderIronh Date: Thu, 26 May 2022 19:01:42 +0800 Subject: [PATCH] tcp punch --- common.go | 9 +++ common_test.go | 25 +++++++ config.go | 84 ++++++++++++----------- errorcode.go | 13 ++-- go.mod | 3 +- handlepush.go | 64 +++++++++++++++++- holepunch.go | 8 ++- install.go | 2 +- nat.go | 72 +++++++++++++++++--- openp2p.go | 2 +- p2pnetwork.go | 110 +++++++++++++++++++++++-------- p2ptunnel.go | 172 ++++++++++++++++++++++++++---------------------- protocol.go | 81 ++++++++++++++++++----- underlay_tcp.go | 30 +++++++-- update.go | 4 +- upnp.go | 35 +++------- 16 files changed, 497 insertions(+), 217 deletions(-) diff --git a/common.go b/common.go index 8aa228e..2444277 100644 --- a/common.go +++ b/common.go @@ -188,6 +188,15 @@ func compareVersion(v1, v2 string) int { return LESS } +func parseMajorVer(ver string) int { + v1Arr := strings.Split(ver, ".") + if len(v1Arr) > 0 { + n, _ := strconv.ParseInt(v1Arr[0], 10, 32) + return int(n) + } + return 0 +} + func IsIPv6(address string) bool { return strings.Count(address, ":") >= 2 } diff --git a/common_test.go b/common_test.go index cd299f1..b2ded0d 100644 --- a/common_test.go +++ b/common_test.go @@ -49,6 +49,11 @@ func assertCompareVersion(t *testing.T, v1 string, v2 string, result int) { t.Errorf("compare version %s %s fail\n", v1, v2) } } +func assertParseMajorVer(t *testing.T, v string, result int) { + if parseMajorVer(v) != result { + t.Errorf("ParseMajorVer %s fail\n", v) + } +} func TestCompareVersion(t *testing.T) { // test = assertCompareVersion(t, "0.98.0", "0.98.0", EQUAL) @@ -69,3 +74,23 @@ func TestCompareVersion(t *testing.T) { assertCompareVersion(t, "", "1.5.0", LESS) } + +func TestParseMajorVer(t *testing.T) { + + assertParseMajorVer(t, "0.98.0", 0) + assertParseMajorVer(t, "0.98", 0) + assertParseMajorVer(t, "1.4.0", 1) + assertParseMajorVer(t, "1.5.0", 1) + + assertParseMajorVer(t, "0.98.0.22345", 0) + assertParseMajorVer(t, "1.98.0.12345", 1) + assertParseMajorVer(t, "10.98.0.12345", 10) + assertParseMajorVer(t, "1.4.0", 1) + assertParseMajorVer(t, "1.4", 1) + assertParseMajorVer(t, "1", 1) + assertParseMajorVer(t, "2", 2) + assertParseMajorVer(t, "3", 3) + assertParseMajorVer(t, "2.1.0", 2) + assertParseMajorVer(t, "3.0.0", 3) + +} diff --git a/config.go b/config.go index ffb7173..4984f9b 100644 --- a/config.go +++ b/config.go @@ -24,21 +24,23 @@ type AppConfig struct { PeerUser string Enabled int // default:1 // runtime info - peerVersion string - peerToken uint64 - peerNatType int - hasIPv4 int - IPv6 string - hasUPNPorNATPMP int - peerIP string - peerConeNatPort int - retryNum int - retryTime time.Time - nextRetryTime time.Time - shareBandwidth int - errMsg string - connectTime time.Time - fromToken uint64 + peerVersion string + peerToken uint64 + peerNatType int + hasIPv4 int + IPv6 string + hasUPNPorNATPMP int + peerIP string + peerConeNatPort int + retryNum int + retryTime time.Time + nextRetryTime time.Time + shareBandwidth int + errMsg string + connectTime time.Time + fromToken uint64 + linkMode string + isUnderlayServer int // TODO: bool? } // TODO: add loglevel, maxlogfilesize @@ -105,10 +107,16 @@ func (c *Config) save() { } } +func init() { + gConf.LogLevel = 1 + gConf.Network.ShareBandwidth = 10 + gConf.Network.ServerHost = "api.openp2p.cn" + gConf.Network.ServerPort = WsPort + +} + func (c *Config) load() error { c.mtx.Lock() - c.LogLevel = IntValueNotSet - c.Network.ShareBandwidth = IntValueNotSet defer c.mtx.Unlock() data, err := ioutil.ReadFile("config.json") if err != nil { @@ -162,11 +170,13 @@ type NetworkConfig struct { ServerPort int UDPPort1 int UDPPort2 int + TCPPort int } func parseParams(subCommand string) { fset := flag.NewFlagSet(subCommand, flag.ExitOnError) serverHost := fset.String("serverhost", "api.openp2p.cn", "server host ") + serverPort := fset.Int("serverport", WsPort, "server port ") // serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug token := fset.Uint64("token", 0, "token") node := fset.String("node", "", "node name. 8-31 characters. if not set, it will be hostname") @@ -174,13 +184,14 @@ func parseParams(subCommand string) { dstIP := fset.String("dstip", "127.0.0.1", "destination ip ") dstPort := fset.Int("dstport", 0, "destination port ") srcPort := fset.Int("srcport", 0, "source port ") + tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip") protocol := fset.String("protocol", "tcp", "tcp or udp") appName := fset.String("appname", "", "app name") shareBandwidth := fset.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit") daemonMode := fset.Bool("d", false, "daemonMode") notVerbose := fset.Bool("nv", false, "not log console") newconfig := fset.Bool("newconfig", false, "not load existing config.json") - logLevel := fset.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error") + logLevel := fset.Int("loglevel", 0, "0:info 1:warn 2:error 3:debug") if subCommand == "" { // no subcommand fset.Parse(os.Args[1:]) } else { @@ -197,7 +208,6 @@ func parseParams(subCommand string) { if !*newconfig { gConf.load() // load old config. otherwise will clear all apps } - gConf.LogLevel = *logLevel if config.SrcPort != 0 { gConf.add(config, true) } @@ -217,14 +227,17 @@ func parseParams(subCommand string) { if f.Name == "loglevel" { gConf.LogLevel = *logLevel } + if f.Name == "tcpport" { + gConf.Network.TCPPort = *tcpPort + } + if f.Name == "token" { + gConf.Network.Token = *token + } }) if gConf.Network.ServerHost == "" { gConf.Network.ServerHost = *serverHost } - if *token != 0 { - gConf.Network.Token = *token - } if *node != "" { if len(*node) < 8 { gLog.Println(LvERROR, ErrNodeTooShort) @@ -236,16 +249,17 @@ func parseParams(subCommand string) { gConf.Network.Node = defaultNodeName() } } - if gConf.LogLevel == IntValueNotSet { - gConf.LogLevel = *logLevel - } - if gConf.Network.ShareBandwidth == IntValueNotSet { - gConf.Network.ShareBandwidth = *shareBandwidth + if gConf.Network.TCPPort == 0 { + if *tcpPort == 0 { + p := int(nodeNameToID(gConf.Network.Node)%15000 + 50000) + tcpPort = &p + } + gConf.Network.TCPPort = *tcpPort } - gConf.Network.ServerPort = 27183 - gConf.Network.UDPPort1 = 27182 - gConf.Network.UDPPort2 = 27183 + gConf.Network.ServerPort = *serverPort + gConf.Network.UDPPort1 = UDPPort1 + gConf.Network.UDPPort2 = UDPPort2 gLog.setLevel(LogLevel(gConf.LogLevel)) if *notVerbose { gLog.setMode(LogFile) @@ -253,13 +267,3 @@ func parseParams(subCommand string) { // gConf.mtx.Unlock() gConf.save() } - -func (conf *AppConfig) isSupportTCP(pnConf NetworkConfig) bool { - if conf.peerVersion == "" || compareVersion(conf.peerVersion, LeastSupportTCPVersion) == LESS { - return false - } - if pnConf.hasIPv4 == 1 || pnConf.hasUPNPorNATPMP == 1 || conf.hasIPv4 == 1 || conf.hasUPNPorNATPMP == 1 || (IsIPv6(pnConf.IPv6) && IsIPv6(conf.IPv6)) { - return true - } - return false -} diff --git a/errorcode.go b/errorcode.go index f97db46..a39446b 100644 --- a/errorcode.go +++ b/errorcode.go @@ -8,9 +8,12 @@ import ( var ( // ErrorS2S string = "s2s is not supported" // ErrorHandshake string = "handshake error" - ErrorS2S = errors.New("s2s is not supported") - ErrorHandshake = errors.New("handshake error") - ErrorNewUser = errors.New("new user") - ErrorLogin = errors.New("user or password not correct") - ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters") + ErrorS2S = errors.New("s2s is not supported") + ErrorHandshake = errors.New("handshake error") + ErrorNewUser = errors.New("new user") + ErrorLogin = errors.New("user or password not correct") + ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters") + ErrPeerOffline = errors.New("peer offline") + ErrMsgFormat = errors.New("message format wrong") + ErrVersionNotCompatible = errors.New("version not compatible") ) diff --git a/go.mod b/go.mod index 4c8db7a..e9656c5 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.18 require ( github.com/gorilla/websocket v1.4.2 github.com/kardianos/service v1.2.0 + github.com/libp2p/go-reuseport v0.2.0 github.com/lucas-clemente/quic-go v0.27.0 - golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 + golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 ) require ( diff --git a/handlepush.go b/handlepush.go index 2becf98..4162019 100644 --- a/handlepush.go +++ b/handlepush.go @@ -19,7 +19,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { } gLog.Printf(LvDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead) switch subType { - case MsgPushConnectReq: + case MsgPushConnectReq: // TODO: handle a msg move to a new function req := PushConnectReq{} err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req) if err != nil { @@ -28,6 +28,17 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { } gLog.Printf(LvINFO, "%s is connecting...", req.From) gLog.Println(LvDEBUG, "push connect response to ", req.From) + if compareVersion(req.Version, LeastSupportVersion) == LESS { + gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From) + rsp := PushConnectRsp{ + Error: 10, + Detail: ErrVersionNotCompatible.Error(), + To: req.From, + From: pn.config.Node, + } + pn.push(req.From, MsgPushConnectRsp, rsp) + return ErrVersionNotCompatible + } // verify totp token or token if VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()) { @@ -40,6 +51,10 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { config.peerVersion = req.Version config.fromToken = req.Token config.IPv6 = req.IPv6 + config.hasIPv4 = req.HasIPv4 + config.hasUPNPorNATPMP = req.HasUPNPorNATPMP + config.linkMode = req.LinkMode + config.isUnderlayServer = req.IsUnderlayServer // share relay node will limit bandwidth if req.Token != pn.config.Token { gLog.Printf(LvINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth) @@ -98,7 +113,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { SaveKey(req.AppID, req.AppKey) case MsgPushUpdate: gLog.Println(LvINFO, "MsgPushUpdate") - update() // download new version first, then exec ./openp2p update + update(pn.config.ServerHost, pn.config.ServerPort) // download new version first, then exec ./openp2p update targetPath := filepath.Join(defaultInstallPath, defaultBinName) args := []string{"update"} env := os.Environ() @@ -134,7 +149,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { } relayNode = app.relayNode relayMode = app.relayMode - linkMode = app.tunnel.linkMode + linkMode = app.tunnel.linkModeWeb } appInfo := AppInfo{ AppName: config.AppName, @@ -158,6 +173,49 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error { req.Apps = append(req.Apps, appInfo) } pn.write(MsgReport, MsgReportApps, &req) + case MsgPushReportLog: + gLog.Println(LvINFO, "MsgPushReportLog") + req := ReportLogReq{} + err := json.Unmarshal(msg[openP2PHeaderSize:], &req) + if err != nil { + gLog.Printf(LvERROR, "wrong MsgPushReportLog:%s %s", err, string(msg[openP2PHeaderSize:])) + return err + } + if req.FileName == "" { + req.FileName = "openp2p.log" + } + f, err := os.Open(filepath.Join("log", req.FileName)) + if err != nil { + gLog.Println(LvERROR, "read log file error:", err) + break + } + fi, err := f.Stat() + if err != nil { + break + } + if req.Offset == 0 && fi.Size() > 4096 { + req.Offset = fi.Size() - 4096 + } + if req.Len <= 0 { + req.Len = 4096 + } + f.Seek(req.Offset, 0) + if req.Len > 1024*1024 { // too large + break + } + buff := make([]byte, req.Len) + readLength, err := f.Read(buff) + f.Close() + if err != nil { + gLog.Println(LvERROR, "read log content error:", err) + break + } + rsp := ReportLogRsp{} + rsp.Content = string(buff[:readLength]) + rsp.FileName = req.FileName + rsp.Total = fi.Size() + rsp.Len = req.Len + pn.write(MsgReport, MsgPushReportLog, &rsp) case MsgPushEditApp: gLog.Println(LvINFO, "MsgPushEditApp") newApp := AppInfo{} diff --git a/holepunch.go b/holepunch.go index 9598c28..83d8684 100644 --- a/holepunch.go +++ b/holepunch.go @@ -12,7 +12,7 @@ import ( func handshakeC2C(t *P2PTunnel) (err error) { gLog.Printf(LvDEBUG, "handshakeC2C %s:%d:%d to %s:%d", t.pn.config.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort) - defer gLog.Printf(LvDEBUG, "handshakeC2C ok") + defer gLog.Printf(LvDEBUG, "handshakeC2C end") conn, err := net.ListenUDP("udp", t.la) if err != nil { return err @@ -45,6 +45,7 @@ func handshakeC2C(t *P2PTunnel) (err error) { } if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck { gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id) + gLog.Printf(LvINFO, "handshakeC2C ok") return nil } } @@ -54,9 +55,10 @@ func handshakeC2C(t *P2PTunnel) (err error) { _, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) if err != nil { gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err) + return err } - return err } + gLog.Printf(LvINFO, "handshakeC2C ok") return nil } @@ -115,6 +117,7 @@ func handshakeC2S(t *P2PTunnel) error { _, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) return err } + gLog.Printf(LvINFO, "handshakeC2S ok") return nil } @@ -175,6 +178,7 @@ func handshakeS2C(t *P2PTunnel) error { return fmt.Errorf("wait handshake failed") case la := <-gotCh: gLog.Println(LvDEBUG, "symmetric handshake ok", la) + gLog.Printf(LvINFO, "handshakeS2C ok") } return nil } diff --git a/install.go b/install.go index 7b3e07c..6f7a012 100644 --- a/install.go +++ b/install.go @@ -17,7 +17,7 @@ import ( // ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22 func install() { gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ Group: 16947733, Email: openp2p.cn@gmail.com") + gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com") gLog.Println(LvINFO, "install start") defer gLog.Println(LvINFO, "install end") // auto uninstall diff --git a/nat.go b/nat.go index bf5aa30..db2575d 100644 --- a/nat.go +++ b/nat.go @@ -6,12 +6,55 @@ import ( "log" "math/rand" "net" + "strconv" + "strings" "time" + + reuse "github.com/libp2p/go-reuseport" ) +var echoConn *net.UDPConn + +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.Dial("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", localPort), fmt.Sprintf("%s:%d", serverHost, serverPort)) + // 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, echoPort int) (publicIP string, hasPublicIP int, hasUPNPorNATPMP int, 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, 0, 0, err } defer conn.Close() @@ -48,7 +91,7 @@ func natTest(serverHost string, serverPort int, localPort int, echoPort int) (pu if i == 1 { // test upnp or nat-pmp nat, err := Discover() - if err != nil { + if err != nil || nat == nil { gLog.Println(LvDEBUG, "could not perform UPNP discover:", err) break } @@ -59,10 +102,12 @@ func natTest(serverHost string, serverPort int, localPort int, echoPort int) (pu } log.Println("PublicIP:", ext) - externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 60) + 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", natRsp.IP, echoPort) @@ -98,14 +143,22 @@ func natTest(serverHost string, serverPort int, localPort int, echoPort int) (pu 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()%10000 + 50000) - echoPort := int(rand.Uint32()%10000 + 50000) + localPort := int(rand.Uint32()%15000 + 50000) + echoPort := P2PNetworkInstance(nil).config.TCPPort go echo(echoPort) + // _, natPort := natTCP(host, 27181, localPort) + // gLog.Println(LvINFO, "nattcp:", natPort) + // _, natPort = natTCP(host, 27180, localPort) + // gLog.Println(LvINFO, "nattcp:", natPort) ip1, hasIPv4, hasUPNPorNATPMP, port1, err := natTest(host, udp1, localPort, echoPort) gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port1) if err != nil { return "", 0, hasIPv4, hasUPNPorNATPMP, err } + if echoConn != nil { + echoConn.Close() + echoConn = nil + } // if hasPublicIP == 1 || hasUPNPorNATPMP == 1 { // return ip1, NATNone, hasUPNPorNATPMP, nil // } @@ -122,18 +175,19 @@ func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, } func echo(echoPort int) { - conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort}) + 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) - defer conn.Close() + // close outside for breaking the ReadFromUDP // wait 5s for echo testing - conn.SetReadDeadline(time.Now().Add(time.Second * 30)) - n, addr, err := conn.ReadFromUDP(buf) + echoConn.SetReadDeadline(time.Now().Add(time.Second * 30)) + n, addr, err := echoConn.ReadFromUDP(buf) if err != nil { return } - conn.WriteToUDP(buf[0:n], addr) + echoConn.WriteToUDP(buf[0:n], addr) } diff --git a/openp2p.go b/openp2p.go index 2b7074e..2612846 100644 --- a/openp2p.go +++ b/openp2p.go @@ -42,7 +42,7 @@ func main() { } parseParams("") gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ Group: 16947733, Email: openp2p.cn@gmail.com") + gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com") if gConf.daemonMode { d := daemon{} diff --git a/p2pnetwork.go b/p2pnetwork.go index 2871862..811c3eb 100644 --- a/p2pnetwork.go +++ b/p2pnetwork.go @@ -160,6 +160,7 @@ func (pn *P2PNetwork) autorunApp() { func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, string, error) { gLog.Printf(LvINFO, "addRelayTunnel to %s start", config.PeerNode) defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.PeerNode) + // request a relay node or specify manually(TODO) pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode}) head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, time.Second*10) if head == nil { @@ -179,6 +180,7 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri relayConfig := config relayConfig.PeerNode = rsp.RelayName relayConfig.peerToken = rsp.RelayToken + /// t, err := pn.addDirectTunnel(relayConfig, 0) if err != nil { gLog.Println(LvERROR, "direct connect error:", err) @@ -238,13 +240,7 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error { peerIP = t.config.peerIP } // TODO: if tcp failed, should try udp punching, nattype should refactor also, when NATNONE and failed we don't know the peerNatType - // if err != nil && err == ErrorHandshake && t.isSupportTCP() { - // t, err = pn.addDirectTunnel(config, 0) - // if t != nil { - // peerNatType = t.config.peerNatType - // peerIP = t.config.peerIP - // } - // } + if err != nil && err == ErrorHandshake { gLog.Println(LvERROR, "direct connect failed, try to relay") t, rtid, relayMode, err = pn.addRelayTunnel(config) @@ -349,34 +345,96 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel, } return true }) + if exist { + return t, nil + } // create tunnel if not exist - if !exist { - t = &P2PTunnel{pn: pn, - config: config, - id: tid, + t = &P2PTunnel{pn: pn, + config: config, + id: tid, + } + pn.msgMapMtx.Lock() + pn.msgMap[nodeNameToID(config.PeerNode)] = make(chan []byte, 50) + pn.msgMapMtx.Unlock() + // server side + if !isClient { + err := pn.newTunnel(t, tid, isClient) + return t, err // always return + } + // client side + // peer info + initErr := t.requestPeerInfo() + if initErr != nil { + gLog.Println(LvERROR, "init error:", initErr) + return nil, initErr + } + err := ErrorHandshake + // try TCP6 + if IsIPv6(t.config.IPv6) && IsIPv6(t.pn.config.IPv6) { + gLog.Println(LvINFO, "try TCP6") + t.config.linkMode = LinkModeTCP6 + t.config.isUnderlayServer = 0 + if err = pn.newTunnel(t, tid, isClient); err == nil { + return t, nil } - pn.msgMapMtx.Lock() - pn.msgMap[nodeNameToID(config.PeerNode)] = make(chan []byte, 50) - pn.msgMapMtx.Unlock() - t.init() - if isClient { - if err := t.connect(); err != nil { - gLog.Println(LvERROR, "p2pTunnel connect error:", err) - return t, err - } + } + + // TODO: try UDP6 + + // try TCP4 + if t.config.hasIPv4 == 1 || t.pn.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1 || t.pn.config.hasUPNPorNATPMP == 1 { + gLog.Println(LvINFO, "try TCP4") + t.config.linkMode = LinkModeTCP4 + if t.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1 { + t.config.isUnderlayServer = 0 } else { - if err := t.listen(); err != nil { - gLog.Println(LvERROR, "p2pTunnel listen error:", err) - return t, err - } + t.config.isUnderlayServer = 1 + } + if err = pn.newTunnel(t, tid, isClient); err == nil { + return t, nil + } + } + // TODO: try UDP4 + + // try TCPPunch + if t.config.peerNatType == NATCone && t.pn.config.natType == NATCone { // TODO: support c2s + gLog.Println(LvINFO, "try TCP4 Punch") + t.config.linkMode = LinkModeTCPPunch + t.config.isUnderlayServer = 0 + if err = pn.newTunnel(t, tid, isClient); err == nil { + return t, nil + } + } + // try UDPPunch + if t.config.peerNatType == NATCone || t.pn.config.natType == NATCone { + gLog.Println(LvINFO, "try UDP4 Punch") + t.config.linkMode = LinkModeUDPPunch + t.config.isUnderlayServer = 0 + if err = pn.newTunnel(t, tid, isClient); err == nil { + return t, nil + } + } + return nil, err +} + +func (pn *P2PNetwork) newTunnel(t *P2PTunnel, tid uint64, isClient bool) error { + t.initPort() + if isClient { + if err := t.connect(); err != nil { + gLog.Println(LvERROR, "p2pTunnel connect error:", err) + return err + } + } else { + if err := t.listen(); err != nil { + gLog.Println(LvERROR, "p2pTunnel listen error:", err) + return err } } // store it when success gLog.Printf(LvDEBUG, "store tunnel %d", tid) pn.allTunnels.Store(tid, t) - return t, nil + return nil } - func (pn *P2PNetwork) init() error { gLog.Println(LvINFO, "init start") var err error diff --git a/p2ptunnel.go b/p2ptunnel.go index 0310e6e..c856364 100644 --- a/p2ptunnel.go +++ b/p2ptunnel.go @@ -28,58 +28,85 @@ type P2PTunnel struct { tunnelServer bool // different from underlayServer coneLocalPort int coneNatPort int - linkMode string + linkModeWeb string // use config.linkmode } -func (t *P2PTunnel) init() { +func (t *P2PTunnel) requestPeerInfo() error { + // request peer info + t.pn.write(MsgQuery, MsgQueryPeerInfoReq, &QueryPeerInfoReq{t.pn.config.Token, t.config.PeerNode}) + head, body := t.pn.read("", MsgQuery, MsgQueryPeerInfoRsp, time.Second*10) + if head == nil { + return ErrPeerOffline + } + rsp := QueryPeerInfoRsp{} + err := json.Unmarshal(body, &rsp) + if err != nil { + gLog.Printf(LvERROR, "wrong QueryPeerInfoRsp:%s", err) + return ErrMsgFormat + } + if rsp.Online == 0 { + return ErrPeerOffline + } + if compareVersion(rsp.Version, LeastSupportVersion) == LESS { + return ErrVersionNotCompatible + } + t.config.peerVersion = rsp.Version + t.config.hasIPv4 = rsp.HasIPv4 + t.config.peerIP = rsp.IPv4 + t.config.IPv6 = rsp.IPv6 + t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP + t.config.peerNatType = rsp.NatType + /// + return nil +} +func (t *P2PTunnel) initPort() { t.running = true t.hbMtx.Lock() t.hbTime = time.Now() t.hbMtx.Unlock() t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time - localPort := int(rand.Uint32()%10000 + 50000) - if t.pn.config.natType == NATCone { - // prepare one random cone hole - _, _, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0) - t.coneLocalPort = localPort - t.coneNatPort = port1 - t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort} - } else { - t.coneLocalPort = localPort - t.coneNatPort = localPort // symmetric doesn't need coneNatPort - if t.pn.config.hasUPNPorNATPMP == 1 { - nat, err := Discover() - if err != nil { - gLog.Println(LvDEBUG, "could not perform UPNP discover:", err) - } else { - externalPort, err := nat.AddPortMapping("tcp", localPort, localPort, "openp2p", 30) // timeout the connection still alive, make the timeout short - if err != nil { - gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort) - } - } - } - t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort} + localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param + if t.config.linkMode == LinkModeTCP6 { + t.pn.refreshIPv6() } + if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 { + t.coneLocalPort = t.pn.config.TCPPort + t.coneNatPort = t.pn.config.TCPPort // symmetric doesn't need coneNatPort + } + if t.config.linkMode == LinkModeUDPPunch { + // prepare one random cone hole + _, _, _, natPort, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0) + t.coneLocalPort = localPort + t.coneNatPort = natPort + } + if t.config.linkMode == LinkModeTCPPunch { + // prepare one random cone hole + _, natPort := natTCP(t.pn.config.ServerHost, IfconfigPort1, localPort) + t.coneLocalPort = localPort + t.coneNatPort = natPort + } + t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort} gLog.Printf(LvDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort) } func (t *P2PTunnel) connect() error { gLog.Printf(LvDEBUG, "start p2pTunnel to %s ", t.config.PeerNode) t.tunnelServer = false - t.pn.refreshIPv6() appKey := uint64(0) req := PushConnectReq{ - Token: t.config.peerToken, - From: t.pn.config.Node, - FromIP: t.pn.config.publicIP, - ConeNatPort: t.coneNatPort, - NatType: t.pn.config.natType, - HasIPv4: t.pn.config.hasIPv4, - IPv6: t.pn.config.IPv6, - HasUPNPorNATPMP: t.pn.config.hasUPNPorNATPMP, - ID: t.id, - AppKey: appKey, - Version: OpenP2PVersion, + Token: t.config.peerToken, + From: t.pn.config.Node, + FromIP: t.pn.config.publicIP, + ConeNatPort: t.coneNatPort, + NatType: t.pn.config.natType, + HasIPv4: t.pn.config.hasIPv4, + IPv6: t.pn.config.IPv6, + HasUPNPorNATPMP: t.pn.config.hasUPNPorNATPMP, + ID: t.id, + AppKey: appKey, + Version: OpenP2PVersion, + LinkMode: t.config.linkMode, + IsUnderlayServer: t.config.isUnderlayServer ^ 1, } if req.Token == 0 { // no relay token req.Token = t.pn.config.Token @@ -162,7 +189,7 @@ func (t *P2PTunnel) close() { } func (t *P2PTunnel) start() error { - if !t.config.isSupportTCP(t.pn.config) { + if t.config.linkMode == LinkModeUDPPunch { if err := t.handshake(); err != nil { return err } @@ -176,7 +203,7 @@ func (t *P2PTunnel) start() error { } func (t *P2PTunnel) handshake() error { - if t.config.peerConeNatPort > 0 { + if t.config.peerConeNatPort > 0 { // only peer is cone should prepare t.ra var err error t.ra, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort)) if err != nil { @@ -207,23 +234,22 @@ func (t *P2PTunnel) handshake() error { } func (t *P2PTunnel) connectUnderlay() (err error) { - if !t.config.isSupportTCP(t.pn.config) { + switch t.config.linkMode { + case LinkModeTCP6: + t.conn, err = t.connectUnderlayTCP6() + case LinkModeTCP4: + t.conn, err = t.connectUnderlayTCP() + case LinkModeTCPPunch: + t.conn, err = t.connectUnderlayTCP() + case LinkModeUDPPunch: t.conn, err = t.connectUnderlayQuic() - if err != nil { - return err - } - } else { - if IsIPv6(t.pn.config.IPv6) && IsIPv6(t.config.IPv6) { // both have ipv6 - t.conn, err = t.connectUnderlayTCP6() - if err != nil { - return err - } - } else { // hasipv4 or upnp - t.conn, err = t.connectUnderlayTCP() - if err != nil { - return err - } - } + + } + if err != nil { + return err + } + if t.conn == nil { + return errors.New("connect underlay error") } t.setRun(true) go t.readLoop() @@ -235,7 +261,7 @@ func (t *P2PTunnel) connectUnderlayQuic() (c underlay, err error) { gLog.Println(LvINFO, "connectUnderlayQuic start") defer gLog.Println(LvINFO, "connectUnderlayQuic end") var qConn *underlayQUIC - if t.isUnderlayServer() { + if t.config.isUnderlayServer == 1 { time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env qConn, err = listenQuic(t.la.String(), TunnelIdleTimeout) if err != nil { @@ -288,7 +314,7 @@ func (t *P2PTunnel) connectUnderlayQuic() (c underlay, err error) { gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) gLog.Println(LvDEBUG, "quic connection ok") - t.linkMode = LinkModeUDPPunch + t.linkModeWeb = LinkModeUDPPunch return qConn, nil } @@ -297,37 +323,36 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) { gLog.Println(LvINFO, "connectUnderlayTCP start") defer gLog.Println(LvINFO, "connectUnderlayTCP end") var qConn *underlayTCP - if t.isUnderlayServer() { + if t.config.isUnderlayServer == 1 { t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) - qConn, err = listenTCP(t.coneNatPort, TunnelIdleTimeout) + qConn, err = listenTCP(t.config.peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode) if err != nil { return nil, fmt.Errorf("listen TCP error:%s", err) } + _, buff, err := qConn.ReadBuffer() if err != nil { - qConn.listener.Close() return nil, fmt.Errorf("read start msg error:%s", err) } if buff != nil { gLog.Println(LvDEBUG, string(buff)) } qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) - gLog.Println(LvDEBUG, "TCP connection ok") + gLog.Println(LvINFO, "TCP connection ok") return qConn, nil } //else t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, time.Second*5) - gLog.Println(LvDEBUG, "TCP dial to ", t.ra.String()) - qConn, err = dialTCP(t.config.peerIP, t.config.peerConeNatPort) + gLog.Println(LvDEBUG, "TCP dial to ", t.config.peerIP, ":", t.config.peerConeNatPort) + qConn, err = dialTCP(t.config.peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode) if err != nil { - return nil, fmt.Errorf("TCP dial to %s error:%s", t.ra.String(), err) + return nil, fmt.Errorf("TCP dial to %s:%d error:%s", t.config.peerIP, t.config.peerConeNatPort, err) } handshakeBegin := time.Now() qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) _, buff, err := qConn.ReadBuffer() if err != nil { - qConn.listener.Close() return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) } if buff != nil { @@ -335,8 +360,8 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) { } gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) - gLog.Println(LvDEBUG, "TCP connection ok") - t.linkMode = LinkModeIPv4 + gLog.Println(LvINFO, "TCP connection ok") + t.linkModeWeb = LinkModeIPv4 return qConn, nil } @@ -344,7 +369,7 @@ func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) { gLog.Println(LvINFO, "connectUnderlayTCP6 start") defer gLog.Println(LvINFO, "connectUnderlayTCP6 end") var qConn *underlayTCP6 - if t.isUnderlayServer() { + if t.config.isUnderlayServer == 1 { t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) qConn, err = listenTCP6(t.coneNatPort, TunnelIdleTimeout) if err != nil { @@ -383,7 +408,7 @@ func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) { gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) gLog.Println(LvDEBUG, "TCP6 connection ok") - t.linkMode = LinkModeIPv6 + t.linkModeWeb = LinkModeIPv6 return qConn, nil } @@ -594,14 +619,3 @@ func (t *P2PTunnel) closeOverlayConns(appID uint64) { return true }) } - -func (t *P2PTunnel) isUnderlayServer() bool { - if (t.pn.config.hasIPv4 == 1 || t.pn.config.hasUPNPorNATPMP == 1) && (t.config.hasIPv4 != 1 || t.config.hasUPNPorNATPMP != 1) { - return true - } - if (t.pn.config.hasIPv4 != 1 || t.pn.config.hasUPNPorNATPMP != 1) && (t.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1) { - return false - } - // NAT or both has public IP - return t.tunnelServer -} diff --git a/protocol.go b/protocol.go index 6bff862..d08e65c 100644 --- a/protocol.go +++ b/protocol.go @@ -10,9 +10,17 @@ import ( "time" ) -const OpenP2PVersion = "2.0.1" +const OpenP2PVersion = "3.1.0" const ProducnName string = "openp2p" -const LeastSupportTCPVersion = "1.5.0" +const LeastSupportVersion = "3.0.0" + +const ( + IfconfigPort1 = 27180 + IfconfigPort2 = 27181 + WsPort = 27183 + UDPPort1 = 27182 + UDPPort2 = 27183 +) type openP2PHeader struct { DataLen uint32 @@ -68,6 +76,7 @@ const ( MsgP2P = 4 MsgRelay = 5 MsgReport = 6 + MsgQuery = 7 ) // TODO: seperate node push and web push. @@ -86,6 +95,7 @@ const ( MsgPushRestart = 11 MsgPushEditNode = 12 MsgPushAPPKey = 13 + MsgPushReportLog = 14 ) // MsgP2P sub type message @@ -117,6 +127,7 @@ const ( MsgReportQuery MsgReportConnect MsgReportApps + MsgReportLog ) const ( @@ -158,8 +169,18 @@ const ( // linkmode const ( LinkModeUDPPunch = "udppunch" - LinkModeIPv4 = "ipv4" - LinkModeIPv6 = "ipv6" + LinkModeTCPPunch = "tcppunch" + LinkModeIPv4 = "ipv4" // for web + LinkModeIPv6 = "ipv6" // for web + LinkModeTCP6 = "tcp6" + LinkModeTCP4 = "tcp4" + LinkModeUDP6 = "udp6" + LinkModeUDP4 = "udp4" +) + +const ( + MsgQueryPeerInfoReq = iota + MsgQueryPeerInfoRsp ) func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) { @@ -187,18 +208,20 @@ func nodeNameToID(name string) uint64 { } type PushConnectReq struct { - From string `json:"from,omitempty"` - FromToken uint64 `json:"fromToken,omitempty"` // deprecated - Version string `json:"version,omitempty"` - Token uint64 `json:"token,omitempty"` // if public totp token - ConeNatPort int `json:"coneNatPort,omitempty"` // if isPublic, is public port - NatType int `json:"natType,omitempty"` - HasIPv4 int `json:"hasIPv4,omitempty"` - IPv6 string `json:"IPv6,omitempty"` - HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"` - FromIP string `json:"fromIP,omitempty"` - ID uint64 `json:"id,omitempty"` - AppKey uint64 `json:"appKey,omitempty"` // for underlay tcp + From string `json:"from,omitempty"` + FromToken uint64 `json:"fromToken,omitempty"` // deprecated + Version string `json:"version,omitempty"` + Token uint64 `json:"token,omitempty"` // if public totp token + ConeNatPort int `json:"coneNatPort,omitempty"` // if isPublic, is public port + NatType int `json:"natType,omitempty"` + HasIPv4 int `json:"hasIPv4,omitempty"` + IPv6 string `json:"IPv6,omitempty"` + HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"` + FromIP string `json:"fromIP,omitempty"` + ID uint64 `json:"id,omitempty"` + AppKey uint64 `json:"appKey,omitempty"` // for underlay tcp + LinkMode string `json:"linkMode,omitempty"` + IsUnderlayServer int `json:"isServer,omitempty"` // Requset spec peer is server } type PushConnectRsp struct { Error int `json:"error,omitempty"` @@ -342,6 +365,18 @@ type ReportApps struct { Apps []AppInfo } +type ReportLogReq struct { + FileName string `json:"fileName,omitempty"` + Offset int64 `json:"offset,omitempty"` + Len int64 `json:"len,omitempty"` +} +type ReportLogRsp struct { + FileName string `json:"fileName,omitempty"` + Content string `json:"content,omitempty"` + Len int64 `json:"len,omitempty"` + Total int64 `json:"total,omitempty"` +} + type UpdateInfo struct { Error int `json:"error,omitempty"` ErrorDetail string `json:"errorDetail,omitempty"` @@ -380,3 +415,17 @@ type EditNode struct { NewName string `json:"newName,omitempty"` Bandwidth int `json:"bandwidth,omitempty"` } + +type QueryPeerInfoReq struct { + Token uint64 `json:"token,omitempty"` // if public totp token + PeerNode string `json:"peerNode,omitempty"` +} +type QueryPeerInfoRsp struct { + Online int `json:"online,omitempty"` + Version string `json:"version,omitempty"` + NatType int `json:"natType,omitempty"` + IPv4 string `json:"IPv4,omitempty"` + HasIPv4 int `json:"hasIPv4,omitempty"` // has public ipv4 + IPv6 string `json:"IPv6,omitempty"` // if public relay node, ipv6 not set + HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"` +} diff --git a/underlay_tcp.go b/underlay_tcp.go index 65d388e..5bd0f29 100644 --- a/underlay_tcp.go +++ b/underlay_tcp.go @@ -7,10 +7,11 @@ import ( "net" "sync" "time" + + reuse "github.com/libp2p/go-reuseport" ) type underlayTCP struct { - listener net.Listener writeMtx *sync.Mutex net.Conn } @@ -66,13 +67,20 @@ func (conn *underlayTCP) Close() error { return conn.Conn.Close() } -func listenTCP(port int, idleTimeout time.Duration) (*underlayTCP, error) { - addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", port)) +func listenTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) { + if mode == LinkModeTCPPunch { + c, err := reuse.Dial("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port)) // TODO: timeout + if err != nil { + gLog.Println(LvDEBUG, "send tcp punch: ", err) + return nil, err + } + return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil + } + addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", localPort)) l, err := net.ListenTCP("tcp", addr) if err != nil { return nil, err } - defer l.Close() l.SetDeadline(time.Now().Add(SymmetricHandshakeAckTimeout)) c, err := l.Accept() defer l.Close() @@ -82,11 +90,19 @@ func listenTCP(port int, idleTimeout time.Duration) (*underlayTCP, error) { return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil } -func dialTCP(host string, port int) (*underlayTCP, error) { - c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout) +func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) { + var c net.Conn + var err error + if mode == LinkModeTCPPunch { + c, err = reuse.Dial("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port)) + } else { + c, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout) + } + if err != nil { - fmt.Printf("Dial %s:%d error:%s", host, port, err) + gLog.Printf(LvERROR, "Dial %s:%d error:%s", host, port, err) return nil, err } + gLog.Printf(LvDEBUG, "Dial %s:%d OK", host, port) return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil } diff --git a/update.go b/update.go index 19fc1f6..d3559d4 100644 --- a/update.go +++ b/update.go @@ -16,7 +16,7 @@ import ( "time" ) -func update() { +func update(host string, port int) { gLog.Println(LvINFO, "update start") defer gLog.Println(LvINFO, "update end") c := http.Client{ @@ -27,7 +27,7 @@ func update() { } goos := runtime.GOOS goarch := runtime.GOARCH - rsp, err := c.Get(fmt.Sprintf("https://api.openp2p.cn:27183/api/v1/update?fromver=%s&os=%s&arch=%s", OpenP2PVersion, goos, goarch)) + rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s", host, port, OpenP2PVersion, goos, goarch)) if err != nil { gLog.Println(LvERROR, "update:query update list failed:", err) return diff --git a/upnp.go b/upnp.go index d9aa86a..7b7aa3e 100644 --- a/upnp.go +++ b/upnp.go @@ -34,11 +34,12 @@ type NAT interface { } func Discover() (nat NAT, err error) { + localIP := localIPv4() ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900") if err != nil { return } - conn, err := net.ListenPacket("udp4", ":0") + conn, err := net.ListenPacket("udp4", fmt.Sprintf("%s:0", localIP)) if err != nil { return } @@ -67,6 +68,7 @@ func Discover() (nat NAT, err error) { var n int _, _, err = socket.ReadFromUDP(answerBytes) if err != nil { + gLog.Println(LvERROR, "UPNP discover error:", err) return } @@ -98,12 +100,10 @@ func Discover() (nat NAT, err error) { if err != nil { return } - var ourIP net.IP - ourIP, err = localIPv4() if err != nil { return } - nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain} + nat = &upnpNAT{serviceURL: serviceURL, ourIP: localIP, urnDomain: urnDomain} return } } @@ -174,29 +174,14 @@ func getChildService(d *Device, serviceType string) *UPNPService { return nil } -func localIPv4() (net.IP, error) { - tt, err := net.Interfaces() +func localIPv4() string { // TODO: multi nic will wrong + conn, err := net.Dial("udp", "8.8.8.8:80") if err != nil { - return nil, err + return "" } - for _, t := range tt { - aa, err := t.Addrs() - if err != nil { - return nil, err - } - for _, a := range aa { - ipnet, ok := a.(*net.IPNet) - if !ok { - continue - } - v4 := ipnet.IP.To4() - if v4 == nil || v4[0] == 127 { // loopback address - continue - } - return v4, nil - } - } - return nil, errors.New("cannot find local IP address") + defer conn.Close() + localAddr := conn.LocalAddr().(*net.UDPAddr) + return localAddr.IP.String() } func getServiceURL(rootURL string) (url, urnDomain string, err error) {