Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9dda148232 | ||
|
|
e7b696c474 | ||
|
|
7e57237ec9 | ||
|
|
52dfe5c938 | ||
|
|
b72ede9a6a | ||
|
|
b39fab2188 | ||
|
|
9b0525294a | ||
|
|
276a4433f1 | ||
|
|
e21adebc26 | ||
|
|
b2a7619bd6 | ||
|
|
fe4022ba6c | ||
|
|
82c74b4f85 | ||
|
|
0af65b7204 | ||
|
|
46b4f78010 | ||
|
|
8ebdf3341e | ||
|
|
b667e5b766 | ||
|
|
cd415e7bf4 | ||
|
|
67e3a8915a | ||
|
|
791d910314 | ||
|
|
c3a43be3cc | ||
|
|
c8b8bf05a5 | ||
|
|
8311341960 | ||
|
|
d5c098ca74 | ||
|
|
af82fc6e36 |
19
.gitignore
vendored
@@ -1,10 +1,23 @@
|
||||
__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
|
||||
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
|
||||
37
README-ZH.md
@@ -19,9 +19,9 @@
|
||||
|
||||
[查看详细](#安全性)
|
||||
### 4. 轻量
|
||||
文件大小2MB+,运行内存2MB+;全部在应用层实现,没有虚拟网卡,没有内核程序
|
||||
文件大小2MB+,运行内存2MB+;它可以仅跑在应用层,或者配合wintun驱动使用组网功能
|
||||
### 5. 跨平台
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le
|
||||
### 6. 高效
|
||||
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),UDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
|
||||
|
||||
@@ -31,6 +31,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
|
||||
## 快速入门
|
||||
仅需简单4步就能用起来。
|
||||
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
||||
(另外一个快速入门视频 https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||
### 1.注册
|
||||
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
||||
|
||||
@@ -98,9 +99,23 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
||||
go version go1.18.1+
|
||||
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
|
||||
```
|
||||
|
||||
## RoadMap
|
||||
@@ -111,15 +126,17 @@ go build
|
||||
4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%)
|
||||
5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备
|
||||
6. 客户端提供WebUI
|
||||
7. 支持自有服务器,开源服务器程序
|
||||
7. ~~支持自有服务器,开源服务器程序~~(100%)
|
||||
8. 共享节点调度模型优化,对不同的运营商优化
|
||||
9. 方便二次开发,提供API和lib
|
||||
10. 应用层支持UDP协议,实现很简单,但UDP应用较少暂不急(100%)
|
||||
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. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。
|
||||
|
||||
38
README.md
@@ -19,10 +19,10 @@ The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and t
|
||||
[details](#Safety)
|
||||
|
||||
### 4. Lightweight
|
||||
2MB+ filesize, 2MB+ memory. It runs at appllication layer, no vitrual NIC, no kernel driver.
|
||||
2MB+ filesize, 2MB+ memory. It could only runs at application layer, or uses wintun driver for SDWAN.
|
||||
|
||||
### 5. Cross-platform
|
||||
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64.
|
||||
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le.
|
||||
|
||||
### 6. Efficient
|
||||
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric),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.
|
||||
@@ -33,7 +33,7 @@ Your applicaiton can call OpenP2P with a few code to make any internal networks
|
||||
## Get Started
|
||||
Just 4 simple steps to use.
|
||||
Here's an example of remote work: connecting to an office Windows computer at home.
|
||||
|
||||
(Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||
### 1.Register
|
||||
Go to <https://console.openp2p.cn> register a new user
|
||||
|
||||
@@ -106,9 +106,24 @@ The server side has a scheduling model, which calculate bandwith, ping value,st
|
||||
go version go1.18.1+
|
||||
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
|
||||
```
|
||||
|
||||
## RoadMap
|
||||
@@ -119,16 +134,17 @@ Short-Term:
|
||||
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 private server, open source server program.
|
||||
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.
|
||||
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.
|
||||
12. Support Android platform, let the phones to be mobile gateway.
|
||||
13. Support SMB Windows neighborhood.
|
||||
14. Direct connection on intranet, for testing.
|
||||
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.
|
||||
|
||||
15
USAGE-ZH.md
@@ -43,7 +43,7 @@ nohup ./openp2p -d -node OFFICEPC1 -token TOKEN &
|
||||
```
|
||||
{
|
||||
"network": {
|
||||
"Node": "hhd1207-222",
|
||||
"Node": "YOUR-NODE-NAME",
|
||||
"Token": "TOKEN",
|
||||
"ShareBandwidth": 0,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
@@ -87,7 +87,8 @@ systemctl stop firewalld.service
|
||||
systemctl start firewalld.service
|
||||
firewall-cmd --state
|
||||
```
|
||||
|
||||
## 停止
|
||||
TODO: windows linux macos
|
||||
## 卸载
|
||||
```
|
||||
./openp2p uninstall
|
||||
@@ -96,4 +97,12 @@ firewall-cmd --state
|
||||
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
|
||||
```
|
||||
|
||||
10
USAGE.md
@@ -46,7 +46,7 @@ Configuration example
|
||||
```
|
||||
{
|
||||
"network": {
|
||||
"Node": "hhd1207-222",
|
||||
"Node": "YOUR-NODE-NAME",
|
||||
"Token": "TOKEN",
|
||||
"ShareBandwidth": 0,
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
@@ -98,4 +98,12 @@ firewall-cmd --state
|
||||
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>
|
||||
32
app/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Build
|
||||
depends on openjdk 11, gradle 8.1.3, ndk 21
|
||||
```
|
||||
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
go get -v golang.org/x/mobile/bind
|
||||
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 {
|
||||
ts time.Time
|
||||
bw int // mbps
|
||||
freeBytes int // bytes
|
||||
maxFreeBytes int // bytes
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
// mbps
|
||||
func newBandwidthLimiter(bw int) *BandwidthLimiter {
|
||||
return &BandwidthLimiter{
|
||||
bw: bw,
|
||||
ts: time.Now(),
|
||||
maxFreeBytes: bw * 1024 * 1024 / 8,
|
||||
freeBytes: bw * 1024 * 1024 / 8,
|
||||
}
|
||||
}
|
||||
|
||||
// Add ...
|
||||
func (bl *BandwidthLimiter) Add(bytes int) {
|
||||
if bl.bw <= 0 {
|
||||
return
|
||||
}
|
||||
bl.mtx.Lock()
|
||||
defer bl.mtx.Unlock()
|
||||
// calc free flow 1000*1000/1024/1024=0.954; 1024*1024/1000/1000=1.048
|
||||
bl.freeBytes += int(time.Since(bl.ts) * time.Duration(bl.bw) / 8 / 954)
|
||||
if bl.freeBytes > bl.maxFreeBytes {
|
||||
bl.freeBytes = bl.maxFreeBytes
|
||||
}
|
||||
bl.freeBytes -= bytes
|
||||
bl.ts = time.Now()
|
||||
if bl.freeBytes < 0 {
|
||||
// sleep for the overflow
|
||||
time.Sleep(time.Millisecond * time.Duration(-bl.freeBytes/(bl.bw*1048/8)))
|
||||
}
|
||||
}
|
||||
9
cmd/openp2p.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
op "openp2p/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op.Run()
|
||||
}
|
||||
267
config.go
@@ -1,267 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var gConf Config
|
||||
|
||||
type AppConfig struct {
|
||||
// required
|
||||
AppName string
|
||||
Protocol string
|
||||
SrcPort int
|
||||
PeerNode string
|
||||
DstPort int
|
||||
DstHost string
|
||||
PeerUser string
|
||||
Enabled int // default:1
|
||||
// runtime info
|
||||
peerVersion string
|
||||
peerToken uint64
|
||||
peerNatType int
|
||||
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 // TODO: bool?
|
||||
}
|
||||
|
||||
// TODO: add loglevel, maxlogfilesize
|
||||
type Config struct {
|
||||
Network NetworkConfig `json:"network"`
|
||||
Apps []*AppConfig `json:"apps"`
|
||||
LogLevel int
|
||||
daemonMode bool
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
func (c *Config) switchApp(app AppConfig, enabled int) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
c.Apps[i].Enabled = enabled
|
||||
c.Apps[i].retryNum = 0
|
||||
c.Apps[i].nextRetryTime = time.Now()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) add(app AppConfig, override bool) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
if app.SrcPort == 0 || app.DstPort == 0 {
|
||||
gLog.Println(LvERROR, "invalid app ", app)
|
||||
return
|
||||
}
|
||||
if override {
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
c.Apps[i] = &app // override it
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Apps = append(c.Apps, &app)
|
||||
}
|
||||
|
||||
func (c *Config) delete(app AppConfig) {
|
||||
if app.SrcPort == 0 || app.DstPort == 0 {
|
||||
return
|
||||
}
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) save() {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
data, _ := json.MarshalIndent(c, "", " ")
|
||||
err := ioutil.WriteFile("config.json", data, 0644)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "save config.json error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
gConf.LogLevel = 1
|
||||
gConf.Network.ShareBandwidth = 10
|
||||
gConf.Network.ServerHost = "api.openp2p.cn"
|
||||
gConf.Network.ServerPort = WsPort
|
||||
|
||||
}
|
||||
|
||||
func (c *Config) load() error {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
data, err := ioutil.ReadFile("config.json")
|
||||
if err != nil {
|
||||
// gLog.Println(LevelERROR, "read config.json error:", err)
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, &c)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "parse config.json error:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) setToken(token uint64) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.Network.Token = token
|
||||
}
|
||||
func (c *Config) setUser(user string) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.Network.User = user
|
||||
}
|
||||
func (c *Config) setNode(node string) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.Network.Node = node
|
||||
}
|
||||
func (c *Config) setShareBandwidth(bw int) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.Network.ShareBandwidth = bw
|
||||
}
|
||||
|
||||
type NetworkConfig struct {
|
||||
// local info
|
||||
Token uint64
|
||||
Node string
|
||||
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) {
|
||||
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 ")
|
||||
dstPort := fset.Int("dstport", 0, "destination port ")
|
||||
srcPort := fset.Int("srcport", 0, "source port ")
|
||||
tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip")
|
||||
protocol := fset.String("protocol", "tcp", "tcp or udp")
|
||||
appName := fset.String("appname", "", "app name")
|
||||
shareBandwidth := fset.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
|
||||
daemonMode := fset.Bool("d", false, "daemonMode")
|
||||
notVerbose := fset.Bool("nv", false, "not log console")
|
||||
newconfig := fset.Bool("newconfig", false, "not load existing config.json")
|
||||
logLevel := fset.Int("loglevel", 0, "0:info 1:warn 2:error 3:debug")
|
||||
if subCommand == "" { // no subcommand
|
||||
fset.Parse(os.Args[1:])
|
||||
} else {
|
||||
fset.Parse(os.Args[2:])
|
||||
}
|
||||
|
||||
config := AppConfig{Enabled: 1}
|
||||
config.PeerNode = *peerNode
|
||||
config.DstHost = *dstIP
|
||||
config.DstPort = *dstPort
|
||||
config.SrcPort = *srcPort
|
||||
config.Protocol = *protocol
|
||||
config.AppName = *appName
|
||||
if !*newconfig {
|
||||
gConf.load() // load old config. otherwise will clear all apps
|
||||
}
|
||||
if config.SrcPort != 0 {
|
||||
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.Network.Node = *node
|
||||
}
|
||||
if f.Name == "serverhost" {
|
||||
gConf.Network.ServerHost = *serverHost
|
||||
}
|
||||
if f.Name == "loglevel" {
|
||||
gConf.LogLevel = *logLevel
|
||||
}
|
||||
if f.Name == "tcpport" {
|
||||
gConf.Network.TCPPort = *tcpPort
|
||||
}
|
||||
if f.Name == "token" {
|
||||
gConf.Network.Token = *token
|
||||
}
|
||||
})
|
||||
|
||||
if gConf.Network.ServerHost == "" {
|
||||
gConf.Network.ServerHost = *serverHost
|
||||
}
|
||||
if *node != "" {
|
||||
if len(*node) < MinNodeNameLen {
|
||||
gLog.Println(LvERROR, ErrNodeTooShort)
|
||||
os.Exit(9)
|
||||
}
|
||||
gConf.Network.Node = *node
|
||||
} else {
|
||||
if gConf.Network.Node == "" { // if node name not set. use os.Hostname
|
||||
gConf.Network.Node = defaultNodeName()
|
||||
}
|
||||
}
|
||||
if gConf.Network.TCPPort == 0 {
|
||||
if *tcpPort == 0 {
|
||||
p := int(nodeNameToID(gConf.Network.Node)%15000 + 50000)
|
||||
tcpPort = &p
|
||||
}
|
||||
gConf.Network.TCPPort = *tcpPort
|
||||
}
|
||||
|
||||
gConf.Network.ServerPort = *serverPort
|
||||
gConf.Network.UDPPort1 = UDPPort1
|
||||
gConf.Network.UDPPort2 = UDPPort2
|
||||
gLog.setLevel(LogLevel(gConf.LogLevel))
|
||||
if *notVerbose {
|
||||
gLog.setMode(LogFile)
|
||||
}
|
||||
// gConf.mtx.Unlock()
|
||||
gConf.save()
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -20,7 +22,6 @@ import (
|
||||
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 ""
|
||||
@@ -127,19 +128,18 @@ 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(LvINFO, "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(LvINFO, "netInfo error:", err)
|
||||
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||
continue
|
||||
}
|
||||
rsp := NetInfo{}
|
||||
err = json.Unmarshal(buf[:n], &rsp)
|
||||
if err != nil {
|
||||
if err = json.Unmarshal(buf[:n], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong NetInfo:%s", err)
|
||||
continue
|
||||
}
|
||||
@@ -199,8 +199,12 @@ func parseMajorVer(ver string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func IsIPv6(address string) bool {
|
||||
return strings.Count(address, ":") >= 2
|
||||
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-")
|
||||
@@ -212,3 +216,67 @@ func randStr(n int) string {
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -94,3 +94,23 @@ func TestParseMajorVer(t *testing.T) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
497
core/config.go
Normal file
@@ -0,0 +1,497 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"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
|
||||
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
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Network NetworkConfig `json:"network"`
|
||||
Apps []*AppConfig `json:"apps"`
|
||||
|
||||
LogLevel 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) 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 ", peerNode)
|
||||
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 ", peerNode)
|
||||
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.PeerNode)
|
||||
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.PeerNode)
|
||||
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++ {
|
||||
got := false
|
||||
if app.SrcPort != 0 { // normal p2papp
|
||||
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
got = true
|
||||
}
|
||||
} else { // memapp
|
||||
if c.Apps[i].PeerNode == app.PeerNode {
|
||||
got = true
|
||||
}
|
||||
}
|
||||
if got {
|
||||
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
|
||||
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
|
||||
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.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 == "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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
"github.com/openp2p-cn/service"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
@@ -44,9 +44,9 @@ func (d *daemon) run() {
|
||||
}
|
||||
gLog.Println(LvINFO, mydir)
|
||||
conf := &service.Config{
|
||||
Name: ProducnName,
|
||||
DisplayName: ProducnName,
|
||||
Description: ProducnName,
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: binPath,
|
||||
}
|
||||
|
||||
@@ -95,9 +95,9 @@ func (d *daemon) run() {
|
||||
|
||||
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
|
||||
svcConfig := &service.Config{
|
||||
Name: ProducnName,
|
||||
DisplayName: ProducnName,
|
||||
Description: ProducnName,
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: exeAbsPath,
|
||||
Arguments: args,
|
||||
}
|
||||
32
core/errorcode.go
Normal file
@@ -0,0 +1,32 @@
|
||||
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")
|
||||
)
|
||||
460
core/handlepush.go
Normal file
@@ -0,0 +1,460 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"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
|
||||
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 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]))
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,14 +10,9 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// examples:
|
||||
// listen:
|
||||
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0
|
||||
// listen and build p2papp:
|
||||
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
|
||||
func install() {
|
||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com")
|
||||
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
|
||||
@@ -35,7 +30,7 @@ func install() {
|
||||
|
||||
uninstall()
|
||||
// save config file
|
||||
parseParams("install")
|
||||
parseParams("install", "")
|
||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
d := daemon{}
|
||||
// copy files
|
||||
@@ -74,6 +69,7 @@ func install() {
|
||||
} else {
|
||||
gLog.Println(LvINFO, "start openp2p service ok.")
|
||||
}
|
||||
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
|
||||
}
|
||||
|
||||
func installByFilename() {
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -13,6 +13,7 @@ type LogLevel int
|
||||
var gLog *logger
|
||||
|
||||
const (
|
||||
LvDev LogLevel = -1
|
||||
LvDEBUG LogLevel = iota
|
||||
LvINFO
|
||||
LvWARN
|
||||
@@ -32,13 +33,13 @@ func init() {
|
||||
loglevel[LvINFO] = "INFO"
|
||||
loglevel[LvWARN] = "WARN"
|
||||
loglevel[LvERROR] = "ERROR"
|
||||
loglevel[LvDev] = "Dev"
|
||||
|
||||
}
|
||||
|
||||
const (
|
||||
LogFile = iota
|
||||
LogConsole
|
||||
LogFileAndConsole
|
||||
LogFile = 1
|
||||
LogConsole = 1 << 1
|
||||
)
|
||||
|
||||
type logger struct {
|
||||
@@ -51,6 +52,7 @@ type logger struct {
|
||||
pid int
|
||||
maxLogSize int64
|
||||
mode int
|
||||
stdLogger *log.Logger
|
||||
}
|
||||
|
||||
func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger {
|
||||
@@ -67,13 +69,13 @@ func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64,
|
||||
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, 0666)
|
||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Chmod(logFilePath, 0666)
|
||||
os.Chmod(logFilePath, 0644)
|
||||
logfiles[lv] = f
|
||||
loggers[lv] = log.New(f, "", log.LstdFlags)
|
||||
loggers[lv] = log.New(f, "", log.LstdFlags|log.Lmicroseconds)
|
||||
}
|
||||
var le string
|
||||
if runtime.GOOS == "windows" {
|
||||
@@ -81,7 +83,8 @@ func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64,
|
||||
} else {
|
||||
le = "\n"
|
||||
}
|
||||
pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode}
|
||||
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
|
||||
}
|
||||
@@ -91,6 +94,13 @@ func (l *logger) setLevel(level LogLevel) {
|
||||
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()
|
||||
@@ -119,7 +129,7 @@ func (l *logger) checkFile() {
|
||||
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, 0666)
|
||||
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
|
||||
@@ -138,11 +148,11 @@ func (l *logger) Printf(level LogLevel, format string, params ...interface{}) {
|
||||
}
|
||||
pidAndLevel := []interface{}{l.pid, loglevel[level]}
|
||||
params = append(pidAndLevel, params...)
|
||||
if l.mode == LogFile || l.mode == LogFileAndConsole {
|
||||
if l.mode&LogFile != 0 {
|
||||
l.loggers[0].Printf("%d %s "+format+l.lineEnding, params...)
|
||||
}
|
||||
if l.mode == LogConsole || l.mode == LogFileAndConsole {
|
||||
log.Printf("%d %s "+format+l.lineEnding, params...)
|
||||
if l.mode&LogConsole != 0 {
|
||||
l.stdLogger.Printf("%d %s "+format+l.lineEnding, params...)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +165,10 @@ func (l *logger) Println(level LogLevel, params ...interface{}) {
|
||||
pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "}
|
||||
params = append(pidAndLevel, params...)
|
||||
params = append(params, l.lineEnding)
|
||||
if l.mode == LogFile || l.mode == LogFileAndConsole {
|
||||
if l.mode&LogFile != 0 {
|
||||
l.loggers[0].Print(params...)
|
||||
}
|
||||
if l.mode == LogConsole || l.mode == LogFileAndConsole {
|
||||
log.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)
|
||||
}
|
||||
87
core/optun_darwin.go
Normal file
@@ -0,0 +1,87 @@
|
||||
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, 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) >= 7 && fields[0] == "default" && fields[len(fields)-1] == gateway {
|
||||
delCmd := exec.Command("route", "delete", "default", gateway)
|
||||
err := delCmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Delete route ok: %s %s\n", "default", gateway)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func addTunAddr(localAddr, remoteAddr string) error {
|
||||
return nil
|
||||
}
|
||||
func delTunAddr(localAddr, remoteAddr string) error {
|
||||
return nil
|
||||
}
|
||||
133
core/optun_linux.go
Normal file
@@ -0,0 +1,133 @@
|
||||
//go:build !android
|
||||
// +build !android
|
||||
|
||||
// optun_linux.go
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/openp2p-cn/wireguard-go/tun"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
)
|
||||
|
||||
var previousIP = ""
|
||||
|
||||
func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
var err error
|
||||
t.tunName = tunIfaceName
|
||||
t.dev, err = tun.CreateTUN(t.tunName, 1420)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||
return t.dev.Read(bufs, sizes, offset)
|
||||
}
|
||||
|
||||
func (t *optun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
return t.dev.Write(bufs, offset)
|
||||
}
|
||||
|
||||
func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error {
|
||||
ifce, err := netlink.LinkByName(ifname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netlink.LinkSetMTU(ifce, 1375)
|
||||
netlink.LinkSetTxQLen(ifce, 100)
|
||||
netlink.LinkSetUp(ifce)
|
||||
|
||||
ln, err := netlink.ParseIPNet(localAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ln.Mask = net.CIDRMask(32, 32)
|
||||
rn, err := netlink.ParseIPNet(remoteAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rn.Mask = net.CIDRMask(32, 32)
|
||||
|
||||
addr := &netlink.Addr{
|
||||
IPNet: ln,
|
||||
Peer: rn,
|
||||
}
|
||||
if previousIP != "" {
|
||||
lnDel, err := netlink.ParseIPNet(previousIP)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lnDel.Mask = net.CIDRMask(32, 32)
|
||||
|
||||
addrDel := &netlink.Addr{
|
||||
IPNet: lnDel,
|
||||
Peer: rn,
|
||||
}
|
||||
netlink.AddrDel(ifce, addrDel)
|
||||
}
|
||||
previousIP = localAddr
|
||||
return netlink.AddrAdd(ifce, addr)
|
||||
}
|
||||
|
||||
func addRoute(dst, gw, ifname string) error {
|
||||
_, networkid, err := net.ParseCIDR(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ipGW := net.ParseIP(gw)
|
||||
if ipGW == nil {
|
||||
return fmt.Errorf("parse gateway %s failed", gw)
|
||||
}
|
||||
route := &netlink.Route{
|
||||
Dst: networkid,
|
||||
Gw: ipGW,
|
||||
}
|
||||
return netlink.RouteAdd(route)
|
||||
}
|
||||
|
||||
func delRoute(dst, gw string) error {
|
||||
_, networkid, err := net.ParseCIDR(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
route := &netlink.Route{
|
||||
Dst: networkid,
|
||||
}
|
||||
return netlink.RouteDel(route)
|
||||
}
|
||||
|
||||
func delRoutesByGateway(gateway string) error {
|
||||
cmd := exec.Command("route", "-n")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, gateway) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway {
|
||||
delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway)
|
||||
err := delCmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
142
core/optun_windows.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/openp2p-cn/wireguard-go/tun"
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
|
||||
)
|
||||
|
||||
const (
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
)
|
||||
|
||||
func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
// check wintun.dll
|
||||
tmpFile := filepath.Dir(os.Args[0]) + "/wintun.dll"
|
||||
fs, err := os.Stat(tmpFile)
|
||||
if err != nil || fs.Size() == 0 {
|
||||
url := fmt.Sprintf("https://openp2p.cn/download/v1/latest/wintun/%s/wintun.dll", runtime.GOARCH)
|
||||
err = downloadFile(url, "", tmpFile)
|
||||
if err != nil {
|
||||
os.Remove(tmpFile)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
t.tunName = tunIfaceName
|
||||
|
||||
uuid := &windows.GUID{
|
||||
Data1: 0xf411e821,
|
||||
Data2: 0xb310,
|
||||
Data3: 0x4567,
|
||||
Data4: [8]byte{0x80, 0x42, 0x83, 0x7e, 0xf4, 0x56, 0xce, 0x13},
|
||||
}
|
||||
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420)
|
||||
if err != nil { // retry
|
||||
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||
return t.dev.Read(bufs, sizes, offset)
|
||||
}
|
||||
|
||||
func (t *optun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
return t.dev.Write(bufs, offset)
|
||||
}
|
||||
|
||||
func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error {
|
||||
nativeTunDevice := wintun.(*tun.NativeTun)
|
||||
link := winipcfg.LUID(nativeTunDevice.LUID())
|
||||
ip, err := netip.ParsePrefix(localAddr)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr)
|
||||
return err
|
||||
}
|
||||
err = link.SetIPAddresses([]netip.Prefix{ip})
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRoute(dst, gw, ifname string) error {
|
||||
_, dstNet, err := net.ParseCIDR(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := net.InterfaceByName(ifname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := make([]string, 0)
|
||||
params = append(params, "add")
|
||||
params = append(params, dstNet.IP.String())
|
||||
params = append(params, "mask")
|
||||
params = append(params, net.IP(dstNet.Mask).String())
|
||||
params = append(params, gw)
|
||||
params = append(params, "if")
|
||||
params = append(params, strconv.Itoa(i.Index))
|
||||
// gLogger.Println(LevelINFO, "windows add route params:", params)
|
||||
execCommand("route", true, params...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func delRoute(dst, gw string) error {
|
||||
_, dstNet, err := net.ParseCIDR(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := make([]string, 0)
|
||||
params = append(params, "delete")
|
||||
params = append(params, dstNet.IP.String())
|
||||
params = append(params, "mask")
|
||||
params = append(params, net.IP(dstNet.Mask).String())
|
||||
params = append(params, gw)
|
||||
// gLogger.Println(LevelINFO, "windows delete route params:", params)
|
||||
execCommand("route", true, params...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func delRoutesByGateway(gateway string) error {
|
||||
cmd := exec.Command("route", "print", "-4")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, gateway) {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 5 {
|
||||
cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
fmt.Println("Delete route error:", err)
|
||||
}
|
||||
fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -23,36 +23,36 @@ func (e *DeadlineExceededError) Temporary() bool { return true }
|
||||
|
||||
// implement io.Writer
|
||||
type overlayConn struct {
|
||||
tunnel *P2PTunnel
|
||||
tunnel *P2PTunnel // TODO: del
|
||||
app *p2pApp
|
||||
connTCP net.Conn
|
||||
id uint64
|
||||
rtid uint64
|
||||
running bool
|
||||
isClient bool
|
||||
appID uint64
|
||||
appKey uint64
|
||||
appKeyBytes []byte
|
||||
appID uint64 // TODO: del
|
||||
appKey uint64 // TODO: del
|
||||
appKeyBytes []byte // TODO: del
|
||||
// for udp
|
||||
connUDP *net.UDPConn
|
||||
remoteAddr net.Addr
|
||||
udpRelayData chan []byte
|
||||
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.running = true
|
||||
oConn.lastReadUDPTs = time.Now()
|
||||
buffer := make([]byte, ReadBuffLen+PaddingSize)
|
||||
readBuf := buffer[:ReadBuffLen]
|
||||
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() {
|
||||
buff, dataLen, err := oConn.Read(readBuf)
|
||||
readBuff, dataLen, err := oConn.Read(reuseBuff)
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
continue
|
||||
@@ -61,20 +61,21 @@ func (oConn *overlayConn) run() {
|
||||
gLog.Printf(LvDEBUG, "overlayConn %d read error:%s,close it", oConn.id, err)
|
||||
break
|
||||
}
|
||||
payload := buff[:dataLen]
|
||||
payload := readBuff[:dataLen]
|
||||
if oConn.appKey != 0 {
|
||||
payload, _ = encryptBytes(oConn.appKeyBytes, encryptData, buffer[:dataLen], dataLen)
|
||||
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(LvDEBUG, "write overlay data to %d:%d bodylen=%d", oConn.rtid, oConn.id, len(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(LvDEBUG, "write relay data to %d:%d bodylen=%d", oConn.rtid, oConn.id, len(writeBytes))
|
||||
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 {
|
||||
@@ -85,20 +86,15 @@ func (oConn *overlayConn) run() {
|
||||
}
|
||||
oConn.tunnel.overlayConns.Delete(oConn.id)
|
||||
// notify peer disconnect
|
||||
if oConn.isClient {
|
||||
req := OverlayDisconnectReq{ID: oConn.id}
|
||||
if oConn.rtid == 0 {
|
||||
oConn.tunnel.conn.WriteMessage(MsgP2P, MsgOverlayDisconnectReq, &req)
|
||||
} else {
|
||||
// write relay data
|
||||
msg, _ := newMessage(MsgP2P, MsgOverlayDisconnectReq, &req)
|
||||
msgWithHead := append(relayHead.Bytes(), msg...)
|
||||
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
|
||||
}
|
||||
}
|
||||
req := OverlayDisconnectReq{ID: oConn.id}
|
||||
oConn.tunnel.WriteMessage(oConn.rtid, MsgP2P, MsgOverlayDisconnectReq, &req)
|
||||
}
|
||||
|
||||
func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, n int, err error) {
|
||||
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")
|
||||
@@ -106,15 +102,15 @@ func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, n int, err error)
|
||||
}
|
||||
if oConn.remoteAddr != nil { // as server
|
||||
select {
|
||||
case buff = <-oConn.udpRelayData:
|
||||
n = len(buff)
|
||||
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(5 * time.Second))
|
||||
n, _, err = oConn.connUDP.ReadFrom(reuseBuff)
|
||||
oConn.connUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
|
||||
dataLen, _, err = oConn.connUDP.ReadFrom(reuseBuff)
|
||||
if err == nil {
|
||||
oConn.lastReadUDPTs = time.Now()
|
||||
}
|
||||
@@ -122,15 +118,21 @@ func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, n int, err error)
|
||||
}
|
||||
return
|
||||
}
|
||||
oConn.connTCP.SetReadDeadline(time.Now().Add(time.Second * 5))
|
||||
n, err = oConn.connTCP.Read(reuseBuff)
|
||||
buff = reuseBuff
|
||||
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)
|
||||
@@ -142,9 +144,25 @@ func (oConn *overlayConn) Write(buff []byte) (n int, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
n, err = oConn.connTCP.Write(buff)
|
||||
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.PeerNode, 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.PeerNode)
|
||||
}
|
||||
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.PeerNode, 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.PeerNode)
|
||||
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.PeerNode, 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.PeerNode, 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.PeerNode)
|
||||
}
|
||||
}
|
||||
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.PeerNode, 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.PeerNode)
|
||||
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.PeerNode, 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.PeerNode, app.id, app.rtid)
|
||||
defer gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid%d end", app.config.PeerNode, 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.PeerNode, 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.PeerNode, app.id, app.rtid)
|
||||
time.Sleep(TunnelHeartbeatTime)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"sync"
|
||||
978
core/p2pnetwork.go
Normal file
@@ -0,0 +1,978 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var (
|
||||
v4l *v4Listener
|
||||
instance *P2PNetwork
|
||||
onceP2PNetwork sync.Once
|
||||
onceV4Listener sync.Once
|
||||
)
|
||||
|
||||
const (
|
||||
retryLimit = 20
|
||||
retryInterval = 10 * time.Second
|
||||
DefaultLoginMaxDelaySeconds = 60
|
||||
)
|
||||
|
||||
// golang not support float64 const
|
||||
var (
|
||||
ma10 float64 = 1.0 / 10
|
||||
ma5 float64 = 1.0 / 5
|
||||
)
|
||||
|
||||
type NodeData struct {
|
||||
NodeID uint64
|
||||
Data []byte
|
||||
}
|
||||
|
||||
type P2PNetwork struct {
|
||||
conn *websocket.Conn
|
||||
online bool
|
||||
running bool
|
||||
restartCh chan bool
|
||||
wgReconnect sync.WaitGroup
|
||||
writeMtx sync.Mutex
|
||||
reqGatewayMtx sync.Mutex
|
||||
hbTime time.Time
|
||||
// for sync server time
|
||||
t1 int64 // nanoSeconds
|
||||
preRtt int64 // nanoSeconds
|
||||
dt int64 // client faster then server dt nanoSeconds
|
||||
ddtma int64
|
||||
ddt int64 // differential of dt
|
||||
msgMap sync.Map //key: nodeID
|
||||
// msgMap map[uint64]chan pushMsg //key: nodeID
|
||||
allTunnels sync.Map // key: tid
|
||||
apps sync.Map //key: config.ID(); value: *p2pApp
|
||||
limiter *SpeedLimiter
|
||||
nodeData chan *NodeData
|
||||
sdwan *p2pSDWAN
|
||||
tunnelCloseCh chan *P2PTunnel
|
||||
loginMaxDelaySeconds int
|
||||
}
|
||||
|
||||
type msgCtx struct {
|
||||
data []byte
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
func P2PNetworkInstance() *P2PNetwork {
|
||||
if instance == nil {
|
||||
onceP2PNetwork.Do(func() {
|
||||
instance = &P2PNetwork{
|
||||
restartCh: make(chan bool, 1),
|
||||
tunnelCloseCh: make(chan *P2PTunnel, 100),
|
||||
nodeData: make(chan *NodeData, 10000),
|
||||
online: false,
|
||||
running: true,
|
||||
limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1),
|
||||
dt: 0,
|
||||
ddt: 0,
|
||||
loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds,
|
||||
}
|
||||
instance.msgMap.Store(uint64(0), make(chan msgCtx, 50)) // for gateway
|
||||
instance.StartSDWAN()
|
||||
instance.init()
|
||||
go instance.run()
|
||||
go func() {
|
||||
for {
|
||||
instance.refreshIPv6()
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
}()
|
||||
cleanTempFiles()
|
||||
})
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) run() {
|
||||
heartbeatTimer := time.NewTicker(NetworkHeartbeatTime)
|
||||
pn.t1 = time.Now().UnixNano()
|
||||
pn.write(MsgHeartbeat, 0, "")
|
||||
for {
|
||||
select {
|
||||
case <-heartbeatTimer.C:
|
||||
pn.t1 = time.Now().UnixNano()
|
||||
pn.write(MsgHeartbeat, 0, "")
|
||||
case <-pn.restartCh:
|
||||
gLog.Printf(LvDEBUG, "got restart channel")
|
||||
pn.online = false
|
||||
pn.wgReconnect.Wait() // wait read/autorunapp goroutine end
|
||||
delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second
|
||||
time.Sleep(delay)
|
||||
err := pn.init()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
}
|
||||
gConf.retryAllApp()
|
||||
case t := <-pn.tunnelCloseCh:
|
||||
gLog.Printf(LvDEBUG, "got tunnelCloseCh %s", t.config.PeerNode)
|
||||
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.PeerNode)
|
||||
defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.PeerNode)
|
||||
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.PeerNode)
|
||||
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", rsp.RelayName)
|
||||
|
||||
relayConfig.PeerNode = rsp.RelayName
|
||||
relayConfig.peerToken = rsp.RelayToken
|
||||
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: relayMode,
|
||||
RelayTunnelID: t.id,
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "push %s the relay node(%s)", config.PeerNode, relayConfig.PeerNode)
|
||||
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, 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.PeerNode, config.DstHost, config.DstPort)
|
||||
defer gLog.Printf(LvINFO, "addApp %s to %s:%s:%d end", config.AppName, config.PeerNode, 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.PeerNode, config.DstHost, config.DstPort)
|
||||
defer gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d end", config.AppName, config.PeerNode, 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.PeerNode, config.DstHost, config.DstPort, tid)
|
||||
defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.PeerNode, 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.PeerNode, 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.PeerNode, config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType)
|
||||
// try Intranet
|
||||
if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP
|
||||
gLog.Println(LvINFO, "try Intranet")
|
||||
config.linkMode = LinkModeIntranet
|
||||
config.isUnderlayServer = 0
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
// try TCP6
|
||||
if IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) {
|
||||
gLog.Println(LvINFO, "try TCP6")
|
||||
config.linkMode = LinkModeTCP6
|
||||
config.isUnderlayServer = 0
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
// try UDP6? maybe no
|
||||
|
||||
// try TCP4
|
||||
if config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
gLog.Println(LvINFO, "try TCP4")
|
||||
config.linkMode = LinkModeTCP4
|
||||
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
config.isUnderlayServer = 1
|
||||
} else {
|
||||
config.isUnderlayServer = 0
|
||||
}
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
// try UDP4? maybe no
|
||||
var primaryPunchFunc func() (*P2PTunnel, error)
|
||||
var secondaryPunchFunc func() (*P2PTunnel, error)
|
||||
funcUDP := func() (t *P2PTunnel, err error) {
|
||||
if config.PunchPriority&PunchPriorityUDPDisable != 0 {
|
||||
return
|
||||
}
|
||||
// try UDPPunch
|
||||
for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
|
||||
if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
|
||||
gLog.Println(LvINFO, "try UDP4 Punch")
|
||||
config.linkMode = LinkModeUDPPunch
|
||||
config.isUnderlayServer = 0
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
if !(config.peerNatType == NATCone && gConf.Network.natType == NATCone) { // not cone2cone, no more try
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
funcTCP := func() (t *P2PTunnel, err error) {
|
||||
if config.PunchPriority&PunchPriorityTCPDisable != 0 {
|
||||
return
|
||||
}
|
||||
// try TCPPunch
|
||||
for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
|
||||
if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
|
||||
gLog.Println(LvINFO, "try TCP4 Punch")
|
||||
config.linkMode = LinkModeTCPPunch
|
||||
config.isUnderlayServer = 0
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
gLog.Println(LvINFO, "TCP4 Punch ok")
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
if config.PunchPriority&PunchPriorityTCPFirst != 0 {
|
||||
primaryPunchFunc = funcTCP
|
||||
secondaryPunchFunc = funcUDP
|
||||
} else {
|
||||
primaryPunchFunc = funcTCP
|
||||
secondaryPunchFunc = funcUDP
|
||||
}
|
||||
if t, err = primaryPunchFunc(); t != nil && err == nil {
|
||||
return t, err
|
||||
}
|
||||
if t, err = secondaryPunchFunc(); t != nil && err == nil {
|
||||
return t, err
|
||||
}
|
||||
|
||||
// TODO: s2s won't return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) {
|
||||
if isClient { // only client side find existing tunnel
|
||||
if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil {
|
||||
return existTunnel, nil
|
||||
}
|
||||
}
|
||||
|
||||
t = &P2PTunnel{pn: pn,
|
||||
config: config,
|
||||
id: tid,
|
||||
writeData: make(chan []byte, WriteDataChanSize),
|
||||
writeDataSmall: make(chan []byte, WriteDataChanSize/30),
|
||||
}
|
||||
t.initPort()
|
||||
if isClient {
|
||||
if err = t.connect(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel connect error:", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = t.listen(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel listen error:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// store it when success
|
||||
gLog.Printf(LvDEBUG, "store tunnel %d", tid)
|
||||
pn.allTunnels.Store(tid, t)
|
||||
return
|
||||
}
|
||||
func (pn *P2PNetwork) init() error {
|
||||
gLog.Println(LvINFO, "P2PNetwork init start")
|
||||
defer gLog.Println(LvINFO, "P2PNetwork init end")
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
var err error
|
||||
for {
|
||||
// detect nat type
|
||||
gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerHost, gConf.Network.UDPPort1, gConf.Network.UDPPort2)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "detect NAT type error:", err)
|
||||
break
|
||||
}
|
||||
if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again
|
||||
gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort)
|
||||
}
|
||||
|
||||
// for testcase
|
||||
if strings.Contains(gConf.Network.Node, "openp2pS2STest") {
|
||||
gConf.Network.natType = NATSymmetric
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pS2STest debug")
|
||||
|
||||
}
|
||||
if strings.Contains(gConf.Network.Node, "openp2pC2CTest") {
|
||||
gConf.Network.natType = NATCone
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pC2CTest debug")
|
||||
}
|
||||
|
||||
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
onceV4Listener.Do(func() {
|
||||
v4l = &v4Listener{port: gConf.Network.TCPPort}
|
||||
go v4l.start()
|
||||
})
|
||||
}
|
||||
gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP)
|
||||
gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort)
|
||||
uri := "/api/v1/login"
|
||||
caCertPool, errCert := x509.SystemCertPool()
|
||||
if errCert != nil {
|
||||
gLog.Println(LvERROR, "Failed to load system root CAs:", errCert)
|
||||
} else {
|
||||
caCertPool = x509.NewCertPool()
|
||||
}
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
|
||||
config := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
|
||||
websocket.DefaultDialer.TLSClientConfig = &config
|
||||
websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout
|
||||
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri}
|
||||
q := u.Query()
|
||||
q.Add("node", gConf.Network.Node)
|
||||
q.Add("token", fmt.Sprintf("%d", gConf.Network.Token))
|
||||
q.Add("version", OpenP2PVersion)
|
||||
q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType))
|
||||
q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth))
|
||||
u.RawQuery = q.Encode()
|
||||
var ws *websocket.Conn
|
||||
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "Dial error:", err)
|
||||
break
|
||||
}
|
||||
pn.running = true
|
||||
pn.online = true
|
||||
pn.conn = ws
|
||||
localAddr := strings.Split(ws.LocalAddr().String(), ":")
|
||||
if len(localAddr) == 2 {
|
||||
gConf.Network.localIP = localAddr[0]
|
||||
} else {
|
||||
err = errors.New("get local ip failed")
|
||||
break
|
||||
}
|
||||
go pn.readLoop()
|
||||
gConf.Network.mac = getmac(gConf.Network.localIP)
|
||||
gConf.Network.os = getOsName()
|
||||
go func() {
|
||||
req := ReportBasic{
|
||||
Mac: gConf.Network.mac,
|
||||
LanIP: gConf.Network.localIP,
|
||||
OS: gConf.Network.os,
|
||||
HasIPv4: gConf.Network.hasIPv4,
|
||||
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
rsp := netInfo()
|
||||
gLog.Println(LvDEBUG, "netinfo:", rsp)
|
||||
if rsp != nil && rsp.Country != "" {
|
||||
if IsIPv6(rsp.IP.String()) {
|
||||
gConf.setIPv6(rsp.IP.String())
|
||||
}
|
||||
req.NetInfo = *rsp
|
||||
} else {
|
||||
pn.refreshIPv6()
|
||||
}
|
||||
req.IPv6 = gConf.IPv6()
|
||||
pn.write(MsgReport, MsgReportBasic, &req)
|
||||
}()
|
||||
go pn.autorunApp()
|
||||
pn.write(MsgSDWAN, MsgSDWANInfoReq, nil)
|
||||
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// init failed, retry
|
||||
pn.close()
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
head := openP2PHeader{}
|
||||
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handleMessage error:", err)
|
||||
return
|
||||
}
|
||||
switch head.MainType {
|
||||
case MsgLogin:
|
||||
// gLog.Println(LevelINFO,string(msg))
|
||||
rsp := LoginRsp{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err)
|
||||
return
|
||||
}
|
||||
if rsp.Error != 0 {
|
||||
gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
|
||||
pn.running = false
|
||||
} else {
|
||||
gConf.Network.Token = rsp.Token
|
||||
gConf.Network.User = rsp.User
|
||||
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.PeerNode)
|
||||
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.PeerNode)
|
||||
}
|
||||
|
||||
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.PeerNode)
|
||||
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.PeerNode)
|
||||
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.PeerNode)
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.PeerNode)
|
||||
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.PeerNode)
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.PeerNode)
|
||||
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.PeerNode)
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.PeerNode)
|
||||
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
|
||||
if ts > PunchTsDelay || ts < 0 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
startTime := time.Now()
|
||||
t.linkModeWeb = LinkModeTCPPunch
|
||||
gotCh := make(chan *underlayTCP, 1)
|
||||
var wg sync.WaitGroup
|
||||
var success atomic.Int32
|
||||
if t.config.peerNatType == NATSymmetric { // c2s
|
||||
randPorts := rand.Perm(65532)
|
||||
for i := 0; i < SymmetricHandshakeNum; i++ {
|
||||
wg.Add(1)
|
||||
go func(port int) {
|
||||
defer wg.Done()
|
||||
ul, err := dialTCP(t.config.peerIP, port, t.coneLocalPort, LinkModeTCPPunch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !success.CompareAndSwap(0, 1) {
|
||||
ul.Close() // only cone side close
|
||||
return
|
||||
}
|
||||
err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err)
|
||||
return
|
||||
}
|
||||
req := P2PHandshakeReq{}
|
||||
if err = json.Unmarshal(buff, &req); err != nil {
|
||||
return
|
||||
}
|
||||
if req.ID != t.id {
|
||||
return
|
||||
}
|
||||
gLog.Printf(LvINFO, "handshakeS2C TCP ok. cost %dms", time.Since(startTime)/time.Millisecond)
|
||||
|
||||
gotCh <- ul
|
||||
close(gotCh)
|
||||
}(randPorts[i] + 2)
|
||||
}
|
||||
|
||||
} else { // s2c
|
||||
for i := 0; i < SymmetricHandshakeNum; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ul, err := dialTCP(t.config.peerIP, t.config.peerConeNatPort, 0, LinkModeTCPPunch)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err)
|
||||
return
|
||||
}
|
||||
req := P2PHandshakeReq{}
|
||||
if err = json.Unmarshal(buff, &req); err != nil {
|
||||
return
|
||||
}
|
||||
if req.ID != t.id {
|
||||
return
|
||||
}
|
||||
err = ul.WriteMessage(MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return
|
||||
}
|
||||
if success.CompareAndSwap(0, 1) {
|
||||
gotCh <- ul
|
||||
close(gotCh)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-time.After(HandshakeTimeout):
|
||||
return nil, fmt.Errorf("wait tcp handshake timeout")
|
||||
case ul := <-gotCh:
|
||||
return ul, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s start ", t.config.PeerNode)
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s end ", t.config.PeerNode)
|
||||
var ul *underlayTCP6
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen TCP6 error:%s", err)
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
}
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Println(LvDEBUG, "TCP6 connection ok")
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
//else
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6)
|
||||
ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("TCP6 dial to %s:%d error:%s", t.config.peerIPv6, t.config.peerConeNatPort, err)
|
||||
}
|
||||
handshakeBegin := time.Now()
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvINFO, "TCP6 connection ok")
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) readLoop() {
|
||||
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
|
||||
gLog.Printf(LvDEBUG, "%d tunnel readloop start", t.id)
|
||||
for t.isRuning() {
|
||||
t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2))
|
||||
head, body, err := t.conn.ReadBuffer()
|
||||
if err != nil {
|
||||
if t.isRuning() {
|
||||
gLog.Printf(LvERROR, "%d tunnel read error:%s", t.id, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if head.MainType != MsgP2P {
|
||||
gLog.Printf(LvWARN, "%d head.MainType != MsgP2P", t.id)
|
||||
continue
|
||||
}
|
||||
// TODO: replace some case implement to functions
|
||||
switch head.SubType {
|
||||
case MsgTunnelHeartbeat:
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
t.hbMtx.Unlock()
|
||||
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil)
|
||||
gLog.Printf(LvDev, "%d read tunnel heartbeat", t.id)
|
||||
case MsgTunnelHeartbeatAck:
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
t.hbMtx.Unlock()
|
||||
gLog.Printf(LvDev, "%d read tunnel heartbeat ack", t.id)
|
||||
case MsgOverlayData:
|
||||
if len(body) < overlayHeaderSize {
|
||||
gLog.Printf(LvWARN, "%d len(body) < overlayHeaderSize", t.id)
|
||||
continue
|
||||
}
|
||||
overlayID := binary.LittleEndian.Uint64(body[:8])
|
||||
gLog.Printf(LvDev, "%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen)
|
||||
s, ok := t.overlayConns.Load(overlayID)
|
||||
if !ok {
|
||||
// debug level, when overlay connection closed, always has some packet not found tunnel
|
||||
gLog.Printf(LvDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID)
|
||||
continue
|
||||
}
|
||||
overlayConn, ok := s.(*overlayConn)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
payload := body[overlayHeaderSize:]
|
||||
var err error
|
||||
if overlayConn.appKey != 0 {
|
||||
payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize)))
|
||||
}
|
||||
_, err = overlayConn.Write(payload)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "overlay write error:", err)
|
||||
}
|
||||
case MsgNodeData:
|
||||
t.handleNodeData(head, body, false)
|
||||
case MsgRelayNodeData:
|
||||
t.handleNodeData(head, body, true)
|
||||
case MsgRelayData:
|
||||
if len(body) < 8 {
|
||||
continue
|
||||
}
|
||||
tunnelID := binary.LittleEndian.Uint64(body[:8])
|
||||
gLog.Printf(LvDev, "relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize)
|
||||
if err := t.pn.relay(tunnelID, body[RelayHeaderSize:]); err != nil {
|
||||
gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.PeerNode, 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.PeerNode, t.id)
|
||||
defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.PeerNode, 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.PeerNode, t.id)
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case t.writeData <- writeBytes:
|
||||
default:
|
||||
gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.PeerNode, 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))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
621
core/protocol.go
Normal file
@@ -0,0 +1,621 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"hash/crc64"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const OpenP2PVersion = "3.18.4"
|
||||
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
|
||||
)
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
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-----
|
||||
`
|
||||
292
core/sdwan.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PacketHeader struct {
|
||||
version int
|
||||
// src uint32
|
||||
// prot uint8
|
||||
protocol byte
|
||||
dst uint32
|
||||
port uint16
|
||||
}
|
||||
|
||||
func parseHeader(b []byte, h *PacketHeader) error {
|
||||
if len(b) < 20 {
|
||||
return fmt.Errorf("small packet")
|
||||
}
|
||||
h.version = int(b[0] >> 4)
|
||||
h.protocol = byte(b[9])
|
||||
if h.version == 4 {
|
||||
h.dst = binary.BigEndian.Uint32(b[16:20])
|
||||
} else if h.version != 6 {
|
||||
return fmt.Errorf("unknown version in ip header:%d", h.version)
|
||||
}
|
||||
if h.protocol == 6 || h.protocol == 17 { // TCP or UDP
|
||||
h.port = binary.BigEndian.Uint16(b[22:24])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type sdwanNode struct {
|
||||
name string
|
||||
id uint64
|
||||
}
|
||||
|
||||
type p2pSDWAN struct {
|
||||
nodeName string
|
||||
tun *optun
|
||||
sysRoute sync.Map // ip:sdwanNode
|
||||
subnet *net.IPNet
|
||||
gateway net.IP
|
||||
virtualIP *net.IPNet
|
||||
internalRoute *IPTree
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) init(name string) error {
|
||||
if gConf.getSDWAN().Gateway == "" {
|
||||
gLog.Println(LvDEBUG, "not in sdwan clear all ")
|
||||
}
|
||||
if s.internalRoute == nil {
|
||||
s.internalRoute = NewIPTree("")
|
||||
}
|
||||
|
||||
s.nodeName = name
|
||||
s.gateway, s.subnet, _ = net.ParseCIDR(gConf.getSDWAN().Gateway)
|
||||
for _, node := range gConf.getDelNodes() {
|
||||
gLog.Println(LvDEBUG, "deal deleted node: ", node.Name)
|
||||
delRoute(node.IP, s.gateway.String())
|
||||
s.internalRoute.Del(node.IP, node.IP)
|
||||
ipNum, _ := inetAtoN(node.IP)
|
||||
s.sysRoute.Delete(ipNum)
|
||||
gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name})
|
||||
GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name})
|
||||
arr := strings.Split(node.Resource, ",")
|
||||
for _, r := range arr {
|
||||
_, ipnet, err := net.ParseCIDR(r)
|
||||
if err != nil {
|
||||
// fmt.Println("Error parsing CIDR:", err)
|
||||
continue
|
||||
}
|
||||
if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan
|
||||
continue
|
||||
}
|
||||
minIP := ipnet.IP
|
||||
maxIP := make(net.IP, len(minIP))
|
||||
copy(maxIP, minIP)
|
||||
for i := range minIP {
|
||||
maxIP[i] = minIP[i] | ^ipnet.Mask[i]
|
||||
}
|
||||
s.internalRoute.Del(minIP.String(), maxIP.String())
|
||||
delRoute(ipnet.String(), s.gateway.String())
|
||||
}
|
||||
}
|
||||
for _, node := range gConf.getAddNodes() {
|
||||
gLog.Println(LvDEBUG, "deal add node: ", node.Name)
|
||||
ipNet := &net.IPNet{
|
||||
IP: net.ParseIP(node.IP),
|
||||
Mask: s.subnet.Mask,
|
||||
}
|
||||
if node.Name == s.nodeName {
|
||||
s.virtualIP = ipNet
|
||||
gLog.Println(LvINFO, "start tun ", ipNet.String())
|
||||
err := s.StartTun()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "start tun error:", err)
|
||||
return err
|
||||
}
|
||||
gLog.Println(LvINFO, "start tun ok")
|
||||
allowTunForward()
|
||||
addRoute(s.subnet.String(), s.gateway.String(), s.tun.tunName)
|
||||
// addRoute("255.255.255.255/32", s.gateway.String(), s.tun.tunName) // for broadcast
|
||||
// addRoute("224.0.0.0/4", s.gateway.String(), s.tun.tunName) // for multicast
|
||||
initSNATRule(s.subnet.String()) // for network resource
|
||||
continue
|
||||
}
|
||||
ip, err := inetAtoN(ipNet.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.sysRoute.Store(ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
|
||||
s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
|
||||
}
|
||||
for _, node := range gConf.getAddNodes() {
|
||||
if node.Name == s.nodeName { // not deal resource itself
|
||||
continue
|
||||
}
|
||||
if len(node.Resource) > 0 {
|
||||
gLog.Printf(LvINFO, "deal add node: %s resource: %s", node.Name, node.Resource)
|
||||
arr := strings.Split(node.Resource, ",")
|
||||
for _, r := range arr {
|
||||
// add internal route
|
||||
_, ipnet, err := net.ParseCIDR(r)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing CIDR:", err)
|
||||
continue
|
||||
}
|
||||
if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan
|
||||
continue
|
||||
}
|
||||
minIP := ipnet.IP
|
||||
maxIP := make(net.IP, len(minIP))
|
||||
copy(maxIP, minIP)
|
||||
for i := range minIP {
|
||||
maxIP[i] = minIP[i] | ^ipnet.Mask[i]
|
||||
}
|
||||
s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
|
||||
// add sys route
|
||||
addRoute(ipnet.String(), s.gateway.String(), s.tun.tunName)
|
||||
}
|
||||
}
|
||||
}
|
||||
gConf.retryAllMemApp()
|
||||
gLog.Printf(LvINFO, "sdwan init ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) run() {
|
||||
s.sysRoute.Range(func(key, value interface{}) bool {
|
||||
node := value.(*sdwanNode)
|
||||
GNetwork.ConnectNode(node.name)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) readNodeLoop() {
|
||||
gLog.Printf(LvDEBUG, "sdwan readNodeLoop start")
|
||||
defer gLog.Printf(LvDEBUG, "sdwan readNodeLoop end")
|
||||
writeBuff := make([][]byte, 1)
|
||||
for {
|
||||
nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet
|
||||
if nd == nil {
|
||||
gLog.Printf(LvDev, "waiting for node data")
|
||||
continue
|
||||
}
|
||||
head := PacketHeader{}
|
||||
parseHeader(nd.Data, &head)
|
||||
gLog.Printf(LvDev, "write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd.Data))
|
||||
if PIHeaderSize == 0 {
|
||||
writeBuff[0] = nd.Data
|
||||
} else {
|
||||
writeBuff[0] = make([]byte, PIHeaderSize+len(nd.Data))
|
||||
copy(writeBuff[0][PIHeaderSize:], nd.Data)
|
||||
}
|
||||
|
||||
len, err := s.tun.Write(writeBuff, PIHeaderSize)
|
||||
if err != nil {
|
||||
gLog.Printf(LvDEBUG, "write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isBroadcastOrMulticast(ipUint32 uint32, subnet *net.IPNet) bool {
|
||||
// return ipUint32 == 0xffffffff || (byte(ipUint32) == 0xff) || (ipUint32>>28 == 0xe)
|
||||
return ipUint32 == 0xffffffff || (ipUint32>>28 == 0xe) // 225.255.255.255/32, 224.0.0.0/4
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) {
|
||||
var node *sdwanNode
|
||||
// v, ok := s.routes.Load(ih.dst)
|
||||
v, ok := s.internalRoute.Load(head.dst)
|
||||
if !ok || v == nil {
|
||||
if isBroadcastOrMulticast(head.dst, s.subnet) {
|
||||
gLog.Printf(LvDev, "multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String())
|
||||
GNetwork.WriteBroadcast(p)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
node = v.(*sdwanNode)
|
||||
}
|
||||
|
||||
err := GNetwork.WriteNode(node.id, p)
|
||||
if err != nil {
|
||||
gLog.Printf(LvDev, "write packet to %s fail: %s", node.name, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) readTunLoop() {
|
||||
gLog.Printf(LvDEBUG, "sdwan readTunLoop start")
|
||||
defer gLog.Printf(LvDEBUG, "sdwan readTunLoop end")
|
||||
readBuff := make([][]byte, ReadTunBuffNum)
|
||||
for i := 0; i < ReadTunBuffNum; i++ {
|
||||
readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize)
|
||||
}
|
||||
readBuffSize := make([]int, ReadTunBuffNum)
|
||||
ih := PacketHeader{}
|
||||
for {
|
||||
n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "read tun fail: ", err)
|
||||
return
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if readBuffSize[i] > ReadTunBuffSize {
|
||||
gLog.Printf(LvERROR, "read tun overflow: len=", readBuffSize[i])
|
||||
continue
|
||||
}
|
||||
parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih)
|
||||
gLog.Printf(LvDev, "read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0])
|
||||
s.routeTunPacket(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) StartTun() error {
|
||||
sdwan := gConf.getSDWAN()
|
||||
if s.tun == nil {
|
||||
tun := &optun{}
|
||||
err := tun.Start(s.virtualIP.String(), &sdwan)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "open tun fail:", err)
|
||||
return err
|
||||
}
|
||||
s.tun = tun
|
||||
go s.readTunLoop()
|
||||
go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds
|
||||
}
|
||||
err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSDWAN(subType uint16, msg []byte) error {
|
||||
gLog.Printf(LvDEBUG, "handle sdwan msg type:%d", subType)
|
||||
var err error
|
||||
switch subType {
|
||||
case MsgSDWANInfoRsp:
|
||||
rsp := SDWANInfo{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
return ErrMsgFormat
|
||||
}
|
||||
gLog.Println(LvINFO, "sdwan init:", prettyJson(rsp))
|
||||
if runtime.GOOS == "android" {
|
||||
AndroidSDWANConfig <- msg[openP2PHeaderSize:]
|
||||
}
|
||||
// GNetwork.sdwan.detail = &rsp
|
||||
gConf.setSDWAN(rsp)
|
||||
err = GNetwork.sdwan.init(gConf.Network.Node)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "sdwan init fail: ", err)
|
||||
if GNetwork.sdwan.tun != nil {
|
||||
GNetwork.sdwan.tun.Stop()
|
||||
GNetwork.sdwan.tun = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
go GNetwork.sdwan.run()
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
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,25 +18,24 @@ 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(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 {
|
||||
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
|
||||
}
|
||||
65
core/underlay.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type underlay interface {
|
||||
Read([]byte) (int, error)
|
||||
Write([]byte) (int, error)
|
||||
ReadBuffer() (*openP2PHeader, []byte, error)
|
||||
WriteBytes(uint16, uint16, []byte) error
|
||||
WriteBuffer([]byte) error
|
||||
WriteMessage(uint16, uint16, interface{}) error
|
||||
Close() error
|
||||
WLock()
|
||||
WUnlock()
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
Protocol() string
|
||||
}
|
||||
|
||||
func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
|
||||
headBuf := make([]byte, openP2PHeaderSize)
|
||||
_, err := io.ReadFull(ul, headBuf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
head, err := decodeHeader(headBuf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dataBuf := make([]byte, head.DataLen)
|
||||
_, err = io.ReadFull(ul, dataBuf)
|
||||
return head, dataBuf, err
|
||||
}
|
||||
|
||||
func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error {
|
||||
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err := ul.Write(writeBytes)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func DefaultWriteBuffer(ul underlay, data []byte) error {
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err := ul.Write(data)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func DefaultWriteMessage(ul underlay, mainType uint16, subType uint16, packet interface{}) error {
|
||||
writeBytes, err := newMessage(mainType, subType, packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err = ul.Write(writeBytes)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
94
core/underlay_kcp.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/xtaci/kcp-go/v5"
|
||||
)
|
||||
|
||||
type underlayKCP struct {
|
||||
listener *kcp.Listener
|
||||
writeMtx *sync.Mutex
|
||||
*kcp.UDPSession
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) Protocol() string {
|
||||
return "kcp"
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) ReadBuffer() (*openP2PHeader, []byte, error) {
|
||||
return DefaultReadBuffer(conn)
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) WriteBytes(mainType uint16, subType uint16, data []byte) error {
|
||||
return DefaultWriteBytes(conn, mainType, subType, data)
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) WriteBuffer(data []byte) error {
|
||||
return DefaultWriteBuffer(conn, data)
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
|
||||
return DefaultWriteMessage(conn, mainType, subType, packet)
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) Close() error {
|
||||
conn.UDPSession.Close()
|
||||
return nil
|
||||
}
|
||||
func (conn *underlayKCP) WLock() {
|
||||
conn.writeMtx.Lock()
|
||||
}
|
||||
func (conn *underlayKCP) WUnlock() {
|
||||
conn.writeMtx.Unlock()
|
||||
}
|
||||
func (conn *underlayKCP) CloseListener() {
|
||||
if conn.listener != nil {
|
||||
conn.listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *underlayKCP) Accept() error {
|
||||
kConn, err := conn.listener.AcceptKCP()
|
||||
if err != nil {
|
||||
conn.listener.Close()
|
||||
return err
|
||||
}
|
||||
kConn.SetNoDelay(0, 40, 0, 0)
|
||||
kConn.SetWindowSize(512, 512)
|
||||
kConn.SetWriteBuffer(1024 * 128)
|
||||
kConn.SetReadBuffer(1024 * 128)
|
||||
conn.UDPSession = kConn
|
||||
return nil
|
||||
}
|
||||
|
||||
func listenKCP(addr string, idleTimeout time.Duration) (*underlayKCP, error) {
|
||||
gLog.Println(LvDEBUG, "kcp listen on ", addr)
|
||||
listener, err := kcp.ListenWithOptions(addr, nil, 0, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
|
||||
}
|
||||
ul := &underlayKCP{listener: listener, writeMtx: &sync.Mutex{}}
|
||||
err = ul.Accept()
|
||||
if err != nil {
|
||||
ul.CloseListener()
|
||||
return nil, fmt.Errorf("accept KCP error:%s", err)
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func dialKCP(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayKCP, error) {
|
||||
kConn, err := kcp.NewConn(remoteAddr.String(), nil, 0, 0, conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic.DialContext error:%s", err)
|
||||
}
|
||||
kConn.SetNoDelay(0, 40, 0, 0)
|
||||
kConn.SetWindowSize(512, 512)
|
||||
kConn.SetWriteBuffer(1024 * 128)
|
||||
kConn.SetReadBuffer(1024 * 128)
|
||||
ul := &underlayKCP{nil, &sync.Mutex{}, kConn}
|
||||
return ul, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,19 +6,17 @@ import (
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
//quic.DialContext do not support version 44,disable it
|
||||
// quic.DialContext do not support version 44,disable it
|
||||
var quicVersion []quic.VersionNumber
|
||||
|
||||
type underlayQUIC struct {
|
||||
@@ -33,53 +31,33 @@ func (conn *underlayQUIC) Protocol() string {
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) ReadBuffer() (*openP2PHeader, []byte, error) {
|
||||
headBuf := make([]byte, openP2PHeaderSize)
|
||||
_, err := io.ReadFull(conn, headBuf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
head, err := decodeHeader(headBuf)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
dataBuf := make([]byte, head.DataLen)
|
||||
_, err = io.ReadFull(conn, dataBuf)
|
||||
return head, dataBuf, err
|
||||
return DefaultReadBuffer(conn)
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) WriteBytes(mainType uint16, subType uint16, data []byte) error {
|
||||
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
|
||||
conn.writeMtx.Lock()
|
||||
_, err := conn.Write(writeBytes)
|
||||
conn.writeMtx.Unlock()
|
||||
return err
|
||||
return DefaultWriteBytes(conn, mainType, subType, data)
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) WriteBuffer(data []byte) error {
|
||||
conn.writeMtx.Lock()
|
||||
_, err := conn.Write(data)
|
||||
conn.writeMtx.Unlock()
|
||||
return err
|
||||
return DefaultWriteBuffer(conn, data)
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
|
||||
// TODO: call newMessage
|
||||
data, err := json.Marshal(packet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
|
||||
conn.writeMtx.Lock()
|
||||
_, err = conn.Write(writeBytes)
|
||||
conn.writeMtx.Unlock()
|
||||
return err
|
||||
return DefaultWriteMessage(conn, mainType, subType, packet)
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) Close() error {
|
||||
conn.Stream.CancelRead(1)
|
||||
conn.Connection.CloseWithError(0, "")
|
||||
conn.CloseListener()
|
||||
return nil
|
||||
}
|
||||
func (conn *underlayQUIC) WLock() {
|
||||
conn.writeMtx.Lock()
|
||||
}
|
||||
func (conn *underlayQUIC) WUnlock() {
|
||||
conn.writeMtx.Unlock()
|
||||
}
|
||||
func (conn *underlayQUIC) CloseListener() {
|
||||
if conn.listener != nil {
|
||||
conn.listener.Close()
|
||||
@@ -87,7 +65,7 @@ func (conn *underlayQUIC) CloseListener() {
|
||||
}
|
||||
|
||||
func (conn *underlayQUIC) Accept() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), UnderlayConnectTimeout)
|
||||
defer cancel()
|
||||
sess, err := conn.listener.Accept(ctx)
|
||||
if err != nil {
|
||||
@@ -109,7 +87,13 @@ func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
|
||||
}
|
||||
return &underlayQUIC{listener: listener, writeMtx: &sync.Mutex{}}, nil
|
||||
ul := &underlayQUIC{listener: listener, writeMtx: &sync.Mutex{}}
|
||||
err = ul.Accept()
|
||||
if err != nil {
|
||||
ul.CloseListener()
|
||||
return nil, fmt.Errorf("accept quic error:%s", err)
|
||||
}
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayQUIC, error) {
|
||||