Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f015b828fc | ||
|
|
df1e16e708 | ||
|
|
c68094cc12 | ||
|
|
a0df0b1e95 | ||
|
|
9c3d557f5d | ||
|
|
2dea3a718d | ||
|
|
9dda148232 | ||
|
|
e7b696c474 | ||
|
|
7e57237ec9 | ||
|
|
52dfe5c938 | ||
|
|
b72ede9a6a | ||
|
|
b39fab2188 | ||
|
|
9b0525294a | ||
|
|
276a4433f1 | ||
|
|
e21adebc26 | ||
|
|
b2a7619bd6 | ||
|
|
fe4022ba6c | ||
|
|
82c74b4f85 | ||
|
|
0af65b7204 | ||
|
|
46b4f78010 | ||
|
|
8ebdf3341e | ||
|
|
b667e5b766 | ||
|
|
cd415e7bf4 | ||
|
|
67e3a8915a | ||
|
|
791d910314 | ||
|
|
c3a43be3cc | ||
|
|
c8b8bf05a5 | ||
|
|
8311341960 | ||
|
|
d5c098ca74 | ||
|
|
af82fc6e36 | ||
|
|
2af77668fe | ||
|
|
215feb8721 | ||
|
|
4a115fb3fa | ||
|
|
0fb3bc4e26 | ||
|
|
df23b30d2b | ||
|
|
f9b5073e0d | ||
|
|
9ea467c7b3 | ||
|
|
c0bad61eb6 | ||
|
|
bb32133038 | ||
|
|
532d3667ce | ||
|
|
b2d35c6f97 | ||
|
|
037f3cc34e | ||
|
|
6b8d3f7d47 | ||
|
|
26e0fdf605 | ||
|
|
3653ec19cd | ||
|
|
c733a2a4a1 | ||
|
|
b54fa2c6be | ||
|
|
133fe046f8 | ||
|
|
95b46f51d0 | ||
|
|
7686af39e0 | ||
|
|
16b937ebd7 | ||
|
|
ac454ec694 | ||
|
|
029d69869f | ||
|
|
a528441342 | ||
|
|
2d6521be43 | ||
|
|
2223634c83 | ||
|
|
6c1551d951 | ||
|
|
dd3d87c3d2 |
20
.gitignore
vendored
@@ -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
Makefile
Normal 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
|
||||
157
README-ZH.md
@@ -1,65 +1,81 @@
|
||||
[English](/README.md)|中文
|
||||
# [English](/README.md)|中文
|
||||
网站: [openp2p.cn](https://openp2p.cn)
|
||||
## OpenP2P是什么
|
||||
它是一个开源、免费、轻量级的P2P共享网络。任何设备接入OpenP2P,随时随地访问它们。
|
||||
它是一个开源、免费、轻量级的P2P共享网络。你的设备将组成一个私有P2P网络,里面的设备可以直接访问其它成员,或者通过其它成员转发数据间接访问。如果私有网络无法完成通信,将会到公有P2P网络寻找共享节点协助通信。
|
||||
相比BT网络用来共享文件,OpenP2P网络用来共享带宽。
|
||||
我们的目标是:充分利用带宽,利用共享节点转发数据,建设一个远程连接的通用基础设施。
|
||||
|
||||
## 为什么选择OpenP2P
|
||||
### 免费
|
||||
完全免费,满足大部分用户的核心白票需求。不像其它类似的产品,我们不需要有公网IP的服务器,不需要花钱买服务。了解它原理即可理解为什么能做到免费。
|
||||
### 安全
|
||||
代码开源,接受各位大佬检验。下面详细展开
|
||||
### 轻量
|
||||
文件大小2MB+,运行内存2MB+;全部在应用层实现,没有虚拟网卡,没有内核程序
|
||||
### 跨平台
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
|
||||
### 高效
|
||||
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
|
||||
### 1. 免费
|
||||
完全免费,满足大部分用户的核心白票需求。不像其它类似的产品,OpenP2P不需要有公网IP的服务器,不需要花钱买服务。了解它原理即可理解为什么能做到免费。
|
||||
### 2. 共享
|
||||
你的设备会形成一个私有P2P网络,它们之间共享带宽,提供网络数据转发服务。
|
||||
当你的私有P2P网络下没有可以提供转发服务的节点时,会尝试在公共P2P网络寻找转发节点。
|
||||
默认会开启共享限速10mbps,只有你用户下提供了共享节点才能使用别人的共享节点。这非常公平,也是这个项目的初衷。
|
||||
我们建议你在带宽足够的地方(比如办公室,家里的百兆光纤)加入共享网络。
|
||||
如果你不想共享任何节点,或设置共享带宽,请查看[详细使用说明](/USAGE-ZH.md)
|
||||
### 3. 安全
|
||||
代码开源,P2P隧道使用TLS1.3+AES双重加密,共享节点临时授权使用TOTP一次性密码
|
||||
|
||||
### 二次开发
|
||||
[查看详细](#安全性)
|
||||
### 4. 轻量
|
||||
文件大小2MB+,运行内存2MB+;它可以仅跑在应用层,或者配合wintun驱动使用组网功能
|
||||
### 5. 跨平台
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le
|
||||
### 6. 高效
|
||||
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),UDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
|
||||
|
||||
### 7. 二次开发
|
||||
基于OpenP2P只需数行代码,就能让原来只能局域网通信的程序,变成任何内网都能通信
|
||||
|
||||
## 快速入门
|
||||
仅需简单4步就能用起来。
|
||||
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
||||
(另外一个快速入门视频 <https://www.bilibili.com/video/BV1Et4y1P7bF/>)
|
||||
### 1.注册
|
||||
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
||||
|
||||
> :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p"
|
||||

|
||||
### 2.安装
|
||||
分别在本地和远程电脑下载后双击运行,一键安装
|
||||
|
||||
|
||||
以一个最常见的例子说明OpenP2P如何使用:远程办公,在家里连入办公室Windows电脑。
|
||||
相信很多人在疫情下远程办公是刚需。
|
||||
1. 先确认办公室电脑已开启远程桌面功能(如何开启参考官方说明https://docs.microsoft.com/zh-cn/windows-server/remote/remote-desktop-services/clients/remote-desktop-allow-access)
|
||||
2. 在办公室下载最新的[OpenP2P](https://gitee.com/tenderiron/openp2p/releases/),解压出来,在命令行执行
|
||||
```
|
||||
openp2p.exe install -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
```
|
||||

|
||||
|
||||
> :warning: **切记将标记大写的参数改成自己的**
|
||||
Windows默认会阻止没有花钱买它家证书签名过的程序,选择“仍要运行”即可。
|
||||
|
||||

|
||||
3. 在家里下载最新的[OpenP2P](https://gitee.com/tenderiron/openp2p/releases/),解压出来,在命令行执行
|
||||
```
|
||||
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 --peernode OFFICEPC1 --dstip 127.0.0.1 --dstport 3389 --srcport 23389 --protocol tcp
|
||||
```
|
||||
> :warning: **切记将标记大写的参数改成自己的**
|
||||

|
||||
|
||||

|
||||
### 3.新建P2P应用
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 4.使用P2P应用
|
||||
在“MyHomePC”设备上能看到刚才创建的P2P应用,连接下图显示的“本地监听端口”即可。
|
||||
|
||||

|
||||
|
||||
在家里Windows电脑,按Win+R输入mstsc打开远程桌面,输入127.0.0.1:23389 /admin
|
||||
|
||||

|
||||

|
||||
`LISTEN ON PORT 23389 START` 看到这行日志表示P2PApp建立成功,监听23389端口。只需连接本机的127.0.0.1:23389就相当于连接公司Windows电脑的3389端口。
|
||||
|
||||
4. 在家里Windows电脑,按Win+R输入mstsc打开远程桌面,输入127.0.0.1:23389 /admin
|
||||

|
||||
|
||||

|
||||
|
||||
## [详细使用说明](/USAGE-ZH.md)
|
||||
## 详细使用说明
|
||||
[这里](/USAGE-ZH.md)介绍如何手动运行
|
||||
|
||||
## 典型应用场景
|
||||
特别适合大流量的内网访问
|
||||
### 远程办公
|
||||
Windows MSTSC、VNC等远程桌面,SSH,内网各种ERP系统
|
||||
### 远程访问NAS
|
||||
管理大量视频、图片
|
||||
### 远程监控摄像头
|
||||
### 远程刷机
|
||||
### 远程数据备份
|
||||
>* 远程办公: Windows MSTSC、VNC等远程桌面,SSH,内网各种ERP系统
|
||||
>* 远程访问内网ERP系统
|
||||
>* 远程访问NAS: 管理大量视频、图片
|
||||
>* 远程监控摄像头
|
||||
>* 远程刷机
|
||||
>* 远程数据备份
|
||||
---
|
||||
## 概要设计
|
||||
### 原型
|
||||
@@ -69,44 +85,58 @@ Windows MSTSC、VNC等远程桌面,SSH,内网各种ERP系统
|
||||
### P2PApp
|
||||
它是项目里最重要的概念,一个P2PApp就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理P2PApp
|
||||

|
||||
## 共享
|
||||
默认会开启共享限速10mbps,只有你用户下提供了共享节点才能使用别人的共享节点。这非常公平,也是这个项目的初衷。
|
||||
我们建议你在带宽足够的地方(比如办公室,家里的百兆光纤)加入共享网络。
|
||||
如果你仍然不想共享任何节点,请查看运行参数
|
||||
## 安全性
|
||||
加入OpenP2P共享网络的节点,只能凭授权访问。共享节点只会中转数据,别人无法访问内网任何资源。
|
||||
### TLS1.3+AES
|
||||
### 1. TLS1.3+AES
|
||||
两个节点间通信数据走业界最安全的TLS1.3通道。通信内容还会使用AES加密,双重安全,密钥是通过服务端作换。有效阻止中间人攻击
|
||||
### 共享的中转节点是否会获得我的数据
|
||||
### 2. 共享的中转节点是否会获得我的数据
|
||||
没错,中转节点天然就是一个中间人,所以才加上AES加密通信内容保证安全。中转节点是无法获取明文的
|
||||
|
||||
### 中转节点是如何校验权限的
|
||||
### 3. 中转节点是如何校验权限的
|
||||
服务端有个调度模型,根据带宽、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. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。
|
||||
@@ -114,8 +144,7 @@ go build
|
||||
|
||||
## 参与贡献
|
||||
TODO或ISSUE里如果有你擅长的领域,或者你有特别好的主意,可以加入OpenP2P项目,贡献你的代码。待项目茁壮成长后,你们就是知名开源项目的主要代码贡献者,岂不快哉。
|
||||
## 商业合作
|
||||
它是一个中国人发起的项目,更懂国内网络环境,更懂用户需求,更好的企业级支持
|
||||
|
||||
## 技术交流
|
||||
QQ群:16947733
|
||||
邮箱:openp2p.cn@gmail.com tenderiron@139.com
|
||||
|
||||
174
README.md
@@ -1,84 +1,85 @@
|
||||
English|[中文](/README-ZH.md)
|
||||
# English|[中文](/README-ZH.md)
|
||||
Website: [openp2p.cn](https://openp2p.cn)
|
||||
## What is OpenP2P
|
||||
It is an open source, free, and lightweight P2P sharing network. As long as any device joins in, you can access them anywhere.
|
||||
It is an open source, free, and lightweight P2P sharing network. Your devices will form a private P2P network, in which devices can directly access other members, or indirectly access through other members forwarding data.
|
||||
If the private network cannot complete the communication, it will go to the public P2P network to find a shared node to assist in the communication. Compared with the BT network used to share files, the OpenP2P network is used to share bandwidth.
|
||||
Our goal is to make full use of bandwidth, use shared nodes to relay data, and build a common infrastructure for remote connections.
|
||||
## Why OpenP2P
|
||||
### Free
|
||||
Totaly free, fullfills most of users(especially free-rider). Unlike other similar products, we don't need a server with public IP, and don't need to pay for services.By understanding its principle, you can understand why it can be done for free.
|
||||
### 1. Free
|
||||
Totaly free, fullfills most of users(especially free-rider). Unlike other similar products, OpenP2p doesn't need a server with public IP, and doesn't need to pay for services.By understanding its principle, you can understand why it can be done for free.
|
||||
### 2. Share
|
||||
Your devices will form a private P2P network, share bandwidth between them, and provide network data forwarding services.
|
||||
When there is no node that can provide forwarding services in your private P2P network, you will try to find forwarding nodes in the public P2P network.
|
||||
10mbps is its default setting of share speed limit. Only when you have shared their nodes, you are allowed to use others' shared nodes. This is very fair, and it is also the original intention of this project.
|
||||
We recommend that you join a shared network in a place with sufficient bandwidth (such as an office or home with 100M optical fiber).
|
||||
If you are not willing to contribute any node to the OpenP2P share network, please refer to the [usage](/USAGE.md) for your own setting.
|
||||
### 3. Safe
|
||||
The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and the shared node temporarily authorizes the use of the TOTP one-time password
|
||||
|
||||
[details](#Safety)
|
||||
|
||||
### Safe
|
||||
Open source, trustable(see details below)
|
||||
### 4. Lightweight
|
||||
2MB+ filesize, 2MB+ memory. It could only runs at application layer, or uses wintun driver for SDWAN.
|
||||
|
||||
### Lightweight
|
||||
2MB+ filesize, 2MB+ memory. It runs at appllication layer, no vitrual NIC, no kernel driver.
|
||||
### 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、s390x、ppc64le.
|
||||
|
||||
### 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.
|
||||
### 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),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.
|
||||
|
||||
### 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.
|
||||
|
||||
### Integration
|
||||
### 7. Integration
|
||||
Your applicaiton can call OpenP2P with a few code to make any internal networks communicate with each other.
|
||||
|
||||
## Get Started
|
||||
A common scenario to introduce OpenP2P: remote work. At home connects to office's Linux PC .
|
||||
Under the outbreak of covid-19 pandemic, surely remote work becomes a fundamental demand.
|
||||
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
|
||||
|
||||
> :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p"
|
||||
|
||||

|
||||
### 2.Install
|
||||
Download on local and remote computers and double-click to run, one-click installation
|
||||
|
||||
1. Make sure your office device(Linux) has opened the access of ssh.
|
||||
```
|
||||
netstat -nl | grep 22
|
||||
```
|
||||
Output sample
|
||||

|
||||

|
||||
|
||||
2. Download the latest version of [OpenP2P](https://github.com/openp2p-cn/openp2p/releases),unzip the downloaded package, and execute below command line.
|
||||
```
|
||||
tar xvf openp2p0.95.3.linux-amd64.tar.gz
|
||||
./openp2p install -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
```
|
||||
By default, Windows will block programs that have not been signed by the Microsoft's certificate, and you can select "Run anyway".
|
||||
|
||||
> :warning: **Must change the parameters marked in uppercase to your own**
|
||||

|
||||
|
||||
Output sample
|
||||

|
||||
|
||||
3. Download the same package of [OpenP2P](https://github.com/openp2p-cn/openp2p/releases) on your home device,unzip and execute below command line.
|
||||
```
|
||||
openp2p.exe -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 --peernode OFFICEPC1 --dstip 127.0.0.1 --dstport 22 --srcport 22022 --protocol tcp
|
||||
```
|
||||

|
||||
|
||||
> :warning: **Must change the parameters marked in uppercase to your own**
|
||||
### 3.New P2PApp
|
||||
|
||||
Output sample
|
||||

|
||||
The log of `LISTEN ON PORT 22022 START` indicates P2PApp runs successfully on your home device, listing port is 22022. Once connects to local ip:port,127.0.0.1:22022, it means the home device has conneccted to the office device's port, 22.
|
||||

|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 4.Use P2PApp
|
||||
You can see the P2P application you just created on the "MyHomePC" device, just connect to the "local listening port" shown in the figure below.
|
||||
|
||||

|
||||
|
||||
On MyHomePC, press Win+R and enter MSTSC to open the remote desktop, input `127.0.0.1:23389 /admin`
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
4. Test the connection between office device and home device.In your home deivce, run SSH to login the office device.
|
||||
```
|
||||
ssh -p22022 root@127.0.0.1:22022
|
||||
```
|
||||

|
||||
|
||||
|
||||
## [Usage](/USAGE.md)
|
||||
## Usage
|
||||
[Here](/USAGE.md) describes how to run manually
|
||||
|
||||
## Scenarios
|
||||
Especially suitable for large traffic intranet access.
|
||||
### Remote work
|
||||
Windows MSTSC, VNC and other remote desktops, SSH, various ERP systems in the intranet
|
||||
|
||||
### Remote Access NAS
|
||||
Manage a large number of videos and pictures
|
||||
### Remote Access Camera
|
||||
### Remote Flashing Phone
|
||||
### Remotely Data Backup
|
||||
>* Remote work: Windows MSTSC, VNC and other remote desktops, SSH, various ERP systems in the intranet
|
||||
>* Remote access ERP systems in the intranet
|
||||
>* Remote access NAS: Manage a large number of videos and pictures
|
||||
>* Remote access camera
|
||||
>* Remote flashing phone
|
||||
>* Remotely data backup
|
||||
---
|
||||
## Overview Design
|
||||
### Prototype
|
||||
@@ -89,46 +90,61 @@ Manage a large number of videos and pictures
|
||||
P2PAPP is the most import concept in this project, one P2PApp is able to map the remote service(mstsc/ssh) to the local listening. The main job of re-development or restful API we provide is to manage P2PApp.
|
||||
|
||||

|
||||
## Share
|
||||
10mbps is its default setting of share speed limit. Only when your users have shared their nodes, they are allowed to use others' shared nodes. This is very fair, and it is also the original intention of this project.
|
||||
We recommend that you join a shared network in a place with sufficient bandwidth (such as an office or home with 100M optical fiber).
|
||||
If you are still not willing to contribute any node to the OpenP2P share network, please refer to the operating parameters for your own setting.
|
||||
|
||||
## Safety
|
||||
The nodes which have joined the OpenP2P share network can vist each other by authentications. Shared nodes will only relay data, and others cannot access any resources in the intranet.
|
||||
|
||||
### TLS1.3+AES
|
||||
### 1. TLS1.3+AES
|
||||
The communication data between the two nodes uses the industry's most secure TLS1.3 channel. The communication content will also use AES encryption, double security, the key is exchanged through the server. Effectively prevent man-in-the-middle attacks.
|
||||
|
||||
### Will the shared node capture my data?
|
||||
### 2. Will the shared node capture my data?
|
||||
That's right, the relay node is naturally an man-in-middle, so AES encryption is added to ensure the security of the communication content. The relay node cannot obtain the plaintext.
|
||||
### How does the shared relay node verify the authority?
|
||||
### 3. How does the shared relay node verify the authority?
|
||||
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.
|
||||
|
||||
163
USAGE-ZH.md
@@ -1,55 +1,108 @@
|
||||
# 详细运行参数说明
|
||||
> :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p"
|
||||
|
||||
|
||||
## 安装和监听
|
||||
```
|
||||
./openp2p install -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
或
|
||||
./openp2p -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
# 注意Windows系统把“./openp2p” 换成“openp2p.exe”
|
||||
```
|
||||
>* install: 安装模式【推荐】,会安装成系统服务,这样它就能随系统自动启动
|
||||
>* -d: daemon模式。发现worker进程意外退出就会自动启动新的worker进程
|
||||
>* -node: 独一无二的节点名字,唯一标识
|
||||
>* -user: 独一无二的用户名字,该节点属于这个user
|
||||
>* -password: 密码
|
||||
>* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好
|
||||
>* -loglevel: 需要查看更多调试日志,设置0;默认是1
|
||||
>* -noshare: 不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点
|
||||
|
||||
## 连接
|
||||
```
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
使用配置文件,建立多个P2PApp
|
||||
./openp2p -d -f
|
||||
./openp2p -f
|
||||
```
|
||||
>* -peernode: 目标节点名字
|
||||
>* -dstip: 目标服务地址,默认本机127.0.0.1
|
||||
>* -dstport: 目标服务端口,常见的如windows远程桌面3389,Linux ssh 22
|
||||
>* -protocol: 目标服务协议 tcp、udp
|
||||
>* -peeruser: 目标用户,如果是同一个用户下的节点,则无需设置
|
||||
>* -peerpassword: 目标密码,如果是同一个用户下的节点,则无需设置
|
||||
>* -f: 配置文件,如果希望配置多个P2PApp参考[config.json](/config.json)
|
||||
|
||||
## 升级客户端
|
||||
```
|
||||
# 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远程桌面3389,Linux ssh 22
|
||||
>* -protocol: 目标服务协议 tcp、udp
|
||||
|
||||
## 配置文件
|
||||
一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json` 或 `/usr/local/openp2p/config.json`
|
||||
希望修改参数,或者配置多个P2PApp可手动修改配置文件
|
||||
|
||||
配置实例
|
||||
```
|
||||
{
|
||||
"network": {
|
||||
"Node": "YOUR-NODE-NAME",
|
||||
"Token": "TOKEN",
|
||||
"ShareBandwidth": 0,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
"ServerPort": 27183,
|
||||
"UDPPort1": 27182,
|
||||
"UDPPort2": 27183
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"AppName": "OfficeWindowsPC",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 23389,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 3389,
|
||||
"DstHost": "localhost",
|
||||
},
|
||||
{
|
||||
"AppName": "OfficeServerSSH",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 22,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 22,
|
||||
"DstHost": "192.168.1.5",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 升级客户端
|
||||
```
|
||||
# update local client
|
||||
./openp2p update
|
||||
# update remote client
|
||||
curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password='
|
||||
```
|
||||
|
||||
Windows系统需要设置防火墙放行本程序,程序会自动设置,如果设置失败会影响连接功能。
|
||||
Linux系统(Ubuntu和CentOS7)的防火墙默认配置均不会有影响,如果不行可尝试关闭防火墙
|
||||
```
|
||||
systemctl stop firewalld.service
|
||||
systemctl start firewalld.service
|
||||
firewall-cmd --state
|
||||
```
|
||||
## 停止
|
||||
TODO: windows linux macos
|
||||
## 卸载
|
||||
```
|
||||
./openp2p uninstall
|
||||
# 已安装时
|
||||
# windows
|
||||
C:\Program Files\OpenP2P\openp2p.exe uninstall
|
||||
# linux,macos
|
||||
sudo /usr/local/openp2p/openp2p uninstall
|
||||
```
|
||||
|
||||
## Docker运行
|
||||
```
|
||||
# 把YOUR-TOKEN和YOUR-NODE-NAME替换成自己的
|
||||
docker run -d --restart=always --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest
|
||||
OR
|
||||
docker run -d --restart=always --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME
|
||||
```
|
||||
|
||||
163
USAGE.md
@@ -1,56 +1,109 @@
|
||||
# Parameters details
|
||||
|
||||
> :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p"
|
||||
|
||||
|
||||
## Install and Listen
|
||||
```
|
||||
./openp2p install -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
Or
|
||||
./openp2p -d -node OFFICEPC1 -user USERNAME1 -password PASSWORD1
|
||||
|
||||
```
|
||||
>* 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
|
||||
>* -user: Unique user name, the node belongs to this user
|
||||
>* -password: Password
|
||||
>* -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
|
||||
>* -loglevel: Need to view more debug logs, set 0; the default is 1
|
||||
>* -noshare: Not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other people’s shared nodes
|
||||
|
||||
## Connect
|
||||
```
|
||||
./openp2p -d -node HOMEPC123 -user USERNAME1 -password PASSWORD1 -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 -protocol tcp
|
||||
Create multiple P2PApp by config file
|
||||
./openp2p -d -f
|
||||
./openp2p -f
|
||||
```
|
||||
>* -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
|
||||
>* -peeruser: The target user, if it is a node under the same user, no need to set
|
||||
>* -peerpassword: The target password, if it is a node under the same user, no need to set
|
||||
>* -f: Configuration file, if you want to configure multiple P2PApp refer to [config.json](/config.json)
|
||||
|
||||
## 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 people’s shared nodes
|
||||
>* -loglevel: Need to view more debug logs, set 0; the default is 1
|
||||
|
||||
### Run in Docker container
|
||||
We don't provide official docker image yet, you can run it in any container
|
||||
```
|
||||
nohup ./openp2p -d -node OFFICEPC1 -token TOKEN &
|
||||
# Since many docker images have been simplified, the install system service will fail, so the daemon mode is used to run in the background
|
||||
```
|
||||
|
||||
## Connect
|
||||
```
|
||||
./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389
|
||||
Create multiple P2PApp by config file
|
||||
./openp2p -d
|
||||
```
|
||||
>* -appname: This P2PApp name
|
||||
>* -peernode: Target node name
|
||||
>* -dstip: Target service address, default local 127.0.0.1
|
||||
>* -dstport: Target service port, such as windows remote desktop 3389, Linux ssh 22
|
||||
>* -protocol: Target service protocol tcp, udp
|
||||
|
||||
## Config file
|
||||
Generally saved in the current directory, in installation mode it will be saved to `C:\Program Files\OpenP2P\config.json` or `/usr/local/openp2p/config.json`
|
||||
If you want to modify the parameters, or configure multiple P2PApps, you can manually modify the configuration file
|
||||
|
||||
Configuration example
|
||||
```
|
||||
{
|
||||
"network": {
|
||||
"Node": "YOUR-NODE-NAME",
|
||||
"Token": "TOKEN",
|
||||
"ShareBandwidth": 0,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
"ServerPort": 27183,
|
||||
"UDPPort1": 27182,
|
||||
"UDPPort2": 27183
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"AppName": "OfficeWindowsPC",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 23389,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 3389,
|
||||
"DstHost": "localhost",
|
||||
},
|
||||
{
|
||||
"AppName": "OfficeServerSSH",
|
||||
"Protocol": "tcp",
|
||||
"SrcPort": 22,
|
||||
"PeerNode": "OFFICEPC1",
|
||||
"DstPort": 22,
|
||||
"DstHost": "192.168.1.5",
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
## Client update
|
||||
```
|
||||
# update local client
|
||||
./openp2p update
|
||||
# update remote client
|
||||
curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password='
|
||||
```
|
||||
|
||||
Windows system needs to set up firewall for this program, the program will automatically set the firewall, if the setting fails, the UDP punching will be affected.
|
||||
The default firewall configuration of Linux system (Ubuntu and CentOS7) will not have any effect, if not, you can try to turn off the firewall
|
||||
```
|
||||
systemctl stop firewalld.service
|
||||
systemctl start firewalld.service
|
||||
firewall-cmd --state
|
||||
```
|
||||
|
||||
## Uninstall
|
||||
```
|
||||
./openp2p uninstall
|
||||
# when already installed
|
||||
# windows
|
||||
C:\Program Files\OpenP2P\openp2p.exe uninstall
|
||||
# linux,macos
|
||||
sudo /usr/local/openp2p/openp2p uninstall
|
||||
```
|
||||
|
||||
## Run with Docker
|
||||
```
|
||||
# Replace YOUR-TOKEN and YOUR-NODE-NAME with yours
|
||||
docker run -d --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest
|
||||
OR
|
||||
docker run -d --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME
|
||||
```
|
||||
15
app/.gitignore
vendored
Normal 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
app/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
app/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
OpenP2P
|
||||
6
app/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
19
app/.idea/gradle.xml
generated
Normal 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
app/.idea/jarRepositories.xml
generated
Normal 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
app/.idea/misc.xml
generated
Normal 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
app/.idea/vcs.xml
generated
Normal 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
app/README.md
Normal 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
app/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
63
app/app/build.gradle
Normal 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
app/app/proguard-rules.pro
vendored
Normal 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
app/app/src/main/AndroidManifest.xml
Normal 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>
|
||||
27
app/app/src/main/java/cn/openp2p/BootReceiver.kt
Normal file
@@ -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())
|
||||
}
|
||||
}
|
||||
283
app/app/src/main/java/cn/openp2p/OpenP2PService.kt
Normal file
@@ -0,0 +1,283 @@
|
||||
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("8.8.8.8")
|
||||
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
|
||||
}
|
||||
}
|
||||
24
app/app/src/main/java/cn/openp2p/data/LoginDataSource.kt
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
46
app/app/src/main/java/cn/openp2p/data/LoginRepository.kt
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
18
app/app/src/main/java/cn/openp2p/data/Result.kt
Normal file
@@ -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
app/app/src/main/java/cn/openp2p/log.kt
Normal 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
|
||||
)
|
||||
212
app/app/src/main/java/cn/openp2p/ui/login/LoginActivity.kt
Normal file
@@ -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) {}
|
||||
})
|
||||
}
|
||||
10
app/app/src/main/java/cn/openp2p/ui/login/LoginFormState.kt
Normal file
@@ -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
|
||||
)
|
||||
9
app/app/src/main/java/cn/openp2p/ui/login/LoginResult.kt
Normal file
@@ -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
|
||||
)
|
||||
57
app/app/src/main/java/cn/openp2p/ui/login/LoginViewModel.kt
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
30
app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@@ -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>
|
||||
170
app/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -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
app/app/src/main/res/drawable/icon.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</selector>
|
||||
94
app/app/src/main/res/layout/activity_login.xml
Normal file
@@ -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>
|
||||
5
app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -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>
|
||||
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
16
app/app/src/main/res/values-night/themes.xml
Normal 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_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
app/app/src/main/res/values/colors.xml
Normal 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
app/app/src/main/res/values/dimens.xml
Normal 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
app/app/src/main/res/values/strings.xml
Normal 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
app/app/src/main/res/values/themes.xml
Normal 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
app/build.gradle
Normal 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
app/gradle.properties
Normal 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
|
||||
BIN
app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal 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
|
||||
172
app/gradlew
vendored
Normal 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
app/gradlew.bat
vendored
Normal 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
app/openp2p.jks
Normal file
2
app/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
||||
rootProject.name = "OpenP2P"
|
||||
include ':app'
|
||||
@@ -1,45 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BandwidthLimiter ...
|
||||
type BandwidthLimiter struct {
|
||||
freeFlowTime time.Time
|
||||
bandwidth int // mbps
|
||||
freeFlow int // bytes
|
||||
maxFreeFlow int // bytes
|
||||
freeFlowMtx sync.Mutex
|
||||
}
|
||||
|
||||
// mbps
|
||||
func newBandwidthLimiter(bw int) *BandwidthLimiter {
|
||||
return &BandwidthLimiter{
|
||||
bandwidth: bw,
|
||||
freeFlowTime: time.Now(),
|
||||
maxFreeFlow: bw * 1024 * 1024 / 8,
|
||||
freeFlow: bw * 1024 * 1024 / 8,
|
||||
}
|
||||
}
|
||||
|
||||
// Add ...
|
||||
func (bl *BandwidthLimiter) Add(bytes int) {
|
||||
if bl.bandwidth <= 0 {
|
||||
return
|
||||
}
|
||||
bl.freeFlowMtx.Lock()
|
||||
defer bl.freeFlowMtx.Unlock()
|
||||
// calc free flow 1000*1000/1024/1024=0.954; 1024*1024/1000/1000=1.048
|
||||
bl.freeFlow += int(time.Now().Sub(bl.freeFlowTime) * time.Duration(bl.bandwidth) / 8 / 954)
|
||||
if bl.freeFlow > bl.maxFreeFlow {
|
||||
bl.freeFlow = bl.maxFreeFlow
|
||||
}
|
||||
bl.freeFlow -= bytes
|
||||
bl.freeFlowTime = time.Now()
|
||||
if bl.freeFlow < 0 {
|
||||
// sleep for the overflow
|
||||
time.Sleep(time.Millisecond * time.Duration(-bl.freeFlow/(bl.bandwidth*1048/8)))
|
||||
}
|
||||
}
|
||||
9
cmd/openp2p.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
op "openp2p/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op.Run()
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
86
config.go
@@ -1,86 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
var gConf Config
|
||||
|
||||
type AppConfig struct {
|
||||
// required
|
||||
Protocol string
|
||||
SrcPort int
|
||||
PeerNode string
|
||||
DstPort int
|
||||
DstHost string
|
||||
PeerUser string
|
||||
PeerPassword string
|
||||
// runtime info
|
||||
peerToken uint64
|
||||
peerNatType int
|
||||
peerIP string
|
||||
peerConeNatPort int
|
||||
retryNum int
|
||||
retryTime time.Time
|
||||
shareBandwidth int
|
||||
}
|
||||
|
||||
// TODO: add loglevel, maxlogfilesize
|
||||
type Config struct {
|
||||
Network NetworkConfig `json:"network"`
|
||||
Apps []AppConfig `json:"apps"`
|
||||
daemonMode bool
|
||||
}
|
||||
|
||||
func (c *Config) add(app AppConfig) {
|
||||
if app.SrcPort == 0 || app.DstPort == 0 {
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Apps = append(c.Apps, app)
|
||||
}
|
||||
|
||||
func (c *Config) save() {
|
||||
data, _ := json.MarshalIndent(c, "", "")
|
||||
ioutil.WriteFile("config.json", data, 0644)
|
||||
}
|
||||
|
||||
func (c *Config) load() error {
|
||||
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
|
||||
Node string
|
||||
User string
|
||||
Password string
|
||||
NoShare bool
|
||||
localIP string
|
||||
ipv6 string
|
||||
hostName string
|
||||
mac string
|
||||
os string
|
||||
publicIP string
|
||||
natType int
|
||||
shareBandwidth int
|
||||
// server info
|
||||
ServerHost string
|
||||
ServerPort int
|
||||
UDPPort1 int
|
||||
UDPPort2 int
|
||||
}
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -1,20 +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 ""
|
||||
@@ -75,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
|
||||
@@ -120,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
|
||||
@@ -148,3 +155,128 @@ func execOutput(name string, args ...string) string {
|
||||
cmdGetOsName.Run()
|
||||
return cmdOut.String()
|
||||
}
|
||||
|
||||
func defaultNodeName() string {
|
||||
name, _ := os.Hostname()
|
||||
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
core/common_test.go
Normal 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
core/config.go
Normal 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].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
core/config_test.go
Normal 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)
|
||||
}
|
||||
115
core/daemon.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/openp2p-cn/service"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
running bool
|
||||
proc *os.Process
|
||||
}
|
||||
|
||||
func (d *daemon) Start(s service.Service) error {
|
||||
gLog.Println(LvINFO, "daemon start")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *daemon) Stop(s service.Service) error {
|
||||
gLog.Println(LvINFO, "service stop")
|
||||
d.running = false
|
||||
if d.proc != nil {
|
||||
gLog.Println(LvINFO, "stop worker")
|
||||
d.proc.Kill()
|
||||
}
|
||||
if service.Interactive() {
|
||||
gLog.Println(LvINFO, "stop daemon")
|
||||
os.Exit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *daemon) run() {
|
||||
gLog.Println(LvINFO, "daemon run start")
|
||||
defer gLog.Println(LvINFO, "daemon run end")
|
||||
d.running = true
|
||||
binPath, _ := os.Executable()
|
||||
mydir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
gLog.Println(LvINFO, mydir)
|
||||
conf := &service.Config{
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: binPath,
|
||||
}
|
||||
|
||||
s, _ := service.New(d, conf)
|
||||
go s.Run()
|
||||
var args []string
|
||||
// rm -d parameter
|
||||
for i := 0; i < len(os.Args); i++ {
|
||||
if os.Args[i] == "-d" {
|
||||
args = append(os.Args[0:i], os.Args[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
args = append(args, "-nv")
|
||||
for {
|
||||
// start worker
|
||||
tmpDump := filepath.Join("log", "dump.log.tmp")
|
||||
dumpFile := filepath.Join("log", "dump.log")
|
||||
f, err := os.Create(filepath.Join(tmpDump))
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "start worker error:%s", err)
|
||||
return
|
||||
}
|
||||
gLog.Println(LvINFO, "start worker process, args:", args)
|
||||
execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}}
|
||||
p, err := os.StartProcess(binPath, args, execSpec)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "start worker error:%s", err)
|
||||
return
|
||||
}
|
||||
d.proc = p
|
||||
_, _ = p.Wait()
|
||||
f.Close()
|
||||
time.Sleep(time.Second)
|
||||
err = os.Rename(tmpDump, dumpFile)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "rename dump error:%s", err)
|
||||
}
|
||||
if !d.running {
|
||||
return
|
||||
}
|
||||
gLog.Printf(LvERROR, "worker stop, restart it after 10s")
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
|
||||
svcConfig := &service.Config{
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: exeAbsPath,
|
||||
Arguments: args,
|
||||
}
|
||||
|
||||
s, e := service.New(d, svcConfig)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
e = service.Control(s, ctrlComm)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
33
core/errorcode.go
Normal 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
core/handlepush.go
Normal 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
core/holepunch.go
Normal 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.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(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.ra, _ = 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.ra, 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.ra, 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.la)
|
||||
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.ra, _ = 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.ra, 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.ra.String())
|
||||
_, err = UDPWrite(conn, t.ra, 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.ra, 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.ra, 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.ra, 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")
|
||||
t.pn.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(HandshakeTimeout):
|
||||
return fmt.Errorf("wait handshake timeout")
|
||||
case la := <-gotCh:
|
||||
t.la = la
|
||||
gLog.Println(LvDEBUG, "symmetric handshake ok", la)
|
||||
gLog.Printf(LvINFO, "handshakeS2C ok. cost %dms", time.Since(startTime)/time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
124
core/install.go
Normal 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 openp2p.cn@gmail.com")
|
||||
gLog.Println(LvINFO, "install start")
|
||||
defer gLog.Println(LvINFO, "install end")
|
||||
// auto uninstall
|
||||
err := os.MkdirAll(defaultInstallPath, 0775)
|
||||
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
|
||||
return
|
||||
}
|
||||
err = os.Chdir(defaultInstallPath)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "cd error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
uninstall()
|
||||
// save config file
|
||||
parseParams("install", "")
|
||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
d := daemon{}
|
||||
// copy files
|
||||
|
||||
binPath, _ := os.Executable()
|
||||
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles)
|
||||
return
|
||||
}
|
||||
|
||||
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
|
||||
return
|
||||
}
|
||||
|
||||
_, errFiles = io.Copy(dst, src)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "io.Copy error:%s", errFiles)
|
||||
return
|
||||
}
|
||||
src.Close()
|
||||
dst.Close()
|
||||
|
||||
// install system service
|
||||
gLog.Println(LvINFO, "targetPath:", targetPath)
|
||||
err = d.Control("install", targetPath, []string{"-d"})
|
||||
if err == nil {
|
||||
gLog.Println(LvINFO, "install system service ok.")
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
err = d.Control("start", targetPath, []string{"-d"})
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "start openp2p service error:", err)
|
||||
} else {
|
||||
gLog.Println(LvINFO, "start openp2p service ok.")
|
||||
}
|
||||
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
|
||||
}
|
||||
|
||||
func installByFilename() {
|
||||
params := strings.Split(filepath.Base(os.Args[0]), "-")
|
||||
if len(params) < 4 {
|
||||
return
|
||||
}
|
||||
serverHost := params[1]
|
||||
token := params[2]
|
||||
gLog.Println(LvINFO, "install start")
|
||||
targetPath := os.Args[0]
|
||||
args := []string{"install"}
|
||||
args = append(args, "-serverhost")
|
||||
args = append(args, serverHost)
|
||||
args = append(args, "-token")
|
||||
args = append(args, token)
|
||||
env := os.Environ()
|
||||
cmd := exec.Command(targetPath, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Env = env
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "install by filename, start process error:", err)
|
||||
return
|
||||
}
|
||||
gLog.Println(LvINFO, "install end")
|
||||
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
|
||||
fmt.Println("Press the Any Key to exit")
|
||||
fmt.Scanln()
|
||||
os.Exit(0)
|
||||
}
|
||||
func uninstall() {
|
||||
gLog.Println(LvINFO, "uninstall start")
|
||||
defer gLog.Println(LvINFO, "uninstall end")
|
||||
d := daemon{}
|
||||
err := d.Control("stop", "", nil)
|
||||
if err != nil { // service maybe not install
|
||||
return
|
||||
}
|
||||
err = d.Control("uninstall", "", nil)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "uninstall system service error:", err)
|
||||
} else {
|
||||
gLog.Println(LvINFO, "uninstall system service ok.")
|
||||
}
|
||||
binPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
os.Remove(binPath + "0")
|
||||
os.Remove(binPath)
|
||||
// os.RemoveAll(defaultInstallPath) // reserve config.json
|
||||
}
|
||||
74
core/iptables.go
Normal 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
core/iptree.go
Normal 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
core/iptree_test.go
Normal 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
core/log.go
Normal 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...)
|
||||
}
|
||||
}
|
||||
189
core/nat.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
reuse "github.com/openp2p-cn/go-reuseport"
|
||||
)
|
||||
|
||||
func natTCP(serverHost string, serverPort int) (publicIP string, publicPort int, localPort int) {
|
||||
// dialer := &net.Dialer{
|
||||
// LocalAddr: &net.TCPAddr{
|
||||
// IP: net.ParseIP("0.0.0.0"),
|
||||
// Port: localPort,
|
||||
// },
|
||||
// }
|
||||
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", 0), fmt.Sprintf("%s:%d", serverHost, serverPort), NatTestTimeout)
|
||||
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||
// log.Println(LvINFO, conn.LocalAddr())
|
||||
if err != nil {
|
||||
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
localPort, _ = strconv.Atoi(strings.Split(conn.LocalAddr().String(), ":")[1])
|
||||
_, wrerr := conn.Write([]byte("1"))
|
||||
if wrerr != nil {
|
||||
fmt.Printf("Write error: %s\n", wrerr)
|
||||
return
|
||||
}
|
||||
b := make([]byte, 1000)
|
||||
conn.SetReadDeadline(time.Now().Add(NatTestTimeout))
|
||||
n, rderr := conn.Read(b)
|
||||
if rderr != nil {
|
||||
fmt.Printf("Read error: %s\n", rderr)
|
||||
return
|
||||
}
|
||||
arr := strings.Split(string(b[:n]), ":")
|
||||
if len(arr) < 2 {
|
||||
return
|
||||
}
|
||||
publicIP = arr[0]
|
||||
port, _ := strconv.ParseInt(arr[1], 10, 32)
|
||||
publicPort = int(port)
|
||||
return
|
||||
|
||||
}
|
||||
func natTest(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
|
||||
gLog.Println(LvDEBUG, "natTest start")
|
||||
defer gLog.Println(LvDEBUG, "natTest end")
|
||||
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "natTest listen udp error:", err)
|
||||
return "", 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
||||
// The connection can write data to the desired address.
|
||||
msg, err := newMessage(MsgNATDetect, 0, nil)
|
||||
_, err = conn.WriteTo(msg, dst)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
deadline := time.Now().Add(NatTestTimeout)
|
||||
err = conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
buffer := make([]byte, 1024)
|
||||
nRead, _, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "NAT detect error:", err)
|
||||
return "", 0, err
|
||||
}
|
||||
natRsp := NatDetectRsp{}
|
||||
json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
||||
|
||||
return natRsp.IP, natRsp.Port, nil
|
||||
}
|
||||
|
||||
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, err error) {
|
||||
// the random local port may be used by other.
|
||||
localPort := int(rand.Uint32()%15000 + 50000)
|
||||
|
||||
ip1, port1, err := natTest(host, udp1, localPort)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
_, port2, err := natTest(host, udp2, localPort) // 2rd nat test not need testing publicip
|
||||
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
natType := NATSymmetric
|
||||
if port1 == port2 {
|
||||
natType = NATCone
|
||||
}
|
||||
return ip1, natType, nil
|
||||
}
|
||||
|
||||
func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATPMP int) {
|
||||
if publicIP == "" || echoPort == 0 {
|
||||
return
|
||||
}
|
||||
var echoConn *net.UDPConn
|
||||
gLog.Println(LvDEBUG, "echo server start")
|
||||
var err error
|
||||
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
||||
if err != nil { // listen error
|
||||
gLog.Println(LvERROR, "echo server listen error:", err)
|
||||
return
|
||||
}
|
||||
defer echoConn.Close()
|
||||
go func() {
|
||||
// close outside for breaking the ReadFromUDP
|
||||
// wait 30s for echo testing
|
||||
buf := make([]byte, 1600)
|
||||
echoConn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
||||
n, addr, err := echoConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
echoConn.WriteToUDP(buf[0:n], addr)
|
||||
gLog.Println(LvDEBUG, "echo server end")
|
||||
}()
|
||||
// testing for public ip
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 1 {
|
||||
// test upnp or nat-pmp
|
||||
gLog.Println(LvDEBUG, "upnp test start")
|
||||
nat, err := Discover()
|
||||
if err != nil || nat == nil {
|
||||
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
||||
break
|
||||
}
|
||||
ext, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
|
||||
break
|
||||
}
|
||||
gLog.Println(LvINFO, "PublicIP:", ext)
|
||||
|
||||
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30) // 30 seconds fot upnp testing
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||
break
|
||||
} else {
|
||||
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800) // 7 days for tcp connection
|
||||
}
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "public ip test start %s:%d", publicIP, echoPort)
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer conn.Close()
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", publicIP, echoPort))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
conn.WriteTo([]byte("echo"), dst)
|
||||
buf := make([]byte, 1600)
|
||||
|
||||
// wait for echo testing
|
||||
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
||||
_, _, err = conn.ReadFromUDP(buf)
|
||||
if err == nil {
|
||||
if i == 1 {
|
||||
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
||||
hasUPNPorNATPMP = 1
|
||||
} else {
|
||||
gLog.Println(LvDEBUG, "public ip:YES")
|
||||
hasPublicIP = 1
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
121
core/openp2p.go
Normal 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 openp2p.cn@gmail.com")
|
||||
|
||||
if gConf.daemonMode {
|
||||
d := daemon{}
|
||||
d.run()
|
||||
return
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, &gConf)
|
||||
setFirewall()
|
||||
err := setRLimit()
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "setRLimit error:", err)
|
||||
}
|
||||
GNetwork = P2PNetworkInstance()
|
||||
if ok := GNetwork.Connect(30000); !ok {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
return
|
||||
}
|
||||
// gLog.Println(LvINFO, "waiting for connection...")
|
||||
forever := make(chan bool)
|
||||
<-forever
|
||||
}
|
||||
|
||||
// for Android app
|
||||
// gomobile not support uint64 exported to java
|
||||
|
||||
func RunAsModule(baseDir string, token string, bw int, logLevel int) *P2PNetwork {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
os.Chdir(baseDir) // for system service
|
||||
gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole)
|
||||
|
||||
parseParams("", "")
|
||||
|
||||
n, err := strconv.ParseUint(token, 10, 64)
|
||||
if err == nil && n > 0 {
|
||||
gConf.setToken(n)
|
||||
}
|
||||
if n <= 0 && gConf.Network.Token == 0 { // not input token
|
||||
return nil
|
||||
}
|
||||
// gLog.setLevel(LogLevel(logLevel))
|
||||
gConf.setShareBandwidth(bw)
|
||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com")
|
||||
gLog.Println(LvINFO, &gConf)
|
||||
|
||||
GNetwork = P2PNetworkInstance()
|
||||
if ok := GNetwork.Connect(30000); !ok {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
return nil
|
||||
}
|
||||
// gLog.Println(LvINFO, "waiting for connection...")
|
||||
return GNetwork
|
||||
}
|
||||
|
||||
func RunCmd(cmd string) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
baseDir := filepath.Dir(os.Args[0])
|
||||
os.Chdir(baseDir) // for system service
|
||||
gLog = NewLogger(baseDir, ProductName, LvINFO, 1024*1024, LogFile|LogConsole)
|
||||
|
||||
parseParams("", cmd)
|
||||
setFirewall()
|
||||
err := setRLimit()
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "setRLimit error:", err)
|
||||
}
|
||||
GNetwork = P2PNetworkInstance()
|
||||
if ok := GNetwork.Connect(30000); !ok {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
return
|
||||
}
|
||||
forever := make(chan bool)
|
||||
<-forever
|
||||
}
|
||||
|
||||
func GetToken(baseDir string) string {
|
||||
os.Chdir(baseDir)
|
||||
gConf.load()
|
||||
return fmt.Sprintf("%d", gConf.Network.Token)
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
os.Exit(0)
|
||||
}
|
||||
20
core/optun.go
Normal 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
core/optun_android.go
Normal 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
core/optun_darwin.go
Normal 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
core/optun_linux.go
Normal 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
core/optun_other.go
Normal 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
core/optun_windows.go
Normal 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
core/overlay.go
Normal 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
core/p2papp.go
Normal 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 init 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,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"sync"
|
||||
977
core/p2pnetwork.go
Normal 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{pn: pn,
|
||||
config: config,
|
||||
id: tid,
|
||||
writeData: make(chan []byte, WriteDataChanSize),
|
||||
writeDataSmall: make(chan []byte, WriteDataChanSize/30),
|
||||
}
|
||||
t.initPort()
|
||||
if isClient {
|
||||
if err = t.connect(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel connect error:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = t.listen(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel listen error:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// store it when success
|
||||
gLog.Printf(LvDEBUG, "store tunnel %d", tid)
|
||||
pn.allTunnels.Store(tid, t)
|
||||
return
|
||||
}
|
||||
func (pn *P2PNetwork) init() error {
|
||||
gLog.Println(LvINFO, "P2PNetwork init start")
|
||||
defer gLog.Println(LvINFO, "P2PNetwork init end")
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
var err error
|
||||
for {
|
||||
// detect nat type
|
||||
gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerHost, gConf.Network.UDPPort1, gConf.Network.UDPPort2)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "detect NAT type error:", err)
|
||||
break
|
||||
}
|
||||
if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again
|
||||
gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort)
|
||||
}
|
||||
|
||||
// for testcase
|
||||
if strings.Contains(gConf.Network.Node, "openp2pS2STest") {
|
||||
gConf.Network.natType = NATSymmetric
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pS2STest debug")
|
||||
|
||||
}
|
||||
if strings.Contains(gConf.Network.Node, "openp2pC2CTest") {
|
||||
gConf.Network.natType = NATCone
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pC2CTest debug")
|
||||
}
|
||||
|
||||
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
onceV4Listener.Do(func() {
|
||||
v4l = &v4Listener{port: gConf.Network.TCPPort}
|
||||
go v4l.start()
|
||||
})
|
||||
}
|
||||
gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP)
|
||||
gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort)
|
||||
uri := "/api/v1/login"
|
||||
caCertPool, errCert := x509.SystemCertPool()
|
||||
if errCert != nil {
|
||||
gLog.Println(LvERROR, "Failed to load system root CAs:", errCert)
|
||||
caCertPool = x509.NewCertPool()
|
||||
}
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
|
||||
config := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
|
||||
websocket.DefaultDialer.TLSClientConfig = &config
|
||||
websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout
|
||||
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri}
|
||||
q := u.Query()
|
||||
q.Add("node", gConf.Network.Node)
|
||||
q.Add("token", fmt.Sprintf("%d", gConf.Network.Token))
|
||||
q.Add("version", OpenP2PVersion)
|
||||
q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType))
|
||||
q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth))
|
||||
u.RawQuery = q.Encode()
|
||||
var ws *websocket.Conn
|
||||
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "Dial error:", err)
|
||||
break
|
||||
}
|
||||
pn.running = true
|
||||
pn.online = true
|
||||
pn.conn = ws
|
||||
localAddr := strings.Split(ws.LocalAddr().String(), ":")
|
||||
if len(localAddr) == 2 {
|
||||
gConf.Network.localIP = localAddr[0]
|
||||
} else {
|
||||
err = errors.New("get local ip failed")
|
||||
break
|
||||
}
|
||||
go pn.readLoop()
|
||||
gConf.Network.mac = getmac(gConf.Network.localIP)
|
||||
gConf.Network.os = getOsName()
|
||||
go func() {
|
||||
req := ReportBasic{
|
||||
Mac: gConf.Network.mac,
|
||||
LanIP: gConf.Network.localIP,
|
||||
OS: gConf.Network.os,
|
||||
HasIPv4: gConf.Network.hasIPv4,
|
||||
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
rsp := netInfo()
|
||||
gLog.Println(LvDEBUG, "netinfo:", rsp)
|
||||
if rsp != nil && rsp.Country != "" {
|
||||
if IsIPv6(rsp.IP.String()) {
|
||||
gConf.setIPv6(rsp.IP.String())
|
||||
}
|
||||
req.NetInfo = *rsp
|
||||
} else {
|
||||
pn.refreshIPv6()
|
||||
}
|
||||
req.IPv6 = gConf.IPv6()
|
||||
pn.write(MsgReport, MsgReportBasic, &req)
|
||||
}()
|
||||
go pn.autorunApp()
|
||||
pn.write(MsgSDWAN, MsgSDWANInfoReq, nil)
|
||||
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// init failed, retry
|
||||
pn.close()
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
head := openP2PHeader{}
|
||||
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handleMessage error:", err)
|
||||
return
|
||||
}
|
||||
switch head.MainType {
|
||||
case MsgLogin:
|
||||
// gLog.Println(LevelINFO,string(msg))
|
||||
rsp := LoginRsp{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err)
|
||||
return
|
||||
}
|
||||
if rsp.Error != 0 {
|
||||
gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
|
||||
pn.running = false
|
||||
} else {
|
||||
gConf.setToken(rsp.Token)
|
||||
gConf.setUser(rsp.User)
|
||||
if len(rsp.Node) >= MinNodeNameLen {
|
||||
gConf.setNode(rsp.Node)
|
||||
}
|
||||
if rsp.LoginMaxDelay > 0 {
|
||||
pn.loginMaxDelaySeconds = rsp.LoginMaxDelay
|
||||
}
|
||||
gLog.Printf(LvINFO, "login ok. user=%s,node=%s", rsp.User, rsp.Node)
|
||||
}
|
||||
case MsgHeartbeat:
|
||||
gLog.Printf(LvDev, "P2PNetwork heartbeat ok")
|
||||
pn.hbTime = time.Now()
|
||||
rtt := pn.hbTime.UnixNano() - pn.t1
|
||||
if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) {
|
||||
gLog.Printf(LvINFO, "rtt=%d too large ignore", rtt)
|
||||
return // invalid hb rsp
|
||||
}
|
||||
pn.preRtt = rtt
|
||||
t2 := int64(binary.LittleEndian.Uint64(msg[openP2PHeaderSize : openP2PHeaderSize+8]))
|
||||
thisdt := pn.t1 + rtt/2 - t2
|
||||
newdt := thisdt
|
||||
if pn.dt != 0 {
|
||||
ddt := thisdt - pn.dt
|
||||
pn.ddt = ddt
|
||||
if pn.ddtma == 0 {
|
||||
pn.ddtma = pn.ddt
|
||||
} else {
|
||||
pn.ddtma = int64(float64(pn.ddtma)*(1-ma10) + float64(pn.ddt)*ma10) // avoid int64 overflow
|
||||
newdt = pn.dt + pn.ddtma
|
||||
}
|
||||
}
|
||||
pn.dt = newdt
|
||||
gLog.Printf(LvDEBUG, "synctime thisdt=%dms dt=%dms ddt=%dns ddtma=%dns rtt=%dms ", thisdt/int64(time.Millisecond), pn.dt/int64(time.Millisecond), pn.ddt, pn.ddtma, rtt/int64(time.Millisecond))
|
||||
case MsgPush:
|
||||
handlePush(head.SubType, msg)
|
||||
case MsgSDWAN:
|
||||
handleSDWAN(head.SubType, msg)
|
||||
default:
|
||||
i, ok := pn.msgMap.Load(uint64(0))
|
||||
if ok {
|
||||
ch := i.(chan msgCtx)
|
||||
ch <- msgCtx{data: msg, ts: time.Now()}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) readLoop() {
|
||||
gLog.Printf(LvDEBUG, "P2PNetwork readLoop start")
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
for pn.running {
|
||||
pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second))
|
||||
_, msg, err := pn.conn.ReadMessage()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "P2PNetwork read error:%s", err)
|
||||
pn.close()
|
||||
break
|
||||
}
|
||||
pn.handleMessage(msg)
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "P2PNetwork readLoop end")
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error {
|
||||
if !pn.online {
|
||||
return errors.New("P2P network offline")
|
||||
}
|
||||
msg, err := newMessage(mainType, subType, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pn.writeMtx.Lock()
|
||||
defer pn.writeMtx.Unlock()
|
||||
if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
|
||||
gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err)
|
||||
pn.close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) relay(to uint64, body []byte) error {
|
||||
i, ok := pn.allTunnels.Load(to)
|
||||
if !ok {
|
||||
return ErrRelayTunnelNotFound
|
||||
}
|
||||
tunnel := i.(*P2PTunnel)
|
||||
if tunnel.config.shareBandwidth > 0 {
|
||||
pn.limiter.Add(len(body), true)
|
||||
}
|
||||
var err error
|
||||
if err = tunnel.conn.WriteBuffer(body); err != nil {
|
||||
gLog.Printf(LvERROR, "relay to %d len=%d error:%s", to, len(body), err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error {
|
||||
// gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to)
|
||||
if !pn.online {
|
||||
return errors.New("client offline")
|
||||
}
|
||||
pushHead := PushHeader{}
|
||||
pushHead.From = gConf.nodeID()
|
||||
pushHead.To = NodeNameToID(to)
|
||||
pushHeadBuf := new(bytes.Buffer)
|
||||
err := binary.Write(pushHeadBuf, binary.LittleEndian, pushHead)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.Marshal(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// gLog.Println(LevelINFO,"write packet:", string(data))
|
||||
pushMsg := append(encodeHeader(MsgPush, subType, uint32(len(data)+PushHeaderSize)), pushHeadBuf.Bytes()...)
|
||||
pushMsg = append(pushMsg, data...)
|
||||
pn.writeMtx.Lock()
|
||||
defer pn.writeMtx.Unlock()
|
||||
if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil {
|
||||
gLog.Printf(LvERROR, "push to %s error:%s", to, err)
|
||||
pn.close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) close() {
|
||||
if pn.running {
|
||||
if pn.conn != nil {
|
||||
pn.conn.Close()
|
||||
}
|
||||
pn.running = false
|
||||
}
|
||||
select {
|
||||
case pn.restartCh <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout time.Duration) (head *openP2PHeader, body []byte) {
|
||||
var nodeID uint64
|
||||
if node == "" {
|
||||
nodeID = 0
|
||||
} else {
|
||||
nodeID = NodeNameToID(node)
|
||||
}
|
||||
i, ok := pn.msgMap.Load(nodeID)
|
||||
if !ok {
|
||||
gLog.Printf(LvERROR, "read msg error: %s not found", node)
|
||||
return
|
||||
}
|
||||
ch := i.(chan msgCtx)
|
||||
for {
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
gLog.Printf(LvERROR, "read msg error %d:%d timeout", mainType, subType)
|
||||
return
|
||||
case msg := <-ch:
|
||||
head = &openP2PHeader{}
|
||||
err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "read msg error:", err)
|
||||
break
|
||||
}
|
||||
if time.Since(msg.ts) > ReadMsgTimeout {
|
||||
gLog.Printf(LvDEBUG, "read msg error expired %d:%d", head.MainType, head.SubType)
|
||||
continue
|
||||
}
|
||||
if head.MainType != mainType || head.SubType != subType {
|
||||
gLog.Printf(LvDEBUG, "read msg error type %d:%d, requeue it", head.MainType, head.SubType)
|
||||
ch <- msg
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
if mainType == MsgPush {
|
||||
body = msg.data[openP2PHeaderSize+PushHeaderSize:]
|
||||
} else {
|
||||
body = msg.data[openP2PHeaderSize:]
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
|
||||
pn.apps.Range(func(id, i interface{}) bool {
|
||||
app := i.(*p2pApp)
|
||||
if app.id == appID {
|
||||
app.updateHeartbeat()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// ipv6 will expired need to refresh.
|
||||
func (pn *P2PNetwork) refreshIPv6() {
|
||||
for i := 0; i < 2; i++ {
|
||||
client := &http.Client{Timeout: time.Second * 10}
|
||||
r, err := client.Get("http://ipv6.ddnspod.com/")
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "refreshIPv6 error:", err)
|
||||
continue
|
||||
}
|
||||
defer r.Body.Close()
|
||||
buf := make([]byte, 1024)
|
||||
n, err := r.Body.Read(buf)
|
||||
if n <= 0 {
|
||||
gLog.Println(LvINFO, "refreshIPv6 error:", err, n)
|
||||
continue
|
||||
}
|
||||
if IsIPv6(string(buf[:n])) {
|
||||
gConf.setIPv6(string(buf[:n]))
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) requestPeerInfo(config *AppConfig) error {
|
||||
// request peer info
|
||||
// TODO: multi-thread issue
|
||||
pn.reqGatewayMtx.Lock()
|
||||
pn.write(MsgQuery, MsgQueryPeerInfoReq, &QueryPeerInfoReq{config.peerToken, config.PeerNode})
|
||||
head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout)
|
||||
pn.reqGatewayMtx.Unlock()
|
||||
if head == nil {
|
||||
gLog.Println(LvERROR, "requestPeerInfo error")
|
||||
return ErrNetwork // network error, should not be ErrPeerOffline
|
||||
}
|
||||
rsp := QueryPeerInfoRsp{}
|
||||
if err := json.Unmarshal(body, &rsp); err != nil {
|
||||
return ErrMsgFormat
|
||||
}
|
||||
if rsp.Online == 0 {
|
||||
return ErrPeerOffline
|
||||
}
|
||||
if compareVersion(rsp.Version, LeastSupportVersion) < 0 {
|
||||
return ErrVersionNotCompatible
|
||||
}
|
||||
config.peerVersion = rsp.Version
|
||||
config.peerLanIP = rsp.LanIP
|
||||
config.hasIPv4 = rsp.HasIPv4
|
||||
config.peerIP = rsp.IPv4
|
||||
config.peerIPv6 = rsp.IPv6
|
||||
config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP
|
||||
config.peerNatType = rsp.NatType
|
||||
///
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) StartSDWAN() {
|
||||
// request peer info
|
||||
pn.sdwan = &p2pSDWAN{}
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) ConnectNode(node string) error {
|
||||
if gConf.nodeID() < NodeNameToID(node) {
|
||||
return errors.New("only the bigger nodeid connect")
|
||||
}
|
||||
peerNodeID := fmt.Sprintf("%d", NodeNameToID(node))
|
||||
config := AppConfig{Enabled: 1}
|
||||
config.AppName = peerNodeID
|
||||
config.SrcPort = 0
|
||||
config.PeerNode = node
|
||||
sdwan := gConf.getSDWAN()
|
||||
config.PunchPriority = int(sdwan.PunchPriority)
|
||||
if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode
|
||||
config.RelayNode = sdwan.CentralNode
|
||||
config.ForceRelay = int(sdwan.ForceRelay)
|
||||
if sdwan.Mode == SDWANModeCentral {
|
||||
config.ForceRelay = 1
|
||||
}
|
||||
}
|
||||
|
||||
gConf.add(config, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) WriteNode(nodeID uint64, buff []byte) error {
|
||||
i, ok := pn.apps.Load(nodeID)
|
||||
if !ok {
|
||||
return errors.New("peer not found")
|
||||
}
|
||||
var err error
|
||||
app := i.(*p2pApp)
|
||||
if app.Tunnel() == nil {
|
||||
return errors.New("peer tunnel nil")
|
||||
}
|
||||
// TODO: move to app.write
|
||||
gLog.Printf(LvDev, "%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect())
|
||||
if app.isDirect() { // direct
|
||||
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff)
|
||||
} else { // relay
|
||||
fromNodeIDHead := new(bytes.Buffer)
|
||||
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
|
||||
all := app.RelayHead().Bytes()
|
||||
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
|
||||
all = append(all, fromNodeIDHead.Bytes()...)
|
||||
all = append(all, buff...)
|
||||
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) WriteBroadcast(buff []byte) error {
|
||||
///
|
||||
pn.apps.Range(func(id, i interface{}) bool {
|
||||
// newDestIP := net.ParseIP("10.2.3.2")
|
||||
// copy(buff[16:20], newDestIP.To4())
|
||||
// binary.BigEndian.PutUint16(buff[10:12], 0) // set checksum=0 for calc checksum
|
||||
// ipChecksum := calculateChecksum(buff[0:20])
|
||||
// binary.BigEndian.PutUint16(buff[10:12], ipChecksum)
|
||||
// binary.BigEndian.PutUint16(buff[26:28], 0x082e)
|
||||
app := i.(*p2pApp)
|
||||
if app.Tunnel() == nil {
|
||||
return true
|
||||
}
|
||||
if app.config.SrcPort != 0 { // normal portmap app
|
||||
return true
|
||||
}
|
||||
if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan
|
||||
return true
|
||||
}
|
||||
if app.isDirect() { // direct
|
||||
app.Tunnel().conn.WriteBytes(MsgP2P, MsgNodeData, buff)
|
||||
} else { // relay
|
||||
fromNodeIDHead := new(bytes.Buffer)
|
||||
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
|
||||
all := app.RelayHead().Bytes()
|
||||
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
|
||||
all = append(all, fromNodeIDHead.Bytes()...)
|
||||
all = append(all, buff...)
|
||||
app.Tunnel().conn.WriteBytes(MsgP2P, MsgRelayData, all)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) ReadNode(tm time.Duration) *NodeData {
|
||||
select {
|
||||
case nd := <-pn.nodeData:
|
||||
return nd
|
||||
case <-time.After(tm):
|
||||
}
|
||||
return nil
|
||||
}
|
||||
806
core/p2ptunnel.go
Normal file
@@ -0,0 +1,806 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const WriteDataChanSize int = 3000
|
||||
|
||||
var buildTunnelMtx sync.Mutex
|
||||
|
||||
type P2PTunnel struct {
|
||||
pn *P2PNetwork
|
||||
conn underlay
|
||||
hbTime time.Time
|
||||
hbMtx sync.Mutex
|
||||
config AppConfig
|
||||
la *net.UDPAddr // local hole address
|
||||
ra *net.UDPAddr // remote hole address
|
||||
overlayConns sync.Map // both TCP and UDP
|
||||
id uint64 // client side alloc rand.uint64 = server side
|
||||
running bool
|
||||
runMtx sync.Mutex
|
||||
tunnelServer bool // different from underlayServer
|
||||
coneLocalPort int
|
||||
coneNatPort int
|
||||
linkModeWeb string // use config.linkmode
|
||||
punchTs uint64
|
||||
writeData chan []byte
|
||||
writeDataSmall chan []byte
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) initPort() {
|
||||
t.running = true
|
||||
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param
|
||||
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeIntranet {
|
||||
t.coneLocalPort = gConf.Network.TCPPort
|
||||
t.coneNatPort = gConf.Network.TCPPort // symmetric doesn't need coneNatPort
|
||||
}
|
||||
if t.config.linkMode == LinkModeUDPPunch {
|
||||
// prepare one random cone hole manually
|
||||
_, natPort, _ := natTest(gConf.Network.ServerHost, gConf.Network.UDPPort1, localPort)
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = natPort
|
||||
}
|
||||
if t.config.linkMode == LinkModeTCPPunch {
|
||||
// prepare one random cone hole by system automatically
|
||||
_, natPort, localPort2 := natTCP(gConf.Network.ServerHost, IfconfigPort1)
|
||||
t.coneLocalPort = localPort2
|
||||
t.coneNatPort = natPort
|
||||
}
|
||||
t.la = &net.UDPAddr{IP: net.ParseIP(gConf.Network.localIP), Port: t.coneLocalPort}
|
||||
gLog.Printf(LvDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connect() error {
|
||||
gLog.Printf(LvDEBUG, "start p2pTunnel to %s ", t.config.LogPeerNode())
|
||||
t.tunnelServer = false
|
||||
appKey := uint64(0)
|
||||
req := PushConnectReq{
|
||||
Token: t.config.peerToken,
|
||||
From: gConf.Network.Node,
|
||||
FromIP: gConf.Network.publicIP,
|
||||
ConeNatPort: t.coneNatPort,
|
||||
NatType: gConf.Network.natType,
|
||||
HasIPv4: gConf.Network.hasIPv4,
|
||||
IPv6: gConf.IPv6(),
|
||||
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
|
||||
ID: t.id,
|
||||
AppKey: appKey,
|
||||
Version: OpenP2PVersion,
|
||||
LinkMode: t.config.linkMode,
|
||||
IsUnderlayServer: t.config.isUnderlayServer ^ 1, // peer
|
||||
UnderlayProtocol: t.config.UnderlayProtocol,
|
||||
}
|
||||
if req.Token == 0 { // no relay token
|
||||
req.Token = gConf.Network.Token
|
||||
}
|
||||
t.pn.push(t.config.PeerNode, MsgPushConnectReq, req)
|
||||
head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, UnderlayConnectTimeout*3)
|
||||
if head == nil {
|
||||
return errors.New("connect error")
|
||||
}
|
||||
rsp := PushConnectRsp{}
|
||||
if err := json.Unmarshal(body, &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err)
|
||||
return err
|
||||
}
|
||||
// gLog.Println(LevelINFO, rsp)
|
||||
if rsp.Error != 0 {
|
||||
return errors.New(rsp.Detail)
|
||||
}
|
||||
t.config.peerNatType = rsp.NatType
|
||||
t.config.hasIPv4 = rsp.HasIPv4
|
||||
t.config.peerIPv6 = rsp.IPv6
|
||||
t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP
|
||||
t.config.peerVersion = rsp.Version
|
||||
t.config.peerConeNatPort = rsp.ConeNatPort
|
||||
t.config.peerIP = rsp.FromIP
|
||||
t.punchTs = rsp.PunchTs
|
||||
err := t.start()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handshake error:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) isRuning() bool {
|
||||
t.runMtx.Lock()
|
||||
defer t.runMtx.Unlock()
|
||||
return t.running
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) setRun(running bool) {
|
||||
t.runMtx.Lock()
|
||||
defer t.runMtx.Unlock()
|
||||
t.running = running
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) isActive() bool {
|
||||
if !t.isRuning() || t.conn == nil {
|
||||
return false
|
||||
}
|
||||
t.hbMtx.Lock()
|
||||
defer t.hbMtx.Unlock()
|
||||
res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2))
|
||||
if !res {
|
||||
gLog.Printf(LvDEBUG, "%d tunnel isActive false", t.id)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) checkActive() bool {
|
||||
if !t.isActive() {
|
||||
return false
|
||||
}
|
||||
hbt := time.Now()
|
||||
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
|
||||
isActive := false
|
||||
// wait at most 5s
|
||||
for i := 0; i < 50 && !isActive; i++ {
|
||||
t.hbMtx.Lock()
|
||||
if t.hbTime.After(hbt) {
|
||||
isActive = true
|
||||
}
|
||||
t.hbMtx.Unlock()
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
gLog.Printf(LvINFO, "checkActive %t. hbtime=%d", isActive, t.hbTime)
|
||||
return isActive
|
||||
}
|
||||
|
||||
// call when user delete tunnel
|
||||
func (t *P2PTunnel) close() {
|
||||
t.pn.NotifyTunnelClose(t)
|
||||
if !t.running {
|
||||
return
|
||||
}
|
||||
t.setRun(false)
|
||||
if t.conn != nil {
|
||||
t.conn.Close()
|
||||
}
|
||||
t.pn.allTunnels.Delete(t.id)
|
||||
gLog.Printf(LvINFO, "%d p2ptunnel close %s ", t.id, t.config.LogPeerNode())
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) start() error {
|
||||
if t.config.linkMode == LinkModeUDPPunch {
|
||||
if err := t.handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := t.connectUnderlay()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) handshake() error {
|
||||
if t.config.peerConeNatPort > 0 { // only peer is cone should prepare t.ra
|
||||
var err error
|
||||
t.ra, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
|
||||
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
|
||||
} else {
|
||||
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
|
||||
if ts > PunchTsDelay || ts < 0 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
}
|
||||
gLog.Println(LvDEBUG, "handshake to ", t.config.LogPeerNode())
|
||||
var err error
|
||||
if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone {
|
||||
err = handshakeC2C(t)
|
||||
} else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATSymmetric {
|
||||
err = ErrorS2S
|
||||
t.close()
|
||||
} else if t.config.peerNatType == NATSymmetric && gConf.Network.natType == NATCone {
|
||||
err = handshakeC2S(t)
|
||||
} else if t.config.peerNatType == NATCone && gConf.Network.natType == NATSymmetric {
|
||||
err = handshakeS2C(t)
|
||||
} else {
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "punch handshake error:", err)
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "handshake to %s ok", t.config.LogPeerNode())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlay() (err error) {
|
||||
switch t.config.linkMode {
|
||||
case LinkModeTCP6:
|
||||
t.conn, err = t.connectUnderlayTCP6()
|
||||
case LinkModeTCP4:
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
case LinkModeTCPPunch:
|
||||
if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric {
|
||||
t.conn, err = t.connectUnderlayTCPSymmetric()
|
||||
} else {
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
}
|
||||
case LinkModeIntranet:
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
case LinkModeUDPPunch:
|
||||
t.conn, err = t.connectUnderlayUDP()
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.conn == nil {
|
||||
return errors.New("connect underlay error")
|
||||
}
|
||||
t.setRun(true)
|
||||
go t.readLoop()
|
||||
go t.writeLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayUDP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.LogPeerNode())
|
||||
var ul underlay
|
||||
underlayProtocol := t.config.UnderlayProtocol
|
||||
if underlayProtocol == "" {
|
||||
underlayProtocol = "quic"
|
||||
}
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env
|
||||
go t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
if t.config.UnderlayProtocol == "kcp" {
|
||||
ul, err = listenKCP(t.la.String(), TunnelIdleTimeout)
|
||||
} else {
|
||||
ul, err = listenQuic(t.la.String(), TunnelIdleTimeout)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
gLog.Printf(LvINFO, "listen %s error:%s", underlayProtocol, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
}
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Printf(LvDEBUG, "%s connection ok", underlayProtocol)
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
//else
|
||||
conn, errL := net.ListenUDP("udp", t.la)
|
||||
if errL != nil {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
conn, errL = net.ListenUDP("udp", t.la)
|
||||
if errL != nil {
|
||||
return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL)
|
||||
}
|
||||
}
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.ra.String())
|
||||
if t.config.UnderlayProtocol == "kcp" {
|
||||
ul, errL = dialKCP(conn, t.ra, TunnelIdleTimeout)
|
||||
} else {
|
||||
ul, errL = dialQuic(conn, t.ra, TunnelIdleTimeout)
|
||||
}
|
||||
|
||||
if errL != nil {
|
||||
return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.ra.String(), errL)
|
||||
}
|
||||
handshakeBegin := time.Now()
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
|
||||
_, buff, err := ul.ReadBuffer() // TODO: kcp need timeout
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Printf(LvINFO, "%s connection ok", underlayProtocol)
|
||||
t.linkModeWeb = LinkModeUDPPunch
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.LogPeerNode())
|
||||
var ul *underlayTCP
|
||||
peerIP := t.config.peerIP
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
peerIP = t.config.peerLanIP
|
||||
}
|
||||
// server side
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
ul, err = listenTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode, t)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen TCP error:%s", err)
|
||||
}
|
||||
gLog.Println(LvINFO, "TCP connection ok")
|
||||
t.linkModeWeb = LinkModeIPv4
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
t.linkModeWeb = LinkModeIntranet
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
// client side
|
||||
if t.config.linkMode == LinkModeTCP4 {
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
} else { //tcp punch should sleep for punch the same time
|
||||
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
|
||||
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
|
||||
} else {
|
||||
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
|
||||
if ts > PunchTsDelay || ts < 0 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
}
|
||||
}
|
||||
ul, err = dialTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TCP dial to %s:%d error:%s", t.config.peerIP, t.config.peerConeNatPort, err)
|
||||
}
|
||||
handshakeBegin := time.Now()
|
||||
tidBuff := new(bytes.Buffer)
|
||||
binary.Write(tidBuff, binary.LittleEndian, t.id)
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, "hello ", string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvINFO, "TCP connection ok")
|
||||
t.linkModeWeb = LinkModeIPv4
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
t.linkModeWeb = LinkModeIntranet
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode())
|
||||
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
|
||||
if ts > PunchTsDelay || ts < 0 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
startTime := time.Now()
|
||||
t.linkModeWeb = LinkModeTCPPunch
|
||||
gotCh := make(chan *underlayTCP, 1)
|
||||
var wg sync.WaitGroup
|
||||
var success atomic.Int32
|
||||
if t.config.peerNatType == NATSymmetric { // c2s
|
||||
randPorts := rand.Perm(65532)
|
||||
for i := 0; i < SymmetricHandshakeNum; i++ {
|
||||
wg.Add(1)
|
||||
go func(port int) {
|
||||
defer wg.Done()
|
||||
ul, err := dialTCP(t.config.peerIP, port, t.coneLocalPort, LinkModeTCPPunch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !success.CompareAndSwap(0, 1) {
|
||||
ul.Close() // only cone side close
|
||||
return
|
||||
}
|
||||
err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
gLog.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 {
|
||||
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen TCP6 error:%s", err)
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
}
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Println(LvDEBUG, "TCP6 connection ok")
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
//else
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6)
|
||||
ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort)
|
||||
if err != nil || 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 := t.pn.relay(tunnelID, body[RelayHeaderSize:]); err != nil {
|
||||
gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.LogPeerNode(), t.id, tunnelID, len(body), ErrRelayTunnelNotFound)
|
||||
}
|
||||
case MsgRelayHeartbeat:
|
||||
req := RelayHeartbeat{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
continue
|
||||
}
|
||||
// TODO: debug relay heartbeat
|
||||
gLog.Printf(LvDEBUG, "read MsgRelayHeartbeat from rtid:%d,appid:%d", req.RelayTunnelID, req.AppID)
|
||||
// update app hbtime
|
||||
t.pn.updateAppHeartbeat(req.AppID)
|
||||
req.From = gConf.Network.Node
|
||||
t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req)
|
||||
case MsgRelayHeartbeatAck:
|
||||
req := RelayHeartbeat{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err)
|
||||
continue
|
||||
}
|
||||
// TODO: debug relay heartbeat
|
||||
gLog.Printf(LvDEBUG, "read MsgRelayHeartbeatAck to appid:%d", req.AppID)
|
||||
t.pn.updateAppHeartbeat(req.AppID)
|
||||
case MsgOverlayConnectReq:
|
||||
req := OverlayConnectReq{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
continue
|
||||
}
|
||||
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
|
||||
if req.Token != gConf.Network.Token {
|
||||
gLog.Println(LvERROR, "Access Denied:", req.Token)
|
||||
continue
|
||||
}
|
||||
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort)
|
||||
oConn := overlayConn{
|
||||
tunnel: t,
|
||||
id: overlayID,
|
||||
isClient: false,
|
||||
rtid: req.RelayTunnelID,
|
||||
appID: req.AppID,
|
||||
appKey: GetKey(req.AppID),
|
||||
running: true,
|
||||
}
|
||||
if req.Protocol == "udp" {
|
||||
oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(req.DstIP), Port: req.DstPort})
|
||||
} else {
|
||||
oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), ReadMsgTimeout)
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// calc key bytes for encrypt
|
||||
if oConn.appKey != 0 {
|
||||
encryptKey := make([]byte, AESKeySize)
|
||||
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
|
||||
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
|
||||
oConn.appKeyBytes = encryptKey
|
||||
}
|
||||
|
||||
t.overlayConns.Store(oConn.id, &oConn)
|
||||
go oConn.run()
|
||||
case MsgOverlayDisconnectReq:
|
||||
req := OverlayDisconnectReq{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
continue
|
||||
}
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LvDEBUG, "%d disconnect overlay connection %d", t.id, overlayID)
|
||||
i, ok := t.overlayConns.Load(overlayID)
|
||||
if ok {
|
||||
oConn := i.(*overlayConn)
|
||||
oConn.Close()
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
t.close()
|
||||
gLog.Printf(LvDEBUG, "%d tunnel readloop end", t.id)
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) writeLoop() {
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now() // init
|
||||
t.hbMtx.Unlock()
|
||||
tc := time.NewTicker(TunnelHeartbeatTime)
|
||||
defer tc.Stop()
|
||||
gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop start", t.config.LogPeerNode(), t.id)
|
||||
defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.LogPeerNode(), t.id)
|
||||
for t.isRuning() {
|
||||
select {
|
||||
case buff := <-t.writeDataSmall:
|
||||
t.conn.WriteBuffer(buff)
|
||||
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix())
|
||||
default:
|
||||
select {
|
||||
case buff := <-t.writeDataSmall:
|
||||
t.conn.WriteBuffer(buff)
|
||||
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix())
|
||||
case buff := <-t.writeData:
|
||||
t.conn.WriteBuffer(buff)
|
||||
case <-tc.C:
|
||||
// tunnel send
|
||||
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "%d write tunnel heartbeat error %s", t.id, err)
|
||||
t.close()
|
||||
return
|
||||
}
|
||||
gLog.Printf(LvDev, "%d write tunnel heartbeat ok", t.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) listen() error {
|
||||
// notify client to connect
|
||||
rsp := PushConnectRsp{
|
||||
Error: 0,
|
||||
Detail: "connect ok",
|
||||
To: t.config.PeerNode,
|
||||
From: gConf.Network.Node,
|
||||
NatType: gConf.Network.natType,
|
||||
HasIPv4: gConf.Network.hasIPv4,
|
||||
// IPv6: gConf.Network.IPv6,
|
||||
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
|
||||
FromIP: gConf.Network.publicIP,
|
||||
ConeNatPort: t.coneNatPort,
|
||||
ID: t.id,
|
||||
PunchTs: uint64(time.Now().UnixNano() + int64(PunchTsDelay) - t.pn.dt),
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
t.punchTs = rsp.PunchTs
|
||||
// only private node set ipv6
|
||||
if t.config.fromToken == gConf.Network.Token {
|
||||
rsp.IPv6 = gConf.IPv6()
|
||||
}
|
||||
|
||||
t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
|
||||
gLog.Printf(LvDEBUG, "p2ptunnel wait for connecting")
|
||||
t.tunnelServer = true
|
||||
return t.start()
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) closeOverlayConns(appID uint64) {
|
||||
t.overlayConns.Range(func(_, i interface{}) bool {
|
||||
oConn := i.(*overlayConn)
|
||||
if oConn.appID == appID {
|
||||
oConn.Close()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) {
|
||||
gLog.Printf(LvDev, "%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay)
|
||||
ch := t.pn.nodeData
|
||||
// if body[9] == 1 { // TODO: deal relay
|
||||
// ch = t.pn.nodeDataSmall
|
||||
// gLog.Printf(LvDEBUG, "read icmp %d", time.Now().Unix())
|
||||
// }
|
||||
if isRelay {
|
||||
fromPeerID := binary.LittleEndian.Uint64(body[:8])
|
||||
ch <- &NodeData{fromPeerID, body[8:]} // TODO: cache peerNodeID; encrypt/decrypt
|
||||
} else {
|
||||
ch <- &NodeData{NodeNameToID(t.config.PeerNode), body} // TODO: cache peerNodeID; encrypt/decrypt
|
||||
}
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) asyncWriteNodeData(mainType, subType uint16, data []byte) {
|
||||
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
|
||||
// if len(data) < 192 {
|
||||
if data[9] == 1 { // icmp
|
||||
select {
|
||||
case t.writeDataSmall <- writeBytes:
|
||||
// gLog.Printf(LvWARN, "%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall))
|
||||
default:
|
||||
gLog.Printf(LvWARN, "%s:%d t.writeDataSmall is full, drop it", t.config.LogPeerNode(), t.id)
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case t.writeData <- writeBytes:
|
||||
default:
|
||||
gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.LogPeerNode(), t.id)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, req interface{}) error {
|
||||
if rtid == 0 {
|
||||
return t.conn.WriteMessage(mainType, subType, &req)
|
||||
}
|
||||
relayHead := new(bytes.Buffer)
|
||||
binary.Write(relayHead, binary.LittleEndian, rtid)
|
||||
msg, _ := newMessage(mainType, subType, &req)
|
||||
msgWithHead := append(relayHead.Bytes(), msg...)
|
||||
return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead)
|
||||
|
||||
}
|
||||
24
core/p2ptunnel_test.go
Normal 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
core/ping.go
Normal 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)
|
||||
}
|
||||
}
|
||||
628
core/protocol.go
Normal file
@@ -0,0 +1,628 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"hash/crc64"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const OpenP2PVersion = "3.21.8"
|
||||
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 * 1
|
||||
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
|
||||
)
|
||||
|
||||
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
core/sdwan.go
Normal 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
core/speedlimiter.go
Normal 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
core/speedlimiter_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||