Compare commits

..
47 Commits
Author SHA1 Message Date
TenderIronh 080e6af779 fix public ip detect bug 2024-12-02 21:10:15 +08:00
TenderIronh 77bfa45172 portmap loss & android ipv6 failed & public ip detect 2024-11-21 10:31:07 +08:00
TenderIronh 3616768682 rename 2024-11-21 10:29:06 +08:00
TenderIronh f015b828fc specified gomobile version 2024-10-20 21:33:20 +08:00
TenderIronh df1e16e708 3.21.8 2024-10-20 11:33:07 +08:00
W192547975 c68094cc12 CertPool Fix (#96)
Remove caCertPool errCert “else” in p2pnetwork.go
2024-08-02 14:23:47 +08:00
CAESIUS_TIM a0df0b1e95 [doc ]no bare urls (#80) 2024-08-02 14:22:31 +08:00
TenderIronh 9c3d557f5d LF 2024-07-26 22:27:24 +08:00
TenderIronh 2dea3a718d check remote service 2024-07-26 22:07:48 +08:00
TenderIronh 9dda148232 3.18.4 virtual private network 2024-07-14 11:00:43 +08:00
TenderIronh e7b696c474 miss build.gradle 2023-11-18 11:33:14 +08:00
TenderIronh 7e57237ec9 3.12.0 2023-10-28 18:03:42 +08:00
TenderIronh 52dfe5c938 fix client update bug 2023-10-28 17:56:58 +08:00
TenderIronh b72ede9a6a optimize hole punching 2023-09-04 23:11:42 +08:00
Ahackerl b39fab2188 fix unused sync (#47) 2023-09-04 09:07:20 +08:00
W192547975 9b0525294a echo server in publicIPTest update (#39)
* Update nat.go
func publicIPTest echo server
2023-08-24 10:55:09 +08:00
W192547975andOpenP2P 276a4433f1 Bitset logmode update (#40)
* Update log.go

bitset loglevel

---------

Co-authored-by: OpenP2P <[email protected]>
2023-08-10 11:23:36 +08:00
TenderIronh e21adebc26 3.10.3 2023-08-09 22:52:29 +08:00
OpenP2P b2a7619bd6 Merge pull request #43 from W192547975/fixbug
Bug fixing from 2023/8/1
2023-08-08 16:50:25 +08:00
W192547975 fe4022ba6c Update openp2p.go 2023-08-06 23:38:14 +08:00
W192547975 82c74b4f85 Update config.go 2023-08-06 23:31:17 +08:00
TenderIronh 0af65b7204 tls verify server and support docker 2023-08-03 23:05:45 +08:00
TenderIronh 46b4f78010 optimize tcp and udp punch 2023-07-29 20:36:35 +08:00
TenderIronh 8ebdf3341e 3.9.1 2023-07-21 22:25:33 +08:00
TenderIronh b667e5b766 3.8.0 2023-05-07 20:42:06 +08:00
TenderIronh cd415e7bf4 3.6.11 2023-03-25 12:00:27 +08:00
TenderIronh 67e3a8915a 3.6.8 2023-03-22 23:11:38 +08:00
TenderIronh 791d910314 3.6.5 2023-03-05 00:44:22 +08:00
TenderIronh c3a43be3cc improve gatway and p2papp reconnect 2022-11-25 23:55:33 +08:00
TenderIronh c8b8bf05a5 support openwrt and improve app and gateway reconnect time 2022-11-18 23:19:47 +08:00
hhd 8311341960 readloop error 2022-11-10 23:34:04 +08:00
hhd d5c098ca74 support android 2022-11-05 18:22:16 +08:00
hhd af82fc6e36 support andoird 2022-11-03 11:07:32 +08:00
TenderIronh 2af77668fe auto adjust node name 2022-09-13 20:47:32 +08:00
TenderIronh 215feb8721 doc 2022-08-08 23:19:47 +08:00
TenderIronh 4a115fb3fa doc remote debug 2022-08-08 23:10:37 +08:00
TenderIronh 0fb3bc4e26 request peer info token wrong 2022-06-15 23:20:13 +08:00
TenderIronh df23b30d2b doc 2022-05-29 16:05:18 +08:00
TenderIronh f9b5073e0d fix some bug 2022-05-29 15:48:56 +08:00
TenderIronh 9ea467c7b3 rm rename 2022-05-26 23:43:06 +08:00
TenderIronh c0bad61eb6 tcp punch 2022-05-26 23:18:22 +08:00
TenderIronh bb32133038 config template 2022-05-17 15:53:29 +08:00
TenderIronh 532d3667ce support ipv6 2022-05-15 13:08:56 +08:00
TenderIronh b2d35c6f97 1.5.5 2022-04-27 23:26:23 +08:00
TenderIronh 037f3cc34e doc 2022-04-07 23:22:46 +08:00
TenderIronh 6b8d3f7d47 1.4.2 2022-04-07 23:09:47 +08:00
TenderIronh 26e0fdf605 support udp 2022-02-26 18:50:57 +08:00
140 changed files with 10775 additions and 3543 deletions
+17 -3
View File
@@ -1,10 +1,24 @@
__debug_bin
__debug_bin.exe
# .vscode
openp2p
test/
openp2p.exe*
*.log
*.log*
go.sum
*.tar.gz
*.zip
*.exe
*.exe
config.json
libs/
*/app/.idea/
*/app/release/
openp2p.app.jks
openp2p.aar
openp2p-sources.jar
wintun/
wintun.dll
.vscode/
app/.idea/
*_debug_bin*
cmd/openp2p
vendor/
+7
View File
@@ -0,0 +1,7 @@
build:
export GOPROXY=https://goproxy.io,direct
go mod tidy
go build cmd/openp2p.go
.PHONY: build
.DEFAULT_GOAL := build
+41 -18
View File
@@ -19,11 +19,11 @@
[查看详细](#安全性)
### 4. 轻量
文件大小2MB+,运行内存2MB+全部在应用层实现,没有虚拟网卡,没有内核程序
文件大小2MB+,运行内存2MB+它可以仅跑在应用层,或者配合wintun驱动使用组网功能
### 5. 跨平台
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le
### 6. 高效
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4Cone或Symmetric),UDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
### 7. 二次开发
基于OpenP2P只需数行代码,就能让原来只能局域网通信的程序,变成任何内网都能通信
@@ -31,6 +31,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
## 快速入门
仅需简单4步就能用起来。
下面是一个远程办公例子:在家里连入办公室Windows电脑。
(另外一个快速入门视频 <https://www.bilibili.com/video/BV1Et4y1P7bF/>
### 1.注册
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
@@ -39,7 +40,12 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
分别在本地和远程电脑下载后双击运行,一键安装
![image](/doc/images/install.png)
Windows默认会阻止没有花钱买它家证书签名过的程序,选择“仍要运行”即可。
![image](/doc/images/win10warn.png)
![image](/doc/images/stillrun.png)
### 3.新建P2P应用
![image](/doc/images/devices.png)
@@ -90,29 +96,47 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
服务端有个调度模型,根据带宽、ping值、稳定性、服务时长,尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码,hmac-sha256算法校验,它是一次性密码,和我们平时使用的手机验证码或银行密码器一样的原理。
## 编译
go version 1.20 only (支持win7)
cd到代码根目录,执行
```
make
```
手动编译特定系统和架构
All GOOS values:
```
"aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos"
```
All GOARCH values:
```
"386", "amd64", "amd64p32", "arm", "arm64", "arm64be", "armbe", "loong64", "mips", "mips64", "mips64le", "mips64p32", "mips64p32le", "mipsle", "ppc", "ppc64", "ppc64le", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm"
```
比如linux+amd64
```
export GOPROXY=https://goproxy.io,direct
go mod tidy
go build
CGO_ENABLED=0 env GOOS=linux GOARCH=amd64 go build -o openp2p --ldflags '-s -w ' -gcflags '-l' -p 8 -installsuffix cgo ./cmd
```
## TODO
## RoadMap
近期计划:
1. 支持IPv6
2. 支持随系统自动启动,安装成系统服务
3. 提供一些免费服务器给特别差的网络,如广电网络
4. 建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等
1. ~~支持IPv6~~(100%)
2. ~~支持随系统自动启动,安装成系统服务~~(100%)
3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%)
4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%)
5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备
6. 客户端提供WebUI
7. 支持自有服务器高并发连接
7. ~~支持自有服务器,开源服务器程序~~(100%)
8. 共享节点调度模型优化,对不同的运营商优化
9. 方便二次开发,提供API和lib
10. 应用层支持UDP协议,实现很简单,但UDP应用较少暂不急
11. 底层通信支持KCP协议,目前仅支持Quic;KCP专门对延时优化,被游戏加速器广泛使用,可以牺牲一定的带宽降低延时
12. 支持Android系统,让旧手机焕发青春变成移动网关
13. 支持Windows网上邻居共享文件
14. 内网直连优化,用处不大,估计就用户测试时用到
9. ~~方便二次开发,提供API和lib~~(100%)
10. ~~应用层支持UDP协议,实现很简单,但UDP应用较少暂不急~~(100%)
11. ~~底层通信支持KCP协议,目前仅支持Quic;KCP专门对延时优化,被游戏加速器广泛使用,可以牺牲一定的带宽降低延时~~(100%)
12. ~~支持Android系统,让旧手机焕发青春变成移动网关~~(100%)
13. ~~支持Windows网上邻居共享文件~~(100%)
14. ~~内网直连优化~~(100%)
15. ~~支持UPNP~~(100%)
16. ~~支持Android~~(100%)
17. 支持IOS
远期计划:
1. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。
@@ -120,8 +144,7 @@ go build
## 参与贡献
TODO或ISSUE里如果有你擅长的领域,或者你有特别好的主意,可以加入OpenP2P项目,贡献你的代码。待项目茁壮成长后,你们就是知名开源项目的主要代码贡献者,岂不快哉。
## 商业合作
它是一个中国人发起的项目,更懂国内网络环境,更懂用户需求,更好的企业级支持
## 技术交流
QQ群:16947733
邮箱:openp2p.cn@gmail.com tenderiron@139.com
+42 -18
View File
@@ -19,13 +19,13 @@ The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and t
[details](#Safety)
### 4. Lightweight
2MB+ filesize, 2MB+ memory. It runs at appllication layer, no vitrual NIC, no kernel driver.
2MB+ filesize, 2MB+ memory. It could only runs at application layer, or uses wintun driver for SDWAN.
### 5. Cross-platform
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64.
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le.
### 6. Efficient
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric). Relying on the excellent congestion algorithm of the Quic protocol, high bandwidth and low latency can be obtained in a bad network environment.
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric),UDP or TCP punching,UPNP,IPv6. Relying on the excellent congestion algorithm of the Quic protocol, high bandwidth and low latency can be obtained in a bad network environment.
### 7. Integration
Your applicaiton can call OpenP2P with a few code to make any internal networks communicate with each other.
@@ -33,7 +33,7 @@ Your applicaiton can call OpenP2P with a few code to make any internal networks
## Get Started
Just 4 simple steps to use.
Here's an example of remote work: connecting to an office Windows computer at home.
(Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
### 1.Register
Go to <https://console.openp2p.cn> register a new user
@@ -43,6 +43,12 @@ Download on local and remote computers and double-click to run, one-click instal
![image](/doc/images/install_en.png)
By default, Windows will block programs that have not been signed by the Microsoft's certificate, and you can select "Run anyway".
![image](/doc/images/win10warn_en.png)
![image](/doc/images/stillrun_en.png)
### 3.New P2PApp
![image](/doc/images/devices_en.png)
@@ -97,30 +103,48 @@ That's right, the relay node is naturally an man-in-middle, so AES encryption is
The server side has a scheduling model, which calculate bandwith, ping value,stability and service duration to provide a well-proportioned service to every share node. It uses TOTP(Time-based One-time Password) with hmac-sha256 algorithem, its theory as same as the cellphone validation code or bank cipher coder.
## Build
go version 1.20 only (support win7)
cd root directory of the socure code and execute
```
make
```
build specified os and arch.
All GOOS values:
```
"aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos"
```
All GOARCH values:
```
"386", "amd64", "amd64p32", "arm", "arm64", "arm64be", "armbe", "loong64", "mips", "mips64", "mips64le", "mips64p32", "mips64p32le", "mipsle", "ppc", "ppc64", "ppc64le", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm"
```
For example linux+amd64
```
export GOPROXY=https://goproxy.io,direct
go mod tidy
go build
CGO_ENABLED=0 env GOOS=linux GOARCH=amd64 go build -o openp2p --ldflags '-s -w ' -gcflags '-l' -p 8 -installsuffix cgo ./cmd
```
## TODO
## RoadMap
Short-Term:
1. Support IPv6.
2. Support auto run when system boot, setup system service.
3. Provide free servers to some low-performance network.
4. Build website, users can manage all P2PApp and devices via it. View devices' online status, upgrade, restart or CURD P2PApp .
1. ~~Support IPv6.~~(100%)
2. ~~Support auto run when system boot, setup system service.~~(100%)
3. ~~Provide free servers to some low-performance network.~~(100%)
4. ~~Build website, users can manage all P2PApp and devices via it. View devices' online status, upgrade, restart or CURD P2PApp .~~(100%)
5. Provide wechat official account, user can manage P2PApp nodes and deivce as same as website.
6. Provide WebUI on client side.
7. Support high concurrency on server side.
7. ~~Support private server, open source server program.~~(100%)
8. Optimize our share scheduling model for different network operators.
9. Provide REST APIs and libary for secondary development.
10. Support UDP at application layer, it is easy to implement but not urgent due to only a few applicaitons using UDP protocol.
11. Support KCP protocol underlay, currently support Quic only. KCP focus on delay optimization,which has been widely used as game accelerator,it can sacrifice part of bandwidth to reduce timelag.
12. Support Android platform, let the phones to be mobile gateway .
13. Support SMB Windows neighborhood.
14. Direct connection on intranet, for testing.
9. ~~Provide REST APIs and libary for secondary development.~~(100%)
10. ~~Support UDP at application layer, it is easy to implement but not urgent due to only a few applicaitons using UDP protocol.~~(100%)
11. ~~Support KCP protocol underlay, currently support Quic only. KCP focus on delay optimization,which has been widely used as game accelerator,it can sacrifice part of bandwidth to reduce timelag. ~~(100%)
12. ~~Support Android platform, let the phones to be mobile gateway.~~(100%)
13. ~~Support SMB Windows neighborhood.~~(100%)
14. ~~Direct connection on intranet, for testing.~~(100%)
15. ~~Support UPNP.~~(100%)
16. ~~Support Android~~(100%)
17. Support IOS
Long-Term:
1. Use blockchain technology to decentralize, so that users who share equipment have benefits, thereby promoting more users to share, and achieving a positive closed loop.
+108 -94
View File
@@ -1,94 +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": "hhd1207-222",
"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://openp2p.cn:27182/api/v1/device/YOUR-NODE-NAME/update?user=&password='
```
Windows系统需要设置防火墙放行本程序,程序会自动设置,如果设置失败会影响连接功能。
Linux系统(Ubuntu和CentOS7)的防火墙默认配置均不会有影响,如果不行可尝试关闭防火墙
```
systemctl stop firewalld.service
systemctl start firewalld.service
firewall-cmd --state
```
## 卸载
```
./openp2p uninstall
```
# 手动运行说明
大部分情况通过<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 -95
View File
@@ -1,96 +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": "hhd1207-222",
"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://openp2p.cn:27182/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
# 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
```
+15
View File
@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
+3
View File
@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml
+1
View File
@@ -0,0 +1 @@
OpenP2P
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+33
View File
@@ -0,0 +1,33 @@
## Build
depends on openjdk 11, gradle 8.1.3, ndk 21
```
# latest version not support go1.20
go install golang.org/x/mobile/cmd/gomobile@7c4916698cc93475ebfea76748ee0faba2deb2a5
gomobile init
go get -v golang.org/x/mobile/bind@7c4916698cc93475ebfea76748ee0faba2deb2a5
cd core
gomobile bind -target android -v
if [[ $? -ne 0 ]]; then
echo "build error"
exit 9
fi
echo "build ok"
cp openp2p.aar openp2p-sources.jar ../app/app/libs
echo "copy to APP libs"
edit app/app/build.gradle
```
signingConfigs {
release {
storeFile file('YOUR-JKS-PATH')
storePassword 'YOUR-PASSWORD'
keyAlias 'openp2p.keys'
keyPassword 'YOUR-PASSWORD'
}
}
```
cd ../app
./gradlew build
```
+1
View File
@@ -0,0 +1 @@
/build
+63
View File
@@ -0,0 +1,63 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
signingConfigs {
release {
storeFile file('C:\\work\\src\\openp2p-client\\app\\openp2p.jks')
storePassword 'YOUR-PASSWORD'
keyAlias 'openp2p.keys'
keyPassword 'YOUR-PASSWORD'
}
}
compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cn.openp2p"
minSdkVersion 16
targetSdkVersion 31
versionCode 1
versionName "2718281828"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
namespace "cn.openp2p"
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation files('libs\\openp2p-sources.jar')
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package cn.openp2p
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("cn.openp2p", appContext.packageName)
}
}
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OpenP2P">
<service
android:name=".OpenP2PService"
android:enabled="true"
android:exported="true"
android:permission="android.permission.BIND_VPN_SERVICE">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<activity
android:name=".ui.login.LoginActivity"
android:label="@string/app_name"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="BootReceiver"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
@@ -0,0 +1,27 @@
package cn.openp2p
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.net.VpnService
import android.os.Build
import android.os.Bundle
import android.util.Log
import cn.openp2p.ui.login.LoginActivity
class BootReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Logger.log("pp onReceive "+intent.action.toString())
Log.i("onReceive","start "+intent.action.toString())
// if (Intent.ACTION_BOOT_COMPLETED == intent.action) {
// Log.i("onReceive","match "+intent.action.toString())
// VpnService.prepare(context)
// val intent = Intent(context, OpenP2PService::class.java)
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// context.startForegroundService(intent)
// } else {
// context.startService(intent)
// }
// }
Log.i("onReceive","end "+intent.action.toString())
}
}
@@ -0,0 +1,284 @@
package cn.openp2p
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import java.io.IOException
import android.net.VpnService
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import cn.openp2p.ui.login.LoginActivity
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import openp2p.Openp2p
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import kotlinx.coroutines.*
import org.json.JSONObject
data class Node(val name: String, val ip: String, val resource: String? = null)
data class Network(
val id: Long,
val name: String,
val gateway: String,
val Nodes: List<Node>
)
class OpenP2PService : VpnService() {
companion object {
private val LOG_TAG = OpenP2PService::class.simpleName
}
inner class LocalBinder : Binder() {
fun getService(): OpenP2PService = this@OpenP2PService
}
private val binder = LocalBinder()
private lateinit var network: openp2p.P2PNetwork
private lateinit var mToken: String
private var running:Boolean =true
private var sdwanRunning:Boolean =false
private var vpnInterface: ParcelFileDescriptor? = null
private var sdwanJob: Job? = null
override fun onCreate() {
Log.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id)
var channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("kim.hsl", "ForegroundService")
} else {
""
}
val notificationIntent = Intent(this, LoginActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this, 0,
notificationIntent, PendingIntent.FLAG_IMMUTABLE
)
val notification = channelId?.let {
NotificationCompat.Builder(this, it)
// .setSmallIcon(R.mipmap.app_icon)
.setContentTitle("My Awesome App")
.setContentText("Doing some work...")
.setContentIntent(pendingIntent).build()
}
startForeground(1337, notification)
super.onCreate()
refreshSDWAN()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(
LOG_TAG,
"onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id
)
startOpenP2P(null)
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(p0: Intent?): IBinder? {
val token = p0?.getStringExtra("token")
Log.i(LOG_TAG, "onBind token=$token")
startOpenP2P(token)
return binder
}
private fun startOpenP2P(token : String?): Boolean {
if (sdwanRunning) {
return true
}
Log.i(LOG_TAG, "startOpenP2P - Thread ID = " + Thread.currentThread().id + token)
val oldToken = Openp2p.getToken(getExternalFilesDir(null).toString())
Log.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token")
if (oldToken=="0" && token==null){
return false
}
sdwanRunning = true
// runSDWAN()
GlobalScope.launch {
network = Openp2p.runAsModule(
getExternalFilesDir(null).toString(),
token,
0,
1
) // /storage/emulated/0/Android/data/cn.openp2p/files/
val isConnect = network.connect(30000) // ms
Log.i(LOG_TAG, "login result: " + isConnect.toString());
do {
Thread.sleep(1000)
} while (network.connect(30000) && running)
stopSelf()
}
return false
}
private fun refreshSDWAN() {
GlobalScope.launch {
Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN start");
while (true) {
Log.i(OpenP2PService.LOG_TAG, "waiting new sdwan config");
val buf = ByteArray(4096)
val buffLen = Openp2p.getAndroidSDWANConfig(buf)
Log.i(OpenP2PService.LOG_TAG, "closing running sdwan instance");
sdwanRunning = false
vpnInterface?.close()
vpnInterface = null
Thread.sleep(10000)
runSDWAN(buf.copyOfRange(0,buffLen.toInt() ))
}
Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN end");
}
}
private suspend fun readTunLoop() {
val inputStream = FileInputStream(vpnInterface?.fileDescriptor).channel
if (inputStream==null){
Log.i(OpenP2PService.LOG_TAG, "open FileInputStream error: ");
return
}
Log.d(LOG_TAG, "read tun loop start")
val buffer = ByteBuffer.allocate(4096)
val byteArrayRead = ByteArray(4096)
while (sdwanRunning) {
buffer.clear()
val readBytes = inputStream.read(buffer)
if (readBytes <= 0) {
// Log.i(OpenP2PService.LOG_TAG, "inputStream.read error: ")
delay(1)
continue
}
buffer.flip()
buffer.get(byteArrayRead,0,readBytes)
Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidRead: %d", readBytes))
Openp2p.androidRead(byteArrayRead, readBytes.toLong())
}
Log.d(LOG_TAG, "read tun loop end")
}
private fun runSDWAN(buf:ByteArray) {
sdwanRunning=true
sdwanJob=GlobalScope.launch(context = Dispatchers.IO) {
Log.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}");
try{
var builder = Builder()
val jsonObject = JSONObject(buf.decodeToString())
val id = jsonObject.getLong("id")
val name = jsonObject.getString("name")
val gateway = jsonObject.getString("gateway")
val nodesArray = jsonObject.getJSONArray("Nodes")
val nodesList = mutableListOf<JSONObject>()
for (i in 0 until nodesArray.length()) {
nodesList.add(nodesArray.getJSONObject(i))
}
val myNodeName = Openp2p.getAndroidNodeName()
Log.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}");
val nodeList = nodesList.map {
val nodeName = it.getString("name")
val nodeIp = it.getString("ip")
if (nodeName==myNodeName){
builder.addAddress(nodeIp, 24)
}
val nodeResource = if (it.has("resource")) it.getString("resource") else null
val parts = nodeResource?.split("/")
if (parts?.size == 2) {
val ipAddress = parts[0]
val subnetMask = parts[1]
builder.addRoute(ipAddress, subnetMask.toInt())
Log.i(OpenP2PService.LOG_TAG, "sdwan addRoute:${ipAddress},${subnetMask.toInt()}");
}
Node(nodeName, nodeIp, nodeResource)
}
val network = Network(id, name, gateway, nodeList)
println(network)
Log.i(OpenP2PService.LOG_TAG, "onBind");
builder.addDnsServer("223.5.5.5")
builder.addDnsServer("2400:3200::1") // alicloud dns v6 & v4
builder.addRoute("10.2.3.0", 24)
// builder.addRoute("0.0.0.0", 0);
builder.setSession(LOG_TAG!!)
builder.setMtu(1420)
vpnInterface = builder.establish()
if (vpnInterface==null){
Log.e(OpenP2PService.LOG_TAG, "start vpnservice error: ");
}
val outputStream = FileOutputStream(vpnInterface?.fileDescriptor).channel
if (outputStream==null){
Log.e(OpenP2PService.LOG_TAG, "open FileOutputStream error: ");
return@launch
}
val byteArrayWrite = ByteArray(4096)
launch {
readTunLoop()
}
Log.d(LOG_TAG, "write tun loop start")
while (sdwanRunning) {
val len = Openp2p.androidWrite(byteArrayWrite)
Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidWrite: %d",len));
val writeBytes = outputStream?.write(ByteBuffer.wrap(byteArrayWrite))
if (writeBytes != null && writeBytes <= 0) {
Log.i(OpenP2PService.LOG_TAG, "outputStream?.write error: ");
continue
}
}
outputStream.close()
// 关闭 VPN 接口
vpnInterface?.close()
// 置空变量以释放资源
vpnInterface = null
Log.d(LOG_TAG, "write tun loop end")
}catch (e: Exception) {
// 捕获异常并记录
Log.e("VPN Connection", "发生异常: ${e.message}")
}
Log.i(OpenP2PService.LOG_TAG, "runSDWAN end");
}
}
override fun onDestroy() {
Log.i(LOG_TAG, "onDestroy - Thread ID = " + Thread.currentThread().id)
super.onDestroy()
}
override fun onUnbind(intent: Intent?): Boolean {
Log.i(LOG_TAG, "onUnbind - Thread ID = " + Thread.currentThread().id)
stopSelf()
return super.onUnbind(intent)
}
fun isConnected(): Boolean {
if (!::network.isInitialized) return false
return network.connect(1000)
}
fun stop() {
running=false
stopSelf()
Openp2p.stop()
}
@RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String? {
val chan = NotificationChannel(
channelId,
channelName, NotificationManager.IMPORTANCE_NONE
)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
}
@@ -0,0 +1,24 @@
package cn.openp2p.data
import cn.openp2p.data.model.LoggedInUser
import java.io.IOException
/**
* Class that handles authentication w/ login credentials and retrieves user information.
*/
class LoginDataSource {
fun login(username: String, password: String): Result<LoggedInUser> {
try {
// TODO: handle loggedInUser authentication
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
return Result.Success(fakeUser)
} catch (e: Throwable) {
return Result.Error(IOException("Error logging in", e))
}
}
fun logout() {
// TODO: revoke authentication
}
}
@@ -0,0 +1,46 @@
package cn.openp2p.data
import cn.openp2p.data.model.LoggedInUser
/**
* Class that requests authentication and user information from the remote data source and
* maintains an in-memory cache of login status and user credentials information.
*/
class LoginRepository(val dataSource: LoginDataSource) {
// in-memory cache of the loggedInUser object
var user: LoggedInUser? = null
private set
val isLoggedIn: Boolean
get() = user != null
init {
// If user credentials will be cached in local storage, it is recommended it be encrypted
// @see https://developer.android.com/training/articles/keystore
user = null
}
fun logout() {
user = null
dataSource.logout()
}
fun login(username: String, password: String): Result<LoggedInUser> {
// handle login
val result = dataSource.login(username, password)
if (result is Result.Success) {
setLoggedInUser(result.data)
}
return result
}
private fun setLoggedInUser(loggedInUser: LoggedInUser) {
this.user = loggedInUser
// If user credentials will be cached in local storage, it is recommended it be encrypted
// @see https://developer.android.com/training/articles/keystore
}
}
@@ -0,0 +1,18 @@
package cn.openp2p.data
/**
* A generic class that holds a value with its loading status.
* @param <T>
*/
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
}
}
}
@@ -0,0 +1,9 @@
package cn.openp2p.data.model
/**
* Data class that captures user information for logged in users retrieved from LoginRepository
*/
data class LoggedInUser(
val userId: String,
val displayName: String
)
+45
View File
@@ -0,0 +1,45 @@
package cn.openp2p
import android.content.*
import java.io.File
import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import java.io.IOException
import android.net.VpnService
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import cn.openp2p.ui.login.LoginActivity
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import openp2p.Openp2p
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import kotlinx.coroutines.*
import org.json.JSONObject
object Logger {
private val logFile: File = File("app.log")
fun log(message: String) {
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
val logMessage = "$timestamp: $message\n"
try {
val fileWriter = FileWriter(logFile, true)
fileWriter.append(logMessage)
fileWriter.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
@@ -0,0 +1,9 @@
package cn.openp2p.ui.login
/**
* User details post authentication that is exposed to the UI
*/
data class LoggedInUserView(
val displayName: String
//... other data fields that may be accessible to the UI
)
@@ -0,0 +1,212 @@
package cn.openp2p.ui.login
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Notification
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import cn.openp2p.Logger
import cn.openp2p.OpenP2PService
import cn.openp2p.R
import cn.openp2p.databinding.ActivityLoginBinding
import openp2p.Openp2p
import kotlin.concurrent.thread
import kotlin.system.exitProcess
class LoginActivity : AppCompatActivity() {
companion object {
private val LOG_TAG = LoginActivity::class.simpleName
}
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as OpenP2PService.LocalBinder
mService = binder.getService()
}
override fun onServiceDisconnected(className: ComponentName) {
}
}
private lateinit var loginViewModel: LoginViewModel
private lateinit var binding: ActivityLoginBinding
private lateinit var mService: OpenP2PService
@RequiresApi(Build.VERSION_CODES.O)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0 && resultCode == Activity.RESULT_OK) {
startService(Intent(this, OpenP2PService::class.java))
}
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
val token = binding.token
val login = binding.login
val onlineState = binding.onlineState
val openp2pLog = binding.openp2pLog
val profile = binding.profile
val loading = binding.loading
loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
.get(LoginViewModel::class.java)
loginViewModel.loginFormState.observe(this@LoginActivity, Observer {
val loginState = it ?: return@Observer
// disable login button unless both username / password is valid
login.isEnabled = loginState.isDataValid
if (loginState.passwordError != null) {
token.error = getString(loginState.passwordError)
}
})
openp2pLog.setText(R.string.phone_setting)
val intent = VpnService.prepare(this)
if (intent != null)
{
Log.i("openp2p", "VpnService.prepare need permission");
startActivityForResult(intent, 0)
}
else {
Log.i("openp2p", "VpnService.prepare ready");
onActivityResult(0, Activity.RESULT_OK, null)
}
profile.setOnClickListener {
val url = "https://console.openp2p.cn/profile"
val i = Intent(Intent.ACTION_VIEW)
i.data = Uri.parse(url)
startActivity(i)
}
token.apply {
afterTextChanged {
loginViewModel.loginDataChanged(
"username.text.toString()",
token.text.toString()
)
}
openp2pLog.setText(R.string.phone_setting)
login.setOnClickListener {
if (login.text.toString()=="退出"){
// val intent = Intent(this@LoginActivity, OpenP2PService::class.java)
// stopService(intent)
Log.i(LOG_TAG, "quit")
mService.stop()
val intent = Intent(this@LoginActivity, OpenP2PService::class.java)
stopService(intent)
// 解绑服务
unbindService(connection)
// 结束当前 Activity
finish() // 或者使用 finishAffinity() 来结束整个应用程序
exitAPP()
// finishAffinity()
}
login.setText("退出")
Log.i(LOG_TAG, "start")
val intent = Intent(this@LoginActivity, OpenP2PService::class.java)
intent.putExtra("token", token.text.toString())
bindService(intent, connection, Context.BIND_AUTO_CREATE)
startService(intent)
thread {
do {
Thread.sleep(1000)
if (!::mService.isInitialized) continue
val isConnect = mService.isConnected()
// Log.i(LOG_TAG, "mService.isConnected() = " + isConnect.toString())
runOnUiThread {
if (isConnect) {
onlineState.setText("在线")
} else {
onlineState.setText("正在登录")
}
}
} while (true)
}
}
val tokenText = Openp2p.getToken(getExternalFilesDir(null).toString())
token.setText(tokenText.toString())
// Check token length and automatically click login if length > 10
if (tokenText.length > 10) {
// Logger.log("performClick ")
login.performClick()
}
}
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
@SuppressLint("ServiceCast")
fun exitAPP() {
val activityManager =
applicationContext?.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val appTaskList = activityManager.appTasks
for (i in appTaskList.indices) {
appTaskList[i].finishAndRemoveTask()
}
exitProcess(0)
}
private fun updateUiWithUser(model: LoggedInUserView) {
val welcome = getString(R.string.welcome)
val displayName = model.displayName
// TODO : initiate successful logged in experience
Toast.makeText(
applicationContext,
"$welcome $displayName",
Toast.LENGTH_LONG
).show()
}
private fun showLoginFailed(@StringRes errorString: Int) {
Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show()
}
}
/**
* Extension function to simplify setting an afterTextChanged action to EditText components.
*/
fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
afterTextChanged.invoke(editable.toString())
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
})
}
@@ -0,0 +1,10 @@
package cn.openp2p.ui.login
/**
* Data validation state of the login form.
*/
data class LoginFormState(
val usernameError: Int? = null,
val passwordError: Int? = null,
val isDataValid: Boolean = false
)
@@ -0,0 +1,9 @@
package cn.openp2p.ui.login
/**
* Authentication result : success (user details) or error message.
*/
data class LoginResult(
val success: LoggedInUserView? = null,
val error: Int? = null
)
@@ -0,0 +1,57 @@
package cn.openp2p.ui.login
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import android.util.Patterns
import cn.openp2p.data.LoginRepository
import cn.openp2p.data.Result
import cn.openp2p.R
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
private val _loginForm = MutableLiveData<LoginFormState>()
val loginFormState: LiveData<LoginFormState> = _loginForm
private val _loginResult = MutableLiveData<LoginResult>()
val loginResult: LiveData<LoginResult> = _loginResult
fun login(username: String, password: String) {
// can be launched in a separate asynchronous job
val result = loginRepository.login(username, password)
if (result is Result.Success) {
_loginResult.value =
LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
} else {
_loginResult.value = LoginResult(error = R.string.login_failed)
}
}
fun loginDataChanged(username: String, password: String) {
// if (!isUserNameValid(username)) {
// _loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
// } else
if (!isPasswordValid(password)) {
_loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
} else {
_loginForm.value = LoginFormState(isDataValid = true)
}
}
// A placeholder username validation check
private fun isUserNameValid(username: String): Boolean {
return true
return if (username.contains('@')) {
Patterns.EMAIL_ADDRESS.matcher(username).matches()
} else {
username.isNotBlank()
}
}
// A placeholder password validation check
private fun isPasswordValid(password: String): Boolean {
return password.length > 5
}
}
@@ -0,0 +1,25 @@
package cn.openp2p.ui.login
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cn.openp2p.data.LoginDataSource
import cn.openp2p.data.LoginRepository
/**
* ViewModel provider factory to instantiate LoginViewModel.
* Required given LoginViewModel has a non-empty constructor
*/
class LoginViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
return LoginViewModel(
loginRepository = LoginRepository(
dataSource = LoginDataSource()
)
) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".ui.login.LoginActivity">
<EditText
android:id="@+id/token"
android:layout_width="250dp"
android:layout_height="45dp"
android:hint="Token"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:selectAllOnFocus="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/login"
android:layout_width="85dp"
android:layout_height="45dp"
android:layout_gravity="start"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:enabled="false"
android:text="@string/action_sign_in"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/token"
app:layout_constraintTop_toTopOf="parent" />
<ProgressBar
android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="64dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/token"
app:layout_constraintStart_toStartOf="@+id/token"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<EditText
android:id="@+id/openp2pLog"
android:layout_width="359dp"
android:layout_height="548dp"
android:layout_marginTop="24dp"
android:ems="10"
android:focusable="false"
android:inputType="none"
android:text="Name"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/onlineState" />
<Button
android:id="@+id/profile"
android:layout_width="250dp"
android:layout_height="45dp"
android:layout_marginTop="8dp"
android:text="打开控制台查看Token"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/token" />
<EditText
android:id="@+id/onlineState"
android:layout_width="85dp"
android:layout_height="45dp"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="8dp"
android:ems="10"
android:inputType="textPersonName"
android:text="未登录"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profile"
app:layout_constraintTop_toBottomOf="@+id/login" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
+5
View File
@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
+43
View File
@@ -0,0 +1,43 @@
<resources>
<string name="app_name">OpenP2P</string>
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">登录</string>
<string name="action_sign_in_short">Sign in</string>
<string name="welcome">"Welcome !"</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Token可以在 https://console.openp2p.cn/profile 获得</string>
<string name="login_failed">"Login failed"</string>
<string name="phone_setting">"安卓系统默认设置的”杀后台进程“会导致 OpenP2P 在后台运行一会后,被系统杀死进程,导致您的体验受到影响。您可以通过以下方式修改几个设置,解决此问题:
华为鸿蒙:
1. 允许应用后台运行:进入设置 → 搜索进入 应用启动管理 → 关闭 OpenP2P 的 自动管理 开关 → 在弹框中勾选 允许后台活动
2. 避免应用被电池优化程序清理:进入设置 → 搜索进入电池优化 → 不允许 →选择所有应用 → 找到无法后台运行的应用 → 设置为不允许
3. 关闭省电模式:进入设置 → 电池 → 关闭 省电模式 开关
4. 保持设备网络连接:进入设置 → 电池 → 更多电池设置 → 开启 休眠时始终保持网络连接 开关。
5. 给后台运行的应用加锁:打开应用后 → 进入多任务界面 → 下拉选中的卡片进行加锁 → 然后点击清理图标清理其他不经常使用的应用
6. 设置开发人员选项中相关开关:进入设置 → 搜索进入 开发人员选项 → 找到 不保留活动 开关后关闭 → 并在 后台进程限制 选择 标准限制
华为手机:
进入”设置“,搜索并进入“电池优化“界面,选中 OpenP2P 程序,不允许系统对其进行电池优化;
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电情况“,开启”允许后台活动“即可;
小米手机:
进入”设置“,进入”更多应用“界面,选中 OpenP2P 程序,点击”省电策略“,设置为”无限制“;
进入”设置“,进入”特色功能“界面,选中”游戏加速“,将其关闭即可;
OPPO手机:
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电保护“,关闭所有设置项即可;
VIVO手机:
进入”设置“,进入”电池“界面,点击”后台高耗电“,将 OpenP2P 设为允许高耗电时继续运行即可;
魅族手机:
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”权限管理“,点击”后台管理“,选择”允许后台运行“即可;
一加手机:
进入”设置“,进入”电池“界面,点击”电池优化“,将 OpenP2P 设为”不优化“即可;
三星手机:
进入”智能管理器“,进入”应用程序管理“界面,点击”管理自动运行“,将 OpenP2P 设为”允许后台运行“即可;"</string>
</resources>
+16
View File
@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
+30
View File
@@ -0,0 +1,30 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.8.20"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:8.1.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+19
View File
@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Tue Dec 05 16:04:08 CST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
+84
View File
@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
BIN
View File
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = "OpenP2P"
include ':app'
-45
View File
@@ -1,45 +0,0 @@
package main
import (
"sync"
"time"
)
// BandwidthLimiter ...
type BandwidthLimiter struct {
ts time.Time
bw int // mbps
freeBytes int // bytes
maxFreeBytes int // bytes
mtx sync.Mutex
}
// mbps
func newBandwidthLimiter(bw int) *BandwidthLimiter {
return &BandwidthLimiter{
bw: bw,
ts: time.Now(),
maxFreeBytes: bw * 1024 * 1024 / 8,
freeBytes: bw * 1024 * 1024 / 8,
}
}
// Add ...
func (bl *BandwidthLimiter) Add(bytes int) {
if bl.bw <= 0 {
return
}
bl.mtx.Lock()
defer bl.mtx.Unlock()
// calc free flow 1000*1000/1024/1024=0.954; 1024*1024/1000/1000=1.048
bl.freeBytes += int(time.Since(bl.ts) * time.Duration(bl.bw) / 8 / 954)
if bl.freeBytes > bl.maxFreeBytes {
bl.freeBytes = bl.maxFreeBytes
}
bl.freeBytes -= bytes
bl.ts = time.Now()
if bl.freeBytes < 0 {
// sleep for the overflow
time.Sleep(time.Millisecond * time.Duration(-bl.freeBytes/(bl.bw*1048/8)))
}
}
+9
View File
@@ -0,0 +1,9 @@
package main
import (
op "openp2p/core"
)
func main() {
op.Run()
}
-45
View File
@@ -1,45 +0,0 @@
package main
import (
"log"
"testing"
)
func TestAESCBC(t *testing.T) {
for packetSize := 1; packetSize <= 8192; packetSize++ {
log.Println("test packetSize=", packetSize)
data := make([]byte, packetSize)
for i := 0; i < packetSize; i++ {
data[i] = byte('0' + i%10)
}
p2pEncryptBuf := make([]byte, len(data)+PaddingSize)
inBuf := make([]byte, len(data)+PaddingSize)
copy(inBuf, data)
cryptKey := []byte("0123456789ABCDEF")
sendBuf, err := encryptBytes(cryptKey, p2pEncryptBuf, inBuf, len(data))
if err != nil {
t.Errorf("encrypt packet failed:%s", err)
}
log.Printf("encrypt data len=%d\n", len(sendBuf))
decryptBuf := make([]byte, len(sendBuf))
outBuf, err := decryptBytes(cryptKey, decryptBuf, sendBuf, len(sendBuf))
if err != nil {
t.Errorf("decrypt packet failed:%s", err)
}
// log.Printf("len=%d,content=%s\n", len(outBuf), outBuf)
log.Printf("decrypt data len=%d\n", len(outBuf))
log.Println("validate")
for i := 0; i < len(outBuf); i++ {
if outBuf[i] != byte('0'+i%10) {
t.Error("validate failed")
}
}
log.Println("validate ok")
}
}
func TestNetInfo(t *testing.T) {
log.Println(netInfo())
}
-213
View File
@@ -1,213 +0,0 @@
package main
import (
"encoding/json"
"flag"
"io/ioutil"
"os"
"sync"
"time"
)
var gConf Config
const IntValueNotSet int = -99999999
type AppConfig struct {
// required
AppName string
Protocol string
SrcPort int
PeerNode string
DstPort int
DstHost string
PeerUser string
Enabled int // default:1
// runtime info
peerToken uint64
peerNatType int
peerIP string
peerConeNatPort int
retryNum int
retryTime time.Time
nextRetryTime time.Time
shareBandwidth int
}
// TODO: add loglevel, maxlogfilesize
type Config struct {
Network NetworkConfig `json:"network"`
Apps []*AppConfig `json:"apps"`
LogLevel int
mtx sync.Mutex
}
func (c *Config) switchApp(app AppConfig, enabled int) {
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps[i].Enabled = enabled
c.Apps[i].retryNum = 0
c.Apps[i].nextRetryTime = time.Now()
return
}
}
}
func (c *Config) add(app AppConfig, override bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
if app.SrcPort == 0 || app.DstPort == 0 {
gLog.Println(LevelERROR, "invalid app ", app)
return
}
if override {
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps[i] = &app // override it
return
}
}
}
c.Apps = append(c.Apps, &app)
}
func (c *Config) delete(app AppConfig) {
if app.SrcPort == 0 || app.DstPort == 0 {
return
}
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
return
}
}
}
func (c *Config) save() {
c.mtx.Lock()
defer c.mtx.Unlock()
data, _ := json.MarshalIndent(c, "", " ")
err := ioutil.WriteFile("config.json", data, 0644)
if err != nil {
gLog.Println(LevelERROR, "save config.json error:", err)
}
}
func (c *Config) load() error {
c.mtx.Lock()
c.LogLevel = IntValueNotSet
c.Network.ShareBandwidth = IntValueNotSet
defer c.mtx.Unlock()
data, err := ioutil.ReadFile("config.json")
if err != nil {
// gLog.Println(LevelERROR, "read config.json error:", err)
return err
}
err = json.Unmarshal(data, &c)
if err != nil {
gLog.Println(LevelERROR, "parse config.json error:", err)
}
return err
}
type NetworkConfig struct {
// local info
Token uint64
Node string
User string
localIP string
ipv6 string
mac string
os string
publicIP string
natType int
ShareBandwidth int
// server info
ServerHost string
ServerPort int
UDPPort1 int
UDPPort2 int
}
func parseParams() {
serverHost := flag.String("serverhost", "api.openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
node := flag.String("node", "", "node name. 8-31 characters")
token := flag.Uint64("token", 0, "token")
peerNode := flag.String("peernode", "", "peer node name that you want to connect")
dstIP := flag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := flag.Int("dstport", 0, "destination port ")
srcPort := flag.Int("srcport", 0, "source port ")
protocol := flag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
shareBandwidth := flag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
daemonMode := flag.Bool("d", false, "daemonMode")
logLevel := flag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
flag.Parse()
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
gConf.load()
if config.SrcPort != 0 {
gConf.add(config, true)
}
gConf.mtx.Lock()
// spec paramters in commandline will always be used
flag.Visit(func(f *flag.Flag) {
if f.Name == "sharebandwidth" {
gConf.Network.ShareBandwidth = *shareBandwidth
}
if f.Name == "node" {
gConf.Network.Node = *node
}
if f.Name == "serverhost" {
gConf.Network.ServerHost = *serverHost
}
if f.Name == "loglevel" {
gConf.LogLevel = *logLevel
}
})
if gConf.Network.ServerHost == "" {
gConf.Network.ServerHost = *serverHost
}
if gConf.Network.Node == "" {
if *node == "" { // config and param's node both empty
hostname := defaultNodeName()
node = &hostname
}
gConf.Network.Node = *node
}
if *token != 0 {
gConf.Network.Token = *token
}
if gConf.LogLevel == IntValueNotSet {
gConf.LogLevel = *logLevel
}
if gConf.Network.ShareBandwidth == IntValueNotSet {
gConf.Network.ShareBandwidth = *shareBandwidth
}
gConf.Network.ServerPort = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gLog.setLevel(LogLevel(gConf.LogLevel))
gConf.mtx.Unlock()
gConf.save()
if *daemonMode {
d := daemon{}
d.run()
os.Exit(0)
}
}
+3 -4
View File
@@ -1,10 +1,9 @@
{
"network": {
"Node": "YOUR_NODE_NAME",
"User": "YOUR_USER_NAME",
"Password": "YOUR_PASSWORD",
"ServerHost": "openp2p.cn",
"ServerPort": 27182,
"Token": "YOUR_TOKEN",
"ServerHost": "api.openp2p.cn",
"ServerPort": 27183,
"UDPPort1": 27182,
"UDPPort2": 27183
},
+130 -8
View File
@@ -1,22 +1,27 @@
package main
package openp2p
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/tls"
"encoding/binary"
"encoding/json"
"fmt"
"math/big"
"math/rand"
"net"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
const MinNodeNameLen = 8
func getmac(ip string) string {
//get mac relative to the ip address which connected to the mq.
ifaces, err := net.Interfaces()
if err != nil {
return ""
@@ -77,6 +82,7 @@ func pkcs7UnPadding(origData []byte, dataLen int) ([]byte, error) {
return origData[:(dataLen - unPadLen)], nil
}
// AES-CBC
func encryptBytes(key []byte, out, in []byte, plainLen int) ([]byte, error) {
if len(key) == 0 {
return in[:plainLen], nil
@@ -122,20 +128,19 @@ func netInfo() *NetInfo {
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
r, err := client.Get("https://ifconfig.co/json")
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
gLog.Println(LvDEBUG, "netInfo error:", err)
continue
}
defer r.Body.Close()
buf := make([]byte, 1024*64)
n, err := r.Body.Read(buf)
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
gLog.Println(LvDEBUG, "netInfo error:", err)
continue
}
rsp := NetInfo{}
err = json.Unmarshal(buf[:n], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong NetInfo:%s", err)
if err = json.Unmarshal(buf[:n], &rsp); err != nil {
gLog.Printf(LvERROR, "wrong NetInfo:%s", err)
continue
}
return &rsp
@@ -153,8 +158,125 @@ func execOutput(name string, args ...string) string {
func defaultNodeName() string {
name, _ := os.Hostname()
for len(name) < 8 {
for len(name) < MinNodeNameLen {
name = fmt.Sprintf("%s%d", name, rand.Int()%10)
}
return name
}
const EQUAL int = 0
const GREATER int = 1
const LESS int = -1
func compareVersion(v1, v2 string) int {
if v1 == v2 {
return EQUAL
}
v1Arr := strings.Split(v1, ".")
v2Arr := strings.Split(v2, ".")
for i, subVer := range v1Arr {
if len(v2Arr) <= i {
return GREATER
}
subv1, _ := strconv.Atoi(subVer)
subv2, _ := strconv.Atoi(v2Arr[i])
if subv1 > subv2 {
return GREATER
}
if subv1 < subv2 {
return LESS
}
}
return LESS
}
func parseMajorVer(ver string) int {
v1Arr := strings.Split(ver, ".")
if len(v1Arr) > 0 {
n, _ := strconv.ParseInt(v1Arr[0], 10, 32)
return int(n)
}
return 0
}
func IsIPv6(ipStr string) bool {
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
return ip.To16() != nil && ip.To4() == nil
}
var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-")
func randStr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func execCommand(commandPath string, wait bool, arg ...string) (err error) {
command := exec.Command(commandPath, arg...)
err = command.Start()
if err != nil {
return
}
if wait {
err = command.Wait()
}
return
}
func sanitizeFileName(fileName string) string {
validFileName := fileName
invalidChars := []string{"\\", "/", ":", "*", "?", "\"", "<", ">", "|"}
for _, char := range invalidChars {
validFileName = strings.ReplaceAll(validFileName, char, " ")
}
return validFileName
}
func prettyJson(s interface{}) string {
jsonData, err := json.MarshalIndent(s, "", " ")
if err != nil {
fmt.Println("Error marshalling JSON:", err)
return ""
}
return string(jsonData)
}
func inetAtoN(ipstr string) (uint32, error) { // support both ipnet or single ip
i, _, err := net.ParseCIDR(ipstr)
if err != nil {
i = net.ParseIP(ipstr)
if i == nil {
return 0, err
}
}
ret := big.NewInt(0)
ret.SetBytes(i.To4())
return uint32(ret.Int64()), nil
}
func calculateChecksum(data []byte) uint16 {
length := len(data)
sum := uint32(0)
// Calculate the sum of 16-bit words
for i := 0; i < length-1; i += 2 {
sum += uint32(binary.BigEndian.Uint16(data[i : i+2]))
}
// Add the last byte (if odd length)
if length%2 != 0 {
sum += uint32(data[length-1])
}
// Fold 32-bit sum to 16 bits
sum = (sum >> 16) + (sum & 0xffff)
sum += (sum >> 16)
return uint16(^sum)
}
+129
View File
@@ -0,0 +1,129 @@
package openp2p
import (
"fmt"
"log"
"testing"
)
func TestAESCBC(t *testing.T) {
for packetSize := 1; packetSize <= 8192; packetSize++ {
log.Println("test packetSize=", packetSize)
data := make([]byte, packetSize)
for i := 0; i < packetSize; i++ {
data[i] = byte('0' + i%10)
}
p2pEncryptBuf := make([]byte, len(data)+PaddingSize)
inBuf := make([]byte, len(data)+PaddingSize)
copy(inBuf, data)
cryptKey := []byte("0123456789ABCDEF")
sendBuf, err := encryptBytes(cryptKey, p2pEncryptBuf, inBuf, len(data))
if err != nil {
t.Errorf("encrypt packet failed:%s", err)
}
log.Printf("encrypt data len=%d\n", len(sendBuf))
decryptBuf := make([]byte, len(sendBuf))
outBuf, err := decryptBytes(cryptKey, decryptBuf, sendBuf, len(sendBuf))
if err != nil {
t.Errorf("decrypt packet failed:%s", err)
}
// log.Printf("len=%d,content=%s\n", len(outBuf), outBuf)
log.Printf("decrypt data len=%d\n", len(outBuf))
log.Println("validate")
for i := 0; i < len(outBuf); i++ {
if outBuf[i] != byte('0'+i%10) {
t.Error("validate failed")
}
}
log.Println("validate ok")
}
}
func TestNetInfo(t *testing.T) {
log.Println(netInfo())
}
func assertCompareVersion(t *testing.T, v1 string, v2 string, result int) {
if compareVersion(v1, v2) != result {
t.Errorf("compare version %s %s fail\n", v1, v2)
}
}
func assertParseMajorVer(t *testing.T, v string, result int) {
if parseMajorVer(v) != result {
t.Errorf("ParseMajorVer %s fail\n", v)
}
}
func TestCompareVersion(t *testing.T) {
// test =
assertCompareVersion(t, "0.98.0", "0.98.0", EQUAL)
assertCompareVersion(t, "0.98", "0.98", EQUAL)
assertCompareVersion(t, "1.4.0", "1.4.0", EQUAL)
assertCompareVersion(t, "1.5.0", "1.5.0", EQUAL)
// test >
assertCompareVersion(t, "0.98.0.22345", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1.98.0.12345", "0.98", GREATER)
assertCompareVersion(t, "10.98.0.12345", "9.98.0.12345", GREATER)
assertCompareVersion(t, "1.4.0", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1.4", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1", "0.98.0.12345", GREATER)
// test <
assertCompareVersion(t, "0.98.0.12345", "0.98.0.12346", LESS)
assertCompareVersion(t, "9.98.0.12345", "10.98.0.12345", LESS)
assertCompareVersion(t, "1.4.2", "1.5.0", LESS)
assertCompareVersion(t, "", "1.5.0", LESS)
}
func TestParseMajorVer(t *testing.T) {
assertParseMajorVer(t, "0.98.0", 0)
assertParseMajorVer(t, "0.98", 0)
assertParseMajorVer(t, "1.4.0", 1)
assertParseMajorVer(t, "1.5.0", 1)
assertParseMajorVer(t, "0.98.0.22345", 0)
assertParseMajorVer(t, "1.98.0.12345", 1)
assertParseMajorVer(t, "10.98.0.12345", 10)
assertParseMajorVer(t, "1.4.0", 1)
assertParseMajorVer(t, "1.4", 1)
assertParseMajorVer(t, "1", 1)
assertParseMajorVer(t, "2", 2)
assertParseMajorVer(t, "3", 3)
assertParseMajorVer(t, "2.1.0", 2)
assertParseMajorVer(t, "3.0.0", 3)
}
func TestIsIPv6(t *testing.T) {
tests := []struct {
ipStr string
want bool
}{
{"2001:0db8:85a3:0000:0000:8a2e:0370:7334", true}, // 有效的 IPv6 地址
{"2001:db8::2:1", true}, // 有效的 IPv6 地址
{"192.168.1.1", false}, // 无效的 IPv6 地址,是 IPv4
{"2001:db8::G:1", false}, // 无效的 IPv6 地址,包含非法字符
// 可以添加更多测试用例
}
for _, tt := range tests {
got := IsIPv6(tt.ipStr)
if got != tt.want {
t.Errorf("isValidIPv6(%s) = %v, want %v", tt.ipStr, got, tt.want)
}
}
}
func TestNodeID(t *testing.T) {
node1 := "n1-stable"
node2 := "tony-stable"
nodeID1 := NodeNameToID(node1)
nodeID2 := NodeNameToID(node2)
if nodeID1 < nodeID2 {
fmt.Printf("%s < %s\n", node1, node2)
} else {
fmt.Printf("%s >= %s\n", node1, node2)
}
}
+514
View File
@@ -0,0 +1,514 @@
package openp2p
import (
"encoding/json"
"flag"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
)
var gConf Config
type AppConfig struct {
// required
AppName string
Protocol string
UnderlayProtocol string
PunchPriority int // bitwise DisableTCP|DisableUDP|TCPFirst 0:tcp and udp both enable, udp first
Whitelist string
SrcPort int
PeerNode string
DstPort int
DstHost string
PeerUser string
RelayNode string
ForceRelay int // default:0 disable;1 enable
Enabled int // default:1
// runtime info
relayMode string // private|public
peerVersion string
peerToken uint64
peerNatType int
peerLanIP string
hasIPv4 int
peerIPv6 string
hasUPNPorNATPMP int
peerIP string
peerConeNatPort int
retryNum int
retryTime time.Time
nextRetryTime time.Time
shareBandwidth int
errMsg string
connectTime time.Time
fromToken uint64
linkMode string
isUnderlayServer int
}
const (
PunchPriorityTCPFirst = 1
PunchPriorityUDPDisable = 1 << 1
PunchPriorityTCPDisable = 1 << 2
)
func (c *AppConfig) ID() uint64 {
if c.SrcPort == 0 { // memapp
return NodeNameToID(c.PeerNode)
}
if c.Protocol == "tcp" {
return uint64(c.SrcPort) * 10
}
return uint64(c.SrcPort)*10 + 1
}
func (c *AppConfig) LogPeerNode() string {
if c.relayMode == "public" { // memapp
return fmt.Sprintf("%d", NodeNameToID(c.PeerNode))
}
return c.PeerNode
}
type Config struct {
Network NetworkConfig `json:"network"`
Apps []*AppConfig `json:"apps"`
LogLevel int
MaxLogSize int
daemonMode bool
mtx sync.Mutex
sdwanMtx sync.Mutex
sdwan SDWANInfo
delNodes []*SDWANNode
addNodes []*SDWANNode
}
func (c *Config) getSDWAN() SDWANInfo {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
return c.sdwan
}
func (c *Config) getDelNodes() []*SDWANNode {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
return c.delNodes
}
func (c *Config) getAddNodes() []*SDWANNode {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
return c.addNodes
}
func (c *Config) resetSDWAN() {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
c.delNodes = []*SDWANNode{}
c.addNodes = []*SDWANNode{}
c.sdwan = SDWANInfo{}
}
func (c *Config) setSDWAN(s SDWANInfo) {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
// get old-new
c.delNodes = []*SDWANNode{}
for _, oldNode := range c.sdwan.Nodes {
isDeleted := true
for _, newNode := range s.Nodes {
if oldNode.Name == newNode.Name && oldNode.IP == newNode.IP && oldNode.Resource == newNode.Resource && c.sdwan.Mode == s.Mode && c.sdwan.CentralNode == s.CentralNode {
isDeleted = false
break
}
}
if isDeleted {
c.delNodes = append(c.delNodes, oldNode)
}
}
// get new-old
c.addNodes = []*SDWANNode{}
for _, newNode := range s.Nodes {
isNew := true
for _, oldNode := range c.sdwan.Nodes {
if oldNode.Name == newNode.Name && oldNode.IP == newNode.IP && oldNode.Resource == newNode.Resource && c.sdwan.Mode == s.Mode && c.sdwan.CentralNode == s.CentralNode {
isNew = false
break
}
}
if isNew {
c.addNodes = append(c.addNodes, newNode)
}
}
c.sdwan = s
}
func (c *Config) switchApp(app AppConfig, enabled int) {
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps[i].Enabled = enabled
c.Apps[i].retryNum = 0
c.Apps[i].nextRetryTime = time.Now()
break
}
}
c.save()
}
func (c *Config) retryApp(peerNode string) {
GNetwork.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp)
if app.config.PeerNode == peerNode {
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
}
if app.config.RelayNode == peerNode {
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
}
return true
})
}
func (c *Config) retryAllApp() {
GNetwork.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp)
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
return true
})
}
func (c *Config) retryAllMemApp() {
GNetwork.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp)
if app.config.SrcPort != 0 {
return true
}
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
return true
})
}
func (c *Config) add(app AppConfig, override bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
if override {
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].PeerNode == app.PeerNode && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
c.Apps[i] = &app // override it
return
}
}
}
c.Apps = append(c.Apps, &app)
}
func (c *Config) delete(app AppConfig) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
for i := 0; i < len(c.Apps); i++ {
if (app.SrcPort != 0 && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort) || // normal app
(app.SrcPort == 0 && c.Apps[i].SrcPort == 0 && c.Apps[i].PeerNode == app.PeerNode) { // memapp
if i == len(c.Apps)-1 {
c.Apps = c.Apps[:i]
} else {
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
}
return
}
}
}
func (c *Config) save() {
// c.mtx.Lock()
// defer c.mtx.Unlock() // internal call
if c.Network.Token == 0 {
return
}
data, _ := json.MarshalIndent(c, "", " ")
err := os.WriteFile("config.json", data, 0644)
if err != nil {
gLog.Println(LvERROR, "save config.json error:", err)
}
}
func (c *Config) saveCache() {
// c.mtx.Lock()
// defer c.mtx.Unlock() // internal call
if c.Network.Token == 0 {
return
}
data, _ := json.MarshalIndent(c, "", " ")
err := os.WriteFile("config.json0", data, 0644)
if err != nil {
gLog.Println(LvERROR, "save config.json0 error:", err)
}
}
func init() {
gConf.LogLevel = int(LvINFO)
gConf.MaxLogSize = 1024 * 1024
gConf.Network.ShareBandwidth = 10
gConf.Network.ServerHost = "api.openp2p.cn"
gConf.Network.ServerPort = WsPort
}
func (c *Config) load() error {
c.mtx.Lock()
defer c.mtx.Unlock()
data, err := os.ReadFile("config.json")
if err != nil {
return c.loadCache()
}
err = json.Unmarshal(data, &c)
if err != nil {
gLog.Println(LvERROR, "parse config.json error:", err)
// try cache
return c.loadCache()
}
// load ok. cache it
var filteredApps []*AppConfig // filter memapp
for _, app := range c.Apps {
if app.SrcPort != 0 {
filteredApps = append(filteredApps, app)
}
}
c.Apps = filteredApps
c.saveCache()
return err
}
func (c *Config) loadCache() error {
data, err := os.ReadFile("config.json0")
if err != nil {
return err
}
err = json.Unmarshal(data, &c)
if err != nil {
gLog.Println(LvERROR, "parse config.json0 error:", err)
}
return err
}
// deal with multi-thread r/w
func (c *Config) setToken(token uint64) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
if token != 0 {
c.Network.Token = token
}
}
func (c *Config) setUser(user string) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
c.Network.User = user
}
func (c *Config) setNode(node string) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
c.Network.Node = node
c.Network.nodeID = NodeNameToID(c.Network.Node)
}
func (c *Config) nodeID() uint64 {
c.mtx.Lock()
defer c.mtx.Unlock()
if c.Network.nodeID == 0 {
c.Network.nodeID = NodeNameToID(c.Network.Node)
}
return c.Network.nodeID
}
func (c *Config) setShareBandwidth(bw int) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
c.Network.ShareBandwidth = bw
}
func (c *Config) setIPv6(v6 string) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.Network.publicIPv6 = v6
}
func (c *Config) IPv6() string {
c.mtx.Lock()
defer c.mtx.Unlock()
return c.Network.publicIPv6
}
type NetworkConfig struct {
// local info
Token uint64
Node string
nodeID uint64
User string
localIP string
mac string
os string
publicIP string
natType int
hasIPv4 int
publicIPv6 string // must lowwer-case not save json
hasUPNPorNATPMP int
ShareBandwidth int
// server info
ServerHost string
ServerPort int
UDPPort1 int
UDPPort2 int
TCPPort int
}
func parseParams(subCommand string, cmd string) {
fset := flag.NewFlagSet(subCommand, flag.ExitOnError)
serverHost := fset.String("serverhost", "api.openp2p.cn", "server host ")
serverPort := fset.Int("serverport", WsPort, "server port ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
token := fset.Uint64("token", 0, "token")
node := fset.String("node", "", "node name. 8-31 characters. if not set, it will be hostname")
peerNode := fset.String("peernode", "", "peer node name that you want to connect")
dstIP := fset.String("dstip", "127.0.0.1", "destination ip ")
whiteList := fset.String("whitelist", "", "whitelist for p2pApp ")
dstPort := fset.Int("dstport", 0, "destination port ")
srcPort := fset.Int("srcport", 0, "source port ")
tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip")
protocol := fset.String("protocol", "tcp", "tcp or udp")
underlayProtocol := fset.String("underlay_protocol", "quic", "quic or kcp")
punchPriority := fset.Int("punch_priority", 0, "bitwise DisableTCP|DisableUDP|UDPFirst 0:tcp and udp both enable, tcp first")
appName := fset.String("appname", "", "app name")
relayNode := fset.String("relaynode", "", "relaynode")
shareBandwidth := fset.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
daemonMode := fset.Bool("d", false, "daemonMode")
notVerbose := fset.Bool("nv", false, "not log console")
newconfig := fset.Bool("newconfig", false, "not load existing config.json")
logLevel := fset.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
maxLogSize := fset.Int("maxlogsize", 1024*1024, "default 1MB")
if cmd == "" {
if subCommand == "" { // no subcommand
fset.Parse(os.Args[1:])
} else {
fset.Parse(os.Args[2:])
}
} else {
gLog.Println(LvINFO, "cmd=", cmd)
args := strings.Split(cmd, " ")
fset.Parse(args)
}
gLog.setMaxSize(int64(*maxLogSize))
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.Whitelist = *whiteList
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.UnderlayProtocol = *underlayProtocol
config.PunchPriority = *punchPriority
config.AppName = *appName
config.RelayNode = *relayNode
if !*newconfig {
gConf.load() // load old config. otherwise will clear all apps
}
if config.SrcPort != 0 { // filter memapp
gConf.add(config, true)
}
// gConf.mtx.Lock() // when calling this func it's single-thread no lock
gConf.daemonMode = *daemonMode
// spec paramters in commandline will always be used
fset.Visit(func(f *flag.Flag) {
if f.Name == "sharebandwidth" {
gConf.Network.ShareBandwidth = *shareBandwidth
}
if f.Name == "node" {
gConf.setNode(*node)
}
if f.Name == "serverhost" {
gConf.Network.ServerHost = *serverHost
}
if f.Name == "loglevel" {
gConf.LogLevel = *logLevel
}
if f.Name == "maxlogsize" {
gConf.MaxLogSize = *maxLogSize
}
if f.Name == "tcpport" {
gConf.Network.TCPPort = *tcpPort
}
if f.Name == "token" {
gConf.setToken(*token)
}
})
// set default value
if gConf.Network.ServerHost == "" {
gConf.Network.ServerHost = *serverHost
}
if *node != "" {
gConf.setNode(*node)
} else {
envNode := os.Getenv("OPENP2P_NODE")
if envNode != "" {
gConf.setNode(envNode)
}
if gConf.Network.Node == "" { // if node name not set. use os.Hostname
gConf.setNode(defaultNodeName())
}
}
if gConf.Network.TCPPort == 0 {
if *tcpPort == 0 {
p := int(gConf.nodeID()%15000 + 50000)
tcpPort = &p
}
gConf.Network.TCPPort = *tcpPort
}
if *token == 0 {
envToken := os.Getenv("OPENP2P_TOKEN")
if envToken != "" {
if n, err := strconv.ParseUint(envToken, 10, 64); n != 0 && err == nil {
gConf.setToken(n)
}
}
}
gConf.Network.ServerPort = *serverPort
gConf.Network.UDPPort1 = UDPPort1
gConf.Network.UDPPort2 = UDPPort2
gLog.setLevel(LogLevel(gConf.LogLevel))
if *notVerbose {
gLog.setMode(LogFile)
}
// gConf.mtx.Unlock()
gConf.save()
}
+178
View File
@@ -0,0 +1,178 @@
package openp2p
import (
"encoding/json"
"testing"
)
func TestSetSDWAN_ChangeNode(t *testing.T) {
conf := Config{}
sdwanInfo := SDWANInfo{}
sdwanStr := `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.4","resource":"10.1.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo)
if len(conf.getDelNodes()) > 0 {
t.Errorf("getDelNodes error")
return
}
if len(conf.getAddNodes()) != 7 {
t.Errorf("getAddNodes error")
return
}
sdwanInfo2 := SDWANInfo{}
sdwanStr = `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo2); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo2)
diff := conf.getDelNodes()
if len(diff) != 1 && diff[0].IP != "10.2.3.4" {
t.Errorf("getDelNodes error")
return
}
sdwanInfo3 := SDWANInfo{}
sdwanStr = `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo3); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo3)
diff = conf.getDelNodes()
if len(diff) != 1 && diff[0].IP != "10.2.3.60" {
t.Errorf("getDelNodes error")
return
}
// add new node
sdwanInfo4 := SDWANInfo{}
sdwanStr = `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo4); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo4)
diff = conf.getDelNodes()
if len(diff) > 0 {
t.Errorf("getDelNodes error")
return
}
diff = conf.getAddNodes()
if len(diff) != 1 && diff[0].IP != "10.2.3.60" {
t.Errorf("getAddNodes error")
return
}
}
func TestSetSDWAN_ChangeNodeIP(t *testing.T) {
conf := Config{}
sdwanInfo := SDWANInfo{}
sdwanStr := `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.4","resource":"10.1.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo)
if len(conf.getDelNodes()) > 0 {
t.Errorf("getDelNodes error")
return
}
sdwanInfo2 := SDWANInfo{}
sdwanStr = `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.44","resource":"10.1.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo2); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo2)
diff := conf.getDelNodes()
if len(diff) != 1 && diff[0].IP != "10.2.3.4" {
t.Errorf("getDelNodes error")
return
}
diff = conf.getAddNodes()
if len(diff) != 1 || diff[0].IP != "10.2.3.44" {
t.Errorf("getAddNodes error")
return
}
}
func TestSetSDWAN_ClearAll(t *testing.T) {
conf := Config{}
sdwanInfo := SDWANInfo{}
sdwanStr := `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.4","resource":"10.1.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo)
if len(conf.getDelNodes()) > 0 {
t.Errorf("getDelNodes error")
return
}
sdwanInfo2 := SDWANInfo{}
sdwanStr = `{"Nodes":null}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo2); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo2)
diff := conf.getDelNodes()
if len(diff) != 7 {
t.Errorf("getDelNodes error")
return
}
diff = conf.getAddNodes()
if len(diff) != 0 {
t.Errorf("getAddNodes error")
return
}
}
func TestSetSDWAN_ChangeNodeResource(t *testing.T) {
conf := Config{}
sdwanInfo := SDWANInfo{}
sdwanStr := `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.4","resource":"10.1.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo)
if len(conf.getDelNodes()) > 0 {
t.Errorf("getDelNodes error")
return
}
sdwanInfo2 := SDWANInfo{}
sdwanStr = `{"id":1312667996276071700,"name":"network1","gateway":"10.2.3.254/24","mode":"fullmesh","centralNode":"n1-stable","enable":1,"Nodes":[{"name":"222-debug","ip":"10.2.3.13"},{"name":"222stable","ip":"10.2.3.222"},{"name":"5800-debug","ip":"10.2.3.56"},{"name":"Mate60pro","ip":"10.2.3.60"},{"name":"Mymatepad2023","ip":"10.2.3.23"},{"name":"n1-stable","ip":"10.2.3.29","resource":"192.168.3.0/24"},{"name":"tony-stable","ip":"10.2.3.4","resource":"10.11.0.0/16"}]}`
if err := json.Unmarshal([]byte(sdwanStr), &sdwanInfo2); err != nil {
t.Errorf("unmarshal error")
return
}
conf.setSDWAN(sdwanInfo2)
diff := conf.getDelNodes()
if len(diff) != 1 && diff[0].IP != "10.2.3.4" {
t.Errorf("getDelNodes error")
return
}
diff = conf.getAddNodes()
if len(diff) != 1 || diff[0].Resource != "10.11.0.0/16" {
t.Errorf("getAddNodes error")
return
}
}
func TestInetAtoN(t *testing.T) {
ipa, _ := inetAtoN("121.5.147.4")
t.Log(ipa)
ipa, _ = inetAtoN("121.5.147.4/32")
t.Log(ipa)
}
+120
View File
@@ -0,0 +1,120 @@
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}}
lastRebootTime := time.Now()
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
}
if time.Since(lastRebootTime) < time.Second*10 {
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
}
+33
View File
@@ -0,0 +1,33 @@
package openp2p
import (
"errors"
)
// error message
var (
// ErrorS2S string = "s2s is not supported"
// ErrorHandshake string = "handshake error"
ErrorS2S = errors.New("s2s is not supported")
ErrorHandshake = errors.New("handshake error")
ErrorNewUser = errors.New("new user")
ErrorLogin = errors.New("user or password not correct")
ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters")
ErrReadDB = errors.New("read db error")
ErrNoUpdate = errors.New("there are currently no updates available")
ErrPeerOffline = errors.New("peer offline")
ErrNetwork = errors.New("network error")
ErrMsgFormat = errors.New("message format wrong")
ErrVersionNotCompatible = errors.New("version not compatible")
ErrOverlayConnDisconnect = errors.New("overlay connection is disconnected")
ErrConnectRelayNode = errors.New("connect relay node error")
ErrConnectPublicV4 = errors.New("connect public ipv4 error")
ErrMsgChannelNotFound = errors.New("message channel not found")
ErrRelayTunnelNotFound = errors.New("relay tunnel not found")
ErrSymmetricLimit = errors.New("symmetric limit")
ErrForceRelay = errors.New("force relay")
ErrPeerConnectRelay = errors.New("peer connect relayNode error")
ErrBuildTunnelBusy = errors.New("build tunnel busy")
ErrMemAppTunnelNotFound = errors.New("memapp tunnel not found")
ErrRemoteServiceUnable = errors.New("remote service unable")
)
+482
View File
@@ -0,0 +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)
}
+253
View File
@@ -0,0 +1,253 @@
package openp2p
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"math/rand"
"net"
"time"
)
func handshakeC2C(t *P2PTunnel) (err error) {
gLog.Printf(LvDEBUG, "handshakeC2C %s:%d:%d to %s:%d", gConf.Network.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
defer gLog.Printf(LvDEBUG, "handshakeC2C end")
conn, err := net.ListenUDP("udp", t.localHoleAddr)
if err != nil {
return err
}
defer conn.Close()
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshake error:", err)
return err
}
ra, head, buff, _, err := UDPRead(conn, HandshakeTimeout)
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C read MsgPunchHandshake error:", err)
return err
}
t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", ra.String())
var tunnelID uint64
if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{}
if err := json.Unmarshal(buff[openP2PHeaderSize:openP2PHeaderSize+int(head.DataLen)], &req); err == nil {
tunnelID = req.ID
}
} else { // compatible with old version
tunnelID = t.id
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "read %d handshake ", t.id)
UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
return err
}
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id)
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
return err
}
}
gLog.Printf(LvINFO, "handshakeC2C ok")
return nil
}
func handshakeC2S(t *P2PTunnel) error {
gLog.Printf(LvDEBUG, "handshakeC2S start")
defer gLog.Printf(LvDEBUG, "handshakeC2S end")
if !buildTunnelMtx.TryLock() {
// time.Sleep(time.Second * 3)
return ErrBuildTunnelBusy
}
defer buildTunnelMtx.Unlock()
startTime := time.Now()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randPorts := r.Perm(65532)
conn, err := net.ListenUDP("udp", t.localHoleAddr)
if err != nil {
return err
}
defer conn.Close()
go func() error {
gLog.Printf(LvDEBUG, "send symmetric handshake to %s from %d:%d start", t.config.peerIP, t.coneLocalPort, t.coneNatPort)
for i := 0; i < SymmetricHandshakeNum; i++ {
// time.Sleep(SymmetricHandshakeInterval)
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, randPorts[i]+2))
if err != nil {
return err
}
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2S write MsgPunchHandshake error:", err)
return err
}
}
gLog.Println(LvDEBUG, "send symmetric handshake end")
return nil
}()
err = conn.SetReadDeadline(time.Now().Add(HandshakeTimeout))
if err != nil {
gLog.Println(LvERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error")
return err
}
// read response of the punching hole ok port
buff := make([]byte, 1024)
_, dst, err := conn.ReadFrom(buff)
if err != nil {
gLog.Println(LvERROR, "handshakeC2S wait timeout")
return err
}
head := &openP2PHeader{}
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LvERROR, "parse p2pheader error:", err)
return err
}
t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", dst.String())
var tunnelID uint64
if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{}
if err := json.Unmarshal(buff[openP2PHeaderSize:openP2PHeaderSize+int(head.DataLen)], &req); err == nil {
tunnelID = req.ID
}
} else { // compatible with old version
tunnelID = t.id
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ", t.id)
UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
for {
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil {
gLog.Println(LvDEBUG, "handshakeC2S handshake error")
return err
}
var tunnelID uint64
if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{}
if err := json.Unmarshal(buff[openP2PHeaderSize:openP2PHeaderSize+int(head.DataLen)], &req); err == nil {
tunnelID = req.ID
}
} else { // compatible with old version
tunnelID = t.id
}
// waiting ack
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
break
}
}
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ack %s", t.id, t.remoteHoleAddr.String())
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
return err
} else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck")
}
gLog.Printf(LvINFO, "handshakeC2S ok. cost %d ms", time.Since(startTime)/time.Millisecond)
return nil
}
func handshakeS2C(t *P2PTunnel) error {
gLog.Printf(LvDEBUG, "handshakeS2C start")
defer gLog.Printf(LvDEBUG, "handshakeS2C end")
if !buildTunnelMtx.TryLock() {
// time.Sleep(time.Second * 3)
return ErrBuildTunnelBusy
}
defer buildTunnelMtx.Unlock()
startTime := time.Now()
gotCh := make(chan *net.UDPAddr, 5)
// sequencely udp send handshake, do not parallel send
gLog.Printf(LvDEBUG, "send symmetric handshake to %s:%d start", t.config.peerIP, t.config.peerConeNatPort)
gotIt := false
for i := 0; i < SymmetricHandshakeNum; i++ {
// time.Sleep(SymmetricHandshakeInterval)
go func(t *P2PTunnel) error {
conn, err := net.ListenUDP("udp", nil) // TODO: system allocated port really random?
if err != nil {
gLog.Printf(LvDEBUG, "listen error")
return err
}
defer conn.Close()
UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
_, head, buff, _, err := UDPRead(conn, HandshakeTimeout)
if err != nil {
// gLog.Println(LevelDEBUG, "one of the handshake error:", err)
return err
}
if gotIt {
return nil
}
var tunnelID uint64
if len(buff) >= openP2PHeaderSize+8 {
req := P2PHandshakeReq{}
if err := json.Unmarshal(buff[openP2PHeaderSize:openP2PHeaderSize+int(head.DataLen)], &req); err == nil {
tunnelID = req.ID
}
} else { // compatible with old version
tunnelID = t.id
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ", t.id)
UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
// may read several MsgPunchHandshake
for {
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil {
gLog.Println(LvDEBUG, "handshakeS2C handshake error")
return err
}
if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{}
if err := json.Unmarshal(buff[openP2PHeaderSize:openP2PHeaderSize+int(head.DataLen)], &req); err == nil {
tunnelID = req.ID
}
} else { // compatible with old version
tunnelID = t.id
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
break
} else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck")
}
}
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ack %s", t.id, conn.LocalAddr().String())
UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
gotIt = true
la, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
gotCh <- la
return nil
} else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck")
}
return nil
}(t)
}
gLog.Printf(LvDEBUG, "send symmetric handshake end")
if compareVersion(t.config.peerVersion, SymmetricSimultaneouslySendVersion) < 0 { // compatible with old client
gLog.Println(LvDEBUG, "handshakeS2C ready, notify peer connect")
GNetwork.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
}
select {
case <-time.After(HandshakeTimeout):
return fmt.Errorf("wait handshake timeout")
case la := <-gotCh:
t.localHoleAddr = la
gLog.Println(LvDEBUG, "symmetric handshake ok", la)
gLog.Printf(LvINFO, "handshakeS2C ok. cost %dms", time.Since(startTime)/time.Millisecond)
}
return nil
}
+124
View File
@@ -0,0 +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
}
+74
View File
@@ -0,0 +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
}
}
+179
View File
@@ -0,0 +1,179 @@
package openp2p
import (
"bytes"
"encoding/binary"
"fmt"
"log"
"net"
"strings"
"sync"
"github.com/emirpasic/gods/trees/avltree"
"github.com/emirpasic/gods/utils"
)
type IPTree struct {
tree *avltree.Tree
treeMtx sync.RWMutex
}
type IPTreeValue struct {
maxIP uint32
v interface{}
}
// TODO: deal interset
func (iptree *IPTree) DelIntIP(minIP uint32, maxIP uint32) {
iptree.tree.Remove(minIP)
}
// add 120k cost 0.5s
func (iptree *IPTree) AddIntIP(minIP uint32, maxIP uint32, v interface{}) bool {
if minIP > maxIP {
return false
}
iptree.treeMtx.Lock()
defer iptree.treeMtx.Unlock()
newMinIP := minIP
newMaxIP := maxIP
cur := iptree.tree.Root
for {
if cur == nil {
break
}
tv := cur.Value.(*IPTreeValue)
curMinIP := cur.Key.(uint32)
// newNode all in existNode, treat as inserted.
if newMinIP >= curMinIP && newMaxIP <= tv.maxIP {
return true
}
// has no interset
if newMinIP > tv.maxIP {
cur = cur.Children[1]
continue
}
if newMaxIP < curMinIP {
cur = cur.Children[0]
continue
}
// has interset, rm it and Add the new merged ip segment
iptree.tree.Remove(curMinIP)
if curMinIP < newMinIP {
newMinIP = curMinIP
}
if tv.maxIP > newMaxIP {
newMaxIP = tv.maxIP
}
cur = iptree.tree.Root
}
// put in the tree
iptree.tree.Put(newMinIP, &IPTreeValue{newMaxIP, v})
return true
}
func (iptree *IPTree) Add(minIPStr string, maxIPStr string, v interface{}) bool {
var minIP, maxIP uint32
binary.Read(bytes.NewBuffer(net.ParseIP(minIPStr).To4()), binary.BigEndian, &minIP)
binary.Read(bytes.NewBuffer(net.ParseIP(maxIPStr).To4()), binary.BigEndian, &maxIP)
return iptree.AddIntIP(minIP, maxIP, v)
}
func (iptree *IPTree) Del(minIPStr string, maxIPStr string) {
var minIP, maxIP uint32
binary.Read(bytes.NewBuffer(net.ParseIP(minIPStr).To4()), binary.BigEndian, &minIP)
binary.Read(bytes.NewBuffer(net.ParseIP(maxIPStr).To4()), binary.BigEndian, &maxIP)
iptree.DelIntIP(minIP, maxIP)
}
func (iptree *IPTree) Contains(ipStr string) bool {
var ip uint32
binary.Read(bytes.NewBuffer(net.ParseIP(ipStr).To4()), binary.BigEndian, &ip)
_, ok := iptree.Load(ip)
return ok
}
func IsLocalhost(ipStr string) bool {
if ipStr == "localhost" || ipStr == "127.0.0.1" || ipStr == "::1" {
return true
}
return false
}
func (iptree *IPTree) Load(ip uint32) (interface{}, bool) {
iptree.treeMtx.RLock()
defer iptree.treeMtx.RUnlock()
if iptree.tree == nil {
return nil, false
}
n := iptree.tree.Root
for n != nil {
tv := n.Value.(*IPTreeValue)
curMinIP := n.Key.(uint32)
switch {
case ip >= curMinIP && ip <= tv.maxIP: // hit
return tv.v, true
case ip < curMinIP:
n = n.Children[0]
default:
n = n.Children[1]
}
}
return nil, false
}
func (iptree *IPTree) Size() int {
iptree.treeMtx.RLock()
defer iptree.treeMtx.RUnlock()
return iptree.tree.Size()
}
func (iptree *IPTree) Print() {
iptree.treeMtx.RLock()
defer iptree.treeMtx.RUnlock()
log.Println("size:", iptree.Size())
log.Println(iptree.tree.String())
}
func (iptree *IPTree) Clear() {
iptree.treeMtx.Lock()
defer iptree.treeMtx.Unlock()
iptree.tree.Clear()
}
// input format 127.0.0.1,192.168.1.0/24,10.1.1.30-10.1.1.50
// 127.0.0.1
// 192.168.1.0/24
// 192.168.1.1-192.168.1.10
func NewIPTree(ips string) *IPTree {
iptree := &IPTree{
tree: avltree.NewWith(utils.UInt32Comparator),
}
ipArr := strings.Split(ips, ",")
for _, ip := range ipArr {
if strings.Contains(ip, "/") { // x.x.x.x/24
_, ipNet, err := net.ParseCIDR(ip)
if err != nil {
fmt.Println("Error parsing CIDR:", err)
continue
}
minIP := ipNet.IP.Mask(ipNet.Mask).String()
maxIP := calculateMaxIP(ipNet).String()
iptree.Add(minIP, maxIP, nil)
} else if strings.Contains(ip, "-") { // x.x.x.x-y.y.y.y
minAndMax := strings.Split(ip, "-")
iptree.Add(minAndMax[0], minAndMax[1], nil)
} else { // single ip
iptree.Add(ip, ip, nil)
}
}
return iptree
}
func calculateMaxIP(ipNet *net.IPNet) net.IP {
maxIP := make(net.IP, len(ipNet.IP))
copy(maxIP, ipNet.IP)
for i := range maxIP {
maxIP[i] |= ^ipNet.Mask[i]
}
return maxIP
}
+174
View File
@@ -0,0 +1,174 @@
package openp2p
import (
"bytes"
"encoding/binary"
"net"
"testing"
"time"
)
func wrapTestContains(t *testing.T, iptree *IPTree, ip string, result bool) {
if iptree.Contains(ip) == result {
// t.Logf("compare version %s %s ok\n", v1, v2)
} else {
t.Errorf("test %s fail\n", ip)
}
}
func wrapBenchmarkContains(t *testing.B, iptree *IPTree, ip string, result bool) {
if iptree.Contains(ip) == result {
// t.Logf("compare version %s %s ok\n", v1, v2)
} else {
t.Errorf("test %s fail\n", ip)
}
}
func TestAllInputFormat(t *testing.T) {
iptree := NewIPTree("219.137.185.70,127.0.0.1,127.0.0.0/8,192.168.1.0/24,192.168.3.100-192.168.3.255,192.168.100.0-192.168.200.255")
wrapTestContains(t, iptree, "127.0.0.1", true)
wrapTestContains(t, iptree, "127.0.0.2", true)
wrapTestContains(t, iptree, "127.1.1.1", true)
wrapTestContains(t, iptree, "219.137.185.70", true)
wrapTestContains(t, iptree, "219.137.185.71", false)
wrapTestContains(t, iptree, "192.168.1.2", true)
wrapTestContains(t, iptree, "192.168.2.2", false)
wrapTestContains(t, iptree, "192.168.3.1", false)
wrapTestContains(t, iptree, "192.168.3.100", true)
wrapTestContains(t, iptree, "192.168.3.255", true)
wrapTestContains(t, iptree, "192.168.150.1", true)
wrapTestContains(t, iptree, "192.168.250.1", false)
}
func TestSingleIP(t *testing.T) {
iptree := NewIPTree("")
iptree.Add("219.137.185.70", "219.137.185.70", nil)
wrapTestContains(t, iptree, "219.137.185.70", true)
wrapTestContains(t, iptree, "219.137.185.71", false)
}
func TestWrongSegment(t *testing.T) {
iptree := NewIPTree("")
inserted := iptree.Add("87.251.75.0", "82.251.75.255", nil)
if inserted {
t.Errorf("TestWrongSegment failed\n")
}
}
func TestSegment2(t *testing.T) {
iptree := NewIPTree("")
iptree.Clear()
iptree.Add("10.1.5.50", "10.1.5.100", nil)
iptree.Add("10.1.1.50", "10.1.1.100", nil)
iptree.Add("10.1.2.50", "10.1.2.100", nil)
iptree.Add("10.1.6.50", "10.1.6.100", nil)
iptree.Add("10.1.7.50", "10.1.7.100", nil)
iptree.Add("10.1.3.50", "10.1.3.100", nil)
iptree.Add("10.1.1.1", "10.1.1.10", nil) // no interset
iptree.Add("10.1.1.200", "10.1.1.250", nil) // no interset
iptree.Print()
iptree.Add("10.1.1.80", "10.1.1.90", nil) // all in
iptree.Add("10.1.1.40", "10.1.1.60", nil) // interset
iptree.Print()
iptree.Add("10.1.1.90", "10.1.1.110", nil) // interset
iptree.Print()
t.Logf("ipTree size:%d\n", iptree.Size())
wrapTestContains(t, iptree, "10.1.1.40", true)
wrapTestContains(t, iptree, "10.1.5.50", true)
wrapTestContains(t, iptree, "10.1.6.50", true)
wrapTestContains(t, iptree, "10.1.7.50", true)
wrapTestContains(t, iptree, "10.1.2.50", true)
wrapTestContains(t, iptree, "10.1.3.50", true)
wrapTestContains(t, iptree, "10.1.1.60", true)
wrapTestContains(t, iptree, "10.1.1.90", true)
wrapTestContains(t, iptree, "10.1.1.110", true)
wrapTestContains(t, iptree, "10.1.1.250", true)
wrapTestContains(t, iptree, "10.1.2.60", true)
wrapTestContains(t, iptree, "10.1.100.30", false)
wrapTestContains(t, iptree, "10.1.200.30", false)
iptree.Add("10.0.0.0", "10.255.255.255", nil) // will merge all segment
iptree.Print()
if iptree.Size() != 1 {
t.Errorf("merge ip segment error\n")
}
}
func BenchmarkBuildipTree20k(t *testing.B) {
iptree := NewIPTree("")
iptree.Clear()
iptree.Add("10.1.5.50", "10.1.5.100", nil)
iptree.Add("10.1.1.50", "10.1.1.100", nil)
iptree.Add("10.1.2.50", "10.1.2.100", nil)
iptree.Add("10.1.6.50", "10.1.6.100", nil)
iptree.Add("10.1.7.50", "10.1.7.100", nil)
iptree.Add("10.1.3.50", "10.1.3.100", nil)
iptree.Add("10.1.1.1", "10.1.1.10", nil) // no interset
iptree.Add("10.1.1.200", "10.1.1.250", nil) // no interset
iptree.Add("10.1.1.80", "10.1.1.90", nil) // all in
iptree.Add("10.1.1.40", "10.1.1.60", nil) // interset
iptree.Add("10.1.1.90", "10.1.1.110", nil) // interset
var minIP uint32
binary.Read(bytes.NewBuffer(net.ParseIP("10.1.1.1").To4()), binary.BigEndian, &minIP)
// insert 10k block ip single
nodeNum := uint32(10000 * 1)
gap := uint32(10)
for i := minIP; i < minIP+nodeNum*gap; i += gap {
iptree.AddIntIP(i, i, nil)
// t.Logf("ipTree size:%d\n", iptree.Size())
}
binary.Read(bytes.NewBuffer(net.ParseIP("100.1.1.1").To4()), binary.BigEndian, &minIP)
// insert 100k block ip segment
for i := minIP; i < minIP+nodeNum*gap; i += gap {
iptree.AddIntIP(i, i+5, nil)
}
t.Logf("ipTree size:%d\n", iptree.Size())
iptree.Clear()
t.Logf("clear. ipTree size:%d\n", iptree.Size())
}
func BenchmarkQuery(t *testing.B) {
ts := time.Now()
iptree := NewIPTree("")
iptree.Clear()
iptree.Add("10.1.5.50", "10.1.5.100", nil)
iptree.Add("10.1.1.50", "10.1.1.100", nil)
iptree.Add("10.1.2.50", "10.1.2.100", nil)
iptree.Add("10.1.6.50", "10.1.6.100", nil)
iptree.Add("10.1.7.50", "10.1.7.100", nil)
iptree.Add("10.1.3.50", "10.1.3.100", nil)
iptree.Add("10.1.1.1", "10.1.1.10", nil) // no interset
iptree.Add("10.1.1.200", "10.1.1.250", nil) // no interset
iptree.Add("10.1.1.80", "10.1.1.90", nil) // all in
iptree.Add("10.1.1.40", "10.1.1.60", nil) // interset
iptree.Add("10.1.1.90", "10.1.1.110", nil) // interset
var minIP uint32
binary.Read(bytes.NewBuffer(net.ParseIP("10.1.1.1").To4()), binary.BigEndian, &minIP)
// insert 10k block ip single
nodeNum := uint32(10000 * 1000)
gap := uint32(10)
for i := minIP; i < minIP+nodeNum*gap; i += gap {
iptree.AddIntIP(i, i, nil)
// t.Logf("ipTree size:%d\n", iptree.Size())
}
binary.Read(bytes.NewBuffer(net.ParseIP("100.1.1.1").To4()), binary.BigEndian, &minIP)
// insert 100k block ip segment
for i := minIP; i < minIP+nodeNum*gap; i += gap {
iptree.AddIntIP(i, i+5, nil)
}
t.Logf("ipTree size:%d cost:%dms\n", iptree.Size(), time.Since(ts)/time.Millisecond)
ts = time.Now()
// t.ResetTimer()
queryNum := 100 * 10000
for i := 0; i < queryNum; i++ {
iptree.Load(minIP + uint32(i))
wrapBenchmarkContains(t, iptree, "10.1.5.55", true)
wrapBenchmarkContains(t, iptree, "10.1.1.1", true)
wrapBenchmarkContains(t, iptree, "10.1.5.200", false)
wrapBenchmarkContains(t, iptree, "200.1.1.1", false)
}
t.Logf("query num:%d cost:%dms\n", queryNum*4, time.Since(ts)/time.Millisecond)
}
+174
View File
@@ -0,0 +1,174 @@
package openp2p
import (
"log"
"os"
"runtime"
"sync"
"time"
)
type LogLevel int
var gLog *logger
const (
LvDev LogLevel = -1
LvDEBUG LogLevel = iota
LvINFO
LvWARN
LvERROR
)
var (
logFileNames map[LogLevel]string
loglevel map[LogLevel]string
)
func init() {
logFileNames = make(map[LogLevel]string)
loglevel = make(map[LogLevel]string)
logFileNames[0] = ".log"
loglevel[LvDEBUG] = "DEBUG"
loglevel[LvINFO] = "INFO"
loglevel[LvWARN] = "WARN"
loglevel[LvERROR] = "ERROR"
loglevel[LvDev] = "Dev"
}
const (
LogFile = 1
LogConsole = 1 << 1
)
type logger struct {
loggers map[LogLevel]*log.Logger
files map[LogLevel]*os.File
level LogLevel
logDir string
mtx *sync.Mutex
lineEnding string
pid int
maxLogSize int64
mode int
stdLogger *log.Logger
}
func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger {
loggers := make(map[LogLevel]*log.Logger)
logfiles := make(map[LogLevel]*os.File)
var (
logdir string
)
if path == "" {
logdir = "log/"
} else {
logdir = path + "/log/"
}
os.MkdirAll(logdir, 0777)
for lv := range logFileNames {
logFilePath := logdir + filePrefix + logFileNames[lv]
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
os.Chmod(logFilePath, 0644)
logfiles[lv] = f
loggers[lv] = log.New(f, "", log.LstdFlags|log.Lmicroseconds)
}
var le string
if runtime.GOOS == "windows" {
le = "\r\n"
} else {
le = "\n"
}
pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode, log.New(os.Stdout, "", 0)}
pLog.stdLogger.SetFlags(log.LstdFlags | log.Lmicroseconds)
go pLog.checkFile()
return pLog
}
func (l *logger) setLevel(level LogLevel) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.level = level
}
func (l *logger) setMaxSize(size int64) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.maxLogSize = size
}
func (l *logger) setMode(mode int) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.mode = mode
}
func (l *logger) checkFile() {
if l.maxLogSize <= 0 {
return
}
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
l.mtx.Lock()
for lv, logFile := range l.files {
f, e := logFile.Stat()
if e != nil {
continue
}
if f.Size() <= l.maxLogSize {
continue
}
logFile.Close()
fname := f.Name()
backupPath := l.logDir + fname + ".0"
os.Remove(backupPath)
os.Rename(l.logDir+fname, backupPath)
newFile, e := os.OpenFile(l.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if e == nil {
l.loggers[lv].SetOutput(newFile)
l.files[lv] = newFile
}
}
l.mtx.Unlock()
}
}
}
func (l *logger) Printf(level LogLevel, format string, params ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
if level < l.level {
return
}
pidAndLevel := []interface{}{l.pid, loglevel[level]}
params = append(pidAndLevel, params...)
if l.mode&LogFile != 0 {
l.loggers[0].Printf("%d %s "+format+l.lineEnding, params...)
}
if l.mode&LogConsole != 0 {
l.stdLogger.Printf("%d %s "+format+l.lineEnding, params...)
}
}
func (l *logger) Println(level LogLevel, params ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
if level < l.level {
return
}
pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...)
params = append(params, l.lineEnding)
if l.mode&LogFile != 0 {
l.loggers[0].Print(params...)
}
if l.mode&LogConsole != 0 {
l.stdLogger.Print(params...)
}
}
+193
View File
@@ -0,0 +1,193 @@
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, MsgNAT, 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()
// 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", gConf.Network.ServerHost, gConf.Network.ServerPort))
if err != nil {
break
}
// The connection can write data to the desired address.
msg, _ := newMessage(MsgNATDetect, MsgPublicIP, NatDetectReq{EchoPort: echoPort})
_, err = conn.WriteTo(msg, dst)
if err != nil {
continue
}
buf := make([]byte, 1600)
// wait for echo testing
echoConn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
nRead, _, err := echoConn.ReadFromUDP(buf)
if err != nil {
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
continue
}
natRsp := NatDetectRsp{}
err = json.Unmarshal(buf[openP2PHeaderSize:nRead], &natRsp)
if err != nil {
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
continue
}
if natRsp.Port == echoPort {
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
View File
@@ -0,0 +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)
}
+20
View File
@@ -0,0 +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)
}
+85
View File
@@ -0,0 +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)
}
+88
View File
@@ -0,0 +1,88 @@
package openp2p
import (
"fmt"
"net"
"os/exec"
"strings"
"github.com/openp2p-cn/wireguard-go/tun"
)
const (
tunIfaceName = "utun"
PIHeaderSize = 4 // utun has no IFF_NO_PI
)
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
}
t.tunName, _ = t.dev.Name()
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 {
li, _, err := net.ParseCIDR(localAddr)
if err != nil {
return fmt.Errorf("parse local addr fail:%s", err)
}
ri, _, err := net.ParseCIDR(remoteAddr)
if err != nil {
return fmt.Errorf("parse remote addr fail:%s", err)
}
err = exec.Command("ifconfig", ifname, "inet", li.String(), ri.String(), "up").Run()
return err
}
func addRoute(dst, gw, ifname string) error {
err := exec.Command("route", "add", dst, gw).Run()
return err
}
func delRoute(dst, gw string) error {
err := exec.Command("route", "delete", dst, "-gateway", gw).Run()
return err
}
func delRoutesByGateway(gateway string) error {
cmd := exec.Command("netstat", "-rn")
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) >= 2 {
cmd := exec.Command("route", "delete", fields[0], gateway)
err := cmd.Run()
if err != nil {
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
continue
}
gLog.Printf(LvINFO, "Delete route ok: %s %s\n", fields[0], gateway)
}
}
return nil
}
func addTunAddr(localAddr, remoteAddr string) error {
return nil
}
func delTunAddr(localAddr, remoteAddr string) error {
return nil
}
+134
View File
@@ -0,0 +1,134 @@
//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 {
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
continue
}
gLog.Printf(LvINFO, "Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
}
}
return nil
}
+42
View File
@@ -0,0 +1,42 @@
//go:build !linux && !windows && !darwin
// +build !linux,!windows,!darwin
package openp2p
import "github.com/openp2p-cn/wireguard-go/tun"
const (
tunIfaceName = "optun"
PIHeaderSize = 0
)
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
}
err = setTunAddr(t.tunName, localAddr, detail.Gateway, t.dev)
if err != nil {
return err
}
return nil
}
func addRoute(dst, gw, ifname string) error {
return nil
}
func delRoute(dst, gw string) error {
return nil
}
func addTunAddr(localAddr, remoteAddr string) error {
return nil
}
func delTunAddr(localAddr, remoteAddr string) error {
return nil
}
+143
View File
@@ -0,0 +1,143 @@
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 {
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
continue
}
gLog.Printf(LvINFO, "Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
}
}
return nil
}
+168
View File
@@ -0,0 +1,168 @@
package openp2p
import (
"bytes"
"encoding/binary"
"errors"
"net"
"time"
)
var ErrDeadlineExceeded error = &DeadlineExceededError{}
// DeadlineExceededError is returned for an expired deadline.
type DeadlineExceededError struct{}
// Implement the net.Error interface.
// The string is "i/o timeout" because that is what was returned
// by earlier Go versions. Changing it may break programs that
// match on error strings.
func (e *DeadlineExceededError) Error() string { return "i/o timeout" }
func (e *DeadlineExceededError) Timeout() bool { return true }
func (e *DeadlineExceededError) Temporary() bool { return true }
// implement io.Writer
type overlayConn struct {
tunnel *P2PTunnel // TODO: del
app *p2pApp
connTCP net.Conn
id uint64
rtid uint64
running bool
isClient bool
appID uint64 // TODO: del
appKey uint64 // TODO: del
appKeyBytes []byte // TODO: del
// for udp
connUDP *net.UDPConn
remoteAddr net.Addr
udpData chan []byte
lastReadUDPTs time.Time
}
func (oConn *overlayConn) run() {
gLog.Printf(LvDEBUG, "%d overlayConn run start", oConn.id)
defer gLog.Printf(LvDEBUG, "%d overlayConn run end", oConn.id)
oConn.lastReadUDPTs = time.Now()
buffer := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
reuseBuff := buffer[:ReadBuffLen]
encryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
tunnelHead := new(bytes.Buffer)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, oConn.rtid)
binary.Write(tunnelHead, binary.LittleEndian, oConn.id)
for oConn.running && oConn.tunnel.isRuning() {
readBuff, dataLen, err := oConn.Read(reuseBuff)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
}
// overlay tcp connection normal close, debug log
gLog.Printf(LvDEBUG, "overlayConn %d read error:%s,close it", oConn.id, err)
break
}
payload := readBuff[:dataLen]
if oConn.appKey != 0 {
payload, _ = encryptBytes(oConn.appKeyBytes, encryptData, readBuff[:dataLen], dataLen)
}
writeBytes := append(tunnelHead.Bytes(), payload...)
// TODO: app.write
if oConn.rtid == 0 {
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgOverlayData, writeBytes)
gLog.Printf(LvDev, "write overlay data to tid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.id, len(writeBytes))
} else {
// write raley data
all := append(relayHead.Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(writeBytes)))...)
all = append(all, writeBytes...)
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, all)
gLog.Printf(LvDev, "write relay data to tid:%d,rtid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.rtid, oConn.id, len(writeBytes))
}
}
if oConn.connTCP != nil {
oConn.connTCP.Close()
}
if oConn.connUDP != nil {
oConn.connUDP.Close()
}
oConn.tunnel.overlayConns.Delete(oConn.id)
// notify peer disconnect
req := OverlayDisconnectReq{ID: oConn.id}
oConn.tunnel.WriteMessage(oConn.rtid, MsgP2P, MsgOverlayDisconnectReq, &req)
}
func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, dataLen int, err error) {
if !oConn.running {
err = ErrOverlayConnDisconnect
return
}
if oConn.connUDP != nil {
if time.Now().After(oConn.lastReadUDPTs.Add(time.Minute * 5)) {
err = errors.New("udp close")
return
}
if oConn.remoteAddr != nil { // as server
select {
case buff = <-oConn.udpData:
dataLen = len(buff) - PaddingSize
oConn.lastReadUDPTs = time.Now()
case <-time.After(time.Second * 10):
err = ErrDeadlineExceeded
}
} else { // as client
oConn.connUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
dataLen, _, err = oConn.connUDP.ReadFrom(reuseBuff)
if err == nil {
oConn.lastReadUDPTs = time.Now()
}
buff = reuseBuff
}
return
}
if oConn.connTCP != nil {
oConn.connTCP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
dataLen, err = oConn.connTCP.Read(reuseBuff)
buff = reuseBuff
}
return
}
// calling by p2pTunnel
func (oConn *overlayConn) Write(buff []byte) (n int, err error) {
// add mutex when multi-thread calling
if !oConn.running {
return 0, ErrOverlayConnDisconnect
}
if oConn.connUDP != nil {
if oConn.remoteAddr == nil {
n, err = oConn.connUDP.Write(buff)
} else {
n, err = oConn.connUDP.WriteTo(buff, oConn.remoteAddr)
}
if err != nil {
oConn.running = false
}
return
}
if oConn.connTCP != nil {
n, err = oConn.connTCP.Write(buff)
}
if err != nil {
oConn.running = false
}
return
}
func (oConn *overlayConn) Close() (err error) {
oConn.running = false
if oConn.connTCP != nil {
oConn.connTCP.Close()
// oConn.connTCP = nil
}
if oConn.connUDP != nil {
oConn.connUDP.Close()
// oConn.connUDP = nil
}
return nil
}
+616
View File
@@ -0,0 +1,616 @@
package openp2p
import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
)
type p2pApp struct {
config AppConfig
listener net.Listener
listenerUDP *net.UDPConn
directTunnel *P2PTunnel
relayTunnel *P2PTunnel
tunnelMtx sync.Mutex
iptree *IPTree // for whitelist
rtid uint64 // relay tunnelID
relayNode string
relayMode string // public/private
hbTimeRelay time.Time
hbMtx sync.Mutex
running bool
id uint64
key uint64 // aes
wg sync.WaitGroup
relayHead *bytes.Buffer
once sync.Once
// for relayTunnel
retryRelayNum int
retryRelayTime time.Time
nextRetryRelayTime time.Time
errMsg string
connectTime time.Time
}
func (app *p2pApp) Tunnel() *P2PTunnel {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
if app.directTunnel != nil {
return app.directTunnel
}
return app.relayTunnel
}
func (app *p2pApp) DirectTunnel() *P2PTunnel {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
return app.directTunnel
}
func (app *p2pApp) setDirectTunnel(t *P2PTunnel) {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
app.directTunnel = t
}
func (app *p2pApp) RelayTunnel() *P2PTunnel {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
return app.relayTunnel
}
func (app *p2pApp) setRelayTunnel(t *P2PTunnel) {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
app.relayTunnel = t
}
func (app *p2pApp) isDirect() bool {
return app.directTunnel != nil
}
func (app *p2pApp) RelayTunnelID() uint64 {
if app.isDirect() {
return 0
}
return app.rtid
}
func (app *p2pApp) ConnectTime() time.Time {
if app.isDirect() {
return app.config.connectTime
}
return app.connectTime
}
func (app *p2pApp) RetryTime() time.Time {
if app.isDirect() {
return app.config.retryTime
}
return app.retryRelayTime
}
func (app *p2pApp) checkP2PTunnel() error {
for app.running {
app.checkDirectTunnel()
app.checkRelayTunnel()
time.Sleep(time.Second * 3)
}
return nil
}
func (app *p2pApp) directRetryLimit() int {
if app.config.peerIP == gConf.Network.publicIP && compareVersion(app.config.peerVersion, SupportIntranetVersion) >= 0 {
return retryLimit
}
if IsIPv6(app.config.peerIPv6) && IsIPv6(gConf.IPv6()) {
return retryLimit
}
if app.config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || app.config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
return retryLimit
}
if gConf.Network.natType == NATCone && app.config.peerNatType == NATCone {
return retryLimit
}
if app.config.peerNatType == NATSymmetric && gConf.Network.natType == NATSymmetric {
return 0
}
return retryLimit / 10 // c2s or s2c
}
func (app *p2pApp) checkDirectTunnel() error {
if app.config.ForceRelay == 1 && app.config.RelayNode != app.config.PeerNode {
return nil
}
if app.DirectTunnel() != nil && app.DirectTunnel().isActive() {
return nil
}
if app.config.nextRetryTime.After(time.Now()) || app.config.Enabled == 0 || app.config.retryNum >= app.directRetryLimit() {
return nil
}
if time.Now().Add(-time.Minute * 15).After(app.config.retryTime) { // run normally 15min, reset retrynum
app.config.retryNum = 1
}
if app.config.retryNum > 0 { // first time not show reconnect log
gLog.Printf(LvINFO, "detect app %s appid:%d disconnect, reconnecting the %d times...", app.config.LogPeerNode(), app.id, app.config.retryNum)
}
app.config.retryNum++
app.config.retryTime = time.Now()
app.config.nextRetryTime = time.Now().Add(retryInterval)
app.config.connectTime = time.Now()
err := app.buildDirectTunnel()
if err != nil {
app.config.errMsg = err.Error()
if err == ErrPeerOffline && app.config.retryNum > 2 { // stop retry, waiting for online
app.config.retryNum = retryLimit
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.LogPeerNode())
}
if err == ErrBuildTunnelBusy {
app.config.retryNum--
}
}
if app.Tunnel() != nil {
app.once.Do(func() {
go app.listen()
// memapp also need
go app.relayHeartbeatLoop()
})
}
return nil
}
func (app *p2pApp) buildDirectTunnel() error {
relayNode := ""
peerNatType := NATUnknown
peerIP := ""
errMsg := ""
var t *P2PTunnel
var err error
pn := GNetwork
initErr := pn.requestPeerInfo(&app.config)
if initErr != nil {
gLog.Printf(LvERROR, "%s requestPeerInfo error:%s", app.config.LogPeerNode(), initErr)
return initErr
}
t, err = pn.addDirectTunnel(app.config, 0)
if t != nil {
peerNatType = t.config.peerNatType
peerIP = t.config.peerIP
}
if err != nil {
errMsg = err.Error()
}
req := ReportConnect{
Error: errMsg,
Protocol: app.config.Protocol,
SrcPort: app.config.SrcPort,
NatType: gConf.Network.natType,
PeerNode: app.config.PeerNode,
DstPort: app.config.DstPort,
DstHost: app.config.DstHost,
PeerNatType: peerNatType,
PeerIP: peerIP,
ShareBandwidth: gConf.Network.ShareBandwidth,
RelayNode: relayNode,
Version: OpenP2PVersion,
}
pn.write(MsgReport, MsgReportConnect, &req)
if err != nil {
return err
}
// if rtid != 0 || t.conn.Protocol() == "tcp" {
// sync appkey
if t == nil {
return err
}
syncKeyReq := APPKeySync{
AppID: app.id,
AppKey: app.key,
}
gLog.Printf(LvDEBUG, "sync appkey direct to %s", app.config.LogPeerNode())
pn.push(app.config.PeerNode, MsgPushAPPKey, &syncKeyReq)
app.setDirectTunnel(t)
// if memapp notify peer addmemapp
if app.config.SrcPort == 0 {
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, AppID: app.id}
pn.push(app.config.PeerNode, MsgPushServerSideSaveMemApp, &req)
gLog.Printf(LvDEBUG, "push %s ServerSideSaveMemApp: %s", app.config.LogPeerNode(), prettyJson(req))
}
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id)
return nil
}
func (app *p2pApp) checkRelayTunnel() error {
// if app.config.ForceRelay == 1 && (gConf.sdwan.CentralNode == app.config.PeerNode && compareVersion(app.config.peerVersion, SupportDualTunnelVersion) < 0) {
if app.config.SrcPort == 0 && (gConf.sdwan.CentralNode == app.config.PeerNode || gConf.sdwan.CentralNode == gConf.Network.Node) { // memapp central node not build relay tunnel
return nil
}
app.hbMtx.Lock()
if app.RelayTunnel() != nil && time.Now().Before(app.hbTimeRelay.Add(TunnelHeartbeatTime*2)) { // must check app.hbtime instead of relayTunnel
app.hbMtx.Unlock()
return nil
}
app.hbMtx.Unlock()
if app.nextRetryRelayTime.After(time.Now()) || app.config.Enabled == 0 || app.retryRelayNum >= retryLimit {
return nil
}
if time.Now().Add(-time.Minute * 15).After(app.retryRelayTime) { // run normally 15min, reset retrynum
app.retryRelayNum = 1
}
if app.retryRelayNum > 0 { // first time not show reconnect log
gLog.Printf(LvINFO, "detect app %s appid:%d relay disconnect, reconnecting the %d times...", app.config.LogPeerNode(), app.id, app.retryRelayNum)
}
app.setRelayTunnel(nil) // reset relayTunnel
app.retryRelayNum++
app.retryRelayTime = time.Now()
app.nextRetryRelayTime = time.Now().Add(retryInterval)
app.connectTime = time.Now()
err := app.buildRelayTunnel()
if err != nil {
app.errMsg = err.Error()
if err == ErrPeerOffline && app.retryRelayNum > 2 { // stop retry, waiting for online
app.retryRelayNum = retryLimit
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.LogPeerNode())
}
}
if app.Tunnel() != nil {
app.once.Do(func() {
go app.listen()
// memapp also need
go app.relayHeartbeatLoop()
})
}
return nil
}
func (app *p2pApp) buildRelayTunnel() error {
var rtid uint64
relayNode := ""
relayMode := ""
peerNatType := NATUnknown
peerIP := ""
errMsg := ""
var t *P2PTunnel
var err error
pn := GNetwork
config := app.config
initErr := pn.requestPeerInfo(&config)
if initErr != nil {
gLog.Printf(LvERROR, "%s init error:%s", config.LogPeerNode(), initErr)
return initErr
}
t, rtid, relayMode, err = pn.addRelayTunnel(config)
if t != nil {
relayNode = t.config.PeerNode
}
if err != nil {
errMsg = err.Error()
}
req := ReportConnect{
Error: errMsg,
Protocol: config.Protocol,
SrcPort: config.SrcPort,
NatType: gConf.Network.natType,
PeerNode: config.PeerNode,
DstPort: config.DstPort,
DstHost: config.DstHost,
PeerNatType: peerNatType,
PeerIP: peerIP,
ShareBandwidth: gConf.Network.ShareBandwidth,
RelayNode: relayNode,
Version: OpenP2PVersion,
}
pn.write(MsgReport, MsgReportConnect, &req)
if err != nil {
return err
}
// if rtid != 0 || t.conn.Protocol() == "tcp" {
// sync appkey
syncKeyReq := APPKeySync{
AppID: app.id,
AppKey: app.key,
}
gLog.Printf(LvDEBUG, "sync appkey relay to %s", config.LogPeerNode())
pn.push(config.PeerNode, MsgPushAPPKey, &syncKeyReq)
app.setRelayTunnelID(rtid)
app.setRelayTunnel(t)
app.relayNode = relayNode
app.relayMode = relayMode
app.hbTimeRelay = time.Now()
// if memapp notify peer addmemapp
if config.SrcPort == 0 {
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: relayNode, TunnelID: rtid, RelayTunnelID: t.id, AppID: app.id, RelayMode: relayMode}
pn.push(config.PeerNode, MsgPushServerSideSaveMemApp, &req)
gLog.Printf(LvDEBUG, "push %s relay ServerSideSaveMemApp: %s", config.LogPeerNode(), prettyJson(req))
}
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id)
return nil
}
func (app *p2pApp) buildOfficialTunnel() error {
return nil
}
// cache relayHead, refresh when rtid change
func (app *p2pApp) RelayHead() *bytes.Buffer {
if app.relayHead == nil {
app.relayHead = new(bytes.Buffer)
binary.Write(app.relayHead, binary.LittleEndian, app.rtid)
}
return app.relayHead
}
func (app *p2pApp) setRelayTunnelID(rtid uint64) {
app.rtid = rtid
app.relayHead = new(bytes.Buffer)
binary.Write(app.relayHead, binary.LittleEndian, app.rtid)
}
func (app *p2pApp) isActive() bool {
if app.Tunnel() == nil {
// gLog.Printf(LvDEBUG, "isActive app.tunnel==nil")
return false
}
if app.isDirect() { // direct mode app heartbeat equals to tunnel heartbeat
return app.Tunnel().isActive()
}
// relay mode calc app heartbeat
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
res := time.Now().Before(app.hbTimeRelay.Add(TunnelHeartbeatTime * 2))
// if !res {
// gLog.Printf(LvDEBUG, "%d app isActive false. peer=%s", app.id, app.config.PeerNode)
// }
return res
}
func (app *p2pApp) updateHeartbeat() {
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now()
}
func (app *p2pApp) listenTCP() error {
gLog.Printf(LvDEBUG, "tcp accept on port %d start", app.config.SrcPort)
defer gLog.Printf(LvDEBUG, "tcp accept on port %d end", app.config.SrcPort)
var err error
listenAddr := ""
if IsLocalhost(app.config.Whitelist) { // not expose port
listenAddr = "127.0.0.1"
}
app.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", listenAddr, app.config.SrcPort))
if err != nil {
gLog.Printf(LvERROR, "listen error:%s", err)
return err
}
defer app.listener.Close()
for app.running {
conn, err := app.listener.Accept()
if err != nil {
if app.running {
gLog.Printf(LvERROR, "%d accept error:%s", app.id, err)
}
break
}
if app.Tunnel() == nil {
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort)
time.Sleep(time.Second)
continue
}
// check white list
if app.config.Whitelist != "" {
remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP.String()
if !app.iptree.Contains(remoteIP) && !IsLocalhost(remoteIP) {
conn.Close()
gLog.Printf(LvERROR, "%s not in whitelist, access denied", remoteIP)
continue
}
}
oConn := overlayConn{
tunnel: app.Tunnel(),
app: app,
connTCP: conn,
id: rand.Uint64(),
isClient: true,
appID: app.id,
appKey: app.key,
running: true,
}
if !app.isDirect() {
oConn.rtid = app.rtid
}
// pre-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
}
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LvDEBUG, "Accept TCP overlayID:%d, %s", oConn.id, oConn.connTCP.RemoteAddr())
// tell peer connect
req := OverlayConnectReq{ID: oConn.id,
Token: gConf.Network.Token,
DstIP: app.config.DstHost,
DstPort: app.config.DstPort,
Protocol: app.config.Protocol,
AppID: app.id,
}
if !app.isDirect() {
req.RelayTunnelID = app.Tunnel().id
}
app.Tunnel().WriteMessage(app.RelayTunnelID(), MsgP2P, MsgOverlayConnectReq, &req)
// TODO: wait OverlayConnectRsp instead of sleep
time.Sleep(time.Second) // waiting remote node connection ok
go oConn.run()
}
return nil
}
func (app *p2pApp) listenUDP() error {
gLog.Printf(LvDEBUG, "udp accept on port %d start", app.config.SrcPort)
defer gLog.Printf(LvDEBUG, "udp accept on port %d end", app.config.SrcPort)
var err error
app.listenerUDP, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: app.config.SrcPort})
if err != nil {
gLog.Printf(LvERROR, "listen error:%s", err)
return err
}
defer app.listenerUDP.Close()
buffer := make([]byte, 64*1024+PaddingSize)
udpID := make([]byte, 8)
for {
app.listenerUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
len, remoteAddr, err := app.listenerUDP.ReadFrom(buffer)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
} else {
gLog.Printf(LvERROR, "udp read failed:%s", err)
break
}
} else {
if app.Tunnel() == nil {
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort)
time.Sleep(time.Second)
continue
}
dupData := bytes.Buffer{} // should uses memory pool
dupData.Write(buffer[:len+PaddingSize])
// load from app.tunnel.overlayConns by remoteAddr error, new udp connection
remoteIP := strings.Split(remoteAddr.String(), ":")[0]
port, _ := strconv.Atoi(strings.Split(remoteAddr.String(), ":")[1])
a := net.ParseIP(remoteIP)
udpID[0] = a[0]
udpID[1] = a[1]
udpID[2] = a[2]
udpID[3] = a[3]
udpID[4] = byte(port)
udpID[5] = byte(port >> 8)
id := binary.LittleEndian.Uint64(udpID) // convert remoteIP:port to uint64
s, ok := app.Tunnel().overlayConns.Load(id)
if !ok {
oConn := overlayConn{
tunnel: app.Tunnel(),
connUDP: app.listenerUDP,
remoteAddr: remoteAddr,
udpData: make(chan []byte, 1000),
id: id,
isClient: true,
appID: app.id,
appKey: app.key,
running: true,
}
if !app.isDirect() {
oConn.rtid = app.rtid
}
// 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
}
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LvDEBUG, "Accept UDP overlayID:%d", oConn.id)
// tell peer connect
req := OverlayConnectReq{ID: oConn.id,
Token: gConf.Network.Token,
DstIP: app.config.DstHost,
DstPort: app.config.DstPort,
Protocol: app.config.Protocol,
AppID: app.id,
}
if !app.isDirect() {
req.RelayTunnelID = app.Tunnel().id
}
app.Tunnel().WriteMessage(app.RelayTunnelID(), MsgP2P, MsgOverlayConnectReq, &req)
// TODO: wait OverlayConnectRsp instead of sleep
time.Sleep(time.Second) // waiting remote node connection ok
go oConn.run()
oConn.udpData <- dupData.Bytes()
}
// load from app.tunnel.overlayConns by remoteAddr ok, write relay data
overlayConn, ok := s.(*overlayConn)
if !ok {
continue
}
overlayConn.udpData <- dupData.Bytes()
}
}
return nil
}
func (app *p2pApp) listen() error {
if app.config.SrcPort == 0 {
return nil
}
gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d START", app.config.Protocol, app.config.SrcPort)
defer gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d END", app.config.Protocol, app.config.SrcPort)
app.wg.Add(1)
defer app.wg.Done()
for app.running {
if app.config.Protocol == "udp" {
app.listenUDP()
} else {
app.listenTCP()
}
if !app.running {
break
}
time.Sleep(time.Second * 10)
}
return nil
}
func (app *p2pApp) close() {
app.running = false
if app.listener != nil {
app.listener.Close()
}
if app.listenerUDP != nil {
app.listenerUDP.Close()
}
if app.DirectTunnel() != nil {
app.DirectTunnel().closeOverlayConns(app.id)
}
if app.RelayTunnel() != nil {
app.RelayTunnel().closeOverlayConns(app.id)
}
app.wg.Wait()
}
// TODO: many relay app on the same P2PTunnel will send a lot of relay heartbeat
func (app *p2pApp) relayHeartbeatLoop() {
app.wg.Add(1)
defer app.wg.Done()
gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid:%d start", app.config.LogPeerNode(), app.id, app.rtid)
defer gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid%d end", app.config.LogPeerNode(), app.id, app.rtid)
for app.running {
if app.RelayTunnel() == nil || !app.RelayTunnel().isRuning() {
time.Sleep(TunnelHeartbeatTime)
continue
}
req := RelayHeartbeat{From: gConf.Network.Node, RelayTunnelID: app.RelayTunnel().id,
AppID: app.id}
err := app.RelayTunnel().WriteMessage(app.rtid, MsgP2P, MsgRelayHeartbeat, &req)
if err != nil {
gLog.Printf(LvERROR, "%s appid:%d rtid:%d write relay tunnel heartbeat error %s", app.config.LogPeerNode(), app.id, app.rtid, err)
return
}
// TODO: debug relay heartbeat
gLog.Printf(LvDEBUG, "%s appid:%d rtid:%d write relay tunnel heartbeat ok", app.config.LogPeerNode(), app.id, app.rtid)
time.Sleep(TunnelHeartbeatTime)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
package main
package openp2p
import (
"sync"
+977
View File
@@ -0,0 +1,977 @@
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")
GNetwork.sdwan.reset()
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{
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)
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
}
+805
View File
@@ -0,0 +1,805 @@
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 {
conn underlay
hbTime time.Time
hbMtx sync.Mutex
config AppConfig
localHoleAddr *net.UDPAddr // local hole address
remoteHoleAddr *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.localHoleAddr = &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
}
GNetwork.push(t.config.PeerNode, MsgPushConnectReq, req)
head, body := GNetwork.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() {
GNetwork.NotifyTunnelClose(t)
if !t.running {
return
}
t.setRun(false)
if t.conn != nil {
t.conn.Close()
}
GNetwork.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.remoteHoleAddr, 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) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.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 GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
if t.config.UnderlayProtocol == "kcp" {
ul, err = listenKCP(t.localHoleAddr.String(), TunnelIdleTimeout)
} else {
ul, err = listenQuic(t.localHoleAddr.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.localHoleAddr)
if errL != nil {
time.Sleep(time.Millisecond * 10)
conn, errL = net.ListenUDP("udp", t.localHoleAddr)
if errL != nil {
return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL)
}
}
GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.remoteHoleAddr.String())
if t.config.UnderlayProtocol == "kcp" {
ul, errL = dialKCP(conn, t.remoteHoleAddr, TunnelIdleTimeout)
} else {
ul, errL = dialQuic(conn, t.remoteHoleAddr, TunnelIdleTimeout)
}
if errL != nil {
return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.remoteHoleAddr.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 {
GNetwork.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) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.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) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.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.Println(LvDEBUG, "c2s ul.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.Println(LvDEBUG, "s2c ul.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 {
GNetwork.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
GNetwork.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 || ul == 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, errR := ul.ReadBuffer()
if errR != nil {
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", errR)
}
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 := GNetwork.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
GNetwork.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)
GNetwork.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) - GNetwork.dt),
Version: OpenP2PVersion,
}
t.punchTs = rsp.PunchTs
// only private node set ipv6
if t.config.fromToken == gConf.Network.Token {
rsp.IPv6 = gConf.IPv6()
}
GNetwork.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 := GNetwork.nodeData
// if body[9] == 1 { // TODO: deal relay
// ch = GNetwork.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)
}
+24
View File
@@ -0,0 +1,24 @@
package openp2p
import (
"fmt"
"testing"
)
func TestSelectPriority(t *testing.T) {
writeData := make(chan []byte, WriteDataChanSize)
writeDataSmall := make(chan []byte, WriteDataChanSize/30)
for i := 0; i < 100; i++ {
writeData <- []byte("data")
writeDataSmall <- []byte("small data")
}
for i := 0; i < 100; i++ {
select {
case buff := <-writeDataSmall:
fmt.Printf("got small data:%s\n", string(buff))
case buff := <-writeData:
fmt.Printf("got data:%s\n", string(buff))
}
}
}
+87
View File
@@ -0,0 +1,87 @@
package openp2p
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// 定义ICMP回显请求和应答的结构
type ICMPMessage struct {
Type uint8
Code uint8
Checksum uint16
Ident uint16
Seq uint16
Data []byte
}
// Ping sends an ICMP Echo request to the specified host and returns the response time.
func Ping(host string) (time.Duration, error) {
// Resolve the IP address of the host
ipAddr, err := net.ResolveIPAddr("ip4", host)
if err != nil {
return 0, fmt.Errorf("failed to resolve host: %v", err)
}
// Create an ICMP listener
conn, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return 0, fmt.Errorf("failed to create ICMP connection: %v", err)
}
defer conn.Close()
// Create an ICMP Echo request message
message := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
Seq: 1,
Data: []byte("HELLO-R-U-THERE"),
},
}
// Marshal the message into binary form
messageBytes, err := message.Marshal(nil)
if err != nil {
return 0, fmt.Errorf("failed to marshal ICMP message: %v", err)
}
// Send the ICMP Echo request
start := time.Now()
if _, err := conn.WriteTo(messageBytes, ipAddr); err != nil {
return 0, fmt.Errorf("failed to send ICMP request: %v", err)
}
// Set a deadline for the response
err = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
if err != nil {
return 0, fmt.Errorf("failed to set read deadline: %v", err)
}
// Read the ICMP response
response := make([]byte, 1500)
n, _, err := conn.ReadFrom(response)
if err != nil {
return 0, fmt.Errorf("failed to read ICMP response: %v", err)
}
// Parse the ICMP response message
parsedMessage, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), response[:n])
if err != nil {
return 0, fmt.Errorf("failed to parse ICMP response: %v", err)
}
// Check if the response is an Echo reply
if parsedMessage.Type == ipv4.ICMPTypeEchoReply {
duration := time.Since(start)
return duration, nil
} else {
return 0, fmt.Errorf("unexpected ICMP message: %+v", parsedMessage)
}
}
+634
View File
@@ -0,0 +1,634 @@
package openp2p
import (
"bytes"
"encoding/binary"
"encoding/json"
"hash/crc64"
"math/big"
"net"
"time"
)
const OpenP2PVersion = "3.21.12"
const ProductName string = "openp2p"
const LeastSupportVersion = "3.0.0"
const SyncServerTimeVersion = "3.9.0"
const SymmetricSimultaneouslySendVersion = "3.10.7"
const PublicIPVersion = "3.11.2"
const SupportIntranetVersion = "3.14.5"
const SupportDualTunnelVersion = "3.15.5"
const (
IfconfigPort1 = 27180
IfconfigPort2 = 27181
WsPort = 27183
UDPPort1 = 27182
UDPPort2 = 27183
)
type openP2PHeader struct {
DataLen uint32
MainType uint16
SubType uint16
}
var openP2PHeaderSize = binary.Size(openP2PHeader{})
type PushHeader struct {
From uint64
To uint64
}
var PushHeaderSize = binary.Size(PushHeader{})
const RelayHeaderSize = 8
type overlayHeader struct {
id uint64
}
var overlayHeaderSize = binary.Size(overlayHeader{})
func decodeHeader(data []byte) (*openP2PHeader, error) {
head := openP2PHeader{}
rd := bytes.NewReader(data)
err := binary.Read(rd, binary.LittleEndian, &head)
if err != nil {
return nil, err
}
return &head, nil
}
func encodeHeader(mainType uint16, subType uint16, len uint32) []byte {
head := openP2PHeader{
len,
mainType,
subType,
}
headBuf := new(bytes.Buffer)
err := binary.Write(headBuf, binary.LittleEndian, head)
if err != nil {
return []byte("")
}
return headBuf.Bytes()
}
// Message type
const (
MsgLogin = 0
MsgHeartbeat = 1
MsgNATDetect = 2
MsgPush = 3
MsgP2P = 4
MsgRelay = 5
MsgReport = 6
MsgQuery = 7
MsgSDWAN = 8
)
// TODO: seperate node push and web push.
const (
MsgPushRsp = 0
MsgPushConnectReq = 1
MsgPushConnectRsp = 2
MsgPushHandshakeStart = 3
MsgPushAddRelayTunnelReq = 4
MsgPushAddRelayTunnelRsp = 5
MsgPushUpdate = 6
MsgPushReportApps = 7
MsgPushUnderlayConnect = 8
MsgPushEditApp = 9
MsgPushSwitchApp = 10
MsgPushRestart = 11
MsgPushEditNode = 12
MsgPushAPPKey = 13
MsgPushReportLog = 14
MsgPushDstNodeOnline = 15
MsgPushReportGoroutine = 16
MsgPushReportMemApps = 17
MsgPushServerSideSaveMemApp = 18
MsgPushCheckRemoteService = 19
)
// MsgP2P sub type message
const (
MsgPunchHandshake = iota
MsgPunchHandshakeAck
MsgTunnelHandshake
MsgTunnelHandshakeAck
MsgTunnelHeartbeat
MsgTunnelHeartbeatAck
MsgOverlayConnectReq
MsgOverlayConnectRsp
MsgOverlayDisconnectReq
MsgOverlayData
MsgRelayData
MsgRelayHeartbeat
MsgRelayHeartbeatAck
MsgNodeData
MsgRelayNodeData
)
// MsgRelay sub type message
const (
MsgRelayNodeReq = iota
MsgRelayNodeRsp
)
// MsgReport sub type message
const (
MsgReportBasic = iota
MsgReportQuery
MsgReportConnect
MsgReportApps
MsgReportLog
MsgReportMemApps
MsgReportResponse
)
const (
ReadBuffLen = 4096 // for UDP maybe not enough
NetworkHeartbeatTime = time.Second * 30
TunnelHeartbeatTime = time.Second * 10 // some nat udp session expired time less than 15s. change to 10s
UnderlayTCPKeepalive = time.Second * 5
UnderlayTCPConnectTimeout = time.Second * 5
TunnelIdleTimeout = time.Minute
SymmetricHandshakeNum = 800 // 0.992379
// SymmetricHandshakeNum = 1000 // 0.999510
SymmetricHandshakeInterval = time.Millisecond
HandshakeTimeout = time.Second * 7
PunchTsDelay = time.Second * 3
PeerAddRelayTimeount = time.Second * 30 // peer need times. S2C\TCP\TCP Punch\UDP Punch
CheckActiveTimeout = time.Second * 5
ReadMsgTimeout = time.Second * 5
PaddingSize = 16
AESKeySize = 16
MaxRetry = 10
Cone2ConeTCPPunchMaxRetry = 1
Cone2ConeUDPPunchMaxRetry = 1
PublicIPEchoTimeout = time.Second * 3
NatTestTimeout = time.Second * 5
UDPReadTimeout = time.Second * 5
ClientAPITimeout = time.Second * 10
UnderlayConnectTimeout = time.Second * 10
MaxDirectTry = 3
// sdwan
ReadTunBuffSize = 1600
ReadTunBuffNum = 10
)
// NATNone has public ip
const (
NATNone = 0
NATCone = 1
NATSymmetric = 2
NATUnknown = 314
)
// underlay protocol
const (
UderlayAuto = "auto"
UderlayQUIC = "quic"
UderlayTCP = "tcp"
)
// linkmode
const (
LinkModeUDPPunch = "udppunch"
LinkModeTCPPunch = "tcppunch"
LinkModeIPv4 = "ipv4" // for web
LinkModeIntranet = "intranet" // for web
LinkModeIPv6 = "ipv6" // for web
LinkModeTCP6 = "tcp6"
LinkModeTCP4 = "tcp4"
LinkModeUDP6 = "udp6"
LinkModeUDP4 = "udp4"
)
const (
MsgQueryPeerInfoReq = iota
MsgQueryPeerInfoRsp
)
const (
MsgSDWANInfoReq = iota
MsgSDWANInfoRsp
)
// MsgNATDetect
const (
MsgNAT = iota
MsgPublicIP
)
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
data, err := json.Marshal(packet)
if err != nil {
return nil, err
}
// gLog.Println(LevelINFO,"write packet:", string(data))
head := openP2PHeader{
uint32(len(data)),
mainType,
subType,
}
headBuf := new(bytes.Buffer)
err = binary.Write(headBuf, binary.LittleEndian, head)
if err != nil {
return nil, err
}
writeBytes := append(headBuf.Bytes(), data...)
return writeBytes, nil
}
func NodeNameToID(name string) uint64 {
return crc64.Checksum([]byte(name), crc64.MakeTable(crc64.ISO))
}
type PushConnectReq struct {
From string `json:"from,omitempty"`
FromToken uint64 `json:"fromToken,omitempty"` // deprecated
Version string `json:"version,omitempty"`
Token uint64 `json:"token,omitempty"` // if public totp token
ConeNatPort int `json:"coneNatPort,omitempty"` // if isPublic, is public port
NatType int `json:"natType,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
AppKey uint64 `json:"appKey,omitempty"` // for underlay tcp
LinkMode string `json:"linkMode,omitempty"`
IsUnderlayServer int `json:"isServer,omitempty"` // Requset spec peer is server
UnderlayProtocol string `json:"underlayProtocol,omitempty"` // quic or kcp, default quic
}
type PushDstNodeOnline struct {
Node string `json:"node,omitempty"`
}
type PushConnectRsp struct {
Error int `json:"error,omitempty"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Detail string `json:"detail,omitempty"`
NatType int `json:"natType,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"`
IPv6 string `json:"IPv6,omitempty"` // if public relay node, ipv6 not set
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
ConeNatPort int `json:"coneNatPort,omitempty"` //it's not only cone, but also upnp or nat-pmp hole
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
PunchTs uint64 `json:"punchts,omitempty"` // server timestamp
Version string `json:"version,omitempty"`
}
type PushRsp struct {
Error int `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
}
type LoginRsp struct {
Error int `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
User string `json:"user,omitempty"`
Node string `json:"node,omitempty"`
Token uint64 `json:"token,omitempty"`
Ts int64 `json:"ts,omitempty"`
LoginMaxDelay int `json:"loginMaxDelay,omitempty"` // seconds
}
type NatDetectReq struct {
SrcPort int `json:"srcPort,omitempty"`
EchoPort int `json:"echoPort,omitempty"`
}
type NatDetectRsp struct {
IP string `json:"IP,omitempty"`
Port int `json:"port,omitempty"`
IsPublicIP int `json:"isPublicIP,omitempty"`
}
type P2PHandshakeReq struct {
ID uint64 `json:"id,omitempty"`
}
type OverlayConnectReq struct {
ID uint64 `json:"id,omitempty"`
Token uint64 `json:"token,omitempty"` // not totp token
DstIP string `json:"dstIP,omitempty"`
DstPort int `json:"dstPort,omitempty"`
Protocol string `json:"protocol,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // if not 0 relay
AppID uint64 `json:"appID,omitempty"`
}
type OverlayDisconnectReq struct {
ID uint64 `json:"id,omitempty"`
}
type TunnelMsg struct {
ID uint64 `json:"id,omitempty"`
}
type RelayNodeReq struct {
PeerNode string `json:"peerNode,omitempty"`
}
type RelayNodeRsp struct {
Mode string `json:"mode,omitempty"` // private,public
RelayName string `json:"relayName,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
}
type AddRelayTunnelReq struct {
From string `json:"from,omitempty"`
RelayName string `json:"relayName,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
RelayMode string `json:"relayMode,omitempty"`
AppID uint64 `json:"appID,omitempty"` // deprecated
AppKey uint64 `json:"appKey,omitempty"` // deprecated
}
type APPKeySync struct {
AppID uint64 `json:"appID,omitempty"`
AppKey uint64 `json:"appKey,omitempty"`
}
type RelayHeartbeat struct {
From string `json:"from,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
AppID uint64 `json:"appID,omitempty"`
}
type ReportBasic struct {
OS string `json:"os,omitempty"`
Mac string `json:"mac,omitempty"`
LanIP string `json:"lanIP,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"`
}
type ReportConnect struct {
Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"`
SrcPort int `json:"srcPort,omitempty"`
NatType int `json:"natType,omitempty"`
PeerNode string `json:"peerNode,omitempty"`
DstPort int `json:"dstPort,omitempty"`
DstHost string `json:"dstHost,omitempty"`
PeerUser string `json:"peerUser,omitempty"`
PeerNatType int `json:"peerNatType,omitempty"`
PeerIP string `json:"peerIP,omitempty"`
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,omitempty"`
Version string `json:"version,omitempty"`
}
type AppInfo struct {
AppName string `json:"appName,omitempty"`
Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"`
PunchPriority int `json:"punchPriority,omitempty"`
Whitelist string `json:"whitelist,omitempty"`
SrcPort int `json:"srcPort,omitempty"`
Protocol0 string `json:"protocol0,omitempty"`
SrcPort0 int `json:"srcPort0,omitempty"` // srcport+protocol is uneque, use as old app id
NatType int `json:"natType,omitempty"`
PeerNode string `json:"peerNode,omitempty"`
DstPort int `json:"dstPort,omitempty"`
DstHost string `json:"dstHost,omitempty"`
PeerUser string `json:"peerUser,omitempty"`
PeerNatType int `json:"peerNatType,omitempty"`
PeerIP string `json:"peerIP,omitempty"`
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,omitempty"`
SpecRelayNode string `json:"specRelayNode,omitempty"`
RelayMode string `json:"relayMode,omitempty"`
LinkMode string `json:"linkMode,omitempty"`
Version string `json:"version,omitempty"`
RetryTime string `json:"retryTime,omitempty"`
ConnectTime string `json:"connectTime,omitempty"`
IsActive int `json:"isActive,omitempty"`
Enabled int `json:"enabled,omitempty"`
}
type ReportApps struct {
Apps []AppInfo
}
type ReportLogReq struct {
FileName string `json:"fileName,omitempty"`
Offset int64 `json:"offset,omitempty"`
Len int64 `json:"len,omitempty"`
}
type ReportLogRsp struct {
FileName string `json:"fileName,omitempty"`
Content string `json:"content,omitempty"`
Len int64 `json:"len,omitempty"`
Total int64 `json:"total,omitempty"`
}
type UpdateInfo struct {
Error int `json:"error,omitempty"`
ErrorDetail string `json:"errorDetail,omitempty"`
Url string `json:"url,omitempty"`
}
type NetInfo struct {
IP net.IP `json:"ip"`
IPDecimal *big.Int `json:"ip_decimal"`
Country string `json:"country,omitempty"`
CountryISO string `json:"country_iso,omitempty"`
CountryEU *bool `json:"country_eu,omitempty"`
RegionName string `json:"region_name,omitempty"`
RegionCode string `json:"region_code,omitempty"`
MetroCode uint `json:"metro_code,omitempty"`
PostalCode string `json:"zip_code,omitempty"`
City string `json:"city,omitempty"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
Timezone string `json:"time_zone,omitempty"`
ASN string `json:"asn,omitempty"`
ASNOrg string `json:"asn_org,omitempty"`
Hostname string `json:"hostname,omitempty"`
}
type ProfileInfo struct {
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
Token string `json:"token,omitempty"`
Addtime string `json:"addtime,omitempty"`
}
type EditNode struct {
NewName string `json:"newName,omitempty"`
Bandwidth int `json:"bandwidth,omitempty"`
}
type QueryPeerInfoReq struct {
Token uint64 `json:"token,omitempty"` // if public totp token
PeerNode string `json:"peerNode,omitempty"`
}
type QueryPeerInfoRsp struct {
PeerNode string `json:"peerNode,omitempty"`
Online int `json:"online,omitempty"`
Version string `json:"version,omitempty"`
NatType int `json:"natType,omitempty"`
IPv4 string `json:"IPv4,omitempty"`
LanIP string `json:"lanIP,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"` // has public ipv4
IPv6 string `json:"IPv6,omitempty"` // if public relay node, ipv6 not set
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
}
type SDWANNode struct {
Name string `json:"name,omitempty"`
IP string `json:"ip,omitempty"`
Resource string `json:"resource,omitempty"`
Enable int32 `json:"enable,omitempty"`
}
type SDWANInfo struct {
ID uint64 `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Gateway string `json:"gateway,omitempty"`
Mode string `json:"mode,omitempty"` // default: fullmesh; central
CentralNode string `json:"centralNode,omitempty"`
ForceRelay int32 `json:"forceRelay,omitempty"`
PunchPriority int32 `json:"punchPriority,omitempty"`
Enable int32 `json:"enable,omitempty"`
Nodes []*SDWANNode
}
const (
SDWANModeFullmesh = "fullmesh"
SDWANModeCentral = "central"
)
type ServerSideSaveMemApp struct {
From string `json:"from,omitempty"`
Node string `json:"node,omitempty"` // for server side findtunnel, maybe relayNode
TunnelID uint64 `json:"tunnelID,omitempty"` // save in app.tunnel or app.relayTunnel
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // rtid, if not 0 relay
RelayMode string `json:"relayMode,omitempty"`
AppID uint64 `json:"appID,omitempty"`
}
type CheckRemoteService struct {
Host string `json:"host,omitempty"`
Port uint32 `json:"port,omitempty"`
}
const rootCA = `-----BEGIN CERTIFICATE-----
MIIDhTCCAm0CFHm0cd8dnGCbUW/OcS56jf0gvRk7MA0GCSqGSIb3DQEBCwUAMH4x
CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJHRDETMBEGA1UECgwKb3BlbnAycC5jbjET
MBEGA1UECwwKb3BlbnAycC5jbjETMBEGA1UEAwwKb3BlbnAycC5jbjEjMCEGCSqG
SIb3DQEJARYUb3BlbnAycC5jbkBnbWFpbC5jb20wIBcNMjMwODAxMDkwMjMwWhgP
MjEyMzA3MDgwOTAyMzBaMH4xCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJHRDETMBEG
A1UECgwKb3BlbnAycC5jbjETMBEGA1UECwwKb3BlbnAycC5jbjETMBEGA1UEAwwK
b3BlbnAycC5jbjEjMCEGCSqGSIb3DQEJARYUb3BlbnAycC5jbkBnbWFpbC5jb20w
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWg8wPy5hBLUaY4WOXayKu
+magEz1LAY0krzXYSZaSCvGMwA0cervwAqgKfiiZEhho5UNA5iVOJ6bO1RL9H7Vp
4HuW9BttDU/NQHguD8pyqx06Kaosz5LRw8USz1BCWWFdmi8Mv4I0omtd7m6lbWnY
nrjQKLYPahPW481jUfJPqR6wUTnBuBMr2ZAGqmFR4Lhqs9B1P9GeBfDWNwVApJUC
VEhbElukRJxdUvWeJ5+HMENKQcHCTTgmQbmDLMobHXs3Xf7fT9qC76wOe9LFHI6L
dAww9gryQhxWauQl1NO8aGJTFu+3wgnKBdTMJmF/1iuZYXJOCR1solwqU1hCgBsj
AgMBAAEwDQYJKoZIhvcNAQELBQADggEBADp153YNVN8p6/3PLnXxHBDeDViAfeQd
VJmy8eH1LTq/xtUY71HGSpL7iIBNoQdDTHfsg3c6ZANBCxbO/7AhFAzPt1aK8eHy
XuEiW0Z6R8np1Khh3alCOfD15tKcjok//Wxisbz+YItlbDus/eWRbLGB3HGrzn4l
GB18jw+G7o4U3rGX8agHqVGQEd06gk1ZaprASpTGwSsv4A5ehosjT1d7re8Z5eD4
RVtXS+DplMClQ5QSlv3StwcWOsjyiAimNfLEU5xoEfq17yOJUTU1OTL4YOt16QUc
C1tnzFr3k/ioqFR7cnyzNrbjlfPOmO9l2WReEbMP3bvaSHm6EcpJKS8=
-----END CERTIFICATE-----`
const ISRGRootX1 = `-----BEGIN CERTIFICATE-----
MIIEJjCCAw6gAwIBAgISAztStWq026ej0RCsk3ErbUdPMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMzA4MDQwODUyMjlaFw0yMzExMDIwODUyMjhaMBcxFTATBgNVBAMM
DCoub3BlbnAycC5jbjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPRdkgLV2FA+
3g/GjcA9UcfDfIFYgofSTNbOCQFIiQVMXrTgAToF1/tWaS2LOuysZcCX6OE7SCeG
lQ+0g+L2qvujggIaMIICFjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYB
BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFIdL5LNQC+X4
8r6u+3NlM238Vmk5MB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLGMFUG
CCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iub3Jn
MCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMCMGA1UdEQQcMBqC
DCoub3BlbnAycC5jboIKb3BlbnAycC5jbjATBgNVHSAEDDAKMAgGBmeBDAECATCC
AQQGCisGAQQB1nkCBAIEgfUEgfIA8AB2AHoyjFTYty22IOo44FIe6YQWcDIThU07
0ivBOlejUutSAAABib/2fCgAAAQDAEcwRQIhAJzf9XNe0cu9CNYLLqtDCZZMqI6u
qsHrnnXcFQW23ioZAiAgwKp5DwZw9RmF19KOjD6lYJfTxc+anJUuWAlMwu1HYQB2
AK33vvp8/xDIi509nB4+GGq0Zyldz7EMJMqFhjTr3IKKAAABib/2fEEAAAQDAEcw
RQIgKeI7DopyzFXPdRQZKZrHVqfXQ8OipvlKXd5xRnKFjH4CIQDMM+TU+LOux8xK
1NlTiSs9DhQI/eU3ZXKxSQAqF50RnTANBgkqhkiG9w0BAQsFAAOCAQEATqZ+H2NT
cv4FzArD/Krlnur1OTitvpubRWM+ClB9Cr6pvPVB7Dp0/ALxu35ZmCtrzdJWTfmp
lHxU4nPXRPVjuPRNXooSyH//KTfHyf32919PQOi/qc/QEAuIzkGLJg0dIPKLxaNK
CiTWU+2iAYSHBgCWulfLX/RYNbBZQ9w0xIm3XhuMjCF/omG8ofuz1DmiRVR+17JA
nuDXQkxm7KhmbxSA4PsLwzvIWA8Wk44ZK7uncgRY3WIUXcVRELSFA5LuH67TOwag
al6iG56KW1N2Yy9YmeG27SYvHZYkjmuJ8NEy7Ku+Mi6gwO4hs0CYr2wtUacPfjKF
aYTGWSt6Pt8kmw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
-----END CERTIFICATE-----
`
+323
View File
@@ -0,0 +1,323 @@
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) reset() {
gLog.Println(LvINFO, "reset sdwan when network disconnected")
// clear sysroute
delRoutesByGateway(s.gateway.String())
// clear internel route
s.internalRoute = NewIPTree("")
// clear p2papp
for _, node := range gConf.getAddNodes() {
gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name})
}
gConf.resetSDWAN()
}
func (s *p2pSDWAN) init(name string) error {
if gConf.getSDWAN().Gateway == "" {
gLog.Println(LvDEBUG, "sdwan init: not in sdwan clear all ")
}
if s.internalRoute == nil {
s.internalRoute = NewIPTree("")
}
s.nodeName = name
if gw, sn, err := net.ParseCIDR(gConf.getSDWAN().Gateway); err == nil { // preserve old gateway
s.gateway = gw
s.subnet = sn
}
for _, node := range gConf.getDelNodes() {
gLog.Println(LvDEBUG, "sdwan init: deal deleted node: ", node.Name)
gLog.Printf(LvDEBUG, "sdwan init: delRoute: %s, %s ", node.IP, s.gateway.String())
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())
gLog.Printf(LvDEBUG, "sdwan init: resource delRoute: %s, %s ", ipnet.String(), s.gateway.String())
}
}
for _, node := range gConf.getAddNodes() {
gLog.Println(LvDEBUG, "sdwan init: 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, "sdwan init: start tun ", ipNet.String())
err := s.StartTun()
if err != nil {
gLog.Println(LvERROR, "sdwan init: start tun error:", err)
return err
}
gLog.Println(LvINFO, "sdwan init: start tun ok")
allowTunForward()
gLog.Printf(LvDEBUG, "sdwan init: addRoute %s %s %s", s.subnet.String(), s.gateway.String(), s.tun.tunName)
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, "sdwan init: 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("sdwan init: Error parsing CIDR:", err)
continue
}
if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan
gLog.Printf(LvDEBUG, "sdwan init: local ip %s in this resource %s, ignore", gConf.Network.localIP, ipnet.IP.String())
continue
}
// local net could access this single ip
if ipnet.Mask[0] == 255 && ipnet.Mask[1] == 255 && ipnet.Mask[2] == 255 && ipnet.Mask[3] == 255 {
gLog.Printf(LvDEBUG, "sdwan init: ping %s start", ipnet.IP.String())
if _, err := Ping(ipnet.IP.String()); err == nil {
gLog.Printf(LvDEBUG, "sdwan init: ping %s ok, ignore this resource", ipnet.IP.String())
continue
}
gLog.Printf(LvDEBUG, "sdwan init: ping %s failed", ipnet.IP.String())
}
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
gLog.Printf(LvDEBUG, "sdwan init: addRoute %s %s %s", ipnet.String(), s.gateway.String(), s.tun.tunName)
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
}
+50
View File
@@ -0,0 +1,50 @@
package openp2p
import (
"sync"
"time"
)
// SpeedLimiter ...
type SpeedLimiter struct {
lastUpdate time.Time
speed int // per second
precision int // seconds
freeCap int
maxFreeCap int
mtx sync.Mutex
}
func newSpeedLimiter(speed int, precision int) *SpeedLimiter {
return &SpeedLimiter{
speed: speed,
precision: precision,
lastUpdate: time.Now(),
maxFreeCap: speed * precision,
freeCap: speed * precision,
}
}
// Add ...
func (sl *SpeedLimiter) Add(increment int, wait bool) bool {
if sl.speed <= 0 {
return true
}
sl.mtx.Lock()
defer sl.mtx.Unlock()
sl.freeCap += int(time.Since(sl.lastUpdate) * time.Duration(sl.speed) / time.Second)
if sl.freeCap > sl.maxFreeCap {
sl.freeCap = sl.maxFreeCap
}
if !wait && sl.freeCap < increment {
return false
}
sl.freeCap -= increment
sl.lastUpdate = time.Now()
if sl.freeCap < 0 {
// sleep for the overflow
// fmt.Println("sleep ", time.Millisecond*time.Duration(-sl.freeCap*100)/time.Duration(sl.speed))
time.Sleep(time.Millisecond * time.Duration(-sl.freeCap*1000) / time.Duration(sl.speed)) // sleep ms
}
return true
}
+59
View File
@@ -0,0 +1,59 @@
package openp2p
import (
"testing"
"time"
)
func TestBandwidth(t *testing.T) {
speed := 10 * 1024 * 1024 / 8 // 10mbps
speedl := newSpeedLimiter(speed, 1)
oneBuffSize := 4096
writeNum := 5000
expectTime := oneBuffSize * writeNum / speed
startTs := time.Now()
for i := 0; i < writeNum; i++ {
speedl.Add(oneBuffSize, true)
}
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error")
}
}
func TestSymmetric(t *testing.T) {
speed := 20000 / 180
speedl := newSpeedLimiter(speed, 180)
oneBuffSize := 300
writeNum := 70
expectTime := (oneBuffSize*writeNum - 20000) / speed
t.Logf("expect %ds", expectTime)
startTs := time.Now()
for i := 0; i < writeNum; i++ {
speedl.Add(oneBuffSize, true)
}
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error")
}
}
func TestSymmetric2(t *testing.T) {
speed := 30000 / 180
speedl := newSpeedLimiter(speed, 180)
oneBuffSize := 800
writeNum := 40
expectTime := (oneBuffSize*writeNum - 30000) / speed
startTs := time.Now()
for i := 0; i < writeNum; {
if speedl.Add(oneBuffSize, true) {
i++
} else {
time.Sleep(time.Millisecond)
}
}
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error")
}
}
+9 -10
View File
@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -18,26 +18,25 @@ func UDPWrite(conn *net.UDPConn, dst net.Addr, mainType uint16, subType uint16,
return conn.WriteTo(msg, dst)
}
func UDPRead(conn *net.UDPConn, timeout int) (ra net.Addr, head *openP2PHeader, result []byte, len int, err error) {
func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP2PHeader, buff []byte, length int, err error) {
if timeout > 0 {
deadline := time.Now().Add(time.Millisecond * time.Duration(timeout))
err = conn.SetReadDeadline(deadline)
err = conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
gLog.Println(LevelERROR, "SetReadDeadline error")
gLog.Println(LvERROR, "SetReadDeadline error")
return nil, nil, nil, 0, err
}
}
result = make([]byte, 1024)
len, ra, err = conn.ReadFrom(result)
buff = make([]byte, 1024)
length, ra, err = conn.ReadFrom(buff)
if err != nil {
// gLog.Println(LevelDEBUG, "ReadFrom error")
return nil, nil, nil, 0, err
}
head = &openP2PHeader{}
err = binary.Read(bytes.NewReader(result[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "parse p2pheader error:", err)
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil || head.DataLen > uint32(len(buff)-openP2PHeaderSize) {
gLog.Println(LvERROR, "parse p2pheader error:", err)
return nil, nil, nil, 0, err
}
return
+65
View File
@@ -0,0 +1,65 @@
package openp2p
import (
"io"
"time"
)
type underlay interface {
Read([]byte) (int, error)
Write([]byte) (int, error)
ReadBuffer() (*openP2PHeader, []byte, error)
WriteBytes(uint16, uint16, []byte) error
WriteBuffer([]byte) error
WriteMessage(uint16, uint16, interface{}) error
Close() error
WLock()
WUnlock()
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
Protocol() string
}
func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
headBuf := make([]byte, openP2PHeaderSize)
_, err := io.ReadFull(ul, headBuf)
if err != nil {
return nil, nil, err
}
head, err := decodeHeader(headBuf)
if err != nil || head.MainType > 16 {
return nil, nil, err
}
dataBuf := make([]byte, head.DataLen)
_, err = io.ReadFull(ul, dataBuf)
return head, dataBuf, err
}
func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error {
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
_, err := ul.Write(writeBytes)
ul.WUnlock()
return err
}
func DefaultWriteBuffer(ul underlay, data []byte) error {
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
_, err := ul.Write(data)
ul.WUnlock()
return err
}
func DefaultWriteMessage(ul underlay, mainType uint16, subType uint16, packet interface{}) error {
writeBytes, err := newMessage(mainType, subType, packet)
if err != nil {
return err
}
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock()
_, err = ul.Write(writeBytes)
ul.WUnlock()
return err
}
+94
View File
@@ -0,0 +1,94 @@
package openp2p
import (
"fmt"
"net"
"sync"
"time"
"github.com/xtaci/kcp-go/v5"
)
type underlayKCP struct {
listener *kcp.Listener
writeMtx *sync.Mutex
*kcp.UDPSession
}
func (conn *underlayKCP) Protocol() string {
return "kcp"
}
func (conn *underlayKCP) ReadBuffer() (*openP2PHeader, []byte, error) {
return DefaultReadBuffer(conn)
}
func (conn *underlayKCP) WriteBytes(mainType uint16, subType uint16, data []byte) error {
return DefaultWriteBytes(conn, mainType, subType, data)
}
func (conn *underlayKCP) WriteBuffer(data []byte) error {
return DefaultWriteBuffer(conn, data)
}
func (conn *underlayKCP) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
return DefaultWriteMessage(conn, mainType, subType, packet)
}
func (conn *underlayKCP) Close() error {
conn.UDPSession.Close()
return nil
}
func (conn *underlayKCP) WLock() {
conn.writeMtx.Lock()
}
func (conn *underlayKCP) WUnlock() {
conn.writeMtx.Unlock()
}
func (conn *underlayKCP) CloseListener() {
if conn.listener != nil {
conn.listener.Close()
}
}
func (conn *underlayKCP) Accept() error {
kConn, err := conn.listener.AcceptKCP()
if err != nil {
conn.listener.Close()
return err
}
kConn.SetNoDelay(0, 40, 0, 0)
kConn.SetWindowSize(512, 512)
kConn.SetWriteBuffer(1024 * 128)
kConn.SetReadBuffer(1024 * 128)
conn.UDPSession = kConn
return nil
}
func listenKCP(addr string, idleTimeout time.Duration) (*underlayKCP, error) {
gLog.Println(LvDEBUG, "kcp listen on ", addr)
listener, err := kcp.ListenWithOptions(addr, nil, 0, 0)
if err != nil {
return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
}
ul := &underlayKCP{listener: listener, writeMtx: &sync.Mutex{}}
err = ul.Accept()
if err != nil {
ul.CloseListener()
return nil, fmt.Errorf("accept KCP error:%s", err)
}
return ul, nil
}
func dialKCP(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayKCP, error) {
kConn, err := kcp.NewConn(remoteAddr.String(), nil, 0, 0, conn)
if err != nil {
return nil, fmt.Errorf("quic.DialContext error:%s", err)
}
kConn.SetNoDelay(0, 40, 0, 0)
kConn.SetWindowSize(512, 512)
kConn.SetWriteBuffer(1024 * 128)
kConn.SetReadBuffer(1024 * 128)
ul := &underlayKCP{nil, &sync.Mutex{}, kConn}
return ul, nil
}
+43 -55
View File
@@ -1,4 +1,4 @@
package main
package openp2p
import (
"context"
@@ -6,84 +6,66 @@ import (
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"math/big"
"net"
"sync"
"time"
"github.com/lucas-clemente/quic-go"
"github.com/quic-go/quic-go"
)
//quic.DialContext do not support version 44,disable it
// quic.DialContext do not support version 44,disable it
var quicVersion []quic.VersionNumber
type quicConn struct {
type underlayQUIC struct {
listener quic.Listener
writeMtx *sync.Mutex
quic.Stream
quic.Session
quic.Connection
}
func (conn *quicConn) ReadMessage() (*openP2PHeader, []byte, error) {
headBuf := make([]byte, openP2PHeaderSize)
_, err := io.ReadFull(conn, headBuf)
if err != nil {
return nil, nil, err
}
head, err := decodeHeader(headBuf)
if err != nil {
return nil, nil, err
}
dataBuf := make([]byte, head.DataLen)
_, err = io.ReadFull(conn, dataBuf)
return head, dataBuf, err
func (conn *underlayQUIC) Protocol() string {
return "quic"
}
func (conn *quicConn) WriteBytes(mainType uint16, subType uint16, data []byte) error {
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err := conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
func (conn *underlayQUIC) ReadBuffer() (*openP2PHeader, []byte, error) {
return DefaultReadBuffer(conn)
}
func (conn *quicConn) WriteBuffer(data []byte) error {
conn.writeMtx.Lock()
_, err := conn.Write(data)
conn.writeMtx.Unlock()
return err
func (conn *underlayQUIC) WriteBytes(mainType uint16, subType uint16, data []byte) error {
return DefaultWriteBytes(conn, mainType, subType, data)
}
func (conn *quicConn) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
// TODO: call newMessage
data, err := json.Marshal(packet)
if err != nil {
return err
}
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err = conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
func (conn *underlayQUIC) WriteBuffer(data []byte) error {
return DefaultWriteBuffer(conn, data)
}
func (conn *quicConn) Close() error {
func (conn *underlayQUIC) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
return DefaultWriteMessage(conn, mainType, subType, packet)
}
func (conn *underlayQUIC) Close() error {
conn.Stream.CancelRead(1)
conn.Session.CloseWithError(0, "")
conn.Connection.CloseWithError(0, "")
conn.CloseListener()
return nil
}
func (conn *quicConn) CloseListener() {
func (conn *underlayQUIC) WLock() {
conn.writeMtx.Lock()
}
func (conn *underlayQUIC) WUnlock() {
conn.writeMtx.Unlock()
}
func (conn *underlayQUIC) CloseListener() {
if conn.listener != nil {
conn.listener.Close()
}
}
func (conn *quicConn) Accept() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
func (conn *underlayQUIC) Accept() error {
ctx, cancel := context.WithTimeout(context.Background(), UnderlayConnectTimeout)
defer cancel()
sess, err := conn.listener.Accept(ctx)
if err != nil {
@@ -94,35 +76,41 @@ func (conn *quicConn) Accept() error {
return err
}
conn.Stream = stream
conn.Session = sess
conn.Connection = sess
return nil
}
func listenQuic(addr string, idleTimeout time.Duration) (*quicConn, error) {
gLog.Println(LevelDEBUG, "quic listen on ", addr)
func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
gLog.Println(LvDEBUG, "quic listen on ", addr)
listener, err := quic.ListenAddr(addr, generateTLSConfig(),
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
}
return &quicConn{listener: listener, writeMtx: &sync.Mutex{}}, nil
ul := &underlayQUIC{listener: listener, writeMtx: &sync.Mutex{}}
err = ul.Accept()
if err != nil {
ul.CloseListener()
return nil, fmt.Errorf("accept quic error:%s", err)
}
return ul, nil
}
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*quicConn, error) {
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayQUIC, error) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"openp2pv1"},
}
session, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
Connection, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
return nil, fmt.Errorf("quic.DialContext error:%s", err)
}
stream, err := session.OpenStreamSync(context.Background())
stream, err := Connection.OpenStreamSync(context.Background())
if err != nil {
return nil, fmt.Errorf("OpenStreamSync error:%s", err)
}
qConn := &quicConn{nil, &sync.Mutex{}, stream, session}
qConn := &underlayQUIC{nil, &sync.Mutex{}, stream, Connection}
return qConn, nil
}
+135
View File
@@ -0,0 +1,135 @@
package openp2p
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
reuse "github.com/openp2p-cn/go-reuseport"
)
type underlayTCP struct {
writeMtx *sync.Mutex
net.Conn
connectTime time.Time
}
func (conn *underlayTCP) Protocol() string {
return "tcp"
}
func (conn *underlayTCP) ReadBuffer() (*openP2PHeader, []byte, error) {
return DefaultReadBuffer(conn)
}
func (conn *underlayTCP) WriteBytes(mainType uint16, subType uint16, data []byte) error {
return DefaultWriteBytes(conn, mainType, subType, data)
}
func (conn *underlayTCP) WriteBuffer(data []byte) error {
return DefaultWriteBuffer(conn, data)
}
func (conn *underlayTCP) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
return DefaultWriteMessage(conn, mainType, subType, packet)
}
func (conn *underlayTCP) Close() error {
return conn.Conn.Close()
}
func (conn *underlayTCP) WLock() {
conn.writeMtx.Lock()
}
func (conn *underlayTCP) WUnlock() {
conn.writeMtx.Unlock()
}
func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel) (*underlayTCP, error) {
if mode == LinkModeTCPPunch {
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) + GNetwork.dt - time.Now().UnixNano())
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
time.Sleep(ts)
}
gLog.Println(LvDEBUG, " send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port))
c, err := reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout)
if err != nil {
gLog.Println(LvDEBUG, "send tcp punch: ", err)
return nil, err
}
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}
_, buff, err := utcp.ReadBuffer()
if err != nil {
return nil, fmt.Errorf("read start msg error:%s", err)
}
if buff != nil {
gLog.Println(LvDEBUG, string(buff))
}
utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
return utcp, nil
}
GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
tid := t.id
if compareVersion(t.config.peerVersion, PublicIPVersion) < 0 { // old version
ipBytes := net.ParseIP(t.config.peerIP).To4()
tid = uint64(binary.BigEndian.Uint32(ipBytes))
gLog.Println(LvDEBUG, "compatible with old client, use ip as key:", tid)
}
var utcp *underlayTCP
if mode == LinkModeIntranet && gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 {
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", localPort))
l, err := net.ListenTCP("tcp4", addr)
if err != nil {
gLog.Printf(LvERROR, "listen %d error:", localPort, err)
return nil, err
}
defer l.Close()
err = l.SetDeadline(time.Now().Add(UnderlayTCPConnectTimeout))
if err != nil {
gLog.Printf(LvERROR, "set listen timeout:", err)
return nil, err
}
c, err := l.Accept()
if err != nil {
return nil, err
}
utcp = &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}
} else {
if v4l != nil {
utcp = v4l.getUnderlayTCP(tid)
}
}
if utcp == nil {
return nil, ErrConnectPublicV4
}
return utcp, nil
}
func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) {
var c net.Conn
var err error
if mode == LinkModeTCPPunch {
gLog.Println(LvDev, " send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port))
if c, err = reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout); err != nil {
gLog.Println(LvDev, "send tcp punch: ", err)
}
} else {
c, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout)
}
if err != nil {
gLog.Printf(LvDev, "Dial %s:%d error:%s", host, port, err)
return nil, err
}
tc := c.(*net.TCPConn)
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(UnderlayTCPKeepalive)
gLog.Printf(LvDEBUG, "Dial %s:%d OK", host, port)
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
+67
View File
@@ -0,0 +1,67 @@
package openp2p
import (
"fmt"
"net"
"sync"
"time"
)
type underlayTCP6 struct {
writeMtx *sync.Mutex
net.Conn
}
func (conn *underlayTCP6) Protocol() string {
return "tcp6"
}
func (conn *underlayTCP6) ReadBuffer() (*openP2PHeader, []byte, error) {
return DefaultReadBuffer(conn)
}
func (conn *underlayTCP6) WriteBytes(mainType uint16, subType uint16, data []byte) error {
return DefaultWriteBytes(conn, mainType, subType, data)
}
func (conn *underlayTCP6) WriteBuffer(data []byte) error {
return DefaultWriteBuffer(conn, data)
}
func (conn *underlayTCP6) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
return DefaultWriteMessage(conn, mainType, subType, packet)
}
func (conn *underlayTCP6) Close() error {
return conn.Conn.Close()
}
func (conn *underlayTCP6) WLock() {
conn.writeMtx.Lock()
}
func (conn *underlayTCP6) WUnlock() {
conn.writeMtx.Unlock()
}
func listenTCP6(port int, timeout time.Duration) (*underlayTCP6, error) {
addr, _ := net.ResolveTCPAddr("tcp6", fmt.Sprintf("[::]:%d", port))
l, err := net.ListenTCP("tcp6", addr)
if err != nil {
return nil, err
}
defer l.Close()
l.SetDeadline(time.Now().Add(timeout))
c, err := l.Accept()
defer l.Close()
if err != nil {
return nil, err
}
return &underlayTCP6{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
func dialTCP6(host string, port int) (*underlayTCP6, error) {
c, err := net.DialTimeout("tcp6", fmt.Sprintf("[%s]:%d", host, port), UnderlayConnectTimeout)
if err != nil {
gLog.Printf(LvERROR, "Dial %s:%d error:%s", host, port, err)
return nil, err
}
return &underlayTCP6{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
+239
View File
@@ -0,0 +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)
}
}
}
+384
View File
@@ -0,0 +1,384 @@
/*
Taken from taipei-torrent And fix some bugs
*/
package openp2p
import (
"bytes"
"crypto/tls"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"time"
)
type upnpNAT struct {
serviceURL string
ourIP string
urnDomain string
}
// protocol is either "udp" or "tcp"
type NAT interface {
GetExternalAddress() (addr net.IP, err error)
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
}
func Discover() (nat NAT, err error) {
localIP := localIPv4()
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
if err != nil {
return
}
conn, err := net.ListenPacket("udp4", fmt.Sprintf("%s:0", localIP))
if err != nil {
return
}
socket := conn.(*net.UDPConn)
defer socket.Close() // nolint: errcheck
if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil {
return nil, err
}
st := "InternetGatewayDevice:1"
buf := bytes.NewBufferString(
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST: ssdp:all\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n\r\n")
message := buf.Bytes()
answerBytes := make([]byte, 1024)
for i := 0; i < 3; i++ {
_, err = socket.WriteToUDP(message, ssdp)
if err != nil {
return
}
var n int
_, _, err = socket.ReadFromUDP(answerBytes)
if err != nil {
gLog.Println(LvDEBUG, "UPNP discover error:", err)
return
}
for {
n, _, err = socket.ReadFromUDP(answerBytes)
if err != nil {
break
}
answer := string(answerBytes[0:n])
if !strings.Contains(answer, st) {
continue
}
// HTTP header field names are case-insensitive.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
locString := "\r\nlocation:"
answer = strings.ToLower(answer)
locIndex := strings.Index(answer, locString)
if locIndex < 0 {
continue
}
loc := answer[locIndex+len(locString):]
endIndex := strings.Index(loc, "\r\n")
if endIndex < 0 {
continue
}
locURL := strings.TrimSpace(loc[0:endIndex])
var serviceURL, urnDomain string
serviceURL, urnDomain, err = getServiceURL(locURL)
if err != nil {
return
}
if err != nil {
return
}
nat = &upnpNAT{serviceURL: serviceURL, ourIP: localIP, urnDomain: urnDomain}
return
}
}
err = errors.New("UPnP port discovery failed")
return
}
type Envelope struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Soap *SoapBody
}
type SoapBody struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
ExternalIP *ExternalIPAddressResponse
}
type ExternalIPAddressResponse struct {
XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
IPAddress string `xml:"NewExternalIPAddress"`
}
type ExternalIPAddress struct {
XMLName xml.Name `xml:"NewExternalIPAddress"`
IP string
}
type UPNPService struct {
ServiceType string `xml:"serviceType"`
ControlURL string `xml:"controlURL"`
}
type DeviceList struct {
Device []Device `xml:"device"`
}
type ServiceList struct {
Service []UPNPService `xml:"service"`
}
type Device struct {
XMLName xml.Name `xml:"device"`
DeviceType string `xml:"deviceType"`
DeviceList DeviceList `xml:"deviceList"`
ServiceList ServiceList `xml:"serviceList"`
}
type Root struct {
Device Device
}
func getChildDevice(d *Device, deviceType string) *Device {
dl := d.DeviceList.Device
for i := 0; i < len(dl); i++ {
if strings.Contains(dl[i].DeviceType, deviceType) {
return &dl[i]
}
}
return nil
}
func getChildService(d *Device, serviceType string) *UPNPService {
sl := d.ServiceList.Service
for i := 0; i < len(sl); i++ {
if strings.Contains(sl[i].ServiceType, serviceType) {
return &sl[i]
}
}
return nil
}
func localIPv4() string { // TODO: multi nic will wrong
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
func getServiceURL(rootURL string) (url, urnDomain string, err error) {
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: time.Second * 3}
r, err := client.Get(rootURL)
if err != nil {
return
}
defer r.Body.Close() // nolint: errcheck
if r.StatusCode >= 400 {
err = errors.New(fmt.Sprint(r.StatusCode))
return
}
var root Root
err = xml.NewDecoder(r.Body).Decode(&root)
if err != nil {
return
}
a := &root.Device
if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") {
err = errors.New("No InternetGatewayDevice")
return
}
b := getChildDevice(a, "WANDevice:1")
if b == nil {
err = errors.New("No WANDevice")
return
}
c := getChildDevice(b, "WANConnectionDevice:1")
if c == nil {
err = errors.New("No WANConnectionDevice")
return
}
d := getChildService(c, "WANIPConnection:1")
if d == nil {
// Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
// instead of under WanConnectionDevice
d = getChildService(b, "WANIPConnection:1")
if d == nil {
d = getChildService(c, "WANPPPConnection:1")
if d == nil {
err = errors.New("No WANIPConnection or WANPPPConnection")
return
}
}
}
// Extract the domain name, which isn't always 'schemas-upnp-org'
urnDomain = strings.Split(d.ServiceType, ":")[1]
url = combineURL(rootURL, d.ControlURL)
return
}
func combineURL(rootURL, subURL string) string {
protocolEnd := "://"
protoEndIndex := strings.Index(rootURL, protocolEnd)
a := rootURL[protoEndIndex+len(protocolEnd):]
rootIndex := strings.Index(a, "/")
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
}
func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
fullMessage := "<?xml version=\"1.0\" ?>" +
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
"<s:Body>" + message + "</s:Body></s:Envelope>"
req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
//req.Header.Set("Transfer-Encoding", "chunked")
req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
req.Header.Set("Connection", "Close")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
// log.Stderr("soapRequest ", req)
r, err = http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
/*if r.Body != nil {
defer r.Body.Close()
}*/
if r.StatusCode >= 400 {
// log.Stderr(function, r.StatusCode)
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
r = nil
return
}
return
}
type statusInfo struct {
externalIpAddress string
}
func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"</u:GetExternalIPAddress>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
var envelope Envelope
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return
}
reader := bytes.NewReader(data)
err = xml.NewDecoder(reader).Decode(&envelope)
if err != nil {
return
}
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
if err != nil {
return
}
return
}
// GetExternalAddress returns an external IP. If GetExternalIPAddress action
// fails or IP returned is invalid, GetExternalAddress returns an error.
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
info, err := n.getExternalIPAddress()
if err != nil {
return
}
addr = net.ParseIP(info.externalIpAddress)
if addr == nil {
err = fmt.Errorf("Failed to parse IP: %v", info.externalIpAddress)
}
return
}
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
// A single concatenation would break ARM compilation.
message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
message += description +
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
"</NewLeaseDuration></u:AddPortMapping>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
mappedExternalPort = externalPort
_ = response
return
}
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
"</u:DeletePortMapping>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
_ = response
return
}
+2 -5
View File
@@ -1,7 +1,4 @@
//go:build darwin
// +build darwin
package main
package openp2p
import (
"strings"
@@ -24,7 +21,7 @@ func setRLimit() error {
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Cur = 10240
limit.Cur = 65536
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
+69
View File
@@ -0,0 +1,69 @@
package openp2p
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"runtime"
"strings"
"syscall"
)
const (
defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p"
)
func getOsName() (osName string) {
var sysnamePath string
sysnamePath = "/etc/redhat-release"
_, err := os.Stat(sysnamePath)
if err != nil && os.IsNotExist(err) {
str := "PRETTY_NAME="
f, err := os.Open("/etc/os-release")
if err == nil {
buf := bufio.NewReader(f)
for {
line, err := buf.ReadString('\n')
if err == nil {
line = strings.TrimSpace(line)
pos := strings.Count(line, str)
if pos > 0 {
len1 := len([]rune(str)) + 1
rs := []rune(line)
osName = string(rs[len1 : (len(rs))-1])
break
}
} else {
break
}
}
}
} else {
buff, err := ioutil.ReadFile(sysnamePath)
if err == nil {
osName = string(bytes.TrimSpace(buff))
}
}
if osName == "" {
osName = "FreeBSD"
}
return
}
func setRLimit() error {
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Max = 65536
limit.Cur = limit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
return nil
}
func setFirewall() {
}
+6 -5
View File
@@ -1,13 +1,11 @@
//go:build linux
// +build linux
package main
package openp2p
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"runtime"
"strings"
"syscall"
)
@@ -18,6 +16,9 @@ const (
)
func getOsName() (osName string) {
if runtime.GOOS == "android" {
return "Android"
}
var sysnamePath string
sysnamePath = "/etc/redhat-release"
_, err := os.Stat(sysnamePath)
@@ -63,7 +64,7 @@ func setRLimit() error {
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Max = 1024 * 1024
limit.Max = 65536
limit.Cur = limit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
+17 -8
View File
@@ -1,13 +1,11 @@
//go:build windows
// +build windows
package main
package openp2p
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"golang.org/x/sys/windows/registry"
@@ -26,6 +24,17 @@ func getOsName() (osName string) {
defer k.Close()
pn, _, err := k.GetStringValue("ProductName")
if err == nil {
currentBuild, _, err := k.GetStringValue("CurrentBuild")
if err != nil {
return
}
buildNumber, err := strconv.Atoi(currentBuild)
if err != nil {
return
}
if buildNumber >= 22000 {
pn = strings.Replace(pn, "Windows 10", "Windows 11", 1)
}
osName = pn
}
return
@@ -38,7 +47,7 @@ func setRLimit() error {
func setFirewall() {
fullPath, err := filepath.Abs(os.Args[0])
if err != nil {
gLog.Println(LevelERROR, "add firewall error:", err)
gLog.Println(LvERROR, "add firewall error:", err)
return
}
isXP := false
@@ -48,9 +57,9 @@ func setFirewall() {
}
if isXP {
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall del allowedprogram "%s"`, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall add allowedprogram "%s" "%s" ENABLE`, ProducnName, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall add allowedprogram "%s" "%s" ENABLE`, ProductName, fullPath)).Run()
} else { // win7 or later
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall del rule name="%s"`, ProducnName)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall add rule name="%s" dir=in action=allow program="%s" enable=yes`, ProducnName, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall del rule name="%s"`, ProductName)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall add rule name="%s" dir=in action=allow program="%s" enable=yes`, ProductName, fullPath)).Run()
}
}
+92
View File
@@ -0,0 +1,92 @@
package openp2p
import (
"encoding/binary"
"fmt"
"net"
"sync"
"time"
)
type v4Listener struct {
conns sync.Map
port int
acceptCh chan bool
}
func (vl *v4Listener) start() error {
v4l.acceptCh = make(chan bool, 500)
for {
vl.listen()
time.Sleep(UnderlayTCPConnectTimeout)
}
}
func (vl *v4Listener) listen() error {
gLog.Printf(LvINFO, "v4Listener listen %d start", vl.port)
defer gLog.Printf(LvINFO, "v4Listener listen %d end", vl.port)
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", vl.port))
l, err := net.ListenTCP("tcp4", addr)
if err != nil {
gLog.Printf(LvERROR, "v4Listener listen %d error:", vl.port, err)
return err
}
defer l.Close()
for {
c, err := l.Accept()
if err != nil {
break
}
go vl.handleConnection(c)
}
return nil
}
func (vl *v4Listener) handleConnection(c net.Conn) {
gLog.Println(LvDEBUG, "v4Listener accept connection: ", c.RemoteAddr().String())
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()}
utcp.SetReadDeadline(time.Now().Add(UnderlayTCPConnectTimeout))
_, buff, err := utcp.ReadBuffer()
if err != nil {
gLog.Println(LvERROR, "utcp.ReadBuffer error:", err)
}
utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
var tid uint64
if string(buff) == "OpenP2P,hello" { // old client
// save remoteIP as key
remoteAddr := c.RemoteAddr().(*net.TCPAddr).IP
ipBytes := remoteAddr.To4()
tid = uint64(binary.BigEndian.Uint32(ipBytes)) // bytes not enough for uint64
gLog.Println(LvDEBUG, "hello ", string(buff))
} else {
if len(buff) < 8 {
return
}
tid = binary.LittleEndian.Uint64(buff[:8])
gLog.Println(LvDEBUG, "hello ", tid)
}
// clear timeout connection
vl.conns.Range(func(idx, i interface{}) bool {
ut := i.(*underlayTCP)
if ut.connectTime.Before(time.Now().Add(-UnderlayTCPConnectTimeout)) {
vl.conns.Delete(idx)
}
return true
})
vl.conns.Store(tid, utcp)
if len(vl.acceptCh) == 0 {
vl.acceptCh <- true
}
}
func (vl *v4Listener) getUnderlayTCP(tid uint64) *underlayTCP {
for i := 0; i < 100; i++ {
select {
case <-time.After(time.Millisecond * 50):
case <-vl.acceptCh:
}
if u, ok := vl.conns.LoadAndDelete(tid); ok {
return u.(*underlayTCP)
}
}
return nil
}
-261
View File
@@ -1,261 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/kardianos/service"
)
type daemon struct {
running bool
proc *os.Process
}
func (d *daemon) Start(s service.Service) error {
gLog.Println(LevelINFO, "daemon start")
return nil
}
func (d *daemon) Stop(s service.Service) error {
gLog.Println(LevelINFO, "service stop")
d.running = false
if d.proc != nil {
gLog.Println(LevelINFO, "stop worker")
d.proc.Kill()
}
if service.Interactive() {
gLog.Println(LevelINFO, "stop daemon")
os.Exit(0)
}
return nil
}
func (d *daemon) run() {
gLog.Println(LevelINFO, "daemon run start")
defer gLog.Println(LevelINFO, "daemon run end")
d.running = true
binPath, _ := os.Executable()
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
gLog.Println(LevelINFO, mydir)
conf := &service.Config{
Name: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
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
}
}
for {
// start worker
gLog.Println(LevelINFO, "start worker process, args:", args)
execSpec := &os.ProcAttr{Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}}
p, err := os.StartProcess(binPath, args, execSpec)
if err != nil {
gLog.Printf(LevelERROR, "start worker error:%s", err)
return
}
d.proc = p
_, _ = p.Wait()
if !d.running {
return
}
gLog.Printf(LevelERROR, "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: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
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
}
// examples:
// listen:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0
// listen and build p2papp:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
func install() {
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
gLog.Println(LevelINFO, "Contact: QQ Group: 16947733, Email: [email protected]")
gLog.Println(LevelINFO, "install start")
defer gLog.Println(LevelINFO, "install end")
// auto uninstall
uninstall()
// save config file
installFlag := flag.NewFlagSet("install", flag.ExitOnError)
serverHost := installFlag.String("serverhost", "api.openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
token := installFlag.Uint64("token", 0, "token")
node := installFlag.String("node", "", "node name. 8-31 characters. if not set, it will be hostname")
peerNode := installFlag.String("peernode", "", "peer node name that you want to connect")
dstIP := installFlag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := installFlag.Int("dstport", 0, "destination port ")
srcPort := installFlag.Int("srcport", 0, "source port ")
protocol := installFlag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
shareBandwidth := installFlag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
logLevel := installFlag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
installFlag.Parse(os.Args[2:])
if *node != "" && len(*node) < 8 {
gLog.Println(LevelERROR, ErrNodeTooShort)
os.Exit(9)
}
if *node == "" { // if node name not set. use os.Hostname
hostname := defaultNodeName()
node = &hostname
}
gConf.load() // load old config. otherwise will clear all apps
gConf.LogLevel = *logLevel
gConf.Network.ServerHost = *serverHost
gConf.Network.Token = *token
gConf.Network.Node = *node
gConf.Network.ServerPort = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gConf.Network.ShareBandwidth = *shareBandwidth
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
if config.SrcPort != 0 {
gConf.add(config, true)
}
err := os.MkdirAll(defaultInstallPath, 0775)
if err != nil {
gLog.Printf(LevelERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
return
}
err = os.Chdir(defaultInstallPath)
if err != nil {
gLog.Println(LevelERROR, "cd error:", err)
return
}
gConf.save()
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(LevelERROR, "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(LevelERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
return
}
_, errFiles = io.Copy(dst, src)
if errFiles != nil {
gLog.Printf(LevelERROR, "io.Copy error:%s", errFiles)
return
}
src.Close()
dst.Close()
// install system service
gLog.Println(LevelINFO, "targetPath:", targetPath)
err = d.Control("install", targetPath, []string{"-d"})
if err == nil {
gLog.Println(LevelINFO, "install system service ok.")
}
time.Sleep(time.Second * 2)
err = d.Control("start", targetPath, []string{"-d"})
if err != nil {
gLog.Println(LevelERROR, "start openp2p service error:", err)
} else {
gLog.Println(LevelINFO, "start openp2p service ok.")
}
}
func installByFilename() {
params := strings.Split(filepath.Base(os.Args[0]), "-")
if len(params) < 4 {
return
}
serverHost := params[1]
token := params[2]
gLog.Println(LevelINFO, "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(LevelERROR, "install by filename, start process error:", err)
return
}
gLog.Println(LevelINFO, "install end")
fmt.Println("Press the Any Key to exit")
fmt.Scanln()
os.Exit(0)
}
func uninstall() {
gLog.Println(LevelINFO, "uninstall start")
defer gLog.Println(LevelINFO, "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(LevelERROR, "uninstall system service error:", err)
} else {
gLog.Println(LevelINFO, "uninstall system service ok.")
}
binPath := filepath.Join(defaultInstallPath, defaultBinName)
os.Remove(binPath + "0")
os.Remove(binPath)
// os.RemoveAll(defaultInstallPath) // reserve config.json
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

+73
View File
@@ -0,0 +1,73 @@
# 1. 环境
golang1.18.1
调试端: win10+vscode
被调试端: ubuntu20.04LTS+dlv
## dlv安装
参考官方文档https://github.com/go-delve/delve/tree/master/Documentation/installation
```
go install github.com/go-delve/delve/cmd/dlv@latest
```
## 被调试端
```
cd /your-src-path #要确保两端源码一致
dlv debug --headless --listen=:2345 --api-version=2
# 如果失败可查看更多日志
# dlv debug --headless --listen=:2345 --api-version=2 --log --log-output=rpc,dap,debugger
```
## 调试端(vscode
```
打开vscode,修改launch.json,增加下面远程调试配置
{
"version": "0.2.0",
"configurations": [
{
"name": "RemoteDebug",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 2345,
"host": "192.168.3.29",
}
]
}
按F5启动调试
```
## 没有公网IP或不在同一个局域网,无法直连如何调试
到https://openp2p.cn/注册一个用户获得token,两端安装一个客户端程序,可将被调试端的2345端口,通过p2p连接映射到调试端本地。
p2p连接可通过web配置 https://github.com/openp2p-cn/openp2p/blob/master/README-ZH.md#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8
也可以手动下载https://github.com/openp2p-cn/openp2p/releases 通过命令行手动配置
```
# 注意替换下面YOUR-开头的参数改成自己的
./openp2p -node YOUR-DEBUG-SERVER -token YOUR-TOKEN
openp2p.exe -node YOUR-DEBUG-CLIENT -token YOUR-TOKEN -peernode YOUR-DEBUG-SERVER -dstport 2345 -srcport 2345
2022/04/22 11:07:26 25680 INFO LISTEN ON PORT tcp:2345 START
#显示这条日志说明成功了
```
p2p端口映射完成后,把vscode的配置改成本地127.0.0.1,一样可以顺利调试。
```
{
"version": "0.2.0",
"configurations": [
{
"name": "RemoteDebug",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 2345,
"host": "127.0.0.1",
}
]
}
```
# 参考
https://github.com/golang/vscode-go/blob/master/docs/debugging.md#remote-debugging
+50
View File
@@ -0,0 +1,50 @@
# 1. 环境
visual studio 2022
调试端: win10
被调试端: win10
## 程序编译
一般远程调试我会选择release版本,然后将优化去掉即可,这样更接近真实版本。
![image](/doc/images/release-debug.png)
```
go install github.com/go-delve/delve/cmd/dlv@latest
```
## 运行远程调试器
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Remote Debugger 拷贝到目标机器
如果调试x64程序则cd到Remote Debugger\x64目录
管理员方式打开cmd,执行
```
netsh advfirewall set allprofiles state off
msvsmon.exe /noauth /anyuser /silent
```
## visual studio
Attach远程进程,按下Ctrl+Atl+P
![image](/doc/images/vs2022-remote-debug-attach.png)
## 没有公网IP或不在同一个局域网,无法直连如何调试
到 https://openp2p.cn/ 注册一个用户获得token,两端安装一个客户端程序,可将被调试端的2345端口,通过p2p连接映射到调试端本地。
p2p连接可通过web配置 https://github.com/openp2p-cn/openp2p/blob/master/README-ZH.md#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8
也可以手动下载https://github.com/openp2p-cn/openp2p/releases 通过命令行手动配置
```
# 注意替换下面YOUR-开头的参数改成自己的
./openp2p -node YOUR-DEBUG-SERVER -token YOUR-TOKEN
openp2p.exe -node YOUR-DEBUG-CLIENT -token YOUR-TOKEN -peernode YOUR-DEBUG-SERVER -dstport 4026 -srcport 4026
2022/04/22 11:07:26 25680 INFO LISTEN ON PORT tcp:4026 START
#显示这条日志说明成功了
```
![image](/doc/images/p2p-debug.png)
可以顺利远程调试
```
+12
View File
@@ -0,0 +1,12 @@
FROM alpine:3.18.2
# Replace the default Alpine repositories with Aliyun mirrors
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories && \
apk add --no-cache ca-certificates iptables && \
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
COPY get-client.sh /
ARG DOCKER_VER="latest"
RUN echo $TARGETPLATFORM && chmod +x /get-client.sh && ./get-client.sh
ENTRYPOINT ["/openp2p"]
+45
View File
@@ -0,0 +1,45 @@
#!/bin/sh
echo "Building version:${DOCKER_VER}"
echo "Running on platform: $TARGETPLATFORM"
# TARGETPLATFORM=$(echo $TARGETPLATFORM | tr ',' '/')
echo "Running on platform: $TARGETPLATFORM"
sysType="linux-amd64"
archType=$(uname -m)
if [[ $archType == aarch64 ]] ;
then
sysType="linux-arm64"
elif [[ $archType == arm* ]] ;
then
sysType="linux-arm"
elif [[ $archType == i*86 ]] ;
then
sysType="linux-386"
elif [[ $archType == mips ]] ;
then
sysType="linux-mipsle"
ls /lib |grep mipsel
if [[ $? -ne 0 ]]; then
# mipsel not found, it's mipseb
sysType="linux-mipsbe"
fi
fi
url="https://openp2p.cn/download/v1/${DOCKER_VER}/openp2p-latest.$sysType.tar.gz"
echo "download $url start"
if [ -f /usr/bin/curl ]; then
curl -k -o openp2p.tar.gz $url
else
wget --no-check-certificate -O openp2p.tar.gz $url
fi
if [ $? -ne 0 ]; then
echo "download error $?"
exit 9
fi
echo "download ok"
tar -xzvf openp2p.tar.gz
chmod +x openp2p
pwd
ls -l
exit 0
-16
View File
@@ -1,16 +0,0 @@
package main
import (
"errors"
)
// error message
var (
// ErrorS2S string = "s2s is not supported"
// ErrorHandshake string = "handshake error"
ErrorS2S = errors.New("s2s is not supported")
ErrorHandshake = errors.New("handshake error")
ErrorNewUser = errors.New("new user")
ErrorLogin = errors.New("user or password not correct")
ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters")
)
+14
View File
@@ -0,0 +1,14 @@
#include <iostream>
#include <windows.h>
using namespace std;
typedef void (*pRun)(const char *);
int main(int argc, char *argv[])
{
HMODULE dll = LoadLibraryA("openp2p.dll");
pRun run = (pRun)GetProcAddress(dll, "RunCmd");
run("-node 5800-debug2 -token YOUR-TOKEN");
FreeLibrary(dll);
return 0;
}
+42
View File
@@ -0,0 +1,42 @@
package main
import (
"fmt"
op "openp2p/core"
"time"
)
func main() {
op.Run()
for i := 0; i < 10; i++ {
go echoClient("5800-debug")
}
echoClient("5800-debug")
}
func echoClient(peerNode string) {
sendDatalen := op.ReadBuffLen
sendBuff := make([]byte, sendDatalen)
for i := 0; i < len(sendBuff); i++ {
sendBuff[i] = byte('A' + i/100)
}
// peerNode = "YOUR-PEER-NODE-NAME"
if err := op.GNetwork.ConnectNode(peerNode); err != nil {
fmt.Println("connect error:", err)
return
}
for i := 0; ; i++ {
sendBuff[1] = 'A' + byte(i%26)
if err := op.GNetwork.WriteNode(op.NodeNameToID(peerNode), sendBuff[:sendDatalen]); err != nil {
fmt.Println("write error:", err)
break
}
nd := op.GNetwork.ReadNode(time.Second * 10)
if nd == nil {
fmt.Printf("waiting for node data\n")
time.Sleep(time.Second * 10)
continue
}
fmt.Printf("read %d len=%d data=%s\n", nd.NodeID, len(nd.Data), nd.Data[:16]) // only print 16 bytes
}
}
+32
View File
@@ -0,0 +1,32 @@
package main
import (
"fmt"
op "openp2p/core"
"time"
)
func main() {
op.Run()
echoServer()
forever := make(chan bool)
<-forever
}
func echoServer() {
// peerID := fmt.Sprintf("%d", core.NodeNameToID(peerNode))
for {
nd := op.GNetwork.ReadNode(time.Second * 10)
if nd == nil {
fmt.Printf("waiting for node data\n")
// time.Sleep(time.Second * 10)
continue
}
// fmt.Printf("read %s len=%d data=%s\n", nd.Node, len(nd.Data), nd.Data[:16])
nd.Data[0] = 'R' // echo server mark as replied
if err := op.GNetwork.WriteNode(nd.NodeID, nd.Data); err != nil {
fmt.Println("write error:", err)
break
}
}
}
+35 -4
View File
@@ -1,10 +1,41 @@
module openp2p
go 1.16
go 1.20
require (
github.com/emirpasic/gods v1.18.1
github.com/gorilla/websocket v1.4.2
github.com/kardianos/service v1.2.0
github.com/lucas-clemente/quic-go v0.24.0
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
github.com/openp2p-cn/go-reuseport v0.3.2
github.com/openp2p-cn/service v1.0.0
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e
github.com/quic-go/quic-go v0.34.0
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
github.com/xtaci/kcp-go/v5 v5.5.17
golang.org/x/sys v0.26.0
golang.zx2c4.com/wireguard/windows v0.5.3
)
require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/kardianos/service v1.2.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/templexxx/cpu v0.1.0 // indirect
github.com/templexxx/xorsimd v0.4.2 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/tools v0.26.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 // indirect
)
-213
View File
@@ -1,213 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
)
func handlePush(pn *P2PNetwork, 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(LevelDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead)
switch subType {
case MsgPushConnectReq:
req := PushConnectReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushConnectReq:%s", err)
return err
}
gLog.Printf(LevelINFO, "%s is connecting...", req.From)
gLog.Println(LevelDEBUG, "push connect response to ", req.From)
// verify totp token or token
if VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()) ||
(req.FromToken == pn.config.Token) {
gLog.Printf(LevelINFO, "Access Granted\n")
config := AppConfig{}
config.peerNatType = req.NatType
config.peerConeNatPort = req.ConeNatPort
config.peerIP = req.FromIP
config.PeerNode = req.From
// share relay node will limit bandwidth
if req.FromToken != pn.config.Token {
gLog.Printf(LevelINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth)
config.shareBandwidth = pn.config.ShareBandwidth
}
// go pn.AddTunnel(config, req.ID)
go pn.addDirectTunnel(config, req.ID)
break
}
gLog.Println(LevelERROR, "Access Denied:", req.From)
rsp := PushConnectRsp{
Error: 1,
Detail: fmt.Sprintf("connect to %s error: Access Denied", pn.config.Node),
To: req.From,
From: pn.config.Node,
}
pn.push(req.From, MsgPushConnectRsp, rsp)
case MsgPushRsp:
rsp := PushRsp{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong pushRsp:%s", err)
return err
}
if rsp.Error == 0 {
gLog.Printf(LevelDEBUG, "push ok, detail:%s", rsp.Detail)
} else {
gLog.Printf(LevelERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail)
}
case MsgPushAddRelayTunnelReq:
req := AddRelayTunnelReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
return err
}
config := AppConfig{}
config.PeerNode = req.RelayName
config.peerToken = req.RelayToken
go func(r AddRelayTunnelReq) {
t, errDt := pn.addDirectTunnel(config, 0)
if errDt == nil {
// notify peer relay ready
msg := TunnelMsg{ID: t.id}
pn.push(r.From, MsgPushAddRelayTunnelRsp, msg)
SaveKey(req.AppID, req.AppKey)
}
}(req)
case MsgPushUpdate:
gLog.Println(LevelINFO, "MsgPushUpdate")
update() // download new version first, then exec ./openp2p update
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
args := []string{"update"}
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 {
os.Exit(0)
}
return err
case MsgPushRestart:
gLog.Println(LevelINFO, "MsgPushRestart")
os.Exit(0)
return err
case MsgPushReportApps:
gLog.Println(LevelINFO, "MsgPushReportApps")
req := ReportApps{}
// TODO: add the retrying apps
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
for _, config := range gConf.Apps {
appActive := 0
relayNode := ""
relayMode := ""
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
if app.isActive() {
appActive = 1
}
relayNode = app.relayNode
relayMode = app.relayMode
}
appInfo := AppInfo{
AppName: config.AppName,
Protocol: config.Protocol,
SrcPort: config.SrcPort,
RelayNode: relayNode,
RelayMode: relayMode,
PeerNode: config.PeerNode,
DstHost: config.DstHost,
DstPort: config.DstPort,
PeerUser: config.PeerUser,
PeerIP: config.peerIP,
PeerNatType: config.peerNatType,
RetryTime: config.retryTime.String(),
IsActive: appActive,
Enabled: config.Enabled,
}
req.Apps = append(req.Apps, appInfo)
}
pn.write(MsgReport, MsgReportApps, &req)
case MsgPushEditApp:
gLog.Println(LevelINFO, "MsgPushEditApp")
newApp := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &newApp)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
oldConf := AppConfig{Enabled: 1}
// protocol0+srcPort0 exist, delApp
oldConf.AppName = newApp.AppName
oldConf.Protocol = newApp.Protocol0
oldConf.SrcPort = newApp.SrcPort0
oldConf.PeerNode = newApp.PeerNode
oldConf.DstHost = newApp.DstHost
oldConf.DstPort = newApp.DstPort
gConf.delete(oldConf)
// AddApp
newConf := oldConf
newConf.Protocol = newApp.Protocol
newConf.SrcPort = newApp.SrcPort
gConf.add(newConf, false)
gConf.save() // save quickly for the next request reportApplist
pn.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end
// autoReconnect will auto AddApp
// pn.AddApp(config)
// TODO: report result
case MsgPushEditNode:
gLog.Println(LevelINFO, "MsgPushEditNode")
req := EditNode{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditNode:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
gConf.mtx.Lock()
gConf.Network.Node = req.NewName
gConf.Network.ShareBandwidth = req.Bandwidth
gConf.mtx.Unlock()
gConf.save()
// TODO: hot reload
os.Exit(0)
case MsgPushSwitchApp:
gLog.Println(LevelINFO, "MsgPushSwitchApp")
app := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &app)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushSwitchApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
gLog.Println(LevelINFO, app.AppName, " switch to ", app.Enabled)
gConf.switchApp(config, app.Enabled)
if app.Enabled == 0 {
// disable APP
pn.DeleteApp(config)
}
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[pushHead.From]
pn.msgMapMtx.Unlock()
ch <- msg
}
return nil
}
-180
View File
@@ -1,180 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"net"
"sync"
"time"
)
func handshakeC2C(t *P2PTunnel) (err error) {
gLog.Printf(LevelDEBUG, "handshakeC2C %s:%d:%d to %s:%d", t.pn.config.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
defer gLog.Printf(LevelDEBUG, "handshakeC2C ok")
conn, err := net.ListenUDP("udp", t.la)
if err != nil {
return err
}
defer conn.Close()
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C write MsgPunchHandshake error:", err)
return err
}
ra, head, _, _, err := UDPRead(conn, 5000)
if err != nil {
time.Sleep(time.Millisecond * 200)
gLog.Println(LevelDEBUG, err, ", return this error when ip was not reachable, retry read")
ra, head, _, _, err = UDPRead(conn, 5000)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C read MsgPunchHandshake error:", err)
return err
}
}
t.ra, _ = net.ResolveUDPAddr("udp", ra.String())
// cone server side
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake {
gLog.Printf(LevelDEBUG, "read %d handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, 5000)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
return err
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "read %d handshake ack ", t.id)
return nil
}
}
// cone client side will only read handshake ack
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "read %d handshake ack ", t.id)
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
}
return err
}
return nil
}
func handshakeC2S(t *P2PTunnel) error {
gLog.Printf(LevelDEBUG, "handshakeC2S start")
defer gLog.Printf(LevelDEBUG, "handshakeC2S end")
// even if read timeout, continue handshake
t.pn.read(t.config.PeerNode, MsgPush, MsgPushHandshakeStart, SymmetricHandshakeAckTimeout)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randPorts := r.Perm(65532)
conn, err := net.ListenUDP("udp", t.la)
if err != nil {
return err
}
defer conn.Close()
go func() error {
gLog.Printf(LevelDEBUG, "send symmetric handshake to %s from %d:%d start", t.config.peerIP, t.coneLocalPort, t.coneNatPort)
for i := 0; i < SymmetricHandshakeNum; i++ {
// TODO: auto calc cost time
time.Sleep(SymmetricHandshakeInterval)
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, randPorts[i]+2))
if err != nil {
return err
}
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2S write MsgPunchHandshake error:", err)
return err
}
}
gLog.Println(LevelDEBUG, "send symmetric handshake end")
return nil
}()
deadline := time.Now().Add(SymmetricHandshakeAckTimeout)
err = conn.SetReadDeadline(deadline)
if err != nil {
gLog.Println(LevelERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error")
return err
}
// read response of the punching hole ok port
result := make([]byte, 1024)
_, dst, err := conn.ReadFrom(result)
if err != nil {
gLog.Println(LevelERROR, "handshakeC2S wait timeout")
return err
}
head := &openP2PHeader{}
err = binary.Read(bytes.NewReader(result[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "parse p2pheader error:", err)
return err
}
t.ra, _ = net.ResolveUDPAddr("udp", dst.String())
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "handshakeC2S read %d handshake ack %s", t.id, dst.String())
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
return err
}
return nil
}
func handshakeS2C(t *P2PTunnel) error {
gLog.Printf(LevelDEBUG, "handshakeS2C start")
defer gLog.Printf(LevelDEBUG, "handshakeS2C end")
gotCh := make(chan *net.UDPAddr, 5)
// sequencely udp send handshake, do not parallel send
gLog.Printf(LevelDEBUG, "send symmetric handshake to %s:%d start", t.config.peerIP, t.config.peerConeNatPort)
gotIt := false
gotMtx := sync.Mutex{}
for i := 0; i < SymmetricHandshakeNum; i++ {
// TODO: auto calc cost time
time.Sleep(SymmetricHandshakeInterval)
go func(t *P2PTunnel) error {
conn, err := net.ListenUDP("udp", nil)
if err != nil {
gLog.Printf(LevelDEBUG, "listen error")
return err
}
defer conn.Close()
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
_, head, _, _, err := UDPRead(conn, 10000)
if err != nil {
// gLog.Println(LevelDEBUG, "one of the handshake error:", err)
return err
}
gotMtx.Lock()
defer gotMtx.Unlock()
if gotIt {
return nil
}
gotIt = true
t.la, _ = net.ResolveUDPAddr("udp", conn.LocalAddr().String())
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake {
gLog.Printf(LevelDEBUG, "handshakeS2C read %d handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, 5000)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeS2C handshake error")
return err
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "handshakeS2C read %d handshake ack %s", t.id, conn.LocalAddr().String())
gotCh <- t.la
return nil
}
}
return nil
}(t)
}
gLog.Printf(LevelDEBUG, "send symmetric handshake end")
gLog.Println(LevelDEBUG, "handshakeS2C ready, notify peer connect")
t.pn.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
select {
case <-time.After(SymmetricHandshakeAckTimeout):
return fmt.Errorf("wait handshake failed")
case la := <-gotCh:
gLog.Println(LevelDEBUG, "symmetric handshake ok", la)
}
return nil
}
+18
View File
@@ -0,0 +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))
}
-178
View File
@@ -1,178 +0,0 @@
package main
import (
"log"
"os"
"runtime"
"sync"
"time"
)
// LogLevel ...
type LogLevel int
var gLog *V8log
// LevelDEBUG ...
const (
LevelDEBUG LogLevel = iota
LevelINFO
LevelWARN
LevelERROR
)
var (
logFileNames map[LogLevel]string
loglevel map[LogLevel]string
)
func init() {
logFileNames = make(map[LogLevel]string)
loglevel = make(map[LogLevel]string)
logFileNames[0] = ".log"
loglevel[LevelDEBUG] = "DEBUG"
loglevel[LevelINFO] = "INFO"
loglevel[LevelWARN] = "WARN"
loglevel[LevelERROR] = "ERROR"
}
const (
LogFile = iota
LogConsole
LogFileAndConsole
)
// V8log ...
type V8log struct {
loggers map[LogLevel]*log.Logger
files map[LogLevel]*os.File
level LogLevel
stopSig chan bool
logDir string
mtx *sync.Mutex
stoped bool
lineEnding string
pid int
maxLogSize int64
mode int
}
// InitLogger ...
func InitLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *V8log {
logger := make(map[LogLevel]*log.Logger)
openedfile := make(map[LogLevel]*os.File)
var (
logdir string
)
if path == "" {
logdir = "log/"
} else {
logdir = path + "/log/"
}
os.MkdirAll(logdir, 0777)
for l := range logFileNames {
logFilePath := logdir + filePrefix + logFileNames[l]
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
os.Chmod(logFilePath, 0666)
openedfile[l] = f
logger[l] = log.New(f, "", log.LstdFlags)
}
var le string
if runtime.GOOS == "windows" {
le = "\r\n"
} else {
le = "\n"
}
pLog := &V8log{logger, openedfile, level, make(chan bool, 10), logdir, &sync.Mutex{}, false, le, os.Getpid(), maxLogSize, mode}
go pLog.checkFile()
return pLog
}
func (vl *V8log) setLevel(level LogLevel) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
vl.level = level
}
func (vl *V8log) checkFile() {
if vl.maxLogSize <= 0 {
return
}
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
vl.mtx.Lock()
for l, logFile := range vl.files {
f, e := logFile.Stat()
if e != nil {
break
}
if f.Size() <= vl.maxLogSize {
break
}
logFile.Close()
fname := f.Name()
backupPath := vl.logDir + fname + ".0"
os.Remove(backupPath)
os.Rename(vl.logDir+fname, backupPath)
newFile, e := os.OpenFile(vl.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if e == nil {
vl.loggers[l].SetOutput(newFile)
vl.files[l] = newFile
}
}
vl.mtx.Unlock()
case <-vl.stopSig:
}
if vl.stoped {
break
}
}
}
// Printf Warning: report error log depends on this Print format.
func (vl *V8log) Printf(level LogLevel, format string, params ...interface{}) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
if vl.stoped {
return
}
if level < vl.level {
return
}
pidAndLevel := []interface{}{vl.pid, loglevel[level]}
params = append(pidAndLevel, params...)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Printf("%d %s "+format+vl.lineEnding, params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Printf("%d %s "+format+vl.lineEnding, params...)
}
}
// Println ...
func (vl *V8log) Println(level LogLevel, params ...interface{}) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
if vl.stoped {
return
}
if level < vl.level {
return
}
pidAndLevel := []interface{}{vl.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...)
params = append(params, vl.lineEnding)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Print(params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Print(params...)
}
}
-118
View File
@@ -1,118 +0,0 @@
package main
import (
"encoding/json"
"fmt"
"math/rand"
"net"
"time"
)
func natTest(serverHost string, serverPort int, localPort int, echoPort int) (publicIP string, isPublicIP int, publicPort int, err error) {
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
if err != nil {
return "", 0, 0, err
}
defer conn.Close()
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
if err != nil {
return "", 0, 0, err
}
// The connection can write data to the desired address.
msg, err := newMessage(MsgNATDetect, 0, &NatDetectReq{SrcPort: localPort, EchoPort: echoPort})
_, err = conn.WriteTo(msg, dst)
if err != nil {
return "", 0, 0, err
}
deadline := time.Now().Add(NatTestTimeout)
err = conn.SetReadDeadline(deadline)
if err != nil {
return "", 0, 0, err
}
buffer := make([]byte, 1024)
nRead, _, err := conn.ReadFrom(buffer)
if err != nil {
gLog.Println(LevelERROR, "NAT detect error:", err)
return "", 0, 0, err
}
natRsp := NatDetectRsp{}
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
// testing for public ip
if echoPort != 0 {
for {
gLog.Printf(LevelDEBUG, "public ip test start %s:%d", natRsp.IP, echoPort)
conn, err := net.ListenUDP("udp", nil)
if err != nil {
break
}
defer conn.Close()
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", natRsp.IP, 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 {
gLog.Println(LevelDEBUG, "public ip:YES")
natRsp.IsPublicIP = 1
} else {
gLog.Println(LevelDEBUG, "public ip:NO")
}
break
}
}
return natRsp.IP, natRsp.IsPublicIP, 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()%10000 + 50000)
echoPort := int(rand.Uint32()%10000 + 50000)
go echo(echoPort)
ip1, isPublicIP, port1, err := natTest(host, udp1, localPort, echoPort)
gLog.Printf(LevelDEBUG, "local port:%d nat port:%d", localPort, port1)
if err != nil {
return "", 0, err
}
if isPublicIP == 1 {
return ip1, NATNone, nil
}
ip2, _, port2, err := natTest(host, udp2, localPort, 0) // 2rd nat test not need testing publicip
gLog.Printf(LevelDEBUG, "local port:%d nat port:%d", localPort, port2)
if err != nil {
return "", 0, err
}
if ip1 != ip2 {
return "", 0, fmt.Errorf("ip have changed, please retry again")
}
natType := NATSymmetric
if port1 == port2 {
natType = NATCone
}
//TODO: NATNone
return ip1, natType, nil
}
func echo(echoPort int) {
conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
if err != nil {
gLog.Println(LevelERROR, "echo server listen error:", err)
return
}
buf := make([]byte, 1600)
defer conn.Close()
// wait 5s for echo testing
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
n, addr, err := conn.ReadFromUDP(buf)
if err != nil {
return
}
conn.WriteToUDP(buf[0:n], addr)
}
-57
View File
@@ -1,57 +0,0 @@
package main
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
binDir := filepath.Dir(os.Args[0])
os.Chdir(binDir) // for system service
gLog = InitLogger(binDir, "openp2p", LevelDEBUG, 1024*1024, LogFileAndConsole)
// TODO: install sub command, deamon process
if len(os.Args) > 1 {
switch os.Args[1] {
case "version", "-v", "--version":
fmt.Println(OpenP2PVersion)
return
case "update":
gLog = InitLogger(filepath.Dir(os.Args[0]), "openp2p", LevelDEBUG, 1024*1024, LogFileAndConsole)
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
err := d.Control("restart", targetPath, nil)
if err != nil {
gLog.Println(LevelERROR, "restart service error:", err)
} else {
gLog.Println(LevelINFO, "restart service ok.")
}
return
case "install":
install()
return
case "uninstall":
uninstall()
return
}
} else {
installByFilename()
}
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
gLog.Println(LevelINFO, "Contact: QQ Group: 16947733, Email: [email protected]")
parseParams()
gLog.Println(LevelINFO, &gConf)
setFirewall()
network := P2PNetworkInstance(&gConf.Network)
if ok := network.Connect(30000); !ok {
gLog.Println(LevelERROR, "P2PNetwork login error")
return
}
gLog.Println(LevelINFO, "waiting for connection...")
forever := make(chan bool)
<-forever
}
-85
View File
@@ -1,85 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"net"
"time"
)
// implement io.Writer
type overlayTCP struct {
tunnel *P2PTunnel
conn net.Conn
id uint64
rtid uint64
running bool
isClient bool
appID uint64
appKey uint64
appKeyBytes []byte
}
func (otcp *overlayTCP) run() {
gLog.Printf(LevelDEBUG, "%d overlayTCP run start", otcp.id)
defer gLog.Printf(LevelDEBUG, "%d overlayTCP run end", otcp.id)
otcp.running = true
buffer := make([]byte, ReadBuffLen+PaddingSize)
readBuf := buffer[:ReadBuffLen]
encryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
tunnelHead := new(bytes.Buffer)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, otcp.rtid)
binary.Write(tunnelHead, binary.LittleEndian, otcp.id)
for otcp.running && otcp.tunnel.isRuning() {
otcp.conn.SetReadDeadline(time.Now().Add(time.Second * 5))
dataLen, err := otcp.conn.Read(readBuf)
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue
}
// overlay tcp connection normal close, debug log
gLog.Printf(LevelDEBUG, "overlayTCP %d read error:%s,close it", otcp.id, err)
break
} else {
payload := readBuf[:dataLen]
if otcp.appKey != 0 {
payload, _ = encryptBytes(otcp.appKeyBytes, encryptData, buffer[:dataLen], dataLen)
}
writeBytes := append(tunnelHead.Bytes(), payload...)
if otcp.rtid == 0 {
otcp.tunnel.conn.WriteBytes(MsgP2P, MsgOverlayData, writeBytes)
} else {
// write raley data
all := append(relayHead.Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(writeBytes)))...)
all = append(all, writeBytes...)
otcp.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, all)
gLog.Printf(LevelDEBUG, "write relay data to %d:%d bodylen=%d", otcp.rtid, otcp.id, len(writeBytes))
}
}
}
otcp.conn.Close()
otcp.tunnel.overlayConns.Delete(otcp.id)
// notify peer disconnect
if otcp.isClient {
req := OverlayDisconnectReq{ID: otcp.id}
if otcp.rtid == 0 {
otcp.tunnel.conn.WriteMessage(MsgP2P, MsgOverlayDisconnectReq, &req)
} else {
// write relay data
msg, _ := newMessage(MsgP2P, MsgOverlayDisconnectReq, &req)
msgWithHead := append(relayHead.Bytes(), msg...)
otcp.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
}
}
}
// calling by p2pTunnel
func (otcp *overlayTCP) Write(buff []byte) (n int, err error) {
// add mutex when multi-thread calling
n, err = otcp.conn.Write(buff)
if err != nil {
otcp.tunnel.overlayConns.Delete(otcp.id)
}
return
}
-152
View File
@@ -1,152 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"fmt"
"math/rand"
"net"
"sync"
"time"
)
type p2pApp struct {
config AppConfig
listener net.Listener
tunnel *P2PTunnel
rtid uint64
relayNode string
relayMode string
hbTime time.Time
hbMtx sync.Mutex
running bool
id uint64
key uint64
wg sync.WaitGroup
}
func (app *p2pApp) isActive() bool {
if app.tunnel == nil {
return false
}
if app.rtid == 0 { // direct mode app heartbeat equals to tunnel heartbeat
return app.tunnel.isActive()
}
// relay mode calc app heartbeat
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
return time.Now().Before(app.hbTime.Add(TunnelIdleTimeout))
}
func (app *p2pApp) updateHeartbeat() {
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
app.hbTime = time.Now()
}
func (app *p2pApp) listenTCP() error {
var err error
if app.config.Protocol == "udp" {
app.listener, err = net.Listen("udp4", fmt.Sprintf("0.0.0.0:%d", app.config.SrcPort))
} else {
app.listener, err = net.Listen("tcp4", fmt.Sprintf("0.0.0.0:%d", app.config.SrcPort))
}
if err != nil {
gLog.Printf(LevelERROR, "listen error:%s", err)
return err
}
for {
conn, err := app.listener.Accept()
if err != nil {
gLog.Printf(LevelERROR, "%d accept error:%s", app.tunnel.id, err)
break
}
otcp := overlayTCP{
tunnel: app.tunnel,
conn: conn,
id: rand.Uint64(),
isClient: true,
rtid: app.rtid,
appID: app.id,
appKey: app.key,
}
// calc key bytes for encrypt
if otcp.appKey != 0 {
encryptKey := make([]byte, AESKeySize)
binary.LittleEndian.PutUint64(encryptKey, otcp.appKey)
binary.LittleEndian.PutUint64(encryptKey[8:], otcp.appKey)
otcp.appKeyBytes = encryptKey
}
app.tunnel.overlayConns.Store(otcp.id, &otcp)
gLog.Printf(LevelDEBUG, "Accept overlayID:%d", otcp.id)
// tell peer connect
req := OverlayConnectReq{ID: otcp.id,
Token: app.tunnel.pn.config.Token,
DstIP: app.config.DstHost,
DstPort: app.config.DstPort,
Protocol: app.config.Protocol,
AppID: app.id,
}
if app.rtid == 0 {
app.tunnel.conn.WriteMessage(MsgP2P, MsgOverlayConnectReq, &req)
} else {
req.RelayTunnelID = app.tunnel.id
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, app.rtid)
msg, _ := newMessage(MsgP2P, MsgOverlayConnectReq, &req)
msgWithHead := append(relayHead.Bytes(), msg...)
app.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
}
go otcp.run()
}
return nil
}
func (app *p2pApp) listen() error {
gLog.Printf(LevelINFO, "LISTEN ON PORT %d START", app.config.SrcPort)
defer gLog.Printf(LevelINFO, "LISTEN ON PORT %d START", app.config.SrcPort)
app.wg.Add(1)
defer app.wg.Done()
app.running = true
if app.rtid != 0 {
go app.relayHeartbeatLoop()
}
for app.running {
app.listenTCP()
time.Sleep(time.Second * 5)
}
return nil
}
func (app *p2pApp) close() {
app.running = false
if app.listener != nil {
app.listener.Close()
}
if app.tunnel != nil {
app.tunnel.closeOverlayConns(app.id)
}
app.wg.Wait()
}
// TODO: many relay app on the same P2PTunnel will send a lot of relay heartbeat
func (app *p2pApp) relayHeartbeatLoop() {
app.wg.Add(1)
defer app.wg.Done()
gLog.Printf(LevelDEBUG, "relayHeartbeat to %d start", app.rtid)
defer gLog.Printf(LevelDEBUG, "relayHeartbeat to %d end", app.rtid)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, app.rtid)
req := RelayHeartbeat{RelayTunnelID: app.tunnel.id,
AppID: app.id}
msg, _ := newMessage(MsgP2P, MsgRelayHeartbeat, &req)
msgWithHead := append(relayHead.Bytes(), msg...)
for app.tunnel.isRuning() && app.running {
app.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
time.Sleep(TunnelHeartbeatTime)
}
}
-17
View File
@@ -1,17 +0,0 @@
package main
import (
"time"
)
type p2pConn interface {
ReadMessage() (*openP2PHeader, []byte, error)
WriteBytes(uint16, uint16, []byte) error
WriteBuffer([]byte) error
WriteMessage(uint16, uint16, interface{}) error
Close() error
Accept() error
CloseListener()
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
-608
View File
@@ -1,608 +0,0 @@
package main
import (
"bytes"
"crypto/tls"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"net"
"net/url"
"strings"
"sync"
"time"
"github.com/gorilla/websocket"
)
var (
instance *P2PNetwork
once sync.Once
)
type P2PNetwork struct {
conn *websocket.Conn
online bool
running bool
restartCh chan bool
wg sync.WaitGroup
writeMtx sync.Mutex
serverTs int64
localTs int64
// msgMap sync.Map
msgMap map[uint64]chan []byte //key: nodeID
msgMapMtx sync.Mutex
config NetworkConfig
allTunnels sync.Map
apps sync.Map //key: protocol+srcport; value: p2pApp
limiter *BandwidthLimiter
}
func P2PNetworkInstance(config *NetworkConfig) *P2PNetwork {
if instance == nil {
once.Do(func() {
instance = &P2PNetwork{
restartCh: make(chan bool, 2),
online: false,
running: true,
msgMap: make(map[uint64]chan []byte),
limiter: newBandwidthLimiter(config.ShareBandwidth),
}
instance.msgMap[0] = make(chan []byte) // for gateway
if config != nil {
instance.config = *config
}
instance.init()
go instance.run()
})
}
return instance
}
func (pn *P2PNetwork) run() {
go pn.autorunApp()
heartbeatTimer := time.NewTicker(NetworkHeartbeatTime)
for pn.running {
select {
case <-heartbeatTimer.C: // TODO: deal with connect failed, no send hb
pn.write(MsgHeartbeat, 0, "")
case <-pn.restartCh:
pn.online = false
pn.wg.Wait() // wait read/write goroutine exited
time.Sleep(NetworkHeartbeatTime)
err := pn.init()
if err != nil {
gLog.Println(LevelERROR, "P2PNetwork init error:", err)
}
}
}
}
func (pn *P2PNetwork) Connect(timeout int) bool {
// waiting for login response
for i := 0; i < (timeout / 1000); i++ {
if pn.serverTs != 0 {
return true
}
time.Sleep(time.Second)
}
return false
}
func (pn *P2PNetwork) runAll() {
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
for _, config := range gConf.Apps {
if config.nextRetryTime.After(time.Now()) {
continue
}
if config.Enabled == 0 {
continue
}
if config.AppName == "" {
config.AppName = fmt.Sprintf("%s%d", config.Protocol, config.SrcPort)
}
appExist := false
appActive := false
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
appExist = true
if app.isActive() {
appActive = true
}
}
if appExist && appActive {
continue
}
if appExist && !appActive {
pn.DeleteApp(*config)
}
if config.retryNum > 0 {
gLog.Printf(LevelINFO, "detect app %s disconnect, reconnecting the %d times...", config.AppName, config.retryNum)
if time.Now().Add(-time.Minute * 15).After(config.retryTime) { // normal lasts 15min
config.retryNum = 0
}
}
config.retryNum++
config.retryTime = time.Now()
increase := math.Pow(1.3, float64(config.retryNum))
if increase > 900 {
increase = 900
}
config.nextRetryTime = time.Now().Add(time.Second * time.Duration(increase)) // exponential increase retry time. 1.3^x
go pn.AddApp(*config)
}
}
func (pn *P2PNetwork) autorunApp() {
gLog.Println(LevelINFO, "autorunApp start")
// TODO: use gConf to check reconnect
for pn.running {
time.Sleep(time.Second)
if !pn.online {
continue
}
pn.runAll()
time.Sleep(time.Second * 10)
}
gLog.Println(LevelINFO, "autorunApp end")
}
func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint64) (*P2PTunnel, uint64, string, error) {
gLog.Printf(LevelINFO, "addRelayTunnel to %s start", config.PeerNode)
defer gLog.Printf(LevelINFO, "addRelayTunnel to %s end", config.PeerNode)
pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode})
head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, time.Second*10)
if head == nil {
return nil, 0, "", errors.New("read MsgRelayNodeRsp error")
}
rsp := RelayNodeRsp{}
err := json.Unmarshal(body, &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
}
if rsp.RelayName == "" || rsp.RelayToken == 0 {
gLog.Printf(LevelERROR, "MsgRelayNodeReq error")
return nil, 0, "", errors.New("MsgRelayNodeReq error")
}
gLog.Printf(LevelINFO, "got relay node:%s", rsp.RelayName)
relayConfig := config
relayConfig.PeerNode = rsp.RelayName
relayConfig.peerToken = rsp.RelayToken
t, err := pn.addDirectTunnel(relayConfig, 0)
if err != nil {
gLog.Println(LevelERROR, "direct connect error:", err)
return nil, 0, "", err
}
// notify peer addRelayTunnel
req := AddRelayTunnelReq{
From: pn.config.Node,
RelayName: rsp.RelayName,
RelayToken: rsp.RelayToken,
AppID: appid,
AppKey: appkey,
}
gLog.Printf(LevelINFO, "push relay %s---------%s", config.PeerNode, rsp.RelayName)
pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req)
// wait relay ready
head, body = pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount) // TODO: const value
if head == nil {
gLog.Printf(LevelERROR, "read MsgPushAddRelayTunnelRsp error")
return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error")
}
rspID := TunnelMsg{}
err = json.Unmarshal(body, &rspID)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
}
return t, rspID.ID, rsp.Mode, err
}
func (pn *P2PNetwork) AddApp(config AppConfig) error {
gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d start", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d end", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
if !pn.online {
return errors.New("P2PNetwork offline")
}
// check if app already exist?
appExist := false
_, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
appExist = true
}
if appExist {
return errors.New("P2PApp already exist")
}
appID := rand.Uint64()
appKey := uint64(0)
var rtid uint64
relayNode := ""
relayMode := ""
peerNatType := NATUnknown
peerIP := ""
errMsg := ""
t, err := pn.addDirectTunnel(config, 0)
if t != nil {
peerNatType = t.config.peerNatType
peerIP = t.config.peerIP
}
if err != nil && err == ErrorHandshake {
gLog.Println(LevelERROR, "direct connect failed, try to relay")
appKey = rand.Uint64()
t, rtid, relayMode, err = pn.addRelayTunnel(config, appID, appKey)
if t != nil {
relayNode = t.config.PeerNode
}
}
if err != nil {
errMsg = err.Error()
}
req := ReportConnect{
Error: errMsg,
Protocol: config.Protocol,
SrcPort: config.SrcPort,
NatType: pn.config.natType,
PeerNode: config.PeerNode,
DstPort: config.DstPort,
DstHost: config.DstHost,
PeerNatType: peerNatType,
PeerIP: peerIP,
ShareBandwidth: pn.config.ShareBandwidth,
RelayNode: relayNode,
Version: OpenP2PVersion,
}
pn.write(MsgReport, MsgReportConnect, &req)
if err != nil {
return err
}
app := p2pApp{
id: appID,
key: appKey,
tunnel: t,
config: config,
rtid: rtid,
relayNode: relayNode,
relayMode: relayMode,
hbTime: time.Now()}
pn.apps.Store(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort), &app)
if err == nil {
go app.listen()
}
return err
}
func (pn *P2PNetwork) DeleteApp(config AppConfig) {
gLog.Printf(LevelINFO, "DeleteApp %s%d start", config.Protocol, config.SrcPort)
defer gLog.Printf(LevelINFO, "DeleteApp %s%d end", config.Protocol, config.SrcPort)
// close the apps of this config
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
gLog.Printf(LevelINFO, "app %s exist, delete it", fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
app.close()
pn.apps.Delete(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
}
}
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel, error) {
gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
isClient := false
// client side tid=0, assign random uint64
if tid == 0 {
tid = rand.Uint64()
isClient = true
}
exist := false
// find existing tunnel to peer
var t *P2PTunnel
pn.allTunnels.Range(func(id, i interface{}) bool {
t = i.(*P2PTunnel)
if t.config.PeerNode == config.PeerNode {
// server side force close existing tunnel
if !isClient {
t.close()
return false
}
// client side checking
gLog.Println(LevelINFO, "tunnel already exist ", config.PeerNode)
isActive := t.checkActive()
// inactive, close it
if !isActive {
gLog.Println(LevelINFO, "but it's not active, close it ", config.PeerNode)
t.close()
} else {
// active
exist = true
}
return false
}
return true
})
// create tunnel if not exist
if !exist {
t = &P2PTunnel{pn: pn,
config: config,
id: tid,
}
pn.msgMapMtx.Lock()
pn.msgMap[nodeNameToID(config.PeerNode)] = make(chan []byte, 50)
pn.msgMapMtx.Unlock()
t.init()
if isClient {
if err := t.connect(); err != nil {
gLog.Println(LevelERROR, "p2pTunnel connect error:", err)
return t, err
}
} else {
rsp := PushConnectRsp{
Error: 0,
Detail: "connect ok",
To: t.config.PeerNode,
From: pn.config.Node,
NatType: pn.config.natType,
FromIP: pn.config.publicIP,
ConeNatPort: t.coneNatPort,
ID: t.id}
t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
if err := t.listen(); err != nil {
gLog.Println(LevelERROR, "p2pTunnel listen error:", err)
return t, err
}
}
}
// store it when success
gLog.Printf(LevelDEBUG, "store tunnel %d", tid)
pn.allTunnels.Store(tid, t)
return t, nil
}
func (pn *P2PNetwork) init() error {
gLog.Println(LevelINFO, "init start")
var err error
for {
// detect nat type
pn.config.publicIP, pn.config.natType, err = getNATType(pn.config.ServerHost, pn.config.UDPPort1, pn.config.UDPPort2)
// TODO rm test s2s
if strings.Contains(pn.config.Node, "openp2pS2STest") {
pn.config.natType = NATSymmetric
}
if err != nil {
gLog.Println(LevelDEBUG, "detect NAT type error:", err)
break
}
gLog.Println(LevelDEBUG, "detect NAT type:", pn.config.natType, " publicIP:", pn.config.publicIP)
gatewayURL := fmt.Sprintf("%s:%d", pn.config.ServerHost, pn.config.ServerPort)
forwardPath := "/openp2p/v1/login"
config := tls.Config{InsecureSkipVerify: true} // 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
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: forwardPath}
q := u.Query()
q.Add("node", pn.config.Node)
q.Add("token", fmt.Sprintf("%d", pn.config.Token))
q.Add("version", OpenP2PVersion)
q.Add("nattype", fmt.Sprintf("%d", pn.config.natType))
q.Add("sharebandwidth", fmt.Sprintf("%d", pn.config.ShareBandwidth))
u.RawQuery = q.Encode()
var ws *websocket.Conn
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
break
}
pn.online = true
pn.conn = ws
localAddr := strings.Split(ws.LocalAddr().String(), ":")
if len(localAddr) == 2 {
pn.config.localIP = localAddr[0]
} else {
err = errors.New("get local ip failed")
break
}
go pn.readLoop()
pn.config.mac = getmac(pn.config.localIP)
pn.config.os = getOsName()
req := ReportBasic{
Mac: pn.config.mac,
LanIP: pn.config.localIP,
OS: pn.config.os,
Version: OpenP2PVersion,
}
rsp := netInfo()
gLog.Println(LevelDEBUG, "netinfo:", rsp)
if rsp != nil && rsp.Country != "" {
if len(rsp.IP) == net.IPv6len {
pn.config.ipv6 = rsp.IP.String()
req.IPv6 = rsp.IP.String()
}
req.NetInfo = *rsp
}
pn.write(MsgReport, MsgReportBasic, &req)
gLog.Println(LevelDEBUG, "P2PNetwork init ok")
break
}
if err != nil {
// init failed, retry
pn.restartCh <- true
gLog.Println(LevelERROR, "P2PNetwork init error:", err)
}
return err
}
func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
head := openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
if err != nil {
gLog.Println(LevelERROR, "handleMessage error:", err)
return
}
switch head.MainType {
case MsgLogin:
// gLog.Println(LevelINFO,string(msg))
rsp := LoginRsp{}
err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong login response:%s", err)
return
}
if rsp.Error != 0 {
gLog.Printf(LevelERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
pn.running = false
} else {
pn.serverTs = rsp.Ts
pn.config.Token = rsp.Token
pn.config.User = rsp.User
gConf.mtx.Lock()
gConf.Network.Token = rsp.Token
gConf.Network.User = rsp.User
gConf.mtx.Unlock()
gConf.save()
pn.localTs = time.Now().Unix()
gLog.Printf(LevelINFO, "login ok. user=%s,Server ts=%d, local ts=%d", rsp.User, rsp.Ts, pn.localTs)
}
case MsgHeartbeat:
gLog.Printf(LevelDEBUG, "P2PNetwork heartbeat ok")
case MsgPush:
handlePush(pn, head.SubType, msg)
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[0]
pn.msgMapMtx.Unlock()
ch <- msg
return
}
}
func (pn *P2PNetwork) readLoop() {
gLog.Printf(LevelDEBUG, "P2PNetwork readLoop start")
pn.wg.Add(1)
defer pn.wg.Done()
for pn.running {
pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second))
t, msg, err := pn.conn.ReadMessage()
if err != nil {
gLog.Printf(LevelERROR, "P2PNetwork read error:%s", err)
pn.conn.Close()
pn.restartCh <- true
break
}
pn.handleMessage(t, msg)
}
gLog.Printf(LevelDEBUG, "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(LevelERROR, "write msgType %d,%d error:%s", mainType, subType, err)
pn.conn.Close()
}
return err
}
func (pn *P2PNetwork) relay(to uint64, body []byte) error {
gLog.Printf(LevelDEBUG, "relay data to %d", to)
i, ok := pn.allTunnels.Load(to)
if !ok {
return nil
}
tunnel := i.(*P2PTunnel)
if tunnel.config.shareBandwidth > 0 {
pn.limiter.Add(len(body))
}
tunnel.conn.WriteBuffer(body)
return nil
}
func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error {
gLog.Printf(LevelDEBUG, "push msgType %d to %s", subType, to)
if !pn.online {
return errors.New("client offline")
}
pushHead := PushHeader{}
pushHead.From = nodeNameToID(pn.config.Node)
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(LevelERROR, "push to %s error:%s", to, err)
pn.conn.Close()
}
return err
}
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)
}
pn.msgMapMtx.Lock()
ch := pn.msgMap[nodeID]
pn.msgMapMtx.Unlock()
for {
select {
case <-time.After(timeout):
gLog.Printf(LevelERROR, "wait msg%d:%d timeout", mainType, subType)
return
case msg := <-ch:
head = &openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "read msg error:", err)
break
}
if head.MainType != mainType || head.SubType != subType {
continue
}
if mainType == MsgPush {
body = msg[openP2PHeaderSize+PushHeaderSize:]
} else {
body = msg[openP2PHeaderSize:]
}
return
}
}
}
func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
pn.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp)
if app.id != appID {
return true
}
app.updateHeartbeat()
return false
})
}
-420
View File
@@ -1,420 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net"
"sync"
"time"
)
type P2PTunnel struct {
pn *P2PNetwork
conn p2pConn
hbTime time.Time
hbMtx sync.Mutex
hbTimeRelay time.Time
config AppConfig
la *net.UDPAddr // local hole address
ra *net.UDPAddr // remote hole address
overlayConns sync.Map // both TCP and UDP
id uint64
running bool
runMtx sync.Mutex
isServer bool // 0:server 1:client
coneLocalPort int
coneNatPort int
}
func (t *P2PTunnel) init() {
t.running = true
t.hbMtx.Lock()
t.hbTime = time.Now()
t.hbMtx.Unlock()
t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time
localPort := int(rand.Uint32()%10000 + 50000)
if t.pn.config.natType == NATCone {
// prepare one random cone hole
_, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0)
t.coneLocalPort = localPort
t.coneNatPort = port1
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
} else {
t.coneLocalPort = localPort
t.coneNatPort = localPort // NATNONE or symmetric doesn't need coneNatPort
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
}
gLog.Printf(LevelDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
}
func (t *P2PTunnel) connect() error {
gLog.Printf(LevelDEBUG, "start p2pTunnel to %s ", t.config.PeerNode)
t.isServer = false
req := PushConnectReq{
Token: t.config.peerToken,
From: t.pn.config.Node,
FromToken: t.pn.config.Token,
FromIP: t.pn.config.publicIP,
ConeNatPort: t.coneNatPort,
NatType: t.pn.config.natType,
ID: t.id}
t.pn.push(t.config.PeerNode, MsgPushConnectReq, req)
head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, time.Second*10)
if head == nil {
return errors.New("connect error")
}
rsp := PushConnectRsp{}
err := json.Unmarshal(body, &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushConnectRsp:%s", err)
return err
}
// gLog.Println(LevelINFO, rsp)
if rsp.Error != 0 {
return errors.New(rsp.Detail)
}
t.config.peerNatType = int(rsp.NatType)
t.config.peerConeNatPort = rsp.ConeNatPort
t.config.peerIP = rsp.FromIP
err = t.handshake()
if err != nil {
gLog.Println(LevelERROR, "handshake error:", err)
err = ErrorHandshake
}
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 {
t.hbMtx.Lock()
defer t.hbMtx.Unlock()
return time.Now().Before(t.hbTime.Add(TunnelIdleTimeout))
}
func (t *P2PTunnel) checkActive() bool {
hbt := time.Now()
t.hbMtx.Lock()
if t.hbTime.Before(time.Now().Add(-TunnelHeartbeatTime)) {
t.hbMtx.Unlock()
return false
}
t.hbMtx.Unlock()
// hbtime within TunnelHeartbeatTime, check it 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)
}
return isActive
}
// call when user delete tunnel
func (t *P2PTunnel) close() {
t.setRun(false)
t.pn.allTunnels.Delete(t.id)
}
func (t *P2PTunnel) handshake() error {
if t.config.peerConeNatPort > 0 {
var err error
t.ra, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort))
if err != nil {
return err
}
}
gLog.Println(LevelDEBUG, "handshake to ", t.config.PeerNode)
var err error
// TODO: handle NATNone, nodes with public ip has no punching
if (t.pn.config.natType == NATCone && t.config.peerNatType == NATCone) || (t.pn.config.natType == NATNone || t.config.peerNatType == NATNone) {
err = handshakeC2C(t)
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATSymmetric {
err = ErrorS2S
t.close()
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATCone {
err = handshakeC2S(t)
} else if t.config.peerNatType == NATCone && t.pn.config.natType == NATSymmetric {
err = handshakeS2C(t)
} else {
return errors.New("unknown error")
}
if err != nil {
gLog.Println(LevelERROR, "punch handshake error:", err)
return err
}
gLog.Printf(LevelDEBUG, "handshake to %s ok", t.config.PeerNode)
err = t.run()
if err != nil {
gLog.Println(LevelERROR, err)
return err
}
return nil
}
func (t *P2PTunnel) run() error {
if t.isServer {
qConn, e := listenQuic(t.la.String(), TunnelIdleTimeout)
if e != nil {
gLog.Println(LevelINFO, "listen quic error:", e, ", retry...")
time.Sleep(time.Millisecond * 10)
qConn, e = listenQuic(t.la.String(), TunnelIdleTimeout)
if e != nil {
return fmt.Errorf("listen quic error:%s", e)
}
}
t.pn.push(t.config.PeerNode, MsgPushQuicConnect, nil)
e = qConn.Accept()
if e != nil {
qConn.CloseListener()
return fmt.Errorf("accept quic error:%s", e)
}
_, buff, err := qConn.ReadMessage()
if e != nil {
qConn.listener.Close()
return fmt.Errorf("read start msg error:%s", err)
}
if buff != nil {
gLog.Println(LevelDEBUG, string(buff))
}
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Println(LevelDEBUG, "quic connection ok")
t.conn = qConn
t.setRun(true)
go t.readLoop()
go t.writeLoop()
return nil
}
//else
conn, e := net.ListenUDP("udp", t.la)
if e != nil {
time.Sleep(time.Millisecond * 10)
conn, e = net.ListenUDP("udp", t.la)
if e != nil {
return fmt.Errorf("quic listen error:%s", e)
}
}
t.pn.read(t.config.PeerNode, MsgPush, MsgPushQuicConnect, time.Second*5)
gLog.Println(LevelDEBUG, "quic dial to ", t.ra.String())
qConn, e := dialQuic(conn, t.ra, TunnelIdleTimeout)
if e != nil {
return fmt.Errorf("quic dial to %s error:%s", t.ra.String(), e)
}
handshakeBegin := time.Now()
qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
_, buff, err := qConn.ReadMessage()
if e != nil {
qConn.listener.Close()
return fmt.Errorf("read MsgTunnelHandshake error:%s", err)
}
if buff != nil {
gLog.Println(LevelDEBUG, string(buff))
}
gLog.Println(LevelINFO, "rtt=", time.Since(handshakeBegin))
gLog.Println(LevelDEBUG, "quic connection ok")
t.conn = qConn
t.setRun(true)
go t.readLoop()
go t.writeLoop()
return nil
}
func (t *P2PTunnel) readLoop() {
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
gLog.Printf(LevelDEBUG, "%d tunnel readloop start", t.id)
for t.isRuning() {
t.conn.SetReadDeadline(time.Now().Add(TunnelIdleTimeout))
head, body, err := t.conn.ReadMessage()
if err != nil {
if t.isRuning() {
gLog.Printf(LevelERROR, "%d tunnel read error:%s", t.id, err)
}
break
}
if head.MainType != MsgP2P {
continue
}
switch head.SubType {
case MsgTunnelHeartbeat:
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil)
gLog.Printf(LevelDEBUG, "%d read tunnel heartbeat", t.id)
case MsgTunnelHeartbeatAck:
t.hbMtx.Lock()
t.hbTime = time.Now()
t.hbMtx.Unlock()
gLog.Printf(LevelDEBUG, "%d read tunnel heartbeat ack", t.id)
case MsgOverlayData:
if len(body) < overlayHeaderSize {
continue
}
overlayID := binary.LittleEndian.Uint64(body[:8])
gLog.Printf(LevelDEBUG, "%d tunnel read overlay data %d", t.id, overlayID)
s, ok := t.overlayConns.Load(overlayID)
if !ok {
// debug level, when overlay connection closed, always has some packet not found tunnel
gLog.Printf(LevelDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID)
continue
}
overlayConn, ok := s.(*overlayTCP)
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(LevelERROR, "overlay write error:", err)
}
case MsgRelayData:
gLog.Printf(LevelDEBUG, "got relay data datalen=%d", head.DataLen)
if len(body) < 8 {
continue
}
tunnelID := binary.LittleEndian.Uint64(body[:8])
t.pn.relay(tunnelID, body[8:])
case MsgRelayHeartbeat:
req := RelayHeartbeat{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayHeartbeat:%s", err)
continue
}
gLog.Printf(LevelDEBUG, "got MsgRelayHeartbeat from %d:%d", req.RelayTunnelID, req.AppID)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, req.RelayTunnelID)
msg, _ := newMessage(MsgP2P, MsgRelayHeartbeatAck, &req)
msgWithHead := append(relayHead.Bytes(), msg...)
t.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
case MsgRelayHeartbeatAck:
req := RelayHeartbeat{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayHeartbeat:%s", err)
continue
}
gLog.Printf(LevelDEBUG, "got MsgRelayHeartbeatAck to %d", req.AppID)
t.pn.updateAppHeartbeat(req.AppID)
case MsgOverlayConnectReq:
req := OverlayConnectReq{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgOverlayConnectReq:%s", err)
continue
}
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
if req.Token != t.pn.config.Token {
gLog.Println(LevelERROR, "Access Denied:", req.Token)
continue
}
overlayID := req.ID
gLog.Printf(LevelDEBUG, "App:%d overlayID:%d connect %+v", req.AppID, overlayID, req)
var conn net.Conn
if req.Protocol == "udp" {
conn, err = net.DialTimeout("udp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), time.Second*5)
} else {
conn, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), time.Second*5)
}
if err != nil {
gLog.Println(LevelERROR, err)
continue
}
otcp := overlayTCP{
tunnel: t,
conn: conn,
id: overlayID,
isClient: false,
rtid: req.RelayTunnelID,
appID: req.AppID,
appKey: GetKey(req.AppID),
}
// calc key bytes for encrypt
if otcp.appKey != 0 {
encryptKey := make([]byte, 16)
binary.LittleEndian.PutUint64(encryptKey, otcp.appKey)
binary.LittleEndian.PutUint64(encryptKey[8:], otcp.appKey)
otcp.appKeyBytes = encryptKey
}
t.overlayConns.Store(otcp.id, &otcp)
go otcp.run()
case MsgOverlayDisconnectReq:
req := OverlayDisconnectReq{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong OverlayDisconnectRequest:%s", err)
continue
}
overlayID := req.ID
gLog.Printf(LevelDEBUG, "%d disconnect overlay connection %d", t.id, overlayID)
i, ok := t.overlayConns.Load(overlayID)
if ok {
otcp := i.(*overlayTCP)
otcp.running = false
}
default:
}
}
t.setRun(false)
t.conn.Close()
gLog.Printf(LevelDEBUG, "%d tunnel readloop end", t.id)
}
func (t *P2PTunnel) writeLoop() {
tc := time.NewTicker(TunnelHeartbeatTime)
defer tc.Stop()
defer gLog.Printf(LevelDEBUG, "%d tunnel writeloop end", t.id)
for t.isRuning() {
select {
case <-tc.C:
// tunnel send
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
if err != nil {
gLog.Printf(LevelERROR, "%d write tunnel heartbeat error %s", t.id, err)
t.setRun(false)
return
}
gLog.Printf(LevelDEBUG, "%d write tunnel heartbeat ok", t.id)
}
}
}
func (t *P2PTunnel) listen() error {
gLog.Printf(LevelDEBUG, "p2ptunnel wait for connecting")
t.isServer = true
return t.handshake()
}
func (t *P2PTunnel) closeOverlayConns(appID uint64) {
t.overlayConns.Range(func(_, i interface{}) bool {
otcp := i.(*overlayTCP)
if otcp.appID == appID {
otcp.conn.Close()
}
return true
})
}
-346
View File
@@ -1,346 +0,0 @@
package main
import (
"bytes"
"encoding/binary"
"encoding/json"
"hash/crc64"
"math/big"
"net"
"time"
)
const OpenP2PVersion = "1.2.0"
const ProducnName string = "openp2p"
type openP2PHeader struct {
DataLen uint32
MainType uint16
SubType uint16
}
var openP2PHeaderSize = binary.Size(openP2PHeader{})
type PushHeader struct {
From uint64
To uint64
}
var PushHeaderSize = binary.Size(PushHeader{})
type overlayHeader struct {
id uint64
}
var overlayHeaderSize = binary.Size(overlayHeader{})
func decodeHeader(data []byte) (*openP2PHeader, error) {
head := openP2PHeader{}
rd := bytes.NewReader(data)
err := binary.Read(rd, binary.LittleEndian, &head)
if err != nil {
return nil, err
}
return &head, nil
}
func encodeHeader(mainType uint16, subType uint16, len uint32) []byte {
head := openP2PHeader{
len,
mainType,
subType,
}
headBuf := new(bytes.Buffer)
err := binary.Write(headBuf, binary.LittleEndian, head)
if err != nil {
return []byte("")
}
return headBuf.Bytes()
}
// Message type
const (
MsgLogin = 0
MsgHeartbeat = 1
MsgNATDetect = 2
MsgPush = 3
MsgP2P = 4
MsgRelay = 5
MsgReport = 6
)
const (
MsgPushRsp = 0
MsgPushConnectReq = 1
MsgPushConnectRsp = 2
MsgPushHandshakeStart = 3
MsgPushAddRelayTunnelReq = 4
MsgPushAddRelayTunnelRsp = 5
MsgPushUpdate = 6
MsgPushReportApps = 7
MsgPushQuicConnect = 8
MsgPushEditApp = 9
MsgPushSwitchApp = 10
MsgPushRestart = 11
MsgPushEditNode = 12
)
// MsgP2P sub type message
const (
MsgPunchHandshake = iota
MsgPunchHandshakeAck
MsgTunnelHandshake
MsgTunnelHandshakeAck
MsgTunnelHeartbeat
MsgTunnelHeartbeatAck
MsgOverlayConnectReq
MsgOverlayConnectRsp
MsgOverlayDisconnectReq
MsgOverlayData
MsgRelayData
MsgRelayHeartbeat
MsgRelayHeartbeatAck
)
// MsgRelay sub type message
const (
MsgRelayNodeReq = iota
MsgRelayNodeRsp
)
// MsgReport sub type message
const (
MsgReportBasic = iota
MsgReportQuery
MsgReportConnect
MsgReportApps
)
const (
ReadBuffLen = 1024
NetworkHeartbeatTime = time.Second * 30 // TODO: server no response hb, save flow
TunnelHeartbeatTime = time.Second * 15
TunnelIdleTimeout = time.Minute
SymmetricHandshakeNum = 800 // 0.992379
// SymmetricHandshakeNum = 1000 // 0.999510
SymmetricHandshakeInterval = time.Millisecond
SymmetricHandshakeAckTimeout = time.Second * 11
PeerAddRelayTimeount = time.Second * 20
CheckActiveTimeout = time.Second * 5
PaddingSize = 16
AESKeySize = 16
MaxRetry = 10
RetryInterval = time.Second * 30
PublicIPEchoTimeout = time.Second * 3
NatTestTimeout = time.Second * 10
ClientAPITimeout = time.Second * 10
)
// NATNone has public ip
const (
NATNone = 0
NATCone = 1
NATSymmetric = 2
NATUnknown = 314
)
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
data, err := json.Marshal(packet)
if err != nil {
return nil, err
}
// gLog.Println(LevelINFO,"write packet:", string(data))
head := openP2PHeader{
uint32(len(data)),
mainType,
subType,
}
headBuf := new(bytes.Buffer)
err = binary.Write(headBuf, binary.LittleEndian, head)
if err != nil {
return nil, err
}
writeBytes := append(headBuf.Bytes(), data...)
return writeBytes, nil
}
func nodeNameToID(name string) uint64 {
return crc64.Checksum([]byte(name), crc64.MakeTable(crc64.ISO))
}
type PushConnectReq struct {
From string `json:"from,omitempty"`
FromToken uint64 `json:"fromToken,omitempty"` //my token
Token uint64 `json:"token,omitempty"` // totp token
ConeNatPort int `json:"coneNatPort,omitempty"`
NatType int `json:"natType,omitempty"`
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
}
type PushConnectRsp struct {
Error int `json:"error,omitempty"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Detail string `json:"detail,omitempty"`
NatType int `json:"natType,omitempty"`
ConeNatPort int `json:"coneNatPort,omitempty"`
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
}
type PushRsp struct {
Error int `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
}
type LoginRsp struct {
Error int `json:"error,omitempty"`
Detail string `json:"detail,omitempty"`
User string `json:"user,omitempty"`
Token uint64 `json:"token,omitempty"`
Ts int64 `json:"ts,omitempty"`
}
type NatDetectReq struct {
SrcPort int `json:"srcPort,omitempty"`
EchoPort int `json:"echoPort,omitempty"`
}
type NatDetectRsp struct {
IP string `json:"IP,omitempty"`
Port int `json:"port,omitempty"`
IsPublicIP int `json:"isPublicIP,omitempty"`
}
type P2PHandshakeReq struct {
ID uint64 `json:"id,omitempty"`
}
type OverlayConnectReq struct {
ID uint64 `json:"id,omitempty"`
Token uint64 `json:"token,omitempty"` // not totp token
DstIP string `json:"dstIP,omitempty"`
DstPort int `json:"dstPort,omitempty"`
Protocol string `json:"protocol,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // if not 0 relay
AppID uint64 `json:"appID,omitempty"`
}
type OverlayDisconnectReq struct {
ID uint64 `json:"id,omitempty"`
}
type TunnelMsg struct {
ID uint64 `json:"id,omitempty"`
}
type RelayNodeReq struct {
PeerNode string `json:"peerNode,omitempty"`
}
type RelayNodeRsp struct {
Mode string `json:"mode,omitempty"` // private,public
RelayName string `json:"relayName,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
}
type AddRelayTunnelReq struct {
From string `json:"from,omitempty"`
RelayName string `json:"relayName,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
AppID uint64 `json:"appID,omitempty"`
AppKey uint64 `json:"appKey,omitempty"`
}
type RelayHeartbeat struct {
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
AppID uint64 `json:"appID,omitempty"`
}
type ReportBasic struct {
OS string `json:"os,omitempty"`
Mac string `json:"mac,omitempty"`
LanIP string `json:"lanIP,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"`
}
type ReportConnect struct {
Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"`
SrcPort int `json:"srcPort,omitempty"`
NatType int `json:"natType,omitempty"`
PeerNode string `json:"peerNode,omitempty"`
DstPort int `json:"dstPort,omitempty"`
DstHost string `json:"dsdtHost,omitempty"`
PeerUser string `json:"peerUser,omitempty"`
PeerNatType int `json:"peerNatType,omitempty"`
PeerIP string `json:"peerIP,omitempty"`
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,omitempty"`
Version string `json:"version,omitempty"`
}
type AppInfo struct {
AppName string `json:"appName,omitempty"`
Error string `json:"error,omitempty"`
Protocol string `json:"protocol,omitempty"`
SrcPort int `json:"srcPort,omitempty"`
Protocol0 string `json:"protocol0,omitempty"`
SrcPort0 int `json:"srcPort0,omitempty"`
NatType int `json:"natType,omitempty"`
PeerNode string `json:"peerNode,omitempty"`
DstPort int `json:"dstPort,omitempty"`
DstHost string `json:"dstHost,omitempty"`
PeerUser string `json:"peerUser,omitempty"`
PeerNatType int `json:"peerNatType,omitempty"`
PeerIP string `json:"peerIP,omitempty"`
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,omitempty"`
RelayMode string `json:"relayMode,omitempty"`
Version string `json:"version,omitempty"`
RetryTime string `json:"retryTime,omitempty"`
IsActive int `json:"isActive,omitempty"`
Enabled int `json:"enabled,omitempty"`
}
type ReportApps struct {
Apps []AppInfo
}
type UpdateInfo struct {
Error int `json:"error,omitempty"`
ErrorDetail string `json:"errorDetail,omitempty"`
Url string `json:"url,omitempty"`
}
type NetInfo struct {
IP net.IP `json:"ip"`
IPDecimal *big.Int `json:"ip_decimal"`
Country string `json:"country,omitempty"`
CountryISO string `json:"country_iso,omitempty"`
CountryEU *bool `json:"country_eu,omitempty"`
RegionName string `json:"region_name,omitempty"`
RegionCode string `json:"region_code,omitempty"`
MetroCode uint `json:"metro_code,omitempty"`
PostalCode string `json:"zip_code,omitempty"`
City string `json:"city,omitempty"`
Latitude float64 `json:"latitude,omitempty"`
Longitude float64 `json:"longitude,omitempty"`
Timezone string `json:"time_zone,omitempty"`
ASN string `json:"asn,omitempty"`
ASNOrg string `json:"asn_org,omitempty"`
Hostname string `json:"hostname,omitempty"`
}
type ProfileInfo struct {
User string `json:"user,omitempty"`
Password string `json:"password,omitempty"`
Email string `json:"email,omitempty"`
Phone string `json:"phone,omitempty"`
Token string `json:"token,omitempty"`
Addtime string `json:"addtime,omitempty"`
}
type EditNode struct {
NewName string `json:"newName,omitempty"`
Bandwidth int `json:"bandwidth,omitempty"`
}
-32
View File
@@ -1,32 +0,0 @@
// Time-based One-time Password
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
)
const TOTPStep = 30 // 30s
func GenTOTP(token uint64, ts int64) uint64 {
step := ts / TOTPStep
tbuff := make([]byte, 8)
binary.LittleEndian.PutUint64(tbuff, token)
mac := hmac.New(sha256.New, tbuff)
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(step))
mac.Write(b)
num := binary.LittleEndian.Uint64(mac.Sum(nil)[:8])
// fmt.Printf("%x\n", mac.Sum(nil))
return num
}
func VerifyTOTP(code uint64, token uint64, ts int64) bool {
if code == 0 {
return false
}
if code == GenTOTP(token, ts) || code == GenTOTP(token, ts-TOTPStep) || code == GenTOTP(token, ts+TOTPStep) {
return true
}
return false
}
-36
View File
@@ -1,36 +0,0 @@
// Time-based One-time Password
package main
import (
"testing"
"time"
)
func TestTOTP(t *testing.T) {
for i := 0; i < 20; i++ {
ts := time.Now().Unix()
code := GenTOTP(13666999958022769123, ts)
t.Log(code)
if !VerifyTOTP(code, 13666999958022769123, ts) {
t.Error("TOTP error")
}
if !VerifyTOTP(code, 13666999958022769123, ts-10) {
t.Error("TOTP error")
}
if !VerifyTOTP(code, 13666999958022769123, ts+10) {
t.Error("TOTP error")
}
if VerifyTOTP(code, 13666999958022769123, ts+60) {
t.Error("TOTP error")
}
if VerifyTOTP(code, 13666999958022769124, ts+1) {
t.Error("TOTP error")
}
if VerifyTOTP(code, 13666999958022769125, ts+1) {
t.Error("TOTP error")
}
time.Sleep(time.Second)
t.Log("round", i, " ", ts, " test ok")
}
}
-193
View File
@@ -1,193 +0,0 @@
package main
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"runtime"
"time"
)
func update() {
gLog.Println(LevelINFO, "update start")
defer gLog.Println(LevelINFO, "update end")
c := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
Timeout: time.Second * 30,
}
goos := runtime.GOOS
goarch := runtime.GOARCH
rsp, err := c.Get(fmt.Sprintf("https://openp2p.cn:27183/api/v1/update?fromver=%s&os=%s&arch=%s", OpenP2PVersion, goos, goarch))
if err != nil {
gLog.Println(LevelERROR, "update:query update list failed:", err)
return
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
gLog.Println(LevelERROR, "get update info error:", rsp.Status)
return
}
rspBuf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
gLog.Println(LevelERROR, "update:read update list failed:", err)
return
}
updateInfo := UpdateInfo{}
err = json.Unmarshal(rspBuf, &updateInfo)
if err != nil {
gLog.Println(LevelERROR, rspBuf, " update info decode error:", err)
return
}
if updateInfo.Error != 0 {
gLog.Println(LevelERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail)
return
}
err = updateFile(updateInfo.Url, "", "openp2p")
if err != nil {
gLog.Println(LevelERROR, "update: download failed:", err)
return
}
}
// todo rollback on error
func updateFile(url string, checksum string, dst string) error {
gLog.Println(LevelINFO, "download ", url)
tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp"
output, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776)
if err != nil {
gLog.Printf(LevelERROR, "OpenFile %s error:%s", tmpFile, err)
return err
}
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
response, err := client.Get(url)
if err != nil {
gLog.Printf(LevelERROR, "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(LevelERROR, "io.Copy error:%s", err)
output.Close()
return err
}
output.Sync()
output.Close()
gLog.Println(LevelINFO, "download ", url, " ok")
gLog.Printf(LevelINFO, "size: %d bytes", n)
err = os.Rename(os.Args[0], os.Args[0]+"0")
if err != nil && os.IsExist(err) {
gLog.Printf(LevelINFO, " rename %s error:%s", os.Args[0], err)
}
// extract
gLog.Println(LevelINFO, "extract files")
err = extract(filepath.Dir(os.Args[0]), tmpFile)
if err != nil {
gLog.Printf(LevelERROR, "extract error:%s. revert rename", err)
os.Rename(os.Args[0]+"0", 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
}
if err != nil {
return err
}
defer outFile.Close()
if _, err := io.Copy(outFile, tarReader); err != nil {
return err
}
default:
return err
}
}
return nil
}