Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c3a43be3cc | ||
|
|
c8b8bf05a5 | ||
|
|
8311341960 | ||
|
|
d5c098ca74 | ||
|
|
af82fc6e36 | ||
|
|
2af77668fe | ||
|
|
215feb8721 | ||
|
|
4a115fb3fa | ||
|
|
0fb3bc4e26 | ||
|
|
df23b30d2b | ||
|
|
f9b5073e0d | ||
|
|
9ea467c7b3 | ||
|
|
c0bad61eb6 | ||
|
|
bb32133038 |
14
.gitignore
vendored
@@ -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
@@ -0,0 +1,7 @@
|
||||
build:
|
||||
export GOPROXY=https://goproxy.io,direct
|
||||
go mod tidy
|
||||
go build cmd/openp2p.go
|
||||
.PHONY: build
|
||||
|
||||
.DEFAULT_GOAL := build
|
||||
20
README-ZH.md
@@ -23,7 +23,7 @@
|
||||
### 5. 跨平台
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64
|
||||
### 6. 高效
|
||||
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
|
||||
P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),UDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。
|
||||
|
||||
### 7. 二次开发
|
||||
基于OpenP2P只需数行代码,就能让原来只能局域网通信的程序,变成任何内网都能通信
|
||||
@@ -31,6 +31,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
|
||||
## 快速入门
|
||||
仅需简单4步就能用起来。
|
||||
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
||||
(另外一个快速入门视频 https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||
### 1.注册
|
||||
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
||||
|
||||
@@ -98,20 +99,18 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
||||
go version go1.18.1+
|
||||
cd到代码根目录,执行
|
||||
```
|
||||
export GOPROXY=https://goproxy.io,direct
|
||||
go mod tidy
|
||||
go build
|
||||
make
|
||||
```
|
||||
|
||||
## TODO
|
||||
## RoadMap
|
||||
近期计划:
|
||||
1. 支持IPv6(100%)
|
||||
2. 支持随系统自动启动,安装成系统服务(100%)
|
||||
3. 提供一些免费服务器给特别差的网络,如广电网络(100%)
|
||||
4. 建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等(100%)
|
||||
1. ~~支持IPv6~~(100%)
|
||||
2. ~~支持随系统自动启动,安装成系统服务~~(100%)
|
||||
3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%)
|
||||
4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%)
|
||||
5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备
|
||||
6. 客户端提供WebUI
|
||||
7. 支持自有服务器高并发连接
|
||||
7. 支持自有服务器,开源服务器程序
|
||||
8. 共享节点调度模型优化,对不同的运营商优化
|
||||
9. 方便二次开发,提供API和lib
|
||||
10. 应用层支持UDP协议,实现很简单,但UDP应用较少暂不急(100%)
|
||||
@@ -119,6 +118,7 @@ go build
|
||||
12. 支持Android系统,让旧手机焕发青春变成移动网关
|
||||
13. 支持Windows网上邻居共享文件
|
||||
14. 内网直连优化,用处不大,估计就用户测试时用到
|
||||
15. ~~支持UPNP~~(100%)
|
||||
|
||||
远期计划:
|
||||
1. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。
|
||||
|
||||
23
README.md
@@ -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),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.
|
||||
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
|
||||
|
||||
@@ -106,27 +106,26 @@ The server side has a scheduling model, which calculate bandwith, ping value,st
|
||||
go version go1.18.1+
|
||||
cd root directory of the socure code and execute
|
||||
```
|
||||
export GOPROXY=https://goproxy.io,direct
|
||||
go mod tidy
|
||||
go build
|
||||
make
|
||||
```
|
||||
|
||||
## TODO
|
||||
## RoadMap
|
||||
Short-Term:
|
||||
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%)
|
||||
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.
|
||||
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.(100%)
|
||||
10. ~~Support UDP at application layer, it is easy to implement but not urgent due to only a few applicaitons using UDP protocol.~~(100%)
|
||||
11. Support KCP protocol underlay, currently support Quic only. KCP focus on delay optimization,which has been widely used as game accelerator,it can sacrifice part of bandwidth to reduce timelag.
|
||||
12. Support Android platform, let the phones to be mobile gateway.
|
||||
13. Support SMB Windows neighborhood.
|
||||
14. Direct connection on intranet, for testing.
|
||||
15. ~~Support UPNP.~~(100%)
|
||||
|
||||
|
||||
Long-Term:
|
||||
|
||||
@@ -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
|
||||
```
|
||||
5
USAGE.md
@@ -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
@@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
3
app/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
app/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
||||
OpenP2P
|
||||
6
app/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
||||
19
app/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
30
app/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="MavenRepo" />
|
||||
<option name="name" value="MavenRepo" />
|
||||
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
||||
9
app/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
10
app/.idea/runConfigurations.xml
generated
Normal 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
@@ -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>
|
||||
1
app/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
21
app/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.openp2p
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("cn.openp2p", appContext.packageName)
|
||||
}
|
||||
}
|
||||
39
app/app/src/main/AndroidManifest.xml
Normal 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>
|
||||
115
app/app/src/main/java/cn/openp2p/OpenP2PService.kt
Normal 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
|
||||
}
|
||||
}
|
||||
24
app/app/src/main/java/cn/openp2p/data/LoginDataSource.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
package cn.openp2p.data
|
||||
|
||||
import cn.openp2p.data.model.LoggedInUser
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* Class that handles authentication w/ login credentials and retrieves user information.
|
||||
*/
|
||||
class LoginDataSource {
|
||||
|
||||
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||
try {
|
||||
// TODO: handle loggedInUser authentication
|
||||
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
|
||||
return Result.Success(fakeUser)
|
||||
} catch (e: Throwable) {
|
||||
return Result.Error(IOException("Error logging in", e))
|
||||
}
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
// TODO: revoke authentication
|
||||
}
|
||||
}
|
||||
46
app/app/src/main/java/cn/openp2p/data/LoginRepository.kt
Normal file
@@ -0,0 +1,46 @@
|
||||
package cn.openp2p.data
|
||||
|
||||
import cn.openp2p.data.model.LoggedInUser
|
||||
|
||||
/**
|
||||
* Class that requests authentication and user information from the remote data source and
|
||||
* maintains an in-memory cache of login status and user credentials information.
|
||||
*/
|
||||
|
||||
class LoginRepository(val dataSource: LoginDataSource) {
|
||||
|
||||
// in-memory cache of the loggedInUser object
|
||||
var user: LoggedInUser? = null
|
||||
private set
|
||||
|
||||
val isLoggedIn: Boolean
|
||||
get() = user != null
|
||||
|
||||
init {
|
||||
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||
// @see https://developer.android.com/training/articles/keystore
|
||||
user = null
|
||||
}
|
||||
|
||||
fun logout() {
|
||||
user = null
|
||||
dataSource.logout()
|
||||
}
|
||||
|
||||
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||
// handle login
|
||||
val result = dataSource.login(username, password)
|
||||
|
||||
if (result is Result.Success) {
|
||||
setLoggedInUser(result.data)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun setLoggedInUser(loggedInUser: LoggedInUser) {
|
||||
this.user = loggedInUser
|
||||
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||
// @see https://developer.android.com/training/articles/keystore
|
||||
}
|
||||
}
|
||||
18
app/app/src/main/java/cn/openp2p/data/Result.kt
Normal file
@@ -0,0 +1,18 @@
|
||||
package cn.openp2p.data
|
||||
|
||||
/**
|
||||
* A generic class that holds a value with its loading status.
|
||||
* @param <T>
|
||||
*/
|
||||
sealed class Result<out T : Any> {
|
||||
|
||||
data class Success<out T : Any>(val data: T) : Result<T>()
|
||||
data class Error(val exception: Exception) : Result<Nothing>()
|
||||
|
||||
override fun toString(): String {
|
||||
return when (this) {
|
||||
is Success<*> -> "Success[data=$data]"
|
||||
is Error -> "Error[exception=$exception]"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package cn.openp2p.data.model
|
||||
|
||||
/**
|
||||
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||
*/
|
||||
data class LoggedInUser(
|
||||
val userId: String,
|
||||
val displayName: String
|
||||
)
|
||||
@@ -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
|
||||
)
|
||||
193
app/app/src/main/java/cn/openp2p/ui/login/LoginActivity.kt
Normal 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) {}
|
||||
})
|
||||
}
|
||||
10
app/app/src/main/java/cn/openp2p/ui/login/LoginFormState.kt
Normal file
@@ -0,0 +1,10 @@
|
||||
package cn.openp2p.ui.login
|
||||
|
||||
/**
|
||||
* Data validation state of the login form.
|
||||
*/
|
||||
data class LoginFormState(
|
||||
val usernameError: Int? = null,
|
||||
val passwordError: Int? = null,
|
||||
val isDataValid: Boolean = false
|
||||
)
|
||||
9
app/app/src/main/java/cn/openp2p/ui/login/LoginResult.kt
Normal file
@@ -0,0 +1,9 @@
|
||||
package cn.openp2p.ui.login
|
||||
|
||||
/**
|
||||
* Authentication result : success (user details) or error message.
|
||||
*/
|
||||
data class LoginResult(
|
||||
val success: LoggedInUserView? = null,
|
||||
val error: Int? = null
|
||||
)
|
||||
57
app/app/src/main/java/cn/openp2p/ui/login/LoginViewModel.kt
Normal file
@@ -0,0 +1,57 @@
|
||||
package cn.openp2p.ui.login
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import android.util.Patterns
|
||||
import cn.openp2p.data.LoginRepository
|
||||
import cn.openp2p.data.Result
|
||||
|
||||
import cn.openp2p.R
|
||||
|
||||
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
|
||||
|
||||
private val _loginForm = MutableLiveData<LoginFormState>()
|
||||
val loginFormState: LiveData<LoginFormState> = _loginForm
|
||||
|
||||
private val _loginResult = MutableLiveData<LoginResult>()
|
||||
val loginResult: LiveData<LoginResult> = _loginResult
|
||||
|
||||
fun login(username: String, password: String) {
|
||||
// can be launched in a separate asynchronous job
|
||||
val result = loginRepository.login(username, password)
|
||||
|
||||
if (result is Result.Success) {
|
||||
_loginResult.value =
|
||||
LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
|
||||
} else {
|
||||
_loginResult.value = LoginResult(error = R.string.login_failed)
|
||||
}
|
||||
}
|
||||
|
||||
fun loginDataChanged(username: String, password: String) {
|
||||
// if (!isUserNameValid(username)) {
|
||||
// _loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
|
||||
// } else
|
||||
if (!isPasswordValid(password)) {
|
||||
_loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
|
||||
} else {
|
||||
_loginForm.value = LoginFormState(isDataValid = true)
|
||||
}
|
||||
}
|
||||
|
||||
// A placeholder username validation check
|
||||
private fun isUserNameValid(username: String): Boolean {
|
||||
return true
|
||||
return if (username.contains('@')) {
|
||||
Patterns.EMAIL_ADDRESS.matcher(username).matches()
|
||||
} else {
|
||||
username.isNotBlank()
|
||||
}
|
||||
}
|
||||
|
||||
// A placeholder password validation check
|
||||
private fun isPasswordValid(password: String): Boolean {
|
||||
return password.length > 5
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.openp2p.ui.login
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import cn.openp2p.data.LoginDataSource
|
||||
import cn.openp2p.data.LoginRepository
|
||||
|
||||
/**
|
||||
* ViewModel provider factory to instantiate LoginViewModel.
|
||||
* Required given LoginViewModel has a non-empty constructor
|
||||
*/
|
||||
class LoginViewModelFactory : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
|
||||
return LoginViewModel(
|
||||
loginRepository = LoginRepository(
|
||||
dataSource = LoginDataSource()
|
||||
)
|
||||
) as T
|
||||
}
|
||||
throw IllegalArgumentException("Unknown ViewModel class")
|
||||
}
|
||||
}
|
||||
30
app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
170
app/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
4
app/app/src/main/res/drawable/icon.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</selector>
|
||||
87
app/app/src/main/res/layout/activity_login.xml
Normal 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>
|
||||
5
app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
16
app/app/src/main/res/values-night/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
10
app/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
5
app/app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
</resources>
|
||||
36
app/app/src/main/res/values/strings.xml
Normal 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>
|
||||
16
app/app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
19
app/gradle.properties
Normal file
@@ -0,0 +1,19 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
BIN
app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#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
@@ -0,0 +1,172 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
84
app/gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
BIN
app/openp2p.jks
Normal file
2
app/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
||||
rootProject.name = "OpenP2P"
|
||||
include ':app'
|
||||
7
cmd/openp2p.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import openp2p "openp2p/core"
|
||||
|
||||
func main() {
|
||||
openp2p.Run()
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
{
|
||||
"network": {
|
||||
"Node": "YOUR_NODE_NAME",
|
||||
"User": "YOUR_USER_NAME",
|
||||
"Password": "YOUR_PASSWORD",
|
||||
"ServerHost": "openp2p.cn",
|
||||
"ServerPort": 27182,
|
||||
"Token": "YOUR_TOKEN",
|
||||
"ServerHost": "api.openp2p.cn",
|
||||
"ServerPort": 27183,
|
||||
"UDPPort1": 27182,
|
||||
"UDPPort2": 27183
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -17,8 +17,9 @@ import (
|
||||
"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 ""
|
||||
@@ -125,14 +126,14 @@ func netInfo() *NetInfo {
|
||||
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
|
||||
r, err := client.Get("https://ifconfig.co/json")
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "netInfo error:", err)
|
||||
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||
continue
|
||||
}
|
||||
defer r.Body.Close()
|
||||
buf := make([]byte, 1024*64)
|
||||
n, err := r.Body.Read(buf)
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "netInfo error:", err)
|
||||
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||
continue
|
||||
}
|
||||
rsp := NetInfo{}
|
||||
@@ -156,7 +157,7 @@ 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
|
||||
@@ -188,6 +189,25 @@ func compareVersion(v1, v2 string) int {
|
||||
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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
@@ -49,6 +49,11 @@ func assertCompareVersion(t *testing.T, v1 string, v2 string, result int) {
|
||||
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)
|
||||
@@ -69,3 +74,23 @@ func TestCompareVersion(t *testing.T) {
|
||||
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)
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -11,8 +11,6 @@ import (
|
||||
|
||||
var gConf Config
|
||||
|
||||
const IntValueNotSet int = -99999999
|
||||
|
||||
type AppConfig struct {
|
||||
// required
|
||||
AppName string
|
||||
@@ -24,21 +22,23 @@ type AppConfig struct {
|
||||
PeerUser string
|
||||
Enabled int // default:1
|
||||
// runtime info
|
||||
peerVersion string
|
||||
peerToken uint64
|
||||
peerNatType int
|
||||
hasIPv4 int
|
||||
IPv6 string
|
||||
hasUPNPorNATPMP int
|
||||
peerIP string
|
||||
peerConeNatPort int
|
||||
retryNum int
|
||||
retryTime time.Time
|
||||
nextRetryTime time.Time
|
||||
shareBandwidth int
|
||||
errMsg string
|
||||
connectTime time.Time
|
||||
fromToken uint64
|
||||
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
|
||||
@@ -105,10 +105,16 @@ func (c *Config) save() {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -154,7 +160,7 @@ type NetworkConfig struct {
|
||||
publicIP string
|
||||
natType int
|
||||
hasIPv4 int
|
||||
IPv6 string
|
||||
publicIPv6 string // must lowwer-case not save json
|
||||
hasUPNPorNATPMP int
|
||||
ShareBandwidth int
|
||||
// server info
|
||||
@@ -162,11 +168,13 @@ type NetworkConfig struct {
|
||||
ServerPort int
|
||||
UDPPort1 int
|
||||
UDPPort2 int
|
||||
TCPPort int
|
||||
}
|
||||
|
||||
func parseParams(subCommand string) {
|
||||
fset := flag.NewFlagSet(subCommand, flag.ExitOnError)
|
||||
serverHost := fset.String("serverhost", "api.openp2p.cn", "server host ")
|
||||
serverPort := fset.Int("serverport", WsPort, "server port ")
|
||||
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
|
||||
token := fset.Uint64("token", 0, "token")
|
||||
node := fset.String("node", "", "node name. 8-31 characters. if not set, it will be hostname")
|
||||
@@ -174,13 +182,14 @@ func parseParams(subCommand string) {
|
||||
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", 1, "0:debug 1:info 2:warn 3:error")
|
||||
logLevel := fset.Int("loglevel", 0, "0:info 1:warn 2:error 3:debug")
|
||||
if subCommand == "" { // no subcommand
|
||||
fset.Parse(os.Args[1:])
|
||||
} else {
|
||||
@@ -197,7 +206,6 @@ func parseParams(subCommand string) {
|
||||
if !*newconfig {
|
||||
gConf.load() // load old config. otherwise will clear all apps
|
||||
}
|
||||
gConf.LogLevel = *logLevel
|
||||
if config.SrcPort != 0 {
|
||||
gConf.add(config, true)
|
||||
}
|
||||
@@ -217,16 +225,19 @@ func parseParams(subCommand string) {
|
||||
if f.Name == "loglevel" {
|
||||
gConf.LogLevel = *logLevel
|
||||
}
|
||||
if f.Name == "tcpport" {
|
||||
gConf.Network.TCPPort = *tcpPort
|
||||
}
|
||||
if f.Name == "token" {
|
||||
gConf.Network.Token = *token
|
||||
}
|
||||
})
|
||||
|
||||
if gConf.Network.ServerHost == "" {
|
||||
gConf.Network.ServerHost = *serverHost
|
||||
}
|
||||
if *token != 0 {
|
||||
gConf.Network.Token = *token
|
||||
}
|
||||
if *node != "" {
|
||||
if len(*node) < 8 {
|
||||
if len(*node) < MinNodeNameLen {
|
||||
gLog.Println(LvERROR, ErrNodeTooShort)
|
||||
os.Exit(9)
|
||||
}
|
||||
@@ -236,16 +247,17 @@ func parseParams(subCommand string) {
|
||||
gConf.Network.Node = defaultNodeName()
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -253,13 +265,3 @@ func parseParams(subCommand string) {
|
||||
// gConf.mtx.Unlock()
|
||||
gConf.save()
|
||||
}
|
||||
|
||||
func (conf *AppConfig) isSupportTCP(pnConf NetworkConfig) bool {
|
||||
if conf.peerVersion == "" || compareVersion(conf.peerVersion, LeastSupportTCPVersion) == LESS {
|
||||
return false
|
||||
}
|
||||
if pnConf.hasIPv4 == 1 || pnConf.hasUPNPorNATPMP == 1 || conf.hasIPv4 == 1 || conf.hasUPNPorNATPMP == 1 || (IsIPv6(pnConf.IPv6) && IsIPv6(conf.IPv6)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/kardianos/service"
|
||||
"github.com/openp2p-cn/service"
|
||||
)
|
||||
|
||||
type daemon struct {
|
||||
@@ -44,9 +44,9 @@ func (d *daemon) run() {
|
||||
}
|
||||
gLog.Println(LvINFO, mydir)
|
||||
conf := &service.Config{
|
||||
Name: ProducnName,
|
||||
DisplayName: ProducnName,
|
||||
Description: ProducnName,
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: binPath,
|
||||
}
|
||||
|
||||
@@ -95,9 +95,9 @@ func (d *daemon) run() {
|
||||
|
||||
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
|
||||
svcConfig := &service.Config{
|
||||
Name: ProducnName,
|
||||
DisplayName: ProducnName,
|
||||
Description: ProducnName,
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: exeAbsPath,
|
||||
Arguments: args,
|
||||
}
|
||||
19
core/errorcode.go
Normal file
@@ -0,0 +1,19 @@
|
||||
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")
|
||||
ErrMsgFormat = errors.New("message format wrong")
|
||||
ErrVersionNotCompatible = errors.New("version not compatible")
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -19,7 +19,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
}
|
||||
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 {
|
||||
@@ -28,6 +28,17 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
}
|
||||
gLog.Printf(LvINFO, "%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()) {
|
||||
@@ -39,7 +50,11 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
config.PeerNode = req.From
|
||||
config.peerVersion = req.Version
|
||||
config.fromToken = req.Token
|
||||
config.IPv6 = req.IPv6
|
||||
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.Token != pn.config.Token {
|
||||
gLog.Printf(LvINFO, "set share bandwidth %d mbps", pn.config.ShareBandwidth)
|
||||
@@ -86,7 +101,6 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
msg := TunnelMsg{ID: t.id}
|
||||
pn.push(r.From, MsgPushAddRelayTunnelRsp, msg)
|
||||
}
|
||||
|
||||
}(req)
|
||||
case MsgPushAPPKey:
|
||||
req := APPKeySync{}
|
||||
@@ -98,7 +112,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
SaveKey(req.AppID, req.AppKey)
|
||||
case MsgPushUpdate:
|
||||
gLog.Println(LvINFO, "MsgPushUpdate")
|
||||
update() // download new version first, then exec ./openp2p update
|
||||
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()
|
||||
@@ -134,7 +148,7 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
}
|
||||
relayNode = app.relayNode
|
||||
relayMode = app.relayMode
|
||||
linkMode = app.tunnel.linkMode
|
||||
linkMode = app.tunnel.linkModeWeb
|
||||
}
|
||||
appInfo := AppInfo{
|
||||
AppName: config.AppName,
|
||||
@@ -158,6 +172,49 @@ func handlePush(pn *P2PNetwork, subType uint16, msg []byte) error {
|
||||
req.Apps = append(req.Apps, appInfo)
|
||||
}
|
||||
pn.write(MsgReport, MsgReportApps, &req)
|
||||
case MsgPushReportLog:
|
||||
gLog.Println(LvINFO, "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(LvINFO, "MsgPushEditApp")
|
||||
newApp := AppInfo{}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
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 ok")
|
||||
defer gLog.Printf(LvDEBUG, "handshakeC2C end")
|
||||
conn, err := net.ListenUDP("udp", t.la)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -45,6 +45,7 @@ func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
}
|
||||
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
|
||||
gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id)
|
||||
gLog.Printf(LvINFO, "handshakeC2C ok")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -54,9 +55,10 @@ func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LvINFO, "handshakeC2C ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -115,6 +117,7 @@ func handshakeC2S(t *P2PTunnel) error {
|
||||
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LvINFO, "handshakeC2S ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -175,6 +178,7 @@ func handshakeS2C(t *P2PTunnel) error {
|
||||
return fmt.Errorf("wait handshake failed")
|
||||
case la := <-gotCh:
|
||||
gLog.Println(LvDEBUG, "symmetric handshake ok", la)
|
||||
gLog.Printf(LvINFO, "handshakeS2C ok")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// ./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, "Contact: QQ group 16947733, Email openp2p.cn@gmail.com")
|
||||
gLog.Println(LvINFO, "install start")
|
||||
defer gLog.Println(LvINFO, "install end")
|
||||
// auto uninstall
|
||||
@@ -74,6 +74,7 @@ func install() {
|
||||
} else {
|
||||
gLog.Println(LvINFO, "start openp2p service ok.")
|
||||
}
|
||||
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
|
||||
}
|
||||
|
||||
func installByFilename() {
|
||||
@@ -102,6 +103,7 @@ func installByFilename() {
|
||||
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)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
192
core/nat.go
Normal 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(time.Second * 5))
|
||||
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 := P2PNetworkInstance(nil).config.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", 30)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||
break
|
||||
} else {
|
||||
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
100
core/openp2p.go
Normal file
@@ -0,0 +1,100 @@
|
||||
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()
|
||||
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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -204,12 +204,15 @@ func (app *p2pApp) listen() error {
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -24,14 +23,15 @@ var (
|
||||
)
|
||||
|
||||
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
|
||||
@@ -67,13 +67,12 @@ func (pn *P2PNetwork) run() {
|
||||
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/write goroutine end
|
||||
err := pn.init()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
@@ -85,7 +84,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 +108,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
|
||||
}
|
||||
@@ -123,19 +120,21 @@ func (pn *P2PNetwork) runAll() {
|
||||
pn.DeleteApp(*config)
|
||||
}
|
||||
if config.retryNum > 0 {
|
||||
gLog.Printf(LvINFO, "detect app %s(%d) disconnect, reconnecting the %d times...", config.AppName, appID, config.retryNum)
|
||||
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) { // normal lasts 15min
|
||||
config.retryNum = 0
|
||||
}
|
||||
}
|
||||
config.retryNum++
|
||||
config.retryTime = time.Now()
|
||||
increase := math.Pow(1.3, float64(config.retryNum))
|
||||
if increase > 900 {
|
||||
increase = 900
|
||||
if config.retryNum > 20 {
|
||||
config.Enabled = 0
|
||||
gLog.Printf(LvWARN, "app %s has stopped retry, manually enable it on Web console", config.AppName)
|
||||
continue
|
||||
}
|
||||
config.nextRetryTime = time.Now().Add(time.Second * time.Duration(increase)) // exponential increase retry time. 1.3^x
|
||||
config.nextRetryTime = time.Now().Add(time.Second * 10)
|
||||
config.connectTime = time.Now()
|
||||
config.peerToken = pn.config.Token
|
||||
gConf.mtx.Unlock() // AddApp will take a period of time
|
||||
err := pn.AddApp(*config)
|
||||
gConf.mtx.Lock()
|
||||
@@ -152,7 +151,6 @@ func (pn *P2PNetwork) autorunApp() {
|
||||
continue
|
||||
}
|
||||
pn.runAll()
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
gLog.Println(LvINFO, "autorunApp end")
|
||||
}
|
||||
@@ -160,6 +158,7 @@ func (pn *P2PNetwork) autorunApp() {
|
||||
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)
|
||||
if head == nil {
|
||||
@@ -179,6 +178,7 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri
|
||||
relayConfig := config
|
||||
relayConfig.PeerNode = rsp.RelayName
|
||||
relayConfig.peerToken = rsp.RelayToken
|
||||
///
|
||||
t, err := pn.addDirectTunnel(relayConfig, 0)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "direct connect error:", err)
|
||||
@@ -238,13 +238,7 @@ func (pn *P2PNetwork) AddApp(config AppConfig) error {
|
||||
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 && t.isSupportTCP() {
|
||||
// t, err = pn.addDirectTunnel(config, 0)
|
||||
// if t != nil {
|
||||
// peerNatType = t.config.peerNatType
|
||||
// peerIP = t.config.peerIP
|
||||
// }
|
||||
// }
|
||||
|
||||
if err != nil && err == ErrorHandshake {
|
||||
gLog.Println(LvERROR, "direct connect failed, try to relay")
|
||||
t, rtid, relayMode, err = pn.addRelayTunnel(config)
|
||||
@@ -349,36 +343,103 @@ 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(LvERROR, "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 {
|
||||
if err := t.listen(); err != nil {
|
||||
gLog.Println(LvERROR, "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 {
|
||||
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(LvDEBUG, "store tunnel %d", tid)
|
||||
pn.allTunnels.Store(tid, t)
|
||||
return t, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) init() error {
|
||||
gLog.Println(LvINFO, "init start")
|
||||
go func() { //reconnect at least 5s
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
time.Sleep(NatTestTimeout)
|
||||
}()
|
||||
var err error
|
||||
for {
|
||||
// detect nat type
|
||||
@@ -401,10 +462,10 @@ func (pn *P2PNetwork) init() error {
|
||||
}
|
||||
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 := "/openp2p/v1/login"
|
||||
config := tls.Config{InsecureSkipVerify: true} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
|
||||
websocket.DefaultDialer.TLSClientConfig = &config
|
||||
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: forwardPath}
|
||||
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))
|
||||
@@ -427,28 +488,30 @@ 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,
|
||||
HasIPv4: pn.config.hasIPv4,
|
||||
HasUPNPorNATPMP: pn.config.hasUPNPorNATPMP,
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
rsp := netInfo()
|
||||
gLog.Println(LvDEBUG, "netinfo:", rsp)
|
||||
if rsp != nil && rsp.Country != "" {
|
||||
if IsIPv6(rsp.IP.String()) {
|
||||
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)
|
||||
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)
|
||||
}()
|
||||
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
||||
break
|
||||
}
|
||||
@@ -481,16 +544,21 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
|
||||
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)
|
||||
if len(rsp.Node) >= MinNodeNameLen {
|
||||
gConf.setNode(rsp.Node)
|
||||
}
|
||||
gConf.save()
|
||||
pn.localTs = time.Now().Unix()
|
||||
gLog.Printf(LvINFO, "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(LvDEBUG, "P2PNetwork heartbeat ok")
|
||||
pn.hbTime = time.Now()
|
||||
case MsgPush:
|
||||
handlePush(pn, head.SubType, msg)
|
||||
default:
|
||||
@@ -504,8 +572,8 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
|
||||
|
||||
func (pn *P2PNetwork) readLoop() {
|
||||
gLog.Printf(LvDEBUG, "P2PNetwork readLoop start")
|
||||
pn.wg.Add(1)
|
||||
defer pn.wg.Done()
|
||||
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()
|
||||
@@ -626,22 +694,22 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
|
||||
})
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) refreshIPv6() {
|
||||
if !IsIPv6(pn.config.IPv6) { // not support ipv6, not 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(LvINFO, "refreshIPv6 error:", err)
|
||||
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, "netInfo error:", err, n)
|
||||
gLog.Println(LvINFO, "refreshIPv6 error:", err, n)
|
||||
return
|
||||
}
|
||||
pn.config.IPv6 = string(buf[:n])
|
||||
pn.config.publicIPv6 = string(buf[:n])
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -28,58 +28,85 @@ type P2PTunnel struct {
|
||||
tunnelServer bool // different from underlayServer
|
||||
coneLocalPort int
|
||||
coneNatPort int
|
||||
linkMode string
|
||||
linkModeWeb string // use config.linkmode
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) init() {
|
||||
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 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()%10000 + 50000)
|
||||
if t.pn.config.natType == NATCone {
|
||||
// prepare one random cone hole
|
||||
_, _, _, port1, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0)
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = port1
|
||||
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
|
||||
} else {
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = localPort // symmetric doesn't need coneNatPort
|
||||
if t.pn.config.hasUPNPorNATPMP == 1 {
|
||||
nat, err := Discover()
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
||||
} else {
|
||||
externalPort, err := nat.AddPortMapping("tcp", localPort, localPort, "openp2p", 30) // timeout the connection still alive, make the timeout short
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.la = &net.UDPAddr{IP: net.ParseIP(t.pn.config.localIP), Port: t.coneLocalPort}
|
||||
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
|
||||
t.pn.refreshIPv6()
|
||||
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.IPv6,
|
||||
HasUPNPorNATPMP: t.pn.config.hasUPNPorNATPMP,
|
||||
ID: t.id,
|
||||
AppKey: appKey,
|
||||
Version: OpenP2PVersion,
|
||||
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
|
||||
@@ -101,7 +128,7 @@ func (t *P2PTunnel) connect() error {
|
||||
}
|
||||
t.config.peerNatType = rsp.NatType
|
||||
t.config.hasIPv4 = rsp.HasIPv4
|
||||
t.config.IPv6 = rsp.IPv6
|
||||
t.config.peerIPv6 = rsp.IPv6
|
||||
t.config.hasUPNPorNATPMP = rsp.HasUPNPorNATPMP
|
||||
t.config.peerVersion = rsp.Version
|
||||
t.config.peerConeNatPort = rsp.ConeNatPort
|
||||
@@ -127,6 +154,9 @@ func (t *P2PTunnel) setRun(running bool) {
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -162,7 +192,7 @@ func (t *P2PTunnel) close() {
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) start() error {
|
||||
if !t.config.isSupportTCP(t.pn.config) {
|
||||
if t.config.linkMode == LinkModeUDPPunch {
|
||||
if err := t.handshake(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -176,7 +206,7 @@ func (t *P2PTunnel) start() error {
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) handshake() error {
|
||||
if t.config.peerConeNatPort > 0 {
|
||||
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 {
|
||||
@@ -207,23 +237,22 @@ func (t *P2PTunnel) handshake() error {
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlay() (err error) {
|
||||
if !t.config.isSupportTCP(t.pn.config) {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
if IsIPv6(t.pn.config.IPv6) && IsIPv6(t.config.IPv6) { // both have ipv6
|
||||
t.conn, err = t.connectUnderlayTCP6()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else { // hasipv4 or upnp
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.conn == nil {
|
||||
return errors.New("connect underlay error")
|
||||
}
|
||||
t.setRun(true)
|
||||
go t.readLoop()
|
||||
@@ -235,7 +264,7 @@ func (t *P2PTunnel) connectUnderlayQuic() (c underlay, err error) {
|
||||
gLog.Println(LvINFO, "connectUnderlayQuic start")
|
||||
defer gLog.Println(LvINFO, "connectUnderlayQuic end")
|
||||
var qConn *underlayQUIC
|
||||
if t.isUnderlayServer() {
|
||||
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 {
|
||||
@@ -288,7 +317,7 @@ func (t *P2PTunnel) connectUnderlayQuic() (c underlay, err error) {
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvDEBUG, "quic connection ok")
|
||||
t.linkMode = LinkModeUDPPunch
|
||||
t.linkModeWeb = LinkModeUDPPunch
|
||||
return qConn, nil
|
||||
}
|
||||
|
||||
@@ -297,37 +326,36 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
gLog.Println(LvINFO, "connectUnderlayTCP start")
|
||||
defer gLog.Println(LvINFO, "connectUnderlayTCP end")
|
||||
var qConn *underlayTCP
|
||||
if t.isUnderlayServer() {
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
qConn, err = listenTCP(t.coneNatPort, TunnelIdleTimeout)
|
||||
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 {
|
||||
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, "TCP connection ok")
|
||||
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.ra.String())
|
||||
qConn, err = dialTCP(t.config.peerIP, t.config.peerConeNatPort)
|
||||
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 error:%s", t.ra.String(), err)
|
||||
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 {
|
||||
qConn.listener.Close()
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
@@ -335,8 +363,8 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvDEBUG, "TCP connection ok")
|
||||
t.linkMode = LinkModeIPv4
|
||||
gLog.Println(LvINFO, "TCP connection ok")
|
||||
t.linkModeWeb = LinkModeIPv4
|
||||
return qConn, nil
|
||||
}
|
||||
|
||||
@@ -344,7 +372,7 @@ func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
|
||||
gLog.Println(LvINFO, "connectUnderlayTCP6 start")
|
||||
defer gLog.Println(LvINFO, "connectUnderlayTCP6 end")
|
||||
var qConn *underlayTCP6
|
||||
if t.isUnderlayServer() {
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
qConn, err = listenTCP6(t.coneNatPort, TunnelIdleTimeout)
|
||||
if err != nil {
|
||||
@@ -365,10 +393,10 @@ func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
|
||||
|
||||
//else
|
||||
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, time.Second*5)
|
||||
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.IPv6)
|
||||
qConn, err = dialTCP6(t.config.IPv6, t.config.peerConeNatPort)
|
||||
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.IPv6, t.config.peerConeNatPort, err)
|
||||
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"))
|
||||
@@ -383,7 +411,7 @@ func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvDEBUG, "TCP6 connection ok")
|
||||
t.linkMode = LinkModeIPv6
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return qConn, nil
|
||||
}
|
||||
|
||||
@@ -568,8 +596,8 @@ func (t *P2PTunnel) listen() error {
|
||||
}
|
||||
// only private node set ipv6
|
||||
if t.config.fromToken == t.pn.config.Token {
|
||||
t.pn.refreshIPv6()
|
||||
rsp.IPv6 = t.pn.config.IPv6
|
||||
t.pn.refreshIPv6(false)
|
||||
rsp.IPv6 = t.pn.config.publicIPv6
|
||||
}
|
||||
|
||||
t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
|
||||
@@ -594,14 +622,3 @@ func (t *P2PTunnel) closeOverlayConns(appID uint64) {
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) isUnderlayServer() bool {
|
||||
if (t.pn.config.hasIPv4 == 1 || t.pn.config.hasUPNPorNATPMP == 1) && (t.config.hasIPv4 != 1 || t.config.hasUPNPorNATPMP != 1) {
|
||||
return true
|
||||
}
|
||||
if (t.pn.config.hasIPv4 != 1 || t.pn.config.hasUPNPorNATPMP != 1) && (t.config.hasIPv4 == 1 || t.config.hasUPNPorNATPMP == 1) {
|
||||
return false
|
||||
}
|
||||
// NAT or both has public IP
|
||||
return t.tunnelServer
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -10,9 +10,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const OpenP2PVersion = "2.0.1"
|
||||
const ProducnName string = "openp2p"
|
||||
const LeastSupportTCPVersion = "1.5.0"
|
||||
const OpenP2PVersion = "3.5.6"
|
||||
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
|
||||
@@ -68,6 +76,7 @@ const (
|
||||
MsgP2P = 4
|
||||
MsgRelay = 5
|
||||
MsgReport = 6
|
||||
MsgQuery = 7
|
||||
)
|
||||
|
||||
// TODO: seperate node push and web push.
|
||||
@@ -86,6 +95,7 @@ const (
|
||||
MsgPushRestart = 11
|
||||
MsgPushEditNode = 12
|
||||
MsgPushAPPKey = 13
|
||||
MsgPushReportLog = 14
|
||||
)
|
||||
|
||||
// MsgP2P sub type message
|
||||
@@ -117,6 +127,7 @@ const (
|
||||
MsgReportQuery
|
||||
MsgReportConnect
|
||||
MsgReportApps
|
||||
MsgReportLog
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -135,9 +146,9 @@ const (
|
||||
MaxRetry = 10
|
||||
RetryInterval = time.Second * 30
|
||||
PublicIPEchoTimeout = time.Second * 1
|
||||
NatTestTimeout = time.Second * 10
|
||||
NatTestTimeout = time.Second * 5
|
||||
ClientAPITimeout = time.Second * 10
|
||||
MaxDirectTry = 5
|
||||
MaxDirectTry = 3
|
||||
)
|
||||
|
||||
// NATNone has public ip
|
||||
@@ -158,8 +169,18 @@ const (
|
||||
// linkmode
|
||||
const (
|
||||
LinkModeUDPPunch = "udppunch"
|
||||
LinkModeIPv4 = "ipv4"
|
||||
LinkModeIPv6 = "ipv6"
|
||||
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) {
|
||||
@@ -187,18 +208,20 @@ func nodeNameToID(name string) uint64 {
|
||||
}
|
||||
|
||||
type PushConnectReq struct {
|
||||
From string `json:"from,omitempty"`
|
||||
FromToken uint64 `json:"fromToken,omitempty"` // deprecated
|
||||
Version string `json:"version,omitempty"`
|
||||
Token uint64 `json:"token,omitempty"` // if public totp token
|
||||
ConeNatPort int `json:"coneNatPort,omitempty"` // if isPublic, is public port
|
||||
NatType int `json:"natType,omitempty"`
|
||||
HasIPv4 int `json:"hasIPv4,omitempty"`
|
||||
IPv6 string `json:"IPv6,omitempty"`
|
||||
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
|
||||
FromIP string `json:"fromIP,omitempty"`
|
||||
ID uint64 `json:"id,omitempty"`
|
||||
AppKey uint64 `json:"appKey,omitempty"` // for underlay tcp
|
||||
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 PushConnectRsp struct {
|
||||
Error int `json:"error,omitempty"`
|
||||
@@ -223,6 +246,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"`
|
||||
}
|
||||
@@ -342,6 +366,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"`
|
||||
@@ -380,3 +416,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"`
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Time-based One-time Password
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
@@ -1,5 +1,5 @@
|
||||
// Time-based One-time Password
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"time"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -7,10 +7,11 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
reuse "github.com/openp2p-cn/go-reuseport"
|
||||
)
|
||||
|
||||
type underlayTCP struct {
|
||||
listener net.Listener
|
||||
writeMtx *sync.Mutex
|
||||
net.Conn
|
||||
}
|
||||
@@ -66,13 +67,20 @@ func (conn *underlayTCP) Close() error {
|
||||
return conn.Conn.Close()
|
||||
}
|
||||
|
||||
func listenTCP(port int, idleTimeout time.Duration) (*underlayTCP, error) {
|
||||
addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", port))
|
||||
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) // TODO: timeout
|
||||
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
|
||||
}
|
||||
defer l.Close()
|
||||
l.SetDeadline(time.Now().Add(SymmetricHandshakeAckTimeout))
|
||||
c, err := l.Accept()
|
||||
defer l.Close()
|
||||
@@ -82,11 +90,19 @@ func listenTCP(port int, idleTimeout time.Duration) (*underlayTCP, error) {
|
||||
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
|
||||
}
|
||||
|
||||
func dialTCP(host string, port int) (*underlayTCP, error) {
|
||||
c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), SymmetricHandshakeAckTimeout)
|
||||
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 {
|
||||
fmt.Printf("Dial %s:%d error:%s", host, port, err)
|
||||
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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func update() {
|
||||
func update(host string, port int) {
|
||||
gLog.Println(LvINFO, "update start")
|
||||
defer gLog.Println(LvINFO, "update end")
|
||||
c := http.Client{
|
||||
@@ -27,7 +27,7 @@ func update() {
|
||||
}
|
||||
goos := runtime.GOOS
|
||||
goarch := runtime.GOARCH
|
||||
rsp, err := c.Get(fmt.Sprintf("https://api.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(LvERROR, "update:query update list failed:", err)
|
||||
return
|
||||
@@ -1,11 +1,7 @@
|
||||
/*
|
||||
Taken from taipei-torrent
|
||||
|
||||
Just enough UPnP to be able to forward ports
|
||||
Taken from taipei-torrent And fix some bugs
|
||||
*/
|
||||
package main
|
||||
|
||||
// BUG(jae): TODO: use syscalls to get actual ourIP. http://pastebin.com/9exZG4rh
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -34,11 +30,12 @@ type NAT interface {
|
||||
}
|
||||
|
||||
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", ":0")
|
||||
conn, err := net.ListenPacket("udp4", fmt.Sprintf("%s:0", localIP))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -67,6 +64,7 @@ func Discover() (nat NAT, err error) {
|
||||
var n int
|
||||
_, _, err = socket.ReadFromUDP(answerBytes)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "UPNP discover error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,12 +96,10 @@ func Discover() (nat NAT, err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var ourIP net.IP
|
||||
ourIP, err = localIPv4()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, ourIP: ourIP.String(), urnDomain: urnDomain}
|
||||
nat = &upnpNAT{serviceURL: serviceURL, ourIP: localIP, urnDomain: urnDomain}
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -174,29 +170,14 @@ func getChildService(d *Device, serviceType string) *UPNPService {
|
||||
return nil
|
||||
}
|
||||
|
||||
func localIPv4() (net.IP, error) {
|
||||
tt, err := net.Interfaces()
|
||||
func localIPv4() string { // TODO: multi nic will wrong
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return ""
|
||||
}
|
||||
for _, t := range tt {
|
||||
aa, err := t.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, a := range aa {
|
||||
ipnet, ok := a.(*net.IPNet)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
v4 := ipnet.IP.To4()
|
||||
if v4 == nil || v4[0] == 127 { // loopback address
|
||||
continue
|
||||
}
|
||||
return v4, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New("cannot find local IP address")
|
||||
defer conn.Close()
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
return localAddr.IP.String()
|
||||
}
|
||||
|
||||
func getServiceURL(rootURL string) (url, urnDomain string, err error) {
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
@@ -15,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)
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -45,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()
|
||||
}
|
||||
}
|
||||
BIN
doc/images/p2p-debug.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
doc/images/release-debug.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
doc/images/vs2022-remote-debug-attach.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
73
doc/remote-debug-golang.md
Normal 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
@@ -0,0 +1,50 @@
|
||||
# 1. 环境
|
||||
visual studio 2022
|
||||
调试端: win10
|
||||
被调试端: win10
|
||||
## 程序编译
|
||||
一般远程调试我会选择release版本,然后将优化去掉即可,这样更接近真实版本。
|
||||

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

|
||||
|
||||
|
||||
## 没有公网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
|
||||
#显示这条日志说明成功了
|
||||
```
|
||||
|
||||

|
||||
|
||||
可以顺利远程调试
|
||||
```
|
||||
16
errorcode.go
@@ -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")
|
||||
)
|
||||
15
go.mod
@@ -4,24 +4,25 @@ 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.27.0
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
|
||||
github.com/openp2p-cn/go-reuseport v0.3.2
|
||||
github.com/openp2p-cn/service v1.0.0
|
||||
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-20200622213623-75b288015ac9 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
|
||||
golang.org/x/tools v0.1.1 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // 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
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
139
nat.go
@@ -1,139 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
func natTest(serverHost string, serverPort int, localPort int, echoPort int) (publicIP string, hasPublicIP int, hasUPNPorNATPMP int, publicPort int, err error) {
|
||||
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
return "", 0, 0, 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||
if err != nil {
|
||||
return "", 0, 0, 0, err
|
||||
}
|
||||
|
||||
// The connection can write data to the desired address.
|
||||
msg, err := newMessage(MsgNATDetect, 0, &NatDetectReq{SrcPort: localPort, EchoPort: echoPort})
|
||||
_, err = conn.WriteTo(msg, dst)
|
||||
if err != nil {
|
||||
return "", 0, 0, 0, err
|
||||
}
|
||||
deadline := time.Now().Add(NatTestTimeout)
|
||||
err = conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return "", 0, 0, 0, err
|
||||
}
|
||||
buffer := make([]byte, 1024)
|
||||
nRead, _, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "NAT detect error:", err)
|
||||
return "", 0, 0, 0, err
|
||||
}
|
||||
natRsp := NatDetectRsp{}
|
||||
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
||||
hasPublicIP = 0
|
||||
hasUPNPorNATPMP = 0
|
||||
// testing for public ip
|
||||
if echoPort != 0 {
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 1 {
|
||||
// test upnp or nat-pmp
|
||||
nat, err := Discover()
|
||||
if err != 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", 60)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||
break
|
||||
}
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "public ip test start %s:%d", natRsp.IP, echoPort)
|
||||
conn, err := net.ListenUDP("udp", nil)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer conn.Close()
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", natRsp.IP, echoPort))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
conn.WriteTo([]byte("echo"), dst)
|
||||
buf := make([]byte, 1600)
|
||||
|
||||
// wait for echo testing
|
||||
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
||||
_, _, err = conn.ReadFromUDP(buf)
|
||||
if err == nil {
|
||||
if i == 1 {
|
||||
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
||||
hasUPNPorNATPMP = 1
|
||||
} else {
|
||||
gLog.Println(LvDEBUG, "public ip:YES")
|
||||
hasPublicIP = 1
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return natRsp.IP, hasPublicIP, hasUPNPorNATPMP, 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()%10000 + 50000)
|
||||
echoPort := int(rand.Uint32()%10000 + 50000)
|
||||
go echo(echoPort)
|
||||
ip1, hasIPv4, hasUPNPorNATPMP, port1, err := natTest(host, udp1, localPort, echoPort)
|
||||
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port1)
|
||||
if err != nil {
|
||||
return "", 0, hasIPv4, hasUPNPorNATPMP, err
|
||||
}
|
||||
// if hasPublicIP == 1 || hasUPNPorNATPMP == 1 {
|
||||
// return ip1, NATNone, hasUPNPorNATPMP, nil
|
||||
// }
|
||||
_, _, _, port2, err := natTest(host, udp2, localPort, 0) // 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 echo(echoPort int) {
|
||||
conn, 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)
|
||||
defer conn.Close()
|
||||
// wait 5s for echo testing
|
||||
conn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
||||
n, addr, err := conn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn.WriteToUDP(buf[0:n], addr)
|
||||
}
|
||||
63
openp2p.go
@@ -1,63 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
binDir := filepath.Dir(os.Args[0])
|
||||
os.Chdir(binDir) // for system service
|
||||
gLog = NewLogger(binDir, "openp2p", 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(filepath.Dir(os.Args[0]), "openp2p", 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()
|
||||
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
|
||||
}
|
||||
17
p2pconn.go
@@ -1,17 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type p2pConn interface {
|
||||
ReadMessage() (*openP2PHeader, []byte, error)
|
||||
WriteBytes(uint16, uint16, []byte) error
|
||||
WriteBuffer([]byte) error
|
||||
WriteMessage(uint16, uint16, interface{}) error
|
||||
Close() error
|
||||
Accept() error
|
||||
CloseListener()
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||