This commit is contained in:
TenderIronh
2024-07-26 22:27:24 +08:00
parent 2dea3a718d
commit 9c3d557f5d
18 changed files with 4041 additions and 4041 deletions
+108 -108
View File
@@ -1,108 +1,108 @@
# 手动运行说明
大部分情况通过<https://console.openp2p.cn> 操作即可。有些情况需要手动运行
> :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: 在<console.openp2p.cn>“我的”里面找到
>* -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远程桌面3389Linux 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
```
# 手动运行说明
大部分情况通过<https://console.openp2p.cn> 操作即可。有些情况需要手动运行
> :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: 在<console.openp2p.cn>“我的”里面找到
>* -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远程桌面3389Linux 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
```
+108 -108
View File
@@ -1,109 +1,109 @@
# Parameters details
In most cases, you can operate it through <https://console.openp2p.cn>. 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 <console.openp2p.cn> "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 peoples 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 <https://console.openp2p.cn>. 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 <console.openp2p.cn> "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 peoples 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
```
+9 -9
View File
@@ -1,9 +1,9 @@
package main
import (
op "openp2p/core"
)
func main() {
op.Run()
}
package main
import (
op "openp2p/core"
)
func main() {
op.Run()
}
+115 -115
View File
@@ -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
}
+482 -482
View File
@@ -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)
}
+124 -124
View File
@@ -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 [email protected]")
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 [email protected]")
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
}
+74 -74
View File
@@ -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
}
}
+189 -189
View File
@@ -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
}
+121 -121
View File
@@ -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 [email protected]")
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 [email protected]")
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 [email protected]")
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 [email protected]")
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)
}
+20 -20
View File
@@ -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)
}
+85 -85
View File
@@ -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)
}
+133 -133
View File
@@ -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
}
+142 -142
View File
@@ -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
}
+976 -976
View File
@@ -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
}
+806 -806
View File
@@ -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)
}
+292 -292
View File
@@ -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
}
+239 -239
View File
@@ -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)
}
}
}
+18 -18
View File
@@ -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))
}