diff --git a/USAGE-ZH.md b/USAGE-ZH.md index 0b6b91a..ce03187 100644 --- a/USAGE-ZH.md +++ b/USAGE-ZH.md @@ -1,108 +1,108 @@ -# 手动运行说明 -大部分情况通过 操作即可。有些情况需要手动运行 -> :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p" - - -## 安装和监听 -``` -./openp2p install -node OFFICEPC1 -token TOKEN -或 -./openp2p -d -node OFFICEPC1 -token TOKEN -# 注意Windows系统把“./openp2p” 换成“openp2p.exe” -``` ->* install: 安装模式【推荐】,会安装成系统服务,这样它就能随系统自动启动 ->* -d: daemon模式。发现worker进程意外退出就会自动启动新的worker进程 ->* -node: 独一无二的节点名字,唯一标识 ->* -token: 在“我的”里面找到 ->* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好. 0表示不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点 ->* -loglevel: 需要查看更多调试日志,设置0;默认是1 - -### 在docker容器里运行openp2p -我们暂时还没提供官方docker镜像,你可以在随便一个容器里运行 -``` -nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & -#这里由于一般的镜像都精简过,install系统服务会失败,所以使用直接daemon模式后台运行 -``` -## 连接 -``` -./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -使用配置文件,建立多个P2PApp -./openp2p -d -``` ->* -appname: 这个P2P应用名字 ->* -peernode: 目标节点名字 ->* -dstip: 目标服务地址,默认本机127.0.0.1 ->* -dstport: 目标服务端口,常见的如windows远程桌面3389,Linux ssh 22 ->* -protocol: 目标服务协议 tcp、udp - -## 配置文件 -一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json` 或 `/usr/local/openp2p/config.json` -希望修改参数,或者配置多个P2PApp可手动修改配置文件 - -配置实例 -``` -{ - "network": { - "Node": "YOUR-NODE-NAME", - "Token": "TOKEN", - "ShareBandwidth": 0, - "ServerHost": "api.openp2p.cn", - "ServerPort": 27183, - "UDPPort1": 27182, - "UDPPort2": 27183 - }, - "apps": [ - { - "AppName": "OfficeWindowsPC", - "Protocol": "tcp", - "SrcPort": 23389, - "PeerNode": "OFFICEPC1", - "DstPort": 3389, - "DstHost": "localhost", - }, - { - "AppName": "OfficeServerSSH", - "Protocol": "tcp", - "SrcPort": 22, - "PeerNode": "OFFICEPC1", - "DstPort": 22, - "DstHost": "192.168.1.5", - } - ] -} -``` - -## 升级客户端 -``` -# update local client -./openp2p update -# update remote client -curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' -``` - -Windows系统需要设置防火墙放行本程序,程序会自动设置,如果设置失败会影响连接功能。 -Linux系统(Ubuntu和CentOS7)的防火墙默认配置均不会有影响,如果不行可尝试关闭防火墙 -``` -systemctl stop firewalld.service -systemctl start firewalld.service -firewall-cmd --state -``` -## 停止 -TODO: windows linux macos -## 卸载 -``` -./openp2p uninstall -# 已安装时 -# windows -C:\Program Files\OpenP2P\openp2p.exe uninstall -# linux,macos -sudo /usr/local/openp2p/openp2p uninstall -``` - -## Docker运行 -``` -# 把YOUR-TOKEN和YOUR-NODE-NAME替换成自己的 -docker run -d --restart=always --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest -OR -docker run -d --restart=always --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME -``` +# 手动运行说明 +大部分情况通过 操作即可。有些情况需要手动运行 +> :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p" + + +## 安装和监听 +``` +./openp2p install -node OFFICEPC1 -token TOKEN +或 +./openp2p -d -node OFFICEPC1 -token TOKEN +# 注意Windows系统把“./openp2p” 换成“openp2p.exe” +``` +>* install: 安装模式【推荐】,会安装成系统服务,这样它就能随系统自动启动 +>* -d: daemon模式。发现worker进程意外退出就会自动启动新的worker进程 +>* -node: 独一无二的节点名字,唯一标识 +>* -token: 在“我的”里面找到 +>* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好. 0表示不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点 +>* -loglevel: 需要查看更多调试日志,设置0;默认是1 + +### 在docker容器里运行openp2p +我们暂时还没提供官方docker镜像,你可以在随便一个容器里运行 +``` +nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & +#这里由于一般的镜像都精简过,install系统服务会失败,所以使用直接daemon模式后台运行 +``` +## 连接 +``` +./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 +使用配置文件,建立多个P2PApp +./openp2p -d +``` +>* -appname: 这个P2P应用名字 +>* -peernode: 目标节点名字 +>* -dstip: 目标服务地址,默认本机127.0.0.1 +>* -dstport: 目标服务端口,常见的如windows远程桌面3389,Linux ssh 22 +>* -protocol: 目标服务协议 tcp、udp + +## 配置文件 +一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json` 或 `/usr/local/openp2p/config.json` +希望修改参数,或者配置多个P2PApp可手动修改配置文件 + +配置实例 +``` +{ + "network": { + "Node": "YOUR-NODE-NAME", + "Token": "TOKEN", + "ShareBandwidth": 0, + "ServerHost": "api.openp2p.cn", + "ServerPort": 27183, + "UDPPort1": 27182, + "UDPPort2": 27183 + }, + "apps": [ + { + "AppName": "OfficeWindowsPC", + "Protocol": "tcp", + "SrcPort": 23389, + "PeerNode": "OFFICEPC1", + "DstPort": 3389, + "DstHost": "localhost", + }, + { + "AppName": "OfficeServerSSH", + "Protocol": "tcp", + "SrcPort": 22, + "PeerNode": "OFFICEPC1", + "DstPort": 22, + "DstHost": "192.168.1.5", + } + ] +} +``` + +## 升级客户端 +``` +# update local client +./openp2p update +# update remote client +curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' +``` + +Windows系统需要设置防火墙放行本程序,程序会自动设置,如果设置失败会影响连接功能。 +Linux系统(Ubuntu和CentOS7)的防火墙默认配置均不会有影响,如果不行可尝试关闭防火墙 +``` +systemctl stop firewalld.service +systemctl start firewalld.service +firewall-cmd --state +``` +## 停止 +TODO: windows linux macos +## 卸载 +``` +./openp2p uninstall +# 已安装时 +# windows +C:\Program Files\OpenP2P\openp2p.exe uninstall +# linux,macos +sudo /usr/local/openp2p/openp2p uninstall +``` + +## Docker运行 +``` +# 把YOUR-TOKEN和YOUR-NODE-NAME替换成自己的 +docker run -d --restart=always --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest +OR +docker run -d --restart=always --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME +``` diff --git a/USAGE.md b/USAGE.md index a6f2778..b755816 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,109 +1,109 @@ - - -# Parameters details -In most cases, you can operate it through . In some cases it is necessary to run manually -> :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p" - - -## Install and Listen -``` -./openp2p install -node OFFICEPC1 -token TOKEN -Or -./openp2p -d -node OFFICEPC1 -token TOKEN - -``` ->* install: [recommand] will install as system service. So it will autorun when system booting. ->* -d: daemon mode run once. When the worker process is found to exit unexpectedly, a new worker process will be automatically started ->* -node: Unique node name, unique identification ->* -token: See "Profile" ->* -sharebandwidth: Provides bandwidth when used as a shared node, the default is 10mbps. If it is a large bandwidth of optical fiber, the larger the setting, the better the effect. 0 means not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other people’s shared nodes ->* -loglevel: Need to view more debug logs, set 0; the default is 1 - -### Run in Docker container -We don't provide official docker image yet, you can run it in any container -``` -nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & -# Since many docker images have been simplified, the install system service will fail, so the daemon mode is used to run in the background -``` - -## Connect -``` -./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -Create multiple P2PApp by config file -./openp2p -d -``` ->* -appname: This P2PApp name ->* -peernode: Target node name ->* -dstip: Target service address, default local 127.0.0.1 ->* -dstport: Target service port, such as windows remote desktop 3389, Linux ssh 22 ->* -protocol: Target service protocol tcp, udp - -## Config file -Generally saved in the current directory, in installation mode it will be saved to `C:\Program Files\OpenP2P\config.json` or `/usr/local/openp2p/config.json` -If you want to modify the parameters, or configure multiple P2PApps, you can manually modify the configuration file - -Configuration example -``` -{ - "network": { - "Node": "YOUR-NODE-NAME", - "Token": "TOKEN", - "ShareBandwidth": 0, - "ServerHost": "api.openp2p.cn", - "ServerPort": 27183, - "UDPPort1": 27182, - "UDPPort2": 27183 - }, - "apps": [ - { - "AppName": "OfficeWindowsPC", - "Protocol": "tcp", - "SrcPort": 23389, - "PeerNode": "OFFICEPC1", - "DstPort": 3389, - "DstHost": "localhost", - }, - { - "AppName": "OfficeServerSSH", - "Protocol": "tcp", - "SrcPort": 22, - "PeerNode": "OFFICEPC1", - "DstPort": 22, - "DstHost": "192.168.1.5", - } - ] -} -``` -## Client update -``` -# update local client -./openp2p update -# update remote client -curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' -``` - -Windows system needs to set up firewall for this program, the program will automatically set the firewall, if the setting fails, the UDP punching will be affected. -The default firewall configuration of Linux system (Ubuntu and CentOS7) will not have any effect, if not, you can try to turn off the firewall -``` -systemctl stop firewalld.service -systemctl start firewalld.service -firewall-cmd --state -``` - -## Uninstall -``` -./openp2p uninstall -# when already installed -# windows -C:\Program Files\OpenP2P\openp2p.exe uninstall -# linux,macos -sudo /usr/local/openp2p/openp2p uninstall -``` - -## Run with Docker -``` -# Replace YOUR-TOKEN and YOUR-NODE-NAME with yours -docker run -d --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest -OR -docker run -d --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME + + +# Parameters details +In most cases, you can operate it through . In some cases it is necessary to run manually +> :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p" + + +## Install and Listen +``` +./openp2p install -node OFFICEPC1 -token TOKEN +Or +./openp2p -d -node OFFICEPC1 -token TOKEN + +``` +>* install: [recommand] will install as system service. So it will autorun when system booting. +>* -d: daemon mode run once. When the worker process is found to exit unexpectedly, a new worker process will be automatically started +>* -node: Unique node name, unique identification +>* -token: See "Profile" +>* -sharebandwidth: Provides bandwidth when used as a shared node, the default is 10mbps. If it is a large bandwidth of optical fiber, the larger the setting, the better the effect. 0 means not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other people’s shared nodes +>* -loglevel: Need to view more debug logs, set 0; the default is 1 + +### Run in Docker container +We don't provide official docker image yet, you can run it in any container +``` +nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & +# Since many docker images have been simplified, the install system service will fail, so the daemon mode is used to run in the background +``` + +## Connect +``` +./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 +Create multiple P2PApp by config file +./openp2p -d +``` +>* -appname: This P2PApp name +>* -peernode: Target node name +>* -dstip: Target service address, default local 127.0.0.1 +>* -dstport: Target service port, such as windows remote desktop 3389, Linux ssh 22 +>* -protocol: Target service protocol tcp, udp + +## Config file +Generally saved in the current directory, in installation mode it will be saved to `C:\Program Files\OpenP2P\config.json` or `/usr/local/openp2p/config.json` +If you want to modify the parameters, or configure multiple P2PApps, you can manually modify the configuration file + +Configuration example +``` +{ + "network": { + "Node": "YOUR-NODE-NAME", + "Token": "TOKEN", + "ShareBandwidth": 0, + "ServerHost": "api.openp2p.cn", + "ServerPort": 27183, + "UDPPort1": 27182, + "UDPPort2": 27183 + }, + "apps": [ + { + "AppName": "OfficeWindowsPC", + "Protocol": "tcp", + "SrcPort": 23389, + "PeerNode": "OFFICEPC1", + "DstPort": 3389, + "DstHost": "localhost", + }, + { + "AppName": "OfficeServerSSH", + "Protocol": "tcp", + "SrcPort": 22, + "PeerNode": "OFFICEPC1", + "DstPort": 22, + "DstHost": "192.168.1.5", + } + ] +} +``` +## Client update +``` +# update local client +./openp2p update +# update remote client +curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' +``` + +Windows system needs to set up firewall for this program, the program will automatically set the firewall, if the setting fails, the UDP punching will be affected. +The default firewall configuration of Linux system (Ubuntu and CentOS7) will not have any effect, if not, you can try to turn off the firewall +``` +systemctl stop firewalld.service +systemctl start firewalld.service +firewall-cmd --state +``` + +## Uninstall +``` +./openp2p uninstall +# when already installed +# windows +C:\Program Files\OpenP2P\openp2p.exe uninstall +# linux,macos +sudo /usr/local/openp2p/openp2p uninstall +``` + +## Run with Docker +``` +# Replace YOUR-TOKEN and YOUR-NODE-NAME with yours +docker run -d --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest +OR +docker run -d --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME ``` \ No newline at end of file diff --git a/cmd/openp2p.go b/cmd/openp2p.go index cfe18dc..e9eed29 100644 --- a/cmd/openp2p.go +++ b/cmd/openp2p.go @@ -1,9 +1,9 @@ -package main - -import ( - op "openp2p/core" -) - -func main() { - op.Run() -} +package main + +import ( + op "openp2p/core" +) + +func main() { + op.Run() +} diff --git a/core/daemon.go b/core/daemon.go index eff3b46..533fdc4 100644 --- a/core/daemon.go +++ b/core/daemon.go @@ -1,115 +1,115 @@ -package openp2p - -import ( - "fmt" - "os" - "path/filepath" - "time" - - "github.com/openp2p-cn/service" -) - -type daemon struct { - running bool - proc *os.Process -} - -func (d *daemon) Start(s service.Service) error { - gLog.Println(LvINFO, "daemon start") - return nil -} - -func (d *daemon) Stop(s service.Service) error { - gLog.Println(LvINFO, "service stop") - d.running = false - if d.proc != nil { - gLog.Println(LvINFO, "stop worker") - d.proc.Kill() - } - if service.Interactive() { - gLog.Println(LvINFO, "stop daemon") - os.Exit(0) - } - return nil -} - -func (d *daemon) run() { - gLog.Println(LvINFO, "daemon run start") - defer gLog.Println(LvINFO, "daemon run end") - d.running = true - binPath, _ := os.Executable() - mydir, err := os.Getwd() - if err != nil { - fmt.Println(err) - } - gLog.Println(LvINFO, mydir) - conf := &service.Config{ - Name: ProductName, - DisplayName: ProductName, - Description: ProductName, - Executable: binPath, - } - - s, _ := service.New(d, conf) - go s.Run() - var args []string - // rm -d parameter - for i := 0; i < len(os.Args); i++ { - if os.Args[i] == "-d" { - args = append(os.Args[0:i], os.Args[i+1:]...) - break - } - } - args = append(args, "-nv") - for { - // start worker - tmpDump := filepath.Join("log", "dump.log.tmp") - dumpFile := filepath.Join("log", "dump.log") - f, err := os.Create(filepath.Join(tmpDump)) - if err != nil { - gLog.Printf(LvERROR, "start worker error:%s", err) - return - } - gLog.Println(LvINFO, "start worker process, args:", args) - execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}} - p, err := os.StartProcess(binPath, args, execSpec) - if err != nil { - gLog.Printf(LvERROR, "start worker error:%s", err) - return - } - d.proc = p - _, _ = p.Wait() - f.Close() - time.Sleep(time.Second) - err = os.Rename(tmpDump, dumpFile) - if err != nil { - gLog.Printf(LvERROR, "rename dump error:%s", err) - } - if !d.running { - return - } - gLog.Printf(LvERROR, "worker stop, restart it after 10s") - time.Sleep(time.Second * 10) - } -} - -func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error { - svcConfig := &service.Config{ - Name: ProductName, - DisplayName: ProductName, - Description: ProductName, - Executable: exeAbsPath, - Arguments: args, - } - - s, e := service.New(d, svcConfig) - if e != nil { - return e - } - e = service.Control(s, ctrlComm) - if e != nil { - return e - } - - return nil -} +package openp2p + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/openp2p-cn/service" +) + +type daemon struct { + running bool + proc *os.Process +} + +func (d *daemon) Start(s service.Service) error { + gLog.Println(LvINFO, "daemon start") + return nil +} + +func (d *daemon) Stop(s service.Service) error { + gLog.Println(LvINFO, "service stop") + d.running = false + if d.proc != nil { + gLog.Println(LvINFO, "stop worker") + d.proc.Kill() + } + if service.Interactive() { + gLog.Println(LvINFO, "stop daemon") + os.Exit(0) + } + return nil +} + +func (d *daemon) run() { + gLog.Println(LvINFO, "daemon run start") + defer gLog.Println(LvINFO, "daemon run end") + d.running = true + binPath, _ := os.Executable() + mydir, err := os.Getwd() + if err != nil { + fmt.Println(err) + } + gLog.Println(LvINFO, mydir) + conf := &service.Config{ + Name: ProductName, + DisplayName: ProductName, + Description: ProductName, + Executable: binPath, + } + + s, _ := service.New(d, conf) + go s.Run() + var args []string + // rm -d parameter + for i := 0; i < len(os.Args); i++ { + if os.Args[i] == "-d" { + args = append(os.Args[0:i], os.Args[i+1:]...) + break + } + } + args = append(args, "-nv") + for { + // start worker + tmpDump := filepath.Join("log", "dump.log.tmp") + dumpFile := filepath.Join("log", "dump.log") + f, err := os.Create(filepath.Join(tmpDump)) + if err != nil { + gLog.Printf(LvERROR, "start worker error:%s", err) + return + } + gLog.Println(LvINFO, "start worker process, args:", args) + execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}} + p, err := os.StartProcess(binPath, args, execSpec) + if err != nil { + gLog.Printf(LvERROR, "start worker error:%s", err) + return + } + d.proc = p + _, _ = p.Wait() + f.Close() + time.Sleep(time.Second) + err = os.Rename(tmpDump, dumpFile) + if err != nil { + gLog.Printf(LvERROR, "rename dump error:%s", err) + } + if !d.running { + return + } + gLog.Printf(LvERROR, "worker stop, restart it after 10s") + time.Sleep(time.Second * 10) + } +} + +func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error { + svcConfig := &service.Config{ + Name: ProductName, + DisplayName: ProductName, + Description: ProductName, + Executable: exeAbsPath, + Arguments: args, + } + + s, e := service.New(d, svcConfig) + if e != nil { + return e + } + e = service.Control(s, ctrlComm) + if e != nil { + return e + } + + return nil +} diff --git a/core/handlepush.go b/core/handlepush.go index 0cddccc..8859dcd 100644 --- a/core/handlepush.go +++ b/core/handlepush.go @@ -1,482 +1,482 @@ -package openp2p - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "net" - "os" - "path/filepath" - "reflect" - "runtime" - "time" - - "github.com/openp2p-cn/totp" -) - -func handlePush(subType uint16, msg []byte) error { - pushHead := PushHeader{} - err := binary.Read(bytes.NewReader(msg[openP2PHeaderSize:openP2PHeaderSize+PushHeaderSize]), binary.LittleEndian, &pushHead) - if err != nil { - return err - } - gLog.Printf(LvDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead) - switch subType { - case MsgPushConnectReq: - err = handleConnectReq(msg) - case MsgPushRsp: - rsp := PushRsp{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { - gLog.Printf(LvERROR, "wrong pushRsp:%s", err) - return err - } - if rsp.Error == 0 { - gLog.Printf(LvDEBUG, "push ok, detail:%s", rsp.Detail) - } else { - gLog.Printf(LvERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail) - } - case MsgPushAddRelayTunnelReq: - req := AddRelayTunnelReq{} - if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - return err - } - config := AppConfig{} - config.PeerNode = req.RelayName - config.peerToken = req.RelayToken - config.relayMode = req.RelayMode - go func(r AddRelayTunnelReq) { - t, errDt := GNetwork.addDirectTunnel(config, 0) - if errDt == nil { - // notify peer relay ready - msg := TunnelMsg{ID: t.id} - GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, msg) - appConfig := config - appConfig.PeerNode = req.From - } else { - gLog.Printf(LvERROR, "addDirectTunnel error:%s", errDt) - GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, "error") // compatible with old version client, trigger unmarshal error - } - }(req) - case MsgPushServerSideSaveMemApp: - req := ServerSideSaveMemApp{} - if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - return err - } - gLog.Println(LvDEBUG, "handle MsgPushServerSideSaveMemApp:", prettyJson(req)) - var existTunnel *P2PTunnel - i, ok := GNetwork.allTunnels.Load(req.TunnelID) - if !ok { - time.Sleep(time.Millisecond * 100) - i, ok = GNetwork.allTunnels.Load(req.TunnelID) // retry sometimes will receive MsgPushServerSideSaveMemApp but p2ptunnel not store yet. - if !ok { - gLog.Println(LvERROR, "handle MsgPushServerSideSaveMemApp error:", ErrMemAppTunnelNotFound) - return ErrMemAppTunnelNotFound - } - } - existTunnel = i.(*P2PTunnel) - peerID := NodeNameToID(req.From) - existApp, appok := GNetwork.apps.Load(peerID) - if appok { - app := existApp.(*p2pApp) - app.config.AppName = fmt.Sprintf("%d", peerID) - app.id = req.AppID - app.setRelayTunnelID(req.RelayTunnelID) - app.relayMode = req.RelayMode - app.hbTimeRelay = time.Now() - if req.RelayTunnelID == 0 { - app.setDirectTunnel(existTunnel) - } else { - app.setRelayTunnel(existTunnel) - } - gLog.Println(LvDEBUG, "find existing memapp, update it") - } else { - appConfig := existTunnel.config - appConfig.SrcPort = 0 - appConfig.Protocol = "" - appConfig.AppName = fmt.Sprintf("%d", peerID) - appConfig.PeerNode = req.From - app := p2pApp{ - id: req.AppID, - config: appConfig, - relayMode: req.RelayMode, - running: true, - hbTimeRelay: time.Now(), - } - if req.RelayTunnelID == 0 { - app.setDirectTunnel(existTunnel) - } else { - app.setRelayTunnel(existTunnel) - app.setRelayTunnelID(req.RelayTunnelID) - } - if req.RelayTunnelID != 0 { - app.relayNode = req.Node - } - GNetwork.apps.Store(NodeNameToID(req.From), &app) - } - - return nil - case MsgPushAPPKey: - req := APPKeySync{} - if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - return err - } - SaveKey(req.AppID, req.AppKey) - case MsgPushUpdate: - gLog.Println(LvINFO, "MsgPushUpdate") - err := update(gConf.Network.ServerHost, gConf.Network.ServerPort) - if err == nil { - os.Exit(0) - } - return err - case MsgPushRestart: - gLog.Println(LvINFO, "MsgPushRestart") - os.Exit(0) - return err - case MsgPushReportApps: - err = handleReportApps() - case MsgPushReportMemApps: - err = handleReportMemApps() - case MsgPushReportLog: - err = handleLog(msg) - case MsgPushReportGoroutine: - err = handleReportGoroutine() - case MsgPushCheckRemoteService: - err = handleCheckRemoteService(msg) - case MsgPushEditApp: - err = handleEditApp(msg) - case MsgPushEditNode: - gLog.Println(LvINFO, "MsgPushEditNode") - req := EditNode{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) - return err - } - gConf.setNode(req.NewName) - gConf.setShareBandwidth(req.Bandwidth) - os.Exit(0) - case MsgPushSwitchApp: - gLog.Println(LvINFO, "MsgPushSwitchApp") - app := AppInfo{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &app); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:])) - return err - } - config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol} - gLog.Println(LvINFO, app.AppName, " switch to ", app.Enabled) - gConf.switchApp(config, app.Enabled) - if app.Enabled == 0 { - // disable APP - GNetwork.DeleteApp(config) - } - case MsgPushDstNodeOnline: - gLog.Println(LvINFO, "MsgPushDstNodeOnline") - req := PushDstNodeOnline{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) - return err - } - gLog.Println(LvINFO, "retry peerNode ", req.Node) - gConf.retryApp(req.Node) - default: - i, ok := GNetwork.msgMap.Load(pushHead.From) - if !ok { - return ErrMsgChannelNotFound - } - ch := i.(chan msgCtx) - ch <- msgCtx{data: msg, ts: time.Now()} - } - return err -} - -func handleEditApp(msg []byte) (err error) { - gLog.Println(LvINFO, "MsgPushEditApp") - newApp := AppInfo{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &newApp); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:])) - return err - } - oldConf := AppConfig{Enabled: 1} - // protocol0+srcPort0 exist, delApp - oldConf.AppName = newApp.AppName - oldConf.Protocol = newApp.Protocol0 - oldConf.Whitelist = newApp.Whitelist - oldConf.SrcPort = newApp.SrcPort0 - oldConf.PeerNode = newApp.PeerNode - oldConf.DstHost = newApp.DstHost - oldConf.DstPort = newApp.DstPort - if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit - gConf.delete(oldConf) - } - - // AddApp - newConf := oldConf - newConf.Protocol = newApp.Protocol - newConf.SrcPort = newApp.SrcPort - newConf.RelayNode = newApp.SpecRelayNode - newConf.PunchPriority = newApp.PunchPriority - gConf.add(newConf, false) - if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit - GNetwork.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end - } - return nil -} - -func handleConnectReq(msg []byte) (err error) { - req := PushConnectReq{} - if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - return err - } - gLog.Printf(LvDEBUG, "%s is connecting...", req.From) - gLog.Println(LvDEBUG, "push connect response to ", req.From) - if compareVersion(req.Version, LeastSupportVersion) < 0 { - gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From) - rsp := PushConnectRsp{ - Error: 10, - Detail: ErrVersionNotCompatible.Error(), - To: req.From, - From: gConf.Network.Node, - } - GNetwork.push(req.From, MsgPushConnectRsp, rsp) - return ErrVersionNotCompatible - } - // verify totp token or token - t := totp.TOTP{Step: totp.RelayTOTPStep} - if t.Verify(req.Token, gConf.Network.Token, time.Now().Unix()-GNetwork.dt/int64(time.Second)) { // localTs may behind, auto adjust ts - gLog.Printf(LvINFO, "Access Granted") - config := AppConfig{} - config.peerNatType = req.NatType - config.peerConeNatPort = req.ConeNatPort - config.peerIP = req.FromIP - config.PeerNode = req.From - config.peerVersion = req.Version - config.fromToken = req.Token - config.peerIPv6 = req.IPv6 - config.hasIPv4 = req.HasIPv4 - config.hasUPNPorNATPMP = req.HasUPNPorNATPMP - config.linkMode = req.LinkMode - config.isUnderlayServer = req.IsUnderlayServer - config.UnderlayProtocol = req.UnderlayProtocol - // share relay node will limit bandwidth - if req.Token != gConf.Network.Token { - gLog.Printf(LvINFO, "set share bandwidth %d mbps", gConf.Network.ShareBandwidth) - config.shareBandwidth = gConf.Network.ShareBandwidth - } - // go GNetwork.AddTunnel(config, req.ID) - go func() { - GNetwork.addDirectTunnel(config, req.ID) - }() - return nil - } - gLog.Println(LvERROR, "Access Denied:", req.From) - rsp := PushConnectRsp{ - Error: 1, - Detail: fmt.Sprintf("connect to %s error: Access Denied", gConf.Network.Node), - To: req.From, - From: gConf.Network.Node, - } - return GNetwork.push(req.From, MsgPushConnectRsp, rsp) -} - -func handleReportApps() (err error) { - gLog.Println(LvINFO, "MsgPushReportApps") - req := ReportApps{} - gConf.mtx.Lock() - defer gConf.mtx.Unlock() - - for _, config := range gConf.Apps { - appActive := 0 - relayNode := "" - specRelayNode := "" - relayMode := "" - linkMode := LinkModeUDPPunch - var connectTime string - var retryTime string - var app *p2pApp - i, ok := GNetwork.apps.Load(config.ID()) - if ok { - app = i.(*p2pApp) - if app.isActive() { - appActive = 1 - } - if app.config.SrcPort == 0 { // memapp - continue - } - specRelayNode = app.config.RelayNode - if !app.isDirect() { // TODO: should always report relay node for app edit - relayNode = app.relayNode - relayMode = app.relayMode - } - - if app.Tunnel() != nil { - linkMode = app.Tunnel().linkModeWeb - } - retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") - connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") - - } - appInfo := AppInfo{ - AppName: config.AppName, - Error: config.errMsg, - Protocol: config.Protocol, - PunchPriority: config.PunchPriority, - Whitelist: config.Whitelist, - SrcPort: config.SrcPort, - RelayNode: relayNode, - SpecRelayNode: specRelayNode, - RelayMode: relayMode, - LinkMode: linkMode, - PeerNode: config.PeerNode, - DstHost: config.DstHost, - DstPort: config.DstPort, - PeerUser: config.PeerUser, - PeerIP: config.peerIP, - PeerNatType: config.peerNatType, - RetryTime: retryTime, - ConnectTime: connectTime, - IsActive: appActive, - Enabled: config.Enabled, - } - req.Apps = append(req.Apps, appInfo) - } - return GNetwork.write(MsgReport, MsgReportApps, &req) - -} - -func handleReportMemApps() (err error) { - gLog.Println(LvINFO, "handleReportMemApps") - req := ReportApps{} - gConf.mtx.Lock() - defer gConf.mtx.Unlock() - GNetwork.sdwan.sysRoute.Range(func(key, value interface{}) bool { - node := value.(*sdwanNode) - appActive := 0 - relayMode := "" - var connectTime string - var retryTime string - - i, ok := GNetwork.apps.Load(node.id) - var app *p2pApp - if ok { - app = i.(*p2pApp) - if app.isActive() { - appActive = 1 - } - if !app.isDirect() { - relayMode = app.relayMode - } - retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") - connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") - } - appInfo := AppInfo{ - RelayMode: relayMode, - PeerNode: node.name, - IsActive: appActive, - Enabled: 1, - } - if app != nil { - appInfo.AppName = app.config.AppName - appInfo.Error = app.config.errMsg - appInfo.Protocol = app.config.Protocol - appInfo.Whitelist = app.config.Whitelist - appInfo.SrcPort = app.config.SrcPort - if !app.isDirect() { - appInfo.RelayNode = app.relayNode - } - - if app.Tunnel() != nil { - appInfo.LinkMode = app.Tunnel().linkModeWeb - } - appInfo.DstHost = app.config.DstHost - appInfo.DstPort = app.config.DstPort - appInfo.PeerUser = app.config.PeerUser - appInfo.PeerIP = app.config.peerIP - appInfo.PeerNatType = app.config.peerNatType - appInfo.RetryTime = retryTime - appInfo.ConnectTime = connectTime - } - req.Apps = append(req.Apps, appInfo) - return true - }) - gLog.Println(LvDEBUG, "handleReportMemApps res:", prettyJson(req)) - return GNetwork.write(MsgReport, MsgReportMemApps, &req) -} - -func handleLog(msg []byte) (err error) { - gLog.Println(LvDEBUG, "MsgPushReportLog") - const defaultLen = 1024 * 128 - const maxLen = 1024 * 1024 - req := ReportLogReq{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) - return err - } - if req.FileName == "" { - req.FileName = "openp2p.log" - } else { - req.FileName = sanitizeFileName(req.FileName) - } - f, err := os.Open(filepath.Join("log", req.FileName)) - if err != nil { - gLog.Println(LvERROR, "read log file error:", err) - return err - } - fi, err := f.Stat() - if err != nil { - return err - } - if req.Offset > fi.Size() { - req.Offset = fi.Size() - defaultLen - } - // verify input parameters - if req.Offset < 0 { - req.Offset = 0 - } - if req.Len <= 0 || req.Len > maxLen { - req.Len = defaultLen - } - - f.Seek(req.Offset, 0) - buff := make([]byte, req.Len) - readLength, err := f.Read(buff) - f.Close() - if err != nil { - gLog.Println(LvERROR, "read log content error:", err) - return err - } - rsp := ReportLogRsp{} - rsp.Content = string(buff[:readLength]) - rsp.FileName = req.FileName - rsp.Total = fi.Size() - rsp.Len = req.Len - return GNetwork.write(MsgReport, MsgPushReportLog, &rsp) -} - -func handleReportGoroutine() (err error) { - gLog.Println(LvDEBUG, "handleReportGoroutine") - buf := make([]byte, 1024*128) - stackLen := runtime.Stack(buf, true) - return GNetwork.write(MsgReport, MsgPushReportLog, string(buf[:stackLen])) -} - -func handleCheckRemoteService(msg []byte) (err error) { - gLog.Println(LvDEBUG, "handleCheckRemoteService") - req := CheckRemoteService{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) - return err - } - rsp := PushRsp{Error: 0} - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.Host, req.Port), time.Second*3) - if err != nil { - rsp.Error = 1 - rsp.Detail = ErrRemoteServiceUnable.Error() - } else { - conn.Close() - } - return GNetwork.write(MsgReport, MsgReportResponse, rsp) -} +package openp2p + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "net" + "os" + "path/filepath" + "reflect" + "runtime" + "time" + + "github.com/openp2p-cn/totp" +) + +func handlePush(subType uint16, msg []byte) error { + pushHead := PushHeader{} + err := binary.Read(bytes.NewReader(msg[openP2PHeaderSize:openP2PHeaderSize+PushHeaderSize]), binary.LittleEndian, &pushHead) + if err != nil { + return err + } + gLog.Printf(LvDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead) + switch subType { + case MsgPushConnectReq: + err = handleConnectReq(msg) + case MsgPushRsp: + rsp := PushRsp{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { + gLog.Printf(LvERROR, "wrong pushRsp:%s", err) + return err + } + if rsp.Error == 0 { + gLog.Printf(LvDEBUG, "push ok, detail:%s", rsp.Detail) + } else { + gLog.Printf(LvERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail) + } + case MsgPushAddRelayTunnelReq: + req := AddRelayTunnelReq{} + if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + return err + } + config := AppConfig{} + config.PeerNode = req.RelayName + config.peerToken = req.RelayToken + config.relayMode = req.RelayMode + go func(r AddRelayTunnelReq) { + t, errDt := GNetwork.addDirectTunnel(config, 0) + if errDt == nil { + // notify peer relay ready + msg := TunnelMsg{ID: t.id} + GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, msg) + appConfig := config + appConfig.PeerNode = req.From + } else { + gLog.Printf(LvERROR, "addDirectTunnel error:%s", errDt) + GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, "error") // compatible with old version client, trigger unmarshal error + } + }(req) + case MsgPushServerSideSaveMemApp: + req := ServerSideSaveMemApp{} + if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + return err + } + gLog.Println(LvDEBUG, "handle MsgPushServerSideSaveMemApp:", prettyJson(req)) + var existTunnel *P2PTunnel + i, ok := GNetwork.allTunnels.Load(req.TunnelID) + if !ok { + time.Sleep(time.Millisecond * 100) + i, ok = GNetwork.allTunnels.Load(req.TunnelID) // retry sometimes will receive MsgPushServerSideSaveMemApp but p2ptunnel not store yet. + if !ok { + gLog.Println(LvERROR, "handle MsgPushServerSideSaveMemApp error:", ErrMemAppTunnelNotFound) + return ErrMemAppTunnelNotFound + } + } + existTunnel = i.(*P2PTunnel) + peerID := NodeNameToID(req.From) + existApp, appok := GNetwork.apps.Load(peerID) + if appok { + app := existApp.(*p2pApp) + app.config.AppName = fmt.Sprintf("%d", peerID) + app.id = req.AppID + app.setRelayTunnelID(req.RelayTunnelID) + app.relayMode = req.RelayMode + app.hbTimeRelay = time.Now() + if req.RelayTunnelID == 0 { + app.setDirectTunnel(existTunnel) + } else { + app.setRelayTunnel(existTunnel) + } + gLog.Println(LvDEBUG, "find existing memapp, update it") + } else { + appConfig := existTunnel.config + appConfig.SrcPort = 0 + appConfig.Protocol = "" + appConfig.AppName = fmt.Sprintf("%d", peerID) + appConfig.PeerNode = req.From + app := p2pApp{ + id: req.AppID, + config: appConfig, + relayMode: req.RelayMode, + running: true, + hbTimeRelay: time.Now(), + } + if req.RelayTunnelID == 0 { + app.setDirectTunnel(existTunnel) + } else { + app.setRelayTunnel(existTunnel) + app.setRelayTunnelID(req.RelayTunnelID) + } + if req.RelayTunnelID != 0 { + app.relayNode = req.Node + } + GNetwork.apps.Store(NodeNameToID(req.From), &app) + } + + return nil + case MsgPushAPPKey: + req := APPKeySync{} + if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + return err + } + SaveKey(req.AppID, req.AppKey) + case MsgPushUpdate: + gLog.Println(LvINFO, "MsgPushUpdate") + err := update(gConf.Network.ServerHost, gConf.Network.ServerPort) + if err == nil { + os.Exit(0) + } + return err + case MsgPushRestart: + gLog.Println(LvINFO, "MsgPushRestart") + os.Exit(0) + return err + case MsgPushReportApps: + err = handleReportApps() + case MsgPushReportMemApps: + err = handleReportMemApps() + case MsgPushReportLog: + err = handleLog(msg) + case MsgPushReportGoroutine: + err = handleReportGoroutine() + case MsgPushCheckRemoteService: + err = handleCheckRemoteService(msg) + case MsgPushEditApp: + err = handleEditApp(msg) + case MsgPushEditNode: + gLog.Println(LvINFO, "MsgPushEditNode") + req := EditNode{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) + return err + } + gConf.setNode(req.NewName) + gConf.setShareBandwidth(req.Bandwidth) + os.Exit(0) + case MsgPushSwitchApp: + gLog.Println(LvINFO, "MsgPushSwitchApp") + app := AppInfo{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &app); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:])) + return err + } + config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol} + gLog.Println(LvINFO, app.AppName, " switch to ", app.Enabled) + gConf.switchApp(config, app.Enabled) + if app.Enabled == 0 { + // disable APP + GNetwork.DeleteApp(config) + } + case MsgPushDstNodeOnline: + gLog.Println(LvINFO, "MsgPushDstNodeOnline") + req := PushDstNodeOnline{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) + return err + } + gLog.Println(LvINFO, "retry peerNode ", req.Node) + gConf.retryApp(req.Node) + default: + i, ok := GNetwork.msgMap.Load(pushHead.From) + if !ok { + return ErrMsgChannelNotFound + } + ch := i.(chan msgCtx) + ch <- msgCtx{data: msg, ts: time.Now()} + } + return err +} + +func handleEditApp(msg []byte) (err error) { + gLog.Println(LvINFO, "MsgPushEditApp") + newApp := AppInfo{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &newApp); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:])) + return err + } + oldConf := AppConfig{Enabled: 1} + // protocol0+srcPort0 exist, delApp + oldConf.AppName = newApp.AppName + oldConf.Protocol = newApp.Protocol0 + oldConf.Whitelist = newApp.Whitelist + oldConf.SrcPort = newApp.SrcPort0 + oldConf.PeerNode = newApp.PeerNode + oldConf.DstHost = newApp.DstHost + oldConf.DstPort = newApp.DstPort + if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit + gConf.delete(oldConf) + } + + // AddApp + newConf := oldConf + newConf.Protocol = newApp.Protocol + newConf.SrcPort = newApp.SrcPort + newConf.RelayNode = newApp.SpecRelayNode + newConf.PunchPriority = newApp.PunchPriority + gConf.add(newConf, false) + if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit + GNetwork.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end + } + return nil +} + +func handleConnectReq(msg []byte) (err error) { + req := PushConnectReq{} + if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + return err + } + gLog.Printf(LvDEBUG, "%s is connecting...", req.From) + gLog.Println(LvDEBUG, "push connect response to ", req.From) + if compareVersion(req.Version, LeastSupportVersion) < 0 { + gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From) + rsp := PushConnectRsp{ + Error: 10, + Detail: ErrVersionNotCompatible.Error(), + To: req.From, + From: gConf.Network.Node, + } + GNetwork.push(req.From, MsgPushConnectRsp, rsp) + return ErrVersionNotCompatible + } + // verify totp token or token + t := totp.TOTP{Step: totp.RelayTOTPStep} + if t.Verify(req.Token, gConf.Network.Token, time.Now().Unix()-GNetwork.dt/int64(time.Second)) { // localTs may behind, auto adjust ts + gLog.Printf(LvINFO, "Access Granted") + config := AppConfig{} + config.peerNatType = req.NatType + config.peerConeNatPort = req.ConeNatPort + config.peerIP = req.FromIP + config.PeerNode = req.From + config.peerVersion = req.Version + config.fromToken = req.Token + config.peerIPv6 = req.IPv6 + config.hasIPv4 = req.HasIPv4 + config.hasUPNPorNATPMP = req.HasUPNPorNATPMP + config.linkMode = req.LinkMode + config.isUnderlayServer = req.IsUnderlayServer + config.UnderlayProtocol = req.UnderlayProtocol + // share relay node will limit bandwidth + if req.Token != gConf.Network.Token { + gLog.Printf(LvINFO, "set share bandwidth %d mbps", gConf.Network.ShareBandwidth) + config.shareBandwidth = gConf.Network.ShareBandwidth + } + // go GNetwork.AddTunnel(config, req.ID) + go func() { + GNetwork.addDirectTunnel(config, req.ID) + }() + return nil + } + gLog.Println(LvERROR, "Access Denied:", req.From) + rsp := PushConnectRsp{ + Error: 1, + Detail: fmt.Sprintf("connect to %s error: Access Denied", gConf.Network.Node), + To: req.From, + From: gConf.Network.Node, + } + return GNetwork.push(req.From, MsgPushConnectRsp, rsp) +} + +func handleReportApps() (err error) { + gLog.Println(LvINFO, "MsgPushReportApps") + req := ReportApps{} + gConf.mtx.Lock() + defer gConf.mtx.Unlock() + + for _, config := range gConf.Apps { + appActive := 0 + relayNode := "" + specRelayNode := "" + relayMode := "" + linkMode := LinkModeUDPPunch + var connectTime string + var retryTime string + var app *p2pApp + i, ok := GNetwork.apps.Load(config.ID()) + if ok { + app = i.(*p2pApp) + if app.isActive() { + appActive = 1 + } + if app.config.SrcPort == 0 { // memapp + continue + } + specRelayNode = app.config.RelayNode + if !app.isDirect() { // TODO: should always report relay node for app edit + relayNode = app.relayNode + relayMode = app.relayMode + } + + if app.Tunnel() != nil { + linkMode = app.Tunnel().linkModeWeb + } + retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") + connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") + + } + appInfo := AppInfo{ + AppName: config.AppName, + Error: config.errMsg, + Protocol: config.Protocol, + PunchPriority: config.PunchPriority, + Whitelist: config.Whitelist, + SrcPort: config.SrcPort, + RelayNode: relayNode, + SpecRelayNode: specRelayNode, + RelayMode: relayMode, + LinkMode: linkMode, + PeerNode: config.PeerNode, + DstHost: config.DstHost, + DstPort: config.DstPort, + PeerUser: config.PeerUser, + PeerIP: config.peerIP, + PeerNatType: config.peerNatType, + RetryTime: retryTime, + ConnectTime: connectTime, + IsActive: appActive, + Enabled: config.Enabled, + } + req.Apps = append(req.Apps, appInfo) + } + return GNetwork.write(MsgReport, MsgReportApps, &req) + +} + +func handleReportMemApps() (err error) { + gLog.Println(LvINFO, "handleReportMemApps") + req := ReportApps{} + gConf.mtx.Lock() + defer gConf.mtx.Unlock() + GNetwork.sdwan.sysRoute.Range(func(key, value interface{}) bool { + node := value.(*sdwanNode) + appActive := 0 + relayMode := "" + var connectTime string + var retryTime string + + i, ok := GNetwork.apps.Load(node.id) + var app *p2pApp + if ok { + app = i.(*p2pApp) + if app.isActive() { + appActive = 1 + } + if !app.isDirect() { + relayMode = app.relayMode + } + retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") + connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") + } + appInfo := AppInfo{ + RelayMode: relayMode, + PeerNode: node.name, + IsActive: appActive, + Enabled: 1, + } + if app != nil { + appInfo.AppName = app.config.AppName + appInfo.Error = app.config.errMsg + appInfo.Protocol = app.config.Protocol + appInfo.Whitelist = app.config.Whitelist + appInfo.SrcPort = app.config.SrcPort + if !app.isDirect() { + appInfo.RelayNode = app.relayNode + } + + if app.Tunnel() != nil { + appInfo.LinkMode = app.Tunnel().linkModeWeb + } + appInfo.DstHost = app.config.DstHost + appInfo.DstPort = app.config.DstPort + appInfo.PeerUser = app.config.PeerUser + appInfo.PeerIP = app.config.peerIP + appInfo.PeerNatType = app.config.peerNatType + appInfo.RetryTime = retryTime + appInfo.ConnectTime = connectTime + } + req.Apps = append(req.Apps, appInfo) + return true + }) + gLog.Println(LvDEBUG, "handleReportMemApps res:", prettyJson(req)) + return GNetwork.write(MsgReport, MsgReportMemApps, &req) +} + +func handleLog(msg []byte) (err error) { + gLog.Println(LvDEBUG, "MsgPushReportLog") + const defaultLen = 1024 * 128 + const maxLen = 1024 * 1024 + req := ReportLogReq{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) + return err + } + if req.FileName == "" { + req.FileName = "openp2p.log" + } else { + req.FileName = sanitizeFileName(req.FileName) + } + f, err := os.Open(filepath.Join("log", req.FileName)) + if err != nil { + gLog.Println(LvERROR, "read log file error:", err) + return err + } + fi, err := f.Stat() + if err != nil { + return err + } + if req.Offset > fi.Size() { + req.Offset = fi.Size() - defaultLen + } + // verify input parameters + if req.Offset < 0 { + req.Offset = 0 + } + if req.Len <= 0 || req.Len > maxLen { + req.Len = defaultLen + } + + f.Seek(req.Offset, 0) + buff := make([]byte, req.Len) + readLength, err := f.Read(buff) + f.Close() + if err != nil { + gLog.Println(LvERROR, "read log content error:", err) + return err + } + rsp := ReportLogRsp{} + rsp.Content = string(buff[:readLength]) + rsp.FileName = req.FileName + rsp.Total = fi.Size() + rsp.Len = req.Len + return GNetwork.write(MsgReport, MsgPushReportLog, &rsp) +} + +func handleReportGoroutine() (err error) { + gLog.Println(LvDEBUG, "handleReportGoroutine") + buf := make([]byte, 1024*128) + stackLen := runtime.Stack(buf, true) + return GNetwork.write(MsgReport, MsgPushReportLog, string(buf[:stackLen])) +} + +func handleCheckRemoteService(msg []byte) (err error) { + gLog.Println(LvDEBUG, "handleCheckRemoteService") + req := CheckRemoteService{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) + return err + } + rsp := PushRsp{Error: 0} + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.Host, req.Port), time.Second*3) + if err != nil { + rsp.Error = 1 + rsp.Detail = ErrRemoteServiceUnable.Error() + } else { + conn.Close() + } + return GNetwork.write(MsgReport, MsgReportResponse, rsp) +} diff --git a/core/install.go b/core/install.go index 5ca8286..5a3f035 100644 --- a/core/install.go +++ b/core/install.go @@ -1,124 +1,124 @@ -package openp2p - -import ( - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "strings" - "time" -) - -func install() { - gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") - gLog.Println(LvINFO, "install start") - defer gLog.Println(LvINFO, "install end") - // auto uninstall - err := os.MkdirAll(defaultInstallPath, 0775) - - if err != nil { - gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err) - return - } - err = os.Chdir(defaultInstallPath) - if err != nil { - gLog.Println(LvERROR, "cd error:", err) - return - } - - uninstall() - // save config file - parseParams("install", "") - targetPath := filepath.Join(defaultInstallPath, defaultBinName) - d := daemon{} - // copy files - - binPath, _ := os.Executable() - src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe) - if errFiles != nil { - gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles) - return - } - - dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) - if errFiles != nil { - gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles) - return - } - - _, errFiles = io.Copy(dst, src) - if errFiles != nil { - gLog.Printf(LvERROR, "io.Copy error:%s", errFiles) - return - } - src.Close() - dst.Close() - - // install system service - gLog.Println(LvINFO, "targetPath:", targetPath) - err = d.Control("install", targetPath, []string{"-d"}) - if err == nil { - gLog.Println(LvINFO, "install system service ok.") - } - time.Sleep(time.Second * 2) - err = d.Control("start", targetPath, []string{"-d"}) - if err != nil { - gLog.Println(LvERROR, "start openp2p service error:", err) - } else { - gLog.Println(LvINFO, "start openp2p service ok.") - } - gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") -} - -func installByFilename() { - params := strings.Split(filepath.Base(os.Args[0]), "-") - if len(params) < 4 { - return - } - serverHost := params[1] - token := params[2] - gLog.Println(LvINFO, "install start") - targetPath := os.Args[0] - args := []string{"install"} - args = append(args, "-serverhost") - args = append(args, serverHost) - args = append(args, "-token") - args = append(args, token) - env := os.Environ() - cmd := exec.Command(targetPath, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Stdin = os.Stdin - cmd.Env = env - err := cmd.Run() - if err != nil { - gLog.Println(LvERROR, "install by filename, start process error:", err) - return - } - gLog.Println(LvINFO, "install end") - gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") - fmt.Println("Press the Any Key to exit") - fmt.Scanln() - os.Exit(0) -} -func uninstall() { - gLog.Println(LvINFO, "uninstall start") - defer gLog.Println(LvINFO, "uninstall end") - d := daemon{} - err := d.Control("stop", "", nil) - if err != nil { // service maybe not install - return - } - err = d.Control("uninstall", "", nil) - if err != nil { - gLog.Println(LvERROR, "uninstall system service error:", err) - } else { - gLog.Println(LvINFO, "uninstall system service ok.") - } - binPath := filepath.Join(defaultInstallPath, defaultBinName) - os.Remove(binPath + "0") - os.Remove(binPath) - // os.RemoveAll(defaultInstallPath) // reserve config.json -} +package openp2p + +import ( + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +func install() { + gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) + gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") + gLog.Println(LvINFO, "install start") + defer gLog.Println(LvINFO, "install end") + // auto uninstall + err := os.MkdirAll(defaultInstallPath, 0775) + + if err != nil { + gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err) + return + } + err = os.Chdir(defaultInstallPath) + if err != nil { + gLog.Println(LvERROR, "cd error:", err) + return + } + + uninstall() + // save config file + parseParams("install", "") + targetPath := filepath.Join(defaultInstallPath, defaultBinName) + d := daemon{} + // copy files + + binPath, _ := os.Executable() + src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe) + if errFiles != nil { + gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles) + return + } + + dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) + if errFiles != nil { + gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles) + return + } + + _, errFiles = io.Copy(dst, src) + if errFiles != nil { + gLog.Printf(LvERROR, "io.Copy error:%s", errFiles) + return + } + src.Close() + dst.Close() + + // install system service + gLog.Println(LvINFO, "targetPath:", targetPath) + err = d.Control("install", targetPath, []string{"-d"}) + if err == nil { + gLog.Println(LvINFO, "install system service ok.") + } + time.Sleep(time.Second * 2) + err = d.Control("start", targetPath, []string{"-d"}) + if err != nil { + gLog.Println(LvERROR, "start openp2p service error:", err) + } else { + gLog.Println(LvINFO, "start openp2p service ok.") + } + gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") +} + +func installByFilename() { + params := strings.Split(filepath.Base(os.Args[0]), "-") + if len(params) < 4 { + return + } + serverHost := params[1] + token := params[2] + gLog.Println(LvINFO, "install start") + targetPath := os.Args[0] + args := []string{"install"} + args = append(args, "-serverhost") + args = append(args, serverHost) + args = append(args, "-token") + args = append(args, token) + env := os.Environ() + cmd := exec.Command(targetPath, args...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Env = env + err := cmd.Run() + if err != nil { + gLog.Println(LvERROR, "install by filename, start process error:", err) + return + } + gLog.Println(LvINFO, "install end") + gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") + fmt.Println("Press the Any Key to exit") + fmt.Scanln() + os.Exit(0) +} +func uninstall() { + gLog.Println(LvINFO, "uninstall start") + defer gLog.Println(LvINFO, "uninstall end") + d := daemon{} + err := d.Control("stop", "", nil) + if err != nil { // service maybe not install + return + } + err = d.Control("uninstall", "", nil) + if err != nil { + gLog.Println(LvERROR, "uninstall system service error:", err) + } else { + gLog.Println(LvINFO, "uninstall system service ok.") + } + binPath := filepath.Join(defaultInstallPath, defaultBinName) + os.Remove(binPath + "0") + os.Remove(binPath) + // os.RemoveAll(defaultInstallPath) // reserve config.json +} diff --git a/core/iptables.go b/core/iptables.go index 3ecd692..931cd67 100644 --- a/core/iptables.go +++ b/core/iptables.go @@ -1,74 +1,74 @@ -package openp2p - -import ( - "log" - "os/exec" - "runtime" -) - -func allowTunForward() { - if runtime.GOOS != "linux" { // only support Linux - return - } - exec.Command("sh", "-c", `iptables -t filter -D FORWARD -i optun -j ACCEPT`).Run() - exec.Command("sh", "-c", `iptables -t filter -D FORWARD -o optun -j ACCEPT`).Run() - err := exec.Command("sh", "-c", `iptables -t filter -I FORWARD -i optun -j ACCEPT`).Run() - if err != nil { - log.Println("allow foward in error:", err) - } - err = exec.Command("sh", "-c", `iptables -t filter -I FORWARD -o optun -j ACCEPT`).Run() - if err != nil { - log.Println("allow foward out error:", err) - } -} - -func clearSNATRule() { - if runtime.GOOS != "linux" { - return - } - execCommand("iptables", true, "-t", "nat", "-D", "POSTROUTING", "-j", "OPSDWAN") - execCommand("iptables", true, "-t", "nat", "-F", "OPSDWAN") - execCommand("iptables", true, "-t", "nat", "-X", "OPSDWAN") -} - -func initSNATRule(localNet string) { - if runtime.GOOS != "linux" { - return - } - clearSNATRule() - - err := execCommand("iptables", true, "-t", "nat", "-N", "OPSDWAN") - if err != nil { - log.Println("iptables new sdwan chain error:", err) - return - } - err = execCommand("iptables", true, "-t", "nat", "-A", "POSTROUTING", "-j", "OPSDWAN") - if err != nil { - log.Println("iptables append postrouting error:", err) - return - } - err = execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", - "-o", "optun", "!", "-s", localNet, "-j", "MASQUERADE") - if err != nil { - log.Println("add optun snat error:", err) - return - } - err = execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", "!", "-o", "optun", - "-s", localNet, "-j", "MASQUERADE") - if err != nil { - log.Println("add optun snat error:", err) - return - } -} - -func addSNATRule(target string) { - if runtime.GOOS != "linux" { - return - } - err := execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", "!", "-o", "optun", - "-s", target, "-j", "MASQUERADE") - if err != nil { - log.Println("iptables add optun snat error:", err) - return - } -} +package openp2p + +import ( + "log" + "os/exec" + "runtime" +) + +func allowTunForward() { + if runtime.GOOS != "linux" { // only support Linux + return + } + exec.Command("sh", "-c", `iptables -t filter -D FORWARD -i optun -j ACCEPT`).Run() + exec.Command("sh", "-c", `iptables -t filter -D FORWARD -o optun -j ACCEPT`).Run() + err := exec.Command("sh", "-c", `iptables -t filter -I FORWARD -i optun -j ACCEPT`).Run() + if err != nil { + log.Println("allow foward in error:", err) + } + err = exec.Command("sh", "-c", `iptables -t filter -I FORWARD -o optun -j ACCEPT`).Run() + if err != nil { + log.Println("allow foward out error:", err) + } +} + +func clearSNATRule() { + if runtime.GOOS != "linux" { + return + } + execCommand("iptables", true, "-t", "nat", "-D", "POSTROUTING", "-j", "OPSDWAN") + execCommand("iptables", true, "-t", "nat", "-F", "OPSDWAN") + execCommand("iptables", true, "-t", "nat", "-X", "OPSDWAN") +} + +func initSNATRule(localNet string) { + if runtime.GOOS != "linux" { + return + } + clearSNATRule() + + err := execCommand("iptables", true, "-t", "nat", "-N", "OPSDWAN") + if err != nil { + log.Println("iptables new sdwan chain error:", err) + return + } + err = execCommand("iptables", true, "-t", "nat", "-A", "POSTROUTING", "-j", "OPSDWAN") + if err != nil { + log.Println("iptables append postrouting error:", err) + return + } + err = execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", + "-o", "optun", "!", "-s", localNet, "-j", "MASQUERADE") + if err != nil { + log.Println("add optun snat error:", err) + return + } + err = execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", "!", "-o", "optun", + "-s", localNet, "-j", "MASQUERADE") + if err != nil { + log.Println("add optun snat error:", err) + return + } +} + +func addSNATRule(target string) { + if runtime.GOOS != "linux" { + return + } + err := execCommand("iptables", true, "-t", "nat", "-A", "OPSDWAN", "!", "-o", "optun", + "-s", target, "-j", "MASQUERADE") + if err != nil { + log.Println("iptables add optun snat error:", err) + return + } +} diff --git a/core/nat.go b/core/nat.go index a528767..2a4e3de 100644 --- a/core/nat.go +++ b/core/nat.go @@ -1,189 +1,189 @@ -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, 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{} - 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() - go func() { - // close outside for breaking the ReadFromUDP - // wait 30s for echo testing - buf := make([]byte, 1600) - echoConn.SetReadDeadline(time.Now().Add(time.Second * 30)) - n, addr, err := echoConn.ReadFromUDP(buf) - if err != nil { - return - } - echoConn.WriteToUDP(buf[0:n], addr) - gLog.Println(LvDEBUG, "echo server end") - }() - // 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", 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 -} +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, 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{} + 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() + go func() { + // close outside for breaking the ReadFromUDP + // wait 30s for echo testing + buf := make([]byte, 1600) + echoConn.SetReadDeadline(time.Now().Add(time.Second * 30)) + n, addr, err := echoConn.ReadFromUDP(buf) + if err != nil { + return + } + echoConn.WriteToUDP(buf[0:n], addr) + gLog.Println(LvDEBUG, "echo server end") + }() + // 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", 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 +} diff --git a/core/openp2p.go b/core/openp2p.go index e9326a0..b83ebb6 100644 --- a/core/openp2p.go +++ b/core/openp2p.go @@ -1,121 +1,121 @@ -package openp2p - -import ( - "fmt" - "math/rand" - "os" - "path/filepath" - "strconv" - "time" -) - -var GNetwork *P2PNetwork - -func Run() { - rand.Seed(time.Now().UnixNano()) - baseDir := filepath.Dir(os.Args[0]) - os.Chdir(baseDir) // for system service - gLog = NewLogger(baseDir, ProductName, LvDEBUG, 1024*1024, LogFile|LogConsole) - if len(os.Args) > 1 { - switch os.Args[1] { - case "version", "-v", "--version": - fmt.Println(OpenP2PVersion) - return - case "install": - install() - return - case "uninstall": - uninstall() - return - } - } else { - installByFilename() - } - parseParams("", "") - gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") - - if gConf.daemonMode { - d := daemon{} - d.run() - return - } - - gLog.Println(LvINFO, &gConf) - setFirewall() - err := setRLimit() - if err != nil { - gLog.Println(LvINFO, "setRLimit error:", err) - } - GNetwork = P2PNetworkInstance() - if ok := GNetwork.Connect(30000); !ok { - gLog.Println(LvERROR, "P2PNetwork login error") - return - } - // gLog.Println(LvINFO, "waiting for connection...") - forever := make(chan bool) - <-forever -} - -// for Android app -// gomobile not support uint64 exported to java - -func RunAsModule(baseDir string, token string, bw int, logLevel int) *P2PNetwork { - rand.Seed(time.Now().UnixNano()) - os.Chdir(baseDir) // for system service - gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole) - - parseParams("", "") - - n, err := strconv.ParseUint(token, 10, 64) - if err == nil && n > 0 { - gConf.setToken(n) - } - if n <= 0 && gConf.Network.Token == 0 { // not input token - return nil - } - // gLog.setLevel(LogLevel(logLevel)) - gConf.setShareBandwidth(bw) - gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) - gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") - gLog.Println(LvINFO, &gConf) - - GNetwork = P2PNetworkInstance() - if ok := GNetwork.Connect(30000); !ok { - gLog.Println(LvERROR, "P2PNetwork login error") - return nil - } - // gLog.Println(LvINFO, "waiting for connection...") - return GNetwork -} - -func RunCmd(cmd string) { - rand.Seed(time.Now().UnixNano()) - baseDir := filepath.Dir(os.Args[0]) - os.Chdir(baseDir) // for system service - gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole) - - parseParams("", cmd) - setFirewall() - err := setRLimit() - if err != nil { - gLog.Println(LvINFO, "setRLimit error:", err) - } - GNetwork = P2PNetworkInstance() - if ok := GNetwork.Connect(30000); !ok { - gLog.Println(LvERROR, "P2PNetwork login error") - return - } - forever := make(chan bool) - <-forever -} - -func GetToken(baseDir string) string { - os.Chdir(baseDir) - gConf.load() - return fmt.Sprintf("%d", gConf.Network.Token) -} - -func Stop() { - os.Exit(0) -} +package openp2p + +import ( + "fmt" + "math/rand" + "os" + "path/filepath" + "strconv" + "time" +) + +var GNetwork *P2PNetwork + +func Run() { + rand.Seed(time.Now().UnixNano()) + baseDir := filepath.Dir(os.Args[0]) + os.Chdir(baseDir) // for system service + gLog = NewLogger(baseDir, ProductName, LvDEBUG, 1024*1024, LogFile|LogConsole) + if len(os.Args) > 1 { + switch os.Args[1] { + case "version", "-v", "--version": + fmt.Println(OpenP2PVersion) + return + case "install": + install() + return + case "uninstall": + uninstall() + return + } + } else { + installByFilename() + } + parseParams("", "") + gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) + gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") + + if gConf.daemonMode { + d := daemon{} + d.run() + return + } + + gLog.Println(LvINFO, &gConf) + setFirewall() + err := setRLimit() + if err != nil { + gLog.Println(LvINFO, "setRLimit error:", err) + } + GNetwork = P2PNetworkInstance() + if ok := GNetwork.Connect(30000); !ok { + gLog.Println(LvERROR, "P2PNetwork login error") + return + } + // gLog.Println(LvINFO, "waiting for connection...") + forever := make(chan bool) + <-forever +} + +// for Android app +// gomobile not support uint64 exported to java + +func RunAsModule(baseDir string, token string, bw int, logLevel int) *P2PNetwork { + rand.Seed(time.Now().UnixNano()) + os.Chdir(baseDir) // for system service + gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole) + + parseParams("", "") + + n, err := strconv.ParseUint(token, 10, 64) + if err == nil && n > 0 { + gConf.setToken(n) + } + if n <= 0 && gConf.Network.Token == 0 { // not input token + return nil + } + // gLog.setLevel(LogLevel(logLevel)) + gConf.setShareBandwidth(bw) + gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) + gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com") + gLog.Println(LvINFO, &gConf) + + GNetwork = P2PNetworkInstance() + if ok := GNetwork.Connect(30000); !ok { + gLog.Println(LvERROR, "P2PNetwork login error") + return nil + } + // gLog.Println(LvINFO, "waiting for connection...") + return GNetwork +} + +func RunCmd(cmd string) { + rand.Seed(time.Now().UnixNano()) + baseDir := filepath.Dir(os.Args[0]) + os.Chdir(baseDir) // for system service + gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole) + + parseParams("", cmd) + setFirewall() + err := setRLimit() + if err != nil { + gLog.Println(LvINFO, "setRLimit error:", err) + } + GNetwork = P2PNetworkInstance() + if ok := GNetwork.Connect(30000); !ok { + gLog.Println(LvERROR, "P2PNetwork login error") + return + } + forever := make(chan bool) + <-forever +} + +func GetToken(baseDir string) string { + os.Chdir(baseDir) + gConf.load() + return fmt.Sprintf("%d", gConf.Network.Token) +} + +func Stop() { + os.Exit(0) +} diff --git a/core/optun.go b/core/optun.go index d696269..231ff9a 100644 --- a/core/optun.go +++ b/core/optun.go @@ -1,20 +1,20 @@ -package openp2p - -import ( - "github.com/openp2p-cn/wireguard-go/tun" -) - -var AndroidSDWANConfig chan []byte - -type optun struct { - tunName string - dev tun.Device -} - -func (t *optun) Stop() error { - t.dev.Close() - return nil -} -func init() { - AndroidSDWANConfig = make(chan []byte, 1) -} +package openp2p + +import ( + "github.com/openp2p-cn/wireguard-go/tun" +) + +var AndroidSDWANConfig chan []byte + +type optun struct { + tunName string + dev tun.Device +} + +func (t *optun) Stop() error { + t.dev.Close() + return nil +} +func init() { + AndroidSDWANConfig = make(chan []byte, 1) +} diff --git a/core/optun_android.go b/core/optun_android.go index 8fb51c8..6f90666 100644 --- a/core/optun_android.go +++ b/core/optun_android.go @@ -1,85 +1,85 @@ -// optun_android.go -//go:build android -// +build android - -package openp2p - -import ( - "net" -) - -const ( - tunIfaceName = "optun" - PIHeaderSize = 0 -) - -var AndroidReadTun chan []byte // TODO: multi channel -var AndroidWriteTun chan []byte - -func (t *optun) Start(localAddr string, detail *SDWANInfo) error { - - return nil -} - -func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { - bufs[0] = <-AndroidReadTun - sizes[0] = len(bufs[0]) - return 1, nil -} - -func (t *optun) Write(bufs [][]byte, offset int) (int, error) { - AndroidWriteTun <- bufs[0] - return len(bufs[0]), nil -} - -func AndroidRead(data []byte, len int) { - head := PacketHeader{} - parseHeader(data, &head) - gLog.Printf(LvDev, "AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len) - buf := make([]byte, len) - copy(buf, data) - AndroidReadTun <- buf -} - -func AndroidWrite(buf []byte) int { - p := <-AndroidWriteTun - copy(buf, p) - return len(p) -} - -func GetAndroidSDWANConfig(buf []byte) int { - p := <-AndroidSDWANConfig - copy(buf, p) - gLog.Printf(LvINFO, "AndroidSDWANConfig=%s", p) - return len(p) -} - -func GetAndroidNodeName() string { - gLog.Printf(LvINFO, "GetAndroidNodeName=%s", gConf.Network.Node) - return gConf.Network.Node -} - -func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { - // TODO: - return nil -} - -func addRoute(dst, gw, ifname string) error { - // TODO: - return nil -} - -func delRoute(dst, gw string) error { - // TODO: - return nil -} - -func delRoutesByGateway(gateway string) error { - // TODO: - return nil -} - -func init() { - AndroidReadTun = make(chan []byte, 1000) - AndroidWriteTun = make(chan []byte, 1000) -} +// optun_android.go +//go:build android +// +build android + +package openp2p + +import ( + "net" +) + +const ( + tunIfaceName = "optun" + PIHeaderSize = 0 +) + +var AndroidReadTun chan []byte // TODO: multi channel +var AndroidWriteTun chan []byte + +func (t *optun) Start(localAddr string, detail *SDWANInfo) error { + + return nil +} + +func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + bufs[0] = <-AndroidReadTun + sizes[0] = len(bufs[0]) + return 1, nil +} + +func (t *optun) Write(bufs [][]byte, offset int) (int, error) { + AndroidWriteTun <- bufs[0] + return len(bufs[0]), nil +} + +func AndroidRead(data []byte, len int) { + head := PacketHeader{} + parseHeader(data, &head) + gLog.Printf(LvDev, "AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len) + buf := make([]byte, len) + copy(buf, data) + AndroidReadTun <- buf +} + +func AndroidWrite(buf []byte) int { + p := <-AndroidWriteTun + copy(buf, p) + return len(p) +} + +func GetAndroidSDWANConfig(buf []byte) int { + p := <-AndroidSDWANConfig + copy(buf, p) + gLog.Printf(LvINFO, "AndroidSDWANConfig=%s", p) + return len(p) +} + +func GetAndroidNodeName() string { + gLog.Printf(LvINFO, "GetAndroidNodeName=%s", gConf.Network.Node) + return gConf.Network.Node +} + +func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { + // TODO: + return nil +} + +func addRoute(dst, gw, ifname string) error { + // TODO: + return nil +} + +func delRoute(dst, gw string) error { + // TODO: + return nil +} + +func delRoutesByGateway(gateway string) error { + // TODO: + return nil +} + +func init() { + AndroidReadTun = make(chan []byte, 1000) + AndroidWriteTun = make(chan []byte, 1000) +} diff --git a/core/optun_linux.go b/core/optun_linux.go index e3171a0..7670ace 100644 --- a/core/optun_linux.go +++ b/core/optun_linux.go @@ -1,133 +1,133 @@ -//go:build !android -// +build !android - -// optun_linux.go -package openp2p - -import ( - "fmt" - "net" - "os/exec" - "strings" - - "github.com/openp2p-cn/wireguard-go/tun" - "github.com/vishvananda/netlink" -) - -const ( - tunIfaceName = "optun" - PIHeaderSize = 0 -) - -var previousIP = "" - -func (t *optun) Start(localAddr string, detail *SDWANInfo) error { - var err error - t.tunName = tunIfaceName - t.dev, err = tun.CreateTUN(t.tunName, 1420) - if err != nil { - return err - } - return nil -} - -func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { - return t.dev.Read(bufs, sizes, offset) -} - -func (t *optun) Write(bufs [][]byte, offset int) (int, error) { - return t.dev.Write(bufs, offset) -} - -func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { - ifce, err := netlink.LinkByName(ifname) - if err != nil { - return err - } - netlink.LinkSetMTU(ifce, 1375) - netlink.LinkSetTxQLen(ifce, 100) - netlink.LinkSetUp(ifce) - - ln, err := netlink.ParseIPNet(localAddr) - if err != nil { - return err - } - ln.Mask = net.CIDRMask(32, 32) - rn, err := netlink.ParseIPNet(remoteAddr) - if err != nil { - return err - } - rn.Mask = net.CIDRMask(32, 32) - - addr := &netlink.Addr{ - IPNet: ln, - Peer: rn, - } - if previousIP != "" { - lnDel, err := netlink.ParseIPNet(previousIP) - if err != nil { - return err - } - lnDel.Mask = net.CIDRMask(32, 32) - - addrDel := &netlink.Addr{ - IPNet: lnDel, - Peer: rn, - } - netlink.AddrDel(ifce, addrDel) - } - previousIP = localAddr - return netlink.AddrAdd(ifce, addr) -} - -func addRoute(dst, gw, ifname string) error { - _, networkid, err := net.ParseCIDR(dst) - if err != nil { - return err - } - ipGW := net.ParseIP(gw) - if ipGW == nil { - return fmt.Errorf("parse gateway %s failed", gw) - } - route := &netlink.Route{ - Dst: networkid, - Gw: ipGW, - } - return netlink.RouteAdd(route) -} - -func delRoute(dst, gw string) error { - _, networkid, err := net.ParseCIDR(dst) - if err != nil { - return err - } - route := &netlink.Route{ - Dst: networkid, - } - return netlink.RouteDel(route) -} - -func delRoutesByGateway(gateway string) error { - cmd := exec.Command("route", "-n") - output, err := cmd.Output() - if err != nil { - return err - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if !strings.Contains(line, gateway) { - continue - } - fields := strings.Fields(line) - if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway { - delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway) - err := delCmd.Run() - if err != nil { - return err - } - fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) - } - } - return nil -} +//go:build !android +// +build !android + +// optun_linux.go +package openp2p + +import ( + "fmt" + "net" + "os/exec" + "strings" + + "github.com/openp2p-cn/wireguard-go/tun" + "github.com/vishvananda/netlink" +) + +const ( + tunIfaceName = "optun" + PIHeaderSize = 0 +) + +var previousIP = "" + +func (t *optun) Start(localAddr string, detail *SDWANInfo) error { + var err error + t.tunName = tunIfaceName + t.dev, err = tun.CreateTUN(t.tunName, 1420) + if err != nil { + return err + } + return nil +} + +func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + return t.dev.Read(bufs, sizes, offset) +} + +func (t *optun) Write(bufs [][]byte, offset int) (int, error) { + return t.dev.Write(bufs, offset) +} + +func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { + ifce, err := netlink.LinkByName(ifname) + if err != nil { + return err + } + netlink.LinkSetMTU(ifce, 1375) + netlink.LinkSetTxQLen(ifce, 100) + netlink.LinkSetUp(ifce) + + ln, err := netlink.ParseIPNet(localAddr) + if err != nil { + return err + } + ln.Mask = net.CIDRMask(32, 32) + rn, err := netlink.ParseIPNet(remoteAddr) + if err != nil { + return err + } + rn.Mask = net.CIDRMask(32, 32) + + addr := &netlink.Addr{ + IPNet: ln, + Peer: rn, + } + if previousIP != "" { + lnDel, err := netlink.ParseIPNet(previousIP) + if err != nil { + return err + } + lnDel.Mask = net.CIDRMask(32, 32) + + addrDel := &netlink.Addr{ + IPNet: lnDel, + Peer: rn, + } + netlink.AddrDel(ifce, addrDel) + } + previousIP = localAddr + return netlink.AddrAdd(ifce, addr) +} + +func addRoute(dst, gw, ifname string) error { + _, networkid, err := net.ParseCIDR(dst) + if err != nil { + return err + } + ipGW := net.ParseIP(gw) + if ipGW == nil { + return fmt.Errorf("parse gateway %s failed", gw) + } + route := &netlink.Route{ + Dst: networkid, + Gw: ipGW, + } + return netlink.RouteAdd(route) +} + +func delRoute(dst, gw string) error { + _, networkid, err := net.ParseCIDR(dst) + if err != nil { + return err + } + route := &netlink.Route{ + Dst: networkid, + } + return netlink.RouteDel(route) +} + +func delRoutesByGateway(gateway string) error { + cmd := exec.Command("route", "-n") + output, err := cmd.Output() + if err != nil { + return err + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if !strings.Contains(line, gateway) { + continue + } + fields := strings.Fields(line) + if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway { + delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway) + err := delCmd.Run() + if err != nil { + return err + } + fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) + } + } + return nil +} diff --git a/core/optun_windows.go b/core/optun_windows.go index 7786aba..c1a13ef 100644 --- a/core/optun_windows.go +++ b/core/optun_windows.go @@ -1,142 +1,142 @@ -package openp2p - -import ( - "fmt" - "net" - "net/netip" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - - "github.com/openp2p-cn/wireguard-go/tun" - "golang.org/x/sys/windows" - "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" -) - -const ( - tunIfaceName = "optun" - PIHeaderSize = 0 -) - -func (t *optun) Start(localAddr string, detail *SDWANInfo) error { - // check wintun.dll - tmpFile := filepath.Dir(os.Args[0]) + "/wintun.dll" - fs, err := os.Stat(tmpFile) - if err != nil || fs.Size() == 0 { - url := fmt.Sprintf("https://openp2p.cn/download/v1/latest/wintun/%s/wintun.dll", runtime.GOARCH) - err = downloadFile(url, "", tmpFile) - if err != nil { - os.Remove(tmpFile) - return err - } - } - - t.tunName = tunIfaceName - - uuid := &windows.GUID{ - Data1: 0xf411e821, - Data2: 0xb310, - Data3: 0x4567, - Data4: [8]byte{0x80, 0x42, 0x83, 0x7e, 0xf4, 0x56, 0xce, 0x13}, - } - t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) - if err != nil { // retry - t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) - } - - if err != nil { - return err - } - - return nil -} - -func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { - return t.dev.Read(bufs, sizes, offset) -} - -func (t *optun) Write(bufs [][]byte, offset int) (int, error) { - return t.dev.Write(bufs, offset) -} - -func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { - nativeTunDevice := wintun.(*tun.NativeTun) - link := winipcfg.LUID(nativeTunDevice.LUID()) - ip, err := netip.ParsePrefix(localAddr) - if err != nil { - gLog.Printf(LvERROR, "ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr) - return err - } - err = link.SetIPAddresses([]netip.Prefix{ip}) - if err != nil { - gLog.Printf(LvERROR, "SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip}) - return err - } - return nil -} - -func addRoute(dst, gw, ifname string) error { - _, dstNet, err := net.ParseCIDR(dst) - if err != nil { - return err - } - i, err := net.InterfaceByName(ifname) - if err != nil { - return err - } - params := make([]string, 0) - params = append(params, "add") - params = append(params, dstNet.IP.String()) - params = append(params, "mask") - params = append(params, net.IP(dstNet.Mask).String()) - params = append(params, gw) - params = append(params, "if") - params = append(params, strconv.Itoa(i.Index)) - // gLogger.Println(LevelINFO, "windows add route params:", params) - execCommand("route", true, params...) - return nil -} - -func delRoute(dst, gw string) error { - _, dstNet, err := net.ParseCIDR(dst) - if err != nil { - return err - } - params := make([]string, 0) - params = append(params, "delete") - params = append(params, dstNet.IP.String()) - params = append(params, "mask") - params = append(params, net.IP(dstNet.Mask).String()) - params = append(params, gw) - // gLogger.Println(LevelINFO, "windows delete route params:", params) - execCommand("route", true, params...) - return nil -} - -func delRoutesByGateway(gateway string) error { - cmd := exec.Command("route", "print", "-4") - output, err := cmd.Output() - if err != nil { - return err - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - if !strings.Contains(line, gateway) { - continue - } - fields := strings.Fields(line) - if len(fields) >= 5 { - cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway) - err := cmd.Run() - if err != nil { - fmt.Println("Delete route error:", err) - } - fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) - } - } - return nil -} +package openp2p + +import ( + "fmt" + "net" + "net/netip" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + + "github.com/openp2p-cn/wireguard-go/tun" + "golang.org/x/sys/windows" + "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" +) + +const ( + tunIfaceName = "optun" + PIHeaderSize = 0 +) + +func (t *optun) Start(localAddr string, detail *SDWANInfo) error { + // check wintun.dll + tmpFile := filepath.Dir(os.Args[0]) + "/wintun.dll" + fs, err := os.Stat(tmpFile) + if err != nil || fs.Size() == 0 { + url := fmt.Sprintf("https://openp2p.cn/download/v1/latest/wintun/%s/wintun.dll", runtime.GOARCH) + err = downloadFile(url, "", tmpFile) + if err != nil { + os.Remove(tmpFile) + return err + } + } + + t.tunName = tunIfaceName + + uuid := &windows.GUID{ + Data1: 0xf411e821, + Data2: 0xb310, + Data3: 0x4567, + Data4: [8]byte{0x80, 0x42, 0x83, 0x7e, 0xf4, 0x56, 0xce, 0x13}, + } + t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) + if err != nil { // retry + t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) + } + + if err != nil { + return err + } + + return nil +} + +func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) { + return t.dev.Read(bufs, sizes, offset) +} + +func (t *optun) Write(bufs [][]byte, offset int) (int, error) { + return t.dev.Write(bufs, offset) +} + +func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error { + nativeTunDevice := wintun.(*tun.NativeTun) + link := winipcfg.LUID(nativeTunDevice.LUID()) + ip, err := netip.ParsePrefix(localAddr) + if err != nil { + gLog.Printf(LvERROR, "ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr) + return err + } + err = link.SetIPAddresses([]netip.Prefix{ip}) + if err != nil { + gLog.Printf(LvERROR, "SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip}) + return err + } + return nil +} + +func addRoute(dst, gw, ifname string) error { + _, dstNet, err := net.ParseCIDR(dst) + if err != nil { + return err + } + i, err := net.InterfaceByName(ifname) + if err != nil { + return err + } + params := make([]string, 0) + params = append(params, "add") + params = append(params, dstNet.IP.String()) + params = append(params, "mask") + params = append(params, net.IP(dstNet.Mask).String()) + params = append(params, gw) + params = append(params, "if") + params = append(params, strconv.Itoa(i.Index)) + // gLogger.Println(LevelINFO, "windows add route params:", params) + execCommand("route", true, params...) + return nil +} + +func delRoute(dst, gw string) error { + _, dstNet, err := net.ParseCIDR(dst) + if err != nil { + return err + } + params := make([]string, 0) + params = append(params, "delete") + params = append(params, dstNet.IP.String()) + params = append(params, "mask") + params = append(params, net.IP(dstNet.Mask).String()) + params = append(params, gw) + // gLogger.Println(LevelINFO, "windows delete route params:", params) + execCommand("route", true, params...) + return nil +} + +func delRoutesByGateway(gateway string) error { + cmd := exec.Command("route", "print", "-4") + output, err := cmd.Output() + if err != nil { + return err + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if !strings.Contains(line, gateway) { + continue + } + fields := strings.Fields(line) + if len(fields) >= 5 { + cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway) + err := cmd.Run() + if err != nil { + fmt.Println("Delete route error:", err) + } + fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) + } + } + return nil +} diff --git a/core/p2pnetwork.go b/core/p2pnetwork.go index 79b3cb1..52d6faa 100644 --- a/core/p2pnetwork.go +++ b/core/p2pnetwork.go @@ -1,976 +1,976 @@ -package openp2p - -import ( - "bytes" - "crypto/tls" - "crypto/x509" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "math/rand" - "net/http" - "net/url" - "reflect" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" -) - -var ( - v4l *v4Listener - instance *P2PNetwork - onceP2PNetwork sync.Once - onceV4Listener sync.Once -) - -const ( - retryLimit = 20 - retryInterval = 10 * time.Second - DefaultLoginMaxDelaySeconds = 60 -) - -// golang not support float64 const -var ( - ma10 float64 = 1.0 / 10 - ma5 float64 = 1.0 / 5 -) - -type NodeData struct { - NodeID uint64 - Data []byte -} - -type P2PNetwork struct { - conn *websocket.Conn - online bool - running bool - restartCh chan bool - wgReconnect sync.WaitGroup - writeMtx sync.Mutex - reqGatewayMtx sync.Mutex - hbTime time.Time - // for sync server time - t1 int64 // nanoSeconds - preRtt int64 // nanoSeconds - dt int64 // client faster then server dt nanoSeconds - ddtma int64 - ddt int64 // differential of dt - msgMap sync.Map //key: nodeID - // msgMap map[uint64]chan pushMsg //key: nodeID - allTunnels sync.Map // key: tid - apps sync.Map //key: config.ID(); value: *p2pApp - limiter *SpeedLimiter - nodeData chan *NodeData - sdwan *p2pSDWAN - tunnelCloseCh chan *P2PTunnel - loginMaxDelaySeconds int -} - -type msgCtx struct { - data []byte - ts time.Time -} - -func P2PNetworkInstance() *P2PNetwork { - if instance == nil { - onceP2PNetwork.Do(func() { - instance = &P2PNetwork{ - restartCh: make(chan bool, 1), - tunnelCloseCh: make(chan *P2PTunnel, 100), - nodeData: make(chan *NodeData, 10000), - online: false, - running: true, - limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1), - dt: 0, - ddt: 0, - loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds, - } - instance.msgMap.Store(uint64(0), make(chan msgCtx, 50)) // for gateway - instance.StartSDWAN() - instance.init() - go instance.run() - go func() { - for { - instance.refreshIPv6() - time.Sleep(time.Hour) - } - }() - cleanTempFiles() - }) - } - return instance -} - -func (pn *P2PNetwork) run() { - heartbeatTimer := time.NewTicker(NetworkHeartbeatTime) - pn.t1 = time.Now().UnixNano() - pn.write(MsgHeartbeat, 0, "") - for { - select { - case <-heartbeatTimer.C: - pn.t1 = time.Now().UnixNano() - pn.write(MsgHeartbeat, 0, "") - case <-pn.restartCh: - gLog.Printf(LvDEBUG, "got restart channel") - pn.online = false - pn.wgReconnect.Wait() // wait read/autorunapp goroutine end - delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second - time.Sleep(delay) - err := pn.init() - if err != nil { - gLog.Println(LvERROR, "P2PNetwork init error:", err) - } - gConf.retryAllApp() - case t := <-pn.tunnelCloseCh: - gLog.Printf(LvDEBUG, "got tunnelCloseCh %s", t.config.LogPeerNode()) - pn.apps.Range(func(id, i interface{}) bool { - app := i.(*p2pApp) - if app.DirectTunnel() == t { - app.setDirectTunnel(nil) - } - if app.RelayTunnel() == t { - app.setRelayTunnel(nil) - } - return true - }) - } - } -} - -func (pn *P2PNetwork) NotifyTunnelClose(t *P2PTunnel) bool { - select { - case pn.tunnelCloseCh <- t: - return true - default: - } - return false -} - -func (pn *P2PNetwork) Connect(timeout int) bool { - // waiting for heartbeat - for i := 0; i < (timeout / 1000); i++ { - if pn.hbTime.After(time.Now().Add(-NetworkHeartbeatTime)) { - return true - } - time.Sleep(time.Second) - } - return false -} - -func (pn *P2PNetwork) runAll() { - gConf.mtx.Lock() // lock for copy gConf.Apps and the modification of config(it's pointer) - defer gConf.mtx.Unlock() - allApps := gConf.Apps // read a copy, other thread will modify the gConf.Apps - for _, config := range allApps { - if config.AppName == "" { - config.AppName = fmt.Sprintf("%d", config.ID()) - } - if config.Enabled == 0 { - continue - } - if _, ok := pn.apps.Load(config.ID()); ok { - continue - } - - config.peerToken = gConf.Network.Token - gConf.mtx.Unlock() // AddApp will take a period of time, let outside modify gConf - pn.AddApp(*config) - gConf.mtx.Lock() - - } -} - -func (pn *P2PNetwork) autorunApp() { - gLog.Println(LvINFO, "autorunApp start") - pn.wgReconnect.Add(1) - defer pn.wgReconnect.Done() - for pn.running && pn.online { - time.Sleep(time.Second) - pn.runAll() - } - gLog.Println(LvINFO, "autorunApp end") -} - -func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, string, error) { - gLog.Printf(LvINFO, "addRelayTunnel to %s start", config.LogPeerNode()) - defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.LogPeerNode()) - relayConfig := AppConfig{ - PeerNode: config.RelayNode, - peerToken: config.peerToken, - relayMode: "private"} - if relayConfig.PeerNode == "" { - // find existing relay tunnel - pn.apps.Range(func(id, i interface{}) bool { - app := i.(*p2pApp) - if app.config.PeerNode != config.PeerNode { - return true - } - if app.RelayTunnel() == nil { - return true - } - relayConfig.PeerNode = app.RelayTunnel().config.PeerNode - gLog.Printf(LvDEBUG, "found existing relay tunnel %s", relayConfig.LogPeerNode()) - return false - }) - if relayConfig.PeerNode == "" { // request relay node - pn.reqGatewayMtx.Lock() - pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode}) - head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, ClientAPITimeout) - pn.reqGatewayMtx.Unlock() - if head == nil { - return nil, 0, "", errors.New("read MsgRelayNodeRsp error") - } - rsp := RelayNodeRsp{} - if err := json.Unmarshal(body, &rsp); err != nil { - return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error") - } - if rsp.RelayName == "" || rsp.RelayToken == 0 { - gLog.Printf(LvERROR, "MsgRelayNodeReq error") - return nil, 0, "", errors.New("MsgRelayNodeReq error") - } - gLog.Printf(LvDEBUG, "got relay node:%s", relayConfig.LogPeerNode()) - - relayConfig.PeerNode = rsp.RelayName - relayConfig.peerToken = rsp.RelayToken - relayConfig.relayMode = rsp.Mode - } - - } - /// - t, err := pn.addDirectTunnel(relayConfig, 0) - if err != nil { - gLog.Println(LvERROR, "direct connect error:", err) - return nil, 0, "", ErrConnectRelayNode // relay offline will stop retry - } - // notify peer addRelayTunnel - req := AddRelayTunnelReq{ - From: gConf.Network.Node, - RelayName: relayConfig.PeerNode, - RelayToken: relayConfig.peerToken, - RelayMode: relayConfig.relayMode, - RelayTunnelID: t.id, - } - gLog.Printf(LvDEBUG, "push %s the relay node(%s)", config.LogPeerNode(), relayConfig.LogPeerNode()) - pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req) - - // wait relay ready - head, body := pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount) - if head == nil { - gLog.Printf(LvERROR, "read MsgPushAddRelayTunnelRsp error") - return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error") - } - rspID := TunnelMsg{} - if err = json.Unmarshal(body, &rspID); err != nil { - gLog.Println(LvDEBUG, ErrPeerConnectRelay) - return nil, 0, "", ErrPeerConnectRelay - } - return t, rspID.ID, relayConfig.relayMode, err -} - -// use *AppConfig to save status -func (pn *P2PNetwork) AddApp(config AppConfig) error { - gLog.Printf(LvINFO, "addApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) - defer gLog.Printf(LvINFO, "addApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) - if !pn.online { - return errors.New("P2PNetwork offline") - } - if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { - pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) - } - // check if app already exist? - if _, ok := pn.apps.Load(config.ID()); ok { - return errors.New("P2PApp already exist") - } - - app := p2pApp{ - // tunnel: t, - id: rand.Uint64(), - key: rand.Uint64(), - config: config, - iptree: NewIPTree(config.Whitelist), - running: true, - hbTimeRelay: time.Now(), - } - if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { - pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) - } - pn.apps.Store(config.ID(), &app) - gLog.Printf(LvDEBUG, "Store app %d", config.ID()) - go app.checkP2PTunnel() - return nil -} - -func (pn *P2PNetwork) DeleteApp(config AppConfig) { - gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) - defer gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) - // close the apps of this config - i, ok := pn.apps.Load(config.ID()) - if ok { - app := i.(*p2pApp) - gLog.Printf(LvINFO, "app %s exist, delete it", app.config.AppName) - app.close() - pn.apps.Delete(config.ID()) - } -} - -func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) { - t = nil - // find existing tunnel to peer - pn.allTunnels.Range(func(id, i interface{}) bool { - tmpt := i.(*P2PTunnel) - if tmpt.config.PeerNode == peerNode { - gLog.Println(LvINFO, "tunnel already exist ", peerNode) - isActive := tmpt.checkActive() - // inactive, close it - if !isActive { - gLog.Println(LvINFO, "but it's not active, close it ", peerNode) - tmpt.close() - } else { - t = tmpt - } - return false - } - return true - }) - return t -} - -func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunnel, err error) { - gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid) - defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid) - isClient := false - // client side tid=0, assign random uint64 - if tid == 0 { - tid = rand.Uint64() - isClient = true - } - if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { - pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) - } - - // server side - if !isClient { - t, err = pn.newTunnel(config, tid, isClient) - return t, err // always return - } - // client side - // peer info - initErr := pn.requestPeerInfo(&config) - if initErr != nil { - gLog.Printf(LvERROR, "%s init error:%s", config.LogPeerNode(), initErr) - - return nil, initErr - } - gLog.Printf(LvDEBUG, "config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,", - config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType) - // try Intranet - if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP - gLog.Println(LvINFO, "try Intranet") - config.linkMode = LinkModeIntranet - config.isUnderlayServer = 0 - if t, err = pn.newTunnel(config, tid, isClient); err == nil { - return t, nil - } - } - // try TCP6 - if IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) { - gLog.Println(LvINFO, "try TCP6") - config.linkMode = LinkModeTCP6 - config.isUnderlayServer = 0 - if t, err = pn.newTunnel(config, tid, isClient); err == nil { - return t, nil - } - } - - // try UDP6? maybe no - - // try TCP4 - if config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 { - gLog.Println(LvINFO, "try TCP4") - config.linkMode = LinkModeTCP4 - if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { - config.isUnderlayServer = 1 - } else { - config.isUnderlayServer = 0 - } - if t, err = pn.newTunnel(config, tid, isClient); err == nil { - return t, nil - } - } - // try UDP4? maybe no - var primaryPunchFunc func() (*P2PTunnel, error) - var secondaryPunchFunc func() (*P2PTunnel, error) - funcUDP := func() (t *P2PTunnel, err error) { - if config.PunchPriority&PunchPriorityUDPDisable != 0 { - return - } - // try UDPPunch - for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries - if config.peerNatType == NATCone || gConf.Network.natType == NATCone { - gLog.Println(LvINFO, "try UDP4 Punch") - config.linkMode = LinkModeUDPPunch - config.isUnderlayServer = 0 - if t, err = pn.newTunnel(config, tid, isClient); err == nil { - return t, nil - } - } - if !(config.peerNatType == NATCone && gConf.Network.natType == NATCone) { // not cone2cone, no more try - break - } - } - return - } - funcTCP := func() (t *P2PTunnel, err error) { - if config.PunchPriority&PunchPriorityTCPDisable != 0 { - return - } - // try TCPPunch - for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries - if config.peerNatType == NATCone || gConf.Network.natType == NATCone { - gLog.Println(LvINFO, "try TCP4 Punch") - config.linkMode = LinkModeTCPPunch - config.isUnderlayServer = 0 - if t, err = pn.newTunnel(config, tid, isClient); err == nil { - gLog.Println(LvINFO, "TCP4 Punch ok") - return t, nil - } - } - } - return - } - if config.PunchPriority&PunchPriorityTCPFirst != 0 { - primaryPunchFunc = funcTCP - secondaryPunchFunc = funcUDP - } else { - primaryPunchFunc = funcTCP - secondaryPunchFunc = funcUDP - } - if t, err = primaryPunchFunc(); t != nil && err == nil { - return t, err - } - if t, err = secondaryPunchFunc(); t != nil && err == nil { - return t, err - } - - // TODO: s2s won't return err - return nil, err -} - -func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) { - if isClient { // only client side find existing tunnel - if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil { - return existTunnel, nil - } - } - - t = &P2PTunnel{pn: pn, - config: config, - id: tid, - writeData: make(chan []byte, WriteDataChanSize), - writeDataSmall: make(chan []byte, WriteDataChanSize/30), - } - t.initPort() - if isClient { - if err = t.connect(); err != nil { - gLog.Println(LvERROR, "p2pTunnel connect error:", err) - return - } - } else { - if err = t.listen(); err != nil { - gLog.Println(LvERROR, "p2pTunnel listen error:", err) - return - } - } - // store it when success - gLog.Printf(LvDEBUG, "store tunnel %d", tid) - pn.allTunnels.Store(tid, t) - return -} -func (pn *P2PNetwork) init() error { - gLog.Println(LvINFO, "P2PNetwork init start") - defer gLog.Println(LvINFO, "P2PNetwork init end") - pn.wgReconnect.Add(1) - defer pn.wgReconnect.Done() - var err error - for { - // detect nat type - gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerHost, gConf.Network.UDPPort1, gConf.Network.UDPPort2) - if err != nil { - gLog.Println(LvDEBUG, "detect NAT type error:", err) - break - } - if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again - gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort) - } - - // for testcase - if strings.Contains(gConf.Network.Node, "openp2pS2STest") { - gConf.Network.natType = NATSymmetric - gConf.Network.hasIPv4 = 0 - gConf.Network.hasUPNPorNATPMP = 0 - gLog.Println(LvINFO, "openp2pS2STest debug") - - } - if strings.Contains(gConf.Network.Node, "openp2pC2CTest") { - gConf.Network.natType = NATCone - gConf.Network.hasIPv4 = 0 - gConf.Network.hasUPNPorNATPMP = 0 - gLog.Println(LvINFO, "openp2pC2CTest debug") - } - - if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { - onceV4Listener.Do(func() { - v4l = &v4Listener{port: gConf.Network.TCPPort} - go v4l.start() - }) - } - gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP) - gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort) - uri := "/api/v1/login" - caCertPool, errCert := x509.SystemCertPool() - if errCert != nil { - gLog.Println(LvERROR, "Failed to load system root CAs:", errCert) - } else { - caCertPool = x509.NewCertPool() - } - caCertPool.AppendCertsFromPEM([]byte(rootCA)) - caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) - config := tls.Config{ - RootCAs: caCertPool, - InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert - websocket.DefaultDialer.TLSClientConfig = &config - websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout - u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri} - q := u.Query() - q.Add("node", gConf.Network.Node) - q.Add("token", fmt.Sprintf("%d", gConf.Network.Token)) - q.Add("version", OpenP2PVersion) - q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType)) - q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth)) - u.RawQuery = q.Encode() - var ws *websocket.Conn - ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil) - if err != nil { - gLog.Println(LvERROR, "Dial error:", err) - break - } - pn.running = true - pn.online = true - pn.conn = ws - localAddr := strings.Split(ws.LocalAddr().String(), ":") - if len(localAddr) == 2 { - gConf.Network.localIP = localAddr[0] - } else { - err = errors.New("get local ip failed") - break - } - go pn.readLoop() - gConf.Network.mac = getmac(gConf.Network.localIP) - gConf.Network.os = getOsName() - go func() { - req := ReportBasic{ - Mac: gConf.Network.mac, - LanIP: gConf.Network.localIP, - OS: gConf.Network.os, - HasIPv4: gConf.Network.hasIPv4, - HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, - Version: OpenP2PVersion, - } - rsp := netInfo() - gLog.Println(LvDEBUG, "netinfo:", rsp) - if rsp != nil && rsp.Country != "" { - if IsIPv6(rsp.IP.String()) { - gConf.setIPv6(rsp.IP.String()) - } - req.NetInfo = *rsp - } else { - pn.refreshIPv6() - } - req.IPv6 = gConf.IPv6() - pn.write(MsgReport, MsgReportBasic, &req) - }() - go pn.autorunApp() - pn.write(MsgSDWAN, MsgSDWANInfoReq, nil) - gLog.Println(LvDEBUG, "P2PNetwork init ok") - break - } - if err != nil { - // init failed, retry - pn.close() - gLog.Println(LvERROR, "P2PNetwork init error:", err) - } - return err -} - -func (pn *P2PNetwork) handleMessage(msg []byte) { - head := openP2PHeader{} - err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head) - if err != nil { - gLog.Println(LvERROR, "handleMessage error:", err) - return - } - switch head.MainType { - case MsgLogin: - // gLog.Println(LevelINFO,string(msg)) - rsp := LoginRsp{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) - return - } - if rsp.Error != 0 { - gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail) - pn.running = false - } else { - gConf.setToken(rsp.Token) - gConf.setUser(rsp.User) - if len(rsp.Node) >= MinNodeNameLen { - gConf.setNode(rsp.Node) - } - if rsp.LoginMaxDelay > 0 { - pn.loginMaxDelaySeconds = rsp.LoginMaxDelay - } - gLog.Printf(LvINFO, "login ok. user=%s,node=%s", rsp.User, rsp.Node) - } - case MsgHeartbeat: - gLog.Printf(LvDev, "P2PNetwork heartbeat ok") - pn.hbTime = time.Now() - rtt := pn.hbTime.UnixNano() - pn.t1 - if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) { - gLog.Printf(LvINFO, "rtt=%d too large ignore", rtt) - return // invalid hb rsp - } - pn.preRtt = rtt - t2 := int64(binary.LittleEndian.Uint64(msg[openP2PHeaderSize : openP2PHeaderSize+8])) - thisdt := pn.t1 + rtt/2 - t2 - newdt := thisdt - if pn.dt != 0 { - ddt := thisdt - pn.dt - pn.ddt = ddt - if pn.ddtma == 0 { - pn.ddtma = pn.ddt - } else { - pn.ddtma = int64(float64(pn.ddtma)*(1-ma10) + float64(pn.ddt)*ma10) // avoid int64 overflow - newdt = pn.dt + pn.ddtma - } - } - pn.dt = newdt - gLog.Printf(LvDEBUG, "synctime thisdt=%dms dt=%dms ddt=%dns ddtma=%dns rtt=%dms ", thisdt/int64(time.Millisecond), pn.dt/int64(time.Millisecond), pn.ddt, pn.ddtma, rtt/int64(time.Millisecond)) - case MsgPush: - handlePush(head.SubType, msg) - case MsgSDWAN: - handleSDWAN(head.SubType, msg) - default: - i, ok := pn.msgMap.Load(uint64(0)) - if ok { - ch := i.(chan msgCtx) - ch <- msgCtx{data: msg, ts: time.Now()} - } - - return - } -} - -func (pn *P2PNetwork) readLoop() { - gLog.Printf(LvDEBUG, "P2PNetwork readLoop start") - pn.wgReconnect.Add(1) - defer pn.wgReconnect.Done() - for pn.running { - pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second)) - _, msg, err := pn.conn.ReadMessage() - if err != nil { - gLog.Printf(LvERROR, "P2PNetwork read error:%s", err) - pn.close() - break - } - pn.handleMessage(msg) - } - gLog.Printf(LvDEBUG, "P2PNetwork readLoop end") -} - -func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error { - if !pn.online { - return errors.New("P2P network offline") - } - msg, err := newMessage(mainType, subType, packet) - if err != nil { - return err - } - pn.writeMtx.Lock() - defer pn.writeMtx.Unlock() - if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil { - gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err) - pn.close() - } - return err -} - -func (pn *P2PNetwork) relay(to uint64, body []byte) error { - i, ok := pn.allTunnels.Load(to) - if !ok { - return ErrRelayTunnelNotFound - } - tunnel := i.(*P2PTunnel) - if tunnel.config.shareBandwidth > 0 { - pn.limiter.Add(len(body), true) - } - var err error - if err = tunnel.conn.WriteBuffer(body); err != nil { - gLog.Printf(LvERROR, "relay to %d len=%d error:%s", to, len(body), err) - } - return err -} - -func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error { - // gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to) - if !pn.online { - return errors.New("client offline") - } - pushHead := PushHeader{} - pushHead.From = gConf.nodeID() - pushHead.To = NodeNameToID(to) - pushHeadBuf := new(bytes.Buffer) - err := binary.Write(pushHeadBuf, binary.LittleEndian, pushHead) - if err != nil { - return err - } - data, err := json.Marshal(packet) - if err != nil { - return err - } - // gLog.Println(LevelINFO,"write packet:", string(data)) - pushMsg := append(encodeHeader(MsgPush, subType, uint32(len(data)+PushHeaderSize)), pushHeadBuf.Bytes()...) - pushMsg = append(pushMsg, data...) - pn.writeMtx.Lock() - defer pn.writeMtx.Unlock() - if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil { - gLog.Printf(LvERROR, "push to %s error:%s", to, err) - pn.close() - } - return err -} - -func (pn *P2PNetwork) close() { - if pn.running { - if pn.conn != nil { - pn.conn.Close() - } - pn.running = false - } - select { - case pn.restartCh <- true: - default: - } -} - -func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout time.Duration) (head *openP2PHeader, body []byte) { - var nodeID uint64 - if node == "" { - nodeID = 0 - } else { - nodeID = NodeNameToID(node) - } - i, ok := pn.msgMap.Load(nodeID) - if !ok { - gLog.Printf(LvERROR, "read msg error: %s not found", node) - return - } - ch := i.(chan msgCtx) - for { - select { - case <-time.After(timeout): - gLog.Printf(LvERROR, "read msg error %d:%d timeout", mainType, subType) - return - case msg := <-ch: - head = &openP2PHeader{} - err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head) - if err != nil { - gLog.Println(LvERROR, "read msg error:", err) - break - } - if time.Since(msg.ts) > ReadMsgTimeout { - gLog.Printf(LvDEBUG, "read msg error expired %d:%d", head.MainType, head.SubType) - continue - } - if head.MainType != mainType || head.SubType != subType { - gLog.Printf(LvDEBUG, "read msg error type %d:%d, requeue it", head.MainType, head.SubType) - ch <- msg - time.Sleep(time.Second) - continue - } - if mainType == MsgPush { - body = msg.data[openP2PHeaderSize+PushHeaderSize:] - } else { - body = msg.data[openP2PHeaderSize:] - } - return - } - } -} - -func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) { - pn.apps.Range(func(id, i interface{}) bool { - app := i.(*p2pApp) - if app.id == appID { - app.updateHeartbeat() - } - return true - }) -} - -// ipv6 will expired need to refresh. -func (pn *P2PNetwork) refreshIPv6() { - for i := 0; i < 2; i++ { - client := &http.Client{Timeout: time.Second * 10} - r, err := client.Get("http://ipv6.ddnspod.com/") - if err != nil { - gLog.Println(LvDEBUG, "refreshIPv6 error:", err) - continue - } - defer r.Body.Close() - buf := make([]byte, 1024) - n, err := r.Body.Read(buf) - if n <= 0 { - gLog.Println(LvINFO, "refreshIPv6 error:", err, n) - continue - } - if IsIPv6(string(buf[:n])) { - gConf.setIPv6(string(buf[:n])) - } - break - } - -} - -func (pn *P2PNetwork) requestPeerInfo(config *AppConfig) error { - // request peer info - // TODO: multi-thread issue - pn.reqGatewayMtx.Lock() - pn.write(MsgQuery, MsgQueryPeerInfoReq, &QueryPeerInfoReq{config.peerToken, config.PeerNode}) - head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout) - pn.reqGatewayMtx.Unlock() - if head == nil { - gLog.Println(LvERROR, "requestPeerInfo error") - return ErrNetwork // network error, should not be ErrPeerOffline - } - rsp := QueryPeerInfoRsp{} - if err := json.Unmarshal(body, &rsp); err != nil { - return ErrMsgFormat - } - if rsp.Online == 0 { - return ErrPeerOffline - } - if compareVersion(rsp.Version, LeastSupportVersion) < 0 { - return ErrVersionNotCompatible - } - config.peerVersion = rsp.Version - config.peerLanIP = rsp.LanIP - config.hasIPv4 = rsp.HasIPv4 - config.peerIP = rsp.IPv4 - config.peerIPv6 = rsp.IPv6 - config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP - config.peerNatType = rsp.NatType - /// - return nil -} - -func (pn *P2PNetwork) StartSDWAN() { - // request peer info - pn.sdwan = &p2pSDWAN{} -} - -func (pn *P2PNetwork) ConnectNode(node string) error { - if gConf.nodeID() < NodeNameToID(node) { - return errors.New("only the bigger nodeid connect") - } - peerNodeID := fmt.Sprintf("%d", NodeNameToID(node)) - config := AppConfig{Enabled: 1} - config.AppName = peerNodeID - config.SrcPort = 0 - config.PeerNode = node - sdwan := gConf.getSDWAN() - config.PunchPriority = int(sdwan.PunchPriority) - if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode - config.RelayNode = sdwan.CentralNode - config.ForceRelay = int(sdwan.ForceRelay) - if sdwan.Mode == SDWANModeCentral { - config.ForceRelay = 1 - } - } - - gConf.add(config, true) - return nil -} - -func (pn *P2PNetwork) WriteNode(nodeID uint64, buff []byte) error { - i, ok := pn.apps.Load(nodeID) - if !ok { - return errors.New("peer not found") - } - var err error - app := i.(*p2pApp) - if app.Tunnel() == nil { - return errors.New("peer tunnel nil") - } - // TODO: move to app.write - gLog.Printf(LvDev, "%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect()) - if app.isDirect() { // direct - app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff) - } else { // relay - fromNodeIDHead := new(bytes.Buffer) - binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID()) - all := app.RelayHead().Bytes() - all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...) - all = append(all, fromNodeIDHead.Bytes()...) - all = append(all, buff...) - app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all) - } - - return err -} - -func (pn *P2PNetwork) WriteBroadcast(buff []byte) error { - /// - pn.apps.Range(func(id, i interface{}) bool { - // newDestIP := net.ParseIP("10.2.3.2") - // copy(buff[16:20], newDestIP.To4()) - // binary.BigEndian.PutUint16(buff[10:12], 0) // set checksum=0 for calc checksum - // ipChecksum := calculateChecksum(buff[0:20]) - // binary.BigEndian.PutUint16(buff[10:12], ipChecksum) - // binary.BigEndian.PutUint16(buff[26:28], 0x082e) - app := i.(*p2pApp) - if app.Tunnel() == nil { - return true - } - if app.config.SrcPort != 0 { // normal portmap app - return true - } - if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan - return true - } - if app.isDirect() { // direct - app.Tunnel().conn.WriteBytes(MsgP2P, MsgNodeData, buff) - } else { // relay - fromNodeIDHead := new(bytes.Buffer) - binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID()) - all := app.RelayHead().Bytes() - all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...) - all = append(all, fromNodeIDHead.Bytes()...) - all = append(all, buff...) - app.Tunnel().conn.WriteBytes(MsgP2P, MsgRelayData, all) - } - return true - }) - return nil -} - -func (pn *P2PNetwork) ReadNode(tm time.Duration) *NodeData { - select { - case nd := <-pn.nodeData: - return nd - case <-time.After(tm): - } - return nil -} +package openp2p + +import ( + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math/rand" + "net/http" + "net/url" + "reflect" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" +) + +var ( + v4l *v4Listener + instance *P2PNetwork + onceP2PNetwork sync.Once + onceV4Listener sync.Once +) + +const ( + retryLimit = 20 + retryInterval = 10 * time.Second + DefaultLoginMaxDelaySeconds = 60 +) + +// golang not support float64 const +var ( + ma10 float64 = 1.0 / 10 + ma5 float64 = 1.0 / 5 +) + +type NodeData struct { + NodeID uint64 + Data []byte +} + +type P2PNetwork struct { + conn *websocket.Conn + online bool + running bool + restartCh chan bool + wgReconnect sync.WaitGroup + writeMtx sync.Mutex + reqGatewayMtx sync.Mutex + hbTime time.Time + // for sync server time + t1 int64 // nanoSeconds + preRtt int64 // nanoSeconds + dt int64 // client faster then server dt nanoSeconds + ddtma int64 + ddt int64 // differential of dt + msgMap sync.Map //key: nodeID + // msgMap map[uint64]chan pushMsg //key: nodeID + allTunnels sync.Map // key: tid + apps sync.Map //key: config.ID(); value: *p2pApp + limiter *SpeedLimiter + nodeData chan *NodeData + sdwan *p2pSDWAN + tunnelCloseCh chan *P2PTunnel + loginMaxDelaySeconds int +} + +type msgCtx struct { + data []byte + ts time.Time +} + +func P2PNetworkInstance() *P2PNetwork { + if instance == nil { + onceP2PNetwork.Do(func() { + instance = &P2PNetwork{ + restartCh: make(chan bool, 1), + tunnelCloseCh: make(chan *P2PTunnel, 100), + nodeData: make(chan *NodeData, 10000), + online: false, + running: true, + limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1), + dt: 0, + ddt: 0, + loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds, + } + instance.msgMap.Store(uint64(0), make(chan msgCtx, 50)) // for gateway + instance.StartSDWAN() + instance.init() + go instance.run() + go func() { + for { + instance.refreshIPv6() + time.Sleep(time.Hour) + } + }() + cleanTempFiles() + }) + } + return instance +} + +func (pn *P2PNetwork) run() { + heartbeatTimer := time.NewTicker(NetworkHeartbeatTime) + pn.t1 = time.Now().UnixNano() + pn.write(MsgHeartbeat, 0, "") + for { + select { + case <-heartbeatTimer.C: + pn.t1 = time.Now().UnixNano() + pn.write(MsgHeartbeat, 0, "") + case <-pn.restartCh: + gLog.Printf(LvDEBUG, "got restart channel") + pn.online = false + pn.wgReconnect.Wait() // wait read/autorunapp goroutine end + delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second + time.Sleep(delay) + err := pn.init() + if err != nil { + gLog.Println(LvERROR, "P2PNetwork init error:", err) + } + gConf.retryAllApp() + case t := <-pn.tunnelCloseCh: + gLog.Printf(LvDEBUG, "got tunnelCloseCh %s", t.config.LogPeerNode()) + pn.apps.Range(func(id, i interface{}) bool { + app := i.(*p2pApp) + if app.DirectTunnel() == t { + app.setDirectTunnel(nil) + } + if app.RelayTunnel() == t { + app.setRelayTunnel(nil) + } + return true + }) + } + } +} + +func (pn *P2PNetwork) NotifyTunnelClose(t *P2PTunnel) bool { + select { + case pn.tunnelCloseCh <- t: + return true + default: + } + return false +} + +func (pn *P2PNetwork) Connect(timeout int) bool { + // waiting for heartbeat + for i := 0; i < (timeout / 1000); i++ { + if pn.hbTime.After(time.Now().Add(-NetworkHeartbeatTime)) { + return true + } + time.Sleep(time.Second) + } + return false +} + +func (pn *P2PNetwork) runAll() { + gConf.mtx.Lock() // lock for copy gConf.Apps and the modification of config(it's pointer) + defer gConf.mtx.Unlock() + allApps := gConf.Apps // read a copy, other thread will modify the gConf.Apps + for _, config := range allApps { + if config.AppName == "" { + config.AppName = fmt.Sprintf("%d", config.ID()) + } + if config.Enabled == 0 { + continue + } + if _, ok := pn.apps.Load(config.ID()); ok { + continue + } + + config.peerToken = gConf.Network.Token + gConf.mtx.Unlock() // AddApp will take a period of time, let outside modify gConf + pn.AddApp(*config) + gConf.mtx.Lock() + + } +} + +func (pn *P2PNetwork) autorunApp() { + gLog.Println(LvINFO, "autorunApp start") + pn.wgReconnect.Add(1) + defer pn.wgReconnect.Done() + for pn.running && pn.online { + time.Sleep(time.Second) + pn.runAll() + } + gLog.Println(LvINFO, "autorunApp end") +} + +func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, string, error) { + gLog.Printf(LvINFO, "addRelayTunnel to %s start", config.LogPeerNode()) + defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.LogPeerNode()) + relayConfig := AppConfig{ + PeerNode: config.RelayNode, + peerToken: config.peerToken, + relayMode: "private"} + if relayConfig.PeerNode == "" { + // find existing relay tunnel + pn.apps.Range(func(id, i interface{}) bool { + app := i.(*p2pApp) + if app.config.PeerNode != config.PeerNode { + return true + } + if app.RelayTunnel() == nil { + return true + } + relayConfig.PeerNode = app.RelayTunnel().config.PeerNode + gLog.Printf(LvDEBUG, "found existing relay tunnel %s", relayConfig.LogPeerNode()) + return false + }) + if relayConfig.PeerNode == "" { // request relay node + pn.reqGatewayMtx.Lock() + pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode}) + head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, ClientAPITimeout) + pn.reqGatewayMtx.Unlock() + if head == nil { + return nil, 0, "", errors.New("read MsgRelayNodeRsp error") + } + rsp := RelayNodeRsp{} + if err := json.Unmarshal(body, &rsp); err != nil { + return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error") + } + if rsp.RelayName == "" || rsp.RelayToken == 0 { + gLog.Printf(LvERROR, "MsgRelayNodeReq error") + return nil, 0, "", errors.New("MsgRelayNodeReq error") + } + gLog.Printf(LvDEBUG, "got relay node:%s", relayConfig.LogPeerNode()) + + relayConfig.PeerNode = rsp.RelayName + relayConfig.peerToken = rsp.RelayToken + relayConfig.relayMode = rsp.Mode + } + + } + /// + t, err := pn.addDirectTunnel(relayConfig, 0) + if err != nil { + gLog.Println(LvERROR, "direct connect error:", err) + return nil, 0, "", ErrConnectRelayNode // relay offline will stop retry + } + // notify peer addRelayTunnel + req := AddRelayTunnelReq{ + From: gConf.Network.Node, + RelayName: relayConfig.PeerNode, + RelayToken: relayConfig.peerToken, + RelayMode: relayConfig.relayMode, + RelayTunnelID: t.id, + } + gLog.Printf(LvDEBUG, "push %s the relay node(%s)", config.LogPeerNode(), relayConfig.LogPeerNode()) + pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req) + + // wait relay ready + head, body := pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount) + if head == nil { + gLog.Printf(LvERROR, "read MsgPushAddRelayTunnelRsp error") + return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error") + } + rspID := TunnelMsg{} + if err = json.Unmarshal(body, &rspID); err != nil { + gLog.Println(LvDEBUG, ErrPeerConnectRelay) + return nil, 0, "", ErrPeerConnectRelay + } + return t, rspID.ID, relayConfig.relayMode, err +} + +// use *AppConfig to save status +func (pn *P2PNetwork) AddApp(config AppConfig) error { + gLog.Printf(LvINFO, "addApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) + defer gLog.Printf(LvINFO, "addApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) + if !pn.online { + return errors.New("P2PNetwork offline") + } + if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { + pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) + } + // check if app already exist? + if _, ok := pn.apps.Load(config.ID()); ok { + return errors.New("P2PApp already exist") + } + + app := p2pApp{ + // tunnel: t, + id: rand.Uint64(), + key: rand.Uint64(), + config: config, + iptree: NewIPTree(config.Whitelist), + running: true, + hbTimeRelay: time.Now(), + } + if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { + pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) + } + pn.apps.Store(config.ID(), &app) + gLog.Printf(LvDEBUG, "Store app %d", config.ID()) + go app.checkP2PTunnel() + return nil +} + +func (pn *P2PNetwork) DeleteApp(config AppConfig) { + gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) + defer gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort) + // close the apps of this config + i, ok := pn.apps.Load(config.ID()) + if ok { + app := i.(*p2pApp) + gLog.Printf(LvINFO, "app %s exist, delete it", app.config.AppName) + app.close() + pn.apps.Delete(config.ID()) + } +} + +func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) { + t = nil + // find existing tunnel to peer + pn.allTunnels.Range(func(id, i interface{}) bool { + tmpt := i.(*P2PTunnel) + if tmpt.config.PeerNode == peerNode { + gLog.Println(LvINFO, "tunnel already exist ", peerNode) + isActive := tmpt.checkActive() + // inactive, close it + if !isActive { + gLog.Println(LvINFO, "but it's not active, close it ", peerNode) + tmpt.close() + } else { + t = tmpt + } + return false + } + return true + }) + return t +} + +func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunnel, err error) { + gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid) + defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid) + isClient := false + // client side tid=0, assign random uint64 + if tid == 0 { + tid = rand.Uint64() + isClient = true + } + if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { + pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) + } + + // server side + if !isClient { + t, err = pn.newTunnel(config, tid, isClient) + return t, err // always return + } + // client side + // peer info + initErr := pn.requestPeerInfo(&config) + if initErr != nil { + gLog.Printf(LvERROR, "%s init error:%s", config.LogPeerNode(), initErr) + + return nil, initErr + } + gLog.Printf(LvDEBUG, "config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,", + config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType) + // try Intranet + if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP + gLog.Println(LvINFO, "try Intranet") + config.linkMode = LinkModeIntranet + config.isUnderlayServer = 0 + if t, err = pn.newTunnel(config, tid, isClient); err == nil { + return t, nil + } + } + // try TCP6 + if IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) { + gLog.Println(LvINFO, "try TCP6") + config.linkMode = LinkModeTCP6 + config.isUnderlayServer = 0 + if t, err = pn.newTunnel(config, tid, isClient); err == nil { + return t, nil + } + } + + // try UDP6? maybe no + + // try TCP4 + if config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 { + gLog.Println(LvINFO, "try TCP4") + config.linkMode = LinkModeTCP4 + if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { + config.isUnderlayServer = 1 + } else { + config.isUnderlayServer = 0 + } + if t, err = pn.newTunnel(config, tid, isClient); err == nil { + return t, nil + } + } + // try UDP4? maybe no + var primaryPunchFunc func() (*P2PTunnel, error) + var secondaryPunchFunc func() (*P2PTunnel, error) + funcUDP := func() (t *P2PTunnel, err error) { + if config.PunchPriority&PunchPriorityUDPDisable != 0 { + return + } + // try UDPPunch + for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries + if config.peerNatType == NATCone || gConf.Network.natType == NATCone { + gLog.Println(LvINFO, "try UDP4 Punch") + config.linkMode = LinkModeUDPPunch + config.isUnderlayServer = 0 + if t, err = pn.newTunnel(config, tid, isClient); err == nil { + return t, nil + } + } + if !(config.peerNatType == NATCone && gConf.Network.natType == NATCone) { // not cone2cone, no more try + break + } + } + return + } + funcTCP := func() (t *P2PTunnel, err error) { + if config.PunchPriority&PunchPriorityTCPDisable != 0 { + return + } + // try TCPPunch + for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries + if config.peerNatType == NATCone || gConf.Network.natType == NATCone { + gLog.Println(LvINFO, "try TCP4 Punch") + config.linkMode = LinkModeTCPPunch + config.isUnderlayServer = 0 + if t, err = pn.newTunnel(config, tid, isClient); err == nil { + gLog.Println(LvINFO, "TCP4 Punch ok") + return t, nil + } + } + } + return + } + if config.PunchPriority&PunchPriorityTCPFirst != 0 { + primaryPunchFunc = funcTCP + secondaryPunchFunc = funcUDP + } else { + primaryPunchFunc = funcTCP + secondaryPunchFunc = funcUDP + } + if t, err = primaryPunchFunc(); t != nil && err == nil { + return t, err + } + if t, err = secondaryPunchFunc(); t != nil && err == nil { + return t, err + } + + // TODO: s2s won't return err + return nil, err +} + +func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) { + if isClient { // only client side find existing tunnel + if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil { + return existTunnel, nil + } + } + + t = &P2PTunnel{pn: pn, + config: config, + id: tid, + writeData: make(chan []byte, WriteDataChanSize), + writeDataSmall: make(chan []byte, WriteDataChanSize/30), + } + t.initPort() + if isClient { + if err = t.connect(); err != nil { + gLog.Println(LvERROR, "p2pTunnel connect error:", err) + return + } + } else { + if err = t.listen(); err != nil { + gLog.Println(LvERROR, "p2pTunnel listen error:", err) + return + } + } + // store it when success + gLog.Printf(LvDEBUG, "store tunnel %d", tid) + pn.allTunnels.Store(tid, t) + return +} +func (pn *P2PNetwork) init() error { + gLog.Println(LvINFO, "P2PNetwork init start") + defer gLog.Println(LvINFO, "P2PNetwork init end") + pn.wgReconnect.Add(1) + defer pn.wgReconnect.Done() + var err error + for { + // detect nat type + gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerHost, gConf.Network.UDPPort1, gConf.Network.UDPPort2) + if err != nil { + gLog.Println(LvDEBUG, "detect NAT type error:", err) + break + } + if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again + gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort) + } + + // for testcase + if strings.Contains(gConf.Network.Node, "openp2pS2STest") { + gConf.Network.natType = NATSymmetric + gConf.Network.hasIPv4 = 0 + gConf.Network.hasUPNPorNATPMP = 0 + gLog.Println(LvINFO, "openp2pS2STest debug") + + } + if strings.Contains(gConf.Network.Node, "openp2pC2CTest") { + gConf.Network.natType = NATCone + gConf.Network.hasIPv4 = 0 + gConf.Network.hasUPNPorNATPMP = 0 + gLog.Println(LvINFO, "openp2pC2CTest debug") + } + + if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { + onceV4Listener.Do(func() { + v4l = &v4Listener{port: gConf.Network.TCPPort} + go v4l.start() + }) + } + gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP) + gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort) + uri := "/api/v1/login" + caCertPool, errCert := x509.SystemCertPool() + if errCert != nil { + gLog.Println(LvERROR, "Failed to load system root CAs:", errCert) + } else { + caCertPool = x509.NewCertPool() + } + caCertPool.AppendCertsFromPEM([]byte(rootCA)) + caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) + config := tls.Config{ + RootCAs: caCertPool, + InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert + websocket.DefaultDialer.TLSClientConfig = &config + websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout + u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri} + q := u.Query() + q.Add("node", gConf.Network.Node) + q.Add("token", fmt.Sprintf("%d", gConf.Network.Token)) + q.Add("version", OpenP2PVersion) + q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType)) + q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth)) + u.RawQuery = q.Encode() + var ws *websocket.Conn + ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil) + if err != nil { + gLog.Println(LvERROR, "Dial error:", err) + break + } + pn.running = true + pn.online = true + pn.conn = ws + localAddr := strings.Split(ws.LocalAddr().String(), ":") + if len(localAddr) == 2 { + gConf.Network.localIP = localAddr[0] + } else { + err = errors.New("get local ip failed") + break + } + go pn.readLoop() + gConf.Network.mac = getmac(gConf.Network.localIP) + gConf.Network.os = getOsName() + go func() { + req := ReportBasic{ + Mac: gConf.Network.mac, + LanIP: gConf.Network.localIP, + OS: gConf.Network.os, + HasIPv4: gConf.Network.hasIPv4, + HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, + Version: OpenP2PVersion, + } + rsp := netInfo() + gLog.Println(LvDEBUG, "netinfo:", rsp) + if rsp != nil && rsp.Country != "" { + if IsIPv6(rsp.IP.String()) { + gConf.setIPv6(rsp.IP.String()) + } + req.NetInfo = *rsp + } else { + pn.refreshIPv6() + } + req.IPv6 = gConf.IPv6() + pn.write(MsgReport, MsgReportBasic, &req) + }() + go pn.autorunApp() + pn.write(MsgSDWAN, MsgSDWANInfoReq, nil) + gLog.Println(LvDEBUG, "P2PNetwork init ok") + break + } + if err != nil { + // init failed, retry + pn.close() + gLog.Println(LvERROR, "P2PNetwork init error:", err) + } + return err +} + +func (pn *P2PNetwork) handleMessage(msg []byte) { + head := openP2PHeader{} + err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head) + if err != nil { + gLog.Println(LvERROR, "handleMessage error:", err) + return + } + switch head.MainType { + case MsgLogin: + // gLog.Println(LevelINFO,string(msg)) + rsp := LoginRsp{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) + return + } + if rsp.Error != 0 { + gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail) + pn.running = false + } else { + gConf.setToken(rsp.Token) + gConf.setUser(rsp.User) + if len(rsp.Node) >= MinNodeNameLen { + gConf.setNode(rsp.Node) + } + if rsp.LoginMaxDelay > 0 { + pn.loginMaxDelaySeconds = rsp.LoginMaxDelay + } + gLog.Printf(LvINFO, "login ok. user=%s,node=%s", rsp.User, rsp.Node) + } + case MsgHeartbeat: + gLog.Printf(LvDev, "P2PNetwork heartbeat ok") + pn.hbTime = time.Now() + rtt := pn.hbTime.UnixNano() - pn.t1 + if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) { + gLog.Printf(LvINFO, "rtt=%d too large ignore", rtt) + return // invalid hb rsp + } + pn.preRtt = rtt + t2 := int64(binary.LittleEndian.Uint64(msg[openP2PHeaderSize : openP2PHeaderSize+8])) + thisdt := pn.t1 + rtt/2 - t2 + newdt := thisdt + if pn.dt != 0 { + ddt := thisdt - pn.dt + pn.ddt = ddt + if pn.ddtma == 0 { + pn.ddtma = pn.ddt + } else { + pn.ddtma = int64(float64(pn.ddtma)*(1-ma10) + float64(pn.ddt)*ma10) // avoid int64 overflow + newdt = pn.dt + pn.ddtma + } + } + pn.dt = newdt + gLog.Printf(LvDEBUG, "synctime thisdt=%dms dt=%dms ddt=%dns ddtma=%dns rtt=%dms ", thisdt/int64(time.Millisecond), pn.dt/int64(time.Millisecond), pn.ddt, pn.ddtma, rtt/int64(time.Millisecond)) + case MsgPush: + handlePush(head.SubType, msg) + case MsgSDWAN: + handleSDWAN(head.SubType, msg) + default: + i, ok := pn.msgMap.Load(uint64(0)) + if ok { + ch := i.(chan msgCtx) + ch <- msgCtx{data: msg, ts: time.Now()} + } + + return + } +} + +func (pn *P2PNetwork) readLoop() { + gLog.Printf(LvDEBUG, "P2PNetwork readLoop start") + pn.wgReconnect.Add(1) + defer pn.wgReconnect.Done() + for pn.running { + pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second)) + _, msg, err := pn.conn.ReadMessage() + if err != nil { + gLog.Printf(LvERROR, "P2PNetwork read error:%s", err) + pn.close() + break + } + pn.handleMessage(msg) + } + gLog.Printf(LvDEBUG, "P2PNetwork readLoop end") +} + +func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error { + if !pn.online { + return errors.New("P2P network offline") + } + msg, err := newMessage(mainType, subType, packet) + if err != nil { + return err + } + pn.writeMtx.Lock() + defer pn.writeMtx.Unlock() + if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil { + gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err) + pn.close() + } + return err +} + +func (pn *P2PNetwork) relay(to uint64, body []byte) error { + i, ok := pn.allTunnels.Load(to) + if !ok { + return ErrRelayTunnelNotFound + } + tunnel := i.(*P2PTunnel) + if tunnel.config.shareBandwidth > 0 { + pn.limiter.Add(len(body), true) + } + var err error + if err = tunnel.conn.WriteBuffer(body); err != nil { + gLog.Printf(LvERROR, "relay to %d len=%d error:%s", to, len(body), err) + } + return err +} + +func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error { + // gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to) + if !pn.online { + return errors.New("client offline") + } + pushHead := PushHeader{} + pushHead.From = gConf.nodeID() + pushHead.To = NodeNameToID(to) + pushHeadBuf := new(bytes.Buffer) + err := binary.Write(pushHeadBuf, binary.LittleEndian, pushHead) + if err != nil { + return err + } + data, err := json.Marshal(packet) + if err != nil { + return err + } + // gLog.Println(LevelINFO,"write packet:", string(data)) + pushMsg := append(encodeHeader(MsgPush, subType, uint32(len(data)+PushHeaderSize)), pushHeadBuf.Bytes()...) + pushMsg = append(pushMsg, data...) + pn.writeMtx.Lock() + defer pn.writeMtx.Unlock() + if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil { + gLog.Printf(LvERROR, "push to %s error:%s", to, err) + pn.close() + } + return err +} + +func (pn *P2PNetwork) close() { + if pn.running { + if pn.conn != nil { + pn.conn.Close() + } + pn.running = false + } + select { + case pn.restartCh <- true: + default: + } +} + +func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout time.Duration) (head *openP2PHeader, body []byte) { + var nodeID uint64 + if node == "" { + nodeID = 0 + } else { + nodeID = NodeNameToID(node) + } + i, ok := pn.msgMap.Load(nodeID) + if !ok { + gLog.Printf(LvERROR, "read msg error: %s not found", node) + return + } + ch := i.(chan msgCtx) + for { + select { + case <-time.After(timeout): + gLog.Printf(LvERROR, "read msg error %d:%d timeout", mainType, subType) + return + case msg := <-ch: + head = &openP2PHeader{} + err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head) + if err != nil { + gLog.Println(LvERROR, "read msg error:", err) + break + } + if time.Since(msg.ts) > ReadMsgTimeout { + gLog.Printf(LvDEBUG, "read msg error expired %d:%d", head.MainType, head.SubType) + continue + } + if head.MainType != mainType || head.SubType != subType { + gLog.Printf(LvDEBUG, "read msg error type %d:%d, requeue it", head.MainType, head.SubType) + ch <- msg + time.Sleep(time.Second) + continue + } + if mainType == MsgPush { + body = msg.data[openP2PHeaderSize+PushHeaderSize:] + } else { + body = msg.data[openP2PHeaderSize:] + } + return + } + } +} + +func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) { + pn.apps.Range(func(id, i interface{}) bool { + app := i.(*p2pApp) + if app.id == appID { + app.updateHeartbeat() + } + return true + }) +} + +// ipv6 will expired need to refresh. +func (pn *P2PNetwork) refreshIPv6() { + for i := 0; i < 2; i++ { + client := &http.Client{Timeout: time.Second * 10} + r, err := client.Get("http://ipv6.ddnspod.com/") + if err != nil { + gLog.Println(LvDEBUG, "refreshIPv6 error:", err) + continue + } + defer r.Body.Close() + buf := make([]byte, 1024) + n, err := r.Body.Read(buf) + if n <= 0 { + gLog.Println(LvINFO, "refreshIPv6 error:", err, n) + continue + } + if IsIPv6(string(buf[:n])) { + gConf.setIPv6(string(buf[:n])) + } + break + } + +} + +func (pn *P2PNetwork) requestPeerInfo(config *AppConfig) error { + // request peer info + // TODO: multi-thread issue + pn.reqGatewayMtx.Lock() + pn.write(MsgQuery, MsgQueryPeerInfoReq, &QueryPeerInfoReq{config.peerToken, config.PeerNode}) + head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout) + pn.reqGatewayMtx.Unlock() + if head == nil { + gLog.Println(LvERROR, "requestPeerInfo error") + return ErrNetwork // network error, should not be ErrPeerOffline + } + rsp := QueryPeerInfoRsp{} + if err := json.Unmarshal(body, &rsp); err != nil { + return ErrMsgFormat + } + if rsp.Online == 0 { + return ErrPeerOffline + } + if compareVersion(rsp.Version, LeastSupportVersion) < 0 { + return ErrVersionNotCompatible + } + config.peerVersion = rsp.Version + config.peerLanIP = rsp.LanIP + config.hasIPv4 = rsp.HasIPv4 + config.peerIP = rsp.IPv4 + config.peerIPv6 = rsp.IPv6 + config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP + config.peerNatType = rsp.NatType + /// + return nil +} + +func (pn *P2PNetwork) StartSDWAN() { + // request peer info + pn.sdwan = &p2pSDWAN{} +} + +func (pn *P2PNetwork) ConnectNode(node string) error { + if gConf.nodeID() < NodeNameToID(node) { + return errors.New("only the bigger nodeid connect") + } + peerNodeID := fmt.Sprintf("%d", NodeNameToID(node)) + config := AppConfig{Enabled: 1} + config.AppName = peerNodeID + config.SrcPort = 0 + config.PeerNode = node + sdwan := gConf.getSDWAN() + config.PunchPriority = int(sdwan.PunchPriority) + if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode + config.RelayNode = sdwan.CentralNode + config.ForceRelay = int(sdwan.ForceRelay) + if sdwan.Mode == SDWANModeCentral { + config.ForceRelay = 1 + } + } + + gConf.add(config, true) + return nil +} + +func (pn *P2PNetwork) WriteNode(nodeID uint64, buff []byte) error { + i, ok := pn.apps.Load(nodeID) + if !ok { + return errors.New("peer not found") + } + var err error + app := i.(*p2pApp) + if app.Tunnel() == nil { + return errors.New("peer tunnel nil") + } + // TODO: move to app.write + gLog.Printf(LvDev, "%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect()) + if app.isDirect() { // direct + app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff) + } else { // relay + fromNodeIDHead := new(bytes.Buffer) + binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID()) + all := app.RelayHead().Bytes() + all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...) + all = append(all, fromNodeIDHead.Bytes()...) + all = append(all, buff...) + app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all) + } + + return err +} + +func (pn *P2PNetwork) WriteBroadcast(buff []byte) error { + /// + pn.apps.Range(func(id, i interface{}) bool { + // newDestIP := net.ParseIP("10.2.3.2") + // copy(buff[16:20], newDestIP.To4()) + // binary.BigEndian.PutUint16(buff[10:12], 0) // set checksum=0 for calc checksum + // ipChecksum := calculateChecksum(buff[0:20]) + // binary.BigEndian.PutUint16(buff[10:12], ipChecksum) + // binary.BigEndian.PutUint16(buff[26:28], 0x082e) + app := i.(*p2pApp) + if app.Tunnel() == nil { + return true + } + if app.config.SrcPort != 0 { // normal portmap app + return true + } + if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan + return true + } + if app.isDirect() { // direct + app.Tunnel().conn.WriteBytes(MsgP2P, MsgNodeData, buff) + } else { // relay + fromNodeIDHead := new(bytes.Buffer) + binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID()) + all := app.RelayHead().Bytes() + all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...) + all = append(all, fromNodeIDHead.Bytes()...) + all = append(all, buff...) + app.Tunnel().conn.WriteBytes(MsgP2P, MsgRelayData, all) + } + return true + }) + return nil +} + +func (pn *P2PNetwork) ReadNode(tm time.Duration) *NodeData { + select { + case nd := <-pn.nodeData: + return nd + case <-time.After(tm): + } + return nil +} diff --git a/core/p2ptunnel.go b/core/p2ptunnel.go index 3cfd918..36e4ad7 100644 --- a/core/p2ptunnel.go +++ b/core/p2ptunnel.go @@ -1,806 +1,806 @@ -package openp2p - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "errors" - "fmt" - "math/rand" - "net" - "reflect" - "sync" - "sync/atomic" - "time" -) - -const WriteDataChanSize int = 3000 - -var buildTunnelMtx sync.Mutex - -type P2PTunnel struct { - pn *P2PNetwork - conn underlay - hbTime time.Time - hbMtx sync.Mutex - config AppConfig - la *net.UDPAddr // local hole address - ra *net.UDPAddr // remote hole address - overlayConns sync.Map // both TCP and UDP - id uint64 // client side alloc rand.uint64 = server side - running bool - runMtx sync.Mutex - tunnelServer bool // different from underlayServer - coneLocalPort int - coneNatPort int - linkModeWeb string // use config.linkmode - punchTs uint64 - writeData chan []byte - writeDataSmall chan []byte -} - -func (t *P2PTunnel) initPort() { - t.running = true - 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.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeIntranet { - t.coneLocalPort = gConf.Network.TCPPort - t.coneNatPort = gConf.Network.TCPPort // symmetric doesn't need coneNatPort - } - if t.config.linkMode == LinkModeUDPPunch { - // prepare one random cone hole manually - _, natPort, _ := natTest(gConf.Network.ServerHost, gConf.Network.UDPPort1, localPort) - t.coneLocalPort = localPort - t.coneNatPort = natPort - } - if t.config.linkMode == LinkModeTCPPunch { - // prepare one random cone hole by system automatically - _, natPort, localPort2 := natTCP(gConf.Network.ServerHost, IfconfigPort1) - t.coneLocalPort = localPort2 - t.coneNatPort = natPort - } - t.la = &net.UDPAddr{IP: net.ParseIP(gConf.Network.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.LogPeerNode()) - t.tunnelServer = false - appKey := uint64(0) - req := PushConnectReq{ - Token: t.config.peerToken, - From: gConf.Network.Node, - FromIP: gConf.Network.publicIP, - ConeNatPort: t.coneNatPort, - NatType: gConf.Network.natType, - HasIPv4: gConf.Network.hasIPv4, - IPv6: gConf.IPv6(), - HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, - ID: t.id, - AppKey: appKey, - Version: OpenP2PVersion, - LinkMode: t.config.linkMode, - IsUnderlayServer: t.config.isUnderlayServer ^ 1, // peer - UnderlayProtocol: t.config.UnderlayProtocol, - } - if req.Token == 0 { // no relay token - req.Token = gConf.Network.Token - } - t.pn.push(t.config.PeerNode, MsgPushConnectReq, req) - head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, UnderlayConnectTimeout*3) - if head == nil { - return errors.New("connect error") - } - rsp := PushConnectRsp{} - if err := json.Unmarshal(body, &rsp); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) - return err - } - // gLog.Println(LevelINFO, rsp) - if rsp.Error != 0 { - return errors.New(rsp.Detail) - } - t.config.peerNatType = rsp.NatType - t.config.hasIPv4 = rsp.HasIPv4 - t.config.peerIPv6 = rsp.IPv6 - t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP - t.config.peerVersion = rsp.Version - t.config.peerConeNatPort = rsp.ConeNatPort - t.config.peerIP = rsp.FromIP - t.punchTs = rsp.PunchTs - err := t.start() - if err != nil { - gLog.Println(LvERROR, "handshake error:", err) - } - return err -} - -func (t *P2PTunnel) isRuning() bool { - t.runMtx.Lock() - defer t.runMtx.Unlock() - return t.running -} - -func (t *P2PTunnel) setRun(running bool) { - t.runMtx.Lock() - defer t.runMtx.Unlock() - t.running = running -} - -func (t *P2PTunnel) isActive() bool { - if !t.isRuning() || t.conn == nil { - return false - } - t.hbMtx.Lock() - defer t.hbMtx.Unlock() - res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2)) - if !res { - gLog.Printf(LvDEBUG, "%d tunnel isActive false", t.id) - } - return res -} - -func (t *P2PTunnel) checkActive() bool { - if !t.isActive() { - return false - } - hbt := time.Now() - t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil) - isActive := false - // wait at most 5s - for i := 0; i < 50 && !isActive; i++ { - t.hbMtx.Lock() - if t.hbTime.After(hbt) { - isActive = true - } - t.hbMtx.Unlock() - time.Sleep(time.Millisecond * 100) - } - gLog.Printf(LvINFO, "checkActive %t. hbtime=%d", isActive, t.hbTime) - return isActive -} - -// call when user delete tunnel -func (t *P2PTunnel) close() { - t.pn.NotifyTunnelClose(t) - if !t.running { - return - } - t.setRun(false) - if t.conn != nil { - t.conn.Close() - } - t.pn.allTunnels.Delete(t.id) - gLog.Printf(LvINFO, "%d p2ptunnel close %s ", t.id, t.config.LogPeerNode()) -} - -func (t *P2PTunnel) start() error { - if t.config.linkMode == LinkModeUDPPunch { - if err := t.handshake(); err != nil { - return err - } - } - err := t.connectUnderlay() - if err != nil { - gLog.Println(LvERROR, err) - return err - } - return nil -} - -func (t *P2PTunnel) handshake() error { - 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 { - return err - } - } - if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { - gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) - } else { - ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) - if ts > PunchTsDelay || ts < 0 { - ts = PunchTsDelay - } - gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) - time.Sleep(ts) - } - gLog.Println(LvDEBUG, "handshake to ", t.config.LogPeerNode()) - var err error - if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone { - err = handshakeC2C(t) - } else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATSymmetric { - err = ErrorS2S - t.close() - } else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATCone { - err = handshakeC2S(t) - } else if t.config.peerNatType == NATCone && gConf.Network.natType == NATSymmetric { - err = handshakeS2C(t) - } else { - return errors.New("unknown error") - } - if err != nil { - gLog.Println(LvERROR, "punch handshake error:", err) - return err - } - gLog.Printf(LvDEBUG, "handshake to %s ok", t.config.LogPeerNode()) - return nil -} - -func (t *P2PTunnel) connectUnderlay() (err error) { - switch t.config.linkMode { - case LinkModeTCP6: - t.conn, err = t.connectUnderlayTCP6() - case LinkModeTCP4: - t.conn, err = t.connectUnderlayTCP() - case LinkModeTCPPunch: - if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric { - t.conn, err = t.connectUnderlayTCPSymmetric() - } else { - t.conn, err = t.connectUnderlayTCP() - } - case LinkModeIntranet: - t.conn, err = t.connectUnderlayTCP() - case LinkModeUDPPunch: - t.conn, err = t.connectUnderlayUDP() - - } - if err != nil { - return err - } - if t.conn == nil { - return errors.New("connect underlay error") - } - t.setRun(true) - go t.readLoop() - go t.writeLoop() - return nil -} - -func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) { - gLog.Printf(LvDEBUG, "connectUnderlayUDP %s start ", t.config.LogPeerNode()) - defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.LogPeerNode()) - var ul underlay - underlayProtocol := t.config.UnderlayProtocol - if underlayProtocol == "" { - underlayProtocol = "quic" - } - if t.config.isUnderlayServer == 1 { - time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env - go t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) - if t.config.UnderlayProtocol == "kcp" { - ul, err = listenKCP(t.la.String(), TunnelIdleTimeout) - } else { - ul, err = listenQuic(t.la.String(), TunnelIdleTimeout) - } - - if err != nil { - gLog.Printf(LvINFO, "listen %s error:%s", underlayProtocol, err) - return nil, err - } - - _, buff, err := ul.ReadBuffer() - if err != nil { - ul.Close() - return nil, fmt.Errorf("read start msg error:%s", err) - } - if buff != nil { - gLog.Println(LvDEBUG, string(buff)) - } - ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) - gLog.Printf(LvDEBUG, "%s connection ok", underlayProtocol) - return ul, nil - } - - //else - conn, errL := net.ListenUDP("udp", t.la) - if errL != nil { - time.Sleep(time.Millisecond * 10) - conn, errL = net.ListenUDP("udp", t.la) - if errL != nil { - return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL) - } - } - t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) - gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.ra.String()) - if t.config.UnderlayProtocol == "kcp" { - ul, errL = dialKCP(conn, t.ra, TunnelIdleTimeout) - } else { - ul, errL = dialQuic(conn, t.ra, TunnelIdleTimeout) - } - - if errL != nil { - return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.ra.String(), errL) - } - handshakeBegin := time.Now() - ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) - _, buff, err := ul.ReadBuffer() // TODO: kcp need timeout - if err != nil { - ul.Close() - return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) - } - if buff != nil { - gLog.Println(LvDEBUG, string(buff)) - } - - gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) - gLog.Printf(LvINFO, "%s connection ok", underlayProtocol) - t.linkModeWeb = LinkModeUDPPunch - return ul, nil -} - -func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) { - gLog.Printf(LvDEBUG, "connectUnderlayTCP %s start ", t.config.LogPeerNode()) - defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.LogPeerNode()) - var ul *underlayTCP - peerIP := t.config.peerIP - if t.config.linkMode == LinkModeIntranet { - peerIP = t.config.peerLanIP - } - // server side - if t.config.isUnderlayServer == 1 { - ul, err = listenTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode, t) - if err != nil { - return nil, fmt.Errorf("listen TCP error:%s", err) - } - gLog.Println(LvINFO, "TCP connection ok") - t.linkModeWeb = LinkModeIPv4 - if t.config.linkMode == LinkModeIntranet { - t.linkModeWeb = LinkModeIntranet - } - return ul, nil - } - - // client side - if t.config.linkMode == LinkModeTCP4 { - t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) - } else { //tcp punch should sleep for punch the same time - if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { - gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) - } else { - ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) - if ts > PunchTsDelay || ts < 0 { - ts = PunchTsDelay - } - gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) - time.Sleep(ts) - } - } - ul, err = dialTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode) - if err != nil { - return nil, fmt.Errorf("TCP dial to %s:%d error:%s", t.config.peerIP, t.config.peerConeNatPort, err) - } - handshakeBegin := time.Now() - tidBuff := new(bytes.Buffer) - binary.Write(tidBuff, binary.LittleEndian, t.id) - ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID - _, buff, err := ul.ReadBuffer() - if err != nil { - return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) - } - if buff != nil { - gLog.Println(LvDEBUG, "hello ", string(buff)) - } - - gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) - gLog.Println(LvINFO, "TCP connection ok") - t.linkModeWeb = LinkModeIPv4 - if t.config.linkMode == LinkModeIntranet { - t.linkModeWeb = LinkModeIntranet - } - return ul, nil -} - -func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) { - gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode()) - defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode()) - ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) - if ts > PunchTsDelay || ts < 0 { - ts = PunchTsDelay - } - gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) - time.Sleep(ts) - startTime := time.Now() - t.linkModeWeb = LinkModeTCPPunch - gotCh := make(chan *underlayTCP, 1) - var wg sync.WaitGroup - var success atomic.Int32 - if t.config.peerNatType == NATSymmetric { // c2s - randPorts := rand.Perm(65532) - for i := 0; i < SymmetricHandshakeNum; i++ { - wg.Add(1) - go func(port int) { - defer wg.Done() - ul, err := dialTCP(t.config.peerIP, port, t.coneLocalPort, LinkModeTCPPunch) - if err != nil { - return - } - if !success.CompareAndSwap(0, 1) { - ul.Close() // only cone side close - return - } - err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) - if err != nil { - ul.Close() - return - } - _, buff, err := ul.ReadBuffer() - if err != nil { - gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) - return - } - req := P2PHandshakeReq{} - if err = json.Unmarshal(buff, &req); err != nil { - return - } - if req.ID != t.id { - return - } - gLog.Printf(LvINFO, "handshakeS2C TCP ok. cost %dms", time.Since(startTime)/time.Millisecond) - - gotCh <- ul - close(gotCh) - }(randPorts[i] + 2) - } - - } else { // s2c - for i := 0; i < SymmetricHandshakeNum; i++ { - wg.Add(1) - go func() { - defer wg.Done() - ul, err := dialTCP(t.config.peerIP, t.config.peerConeNatPort, 0, LinkModeTCPPunch) - if err != nil { - return - } - - _, buff, err := ul.ReadBuffer() - if err != nil { - gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) - return - } - req := P2PHandshakeReq{} - if err = json.Unmarshal(buff, &req); err != nil { - return - } - if req.ID != t.id { - return - } - err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) - if err != nil { - ul.Close() - return - } - if success.CompareAndSwap(0, 1) { - gotCh <- ul - close(gotCh) - } - }() - } - } - select { - case <-time.After(HandshakeTimeout): - return nil, fmt.Errorf("wait tcp handshake timeout") - case ul := <-gotCh: - return ul, nil - } -} - -func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) { - gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s start ", t.config.LogPeerNode()) - defer gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s end ", t.config.LogPeerNode()) - var ul *underlayTCP6 - if t.config.isUnderlayServer == 1 { - t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) - ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout) - if err != nil { - return nil, fmt.Errorf("listen TCP6 error:%s", err) - } - _, buff, err := ul.ReadBuffer() - if err != nil { - return nil, fmt.Errorf("read start msg error:%s", err) - } - if buff != nil { - gLog.Println(LvDEBUG, string(buff)) - } - ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) - gLog.Println(LvDEBUG, "TCP6 connection ok") - t.linkModeWeb = LinkModeIPv6 - return ul, nil - } - - //else - t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) - gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6) - ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort) - if err != nil { - return nil, fmt.Errorf("TCP6 dial to %s:%d error:%s", t.config.peerIPv6, t.config.peerConeNatPort, err) - } - handshakeBegin := time.Now() - ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) - _, buff, err := ul.ReadBuffer() - if err != nil { - return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) - } - if buff != nil { - gLog.Println(LvDEBUG, string(buff)) - } - - gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) - gLog.Println(LvINFO, "TCP6 connection ok") - t.linkModeWeb = LinkModeIPv6 - return ul, nil -} - -func (t *P2PTunnel) readLoop() { - decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding - gLog.Printf(LvDEBUG, "%d tunnel readloop start", t.id) - for t.isRuning() { - t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2)) - head, body, err := t.conn.ReadBuffer() - if err != nil { - if t.isRuning() { - gLog.Printf(LvERROR, "%d tunnel read error:%s", t.id, err) - } - break - } - if head.MainType != MsgP2P { - gLog.Printf(LvWARN, "%d head.MainType != MsgP2P", t.id) - continue - } - // TODO: replace some case implement to functions - switch head.SubType { - case MsgTunnelHeartbeat: - t.hbMtx.Lock() - t.hbTime = time.Now() - t.hbMtx.Unlock() - t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil) - gLog.Printf(LvDev, "%d read tunnel heartbeat", t.id) - case MsgTunnelHeartbeatAck: - t.hbMtx.Lock() - t.hbTime = time.Now() - t.hbMtx.Unlock() - gLog.Printf(LvDev, "%d read tunnel heartbeat ack", t.id) - case MsgOverlayData: - if len(body) < overlayHeaderSize { - gLog.Printf(LvWARN, "%d len(body) < overlayHeaderSize", t.id) - continue - } - overlayID := binary.LittleEndian.Uint64(body[:8]) - gLog.Printf(LvDev, "%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen) - s, ok := t.overlayConns.Load(overlayID) - if !ok { - // debug level, when overlay connection closed, always has some packet not found tunnel - gLog.Printf(LvDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID) - continue - } - overlayConn, ok := s.(*overlayConn) - if !ok { - continue - } - payload := body[overlayHeaderSize:] - var err error - if overlayConn.appKey != 0 { - payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize))) - } - _, err = overlayConn.Write(payload) - if err != nil { - gLog.Println(LvERROR, "overlay write error:", err) - } - case MsgNodeData: - t.handleNodeData(head, body, false) - case MsgRelayNodeData: - t.handleNodeData(head, body, true) - case MsgRelayData: - if len(body) < 8 { - continue - } - tunnelID := binary.LittleEndian.Uint64(body[:8]) - gLog.Printf(LvDev, "relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize) - if err := t.pn.relay(tunnelID, body[RelayHeaderSize:]); err != nil { - gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.LogPeerNode(), t.id, tunnelID, len(body), ErrRelayTunnelNotFound) - } - case MsgRelayHeartbeat: - req := RelayHeartbeat{} - if err := json.Unmarshal(body, &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - continue - } - // TODO: debug relay heartbeat - gLog.Printf(LvDEBUG, "read MsgRelayHeartbeat from rtid:%d,appid:%d", req.RelayTunnelID, req.AppID) - // update app hbtime - t.pn.updateAppHeartbeat(req.AppID) - req.From = gConf.Network.Node - t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req) - case MsgRelayHeartbeatAck: - req := RelayHeartbeat{} - err := json.Unmarshal(body, &req) - if err != nil { - gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err) - continue - } - // TODO: debug relay heartbeat - gLog.Printf(LvDEBUG, "read MsgRelayHeartbeatAck to appid:%d", req.AppID) - t.pn.updateAppHeartbeat(req.AppID) - case MsgOverlayConnectReq: - req := OverlayConnectReq{} - if err := json.Unmarshal(body, &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - continue - } - // app connect only accept token(not relay totp token), avoid someone using the share relay node's token - if req.Token != gConf.Network.Token { - gLog.Println(LvERROR, "Access Denied:", req.Token) - continue - } - - overlayID := req.ID - gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort) - oConn := overlayConn{ - tunnel: t, - id: overlayID, - isClient: false, - rtid: req.RelayTunnelID, - appID: req.AppID, - appKey: GetKey(req.AppID), - running: true, - } - if req.Protocol == "udp" { - oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(req.DstIP), Port: req.DstPort}) - } else { - oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), ReadMsgTimeout) - - } - if err != nil { - gLog.Println(LvERROR, err) - continue - } - - // calc key bytes for encrypt - if oConn.appKey != 0 { - encryptKey := make([]byte, AESKeySize) - binary.LittleEndian.PutUint64(encryptKey, oConn.appKey) - binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey) - oConn.appKeyBytes = encryptKey - } - - t.overlayConns.Store(oConn.id, &oConn) - go oConn.run() - case MsgOverlayDisconnectReq: - req := OverlayDisconnectReq{} - if err := json.Unmarshal(body, &req); err != nil { - gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) - continue - } - overlayID := req.ID - gLog.Printf(LvDEBUG, "%d disconnect overlay connection %d", t.id, overlayID) - i, ok := t.overlayConns.Load(overlayID) - if ok { - oConn := i.(*overlayConn) - oConn.Close() - } - default: - } - } - t.close() - gLog.Printf(LvDEBUG, "%d tunnel readloop end", t.id) -} - -func (t *P2PTunnel) writeLoop() { - t.hbMtx.Lock() - t.hbTime = time.Now() // init - t.hbMtx.Unlock() - tc := time.NewTicker(TunnelHeartbeatTime) - defer tc.Stop() - gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop start", t.config.LogPeerNode(), t.id) - defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.LogPeerNode(), t.id) - for t.isRuning() { - select { - case buff := <-t.writeDataSmall: - t.conn.WriteBuffer(buff) - // gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) - default: - select { - case buff := <-t.writeDataSmall: - t.conn.WriteBuffer(buff) - // gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) - case buff := <-t.writeData: - t.conn.WriteBuffer(buff) - case <-tc.C: - // tunnel send - err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil) - if err != nil { - gLog.Printf(LvERROR, "%d write tunnel heartbeat error %s", t.id, err) - t.close() - return - } - gLog.Printf(LvDev, "%d write tunnel heartbeat ok", t.id) - } - } - } -} - -func (t *P2PTunnel) listen() error { - // notify client to connect - rsp := PushConnectRsp{ - Error: 0, - Detail: "connect ok", - To: t.config.PeerNode, - From: gConf.Network.Node, - NatType: gConf.Network.natType, - HasIPv4: gConf.Network.hasIPv4, - // IPv6: gConf.Network.IPv6, - HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, - FromIP: gConf.Network.publicIP, - ConeNatPort: t.coneNatPort, - ID: t.id, - PunchTs: uint64(time.Now().UnixNano() + int64(PunchTsDelay) - t.pn.dt), - Version: OpenP2PVersion, - } - t.punchTs = rsp.PunchTs - // only private node set ipv6 - if t.config.fromToken == gConf.Network.Token { - rsp.IPv6 = gConf.IPv6() - } - - t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp) - gLog.Printf(LvDEBUG, "p2ptunnel wait for connecting") - t.tunnelServer = true - return t.start() -} - -func (t *P2PTunnel) closeOverlayConns(appID uint64) { - t.overlayConns.Range(func(_, i interface{}) bool { - oConn := i.(*overlayConn) - if oConn.appID == appID { - oConn.Close() - } - return true - }) -} - -func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) { - gLog.Printf(LvDev, "%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay) - ch := t.pn.nodeData - // if body[9] == 1 { // TODO: deal relay - // ch = t.pn.nodeDataSmall - // gLog.Printf(LvDEBUG, "read icmp %d", time.Now().Unix()) - // } - if isRelay { - fromPeerID := binary.LittleEndian.Uint64(body[:8]) - ch <- &NodeData{fromPeerID, body[8:]} // TODO: cache peerNodeID; encrypt/decrypt - } else { - ch <- &NodeData{NodeNameToID(t.config.PeerNode), body} // TODO: cache peerNodeID; encrypt/decrypt - } -} - -func (t *P2PTunnel) asyncWriteNodeData(mainType, subType uint16, data []byte) { - writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...) - // if len(data) < 192 { - if data[9] == 1 { // icmp - select { - case t.writeDataSmall <- writeBytes: - // gLog.Printf(LvWARN, "%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall)) - default: - gLog.Printf(LvWARN, "%s:%d t.writeDataSmall is full, drop it", t.config.LogPeerNode(), t.id) - } - } else { - select { - case t.writeData <- writeBytes: - default: - gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.LogPeerNode(), t.id) - } - } - -} - -func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, req interface{}) error { - if rtid == 0 { - return t.conn.WriteMessage(mainType, subType, &req) - } - relayHead := new(bytes.Buffer) - binary.Write(relayHead, binary.LittleEndian, rtid) - msg, _ := newMessage(mainType, subType, &req) - msgWithHead := append(relayHead.Bytes(), msg...) - return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead) - -} +package openp2p + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "math/rand" + "net" + "reflect" + "sync" + "sync/atomic" + "time" +) + +const WriteDataChanSize int = 3000 + +var buildTunnelMtx sync.Mutex + +type P2PTunnel struct { + pn *P2PNetwork + conn underlay + hbTime time.Time + hbMtx sync.Mutex + config AppConfig + la *net.UDPAddr // local hole address + ra *net.UDPAddr // remote hole address + overlayConns sync.Map // both TCP and UDP + id uint64 // client side alloc rand.uint64 = server side + running bool + runMtx sync.Mutex + tunnelServer bool // different from underlayServer + coneLocalPort int + coneNatPort int + linkModeWeb string // use config.linkmode + punchTs uint64 + writeData chan []byte + writeDataSmall chan []byte +} + +func (t *P2PTunnel) initPort() { + t.running = true + 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.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeIntranet { + t.coneLocalPort = gConf.Network.TCPPort + t.coneNatPort = gConf.Network.TCPPort // symmetric doesn't need coneNatPort + } + if t.config.linkMode == LinkModeUDPPunch { + // prepare one random cone hole manually + _, natPort, _ := natTest(gConf.Network.ServerHost, gConf.Network.UDPPort1, localPort) + t.coneLocalPort = localPort + t.coneNatPort = natPort + } + if t.config.linkMode == LinkModeTCPPunch { + // prepare one random cone hole by system automatically + _, natPort, localPort2 := natTCP(gConf.Network.ServerHost, IfconfigPort1) + t.coneLocalPort = localPort2 + t.coneNatPort = natPort + } + t.la = &net.UDPAddr{IP: net.ParseIP(gConf.Network.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.LogPeerNode()) + t.tunnelServer = false + appKey := uint64(0) + req := PushConnectReq{ + Token: t.config.peerToken, + From: gConf.Network.Node, + FromIP: gConf.Network.publicIP, + ConeNatPort: t.coneNatPort, + NatType: gConf.Network.natType, + HasIPv4: gConf.Network.hasIPv4, + IPv6: gConf.IPv6(), + HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, + ID: t.id, + AppKey: appKey, + Version: OpenP2PVersion, + LinkMode: t.config.linkMode, + IsUnderlayServer: t.config.isUnderlayServer ^ 1, // peer + UnderlayProtocol: t.config.UnderlayProtocol, + } + if req.Token == 0 { // no relay token + req.Token = gConf.Network.Token + } + t.pn.push(t.config.PeerNode, MsgPushConnectReq, req) + head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, UnderlayConnectTimeout*3) + if head == nil { + return errors.New("connect error") + } + rsp := PushConnectRsp{} + if err := json.Unmarshal(body, &rsp); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) + return err + } + // gLog.Println(LevelINFO, rsp) + if rsp.Error != 0 { + return errors.New(rsp.Detail) + } + t.config.peerNatType = rsp.NatType + t.config.hasIPv4 = rsp.HasIPv4 + t.config.peerIPv6 = rsp.IPv6 + t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP + t.config.peerVersion = rsp.Version + t.config.peerConeNatPort = rsp.ConeNatPort + t.config.peerIP = rsp.FromIP + t.punchTs = rsp.PunchTs + err := t.start() + if err != nil { + gLog.Println(LvERROR, "handshake error:", err) + } + return err +} + +func (t *P2PTunnel) isRuning() bool { + t.runMtx.Lock() + defer t.runMtx.Unlock() + return t.running +} + +func (t *P2PTunnel) setRun(running bool) { + t.runMtx.Lock() + defer t.runMtx.Unlock() + t.running = running +} + +func (t *P2PTunnel) isActive() bool { + if !t.isRuning() || t.conn == nil { + return false + } + t.hbMtx.Lock() + defer t.hbMtx.Unlock() + res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2)) + if !res { + gLog.Printf(LvDEBUG, "%d tunnel isActive false", t.id) + } + return res +} + +func (t *P2PTunnel) checkActive() bool { + if !t.isActive() { + return false + } + hbt := time.Now() + t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil) + isActive := false + // wait at most 5s + for i := 0; i < 50 && !isActive; i++ { + t.hbMtx.Lock() + if t.hbTime.After(hbt) { + isActive = true + } + t.hbMtx.Unlock() + time.Sleep(time.Millisecond * 100) + } + gLog.Printf(LvINFO, "checkActive %t. hbtime=%d", isActive, t.hbTime) + return isActive +} + +// call when user delete tunnel +func (t *P2PTunnel) close() { + t.pn.NotifyTunnelClose(t) + if !t.running { + return + } + t.setRun(false) + if t.conn != nil { + t.conn.Close() + } + t.pn.allTunnels.Delete(t.id) + gLog.Printf(LvINFO, "%d p2ptunnel close %s ", t.id, t.config.LogPeerNode()) +} + +func (t *P2PTunnel) start() error { + if t.config.linkMode == LinkModeUDPPunch { + if err := t.handshake(); err != nil { + return err + } + } + err := t.connectUnderlay() + if err != nil { + gLog.Println(LvERROR, err) + return err + } + return nil +} + +func (t *P2PTunnel) handshake() error { + 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 { + return err + } + } + if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { + gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) + } else { + ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) + if ts > PunchTsDelay || ts < 0 { + ts = PunchTsDelay + } + gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) + time.Sleep(ts) + } + gLog.Println(LvDEBUG, "handshake to ", t.config.LogPeerNode()) + var err error + if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone { + err = handshakeC2C(t) + } else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATSymmetric { + err = ErrorS2S + t.close() + } else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATCone { + err = handshakeC2S(t) + } else if t.config.peerNatType == NATCone && gConf.Network.natType == NATSymmetric { + err = handshakeS2C(t) + } else { + return errors.New("unknown error") + } + if err != nil { + gLog.Println(LvERROR, "punch handshake error:", err) + return err + } + gLog.Printf(LvDEBUG, "handshake to %s ok", t.config.LogPeerNode()) + return nil +} + +func (t *P2PTunnel) connectUnderlay() (err error) { + switch t.config.linkMode { + case LinkModeTCP6: + t.conn, err = t.connectUnderlayTCP6() + case LinkModeTCP4: + t.conn, err = t.connectUnderlayTCP() + case LinkModeTCPPunch: + if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric { + t.conn, err = t.connectUnderlayTCPSymmetric() + } else { + t.conn, err = t.connectUnderlayTCP() + } + case LinkModeIntranet: + t.conn, err = t.connectUnderlayTCP() + case LinkModeUDPPunch: + t.conn, err = t.connectUnderlayUDP() + + } + if err != nil { + return err + } + if t.conn == nil { + return errors.New("connect underlay error") + } + t.setRun(true) + go t.readLoop() + go t.writeLoop() + return nil +} + +func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) { + gLog.Printf(LvDEBUG, "connectUnderlayUDP %s start ", t.config.LogPeerNode()) + defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.LogPeerNode()) + var ul underlay + underlayProtocol := t.config.UnderlayProtocol + if underlayProtocol == "" { + underlayProtocol = "quic" + } + if t.config.isUnderlayServer == 1 { + time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env + go t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) + if t.config.UnderlayProtocol == "kcp" { + ul, err = listenKCP(t.la.String(), TunnelIdleTimeout) + } else { + ul, err = listenQuic(t.la.String(), TunnelIdleTimeout) + } + + if err != nil { + gLog.Printf(LvINFO, "listen %s error:%s", underlayProtocol, err) + return nil, err + } + + _, buff, err := ul.ReadBuffer() + if err != nil { + ul.Close() + return nil, fmt.Errorf("read start msg error:%s", err) + } + if buff != nil { + gLog.Println(LvDEBUG, string(buff)) + } + ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) + gLog.Printf(LvDEBUG, "%s connection ok", underlayProtocol) + return ul, nil + } + + //else + conn, errL := net.ListenUDP("udp", t.la) + if errL != nil { + time.Sleep(time.Millisecond * 10) + conn, errL = net.ListenUDP("udp", t.la) + if errL != nil { + return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL) + } + } + t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) + gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.ra.String()) + if t.config.UnderlayProtocol == "kcp" { + ul, errL = dialKCP(conn, t.ra, TunnelIdleTimeout) + } else { + ul, errL = dialQuic(conn, t.ra, TunnelIdleTimeout) + } + + if errL != nil { + return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.ra.String(), errL) + } + handshakeBegin := time.Now() + ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) + _, buff, err := ul.ReadBuffer() // TODO: kcp need timeout + if err != nil { + ul.Close() + return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) + } + if buff != nil { + gLog.Println(LvDEBUG, string(buff)) + } + + gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) + gLog.Printf(LvINFO, "%s connection ok", underlayProtocol) + t.linkModeWeb = LinkModeUDPPunch + return ul, nil +} + +func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) { + gLog.Printf(LvDEBUG, "connectUnderlayTCP %s start ", t.config.LogPeerNode()) + defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.LogPeerNode()) + var ul *underlayTCP + peerIP := t.config.peerIP + if t.config.linkMode == LinkModeIntranet { + peerIP = t.config.peerLanIP + } + // server side + if t.config.isUnderlayServer == 1 { + ul, err = listenTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode, t) + if err != nil { + return nil, fmt.Errorf("listen TCP error:%s", err) + } + gLog.Println(LvINFO, "TCP connection ok") + t.linkModeWeb = LinkModeIPv4 + if t.config.linkMode == LinkModeIntranet { + t.linkModeWeb = LinkModeIntranet + } + return ul, nil + } + + // client side + if t.config.linkMode == LinkModeTCP4 { + t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) + } else { //tcp punch should sleep for punch the same time + if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { + gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) + } else { + ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) + if ts > PunchTsDelay || ts < 0 { + ts = PunchTsDelay + } + gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) + time.Sleep(ts) + } + } + ul, err = dialTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode) + if err != nil { + return nil, fmt.Errorf("TCP dial to %s:%d error:%s", t.config.peerIP, t.config.peerConeNatPort, err) + } + handshakeBegin := time.Now() + tidBuff := new(bytes.Buffer) + binary.Write(tidBuff, binary.LittleEndian, t.id) + ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID + _, buff, err := ul.ReadBuffer() + if err != nil { + return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) + } + if buff != nil { + gLog.Println(LvDEBUG, "hello ", string(buff)) + } + + gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) + gLog.Println(LvINFO, "TCP connection ok") + t.linkModeWeb = LinkModeIPv4 + if t.config.linkMode == LinkModeIntranet { + t.linkModeWeb = LinkModeIntranet + } + return ul, nil +} + +func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) { + gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode()) + defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode()) + ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) + if ts > PunchTsDelay || ts < 0 { + ts = PunchTsDelay + } + gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) + time.Sleep(ts) + startTime := time.Now() + t.linkModeWeb = LinkModeTCPPunch + gotCh := make(chan *underlayTCP, 1) + var wg sync.WaitGroup + var success atomic.Int32 + if t.config.peerNatType == NATSymmetric { // c2s + randPorts := rand.Perm(65532) + for i := 0; i < SymmetricHandshakeNum; i++ { + wg.Add(1) + go func(port int) { + defer wg.Done() + ul, err := dialTCP(t.config.peerIP, port, t.coneLocalPort, LinkModeTCPPunch) + if err != nil { + return + } + if !success.CompareAndSwap(0, 1) { + ul.Close() // only cone side close + return + } + err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) + if err != nil { + ul.Close() + return + } + _, buff, err := ul.ReadBuffer() + if err != nil { + gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) + return + } + req := P2PHandshakeReq{} + if err = json.Unmarshal(buff, &req); err != nil { + return + } + if req.ID != t.id { + return + } + gLog.Printf(LvINFO, "handshakeS2C TCP ok. cost %dms", time.Since(startTime)/time.Millisecond) + + gotCh <- ul + close(gotCh) + }(randPorts[i] + 2) + } + + } else { // s2c + for i := 0; i < SymmetricHandshakeNum; i++ { + wg.Add(1) + go func() { + defer wg.Done() + ul, err := dialTCP(t.config.peerIP, t.config.peerConeNatPort, 0, LinkModeTCPPunch) + if err != nil { + return + } + + _, buff, err := ul.ReadBuffer() + if err != nil { + gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) + return + } + req := P2PHandshakeReq{} + if err = json.Unmarshal(buff, &req); err != nil { + return + } + if req.ID != t.id { + return + } + err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) + if err != nil { + ul.Close() + return + } + if success.CompareAndSwap(0, 1) { + gotCh <- ul + close(gotCh) + } + }() + } + } + select { + case <-time.After(HandshakeTimeout): + return nil, fmt.Errorf("wait tcp handshake timeout") + case ul := <-gotCh: + return ul, nil + } +} + +func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) { + gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s start ", t.config.LogPeerNode()) + defer gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s end ", t.config.LogPeerNode()) + var ul *underlayTCP6 + if t.config.isUnderlayServer == 1 { + t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) + ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout) + if err != nil { + return nil, fmt.Errorf("listen TCP6 error:%s", err) + } + _, buff, err := ul.ReadBuffer() + if err != nil { + return nil, fmt.Errorf("read start msg error:%s", err) + } + if buff != nil { + gLog.Println(LvDEBUG, string(buff)) + } + ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) + gLog.Println(LvDEBUG, "TCP6 connection ok") + t.linkModeWeb = LinkModeIPv6 + return ul, nil + } + + //else + t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) + gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6) + ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort) + if err != nil { + return nil, fmt.Errorf("TCP6 dial to %s:%d error:%s", t.config.peerIPv6, t.config.peerConeNatPort, err) + } + handshakeBegin := time.Now() + ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) + _, buff, err := ul.ReadBuffer() + if err != nil { + return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) + } + if buff != nil { + gLog.Println(LvDEBUG, string(buff)) + } + + gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) + gLog.Println(LvINFO, "TCP6 connection ok") + t.linkModeWeb = LinkModeIPv6 + return ul, nil +} + +func (t *P2PTunnel) readLoop() { + decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding + gLog.Printf(LvDEBUG, "%d tunnel readloop start", t.id) + for t.isRuning() { + t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2)) + head, body, err := t.conn.ReadBuffer() + if err != nil { + if t.isRuning() { + gLog.Printf(LvERROR, "%d tunnel read error:%s", t.id, err) + } + break + } + if head.MainType != MsgP2P { + gLog.Printf(LvWARN, "%d head.MainType != MsgP2P", t.id) + continue + } + // TODO: replace some case implement to functions + switch head.SubType { + case MsgTunnelHeartbeat: + t.hbMtx.Lock() + t.hbTime = time.Now() + t.hbMtx.Unlock() + t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil) + gLog.Printf(LvDev, "%d read tunnel heartbeat", t.id) + case MsgTunnelHeartbeatAck: + t.hbMtx.Lock() + t.hbTime = time.Now() + t.hbMtx.Unlock() + gLog.Printf(LvDev, "%d read tunnel heartbeat ack", t.id) + case MsgOverlayData: + if len(body) < overlayHeaderSize { + gLog.Printf(LvWARN, "%d len(body) < overlayHeaderSize", t.id) + continue + } + overlayID := binary.LittleEndian.Uint64(body[:8]) + gLog.Printf(LvDev, "%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen) + s, ok := t.overlayConns.Load(overlayID) + if !ok { + // debug level, when overlay connection closed, always has some packet not found tunnel + gLog.Printf(LvDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID) + continue + } + overlayConn, ok := s.(*overlayConn) + if !ok { + continue + } + payload := body[overlayHeaderSize:] + var err error + if overlayConn.appKey != 0 { + payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize))) + } + _, err = overlayConn.Write(payload) + if err != nil { + gLog.Println(LvERROR, "overlay write error:", err) + } + case MsgNodeData: + t.handleNodeData(head, body, false) + case MsgRelayNodeData: + t.handleNodeData(head, body, true) + case MsgRelayData: + if len(body) < 8 { + continue + } + tunnelID := binary.LittleEndian.Uint64(body[:8]) + gLog.Printf(LvDev, "relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize) + if err := t.pn.relay(tunnelID, body[RelayHeaderSize:]); err != nil { + gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.LogPeerNode(), t.id, tunnelID, len(body), ErrRelayTunnelNotFound) + } + case MsgRelayHeartbeat: + req := RelayHeartbeat{} + if err := json.Unmarshal(body, &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + continue + } + // TODO: debug relay heartbeat + gLog.Printf(LvDEBUG, "read MsgRelayHeartbeat from rtid:%d,appid:%d", req.RelayTunnelID, req.AppID) + // update app hbtime + t.pn.updateAppHeartbeat(req.AppID) + req.From = gConf.Network.Node + t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req) + case MsgRelayHeartbeatAck: + req := RelayHeartbeat{} + err := json.Unmarshal(body, &req) + if err != nil { + gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err) + continue + } + // TODO: debug relay heartbeat + gLog.Printf(LvDEBUG, "read MsgRelayHeartbeatAck to appid:%d", req.AppID) + t.pn.updateAppHeartbeat(req.AppID) + case MsgOverlayConnectReq: + req := OverlayConnectReq{} + if err := json.Unmarshal(body, &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + continue + } + // app connect only accept token(not relay totp token), avoid someone using the share relay node's token + if req.Token != gConf.Network.Token { + gLog.Println(LvERROR, "Access Denied:", req.Token) + continue + } + + overlayID := req.ID + gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort) + oConn := overlayConn{ + tunnel: t, + id: overlayID, + isClient: false, + rtid: req.RelayTunnelID, + appID: req.AppID, + appKey: GetKey(req.AppID), + running: true, + } + if req.Protocol == "udp" { + oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(req.DstIP), Port: req.DstPort}) + } else { + oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), ReadMsgTimeout) + + } + if err != nil { + gLog.Println(LvERROR, err) + continue + } + + // calc key bytes for encrypt + if oConn.appKey != 0 { + encryptKey := make([]byte, AESKeySize) + binary.LittleEndian.PutUint64(encryptKey, oConn.appKey) + binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey) + oConn.appKeyBytes = encryptKey + } + + t.overlayConns.Store(oConn.id, &oConn) + go oConn.run() + case MsgOverlayDisconnectReq: + req := OverlayDisconnectReq{} + if err := json.Unmarshal(body, &req); err != nil { + gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) + continue + } + overlayID := req.ID + gLog.Printf(LvDEBUG, "%d disconnect overlay connection %d", t.id, overlayID) + i, ok := t.overlayConns.Load(overlayID) + if ok { + oConn := i.(*overlayConn) + oConn.Close() + } + default: + } + } + t.close() + gLog.Printf(LvDEBUG, "%d tunnel readloop end", t.id) +} + +func (t *P2PTunnel) writeLoop() { + t.hbMtx.Lock() + t.hbTime = time.Now() // init + t.hbMtx.Unlock() + tc := time.NewTicker(TunnelHeartbeatTime) + defer tc.Stop() + gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop start", t.config.LogPeerNode(), t.id) + defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.LogPeerNode(), t.id) + for t.isRuning() { + select { + case buff := <-t.writeDataSmall: + t.conn.WriteBuffer(buff) + // gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) + default: + select { + case buff := <-t.writeDataSmall: + t.conn.WriteBuffer(buff) + // gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) + case buff := <-t.writeData: + t.conn.WriteBuffer(buff) + case <-tc.C: + // tunnel send + err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil) + if err != nil { + gLog.Printf(LvERROR, "%d write tunnel heartbeat error %s", t.id, err) + t.close() + return + } + gLog.Printf(LvDev, "%d write tunnel heartbeat ok", t.id) + } + } + } +} + +func (t *P2PTunnel) listen() error { + // notify client to connect + rsp := PushConnectRsp{ + Error: 0, + Detail: "connect ok", + To: t.config.PeerNode, + From: gConf.Network.Node, + NatType: gConf.Network.natType, + HasIPv4: gConf.Network.hasIPv4, + // IPv6: gConf.Network.IPv6, + HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, + FromIP: gConf.Network.publicIP, + ConeNatPort: t.coneNatPort, + ID: t.id, + PunchTs: uint64(time.Now().UnixNano() + int64(PunchTsDelay) - t.pn.dt), + Version: OpenP2PVersion, + } + t.punchTs = rsp.PunchTs + // only private node set ipv6 + if t.config.fromToken == gConf.Network.Token { + rsp.IPv6 = gConf.IPv6() + } + + t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp) + gLog.Printf(LvDEBUG, "p2ptunnel wait for connecting") + t.tunnelServer = true + return t.start() +} + +func (t *P2PTunnel) closeOverlayConns(appID uint64) { + t.overlayConns.Range(func(_, i interface{}) bool { + oConn := i.(*overlayConn) + if oConn.appID == appID { + oConn.Close() + } + return true + }) +} + +func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) { + gLog.Printf(LvDev, "%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay) + ch := t.pn.nodeData + // if body[9] == 1 { // TODO: deal relay + // ch = t.pn.nodeDataSmall + // gLog.Printf(LvDEBUG, "read icmp %d", time.Now().Unix()) + // } + if isRelay { + fromPeerID := binary.LittleEndian.Uint64(body[:8]) + ch <- &NodeData{fromPeerID, body[8:]} // TODO: cache peerNodeID; encrypt/decrypt + } else { + ch <- &NodeData{NodeNameToID(t.config.PeerNode), body} // TODO: cache peerNodeID; encrypt/decrypt + } +} + +func (t *P2PTunnel) asyncWriteNodeData(mainType, subType uint16, data []byte) { + writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...) + // if len(data) < 192 { + if data[9] == 1 { // icmp + select { + case t.writeDataSmall <- writeBytes: + // gLog.Printf(LvWARN, "%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall)) + default: + gLog.Printf(LvWARN, "%s:%d t.writeDataSmall is full, drop it", t.config.LogPeerNode(), t.id) + } + } else { + select { + case t.writeData <- writeBytes: + default: + gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.LogPeerNode(), t.id) + } + } + +} + +func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, req interface{}) error { + if rtid == 0 { + return t.conn.WriteMessage(mainType, subType, &req) + } + relayHead := new(bytes.Buffer) + binary.Write(relayHead, binary.LittleEndian, rtid) + msg, _ := newMessage(mainType, subType, &req) + msgWithHead := append(relayHead.Bytes(), msg...) + return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead) + +} diff --git a/core/sdwan.go b/core/sdwan.go index 6cc75ed..033bedc 100644 --- a/core/sdwan.go +++ b/core/sdwan.go @@ -1,292 +1,292 @@ -package openp2p - -import ( - "encoding/binary" - "encoding/json" - "fmt" - "net" - "runtime" - "strings" - "sync" - "time" -) - -type PacketHeader struct { - version int - // src uint32 - // prot uint8 - protocol byte - dst uint32 - port uint16 -} - -func parseHeader(b []byte, h *PacketHeader) error { - if len(b) < 20 { - return fmt.Errorf("small packet") - } - h.version = int(b[0] >> 4) - h.protocol = byte(b[9]) - if h.version == 4 { - h.dst = binary.BigEndian.Uint32(b[16:20]) - } else if h.version != 6 { - return fmt.Errorf("unknown version in ip header:%d", h.version) - } - if h.protocol == 6 || h.protocol == 17 { // TCP or UDP - h.port = binary.BigEndian.Uint16(b[22:24]) - } - return nil -} - -type sdwanNode struct { - name string - id uint64 -} - -type p2pSDWAN struct { - nodeName string - tun *optun - sysRoute sync.Map // ip:sdwanNode - subnet *net.IPNet - gateway net.IP - virtualIP *net.IPNet - internalRoute *IPTree -} - -func (s *p2pSDWAN) init(name string) error { - if gConf.getSDWAN().Gateway == "" { - gLog.Println(LvDEBUG, "not in sdwan clear all ") - } - if s.internalRoute == nil { - s.internalRoute = NewIPTree("") - } - - s.nodeName = name - s.gateway, s.subnet, _ = net.ParseCIDR(gConf.getSDWAN().Gateway) - for _, node := range gConf.getDelNodes() { - gLog.Println(LvDEBUG, "deal deleted node: ", node.Name) - delRoute(node.IP, s.gateway.String()) - s.internalRoute.Del(node.IP, node.IP) - ipNum, _ := inetAtoN(node.IP) - s.sysRoute.Delete(ipNum) - gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name}) - GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name}) - arr := strings.Split(node.Resource, ",") - for _, r := range arr { - _, ipnet, err := net.ParseCIDR(r) - if err != nil { - // fmt.Println("Error parsing CIDR:", err) - continue - } - if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan - continue - } - minIP := ipnet.IP - maxIP := make(net.IP, len(minIP)) - copy(maxIP, minIP) - for i := range minIP { - maxIP[i] = minIP[i] | ^ipnet.Mask[i] - } - s.internalRoute.Del(minIP.String(), maxIP.String()) - delRoute(ipnet.String(), s.gateway.String()) - } - } - for _, node := range gConf.getAddNodes() { - gLog.Println(LvDEBUG, "deal add node: ", node.Name) - ipNet := &net.IPNet{ - IP: net.ParseIP(node.IP), - Mask: s.subnet.Mask, - } - if node.Name == s.nodeName { - s.virtualIP = ipNet - gLog.Println(LvINFO, "start tun ", ipNet.String()) - err := s.StartTun() - if err != nil { - gLog.Println(LvERROR, "start tun error:", err) - return err - } - gLog.Println(LvINFO, "start tun ok") - allowTunForward() - addRoute(s.subnet.String(), s.gateway.String(), s.tun.tunName) - // addRoute("255.255.255.255/32", s.gateway.String(), s.tun.tunName) // for broadcast - // addRoute("224.0.0.0/4", s.gateway.String(), s.tun.tunName) // for multicast - initSNATRule(s.subnet.String()) // for network resource - continue - } - ip, err := inetAtoN(ipNet.String()) - if err != nil { - return err - } - s.sysRoute.Store(ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) - s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) - } - for _, node := range gConf.getAddNodes() { - if node.Name == s.nodeName { // not deal resource itself - continue - } - if len(node.Resource) > 0 { - gLog.Printf(LvINFO, "deal add node: %s resource: %s", node.Name, node.Resource) - arr := strings.Split(node.Resource, ",") - for _, r := range arr { - // add internal route - _, ipnet, err := net.ParseCIDR(r) - if err != nil { - fmt.Println("Error parsing CIDR:", err) - continue - } - if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan - continue - } - minIP := ipnet.IP - maxIP := make(net.IP, len(minIP)) - copy(maxIP, minIP) - for i := range minIP { - maxIP[i] = minIP[i] | ^ipnet.Mask[i] - } - s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) - // add sys route - addRoute(ipnet.String(), s.gateway.String(), s.tun.tunName) - } - } - } - gConf.retryAllMemApp() - gLog.Printf(LvINFO, "sdwan init ok") - return nil -} - -func (s *p2pSDWAN) run() { - s.sysRoute.Range(func(key, value interface{}) bool { - node := value.(*sdwanNode) - GNetwork.ConnectNode(node.name) - return true - }) -} - -func (s *p2pSDWAN) readNodeLoop() { - gLog.Printf(LvDEBUG, "sdwan readNodeLoop start") - defer gLog.Printf(LvDEBUG, "sdwan readNodeLoop end") - writeBuff := make([][]byte, 1) - for { - nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet - if nd == nil { - gLog.Printf(LvDev, "waiting for node data") - continue - } - head := PacketHeader{} - parseHeader(nd.Data, &head) - gLog.Printf(LvDev, "write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd.Data)) - if PIHeaderSize == 0 { - writeBuff[0] = nd.Data - } else { - writeBuff[0] = make([]byte, PIHeaderSize+len(nd.Data)) - copy(writeBuff[0][PIHeaderSize:], nd.Data) - } - - len, err := s.tun.Write(writeBuff, PIHeaderSize) - if err != nil { - gLog.Printf(LvDEBUG, "write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err) - } - } -} - -func isBroadcastOrMulticast(ipUint32 uint32, subnet *net.IPNet) bool { - // return ipUint32 == 0xffffffff || (byte(ipUint32) == 0xff) || (ipUint32>>28 == 0xe) - return ipUint32 == 0xffffffff || (ipUint32>>28 == 0xe) // 225.255.255.255/32, 224.0.0.0/4 -} - -func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) { - var node *sdwanNode - // v, ok := s.routes.Load(ih.dst) - v, ok := s.internalRoute.Load(head.dst) - if !ok || v == nil { - if isBroadcastOrMulticast(head.dst, s.subnet) { - gLog.Printf(LvDev, "multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String()) - GNetwork.WriteBroadcast(p) - } - return - } else { - node = v.(*sdwanNode) - } - - err := GNetwork.WriteNode(node.id, p) - if err != nil { - gLog.Printf(LvDev, "write packet to %s fail: %s", node.name, err) - } -} - -func (s *p2pSDWAN) readTunLoop() { - gLog.Printf(LvDEBUG, "sdwan readTunLoop start") - defer gLog.Printf(LvDEBUG, "sdwan readTunLoop end") - readBuff := make([][]byte, ReadTunBuffNum) - for i := 0; i < ReadTunBuffNum; i++ { - readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize) - } - readBuffSize := make([]int, ReadTunBuffNum) - ih := PacketHeader{} - for { - n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize) - if err != nil { - gLog.Printf(LvERROR, "read tun fail: ", err) - return - } - for i := 0; i < n; i++ { - if readBuffSize[i] > ReadTunBuffSize { - gLog.Printf(LvERROR, "read tun overflow: len=", readBuffSize[i]) - continue - } - parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) - gLog.Printf(LvDev, "read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0]) - s.routeTunPacket(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) - } - } -} - -func (s *p2pSDWAN) StartTun() error { - sdwan := gConf.getSDWAN() - if s.tun == nil { - tun := &optun{} - err := tun.Start(s.virtualIP.String(), &sdwan) - if err != nil { - gLog.Println(LvERROR, "open tun fail:", err) - return err - } - s.tun = tun - go s.readTunLoop() - go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds - } - err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev) - if err != nil { - gLog.Printf(LvERROR, "setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway) - return err - } - return nil -} - -func handleSDWAN(subType uint16, msg []byte) error { - gLog.Printf(LvDEBUG, "handle sdwan msg type:%d", subType) - var err error - switch subType { - case MsgSDWANInfoRsp: - rsp := SDWANInfo{} - if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { - return ErrMsgFormat - } - gLog.Println(LvINFO, "sdwan init:", prettyJson(rsp)) - if runtime.GOOS == "android" { - AndroidSDWANConfig <- msg[openP2PHeaderSize:] - } - // GNetwork.sdwan.detail = &rsp - gConf.setSDWAN(rsp) - err = GNetwork.sdwan.init(gConf.Network.Node) - if err != nil { - gLog.Println(LvERROR, "sdwan init fail: ", err) - if GNetwork.sdwan.tun != nil { - GNetwork.sdwan.tun.Stop() - GNetwork.sdwan.tun = nil - return err - } - } - go GNetwork.sdwan.run() - default: - } - return err -} +package openp2p + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "net" + "runtime" + "strings" + "sync" + "time" +) + +type PacketHeader struct { + version int + // src uint32 + // prot uint8 + protocol byte + dst uint32 + port uint16 +} + +func parseHeader(b []byte, h *PacketHeader) error { + if len(b) < 20 { + return fmt.Errorf("small packet") + } + h.version = int(b[0] >> 4) + h.protocol = byte(b[9]) + if h.version == 4 { + h.dst = binary.BigEndian.Uint32(b[16:20]) + } else if h.version != 6 { + return fmt.Errorf("unknown version in ip header:%d", h.version) + } + if h.protocol == 6 || h.protocol == 17 { // TCP or UDP + h.port = binary.BigEndian.Uint16(b[22:24]) + } + return nil +} + +type sdwanNode struct { + name string + id uint64 +} + +type p2pSDWAN struct { + nodeName string + tun *optun + sysRoute sync.Map // ip:sdwanNode + subnet *net.IPNet + gateway net.IP + virtualIP *net.IPNet + internalRoute *IPTree +} + +func (s *p2pSDWAN) init(name string) error { + if gConf.getSDWAN().Gateway == "" { + gLog.Println(LvDEBUG, "not in sdwan clear all ") + } + if s.internalRoute == nil { + s.internalRoute = NewIPTree("") + } + + s.nodeName = name + s.gateway, s.subnet, _ = net.ParseCIDR(gConf.getSDWAN().Gateway) + for _, node := range gConf.getDelNodes() { + gLog.Println(LvDEBUG, "deal deleted node: ", node.Name) + delRoute(node.IP, s.gateway.String()) + s.internalRoute.Del(node.IP, node.IP) + ipNum, _ := inetAtoN(node.IP) + s.sysRoute.Delete(ipNum) + gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name}) + GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name}) + arr := strings.Split(node.Resource, ",") + for _, r := range arr { + _, ipnet, err := net.ParseCIDR(r) + if err != nil { + // fmt.Println("Error parsing CIDR:", err) + continue + } + if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan + continue + } + minIP := ipnet.IP + maxIP := make(net.IP, len(minIP)) + copy(maxIP, minIP) + for i := range minIP { + maxIP[i] = minIP[i] | ^ipnet.Mask[i] + } + s.internalRoute.Del(minIP.String(), maxIP.String()) + delRoute(ipnet.String(), s.gateway.String()) + } + } + for _, node := range gConf.getAddNodes() { + gLog.Println(LvDEBUG, "deal add node: ", node.Name) + ipNet := &net.IPNet{ + IP: net.ParseIP(node.IP), + Mask: s.subnet.Mask, + } + if node.Name == s.nodeName { + s.virtualIP = ipNet + gLog.Println(LvINFO, "start tun ", ipNet.String()) + err := s.StartTun() + if err != nil { + gLog.Println(LvERROR, "start tun error:", err) + return err + } + gLog.Println(LvINFO, "start tun ok") + allowTunForward() + addRoute(s.subnet.String(), s.gateway.String(), s.tun.tunName) + // addRoute("255.255.255.255/32", s.gateway.String(), s.tun.tunName) // for broadcast + // addRoute("224.0.0.0/4", s.gateway.String(), s.tun.tunName) // for multicast + initSNATRule(s.subnet.String()) // for network resource + continue + } + ip, err := inetAtoN(ipNet.String()) + if err != nil { + return err + } + s.sysRoute.Store(ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) + s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) + } + for _, node := range gConf.getAddNodes() { + if node.Name == s.nodeName { // not deal resource itself + continue + } + if len(node.Resource) > 0 { + gLog.Printf(LvINFO, "deal add node: %s resource: %s", node.Name, node.Resource) + arr := strings.Split(node.Resource, ",") + for _, r := range arr { + // add internal route + _, ipnet, err := net.ParseCIDR(r) + if err != nil { + fmt.Println("Error parsing CIDR:", err) + continue + } + if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan + continue + } + minIP := ipnet.IP + maxIP := make(net.IP, len(minIP)) + copy(maxIP, minIP) + for i := range minIP { + maxIP[i] = minIP[i] | ^ipnet.Mask[i] + } + s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) + // add sys route + addRoute(ipnet.String(), s.gateway.String(), s.tun.tunName) + } + } + } + gConf.retryAllMemApp() + gLog.Printf(LvINFO, "sdwan init ok") + return nil +} + +func (s *p2pSDWAN) run() { + s.sysRoute.Range(func(key, value interface{}) bool { + node := value.(*sdwanNode) + GNetwork.ConnectNode(node.name) + return true + }) +} + +func (s *p2pSDWAN) readNodeLoop() { + gLog.Printf(LvDEBUG, "sdwan readNodeLoop start") + defer gLog.Printf(LvDEBUG, "sdwan readNodeLoop end") + writeBuff := make([][]byte, 1) + for { + nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet + if nd == nil { + gLog.Printf(LvDev, "waiting for node data") + continue + } + head := PacketHeader{} + parseHeader(nd.Data, &head) + gLog.Printf(LvDev, "write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd.Data)) + if PIHeaderSize == 0 { + writeBuff[0] = nd.Data + } else { + writeBuff[0] = make([]byte, PIHeaderSize+len(nd.Data)) + copy(writeBuff[0][PIHeaderSize:], nd.Data) + } + + len, err := s.tun.Write(writeBuff, PIHeaderSize) + if err != nil { + gLog.Printf(LvDEBUG, "write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err) + } + } +} + +func isBroadcastOrMulticast(ipUint32 uint32, subnet *net.IPNet) bool { + // return ipUint32 == 0xffffffff || (byte(ipUint32) == 0xff) || (ipUint32>>28 == 0xe) + return ipUint32 == 0xffffffff || (ipUint32>>28 == 0xe) // 225.255.255.255/32, 224.0.0.0/4 +} + +func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) { + var node *sdwanNode + // v, ok := s.routes.Load(ih.dst) + v, ok := s.internalRoute.Load(head.dst) + if !ok || v == nil { + if isBroadcastOrMulticast(head.dst, s.subnet) { + gLog.Printf(LvDev, "multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String()) + GNetwork.WriteBroadcast(p) + } + return + } else { + node = v.(*sdwanNode) + } + + err := GNetwork.WriteNode(node.id, p) + if err != nil { + gLog.Printf(LvDev, "write packet to %s fail: %s", node.name, err) + } +} + +func (s *p2pSDWAN) readTunLoop() { + gLog.Printf(LvDEBUG, "sdwan readTunLoop start") + defer gLog.Printf(LvDEBUG, "sdwan readTunLoop end") + readBuff := make([][]byte, ReadTunBuffNum) + for i := 0; i < ReadTunBuffNum; i++ { + readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize) + } + readBuffSize := make([]int, ReadTunBuffNum) + ih := PacketHeader{} + for { + n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize) + if err != nil { + gLog.Printf(LvERROR, "read tun fail: ", err) + return + } + for i := 0; i < n; i++ { + if readBuffSize[i] > ReadTunBuffSize { + gLog.Printf(LvERROR, "read tun overflow: len=", readBuffSize[i]) + continue + } + parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) + gLog.Printf(LvDev, "read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0]) + s.routeTunPacket(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) + } + } +} + +func (s *p2pSDWAN) StartTun() error { + sdwan := gConf.getSDWAN() + if s.tun == nil { + tun := &optun{} + err := tun.Start(s.virtualIP.String(), &sdwan) + if err != nil { + gLog.Println(LvERROR, "open tun fail:", err) + return err + } + s.tun = tun + go s.readTunLoop() + go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds + } + err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev) + if err != nil { + gLog.Printf(LvERROR, "setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway) + return err + } + return nil +} + +func handleSDWAN(subType uint16, msg []byte) error { + gLog.Printf(LvDEBUG, "handle sdwan msg type:%d", subType) + var err error + switch subType { + case MsgSDWANInfoRsp: + rsp := SDWANInfo{} + if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { + return ErrMsgFormat + } + gLog.Println(LvINFO, "sdwan init:", prettyJson(rsp)) + if runtime.GOOS == "android" { + AndroidSDWANConfig <- msg[openP2PHeaderSize:] + } + // GNetwork.sdwan.detail = &rsp + gConf.setSDWAN(rsp) + err = GNetwork.sdwan.init(gConf.Network.Node) + if err != nil { + gLog.Println(LvERROR, "sdwan init fail: ", err) + if GNetwork.sdwan.tun != nil { + GNetwork.sdwan.tun.Stop() + GNetwork.sdwan.tun = nil + return err + } + } + go GNetwork.sdwan.run() + default: + } + return err +} diff --git a/core/update.go b/core/update.go index fa0a62c..6e9ecaf 100644 --- a/core/update.go +++ b/core/update.go @@ -1,239 +1,239 @@ -package openp2p - -import ( - "archive/tar" - "archive/zip" - "compress/gzip" - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path/filepath" - "runtime" - "time" -) - -func update(host string, port int) error { - gLog.Println(LvINFO, "update start") - defer gLog.Println(LvINFO, "update end") - caCertPool, err := x509.SystemCertPool() - if err != nil { - gLog.Println(LvERROR, "Failed to load system root CAs:", err) - } else { - caCertPool = x509.NewCertPool() - } - caCertPool.AppendCertsFromPEM([]byte(rootCA)) - caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) - - c := http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{RootCAs: caCertPool, - InsecureSkipVerify: false}, - }, - Timeout: time.Second * 30, - } - goos := runtime.GOOS - goarch := runtime.GOARCH - rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s&user=%s&node=%s", host, port, OpenP2PVersion, goos, goarch, url.QueryEscape(gConf.Network.User), url.QueryEscape(gConf.Network.Node))) - if err != nil { - gLog.Println(LvERROR, "update:query update list failed:", err) - return err - } - defer rsp.Body.Close() - if rsp.StatusCode != http.StatusOK { - gLog.Println(LvERROR, "get update info error:", rsp.Status) - return err - } - rspBuf, err := ioutil.ReadAll(rsp.Body) - if err != nil { - gLog.Println(LvERROR, "update:read update list failed:", err) - return err - } - updateInfo := UpdateInfo{} - if err = json.Unmarshal(rspBuf, &updateInfo); err != nil { - gLog.Println(LvERROR, rspBuf, " update info decode error:", err) - return err - } - if updateInfo.Error != 0 { - gLog.Println(LvERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail) - return err - } - err = updateFile(updateInfo.Url, "", "openp2p") - if err != nil { - gLog.Println(LvERROR, "update: download failed:", err) - return err - } - return nil -} - -func downloadFile(url string, checksum string, dstFile string) error { - output, err := os.OpenFile(dstFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776) - if err != nil { - gLog.Printf(LvERROR, "OpenFile %s error:%s", dstFile, err) - return err - } - caCertPool, err := x509.SystemCertPool() - if err != nil { - gLog.Println(LvERROR, "Failed to load system root CAs:", err) - } else { - caCertPool = x509.NewCertPool() - } - caCertPool.AppendCertsFromPEM([]byte(rootCA)) - caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - InsecureSkipVerify: false}, - } - client := &http.Client{Transport: tr} - response, err := client.Get(url) - if err != nil { - gLog.Printf(LvERROR, "download url %s error:%s", url, err) - output.Close() - return err - } - defer response.Body.Close() - n, err := io.Copy(output, response.Body) - if err != nil { - gLog.Printf(LvERROR, "io.Copy error:%s", err) - output.Close() - return err - } - output.Sync() - output.Close() - gLog.Println(LvINFO, "download ", url, " ok") - gLog.Printf(LvINFO, "size: %d bytes", n) - return nil -} - -func updateFile(url string, checksum string, dst string) error { - gLog.Println(LvINFO, "download ", url) - tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp" - err := downloadFile(url, checksum, tmpFile) - if err != nil { - return err - } - backupFile := os.Args[0] + "0" - err = os.Rename(os.Args[0], backupFile) // the old daemon process was using the 0 file, so it will prevent override it - if err != nil { - gLog.Printf(LvINFO, " rename %s error:%s, retry 1", os.Args[0], err) - backupFile = os.Args[0] + "1" - err = os.Rename(os.Args[0], backupFile) - if err != nil { - gLog.Printf(LvINFO, " rename %s error:%s", os.Args[0], err) - } - } - // extract - gLog.Println(LvINFO, "extract files") - err = extract(filepath.Dir(os.Args[0]), tmpFile) - if err != nil { - gLog.Printf(LvERROR, "extract error:%s. revert rename", err) - os.Rename(backupFile, os.Args[0]) - return err - } - os.Remove(tmpFile) - return nil -} - -func extract(dst, src string) (err error) { - if runtime.GOOS == "windows" { - return unzip(dst, src) - } else { - return extractTgz(dst, src) - } -} - -func unzip(dst, src string) (err error) { - archive, err := zip.OpenReader(src) - if err != nil { - return err - } - defer archive.Close() - - for _, f := range archive.File { - filePath := filepath.Join(dst, f.Name) - fmt.Println("unzipping file ", filePath) - if f.FileInfo().IsDir() { - fmt.Println("creating directory...") - os.MkdirAll(filePath, os.ModePerm) - continue - } - if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { - return err - } - dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - fileInArchive, err := f.Open() - if err != nil { - return err - } - if _, err := io.Copy(dstFile, fileInArchive); err != nil { - return err - } - dstFile.Close() - fileInArchive.Close() - } - return nil -} - -func extractTgz(dst, src string) error { - gzipStream, err := os.Open(src) - if err != nil { - return err - } - uncompressedStream, err := gzip.NewReader(gzipStream) - if err != nil { - return err - } - tarReader := tar.NewReader(uncompressedStream) - for { - header, err := tarReader.Next() - if err == io.EOF { - break - } - if err != nil { - return err - } - switch header.Typeflag { - case tar.TypeDir: - if err := os.Mkdir(header.Name, 0755); err != nil { - return err - } - case tar.TypeReg: - filePath := filepath.Join(dst, header.Name) - outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode)) - if err != nil { - return err - } - defer outFile.Close() - if _, err := io.Copy(outFile, tarReader); err != nil { - return err - } - default: - return err - } - } - return nil -} - -func cleanTempFiles() { - tmpFile := os.Args[0] + "0" - if _, err := os.Stat(tmpFile); err == nil { - if err := os.Remove(tmpFile); err != nil { - gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) - } - } - tmpFile = os.Args[0] + "1" - if _, err := os.Stat(tmpFile); err == nil { - if err := os.Remove(tmpFile); err != nil { - gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) - } - } -} +package openp2p + +import ( + "archive/tar" + "archive/zip" + "compress/gzip" + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "path/filepath" + "runtime" + "time" +) + +func update(host string, port int) error { + gLog.Println(LvINFO, "update start") + defer gLog.Println(LvINFO, "update end") + caCertPool, err := x509.SystemCertPool() + if err != nil { + gLog.Println(LvERROR, "Failed to load system root CAs:", err) + } else { + caCertPool = x509.NewCertPool() + } + caCertPool.AppendCertsFromPEM([]byte(rootCA)) + caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) + + c := http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{RootCAs: caCertPool, + InsecureSkipVerify: false}, + }, + Timeout: time.Second * 30, + } + goos := runtime.GOOS + goarch := runtime.GOARCH + rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s&user=%s&node=%s", host, port, OpenP2PVersion, goos, goarch, url.QueryEscape(gConf.Network.User), url.QueryEscape(gConf.Network.Node))) + if err != nil { + gLog.Println(LvERROR, "update:query update list failed:", err) + return err + } + defer rsp.Body.Close() + if rsp.StatusCode != http.StatusOK { + gLog.Println(LvERROR, "get update info error:", rsp.Status) + return err + } + rspBuf, err := ioutil.ReadAll(rsp.Body) + if err != nil { + gLog.Println(LvERROR, "update:read update list failed:", err) + return err + } + updateInfo := UpdateInfo{} + if err = json.Unmarshal(rspBuf, &updateInfo); err != nil { + gLog.Println(LvERROR, rspBuf, " update info decode error:", err) + return err + } + if updateInfo.Error != 0 { + gLog.Println(LvERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail) + return err + } + err = updateFile(updateInfo.Url, "", "openp2p") + if err != nil { + gLog.Println(LvERROR, "update: download failed:", err) + return err + } + return nil +} + +func downloadFile(url string, checksum string, dstFile string) error { + output, err := os.OpenFile(dstFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776) + if err != nil { + gLog.Printf(LvERROR, "OpenFile %s error:%s", dstFile, err) + return err + } + caCertPool, err := x509.SystemCertPool() + if err != nil { + gLog.Println(LvERROR, "Failed to load system root CAs:", err) + } else { + caCertPool = x509.NewCertPool() + } + caCertPool.AppendCertsFromPEM([]byte(rootCA)) + caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + InsecureSkipVerify: false}, + } + client := &http.Client{Transport: tr} + response, err := client.Get(url) + if err != nil { + gLog.Printf(LvERROR, "download url %s error:%s", url, err) + output.Close() + return err + } + defer response.Body.Close() + n, err := io.Copy(output, response.Body) + if err != nil { + gLog.Printf(LvERROR, "io.Copy error:%s", err) + output.Close() + return err + } + output.Sync() + output.Close() + gLog.Println(LvINFO, "download ", url, " ok") + gLog.Printf(LvINFO, "size: %d bytes", n) + return nil +} + +func updateFile(url string, checksum string, dst string) error { + gLog.Println(LvINFO, "download ", url) + tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp" + err := downloadFile(url, checksum, tmpFile) + if err != nil { + return err + } + backupFile := os.Args[0] + "0" + err = os.Rename(os.Args[0], backupFile) // the old daemon process was using the 0 file, so it will prevent override it + if err != nil { + gLog.Printf(LvINFO, " rename %s error:%s, retry 1", os.Args[0], err) + backupFile = os.Args[0] + "1" + err = os.Rename(os.Args[0], backupFile) + if err != nil { + gLog.Printf(LvINFO, " rename %s error:%s", os.Args[0], err) + } + } + // extract + gLog.Println(LvINFO, "extract files") + err = extract(filepath.Dir(os.Args[0]), tmpFile) + if err != nil { + gLog.Printf(LvERROR, "extract error:%s. revert rename", err) + os.Rename(backupFile, os.Args[0]) + return err + } + os.Remove(tmpFile) + return nil +} + +func extract(dst, src string) (err error) { + if runtime.GOOS == "windows" { + return unzip(dst, src) + } else { + return extractTgz(dst, src) + } +} + +func unzip(dst, src string) (err error) { + archive, err := zip.OpenReader(src) + if err != nil { + return err + } + defer archive.Close() + + for _, f := range archive.File { + filePath := filepath.Join(dst, f.Name) + fmt.Println("unzipping file ", filePath) + if f.FileInfo().IsDir() { + fmt.Println("creating directory...") + os.MkdirAll(filePath, os.ModePerm) + continue + } + if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { + return err + } + dstFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + fileInArchive, err := f.Open() + if err != nil { + return err + } + if _, err := io.Copy(dstFile, fileInArchive); err != nil { + return err + } + dstFile.Close() + fileInArchive.Close() + } + return nil +} + +func extractTgz(dst, src string) error { + gzipStream, err := os.Open(src) + if err != nil { + return err + } + uncompressedStream, err := gzip.NewReader(gzipStream) + if err != nil { + return err + } + tarReader := tar.NewReader(uncompressedStream) + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + switch header.Typeflag { + case tar.TypeDir: + if err := os.Mkdir(header.Name, 0755); err != nil { + return err + } + case tar.TypeReg: + filePath := filepath.Join(dst, header.Name) + outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode)) + if err != nil { + return err + } + defer outFile.Close() + if _, err := io.Copy(outFile, tarReader); err != nil { + return err + } + default: + return err + } + } + return nil +} + +func cleanTempFiles() { + tmpFile := os.Args[0] + "0" + if _, err := os.Stat(tmpFile); err == nil { + if err := os.Remove(tmpFile); err != nil { + gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) + } + } + tmpFile = os.Args[0] + "1" + if _, err := os.Stat(tmpFile); err == nil { + if err := os.Remove(tmpFile); err != nil { + gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) + } + } +} diff --git a/lib/openp2p.go b/lib/openp2p.go index 9527136..b6c201a 100644 --- a/lib/openp2p.go +++ b/lib/openp2p.go @@ -1,18 +1,18 @@ -package main - -// On Windows env -// cd lib -// go build -o openp2p.dll -buildmode=c-shared openp2p.go -// caller example see example/dll -import ( - op "openp2p/core" -) -import "C" - -func main() { -} - -//export RunCmd -func RunCmd(cmd *C.char) { - op.RunCmd(C.GoString(cmd)) -} +package main + +// On Windows env +// cd lib +// go build -o openp2p.dll -buildmode=c-shared openp2p.go +// caller example see example/dll +import ( + op "openp2p/core" +) +import "C" + +func main() { +} + +//export RunCmd +func RunCmd(cmd *C.char) { + op.RunCmd(C.GoString(cmd)) +}