Compare commits

...

21 Commits

Author SHA1 Message Date
TenderIronh
b667e5b766 3.8.0 2023-05-07 20:42:06 +08:00
TenderIronh
cd415e7bf4 3.6.11 2023-03-25 12:00:27 +08:00
TenderIronh
67e3a8915a 3.6.8 2023-03-22 23:11:38 +08:00
TenderIronh
791d910314 3.6.5 2023-03-05 00:44:22 +08:00
TenderIronh
c3a43be3cc improve gatway and p2papp reconnect 2022-11-25 23:55:33 +08:00
TenderIronh
c8b8bf05a5 support openwrt and improve app and gateway reconnect time 2022-11-18 23:19:47 +08:00
hhd
8311341960 readloop error 2022-11-10 23:34:04 +08:00
hhd
d5c098ca74 support android 2022-11-05 18:22:16 +08:00
hhd
af82fc6e36 support andoird 2022-11-03 11:07:32 +08:00
TenderIronh
2af77668fe auto adjust node name 2022-09-13 20:47:32 +08:00
TenderIronh
215feb8721 doc 2022-08-08 23:19:47 +08:00
TenderIronh
4a115fb3fa doc remote debug 2022-08-08 23:10:37 +08:00
TenderIronh
0fb3bc4e26 request peer info token wrong 2022-06-15 23:20:13 +08:00
TenderIronh
df23b30d2b doc 2022-05-29 16:05:18 +08:00
TenderIronh
f9b5073e0d fix some bug 2022-05-29 15:48:56 +08:00
TenderIronh
9ea467c7b3 rm rename 2022-05-26 23:43:06 +08:00
TenderIronh
c0bad61eb6 tcp punch 2022-05-26 23:18:22 +08:00
TenderIronh
bb32133038 config template 2022-05-17 15:53:29 +08:00
TenderIronh
532d3667ce support ipv6 2022-05-15 13:08:56 +08:00
TenderIronh
b2d35c6f97 1.5.5 2022-04-27 23:26:23 +08:00
TenderIronh
037f3cc34e doc 2022-04-07 23:22:46 +08:00
105 changed files with 4570 additions and 1659 deletions

14
.gitignore vendored
View File

@@ -1,10 +1,18 @@
__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
build.gradle

7
Makefile Normal file
View File

@@ -0,0 +1,7 @@
build:
export GOPROXY=https://goproxy.io,direct
go mod tidy
go build cmd/openp2p.go
.PHONY: build
.DEFAULT_GOAL := build

View File

@@ -23,7 +23,7 @@
### 5. 跨平台
因为轻量所以很容易支持各个平台。支持主流的操作系统Windows,Linux,MacOS和主流的cpu架构386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
### 6. 高效
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境无论NAT1-4Cone或Symmetric都支持。依靠Quic协议优秀的拥塞算法能在糟糕的网络环境获得高带宽低延时。
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境无论NAT1-4Cone或SymmetricUDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法能在糟糕的网络环境获得高带宽低延时。
### 7. 二次开发
基于OpenP2P只需数行代码就能让原来只能局域网通信的程序变成任何内网都能通信
@@ -31,6 +31,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
## 快速入门
仅需简单4步就能用起来。
下面是一个远程办公例子在家里连入办公室Windows电脑。
(另外一个快速入门视频 https://www.bilibili.com/video/BV1Et4y1P7bF/
### 1.注册
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
@@ -95,29 +96,45 @@ Windows默认会阻止没有花钱买它家证书签名过的程序选择“
服务端有个调度模型根据带宽、ping值、稳定性、服务时长尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码hmac-sha256算法校验它是一次性密码和我们平时使用的手机验证码或银行密码器一样的原理。
## 编译
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
```
## TODO
## RoadMap
近期计划:
1. 支持IPv6
2. 支持随系统自动启动,安装成系统服务
3. 提供一些免费服务器给特别差的网络,如广电网络
4. 建立网站用户可以在网站管理所有P2PApp和设备。查看设备在线状态升级增删查改重启P2PApp等
1. ~~支持IPv6~~(100%)
2. ~~支持随系统自动启动,安装成系统服务~~(100%)
3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%)
4. ~~建立网站用户可以在网站管理所有P2PApp和设备。查看设备在线状态升级增删查改重启P2PApp等~~(100%)
5. 建立公众号用户可在微信公众号管理所有P2PApp和设备
6. 客户端提供WebUI
7. 支持自有服务器高并发连接
7. ~~支持自有服务器,开源服务器程序~~(100%)
8. 共享节点调度模型优化,对不同的运营商优化
9. 方便二次开发提供API和lib
10. 应用层支持UDP协议实现很简单但UDP应用较少暂不急
10. ~~应用层支持UDP协议实现很简单但UDP应用较少暂不急~~(100%)
11. 底层通信支持KCP协议目前仅支持QuicKCP专门对延时优化被游戏加速器广泛使用可以牺牲一定的带宽降低延时
12. 支持Android系统让旧手机焕发青春变成移动网关
12. ~~支持Android系统让旧手机焕发青春变成移动网关~~(100%)
13. 支持Windows网上邻居共享文件
14. 内网直连优化,用处不大,估计就用户测试时用到
15. ~~支持UPNP~~(100%)
远期计划:
1. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。

View File

@@ -25,7 +25,7 @@ The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and t
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64.
### 6. Efficient
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric). Relying on the excellent congestion algorithm of the Quic protocol, high bandwidth and low latency can be obtained in a bad network environment.
P2P direct connection lets your devices make good use of bandwidth. Your device can be connected in any network environments, even supports NAT1-4 (Cone or Symmetric),UDP or TCP punching,UPNP,IPv6. Relying on the excellent congestion algorithm of the Quic protocol, high bandwidth and low latency can be obtained in a bad network environment.
### 7. Integration
Your applicaiton can call OpenP2P with a few code to make any internal networks communicate with each other.
@@ -33,7 +33,7 @@ Your applicaiton can call OpenP2P with a few code to make any internal networks
## Get Started
Just 4 simple steps to use.
Here's an example of remote work: connecting to an office Windows computer at home.
(Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
### 1.Register
Go to <https://console.openp2p.cn> register a new user
@@ -103,29 +103,46 @@ That's right, the relay node is naturally an man-in-middle, so AES encryption is
The server side has a scheduling model, which calculate bandwith, ping value,stability and service duration to provide a well-proportioned service to every share node. It uses TOTP(Time-based One-time Password) with hmac-sha256 algorithem, its theory as same as the cellphone validation code or bank cipher coder.
## Build
go version 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
```
## TODO
## RoadMap
Short-Term:
1. Support IPv6.
2. Support auto run when system boot, setup system service.
3. Provide free servers to some low-performance network.
4. Build website, users can manage all P2PApp and devices via it. View devices' online status, upgrade, restart or CURD P2PApp .
1. ~~Support IPv6.~~(100%)
2. ~~Support auto run when system boot, setup system service.~~(100%)
3. ~~Provide free servers to some low-performance network.~~(100%)
4. ~~Build website, users can manage all P2PApp and devices via it. View devices' online status, upgrade, restart or CURD P2PApp .~~(100%)
5. Provide wechat official account, user can manage P2PApp nodes and deivce as same as website.
6. Provide WebUI on client side.
7. Support high concurrency on server side.
7. ~~Support private server, open source server program.~~(100%)
8. Optimize our share scheduling model for different network operators.
9. Provide REST APIs and libary for secondary development.
10. Support UDP at application layer, it is easy to implement but not urgent due to only a few applicaitons using UDP protocol.
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 .
12. ~~Support Android platform, let the phones to be mobile gateway.~~(100%)
13. Support SMB Windows neighborhood.
14. Direct connection on intranet, for testing.
15. ~~Support UPNP.~~(100%)
Long-Term:

View File

@@ -77,7 +77,7 @@ nohup ./openp2p -d -node OFFICEPC1 -token TOKEN &
# update local client
./openp2p update
# update remote client
curl --insecure 'https://openp2p.cn:27182/api/v1/device/YOUR-NODE-NAME/update?user=&password='
curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password='
```
Windows系统需要设置防火墙放行本程序程序会自动设置如果设置失败会影响连接功能。
@@ -91,4 +91,9 @@ firewall-cmd --state
## 卸载
```
./openp2p uninstall
# 已安装时
# windows
C:\Program Files\OpenP2P\openp2p.exe uninstall
# linux,macos
sudo /usr/local/openp2p/openp2p uninstall
```

View File

@@ -79,7 +79,7 @@ Configuration example
# update local client
./openp2p update
# update remote client
curl --insecure 'https://openp2p.cn:27182/api/v1/device/YOUR-NODE-NAME/update?user=&password='
curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password='
```
Windows system needs to set up firewall for this program, the program will automatically set the firewall, if the setting fails, the UDP punching will be affected.
@@ -93,4 +93,9 @@ firewall-cmd --state
## Uninstall
```
./openp2p uninstall
# when already installed
# windows
C:\Program Files\OpenP2P\openp2p.exe uninstall
# linux,macos
sudo /usr/local/openp2p/openp2p uninstall
```

15
app/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
app/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
app/.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
OpenP2P

6
app/.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="11" />
</component>
</project>

19
app/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

30
app/.idea/jarRepositories.xml generated Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="BintrayJCenter" />
<option name="name" value="BintrayJCenter" />
<option name="url" value="https://jcenter.bintray.com/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>

9
app/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

10
app/.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

6
app/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

17
app/README.md Normal file
View File

@@ -0,0 +1,17 @@
## Build
```
cd core
go get -v golang.org/x/mobile/bind
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"
cd ../app
./gradlew build
```

1
app/app/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

21
app/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -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)
}
}

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.openp2p">
<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" />
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,115 @@
package cn.openp2p
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Binder
import android.os.Build
import android.os.IBinder
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
class OpenP2PService : Service() {
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
override fun onCreate() {
Log.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id)
var channelId: String? = null
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, 0
)
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()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(
LOG_TAG,
"onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id
)
return super.onStartCommand(intent, flags, startId)
}
override fun onBind(p0: Intent?): IBinder? {
val token = p0?.getStringExtra("token")
Log.i(LOG_TAG, "onBind - Thread ID = " + Thread.currentThread().id + token)
GlobalScope.launch {
network = Openp2p.runAsModule(getExternalFilesDir(null).toString(), token, 0, 1)
val isConnect = network.connect(30000) // ms
Log.i(OpenP2PService.LOG_TAG, "login result: " + isConnect.toString());
do {
Thread.sleep(1000)
}while(network.connect(30000)&&running)
stopSelf()
}
return binder
}
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()
}
@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
}
}

View 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
}
}

View 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
}
}

View 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]"
}
}
}

View File

@@ -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
)

View File

@@ -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
)

View File

@@ -0,0 +1,193 @@
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.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.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 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)
}
})
val intent1 = VpnService.prepare(this) ?: return
loginViewModel.loginResult.observe(this@LoginActivity, Observer {
val loginResult = it ?: return@Observer
loading.visibility = View.GONE
if (loginResult.error != null) {
showLoginFailed(loginResult.error)
}
if (loginResult.success != null) {
updateUiWithUser(loginResult.success)
}
setResult(Activity.RESULT_OK)
//Complete and destroy login activity once successful
finish()
})
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)
token.setText(Openp2p.getToken(getExternalFilesDir(null).toString()))
login.setOnClickListener {
if (login.text.toString()=="退出"){
// val intent = Intent(this@LoginActivity, OpenP2PService::class.java)
// stopService(intent)
Log.i(LOG_TAG, "quit")
mService.stop()
unbindService(connection)
val intent = Intent(this@LoginActivity, OpenP2PService::class.java)
stopService(intent)
exitAPP()
}
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)
}
}
}
}
@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) {}
})
}

View 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
)

View 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
)

View 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
}
}

View File

@@ -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")
}
}

View 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>

View 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>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>

View File

@@ -0,0 +1,87 @@
<?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="225dp"
android:layout_height="46dp"
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="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:enabled="false"
android:text="@string/action_sign_in"
app:layout_constraintStart_toEndOf="@+id/token"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteY="-2dp" />
<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:ems="10"
android:inputType="none"
android:textIsSelectable="true"
android:focusable="false"
android:text="Name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/onlineState" />
<Button
android:id="@+id/profile"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="打开控制台查看Token"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/token" />
<EditText
android:id="@+id/onlineState"
android:layout_width="113dp"
android:layout_height="45dp"
android:ems="10"
android:inputType="textPersonName"
android:text="未登录"
app:layout_constraintStart_toEndOf="@+id/profile"
app:layout_constraintTop_toBottomOf="@+id/login" />
</androidx.constraintlayout.widget.ConstraintLayout>

View 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>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_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>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,5 @@
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@@ -0,0 +1,36 @@
<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 在后台运行一会后,被系统杀死进程,导致您的体验受到影响。您可以通过以下方式修改几个设置,解决此问题:
华为手机:
进入”设置“,搜索并进入“电池优化“界面,选中 OpenP2P 程序,不允许系统对其进行电池优化;
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电情况“,开启”允许后台活动“即可;
小米手机:
进入”设置“,进入”更多应用“界面,选中 OpenP2P 程序,点击”省电策略“,设置为”无限制“;
进入”设置“,进入”特色功能“界面,选中”游戏加速“,将其关闭即可;
OPPO手机
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电保护“,关闭所有设置项即可;
VIVO手机
进入”设置“,进入”电池“界面,点击”后台高耗电“,将 OpenP2P 设为允许高耗电时继续运行即可;
魅族手机:
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”权限管理“,点击”后台管理“,选择”允许后台运行“即可;
一加手机:
进入”设置“,进入”电池“界面,点击”电池优化“,将 OpenP2P 设为”不优化“即可;
三星手机:
进入”智能管理器“,进入”应用程序管理“界面,点击”管理自动运行“,将 OpenP2P 设为”允许后台运行“即可;"</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

19
app/gradle.properties Normal file
View File

@@ -0,0 +1,19 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official

BIN
app/gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,6 @@
#Sat Oct 22 21:46:24 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

172
app/gradlew vendored Normal file
View File

@@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
app/gradlew.bat vendored Normal file
View File

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
app/openp2p.jks Normal file

Binary file not shown.

2
app/settings.gradle Normal file
View File

@@ -0,0 +1,2 @@
rootProject.name = "OpenP2P"
include ':app'

7
cmd/openp2p.go Normal file
View File

@@ -0,0 +1,7 @@
package main
import openp2p "openp2p/core"
func main() {
openp2p.Run()
}

View File

@@ -1,45 +0,0 @@
package main
import (
"log"
"testing"
)
func TestAESCBC(t *testing.T) {
for packetSize := 1; packetSize <= 8192; packetSize++ {
log.Println("test packetSize=", packetSize)
data := make([]byte, packetSize)
for i := 0; i < packetSize; i++ {
data[i] = byte('0' + i%10)
}
p2pEncryptBuf := make([]byte, len(data)+PaddingSize)
inBuf := make([]byte, len(data)+PaddingSize)
copy(inBuf, data)
cryptKey := []byte("0123456789ABCDEF")
sendBuf, err := encryptBytes(cryptKey, p2pEncryptBuf, inBuf, len(data))
if err != nil {
t.Errorf("encrypt packet failed:%s", err)
}
log.Printf("encrypt data len=%d\n", len(sendBuf))
decryptBuf := make([]byte, len(sendBuf))
outBuf, err := decryptBytes(cryptKey, decryptBuf, sendBuf, len(sendBuf))
if err != nil {
t.Errorf("decrypt packet failed:%s", err)
}
// log.Printf("len=%d,content=%s\n", len(outBuf), outBuf)
log.Printf("decrypt data len=%d\n", len(outBuf))
log.Println("validate")
for i := 0; i < len(outBuf); i++ {
if outBuf[i] != byte('0'+i%10) {
t.Error("validate failed")
}
}
log.Println("validate ok")
}
}
func TestNetInfo(t *testing.T) {
log.Println(netInfo())
}

View File

@@ -1,10 +1,9 @@
{
"network": {
"Node": "YOUR_NODE_NAME",
"User": "YOUR_USER_NAME",
"Password": "YOUR_PASSWORD",
"ServerHost": "openp2p.cn",
"ServerPort": 27182,
"Token": "YOUR_TOKEN",
"ServerHost": "api.openp2p.cn",
"ServerPort": 27183,
"UDPPort1": 27182,
"UDPPort2": 27183
},

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"sync"

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -12,11 +12,14 @@ import (
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
)
const MinNodeNameLen = 8
func getmac(ip string) string {
//get mac relative to the ip address which connected to the mq.
ifaces, err := net.Interfaces()
if err != nil {
return ""
@@ -77,6 +80,7 @@ func pkcs7UnPadding(origData []byte, dataLen int) ([]byte, error) {
return origData[:(dataLen - unPadLen)], nil
}
// AES-CBC
func encryptBytes(key []byte, out, in []byte, plainLen int) ([]byte, error) {
if len(key) == 0 {
return in[:plainLen], nil
@@ -122,20 +126,20 @@ func netInfo() *NetInfo {
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
r, err := client.Get("https://ifconfig.co/json")
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
gLog.Println(LvDEBUG, "netInfo error:", err)
continue
}
defer r.Body.Close()
buf := make([]byte, 1024*64)
n, err := r.Body.Read(buf)
if err != nil {
gLog.Println(LevelINFO, "netInfo error:", err)
gLog.Println(LvDEBUG, "netInfo error:", err)
continue
}
rsp := NetInfo{}
err = json.Unmarshal(buf[:n], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong NetInfo:%s", err)
gLog.Printf(LvERROR, "wrong NetInfo:%s", err)
continue
}
return &rsp
@@ -153,8 +157,57 @@ func execOutput(name string, args ...string) string {
func defaultNodeName() string {
name, _ := os.Hostname()
for len(name) < 8 {
for len(name) < MinNodeNameLen {
name = fmt.Sprintf("%s%d", name, rand.Int()%10)
}
return name
}
const EQUAL int = 0
const GREATER int = 1
const LESS int = -1
func compareVersion(v1, v2 string) int {
if v1 == v2 {
return EQUAL
}
v1Arr := strings.Split(v1, ".")
v2Arr := strings.Split(v2, ".")
for i, subVer := range v1Arr {
if len(v2Arr) <= i {
return GREATER
}
subv1, _ := strconv.Atoi(subVer)
subv2, _ := strconv.Atoi(v2Arr[i])
if subv1 > subv2 {
return GREATER
}
if subv1 < subv2 {
return LESS
}
}
return LESS
}
func parseMajorVer(ver string) int {
v1Arr := strings.Split(ver, ".")
if len(v1Arr) > 0 {
n, _ := strconv.ParseInt(v1Arr[0], 10, 32)
return int(n)
}
return 0
}
func IsIPv6(address string) bool {
return strings.Count(address, ":") >= 2
}
var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-")
func randStr(n int) string {
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

96
core/common_test.go Normal file
View File

@@ -0,0 +1,96 @@
package openp2p
import (
"log"
"testing"
)
func TestAESCBC(t *testing.T) {
for packetSize := 1; packetSize <= 8192; packetSize++ {
log.Println("test packetSize=", packetSize)
data := make([]byte, packetSize)
for i := 0; i < packetSize; i++ {
data[i] = byte('0' + i%10)
}
p2pEncryptBuf := make([]byte, len(data)+PaddingSize)
inBuf := make([]byte, len(data)+PaddingSize)
copy(inBuf, data)
cryptKey := []byte("0123456789ABCDEF")
sendBuf, err := encryptBytes(cryptKey, p2pEncryptBuf, inBuf, len(data))
if err != nil {
t.Errorf("encrypt packet failed:%s", err)
}
log.Printf("encrypt data len=%d\n", len(sendBuf))
decryptBuf := make([]byte, len(sendBuf))
outBuf, err := decryptBytes(cryptKey, decryptBuf, sendBuf, len(sendBuf))
if err != nil {
t.Errorf("decrypt packet failed:%s", err)
}
// log.Printf("len=%d,content=%s\n", len(outBuf), outBuf)
log.Printf("decrypt data len=%d\n", len(outBuf))
log.Println("validate")
for i := 0; i < len(outBuf); i++ {
if outBuf[i] != byte('0'+i%10) {
t.Error("validate failed")
}
}
log.Println("validate ok")
}
}
func TestNetInfo(t *testing.T) {
log.Println(netInfo())
}
func assertCompareVersion(t *testing.T, v1 string, v2 string, result int) {
if compareVersion(v1, v2) != result {
t.Errorf("compare version %s %s fail\n", v1, v2)
}
}
func assertParseMajorVer(t *testing.T, v string, result int) {
if parseMajorVer(v) != result {
t.Errorf("ParseMajorVer %s fail\n", v)
}
}
func TestCompareVersion(t *testing.T) {
// test =
assertCompareVersion(t, "0.98.0", "0.98.0", EQUAL)
assertCompareVersion(t, "0.98", "0.98", EQUAL)
assertCompareVersion(t, "1.4.0", "1.4.0", EQUAL)
assertCompareVersion(t, "1.5.0", "1.5.0", EQUAL)
// test >
assertCompareVersion(t, "0.98.0.22345", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1.98.0.12345", "0.98", GREATER)
assertCompareVersion(t, "10.98.0.12345", "9.98.0.12345", GREATER)
assertCompareVersion(t, "1.4.0", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1.4", "0.98.0.12345", GREATER)
assertCompareVersion(t, "1", "0.98.0.12345", GREATER)
// test <
assertCompareVersion(t, "0.98.0.12345", "0.98.0.12346", LESS)
assertCompareVersion(t, "9.98.0.12345", "10.98.0.12345", LESS)
assertCompareVersion(t, "1.4.2", "1.5.0", LESS)
assertCompareVersion(t, "", "1.5.0", LESS)
}
func TestParseMajorVer(t *testing.T) {
assertParseMajorVer(t, "0.98.0", 0)
assertParseMajorVer(t, "0.98", 0)
assertParseMajorVer(t, "1.4.0", 1)
assertParseMajorVer(t, "1.5.0", 1)
assertParseMajorVer(t, "0.98.0.22345", 0)
assertParseMajorVer(t, "1.98.0.12345", 1)
assertParseMajorVer(t, "10.98.0.12345", 10)
assertParseMajorVer(t, "1.4.0", 1)
assertParseMajorVer(t, "1.4", 1)
assertParseMajorVer(t, "1", 1)
assertParseMajorVer(t, "2", 2)
assertParseMajorVer(t, "3", 3)
assertParseMajorVer(t, "2.1.0", 2)
assertParseMajorVer(t, "3.0.0", 3)
}

View File

@@ -1,17 +1,16 @@
package main
package openp2p
import (
"encoding/json"
"flag"
"io/ioutil"
"os"
"sync"
"time"
)
var gConf Config
const IntValueNotSet int = -99999999
type AppConfig struct {
// required
AppName string
@@ -23,16 +22,23 @@ type AppConfig struct {
PeerUser string
Enabled int // default:1
// runtime info
peerToken uint64
peerNatType int
peerIP string
peerConeNatPort int
retryNum int
retryTime time.Time
nextRetryTime time.Time
shareBandwidth int
errMsg string
connectTime time.Time
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
@@ -52,7 +58,18 @@ func (c *Config) switchApp(app AppConfig, enabled int) {
c.Apps[i].Enabled = enabled
c.Apps[i].retryNum = 0
c.Apps[i].nextRetryTime = time.Now()
return
break
}
}
c.save()
}
func (c *Config) retryApp(peerNode string) {
c.mtx.Lock()
defer c.mtx.Unlock()
for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].PeerNode == peerNode {
c.Apps[i].retryNum = 0
c.Apps[i].nextRetryTime = time.Now()
}
}
}
@@ -60,8 +77,9 @@ func (c *Config) switchApp(app AppConfig, enabled int) {
func (c *Config) add(app AppConfig, override bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
if app.SrcPort == 0 || app.DstPort == 0 {
gLog.Println(LevelERROR, "invalid app ", app)
gLog.Println(LvERROR, "invalid app ", app)
return
}
if override {
@@ -81,28 +99,36 @@ func (c *Config) delete(app AppConfig) {
}
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
for i := 0; i < len(c.Apps); i++ {
if 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()
// c.mtx.Lock()
// defer c.mtx.Unlock() // internal call
data, _ := json.MarshalIndent(c, "", " ")
err := ioutil.WriteFile("config.json", data, 0644)
if err != nil {
gLog.Println(LevelERROR, "save config.json error:", err)
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()
c.LogLevel = IntValueNotSet
c.Network.ShareBandwidth = IntValueNotSet
defer c.mtx.Unlock()
data, err := ioutil.ReadFile("config.json")
if err != nil {
@@ -111,67 +137,85 @@ func (c *Config) load() error {
}
err = json.Unmarshal(data, &c)
if err != nil {
gLog.Println(LevelERROR, "parse config.json error:", err)
gLog.Println(LvERROR, "parse config.json error:", err)
}
return err
}
// TODO: deal with multi-thread r/w
func (c *Config) setToken(token uint64) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.Network.Token = token
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
}
func (c *Config) setShareBandwidth(bw int) {
c.mtx.Lock()
defer c.mtx.Unlock()
defer c.save()
c.Network.ShareBandwidth = bw
}
type NetworkConfig struct {
// local info
Token uint64
Node string
User string
localIP string
ipv6 string
mac string
os string
publicIP string
natType int
ShareBandwidth int
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() {
serverHost := flag.String("serverhost", "api.openp2p.cn", "server host ")
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
node := flag.String("node", "", "node name. 8-31 characters")
token := flag.Uint64("token", 0, "token")
peerNode := flag.String("peernode", "", "peer node name that you want to connect")
dstIP := flag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := flag.Int("dstport", 0, "destination port ")
srcPort := flag.Int("srcport", 0, "source port ")
protocol := flag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
shareBandwidth := flag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
daemonMode := flag.Bool("d", false, "daemonMode")
notVerbose := flag.Bool("nv", false, "not log console")
logLevel := flag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
flag.Parse()
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
@@ -180,14 +224,16 @@ func parseParams() {
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
gConf.load()
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
flag.Visit(func(f *flag.Flag) {
fset.Visit(func(f *flag.Flag) {
if f.Name == "sharebandwidth" {
gConf.Network.ShareBandwidth = *shareBandwidth
}
@@ -200,31 +246,39 @@ func parseParams() {
if f.Name == "loglevel" {
gConf.LogLevel = *logLevel
}
if f.Name == "tcpport" {
gConf.Network.TCPPort = *tcpPort
}
if f.Name == "token" {
gConf.setToken(*token)
}
})
if gConf.Network.ServerHost == "" {
gConf.Network.ServerHost = *serverHost
}
if gConf.Network.Node == "" {
if *node == "" { // config and param's node both empty
hostname := defaultNodeName()
node = &hostname
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 *token != 0 {
gConf.Network.Token = *token
}
if gConf.LogLevel == IntValueNotSet {
gConf.LogLevel = *logLevel
}
if gConf.Network.ShareBandwidth == IntValueNotSet {
gConf.Network.ShareBandwidth = *shareBandwidth
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 = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gConf.Network.ServerPort = *serverPort
gConf.Network.UDPPort1 = UDPPort1
gConf.Network.UDPPort2 = UDPPort2
gLog.setLevel(LogLevel(gConf.LogLevel))
if *notVerbose {
gLog.setMode(LogFile)

115
core/daemon.go Normal file
View File

@@ -0,0 +1,115 @@
package openp2p
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/openp2p-cn/service"
)
type daemon struct {
running bool
proc *os.Process
}
func (d *daemon) Start(s service.Service) error {
gLog.Println(LvINFO, "daemon start")
return nil
}
func (d *daemon) Stop(s service.Service) error {
gLog.Println(LvINFO, "service stop")
d.running = false
if d.proc != nil {
gLog.Println(LvINFO, "stop worker")
d.proc.Kill()
}
if service.Interactive() {
gLog.Println(LvINFO, "stop daemon")
os.Exit(0)
}
return nil
}
func (d *daemon) run() {
gLog.Println(LvINFO, "daemon run start")
defer gLog.Println(LvINFO, "daemon run end")
d.running = true
binPath, _ := os.Executable()
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
gLog.Println(LvINFO, mydir)
conf := &service.Config{
Name: ProductName,
DisplayName: ProductName,
Description: ProductName,
Executable: binPath,
}
s, _ := service.New(d, conf)
go s.Run()
var args []string
// rm -d parameter
for i := 0; i < len(os.Args); i++ {
if os.Args[i] == "-d" {
args = append(os.Args[0:i], os.Args[i+1:]...)
break
}
}
args = append(args, "-nv")
for {
// start worker
tmpDump := filepath.Join("log", "dump.log.tmp")
dumpFile := filepath.Join("log", "dump.log")
f, err := os.Create(filepath.Join(tmpDump))
if err != nil {
gLog.Printf(LvERROR, "start worker error:%s", err)
return
}
gLog.Println(LvINFO, "start worker process, args:", args)
execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}}
p, err := os.StartProcess(binPath, args, execSpec)
if err != nil {
gLog.Printf(LvERROR, "start worker error:%s", err)
return
}
d.proc = p
_, _ = p.Wait()
f.Close()
time.Sleep(time.Second)
err = os.Rename(tmpDump, dumpFile)
if err != nil {
gLog.Printf(LvERROR, "rename dump error:%s", err)
}
if !d.running {
return
}
gLog.Printf(LvERROR, "worker stop, restart it after 10s")
time.Sleep(time.Second * 10)
}
}
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
svcConfig := &service.Config{
Name: ProductName,
DisplayName: ProductName,
Description: ProductName,
Executable: exeAbsPath,
Arguments: args,
}
s, e := service.New(d, svcConfig)
if e != nil {
return e
}
e = service.Control(s, ctrlComm)
if e != nil {
return e
}
return nil
}

21
core/errorcode.go Normal file
View File

@@ -0,0 +1,21 @@
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")
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")
)

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -9,6 +9,8 @@ import (
"os/exec"
"path/filepath"
"time"
"github.com/openp2p-cn/totp"
)
func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
@@ -17,37 +19,55 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
if err != nil {
return err
}
gLog.Printf(LevelDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead)
gLog.Printf(LvDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead)
switch subType {
case MsgPushConnectReq:
case MsgPushConnectReq: // TODO: handle a msg move to a new function
req := PushConnectReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushConnectReq:%s", err)
gLog.Printf(LvERROR, "wrong MsgPushConnectReq:%s", err)
return err
}
gLog.Printf(LevelINFO, "%s is connecting...", req.From)
gLog.Println(LevelDEBUG, "push connect response to ", req.From)
gLog.Printf(LvDEBUG, "%s is connecting...", req.From)
gLog.Println(LvDEBUG, "push connect response to ", req.From)
if compareVersion(req.Version, LeastSupportVersion) == LESS {
gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From)
rsp := PushConnectRsp{
Error: 10,
Detail: ErrVersionNotCompatible.Error(),
To: req.From,
From: pn.config.Node,
}
pn.push(req.From, MsgPushConnectRsp, rsp)
return ErrVersionNotCompatible
}
// verify totp token or token
if VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
VerifyTOTP(req.Token, pn.config.Token, time.Now().Unix()) ||
(req.FromToken == pn.config.Token) {
gLog.Printf(LevelINFO, "Access Granted\n")
t := totp.TOTP{Step: totp.RelayTOTPStep}
if t.Verify(req.Token, pn.config.Token, time.Now().Unix()+(pn.serverTs-pn.localTs)) || // localTs may behind, auto adjust ts
t.Verify(req.Token, pn.config.Token, time.Now().Unix()) {
gLog.Printf(LvINFO, "Access Granted\n")
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
// share relay node will limit bandwidth
if req.FromToken != pn.config.Token {
gLog.Printf(LevelINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth)
if req.Token != pn.config.Token {
gLog.Printf(LvINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth)
config.shareBandwidth = pn.config.ShareBandwidth
}
// go pn.AddTunnel(config, req.ID)
go pn.addDirectTunnel(config, req.ID)
break
}
gLog.Println(LevelERROR, "Access Denied:", req.From)
gLog.Println(LvERROR, "Access Denied:", req.From)
rsp := PushConnectRsp{
Error: 1,
Detail: fmt.Sprintf("connect to %s error: Access Denied", pn.config.Node),
@@ -59,19 +79,19 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
rsp := PushRsp{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong pushRsp:%s", err)
gLog.Printf(LvERROR, "wrong pushRsp:%s", err)
return err
}
if rsp.Error == 0 {
gLog.Printf(LevelDEBUG, "push ok, detail:%s", rsp.Detail)
gLog.Printf(LvDEBUG, "push ok, detail:%s", rsp.Detail)
} else {
gLog.Printf(LevelERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail)
gLog.Printf(LvERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail)
}
case MsgPushAddRelayTunnelReq:
req := AddRelayTunnelReq{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
gLog.Printf(LvERROR, "wrong RelayNodeRsp:%s", err)
return err
}
config := AppConfig{}
@@ -83,13 +103,19 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
// notify peer relay ready
msg := TunnelMsg{ID: t.id}
pn.push(r.From, MsgPushAddRelayTunnelRsp, msg)
SaveKey(req.AppID, req.AppKey)
}
}(req)
case MsgPushAPPKey:
req := APPKeySync{}
err := json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req)
if err != nil {
gLog.Printf(LvERROR, "wrong APPKeySync:%s", err)
return err
}
SaveKey(req.AppID, req.AppKey)
case MsgPushUpdate:
gLog.Println(LevelINFO, "MsgPushUpdate")
update() // download new version first, then exec ./openp2p update
gLog.Println(LvINFO, "MsgPushUpdate")
update(pn.config.ServerHost, pn.config.ServerPort) // download new version first, then exec ./openp2p update
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
args := []string{"update"}
env := os.Environ()
@@ -104,11 +130,11 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
}
return err
case MsgPushRestart:
gLog.Println(LevelINFO, "MsgPushRestart")
gLog.Println(LvINFO, "MsgPushRestart")
os.Exit(0)
return err
case MsgPushReportApps:
gLog.Println(LevelINFO, "MsgPushReportApps")
gLog.Println(LvINFO, "MsgPushReportApps")
req := ReportApps{}
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
@@ -116,6 +142,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
appActive := 0
relayNode := ""
relayMode := ""
linkMode := LinkModeUDPPunch
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
@@ -124,6 +151,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
}
relayNode = app.relayNode
relayMode = app.relayMode
linkMode = app.tunnel.linkModeWeb
}
appInfo := AppInfo{
AppName: config.AppName,
@@ -132,6 +160,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
SrcPort: config.SrcPort,
RelayNode: relayNode,
RelayMode: relayMode,
LinkMode: linkMode,
PeerNode: config.PeerNode,
DstHost: config.DstHost,
DstPort: config.DstPort,
@@ -146,12 +175,55 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
req.Apps = append(req.Apps, appInfo)
}
pn.write(MsgReport, MsgReportApps, &req)
case MsgPushReportLog:
gLog.Println(LvDEBUG, "MsgPushReportLog")
req := ReportLogReq{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &req)
if err != nil {
gLog.Printf(LvERROR, "wrong MsgPushReportLog:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
if req.FileName == "" {
req.FileName = "openp2p.log"
}
f, err := os.Open(filepath.Join("log", req.FileName))
if err != nil {
gLog.Println(LvERROR, "read log file error:", err)
break
}
fi, err := f.Stat()
if err != nil {
break
}
if req.Offset == 0 && fi.Size() > 4096 {
req.Offset = fi.Size() - 4096
}
if req.Len <= 0 {
req.Len = 4096
}
f.Seek(req.Offset, 0)
if req.Len > 1024*1024 { // too large
break
}
buff := make([]byte, req.Len)
readLength, err := f.Read(buff)
f.Close()
if err != nil {
gLog.Println(LvERROR, "read log content error:", err)
break
}
rsp := ReportLogRsp{}
rsp.Content = string(buff[:readLength])
rsp.FileName = req.FileName
rsp.Total = fi.Size()
rsp.Len = req.Len
pn.write(MsgReport, MsgPushReportLog, &rsp)
case MsgPushEditApp:
gLog.Println(LevelINFO, "MsgPushEditApp")
gLog.Println(LvINFO, "MsgPushEditApp")
newApp := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &newApp)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditApp:%s %s", err, string(msg[openP2PHeaderSize:]))
gLog.Printf(LvERROR, "wrong MsgPushEditApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
oldConf := AppConfig{Enabled: 1}
@@ -169,39 +241,47 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
newConf.Protocol = newApp.Protocol
newConf.SrcPort = newApp.SrcPort
gConf.add(newConf, false)
gConf.save() // save quickly for the next request reportApplist
pn.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end
// autoReconnect will auto AddApp
// pn.AddApp(config)
// TODO: report result
case MsgPushEditNode:
gLog.Println(LevelINFO, "MsgPushEditNode")
gLog.Println(LvINFO, "MsgPushEditNode")
req := EditNode{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &req)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushEditNode:%s %s", err, string(msg[openP2PHeaderSize:]))
gLog.Printf(LvERROR, "wrong MsgPushEditNode:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
gConf.setNode(req.NewName)
gConf.setShareBandwidth(req.Bandwidth)
gConf.save()
// TODO: hot reload
os.Exit(0)
case MsgPushSwitchApp:
gLog.Println(LevelINFO, "MsgPushSwitchApp")
gLog.Println(LvINFO, "MsgPushSwitchApp")
app := AppInfo{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &app)
if err != nil {
gLog.Printf(LevelERROR, "wrong MsgPushSwitchApp:%s %s", err, string(msg[openP2PHeaderSize:]))
gLog.Printf(LvERROR, "wrong MsgPushSwitchApp:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
gLog.Println(LevelINFO, app.AppName, " switch to ", app.Enabled)
gLog.Println(LvINFO, app.AppName, " switch to ", app.Enabled)
gConf.switchApp(config, app.Enabled)
if app.Enabled == 0 {
// disable APP
pn.DeleteApp(config)
}
case MsgPushDstNodeOnline:
gLog.Println(LvINFO, "MsgPushDstNodeOnline")
app := PushDstNodeOnline{}
err := json.Unmarshal(msg[openP2PHeaderSize:], &app)
if err != nil {
gLog.Printf(LvERROR, "wrong MsgPushDstNodeOnline:%s %s", err, string(msg[openP2PHeaderSize:]))
return err
}
gLog.Println(LvINFO, "retry peerNode ", app.Node)
gConf.retryApp(app.Node)
default:
pn.msgMapMtx.Lock()
ch := pn.msgMap[pushHead.From]

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -11,8 +11,8 @@ import (
)
func handshakeC2C(t *P2PTunnel) (err error) {
gLog.Printf(LevelDEBUG, "handshakeC2C %s:%d:%d to %s:%d", t.pn.config.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
defer gLog.Printf(LevelDEBUG, "handshakeC2C ok")
gLog.Printf(LvDEBUG, "handshakeC2C %s:%d:%d to %s:%d", t.pn.config.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
@@ -20,49 +20,51 @@ func handshakeC2C(t *P2PTunnel) (err error) {
defer conn.Close()
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C write MsgPunchHandshake error:", err)
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshake error:", err)
return err
}
ra, head, _, _, err := UDPRead(conn, 5000)
ra, head, _, _, err := UDPRead(conn, SymmetricHandshakeAckTimeout)
if err != nil {
time.Sleep(time.Millisecond * 200)
gLog.Println(LevelDEBUG, err, ", return this error when ip was not reachable, retry read")
ra, head, _, _, err = UDPRead(conn, 5000)
gLog.Println(LvDEBUG, err, ", return this error when ip was not reachable, retry read")
ra, head, _, _, err = UDPRead(conn, SymmetricHandshakeAckTimeout)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C read MsgPunchHandshake error:", err)
gLog.Println(LvDEBUG, "handshakeC2C read MsgPunchHandshake error:", err)
return err
}
}
t.ra, _ = net.ResolveUDPAddr("udp", ra.String())
// cone server side
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake {
gLog.Printf(LevelDEBUG, "read %d handshake ", t.id)
gLog.Printf(LvDEBUG, "read %d handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, 5000)
_, head, _, _, err = UDPRead(conn, SymmetricHandshakeAckTimeout)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
return err
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "read %d handshake ack ", t.id)
gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id)
gLog.Printf(LvINFO, "handshakeC2C ok")
return nil
}
}
// cone client side will only read handshake ack
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "read %d handshake ack ", t.id)
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(LevelDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
return err
}
return err
}
gLog.Printf(LvINFO, "handshakeC2C ok")
return nil
}
func handshakeC2S(t *P2PTunnel) error {
gLog.Printf(LevelDEBUG, "handshakeC2S start")
defer gLog.Printf(LevelDEBUG, "handshakeC2S end")
gLog.Printf(LvDEBUG, "handshakeC2S start")
defer gLog.Printf(LvDEBUG, "handshakeC2S end")
// even if read timeout, continue handshake
t.pn.read(t.config.PeerNode, MsgPush, MsgPushHandshakeStart, SymmetricHandshakeAckTimeout)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
@@ -73,7 +75,7 @@ func handshakeC2S(t *P2PTunnel) error {
}
defer conn.Close()
go func() error {
gLog.Printf(LevelDEBUG, "send symmetric handshake to %s from %d:%d start", t.config.peerIP, t.coneLocalPort, t.coneNatPort)
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++ {
// TODO: auto calc cost time
time.Sleep(SymmetricHandshakeInterval)
@@ -83,47 +85,48 @@ func handshakeC2S(t *P2PTunnel) error {
}
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil {
gLog.Println(LevelDEBUG, "handshakeC2S write MsgPunchHandshake error:", err)
gLog.Println(LvDEBUG, "handshakeC2S write MsgPunchHandshake error:", err)
return err
}
}
gLog.Println(LevelDEBUG, "send symmetric handshake end")
gLog.Println(LvDEBUG, "send symmetric handshake end")
return nil
}()
deadline := time.Now().Add(SymmetricHandshakeAckTimeout)
err = conn.SetReadDeadline(deadline)
if err != nil {
gLog.Println(LevelERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error")
gLog.Println(LvERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error")
return err
}
// read response of the punching hole ok port
result := make([]byte, 1024)
_, dst, err := conn.ReadFrom(result)
if err != nil {
gLog.Println(LevelERROR, "handshakeC2S wait timeout")
gLog.Println(LvERROR, "handshakeC2S wait timeout")
return err
}
head := &openP2PHeader{}
err = binary.Read(bytes.NewReader(result[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "parse p2pheader error:", err)
gLog.Println(LvERROR, "parse p2pheader error:", err)
return err
}
t.ra, _ = net.ResolveUDPAddr("udp", dst.String())
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "handshakeC2S read %d handshake ack %s", t.id, dst.String())
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ack %s", t.id, dst.String())
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
return err
}
gLog.Printf(LvINFO, "handshakeC2S ok")
return nil
}
func handshakeS2C(t *P2PTunnel) error {
gLog.Printf(LevelDEBUG, "handshakeS2C start")
defer gLog.Printf(LevelDEBUG, "handshakeS2C end")
gLog.Printf(LvDEBUG, "handshakeS2C start")
defer gLog.Printf(LvDEBUG, "handshakeS2C end")
gotCh := make(chan *net.UDPAddr, 5)
// sequencely udp send handshake, do not parallel send
gLog.Printf(LevelDEBUG, "send symmetric handshake to %s:%d start", t.config.peerIP, t.config.peerConeNatPort)
gLog.Printf(LvDEBUG, "send symmetric handshake to %s:%d start", t.config.peerIP, t.config.peerConeNatPort)
gotIt := false
gotMtx := sync.Mutex{}
for i := 0; i < SymmetricHandshakeNum; i++ {
@@ -132,12 +135,12 @@ func handshakeS2C(t *P2PTunnel) error {
go func(t *P2PTunnel) error {
conn, err := net.ListenUDP("udp", nil)
if err != nil {
gLog.Printf(LevelDEBUG, "listen error")
gLog.Printf(LvDEBUG, "listen error")
return err
}
defer conn.Close()
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
_, head, _, _, err := UDPRead(conn, 10000)
_, head, _, _, err := UDPRead(conn, SymmetricHandshakeAckTimeout)
if err != nil {
// gLog.Println(LevelDEBUG, "one of the handshake error:", err)
return err
@@ -150,15 +153,15 @@ func handshakeS2C(t *P2PTunnel) error {
gotIt = true
t.la, _ = net.ResolveUDPAddr("udp", conn.LocalAddr().String())
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake {
gLog.Printf(LevelDEBUG, "handshakeS2C read %d handshake ", t.id)
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, 5000)
_, head, _, _, err = UDPRead(conn, SymmetricHandshakeAckTimeout)
if err != nil {
gLog.Println(LevelDEBUG, "handshakeS2C handshake error")
gLog.Println(LvDEBUG, "handshakeS2C handshake error")
return err
}
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LevelDEBUG, "handshakeS2C read %d handshake ack %s", t.id, conn.LocalAddr().String())
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ack %s", t.id, conn.LocalAddr().String())
gotCh <- t.la
return nil
}
@@ -166,15 +169,16 @@ func handshakeS2C(t *P2PTunnel) error {
return nil
}(t)
}
gLog.Printf(LevelDEBUG, "send symmetric handshake end")
gLog.Println(LevelDEBUG, "handshakeS2C ready, notify peer connect")
gLog.Printf(LvDEBUG, "send symmetric handshake end")
gLog.Println(LvDEBUG, "handshakeS2C ready, notify peer connect")
t.pn.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
select {
case <-time.After(SymmetricHandshakeAckTimeout):
return fmt.Errorf("wait handshake failed")
case la := <-gotCh:
gLog.Println(LevelDEBUG, "symmetric handshake ok", la)
gLog.Println(LvDEBUG, "symmetric handshake ok", la)
gLog.Printf(LvINFO, "handshakeS2C ok")
}
return nil
}

129
core/install.go Normal file
View File

@@ -0,0 +1,129 @@
package openp2p
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"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 group 16947733, Email openp2p.cn@gmail.com")
gLog.Println(LvINFO, "install start")
defer gLog.Println(LvINFO, "install end")
// auto uninstall
err := os.MkdirAll(defaultInstallPath, 0775)
if err != nil {
gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
return
}
err = os.Chdir(defaultInstallPath)
if err != nil {
gLog.Println(LvERROR, "cd error:", err)
return
}
uninstall()
// save config file
parseParams("install")
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
// copy files
binPath, _ := os.Executable()
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
if errFiles != nil {
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles)
return
}
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if errFiles != nil {
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
return
}
_, errFiles = io.Copy(dst, src)
if errFiles != nil {
gLog.Printf(LvERROR, "io.Copy error:%s", errFiles)
return
}
src.Close()
dst.Close()
// install system service
gLog.Println(LvINFO, "targetPath:", targetPath)
err = d.Control("install", targetPath, []string{"-d"})
if err == nil {
gLog.Println(LvINFO, "install system service ok.")
}
time.Sleep(time.Second * 2)
err = d.Control("start", targetPath, []string{"-d"})
if err != nil {
gLog.Println(LvERROR, "start openp2p service error:", err)
} else {
gLog.Println(LvINFO, "start openp2p service ok.")
}
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
}
func installByFilename() {
params := strings.Split(filepath.Base(os.Args[0]), "-")
if len(params) < 4 {
return
}
serverHost := params[1]
token := params[2]
gLog.Println(LvINFO, "install start")
targetPath := os.Args[0]
args := []string{"install"}
args = append(args, "-serverhost")
args = append(args, serverHost)
args = append(args, "-token")
args = append(args, token)
env := os.Environ()
cmd := exec.Command(targetPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err != nil {
gLog.Println(LvERROR, "install by filename, start process error:", err)
return
}
gLog.Println(LvINFO, "install end")
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
fmt.Println("Press the Any Key to exit")
fmt.Scanln()
os.Exit(0)
}
func uninstall() {
gLog.Println(LvINFO, "uninstall start")
defer gLog.Println(LvINFO, "uninstall end")
d := daemon{}
err := d.Control("stop", "", nil)
if err != nil { // service maybe not install
return
}
err = d.Control("uninstall", "", nil)
if err != nil {
gLog.Println(LvERROR, "uninstall system service error:", err)
} else {
gLog.Println(LvINFO, "uninstall system service ok.")
}
binPath := filepath.Join(defaultInstallPath, defaultBinName)
os.Remove(binPath + "0")
os.Remove(binPath)
// os.RemoveAll(defaultInstallPath) // reserve config.json
}

164
core/log.go Normal file
View File

@@ -0,0 +1,164 @@
package openp2p
import (
"log"
"os"
"runtime"
"sync"
"time"
)
type LogLevel int
var gLog *logger
const (
LvDEBUG LogLevel = iota
LvINFO
LvWARN
LvERROR
)
var (
logFileNames map[LogLevel]string
loglevel map[LogLevel]string
)
func init() {
logFileNames = make(map[LogLevel]string)
loglevel = make(map[LogLevel]string)
logFileNames[0] = ".log"
loglevel[LvDEBUG] = "DEBUG"
loglevel[LvINFO] = "INFO"
loglevel[LvWARN] = "WARN"
loglevel[LvERROR] = "ERROR"
}
const (
LogFile = iota
LogConsole
LogFileAndConsole
)
type logger struct {
loggers map[LogLevel]*log.Logger
files map[LogLevel]*os.File
level LogLevel
logDir string
mtx *sync.Mutex
lineEnding string
pid int
maxLogSize int64
mode int
}
func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger {
loggers := make(map[LogLevel]*log.Logger)
logfiles := make(map[LogLevel]*os.File)
var (
logdir string
)
if path == "" {
logdir = "log/"
} else {
logdir = path + "/log/"
}
os.MkdirAll(logdir, 0777)
for lv := range logFileNames {
logFilePath := logdir + filePrefix + logFileNames[lv]
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
os.Chmod(logFilePath, 0644)
logfiles[lv] = f
loggers[lv] = log.New(f, "", log.LstdFlags)
}
var le string
if runtime.GOOS == "windows" {
le = "\r\n"
} else {
le = "\n"
}
pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode}
go pLog.checkFile()
return pLog
}
func (l *logger) setLevel(level LogLevel) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.level = level
}
func (l *logger) setMode(mode int) {
l.mtx.Lock()
defer l.mtx.Unlock()
l.mode = mode
}
func (l *logger) checkFile() {
if l.maxLogSize <= 0 {
return
}
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
l.mtx.Lock()
for lv, logFile := range l.files {
f, e := logFile.Stat()
if e != nil {
continue
}
if f.Size() <= l.maxLogSize {
continue
}
logFile.Close()
fname := f.Name()
backupPath := l.logDir + fname + ".0"
os.Remove(backupPath)
os.Rename(l.logDir+fname, backupPath)
newFile, e := os.OpenFile(l.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if e == nil {
l.loggers[lv].SetOutput(newFile)
l.files[lv] = newFile
}
}
l.mtx.Unlock()
}
}
}
func (l *logger) Printf(level LogLevel, format string, params ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
if level < l.level {
return
}
pidAndLevel := []interface{}{l.pid, loglevel[level]}
params = append(pidAndLevel, params...)
if l.mode == LogFile || l.mode == LogFileAndConsole {
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...)
}
}
func (l *logger) Println(level LogLevel, params ...interface{}) {
l.mtx.Lock()
defer l.mtx.Unlock()
if level < l.level {
return
}
pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...)
params = append(params, l.lineEnding)
if l.mode == LogFile || l.mode == LogFileAndConsole {
l.loggers[0].Print(params...)
}
if l.mode == LogConsole || l.mode == LogFileAndConsole {
log.Print(params...)
}
}

192
core/nat.go Normal file
View File

@@ -0,0 +1,192 @@
package openp2p
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"net"
"strconv"
"strings"
"sync"
"time"
reuse "github.com/openp2p-cn/go-reuseport"
)
func natTCP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort 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", localPort), fmt.Sprintf("%s:%d", serverHost, serverPort), time.Second*5)
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
if err != nil {
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
return
}
defer conn.Close()
_, 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{}
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
return natRsp.IP, natRsp.Port, nil
}
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, hasIPvr int, hasUPNPorNATPMP int, err error) {
// the random local port may be used by other.
localPort := int(rand.Uint32()%15000 + 50000)
echoPort := gConf.Network.TCPPort
ip1, port1, err := natTest(host, udp1, localPort)
if err != nil {
return "", 0, 0, 0, err
}
hasIPv4, hasUPNPorNATPMP := publicIPTest(ip1, echoPort)
gLog.Printf(LvINFO, "local port:%d, nat port:%d, hasIPv4:%d, UPNP:%d", localPort, port1, hasIPv4, hasUPNPorNATPMP)
_, 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, hasIPv4, hasUPNPorNATPMP, err
}
natType := NATSymmetric
if port1 == port2 {
natType = NATCone
}
return ip1, natType, hasIPv4, hasUPNPorNATPMP, nil
}
func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATPMP int) {
var echoConn *net.UDPConn
var wg sync.WaitGroup
wg.Add(1)
go func() {
gLog.Println(LvDEBUG, "echo server start")
var err error
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
if err != nil {
gLog.Println(LvERROR, "echo server listen error:", err)
return
}
buf := make([]byte, 1600)
// close outside for breaking the ReadFromUDP
// wait 5s for echo testing
wg.Done()
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")
}()
wg.Wait() // wait echo udp
defer echoConn.Close()
// testing for public ip
for i := 0; i < 2; i++ {
if i == 1 {
// test upnp or nat-pmp
gLog.Println(LvDEBUG, "upnp test start")
nat, err := Discover()
if err != nil || nat == nil {
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
break
}
ext, err := nat.GetExternalAddress()
if err != nil {
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
break
}
log.Println("PublicIP:", ext)
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 604800) // 7 days, upnp will perform failed when os start
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
}
}
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
}

104
core/openp2p.go Normal file
View File

@@ -0,0 +1,104 @@
package openp2p
import (
"fmt"
"math/rand"
"os"
"path/filepath"
"strconv"
"time"
)
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, LogFileAndConsole)
// TODO: install sub command, deamon process
if len(os.Args) > 1 {
switch os.Args[1] {
case "version", "-v", "--version":
fmt.Println(OpenP2PVersion)
return
case "update":
gLog = NewLogger(baseDir, ProductName, LvDEBUG, 1024*1024, LogFileAndConsole)
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
err := d.Control("restart", targetPath, nil)
if err != nil {
gLog.Println(LvERROR, "restart service error:", err)
} else {
gLog.Println(LvINFO, "restart service ok.")
}
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)
}
network := P2PNetworkInstance(&gConf.Network)
if ok := network.Connect(30000); !ok {
gLog.Println(LvERROR, "P2PNetwork login error")
return
}
gLog.Println(LvINFO, "waiting for connection...")
forever := make(chan bool)
<-forever
}
var network *P2PNetwork
// 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, LvDEBUG, 1024*1024, LogFileAndConsole)
parseParams("")
n, err := strconv.ParseUint(token, 10, 64)
if err == nil {
gConf.setToken(n)
}
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)
network = P2PNetworkInstance(&gConf.Network)
if ok := network.Connect(30000); !ok {
gLog.Println(LvERROR, "P2PNetwork login error")
return nil
}
gLog.Println(LvINFO, "waiting for connection...")
return network
}
func GetToken(baseDir string) string {
os.Chdir(baseDir)
gConf.load()
return fmt.Sprintf("%d", gConf.Network.Token)
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -35,46 +35,45 @@ type overlayConn struct {
// for udp
connUDP *net.UDPConn
remoteAddr net.Addr
udpRelayData chan []byte
udpData chan []byte
lastReadUDPTs time.Time
}
func (oConn *overlayConn) run() {
gLog.Printf(LevelDEBUG, "%d overlayConn run start", oConn.id)
defer gLog.Printf(LevelDEBUG, "%d overlayConn run end", oConn.id)
oConn.running = true
gLog.Printf(LvDEBUG, "%d overlayConn run start", oConn.id)
defer gLog.Printf(LvDEBUG, "%d overlayConn run end", oConn.id)
oConn.lastReadUDPTs = time.Now()
buffer := make([]byte, ReadBuffLen+PaddingSize)
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
}
// overlay tcp connection normal close, debug log
gLog.Printf(LevelDEBUG, "overlayConn %d read error:%s,close it", oConn.id, err)
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...)
if oConn.rtid == 0 {
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgOverlayData, writeBytes)
gLog.Printf(LevelDEBUG, "write overlay data to %d:%d bodylen=%d", oConn.rtid, oConn.id, len(writeBytes))
gLog.Printf(LvDEBUG, "write overlay data to %d:%d bodylen=%d", oConn.rtid, 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(LevelDEBUG, "write relay data to %d:%d bodylen=%d", oConn.rtid, oConn.id, len(writeBytes))
gLog.Printf(LvDEBUG, "write relay data to %d:%d bodylen=%d", oConn.rtid, oConn.id, len(writeBytes))
}
}
if oConn.connTCP != nil {
@@ -98,7 +97,11 @@ func (oConn *overlayConn) run() {
}
}
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 +109,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 +125,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 +151,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
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -17,7 +17,7 @@ type p2pApp struct {
listener net.Listener
listenerUDP *net.UDPConn
tunnel *P2PTunnel
rtid uint64
rtid uint64 // relay tunnelID
relayNode string
relayMode string
hbTime time.Time
@@ -48,19 +48,19 @@ func (app *p2pApp) updateHeartbeat() {
}
func (app *p2pApp) listenTCP() error {
gLog.Printf(LevelDEBUG, "tcp accept on port %d start", app.config.SrcPort)
defer gLog.Printf(LevelDEBUG, "tcp accept on port %d end", app.config.SrcPort)
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
app.listener, err = net.Listen("tcp4", fmt.Sprintf("0.0.0.0:%d", app.config.SrcPort))
app.listener, err = net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", app.config.SrcPort)) // support tcp4 and tcp6
if err != nil {
gLog.Printf(LevelERROR, "listen error:%s", err)
gLog.Printf(LvERROR, "listen error:%s", err)
return err
}
for app.running {
conn, err := app.listener.Accept()
if err != nil {
if app.running {
gLog.Printf(LevelERROR, "%d accept error:%s", app.tunnel.id, err)
gLog.Printf(LvERROR, "%d accept error:%s", app.id, err)
}
break
}
@@ -72,6 +72,7 @@ func (app *p2pApp) listenTCP() error {
rtid: app.rtid,
appID: app.id,
appKey: app.key,
running: true,
}
// pre-calc key bytes for encrypt
if oConn.appKey != 0 {
@@ -81,7 +82,7 @@ func (app *p2pApp) listenTCP() error {
oConn.appKeyBytes = encryptKey
}
app.tunnel.overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LevelDEBUG, "Accept TCP overlayID:%d", oConn.id)
gLog.Printf(LvDEBUG, "Accept TCP overlayID:%d", oConn.id)
// tell peer connect
req := OverlayConnectReq{ID: oConn.id,
Token: app.tunnel.pn.config.Token,
@@ -100,35 +101,37 @@ func (app *p2pApp) listenTCP() error {
msgWithHead := append(relayHead.Bytes(), msg...)
app.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
}
// 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(LevelDEBUG, "udp accept on port %d start", app.config.SrcPort)
defer gLog.Printf(LevelDEBUG, "udp accept on port %d end", app.config.SrcPort)
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(LevelERROR, "listen error:%s", err)
gLog.Printf(LvERROR, "listen error:%s", err)
return err
}
buffer := make([]byte, 64*1024)
buffer := make([]byte, 64*1024+PaddingSize)
udpID := make([]byte, 8)
for {
app.listenerUDP.SetReadDeadline(time.Now().Add(time.Second * 10))
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(LevelERROR, "udp read failed:%s", err)
gLog.Printf(LvERROR, "udp read failed:%s", err)
break
}
} else {
b := bytes.Buffer{}
b.Write(buffer[:len])
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])
@@ -139,19 +142,20 @@ func (app *p2pApp) listenUDP() error {
udpID[3] = a[3]
udpID[4] = byte(port)
udpID[5] = byte(port >> 8)
id := binary.LittleEndian.Uint64(udpID)
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,
udpRelayData: make(chan []byte, 1000),
id: id,
isClient: true,
rtid: app.rtid,
appID: app.id,
appKey: app.key,
tunnel: app.tunnel,
connUDP: app.listenerUDP,
remoteAddr: remoteAddr,
udpData: make(chan []byte, 1000),
id: id,
isClient: true,
rtid: app.rtid,
appID: app.id,
appKey: app.key,
running: true,
}
// calc key bytes for encrypt
if oConn.appKey != 0 {
@@ -161,7 +165,7 @@ func (app *p2pApp) listenUDP() error {
oConn.appKeyBytes = encryptKey
}
app.tunnel.overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LevelDEBUG, "Accept UDP overlayID:%d", oConn.id)
gLog.Printf(LvDEBUG, "Accept UDP overlayID:%d", oConn.id)
// tell peer connect
req := OverlayConnectReq{ID: oConn.id,
Token: app.tunnel.pn.config.Token,
@@ -180,8 +184,10 @@ func (app *p2pApp) listenUDP() error {
msgWithHead := append(relayHead.Bytes(), msg...)
app.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
}
// TODO: wait OverlayConnectRsp instead of sleep
time.Sleep(time.Second) // waiting remote node connection ok
go oConn.run()
oConn.udpRelayData <- b.Bytes()
oConn.udpData <- dupData.Bytes()
}
// load from app.tunnel.overlayConns by remoteAddr ok, write relay data
@@ -189,27 +195,30 @@ func (app *p2pApp) listenUDP() error {
if !ok {
continue
}
overlayConn.udpRelayData <- b.Bytes()
overlayConn.udpData <- dupData.Bytes()
}
}
return nil
}
func (app *p2pApp) listen() error {
gLog.Printf(LevelINFO, "LISTEN ON PORT %s:%d START", app.config.Protocol, app.config.SrcPort)
defer gLog.Printf(LevelINFO, "LISTEN ON PORT %s:%d END", app.config.Protocol, app.config.SrcPort)
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()
app.running = true
if app.rtid != 0 {
go app.relayHeartbeatLoop()
}
for app.tunnel.isRuning() && app.running {
for app.tunnel.isRuning() {
if app.config.Protocol == "udp" {
app.listenUDP()
} else {
app.listenTCP()
}
if !app.running {
break
}
time.Sleep(time.Second * 10)
}
return nil
@@ -233,8 +242,8 @@ func (app *p2pApp) close() {
func (app *p2pApp) relayHeartbeatLoop() {
app.wg.Add(1)
defer app.wg.Done()
gLog.Printf(LevelDEBUG, "relayHeartbeat to %d start", app.rtid)
defer gLog.Printf(LevelDEBUG, "relayHeartbeat to %d end", app.rtid)
gLog.Printf(LvDEBUG, "relayHeartbeat to %d start", app.rtid)
defer gLog.Printf(LvDEBUG, "relayHeartbeat to %d end", app.rtid)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, app.rtid)
req := RelayHeartbeat{RelayTunnelID: app.tunnel.id,

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"sync"

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -7,9 +7,8 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"math/rand"
"net"
"net/http"
"net/url"
"strings"
"sync"
@@ -23,15 +22,21 @@ var (
once sync.Once
)
const (
retryLimit = 20
retryInterval = 10 * time.Second
)
type P2PNetwork struct {
conn *websocket.Conn
online bool
running bool
restartCh chan bool
wg sync.WaitGroup
writeMtx sync.Mutex
serverTs int64
localTs int64
conn *websocket.Conn
online bool
running bool
restartCh chan bool
wgReconnect sync.WaitGroup
writeMtx sync.Mutex
serverTs int64
localTs int64
hbTime time.Time
// msgMap sync.Map
msgMap map[uint64]chan []byte //key: nodeID
msgMapMtx sync.Mutex
@@ -63,20 +68,19 @@ func P2PNetworkInstance(config *NetworkConfig) *P2PNetwork {
}
func (pn *P2PNetwork) run() {
go pn.autorunApp()
heartbeatTimer := time.NewTicker(NetworkHeartbeatTime)
for pn.running {
select {
case <-heartbeatTimer.C: // TODO: deal with connect failed, no send hb
case <-heartbeatTimer.C:
pn.write(MsgHeartbeat, 0, "")
case <-pn.restartCh:
pn.online = false
pn.wg.Wait() // wait read/write goroutine exited
time.Sleep(NetworkHeartbeatTime)
pn.wgReconnect.Wait() // wait read/autorunapp goroutine end
time.Sleep(ClientAPITimeout)
err := pn.init()
if err != nil {
gLog.Println(LevelERROR, "P2PNetwork init error:", err)
gLog.Println(LvERROR, "P2PNetwork init error:", err)
}
}
}
@@ -85,7 +89,7 @@ func (pn *P2PNetwork) run() {
func (pn *P2PNetwork) Connect(timeout int) bool {
// waiting for login response
for i := 0; i < (timeout / 1000); i++ {
if pn.serverTs != 0 {
if pn.hbTime.After(time.Now().Add(-NetworkHeartbeatTime)) {
return true
}
time.Sleep(time.Second)
@@ -109,12 +113,10 @@ func (pn *P2PNetwork) runAll() {
config.AppName = fmt.Sprintf("%s%d", config.Protocol, config.SrcPort)
}
appExist := false
var appID uint64
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
appExist = true
appID = app.id
if app.isActive() {
continue
}
@@ -122,66 +124,71 @@ func (pn *P2PNetwork) runAll() {
if appExist {
pn.DeleteApp(*config)
}
if config.retryNum > 0 {
gLog.Printf(LevelINFO, "detect app %s(%d) disconnect, reconnecting the %d times...", config.AppName, appID, config.retryNum)
if time.Now().Add(-time.Minute * 15).After(config.retryTime) { // normal lasts 15min
if config.retryNum >= retryLimit {
continue
}
if config.retryNum > 0 { // first time not show reconnect log
gLog.Printf(LvINFO, "detect app %s disconnect, reconnecting the %d times...", config.AppName, config.retryNum)
if time.Now().Add(-time.Minute * 15).After(config.retryTime) { // run normally 15min, reset retrynum
config.retryNum = 0
}
}
config.retryNum++
config.retryTime = time.Now()
increase := math.Pow(1.3, float64(config.retryNum))
if increase > 900 {
increase = 900
}
config.nextRetryTime = time.Now().Add(time.Second * time.Duration(increase)) // exponential increase retry time. 1.3^x
config.nextRetryTime = time.Now().Add(retryInterval)
config.connectTime = time.Now()
gConf.mtx.Unlock() // AddApp will take a period of time
config.peerToken = pn.config.Token
gConf.mtx.Unlock() // AddApp will take a period of time, let outside modify gConf
err := pn.AddApp(*config)
gConf.mtx.Lock()
if err != nil {
config.errMsg = err.Error()
if err == ErrPeerOffline { // stop retry, waiting for online
config.retryNum = retryLimit
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", config.PeerNode)
}
}
}
}
func (pn *P2PNetwork) autorunApp() {
gLog.Println(LevelINFO, "autorunApp start")
for pn.running {
gLog.Println(LvINFO, "autorunApp start")
pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done()
for pn.running && pn.online {
time.Sleep(time.Second)
if !pn.online {
continue
}
pn.runAll()
time.Sleep(time.Second * 10)
}
gLog.Println(LevelINFO, "autorunApp end")
gLog.Println(LvINFO, "autorunApp end")
}
func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint64) (*P2PTunnel, uint64, string, error) {
gLog.Printf(LevelINFO, "addRelayTunnel to %s start", config.PeerNode)
defer gLog.Printf(LevelINFO, "addRelayTunnel to %s end", config.PeerNode)
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)
// request a relay node or specify manually(TODO)
pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode})
head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, time.Second*10)
head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, ClientAPITimeout)
if head == nil {
return nil, 0, "", errors.New("read MsgRelayNodeRsp error")
}
rsp := RelayNodeRsp{}
err := json.Unmarshal(body, &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
gLog.Printf(LvERROR, "wrong RelayNodeRsp:%s", err)
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
}
if rsp.RelayName == "" || rsp.RelayToken == 0 {
gLog.Printf(LevelERROR, "MsgRelayNodeReq error")
gLog.Printf(LvERROR, "MsgRelayNodeReq error")
return nil, 0, "", errors.New("MsgRelayNodeReq error")
}
gLog.Printf(LevelINFO, "got relay node:%s", rsp.RelayName)
gLog.Printf(LvINFO, "got relay node:%s", rsp.RelayName)
relayConfig := config
relayConfig.PeerNode = rsp.RelayName
relayConfig.peerToken = rsp.RelayToken
///
t, err := pn.addDirectTunnel(relayConfig, 0)
if err != nil {
gLog.Println(LevelERROR, "direct connect error:", err)
gLog.Println(LvERROR, "direct connect error:", err)
return nil, 0, "", err
}
// notify peer addRelayTunnel
@@ -189,22 +196,20 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint
From: pn.config.Node,
RelayName: rsp.RelayName,
RelayToken: rsp.RelayToken,
AppID: appid,
AppKey: appkey,
}
gLog.Printf(LevelINFO, "push relay %s---------%s", config.PeerNode, rsp.RelayName)
gLog.Printf(LvINFO, "push relay %s---------%s", config.PeerNode, rsp.RelayName)
pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req)
// wait relay ready
head, body = pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount) // TODO: const value
if head == nil {
gLog.Printf(LevelERROR, "read MsgPushAddRelayTunnelRsp error")
gLog.Printf(LvERROR, "read MsgPushAddRelayTunnelRsp error")
return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error")
}
rspID := TunnelMsg{}
err = json.Unmarshal(body, &rspID)
if err != nil {
gLog.Printf(LevelERROR, "wrong RelayNodeRsp:%s", err)
gLog.Printf(LvERROR, "wrong RelayNodeRsp:%s", err)
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
}
return t, rspID.ID, rsp.Mode, err
@@ -212,8 +217,8 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig, appid uint64, appkey uint
// use *AppConfig to save status
func (pn *P2PNetwork) AddApp(config AppConfig) error {
gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d start", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelINFO, "addApp %s to %s:%s:%d end", config.AppName, config.PeerNode, config.DstHost, config.DstPort)
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")
}
@@ -239,10 +244,11 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
peerNatType = t.config.peerNatType
peerIP = t.config.peerIP
}
// TODO: if tcp failed, should try udp punching, nattype should refactor also, when NATNONE and failed we don't know the peerNatType
if err != nil && err == ErrorHandshake {
gLog.Println(LevelERROR, "direct connect failed, try to relay")
appKey = rand.Uint64()
t, rtid, relayMode, err = pn.addRelayTunnel(config, appID, appKey)
gLog.Println(LvERROR, "direct connect failed, try to relay")
t, rtid, relayMode, err = pn.addRelayTunnel(config)
if t != nil {
relayNode = t.config.PeerNode
}
@@ -269,6 +275,16 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
if err != nil {
return err
}
if rtid != 0 || t.conn.Protocol() == "tcp" {
// sync appkey
appKey = rand.Uint64()
req := APPKeySync{
AppID: appID,
AppKey: appKey,
}
gLog.Printf(LvINFO, "sync appkey to %s", config.PeerNode)
pn.push(config.PeerNode, MsgPushAPPKey, &req)
}
app := p2pApp{
id: appID,
key: appKey,
@@ -286,21 +302,21 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
}
func (pn *P2PNetwork) DeleteApp(config AppConfig) {
gLog.Printf(LevelINFO, "DeleteApp %s%d start", config.Protocol, config.SrcPort)
defer gLog.Printf(LevelINFO, "DeleteApp %s%d end", config.Protocol, config.SrcPort)
gLog.Printf(LvINFO, "DeleteApp %s%d start", config.Protocol, config.SrcPort)
defer gLog.Printf(LvINFO, "DeleteApp %s%d end", config.Protocol, config.SrcPort)
// close the apps of this config
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
if ok {
app := i.(*p2pApp)
gLog.Printf(LevelINFO, "app %s exist, delete it", fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
gLog.Printf(LvINFO, "app %s exist, delete it", fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
app.close()
pn.apps.Delete(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
}
}
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel, error) {
gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LevelDEBUG, "addDirectTunnel %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort)
isClient := false
// client side tid=0, assign random uint64
if tid == 0 {
@@ -320,11 +336,11 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel,
}
// client side checking
gLog.Println(LevelINFO, "tunnel already exist ", config.PeerNode)
gLog.Println(LvINFO, "tunnel already exist ", config.PeerNode)
isActive := t.checkActive()
// inactive, close it
if !isActive {
gLog.Println(LevelINFO, "but it's not active, close it ", config.PeerNode)
gLog.Println(LvINFO, "but it's not active, close it ", config.PeerNode)
t.close()
} else {
// active
@@ -334,64 +350,130 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (*P2PTunnel,
}
return true
})
if exist {
return t, nil
}
// create tunnel if not exist
if !exist {
t = &P2PTunnel{pn: pn,
config: config,
id: tid,
t = &P2PTunnel{pn: pn,
config: config,
id: tid,
}
pn.msgMapMtx.Lock()
pn.msgMap[nodeNameToID(config.PeerNode)] = make(chan []byte, 50)
pn.msgMapMtx.Unlock()
// server side
if !isClient {
err := pn.newTunnel(t, tid, isClient)
return t, err // always return
}
// client side
// peer info
initErr := t.requestPeerInfo()
if initErr != nil {
gLog.Println(LvERROR, "init error:", initErr)
return nil, initErr
}
var err error
// try TCP6
if IsIPv6(t.config.peerIPv6) && IsIPv6(t.pn.config.publicIPv6) {
gLog.Println(LvINFO, "try TCP6")
t.config.linkMode = LinkModeTCP6
t.config.isUnderlayServer = 0
if err = pn.newTunnel(t, tid, isClient); err == nil {
return t, nil
}
pn.msgMapMtx.Lock()
pn.msgMap[nodeNameToID(config.PeerNode)] = make(chan []byte, 50)
pn.msgMapMtx.Unlock()
t.init()
if isClient {
if err := t.connect(); err != nil {
gLog.Println(LevelERROR, "p2pTunnel connect error:", err)
return t, err
}
}
// TODO: try UDP6
// try TCP4
if t.config.hasIPv4 == 1 || t.pn.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1 || t.pn.config.hasUPNPorNATPMP == 1 {
gLog.Println(LvINFO, "try TCP4")
t.config.linkMode = LinkModeTCP4
if t.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1 {
t.config.isUnderlayServer = 0
} else {
rsp := PushConnectRsp{
Error: 0,
Detail: "connect ok",
To: t.config.PeerNode,
From: pn.config.Node,
NatType: pn.config.natType,
FromIP: pn.config.publicIP,
ConeNatPort: t.coneNatPort,
ID: t.id}
t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
if err := t.listen(); err != nil {
gLog.Println(LevelERROR, "p2pTunnel listen error:", err)
return t, err
}
t.config.isUnderlayServer = 1
}
if err = pn.newTunnel(t, tid, isClient); err == nil {
return t, nil
}
}
// TODO: try UDP4
// try TCPPunch
if t.config.peerNatType == NATCone && t.pn.config.natType == NATCone { // TODO: support c2s
gLog.Println(LvINFO, "try TCP4 Punch")
t.config.linkMode = LinkModeTCPPunch
t.config.isUnderlayServer = 0
if err = pn.newTunnel(t, tid, isClient); err == nil {
gLog.Println(LvINFO, "TCP4 Punch ok")
return t, nil
}
}
// try UDPPunch
if t.config.peerNatType == NATCone || t.pn.config.natType == NATCone {
gLog.Println(LvINFO, "try UDP4 Punch")
t.config.linkMode = LinkModeUDPPunch
t.config.isUnderlayServer = 0
if err = pn.newTunnel(t, tid, isClient); err == nil {
return t, nil
}
}
return nil, ErrorHandshake // only ErrorHandshake will try relay
}
func (pn *P2PNetwork) newTunnel(t *P2PTunnel, tid uint64, isClient bool) error {
t.initPort()
if isClient {
if err := t.connect(); err != nil {
gLog.Println(LvERROR, "p2pTunnel connect error:", err)
return err
}
} else {
if err := t.listen(); err != nil {
gLog.Println(LvERROR, "p2pTunnel listen error:", err)
return err
}
}
// store it when success
gLog.Printf(LevelDEBUG, "store tunnel %d", tid)
gLog.Printf(LvDEBUG, "store tunnel %d", tid)
pn.allTunnels.Store(tid, t)
return t, nil
return nil
}
func (pn *P2PNetwork) init() error {
gLog.Println(LevelINFO, "init start")
gLog.Println(LvINFO, "init start")
pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done()
var err error
for {
// detect nat type
pn.config.publicIP, pn.config.natType, err = getNATType(pn.config.ServerHost, pn.config.UDPPort1, pn.config.UDPPort2)
// TODO rm test s2s
pn.config.publicIP, pn.config.natType, pn.config.hasIPv4, pn.config.hasUPNPorNATPMP, err = getNATType(pn.config.ServerHost, pn.config.UDPPort1, pn.config.UDPPort2)
// for testcase
if strings.Contains(pn.config.Node, "openp2pS2STest") {
pn.config.natType = NATSymmetric
pn.config.hasIPv4 = 0
pn.config.hasUPNPorNATPMP = 0
gLog.Println(LvINFO, "openp2pS2STest debug")
}
if strings.Contains(pn.config.Node, "openp2pC2CTest") {
pn.config.natType = NATCone
pn.config.hasIPv4 = 0
pn.config.hasUPNPorNATPMP = 0
gLog.Println(LvINFO, "openp2pC2CTest debug")
}
if err != nil {
gLog.Println(LevelDEBUG, "detect NAT type error:", err)
gLog.Println(LvDEBUG, "detect NAT type error:", err)
break
}
gLog.Println(LevelDEBUG, "detect NAT type:", pn.config.natType, " publicIP:", pn.config.publicIP)
gLog.Println(LvDEBUG, "detect NAT type:", pn.config.natType, " publicIP:", pn.config.publicIP)
gatewayURL := fmt.Sprintf("%s:%d", pn.config.ServerHost, pn.config.ServerPort)
forwardPath := "/openp2p/v1/login"
uri := "/api/v1/login"
config := tls.Config{InsecureSkipVerify: true} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
websocket.DefaultDialer.TLSClientConfig = &config
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: forwardPath}
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri}
q := u.Query()
q.Add("node", pn.config.Node)
q.Add("token", fmt.Sprintf("%d", pn.config.Token))
@@ -414,33 +496,38 @@ func (pn *P2PNetwork) init() error {
break
}
go pn.readLoop()
pn.config.mac = getmac(pn.config.localIP)
pn.config.os = getOsName()
req := ReportBasic{
Mac: pn.config.mac,
LanIP: pn.config.localIP,
OS: pn.config.os,
Version: OpenP2PVersion,
}
rsp := netInfo()
gLog.Println(LevelDEBUG, "netinfo:", rsp)
if rsp != nil && rsp.Country != "" {
if len(rsp.IP) == net.IPv6len {
pn.config.ipv6 = rsp.IP.String()
req.IPv6 = rsp.IP.String()
go func() {
req := ReportBasic{
Mac: pn.config.mac,
LanIP: pn.config.localIP,
OS: pn.config.os,
HasIPv4: pn.config.hasIPv4,
HasUPNPorNATPMP: pn.config.hasUPNPorNATPMP,
Version: OpenP2PVersion,
}
req.NetInfo = *rsp
}
pn.write(MsgReport, MsgReportBasic, &req)
gLog.Println(LevelDEBUG, "P2PNetwork init ok")
rsp := netInfo()
gLog.Println(LvDEBUG, "netinfo:", rsp)
if rsp != nil && rsp.Country != "" {
if IsIPv6(rsp.IP.String()) {
pn.config.publicIPv6 = rsp.IP.String()
}
req.NetInfo = *rsp
} else {
pn.refreshIPv6(true)
}
req.IPv6 = pn.config.publicIPv6
pn.write(MsgReport, MsgReportBasic, &req)
}()
go pn.autorunApp()
gLog.Println(LvDEBUG, "P2PNetwork init ok")
break
}
if err != nil {
// init failed, retry
pn.restartCh <- true
gLog.Println(LevelERROR, "P2PNetwork init error:", err)
gLog.Println(LvERROR, "P2PNetwork init error:", err)
}
return err
}
@@ -449,7 +536,7 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
head := openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
if err != nil {
gLog.Println(LevelERROR, "handleMessage error:", err)
gLog.Println(LvERROR, "handleMessage error:", err)
return
}
switch head.MainType {
@@ -458,24 +545,29 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
rsp := LoginRsp{}
err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp)
if err != nil {
gLog.Printf(LevelERROR, "wrong login response:%s", err)
gLog.Printf(LvERROR, "wrong login response:%s", err)
return
}
if rsp.Error != 0 {
gLog.Printf(LevelERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail)
pn.running = false
} else {
pn.serverTs = rsp.Ts
pn.hbTime = time.Now()
pn.config.Token = rsp.Token
pn.config.User = rsp.User
gConf.setToken(rsp.Token)
gConf.setUser(rsp.User)
gConf.save()
if len(rsp.Node) >= MinNodeNameLen {
gConf.setNode(rsp.Node)
pn.config.Node = rsp.Node
}
pn.localTs = time.Now().Unix()
gLog.Printf(LevelINFO, "login ok. user=%s,Server ts=%d, local ts=%d", rsp.User, rsp.Ts, pn.localTs)
gLog.Printf(LvINFO, "login ok. user=%s,node=%s,Server ts=%d, local ts=%d", rsp.User, rsp.Node, rsp.Ts, pn.localTs)
}
case MsgHeartbeat:
gLog.Printf(LevelDEBUG, "P2PNetwork heartbeat ok")
gLog.Printf(LvDEBUG, "P2PNetwork heartbeat ok")
pn.hbTime = time.Now()
case MsgPush:
handlePush(pn, head.SubType, msg)
default:
@@ -488,21 +580,21 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
}
func (pn *P2PNetwork) readLoop() {
gLog.Printf(LevelDEBUG, "P2PNetwork readLoop start")
pn.wg.Add(1)
defer pn.wg.Done()
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))
t, msg, err := pn.conn.ReadMessage()
if err != nil {
gLog.Printf(LevelERROR, "P2PNetwork read error:%s", err)
gLog.Printf(LvERROR, "P2PNetwork read error:%s", err)
pn.conn.Close()
pn.restartCh <- true
break
}
pn.handleMessage(t, msg)
}
gLog.Printf(LevelDEBUG, "P2PNetwork readLoop end")
gLog.Printf(LvDEBUG, "P2PNetwork readLoop end")
}
func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error {
@@ -516,14 +608,14 @@ func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{})
pn.writeMtx.Lock()
defer pn.writeMtx.Unlock()
if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
gLog.Printf(LevelERROR, "write msgType %d,%d error:%s", mainType, subType, err)
gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err)
pn.conn.Close()
}
return err
}
func (pn *P2PNetwork) relay(to uint64, body []byte) error {
gLog.Printf(LevelDEBUG, "relay data to %d", to)
gLog.Printf(LvDEBUG, "relay data to %d", to)
i, ok := pn.allTunnels.Load(to)
if !ok {
return nil
@@ -537,7 +629,7 @@ func (pn *P2PNetwork) relay(to uint64, body []byte) error {
}
func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error {
gLog.Printf(LevelDEBUG, "push msgType %d to %s", subType, to)
gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to)
if !pn.online {
return errors.New("client offline")
}
@@ -559,7 +651,7 @@ func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error
pn.writeMtx.Lock()
defer pn.writeMtx.Unlock()
if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil {
gLog.Printf(LevelERROR, "push to %s error:%s", to, err)
gLog.Printf(LvERROR, "push to %s error:%s", to, err)
pn.conn.Close()
}
return err
@@ -578,13 +670,13 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
for {
select {
case <-time.After(timeout):
gLog.Printf(LevelERROR, "wait msg%d:%d timeout", mainType, subType)
gLog.Printf(LvERROR, "wait msg%d:%d timeout", mainType, subType)
return
case msg := <-ch:
head = &openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "read msg error:", err)
gLog.Println(LvERROR, "read msg error:", err)
break
}
if head.MainType != mainType || head.SubType != subType {
@@ -610,3 +702,24 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
return false
})
}
// ipv6 will expired need to refresh.
func (pn *P2PNetwork) refreshIPv6(force bool) {
if !force && !IsIPv6(pn.config.publicIPv6) { // not support ipv6, not refresh
return
}
client := &http.Client{Timeout: time.Second * 10}
r, err := client.Get("http://6.ipw.cn")
if err != nil {
gLog.Println(LvDEBUG, "refreshIPv6 error:", err)
return
}
defer r.Body.Close()
buf := make([]byte, 1024)
n, err := r.Body.Read(buf)
if n <= 0 {
gLog.Println(LvINFO, "refreshIPv6 error:", err, n)
return
}
pn.config.publicIPv6 = string(buf[:n])
}

618
core/p2ptunnel.go Normal file
View File

@@ -0,0 +1,618 @@
package openp2p
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net"
"sync"
"time"
)
type P2PTunnel struct {
pn *P2PNetwork
conn underlay
hbTime time.Time
hbMtx sync.Mutex
hbTimeRelay time.Time
config AppConfig
la *net.UDPAddr // local hole address
ra *net.UDPAddr // remote hole address
overlayConns sync.Map // both TCP and UDP
id uint64
running bool
runMtx sync.Mutex
tunnelServer bool // different from underlayServer
coneLocalPort int
coneNatPort int
linkModeWeb string // use config.linkmode
}
func (t *P2PTunnel) requestPeerInfo() error {
// request peer info
t.pn.write(MsgQuery, MsgQueryPeerInfoReq, &QueryPeerInfoReq{t.config.peerToken, t.config.PeerNode})
head, body := t.pn.read("", MsgQuery, MsgQueryPeerInfoRsp, time.Second*10)
if head == nil {
return ErrNetwork // network error, should not be ErrPeerOffline
}
rsp := QueryPeerInfoRsp{}
err := json.Unmarshal(body, &rsp)
if err != nil {
gLog.Printf(LvERROR, "wrong QueryPeerInfoRsp:%s", err)
return ErrMsgFormat
}
if rsp.Online == 0 {
return ErrPeerOffline
}
if compareVersion(rsp.Version, LeastSupportVersion) == LESS {
return ErrVersionNotCompatible
}
t.config.peerVersion = rsp.Version
t.config.hasIPv4 = rsp.HasIPv4
t.config.peerIP = rsp.IPv4
t.config.peerIPv6 = rsp.IPv6
t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP
t.config.peerNatType = rsp.NatType
///
return nil
}
func (t *P2PTunnel) initPort() {
t.running = true
t.hbMtx.Lock()
t.hbTime = time.Now()
t.hbMtx.Unlock()
t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param
if t.config.linkMode == LinkModeTCP6 {
t.pn.refreshIPv6(false)
}
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 {
t.coneLocalPort = t.pn.config.TCPPort
t.coneNatPort = t.pn.config.TCPPort // symmetric doesn't need coneNatPort
}
if t.config.linkMode == LinkModeUDPPunch {
// prepare one random cone hole
_, natPort, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort)
t.coneLocalPort = localPort
t.coneNatPort = natPort
}
if t.config.linkMode == LinkModeTCPPunch {
// prepare one random cone hole
_, natPort := natTCP(t.pn.config.ServerHost, IfconfigPort1, localPort)
t.coneLocalPort = localPort
t.coneNatPort = natPort
}
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.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: t.pn.config.Node,
FromIP: t.pn.config.publicIP,
ConeNatPort: t.coneNatPort,
NatType: t.pn.config.natType,
HasIPv4: t.pn.config.hasIPv4,
IPv6: t.pn.config.publicIPv6,
HasUPNPorNATPMP: t.pn.config.hasUPNPorNATPMP,
ID: t.id,
AppKey: appKey,
Version: OpenP2PVersion,
LinkMode: t.config.linkMode,
IsUnderlayServer: t.config.isUnderlayServer ^ 1, // peer
}
if req.Token == 0 { // no relay token
req.Token = t.pn.config.Token
}
t.pn.push(t.config.PeerNode, MsgPushConnectReq, req)
head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, time.Second*10)
if head == nil {
return errors.New("connect error")
}
rsp := PushConnectRsp{}
err := json.Unmarshal(body, &rsp)
if err != nil {
gLog.Printf(LvERROR, "wrong MsgPushConnectRsp:%s", 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
err = t.start()
if err != nil {
gLog.Println(LvERROR, "handshake error:", err)
err = ErrorHandshake
}
return err
}
func (t *P2PTunnel) isRuning() bool {
t.runMtx.Lock()
defer t.runMtx.Unlock()
return t.running
}
func (t *P2PTunnel) setRun(running bool) {
t.runMtx.Lock()
defer t.runMtx.Unlock()
t.running = running
}
func (t *P2PTunnel) isActive() bool {
if !t.isRuning() {
return false
}
t.hbMtx.Lock()
defer t.hbMtx.Unlock()
return time.Now().Before(t.hbTime.Add(TunnelIdleTimeout))
}
func (t *P2PTunnel) checkActive() bool {
hbt := time.Now()
t.hbMtx.Lock()
if t.hbTime.Before(time.Now().Add(-TunnelHeartbeatTime)) {
t.hbMtx.Unlock()
return false
}
t.hbMtx.Unlock()
// hbtime within TunnelHeartbeatTime, check it now
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
isActive := false
// wait at most 5s
for i := 0; i < 50 && !isActive; i++ {
t.hbMtx.Lock()
if t.hbTime.After(hbt) {
isActive = true
}
t.hbMtx.Unlock()
time.Sleep(time.Millisecond * 100)
}
return isActive
}
// call when user delete tunnel
func (t *P2PTunnel) close() {
t.setRun(false)
t.pn.allTunnels.Delete(t.id)
}
func (t *P2PTunnel) 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
}
}
gLog.Println(LvDEBUG, "handshake to ", t.config.PeerNode)
var err error
// TODO: handle NATNone, nodes with public ip has no punching
if t.pn.config.natType == NATCone && t.config.peerNatType == NATCone {
err = handshakeC2C(t)
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATSymmetric {
err = ErrorS2S
t.close()
} else if t.config.peerNatType == NATSymmetric && t.pn.config.natType == NATCone {
err = handshakeC2S(t)
} else if t.config.peerNatType == NATCone && t.pn.config.natType == NATSymmetric {
err = handshakeS2C(t)
} else {
return errors.New("unknown error")
}
if err != nil {
gLog.Println(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:
t.conn, err = t.connectUnderlayTCP()
case LinkModeUDPPunch:
t.conn, err = t.connectUnderlayQuic()
}
if err != nil {
return err
}
if t.conn == nil {
return errors.New("connect underlay error")
}
t.setRun(true)
go t.readLoop()
go t.heartbeatLoop()
return nil
}
func (t *P2PTunnel) connectUnderlayQuic() (c underlay, err error) {
gLog.Println(LvINFO, "connectUnderlayQuic start")
defer gLog.Println(LvINFO, "connectUnderlayQuic end")
var qConn *underlayQUIC
if t.config.isUnderlayServer == 1 {
time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env
qConn, err = listenQuic(t.la.String(), TunnelIdleTimeout)
if err != nil {
gLog.Println(LvINFO, "listen quic error:", err, ", retry...")
}
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
err = qConn.Accept()
if err != nil {
qConn.CloseListener()
return nil, fmt.Errorf("accept quic error:%s", err)
}
_, buff, err := qConn.ReadBuffer()
if err != nil {
qConn.listener.Close()
return nil, fmt.Errorf("read start msg error:%s", err)
}
if buff != nil {
gLog.Println(LvDEBUG, string(buff))
}
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Println(LvDEBUG, "quic connection ok")
return qConn, nil
}
//else
conn, e := net.ListenUDP("udp", t.la)
if e != nil {
time.Sleep(time.Millisecond * 10)
conn, e = net.ListenUDP("udp", t.la)
if e != nil {
return nil, fmt.Errorf("quic listen error:%s", e)
}
}
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, time.Second*5)
gLog.Println(LvDEBUG, "quic dial to ", t.ra.String())
qConn, e = dialQuic(conn, t.ra, TunnelIdleTimeout)
if e != nil {
return nil, fmt.Errorf("quic dial to %s error:%s", t.ra.String(), e)
}
handshakeBegin := time.Now()
qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
_, buff, err := qConn.ReadBuffer()
if e != nil {
qConn.listener.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.Println(LvDEBUG, "quic connection ok")
t.linkModeWeb = LinkModeUDPPunch
return qConn, nil
}
// websocket
func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
gLog.Println(LvINFO, "connectUnderlayTCP start")
defer gLog.Println(LvINFO, "connectUnderlayTCP end")
var qConn *underlayTCP
if t.config.isUnderlayServer == 1 {
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
qConn, err = listenTCP(t.config.peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode)
if err != nil {
return nil, fmt.Errorf("listen TCP error:%s", err)
}
_, buff, err := qConn.ReadBuffer()
if err != nil {
return nil, fmt.Errorf("read start msg error:%s", err)
}
if buff != nil {
gLog.Println(LvDEBUG, string(buff))
}
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Println(LvINFO, "TCP connection ok")
return qConn, nil
}
//else
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, time.Second*5)
gLog.Println(LvDEBUG, "TCP dial to ", t.config.peerIP, ":", t.config.peerConeNatPort)
qConn, err = dialTCP(t.config.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()
qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
_, buff, err := qConn.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, "TCP connection ok")
t.linkModeWeb = LinkModeIPv4
return qConn, nil
}
func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
gLog.Println(LvINFO, "connectUnderlayTCP6 start")
defer gLog.Println(LvINFO, "connectUnderlayTCP6 end")
var qConn *underlayTCP6
if t.config.isUnderlayServer == 1 {
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
qConn, err = listenTCP6(t.coneNatPort, TunnelIdleTimeout)
if err != nil {
return nil, fmt.Errorf("listen TCP6 error:%s", err)
}
_, buff, err := qConn.ReadBuffer()
if err != nil {
qConn.listener.Close()
return nil, fmt.Errorf("read start msg error:%s", err)
}
if buff != nil {
gLog.Println(LvDEBUG, string(buff))
}
qConn.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Println(LvDEBUG, "TCP6 connection ok")
return qConn, nil
}
//else
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, time.Second*5)
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6)
qConn, 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()
qConn.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
_, buff, err := qConn.ReadBuffer()
if err != nil {
qConn.listener.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.Println(LvDEBUG, "TCP6 connection ok")
t.linkModeWeb = LinkModeIPv6
return qConn, 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(TunnelIdleTimeout))
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 {
continue
}
switch head.SubType {
case MsgTunnelHeartbeat:
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil)
gLog.Printf(LvDEBUG, "%d read tunnel heartbeat", t.id)
case MsgTunnelHeartbeatAck:
t.hbMtx.Lock()
t.hbTime = time.Now()
t.hbMtx.Unlock()
gLog.Printf(LvDEBUG, "%d read tunnel heartbeat ack", t.id)
case MsgOverlayData:
if len(body) < overlayHeaderSize {
continue
}
overlayID := binary.LittleEndian.Uint64(body[:8])
gLog.Printf(LvDEBUG, "%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 MsgRelayData:
gLog.Printf(LvDEBUG, "got relay data datalen=%d", head.DataLen)
if len(body) < 8 {
continue
}
tunnelID := binary.LittleEndian.Uint64(body[:8])
t.pn.relay(tunnelID, body[8:])
case MsgRelayHeartbeat:
req := RelayHeartbeat{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err)
continue
}
gLog.Printf(LvDEBUG, "got MsgRelayHeartbeat from %d:%d", req.RelayTunnelID, req.AppID)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, req.RelayTunnelID)
msg, _ := newMessage(MsgP2P, MsgRelayHeartbeatAck, &req)
msgWithHead := append(relayHead.Bytes(), msg...)
t.conn.WriteBytes(MsgP2P, MsgRelayData, msgWithHead)
case MsgRelayHeartbeatAck:
req := RelayHeartbeat{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err)
continue
}
gLog.Printf(LvDEBUG, "got MsgRelayHeartbeatAck to %d", req.AppID)
t.pn.updateAppHeartbeat(req.AppID)
case MsgOverlayConnectReq:
req := OverlayConnectReq{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LvERROR, "wrong MsgOverlayConnectReq:%s", err)
continue
}
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
if req.Token != t.pn.config.Token {
gLog.Println(LvERROR, "Access Denied:", req.Token)
continue
}
overlayID := req.ID
gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %+v", req.AppID, overlayID, req)
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), time.Second*5)
}
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{}
err := json.Unmarshal(body, &req)
if err != nil {
gLog.Printf(LvERROR, "wrong OverlayDisconnectRequest:%s", 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.setRun(false)
t.conn.Close()
gLog.Printf(LvDEBUG, "%d tunnel readloop end", t.id)
}
func (t *P2PTunnel) heartbeatLoop() {
tc := time.NewTicker(TunnelHeartbeatTime)
defer tc.Stop()
gLog.Printf(LvDEBUG, "%d tunnel heartbeatLoop start", t.id)
defer gLog.Printf(LvDEBUG, "%d tunnel heartbeatLoop end", t.id)
for t.isRuning() {
select {
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.setRun(false)
return
}
gLog.Printf(LvDEBUG, "%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: t.pn.config.Node,
NatType: t.pn.config.natType,
HasIPv4: t.pn.config.hasIPv4,
// IPv6: t.pn.config.IPv6,
HasUPNPorNATPMP: t.pn.config.hasUPNPorNATPMP,
FromIP: t.pn.config.publicIP,
ConeNatPort: t.coneNatPort,
ID: t.id,
Version: OpenP2PVersion,
}
// only private node set ipv6
if t.config.fromToken == t.pn.config.Token {
t.pn.refreshIPv6(false)
rsp.IPv6 = t.pn.config.publicIPv6
}
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
})
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -10,8 +10,17 @@ import (
"time"
)
const OpenP2PVersion = "1.4.2"
const ProducnName string = "openp2p"
const OpenP2PVersion = "3.8.0"
const ProductName string = "openp2p"
const LeastSupportVersion = "3.0.0"
const (
IfconfigPort1 = 27180
IfconfigPort2 = 27181
WsPort = 27183
UDPPort1 = 27182
UDPPort2 = 27183
)
type openP2PHeader struct {
DataLen uint32
@@ -67,8 +76,10 @@ const (
MsgP2P = 4
MsgRelay = 5
MsgReport = 6
MsgQuery = 7
)
// TODO: seperate node push and web push.
const (
MsgPushRsp = 0
MsgPushConnectReq = 1
@@ -78,11 +89,14 @@ const (
MsgPushAddRelayTunnelRsp = 5
MsgPushUpdate = 6
MsgPushReportApps = 7
MsgPushQuicConnect = 8
MsgPushUnderlayConnect = 8
MsgPushEditApp = 9
MsgPushSwitchApp = 10
MsgPushRestart = 11
MsgPushEditNode = 12
MsgPushAPPKey = 13
MsgPushReportLog = 14
MsgPushDstNodeOnline = 15
)
// MsgP2P sub type message
@@ -114,6 +128,7 @@ const (
MsgReportQuery
MsgReportConnect
MsgReportApps
MsgReportLog
)
const (
@@ -124,17 +139,18 @@ const (
SymmetricHandshakeNum = 800 // 0.992379
// SymmetricHandshakeNum = 1000 // 0.999510
SymmetricHandshakeInterval = time.Millisecond
SymmetricHandshakeAckTimeout = time.Second * 11
SymmetricHandshakeAckTimeout = time.Second * 5
PeerAddRelayTimeount = time.Second * 20
CheckActiveTimeout = time.Second * 5
PaddingSize = 16
AESKeySize = 16
MaxRetry = 10
RetryInterval = time.Second * 30
PublicIPEchoTimeout = time.Second * 3
NatTestTimeout = time.Second * 10
PublicIPEchoTimeout = time.Second * 1
NatTestTimeout = time.Second * 5
UDPReadTimeout = time.Second * 5
ClientAPITimeout = time.Second * 10
MaxDirectTry = 5
MaxDirectTry = 3
)
// NATNone has public ip
@@ -145,6 +161,30 @@ const (
NATUnknown = 314
)
// underlay protocol
const (
UderlayAuto = "auto"
UderlayQUIC = "quic"
UderlayTCP = "tcp"
)
// linkmode
const (
LinkModeUDPPunch = "udppunch"
LinkModeTCPPunch = "tcppunch"
LinkModeIPv4 = "ipv4" // for web
LinkModeIPv6 = "ipv6" // for web
LinkModeTCP6 = "tcp6"
LinkModeTCP4 = "tcp4"
LinkModeUDP6 = "udp6"
LinkModeUDP4 = "udp4"
)
const (
MsgQueryPeerInfoReq = iota
MsgQueryPeerInfoRsp
)
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
data, err := json.Marshal(packet)
if err != nil {
@@ -170,23 +210,37 @@ func nodeNameToID(name string) uint64 {
}
type PushConnectReq struct {
From string `json:"from,omitempty"`
FromToken uint64 `json:"fromToken,omitempty"` //my token
Token uint64 `json:"token,omitempty"` // totp token
ConeNatPort int `json:"coneNatPort,omitempty"`
NatType int `json:"natType,omitempty"`
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
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
}
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"`
ConeNatPort int `json:"coneNatPort,omitempty"`
FromIP string `json:"fromIP,omitempty"`
ID uint64 `json:"id,omitempty"`
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"`
Version string `json:"version,omitempty"`
}
type PushRsp struct {
Error int `json:"error,omitempty"`
@@ -197,6 +251,7 @@ 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"`
}
@@ -246,8 +301,13 @@ type AddRelayTunnelReq struct {
From string `json:"from,omitempty"`
RelayName string `json:"relayName,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"`
AppID uint64 `json:"appID,omitempty"`
AppKey uint64 `json:"appKey,omitempty"`
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 {
@@ -256,12 +316,14 @@ type RelayHeartbeat struct {
}
type ReportBasic struct {
OS string `json:"os,omitempty"`
Mac string `json:"mac,omitempty"`
LanIP string `json:"lanIP,omitempty"`
IPv6 string `json:"IPv6,omitempty"`
Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"`
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 {
@@ -297,6 +359,7 @@ type AppInfo struct {
ShareBandwidth int `json:"shareBandWidth,omitempty"`
RelayNode string `json:"relayNode,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"`
@@ -308,6 +371,18 @@ 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"`
@@ -346,3 +421,17 @@ 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 {
Online int `json:"online,omitempty"`
Version string `json:"version,omitempty"`
NatType int `json:"natType,omitempty"`
IPv4 string `json:"IPv4,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"`
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"bytes"
@@ -18,12 +18,12 @@ 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, result []byte, len int, err error) {
if timeout > 0 {
deadline := time.Now().Add(time.Millisecond * time.Duration(timeout))
deadline := time.Now().Add(timeout)
err = conn.SetReadDeadline(deadline)
if err != nil {
gLog.Println(LevelERROR, "SetReadDeadline error")
gLog.Println(LvERROR, "SetReadDeadline error")
return nil, nil, nil, 0, err
}
}
@@ -37,7 +37,7 @@ func UDPRead(conn *net.UDPConn, timeout int) (ra net.Addr, head *openP2PHeader,
head = &openP2PHeader{}
err = binary.Read(bytes.NewReader(result[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil {
gLog.Println(LevelERROR, "parse p2pheader error:", err)
gLog.Println(LvERROR, "parse p2pheader error:", err)
return nil, nil, nil, 0, err
}
return

View File

@@ -1,17 +1,16 @@
package main
package openp2p
import (
"time"
)
type p2pConn interface {
ReadMessage() (*openP2PHeader, []byte, error)
type underlay interface {
ReadBuffer() (*openP2PHeader, []byte, error)
WriteBytes(uint16, uint16, []byte) error
WriteBuffer([]byte) error
WriteMessage(uint16, uint16, interface{}) error
Close() error
Accept() error
CloseListener()
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
Protocol() string
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"context"
@@ -21,14 +21,18 @@ import (
//quic.DialContext do not support version 44,disable it
var quicVersion []quic.VersionNumber
type quicConn struct {
type underlayQUIC struct {
listener quic.Listener
writeMtx *sync.Mutex
quic.Stream
quic.Session
quic.Connection
}
func (conn *quicConn) ReadMessage() (*openP2PHeader, []byte, error) {
func (conn *underlayQUIC) Protocol() string {
return "quic"
}
func (conn *underlayQUIC) ReadBuffer() (*openP2PHeader, []byte, error) {
headBuf := make([]byte, openP2PHeaderSize)
_, err := io.ReadFull(conn, headBuf)
if err != nil {
@@ -43,7 +47,7 @@ func (conn *quicConn) ReadMessage() (*openP2PHeader, []byte, error) {
return head, dataBuf, err
}
func (conn *quicConn) WriteBytes(mainType uint16, subType uint16, data []byte) error {
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)
@@ -51,14 +55,14 @@ func (conn *quicConn) WriteBytes(mainType uint16, subType uint16, data []byte) e
return err
}
func (conn *quicConn) WriteBuffer(data []byte) error {
func (conn *underlayQUIC) WriteBuffer(data []byte) error {
conn.writeMtx.Lock()
_, err := conn.Write(data)
conn.writeMtx.Unlock()
return err
}
func (conn *quicConn) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
func (conn *underlayQUIC) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
// TODO: call newMessage
data, err := json.Marshal(packet)
if err != nil {
@@ -71,18 +75,18 @@ func (conn *quicConn) WriteMessage(mainType uint16, subType uint16, packet inter
return err
}
func (conn *quicConn) Close() error {
func (conn *underlayQUIC) Close() error {
conn.Stream.CancelRead(1)
conn.Session.CloseWithError(0, "")
conn.Connection.CloseWithError(0, "")
return nil
}
func (conn *quicConn) CloseListener() {
func (conn *underlayQUIC) CloseListener() {
if conn.listener != nil {
conn.listener.Close()
}
}
func (conn *quicConn) Accept() error {
func (conn *underlayQUIC) Accept() error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
sess, err := conn.listener.Accept(ctx)
@@ -94,35 +98,35 @@ func (conn *quicConn) Accept() error {
return err
}
conn.Stream = stream
conn.Session = sess
conn.Connection = sess
return nil
}
func listenQuic(addr string, idleTimeout time.Duration) (*quicConn, error) {
gLog.Println(LevelDEBUG, "quic listen on ", addr)
func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
gLog.Println(LvDEBUG, "quic listen on ", addr)
listener, err := quic.ListenAddr(addr, generateTLSConfig(),
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
}
return &quicConn{listener: listener, writeMtx: &sync.Mutex{}}, nil
return &underlayQUIC{listener: listener, writeMtx: &sync.Mutex{}}, nil
}
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*quicConn, error) {
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayQUIC, error) {
tlsConf := &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"openp2pv1"},
}
session, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
Connection, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil {
return nil, fmt.Errorf("quic.DialContext error:%s", err)
}
stream, err := session.OpenStreamSync(context.Background())
stream, err := Connection.OpenStreamSync(context.Background())
if err != nil {
return nil, fmt.Errorf("OpenStreamSync error:%s", err)
}
qConn := &quicConn{nil, &sync.Mutex{}, stream, session}
qConn := &underlayQUIC{nil, &sync.Mutex{}, stream, Connection}
return qConn, nil
}

108
core/underlay_tcp.go Normal file
View File

@@ -0,0 +1,108 @@
package openp2p
import (
"encoding/json"
"fmt"
"io"
"net"
"sync"
"time"
reuse "github.com/openp2p-cn/go-reuseport"
)
type underlayTCP struct {
writeMtx *sync.Mutex
net.Conn
}
func (conn *underlayTCP) Protocol() string {
return "tcp"
}
func (conn *underlayTCP) 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
}
func (conn *underlayTCP) WriteBytes(mainType uint16, subType uint16, data []byte) error {
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err := conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP) WriteBuffer(data []byte) error {
conn.writeMtx.Lock()
_, err := conn.Write(data)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
// TODO: call newMessage
data, err := json.Marshal(packet)
if err != nil {
return err
}
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err = conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP) Close() error {
return conn.Conn.Close()
}
func listenTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) {
if mode == LinkModeTCPPunch {
c, err := reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout)
if err != nil {
gLog.Println(LvDEBUG, "send tcp punch: ", err)
return nil, err
}
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", localPort))
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return nil, err
}
l.SetDeadline(time.Now().Add(SymmetricHandshakeAckTimeout))
c, err := l.Accept()
defer l.Close()
if err != nil {
return nil, err
}
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) {
var c net.Conn
var err error
if mode == LinkModeTCPPunch {
c, err = reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout)
} else {
c, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout)
}
if err != nil {
gLog.Printf(LvERROR, "Dial %s:%d error:%s", host, port, err)
return nil, err
}
gLog.Printf(LvDEBUG, "Dial %s:%d OK", host, port)
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
}

92
core/underlay_tcp6.go Normal file
View File

@@ -0,0 +1,92 @@
package openp2p
import (
"encoding/json"
"fmt"
"io"
"net"
"sync"
"time"
)
type underlayTCP6 struct {
listener net.Listener
writeMtx *sync.Mutex
net.Conn
}
func (conn *underlayTCP6) Protocol() string {
return "tcp6"
}
func (conn *underlayTCP6) 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
}
func (conn *underlayTCP6) WriteBytes(mainType uint16, subType uint16, data []byte) error {
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err := conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP6) WriteBuffer(data []byte) error {
conn.writeMtx.Lock()
_, err := conn.Write(data)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP6) WriteMessage(mainType uint16, subType uint16, packet interface{}) error {
// TODO: call newMessage
data, err := json.Marshal(packet)
if err != nil {
return err
}
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
conn.writeMtx.Lock()
_, err = conn.Write(writeBytes)
conn.writeMtx.Unlock()
return err
}
func (conn *underlayTCP6) Close() error {
return conn.Conn.Close()
}
func listenTCP6(port int, idleTimeout time.Duration) (*underlayTCP6, error) {
addr, _ := net.ResolveTCPAddr("tcp6", fmt.Sprintf("[::]:%d", port))
l, err := net.ListenTCP("tcp6", addr)
if err != nil {
return nil, err
}
defer l.Close()
l.SetDeadline(time.Now().Add(SymmetricHandshakeAckTimeout))
c, err := l.Accept()
defer l.Close()
if err != nil {
return nil, err
}
return &underlayTCP6{writeMtx: &sync.Mutex{}, Conn: c}, nil
}
func dialTCP6(host string, port int) (*underlayTCP6, error) {
c, err := net.DialTimeout("tcp6", fmt.Sprintf("[%s]:%d", host, port), SymmetricHandshakeAckTimeout)
if err != nil {
gLog.Printf(LvERROR, "Dial %s:%d error:%s", host, port, err)
return nil, err
}
return &underlayTCP6{writeMtx: &sync.Mutex{}, Conn: c}, nil
}

View File

@@ -1,4 +1,4 @@
package main
package openp2p
import (
"archive/tar"
@@ -16,9 +16,9 @@ import (
"time"
)
func update() {
gLog.Println(LevelINFO, "update start")
defer gLog.Println(LevelINFO, "update end")
func update(host string, port int) {
gLog.Println(LvINFO, "update start")
defer gLog.Println(LvINFO, "update end")
c := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
@@ -27,45 +27,45 @@ func update() {
}
goos := runtime.GOOS
goarch := runtime.GOARCH
rsp, err := c.Get(fmt.Sprintf("https://openp2p.cn:27183/api/v1/update?fromver=%s&os=%s&arch=%s", OpenP2PVersion, goos, goarch))
rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s", host, port, OpenP2PVersion, goos, goarch))
if err != nil {
gLog.Println(LevelERROR, "update:query update list failed:", err)
gLog.Println(LvERROR, "update:query update list failed:", err)
return
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
gLog.Println(LevelERROR, "get update info error:", rsp.Status)
gLog.Println(LvERROR, "get update info error:", rsp.Status)
return
}
rspBuf, err := ioutil.ReadAll(rsp.Body)
if err != nil {
gLog.Println(LevelERROR, "update:read update list failed:", err)
gLog.Println(LvERROR, "update:read update list failed:", err)
return
}
updateInfo := UpdateInfo{}
err = json.Unmarshal(rspBuf, &updateInfo)
if err != nil {
gLog.Println(LevelERROR, rspBuf, " update info decode error:", err)
gLog.Println(LvERROR, rspBuf, " update info decode error:", err)
return
}
if updateInfo.Error != 0 {
gLog.Println(LevelERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail)
gLog.Println(LvERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail)
return
}
err = updateFile(updateInfo.Url, "", "openp2p")
if err != nil {
gLog.Println(LevelERROR, "update: download failed:", err)
gLog.Println(LvERROR, "update: download failed:", err)
return
}
}
// todo rollback on error
func updateFile(url string, checksum string, dst string) error {
gLog.Println(LevelINFO, "download ", url)
gLog.Println(LvINFO, "download ", url)
tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp"
output, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776)
if err != nil {
gLog.Printf(LevelERROR, "OpenFile %s error:%s", tmpFile, err)
gLog.Printf(LvERROR, "OpenFile %s error:%s", tmpFile, err)
return err
}
tr := &http.Transport{
@@ -74,31 +74,31 @@ func updateFile(url string, checksum string, dst string) error {
client := &http.Client{Transport: tr}
response, err := client.Get(url)
if err != nil {
gLog.Printf(LevelERROR, "download url %s error:%s", url, err)
gLog.Printf(LvERROR, "download url %s error:%s", url, err)
output.Close()
return err
}
defer response.Body.Close()
n, err := io.Copy(output, response.Body)
if err != nil {
gLog.Printf(LevelERROR, "io.Copy error:%s", err)
gLog.Printf(LvERROR, "io.Copy error:%s", err)
output.Close()
return err
}
output.Sync()
output.Close()
gLog.Println(LevelINFO, "download ", url, " ok")
gLog.Printf(LevelINFO, "size: %d bytes", n)
gLog.Println(LvINFO, "download ", url, " ok")
gLog.Printf(LvINFO, "size: %d bytes", n)
err = os.Rename(os.Args[0], os.Args[0]+"0")
if err != nil && os.IsExist(err) {
gLog.Printf(LevelINFO, " rename %s error:%s", os.Args[0], err)
gLog.Printf(LvINFO, " rename %s error:%s", os.Args[0], err)
}
// extract
gLog.Println(LevelINFO, "extract files")
gLog.Println(LvINFO, "extract files")
err = extract(filepath.Dir(os.Args[0]), tmpFile)
if err != nil {
gLog.Printf(LevelERROR, "extract error:%s. revert rename", err)
gLog.Printf(LvERROR, "extract error:%s. revert rename", err)
os.Rename(os.Args[0]+"0", os.Args[0])
return err
}

385
core/upnp.go Normal file
View File

@@ -0,0 +1,385 @@
/*
Taken from taipei-torrent And fix some bugs
*/
package openp2p
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strconv"
"strings"
"time"
)
type upnpNAT struct {
serviceURL string
ourIP string
urnDomain string
}
// protocol is either "udp" or "tcp"
type NAT interface {
GetExternalAddress() (addr net.IP, err error)
AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
}
func Discover() (nat NAT, err error) {
localIP := localIPv4()
ssdp, err := net.ResolveUDPAddr("udp4", "239.255.255.250:1900")
if err != nil {
return
}
conn, err := net.ListenPacket("udp4", fmt.Sprintf("%s:0", localIP))
if err != nil {
return
}
socket := conn.(*net.UDPConn)
defer socket.Close() // nolint: errcheck
if err := socket.SetDeadline(time.Now().Add(3 * time.Second)); err != nil {
return nil, err
}
st := "InternetGatewayDevice:1"
buf := bytes.NewBufferString(
"M-SEARCH * HTTP/1.1\r\n" +
"HOST: 239.255.255.250:1900\r\n" +
"ST: ssdp:all\r\n" +
"MAN: \"ssdp:discover\"\r\n" +
"MX: 2\r\n\r\n")
message := buf.Bytes()
answerBytes := make([]byte, 1024)
for i := 0; i < 3; i++ {
_, err = socket.WriteToUDP(message, ssdp)
if err != nil {
return
}
var n int
_, _, err = socket.ReadFromUDP(answerBytes)
if err != nil {
gLog.Println(LvDEBUG, "UPNP discover error:", err)
return
}
for {
n, _, err = socket.ReadFromUDP(answerBytes)
if err != nil {
break
}
answer := string(answerBytes[0:n])
if !strings.Contains(answer, st) {
continue
}
// HTTP header field names are case-insensitive.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
locString := "\r\nlocation:"
answer = strings.ToLower(answer)
locIndex := strings.Index(answer, locString)
if locIndex < 0 {
continue
}
loc := answer[locIndex+len(locString):]
endIndex := strings.Index(loc, "\r\n")
if endIndex < 0 {
continue
}
locURL := strings.TrimSpace(loc[0:endIndex])
var serviceURL, urnDomain string
serviceURL, urnDomain, err = getServiceURL(locURL)
if err != nil {
return
}
if err != nil {
return
}
nat = &upnpNAT{serviceURL: serviceURL, ourIP: localIP, urnDomain: urnDomain}
return
}
}
err = errors.New("UPnP port discovery failed")
return
}
type Envelope struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
Soap *SoapBody
}
type SoapBody struct {
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
ExternalIP *ExternalIPAddressResponse
}
type ExternalIPAddressResponse struct {
XMLName xml.Name `xml:"GetExternalIPAddressResponse"`
IPAddress string `xml:"NewExternalIPAddress"`
}
type ExternalIPAddress struct {
XMLName xml.Name `xml:"NewExternalIPAddress"`
IP string
}
type UPNPService struct {
ServiceType string `xml:"serviceType"`
ControlURL string `xml:"controlURL"`
}
type DeviceList struct {
Device []Device `xml:"device"`
}
type ServiceList struct {
Service []UPNPService `xml:"service"`
}
type Device struct {
XMLName xml.Name `xml:"device"`
DeviceType string `xml:"deviceType"`
DeviceList DeviceList `xml:"deviceList"`
ServiceList ServiceList `xml:"serviceList"`
}
type Root struct {
Device Device
}
func getChildDevice(d *Device, deviceType string) *Device {
dl := d.DeviceList.Device
for i := 0; i < len(dl); i++ {
if strings.Contains(dl[i].DeviceType, deviceType) {
return &dl[i]
}
}
return nil
}
func getChildService(d *Device, serviceType string) *UPNPService {
sl := d.ServiceList.Service
for i := 0; i < len(sl); i++ {
if strings.Contains(sl[i].ServiceType, serviceType) {
return &sl[i]
}
}
return nil
}
func localIPv4() string { // TODO: multi nic will wrong
conn, err := net.Dial("udp", "8.8.8.8:80")
if err != nil {
return ""
}
defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr)
return localAddr.IP.String()
}
func getServiceURL(rootURL string) (url, urnDomain string, err error) {
r, err := http.Get(rootURL)
if err != nil {
return
}
defer r.Body.Close() // nolint: errcheck
if r.StatusCode >= 400 {
err = errors.New(fmt.Sprint(r.StatusCode))
return
}
var root Root
err = xml.NewDecoder(r.Body).Decode(&root)
if err != nil {
return
}
a := &root.Device
if !strings.Contains(a.DeviceType, "InternetGatewayDevice:1") {
err = errors.New("No InternetGatewayDevice")
return
}
b := getChildDevice(a, "WANDevice:1")
if b == nil {
err = errors.New("No WANDevice")
return
}
c := getChildDevice(b, "WANConnectionDevice:1")
if c == nil {
err = errors.New("No WANConnectionDevice")
return
}
d := getChildService(c, "WANIPConnection:1")
if d == nil {
// Some routers don't follow the UPnP spec, and put WanIPConnection under WanDevice,
// instead of under WanConnectionDevice
d = getChildService(b, "WANIPConnection:1")
if d == nil {
d = getChildService(c, "WANPPPConnection:1")
if d == nil {
err = errors.New("No WANIPConnection or WANPPPConnection")
return
}
}
}
// Extract the domain name, which isn't always 'schemas-upnp-org'
urnDomain = strings.Split(d.ServiceType, ":")[1]
url = combineURL(rootURL, d.ControlURL)
return
}
func combineURL(rootURL, subURL string) string {
protocolEnd := "://"
protoEndIndex := strings.Index(rootURL, protocolEnd)
a := rootURL[protoEndIndex+len(protocolEnd):]
rootIndex := strings.Index(a, "/")
return rootURL[0:protoEndIndex+len(protocolEnd)+rootIndex] + subURL
}
func soapRequest(url, function, message, domain string) (r *http.Response, err error) {
fullMessage := "<?xml version=\"1.0\" ?>" +
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" +
"<s:Body>" + message + "</s:Body></s:Envelope>"
req, err := http.NewRequest("POST", url, strings.NewReader(fullMessage))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "text/xml ; charset=\"utf-8\"")
req.Header.Set("User-Agent", "Darwin/10.0.0, UPnP/1.0, MiniUPnPc/1.3")
//req.Header.Set("Transfer-Encoding", "chunked")
req.Header.Set("SOAPAction", "\"urn:"+domain+":service:WANIPConnection:1#"+function+"\"")
req.Header.Set("Connection", "Close")
req.Header.Set("Cache-Control", "no-cache")
req.Header.Set("Pragma", "no-cache")
// log.Stderr("soapRequest ", req)
r, err = http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
/*if r.Body != nil {
defer r.Body.Close()
}*/
if r.StatusCode >= 400 {
// log.Stderr(function, r.StatusCode)
err = errors.New("Error " + strconv.Itoa(r.StatusCode) + " for " + function)
r = nil
return
}
return
}
type statusInfo struct {
externalIpAddress string
}
func (n *upnpNAT) getExternalIPAddress() (info statusInfo, err error) {
message := "<u:GetExternalIPAddress xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"</u:GetExternalIPAddress>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "GetExternalIPAddress", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
var envelope Envelope
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return
}
reader := bytes.NewReader(data)
err = xml.NewDecoder(reader).Decode(&envelope)
if err != nil {
return
}
info = statusInfo{envelope.Soap.ExternalIP.IPAddress}
if err != nil {
return
}
return
}
// GetExternalAddress returns an external IP. If GetExternalIPAddress action
// fails or IP returned is invalid, GetExternalAddress returns an error.
func (n *upnpNAT) GetExternalAddress() (addr net.IP, err error) {
info, err := n.getExternalIPAddress()
if err != nil {
return
}
addr = net.ParseIP(info.externalIpAddress)
if addr == nil {
err = fmt.Errorf("Failed to parse IP: %v", info.externalIpAddress)
}
return
}
func (n *upnpNAT) AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error) {
// A single concatenation would break ARM compilation.
message := "<u:AddPortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort)
message += "</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>"
message += "<NewInternalPort>" + strconv.Itoa(internalPort) + "</NewInternalPort>" +
"<NewInternalClient>" + n.ourIP + "</NewInternalClient>" +
"<NewEnabled>1</NewEnabled><NewPortMappingDescription>"
message += description +
"</NewPortMappingDescription><NewLeaseDuration>" + strconv.Itoa(timeout) +
"</NewLeaseDuration></u:AddPortMapping>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "AddPortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
// TODO: check response to see if the port was forwarded
// log.Println(message, response)
// JAE:
// body, err := ioutil.ReadAll(response.Body)
// fmt.Println(string(body), err)
mappedExternalPort = externalPort
_ = response
return
}
func (n *upnpNAT) DeletePortMapping(protocol string, externalPort, internalPort int) (err error) {
message := "<u:DeletePortMapping xmlns:u=\"urn:" + n.urnDomain + ":service:WANIPConnection:1\">\r\n" +
"<NewRemoteHost></NewRemoteHost><NewExternalPort>" + strconv.Itoa(externalPort) +
"</NewExternalPort><NewProtocol>" + protocol + "</NewProtocol>" +
"</u:DeletePortMapping>"
var response *http.Response
response, err = soapRequest(n.serviceURL, "DeletePortMapping", message, n.urnDomain)
if response != nil {
defer response.Body.Close() // nolint: errcheck
}
if err != nil {
return
}
// TODO: check response to see if the port was deleted
// log.Println(message, response)
_ = response
return
}

View File

@@ -1,7 +1,4 @@
//go:build darwin
// +build darwin
package main
package openp2p
import (
"strings"
@@ -24,7 +21,7 @@ func setRLimit() error {
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Cur = 10240
limit.Cur = 65536
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}

69
core/util_freebsd.go Normal file
View File

@@ -0,0 +1,69 @@
package openp2p
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"runtime"
"strings"
"syscall"
)
const (
defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p"
)
func getOsName() (osName string) {
var sysnamePath string
sysnamePath = "/etc/redhat-release"
_, err := os.Stat(sysnamePath)
if err != nil && os.IsNotExist(err) {
str := "PRETTY_NAME="
f, err := os.Open("/etc/os-release")
if err == nil {
buf := bufio.NewReader(f)
for {
line, err := buf.ReadString('\n')
if err == nil {
line = strings.TrimSpace(line)
pos := strings.Count(line, str)
if pos > 0 {
len1 := len([]rune(str)) + 1
rs := []rune(line)
osName = string(rs[len1 : (len(rs))-1])
break
}
} else {
break
}
}
}
} else {
buff, err := ioutil.ReadFile(sysnamePath)
if err == nil {
osName = string(bytes.TrimSpace(buff))
}
}
if osName == "" {
osName = "FreeBSD"
}
return
}
func setRLimit() error {
var limit syscall.Rlimit
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Max = 65536
limit.Cur = limit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
return nil
}
func setFirewall() {
}

View File

@@ -1,13 +1,11 @@
//go:build linux
// +build linux
package main
package openp2p
import (
"bufio"
"bytes"
"io/ioutil"
"os"
"runtime"
"strings"
"syscall"
)
@@ -18,6 +16,9 @@ const (
)
func getOsName() (osName string) {
if runtime.GOOS == "android" {
return "Android"
}
var sysnamePath string
sysnamePath = "/etc/redhat-release"
_, err := os.Stat(sysnamePath)
@@ -63,7 +64,7 @@ func setRLimit() error {
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err
}
limit.Max = 1024 * 1024
limit.Max = 65536
limit.Cur = limit.Max
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
return err

View File

@@ -1,7 +1,4 @@
//go:build windows
// +build windows
package main
package openp2p
import (
"fmt"
@@ -38,7 +35,7 @@ func setRLimit() error {
func setFirewall() {
fullPath, err := filepath.Abs(os.Args[0])
if err != nil {
gLog.Println(LevelERROR, "add firewall error:", err)
gLog.Println(LvERROR, "add firewall error:", err)
return
}
isXP := false
@@ -48,9 +45,9 @@ func setFirewall() {
}
if isXP {
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall del allowedprogram "%s"`, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall add allowedprogram "%s" "%s" ENABLE`, ProducnName, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh firewall add allowedprogram "%s" "%s" ENABLE`, ProductName, fullPath)).Run()
} else { // win7 or later
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall del rule name="%s"`, ProducnName)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall add rule name="%s" dir=in action=allow program="%s" enable=yes`, ProducnName, fullPath)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall del rule name="%s"`, ProductName)).Run()
exec.Command("cmd.exe", `/c`, fmt.Sprintf(`netsh advfirewall firewall add rule name="%s" dir=in action=allow program="%s" enable=yes`, ProductName, fullPath)).Run()
}
}

279
daemon.go
View File

@@ -1,279 +0,0 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/kardianos/service"
)
type daemon struct {
running bool
proc *os.Process
}
func (d *daemon) Start(s service.Service) error {
gLog.Println(LevelINFO, "daemon start")
return nil
}
func (d *daemon) Stop(s service.Service) error {
gLog.Println(LevelINFO, "service stop")
d.running = false
if d.proc != nil {
gLog.Println(LevelINFO, "stop worker")
d.proc.Kill()
}
if service.Interactive() {
gLog.Println(LevelINFO, "stop daemon")
os.Exit(0)
}
return nil
}
func (d *daemon) run() {
gLog.Println(LevelINFO, "daemon run start")
defer gLog.Println(LevelINFO, "daemon run end")
d.running = true
binPath, _ := os.Executable()
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
gLog.Println(LevelINFO, mydir)
conf := &service.Config{
Name: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
Executable: binPath,
}
s, _ := service.New(d, conf)
go s.Run()
var args []string
// rm -d parameter
for i := 0; i < len(os.Args); i++ {
if os.Args[i] == "-d" {
args = append(os.Args[0:i], os.Args[i+1:]...)
break
}
}
args = append(args, "-nv")
for {
// start worker
tmpDump := filepath.Join("log", "dump.log.tmp")
dumpFile := filepath.Join("log", "dump.log")
f, err := os.Create(filepath.Join(tmpDump))
if err != nil {
gLog.Printf(LevelERROR, "start worker error:%s", err)
return
}
gLog.Println(LevelINFO, "start worker process, args:", args)
execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}}
p, err := os.StartProcess(binPath, args, execSpec)
if err != nil {
gLog.Printf(LevelERROR, "start worker error:%s", err)
return
}
d.proc = p
_, _ = p.Wait()
f.Close()
time.Sleep(time.Second)
err = os.Rename(tmpDump, dumpFile)
if err != nil {
gLog.Printf(LevelERROR, "rename dump error:%s", err)
}
if !d.running {
return
}
gLog.Printf(LevelERROR, "worker stop, restart it after 10s")
time.Sleep(time.Second * 10)
}
}
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
svcConfig := &service.Config{
Name: ProducnName,
DisplayName: ProducnName,
Description: ProducnName,
Executable: exeAbsPath,
Arguments: args,
}
s, e := service.New(d, svcConfig)
if e != nil {
return e
}
e = service.Control(s, ctrlComm)
if e != nil {
return e
}
return nil
}
// examples:
// listen:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0
// listen and build p2papp:
// ./openp2p install -node hhd1207-222 -token YOUR-TOKEN -sharebandwidth 0 -peernode hhdhome-n1 -dstip 127.0.0.1 -dstport 50022 -protocol tcp -srcport 22
func install() {
gLog.Println(LevelINFO, "openp2p start. version: ", OpenP2PVersion)
gLog.Println(LevelINFO, "Contact: QQ Group: 16947733, Email: openp2p.cn@gmail.com")
gLog.Println(LevelINFO, "install start")
defer gLog.Println(LevelINFO, "install end")
// auto uninstall
err := os.MkdirAll(defaultInstallPath, 0775)
if err != nil {
gLog.Printf(LevelERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
return
}
err = os.Chdir(defaultInstallPath)
if err != nil {
gLog.Println(LevelERROR, "cd error:", err)
return
}
uninstall()
// save config file
installFlag := flag.NewFlagSet("install", flag.ExitOnError)
serverHost := installFlag.String("serverhost", "api.openp2p.cn", "server host ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
token := installFlag.Uint64("token", 0, "token")
node := installFlag.String("node", "", "node name. 8-31 characters. if not set, it will be hostname")
peerNode := installFlag.String("peernode", "", "peer node name that you want to connect")
dstIP := installFlag.String("dstip", "127.0.0.1", "destination ip ")
dstPort := installFlag.Int("dstport", 0, "destination port ")
srcPort := installFlag.Int("srcport", 0, "source port ")
protocol := installFlag.String("protocol", "tcp", "tcp or udp")
appName := flag.String("appname", "", "app name")
shareBandwidth := installFlag.Int("sharebandwidth", 10, "N mbps share bandwidth limit, private network no limit")
logLevel := installFlag.Int("loglevel", 1, "0:debug 1:info 2:warn 3:error")
installFlag.Parse(os.Args[2:])
gConf.load() // load old config. otherwise will clear all apps
gConf.LogLevel = *logLevel
gConf.Network.ServerHost = *serverHost
gConf.Network.Token = *token
if *node != "" {
if len(*node) < 8 {
gLog.Println(LevelERROR, 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()
}
}
gConf.Network.ServerPort = 27183
gConf.Network.UDPPort1 = 27182
gConf.Network.UDPPort2 = 27183
gConf.Network.ShareBandwidth = *shareBandwidth
config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode
config.DstHost = *dstIP
config.DstPort = *dstPort
config.SrcPort = *srcPort
config.Protocol = *protocol
config.AppName = *appName
if config.SrcPort != 0 {
gConf.add(config, true)
}
gConf.save()
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{}
// copy files
binPath, _ := os.Executable()
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
if errFiles != nil {
gLog.Printf(LevelERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles)
return
}
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if errFiles != nil {
gLog.Printf(LevelERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
return
}
_, errFiles = io.Copy(dst, src)
if errFiles != nil {
gLog.Printf(LevelERROR, "io.Copy error:%s", errFiles)
return
}
src.Close()
dst.Close()
// install system service
gLog.Println(LevelINFO, "targetPath:", targetPath)
err = d.Control("install", targetPath, []string{"-d"})
if err == nil {
gLog.Println(LevelINFO, "install system service ok.")
}
time.Sleep(time.Second * 2)
err = d.Control("start", targetPath, []string{"-d"})
if err != nil {
gLog.Println(LevelERROR, "start openp2p service error:", err)
} else {
gLog.Println(LevelINFO, "start openp2p service ok.")
}
}
func installByFilename() {
params := strings.Split(filepath.Base(os.Args[0]), "-")
if len(params) < 4 {
return
}
serverHost := params[1]
token := params[2]
gLog.Println(LevelINFO, "install start")
targetPath := os.Args[0]
args := []string{"install"}
args = append(args, "-serverhost")
args = append(args, serverHost)
args = append(args, "-token")
args = append(args, token)
env := os.Environ()
cmd := exec.Command(targetPath, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
cmd.Env = env
err := cmd.Run()
if err != nil {
gLog.Println(LevelERROR, "install by filename, start process error:", err)
return
}
gLog.Println(LevelINFO, "install end")
fmt.Println("Press the Any Key to exit")
fmt.Scanln()
os.Exit(0)
}
func uninstall() {
gLog.Println(LevelINFO, "uninstall start")
defer gLog.Println(LevelINFO, "uninstall end")
d := daemon{}
err := d.Control("stop", "", nil)
if err != nil { // service maybe not install
return
}
err = d.Control("uninstall", "", nil)
if err != nil {
gLog.Println(LevelERROR, "uninstall system service error:", err)
} else {
gLog.Println(LevelINFO, "uninstall system service ok.")
}
binPath := filepath.Join(defaultInstallPath, defaultBinName)
os.Remove(binPath + "0")
os.Remove(binPath)
// os.RemoveAll(defaultInstallPath) // reserve config.json
}

BIN
doc/images/p2p-debug.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,73 @@
# 1. 环境
golang1.18.1
调试端: win10+vscode
被调试端: ubuntu20.04LTS+dlv
## dlv安装
参考官方文档https://github.com/go-delve/delve/tree/master/Documentation/installation
```
go install github.com/go-delve/delve/cmd/dlv@latest
```
## 被调试端
```
cd /your-src-path #要确保两端源码一致
dlv debug --headless --listen=:2345 --api-version=2
# 如果失败可查看更多日志
# dlv debug --headless --listen=:2345 --api-version=2 --log --log-output=rpc,dap,debugger
```
## 调试端vscode
```
打开vscode修改launch.json增加下面远程调试配置
{
"version": "0.2.0",
"configurations": [
{
"name": "RemoteDebug",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 2345,
"host": "192.168.3.29",
}
]
}
按F5启动调试
```
## 没有公网IP或不在同一个局域网无法直连如何调试
到https://openp2p.cn/注册一个用户获得token两端安装一个客户端程序可将被调试端的2345端口通过p2p连接映射到调试端本地。
p2p连接可通过web配置 https://github.com/openp2p-cn/openp2p/blob/master/README-ZH.md#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8
也可以手动下载https://github.com/openp2p-cn/openp2p/releases 通过命令行手动配置
```
# 注意替换下面YOUR-开头的参数改成自己的
./openp2p -node YOUR-DEBUG-SERVER -token YOUR-TOKEN
openp2p.exe -node YOUR-DEBUG-CLIENT -token YOUR-TOKEN -peernode YOUR-DEBUG-SERVER -dstport 2345 -srcport 2345
2022/04/22 11:07:26 25680 INFO LISTEN ON PORT tcp:2345 START
#显示这条日志说明成功了
```
p2p端口映射完成后把vscode的配置改成本地127.0.0.1,一样可以顺利调试。
```
{
"version": "0.2.0",
"configurations": [
{
"name": "RemoteDebug",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 2345,
"host": "127.0.0.1",
}
]
}
```
# 参考
https://github.com/golang/vscode-go/blob/master/docs/debugging.md#remote-debugging

50
doc/remote-debug-vscpp.md Normal file
View File

@@ -0,0 +1,50 @@
# 1. 环境
visual studio 2022
调试端: win10
被调试端: win10
## 程序编译
一般远程调试我会选择release版本然后将优化去掉即可这样更接近真实版本。
![image](/doc/images/release-debug.png)
```
go install github.com/go-delve/delve/cmd/dlv@latest
```
## 运行远程调试器
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\Remote Debugger 拷贝到目标机器
如果调试x64程序则cd到Remote Debugger\x64目录
管理员方式打开cmd执行
```
netsh advfirewall set allprofiles state off
msvsmon.exe /noauth /anyuser /silent
```
## visual studio
Attach远程进程按下Ctrl+Atl+P
![image](/doc/images/vs2022-remote-debug-attach.png)
## 没有公网IP或不在同一个局域网无法直连如何调试
到 https://openp2p.cn/ 注册一个用户获得token两端安装一个客户端程序可将被调试端的2345端口通过p2p连接映射到调试端本地。
p2p连接可通过web配置 https://github.com/openp2p-cn/openp2p/blob/master/README-ZH.md#%E5%BF%AB%E9%80%9F%E5%85%A5%E9%97%A8
也可以手动下载https://github.com/openp2p-cn/openp2p/releases 通过命令行手动配置
```
# 注意替换下面YOUR-开头的参数改成自己的
./openp2p -node YOUR-DEBUG-SERVER -token YOUR-TOKEN
openp2p.exe -node YOUR-DEBUG-CLIENT -token YOUR-TOKEN -peernode YOUR-DEBUG-SERVER -dstport 4026 -srcport 4026
2022/04/22 11:07:26 25680 INFO LISTEN ON PORT tcp:4026 START
#显示这条日志说明成功了
```
![image](/doc/images/p2p-debug.png)
可以顺利远程调试
```

View File

@@ -1,16 +0,0 @@
package main
import (
"errors"
)
// error message
var (
// ErrorS2S string = "s2s is not supported"
// ErrorHandshake string = "handshake error"
ErrorS2S = errors.New("s2s is not supported")
ErrorHandshake = errors.New("handshake error")
ErrorNewUser = errors.New("new user")
ErrorLogin = errors.New("user or password not correct")
ErrNodeTooShort = errors.New("node name too short, it must >=8 charaters")
)

28
go.mod
View File

@@ -1,10 +1,30 @@
module openp2p
go 1.16
go 1.18
require (
github.com/gorilla/websocket v1.4.2
github.com/kardianos/service v1.2.0
github.com/lucas-clemente/quic-go v0.24.0
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
github.com/lucas-clemente/quic-go v0.27.0
github.com/openp2p-cn/go-reuseport v0.3.2
github.com/openp2p-cn/service v1.0.0
github.com/openp2p-cn/totp v0.0.0-20230102121327-8e02f6b392ed
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
)
require (
github.com/cheekybits/genny v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/kardianos/service v1.2.2 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

183
log.go
View File

@@ -1,183 +0,0 @@
package main
import (
"log"
"os"
"runtime"
"sync"
"time"
)
// LogLevel ...
type LogLevel int
var gLog *V8log
// LevelDEBUG ...
const (
LevelDEBUG LogLevel = iota
LevelINFO
LevelWARN
LevelERROR
)
var (
logFileNames map[LogLevel]string
loglevel map[LogLevel]string
)
func init() {
logFileNames = make(map[LogLevel]string)
loglevel = make(map[LogLevel]string)
logFileNames[0] = ".log"
loglevel[LevelDEBUG] = "DEBUG"
loglevel[LevelINFO] = "INFO"
loglevel[LevelWARN] = "WARN"
loglevel[LevelERROR] = "ERROR"
}
const (
LogFile = iota
LogConsole
LogFileAndConsole
)
// V8log ...
type V8log struct {
loggers map[LogLevel]*log.Logger
files map[LogLevel]*os.File
level LogLevel
stopSig chan bool
logDir string
mtx *sync.Mutex
stoped bool
lineEnding string
pid int
maxLogSize int64
mode int
}
// InitLogger ...
func InitLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *V8log {
logger := make(map[LogLevel]*log.Logger)
openedfile := make(map[LogLevel]*os.File)
var (
logdir string
)
if path == "" {
logdir = "log/"
} else {
logdir = path + "/log/"
}
os.MkdirAll(logdir, 0777)
for l := range logFileNames {
logFilePath := logdir + filePrefix + logFileNames[l]
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
log.Fatal(err)
}
os.Chmod(logFilePath, 0666)
openedfile[l] = f
logger[l] = log.New(f, "", log.LstdFlags)
}
var le string
if runtime.GOOS == "windows" {
le = "\r\n"
} else {
le = "\n"
}
pLog := &V8log{logger, openedfile, level, make(chan bool, 10), logdir, &sync.Mutex{}, false, le, os.Getpid(), maxLogSize, mode}
go pLog.checkFile()
return pLog
}
func (vl *V8log) setLevel(level LogLevel) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
vl.level = level
}
func (vl *V8log) setMode(mode int) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
vl.mode = mode
}
func (vl *V8log) checkFile() {
if vl.maxLogSize <= 0 {
return
}
ticker := time.NewTicker(time.Minute)
for {
select {
case <-ticker.C:
vl.mtx.Lock()
for l, logFile := range vl.files {
f, e := logFile.Stat()
if e != nil {
continue
}
if f.Size() <= vl.maxLogSize {
continue
}
logFile.Close()
fname := f.Name()
backupPath := vl.logDir + fname + ".0"
os.Remove(backupPath)
os.Rename(vl.logDir+fname, backupPath)
newFile, e := os.OpenFile(vl.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if e == nil {
vl.loggers[l].SetOutput(newFile)
vl.files[l] = newFile
}
}
vl.mtx.Unlock()
case <-vl.stopSig:
}
if vl.stoped {
break
}
}
}
// Printf Warning: report error log depends on this Print format.
func (vl *V8log) Printf(level LogLevel, format string, params ...interface{}) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
if vl.stoped {
return
}
if level < vl.level {
return
}
pidAndLevel := []interface{}{vl.pid, loglevel[level]}
params = append(pidAndLevel, params...)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Printf("%d %s "+format+vl.lineEnding, params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Printf("%d %s "+format+vl.lineEnding, params...)
}
}
// Println ...
func (vl *V8log) Println(level LogLevel, params ...interface{}) {
vl.mtx.Lock()
defer vl.mtx.Unlock()
if vl.stoped {
return
}
if level < vl.level {
return
}
pidAndLevel := []interface{}{vl.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...)
params = append(params, vl.lineEnding)
if vl.mode == LogFile || vl.mode == LogFileAndConsole {
vl.loggers[0].Print(params...)
}
if vl.mode == LogConsole || vl.mode == LogFileAndConsole {
log.Print(params...)
}
}

Some files were not shown because too many files have changed in this diff Show More