Compare commits

...
26 Commits
Author SHA1 Message Date
e9e4cd6b3c add(ci): support core build & signing by cert (#166)
* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* add(ci): github action & signing

* chore: update signature CI

* fix(ci): sign windows binaries with direct CERTUM SHA1

* fix(ci): replace cert-store wait with simplysign readiness check

* feat(ci): refactor certum signing flow for release

* fix(ci): fail certum auth when session not ready

* fix(ci): retry certum auth and fail critical steps

* fix(ci): add cert preflight checks before signtool

* fix(install): update SimplySign Desktop MSI download link to version 9.4.3.90

* fix(ci): mask totp and certificate identifiers in scripts

* fix(ci): remove certificate inventory logs from release

* add(ci): CHANGE beta release

* feat(ci): resolve certum msi url dynamically from download page

* add(ci): github action & signing

* add(ci): github action & signing

* revert(ci): app/app/build.gradle for build

* add(ci): github action & signing

* add(ci): github action & signing

* Update default URLs for binary file downloads

---------

Co-authored-by: Suyunmeng <[email protected]>
Co-authored-by: OpenP2P <[email protected]>
2026-06-03 15:39:14 +08:00
TenderIronh 07836fc162 3.25.11 2026-05-15 17:53:49 +08:00
TenderIronh 12393f00c5 v3.25.8 2026-04-01 15:33:30 +08:00
TenderIronh ef4bc1e1e3 go 1.25 2025-12-10 16:50:26 +08:00
TenderIronh a1621bcfdd rm some config save 2025-12-10 16:49:01 +08:00
TenderIronh 47220fe38b go1.25 2025-12-10 16:47:16 +08:00
TenderIronh d3e8ee2a32 websocket readmessage hang 2025-12-10 16:45:34 +08:00
TenderIronh 8e303e93f8 refactor 2025-11-24 10:53:02 +08:00
TenderIronh 471aa5e6ea intranet support udp 2025-11-18 17:07:24 +08:00
TenderIronh a4c6668760 fix system service bug and docker run path and nat detect bug 2025-11-18 16:35:30 +08:00
TenderIronh dfaff2c327 fix system service bug and docker run path and nat detect bug 2025-11-18 16:34:42 +08:00
TenderIronh 57fe6986b0 refactor update and fix token loss bug 2025-11-17 10:00:50 +08:00
TenderIronh 6639f40d70 install bug and new log api 2025-11-14 17:19:50 +08:00
TenderIronh d827fd108d add log 2025-11-13 17:57:49 +08:00
coutps 4daeeaab1a update README.md and README-ZH.md (#152) 2025-10-11 16:18:29 +08:00
TenderIronh 2275620060 dartnode 2025-06-10 15:12:53 +08:00
TenderIronh 29faf4a950 nil pointer 2025-02-04 23:10:47 +08:00
TenderIronh 080e6af779 fix public ip detect bug 2024-12-02 21:10:15 +08:00
TenderIronh 77bfa45172 portmap loss & android ipv6 failed & public ip detect 2024-11-21 10:31:07 +08:00
TenderIronh 3616768682 rename 2024-11-21 10:29:06 +08:00
TenderIronh f015b828fc specified gomobile version 2024-10-20 21:33:20 +08:00
TenderIronh df1e16e708 3.21.8 2024-10-20 11:33:07 +08:00
W192547975 c68094cc12 CertPool Fix (#96)
Remove caCertPool errCert “else” in p2pnetwork.go
2024-08-02 14:23:47 +08:00
CAESIUS_TIM a0df0b1e95 [doc ]no bare urls (#80) 2024-08-02 14:22:31 +08:00
TenderIronh 9c3d557f5d LF 2024-07-26 22:27:24 +08:00
TenderIronh 2dea3a718d check remote service 2024-07-26 22:07:48 +08:00
77 changed files with 9419 additions and 4989 deletions
@@ -0,0 +1,252 @@
param(
[switch]$DebugMode = $false,
[switch]$VerifyOnly = $false
)
# SimplySign Desktop Registry Configuration Script
# Pre-configures optimal registry settings for automated login dialog display
Write-Host "=== SimplySign Desktop Registry Configuration ==="
if ($DebugMode) {
Write-Host "Debug mode enabled - verbose logging active"
}
# Registry path for SimplySign Desktop settings
$RegistryPath = "HKCU:\Software\Certum\SimplySign"
# Optimal configuration values for automation
$OptimalSettings = @{
"ShowLoginDialogOnStart" = 1
"ShowLoginDialogOnAppRequest" = 1
"RememberLastUserName" = 1
"Autostart" = 0
"UnregisterCertificatesOnDisconnect" = 0
"RememberPINinCSP" = 1
"ForgetPINinCSPonDisconnect" = 1
"LangID" = 9
}
# Function to check if registry path exists
function Test-RegistryPath {
param([string]$Path)
try {
$null = Get-Item -Path $Path -ErrorAction Stop
return $true
} catch {
return $false
}
}
# Function to get current registry value
function Get-RegistryValue {
param(
[string]$Path,
[string]$Name
)
try {
$value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop
return $value.$Name
} catch {
return $null
}
}
# Function to set registry value safely
function Set-RegistryValue {
param(
[string]$Path,
[string]$Name,
[int]$Value
)
try {
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type DWord -ErrorAction Stop
if ($DebugMode) {
Write-Host " Set $Name = $Value"
}
return $true
} catch {
Write-Host " ERROR: Failed to set $Name = $Value - $($_.Exception.Message)"
return $false
}
}
# Function to display current settings
function Show-CurrentSettings {
Write-Host "Current SimplySign Desktop registry settings:"
Write-Host "============================================="
if (-not (Test-RegistryPath $RegistryPath)) {
Write-Host "Registry path does not exist: $RegistryPath"
return
}
foreach ($setting in $OptimalSettings.Keys) {
$currentValue = Get-RegistryValue -Path $RegistryPath -Name $setting
if ($null -eq $currentValue) {
Write-Host " $setting : NOT SET"
} else {
Write-Host " $setting : $currentValue"
}
}
Write-Host ""
}
# Function to create registry structure
function Initialize-RegistryStructure {
Write-Host "Initializing registry structure..."
# Create parent keys if they don't exist
$ParentPaths = @(
"HKCU:\Software\Certum",
$RegistryPath
)
$allCreated = $true
foreach ($path in $ParentPaths) {
if (-not (Test-RegistryPath $path)) {
try {
New-Item -Path $path -Force -ErrorAction Stop | Out-Null
if ($DebugMode) {
Write-Host " Created registry path: $path"
}
} catch {
Write-Host " ERROR: Failed to create registry path: $path - $($_.Exception.Message)"
$allCreated = $false
}
} else {
if ($DebugMode) {
Write-Host " Registry path exists: $path"
}
}
}
return $allCreated
}
# Function to apply optimal configuration
function Set-OptimalConfiguration {
Write-Host "Applying optimal configuration for automation..."
$successCount = 0
$totalSettings = $OptimalSettings.Count
foreach ($setting in $OptimalSettings.Keys) {
$value = $OptimalSettings[$setting]
if (Set-RegistryValue -Path $RegistryPath -Name $setting -Value $value) {
$successCount++
}
}
Write-Host "Applied $successCount of $totalSettings settings successfully"
return ($successCount -eq $totalSettings)
}
# Function to verify configuration
function Test-Configuration {
Write-Host "Verifying configuration..."
$verificationResults = @{}
$allCorrect = $true
foreach ($setting in $OptimalSettings.Keys) {
$expectedValue = $OptimalSettings[$setting]
$actualValue = Get-RegistryValue -Path $RegistryPath -Name $setting
$isCorrect = ($actualValue -eq $expectedValue)
$verificationResults[$setting] = @{
Expected = $expectedValue
Actual = $actualValue
Correct = $isCorrect
}
if (-not $isCorrect) {
$allCorrect = $false
}
if ($DebugMode -or -not $isCorrect) {
$status = if ($isCorrect) { "OK" } else { "MISMATCH" }
Write-Host " $setting : Expected=$expectedValue, Actual=$actualValue [$status]"
}
}
return $verificationResults, $allCorrect
}
# Main execution
try {
Write-Host "Starting registry configuration process..."
Write-Host ""
# Show current state
Write-Host "BEFORE CONFIGURATION:"
Show-CurrentSettings
if ($VerifyOnly) {
Write-Host "Verification-only mode - no changes will be made"
$verificationResults, $allCorrect = Test-Configuration
if ($allCorrect) {
Write-Host "SUCCESS: All settings are correctly configured"
exit 0
} else {
Write-Host "CONFIGURATION NEEDED: Some settings require adjustment"
exit 1
}
}
# Initialize registry structure
if (-not (Initialize-RegistryStructure)) {
Write-Host "FATAL ERROR: Failed to initialize registry structure"
exit 1
}
# Apply optimal configuration
if (-not (Set-OptimalConfiguration)) {
Write-Host "ERROR: Failed to apply complete configuration"
exit 1
}
Write-Host ""
Write-Host "AFTER CONFIGURATION:"
Show-CurrentSettings
# Verify the configuration was applied correctly
$verificationResults, $allCorrect = Test-Configuration
if ($allCorrect) {
Write-Host "SUCCESS: Registry configuration completed successfully"
Write-Host ""
Write-Host "Key automation settings enabled:"
Write-Host " ShowLoginDialogOnStart = 1 (Login dialog will appear automatically)"
Write-Host " ShowLoginDialogOnAppRequest = 1 (Dialog appears when apps request access)"
Write-Host " RememberLastUserName = 1 (Username persistence for efficiency)"
Write-Host ""
Write-Host "Next steps:"
Write-Host "1. Launch SimplySign Desktop"
Write-Host "2. Login dialog should appear automatically"
Write-Host "3. Complete authentication process"
# Create a status file for the workflow to check
"REGISTRY_CONFIGURATION_SUCCESS" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
exit 0
} else {
Write-Host "ERROR: Configuration verification failed"
Write-Host "Some settings were not applied correctly"
"REGISTRY_CONFIGURATION_PARTIAL" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
exit 1
}
} catch {
Write-Host "FATAL ERROR: Registry configuration failed - $($_.Exception.Message)"
"REGISTRY_CONFIGURATION_FAILED" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
exit 1
}
@@ -0,0 +1,390 @@
# Connect-SimplySign-Enhanced.ps1
# Registry-Enhanced TOTP Authentication for SimplySign Desktop
# Uses registry pre-configuration + TOTP credential injection approach
param(
[string]$OtpUri = $env:CERTUM_OTP_URI,
[string]$UserId = $env:CERTUM_USERNAME,
[string]$ExePath = $env:CERTUM_EXE_PATH,
[string]$ExpectedCertificateSHA1 = $env:CERTUM_CERTIFICATE_SHA1
)
function Normalize-Sha1 {
param([string]$InputSha1)
if (-not $InputSha1) {
return $null
}
return ($InputSha1 -replace "[^a-fA-F0-9]", "").ToUpperInvariant()
}
function Find-CertificateByThumbprint {
param([string]$Thumbprint)
if (-not $Thumbprint) {
return @()
}
$all = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue
# 对证书库中的 Thumbprint 同样做规范化(去除不可见字符、统一大写),避免 BOM 或格式差异导致匹配失败
return @($all | Where-Object {
$normalizedStoreThumbprint = ($_.Thumbprint -replace "[^a-fA-F0-9]", "").ToUpperInvariant()
$normalizedStoreThumbprint -eq $Thumbprint
})
}
# Validate required parameters
if (-not $OtpUri) {
Write-Host "ERROR: CERTUM_OTP_URI environment variable not provided"
exit 1
}
if (-not $UserId) {
Write-Host "ERROR: CERTUM_USERNAME environment variable not provided"
exit 1
}
if (-not $ExePath) {
$ExePath = "C:\Program Files\Certum\SimplySign Desktop\SimplySignDesktop.exe"
}
Write-Host "=== REGISTRY-ENHANCED TOTP AUTHENTICATION ==="
Write-Host "Using registry pre-configuration + credential injection"
Write-Host "OTP URI provided (length: $($OtpUri.Length))"
Write-Host "User ID: $UserId"
Write-Host "Executable: $ExePath"
Write-Host ""
# Verify SimplySign Desktop exists
if (-not (Test-Path $ExePath)) {
Write-Host "ERROR: SimplySign Desktop not found at: $ExePath"
exit 1
}
# Parse the otpauth:// URI
$uri = [Uri]$OtpUri
# Parse query parameters (compatible with both PowerShell 5.1 and 7+)
try {
$q = [System.Web.HttpUtility]::ParseQueryString($uri.Query)
} catch {
$q = @{}
foreach ($part in $uri.Query.TrimStart('?') -split '&') {
$kv = $part -split '=', 2
if ($kv.Count -eq 2) {
$q[$kv[0]] = [Uri]::UnescapeDataString($kv[1])
}
}
}
$Base32 = $q['secret']
$Digits = if ($q['digits']) { [int]$q['digits'] } else { 6 }
$Period = if ($q['period']) { [int]$q['period'] } else { 30 }
$Algorithm = if ($q['algorithm']) { $q['algorithm'].ToUpper() } else { 'SHA256' }
# Validate supported algorithms
$SupportedAlgorithms = @('SHA1', 'SHA256', 'SHA512')
if ($Algorithm -notin $SupportedAlgorithms) {
Write-Host "ERROR: Unsupported algorithm: $Algorithm. Supported: $($SupportedAlgorithms -join ', ')"
exit 1
}
# TOTP Generator (inline C# implementation)
Add-Type -Language CSharp @"
using System;
using System.Security.Cryptography;
public static class Totp
{
private const string B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
private static byte[] Base32Decode(string s)
{
s = s.TrimEnd('=').ToUpperInvariant();
int byteCount = s.Length * 5 / 8;
byte[] bytes = new byte[byteCount];
int bitBuffer = 0, bitsLeft = 0, idx = 0;
foreach (char c in s)
{
int val = B32.IndexOf(c);
if (val < 0) throw new ArgumentException("Invalid Base32 char: " + c);
bitBuffer = (bitBuffer << 5) | val;
bitsLeft += 5;
if (bitsLeft >= 8)
{
bytes[idx++] = (byte)(bitBuffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
}
return bytes;
}
private static HMAC GetHmacAlgorithm(string algorithm, byte[] key)
{
switch (algorithm.ToUpper())
{
case "SHA1":
return new HMACSHA1(key);
case "SHA256":
return new HMACSHA256(key);
case "SHA512":
return new HMACSHA512(key);
default:
throw new ArgumentException("Unsupported algorithm: " + algorithm);
}
}
public static string Now(string secret, int digits, int period, string algorithm = "SHA256")
{
byte[] key = Base32Decode(secret);
long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / period;
byte[] cnt = BitConverter.GetBytes(counter);
if (BitConverter.IsLittleEndian) Array.Reverse(cnt);
byte[] hash;
using (var hmac = GetHmacAlgorithm(algorithm, key))
{
hash = hmac.ComputeHash(cnt);
}
int offset = hash[hash.Length - 1] & 0x0F;
int binary =
((hash[offset] & 0x7F) << 24) |
((hash[offset + 1] & 0xFF) << 16) |
((hash[offset + 2] & 0xFF) << 8) |
(hash[offset + 3] & 0xFF);
int otp = binary % (int)Math.Pow(10, digits);
return otp.ToString(new string('0', digits));
}
}
"@
function Get-TotpCode {
param([string]$Secret, [int]$Digits = 6, [int]$Period = 30, [string]$Algorithm = 'SHA256')
[Totp]::Now($Secret, $Digits, $Period, $Algorithm)
}
# Add Win32 API for force foreground window
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class Win32 {
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);
public const int SW_RESTORE = 9;
public const int SW_SHOW = 5;
}
"@
# 预先验证证书 SHA1
$normalizedExpectedSha1 = Normalize-Sha1 -InputSha1 $ExpectedCertificateSHA1
if ($normalizedExpectedSha1) {
if ($normalizedExpectedSha1.Length -ne 40) {
Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 is invalid after normalization"
Write-Host "Raw length: $($ExpectedCertificateSHA1.Length), normalized length: $($normalizedExpectedSha1.Length)"
exit 1
}
}
# === 认证重试循环(最多 10 次) ===
$maxAttempts = 10
$authSuccess = $false
for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
Write-Host ""
Write-Host "=========================================="
Write-Host "=== AUTHENTICATION ATTEMPT $attempt / $maxAttempts ==="
Write-Host "=========================================="
Write-Host ""
# 每次重试都重新生成 TOTP(确保验证码有效)
$otp = Get-TotpCode -Secret $Base32 -Digits $Digits -Period $Period -Algorithm $Algorithm
Write-Host "Generated TOTP code successfully (masked) using $Algorithm algorithm"
Write-Host ""
# 终止之前可能残留的 SimplySign Desktop 进程
Get-Process -Name "SimplySignDesktop" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
# 启动 SimplySign Desktop
Write-Host "Launching SimplySign Desktop..."
Write-Host "Registry pre-configuration should auto-open login dialog"
$proc = Start-Process -FilePath $ExePath -PassThru
Write-Host "Process started with ID: $($proc.Id)"
Write-Host ""
# 等待应用初始化
Write-Host "Waiting for SimplySign Desktop to initialize..."
Start-Sleep -Seconds 10
# Allow our process to set foreground window
[Win32]::AllowSetForegroundWindow($proc.Id) | Out-Null
# Create WScript.Shell for window interaction
$wshell = New-Object -ComObject WScript.Shell
# 尝试聚焦 SimplySign Desktop 窗口
Write-Host "Attempting to focus SimplySign Desktop window..."
$focused = $false
# Method 1: Use Win32 API to find and activate window
$mainWindowHandle = $proc.MainWindowHandle
if ($mainWindowHandle -ne $null -and $mainWindowHandle -ne [IntPtr]::Zero) {
[Win32]::ShowWindow($mainWindowHandle, [Win32]::SW_RESTORE) | Out-Null
[Win32]::SetForegroundWindow($mainWindowHandle) | Out-Null
$focused = $true
Write-Host "Focused via MainWindowHandle"
} else {
Write-Host "MainWindowHandle not available yet, will try other methods..."
}
# Method 2: Find window by title
if (-not $focused) {
$hwnd = [Win32]::FindWindow($null, "SimplySign Desktop")
if ($hwnd -ne [IntPtr]::Zero) {
[Win32]::ShowWindow($hwnd, [Win32]::SW_RESTORE) | Out-Null
[Win32]::SetForegroundWindow($hwnd) | Out-Null
$focused = $true
Write-Host "Focused via FindWindow"
}
}
# Method 3: AppActivate with extended retries
for ($i = 0; (-not $focused) -and ($i -lt 20); $i++) {
Start-Sleep -Milliseconds 1000
# Refresh process handle
$proc.Refresh()
$mainWindowHandle = $proc.MainWindowHandle
if ($mainWindowHandle -ne [IntPtr]::Zero) {
[Win32]::ShowWindow($mainWindowHandle, [Win32]::SW_RESTORE) | Out-Null
[Win32]::SetForegroundWindow($mainWindowHandle) | Out-Null
$focused = $true
Write-Host "Focused via MainWindowHandle (attempt $($i + 1))"
break
}
$focused = $wshell.AppActivate($proc.Id)
if (-not $focused) {
$focused = $wshell.AppActivate('SimplySign Desktop')
}
if (-not $focused) {
$focused = $wshell.AppActivate('SimplySign')
}
Write-Host "Focus attempt $($i + 1): $focused"
}
if (-not $focused) {
Write-Host "WARNING: Could not bring SimplySign Desktop to foreground via window handle"
Write-Host "SimplySign Desktop may be running as a background/tray process - proceeding with credential injection anyway"
}
Write-Host ""
# Small delay to ensure window is ready for input
Start-Sleep -Milliseconds 400
# 注入凭据: Username + TAB + TOTP + ENTER
Write-Host "Injecting credentials into login dialog..."
Write-Host "Sending: Username -> TAB -> TOTP -> ENTER"
$wshell.SendKeys($UserId)
Start-Sleep -Milliseconds 200
$wshell.SendKeys("{TAB}")
Start-Sleep -Milliseconds 200
$wshell.SendKeys($otp)
Start-Sleep -Milliseconds 200
$wshell.SendKeys("{ENTER}")
Write-Host "Credentials injected successfully"
Write-Host ""
# 等待认证处理
Write-Host "Waiting for authentication to complete..."
Start-Sleep -Seconds 5
# 验证证书是否可用
if ($normalizedExpectedSha1) {
Write-Host "Validating certificate availability for expected signing certificate"
$ready = $false
$withPrivateKey = $false
for ($i = 0; $i -lt 15; $i++) {
$matched = Find-CertificateByThumbprint -Thumbprint $normalizedExpectedSha1
if ($matched.Count -gt 0) {
$ready = $true
$withPrivateKey = ($matched | Where-Object { $_.HasPrivateKey }).Count -gt 0
if ($withPrivateKey) {
Write-Host "Certificate is available and has private key"
break
}
}
Start-Sleep -Seconds 2
}
if ($ready -and $withPrivateKey) {
$authSuccess = $true
Write-Host "SUCCESS: Authentication verified - certificate with private key is available"
break
}
# 认证失败,准备重试
if (-not $ready) {
Write-Host "WARNING: Target certificate was not found after attempt $attempt"
} elseif (-not $withPrivateKey) {
Write-Host "WARNING: Target certificate found but no private key available after attempt $attempt"
}
} else {
# 没有指定证书 SHA1,无法验证,假设成功
$authSuccess = $true
break
}
# 如果不是最后一次尝试,等待后重试
if ($attempt -lt $maxAttempts) {
Write-Host "Authentication attempt $attempt failed, will retry in 5 seconds..."
# 终止当前 SimplySign Desktop 进程
$stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue
if ($stillRunning) {
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 5
}
}
if (-not $authSuccess) {
Write-Host ""
Write-Host "ERROR: Authentication failed after $maxAttempts attempts"
Write-Host "All TOTP injection attempts were unsuccessful"
exit 1
}
# Verify SimplySign Desktop is still running
$stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue
if ($stillRunning) {
Write-Host "SUCCESS: SimplySign Desktop is running"
Write-Host "Authentication should be complete"
Write-Host "Cloud certificate should now be available"
} else {
Write-Host "WARNING: SimplySign Desktop process has exited"
Write-Host "This may indicate authentication failure"
}
Write-Host ""
Write-Host "=== TOTP AUTHENTICATION COMPLETE ==="
Write-Host "Registry pre-configuration + credential injection finished"
+138
View File
@@ -0,0 +1,138 @@
#!/bin/bash
# Install SimplySign Desktop - Clean MSI Installation
set -euo pipefail
echo "=== INSTALLING SIMPLYSIGN DESKTOP ==="
echo "Using proven installation method from successful testing..."
# Download SimplySign Desktop MSI
CERTUM_INSTALLER="SimplySignDesktop.msi"
CERTUM_DOWNLOAD_PAGE="https://pomoc.certum.pl/pl/oprogramowanie/procertum-smartsign/"
FALLBACK_MSI_URL="https://files.certum.eu/software/SimplySignDesktop/Windows/9.4.3.90/SimplySignDesktop-9.4.3.90-64-bit-pl.msi"
echo "Downloading SimplySign Desktop MSI..."
# Resolve the latest 64-bit MSI URL from Certum software page to avoid hardcoded version expiry.
PAGE_CONTENT="$(curl -L "$CERTUM_DOWNLOAD_PAGE" --fail --max-time 60 || true)"
MSI_CANDIDATES="$(printf '%s' "$PAGE_CONTENT" | grep -oE 'https://(www\.)?files\.certum\.eu/software/SimplySignDesktop/Windows/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/SimplySignDesktop-[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+-64-bit[^"[:space:]]*\.msi' | sort -u || true)"
DOWNLOAD_URL=""
if [ -n "$MSI_CANDIDATES" ]; then
LATEST_VERSION="$(printf '%s\n' "$MSI_CANDIDATES" | sed -E 's#^.*/Windows/([0-9.]+)/.*$#\1#' | sort -V | tail -n1)"
LATEST_VERSION_URLS="$(printf '%s\n' "$MSI_CANDIDATES" | grep "/Windows/${LATEST_VERSION}/" || true)"
DOWNLOAD_URL="$(printf '%s\n' "$LATEST_VERSION_URLS" | grep -- '-64-bit-pl\.msi$' | head -n1 || true)"
if [ -z "$DOWNLOAD_URL" ]; then
DOWNLOAD_URL="$(printf '%s\n' "$LATEST_VERSION_URLS" | head -n1)"
fi
fi
if [ -z "$DOWNLOAD_URL" ]; then
echo "WARNING: Could not resolve latest MSI URL from Certum page, using fallback URL"
DOWNLOAD_URL="$FALLBACK_MSI_URL"
fi
RESOLVED_VERSION="$(printf '%s' "$DOWNLOAD_URL" | sed -E 's#^.*/Windows/([0-9.]+)/.*$#\1#')"
echo "Resolved SimplySign Desktop MSI version: $RESOLVED_VERSION"
if curl -L "$DOWNLOAD_URL" -o "$CERTUM_INSTALLER" --fail --max-time 60; then
echo "✅ Downloaded SimplySign Desktop MSI ($(ls -lh "$CERTUM_INSTALLER" | awk '{print $5}'))"
else
echo "❌ Failed to download SimplySign Desktop"
exit 1
fi
# Install with proven method (matching successful test)
echo "Installing SimplySign Desktop..."
echo "Full command: msiexec /i \"$CERTUM_INSTALLER\" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress"
# Check for administrative privileges (like the successful test)
ADMIN_RIGHTS=false
if powershell -Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)" 2>/dev/null; then
echo "✅ Running with administrative privileges"
ADMIN_RIGHTS=true
else
echo "⚠️ No explicit administrative privileges detected"
fi
# Use the exact method that worked: PowerShell with admin privileges
if [ "$ADMIN_RIGHTS" = true ]; then
echo "Running MSI installation with administrator privileges..."
powershell -Command "Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', '\"$CERTUM_INSTALLER\"', '/quiet', '/norestart', '/l*v', 'install.log', 'ALLUSERS=1', 'REBOOT=ReallySuppress' -Wait -NoNewWindow -PassThru" &
INSTALL_PID=$!
else
echo "Running MSI installation without explicit admin elevation..."
timeout 300 msiexec /i "$CERTUM_INSTALLER" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress &
INSTALL_PID=$!
fi
# Monitor with the same logic as successful test
echo "Monitoring installation progress..."
INSTALL_START_TIME=$(date +%s)
sleep 10
# Check if msiexec process is actually running (like successful test)
if kill -0 $INSTALL_PID 2>/dev/null; then
echo "MSI installation process is running (PID: $INSTALL_PID)"
# Monitor for up to 3 minutes with status updates
for i in {1..18}; do
sleep 10
CURRENT_TIME=$(date +%s)
ELAPSED=$((CURRENT_TIME - INSTALL_START_TIME))
if kill -0 $INSTALL_PID 2>/dev/null; then
echo "Installation still running after ${ELAPSED} seconds..."
# Check log file growth
if [ -f "install.log" ]; then
LOG_SIZE=$(stat -c%s "install.log" 2>/dev/null || stat -f%z "install.log" 2>/dev/null || echo 0)
echo " Log file size: $LOG_SIZE bytes"
fi
else
echo "MSI installation completed after ${ELAPSED} seconds"
break
fi
done
# Final wait if still running
if kill -0 $INSTALL_PID 2>/dev/null; then
echo "Installation taking longer, waiting for completion..."
wait $INSTALL_PID 2>/dev/null || echo "Installation process ended"
fi
else
echo "MSI installation process ended quickly"
fi
# Quick success check using proven patterns
INSTALLATION_SUCCESSFUL=false
if [ -f "install.log" ]; then
if grep -qi "Installation.*operation.*completed.*successfully\|Installation.*success.*or.*error.*status.*0\|MainEngineThread.*is.*returning.*0\|Windows.*Installer.*installed.*the.*product" install.log 2>/dev/null; then
echo "✅ Installation successful (confirmed by log patterns)"
INSTALLATION_SUCCESSFUL=true
fi
fi
# Verify installation directory
INSTALL_PATH="/c/Program Files/Certum/SimplySign Desktop"
if [ -d "$INSTALL_PATH" ]; then
echo "✅ SimplySign Desktop installed successfully"
echo "✅ Virtual card emulation now active for code signing"
INSTALLATION_SUCCESSFUL=true
# Set output for GitHub Actions
if [ -n "${GITHUB_OUTPUT:-}" ]; then
echo "SIMPLYSIGN_PATH=$INSTALL_PATH" >> "$GITHUB_OUTPUT"
fi
fi
if [ "$INSTALLATION_SUCCESSFUL" = false ]; then
echo "❌ Installation verification failed"
echo "Last 10 lines of install log:"
tail -10 install.log 2>/dev/null || echo "No install log available"
exit 1
fi
echo "🎉 SimplySign Desktop installation completed successfully!"
+182
View File
@@ -0,0 +1,182 @@
# Sign-Windows.ps1
# Signs Windows EasyTier executables and libraries with a Certum SimplySign cloud certificate.
param(
[string]$TargetDirectory = "sign_binaries",
[string]$CertificateSHA1 = $env:CERTUM_CERTIFICATE_SHA1,
[string]$TimestampServer = "http://time.certum.pl"
)
function Get-LatestSignToolPath {
$windowsKitsBin = Join-Path ${env:ProgramFiles(x86)} "Windows Kits\10\bin"
if (Test-Path $windowsKitsBin) {
$candidate = (
Get-ChildItem -Path $windowsKitsBin -Recurse -File -Filter "signtool.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "\\x64\\signtool\.exe$" } |
ForEach-Object {
$version = [version]"0.0"
if ($_.FullName -match "\\bin\\([^\\]+)\\x64\\signtool\.exe$") {
try {
$version = [version]$matches[1]
} catch {
$version = [version]"0.0"
}
}
[PSCustomObject]@{
Path = $_.FullName
Version = $version
}
} |
Sort-Object -Property Version -Descending |
Select-Object -First 1
)
if ($candidate) {
return $candidate.Path
}
}
$cmd = Get-Command "signtool.exe" -ErrorAction SilentlyContinue
if ($cmd) {
return $cmd.Source
}
return $null
}
function Find-TargetCertificate {
param([string]$Thumbprint)
$all = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue
# 对证书库中的 Thumbprint 同样做规范化(去除不可见字符、统一大写),避免 BOM 或格式差异导致匹配失败
return @($all | Where-Object {
$normalizedStoreThumprint = ($_.Thumbprint -replace "[^a-fA-F0-9]", "").ToUpperInvariant()
$normalizedStoreThumprint -eq $Thumbprint
})
}
function Show-PrivateKeyCertificateHints {
$candidates = Get-ChildItem -Path "Cert:\CurrentUser\My", "Cert:\LocalMachine\My" -ErrorAction SilentlyContinue |
Where-Object { $_.HasPrivateKey }
if (($null -eq $candidates) -or ($candidates.Count -eq 0)) {
Write-Host "No certificates with private keys were found in Personal stores"
return
}
Write-Host "Certificates with private keys are present in Personal stores, but details are hidden for security"
}
Write-Host "=== WINDOWS BINARY SIGNING (CERTUM SIMPLYSIGN) ==="
Write-Host "Target directory: $TargetDirectory"
if (-not (Test-Path $TargetDirectory)) {
Write-Host "ERROR: Target directory not found: $TargetDirectory"
exit 1
}
if (-not $CertificateSHA1) {
Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 environment variable not provided"
exit 1
}
$normalizedSha1 = ($CertificateSHA1 -replace "[^a-fA-F0-9]", "").ToUpperInvariant()
if ($normalizedSha1.Length -ne 40) {
Write-Host "ERROR: CERTUM_CERTIFICATE_SHA1 is invalid after normalization"
Write-Host "Raw length: $($CertificateSHA1.Length), normalized length: $($normalizedSha1.Length)"
exit 1
}
Write-Host "Expected signing certificate thumbprint has been received (masked)"
$targetCerts = Find-TargetCertificate -Thumbprint $normalizedSha1
if (($null -eq $targetCerts) -or ($targetCerts.Count -eq 0)) {
Write-Host "ERROR: Target certificate not found in Cert:\CurrentUser\My or Cert:\LocalMachine\My"
Write-Host "Authentication likely failed or CERTUM_CERTIFICATE_SHA1 is incorrect"
Show-PrivateKeyCertificateHints
exit 1
}
$targetWithPrivateKey = @($targetCerts | Where-Object { $_.HasPrivateKey })
if (($null -eq $targetWithPrivateKey) -or ($targetWithPrivateKey.Count -eq 0)) {
Write-Host "ERROR: Target certificate exists but has no available private key"
Write-Host "Signing cannot continue without private key access"
Show-PrivateKeyCertificateHints
exit 1
}
Write-Host "Locating signtool..."
$signTool = Get-LatestSignToolPath
if (-not $signTool) {
Write-Host "ERROR: signtool.exe not found"
exit 1
}
Write-Host "Found signtool: $signTool"
Write-Host "Scanning for Windows binaries to sign (.exe, .dll)..."
$filesToSign = Get-ChildItem -Path $TargetDirectory -Recurse -File |
Where-Object { $_.Extension -iin @(".exe", ".dll") }
if (($null -eq $filesToSign) -or ($filesToSign.Count -eq 0)) {
Write-Host "WARNING: No signable files (.exe, .dll) found to sign"
exit 0
}
Write-Host "Found $($filesToSign.Count) files to sign"
$signedCount = 0
$failedCount = 0
foreach ($file in $filesToSign) {
Write-Host "=== Signing: $($file.Name) ==="
Write-Host "Path: $($file.FullName)"
$attempts = @(
@{ Name = "SHA1 thumbprint + /td SHA256"; Args = @("sign", "/sha1", $normalizedSha1, "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) },
@{ Name = "SHA1 thumbprint in CurrentUser\\My"; Args = @("sign", "/sha1", $normalizedSha1, "/s", "My", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) },
@{ Name = "SHA1 thumbprint in LocalMachine\\My"; Args = @("sign", "/sha1", $normalizedSha1, "/sm", "/s", "My", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) },
@{ Name = "Auto-select cert (fallback)"; Args = @("sign", "/a", "/tr", $TimestampServer, "/td", "SHA256", "/fd", "SHA256", "/v", $file.FullName) }
)
$signed = $false
foreach ($attempt in $attempts) {
Write-Host "Attempt: $($attempt.Name)"
$signOutput = & $signTool @($attempt.Args) 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "SUCCESS: $($attempt.Name)"
$signed = $true
break
}
Write-Host "FAILED: $($attempt.Name)"
Write-Host "signtool returned a non-zero exit code; detailed output is hidden for security"
}
if ($signed) {
$signedCount++
$verifyOutput = & $signTool verify /pa /v $file.FullName 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "VERIFIED: Signature verification successful"
} else {
Write-Host "WARNING: Signature verification failed"
Write-Host "Detailed verification output is hidden for security"
}
} else {
$failedCount++
}
Write-Host ""
}
Write-Host "=== SIGNING SUMMARY ==="
Write-Host "Total files: $($filesToSign.Count)"
Write-Host "Successfully signed: $signedCount"
Write-Host "Failed to sign: $failedCount"
if ($failedCount -eq 0) {
Write-Host "ALL WINDOWS BINARIES SIGNED SUCCESSFULLY"
exit 0
}
Write-Host "SOME WINDOWS BINARIES FAILED TO SIGN"
exit 1
+510
View File
@@ -0,0 +1,510 @@
name: OpenP2P Release
on:
#push:
# branches: [master]
workflow_dispatch:
inputs:
release_type:
description: '发布类型 (beta: 测试版, stable: 正式版)'
type: choice
options:
- beta
- stable
default: beta
required: true
version:
description: '正式版版本号 (e.g. v3.25.11)beta发布时可留空'
type: string
required: false
permissions:
contents: write
env:
BINARY_NAME: openp2p
jobs:
build:
name: Build ${{ matrix.os }}-${{ matrix.arch }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
# Windows
- { os: windows, arch: amd64, runner: ubuntu-latest, goos: windows, goarch: amd64, ext: .exe }
- { os: windows, arch: arm64, runner: ubuntu-latest, goos: windows, goarch: arm64, ext: .exe }
- { os: windows, arch: i386, runner: ubuntu-latest, goos: windows, goarch: 386, ext: .exe }
# Linux
- { os: linux, arch: amd64, runner: ubuntu-latest, goos: linux, goarch: amd64, ext: '' }
- { os: linux, arch: arm64, runner: ubuntu-latest, goos: linux, goarch: arm64, ext: '' }
- { os: linux, arch: i386, runner: ubuntu-latest, goos: linux, goarch: 386, ext: '' }
- { os: linux, arch: mips, runner: ubuntu-latest, goos: linux, goarch: mips, ext: '' }
- { os: linux, arch: mips64, runner: ubuntu-latest, goos: linux, goarch: mips64, ext: '' }
# Darwin
- { os: darwin, arch: amd64, runner: ubuntu-latest, goos: darwin, goarch: amd64, ext: '' }
- { os: darwin, arch: arm64, runner: ubuntu-latest, goos: darwin, goarch: arm64, ext: '' }
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.20'
- name: Build
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: '0'
GOPROXY: https://goproxy.io,direct
run: |
OUTPUT_NAME="${{ env.BINARY_NAME }}-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.ext }}"
go build -trimpath -ldflags="-s -w" -o "$OUTPUT_NAME" cmd/openp2p.go
echo "OUTPUT_NAME=$OUTPUT_NAME" >> $GITHUB_ENV
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.BINARY_NAME }}-${{ matrix.os }}-${{ matrix.arch }}
path: ${{ env.OUTPUT_NAME }}
retention-days: 7
# build-android:
# name: Build Android APK
# runs-on: ubuntu-latest
# steps:
# - name: Checkout
# uses: actions/checkout@v6
#
# - name: Setup Go
# uses: actions/setup-go@v5
# with:
# go-version: '1.23'
#
# - name: Setup JDK
# uses: actions/setup-java@v4
# with:
# java-version: '17'
# distribution: 'temurin'
#
# - name: Setup Android SDK & NDK
# uses: android-actions/setup-android@v3
# with:
# packages: 'build-tools;30.0.3 ndk;21.4.7075529 platform-tools platforms;android-31'
#
# - name: Setup Android Environment
# run: |
# echo "$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_PATH
# echo "ANDROID_NDK_HOME=$ANDROID_HOME/ndk/21.4.7075529" >> $GITHUB_ENV
#
# - name: Build Go mobile library (gomobile bind)
# env:
# GOPROXY: https://goproxy.io,direct
# run: |
# # Install gomobile and gobind at the same pinned commit to avoid @latest resolution
# go install golang.org/x/mobile/cmd/gomobile@7c4916698cc93475ebfea76748ee0faba2deb2a5
# go install golang.org/x/mobile/cmd/gobind@7c4916698cc93475ebfea76748ee0faba2deb2a5
# gomobile init
# go get -v golang.org/x/mobile/bind@7c4916698cc93475ebfea76748ee0faba2deb2a5
#
# cd core
# gomobile bind -target android -v -androidapi 16
#
# # Copy artifacts to app libs
# mkdir -p ../app/app/libs
# cp openp2p.aar openp2p-sources.jar ../app/app/libs/
# echo "Go mobile library built and copied to app/app/libs/"
# ls -la ../app/app/libs/
#
# - name: Build unsigned APK
# working-directory: app
# run: |
# chmod +x gradlew
# ./gradlew assembleRelease
#
# # Find the built APK
# APK_PATH=$(find . -name "*.apk" -path "*/release/*" | head -1)
# if [ -z "$APK_PATH" ]; then
# APK_PATH=$(find . -name "*.apk" | head -1)
# fi
#
# if [ -n "$APK_PATH" ]; then
# cp "$APK_PATH" ../openp2p-android.apk
# echo "APK built: $APK_PATH"
# else
# echo "ERROR: No APK found"
# exit 1
# fi
#
# - name: Upload APK artifact
# uses: actions/upload-artifact@v4
# with:
# name: openp2p-android-apk
# path: openp2p-android.apk
# retention-days: 7
sign:
name: Sign Artifacts (Certum SimplySign)
needs: [build]
runs-on: windows-latest
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
pattern: openp2p-windows-*
path: sign_binaries/windows
merge-multiple: true
# - name: Download Android APK
# uses: actions/download-artifact@v4
# with:
# name: openp2p-android-apk
# path: sign_binaries/android
# - name: Setup JDK (for jarsigner)
# uses: actions/setup-java@v4
# with:
# java-version: '17'
# distribution: 'temurin'
- name: Setup Certum Code Signing (Windows)
shell: bash
run: |
echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ==="
echo "Installing SimplySign Desktop and configuring for automatic authentication"
chmod +x ./.github/scripts/install-simplysign.sh
./.github/scripts/install-simplysign.sh
echo "Configuring registry for automatic login dialog..."
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1"
echo "Certum signing environment ready"
- name: Authenticate Certum (Windows)
shell: bash
env:
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
CERTUM_EXE_PATH: ${{ secrets.CERTUM_EXE_PATH }}
run: |
echo "=== CERTUM AUTHENTICATION ==="
echo "Authenticating with Certum cloud certificate using TOTP"
for attempt in 1 2 3; do
echo "Authentication attempt ${attempt}/3"
if powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplySign-enhanced.ps1"; then
echo "Authentication completed"
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "Authentication attempt failed, retrying in 10 seconds..."
sleep 10
fi
done
echo "ERROR: Certum authentication failed after 3 attempts"
exit 1
- name: Verify Certificate and Sign Windows Binaries
shell: bash
env:
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
run: |
echo "=== CERTIFICATE VERIFICATION AND WINDOWS BINARY SIGNING ==="
echo "Allowing connection to stabilize..."
sleep 10
echo "Comprehensive certificate availability check..."
echo ""
echo "Skipping certificate store dump to avoid exposing certificate metadata in logs"
echo ""
echo "=== PKCS#11 Library Check ==="
if [ -f "/c/Windows/System32/SimplySignPKCS.dll" ]; then
echo "PKCS#11 library present: /c/Windows/System32/SimplySignPKCS.dll"
ls -la "/c/Windows/System32/SimplySignPKCS.dll"
else
echo "PKCS#11 library not found"
fi
echo ""
echo "=== SimplySign Desktop Status ==="
powershell -Command "
Write-Host 'SimplySign Desktop process status:'
Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue |
Select-Object Name, Id, MainWindowTitle, Responding |
Format-Table -AutoSize
"
echo ""
echo "Certificate debugging completed - proceeding to signing..."
echo ""
powershell -ExecutionPolicy Bypass -File "./.github/scripts/sign-windows.ps1" -TargetDirectory "sign_binaries/windows"
echo "Windows binary signing completed"
# - name: Sign Android APK (jarsigner + PKCS#11)
# shell: pwsh
# run: |
# Write-Host "=== SIGNING ANDROID APK WITH CERTUM CLOUD CERTIFICATE (PKCS#11) ==="
#
# # Create PKCS#11 config file for Certum SimplySign
# $pkcs11Config = @"
# name = CertumSimplySign
# library = C:\Program Files\Certum\SimplySign Desktop\cryptoCertum3PKCS.dll
# slot = 0
# "@
# $pkcs11Config | Out-File -FilePath "pkcs11.cfg" -Encoding ASCII
#
# Write-Host "PKCS#11 config created"
#
# # Find APK files
# $apkFiles = Get-ChildItem -Path "sign_binaries/android" -Recurse -Include *.apk
#
# if ($apkFiles.Count -eq 0) {
# Write-Host "No APK files found to sign"
# exit 0
# }
#
# # Certum SimplySign default key alias is "1"
# $keyAlias = "1"
# Write-Host "Using key alias: $keyAlias"
#
# foreach ($apk in $apkFiles) {
# Write-Host "Signing APK: $($apk.FullName)"
# try {
# & jarsigner -verbose `
# -keystore NONE `
# -storetype PKCS11 `
# -providerClass sun.security.pkcs11.SunPKCS11 `
# -providerArg pkcs11.cfg `
# -tsa http://time.certum.pl `
# -signedjar "$($apk.DirectoryName)\signed-$($apk.Name)" `
# "$($apk.FullName)" `
# $keyAlias
#
# # Replace original with signed version
# Move-Item -Path "$($apk.DirectoryName)\signed-$($apk.Name)" -Destination $apk.FullName -Force
# Write-Host " OK: APK signed successfully"
# } catch {
# Write-Host " WARNING: Failed to sign APK - $($_.Exception.Message)"
# }
# }
#
# Write-Host "=== ANDROID APK SIGNING COMPLETE ==="
# continue-on-error: true
- name: Verify Windows Signatures
shell: pwsh
run: |
$signedFiles = Get-ChildItem -Path "sign_binaries/windows" -Recurse -Include *.exe
foreach ($file in $signedFiles) {
$result = Get-AuthenticodeSignature -FilePath $file.FullName
$status = if ($result.Status -eq "Valid") { "VALID" } else { "INVALID/UNSIGNED ($($result.Status))" }
Write-Host "$($file.Name): $status"
}
continue-on-error: true
# - name: Verify APK Signature
# shell: pwsh
# run: |
# $apkFiles = Get-ChildItem -Path "sign_binaries/android" -Recurse -Include *.apk
# foreach ($apk in $apkFiles) {
# Write-Host "Verifying: $($apk.Name)"
# & jarsigner -verify -verbose -certs "$($apk.FullName)" 2>&1 | Select-Object -First 10
# }
# continue-on-error: true
- name: Upload signed Windows artifacts
uses: actions/upload-artifact@v4
with:
name: signed-windows-artifacts
path: sign_binaries/windows
retention-days: 7
# - name: Upload signed Android APK
# uses: actions/upload-artifact@v4
# with:
# name: signed-android-apk
# path: sign_binaries/android
# retention-days: 7
release:
name: Create Release
needs: [build, sign]
runs-on: ubuntu-latest
if: always()
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Determine version
id: version
run: |
RELEASE_TYPE="${{ inputs.release_type }}"
if [ "$RELEASE_TYPE" = "stable" ]; then
VERSION="${{ inputs.version }}"
if [ -z "$VERSION" ]; then
echo "ERROR: stable release requires a version number"
exit 1
fi
IS_BETA="false"
else
VERSION="beta"
IS_BETA="true"
fi
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "is_beta=$IS_BETA" >> $GITHUB_OUTPUT
echo "Version: $VERSION, Is Beta: $IS_BETA, Release Type: $RELEASE_TYPE"
# Try to download signed Windows artifacts first, fall back to unsigned
- name: Download signed Windows artifacts
id: download-signed-win
uses: actions/download-artifact@v4
with:
name: signed-windows-artifacts
path: release_binaries
continue-on-error: true
- name: Download unsigned Windows artifacts (fallback)
if: steps.download-signed-win.outcome != 'success'
uses: actions/download-artifact@v4
with:
pattern: openp2p-windows-*
path: release_binaries
merge-multiple: true
# Try to download signed Android APK first, fall back to unsigned
# - name: Download signed Android APK
# id: download-signed-android
# uses: actions/download-artifact@v4
# with:
# name: signed-android-apk
# path: release_binaries
# continue-on-error: true
#
# - name: Download unsigned Android APK (fallback)
# if: steps.download-signed-android.outcome != 'success'
# uses: actions/download-artifact@v4
# with:
# name: openp2p-android-apk
# path: release_binaries
- name: Download Linux artifacts
uses: actions/download-artifact@v4
with:
pattern: openp2p-linux-*
path: release_binaries
merge-multiple: true
- name: Download Darwin artifacts
uses: actions/download-artifact@v4
with:
pattern: openp2p-darwin-*
path: release_binaries
merge-multiple: true
- name: Package release assets
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir -p release_assets
cd release_binaries
chmod +x * 2>/dev/null || true
for file in *; do
if [ -f "$file" ]; then
if [[ "$file" == *.exe ]]; then
zip "../release_assets/${file%.exe}-${VERSION}.zip" "$file"
elif [[ "$file" == *.apk ]]; then
# APK files: rename with version, no compression needed
cp "$file" "../release_assets/${file%.apk}-${VERSION}.apk"
else
tar czf "../release_assets/${file}-${VERSION}.tar.gz" "$file"
fi
fi
done
cd ../release_assets
echo "=== Release Assets ==="
ls -la
- name: Generate release notes
id: notes
env:
VERSION: ${{ steps.version.outputs.version }}
IS_BETA: ${{ steps.version.outputs.is_beta }}
run: |
if [ "$IS_BETA" = "true" ]; then
cat > release_notes.md << EOF
## OpenP2P Beta Release (latest unstable)
Built from commit \`${{ github.sha }}\` on $(date -u +"%Y-%m-%d %H:%M UTC").
### Supported Platforms
| OS | Architectures |
|---|---|
| Windows | amd64, arm64, i386 |
| Linux | amd64, arm64, i386, mips, mips64 |
| Darwin (macOS) | amd64, arm64 |
| Android | amd64, arm64 |
> **Note**: This is a pre-release build and may be unstable. This release is automatically updated on every push to master.
EOF
else
cat > release_notes.md << EOF
## OpenP2P $VERSION
### Supported Platforms
| OS | Architectures |
|---|---|
| Windows | amd64, arm64, i386 |
| Linux | amd64, arm64, i386, mips, mips64 |
| Darwin (macOS) | amd64, arm64 |
| Android | amd64, arm64 |
EOF
fi
- name: Delete existing Beta Release
if: steps.version.outputs.is_beta == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Delete existing beta release if it exists
gh release delete beta --yes --cleanup-tag 2>/dev/null || true
- name: Create Beta Release
if: steps.version.outputs.is_beta == 'true'
uses: softprops/action-gh-release@v2
with:
name: "Beta (latest unstable)"
tag_name: beta
body_path: release_notes.md
prerelease: true
files: release_assets/*
token: ${{ secrets.GITHUB_TOKEN }}
- name: Create Stable Release
if: steps.version.outputs.is_beta == 'false'
uses: softprops/action-gh-release@v2
with:
name: ${{ steps.version.outputs.version }}
tag_name: ${{ steps.version.outputs.version }}
body_path: release_notes.md
prerelease: false
make_latest: true
files: release_assets/*
token: ${{ secrets.GITHUB_TOKEN }}
+171
View File
@@ -0,0 +1,171 @@
name: Sign External Binaries
on:
workflow_dispatch:
inputs:
url_x86:
description: 'X86 (i386) 二进制文件下载地址'
type: string
required: false
default: 'https://console.openpxp.com/download/v1/latest/openp2p386-latest.exe'
url_x64:
description: 'X64 (amd64) 二进制文件下载地址'
type: string
required: false
default: 'https://console.openpxp.com/download/v1/latest/openp2p64-latest.exe'
url_arm:
description: 'ARM (arm64) 二进制文件下载地址'
type: string
required: false
default: 'https://console.openpxp.com/download/v1/latest/openp2parm64-latest.exe'
permissions:
contents: read
jobs:
sign:
name: Sign Binaries (Certum SimplySign)
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Validate inputs
shell: bash
run: |
if [ -z "${{ inputs.url_x86 }}" ] && [ -z "${{ inputs.url_x64 }}" ] && [ -z "${{ inputs.url_arm }}" ]; then
echo "ERROR: 至少需要提供一个二进制文件下载地址"
exit 1
fi
echo "=== 输入的下载地址 ==="
[ -n "${{ inputs.url_x86 }}" ] && echo "X86: ${{ inputs.url_x86 }}"
[ -n "${{ inputs.url_x64 }}" ] && echo "X64: ${{ inputs.url_x64 }}"
[ -n "${{ inputs.url_arm }}" ] && echo "ARM: ${{ inputs.url_arm }}"
- name: Download binaries
shell: bash
run: |
mkdir -p sign_binaries
download_file() {
local url="$1"
local label="$2"
if [ -z "$url" ]; then
echo "跳过 ${label}: 未提供下载地址"
return
fi
echo "正在下载 ${label}: ${url}"
# 从 URL 中提取文件名
local filename=$(basename "$url" | sed 's/[?#].*//')
# 如果文件名为空或不合理,使用 label 作为文件名
if [ -z "$filename" ] || [ "$filename" = "/" ]; then
filename="${label}-binary.exe"
fi
curl -fSL --retry 3 --retry-delay 5 -o "sign_binaries/${filename}" "$url"
if [ $? -eq 0 ]; then
echo "下载成功: ${filename}"
else
echo "ERROR: 下载失败 ${label}: ${url}"
exit 1
fi
}
download_file "${{ inputs.url_x86 }}" "x86"
download_file "${{ inputs.url_x64 }}" "x64"
download_file "${{ inputs.url_arm }}" "arm"
echo ""
echo "=== 已下载的文件 ==="
ls -la sign_binaries/
- name: Setup Certum Code Signing (Windows)
shell: bash
run: |
echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ==="
echo "Installing SimplySign Desktop and configuring for automatic authentication"
chmod +x ./.github/scripts/install-simplysign.sh
./.github/scripts/install-simplysign.sh
echo "Configuring registry for automatic login dialog..."
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1"
echo "Certum signing environment ready"
- name: Authenticate Certum (Windows)
shell: bash
env:
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
CERTUM_EXE_PATH: ${{ secrets.CERTUM_EXE_PATH }}
run: |
echo "=== CERTUM AUTHENTICATION ==="
echo "Authenticating with Certum cloud certificate using TOTP"
for attempt in 1 2 3; do
echo "Authentication attempt ${attempt}/3"
if powershell -ExecutionPolicy Bypass -File "./.github/scripts/connect-simplySign-enhanced.ps1"; then
echo "Authentication completed"
exit 0
fi
if [ "$attempt" -lt 3 ]; then
echo "Authentication attempt failed, retrying in 10 seconds..."
sleep 10
fi
done
echo "ERROR: Certum authentication failed after 3 attempts"
exit 1
- name: Sign Binaries
shell: bash
env:
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
run: |
echo "=== SIGNING BINARIES ==="
echo "Allowing connection to stabilize..."
sleep 10
echo "=== PKCS#11 Library Check ==="
if [ -f "/c/Windows/System32/SimplySignPKCS.dll" ]; then
echo "PKCS#11 library present: /c/Windows/System32/SimplySignPKCS.dll"
else
echo "PKCS#11 library not found"
fi
echo ""
echo "=== SimplySign Desktop Status ==="
powershell -Command "
Write-Host 'SimplySign Desktop process status:'
Get-Process -Name '*SimplySign*' -ErrorAction SilentlyContinue |
Select-Object Name, Id, MainWindowTitle, Responding |
Format-Table -AutoSize
"
echo ""
echo "Proceeding to signing..."
echo ""
powershell -ExecutionPolicy Bypass -File "./.github/scripts/sign-windows.ps1" -TargetDirectory "sign_binaries"
echo "Binary signing completed"
- name: Verify Signatures
shell: pwsh
run: |
$signedFiles = Get-ChildItem -Path "sign_binaries" -Recurse -File
foreach ($file in $signedFiles) {
$result = Get-AuthenticodeSignature -FilePath $file.FullName
$status = if ($result.Status -eq "Valid") { "VALID" } else { "INVALID/UNSIGNED ($($result.Status))" }
Write-Host "$($file.Name): $status"
}
continue-on-error: true
- name: Upload signed artifacts
uses: actions/upload-artifact@v4
with:
name: signed-binaries
path: sign_binaries/
retention-days: 30
+7 -2
View File
@@ -1,10 +1,8 @@
__debug_bin __debug_bin
__debug_bin.exe __debug_bin.exe
# .vscode # .vscode
test/
openp2p.exe* openp2p.exe*
*.log* *.log*
go.sum
*.tar.gz *.tar.gz
*.zip *.zip
*.exe *.exe
@@ -21,3 +19,10 @@ wintun.dll
app/.idea/ app/.idea/
*_debug_bin* *_debug_bin*
cmd/openp2p cmd/openp2p
vendor/
config.json
openp2p
lib/openp2p.dll
cmd/config.json0
test/docker/Dockerfile
test/docker/get-client.sh
+69
View File
@@ -0,0 +1,69 @@
ChangeLog
v3.25.11更新 (2026.5.15)
Feature
1.
Issue
1. 修复编辑组网成员网络资源时某些情况网络不通
1. 修复安卓版本输错token登录崩溃,下次重启自动登录仍会崩溃,不断循环
1. 修复某些情况导致客户端异常重启
v3.25.8更新 (2026.3.13)
Feature
1. web控制台可以修改公网监听端口
1. 可以修改虚拟网络网段
1. 回滚至go1.20支持老版本的macos和windows
Issue
1. 修复客户端重装后强制v6连接失效bug
v3.25.4更新 (2026.2.9)
Feature
1. 优化websocket读数据卡死问题
1. 优化睡眠唤醒客户端恢复慢问题
Issue
1. 修复获取ifconfig异常
1. 修复数据同步异常导致设备间连接失败
1. 修复底层连接潜在发送数据不完整问题
v3.24.33更新 (2025.12.10)
Feature
1. 安装和升级下载文件到临时目录
1. openwrt默认100k日志文件
1. 使用系统dns失败时将使用223.5.5.5和8.8.8.8,安卓和部分系统有dns问题
1. IPv6刷新时上报到服务器
1. 设备间连接可以设置强制使用v6
1. 编译环境使用go1.25
Issue
1. 修复加载系统证书池bug
1. 修复初始化时崩溃
1. 修复安卓处理多个网络资源bug
1. 修复端口转发编辑目标设备bug
1. 修复某些特殊情况IPv6直连失败
v3.24.23更新 (2025.9.4)
Feature
1. 公网UDP直连
1. upnp定期续期
1. 支持边缘服务器
1. 优化mp分配算法
1. 公网IP不变不再检测nat类型
Issue
1. 修复客户端某些特殊情况卡死bug
1. 修复wintun mtu不生效bug,增加缓冲区大小
1. 修复广播bug
1. 修复配置文件清空bug
v3.24.13更新 (2025.6.4)
Feature
1. 虚拟网卡状态上报
1. OpenWrt自动安装tun
1. docker容器运行路径改为/usr/local/openp2p/
Issue
1. 优化客户端卡死问题
+13 -13
View File
@@ -19,7 +19,7 @@
[查看详细](#安全性) [查看详细](#安全性)
### 4. 轻量 ### 4. 轻量
文件大小2MB+,运行内存2MB+;它可以仅跑在应用层,或者配合wintun驱动使用组网功能 文件大小不到10MBcpu占用极低;它可以仅跑在应用层,或者配合kmod-tun/wintun驱动使用组网功能
### 5. 跨平台 ### 5. 跨平台
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le 因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le
### 6. 高效 ### 6. 高效
@@ -31,13 +31,13 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
## 快速入门 ## 快速入门
仅需简单4步就能用起来。 仅需简单4步就能用起来。
下面是一个远程办公例子:在家里连入办公室Windows电脑。 下面是一个远程办公例子:在家里连入办公室Windows电脑。
(另外一个快速入门视频 https://www.bilibili.com/video/BV1Et4y1P7bF/ (另外一个快速入门视频 <https://www.bilibili.com/video/BV1Et4y1P7bF/>
### 1.注册 ### 1.注册
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证 前往<https://console.openp2p.cn> 使用邮箱注册新用户,暂无需任何认证
![image](/doc/images/register.png) ![image](/doc/images/register.png)
### 2.安装 ### 2.安装
分别在本地和远程电脑下载后双击运行,一键安装 分别在本地和远程电脑下载后双击运行,一键安装(如果是windows用户,在浏览器下载后请勿修改文件名!!!)
![image](/doc/images/install.png) ![image](/doc/images/install.png)
@@ -46,7 +46,7 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
![image](/doc/images/win10warn.png) ![image](/doc/images/win10warn.png)
![image](/doc/images/stillrun.png) ![image](/doc/images/stillrun.png)
### 3.新建P2P应用 ### 3.新建端口转发(P2PApp)
![image](/doc/images/devices.png) ![image](/doc/images/devices.png)
@@ -54,12 +54,12 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
![image](/doc/images/newappedit.png) ![image](/doc/images/newappedit.png)
### 4.使用P2P应用 ### 4.使用端口转发(P2PApp)
在“MyHomePC”设备上能看到刚才创建的P2P应用,连接下图显示的“本地监听端口”即可。 在“MyHomePC2”设备上能看到刚才创建的端口转发(P2PApp),连接下图显示的“本地监听端口”即可。
![image](/doc/images/p2pappok.png) ![image](/doc/images/p2pappok.png)
家里Windows电脑,按Win+R输入mstsc打开远程桌面,输入127.0.0.1:23389 /admin MyHomePC2电脑,按Win+R输入mstsc打开远程桌面,输入127.0.0.1:23389 /admin
![image](/doc/images/mstscconnect.png) ![image](/doc/images/mstscconnect.png)
@@ -82,8 +82,8 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
![image](/doc/images/prototype.png) ![image](/doc/images/prototype.png)
### 客户端架构 ### 客户端架构
![image](/doc/images/architecture.png) ![image](/doc/images/architecture.png)
### P2PApp ### 端口转发(P2PApp)
它是项目里最重要的概念,一个P2PApp就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理P2PApp 它是项目里最重要的概念,一个端口转发(P2PApp)就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理端口转发(P2PApp)
![image](/doc/images/appdetail.png) ![image](/doc/images/appdetail.png)
## 安全性 ## 安全性
加入OpenP2P共享网络的节点,只能凭授权访问。共享节点只会中转数据,别人无法访问内网任何资源。 加入OpenP2P共享网络的节点,只能凭授权访问。共享节点只会中转数据,别人无法访问内网任何资源。
@@ -96,7 +96,7 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
服务端有个调度模型,根据带宽、ping值、稳定性、服务时长,尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码,hmac-sha256算法校验,它是一次性密码,和我们平时使用的手机验证码或银行密码器一样的原理。 服务端有个调度模型,根据带宽、ping值、稳定性、服务时长,尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码,hmac-sha256算法校验,它是一次性密码,和我们平时使用的手机验证码或银行密码器一样的原理。
## 编译 ## 编译
go version go1.18.1+ go version 1.20 only (支持win7)
cd到代码根目录,执行 cd到代码根目录,执行
``` ```
make make
@@ -123,8 +123,8 @@ CGO_ENABLED=0 env GOOS=linux GOARCH=amd64 go build -o openp2p --ldflags '-s -w '
1. ~~支持IPv6~~(100%) 1. ~~支持IPv6~~(100%)
2. ~~支持随系统自动启动,安装成系统服务~~(100%) 2. ~~支持随系统自动启动,安装成系统服务~~(100%)
3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%) 3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%)
4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%) 4. ~~建立网站,用户可以在网站管理所有端口转发(P2PApp)和设备。查看设备在线状态,升级,增删查改重启端口转发(P2PApp)等~~(100%)
5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备 5. 建立公众号,用户可在微信公众号管理所有端口转发(P2PApp)和设备
6. 客户端提供WebUI 6. 客户端提供WebUI
7. ~~支持自有服务器,开源服务器程序~~(100%) 7. ~~支持自有服务器,开源服务器程序~~(100%)
8. 共享节点调度模型优化,对不同的运营商优化 8. 共享节点调度模型优化,对不同的运营商优化
+14 -12
View File
@@ -19,7 +19,7 @@ The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and t
[details](#Safety) [details](#Safety)
### 4. Lightweight ### 4. Lightweight
2MB+ filesize, 2MB+ memory. It could only runs at application layer, or uses wintun driver for SDWAN. 10MB filesize, Extremely low CPU usage. It could only runs at application layer, or uses kmod-tun/wintun driver for SDWAN.
### 5. Cross-platform ### 5. Cross-platform
Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le. Benefit from lightweight, it easily supports most of major OS, like Windows, Linux, MacOS, also most of CPU architecture, like 386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le.
@@ -35,11 +35,11 @@ Just 4 simple steps to use.
Here's an example of remote work: connecting to an office Windows computer at home. 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/) (Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
### 1.Register ### 1.Register
Go to <https://console.openp2p.cn> register a new user Go to <https://console.openp2p.cn> register a new user using email
![image](/doc/images/register_en.png) ![image](/doc/images/register_en.png)
### 2.Install ### 2.Install
Download on local and remote computers and double-click to run, one-click installation Download on local and remote computers and double-click to run, one-click installation (Windows user, please do not modify the file name after downloading in the browser!!!)
![image](/doc/images/install_en.png) ![image](/doc/images/install_en.png)
@@ -49,7 +49,7 @@ By default, Windows will block programs that have not been signed by the Microso
![image](/doc/images/stillrun_en.png) ![image](/doc/images/stillrun_en.png)
### 3.New P2PApp ### 3.New Port ForWard (P2PApp)
![image](/doc/images/devices_en.png) ![image](/doc/images/devices_en.png)
@@ -57,12 +57,12 @@ By default, Windows will block programs that have not been signed by the Microso
![image](/doc/images/newappedit_en.png) ![image](/doc/images/newappedit_en.png)
### 4.Use P2PApp ### 4.Use Port ForWard (P2PApp)
You can see the P2P application you just created on the "MyHomePC" device, just connect to the "local listening port" shown in the figure below. You can see the P2P application you just created on the "MyHomePC2" device, just connect to the "local listening port" shown in the figure below.
![image](/doc/images/p2pappok_en.png) ![image](/doc/images/p2pappok_en.png)
On MyHomePC, press Win+R and enter MSTSC to open the remote desktop, input `127.0.0.1:23389 /admin` On MyHomePC2, press Win+R and enter MSTSC to open the remote desktop, input `127.0.0.1:23389 /admin`
![image](/doc/images/mstscconnect_en.png) ![image](/doc/images/mstscconnect_en.png)
@@ -86,8 +86,8 @@ Especially suitable for large traffic intranet access.
![image](/doc/images/prototype.png) ![image](/doc/images/prototype.png)
### Client architecture ### Client architecture
![image](/doc/images/architecture.png) ![image](/doc/images/architecture.png)
### P2PApp ### Port ForWard (P2PApp)
P2PAPP is the most import concept in this project, one P2PApp is able to map the remote service(mstsc/ssh) to the local listening. The main job of re-development or restful API we provide is to manage P2PApp. Port ForWard (P2PApp) is the most import concept in this project, one Port ForWard (P2PApp) is able to map the remote service(mstsc/ssh) to the local listening. The main job of re-development or restful API we provide is to manage Port ForWard (P2PApp).
![image](/doc/images/appdetail.png) ![image](/doc/images/appdetail.png)
@@ -103,7 +103,7 @@ That's right, the relay node is naturally an man-in-middle, so AES encryption is
The server side has a scheduling model, which calculate bandwith, ping value,stability and service duration to provide a well-proportioned service to every share node. It uses TOTP(Time-based One-time Password) with hmac-sha256 algorithem, its theory as same as the cellphone validation code or bank cipher coder. The server side has a scheduling model, which calculate bandwith, ping value,stability and service duration to provide a well-proportioned service to every share node. It uses TOTP(Time-based One-time Password) with hmac-sha256 algorithem, its theory as same as the cellphone validation code or bank cipher coder.
## Build ## Build
go version go1.18.1+ go version 1.20 only (support win7)
cd root directory of the socure code and execute cd root directory of the socure code and execute
``` ```
make make
@@ -131,8 +131,8 @@ Short-Term:
1. ~~Support IPv6.~~(100%) 1. ~~Support IPv6.~~(100%)
2. ~~Support auto run when system boot, setup system service.~~(100%) 2. ~~Support auto run when system boot, setup system service.~~(100%)
3. ~~Provide free servers to some low-performance network.~~(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%) 4. ~~Build website, users can manage all Port ForWard (P2PApp) and devices via it. View devices' online status, upgrade, restart or CURD Port ForWard (P2PApp) .~~(100%)
5. Provide wechat official account, user can manage P2PApp nodes and deivce as same as website. 5. Provide wechat official account, user can manage Port ForWard (P2PApp) nodes and deivce as same as website.
6. Provide WebUI on client side. 6. Provide WebUI on client side.
7. ~~Support private server, open source server program.~~(100%) 7. ~~Support private server, open source server program.~~(100%)
8. Optimize our share scheduling model for different network operators. 8. Optimize our share scheduling model for different network operators.
@@ -162,3 +162,5 @@ Email: [email protected] [email protected]
## Disclaimer ## Disclaimer
This project is open source for everyone to learn and use for free. It is forbidden to be used for illegal purposes. Any loss caused by improper use of this project or accident, this project and related personnel will not bear any responsibility. This project is open source for everyone to learn and use for free. It is forbidden to be used for illegal purposes. Any loss caused by improper use of this project or accident, this project and related personnel will not bear any responsibility.
## Thanks
[![Powered by DartNode](https://dartnode.com/branding/DN-Open-Source-sm.png)](https://dartnode.com "Powered by DartNode - Free VPS for Open Source")
+3 -2
View File
@@ -2,9 +2,10 @@
depends on openjdk 11, gradle 8.1.3, ndk 21 depends on openjdk 11, gradle 8.1.3, ndk 21
``` ```
go install golang.org/x/mobile/cmd/gomobile@latest # latest version not support go1.20
go install golang.org/x/mobile/cmd/gomobile@7c4916698cc93475ebfea76748ee0faba2deb2a5
gomobile init gomobile init
go get -v golang.org/x/mobile/bind go get -v golang.org/x/mobile/bind@7c4916698cc93475ebfea76748ee0faba2deb2a5
cd core cd core
gomobile bind -target android -v gomobile bind -target android -v
if [[ $? -ne 0 ]]; then if [[ $? -ne 0 ]]; then
+280 -111
View File
@@ -4,7 +4,6 @@ import android.app.*
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import java.io.IOException
import android.net.VpnService import android.net.VpnService
import android.os.Binder import android.os.Binder
import android.os.Build import android.os.Build
@@ -22,6 +21,13 @@ import java.io.FileOutputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.json.JSONObject import org.json.JSONObject
import java.io.File
import java.net.InetAddress
import java.net.NetworkInterface
import kotlinx.coroutines.channels.Channel
import java.nio.channels.FileChannel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
data class Node(val name: String, val ip: String, val resource: String? = null) data class Node(val name: String, val ip: String, val resource: String? = null)
@@ -32,9 +38,10 @@ data class Network(
val gateway: String, val gateway: String,
val Nodes: List<Node> val Nodes: List<Node>
) )
class OpenP2PService : VpnService() { class OpenP2PService : VpnService() {
companion object { companion object {
private val LOG_TAG = OpenP2PService::class.simpleName private val LOG_TAG = "OpenP2PService"
} }
inner class LocalBinder : Binder() { inner class LocalBinder : Binder() {
@@ -44,12 +51,17 @@ class OpenP2PService : VpnService() {
private val binder = LocalBinder() private val binder = LocalBinder()
private lateinit var network: openp2p.P2PNetwork private lateinit var network: openp2p.P2PNetwork
private lateinit var mToken: String private lateinit var mToken: String
private var running:Boolean =true private var running: Boolean = true
private var sdwanRunning:Boolean =false private var sdwanRunning: Boolean = false
private var vpnInterface: ParcelFileDescriptor? = null private var vpnInterface: ParcelFileDescriptor? = null
private var sdwanJob: Job? = null private var sdwanJob: Job? = null
private val packetQueue = Channel<ByteBuffer>(capacity = 1024)
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onCreate() { override fun onCreate() {
Log.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id) val logDir = File(getExternalFilesDir(null), "log")
Logger.init(logDir)
Logger.i(LOG_TAG, "onCreate - Thread ID = " + Thread.currentThread().id)
var channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { var channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("kim.hsl", "ForegroundService") createNotificationChannel("kim.hsl", "ForegroundService")
} else { } else {
@@ -64,7 +76,7 @@ class OpenP2PService : VpnService() {
val notification = channelId?.let { val notification = channelId?.let {
NotificationCompat.Builder(this, it) NotificationCompat.Builder(this, it)
// .setSmallIcon(R.mipmap.app_icon) // .setSmallIcon(R.mipmap.app_icon)
.setContentTitle("My Awesome App") .setContentTitle("My Awesome App")
.setContentText("Doing some work...") .setContentText("Doing some work...")
.setContentIntent(pendingIntent).build() .setContentIntent(pendingIntent).build()
@@ -76,7 +88,7 @@ class OpenP2PService : VpnService() {
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i( Logger.i(
LOG_TAG, LOG_TAG,
"onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id
) )
@@ -86,20 +98,20 @@ class OpenP2PService : VpnService() {
override fun onBind(p0: Intent?): IBinder? { override fun onBind(p0: Intent?): IBinder? {
val token = p0?.getStringExtra("token") val token = p0?.getStringExtra("token")
Log.i(LOG_TAG, "onBind token=$token") Logger.i(LOG_TAG, "onBind token=$token")
startOpenP2P(token) startOpenP2P(token)
return binder return binder
} }
private fun startOpenP2P(token : String?): Boolean { private fun startOpenP2P(token: String?): Boolean {
if (sdwanRunning) { if (sdwanRunning) {
return true return true
} }
Log.i(LOG_TAG, "startOpenP2P - Thread ID = " + Thread.currentThread().id + token) Logger.i(LOG_TAG, "startOpenP2P - Thread ID = " + Thread.currentThread().id + token)
val oldToken = Openp2p.getToken(getExternalFilesDir(null).toString()) val oldToken = Openp2p.getToken(getExternalFilesDir(null).toString())
Log.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token") Logger.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token")
if (oldToken=="0" && token==null){ if (oldToken == "0" && token == null) {
return false return false
} }
sdwanRunning = true sdwanRunning = true
@@ -112,7 +124,7 @@ class OpenP2PService : VpnService() {
1 1
) // /storage/emulated/0/Android/data/cn.openp2p/files/ ) // /storage/emulated/0/Android/data/cn.openp2p/files/
val isConnect = network.connect(30000) // ms val isConnect = network.connect(30000) // ms
Log.i(LOG_TAG, "login result: " + isConnect.toString()); Logger.i(LOG_TAG, "login result: " + isConnect.toString());
do { do {
Thread.sleep(1000) Thread.sleep(1000)
} while (network.connect(30000) && running) } while (network.connect(30000) && running)
@@ -123,151 +135,257 @@ class OpenP2PService : VpnService() {
private fun refreshSDWAN() { private fun refreshSDWAN() {
GlobalScope.launch { GlobalScope.launch {
Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN start"); Logger.i(OpenP2PService.LOG_TAG, "refreshSDWAN start");
while (true) { while (true) {
Log.i(OpenP2PService.LOG_TAG, "waiting new sdwan config"); Logger.i(OpenP2PService.LOG_TAG, "waiting new sdwan config");
val buf = ByteArray(4096) val buf = ByteArray(32 * 1024)
val buffLen = Openp2p.getAndroidSDWANConfig(buf) val buffLen = Openp2p.getAndroidSDWANConfig(buf)
Log.i(OpenP2PService.LOG_TAG, "closing running sdwan instance"); Logger.i(OpenP2PService.LOG_TAG, "closing running sdwan instance");
sdwanRunning = false sdwanRunning = false
vpnInterface?.close() vpnInterface?.close()
vpnInterface = null vpnInterface = null
Thread.sleep(10000) sdwanJob?.join()
runSDWAN(buf.copyOfRange(0,buffLen.toInt() )) sdwanJob = serviceScope.launch(context = Dispatchers.IO) {
runSDWAN(buf.copyOfRange(0, buffLen.toInt()))
}
} }
Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN end"); Logger.i(OpenP2PService.LOG_TAG, "refreshSDWAN end");
} }
} }
private suspend fun readTunLoop() { private suspend fun readTunLoop() {
val inputStream = FileInputStream(vpnInterface?.fileDescriptor).channel val inputStream = FileInputStream(vpnInterface?.fileDescriptor).channel
if (inputStream==null){ if (inputStream == null) {
Log.i(OpenP2PService.LOG_TAG, "open FileInputStream error: "); Logger.i(OpenP2PService.LOG_TAG, "open FileInputStream error: ");
return return
} }
Log.d(LOG_TAG, "read tun loop start") Logger.i(LOG_TAG, "read tun loop start")
val buffer = ByteBuffer.allocate(4096) val buffer = ByteBuffer.allocate(4096)
val byteArrayRead = ByteArray(4096) val byteArrayRead = ByteArray(4096)
while (sdwanRunning) { while (sdwanRunning) {
buffer.clear() buffer.clear()
val readBytes = inputStream.read(buffer) withContext(Dispatchers.IO) {
if (readBytes <= 0) { val readBytes = inputStream.read(buffer)
// Log.i(OpenP2PService.LOG_TAG, "inputStream.read error: ") if (readBytes > 0) {
delay(1) buffer.flip()
continue buffer.get(byteArrayRead, 0, readBytes)
// Logger.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidRead: %d", readBytes))
Openp2p.androidRead(byteArrayRead, readBytes.toLong())
// Logger.i(OpenP2PService.LOG_TAG, "inputStream.read error: ")
} else {
delay(50)
}
} }
buffer.flip()
buffer.get(byteArrayRead,0,readBytes)
Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidRead: %d", readBytes))
Openp2p.androidRead(byteArrayRead, readBytes.toLong())
} }
Log.d(LOG_TAG, "read tun loop end") Logger.i(LOG_TAG, "read tun loop end")
} }
private fun runSDWAN(buf:ByteArray) {
sdwanRunning=true
sdwanJob=GlobalScope.launch(context = Dispatchers.IO) {
Log.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}");
try{
var builder = Builder()
val jsonObject = JSONObject(buf.decodeToString())
val id = jsonObject.getLong("id")
val name = jsonObject.getString("name")
val gateway = jsonObject.getString("gateway")
val nodesArray = jsonObject.getJSONArray("Nodes")
val nodesList = mutableListOf<JSONObject>() private suspend fun runSDWAN(buf: ByteArray) {
for (i in 0 until nodesArray.length()) { // val localIps = listOf(
nodesList.add(nodesArray.getJSONObject(i)) // "fe80::14b6:a0ff:fe3e:64de" to 64,
} // "192.168.100.184" to 24,
// "10.93.158.91" to 32,
// "192.168.3.66" to 24
// )
//
// // 测试用例
// val testCases = listOf(
// "192.168.3.11" to true,
// "192.168.100.1" to true,
// "192.168.101.1" to false,
// "10.93.158.91" to true,
// "10.93.158.90" to false,
// "fe80::14b6:a0ff:fe3e:64de" to true,
// "fe80::14b6:a0ff:fe3e:64dd" to true // 在同一子网
// )
//
// for ((ip, expected) in testCases) {
// val result = isSameSubnet(ip, localIps)
// println("Testing IP: $ip, Expected: $expected, Result: $result")
// }
sdwanRunning = true
val myNodeName = Openp2p.getAndroidNodeName() Logger.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}");
Log.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}"); try {
val nodeList = nodesList.map { var builder = Builder()
val nodeName = it.getString("name") val jsonObject = JSONObject(buf.decodeToString())
val nodeIp = it.getString("ip") // debug sdwan info
if (nodeName==myNodeName){ // val jsonObject = JSONObject("""{"id":2817104318517097000,"name":"network1","gateway":"10.2.3.254/24","mode":"central","centralNode":"nanjin-192-168-0-82","enable":1,"tunnelNum":3,"mtu":1420,"Nodes":[{"name":"192-168-24-15","ip":"10.2.3.5"},{"name":"Alpine Linux-172.16","ip":"10.2.3.14","resource":"172.16.0.0/24"},{"name":"ctdeMacBook-Pro.local","ip":"10.2.3.22"},{"name":"dengjiandeMBP.sh.chaitin.net","ip":"10.2.3.32"},{"name":"DESKTOP-WIN11-ARM-self","ip":"10.2.3.19"},{"name":"eastdeMBP.sh.chaitin.net","ip":"10.2.3.3"},{"name":"FN-NAS-HP","ip":"10.2.3.1","resource":"192.168.100.0/24"},{"name":"huangruideMBP.sh.chaitin.net","ip":"10.2.3.30"},{"name":"iStoreOS-virtual-machine","ip":"10.2.3.12"},{"name":"k30s-redmi-10.2.33","ip":"10.2.3.27"},{"name":"lincheng-MacBook-Pro-3.sh.chaitin.net","ip":"10.2.3.15"},{"name":"localhost-mi-13","ip":"10.2.3.8"},{"name":"localhost-华为matepad11","ip":"10.2.3.13"},{"name":"luzhanwendeMacBook-Pro.local","ip":"10.2.3.17"},{"name":"Mi-pad2-local","ip":"10.2.3.9"},{"name":"nanjin-192-168-0-82","ip":"10.2.3.34"},{"name":"R7000P-2021","ip":"10.2.3.7"},{"name":"tanxiaolongsMBP.sh.chaitin.net","ip":"10.2.3.20"},{"name":"TUF-AX3000_V2-3804","ip":"10.2.3.25"},{"name":"WIN-CYZ-10.2.3.16","ip":"10.2.3.16"},{"name":"WODOUYAO","ip":"10.2.3.4"},{"name":"Zstrack01","ip":"10.2.3.51","resource":"192.168.24.0/22,192.168.20.0/24"},{"name":"小米14-localhost","ip":"10.2.3.23"}]}""")
val id = jsonObject.getLong("id")
val mtu = jsonObject.getInt("mtu")
val name = jsonObject.getString("name")
val gateway = jsonObject.getString("gateway")
val nodesArray = jsonObject.getJSONArray("Nodes")
val nodesList = mutableListOf<JSONObject>()
for (i in 0 until nodesArray.length()) {
nodesList.add(nodesArray.getJSONObject(i))
}
val myNodeName = Openp2p.getAndroidNodeName()
// 使用本地 IP 和子网判断是否需要添加路由
val localIps = getLocalIpAndSubnet()
Logger.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}");
val nodeList = nodesList.map {
val nodeName = it.getString("name")
val nodeIp = it.getString("ip")
if (nodeName == myNodeName) {
try {
Logger.i(LOG_TAG, "Attempting to add address: $nodeIp/24")
builder.addAddress(nodeIp, 24) builder.addAddress(nodeIp, 24)
Logger.i(LOG_TAG, "Successfully added address")
} catch (e: Exception) {
Logger.e(LOG_TAG, "Failed to add address $nodeIp: ${e.message}")
throw e // or handle gracefully
} }
val nodeResource = if (it.has("resource")) it.getString("resource") else null }
val parts = nodeResource?.split("/") val nodeResource = it.optString("resource", null)
if (parts?.size == 2) { if (!nodeResource.isNullOrEmpty()) {
val ipAddress = parts[0] // 可能是多个网段,用逗号分隔
val subnetMask = parts[1] val resourceList = nodeResource.split(",")
builder.addRoute(ipAddress, subnetMask.toInt()) for (resource in resourceList) {
Log.i(OpenP2PService.LOG_TAG, "sdwan addRoute:${ipAddress},${subnetMask.toInt()}"); val parts = resource.split("/")
if (parts.size == 2) {
val ipAddress = parts[0].trim()
val subnetMask = parts[1].trim()
// 判断是否属于本机网段
if (!isSameSubnet(ipAddress, localIps)) {
builder.addRoute(ipAddress, subnetMask.toInt())
Logger.i(
OpenP2PService.LOG_TAG,
"sdwan addRoute:${ipAddress},${subnetMask}"
)
} else {
Logger.i(
OpenP2PService.LOG_TAG,
"Skipped adding route for ${ipAddress}, already in local subnet"
)
}
} else {
Logger.w(OpenP2PService.LOG_TAG, "Invalid resource format: $resource")
}
} }
Node(nodeName, nodeIp, nodeResource)
} }
val network = Network(id, name, gateway, nodeList) Node(nodeName, nodeIp, nodeResource)
println(network)
Log.i(OpenP2PService.LOG_TAG, "onBind");
builder.addDnsServer("8.8.8.8")
builder.addRoute("10.2.3.0", 24)
// builder.addRoute("0.0.0.0", 0);
builder.setSession(LOG_TAG!!)
builder.setMtu(1420)
vpnInterface = builder.establish()
if (vpnInterface==null){
Log.e(OpenP2PService.LOG_TAG, "start vpnservice error: ");
}
val outputStream = FileOutputStream(vpnInterface?.fileDescriptor).channel
if (outputStream==null){
Log.e(OpenP2PService.LOG_TAG, "open FileOutputStream error: ");
return@launch
}
val byteArrayWrite = ByteArray(4096)
launch {
readTunLoop()
}
Log.d(LOG_TAG, "write tun loop start")
while (sdwanRunning) {
val len = Openp2p.androidWrite(byteArrayWrite)
Log.i(OpenP2PService.LOG_TAG, String.format("Openp2p.androidWrite: %d",len));
val writeBytes = outputStream?.write(ByteBuffer.wrap(byteArrayWrite))
if (writeBytes != null && writeBytes <= 0) {
Log.i(OpenP2PService.LOG_TAG, "outputStream?.write error: ");
continue
}
}
outputStream.close()
// 关闭 VPN 接口
vpnInterface?.close()
// 置空变量以释放资源
vpnInterface = null
Log.d(LOG_TAG, "write tun loop end")
}catch (e: Exception) {
// 捕获异常并记录
Log.e("VPN Connection", "发生异常: ${e.message}")
} }
Log.i(OpenP2PService.LOG_TAG, "runSDWAN end");
val network = Network(id, name, gateway, nodeList)
println(network)
Logger.i(OpenP2PService.LOG_TAG, "onBind");
builder.addDnsServer("119.29.29.29")
builder.addDnsServer("2400:3200::1") // alicloud dns v6 & v4
// builder.addRoute("10.2.3.0", 24)
// builder.addRoute("0.0.0.0", 0);
val gatewayStr = jsonObject.optString("gateway", "")
val subNet = getNetworkAddress(gatewayStr)
if (subNet != null) {
val (netIp, prefix) = subNet
builder.addRoute(netIp, prefix)
Logger.i(OpenP2PService.LOG_TAG, "Added route from gateway: $netIp/$prefix")
} else {
Logger.w(OpenP2PService.LOG_TAG, "Invalid gateway format: $gatewayStr")
}
builder.setSession(LOG_TAG!!)
builder.setMtu(mtu)
vpnInterface = builder.establish()
if (vpnInterface == null) {
Log.e(OpenP2PService.LOG_TAG, "start vpnservice error: ");
}
val byteArrayWrite = ByteArray(4096)
serviceScope.launch(Dispatchers.IO) {
readTunLoop() // 文件读操作,适合 Dispatchers.IO
}
val outputStream = FileOutputStream(vpnInterface?.fileDescriptor).channel
if (outputStream == null) {
Log.e(OpenP2PService.LOG_TAG, "open FileOutputStream error: ");
return
}
Logger.i(LOG_TAG, "write tun loop start")
while (sdwanRunning) {
val len = Openp2p.androidWrite(byteArrayWrite, 3000)
if (len > mtu || len.toInt() == 0) {
continue
}
try {
val writeBytes =
outputStream?.write(ByteBuffer.wrap(byteArrayWrite, 0, len.toInt()))
if (writeBytes != null && writeBytes <= 0) {
Logger.e(LOG_TAG, "outputStream.write failed: $writeBytes")
}
} catch (e: Exception) {
Logger.e(LOG_TAG, "outputStream.write exception: ${e.message}")
e.printStackTrace()
continue
}
}
outputStream.close()
vpnInterface?.close()
vpnInterface = null
Logger.i(LOG_TAG, "write tun loop end")
} catch (e: Exception) {
Logger.i("VPN Connection", "发生异常: ${e.message}")
} }
Logger.i(OpenP2PService.LOG_TAG, "runSDWAN end");
} }
/**
* 将 "10.2.3.254/16" 这样的 CIDR 转成正确对齐的网络地址,如 "10.2.0.0/16"
*/
fun getNetworkAddress(cidr: String): Pair<String, Int>? {
val parts = cidr.trim().split("/")
if (parts.size != 2) return null
val ip = parts[0]
val prefix = parts[1].toIntOrNull() ?: return null
if (prefix !in 0..32) return null
val octets = ip.split(".").map { it.toInt() }
if (octets.size != 4) return null
// 转成整数
val ipInt = (octets[0] shl 24) or (octets[1] shl 16) or (octets[2] shl 8) or octets[3]
// 生成掩码并计算网络地址
val mask = if (prefix == 0) 0 else (-1 shl (32 - prefix))
val networkInt = ipInt and mask
// 转回点分十进制
val networkIp = listOf(
(networkInt shr 24) and 0xFF,
(networkInt shr 16) and 0xFF,
(networkInt shr 8) and 0xFF,
networkInt and 0xFF
).joinToString(".")
return networkIp to prefix
}
override fun onDestroy() { override fun onDestroy() {
Log.i(LOG_TAG, "onDestroy - Thread ID = " + Thread.currentThread().id)
super.onDestroy() super.onDestroy()
Logger.i(LOG_TAG, "onDestroy - Canceling service scope")
serviceScope.cancel() // 取消所有与服务相关的协程
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
Log.i(LOG_TAG, "onUnbind - Thread ID = " + Thread.currentThread().id) Logger.i(LOG_TAG, "onUnbind - Thread ID = " + Thread.currentThread().id)
stopSelf() stopSelf()
return super.onUnbind(intent) return super.onUnbind(intent)
} }
fun isConnected(): Boolean { fun isConnected(): Boolean {
if (!::network.isInitialized) return false if (!::network.isInitialized) return false
return network.connect(1000) return network.connect(1000)
} }
fun stop() { fun stop() {
running=false running = false
stopSelf() stopSelf()
Openp2p.stop() Openp2p.stop()
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String? { private fun createNotificationChannel(channelId: String, channelName: String): String? {
val chan = NotificationChannel( val chan = NotificationChannel(
@@ -281,3 +399,54 @@ class OpenP2PService : VpnService() {
return channelId return channelId
} }
} }
// 获取本机所有IP地址和对应的子网信息
fun getLocalIpAndSubnet(): List<Pair<String, Int>> {
val localIps = mutableListOf<Pair<String, Int>>()
val networkInterfaces = NetworkInterface.getNetworkInterfaces()
// 手动添加测试数据
//localIps.add(Pair("192.168.3.33", 24))
while (networkInterfaces.hasMoreElements()) {
val networkInterface = networkInterfaces.nextElement()
if (networkInterface.isUp && !networkInterface.isLoopback) {
val interfaceAddresses = networkInterface.interfaceAddresses
for (interfaceAddress in interfaceAddresses) {
val address = interfaceAddress.address
val prefixLength = interfaceAddress.networkPrefixLength
if (address is InetAddress) {
localIps.add(Pair(address.hostAddress, prefixLength.toInt()))
}
}
}
}
return localIps
}
// 判断某个IP是否与本机某网段匹配
fun isSameSubnet(ipAddress: String, localIps: List<Pair<String, Int>>): Boolean {
val targetIp = InetAddress.getByName(ipAddress).address
for ((localIp, prefixLength) in localIps) {
val localIpBytes = InetAddress.getByName(localIp).address
val mask = createSubnetMask(prefixLength, localIpBytes.size) // 动态生成掩码
// 比较目标 IP 和本地 IP 的网络部分
if (targetIp.indices.all { i ->
(targetIp[i].toInt() and mask[i].toInt()) == (localIpBytes[i].toInt() and mask[i].toInt())
}) {
return true
}
}
return false
}
// 根据前缀长度动态生成子网掩码
fun createSubnetMask(prefixLength: Int, addressLength: Int): ByteArray {
val mask = ByteArray(addressLength)
for (i in 0 until prefixLength / 8) {
mask[i] = 0xFF.toByte()
}
if (prefixLength % 8 != 0) {
mask[prefixLength / 8] = (0xFF shl (8 - (prefixLength % 8))).toByte()
}
return mask
}
+79 -33
View File
@@ -1,45 +1,91 @@
package cn.openp2p package cn.openp2p
import android.content.* import android.util.Log
import java.text.SimpleDateFormat
import java.io.BufferedWriter
import java.io.File import java.io.File
import java.io.FileWriter import java.io.FileWriter
import java.text.SimpleDateFormat
import java.util.*
import android.app.*
import android.content.Context
import android.content.Intent
import android.graphics.Color
import java.io.IOException import java.io.IOException
import android.net.VpnService import java.util.Date
import android.os.Binder import java.util.Locale
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import cn.openp2p.ui.login.LoginActivity
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import openp2p.Openp2p
import java.io.FileInputStream
import java.io.FileOutputStream
import java.nio.ByteBuffer
import kotlinx.coroutines.*
import org.json.JSONObject
object Logger { object Logger {
private val logFile: File = File("app.log") private const val LOG_TAG = "OpenP2PLogger"
private var logFile: File? = null
private var bufferedWriter: BufferedWriter? = null
fun log(message: String) { // 初始化日志文件
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) fun init(logDir: File, logFileName: String = "app.log") {
val logMessage = "$timestamp: $message\n" if (!logDir.exists()) logDir.mkdirs()
logFile = File(logDir, logFileName)
try { try {
val fileWriter = FileWriter(logFile, true) bufferedWriter = BufferedWriter(FileWriter(logFile, true))
fileWriter.append(logMessage) } catch (e: IOException) {
fileWriter.close() Log.e(LOG_TAG, "Failed to initialize BufferedWriter: ${e.message}")
} catch (e: Exception) {
e.printStackTrace()
} }
} }
// 写日志(线程安全)
@Synchronized
fun log(level: String, tag: String, message: String, throwable: Throwable? = null) {
val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date())
val logMessage = "$timestamp $level $tag: $message"
// 打印到 console
when (level) {
"ERROR" -> Log.e(tag, message, throwable)
"WARN" -> Log.w(tag, message, throwable)
"INFO" -> Log.i(tag, message)
"DEBUG" -> Log.d(tag, message)
"VERBOSE" -> Log.v(tag, message)
}
// 写入文件
try {
bufferedWriter?.apply {
write(logMessage)
newLine()
flush()
}
throwable?.let {
bufferedWriter?.apply {
write(Log.getStackTraceString(it))
newLine()
flush()
}
}
} catch (e: IOException) {
Log.e(LOG_TAG, "Failed to write log to file: ${e.message}")
}
}
// 清理资源
fun close() {
try {
bufferedWriter?.close()
} catch (e: IOException) {
Log.e(LOG_TAG, "Failed to close BufferedWriter: ${e.message}")
}
}
// 简化方法
fun e(tag: String, message: String, throwable: Throwable? = null) {
log("ERROR", tag, message, throwable)
}
fun w(tag: String, message: String, throwable: Throwable? = null) {
log("WARN", tag, message, throwable)
}
fun i(tag: String, message: String) {
log("INFO", tag, message)
}
fun d(tag: String, message: String) {
log("DEBUG", tag, message)
}
fun v(tag: String, message: String) {
log("VERBOSE", tag, message)
}
} }
+8
View File
@@ -24,6 +24,14 @@ allprojects {
jcenter() // Warning: this repository is going to shut down soon jcenter() // Warning: this repository is going to shut down soon
} }
} }
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
+3 -1
View File
@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # 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 # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@@ -17,3 +17,5 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete": # Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official kotlin.code.style=official
org.gradle.caching=true
+2 -2
View File
@@ -1,9 +1,9 @@
package main package main
import ( import (
op "openp2p/core" op2p "openp2p/core"
) )
func main() { func main() {
op.Run() op2p.Run()
} }
+127 -4
View File
@@ -2,18 +2,22 @@ package openp2p
import ( import (
"bytes" "bytes"
"context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/tls" "crypto/tls"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"math"
"math/big" "math/big"
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -128,19 +132,19 @@ func netInfo() *NetInfo {
client := &http.Client{Transport: tr, Timeout: time.Second * 10} client := &http.Client{Transport: tr, Timeout: time.Second * 10}
r, err := client.Get("https://ifconfig.co/json") r, err := client.Get("https://ifconfig.co/json")
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "netInfo error:", err) gLog.d("netInfo error:%s", err)
continue continue
} }
defer r.Body.Close() defer r.Body.Close()
buf := make([]byte, 1024*64) buf := make([]byte, 1024*64)
n, err := r.Body.Read(buf) n, err := r.Body.Read(buf)
if err != nil { if err != nil && err != io.EOF {
gLog.Println(LvDEBUG, "netInfo error:", err) gLog.d("error reading response body: %s", err)
continue continue
} }
rsp := NetInfo{} rsp := NetInfo{}
if err = json.Unmarshal(buf[:n], &rsp); err != nil { if err = json.Unmarshal(buf[:n], &rsp); err != nil {
gLog.Printf(LvERROR, "wrong NetInfo:%s", err) gLog.d("wrong NetInfo:%s", err)
continue continue
} }
return &rsp return &rsp
@@ -280,3 +284,122 @@ func calculateChecksum(data []byte) uint16 {
return uint16(^sum) return uint16(^sum)
} }
func min(nums ...int32) int32 {
if len(nums) == 0 {
return 0 // 如果没有输入,返回最大值
}
minVal := nums[0]
for _, num := range nums[1:] {
if num < minVal {
minVal = num
}
}
return minVal
}
func calcRetryTimeRelay(x float64) float64 {
return 10 + math.Exp(0.8*(x-3.6))
}
func calcRetryTimeDirect(x float64) float64 {
return 10 + math.Exp(2.8*(x-4))
}
func isAndroid() bool {
if runtime.GOOS == "android" {
return true
}
data, err := os.ReadFile("/proc/version")
if err != nil {
return false
}
return strings.Contains(string(data), "Android")
}
func moveFile(src, dst string) error {
err := os.Rename(src, dst)
if err == nil {
return nil
}
// windows could not rename running executable, so copy then delete
if runtime.GOOS == "windows" {
err = copyFile(src, dst)
if err != nil {
return err
}
os.Remove(src)
}
return nil
}
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
if err != nil {
return err
}
return destFile.Sync()
}
func resolveServerIP(host string) ([]string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 先系统 DNS
ips, err := net.DefaultResolver.LookupHost(ctx, host)
if err == nil && len(ips) > 0 {
gLog.i("system dns resolved %s -> %v", host, ips)
return ips, nil
}
gLog.e("system dns resolve failed for %s: %v", host, err)
gLog.i("retry with fallback dns...")
// 再 fallback dns
return lookupWithCustomDNS(ctx, host)
}
func lookupWithCustomDNS(ctx context.Context, domain string) ([]string, error) {
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
dialer := &net.Dialer{Timeout: 5 * time.Second}
// 先 119.29.29.29
conn, err := dialer.DialContext(ctx, network, "119.29.29.29:53")
if err == nil {
return conn, nil
}
// 再 8.8.8.8
return dialer.DialContext(ctx, network, "8.8.8.8:53")
},
}
return resolver.LookupHost(ctx, domain)
}
func writeFull(w io.Writer, data []byte) error {
totalWritten := 0
for totalWritten < len(data) {
n, err := w.Write(data[totalWritten:])
if err != nil {
return fmt.Errorf("write failed after %d bytes: %w", totalWritten, err)
}
totalWritten += n
}
return nil
}
+69
View File
@@ -1,6 +1,7 @@
package openp2p package openp2p
import ( import (
"fmt"
"log" "log"
"testing" "testing"
) )
@@ -114,3 +115,71 @@ func TestIsIPv6(t *testing.T) {
} }
} }
} }
func TestNodeID(t *testing.T) {
node1 := "n1-stable"
node2 := "tony-stable"
nodeID1 := NodeNameToID(node1)
nodeID2 := NodeNameToID(node2)
if nodeID1 < nodeID2 {
fmt.Printf("%s < %s\n", node1, node2)
} else {
fmt.Printf("%s >= %s\n", node1, node2)
}
}
func TestCalcRetryTime(t *testing.T) {
// 0-2 < 13s
// 3-5:300
// 6-10:600
tests := []struct {
retryNum float64
want float64
}{
{1.0, 10},
{5.0, 13},
{10.0, 180},
{15.0, 9000},
{18.0, 90000},
// 可以添加更多测试用例
}
for _, tt := range tests {
got := calcRetryTimeRelay(tt.retryNum)
if got < tt.want*0.85 || got > tt.want*1.15 {
t.Errorf("calcRetryTime(%f) = %f, want %f", tt.retryNum, got, tt.want)
}
}
for i := 0; i < 20; i++ {
log.Printf("%d retryTime=%fs", i, calcRetryTimeRelay(float64(i)))
}
}
func TestCalcRetryTimeDirect(t *testing.T) {
// 0-2 < 13s
// 3-5:300
// 6-10:600
tests := []struct {
retryNum float64
want float64
}{
{1.0, 10},
{5.0, 13},
{10.0, 180},
{15.0, 9000},
{18.0, 90000},
// 可以添加更多测试用例
}
for _, tt := range tests {
got := calcRetryTimeRelay(tt.retryNum)
if got < tt.want*0.85 || got > tt.want*1.15 {
t.Errorf("calcRetryTime(%f) = %f, want %f", tt.retryNum, got, tt.want)
}
}
for i := 0; i < 20; i++ {
log.Printf("%d retryTime=%fs", i, calcRetryTimeDirect(float64(i)))
}
}
+164 -119
View File
@@ -3,6 +3,7 @@ package openp2p
import ( import (
"encoding/json" "encoding/json"
"flag" "flag"
"fmt"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@@ -28,6 +29,7 @@ type AppConfig struct {
ForceRelay int // default:0 disable;1 enable ForceRelay int // default:0 disable;1 enable
Enabled int // default:1 Enabled int // default:1
// runtime info // runtime info
relayMode string // private|public
peerVersion string peerVersion string
peerToken uint64 peerToken uint64
peerNatType int peerNatType int
@@ -49,9 +51,10 @@ type AppConfig struct {
} }
const ( const (
PunchPriorityTCPFirst = 1 PunchPriorityUDPFirst = 0
PunchPriorityUDPDisable = 1 << 1 PunchPriorityTCPFirst = 1
PunchPriorityTCPDisable = 1 << 2 PunchPriorityTCPOnly = 1 << 1
PunchPriorityUDPOnly = 1 << 2
) )
func (c *AppConfig) ID() uint64 { func (c *AppConfig) ID() uint64 {
@@ -64,17 +67,28 @@ func (c *AppConfig) ID() uint64 {
return uint64(c.SrcPort)*10 + 1 return uint64(c.SrcPort)*10 + 1
} }
func (c *AppConfig) LogPeerNode() string {
if c.relayMode == "public" { // memapp
return fmt.Sprintf("%d", NodeNameToID(c.PeerNode))
}
return c.PeerNode
}
type Config struct { type Config struct {
Network NetworkConfig `json:"network"` Network NetworkConfig `json:"network"`
Apps []*AppConfig `json:"apps"` Apps []*AppConfig `json:"apps"`
LogLevel int LogLevel int
daemonMode bool MaxLogSize int
mtx sync.Mutex TLSInsecureSkipVerify bool
sdwanMtx sync.Mutex Forcev6 bool
sdwan SDWANInfo daemonMode bool
delNodes []SDWANNode mtx sync.RWMutex
addNodes []SDWANNode fileMtx sync.Mutex
sdwanMtx sync.Mutex
sdwan SDWANInfo
delNodes []*SDWANNode
addNodes []*SDWANNode
} }
func (c *Config) getSDWAN() SDWANInfo { func (c *Config) getSDWAN() SDWANInfo {
@@ -83,23 +97,34 @@ func (c *Config) getSDWAN() SDWANInfo {
return c.sdwan return c.sdwan
} }
func (c *Config) getDelNodes() []SDWANNode { func (c *Config) getDelNodes() []*SDWANNode {
c.sdwanMtx.Lock() c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock() defer c.sdwanMtx.Unlock()
return c.delNodes return c.delNodes
} }
func (c *Config) getAddNodes() []SDWANNode { func (c *Config) getAddNodes() []*SDWANNode {
c.sdwanMtx.Lock() c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock() defer c.sdwanMtx.Unlock()
return c.addNodes return c.addNodes
} }
func (c *Config) resetSDWAN() {
c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock()
c.delNodes = []*SDWANNode{}
c.addNodes = []*SDWANNode{}
c.sdwan = SDWANInfo{}
}
func (c *Config) setSDWAN(s SDWANInfo) { func (c *Config) setSDWAN(s SDWANInfo) {
c.sdwanMtx.Lock() c.sdwanMtx.Lock()
defer c.sdwanMtx.Unlock() defer c.sdwanMtx.Unlock()
allNew := false
if c.sdwan.GetResourceByNodeName(c.Network.Node) != s.GetResourceByNodeName(c.Network.Node) {
allNew = true
}
// get old-new // get old-new
c.delNodes = []SDWANNode{} c.delNodes = []*SDWANNode{}
for _, oldNode := range c.sdwan.Nodes { for _, oldNode := range c.sdwan.Nodes {
isDeleted := true isDeleted := true
for _, newNode := range s.Nodes { for _, newNode := range s.Nodes {
@@ -108,12 +133,12 @@ func (c *Config) setSDWAN(s SDWANInfo) {
break break
} }
} }
if isDeleted { if isDeleted || allNew {
c.delNodes = append(c.delNodes, oldNode) c.delNodes = append(c.delNodes, oldNode)
} }
} }
// get new-old // get new-old
c.addNodes = []SDWANNode{} c.addNodes = []*SDWANNode{}
for _, newNode := range s.Nodes { for _, newNode := range s.Nodes {
isNew := true isNew := true
for _, oldNode := range c.sdwan.Nodes { for _, oldNode := range c.sdwan.Nodes {
@@ -122,11 +147,17 @@ func (c *Config) setSDWAN(s SDWANInfo) {
break break
} }
} }
if isNew { if isNew || allNew {
c.addNodes = append(c.addNodes, newNode) c.addNodes = append(c.addNodes, newNode)
} }
} }
c.sdwan = s c.sdwan = s
if c.sdwan.TunnelNum < 2 {
c.sdwan.TunnelNum = 2 // DEBUG
}
// if c.sdwan.TunnelNum > 3 {
// c.sdwan.TunnelNum = 3
// }
} }
func (c *Config) switchApp(app AppConfig, enabled int) { func (c *Config) switchApp(app AppConfig, enabled int) {
@@ -143,26 +174,16 @@ func (c *Config) switchApp(app AppConfig, enabled int) {
c.save() c.save()
} }
// TODO: move to p2pnetwork
func (c *Config) retryApp(peerNode string) { func (c *Config) retryApp(peerNode string) {
GNetwork.apps.Range(func(id, i interface{}) bool { GNetwork.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp) app := i.(*p2pApp)
if app.config.PeerNode == peerNode { if app.config.PeerNode == peerNode {
gLog.Println(LvDEBUG, "retry app ", peerNode) app.Retry(true)
app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
} }
if app.config.RelayNode == peerNode { if app.config.RelayNode == peerNode {
gLog.Println(LvDEBUG, "retry app ", peerNode) app.Retry(false)
app.retryRelayNum = 0 gLog.d("retry app relay=%s", app.config.LogPeerNode())
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
} }
return true return true
}) })
@@ -171,14 +192,7 @@ func (c *Config) retryApp(peerNode string) {
func (c *Config) retryAllApp() { func (c *Config) retryAllApp() {
GNetwork.apps.Range(func(id, i interface{}) bool { GNetwork.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp) app := i.(*p2pApp)
gLog.Println(LvDEBUG, "retry app ", app.config.PeerNode) app.Retry(true)
app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.retryRelayNum = 0
app.nextRetryRelayTime = time.Now()
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
return true return true
}) })
} }
@@ -189,22 +203,22 @@ func (c *Config) retryAllMemApp() {
if app.config.SrcPort != 0 { if app.config.SrcPort != 0 {
return true return true
} }
gLog.Println(LvDEBUG, "retry app ", app.config.PeerNode) if app.tunnelNum != int(gConf.sdwan.TunnelNum) {
app.config.retryNum = 0 gLog.d("memapp %s tunnelNum changed from %d to %d, delete it and not retry", app.config.LogPeerNode(), app.tunnelNum, gConf.sdwan.TunnelNum)
app.config.nextRetryTime = time.Now() GNetwork.DeleteApp(app.config)
app.retryRelayNum = 0 return true
app.nextRetryRelayTime = time.Now() }
app.hbMtx.Lock() app.Retry(true)
defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
return true return true
}) })
} }
func (c *Config) add(app AppConfig, override bool) { func (c *Config) add(app AppConfig, override bool) {
if app.AppName == "" {
app.AppName = fmt.Sprintf("%d", app.ID())
}
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
defer c.save()
if override { if override {
for i := 0; i < len(c.Apps); i++ { for i := 0; i < len(c.Apps); i++ {
if c.Apps[i].PeerNode == app.PeerNode && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort { if c.Apps[i].PeerNode == app.PeerNode && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
@@ -214,57 +228,70 @@ func (c *Config) add(app AppConfig, override bool) {
} }
} }
c.Apps = append(c.Apps, &app) c.Apps = append(c.Apps, &app)
if app.SrcPort != 0 {
c.save()
}
} }
func (c *Config) delete(app AppConfig) { func (c *Config) delete(app AppConfig) {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
defer c.save()
for i := 0; i < len(c.Apps); i++ { for i := 0; i < len(c.Apps); i++ {
got := false if (app.SrcPort != 0 && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort) || // normal app
if app.SrcPort != 0 { // normal p2papp (app.SrcPort == 0 && c.Apps[i].SrcPort == 0 && c.Apps[i].PeerNode == app.PeerNode) { // memapp
if c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
got = true
}
} else { // memapp
if c.Apps[i].PeerNode == app.PeerNode {
got = true
}
}
if got {
if i == len(c.Apps)-1 { if i == len(c.Apps)-1 {
c.Apps = c.Apps[:i] c.Apps = c.Apps[:i]
} else { } else {
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...) c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
} }
return break
} }
} }
if app.SrcPort != 0 {
c.save()
}
} }
func (c *Config) save() { func (c *Config) save() {
// c.mtx.Lock() c.fileMtx.Lock()
// defer c.mtx.Unlock() // internal call defer c.fileMtx.Unlock()
data, _ := json.MarshalIndent(c, "", " ") if c.Network.Token == 0 {
err := os.WriteFile("config.json", data, 0644) gLog.e("c.Network.Token == 0 skip save")
if err != nil { return
gLog.Println(LvERROR, "save config.json error:", err)
}
}
func (c *Config) saveCache() {
// c.mtx.Lock()
// defer c.mtx.Unlock() // internal call
data, _ := json.MarshalIndent(c, "", " ")
err := os.WriteFile("config.json0", data, 0644)
if err != nil {
gLog.Println(LvERROR, "save config.json0 error:", err)
} }
data, err := json.MarshalIndent(c, "", " ")
if err != nil || len(data) < 16 {
gLog.e("MarshalIndent config.json error:%v, len=%d", err, len(data))
return
}
err = os.WriteFile("config.json0", data, 0644)
if err != nil {
gLog.e("save config.json error:%v", err)
}
// verify if the file is written correctly
data, err = os.ReadFile("config.json0")
if err != nil {
return
}
var tmpConfig Config
err = json.Unmarshal(data, &tmpConfig)
if err != nil {
gLog.e("parse config.json error:", err)
return
}
err = os.Rename("config.json0", "config.json")
if err != nil {
gLog.e("rename config file error:%v", err)
}
} }
// -d run, then worker serverport always WsPort.
// func init() {
func init() { func init() {
gConf.LogLevel = int(LvINFO) gConf.LogLevel = int(LvINFO)
gConf.MaxLogSize = 1024 * 1024
gConf.Network.ShareBandwidth = 10 gConf.Network.ShareBandwidth = 10
gConf.Network.ServerHost = "api.openp2p.cn" gConf.Network.ServerHost = "api.openp2p.cn"
gConf.Network.ServerPort = WsPort gConf.Network.ServerPort = WsPort
@@ -272,19 +299,19 @@ func init() {
} }
func (c *Config) load() error { func (c *Config) load() error {
c.mtx.Lock() c.fileMtx.Lock()
defer c.mtx.Unlock() defer c.fileMtx.Unlock()
data, err := os.ReadFile("config.json") data, err := os.ReadFile("config.json")
if err != nil { if err != nil {
return c.loadCache() return err
} }
c.mtx.Lock()
defer c.mtx.Unlock()
err = json.Unmarshal(data, &c) err = json.Unmarshal(data, &c)
if err != nil { if err != nil {
gLog.Println(LvERROR, "parse config.json error:", err) gLog.e("parse config.json error:", err)
// try cache return err
return c.loadCache()
} }
// load ok. cache it
var filteredApps []*AppConfig // filter memapp var filteredApps []*AppConfig // filter memapp
for _, app := range c.Apps { for _, app := range c.Apps {
if app.SrcPort != 0 { if app.SrcPort != 0 {
@@ -292,19 +319,7 @@ func (c *Config) load() error {
} }
} }
c.Apps = filteredApps c.Apps = filteredApps
c.saveCache() c.Network.natType = NATUnknown
return err
}
func (c *Config) loadCache() error {
data, err := os.ReadFile("config.json0")
if err != nil {
return err
}
err = json.Unmarshal(data, &c)
if err != nil {
gLog.Println(LvERROR, "parse config.json0 error:", err)
}
return err return err
} }
@@ -312,7 +327,6 @@ func (c *Config) loadCache() error {
func (c *Config) setToken(token uint64) { func (c *Config) setToken(token uint64) {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
defer c.save()
if token != 0 { if token != 0 {
c.Network.Token = token c.Network.Token = token
} }
@@ -320,16 +334,19 @@ func (c *Config) setToken(token uint64) {
func (c *Config) setUser(user string) { func (c *Config) setUser(user string) {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
defer c.save()
c.Network.User = user c.Network.User = user
} }
func (c *Config) setNode(node string) { func (c *Config) setNode(node string) {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
defer c.save()
c.Network.Node = node c.Network.Node = node
c.Network.nodeID = NodeNameToID(c.Network.Node) c.Network.nodeID = NodeNameToID(c.Network.Node)
} }
func (c *Config) setForcev6(force bool) {
c.mtx.Lock()
defer c.mtx.Unlock()
c.Forcev6 = force
}
func (c *Config) nodeID() uint64 { func (c *Config) nodeID() uint64 {
c.mtx.Lock() c.mtx.Lock()
defer c.mtx.Unlock() defer c.mtx.Unlock()
@@ -365,22 +382,27 @@ type NetworkConfig struct {
mac string mac string
os string os string
publicIP string publicIP string
previousIP string // for publicIP change detect
natType int natType int
hasIPv4 int hasIPv4 int
publicIPv6 string // must lowwer-case not save json publicIPv6 string // must lowwer-case not save json
hasUPNPorNATPMP int hasUPNPorNATPMP int
ShareBandwidth int ShareBandwidth int
// server info // server info
ServerHost string ServerHost string
ServerPort int ServerIP string
UDPPort1 int ServerPort int
UDPPort2 int natDetectPort1 int
TCPPort int natDetectPort2 int
PublicIPPort int // both tcp and udp
specTunnel int
} }
func parseParams(subCommand string, cmd string) { func parseParams(subCommand string, cmd string) {
fset := flag.NewFlagSet(subCommand, flag.ExitOnError) fset := flag.NewFlagSet(subCommand, flag.ExitOnError)
installPath := fset.String("installpath", "", "custom install path")
serverHost := fset.String("serverhost", "api.openp2p.cn", "server host ") serverHost := fset.String("serverhost", "api.openp2p.cn", "server host ")
insecure := fset.Bool("insecure", false, "not verify TLS certificate")
serverPort := fset.Int("serverport", WsPort, "server port ") serverPort := fset.Int("serverport", WsPort, "server port ")
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug // serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
token := fset.Uint64("token", 0, "token") token := fset.Uint64("token", 0, "token")
@@ -390,7 +412,7 @@ func parseParams(subCommand string, cmd string) {
whiteList := fset.String("whitelist", "", "whitelist for p2pApp ") whiteList := fset.String("whitelist", "", "whitelist for p2pApp ")
dstPort := fset.Int("dstport", 0, "destination port ") dstPort := fset.Int("dstport", 0, "destination port ")
srcPort := fset.Int("srcport", 0, "source port ") srcPort := fset.Int("srcport", 0, "source port ")
tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip") publicIPPort := fset.Int("publicipport", 0, "public ip port for upnp or publicip")
protocol := fset.String("protocol", "tcp", "tcp or udp") protocol := fset.String("protocol", "tcp", "tcp or udp")
underlayProtocol := fset.String("underlay_protocol", "quic", "quic or kcp") underlayProtocol := fset.String("underlay_protocol", "quic", "quic or kcp")
punchPriority := fset.Int("punch_priority", 0, "bitwise DisableTCP|DisableUDP|UDPFirst 0:tcp and udp both enable, tcp first") punchPriority := fset.Int("punch_priority", 0, "bitwise DisableTCP|DisableUDP|UDPFirst 0:tcp and udp both enable, tcp first")
@@ -409,12 +431,10 @@ func parseParams(subCommand string, cmd string) {
fset.Parse(os.Args[2:]) fset.Parse(os.Args[2:])
} }
} else { } else {
gLog.Println(LvINFO, "cmd=", cmd)
args := strings.Split(cmd, " ") args := strings.Split(cmd, " ")
fset.Parse(args) fset.Parse(args)
} }
gLog.setMaxSize(int64(*maxLogSize))
config := AppConfig{Enabled: 1} config := AppConfig{Enabled: 1}
config.PeerNode = *peerNode config.PeerNode = *peerNode
config.DstHost = *dstIP config.DstHost = *dstIP
@@ -426,6 +446,19 @@ func parseParams(subCommand string, cmd string) {
config.PunchPriority = *punchPriority config.PunchPriority = *punchPriority
config.AppName = *appName config.AppName = *appName
config.RelayNode = *relayNode config.RelayNode = *relayNode
if *installPath != "" {
defaultInstallPath = *installPath
}
if subCommand == "install" {
if err := os.MkdirAll(defaultInstallPath, 0775); err != nil {
gLog.e("parseParams MkdirAll %s error:%s", defaultInstallPath, err)
return
}
if err := os.Chdir(defaultInstallPath); err != nil {
gLog.e("parseParams Chdir error:%s", err)
return
}
}
if !*newconfig { if !*newconfig {
gConf.load() // load old config. otherwise will clear all apps gConf.load() // load old config. otherwise will clear all apps
} }
@@ -448,17 +481,29 @@ func parseParams(subCommand string, cmd string) {
if f.Name == "loglevel" { if f.Name == "loglevel" {
gConf.LogLevel = *logLevel gConf.LogLevel = *logLevel
} }
if f.Name == "tcpport" { if f.Name == "maxlogsize" {
gConf.Network.TCPPort = *tcpPort gConf.MaxLogSize = *maxLogSize
}
if f.Name == "publicipport" {
gConf.Network.PublicIPPort = *publicIPPort
} }
if f.Name == "token" { if f.Name == "token" {
gConf.setToken(*token) gConf.setToken(*token)
} }
if f.Name == "serverport" {
gConf.Network.ServerPort = *serverPort
}
if f.Name == "insecure" {
gConf.TLSInsecureSkipVerify = *insecure
}
}) })
// set default value // set default value
if gConf.Network.ServerHost == "" { if gConf.Network.ServerHost == "" {
gConf.Network.ServerHost = *serverHost gConf.Network.ServerHost = *serverHost
} }
if gConf.Network.ServerPort == 0 {
gConf.Network.ServerPort = *serverPort
}
if *node != "" { if *node != "" {
gConf.setNode(*node) gConf.setNode(*node)
} else { } else {
@@ -470,12 +515,12 @@ func parseParams(subCommand string, cmd string) {
gConf.setNode(defaultNodeName()) gConf.setNode(defaultNodeName())
} }
} }
if gConf.Network.TCPPort == 0 { if gConf.Network.PublicIPPort == 0 {
if *tcpPort == 0 { if *publicIPPort == 0 {
p := int(gConf.nodeID()%15000 + 50000) p := int(gConf.nodeID()%8192 + 1025)
tcpPort = &p publicIPPort = &p
} }
gConf.Network.TCPPort = *tcpPort gConf.Network.PublicIPPort = *publicIPPort
} }
if *token == 0 { if *token == 0 {
envToken := os.Getenv("OPENP2P_TOKEN") envToken := os.Getenv("OPENP2P_TOKEN")
@@ -485,13 +530,13 @@ func parseParams(subCommand string, cmd string) {
} }
} }
} }
gConf.Network.ServerPort = *serverPort
gConf.Network.UDPPort1 = UDPPort1 gConf.Network.natDetectPort1 = NATDetectPort1
gConf.Network.UDPPort2 = UDPPort2 gConf.Network.natDetectPort2 = NATDetectPort2
gLog.setLevel(LogLevel(gConf.LogLevel)) gLog.setLevel(LogLevel(gConf.LogLevel))
gLog.setMaxSize(int64(gConf.MaxLogSize))
if *notVerbose { if *notVerbose {
gLog.setMode(LogFile) gLog.setMode(LogFile)
} }
// gConf.mtx.Unlock()
gConf.save() gConf.save()
} }
+100 -29
View File
@@ -1,9 +1,9 @@
package openp2p package openp2p
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"time" "time"
"github.com/openp2p-cn/service" "github.com/openp2p-cn/service"
@@ -15,34 +15,32 @@ type daemon struct {
} }
func (d *daemon) Start(s service.Service) error { func (d *daemon) Start(s service.Service) error {
gLog.Println(LvINFO, "daemon start") gLog.i("system service start")
return nil return nil
} }
func (d *daemon) Stop(s service.Service) error { func (d *daemon) Stop(s service.Service) error {
gLog.Println(LvINFO, "service stop") gLog.i("system service stop")
d.running = false d.running = false
if d.proc != nil { if d.proc != nil {
gLog.Println(LvINFO, "stop worker") gLog.i("stop worker")
d.proc.Kill() d.proc.Kill()
} }
if service.Interactive() { if service.Interactive() {
gLog.Println(LvINFO, "stop daemon") gLog.i("stop daemon")
os.Exit(0) os.Exit(0)
} }
return nil return nil
} }
func (d *daemon) run() { func (d *daemon) run() {
gLog.Println(LvINFO, "daemon run start") gLog.close()
defer gLog.Println(LvINFO, "daemon run end") baseDir := filepath.Dir(os.Args[0])
gLog = NewLogger(baseDir, "daemon", LogLevel(gConf.LogLevel), 1024*1024, LogFile|LogConsole)
gLog.i("daemon run start")
defer gLog.i("daemon run end")
d.running = true d.running = true
binPath, _ := os.Executable() binPath, _ := os.Executable()
mydir, err := os.Getwd()
if err != nil {
fmt.Println(err)
}
gLog.Println(LvINFO, mydir)
conf := &service.Config{ conf := &service.Config{
Name: ProductName, Name: ProductName,
DisplayName: ProductName, DisplayName: ProductName,
@@ -60,47 +58,67 @@ func (d *daemon) run() {
break break
} }
} }
args = append(args, "-nv") args = append(args, "-nv")
for { for {
// start worker // start worker
tmpDump := filepath.Join("log", "dump.log.tmp") tmpDump := filepath.Join(filepath.Dir(binPath), "log", "dump.log.tmp")
dumpFile := filepath.Join("log", "dump.log") dumpFile := filepath.Join(filepath.Dir(binPath), "log", "dump.log")
f, err := os.Create(filepath.Join(tmpDump)) // f, err := os.Create(filepath.Join(tmpDump))
f, err := os.OpenFile(filepath.Join(tmpDump), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "start worker error:%s", err) gLog.e("OpenFile %s error:%s", tmpDump, err)
return return
} }
gLog.Println(LvINFO, "start worker process, args:", args) gLog.i("start worker process, args:%v", args)
execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}} execSpec := &os.ProcAttr{Env: append(os.Environ(), "GOTRACEBACK=crash"), Files: []*os.File{os.Stdin, os.Stdout, f}}
lastRebootTime := time.Now()
p, err := os.StartProcess(binPath, args, execSpec) p, err := os.StartProcess(binPath, args, execSpec)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "start worker error:%s", err) gLog.e("start worker error:%s", err)
return return
} }
d.proc = p d.proc = p
_, _ = p.Wait() processState, err := p.Wait()
if err != nil {
gLog.e("wait process error:%s", err)
}
if processState != nil {
exitCode := processState.ExitCode()
gLog.i("worker process exited with code: %d", exitCode)
if exitCode == 9 {
gLog.i("worker process update with code: %d", exitCode)
// os.Exit(9) // old client installed system service will not auto restart. fuck
}
}
// Write the current time to the end of the dump file
currentTime := time.Now().Format("2006-01-02 15:04:05")
_, err = f.WriteString("\nProcess ended at: " + currentTime + "\n")
if err != nil {
gLog.e("Failed to write time to dump file: %s", err)
}
f.Close() f.Close()
time.Sleep(time.Second) time.Sleep(time.Second)
err = os.Rename(tmpDump, dumpFile) err = os.Rename(tmpDump, dumpFile)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "rename dump error:%s", err) gLog.e("rename dump error:%s", err)
} }
if !d.running { if !d.running {
return return
} }
gLog.Printf(LvERROR, "worker stop, restart it after 10s") if time.Since(lastRebootTime) < time.Second*10 {
time.Sleep(time.Second * 10) gLog.e("worker stop, restart it after 10s")
time.Sleep(time.Second * 10)
}
} }
} }
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error { func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
svcConfig := &service.Config{ svcConfig := getServiceConfig(exeAbsPath, args)
Name: ProductName,
DisplayName: ProductName,
Description: ProductName,
Executable: exeAbsPath,
Arguments: args,
}
s, e := service.New(d, svcConfig) s, e := service.New(d, svcConfig)
if e != nil { if e != nil {
@@ -113,3 +131,56 @@ func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) erro
return nil return nil
} }
func getServiceConfig(exeAbsPath string, args []string) *service.Config {
config := &service.Config{
Name: ProductName,
DisplayName: ProductName,
Description: ProductName,
Executable: exeAbsPath,
Arguments: args,
Option: make(map[string]interface{}),
}
if runtime.GOOS == "windows" {
setupWindowsConfig(config)
} else {
setupLinuxConfig(config)
}
return config
}
func setupWindowsConfig(config *service.Config) {
failureActions := []map[string]interface{}{
{
"Type": "restart",
"Delay": "10000",
},
{
"Type": "restart",
"Delay": "10000",
},
{
"Type": "restart",
"Delay": "10000",
},
}
config.Option = map[string]interface{}{
"OnFailure": "restart",
"OnFailureDelay": "10s",
"OnFailureResetPeriod": "3600",
"FailureActions": failureActions,
"DelayedAutoStart": true,
}
}
func setupLinuxConfig(config *service.Config) {
config.Option = map[string]interface{}{
"Restart": "always",
"RestartSec": "10",
"StartLimitBurst": 64,
"SuccessExitStatus": "1 2 8 SIGKILL",
}
}
+4
View File
@@ -29,4 +29,8 @@ var (
ErrPeerConnectRelay = errors.New("peer connect relayNode error") ErrPeerConnectRelay = errors.New("peer connect relayNode error")
ErrBuildTunnelBusy = errors.New("build tunnel busy") ErrBuildTunnelBusy = errors.New("build tunnel busy")
ErrMemAppTunnelNotFound = errors.New("memapp tunnel not found") ErrMemAppTunnelNotFound = errors.New("memapp tunnel not found")
ErrRemoteServiceUnable = errors.New("remote service unable")
ErrAppWithoutTunnel = errors.New("p2papp has no available tunnel")
ErrWriteWindowFull = errors.New("writeWindow full")
ErrHeaderDataLen = errors.New("header datalen error")
) )
+225 -107
View File
@@ -4,11 +4,14 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"net"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"runtime" "runtime"
"runtime/pprof"
"time" "time"
"github.com/openp2p-cn/totp" "github.com/openp2p-cn/totp"
@@ -20,119 +23,141 @@ func handlePush(subType uint16, msg []byte) error {
if err != nil { if err != nil {
return err return err
} }
gLog.Printf(LvDEBUG, "handle push msg type:%d, push header:%+v", subType, pushHead) // gLog.d("handle push msg type:%d, push header:%+v", subType, pushHead)
switch subType { switch subType {
case MsgPushConnectReq: case MsgPushConnectReq:
err = handleConnectReq(msg) err = handleConnectReq(msg)
case MsgPushRsp: case MsgPushRsp:
rsp := PushRsp{} rsp := PushRsp{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
gLog.Printf(LvERROR, "wrong pushRsp:%s", err) gLog.e("Unmarshal pushRsp:%s", err)
return err return err
} }
if rsp.Error == 0 { if rsp.Error == 0 {
gLog.Printf(LvDEBUG, "push ok, detail:%s", rsp.Detail) gLog.dev("push ok, detail:%s", rsp.Detail)
} else { } else {
gLog.Printf(LvERROR, "push error:%d, detail:%s", rsp.Error, rsp.Detail) gLog.e("push error:%d, detail:%s", rsp.Error, rsp.Detail)
} }
case MsgPushAddRelayTunnelReq: case MsgPushAddRelayTunnelReq:
req := AddRelayTunnelReq{} req := AddRelayTunnelReq{}
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) gLog.e("Unmarshal %v:%s", reflect.TypeOf(req), err)
return err return err
} }
config := AppConfig{} config := AppConfig{}
config.PeerNode = req.RelayName config.PeerNode = req.RelayName
config.peerToken = req.RelayToken config.peerToken = req.RelayToken
config.relayMode = req.RelayMode
config.PunchPriority = req.PunchPriority
config.UnderlayProtocol = req.UnderlayProtocol
go func(r AddRelayTunnelReq) { go func(r AddRelayTunnelReq) {
t, errDt := GNetwork.addDirectTunnel(config, 0) t, errDt := GNetwork.addDirectTunnel(config, 0, nil)
if errDt == nil { if errDt == nil && t != nil {
// notify peer relay ready // notify peer relay ready
msg := TunnelMsg{ID: t.id} msg := TunnelMsg{ID: t.id}
GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, msg) GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, msg)
appConfig := config appConfig := config
appConfig.PeerNode = req.From appConfig.PeerNode = req.From
} else { } else {
gLog.Printf(LvERROR, "addDirectTunnel error:%s", errDt) gLog.w("addDirectTunnel error:%s", errDt)
GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, "error") // compatible with old version client, trigger unmarshal error GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, "error") // compatible with old version client, trigger unmarshal error
} }
}(req) }(req)
case MsgPushServerSideSaveMemApp: case MsgPushServerSideSaveMemApp:
req := ServerSideSaveMemApp{} req := ServerSideSaveMemApp{}
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) gLog.e("Unmarshal %v:%s", reflect.TypeOf(req), err)
return err return err
} }
gLog.Println(LvDEBUG, "handle MsgPushServerSideSaveMemApp:", prettyJson(req)) gLog.d("handle MsgPushServerSideSaveMemApp:%s", prettyJson(req))
if req.RelayIndex > uint32(gConf.sdwan.TunnelNum-1) {
return errors.New("wrong relay index")
}
var existTunnel *P2PTunnel var existTunnel *P2PTunnel
i, ok := GNetwork.allTunnels.Load(req.TunnelID) i, ok := GNetwork.allTunnels.Load(req.TunnelID)
if !ok { if !ok {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 3000)
i, ok = GNetwork.allTunnels.Load(req.TunnelID) // retry sometimes will receive MsgPushServerSideSaveMemApp but p2ptunnel not store yet. i, ok = GNetwork.allTunnels.Load(req.TunnelID) // retry sometimes will receive MsgPushServerSideSaveMemApp but p2ptunnel not store yet.
if !ok { if !ok {
gLog.Println(LvERROR, "handle MsgPushServerSideSaveMemApp error:", ErrMemAppTunnelNotFound) gLog.e("handle MsgPushServerSideSaveMemApp error:%s", ErrMemAppTunnelNotFound)
return ErrMemAppTunnelNotFound return ErrMemAppTunnelNotFound
} }
} }
existTunnel = i.(*P2PTunnel) existTunnel = i.(*P2PTunnel)
peerID := NodeNameToID(req.From) peerID := NodeNameToID(req.From)
existApp, appok := GNetwork.apps.Load(peerID) appIdx := peerID
if req.SrcPort != 0 {
appIdx = req.AppID
}
existApp, appok := GNetwork.apps.Load(appIdx)
var app *p2pApp
if appok { if appok {
app := existApp.(*p2pApp) app = existApp.(*p2pApp)
if app.tunnelNum != int(req.TunnelNum) {
gLog.d("memapp tunnelNum changed from %d to %d", app.tunnelNum, req.TunnelNum)
GNetwork.DeleteApp(app.config)
app = nil
}
}
if app != nil {
app.config.AppName = fmt.Sprintf("%d", peerID) app.config.AppName = fmt.Sprintf("%d", peerID)
app.id = req.AppID app.id = req.AppID
app.setRelayTunnelID(req.RelayTunnelID) app.key = req.AppKey
app.relayMode = req.RelayMode app.PreCalcKeyBytes()
app.hbTimeRelay = time.Now() app.relayMode[req.RelayIndex] = req.RelayMode
if req.RelayTunnelID == 0 { app.hbTime[req.RelayIndex] = time.Now()
app.setDirectTunnel(existTunnel) app.SetTunnel(existTunnel, int(req.RelayIndex))
} else { if req.RelayTunnelID != 0 {
app.setRelayTunnel(existTunnel) app.SetRelayTunnelID(req.RelayTunnelID, int(req.RelayIndex)) // direct tunnel rtid=0, no need set rtid
} }
gLog.Println(LvDEBUG, "find existing memapp, update it") gLog.d("found existing memapp, update it")
} else { } else {
appConfig := existTunnel.config appConfig := existTunnel.config
appConfig.SrcPort = 0 appConfig.SrcPort = int(req.SrcPort)
appConfig.Protocol = "" appConfig.Protocol = ""
appConfig.AppName = fmt.Sprintf("%d", peerID) appConfig.AppName = fmt.Sprintf("%d", peerID)
appConfig.PeerNode = req.From appConfig.PeerNode = req.From
app := p2pApp{ app = &p2pApp{
id: req.AppID, id: req.AppID,
config: appConfig, config: appConfig,
relayMode: req.RelayMode, running: true,
running: true, // asyncWriteChan: make(chan []byte, WriteDataChanSize),
hbTimeRelay: time.Now(), key: req.AppKey,
} }
if req.RelayTunnelID == 0 { app.PreCalcKeyBytes()
app.setDirectTunnel(existTunnel) tunnelNum := 2
} else { if req.TunnelNum > uint32(tunnelNum) {
app.setRelayTunnel(existTunnel) tunnelNum = int(req.TunnelNum)
app.setRelayTunnelID(req.RelayTunnelID)
} }
app.Init(tunnelNum)
app.relayMode[req.RelayIndex] = req.RelayMode
app.hbTime[req.RelayIndex] = time.Now()
app.SetTunnel(existTunnel, int(req.RelayIndex))
if req.RelayTunnelID != 0 { if req.RelayTunnelID != 0 {
app.relayNode = req.Node app.SetRelayTunnelID(req.RelayTunnelID, int(req.RelayIndex))
app.relayNode[req.RelayIndex] = req.Node
} }
GNetwork.apps.Store(NodeNameToID(req.From), &app) app.Start(false)
GNetwork.apps.Store(appIdx, app)
gLog.d("store memapp %d %d", appIdx, req.SrcPort)
} }
return nil return nil
case MsgPushAPPKey:
req := APPKeySync{}
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
return err
}
SaveKey(req.AppID, req.AppKey)
case MsgPushUpdate: case MsgPushUpdate:
gLog.Println(LvINFO, "MsgPushUpdate") gLog.i("MsgPushUpdate")
err := update(gConf.Network.ServerHost, gConf.Network.ServerPort) err := update(gConf.Network.ServerHost, gConf.Network.ServerPort)
if err == nil { if err == nil {
os.Exit(0) if !isAndroid() {
os.Exit(9) // 9 tell daemon this exit because of update
}
} }
return err return err
case MsgPushRestart: case MsgPushRestart:
gLog.Println(LvINFO, "MsgPushRestart") gLog.i("MsgPushRestart")
os.Exit(0) if !isAndroid() {
os.Exit(0)
}
return err return err
case MsgPushReportApps: case MsgPushReportApps:
err = handleReportApps() err = handleReportApps()
@@ -142,41 +167,62 @@ func handlePush(subType uint16, msg []byte) error {
err = handleLog(msg) err = handleLog(msg)
case MsgPushReportGoroutine: case MsgPushReportGoroutine:
err = handleReportGoroutine() err = handleReportGoroutine()
case MsgPushReportHeap:
err = handleReportHeap()
case MsgPushCheckRemoteService:
err = handleCheckRemoteService(msg)
case MsgPushEditApp: case MsgPushEditApp:
err = handleEditApp(msg) err = handleEditApp(msg)
case MsgPushEditNode: case MsgPushEditNode:
gLog.Println(LvINFO, "MsgPushEditNode") gLog.i("MsgPushEditNode")
req := EditNode{} req := EditNode{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
return err return err
} }
gConf.setNode(req.NewName) gConf.setNode(req.NewName)
gConf.setShareBandwidth(req.Bandwidth) gConf.setShareBandwidth(req.Bandwidth)
if req.PublicIPPort != 0 {
gConf.Network.PublicIPPort = req.PublicIPPort
}
gConf.Forcev6 = (req.Forcev6 != 0)
gLog.i("set forcev6 to %v", gConf.Forcev6)
gConf.save()
os.Exit(0) os.Exit(0)
case MsgPushSwitchApp: case MsgPushSwitchApp:
gLog.Println(LvINFO, "MsgPushSwitchApp") gLog.i("MsgPushSwitchApp")
app := AppInfo{} app := AppInfo{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &app); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &app); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:])) gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:]))
return err return err
} }
config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol} config := AppConfig{PeerNode: app.PeerNode, Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
gLog.Println(LvINFO, app.AppName, " switch to ", app.Enabled) gLog.i("%s switch to %d", app.AppName, app.Enabled)
gConf.switchApp(config, app.Enabled) gConf.switchApp(config, app.Enabled)
if app.Enabled == 0 { if app.Enabled == 0 {
// disable APP // disable APP
GNetwork.DeleteApp(config) GNetwork.DeleteApp(config)
} }
case MsgPushDstNodeOnline: case MsgPushDstNodeOnline:
gLog.Println(LvINFO, "MsgPushDstNodeOnline")
req := PushDstNodeOnline{} req := PushDstNodeOnline{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
return err return err
} }
gLog.Println(LvINFO, "retry peerNode ", req.Node) gLog.i("%s online, retryApp", req.Node)
gConf.retryApp(req.Node) gConf.retryApp(req.Node)
case MsgPushSpecTunnel:
req := SpecTunnel{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
return err
}
gLog.i("SpecTunnel %d", req.TunnelIndex)
gConf.Network.specTunnel = int(req.TunnelIndex)
case MsgPushSDWanRefresh:
GNetwork.write(MsgSDWAN, MsgSDWANInfoReq, nil)
case MsgPushNat4Detect:
handleNat4Detect(msg)
default: default:
i, ok := GNetwork.msgMap.Load(pushHead.From) i, ok := GNetwork.msgMap.Load(pushHead.From)
if !ok { if !ok {
@@ -188,11 +234,50 @@ func handlePush(subType uint16, msg []byte) error {
return err return err
} }
func handleNat4Detect(msg []byte) (err error) {
gLog.d("handleNat4Detect")
nd := Nat4Detect{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &nd); err != nil {
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(nd), err, string(msg[openP2PHeaderSize:]))
return err
}
detectNatPort := func(protocol, server string, serverPort, localPort int) int {
natPort := 0
if protocol == "tcp" {
_, natPort, _, _ = natDetectTCP(server, serverPort, localPort)
} else {
_, natPort, _ = natDetectUDP(server, serverPort, localPort)
}
// gLog.i("%s %s %d %d %d", protocol, server, serverPort, localPort, natPort)
return natPort
}
result := ""
if nd.Num > 0 {
for i := 0; i < int(nd.Num); i++ {
natPort := detectNatPort(nd.Protocol, nd.Server, int(nd.ServerPort), int(nd.LocalPort)+i)
if i > 0 {
result += ","
}
result += fmt.Sprintf("%d", natPort)
}
} else {
for idx, item := range nd.CustomData {
natPort := detectNatPort(item.Protocol, item.Server, int(item.ServerPort), int(item.LocalPort))
if idx > 0 {
result += ","
}
result += fmt.Sprintf("%d", natPort)
}
}
return GNetwork.write(MsgReport, MsgPushReportLog, &result)
}
func handleEditApp(msg []byte) (err error) { func handleEditApp(msg []byte) (err error) {
gLog.Println(LvINFO, "MsgPushEditApp") gLog.i("MsgPushEditApp")
newApp := AppInfo{} newApp := AppInfo{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &newApp); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &newApp); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:])) gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:]))
return err return err
} }
oldConf := AppConfig{Enabled: 1} oldConf := AppConfig{Enabled: 1}
@@ -208,13 +293,16 @@ func handleEditApp(msg []byte) (err error) {
gConf.delete(oldConf) gConf.delete(oldConf)
} }
// AddApp if newApp.SrcPort != 0 { // delete app
newConf := oldConf // AddApp
newConf.Protocol = newApp.Protocol newConf := oldConf
newConf.SrcPort = newApp.SrcPort newConf.Protocol = newApp.Protocol
newConf.RelayNode = newApp.SpecRelayNode newConf.SrcPort = newApp.SrcPort
newConf.PunchPriority = newApp.PunchPriority newConf.RelayNode = newApp.SpecRelayNode
gConf.add(newConf, false) newConf.PunchPriority = newApp.PunchPriority
gConf.add(newConf, false)
}
if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit
GNetwork.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end GNetwork.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end
} }
@@ -224,13 +312,12 @@ func handleEditApp(msg []byte) (err error) {
func handleConnectReq(msg []byte) (err error) { func handleConnectReq(msg []byte) (err error) {
req := PushConnectReq{} req := PushConnectReq{}
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) gLog.e("Unmarshal %v:%s", reflect.TypeOf(req), err)
return err return err
} }
gLog.Printf(LvDEBUG, "%s is connecting...", req.From) gLog.d("%s is connecting... push connect response", req.From)
gLog.Println(LvDEBUG, "push connect response to ", req.From)
if compareVersion(req.Version, LeastSupportVersion) < 0 { if compareVersion(req.Version, LeastSupportVersion) < 0 {
gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From) gLog.e("%s:%s", ErrVersionNotCompatible.Error(), req.From)
rsp := PushConnectRsp{ rsp := PushConnectRsp{
Error: 10, Error: 10,
Detail: ErrVersionNotCompatible.Error(), Detail: ErrVersionNotCompatible.Error(),
@@ -243,7 +330,7 @@ func handleConnectReq(msg []byte) (err error) {
// verify totp token or token // verify totp token or token
t := totp.TOTP{Step: totp.RelayTOTPStep} t := totp.TOTP{Step: totp.RelayTOTPStep}
if t.Verify(req.Token, gConf.Network.Token, time.Now().Unix()-GNetwork.dt/int64(time.Second)) { // localTs may behind, auto adjust ts if t.Verify(req.Token, gConf.Network.Token, time.Now().Unix()-GNetwork.dt/int64(time.Second)) { // localTs may behind, auto adjust ts
gLog.Printf(LvINFO, "Access Granted") gLog.d("handleConnectReq Access Granted")
config := AppConfig{} config := AppConfig{}
config.peerNatType = req.NatType config.peerNatType = req.NatType
config.peerConeNatPort = req.ConeNatPort config.peerConeNatPort = req.ConeNatPort
@@ -259,16 +346,16 @@ func handleConnectReq(msg []byte) (err error) {
config.UnderlayProtocol = req.UnderlayProtocol config.UnderlayProtocol = req.UnderlayProtocol
// share relay node will limit bandwidth // share relay node will limit bandwidth
if req.Token != gConf.Network.Token { if req.Token != gConf.Network.Token {
gLog.Printf(LvINFO, "set share bandwidth %d mbps", gConf.Network.ShareBandwidth) gLog.i("set share bandwidth %d mbps", gConf.Network.ShareBandwidth)
config.shareBandwidth = gConf.Network.ShareBandwidth config.shareBandwidth = gConf.Network.ShareBandwidth
} }
// go GNetwork.AddTunnel(config, req.ID) // go GNetwork.AddTunnel(config, req.ID)
go func() { go func() {
GNetwork.addDirectTunnel(config, req.ID) GNetwork.addDirectTunnel(config, req.ID, nil)
}() }()
return nil return nil
} }
gLog.Println(LvERROR, "Access Denied:", req.From) gLog.e("handleConnectReq Access Denied:%s", req.From)
rsp := PushConnectRsp{ rsp := PushConnectRsp{
Error: 1, Error: 1,
Detail: fmt.Sprintf("connect to %s error: Access Denied", gConf.Network.Node), Detail: fmt.Sprintf("connect to %s error: Access Denied", gConf.Network.Node),
@@ -279,10 +366,10 @@ func handleConnectReq(msg []byte) (err error) {
} }
func handleReportApps() (err error) { func handleReportApps() (err error) {
gLog.Println(LvINFO, "MsgPushReportApps") gLog.i("MsgPushReportApps")
req := ReportApps{} req := ReportApps{}
gConf.mtx.Lock() gConf.mtx.RLock()
defer gConf.mtx.Unlock() defer gConf.mtx.RUnlock()
for _, config := range gConf.Apps { for _, config := range gConf.Apps {
appActive := 0 appActive := 0
@@ -292,28 +379,24 @@ func handleReportApps() (err error) {
linkMode := LinkModeUDPPunch linkMode := LinkModeUDPPunch
var connectTime string var connectTime string
var retryTime string var retryTime string
var app *p2pApp app := GNetwork.findApp(config)
i, ok := GNetwork.apps.Load(config.ID()) if app != nil {
if ok {
app = i.(*p2pApp) if app.IsActive() {
if app.isActive() {
appActive = 1 appActive = 1
} }
if app.config.SrcPort == 0 { // memapp
continue
}
specRelayNode = app.config.RelayNode specRelayNode = app.config.RelayNode
if !app.isDirect() { // TODO: should always report relay node for app edit t, tidx := app.AvailableTunnel()
relayNode = app.relayNode if tidx != 0 { // TODO: should always report relay node for app edit
relayMode = app.relayMode relayNode = app.relayNode[tidx]
relayMode = app.relayMode[tidx]
} }
if app.Tunnel() != nil { if t != nil {
linkMode = app.Tunnel().linkModeWeb linkMode = t.linkModeWeb
} }
retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700")
connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700")
} }
appInfo := AppInfo{ appInfo := AppInfo{
AppName: config.AppName, AppName: config.AppName,
@@ -344,10 +427,8 @@ func handleReportApps() (err error) {
} }
func handleReportMemApps() (err error) { func handleReportMemApps() (err error) {
gLog.Println(LvINFO, "handleReportMemApps") gLog.i("handleReportMemApps")
req := ReportApps{} req := ReportApps{}
gConf.mtx.Lock()
defer gConf.mtx.Unlock()
GNetwork.sdwan.sysRoute.Range(func(key, value interface{}) bool { GNetwork.sdwan.sysRoute.Range(func(key, value interface{}) bool {
node := value.(*sdwanNode) node := value.(*sdwanNode)
appActive := 0 appActive := 0
@@ -357,13 +438,16 @@ func handleReportMemApps() (err error) {
i, ok := GNetwork.apps.Load(node.id) i, ok := GNetwork.apps.Load(node.id)
var app *p2pApp var app *p2pApp
var t *P2PTunnel
var tidx int
if ok { if ok {
app = i.(*p2pApp) app = i.(*p2pApp)
if app.isActive() { t, tidx = app.AvailableTunnel()
if app.IsActive() {
appActive = 1 appActive = 1
} }
if !app.isDirect() { if tidx != 0 {
relayMode = app.relayMode relayMode = app.relayMode[tidx]
} }
retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700") retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700")
connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700") connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700")
@@ -380,12 +464,13 @@ func handleReportMemApps() (err error) {
appInfo.Protocol = app.config.Protocol appInfo.Protocol = app.config.Protocol
appInfo.Whitelist = app.config.Whitelist appInfo.Whitelist = app.config.Whitelist
appInfo.SrcPort = app.config.SrcPort appInfo.SrcPort = app.config.SrcPort
if !app.isDirect() {
appInfo.RelayNode = app.relayNode if tidx != 0 {
appInfo.RelayNode = app.relayNode[tidx]
} }
if app.Tunnel() != nil { if t != nil {
appInfo.LinkMode = app.Tunnel().linkModeWeb appInfo.LinkMode = t.linkModeWeb
} }
appInfo.DstHost = app.config.DstHost appInfo.DstHost = app.config.DstHost
appInfo.DstPort = app.config.DstPort appInfo.DstPort = app.config.DstPort
@@ -398,17 +483,19 @@ func handleReportMemApps() (err error) {
req.Apps = append(req.Apps, appInfo) req.Apps = append(req.Apps, appInfo)
return true return true
}) })
gLog.Println(LvDEBUG, "handleReportMemApps res:", prettyJson(req)) req.TunError = GNetwork.sdwan.tunErr
gLog.d("handleReportMemApps res:%s", prettyJson(req))
gConf.retryAllMemApp()
return GNetwork.write(MsgReport, MsgReportMemApps, &req) return GNetwork.write(MsgReport, MsgReportMemApps, &req)
} }
func handleLog(msg []byte) (err error) { func handleLog(msg []byte) (err error) {
gLog.Println(LvDEBUG, "MsgPushReportLog") gLog.d("MsgPushReportLog")
const defaultLen = 1024 * 128 const defaultLen = 1024 * 128
const maxLen = 1024 * 1024 const maxLen = 1024 * 1024
req := ReportLogReq{} req := ReportLogReq{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:])) gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
return err return err
} }
if req.FileName == "" { if req.FileName == "" {
@@ -416,9 +503,12 @@ func handleLog(msg []byte) (err error) {
} else { } else {
req.FileName = sanitizeFileName(req.FileName) req.FileName = sanitizeFileName(req.FileName)
} }
if req.IsSetLogLevel == 1 {
gLog.setLevel(LogLevel(req.LogLevel))
}
f, err := os.Open(filepath.Join("log", req.FileName)) f, err := os.Open(filepath.Join("log", req.FileName))
if err != nil { if err != nil {
gLog.Println(LvERROR, "read log file error:", err) gLog.e("read log file error:%s", err)
return err return err
} }
fi, err := f.Stat() fi, err := f.Stat()
@@ -441,7 +531,7 @@ func handleLog(msg []byte) (err error) {
readLength, err := f.Read(buff) readLength, err := f.Read(buff)
f.Close() f.Close()
if err != nil { if err != nil {
gLog.Println(LvERROR, "read log content error:", err) gLog.e("read log content error:%s", err)
return err return err
} }
rsp := ReportLogRsp{} rsp := ReportLogRsp{}
@@ -453,8 +543,36 @@ func handleLog(msg []byte) (err error) {
} }
func handleReportGoroutine() (err error) { func handleReportGoroutine() (err error) {
gLog.Println(LvDEBUG, "handleReportGoroutine") gLog.d("handleReportGoroutine")
buf := make([]byte, 1024*128) buf := make([]byte, 1024*128)
stackLen := runtime.Stack(buf, true) stackLen := runtime.Stack(buf, true)
return GNetwork.write(MsgReport, MsgPushReportLog, string(buf[:stackLen])) return GNetwork.write(MsgReport, MsgReportResponse, string(buf[:stackLen]))
}
func handleReportHeap() error {
var buf bytes.Buffer
err := pprof.Lookup("heap").WriteTo(&buf, 1)
if err != nil {
return err
}
return GNetwork.write(MsgReport, MsgReportResponse, buf.String())
}
func handleCheckRemoteService(msg []byte) (err error) {
gLog.d("handleCheckRemoteService")
req := CheckRemoteService{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
return err
}
rsp := PushRsp{Error: 0}
conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.Host, req.Port), time.Second*3)
if err != nil {
rsp.Error = 1
rsp.Detail = ErrRemoteServiceUnable.Error()
} else {
conn.Close()
}
return GNetwork.write(MsgReport, MsgReportResponse, rsp)
} }
+50 -51
View File
@@ -11,24 +11,24 @@ import (
) )
func handshakeC2C(t *P2PTunnel) (err error) { func handshakeC2C(t *P2PTunnel) (err error) {
gLog.Printf(LvDEBUG, "handshakeC2C %s:%d:%d to %s:%d", gConf.Network.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort) gLog.d("handshakeC2C %s:%d:%d to %s:%d", gConf.Network.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
defer gLog.Printf(LvDEBUG, "handshakeC2C end") defer gLog.d("handshakeC2C end")
conn, err := net.ListenUDP("udp", t.la) conn, err := net.ListenUDP("udp", t.localHoleAddr)
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() defer conn.Close()
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id}) _, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshake error:", err) gLog.d("handshakeC2C write MsgPunchHandshake error:%s", err)
return err return err
} }
ra, head, buff, _, err := UDPRead(conn, HandshakeTimeout) ra, head, buff, _, err := UDPRead(conn, HandshakeTimeout)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C read MsgPunchHandshake error:", err) gLog.d("handshakeC2C read MsgPunchHandshake error:%s", err)
return err return err
} }
t.ra, _ = net.ResolveUDPAddr("udp", ra.String()) t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", ra.String())
var tunnelID uint64 var tunnelID uint64
if len(buff) > openP2PHeaderSize { if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{} req := P2PHandshakeReq{}
@@ -39,29 +39,29 @@ func handshakeC2C(t *P2PTunnel) (err error) {
tunnelID = t.id tunnelID = t.id
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "read %d handshake ", t.id) gLog.d("read tunnelid:%d handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
_, head, _, _, err = UDPRead(conn, HandshakeTimeout) _, head, _, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err) gLog.d("handshakeC2C write MsgPunchHandshakeAck error:", err)
return err return err
} }
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id) gLog.d("read tunnelID:%d handshake ack ", t.id)
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) _, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err) gLog.d("handshakeC2C write MsgPunchHandshakeAck error:%s", err)
return err return err
} }
} }
gLog.Printf(LvINFO, "handshakeC2C ok") gLog.i("handshakeC2C ok")
return nil return nil
} }
func handshakeC2S(t *P2PTunnel) error { func handshakeC2S(t *P2PTunnel) error {
gLog.Printf(LvDEBUG, "handshakeC2S start") gLog.d("tid:%d handshakeC2S start", t.id)
defer gLog.Printf(LvDEBUG, "handshakeC2S end") defer gLog.d("tid:%d handshakeC2S end", t.id)
if !buildTunnelMtx.TryLock() { if !buildTunnelMtx.TryLock() {
// time.Sleep(time.Second * 3) // time.Sleep(time.Second * 3)
return ErrBuildTunnelBusy return ErrBuildTunnelBusy
@@ -70,14 +70,14 @@ func handshakeC2S(t *P2PTunnel) error {
startTime := time.Now() startTime := time.Now()
r := rand.New(rand.NewSource(time.Now().UnixNano())) r := rand.New(rand.NewSource(time.Now().UnixNano()))
randPorts := r.Perm(65532) randPorts := r.Perm(65532)
conn, err := net.ListenUDP("udp", t.la) conn, err := net.ListenUDP("udp", t.localHoleAddr)
if err != nil { if err != nil {
return err return err
} }
defer conn.Close() defer conn.Close()
go func() error { go func() error {
gLog.Printf(LvDEBUG, "send symmetric handshake to %s from %d:%d start", t.config.peerIP, t.coneLocalPort, t.coneNatPort) gLog.d("tid:%d send symmetric handshake to %s from %d:%d start", t.id, t.config.peerIP, t.coneLocalPort, t.coneNatPort)
for i := 0; i < SymmetricHandshakeNum; i++ { for i := 0; i < SymmetricHandshakeNum; i++ {
// time.Sleep(SymmetricHandshakeInterval) // time.Sleep(SymmetricHandshakeInterval)
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, randPorts[i]+2)) dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, randPorts[i]+2))
@@ -86,32 +86,32 @@ func handshakeC2S(t *P2PTunnel) error {
} }
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id}) _, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2S write MsgPunchHandshake error:", err) gLog.d("tid:%d handshakeC2S write MsgPunchHandshake error:%s", t.id, err)
return err return err
} }
} }
gLog.Println(LvDEBUG, "send symmetric handshake end") gLog.d("tid:%d send symmetric handshake end", t.id)
return nil return nil
}() }()
err = conn.SetReadDeadline(time.Now().Add(HandshakeTimeout)) err = conn.SetReadDeadline(time.Now().Add(HandshakeTimeout))
if err != nil { if err != nil {
gLog.Println(LvERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error") gLog.d("tid:%d SymmetricHandshakeAckTimeout SetReadDeadline error", t.id)
return err return err
} }
// read response of the punching hole ok port // read response of the punching hole ok port
buff := make([]byte, 1024) buff := make([]byte, 1024)
_, dst, err := conn.ReadFrom(buff) _, dst, err := conn.ReadFrom(buff)
if err != nil { if err != nil {
gLog.Println(LvERROR, "handshakeC2S wait timeout") gLog.d("tid:%d handshakeC2S wait timeout", t.id)
return err return err
} }
head := &openP2PHeader{} head := &openP2PHeader{}
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head) err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil { if err != nil {
gLog.Println(LvERROR, "parse p2pheader error:", err) gLog.e("tid:%d parse p2pheader error:%s", t.id, err)
return err return err
} }
t.ra, _ = net.ResolveUDPAddr("udp", dst.String()) t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", dst.String())
var tunnelID uint64 var tunnelID uint64
if len(buff) > openP2PHeaderSize { if len(buff) > openP2PHeaderSize {
req := P2PHandshakeReq{} req := P2PHandshakeReq{}
@@ -122,12 +122,12 @@ func handshakeC2S(t *P2PTunnel) error {
tunnelID = t.id tunnelID = t.id
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ", t.id) gLog.d("tid:%d handshakeC2S read handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
for { for {
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout) _, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeC2S handshake error") gLog.d("tid:%d handshakeC2S handshake error", t.id)
return err return err
} }
var tunnelID uint64 var tunnelID uint64
@@ -146,39 +146,39 @@ func handshakeC2S(t *P2PTunnel) error {
} }
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ack %s", t.id, t.ra.String()) gLog.d("tid:%d handshakeC2S read handshake ack %s", t.id, t.remoteHoleAddr.String())
_, err = UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) _, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
return err return err
} else { } else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck") gLog.d("tid:%d handshakeS2C read msg but not MsgPunchHandshakeAck", t.id)
} }
gLog.Printf(LvINFO, "handshakeC2S ok. cost %d ms", time.Since(startTime)/time.Millisecond) gLog.i("tid:%d handshakeC2S ok. cost %d ms", t.id, time.Since(startTime)/time.Millisecond)
return nil return nil
} }
func handshakeS2C(t *P2PTunnel) error { func handshakeS2C(t *P2PTunnel) error {
gLog.Printf(LvDEBUG, "handshakeS2C start") gLog.d("tid:%d handshakeS2C start", t.id)
defer gLog.Printf(LvDEBUG, "handshakeS2C end") defer gLog.d("tid:%d handshakeS2C end", t.id)
if !buildTunnelMtx.TryLock() { if !buildTunnelMtx.TryLock() {
// time.Sleep(time.Second * 3) // time.Sleep(time.Second * 3)
return ErrBuildTunnelBusy return ErrBuildTunnelBusy
} }
defer buildTunnelMtx.Unlock() defer buildTunnelMtx.Unlock()
startTime := time.Now() startTime := time.Now()
gotCh := make(chan *net.UDPAddr, 5) gotCh := make(chan *net.UDPAddr, 50)
// sequencely udp send handshake, do not parallel send // sequencely udp send handshake, do not parallel send
gLog.Printf(LvDEBUG, "send symmetric handshake to %s:%d start", t.config.peerIP, t.config.peerConeNatPort) gLog.d("tid:%d send symmetric handshake to %s:%d start", t.id, t.config.peerIP, t.config.peerConeNatPort)
gotIt := false gotIt := false
for i := 0; i < SymmetricHandshakeNum; i++ { for i := 0; i < SymmetricHandshakeNum; i++ {
// time.Sleep(SymmetricHandshakeInterval) // time.Sleep(SymmetricHandshakeInterval)
go func(t *P2PTunnel) error { go func(t *P2PTunnel) error {
conn, err := net.ListenUDP("udp", nil) // TODO: system allocated port really random? conn, err := net.ListenUDP("udp", nil) // TODO: system allocated port really random?
if err != nil { if err != nil {
gLog.Printf(LvDEBUG, "listen error") gLog.d("tid:%d listen error", t.id)
return err return err
} }
defer conn.Close() defer conn.Close()
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id}) UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
_, head, buff, _, err := UDPRead(conn, HandshakeTimeout) _, head, buff, _, err := UDPRead(conn, HandshakeTimeout)
if err != nil { if err != nil {
// gLog.Println(LevelDEBUG, "one of the handshake error:", err) // gLog.Println(LevelDEBUG, "one of the handshake error:", err)
@@ -198,13 +198,13 @@ func handshakeS2C(t *P2PTunnel) error {
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshake && tunnelID == t.id {
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ", t.id) gLog.d("tid:%d handshakeS2C read handshake ", t.id)
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
// may read several MsgPunchHandshake // may read several MsgPunchHandshake
for { for {
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout) _, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "handshakeS2C handshake error") gLog.d("tid:%d handshakeS2C handshake error", t.id)
return err return err
} }
if len(buff) > openP2PHeaderSize { if len(buff) > openP2PHeaderSize {
@@ -218,36 +218,35 @@ func handshakeS2C(t *P2PTunnel) error {
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
break break
} else { } else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck") gLog.d("tid:%d handshakeS2C read msg but not MsgPunchHandshakeAck", t.id)
} }
} }
} }
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck { if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
gLog.Printf(LvDEBUG, "handshakeS2C read %d handshake ack %s", t.id, conn.LocalAddr().String()) gLog.d("tid:%d handshakeS2C read handshake ack %s", t.id, conn.LocalAddr().String())
UDPWrite(conn, t.ra, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id}) UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
gotIt = true gotIt = true
la, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String()) la, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
gotCh <- la gotCh <- la
return nil return nil
} else { } else {
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck") gLog.d("tid:%d handshakeS2C read msg but not MsgPunchHandshakeAck", t.id)
} }
return nil return nil
}(t) }(t)
} }
gLog.Printf(LvDEBUG, "send symmetric handshake end") gLog.d("tid:%d send symmetric handshake end", t.id)
if compareVersion(t.config.peerVersion, SymmetricSimultaneouslySendVersion) < 0 { // compatible with old client if compareVersion(t.config.peerVersion, SymmetricSimultaneouslySendVersion) < 0 { // compatible with old client
gLog.Println(LvDEBUG, "handshakeS2C ready, notify peer connect") gLog.d("tid:%d handshakeS2C ready, notify peer connect", t.id)
t.pn.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id}) GNetwork.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
} }
select { select {
case <-time.After(HandshakeTimeout): case <-time.After(HandshakeTimeout):
return fmt.Errorf("wait handshake timeout") return fmt.Errorf("tid:%d wait handshake timeout", t.id)
case la := <-gotCh: case la := <-gotCh:
t.la = la t.localHoleAddr = la
gLog.Println(LvDEBUG, "symmetric handshake ok", la) gLog.i("tid:%d handshakeS2C ok. cost %dms", t.id, time.Since(startTime)/time.Millisecond)
gLog.Printf(LvINFO, "handshakeS2C ok. cost %dms", time.Since(startTime)/time.Millisecond)
} }
return nil return nil
} }
+39 -40
View File
@@ -11,26 +11,14 @@ import (
) )
func install() { func install() {
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) gLog.i("openp2p start. version: %s", OpenP2PVersion)
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]") gLog.i("Contact: QQ group 16947733, Email [email protected]")
gLog.Println(LvINFO, "install start") gLog.i("install start")
defer gLog.Println(LvINFO, "install end") defer gLog.i("install end")
// auto uninstall
err := os.MkdirAll(defaultInstallPath, 0775)
if err != nil {
gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
return
}
err = os.Chdir(defaultInstallPath)
if err != nil {
gLog.Println(LvERROR, "cd error:", err)
return
}
uninstall()
// save config file
parseParams("install", "") parseParams("install", "")
// auto uninstall
uninstall(false)
gLog.i("install path: %s", defaultInstallPath)
targetPath := filepath.Join(defaultInstallPath, defaultBinName) targetPath := filepath.Join(defaultInstallPath, defaultBinName)
d := daemon{} d := daemon{}
// copy files // copy files
@@ -38,38 +26,42 @@ func install() {
binPath, _ := os.Executable() binPath, _ := os.Executable()
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe) src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
if errFiles != nil { if errFiles != nil {
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles) gLog.e("os.Open %s error:%s", os.Args[0], errFiles)
return return
} }
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775) dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if errFiles != nil { if errFiles != nil {
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles) time.Sleep(time.Second * 5) // maybe windows defender occupied the file, retry
return dst, errFiles = os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
if errFiles != nil {
gLog.e("os.OpenFile %s error:%s", targetPath, errFiles)
return
}
} }
_, errFiles = io.Copy(dst, src) _, errFiles = io.Copy(dst, src)
if errFiles != nil { if errFiles != nil {
gLog.Printf(LvERROR, "io.Copy error:%s", errFiles) gLog.e("io.Copy error:%s", errFiles)
return return
} }
src.Close() src.Close()
dst.Close() dst.Close()
// install system service // install system service
gLog.Println(LvINFO, "targetPath:", targetPath) err := d.Control("install", targetPath, []string{"-d"})
err = d.Control("install", targetPath, []string{"-d"})
if err == nil { if err == nil {
gLog.Println(LvINFO, "install system service ok.") gLog.i("install system service ok.")
} }
time.Sleep(time.Second * 2) time.Sleep(time.Second * 2)
err = d.Control("start", targetPath, []string{"-d"}) err = d.Control("start", targetPath, []string{"-d"})
if err != nil { if err != nil {
gLog.Println(LvERROR, "start openp2p service error:", err) gLog.e("start openp2p service error:%s", err)
} else { } else {
gLog.Println(LvINFO, "start openp2p service ok.") gLog.i("start openp2p service ok.")
} }
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") gConf.save()
gLog.i("Visit WebUI on https://console.openp2p.cn")
} }
func installByFilename() { func installByFilename() {
@@ -79,7 +71,7 @@ func installByFilename() {
} }
serverHost := params[1] serverHost := params[1]
token := params[2] token := params[2]
gLog.Println(LvINFO, "install start") gLog.i("install start")
targetPath := os.Args[0] targetPath := os.Args[0]
args := []string{"install"} args := []string{"install"}
args = append(args, "-serverhost") args = append(args, "-serverhost")
@@ -94,31 +86,38 @@ func installByFilename() {
cmd.Env = env cmd.Env = env
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
gLog.Println(LvERROR, "install by filename, start process error:", err) gLog.e("install by filename, start process error:%s", err)
return return
} }
gLog.Println(LvINFO, "install end") gLog.i("install end")
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn") gLog.i("Visit WebUI on https://console.openp2p.cn")
fmt.Println("Press the Any Key to exit") fmt.Println("Press the Any Key to exit")
fmt.Scanln() fmt.Scanln()
os.Exit(0) os.Exit(0)
} }
func uninstall() {
gLog.Println(LvINFO, "uninstall start") func uninstall(rmFiles bool) {
defer gLog.Println(LvINFO, "uninstall end") gLog.i("uninstall start")
defer gLog.i("uninstall end")
d := daemon{} d := daemon{}
err := d.Control("stop", "", nil) err := d.Control("stop", "", nil)
if err != nil { // service maybe not install if err != nil { // service maybe not install
return gLog.d("stop service error:%s", err)
} }
err = d.Control("uninstall", "", nil) err = d.Control("uninstall", "", nil)
if err != nil { if err != nil {
gLog.Println(LvERROR, "uninstall system service error:", err) gLog.d("uninstall system service error:%s", err)
} else { } else {
gLog.Println(LvINFO, "uninstall system service ok.") gLog.i("uninstall system service ok.")
} }
time.Sleep(time.Second * 3)
binPath := filepath.Join(defaultInstallPath, defaultBinName) binPath := filepath.Join(defaultInstallPath, defaultBinName)
os.Remove(binPath + "0") os.Remove(binPath + "0")
os.Remove(binPath) os.Remove(binPath)
// os.RemoveAll(defaultInstallPath) // reserve config.json if rmFiles {
if err := os.RemoveAll(defaultInstallPath); err != nil {
gLog.e("RemoveAll %s error:%s", defaultInstallPath, err)
}
}
} }
+113 -88
View File
@@ -3,38 +3,33 @@ package openp2p
import ( import (
"log" "log"
"os" "os"
"path/filepath"
"runtime" "runtime"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
type LogLevel int type LogLevel int32
var gLog *logger var gLog *logger
const ( const (
LvDev LogLevel = -1 LvDev LogLevel = -1
LvDEBUG LogLevel = iota LvDEBUG LogLevel = 0
LvINFO LvINFO LogLevel = 1
LvWARN LvWARN LogLevel = 2
LvERROR LvERROR LogLevel = 3
) )
var ( const logFileNames string = ".log"
logFileNames map[LogLevel]string
loglevel map[LogLevel]string
)
func init() {
logFileNames = make(map[LogLevel]string)
loglevel = make(map[LogLevel]string)
logFileNames[0] = ".log"
loglevel[LvDEBUG] = "DEBUG"
loglevel[LvINFO] = "INFO"
loglevel[LvWARN] = "WARN"
loglevel[LvERROR] = "ERROR"
loglevel[LvDev] = "Dev"
var loglevel = map[LogLevel]string{
LvDEBUG: "DEBUG",
LvINFO: "INFO",
LvWARN: "WARN",
LvERROR: "ERROR",
LvDev: "Dev",
} }
const ( const (
@@ -43,62 +38,54 @@ const (
) )
type logger struct { type logger struct {
loggers map[LogLevel]*log.Logger logger *log.Logger
files map[LogLevel]*os.File files *os.File
level LogLevel level atomic.Int32
logDir string logDir string
mtx *sync.Mutex mtx sync.Mutex
lineEnding string lineEnding string
pid int pid int
maxLogSize int64 maxLogSize atomic.Int64
mode int mode int
stdLogger *log.Logger stdLogger *log.Logger
checkFileRunning bool
} }
func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger { func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger {
loggers := make(map[LogLevel]*log.Logger) logdir := filepath.Join(path, "log")
logfiles := make(map[LogLevel]*os.File) if err := os.MkdirAll(logdir, 0755); err != nil && mode&LogFile != 0 {
var ( return nil
logdir string
)
if path == "" {
logdir = "log/"
} else {
logdir = path + "/log/"
} }
os.MkdirAll(logdir, 0777) logFilePath := filepath.Join(logdir, filePrefix+logFileNames)
for lv := range logFileNames { f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
logFilePath := logdir + filePrefix + logFileNames[lv] if err != nil && mode&LogFile != 0 {
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) log.Fatal(err)
if err != nil {
log.Fatal(err)
}
os.Chmod(logFilePath, 0644)
logfiles[lv] = f
loggers[lv] = log.New(f, "", log.LstdFlags|log.Lmicroseconds)
} }
var le string stdLog := log.New(f, "", log.LstdFlags|log.Lmicroseconds)
le := "\n"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
le = "\r\n" le = "\r\n"
} else {
le = "\n"
} }
pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode, log.New(os.Stdout, "", 0)} pLog := &logger{logger: stdLog,
files: f,
logDir: logdir,
lineEnding: le,
pid: os.Getpid(),
mode: mode,
stdLogger: log.New(os.Stdout, "", 0)}
pLog.setMaxSize(maxLogSize)
pLog.setLevel(level)
pLog.stdLogger.SetFlags(log.LstdFlags | log.Lmicroseconds) pLog.stdLogger.SetFlags(log.LstdFlags | log.Lmicroseconds)
go pLog.checkFile() go pLog.checkFile()
return pLog return pLog
} }
func (l *logger) setLevel(level LogLevel) { func (l *logger) setLevel(level LogLevel) {
l.mtx.Lock() l.level.Store(int32(level))
defer l.mtx.Unlock()
l.level = level
} }
func (l *logger) setMaxSize(size int64) { func (l *logger) setMaxSize(size int64) {
l.mtx.Lock() l.maxLogSize.Store(size)
defer l.mtx.Unlock()
l.maxLogSize = size
} }
func (l *logger) setMode(mode int) { func (l *logger) setMode(mode int) {
@@ -107,49 +94,61 @@ func (l *logger) setMode(mode int) {
l.mode = mode l.mode = mode
} }
func (l *logger) close() {
l.checkFileRunning = false
l.files.Close()
}
func (l *logger) checkFile() { func (l *logger) checkFile() {
if l.maxLogSize <= 0 { if l.maxLogSize.Load() <= 0 {
return return
} }
l.checkFileRunning = true
ticker := time.NewTicker(time.Minute) ticker := time.NewTicker(time.Minute)
for { for l.checkFileRunning {
select { select {
case <-ticker.C: case <-ticker.C:
l.mtx.Lock() f, e := l.files.Stat()
for lv, logFile := range l.files { if e != nil {
f, e := logFile.Stat() continue
if e != nil {
continue
}
if f.Size() <= l.maxLogSize {
continue
}
logFile.Close()
fname := f.Name()
backupPath := l.logDir + fname + ".0"
os.Remove(backupPath)
os.Rename(l.logDir+fname, backupPath)
newFile, e := os.OpenFile(l.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if e == nil {
l.loggers[lv].SetOutput(newFile)
l.files[lv] = newFile
}
} }
l.mtx.Unlock() if f.Size() <= l.maxLogSize.Load() {
continue
}
l.mtx.Lock()
l.files.Close()
fname := f.Name()
backupPath := filepath.Join(l.logDir, fname+".0")
err := os.Remove(backupPath)
if err != nil {
log.Println("remove openp2p.log0 error:", err)
}
if err = os.Rename(filepath.Join(l.logDir, fname), backupPath); err != nil {
log.Println("rename openp2p.log error:", err)
}
if newFile, e := os.OpenFile(filepath.Join(l.logDir, fname), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644); e == nil {
l.logger.SetOutput(newFile)
l.files = newFile
l.mtx.Unlock()
}
case <-time.After(time.Second * 1):
} }
} }
} }
func (l *logger) Printf(level LogLevel, format string, params ...interface{}) { func (l *logger) Printf(level LogLevel, format string, params ...interface{}) {
l.mtx.Lock() if level < LogLevel(l.level.Load()) {
defer l.mtx.Unlock()
if level < l.level {
return return
} }
l.mtx.Lock()
defer l.mtx.Unlock()
pidAndLevel := []interface{}{l.pid, loglevel[level]} pidAndLevel := []interface{}{l.pid, loglevel[level]}
params = append(pidAndLevel, params...) params = append(pidAndLevel, params...)
if l.mode&LogFile != 0 { if l.mode&LogFile != 0 {
l.loggers[0].Printf("%d %s "+format+l.lineEnding, params...) l.logger.Printf("%d %s "+format+l.lineEnding, params...)
} }
if l.mode&LogConsole != 0 { if l.mode&LogConsole != 0 {
l.stdLogger.Printf("%d %s "+format+l.lineEnding, params...) l.stdLogger.Printf("%d %s "+format+l.lineEnding, params...)
@@ -157,18 +156,44 @@ func (l *logger) Printf(level LogLevel, format string, params ...interface{}) {
} }
func (l *logger) Println(level LogLevel, params ...interface{}) { func (l *logger) Println(level LogLevel, params ...interface{}) {
l.mtx.Lock() if level < LogLevel(l.level.Load()) {
defer l.mtx.Unlock()
if level < l.level {
return return
} }
l.mtx.Lock()
defer l.mtx.Unlock()
pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "} pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "}
params = append(pidAndLevel, params...) params = append(pidAndLevel, params...)
params = append(params, l.lineEnding) params = append(params, l.lineEnding)
if l.mode&LogFile != 0 { if l.mode&LogFile != 0 {
l.loggers[0].Print(params...) l.logger.Print(params...)
} }
if l.mode&LogConsole != 0 { if l.mode&LogConsole != 0 {
l.stdLogger.Print(params...) l.stdLogger.Print(params...)
} }
} }
func (l *logger) d(format string, params ...interface{}) {
l.Printf(LvDEBUG, format, params...)
}
func (l *logger) i(format string, params ...interface{}) {
l.Printf(LvINFO, format, params...)
}
func (l *logger) w(format string, params ...interface{}) {
l.Printf(LvWARN, format, params...)
}
func (l *logger) e(format string, params ...interface{}) {
l.Printf(LvERROR, format, params...)
}
func (l *logger) dev(format string, params ...interface{}) {
l.Printf(LvDev, format, params...)
}
func InitForUnitTest(lv LogLevel) {
baseDir := filepath.Dir(os.Args[0])
os.Chdir(baseDir) // for system service
gLog = NewLogger(baseDir, ProductName, lv, 1024*1024, LogFile|LogConsole)
}
+112 -87
View File
@@ -9,53 +9,60 @@ import (
"strings" "strings"
"time" "time"
upnp "openp2p/pkg/upnp"
reuse "github.com/openp2p-cn/go-reuseport" reuse "github.com/openp2p-cn/go-reuseport"
) )
func natTCP(serverHost string, serverPort int) (publicIP string, publicPort int, localPort int) { func natDetectTCP(serverHost string, serverPort int, lp int) (publicIP string, publicPort int, localPort int, err error) {
// dialer := &net.Dialer{ gLog.dev("natDetectTCP start")
// LocalAddr: &net.TCPAddr{ defer gLog.dev("natDetectTCP end")
// IP: net.ParseIP("0.0.0.0"), conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("0.0.0.0:%d", lp), fmt.Sprintf("%s:%d", serverHost, serverPort), NatDetectTimeout)
// Port: localPort,
// },
// }
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", 0), fmt.Sprintf("%s:%d", serverHost, serverPort), NatTestTimeout)
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
// log.Println(LvINFO, conn.LocalAddr())
if err != nil { if err != nil {
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err) err = fmt.Errorf("dial tcp4 %s:%d error: %w", serverHost, serverPort, err)
return return
} }
defer conn.Close() defer conn.Close()
localPort, _ = strconv.Atoi(strings.Split(conn.LocalAddr().String(), ":")[1])
_, wrerr := conn.Write([]byte("1"))
if wrerr != nil {
fmt.Printf("Write error: %s\n", wrerr)
return
}
b := make([]byte, 1000)
conn.SetReadDeadline(time.Now().Add(NatTestTimeout))
n, rderr := conn.Read(b)
if rderr != nil {
fmt.Printf("Read error: %s\n", rderr)
return
}
arr := strings.Split(string(b[:n]), ":")
if len(arr) < 2 {
return
}
publicIP = arr[0]
port, _ := strconv.ParseInt(arr[1], 10, 32)
publicPort = int(port)
return
localAddr := conn.LocalAddr().(*net.TCPAddr)
localPort = localAddr.Port
if _, err = conn.Write([]byte("1")); err != nil {
err = fmt.Errorf("write error: %w", err)
return
}
b := make([]byte, 1000)
conn.SetReadDeadline(time.Now().Add(NatDetectTimeout))
n, err := conn.Read(b)
if err != nil {
err = fmt.Errorf("read error: %w", err)
return
}
response := strings.Split(string(b[:n]), ":")
if len(response) < 2 {
err = fmt.Errorf("invalid response format: %s", string(b[:n]))
return
}
publicIP = response[0]
port, err := strconv.Atoi(response[1])
if err != nil {
err = fmt.Errorf("invalid port format: %w", err)
return
}
publicPort = port
return
} }
func natTest(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
gLog.Println(LvDEBUG, "natTest start") func natDetectUDP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
defer gLog.Println(LvDEBUG, "natTest end") gLog.dev("natDetectUDP start")
defer gLog.dev("natDetectUDP end")
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort)) conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
if err != nil { if err != nil {
gLog.Println(LvERROR, "natTest listen udp error:", err) gLog.e("natDetectUDP listen udp error:%s", err)
return "", 0, err return "", 0, err
} }
defer conn.Close() defer conn.Close()
@@ -66,12 +73,12 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
} }
// The connection can write data to the desired address. // The connection can write data to the desired address.
msg, err := newMessage(MsgNATDetect, 0, nil) msg, err := newMessage(MsgNATDetect, MsgNAT, nil)
_, err = conn.WriteTo(msg, dst) _, err = conn.WriteTo(msg, dst)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
} }
deadline := time.Now().Add(NatTestTimeout) deadline := time.Now().Add(NatDetectTimeout)
err = conn.SetReadDeadline(deadline) err = conn.SetReadDeadline(deadline)
if err != nil { if err != nil {
return "", 0, err return "", 0, err
@@ -79,7 +86,7 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
buffer := make([]byte, 1024) buffer := make([]byte, 1024)
nRead, _, err := conn.ReadFrom(buffer) nRead, _, err := conn.ReadFrom(buffer)
if err != nil { if err != nil {
gLog.Println(LvERROR, "NAT detect error:", err) gLog.e("NAT detect error:%s", err)
return "", 0, err return "", 0, err
} }
natRsp := NatDetectRsp{} natRsp := NatDetectRsp{}
@@ -88,19 +95,27 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
return natRsp.IP, natRsp.Port, nil return natRsp.IP, natRsp.Port, nil
} }
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, err error) { func getNATType(host string, detectPort1 int, detectPort2 int) (publicIP string, NATType int, err error) {
setUPNP(gConf.Network.PublicIPPort)
// the random local port may be used by other. // the random local port may be used by other.
localPort := int(rand.Uint32()%15000 + 50000) localPort := int(rand.Uint32()%15000 + 50000)
ip1, port1, err := natTest(host, udp1, localPort) ip1, port1, err := natDetectUDP(host, detectPort1, localPort)
if err != nil { if err != nil {
return "", 0, err // udp block try tcp
gLog.w("udp block, try tcp nat detect")
if ip1, port1, _, err = natDetectTCP(host, detectPort1, localPort); err != nil {
return "", 0, err
}
} }
_, port2, err := natTest(host, udp2, localPort) // 2rd nat test not need testing publicip _, port2, err := natDetectUDP(host, detectPort2, localPort) // 2rd nat test not need testing publicip
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
if err != nil { if err != nil {
return "", 0, err gLog.w("udp block, try tcp nat detect")
if _, port2, _, err = natDetectTCP(host, detectPort2, localPort); err != nil {
return "", 0, err
}
} }
gLog.d("local port:%d nat port:%d", localPort, port2)
natType := NATSymmetric natType := NATSymmetric
if port1 == port2 { if port1 == port2 {
natType = NATCone natType = NATCone
@@ -113,73 +128,61 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
return return
} }
var echoConn *net.UDPConn var echoConn *net.UDPConn
gLog.Println(LvDEBUG, "echo server start") gLog.d("echo server start")
var err error var err error
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort}) echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
if err != nil { // listen error if err != nil { // listen error
gLog.Println(LvERROR, "echo server listen error:", err) gLog.e("echo server listen error:%s", err)
return return
} }
defer echoConn.Close() defer echoConn.Close()
go func() {
// close outside for breaking the ReadFromUDP
// wait 30s for echo testing
buf := make([]byte, 1600)
echoConn.SetReadDeadline(time.Now().Add(time.Second * 30))
n, addr, err := echoConn.ReadFromUDP(buf)
if err != nil {
return
}
echoConn.WriteToUDP(buf[0:n], addr)
gLog.Println(LvDEBUG, "echo server end")
}()
// testing for public ip // testing for public ip
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
if i == 1 { if i == 1 {
// test upnp or nat-pmp // test upnp or nat-pmp
gLog.Println(LvDEBUG, "upnp test start") gLog.d("upnp test start")
nat, err := Discover() // 7 days for udp connection
if err != nil || nat == nil { // 7 days for tcp connection
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err) setUPNP(echoPort)
break
}
ext, err := nat.GetExternalAddress()
if err != nil {
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
break
}
gLog.Println(LvINFO, "PublicIP:", ext)
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30) // 30 seconds fot upnp testing
if err != nil {
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
break
} else {
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800) // 7 days for tcp connection
}
} }
gLog.Printf(LvDEBUG, "public ip test start %s:%d", publicIP, echoPort) gLog.d("public ip test start %s:%d", publicIP, echoPort)
conn, err := net.ListenUDP("udp", nil) conn, err := net.ListenUDP("udp", nil)
if err != nil { if err != nil {
break break
} }
defer conn.Close() defer conn.Close()
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", publicIP, echoPort)) dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", gConf.Network.ServerIP, gConf.Network.ServerPort))
if err != nil { if err != nil {
break break
} }
conn.WriteTo([]byte("echo"), dst)
// The connection can write data to the desired address.
msg, _ := newMessage(MsgNATDetect, MsgPublicIP, NatDetectReq{EchoPort: echoPort})
_, err = conn.WriteTo(msg, dst)
if err != nil {
continue
}
buf := make([]byte, 1600) buf := make([]byte, 1600)
// wait for echo testing // wait for echo testing
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout)) echoConn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
_, _, err = conn.ReadFromUDP(buf) nRead, _, err := echoConn.ReadFromUDP(buf)
if err == nil { if err != nil {
gLog.d("publicIPTest echoConn read timeout:%s", err)
continue
}
natRsp := NatDetectRsp{}
err = json.Unmarshal(buf[openP2PHeaderSize:nRead], &natRsp)
if err != nil {
gLog.d("publicIPTest Unmarshal error:%s", err)
continue
}
if natRsp.Port == echoPort {
if i == 1 { if i == 1 {
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES") gLog.d("UPNP or NAT-PMP:YES")
hasUPNPorNATPMP = 1 hasUPNPorNATPMP = 1
} else { } else {
gLog.Println(LvDEBUG, "public ip:YES") gLog.d("public ip:YES")
hasPublicIP = 1 hasPublicIP = 1
} }
break break
@@ -187,3 +190,25 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
} }
return return
} }
func setUPNP(echoPort int) {
nat := upnp.Any() // Initialize the NAT interface
if nat == nil {
gLog.d("NAT interface is not available")
return
}
ext, err := nat.ExternalIP()
if err != nil {
gLog.d("could not perform UPNP external address:%s", err)
return
}
gLog.i("PublicIP:%v", ext)
externalPort, err := nat.AddMapping("udp", echoPort, echoPort, "openp2p", 604800)
if err != nil {
gLog.d("could not add udp UPNP port mapping %d", externalPort)
return
} else {
nat.AddMapping("tcp", echoPort, echoPort, "openp2p", 604800)
}
}
+44 -17
View File
@@ -2,6 +2,7 @@ package openp2p
import ( import (
"fmt" "fmt"
"log"
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
@@ -25,15 +26,33 @@ func Run() {
install() install()
return return
case "uninstall": case "uninstall":
uninstall() uninstall(true)
return
case "start":
d := daemon{}
err := d.Control("start", "", nil)
if err != nil {
log.Println("openp2p start error:", err)
return
}
log.Println("openp2p start ok")
return
case "stop":
d := daemon{}
err := d.Control("stop", "", nil)
if err != nil {
log.Println("openp2p stop error:", err)
return
}
log.Println("openp2p stop ok")
return return
} }
} else { } else {
installByFilename() installByFilename()
} }
parseParams("", "") parseParams("", "")
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) gLog.i("openp2p start. version: %s", OpenP2PVersion)
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]") gLog.i("Contact: QQ group 16947733, Email [email protected]")
if gConf.daemonMode { if gConf.daemonMode {
d := daemon{} d := daemon{}
@@ -41,18 +60,18 @@ func Run() {
return return
} }
gLog.Println(LvINFO, &gConf) gLog.i("node=%s, serverHost=%s, serverPort=%d", gConf.Network.Node, gConf.Network.ServerHost, gConf.Network.ServerPort)
setFirewall() setFirewall()
err := setRLimit() err := setRLimit()
if err != nil { if err != nil {
gLog.Println(LvINFO, "setRLimit error:", err) gLog.i("setRLimit error:%s", err)
} }
GNetwork = P2PNetworkInstance() P2PNetworkInstance()
if ok := GNetwork.Connect(30000); !ok { if ok := GNetwork.Connect(30000); !ok {
gLog.Println(LvERROR, "P2PNetwork login error") gLog.e("P2PNetwork login error")
return return
} }
// gLog.Println(LvINFO, "waiting for connection...") // gLog.i("waiting for connection...")
forever := make(chan bool) forever := make(chan bool)
<-forever <-forever
} }
@@ -76,16 +95,16 @@ func RunAsModule(baseDir string, token string, bw int, logLevel int) *P2PNetwork
} }
// gLog.setLevel(LogLevel(logLevel)) // gLog.setLevel(LogLevel(logLevel))
gConf.setShareBandwidth(bw) gConf.setShareBandwidth(bw)
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion) gLog.i("openp2p start. version: %s", OpenP2PVersion)
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]") gLog.i("Contact: QQ group 16947733, Email [email protected]")
gLog.Println(LvINFO, &gConf) gLog.i("node=%s, serverHost=%s, serverPort=%d", gConf.Network.Node, gConf.Network.ServerHost, gConf.Network.ServerPort)
GNetwork = P2PNetworkInstance() P2PNetworkInstance()
if ok := GNetwork.Connect(30000); !ok { if ok := GNetwork.Connect(30000); !ok {
gLog.Println(LvERROR, "P2PNetwork login error") gLog.e("P2PNetwork login error")
return nil return nil
} }
// gLog.Println(LvINFO, "waiting for connection...") // gLog.i("waiting for connection...")
return GNetwork return GNetwork
} }
@@ -99,11 +118,11 @@ func RunCmd(cmd string) {
setFirewall() setFirewall()
err := setRLimit() err := setRLimit()
if err != nil { if err != nil {
gLog.Println(LvINFO, "setRLimit error:", err) gLog.i("setRLimit error:%s", err)
} }
GNetwork = P2PNetworkInstance() P2PNetworkInstance()
if ok := GNetwork.Connect(30000); !ok { if ok := GNetwork.Connect(30000); !ok {
gLog.Println(LvERROR, "P2PNetwork login error") gLog.e("P2PNetwork login error")
return return
} }
forever := make(chan bool) forever := make(chan bool)
@@ -116,6 +135,14 @@ func GetToken(baseDir string) string {
return fmt.Sprintf("%d", gConf.Network.Token) return fmt.Sprintf("%d", gConf.Network.Token)
} }
func SetToken(token string) {
n, err := strconv.ParseUint(token, 10, 64)
if err == nil && n > 0 {
gConf.setToken(n)
gConf.save()
}
}
func Stop() { func Stop() {
os.Exit(0) os.Exit(0)
} }
+3
View File
@@ -4,7 +4,10 @@ import (
"github.com/openp2p-cn/wireguard-go/tun" "github.com/openp2p-cn/wireguard-go/tun"
) )
const optunMTU = 1420
var AndroidSDWANConfig chan []byte var AndroidSDWANConfig chan []byte
var preAndroidSDWANConfig string
type optun struct { type optun struct {
tunName string tunName string
+20 -10
View File
@@ -5,12 +5,14 @@
package openp2p package openp2p
import ( import (
"net" "time"
) )
const ( const (
tunIfaceName = "optun" tunIfaceName = "optun"
PIHeaderSize = 0 PIHeaderSize = 0
ReadTunBuffSize = 2048
ReadTunBuffNum = 16
) )
var AndroidReadTun chan []byte // TODO: multi channel var AndroidReadTun chan []byte // TODO: multi channel
@@ -35,27 +37,35 @@ func (t *optun) Write(bufs [][]byte, offset int) (int, error) {
func AndroidRead(data []byte, len int) { func AndroidRead(data []byte, len int) {
head := PacketHeader{} head := PacketHeader{}
parseHeader(data, &head) parseHeader(data, &head)
gLog.Printf(LvDev, "AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len) // gLog.dev("AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len)
buf := make([]byte, len) buf := make([]byte, len)
copy(buf, data) copy(buf, data)
AndroidReadTun <- buf AndroidReadTun <- buf
} }
func AndroidWrite(buf []byte) int { func AndroidWrite(buf []byte, timeoutMs int) int {
p := <-AndroidWriteTun timeout := time.Duration(timeoutMs) * time.Millisecond
copy(buf, p) select {
return len(p) case p := <-AndroidWriteTun:
if len(p) > int(gConf.sdwan.Mtu) {
gLog.e("AndroidWrite packet too large %d", len(p))
}
copy(buf, p)
return len(p)
case <-time.After(timeout):
return 0
}
} }
func GetAndroidSDWANConfig(buf []byte) int { func GetAndroidSDWANConfig(buf []byte) int {
p := <-AndroidSDWANConfig p := <-AndroidSDWANConfig
copy(buf, p) copy(buf, p)
gLog.Printf(LvINFO, "AndroidSDWANConfig=%s", p) gLog.i("AndroidSDWANConfig=%s", p)
return len(p) return len(p)
} }
func GetAndroidNodeName() string { func GetAndroidNodeName() string {
gLog.Printf(LvINFO, "GetAndroidNodeName=%s", gConf.Network.Node) gLog.i("GetAndroidNodeName=%s", gConf.Network.Node)
return gConf.Network.Node return gConf.Network.Node
} }
+12 -9
View File
@@ -10,14 +10,16 @@ import (
) )
const ( const (
tunIfaceName = "utun" tunIfaceName = "utun"
PIHeaderSize = 4 // utun has no IFF_NO_PI PIHeaderSize = 4 // utun has no IFF_NO_PI
ReadTunBuffSize = 2048
ReadTunBuffNum = 16
) )
func (t *optun) Start(localAddr string, detail *SDWANInfo) error { func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
var err error var err error
t.tunName = tunIfaceName t.tunName = tunIfaceName
t.dev, err = tun.CreateTUN(t.tunName, 1420) t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
if err != nil { if err != nil {
return err return err
} }
@@ -52,7 +54,7 @@ func addRoute(dst, gw, ifname string) error {
} }
func delRoute(dst, gw string) error { func delRoute(dst, gw string) error {
err := exec.Command("route", "delete", dst, gw).Run() err := exec.Command("route", "delete", dst, "-gateway", gw).Run()
return err return err
} }
func delRoutesByGateway(gateway string) error { func delRoutesByGateway(gateway string) error {
@@ -68,13 +70,14 @@ func delRoutesByGateway(gateway string) error {
continue continue
} }
fields := strings.Fields(line) fields := strings.Fields(line)
if len(fields) >= 7 && fields[0] == "default" && fields[len(fields)-1] == gateway { if len(fields) >= 2 {
delCmd := exec.Command("route", "delete", "default", gateway) cmd := exec.Command("route", "delete", fields[0], gateway)
err := delCmd.Run() err := cmd.Run()
if err != nil { if err != nil {
return err gLog.e("Delete route %s error:%s", fields[0], err)
continue
} }
fmt.Printf("Delete route ok: %s %s\n", "default", gateway) gLog.i("Delete route ok: %s %s\n", fields[0], gateway)
} }
} }
return nil return nil
+25 -20
View File
@@ -7,8 +7,7 @@ package openp2p
import ( import (
"fmt" "fmt"
"net" "net"
"os/exec" "os"
"strings"
"github.com/openp2p-cn/wireguard-go/tun" "github.com/openp2p-cn/wireguard-go/tun"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
@@ -17,6 +16,9 @@ import (
const ( const (
tunIfaceName = "optun" tunIfaceName = "optun"
PIHeaderSize = 0 PIHeaderSize = 0
// sdwan
ReadTunBuffSize = 2048
ReadTunBuffNum = 16
) )
var previousIP = "" var previousIP = ""
@@ -24,10 +26,14 @@ var previousIP = ""
func (t *optun) Start(localAddr string, detail *SDWANInfo) error { func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
var err error var err error
t.tunName = tunIfaceName t.tunName = tunIfaceName
t.dev, err = tun.CreateTUN(t.tunName, 1420) t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
if err != nil { if err != nil {
return err return err
} }
err = os.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte("1"), 0644)
if err != nil {
gLog.e("write ip_forward error:%s", err)
}
return nil return nil
} }
@@ -44,8 +50,8 @@ func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error
if err != nil { if err != nil {
return err return err
} }
netlink.LinkSetMTU(ifce, 1375) netlink.LinkSetMTU(ifce, int(gConf.getSDWAN().Mtu))
netlink.LinkSetTxQLen(ifce, 100) netlink.LinkSetTxQLen(ifce, 1000)
netlink.LinkSetUp(ifce) netlink.LinkSetUp(ifce)
ln, err := netlink.ParseIPNet(localAddr) ln, err := netlink.ParseIPNet(localAddr)
@@ -108,25 +114,24 @@ func delRoute(dst, gw string) error {
} }
func delRoutesByGateway(gateway string) error { func delRoutesByGateway(gateway string) error {
cmd := exec.Command("route", "-n") ipGW := net.ParseIP(gateway)
output, err := cmd.Output() if ipGW == nil {
if err != nil { return fmt.Errorf("invalid gateway IP: %s", gateway)
return err
} }
lines := strings.Split(string(output), "\n") routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
for _, line := range lines { if err != nil {
if !strings.Contains(line, gateway) { return fmt.Errorf("failed to list routes: %v", err)
continue }
}
fields := strings.Fields(line) for _, route := range routes {
if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway { if route.Gw != nil && route.Gw.Equal(ipGW) || (route.Dst != nil && route.Dst.IP.Equal(ipGW)) {
delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway) err := netlink.RouteDel(&route)
err := delCmd.Run()
if err != nil { if err != nil {
return err gLog.e("Failed to delete route: %v, error: %v", route, err)
continue
} }
fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) gLog.i("Deleted route: %v", route)
} }
} }
return nil return nil
+109 -16
View File
@@ -3,40 +3,133 @@
package openp2p package openp2p
import "github.com/openp2p-cn/wireguard-go/tun" import (
"fmt"
"net"
"os/exec"
"strings"
"github.com/openp2p-cn/wireguard-go/tun"
"github.com/vishvananda/netlink"
)
const ( const (
tunIfaceName = "optun" tunIfaceName = "optun"
PIHeaderSize = 0 PIHeaderSize = 0
ReadTunBuffSize = 2048
ReadTunBuffNum = 16
) )
var previousIP = ""
func (t *optun) Start(localAddr string, detail *SDWANInfo) error { func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
var err error var err error
t.tunName = tunIfaceName t.tunName = tunIfaceName
t.dev, err = tun.CreateTUN(t.tunName, 1420) t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
if err != nil { if err != nil {
return err return err
} }
err = setTunAddr(t.tunName, localAddr, detail.Gateway, t.dev)
if err != nil {
return err
}
return nil return nil
} }
func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
return t.dev.Read(bufs, sizes, offset)
}
func (t *optun) Write(bufs [][]byte, offset int) (int, error) {
return t.dev.Write(bufs, offset)
}
func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error {
ifce, err := netlink.LinkByName(ifname)
if err != nil {
return err
}
netlink.LinkSetMTU(ifce, int(gConf.getSDWAN().Mtu))
netlink.LinkSetTxQLen(ifce, 1000)
netlink.LinkSetUp(ifce)
ln, err := netlink.ParseIPNet(localAddr)
if err != nil {
return err
}
ln.Mask = net.CIDRMask(32, 32)
rn, err := netlink.ParseIPNet(remoteAddr)
if err != nil {
return err
}
rn.Mask = net.CIDRMask(32, 32)
addr := &netlink.Addr{
IPNet: ln,
Peer: rn,
}
if previousIP != "" {
lnDel, err := netlink.ParseIPNet(previousIP)
if err != nil {
return err
}
lnDel.Mask = net.CIDRMask(32, 32)
addrDel := &netlink.Addr{
IPNet: lnDel,
Peer: rn,
}
netlink.AddrDel(ifce, addrDel)
}
previousIP = localAddr
return netlink.AddrAdd(ifce, addr)
}
func addRoute(dst, gw, ifname string) error { func addRoute(dst, gw, ifname string) error {
return nil _, networkid, err := net.ParseCIDR(dst)
if err != nil {
return err
}
ipGW := net.ParseIP(gw)
if ipGW == nil {
return fmt.Errorf("parse gateway %s failed", gw)
}
route := &netlink.Route{
Dst: networkid,
Gw: ipGW,
}
return netlink.RouteAdd(route)
} }
func delRoute(dst, gw string) error { func delRoute(dst, gw string) error {
return nil _, networkid, err := net.ParseCIDR(dst)
} if err != nil {
func addTunAddr(localAddr, remoteAddr string) error { return err
return nil }
route := &netlink.Route{
Dst: networkid,
}
return netlink.RouteDel(route)
} }
func delTunAddr(localAddr, remoteAddr string) error { func delRoutesByGateway(gateway string) error {
cmd := exec.Command("route", "-n")
output, err := cmd.Output()
if err != nil {
return err
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if !strings.Contains(line, gateway) {
continue
}
fields := strings.Fields(line)
if len(fields) >= 8 && fields[1] == "0.0.0.0" && fields[7] == gateway {
delCmd := exec.Command("route", "del", "-net", fields[0], "gw", gateway)
err := delCmd.Run()
if err != nil {
gLog.e("Delete route %s error:%s", fields[0], err)
continue
}
gLog.i("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
}
}
return nil return nil
} }
+10 -6
View File
@@ -19,6 +19,9 @@ import (
const ( const (
tunIfaceName = "optun" tunIfaceName = "optun"
PIHeaderSize = 0 PIHeaderSize = 0
// sdwan
ReadTunBuffSize = 1024 * 64 // wintun will read date len > mtu, default 64k
ReadTunBuffNum = 4
) )
func (t *optun) Start(localAddr string, detail *SDWANInfo) error { func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
@@ -42,9 +45,9 @@ func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
Data3: 0x4567, Data3: 0x4567,
Data4: [8]byte{0x80, 0x42, 0x83, 0x7e, 0xf4, 0x56, 0xce, 0x13}, Data4: [8]byte{0x80, 0x42, 0x83, 0x7e, 0xf4, 0x56, 0xce, 0x13},
} }
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, int(detail.Mtu))
if err != nil { // retry if err != nil { // retry
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420) t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, int(detail.Mtu))
} }
if err != nil { if err != nil {
@@ -67,12 +70,12 @@ func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error
link := winipcfg.LUID(nativeTunDevice.LUID()) link := winipcfg.LUID(nativeTunDevice.LUID())
ip, err := netip.ParsePrefix(localAddr) ip, err := netip.ParsePrefix(localAddr)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr) gLog.e("ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr)
return err return err
} }
err = link.SetIPAddresses([]netip.Prefix{ip}) err = link.SetIPAddresses([]netip.Prefix{ip})
if err != nil { if err != nil {
gLog.Printf(LvERROR, "SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip}) gLog.e("SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip})
return err return err
} }
return nil return nil
@@ -133,9 +136,10 @@ func delRoutesByGateway(gateway string) error {
cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway) cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway)
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
fmt.Println("Delete route error:", err) gLog.e("Delete route %s error:%s", fields[0], err)
continue
} }
fmt.Printf("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway) gLog.i("Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
} }
} }
return nil return nil
+32 -35
View File
@@ -5,6 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"net" "net"
"sync"
"time" "time"
) )
@@ -21,18 +22,24 @@ func (e *DeadlineExceededError) Error() string { return "i/o timeout" }
func (e *DeadlineExceededError) Timeout() bool { return true } func (e *DeadlineExceededError) Timeout() bool { return true }
func (e *DeadlineExceededError) Temporary() bool { return true } func (e *DeadlineExceededError) Temporary() bool { return true }
var overlayConns sync.Map // both TCP and UDP
func closeOverlayConns(appID uint64) {
overlayConns.Range(func(_, i interface{}) bool {
oConn := i.(*overlayConn)
if oConn.app.id == appID {
oConn.Close()
}
return true
})
}
// implement io.Writer // implement io.Writer
type overlayConn struct { type overlayConn struct {
tunnel *P2PTunnel // TODO: del app *p2pApp
app *p2pApp connTCP net.Conn
connTCP net.Conn id uint64
id uint64 running bool
rtid uint64 isClient bool
running bool
isClient bool
appID uint64 // TODO: del
appKey uint64 // TODO: del
appKeyBytes []byte // TODO: del
// for udp // for udp
connUDP *net.UDPConn connUDP *net.UDPConn
remoteAddr net.Addr remoteAddr net.Addr
@@ -41,42 +48,31 @@ type overlayConn struct {
} }
func (oConn *overlayConn) run() { func (oConn *overlayConn) run() {
gLog.Printf(LvDEBUG, "%d overlayConn run start", oConn.id) gLog.d("oid:%d overlayConn run start", oConn.id)
defer gLog.Printf(LvDEBUG, "%d overlayConn run end", oConn.id) defer gLog.d("oid:%d overlayConn run end", oConn.id)
oConn.lastReadUDPTs = time.Now() oConn.lastReadUDPTs = time.Now()
buffer := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding buffer := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
reuseBuff := buffer[:ReadBuffLen] reuseBuff := buffer[:ReadBuffLen]
encryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding encryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
tunnelHead := new(bytes.Buffer) overlayHead := new(bytes.Buffer)
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, oConn.rtid) binary.Write(overlayHead, binary.LittleEndian, oConn.id)
binary.Write(tunnelHead, binary.LittleEndian, oConn.id) for oConn.running && oConn.app.running {
for oConn.running && oConn.tunnel.isRuning() {
readBuff, dataLen, err := oConn.Read(reuseBuff) readBuff, dataLen, err := oConn.Read(reuseBuff)
if err != nil { if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() { if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue continue
} }
// overlay tcp connection normal close, debug log // overlay tcp connection normal close, debug log
gLog.Printf(LvDEBUG, "overlayConn %d read error:%s,close it", oConn.id, err) gLog.d("oid:%d overlayConn read error:%s,close it", oConn.id, err)
break break
} }
payload := readBuff[:dataLen] payload := readBuff[:dataLen]
if oConn.appKey != 0 { if oConn.app.key != 0 {
payload, _ = encryptBytes(oConn.appKeyBytes, encryptData, readBuff[:dataLen], dataLen) payload, _ = encryptBytes(oConn.app.appKeyBytes, encryptData, readBuff[:dataLen], dataLen)
}
writeBytes := append(tunnelHead.Bytes(), payload...)
// TODO: app.write
if oConn.rtid == 0 {
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgOverlayData, writeBytes)
gLog.Printf(LvDev, "write overlay data to tid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.id, len(writeBytes))
} else {
// write raley data
all := append(relayHead.Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(writeBytes)))...)
all = append(all, writeBytes...)
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, all)
gLog.Printf(LvDev, "write relay data to tid:%d,rtid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.rtid, oConn.id, len(writeBytes))
} }
writeBytes := append(overlayHead.Bytes(), payload...)
oConn.app.WriteBytes(writeBytes)
} }
if oConn.connTCP != nil { if oConn.connTCP != nil {
oConn.connTCP.Close() oConn.connTCP.Close()
@@ -84,10 +80,10 @@ func (oConn *overlayConn) run() {
if oConn.connUDP != nil { if oConn.connUDP != nil {
oConn.connUDP.Close() oConn.connUDP.Close()
} }
oConn.tunnel.overlayConns.Delete(oConn.id) overlayConns.Delete(oConn.id)
// notify peer disconnect // notify peer disconnect
req := OverlayDisconnectReq{ID: oConn.id} req := OverlayDisconnectReq{ID: oConn.id}
oConn.tunnel.WriteMessage(oConn.rtid, MsgP2P, MsgOverlayDisconnectReq, &req) oConn.app.WriteMessage(MsgP2P, MsgOverlayDisconnectReq, &req)
} }
func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, dataLen int, err error) { func (oConn *overlayConn) Read(reuseBuff []byte) (buff []byte, dataLen int, err error) {
@@ -145,7 +141,8 @@ func (oConn *overlayConn) Write(buff []byte) (n int, err error) {
return return
} }
if oConn.connTCP != nil { if oConn.connTCP != nil {
n, err = oConn.connTCP.Write(buff) err = writeFull(oConn.connTCP, buff)
n = len(buff)
} }
if err != nil { if err != nil {
+512 -248
View File
@@ -4,167 +4,233 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"math"
"math/rand" "math/rand"
"net" "net"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic"
"time" "time"
) )
const DefaultRtt int32 = 1000
const MaxWindowSize = 1024 * 128 // max 32k packets in flight
const MergeAckDelay = 40 // 40ms linux kernel tcp
const RetransmissonTime = MergeAckDelay + 2000 // ms
type appMsgCtx struct {
head *openP2PHeader
body []byte
ts time.Time
}
type p2pApp struct { type p2pApp struct {
config AppConfig config AppConfig
listener net.Listener listener net.Listener
listenerUDP *net.UDPConn listenerUDP *net.UDPConn
directTunnel *P2PTunnel
relayTunnel *P2PTunnel tunnelMtx sync.Mutex
tunnelMtx sync.Mutex iptree *IPTree // for whitelist
iptree *IPTree // for whitelist
rtid uint64 // relay tunnelID hbMtx sync.Mutex
relayNode string running bool
relayMode string // public/private id uint64
hbTimeRelay time.Time key uint64 // aes
hbMtx sync.Mutex appKeyBytes []byte // pre-calc
running bool wg sync.WaitGroup
id uint64 msgChan chan appMsgCtx
key uint64 // aes once sync.Once
wg sync.WaitGroup tunnelNum int
relayHead *bytes.Buffer relayIdxStart int
once sync.Once allTunnels []*P2PTunnel
// for relayTunnel retryNum []int
retryRelayNum int retryTime []time.Time
retryRelayTime time.Time nextRetryTime []time.Time
nextRetryRelayTime time.Time rtt []atomic.Int32
errMsg string relayHead []*bytes.Buffer
connectTime time.Time rtid []uint64 // peer relay tunnelID
relayNode []string
relayMode []string // public/private
hbTime []time.Time
whbTime []time.Time // calc each tunnel rtt by hb
unAckSeqStart []atomic.Uint64 // record unack packet for retransmission
unAckSeqEnd []atomic.Uint64
errMsg string
connectTime time.Time
// asyncWriteChan chan []byte
maxWindowSize uint64
unAckTs []atomic.Int64
writeTs []atomic.Int64
readCacheTs atomic.Int64
seqW uint64
seqR uint64
seqRMtx sync.Mutex
handleAckMtx sync.Mutex
mergeAckSeq []atomic.Uint64
mergeAckTs []atomic.Int64
preDirectSuccessIP string
} }
func (app *p2pApp) Tunnel() *P2PTunnel {
app.tunnelMtx.Lock() func (app *p2pApp) Tunnel(idx int) *P2PTunnel {
defer app.tunnelMtx.Unlock() if idx > app.tunnelNum-1 {
if app.directTunnel != nil { return nil
return app.directTunnel
} }
return app.relayTunnel
}
func (app *p2pApp) DirectTunnel() *P2PTunnel {
app.tunnelMtx.Lock() app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock() defer app.tunnelMtx.Unlock()
return app.directTunnel return app.allTunnels[idx]
} }
func (app *p2pApp) setDirectTunnel(t *P2PTunnel) { func (app *p2pApp) SetTunnel(t *P2PTunnel, idx int) {
app.tunnelMtx.Lock() app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock() defer app.tunnelMtx.Unlock()
app.directTunnel = t app.allTunnels[idx] = t
}
func (app *p2pApp) RelayTunnel() *P2PTunnel { app.rtt[idx].Store(DefaultRtt)
app.tunnelMtx.Lock() app.unAckTs[idx].Store(0)
defer app.tunnelMtx.Unlock() app.writeTs[idx].Store(0)
return app.relayTunnel
}
func (app *p2pApp) setRelayTunnel(t *P2PTunnel) {
app.tunnelMtx.Lock()
defer app.tunnelMtx.Unlock()
app.relayTunnel = t
}
func (app *p2pApp) isDirect() bool {
return app.directTunnel != nil
}
func (app *p2pApp) RelayTunnelID() uint64 {
if app.isDirect() {
return 0
}
return app.rtid
} }
func (app *p2pApp) ConnectTime() time.Time { func (app *p2pApp) ConnectTime() time.Time {
if app.isDirect() { if app.allTunnels[0] != nil {
return app.config.connectTime return app.config.connectTime
} }
return app.connectTime return app.connectTime
} }
func (app *p2pApp) RetryTime() time.Time { func (app *p2pApp) RetryTime() time.Time {
if app.isDirect() { if app.allTunnels[0] != nil {
return app.config.retryTime return app.config.retryTime
} }
return app.retryRelayTime return app.retryTime[app.relayIdxStart]
} }
func (app *p2pApp) checkP2PTunnel() error { func (app *p2pApp) Init(tunnelNum int) {
app.tunnelNum = tunnelNum
app.allTunnels = make([]*P2PTunnel, tunnelNum)
app.retryNum = make([]int, tunnelNum)
app.retryTime = make([]time.Time, tunnelNum)
app.nextRetryTime = make([]time.Time, tunnelNum)
app.rtt = make([]atomic.Int32, tunnelNum)
app.relayHead = make([]*bytes.Buffer, tunnelNum)
app.rtid = make([]uint64, tunnelNum)
app.relayNode = make([]string, tunnelNum)
app.relayMode = make([]string, tunnelNum)
app.hbTime = make([]time.Time, tunnelNum)
app.whbTime = make([]time.Time, tunnelNum)
app.unAckSeqEnd = make([]atomic.Uint64, tunnelNum)
app.unAckTs = make([]atomic.Int64, tunnelNum)
app.writeTs = make([]atomic.Int64, tunnelNum)
app.unAckSeqStart = make([]atomic.Uint64, tunnelNum)
app.mergeAckSeq = make([]atomic.Uint64, tunnelNum)
app.mergeAckTs = make([]atomic.Int64, tunnelNum)
app.msgChan = make(chan appMsgCtx, 50)
for i := 0; i < tunnelNum; i++ {
app.hbTime[i] = time.Now()
}
app.relayIdxStart = app.tunnelNum - 2
if app.relayIdxStart == 0 {
app.relayIdxStart = 1 // at least one direct tunnel
}
// app.unAckSeqStart.Store(0)
// app.mergeAckTs.Store(0)
// for i := 0; i < relayNum; i++ {
// app.mergeAckTsRelay[i].Store(0)
// }
}
func (app *p2pApp) Start(isClient bool) {
app.maxWindowSize = MaxWindowSize
app.PreCalcKeyBytes()
if isClient {
go app.daemonP2PTunnel()
}
}
func (app *p2pApp) daemonP2PTunnel() error {
for app.running { for app.running {
app.checkDirectTunnel()
app.checkRelayTunnel() for i := 0; i < app.relayIdxStart; i++ {
app.daemonDirectTunnel(i)
time.Sleep(time.Second)
}
for i := app.relayIdxStart; i < app.tunnelNum; i++ {
if i > app.relayIdxStart {
app.nextRetryTime[i] = time.Now().Add(time.Second * 180) // the second relay tunnel wait 3 mins
}
app.daemonRelayTunnel(i)
time.Sleep(time.Second)
}
time.Sleep(time.Second * 3) time.Sleep(time.Second * 3)
} }
return nil return nil
} }
func (app *p2pApp) directRetryLimit() int { func (app *p2pApp) daemonDirectTunnel(idx int) error {
if app.config.peerIP == gConf.Network.publicIP && compareVersion(app.config.peerVersion, SupportIntranetVersion) >= 0 { if !GNetwork.online {
return retryLimit return nil
} }
if IsIPv6(app.config.peerIPv6) && IsIPv6(gConf.IPv6()) {
return retryLimit
}
if app.config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || app.config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
return retryLimit
}
if gConf.Network.natType == NATCone && app.config.peerNatType == NATCone {
return retryLimit
}
if app.config.peerNatType == NATSymmetric && gConf.Network.natType == NATSymmetric {
return 0
}
return retryLimit / 10 // c2s or s2c
}
func (app *p2pApp) checkDirectTunnel() error {
if app.config.ForceRelay == 1 && app.config.RelayNode != app.config.PeerNode { if app.config.ForceRelay == 1 && app.config.RelayNode != app.config.PeerNode {
return nil return nil
} }
if app.DirectTunnel() != nil && app.DirectTunnel().isActive() { // TODO: multi direct tunnel support symmetric NAT traversal later
if idx > 0 && gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 && app.config.hasIPv4 == 0 && app.config.hasUPNPorNATPMP == 0 && (gConf.Network.natType == NATSymmetric || app.config.peerNatType == NATSymmetric) {
return nil return nil
} }
if app.config.nextRetryTime.After(time.Now()) || app.config.Enabled == 0 || app.config.retryNum >= app.directRetryLimit() { if app.Tunnel(idx) != nil && app.Tunnel(idx).isActive() {
return nil
}
if app.config.nextRetryTime.After(time.Now()) || app.config.Enabled == 0 {
return nil return nil
} }
if time.Now().Add(-time.Minute * 15).After(app.config.retryTime) { // run normally 15min, reset retrynum if time.Now().Add(-time.Minute * 15).After(app.config.retryTime) { // run normally 15min, reset retrynum
app.config.retryNum = 1 app.retryNum[idx] = 1
} }
if app.config.retryNum > 0 { // first time not show reconnect log if app.retryNum[idx] > 0 { // first time not show reconnect log
gLog.Printf(LvINFO, "detect app %s appid:%d disconnect, reconnecting the %d times...", app.config.PeerNode, app.id, app.config.retryNum) gLog.i("appid:%d checkDirectTunnel detect peer %s disconnect, reconnecting the %d times...", app.id, app.config.LogPeerNode(), app.retryNum[idx])
} }
app.config.retryNum++ app.retryNum[idx]++
app.config.retryTime = time.Now() app.config.retryTime = time.Now()
app.config.nextRetryTime = time.Now().Add(retryInterval)
app.config.connectTime = time.Now() app.config.connectTime = time.Now()
err := app.buildDirectTunnel() err := app.buildDirectTunnel(idx)
if err != nil { if err != nil {
app.config.errMsg = err.Error() app.config.errMsg = err.Error()
if err == ErrPeerOffline && app.config.retryNum > 2 { // stop retry, waiting for online if err == ErrPeerOffline && app.retryNum[idx] > 2 { // stop retry, waiting for online
app.config.retryNum = retryLimit app.retryNum[idx] = retryLimit
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.PeerNode) gLog.i("appid:%d checkDirectTunnel %s offline, it will auto reconnect when peer node online", app.id, app.config.LogPeerNode())
} }
if err == ErrBuildTunnelBusy { if err == ErrBuildTunnelBusy {
app.config.retryNum-- app.retryNum[idx]--
} }
} }
if app.Tunnel() != nil { interval := calcRetryTimeRelay(float64(app.retryNum[idx]))
if app.preDirectSuccessIP == app.config.peerIP {
interval = math.Min(interval, 1800) // if peerIP has been direct link succeed, retry 30min max
}
app.config.nextRetryTime = time.Now().Add(time.Duration(interval) * time.Second)
if app.Tunnel(idx) != nil {
app.preDirectSuccessIP = app.config.peerIP
app.once.Do(func() { app.once.Do(func() {
go app.listen() go app.listen()
// memapp also need // memapp also need
go app.relayHeartbeatLoop() for i := app.relayIdxStart; i < app.tunnelNum; i++ {
go app.relayHeartbeatLoop(i)
}
}) })
} }
return nil return nil
} }
func (app *p2pApp) buildDirectTunnel() error { func (app *p2pApp) buildDirectTunnel(idx int) error {
relayNode := "" relayNode := ""
peerNatType := NATUnknown peerNatType := NATUnknown
peerIP := "" peerIP := ""
@@ -172,12 +238,13 @@ func (app *p2pApp) buildDirectTunnel() error {
var t *P2PTunnel var t *P2PTunnel
var err error var err error
pn := GNetwork pn := GNetwork
// TODO: optimize requestPeerInfo call frequency
initErr := pn.requestPeerInfo(&app.config) initErr := pn.requestPeerInfo(&app.config)
if initErr != nil { if initErr != nil {
gLog.Printf(LvERROR, "%s init error:%s", app.config.PeerNode, initErr) gLog.w("appid:%d buildDirectTunnel %s requestPeerInfo error:%s", app.id, app.config.LogPeerNode(), initErr)
return initErr return initErr
} }
t, err = pn.addDirectTunnel(app.config, 0) t, err = pn.addDirectTunnel(app.config, 0, app.Tunnel(idx^1))
if t != nil { if t != nil {
peerNatType = t.config.peerNatType peerNatType = t.config.peerNatType
peerIP = t.config.peerIP peerIP = t.config.peerIP
@@ -212,64 +279,78 @@ func (app *p2pApp) buildDirectTunnel() error {
AppID: app.id, AppID: app.id,
AppKey: app.key, AppKey: app.key,
} }
gLog.Printf(LvDEBUG, "sync appkey direct to %s", app.config.PeerNode) gLog.d("appid:%d buildDirectTunnel sync appkey to %s", app.id, app.config.LogPeerNode())
pn.push(app.config.PeerNode, MsgPushAPPKey, &syncKeyReq) pn.push(app.config.PeerNode, MsgPushAPPKey, &syncKeyReq)
app.setDirectTunnel(t) app.SetTunnel(t, idx)
// if memapp notify peer addmemapp // if memapp notify peer addmemapp
if app.config.SrcPort == 0 { // if app.config.SrcPort == 0 {
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, AppID: app.id} req2 := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, RelayIndex: uint32(idx), TunnelNum: uint32(app.tunnelNum), AppID: app.id, AppKey: app.key, SrcPort: uint32(app.config.SrcPort)}
pn.push(app.config.PeerNode, MsgPushServerSideSaveMemApp, &req) pn.push(app.config.PeerNode, MsgPushServerSideSaveMemApp, &req2)
gLog.Printf(LvDEBUG, "push %s ServerSideSaveMemApp: %s", app.config.PeerNode, prettyJson(req)) gLog.d("appid:%d buildDirectTunnel push %s ServerSideSaveMemApp: %s", app.id, app.config.LogPeerNode(), prettyJson(req2))
}
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id) // }
gLog.d("appid:%d buildDirectTunnel ok. %s use tid %d", app.id, app.config.AppName, t.id)
return nil return nil
} }
func (app *p2pApp) checkRelayTunnel() error { func (app *p2pApp) daemonRelayTunnel(idx int) error {
if !GNetwork.online {
return nil
}
if app.Tunnel(0) != nil && app.relayIdxStart >= 2 { // multi direct tunnel no relay
return nil
}
// if app.config.ForceRelay == 1 && (gConf.sdwan.CentralNode == app.config.PeerNode && compareVersion(app.config.peerVersion, SupportDualTunnelVersion) < 0) { // if app.config.ForceRelay == 1 && (gConf.sdwan.CentralNode == app.config.PeerNode && compareVersion(app.config.peerVersion, SupportDualTunnelVersion) < 0) {
if app.config.SrcPort == 0 && (gConf.sdwan.CentralNode == app.config.PeerNode || gConf.sdwan.CentralNode == gConf.Network.Node) { // memapp central node not build relay tunnel if app.config.SrcPort == 0 && (gConf.sdwan.CentralNode == app.config.PeerNode || gConf.sdwan.CentralNode == gConf.Network.Node) { // memapp central node not build relay tunnel
return nil return nil
} }
if gConf.sdwan.CentralNode != "" && idx != app.relayIdxStart { // if central node exist only need one relayTunnel
return nil
}
app.hbMtx.Lock() app.hbMtx.Lock()
if app.RelayTunnel() != nil && time.Now().Before(app.hbTimeRelay.Add(TunnelHeartbeatTime*2)) { // must check app.hbtime instead of relayTunnel if app.Tunnel(idx) != nil && time.Now().Before(app.hbTime[idx].Add(TunnelHeartbeatTime*2)) { // must check app.hbtime instead of relayTunnel
app.hbMtx.Unlock() app.hbMtx.Unlock()
return nil return nil
} }
app.hbMtx.Unlock() app.hbMtx.Unlock()
if app.nextRetryRelayTime.After(time.Now()) || app.config.Enabled == 0 || app.retryRelayNum >= retryLimit { if app.nextRetryTime[idx].After(time.Now()) || app.config.Enabled == 0 {
return nil return nil
} }
if time.Now().Add(-time.Minute * 15).After(app.retryRelayTime) { // run normally 15min, reset retrynum if time.Now().Add(-time.Minute * 15).After(app.retryTime[idx]) { // run normally 15min, reset retrynum
app.retryRelayNum = 1 app.retryNum[idx] = 1
} }
if app.retryRelayNum > 0 { // first time not show reconnect log if app.retryNum[idx] > 0 { // first time not show reconnect log
gLog.Printf(LvINFO, "detect app %s appid:%d relay disconnect, reconnecting the %d times...", app.config.PeerNode, app.id, app.retryRelayNum) gLog.i("appid:%d checkRelayTunnel detect peer %s relay disconnect, reconnecting the %d times...", app.id, app.config.LogPeerNode(), app.retryNum[idx])
} }
app.setRelayTunnel(nil) // reset relayTunnel app.SetTunnel(nil, idx) // reset relayTunnel
app.retryRelayNum++ app.retryNum[idx]++
app.retryRelayTime = time.Now() app.retryTime[idx] = time.Now()
app.nextRetryRelayTime = time.Now().Add(retryInterval)
app.connectTime = time.Now() app.connectTime = time.Now()
err := app.buildRelayTunnel() err := app.buildRelayTunnel(idx)
if err != nil { if err != nil {
app.errMsg = err.Error() app.errMsg = err.Error()
if err == ErrPeerOffline && app.retryRelayNum > 2 { // stop retry, waiting for online if err == ErrPeerOffline && app.retryNum[idx] > 2 { // stop retry, waiting for online
app.retryRelayNum = retryLimit app.retryNum[idx] = retryLimit
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.PeerNode) gLog.i("appid:%d checkRelayTunnel %s offline, it will auto reconnect when peer node online", app.id, app.config.LogPeerNode())
} }
} }
if app.Tunnel() != nil { interval := calcRetryTimeRelay(float64(app.retryNum[idx]))
app.nextRetryTime[idx] = time.Now().Add(time.Duration(interval) * time.Second)
if app.Tunnel(idx) != nil {
app.once.Do(func() { app.once.Do(func() {
go app.listen() go app.listen()
// memapp also need // memapp also need
go app.relayHeartbeatLoop() for i := 1; i < app.tunnelNum; i++ {
go app.relayHeartbeatLoop(i)
}
}) })
} }
return nil return nil
} }
func (app *p2pApp) buildRelayTunnel() error { func (app *p2pApp) buildRelayTunnel(idx int) error {
var rtid uint64 var rtid uint64
relayNode := "" relayNode := ""
relayMode := "" relayMode := ""
@@ -282,11 +363,18 @@ func (app *p2pApp) buildRelayTunnel() error {
config := app.config config := app.config
initErr := pn.requestPeerInfo(&config) initErr := pn.requestPeerInfo(&config)
if initErr != nil { if initErr != nil {
gLog.Printf(LvERROR, "%s init error:%s", config.PeerNode, initErr) gLog.w("appid:%d buildRelayTunnel %s init error:%s", app.id, config.LogPeerNode(), initErr)
return initErr return initErr
} }
ExcludeNodes := ""
t, rtid, relayMode, err = pn.addRelayTunnel(config) theOtherTunnelIdx := app.relayIdxStart
if idx == app.relayIdxStart {
theOtherTunnelIdx = app.relayIdxStart + 1
}
if app.tunnelNum > 2 && app.allTunnels[theOtherTunnelIdx] != nil {
ExcludeNodes = app.allTunnels[theOtherTunnelIdx].config.PeerNode
}
t, rtid, relayMode, err = pn.addRelayTunnel(config, ExcludeNodes)
if t != nil { if t != nil {
relayNode = t.config.PeerNode relayNode = t.config.PeerNode
} }
@@ -294,45 +382,48 @@ func (app *p2pApp) buildRelayTunnel() error {
if err != nil { if err != nil {
errMsg = err.Error() errMsg = err.Error()
} }
req := ReportConnect{ if app.Tunnel(0) == nil {
Error: errMsg, req := ReportConnect{
Protocol: config.Protocol, Error: errMsg,
SrcPort: config.SrcPort, Protocol: config.Protocol,
NatType: gConf.Network.natType, SrcPort: config.SrcPort,
PeerNode: config.PeerNode, NatType: gConf.Network.natType,
DstPort: config.DstPort, PeerNode: config.PeerNode,
DstHost: config.DstHost, DstPort: config.DstPort,
PeerNatType: peerNatType, DstHost: config.DstHost,
PeerIP: peerIP, PeerNatType: peerNatType,
ShareBandwidth: gConf.Network.ShareBandwidth, PeerIP: peerIP,
RelayNode: relayNode, ShareBandwidth: gConf.Network.ShareBandwidth,
Version: OpenP2PVersion, RelayNode: relayNode,
Version: OpenP2PVersion,
}
pn.write(MsgReport, MsgReportConnect, &req)
} }
pn.write(MsgReport, MsgReportConnect, &req) if err != nil || t == nil {
if err != nil {
return err return err
} }
// if rtid != 0 || t.conn.Protocol() == "tcp" { // if rtid != 0 || t.conn.Protocol() == "tcp" {
// sync appkey // sync appkey
syncKeyReq := APPKeySync{ syncKeyReq := APPKeySync{
AppID: app.id, AppID: app.id,
AppKey: app.key, AppKey: app.key,
} }
gLog.Printf(LvDEBUG, "sync appkey relay to %s", config.PeerNode) gLog.d("appid:%d buildRelayTunnel sync appkey relay to %s", app.id, config.LogPeerNode())
pn.push(config.PeerNode, MsgPushAPPKey, &syncKeyReq) pn.push(config.PeerNode, MsgPushAPPKey, &syncKeyReq)
app.setRelayTunnelID(rtid) app.SetRelayTunnelID(rtid, idx)
app.setRelayTunnel(t) app.SetTunnel(t, idx)
app.relayNode = relayNode app.relayNode[idx] = relayNode
app.relayMode = relayMode app.relayMode[idx] = relayMode
app.hbTimeRelay = time.Now() app.hbTime[idx] = time.Now()
// if memapp notify peer addmemapp // if memapp notify peer addmemapp
if config.SrcPort == 0 { // if config.SrcPort == 0 {
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: relayNode, TunnelID: rtid, RelayTunnelID: t.id, AppID: app.id, RelayMode: relayMode} req2 := ServerSideSaveMemApp{From: gConf.Network.Node, Node: relayNode, TunnelID: rtid, RelayTunnelID: t.id, AppID: app.id, AppKey: app.key, RelayMode: relayMode, RelayIndex: uint32(idx), TunnelNum: uint32(app.tunnelNum), SrcPort: uint32(app.config.SrcPort)}
pn.push(config.PeerNode, MsgPushServerSideSaveMemApp, &req) pn.push(config.PeerNode, MsgPushServerSideSaveMemApp, &req2)
gLog.Printf(LvDEBUG, "push %s relay ServerSideSaveMemApp: %s", config.PeerNode, prettyJson(req)) gLog.d("appid:%d buildRelayTunnel push %s relay ServerSideSaveMemApp: %s", app.id, config.LogPeerNode(), prettyJson(req2))
} // }
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id) gLog.d("appid:%d buildRelayTunnel %s use tunnel %d", app.id, app.config.AppName, t.id)
return nil return nil
} }
@@ -341,47 +432,80 @@ func (app *p2pApp) buildOfficialTunnel() error {
} }
// cache relayHead, refresh when rtid change // cache relayHead, refresh when rtid change
func (app *p2pApp) RelayHead() *bytes.Buffer { func (app *p2pApp) RelayHead(idx int) *bytes.Buffer {
if app.relayHead == nil { if app.relayHead[idx] == nil {
app.relayHead = new(bytes.Buffer) app.relayHead[idx] = new(bytes.Buffer)
binary.Write(app.relayHead, binary.LittleEndian, app.rtid) binary.Write(app.relayHead[idx], binary.LittleEndian, app.rtid[idx])
} }
return app.relayHead return app.relayHead[idx]
} }
func (app *p2pApp) setRelayTunnelID(rtid uint64) { func (app *p2pApp) SetRelayTunnelID(rtid uint64, idx int) {
app.rtid = rtid app.rtid[idx] = rtid
app.relayHead = new(bytes.Buffer) app.relayHead[idx] = new(bytes.Buffer)
binary.Write(app.relayHead, binary.LittleEndian, app.rtid) binary.Write(app.relayHead[idx], binary.LittleEndian, app.rtid[idx])
} }
func (app *p2pApp) isActive() bool { func (app *p2pApp) IsActive() bool {
if app.Tunnel() == nil { if t, _ := app.AvailableTunnel(); t == nil {
// gLog.Printf(LvDEBUG, "isActive app.tunnel==nil") // gLog.d("isActive app.tunnel==nil")
return false return false
} }
if app.isDirect() { // direct mode app heartbeat equals to tunnel heartbeat if app.Tunnel(0) != nil { // direct mode app heartbeat equals to tunnel heartbeat
return app.Tunnel().isActive() return app.Tunnel(0).isActive()
} }
// relay mode calc app heartbeat // relay mode calc app heartbeat
app.hbMtx.Lock() app.hbMtx.Lock()
defer app.hbMtx.Unlock() defer app.hbMtx.Unlock()
res := time.Now().Before(app.hbTimeRelay.Add(TunnelHeartbeatTime * 2)) if app.Tunnel(1) != nil {
return time.Now().Before(app.hbTime[1].Add(TunnelHeartbeatTime * 2))
}
res := time.Now().Before(app.hbTime[2].Add(TunnelHeartbeatTime * 2))
// if !res { // if !res {
// gLog.Printf(LvDEBUG, "%d app isActive false. peer=%s", app.id, app.config.PeerNode) // gLog.d("%d app isActive false. peer=%s", app.id, app.config.PeerNode)
// } // }
return res return res
} }
func (app *p2pApp) updateHeartbeat() { // only for relay tunnel heartbeat update
func (app *p2pApp) UpdateHeartbeat(rtid uint64) {
app.hbMtx.Lock() app.hbMtx.Lock()
defer app.hbMtx.Unlock() defer app.hbMtx.Unlock()
app.hbTimeRelay = time.Now() for i := app.relayIdxStart; i < app.tunnelNum; i++ {
if rtid == app.rtid[i] || (app.Tunnel(i) != nil && app.Tunnel(i).id == rtid) {
app.hbTime[i] = time.Now()
rtt := int32(time.Since(app.whbTime[i]) / time.Millisecond)
preRtt := app.rtt[i].Load()
if preRtt != DefaultRtt {
rtt = int32(float64(preRtt)*(1-ma20) + float64(rtt)*ma20)
}
app.rtt[i].Store(rtt)
gLog.dev("appid:%d relay heartbeat %d store rtt %d", app.id, i, rtt)
return
}
}
}
func (app *p2pApp) UpdateRelayHeartbeatTs(rtid uint64) {
app.hbMtx.Lock()
defer app.hbMtx.Unlock()
for i := app.relayIdxStart; i < app.tunnelNum; i++ {
if rtid == app.rtid[i] || (app.Tunnel(i) != nil && app.Tunnel(i).id == rtid) {
app.whbTime[i] = time.Now()
return
}
}
// relayIdx := 1
// if app.tunnelNum > 2 && rtid == app.rtid[2] || (app.Tunnel(2) != nil && app.Tunnel(2).id == rtid) { // ack return rtid!=
// relayIdx = 2
// }
// app.whbTime[relayIdx] = time.Now() // one side did not write relay hb, so write whbtime in this.
} }
func (app *p2pApp) listenTCP() error { func (app *p2pApp) listenTCP() error {
gLog.Printf(LvDEBUG, "tcp accept on port %d start", app.config.SrcPort) gLog.d("appid:%d tcp accept on port %d start", app.id, app.config.SrcPort)
defer gLog.Printf(LvDEBUG, "tcp accept on port %d end", app.config.SrcPort) defer gLog.d("appid:%d tcp accept on port %d end", app.id, app.config.SrcPort)
var err error var err error
listenAddr := "" listenAddr := ""
if IsLocalhost(app.config.Whitelist) { // not expose port if IsLocalhost(app.config.Whitelist) { // not expose port
@@ -389,7 +513,7 @@ func (app *p2pApp) listenTCP() error {
} }
app.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", listenAddr, app.config.SrcPort)) app.listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", listenAddr, app.config.SrcPort))
if err != nil { if err != nil {
gLog.Printf(LvERROR, "listen error:%s", err) gLog.e("appid:%d listen tcp error:%s", app.id, err)
return err return err
} }
defer app.listener.Close() defer app.listener.Close()
@@ -397,12 +521,13 @@ func (app *p2pApp) listenTCP() error {
conn, err := app.listener.Accept() conn, err := app.listener.Accept()
if err != nil { if err != nil {
if app.running { if app.running {
gLog.Printf(LvERROR, "%d accept error:%s", app.id, err) gLog.e("appid:%d accept error:%s", app.id, err)
} }
break break
} }
if app.Tunnel() == nil { t, tidx := app.AvailableTunnel()
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort) if t == nil {
gLog.d("appid:%d srcPort=%d, app.Tunnel()==nil, not ready", app.id, app.config.SrcPort)
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
@@ -411,32 +536,20 @@ func (app *p2pApp) listenTCP() error {
remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP.String() remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP.String()
if !app.iptree.Contains(remoteIP) && !IsLocalhost(remoteIP) { if !app.iptree.Contains(remoteIP) && !IsLocalhost(remoteIP) {
conn.Close() conn.Close()
gLog.Printf(LvERROR, "%s not in whitelist, access denied", remoteIP) gLog.e("appid:%d %s not in whitelist, access denied", app.id, remoteIP)
continue continue
} }
} }
oConn := overlayConn{ oConn := overlayConn{
tunnel: app.Tunnel(),
app: app, app: app,
connTCP: conn, connTCP: conn,
id: rand.Uint64(), id: rand.Uint64(),
isClient: true, isClient: true,
appID: app.id,
appKey: app.key,
running: true, running: true,
} }
if !app.isDirect() {
oConn.rtid = app.rtid overlayConns.Store(oConn.id, &oConn)
} gLog.d("appid:%d Accept TCP overlayID:%d, %s", app.id, oConn.id, oConn.connTCP.RemoteAddr())
// pre-calc key bytes for encrypt
if oConn.appKey != 0 {
encryptKey := make([]byte, AESKeySize)
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
oConn.appKeyBytes = encryptKey
}
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LvDEBUG, "Accept TCP overlayID:%d, %s", oConn.id, oConn.connTCP.RemoteAddr())
// tell peer connect // tell peer connect
req := OverlayConnectReq{ID: oConn.id, req := OverlayConnectReq{ID: oConn.id,
Token: gConf.Network.Token, Token: gConf.Network.Token,
@@ -445,48 +558,52 @@ func (app *p2pApp) listenTCP() error {
Protocol: app.config.Protocol, Protocol: app.config.Protocol,
AppID: app.id, AppID: app.id,
} }
if !app.isDirect() {
req.RelayTunnelID = app.Tunnel().id if tidx != 0 {
req.RelayTunnelID = t.id
}
app.WriteMessage(MsgP2P, MsgOverlayConnectReq, &req)
head, _ := app.ReadMessage(MsgP2P, MsgOverlayConnectRsp, time.Second*3)
if head == nil {
gLog.w("appid:%d read MsgOverlayConnectRsp error", app.id)
} }
app.Tunnel().WriteMessage(app.RelayTunnelID(), MsgP2P, MsgOverlayConnectReq, &req)
// TODO: wait OverlayConnectRsp instead of sleep
time.Sleep(time.Second) // waiting remote node connection ok
go oConn.run() go oConn.run()
} }
return nil return nil
} }
func (app *p2pApp) listenUDP() error { func (app *p2pApp) listenUDP() error {
gLog.Printf(LvDEBUG, "udp accept on port %d start", app.config.SrcPort) gLog.d("appid:%d udp accept on port %d start", app.id, app.config.SrcPort)
defer gLog.Printf(LvDEBUG, "udp accept on port %d end", app.config.SrcPort) defer gLog.d("appid:%d udp accept on port %d end", app.id, app.config.SrcPort)
var err error var err error
app.listenerUDP, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: app.config.SrcPort}) app.listenerUDP, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: app.config.SrcPort})
if err != nil { if err != nil {
gLog.Printf(LvERROR, "listen error:%s", err) gLog.e("appid:%d listen udp error:%s", app.id, err)
return err return err
} }
defer app.listenerUDP.Close() defer app.listenerUDP.Close()
buffer := make([]byte, 64*1024+PaddingSize) buffer := make([]byte, 64*1024+PaddingSize)
udpID := make([]byte, 8) udpID := make([]byte, 8)
for { for app.running {
app.listenerUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout)) app.listenerUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
len, remoteAddr, err := app.listenerUDP.ReadFrom(buffer) len, remoteAddr, err := app.listenerUDP.ReadFrom(buffer)
if err != nil { if err != nil {
if ne, ok := err.(net.Error); ok && ne.Timeout() { if ne, ok := err.(net.Error); ok && ne.Timeout() {
continue continue
} else { } else {
gLog.Printf(LvERROR, "udp read failed:%s", err) gLog.e("appid:%d udp read failed:%s", app.id, err)
break break
} }
} else { } else {
if app.Tunnel() == nil { t, tidx := app.AvailableTunnel()
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort) if t == nil {
gLog.d("appid:%d srcPort=%d, app.Tunnel()==nil, not ready", app.id, app.config.SrcPort)
time.Sleep(time.Second) time.Sleep(time.Second)
continue continue
} }
dupData := bytes.Buffer{} // should uses memory pool dupData := bytes.Buffer{} // should uses memory pool
dupData.Write(buffer[:len+PaddingSize]) dupData.Write(buffer[:len+PaddingSize])
// load from app.tunnel.overlayConns by remoteAddr error, new udp connection // load from app.overlayConns by remoteAddr error, new udp connection
remoteIP := strings.Split(remoteAddr.String(), ":")[0] remoteIP := strings.Split(remoteAddr.String(), ":")[0]
port, _ := strconv.Atoi(strings.Split(remoteAddr.String(), ":")[1]) port, _ := strconv.Atoi(strings.Split(remoteAddr.String(), ":")[1])
a := net.ParseIP(remoteIP) a := net.ParseIP(remoteIP)
@@ -497,31 +614,19 @@ func (app *p2pApp) listenUDP() error {
udpID[4] = byte(port) udpID[4] = byte(port)
udpID[5] = byte(port >> 8) udpID[5] = byte(port >> 8)
id := binary.LittleEndian.Uint64(udpID) // convert remoteIP:port to uint64 id := binary.LittleEndian.Uint64(udpID) // convert remoteIP:port to uint64
s, ok := app.Tunnel().overlayConns.Load(id) s, ok := overlayConns.Load(id)
if !ok { if !ok {
oConn := overlayConn{ oConn := overlayConn{
tunnel: app.Tunnel(), app: app,
connUDP: app.listenerUDP, connUDP: app.listenerUDP,
remoteAddr: remoteAddr, remoteAddr: remoteAddr,
udpData: make(chan []byte, 1000), udpData: make(chan []byte, 1000),
id: id, id: id,
isClient: true, isClient: true,
appID: app.id,
appKey: app.key,
running: true, running: true,
} }
if !app.isDirect() { overlayConns.Store(oConn.id, &oConn)
oConn.rtid = app.rtid gLog.d("appid:%d Accept UDP overlayID:%d", app.id, oConn.id)
}
// calc key bytes for encrypt
if oConn.appKey != 0 {
encryptKey := make([]byte, AESKeySize)
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
oConn.appKeyBytes = encryptKey
}
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
gLog.Printf(LvDEBUG, "Accept UDP overlayID:%d", oConn.id)
// tell peer connect // tell peer connect
req := OverlayConnectReq{ID: oConn.id, req := OverlayConnectReq{ID: oConn.id,
Token: gConf.Network.Token, Token: gConf.Network.Token,
@@ -530,17 +635,19 @@ func (app *p2pApp) listenUDP() error {
Protocol: app.config.Protocol, Protocol: app.config.Protocol,
AppID: app.id, AppID: app.id,
} }
if !app.isDirect() { if tidx != 0 {
req.RelayTunnelID = app.Tunnel().id req.RelayTunnelID = t.id
}
app.WriteMessage(MsgP2P, MsgOverlayConnectReq, &req)
head, _ := app.ReadMessage(MsgP2P, MsgOverlayConnectRsp, time.Second*3)
if head == nil {
gLog.w("appid:%d read MsgOverlayConnectRsp error", app.id)
} }
app.Tunnel().WriteMessage(app.RelayTunnelID(), MsgP2P, MsgOverlayConnectReq, &req)
// TODO: wait OverlayConnectRsp instead of sleep
time.Sleep(time.Second) // waiting remote node connection ok
go oConn.run() go oConn.run()
oConn.udpData <- dupData.Bytes() oConn.udpData <- dupData.Bytes()
} }
// load from app.tunnel.overlayConns by remoteAddr ok, write relay data // load from overlayConns by remoteAddr ok, write relay data
overlayConn, ok := s.(*overlayConn) overlayConn, ok := s.(*overlayConn)
if !ok { if !ok {
continue continue
@@ -555,8 +662,8 @@ func (app *p2pApp) listen() error {
if app.config.SrcPort == 0 { if app.config.SrcPort == 0 {
return nil return nil
} }
gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d START", app.config.Protocol, app.config.SrcPort) gLog.i("appid:%d LISTEN ON PORT %s:%d START", app.id, app.config.Protocol, app.config.SrcPort)
defer gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d END", app.config.Protocol, app.config.SrcPort) defer gLog.i("appid:%d LISTEN ON PORT %s:%d END", app.id, app.config.Protocol, app.config.SrcPort)
app.wg.Add(1) app.wg.Add(1)
defer app.wg.Done() defer app.wg.Done()
for app.running { for app.running {
@@ -573,7 +680,7 @@ func (app *p2pApp) listen() error {
return nil return nil
} }
func (app *p2pApp) close() { func (app *p2pApp) Close() {
app.running = false app.running = false
if app.listener != nil { if app.listener != nil {
app.listener.Close() app.listener.Close()
@@ -581,36 +688,193 @@ func (app *p2pApp) close() {
if app.listenerUDP != nil { if app.listenerUDP != nil {
app.listenerUDP.Close() app.listenerUDP.Close()
} }
if app.DirectTunnel() != nil { closeOverlayConns(app.id)
app.DirectTunnel().closeOverlayConns(app.id)
}
if app.RelayTunnel() != nil {
app.RelayTunnel().closeOverlayConns(app.id)
}
app.wg.Wait() app.wg.Wait()
} }
// TODO: many relay app on the same P2PTunnel will send a lot of relay heartbeat // TODO: many relay app on the same P2PTunnel will send a lot of relay heartbeat
func (app *p2pApp) relayHeartbeatLoop() { func (app *p2pApp) relayHeartbeatLoop(idx int) {
app.wg.Add(1) app.wg.Add(1)
defer app.wg.Done() defer app.wg.Done()
gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid:%d start", app.config.PeerNode, app.id, app.rtid) gLog.d("appid:%d %s relayHeartbeat to rtid:%d start", app.id, app.config.LogPeerNode(), app.rtid[idx])
defer gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid%d end", app.config.PeerNode, app.id, app.rtid) defer gLog.d("appid:%d %s relayHeartbeat to rtid%d end", app.id, app.config.LogPeerNode(), app.rtid[idx])
for app.running { for app.running {
if app.RelayTunnel() == nil || !app.RelayTunnel().isRuning() { if app.Tunnel(idx) == nil || !app.Tunnel(idx).isRuning() {
time.Sleep(TunnelHeartbeatTime) time.Sleep(TunnelHeartbeatTime)
continue continue
} }
req := RelayHeartbeat{From: gConf.Network.Node, RelayTunnelID: app.RelayTunnel().id, req := RelayHeartbeat{From: gConf.Network.Node, RelayTunnelID: app.Tunnel(idx).id, RelayTunnelID2: app.rtid[idx],
AppID: app.id} AppID: app.id}
err := app.RelayTunnel().WriteMessage(app.rtid, MsgP2P, MsgRelayHeartbeat, &req) err := app.Tunnel(idx).WriteMessage(app.rtid[idx], MsgP2P, MsgRelayHeartbeat, &req)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "%s appid:%d rtid:%d write relay tunnel heartbeat error %s", app.config.PeerNode, app.id, app.rtid, err) gLog.e("appid:%d %s rtid:%d write relay tunnel heartbeat error %s", app.id, app.config.LogPeerNode(), app.rtid[idx], err)
return app.SetTunnel(nil, idx)
continue
} }
app.whbTime[idx] = time.Now()
// TODO: debug relay heartbeat // TODO: debug relay heartbeat
gLog.Printf(LvDEBUG, "%s appid:%d rtid:%d write relay tunnel heartbeat ok", app.config.PeerNode, app.id, app.rtid) gLog.dev("appid:%d %s rtid:%d write relay tunnel heartbeat ok", app.id, app.config.LogPeerNode(), app.rtid[idx])
time.Sleep(TunnelHeartbeatTime) time.Sleep(TunnelHeartbeatTime)
} }
} }
func (app *p2pApp) WriteMessage(mainType uint16, subType uint16, req interface{}) error {
t, tidx := app.AvailableTunnel()
if t == nil {
return ErrAppWithoutTunnel
}
return t.WriteMessage(app.rtid[tidx], mainType, subType, req)
}
func (app *p2pApp) WriteMessageWithAppID(mainType uint16, subType uint16, req interface{}) error {
t, tidx := app.AvailableTunnel()
if t == nil {
return ErrAppWithoutTunnel
}
appID := app.id
if app.config.SrcPort == 0 {
appID = NodeNameToID(app.config.PeerNode)
}
return t.WriteMessageWithAppID(appID, app.rtid[tidx], mainType, subType, req)
}
func (app *p2pApp) WriteBytes(data []byte) error {
t, tidx := app.AvailableTunnel()
if t == nil {
return ErrAppWithoutTunnel
}
if tidx < app.relayIdxStart { // direct mode
return t.conn.WriteBytes(MsgP2P, MsgOverlayData, data)
}
all := append(app.relayHead[tidx].Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(data)))...)
all = append(all, data...)
t.conn.WriteBytes(MsgP2P, MsgRelayData, all)
return nil
}
func (app *p2pApp) PreCalcKeyBytes() {
// pre-calc key bytes for encrypt
if app.key != 0 {
encryptKey := make([]byte, AESKeySize)
binary.LittleEndian.PutUint64(encryptKey, app.key)
binary.LittleEndian.PutUint64(encryptKey[8:], app.key)
app.appKeyBytes = encryptKey
}
}
func (app *p2pApp) WriteNodeDataMP(IPPacket []byte) (err error) {
t, tidx := app.fastestTunnel()
if t == nil {
return ErrAppWithoutTunnel
}
dataWithSeq := new(bytes.Buffer)
binary.Write(dataWithSeq, binary.LittleEndian, gConf.nodeID())
binary.Write(dataWithSeq, binary.LittleEndian, app.seqW)
dataWithSeq.Write(IPPacket)
// gLog.d("DEBUG writeTs=%d, unAckSeqStart=%d", wu.writeTs.UnixMilli(), app.unAckSeqStart[tidx].Load())
if tidx < app.relayIdxStart { // direct mode
t.asyncWriteNodeData(gConf.nodeID(), app.seqW, IPPacket, nil)
gLog.dev("appid:%d asyncWriteDirect IPPacket len=%d", app.id, len(IPPacket))
} else {
t.asyncWriteNodeData(gConf.nodeID(), app.seqW, IPPacket, app.RelayHead(tidx).Bytes())
gLog.dev("appid:%d asyncWriteRelay%d IPPacket len=%d", app.id, tidx, len(IPPacket))
}
app.seqW++
return err
}
func (app *p2pApp) handleNodeDataMP(seq uint64, data []byte, t *P2PTunnel) {
GNetwork.nodeData <- data
}
func (app *p2pApp) isReliable() bool {
// return app.config.SrcPort != 0
return true
}
func (app *p2pApp) AvailableTunnel() (*P2PTunnel, int) {
for i := 0; i < app.tunnelNum; i++ {
if app.allTunnels[i] != nil {
return app.allTunnels[i], i
}
}
return nil, 0
}
func (app *p2pApp) fastestTunnel() (t *P2PTunnel, idx int) {
// gLog.d("appid:%d fastestTunnel %d %d",app.id, app.DirectRTT(), app.MinRelayRTT())
if gConf.Network.specTunnel > 0 {
if app.Tunnel(gConf.Network.specTunnel) != nil {
return app.Tunnel(gConf.Network.specTunnel), gConf.Network.specTunnel
}
}
for i := 0; i < app.tunnelNum; i++ {
if app.Tunnel(i) != nil {
t = app.Tunnel(i)
idx = i
break
}
}
return
}
func (app *p2pApp) ResetWindow() {
app.seqW = 0
app.seqR = 0
for i := 0; i < app.tunnelNum; i++ {
app.unAckSeqEnd[i].Store(0)
app.unAckTs[i].Store(0)
app.writeTs[i].Store(0)
}
}
func (app *p2pApp) Retry(all bool) {
gLog.d("appid:%d retry app %s", app.id, app.config.LogPeerNode())
for i := 0; i < app.tunnelNum; i++ {
app.retryNum[i] = 0
app.nextRetryTime[i] = time.Now()
if all && i == 0 {
app.hbMtx.Lock()
app.hbTime[i] = time.Now().Add(-TunnelHeartbeatTime * 3)
app.hbMtx.Unlock()
// app.config.retryNum = 0
app.config.nextRetryTime = time.Now()
app.ResetWindow()
}
}
}
func (app *p2pApp) StoreMessage(head *openP2PHeader, body []byte) {
app.msgChan <- appMsgCtx{head, body, time.Now()}
}
func (app *p2pApp) ReadMessage(mainType uint16, subType uint16, timeout time.Duration) (head *openP2PHeader, body []byte) {
for {
select {
case <-time.After(timeout):
gLog.e("appid:%d app.ReadMessage error %d:%d timeout", app.id, mainType, subType)
return
case msg := <-app.msgChan:
if time.Since(msg.ts) > ReadMsgTimeout {
gLog.d("appid:%d app.ReadMessage error expired %d:%d", app.id, mainType, subType)
continue
}
if msg.head.MainType != mainType || msg.head.SubType != subType {
gLog.d("appid:%d app.ReadMessage error type %d:%d, requeue it", app.id, msg.head.MainType, msg.head.SubType)
app.msgChan <- msg
time.Sleep(time.Second)
continue
}
head = msg.head
body = msg.body[8:]
return
}
}
}
+513 -235
View File
@@ -2,6 +2,7 @@ package openp2p
import ( import (
"bytes" "bytes"
"context"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/binary" "encoding/binary"
@@ -9,7 +10,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand"
"net"
"net/http" "net/http"
"os"
"runtime"
// _ "net/http/pprof"
"net/url" "net/url"
"reflect" "reflect"
"strings" "strings"
@@ -21,25 +27,25 @@ import (
var ( var (
v4l *v4Listener v4l *v4Listener
instance *P2PNetwork
onceP2PNetwork sync.Once onceP2PNetwork sync.Once
onceV4Listener sync.Once
) )
const ( const (
retryLimit = 20 retryLimit = 20
retryInterval = 10 * time.Second retryInterval = 10 * time.Second
DefaultLoginMaxDelaySeconds = 60 DefaultLoginMaxDelaySeconds = 60
MsgQueueSize = 256
) )
// golang not support float64 const // golang not support float64 const
var ( var (
ma20 float64 = 1.0 / 20
ma10 float64 = 1.0 / 10 ma10 float64 = 1.0 / 10
ma5 float64 = 1.0 / 5 ma5 float64 = 1.0 / 5
) )
type NodeData struct { type NodeData struct {
NodeID uint64 NodeID uint64 // unused
Data []byte Data []byte
} }
@@ -52,6 +58,7 @@ type P2PNetwork struct {
writeMtx sync.Mutex writeMtx sync.Mutex
reqGatewayMtx sync.Mutex reqGatewayMtx sync.Mutex
hbTime time.Time hbTime time.Time
initTime time.Time
// for sync server time // for sync server time
t1 int64 // nanoSeconds t1 int64 // nanoSeconds
preRtt int64 // nanoSeconds preRtt int64 // nanoSeconds
@@ -61,12 +68,13 @@ type P2PNetwork struct {
msgMap sync.Map //key: nodeID msgMap sync.Map //key: nodeID
// msgMap map[uint64]chan pushMsg //key: nodeID // msgMap map[uint64]chan pushMsg //key: nodeID
allTunnels sync.Map // key: tid allTunnels sync.Map // key: tid
apps sync.Map //key: config.ID(); value: *p2pApp apps sync.Map //key: peerid when memapp for sdwan node data indicate app/random uint64 when portforward; value: *p2pApp
limiter *SpeedLimiter limiter *SpeedLimiter
nodeData chan *NodeData nodeData chan []byte
sdwan *p2pSDWAN sdwan *p2pSDWAN
tunnelCloseCh chan *P2PTunnel tunnelCloseCh chan *P2PTunnel
loginMaxDelaySeconds int loginMaxDelaySeconds int
peerNodeMutex sync.Map
} }
type msgCtx struct { type msgCtx struct {
@@ -74,34 +82,93 @@ type msgCtx struct {
ts time.Time ts time.Time
} }
func P2PNetworkInstance() *P2PNetwork { func P2PNetworkInstance() {
if instance == nil { if GNetwork == nil {
onceP2PNetwork.Do(func() { onceP2PNetwork.Do(func() {
instance = &P2PNetwork{ GNetwork = &P2PNetwork{
restartCh: make(chan bool, 1), restartCh: make(chan bool, 1),
tunnelCloseCh: make(chan *P2PTunnel, 100), tunnelCloseCh: make(chan *P2PTunnel, 100),
nodeData: make(chan *NodeData, 10000), nodeData: make(chan []byte, 10000),
online: false, online: false,
running: true, running: true,
limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1), limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1),
dt: 0, dt: 0,
ddt: 0, ddt: 0,
loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds, loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds,
initTime: time.Now(),
} }
instance.msgMap.Store(uint64(0), make(chan msgCtx, 50)) // for gateway GNetwork.msgMap.Store(uint64(0), make(chan msgCtx, MsgQueueSize)) // for gateway
instance.StartSDWAN() GNetwork.StartSDWAN()
instance.init() v4l = &v4Listener{port: gConf.Network.PublicIPPort}
go instance.run() go GNetwork.keepAlive() // init() will block, keepalive should before init
GNetwork.init()
go GNetwork.run()
go func() {
ticker := time.NewTicker(10 * time.Minute)
defer ticker.Stop()
for range ticker.C {
dumpStack()
}
}()
go func() { go func() {
for { for {
instance.refreshIPv6()
time.Sleep(time.Hour) time.Sleep(time.Hour)
oldIPv6 := gConf.IPv6()
GNetwork.refreshIPv6()
newIPv6 := gConf.IPv6()
if oldIPv6 != newIPv6 {
req := ReportBasic{
Mac: gConf.Network.mac,
LanIP: gConf.Network.localIP,
OS: gConf.Network.os,
HasIPv4: gConf.Network.hasIPv4,
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
Version: OpenP2PVersion,
IPv6: newIPv6,
PublicIPPort: gConf.Network.PublicIPPort,
}
GNetwork.write(MsgReport, MsgReportBasic, &req)
}
} }
}() }()
cleanTempFiles() cleanTempFiles()
// go func() {
// log.Println("Starting pprof server on :16060")
// log.Println(http.ListenAndServe("0.0.0.0:16060", nil))
// }()
}) })
} }
return instance }
func (pn *P2PNetwork) keepAlive() {
gLog.i("P2PNetwork keepAlive start")
for {
time.Sleep(time.Second * 10)
if pn.hbTime.Before(time.Now().Add(-NetworkHeartbeatTime * 3)) {
if pn.initTime.After(time.Now().Add(-NetworkHeartbeatTime * 3)) {
gLog.d("Init less than 3 mins, skipping this check")
continue
}
gLog.e("P2PNetwork keepAlive error, exit worker")
dumpStack()
if !isAndroid() {
os.Exit(9)
}
}
}
}
func dumpStack() {
buf := make([]byte, 1024*1024)
n := runtime.Stack(buf, true)
tmpFile := "./log/stack.log.tmp"
if err := os.WriteFile(tmpFile, buf[:n], 0644); err != nil {
gLog.e("print runtime.Stack error")
return
}
os.Rename(tmpFile, "./log/stack.log")
} }
func (pn *P2PNetwork) run() { func (pn *P2PNetwork) run() {
@@ -113,26 +180,41 @@ func (pn *P2PNetwork) run() {
case <-heartbeatTimer.C: case <-heartbeatTimer.C:
pn.t1 = time.Now().UnixNano() pn.t1 = time.Now().UnixNano()
pn.write(MsgHeartbeat, 0, "") pn.write(MsgHeartbeat, 0, "")
case <-pn.restartCh: case isRestartDelay := <-pn.restartCh:
gLog.Printf(LvDEBUG, "got restart channel") gLog.i("got restart channel")
// pn.sdwan.reset()
pn.online = false pn.online = false
pn.wgReconnect.Wait() // wait read/autorunapp goroutine end waitDone := make(chan struct{})
delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second go func() {
time.Sleep(delay) defer close(waitDone)
pn.wgReconnect.Wait()
}()
select {
case <-waitDone:
case <-time.After(30 * time.Second):
gLog.e("pn.wgReconnect.Wait() timeout, mostly websocket hang. restart client")
os.Exit(0)
}
if isRestartDelay {
delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second
time.Sleep(delay)
}
err := pn.init() err := pn.init()
if err != nil { if err != nil {
gLog.Println(LvERROR, "P2PNetwork init error:", err) gLog.e("P2PNetwork init error:%s", err)
} }
gConf.retryAllApp() gConf.retryAllApp()
case t := <-pn.tunnelCloseCh: case t := <-pn.tunnelCloseCh:
gLog.Printf(LvDEBUG, "got tunnelCloseCh %s", t.config.PeerNode) gLog.d("got tunnelCloseCh %s", t.config.LogPeerNode())
pn.apps.Range(func(id, i interface{}) bool { pn.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp) app := i.(*p2pApp)
if app.DirectTunnel() == t { for i := 0; i < app.tunnelNum; i++ {
app.setDirectTunnel(nil) if app.Tunnel(i) == t {
} app.SetTunnel(nil, i)
if app.RelayTunnel() == t { }
app.setRelayTunnel(nil)
} }
return true return true
}) })
@@ -161,46 +243,54 @@ func (pn *P2PNetwork) Connect(timeout int) bool {
} }
func (pn *P2PNetwork) runAll() { func (pn *P2PNetwork) runAll() {
gConf.mtx.Lock() // lock for copy gConf.Apps and the modification of config(it's pointer) gConf.mtx.RLock() // lock for coRUpy gConf.Apps and the modification of config(it's pointer)
defer gConf.mtx.Unlock() defer gConf.mtx.RUnlock()
allApps := gConf.Apps // read a copy, other thread will modify the gConf.Apps allApps := gConf.Apps // read a copy, other thread will modify the gConf.Apps
for _, config := range allApps { for _, config := range allApps {
if config.AppName == "" {
config.AppName = fmt.Sprintf("%d", config.ID())
}
if config.Enabled == 0 { if config.Enabled == 0 {
continue continue
} }
if _, ok := pn.apps.Load(config.ID()); ok { if app := pn.findApp(config); app != nil {
// update some attribute
app.config.PunchPriority = config.PunchPriority
app.config.UnderlayProtocol = config.UnderlayProtocol
app.config.RelayNode = config.RelayNode
continue continue
} }
config.peerToken = gConf.Network.Token // config.peerToken = gConf.Network.Token // move to AddApp
gConf.mtx.Unlock() // AddApp will take a period of time, let outside modify gConf gConf.mtx.RUnlock() // AddApp will take a period of time, let outside modify gConf
pn.AddApp(*config) pn.AddApp(*config)
gConf.mtx.Lock() gConf.mtx.RLock()
} }
} }
func (pn *P2PNetwork) autorunApp() { func (pn *P2PNetwork) autorunApp() {
gLog.Println(LvINFO, "autorunApp start") gLog.i("autorunApp start")
pn.wgReconnect.Add(1) pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done() defer pn.wgReconnect.Done()
for pn.running && pn.online { for pn.running && pn.online {
time.Sleep(time.Second) time.Sleep(time.Second)
pn.runAll() pn.runAll()
} }
gLog.Println(LvINFO, "autorunApp end") gLog.i("autorunApp end")
} }
func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, string, error) { func (pn *P2PNetwork) addRelayTunnel(config AppConfig, excludeNodes string) (*P2PTunnel, uint64, string, error) {
gLog.Printf(LvINFO, "addRelayTunnel to %s start", config.PeerNode) gLog.d("addRelayTunnel to %s start", config.LogPeerNode())
defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.PeerNode) defer gLog.d("addRelayTunnel to %s end", config.LogPeerNode())
var relayTunnel *P2PTunnel
relayConfig := AppConfig{ relayConfig := AppConfig{
PeerNode: config.RelayNode, peerToken: config.peerToken,
peerToken: config.peerToken} PunchPriority: config.PunchPriority,
relayMode := "private" UnderlayProtocol: config.UnderlayProtocol,
relayMode: "private",
}
if config.RelayNode != excludeNodes {
relayConfig.PeerNode = config.RelayNode
// TODO: verify relay node is online
}
if relayConfig.PeerNode == "" { if relayConfig.PeerNode == "" {
// find existing relay tunnel // find existing relay tunnel
pn.apps.Range(func(id, i interface{}) bool { pn.apps.Range(func(id, i interface{}) bool {
@@ -208,16 +298,20 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri
if app.config.PeerNode != config.PeerNode { if app.config.PeerNode != config.PeerNode {
return true return true
} }
if app.RelayTunnel() == nil { for i := 1; i < app.tunnelNum; i++ { // index 1 for relay tunnel
return true if app.Tunnel(i) != nil && app.Tunnel(i).config.PeerNode != excludeNodes && time.Now().Before(app.hbTime[i].Add(TunnelHeartbeatTime*2)) {
relayConfig.PeerNode = app.Tunnel(i).config.PeerNode
relayConfig.relayMode = app.Tunnel(i).config.relayMode
relayTunnel = app.Tunnel(i)
gLog.d("found existing relay tunnel %s", relayConfig.LogPeerNode())
return false
}
} }
relayConfig.PeerNode = app.RelayTunnel().config.PeerNode return true
gLog.Printf(LvDEBUG, "found existing relay tunnel %s", relayConfig.PeerNode)
return false
}) })
if relayConfig.PeerNode == "" { // request relay node if relayConfig.PeerNode == "" { // request relay node
pn.reqGatewayMtx.Lock() pn.reqGatewayMtx.Lock()
pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode}) pn.write(MsgRelay, MsgRelayNodeReq, &RelayNodeReq{config.PeerNode, excludeNodes})
head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, ClientAPITimeout) head, body := pn.read("", MsgRelay, MsgRelayNodeRsp, ClientAPITimeout)
pn.reqGatewayMtx.Unlock() pn.reqGatewayMtx.Unlock()
if head == nil { if head == nil {
@@ -228,105 +322,154 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error") return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
} }
if rsp.RelayName == "" || rsp.RelayToken == 0 { if rsp.RelayName == "" || rsp.RelayToken == 0 {
gLog.Printf(LvERROR, "MsgRelayNodeReq error") gLog.e("MsgRelayNodeReq error")
return nil, 0, "", errors.New("MsgRelayNodeReq error") return nil, 0, "", errors.New("MsgRelayNodeReq error")
} }
gLog.Printf(LvDEBUG, "got relay node:%s", rsp.RelayName)
relayConfig.PeerNode = rsp.RelayName relayConfig.PeerNode = rsp.RelayName
relayConfig.peerToken = rsp.RelayToken relayConfig.peerToken = rsp.RelayToken
relayMode = rsp.Mode relayConfig.relayMode = rsp.Mode
gLog.d("got relay node:%s", relayConfig.LogPeerNode())
} }
} }
/// ///
t, err := pn.addDirectTunnel(relayConfig, 0) if relayTunnel == nil {
if err != nil { var err error
gLog.Println(LvERROR, "direct connect error:", err) relayTunnel, err = pn.addDirectTunnel(relayConfig, 0, nil)
return nil, 0, "", ErrConnectRelayNode // relay offline will stop retry if err != nil || relayTunnel == nil {
gLog.w("direct connect error:%s", err)
if err != nil && config.RelayNode != "" {
return nil, 0, "", err // let outside known the specified relay node offline, than stop retry
}
return nil, 0, "", ErrConnectRelayNode // relay offline will stop retry
}
} }
// notify peer addRelayTunnel // notify peer addRelayTunnel
req := AddRelayTunnelReq{ req := AddRelayTunnelReq{
From: gConf.Network.Node, From: gConf.Network.Node,
RelayName: relayConfig.PeerNode, RelayName: relayConfig.PeerNode,
RelayToken: relayConfig.peerToken, RelayToken: relayConfig.peerToken,
RelayMode: relayMode, RelayMode: relayConfig.relayMode,
RelayTunnelID: t.id, RelayTunnelID: relayTunnel.id,
PunchPriority: relayConfig.PunchPriority,
UnderlayProtocol: relayConfig.UnderlayProtocol,
} }
gLog.Printf(LvDEBUG, "push %s the relay node(%s)", config.PeerNode, relayConfig.PeerNode)
gLog.d("push %s the relay node(%s)", config.LogPeerNode(), relayConfig.LogPeerNode())
pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req) pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req)
// wait relay ready // wait relay ready
head, body := pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount) head, body := pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount)
if head == nil { if head == nil {
gLog.Printf(LvERROR, "read MsgPushAddRelayTunnelRsp error") gLog.e("read MsgPushAddRelayTunnelRsp error")
return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error") return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error")
} }
rspID := TunnelMsg{} rspID := TunnelMsg{}
if err = json.Unmarshal(body, &rspID); err != nil { if err := json.Unmarshal(body, &rspID); err != nil {
gLog.Println(LvDEBUG, ErrPeerConnectRelay) gLog.d("Unmarshal error:%s", ErrPeerConnectRelay)
return nil, 0, "", ErrPeerConnectRelay return nil, 0, "", ErrPeerConnectRelay
} }
return t, rspID.ID, relayMode, err return relayTunnel, rspID.ID, relayConfig.relayMode, nil
} }
// use *AppConfig to save status // use *AppConfig to save status
func (pn *P2PNetwork) AddApp(config AppConfig) error { func (pn *P2PNetwork) AddApp(config AppConfig) error {
gLog.Printf(LvINFO, "addApp %s to %s:%s:%d start", config.AppName, config.PeerNode, config.DstHost, config.DstPort) config.peerToken = gConf.Network.Token
defer gLog.Printf(LvINFO, "addApp %s to %s:%s:%d end", config.AppName, config.PeerNode, config.DstHost, config.DstPort) gLog.i("addApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
defer gLog.i("addApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
if !pn.online { if !pn.online {
return errors.New("P2PNetwork offline") return errors.New("P2PNetwork offline")
} }
if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok {
pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, MsgQueueSize))
} }
// check if app already exist? // check if app already exist?
if _, ok := pn.apps.Load(config.ID()); ok { existApp := pn.findApp(&config)
return errors.New("P2PApp already exist") if existApp != nil {
if existApp.tunnelNum == int(gConf.sdwan.TunnelNum) {
return errors.New("P2PApp already exist")
} else {
gLog.d("app %s exist but tunnelNum changed from %d to %d, delete it and recreate", existApp.config.AppName, existApp.tunnelNum, gConf.sdwan.TunnelNum)
pn.DeleteApp(config)
}
} }
app := p2pApp{ app := p2pApp{
// tunnel: t, // tunnel: t,
id: rand.Uint64(), id: rand.Uint64(),
key: rand.Uint64(), key: rand.Uint64(),
config: config, config: config,
iptree: NewIPTree(config.Whitelist), iptree: NewIPTree(config.Whitelist),
running: true, running: true,
hbTimeRelay: time.Now(), // asyncWriteChan: make(chan []byte, WriteDataChanSize),
} }
if config.SrcPort == 0 {
app.id = NodeNameToID(config.PeerNode)
}
tunnelNum := 2
app.Init(tunnelNum)
if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok { if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok {
pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, MsgQueueSize))
} }
pn.apps.Store(config.ID(), &app) app.Start(true)
gLog.Printf(LvDEBUG, "Store app %d", config.ID()) pn.apps.Store(app.id, &app) // TODO: store appid
go app.checkP2PTunnel() gLog.d("Store app %d", app.id)
return nil return nil
} }
func (pn *P2PNetwork) DeleteApp(config AppConfig) { func (pn *P2PNetwork) findApp(config *AppConfig) (app *p2pApp) {
gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d start", config.AppName, config.PeerNode, config.DstHost, config.DstPort) pn.apps.Range(func(id, i interface{}) bool {
defer gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d end", config.AppName, config.PeerNode, config.DstHost, config.DstPort) tempApp := i.(*p2pApp)
// close the apps of this config if config.SrcPort == 0 { // sdwan app
i, ok := pn.apps.Load(config.ID()) if tempApp.config.SrcPort == config.SrcPort &&
if ok { tempApp.config.PeerNode == config.PeerNode {
app := i.(*p2pApp) app = tempApp
gLog.Printf(LvINFO, "app %s exist, delete it", app.config.AppName) return false
app.close() }
pn.apps.Delete(config.ID()) } else { // portforward app
} if tempApp.config.SrcPort == config.SrcPort &&
tempApp.config.Protocol == config.Protocol {
app = tempApp
return false
}
}
return true
})
return
} }
func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) { func (pn *P2PNetwork) DeleteApp(config AppConfig) {
gLog.i("DeleteApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
defer gLog.i("DeleteApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
// close the apps of this config
if tempApp := pn.findApp(&config); tempApp != nil {
gLog.i("app %s exist, delete it", tempApp.config.AppName)
tempApp.Close()
if config.SrcPort != 0 {
pn.apps.Delete(tempApp.id)
} else {
pn.apps.Delete(NodeNameToID(config.PeerNode))
}
}
}
func (pn *P2PNetwork) findTunnel(peerNode string, ignoredTunnel *P2PTunnel) (t *P2PTunnel) {
t = nil t = nil
// find existing tunnel to peer // find existing tunnel to peer
pn.allTunnels.Range(func(id, i interface{}) bool { pn.allTunnels.Range(func(id, i interface{}) bool {
tmpt := i.(*P2PTunnel) tmpt := i.(*P2PTunnel)
if tmpt.config.PeerNode == peerNode { if tmpt.config.PeerNode == peerNode && tmpt != ignoredTunnel {
gLog.Println(LvINFO, "tunnel already exist ", peerNode) gLog.d("tunnel already exist %s", tmpt.config.LogPeerNode())
isActive := tmpt.checkActive() isActive := tmpt.checkActive()
// inactive, close it // inactive, close it
if !isActive { if !isActive {
gLog.Println(LvINFO, "but it's not active, close it ", peerNode) gLog.i("but it's not active, close it %s", tmpt.config.LogPeerNode())
tmpt.close() tmpt.close()
} else { } else {
t = tmpt t = tmpt
@@ -338,65 +481,90 @@ func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) {
return t return t
} }
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunnel, err error) { func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort, tid) gLog.d("addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.PeerNode, config.DstHost, config.DstPort, tid) defer gLog.d("addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
nodeID := NodeNameToID(config.PeerNode)
mutex, _ := pn.peerNodeMutex.LoadOrStore(nodeID, &sync.Mutex{})
mutex.(*sync.Mutex).Lock()
defer mutex.(*sync.Mutex).Unlock()
isClient := false isClient := false
// client side tid=0, assign random uint64 // client side tid=0, assign random uint64
if tid == 0 { if tid == 0 {
tid = rand.Uint64() tid = rand.Uint64()
isClient = true isClient = true
} }
if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok {
pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50)) if _, ok := pn.msgMap.Load(nodeID); !ok {
pn.msgMap.Store(nodeID, make(chan msgCtx, MsgQueueSize))
}
if isClient { // only client side find existing tunnel, server side should force build tunnel
if existTunnel := pn.findTunnel(config.PeerNode, ignoredTunnel); existTunnel != nil {
return existTunnel, nil
}
} }
// server side // server side
if !isClient { if !isClient {
t, err = pn.newTunnel(config, tid, isClient) t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel)
return t, err // always return return t, err // always return
} }
// client side // client side
// peer info // peer info
initErr := pn.requestPeerInfo(&config) initErr := pn.requestPeerInfo(&config)
if initErr != nil { if initErr != nil {
gLog.Printf(LvERROR, "%s init error:%s", config.PeerNode, initErr) gLog.w("%s init error:%s", config.LogPeerNode(), initErr)
return nil, initErr return nil, initErr
} }
gLog.Printf(LvDEBUG, "config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,",
config.PeerNode, config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType) gLog.d("config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,config.PunchPriority=%d,IPv6=%s",
config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType, config.PunchPriority, gConf.IPv6())
// try Intranet // try Intranet
if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP if config.peerIP == gConf.Network.publicIP && compareVersion(config.peerVersion, SupportIntranetVersion) >= 0 { // old version client has no peerLanIP
gLog.Println(LvINFO, "try Intranet") gLog.i("try Intranet")
config.linkMode = LinkModeIntranet config.linkMode = LinkModeIntranet
config.isUnderlayServer = 0 config.isUnderlayServer = 0
if t, err = pn.newTunnel(config, tid, isClient); err == nil { if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil return t, nil
} }
} }
thisTunnelForcev6 := false
// try TCP6 // try TCP6
if IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) { if !strings.Contains(gConf.Network.Node, "openp2pS2STest") && IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) && (config.PunchPriority&PunchPriorityUDPOnly == 0) {
gLog.Println(LvINFO, "try TCP6") gLog.i("try TCP6")
config.linkMode = LinkModeTCP6 config.linkMode = LinkModeTCP6
config.isUnderlayServer = 0 config.isUnderlayServer = 0
if t, err = pn.newTunnel(config, tid, isClient); err == nil { if gConf.Forcev6 {
thisTunnelForcev6 = true
}
if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil return t, nil
} }
} }
// try UDP6? maybe no // try UDP6? maybe no
// try TCP4 // try IPv4
if config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 { if !thisTunnelForcev6 && !strings.Contains(gConf.Network.Node, "openp2pS2STest") && (config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1) {
gLog.Println(LvINFO, "try TCP4") if config.PunchPriority&PunchPriorityUDPOnly != 0 && compareVersion(config.peerVersion, SupportUDP4DirectVersion) >= 0 {
config.linkMode = LinkModeTCP4 gLog.i("try UDP4")
config.linkMode = LinkModeUDP4
} else {
gLog.i("try TCP4")
config.linkMode = LinkModeTCP4
}
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
config.isUnderlayServer = 1 config.isUnderlayServer = 1
} else { } else {
config.isUnderlayServer = 0 config.isUnderlayServer = 0
} }
if t, err = pn.newTunnel(config, tid, isClient); err == nil { if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil return t, nil
} }
} }
@@ -404,16 +572,16 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
var primaryPunchFunc func() (*P2PTunnel, error) var primaryPunchFunc func() (*P2PTunnel, error)
var secondaryPunchFunc func() (*P2PTunnel, error) var secondaryPunchFunc func() (*P2PTunnel, error)
funcUDP := func() (t *P2PTunnel, err error) { funcUDP := func() (t *P2PTunnel, err error) {
if config.PunchPriority&PunchPriorityUDPDisable != 0 { if thisTunnelForcev6 || config.PunchPriority&PunchPriorityTCPOnly != 0 {
return return
} }
// try UDPPunch // try UDPPunch
for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
if config.peerNatType == NATCone || gConf.Network.natType == NATCone { if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
gLog.Println(LvINFO, "try UDP4 Punch") gLog.i("try UDP4 Punch")
config.linkMode = LinkModeUDPPunch config.linkMode = LinkModeUDPPunch
config.isUnderlayServer = 0 config.isUnderlayServer = 0
if t, err = pn.newTunnel(config, tid, isClient); err == nil { if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
return t, nil return t, nil
} }
} }
@@ -424,17 +592,17 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
return return
} }
funcTCP := func() (t *P2PTunnel, err error) { funcTCP := func() (t *P2PTunnel, err error) {
if config.PunchPriority&PunchPriorityTCPDisable != 0 { if thisTunnelForcev6 || config.PunchPriority&PunchPriorityUDPOnly != 0 {
return return
} }
// try TCPPunch // try TCPPunch
for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
if config.peerNatType == NATCone || gConf.Network.natType == NATCone { if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
gLog.Println(LvINFO, "try TCP4 Punch") gLog.i("try TCP4 Punch")
config.linkMode = LinkModeTCPPunch config.linkMode = LinkModeTCPPunch
config.isUnderlayServer = 0 config.isUnderlayServer = 0
if t, err = pn.newTunnel(config, tid, isClient); err == nil { if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
gLog.Println(LvINFO, "TCP4 Punch ok") gLog.i("TCP4 Punch ok")
return t, nil return t, nil
} }
} }
@@ -445,8 +613,8 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
primaryPunchFunc = funcTCP primaryPunchFunc = funcTCP
secondaryPunchFunc = funcUDP secondaryPunchFunc = funcUDP
} else { } else {
primaryPunchFunc = funcTCP primaryPunchFunc = funcUDP
secondaryPunchFunc = funcUDP secondaryPunchFunc = funcTCP
} }
if t, err = primaryPunchFunc(); t != nil && err == nil { if t, err = primaryPunchFunc(); t != nil && err == nil {
return t, err return t, err
@@ -459,51 +627,81 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
return nil, err return nil, err
} }
func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) { func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
if isClient { // only client side find existing tunnel if isClient { // only client side find existing tunnel, server side should force build tunnel
if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil { if existTunnel := pn.findTunnel(config.PeerNode, ignoredTunnel); existTunnel != nil {
return existTunnel, nil return existTunnel, nil
} }
} }
t = &P2PTunnel{pn: pn, t = &P2PTunnel{
config: config, config: config,
id: tid, id: tid,
writeData: make(chan []byte, WriteDataChanSize), writeData: make(chan []byte, WriteDataChanSize),
writeDataSmall: make(chan []byte, WriteDataChanSize/30), writeDataSmall: make(chan []byte, WriteDataChanSize),
} }
t.initPort() t.initPort()
if isClient { if isClient {
if err = t.connect(); err != nil { if err = t.connect(); err != nil {
gLog.Println(LvERROR, "p2pTunnel connect error:", err) gLog.d("p2pTunnel connect error:%s", err)
return return
} }
} else { } else {
if err = t.listen(); err != nil { if err = t.listen(); err != nil {
gLog.Println(LvERROR, "p2pTunnel listen error:", err) gLog.d("p2pTunnel listen error:%s", err)
return return
} }
} }
// store it when success // store it when success
gLog.Printf(LvDEBUG, "store tunnel %d", tid) gLog.d("store tunnel %d", tid)
pn.allTunnels.Store(tid, t) pn.allTunnels.Store(tid, t)
return return
} }
func (pn *P2PNetwork) init() error { func (pn *P2PNetwork) init() error {
gLog.Println(LvINFO, "P2PNetwork init start") gLog.i("P2PNetwork init start")
defer gLog.Println(LvINFO, "P2PNetwork init end") defer gLog.i("P2PNetwork init end")
pn.initTime = time.Now()
pn.wgReconnect.Add(1) pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done() defer pn.wgReconnect.Done()
var err error var err error
initOK := false
defer func() {
if !initOK {
// init failed, retry
pn.close(true)
gLog.e("P2PNetwork init error:%s", err)
}
}()
ips, err := resolveServerIP(gConf.Network.ServerHost)
if err != nil {
gLog.e("resolve dns failed: %v", err)
return err
}
gConf.Network.ServerIP = ips[0]
if isAndroid() {
net.DefaultResolver = &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
gLog.i("lookup dns %s %s", network, address)
dialer := &net.Dialer{
Timeout: 5 * time.Second,
}
primaryDNS := "119.29.29.29:53" // Tencent Cloud DNS
return dialer.DialContext(ctx, network, primaryDNS)
},
}
}
v4l.stop() // stop old v4 listener if exist
for { for {
// detect nat type // detect nat type
gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerHost, gConf.Network.UDPPort1, gConf.Network.UDPPort2) gConf.Network.publicIP, gConf.Network.natType, err = getNATType(gConf.Network.ServerIP, NATDetectPort1, NATDetectPort2)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "detect NAT type error:", err) gLog.d("detect NAT type error:%s", err)
break break
} }
if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again
gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort) gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.PublicIPPort)
} }
// for testcase // for testcase
@@ -511,50 +709,66 @@ func (pn *P2PNetwork) init() error {
gConf.Network.natType = NATSymmetric gConf.Network.natType = NATSymmetric
gConf.Network.hasIPv4 = 0 gConf.Network.hasIPv4 = 0
gConf.Network.hasUPNPorNATPMP = 0 gConf.Network.hasUPNPorNATPMP = 0
gLog.Println(LvINFO, "openp2pS2STest debug") gLog.i("openp2pS2STest debug")
} }
if strings.Contains(gConf.Network.Node, "openp2pC2CTest") { if strings.Contains(gConf.Network.Node, "openp2pC2CTest") {
gConf.Network.natType = NATCone gConf.Network.natType = NATCone
gConf.Network.hasIPv4 = 0 gConf.Network.hasIPv4 = 0
gConf.Network.hasUPNPorNATPMP = 0 gConf.Network.hasUPNPorNATPMP = 0
gLog.Println(LvINFO, "openp2pC2CTest debug") gLog.i("openp2pC2CTest debug")
} }
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 { // public ip and intranet connect
onceV4Listener.Do(func() { v4l.start()
v4l = &v4Listener{port: gConf.Network.TCPPort} pn.refreshIPv6()
go v4l.start() gLog.i("hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s, IPv6:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP, gConf.IPv6())
}) gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerIP, gConf.Network.ServerPort)
}
gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP)
gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort)
uri := "/api/v1/login" uri := "/api/v1/login"
caCertPool, errCert := x509.SystemCertPool() caCertPool, errCert := x509.SystemCertPool()
if errCert != nil { if errCert != nil {
gLog.Println(LvERROR, "Failed to load system root CAs:", errCert) gLog.e("Failed to load system root CAs:%s", errCert)
} else {
caCertPool = x509.NewCertPool() caCertPool = x509.NewCertPool()
} }
caCertPool.AppendCertsFromPEM([]byte(rootCA)) caCertPool.AppendCertsFromPEM([]byte(rootCA))
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
config := tls.Config{ config := tls.Config{
RootCAs: caCertPool, RootCAs: caCertPool,
InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert InsecureSkipVerify: gConf.TLSInsecureSkipVerify} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
websocket.DefaultDialer.TLSClientConfig = &config websocket.DefaultDialer.TLSClientConfig = &config
websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout * 3
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri} u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri}
q := u.Query() q := u.Query()
q.Add("node", gConf.Network.Node) q.Add("node", gConf.Network.Node)
q.Add("token", fmt.Sprintf("%d", gConf.Network.Token)) q.Add("token", fmt.Sprintf("%d", gConf.Network.Token))
q.Add("version", OpenP2PVersion) q.Add("version", OpenP2PVersion)
q.Add("ipv4", gConf.Network.publicIP)
q.Add("ipv6", gConf.IPv6())
q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType)) q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType))
q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth)) q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth))
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
var ws *websocket.Conn d := websocket.Dialer{
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil) NetDialContext: (&net.Dialer{Timeout: 10 * time.Second}).DialContext,
TLSClientConfig: &tls.Config{
RootCAs: caCertPool, // 你的根证书池
ServerName: gConf.Network.ServerHost, // <--- 关键:把域名放到 ServerName
InsecureSkipVerify: gConf.TLSInsecureSkipVerify,
},
HandshakeTimeout: 10 * time.Second,
}
ws, _, err := d.Dial(u.String(), nil)
if err != nil { if err != nil {
gLog.Println(LvERROR, "Dial error:", err) gLog.e("Dial error:%s", err)
switch gConf.Network.ServerPort {
case WsPort:
gConf.Network.ServerPort = WsPort2
gLog.i("try alternative port %d", WsPort2)
case WsPort2:
gConf.Network.ServerPort = WsPort
gLog.i("try alternative port %d", WsPort)
}
break break
} }
pn.running = true pn.running = true
@@ -564,7 +778,7 @@ func (pn *P2PNetwork) init() error {
if len(localAddr) == 2 { if len(localAddr) == 2 {
gConf.Network.localIP = localAddr[0] gConf.Network.localIP = localAddr[0]
} else { } else {
err = errors.New("get local ip failed") gLog.e("get local ip failed:%s", ws.LocalAddr().String())
break break
} }
go pn.readLoop() go pn.readLoop()
@@ -576,11 +790,12 @@ func (pn *P2PNetwork) init() error {
LanIP: gConf.Network.localIP, LanIP: gConf.Network.localIP,
OS: gConf.Network.os, OS: gConf.Network.os,
HasIPv4: gConf.Network.hasIPv4, HasIPv4: gConf.Network.hasIPv4,
PublicIPPort: gConf.Network.PublicIPPort,
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP, HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
Version: OpenP2PVersion, Version: OpenP2PVersion,
} }
rsp := netInfo() rsp := netInfo()
gLog.Println(LvDEBUG, "netinfo:", rsp) gLog.d("netinfo:%v", rsp)
if rsp != nil && rsp.Country != "" { if rsp != nil && rsp.Country != "" {
if IsIPv6(rsp.IP.String()) { if IsIPv6(rsp.IP.String()) {
gConf.setIPv6(rsp.IP.String()) gConf.setIPv6(rsp.IP.String())
@@ -590,18 +805,28 @@ func (pn *P2PNetwork) init() error {
pn.refreshIPv6() pn.refreshIPv6()
} }
req.IPv6 = gConf.IPv6() req.IPv6 = gConf.IPv6()
pn.write(MsgReport, MsgReportBasic, &req) pn.write(MsgReport, MsgReportBasic, &req) // TODO: if report failed, many logic problems, loss lanip os version...
head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout)
if head == nil {
gLog.e("read MsgReportBasic rsp error, retry")
pn.write(MsgReport, MsgReportBasic, &req) // TODO: if report failed, many logic problems, loss lanip os version...
head, _ := pn.read("", MsgReport, MsgReportBasicRsp, ClientAPITimeout)
if head == nil {
gLog.e("read MsgReportBasic rsp error again, exit")
if !isAndroid() {
os.Exit(9)
}
}
return
}
}() }()
go pn.autorunApp() go pn.autorunApp()
pn.write(MsgSDWAN, MsgSDWANInfoReq, nil) pn.write(MsgSDWAN, MsgSDWANInfoReq, nil)
gLog.Println(LvDEBUG, "P2PNetwork init ok") initOK = true
gLog.d("P2PNetwork init ok")
break break
} }
if err != nil {
// init failed, retry
pn.close()
gLog.Println(LvERROR, "P2PNetwork init error:", err)
}
return err return err
} }
@@ -609,39 +834,43 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
head := openP2PHeader{} head := openP2PHeader{}
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head) err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
if err != nil { if err != nil {
gLog.Println(LvERROR, "handleMessage error:", err) gLog.e("handleMessage error:%s", err)
return return
} }
gLog.dev("handleMessage %+v", head)
switch head.MainType { switch head.MainType {
case MsgLogin: case MsgLogin:
// gLog.Println(LevelINFO,string(msg)) // gLog.Println(LevelINFO,string(msg))
rsp := LoginRsp{} rsp := LoginRsp{}
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) gLog.e("wrong %v:%s", reflect.TypeOf(rsp), err)
return return
} }
if rsp.Error != 0 { if rsp.Error != 0 {
gLog.Printf(LvERROR, "login error:%d, detail:%s", rsp.Error, rsp.Detail) gLog.e("login error:%d, detail:%s", rsp.Error, rsp.Detail)
pn.running = false pn.running = false
} else { } else {
gConf.Network.Token = rsp.Token
gConf.Network.User = rsp.User
gConf.setToken(rsp.Token) gConf.setToken(rsp.Token)
gConf.setUser(rsp.User) gConf.setUser(rsp.User)
gConf.setForcev6(rsp.Forcev6 != 0)
if rsp.PublicIPPort != 0 {
gConf.Network.PublicIPPort = rsp.PublicIPPort
}
if len(rsp.Node) >= MinNodeNameLen { if len(rsp.Node) >= MinNodeNameLen {
gConf.setNode(rsp.Node) gConf.setNode(rsp.Node)
} }
gConf.save()
if rsp.LoginMaxDelay > 0 { if rsp.LoginMaxDelay > 0 {
pn.loginMaxDelaySeconds = rsp.LoginMaxDelay pn.loginMaxDelaySeconds = rsp.LoginMaxDelay
} }
gLog.Printf(LvINFO, "login ok. user=%s,node=%s", rsp.User, rsp.Node) gLog.i("login ok. user=%s, node=%s", rsp.User, rsp.Node)
} }
case MsgHeartbeat: case MsgHeartbeat:
gLog.Printf(LvDev, "P2PNetwork heartbeat ok") gLog.dev("P2PNetwork heartbeat ok")
pn.hbTime = time.Now() pn.hbTime = time.Now()
rtt := pn.hbTime.UnixNano() - pn.t1 rtt := pn.hbTime.UnixNano() - pn.t1
if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) { if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) {
gLog.Printf(LvINFO, "rtt=%d too large ignore", rtt) gLog.d("rtt=%dms too large ignore", rtt/int64(time.Millisecond))
return // invalid hb rsp return // invalid hb rsp
} }
pn.preRtt = rtt pn.preRtt = rtt
@@ -659,7 +888,7 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
} }
} }
pn.dt = newdt pn.dt = newdt
gLog.Printf(LvDEBUG, "synctime thisdt=%dms dt=%dms ddt=%dns ddtma=%dns rtt=%dms ", thisdt/int64(time.Millisecond), pn.dt/int64(time.Millisecond), pn.ddt, pn.ddtma, rtt/int64(time.Millisecond)) gLog.dev("synctime thisdt=%dms dt=%dms ddt=%dns ddtma=%dns rtt=%dms ", thisdt/int64(time.Millisecond), pn.dt/int64(time.Millisecond), pn.ddt, pn.ddtma, rtt/int64(time.Millisecond))
case MsgPush: case MsgPush:
handlePush(head.SubType, msg) handlePush(head.SubType, msg)
case MsgSDWAN: case MsgSDWAN:
@@ -668,7 +897,11 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
i, ok := pn.msgMap.Load(uint64(0)) i, ok := pn.msgMap.Load(uint64(0))
if ok { if ok {
ch := i.(chan msgCtx) ch := i.(chan msgCtx)
ch <- msgCtx{data: msg, ts: time.Now()} select {
case ch <- msgCtx{data: msg, ts: time.Now()}:
default:
gLog.e("msgQueue full, drop it")
}
} }
return return
@@ -676,20 +909,53 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
} }
func (pn *P2PNetwork) readLoop() { func (pn *P2PNetwork) readLoop() {
gLog.Printf(LvDEBUG, "P2PNetwork readLoop start") gLog.d("P2PNetwork readLoop start")
pn.wgReconnect.Add(1) pn.wgReconnect.Add(1)
defer pn.wgReconnect.Done() defer pn.wgReconnect.Done()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 使用带超时的 goroutine 读取
readChan := make(chan []byte, 10)
go func() {
for {
select {
case <-ctx.Done():
return
default:
pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second))
_, msg, err := pn.conn.ReadMessage()
if err != nil {
gLog.e("ReadMessage error:%s", err)
readChan <- nil
return
}
readChan <- msg
}
}
}()
readTimeout := 60 * time.Second
for pn.running { for pn.running {
pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second)) select {
_, msg, err := pn.conn.ReadMessage() case result := <-readChan:
if err != nil { if result == nil {
gLog.Printf(LvERROR, "P2PNetwork read error:%s", err) // 处理错误
pn.close() pn.close(false)
break return
}
pn.handleMessage(result)
case <-time.After(readTimeout):
gLog.e("ReadMessage timeout after %v", readTimeout)
cancel()
pn.close(false)
return
} }
pn.handleMessage(msg)
} }
gLog.Printf(LvDEBUG, "P2PNetwork readLoop end") gLog.d("P2PNetwork readLoop end")
} }
func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error { func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{}) error {
@@ -702,9 +968,10 @@ func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{})
} }
pn.writeMtx.Lock() pn.writeMtx.Lock()
defer pn.writeMtx.Unlock() defer pn.writeMtx.Unlock()
pn.conn.SetWriteDeadline(time.Now().Add(NetworkHeartbeatTime))
if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil { if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err) gLog.e("write msgType %d,%d error:%s", mainType, subType, err)
pn.close() pn.close(false)
} }
return err return err
} }
@@ -720,13 +987,13 @@ func (pn *P2PNetwork) relay(to uint64, body []byte) error {
} }
var err error var err error
if err = tunnel.conn.WriteBuffer(body); err != nil { if err = tunnel.conn.WriteBuffer(body); err != nil {
gLog.Printf(LvERROR, "relay to %d len=%d error:%s", to, len(body), err) gLog.dev("relay to %d len=%d error:%s", to, len(body), err)
} }
return err return err
} }
func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error { func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error {
gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to) // gLog.d("push msgType %d to %s", subType, to)
if !pn.online { if !pn.online {
return errors.New("client offline") return errors.New("client offline")
} }
@@ -747,14 +1014,15 @@ func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error
pushMsg = append(pushMsg, data...) pushMsg = append(pushMsg, data...)
pn.writeMtx.Lock() pn.writeMtx.Lock()
defer pn.writeMtx.Unlock() defer pn.writeMtx.Unlock()
pn.conn.SetWriteDeadline(time.Now().Add(NetworkHeartbeatTime))
if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil { if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil {
gLog.Printf(LvERROR, "push to %s error:%s", to, err) gLog.e("push to %s error:%s", to, err)
pn.close() pn.close(false)
} }
return err return err
} }
func (pn *P2PNetwork) close() { func (pn *P2PNetwork) close(isRestartDelay bool) {
if pn.running { if pn.running {
if pn.conn != nil { if pn.conn != nil {
pn.conn.Close() pn.conn.Close()
@@ -762,7 +1030,7 @@ func (pn *P2PNetwork) close() {
pn.running = false pn.running = false
} }
select { select {
case pn.restartCh <- true: case pn.restartCh <- isRestartDelay:
default: default:
} }
} }
@@ -776,30 +1044,30 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
} }
i, ok := pn.msgMap.Load(nodeID) i, ok := pn.msgMap.Load(nodeID)
if !ok { if !ok {
gLog.Printf(LvERROR, "read msg error: %s not found", node) gLog.e("read msg error: %s not found", node)
return return
} }
ch := i.(chan msgCtx) ch := i.(chan msgCtx)
for { for {
select { select {
case <-time.After(timeout): case <-time.After(timeout):
gLog.Printf(LvERROR, "read msg error %d:%d timeout", mainType, subType) gLog.e("read msg error %d:%d timeout", mainType, subType)
return return
case msg := <-ch: case msg := <-ch:
head = &openP2PHeader{} head = &openP2PHeader{}
err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head) err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil { if err != nil {
gLog.Println(LvERROR, "read msg error:", err) gLog.e("read msg error:%s", err)
break break
} }
if time.Since(msg.ts) > ReadMsgTimeout { if time.Since(msg.ts) > ReadMsgTimeout {
gLog.Printf(LvDEBUG, "read msg error expired %d:%d", head.MainType, head.SubType) gLog.d("read msg error expired %d:%d", head.MainType, head.SubType)
continue continue
} }
if head.MainType != mainType || head.SubType != subType { if head.MainType != mainType || head.SubType != subType {
gLog.Printf(LvDEBUG, "read msg error type %d:%d, requeue it", head.MainType, head.SubType) // gLog.d("read msg error type %d:%d expect %d:%d, requeue it", head.MainType, head.SubType, mainType, subType)
ch <- msg ch <- msg
time.Sleep(time.Second) time.Sleep(time.Millisecond * 50)
continue continue
} }
if mainType == MsgPush { if mainType == MsgPush {
@@ -812,11 +1080,16 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
} }
} }
func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) { func (pn *P2PNetwork) updateAppHeartbeat(appID uint64, rtid uint64, updateRelayTs bool) {
pn.apps.Range(func(id, i interface{}) bool { pn.apps.Range(func(id, i interface{}) bool {
app := i.(*p2pApp) app := i.(*p2pApp)
if app.id == appID { if app.id == appID {
app.updateHeartbeat() if updateRelayTs {
app.UpdateRelayHeartbeatTs(rtid)
} else {
app.UpdateHeartbeat(rtid)
}
} }
return true return true
}) })
@@ -824,23 +1097,34 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
// ipv6 will expired need to refresh. // ipv6 will expired need to refresh.
func (pn *P2PNetwork) refreshIPv6() { func (pn *P2PNetwork) refreshIPv6() {
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
url := "http://ipv6.ddnspod.com/"
if i == 1 {
url = "ipv6.icanhazip.com"
}
client := &http.Client{Timeout: time.Second * 10} client := &http.Client{Timeout: time.Second * 10}
r, err := client.Get("http://ipv6.ddnspod.com/") r, err := client.Get(url)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "refreshIPv6 error:", err) gLog.d("refreshIPv6 error:%s", err)
continue continue
} }
defer r.Body.Close() defer r.Body.Close()
buf := make([]byte, 1024) buf := make([]byte, 1024)
n, err := r.Body.Read(buf) n, err := r.Body.Read(buf)
if n <= 0 { if n <= 0 {
gLog.Println(LvINFO, "refreshIPv6 error:", err, n) gLog.e("refreshIPv6 error:%s", err)
continue continue
} }
if IsIPv6(string(buf[:n])) { if IsIPv6(string(buf[:n])) {
gConf.setIPv6(string(buf[:n])) newIPv6 := string(buf[:n])
if newIPv6 != gConf.IPv6() {
gLog.i("refreshIPv6 change:%s ---> %s", gConf.IPv6(), newIPv6)
gConf.setIPv6(newIPv6)
}
} }
gLog.d("refreshIPv6:%s", gConf.IPv6())
break break
} }
@@ -854,7 +1138,7 @@ func (pn *P2PNetwork) requestPeerInfo(config *AppConfig) error {
head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout) head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout)
pn.reqGatewayMtx.Unlock() pn.reqGatewayMtx.Unlock()
if head == nil { if head == nil {
gLog.Println(LvERROR, "requestPeerInfo error") gLog.e("requestPeerInfo error")
return ErrNetwork // network error, should not be ErrPeerOffline return ErrNetwork // network error, should not be ErrPeerOffline
} }
rsp := QueryPeerInfoRsp{} rsp := QueryPeerInfoRsp{}
@@ -884,7 +1168,7 @@ func (pn *P2PNetwork) StartSDWAN() {
} }
func (pn *P2PNetwork) ConnectNode(node string) error { func (pn *P2PNetwork) ConnectNode(node string) error {
if gConf.nodeID() < NodeNameToID(node) { if gConf.nodeID() <= NodeNameToID(node) {
return errors.New("only the bigger nodeid connect") return errors.New("only the bigger nodeid connect")
} }
peerNodeID := fmt.Sprintf("%d", NodeNameToID(node)) peerNodeID := fmt.Sprintf("%d", NodeNameToID(node))
@@ -894,6 +1178,7 @@ func (pn *P2PNetwork) ConnectNode(node string) error {
config.PeerNode = node config.PeerNode = node
sdwan := gConf.getSDWAN() sdwan := gConf.getSDWAN()
config.PunchPriority = int(sdwan.PunchPriority) config.PunchPriority = int(sdwan.PunchPriority)
// config.UnderlayProtocol = "kcp"
if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode
config.RelayNode = sdwan.CentralNode config.RelayNode = sdwan.CentralNode
config.ForceRelay = int(sdwan.ForceRelay) config.ForceRelay = int(sdwan.ForceRelay)
@@ -913,22 +1198,24 @@ func (pn *P2PNetwork) WriteNode(nodeID uint64, buff []byte) error {
} }
var err error var err error
app := i.(*p2pApp) app := i.(*p2pApp)
if app.Tunnel() == nil {
return errors.New("peer tunnel nil")
}
// TODO: move to app.write // TODO: move to app.write
gLog.Printf(LvDev, "%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect()) err = app.WriteNodeDataMP(buff)
if app.isDirect() { // direct if err != nil {
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff) gLog.dev("appID:%d WriteNodeDataMP %s", app.id, err)
} else { // relay
fromNodeIDHead := new(bytes.Buffer)
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
all := app.RelayHead().Bytes()
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
all = append(all, fromNodeIDHead.Bytes()...)
all = append(all, buff...)
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all)
} }
// gLog.dev("%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect())
// if app.DirectTunnel() != nil { // direct
// app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff)
// }
// if app.Tunnel() != nil { // relay
// fromNodeIDHead := new(bytes.Buffer)
// binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
// all := app.RelayHead().Bytes()
// all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
// all = append(all, fromNodeIDHead.Bytes()...)
// all = append(all, buff...)
// app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all)
// }
return err return err
} }
@@ -943,32 +1230,23 @@ func (pn *P2PNetwork) WriteBroadcast(buff []byte) error {
// binary.BigEndian.PutUint16(buff[10:12], ipChecksum) // binary.BigEndian.PutUint16(buff[10:12], ipChecksum)
// binary.BigEndian.PutUint16(buff[26:28], 0x082e) // binary.BigEndian.PutUint16(buff[26:28], 0x082e)
app := i.(*p2pApp) app := i.(*p2pApp)
if app.Tunnel() == nil {
return true
}
if app.config.SrcPort != 0 { // normal portmap app if app.config.SrcPort != 0 { // normal portmap app
return true return true
} }
if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan
return true return true
} }
if app.isDirect() { // direct err := app.WriteNodeDataMP(buff)
app.Tunnel().conn.WriteBytes(MsgP2P, MsgNodeData, buff) if err != nil {
} else { // relay gLog.dev("appID:%d WriteNodeDataMP %s", app.id, err)
fromNodeIDHead := new(bytes.Buffer)
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
all := app.RelayHead().Bytes()
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
all = append(all, fromNodeIDHead.Bytes()...)
all = append(all, buff...)
app.Tunnel().conn.WriteBytes(MsgP2P, MsgRelayData, all)
} }
return true return true
}) })
return nil return nil
} }
func (pn *P2PNetwork) ReadNode(tm time.Duration) *NodeData { func (pn *P2PNetwork) ReadNode(tm time.Duration) []byte {
select { select {
case nd := <-pn.nodeData: case nd := <-pn.nodeData:
return nd return nd
+397 -216
View File
@@ -2,6 +2,7 @@ package openp2p
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"errors" "errors"
@@ -14,23 +15,27 @@ import (
"time" "time"
) )
const WriteDataChanSize int = 3000 const WriteDataChanSize int = 8192
var buildTunnelMtx sync.Mutex var buildTunnelMtx sync.Mutex
const (
StatusIdle = 0
StatusWriting = 1
)
type P2PTunnel struct { type P2PTunnel struct {
pn *P2PNetwork
conn underlay conn underlay
hbTime time.Time hbTime time.Time
hbMtx sync.Mutex hbMtx sync.Mutex
whbTime time.Time
config AppConfig config AppConfig
la *net.UDPAddr // local hole address localHoleAddr *net.UDPAddr // local hole address
ra *net.UDPAddr // remote hole address remoteHoleAddr *net.UDPAddr // remote hole address
overlayConns sync.Map // both TCP and UDP
id uint64 // client side alloc rand.uint64 = server side id uint64 // client side alloc rand.uint64 = server side
running bool running bool
runMtx sync.Mutex runMtx sync.Mutex
tunnelServer bool // different from underlayServer
coneLocalPort int coneLocalPort int
coneNatPort int coneNatPort int
linkModeWeb string // use config.linkmode linkModeWeb string // use config.linkmode
@@ -41,30 +46,33 @@ type P2PTunnel struct {
func (t *P2PTunnel) initPort() { func (t *P2PTunnel) initPort() {
t.running = true t.running = true
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param localPort := int(rand.Uint32()%8192 + 1025) // if the process has bug, will add many upnp port. use specify p2p port by param
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeIntranet { if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeUDP4 || t.config.linkMode == LinkModeIntranet {
t.coneLocalPort = gConf.Network.TCPPort t.coneLocalPort = gConf.Network.PublicIPPort
t.coneNatPort = gConf.Network.TCPPort // symmetric doesn't need coneNatPort t.coneNatPort = gConf.Network.PublicIPPort // symmetric doesn't need coneNatPort
} }
if t.config.linkMode == LinkModeUDPPunch { if t.config.linkMode == LinkModeUDPPunch {
// prepare one random cone hole manually // prepare one random cone hole manually
_, natPort, _ := natTest(gConf.Network.ServerHost, gConf.Network.UDPPort1, localPort) _, natPort, _ := natDetectUDP(gConf.Network.ServerIP, NATDetectPort1, localPort)
t.coneLocalPort = localPort t.coneLocalPort = localPort
t.coneNatPort = natPort t.coneNatPort = natPort
} }
if t.config.linkMode == LinkModeTCPPunch { if t.config.linkMode == LinkModeTCPPunch {
// prepare one random cone hole by system automatically // prepare one random cone hole by system automatically
_, natPort, localPort2 := natTCP(gConf.Network.ServerHost, IfconfigPort1) _, natPort, localPort2, _ := natDetectTCP(gConf.Network.ServerIP, NATDetectPort1, 0)
t.coneLocalPort = localPort2 t.coneLocalPort = localPort2
t.coneNatPort = natPort t.coneNatPort = natPort
} }
t.la = &net.UDPAddr{IP: net.ParseIP(gConf.Network.localIP), Port: t.coneLocalPort} if t.config.linkMode == LinkModeTCP6 && compareVersion(t.config.peerVersion, IPv6PunchVersion) >= 0 {
gLog.Printf(LvDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort) t.coneLocalPort = localPort
t.coneNatPort = localPort
}
t.localHoleAddr = &net.UDPAddr{IP: net.ParseIP(gConf.Network.localIP), Port: t.coneLocalPort}
gLog.d("prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
} }
func (t *P2PTunnel) connect() error { func (t *P2PTunnel) connect() error {
gLog.Printf(LvDEBUG, "start p2pTunnel to %s ", t.config.PeerNode) gLog.d("start p2pTunnel to %s ", t.config.LogPeerNode())
t.tunnelServer = false
appKey := uint64(0) appKey := uint64(0)
req := PushConnectReq{ req := PushConnectReq{
Token: t.config.peerToken, Token: t.config.peerToken,
@@ -85,14 +93,14 @@ func (t *P2PTunnel) connect() error {
if req.Token == 0 { // no relay token if req.Token == 0 { // no relay token
req.Token = gConf.Network.Token req.Token = gConf.Network.Token
} }
t.pn.push(t.config.PeerNode, MsgPushConnectReq, req) GNetwork.push(t.config.PeerNode, MsgPushConnectReq, req)
head, body := t.pn.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, UnderlayConnectTimeout*3) head, body := GNetwork.read(t.config.PeerNode, MsgPush, MsgPushConnectRsp, UnderlayConnectTimeout*3)
if head == nil { if head == nil {
return errors.New("connect error") return errors.New("connect error")
} }
rsp := PushConnectRsp{} rsp := PushConnectRsp{}
if err := json.Unmarshal(body, &rsp); err != nil { if err := json.Unmarshal(body, &rsp); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err) gLog.e("wrong %v:%s", reflect.TypeOf(rsp), err)
return err return err
} }
// gLog.Println(LevelINFO, rsp) // gLog.Println(LevelINFO, rsp)
@@ -109,7 +117,7 @@ func (t *P2PTunnel) connect() error {
t.punchTs = rsp.PunchTs t.punchTs = rsp.PunchTs
err := t.start() err := t.start()
if err != nil { if err != nil {
gLog.Println(LvERROR, "handshake error:", err) gLog.d("handshake error:%s", err)
} }
return err return err
} }
@@ -134,7 +142,7 @@ func (t *P2PTunnel) isActive() bool {
defer t.hbMtx.Unlock() defer t.hbMtx.Unlock()
res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2)) res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2))
if !res { if !res {
gLog.Printf(LvDEBUG, "%d tunnel isActive false", t.id) gLog.d("%d tunnel isActive false", t.id)
} }
return res return res
} }
@@ -155,13 +163,13 @@ func (t *P2PTunnel) checkActive() bool {
t.hbMtx.Unlock() t.hbMtx.Unlock()
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
} }
gLog.Printf(LvINFO, "checkActive %t. hbtime=%d", isActive, t.hbTime) gLog.d("checkActive %t. hbtime=%d", isActive, t.hbTime)
return isActive return isActive
} }
// call when user delete tunnel // call when user delete tunnel
func (t *P2PTunnel) close() { func (t *P2PTunnel) close() {
t.pn.NotifyTunnelClose(t) GNetwork.NotifyTunnelClose(t)
if !t.running { if !t.running {
return return
} }
@@ -169,8 +177,8 @@ func (t *P2PTunnel) close() {
if t.conn != nil { if t.conn != nil {
t.conn.Close() t.conn.Close()
} }
t.pn.allTunnels.Delete(t.id) GNetwork.allTunnels.Delete(t.id)
gLog.Printf(LvINFO, "%d p2ptunnel close %s ", t.id, t.config.PeerNode) gLog.i("%d p2ptunnel close %s ", t.id, t.config.LogPeerNode())
} }
func (t *P2PTunnel) start() error { func (t *P2PTunnel) start() error {
@@ -181,7 +189,7 @@ func (t *P2PTunnel) start() error {
} }
err := t.connectUnderlay() err := t.connectUnderlay()
if err != nil { if err != nil {
gLog.Println(LvERROR, err) gLog.d("connectUnderlay error:%s", err)
return err return err
} }
return nil return nil
@@ -190,22 +198,22 @@ func (t *P2PTunnel) start() error {
func (t *P2PTunnel) handshake() error { func (t *P2PTunnel) handshake() error {
if t.config.peerConeNatPort > 0 { // only peer is cone should prepare t.ra if t.config.peerConeNatPort > 0 { // only peer is cone should prepare t.ra
var err error var err error
t.ra, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort)) t.remoteHoleAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort))
if err != nil { if err != nil {
return err return err
} }
} }
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) gLog.d("peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
} else { } else {
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) ts := time.Duration(int64(t.punchTs) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
if ts > PunchTsDelay || ts < 0 { if ts > PunchTsDelay || ts < 0 {
ts = PunchTsDelay ts = PunchTsDelay
} }
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) gLog.d("sleep %d ms", ts/time.Millisecond)
time.Sleep(ts) time.Sleep(ts)
} }
gLog.Println(LvDEBUG, "handshake to ", t.config.PeerNode) gLog.d("handshake to %s", t.config.LogPeerNode())
var err error var err error
if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone { if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone {
err = handshakeC2C(t) err = handshakeC2C(t)
@@ -220,19 +228,25 @@ func (t *P2PTunnel) handshake() error {
return errors.New("unknown error") return errors.New("unknown error")
} }
if err != nil { if err != nil {
gLog.Println(LvERROR, "punch handshake error:", err) gLog.d("punch handshake error:%s", err)
return err return err
} }
gLog.Printf(LvDEBUG, "handshake to %s ok", t.config.PeerNode) gLog.d("handshake to %s ok", t.config.LogPeerNode())
return nil return nil
} }
func (t *P2PTunnel) connectUnderlay() (err error) { func (t *P2PTunnel) connectUnderlay() (err error) {
switch t.config.linkMode { switch t.config.linkMode {
case LinkModeTCP6: case LinkModeTCP6:
t.conn, err = t.connectUnderlayTCP6() if compareVersion(t.config.peerVersion, IPv6PunchVersion) >= 0 {
t.conn, err = t.connectUnderlayTCP()
} else {
t.conn, err = t.connectUnderlayTCP6()
}
case LinkModeTCP4: case LinkModeTCP4:
t.conn, err = t.connectUnderlayTCP() t.conn, err = t.connectUnderlayTCP()
case LinkModeUDP4:
t.conn, err = t.connectUnderlayUDP()
case LinkModeTCPPunch: case LinkModeTCPPunch:
if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric { if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric {
t.conn, err = t.connectUnderlayTCPSymmetric() t.conn, err = t.connectUnderlayTCPSymmetric()
@@ -258,24 +272,35 @@ func (t *P2PTunnel) connectUnderlay() (err error) {
} }
func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) { func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
gLog.Printf(LvDEBUG, "connectUnderlayUDP %s start ", t.config.PeerNode) gLog.d("connectUnderlayUDP %s start ", t.config.LogPeerNode())
defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.PeerNode) defer gLog.d("connectUnderlayUDP %s end ", t.config.LogPeerNode())
var ul underlay var ul underlay
underlayProtocol := t.config.UnderlayProtocol underlayProtocol := t.config.UnderlayProtocol
if underlayProtocol == "" { if underlayProtocol == "" {
underlayProtocol = "quic" underlayProtocol = "quic"
} }
if t.config.isUnderlayServer == 1 { if t.config.isUnderlayServer == 1 {
// TODO: move to a func
time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env time.Sleep(time.Millisecond * 10) // punching udp port will need some times in some env
go t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) go GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
if t.config.UnderlayProtocol == "kcp" { if t.config.linkMode == LinkModeUDP4 {
ul, err = listenKCP(t.la.String(), TunnelIdleTimeout) if v4l != nil {
ul = v4l.getUnderlay(t.id)
}
if ul == nil {
return nil, fmt.Errorf("listen UDP4 error")
}
gLog.d("UDP4 connection ok")
} else { } else {
ul, err = listenQuic(t.la.String(), TunnelIdleTimeout) if t.config.UnderlayProtocol == "kcp" {
// ul, err = listenKCP(t.localHoleAddr.String(), TunnelIdleTimeout)
} else {
ul, err = listenQuic(t.localHoleAddr.String(), TunnelIdleTimeout)
}
} }
if err != nil { if err != nil {
gLog.Printf(LvINFO, "listen %s error:%s", underlayProtocol, err) gLog.i("listen %s error:%s", underlayProtocol, err)
return nil, err return nil, err
} }
@@ -285,54 +310,64 @@ func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
return nil, fmt.Errorf("read start msg error:%s", err) return nil, fmt.Errorf("read start msg error:%s", err)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, string(buff)) gLog.d("handshake flag:%s", string(buff))
} }
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.Printf(LvDEBUG, "%s connection ok", underlayProtocol) gLog.d("%s connection ok", underlayProtocol)
return ul, nil return ul, nil
} }
//else //client side
conn, errL := net.ListenUDP("udp", t.la) listenAddr := t.localHoleAddr
if t.config.linkMode == LinkModeUDP4 {
listenAddr = &net.UDPAddr{IP: net.ParseIP(gConf.Network.localIP), Port: 0}
t.remoteHoleAddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, t.config.peerConeNatPort))
if err != nil {
return nil, err
}
}
conn, errL := net.ListenUDP("udp", listenAddr)
if errL != nil { if errL != nil {
time.Sleep(time.Millisecond * 10) time.Sleep(time.Millisecond * 10)
conn, errL = net.ListenUDP("udp", t.la) conn, errL = net.ListenUDP("udp", listenAddr)
if errL != nil { if errL != nil {
return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL) return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL)
} }
} }
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.ra.String()) gLog.d("%s dial to %s", underlayProtocol, t.remoteHoleAddr.String())
if t.config.UnderlayProtocol == "kcp" { if t.config.UnderlayProtocol == "kcp" {
ul, errL = dialKCP(conn, t.ra, TunnelIdleTimeout) // ul, errL = dialKCP(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
} else { } else {
ul, errL = dialQuic(conn, t.ra, TunnelIdleTimeout) ul, errL = dialQuic(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
} }
if errL != nil { if errL != nil {
return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.ra.String(), errL) return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.remoteHoleAddr.String(), errL)
} }
handshakeBegin := time.Now() handshakeBegin := time.Now()
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) tidBuff := new(bytes.Buffer)
binary.Write(tidBuff, binary.LittleEndian, t.id)
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes())
_, buff, err := ul.ReadBuffer() // TODO: kcp need timeout _, buff, err := ul.ReadBuffer() // TODO: kcp need timeout
if err != nil { if err != nil {
ul.Close() ul.Close()
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, string(buff)) gLog.d("handshake flag:%s", string(buff))
} }
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) gLog.i("rtt=%dms", time.Since(handshakeBegin)/time.Millisecond)
gLog.Printf(LvINFO, "%s connection ok", underlayProtocol) gLog.i("%s connection ok", underlayProtocol)
t.linkModeWeb = LinkModeUDPPunch t.linkModeWeb = LinkModeUDPPunch
return ul, nil return ul, nil
} }
func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) { func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
gLog.Printf(LvDEBUG, "connectUnderlayTCP %s start ", t.config.PeerNode) gLog.d("connectUnderlayTCP %s start ", t.config.LogPeerNode())
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.PeerNode) defer gLog.d("connectUnderlayTCP %s end ", t.config.LogPeerNode())
var ul *underlayTCP var ul underlay
peerIP := t.config.peerIP peerIP := t.config.peerIP
if t.config.linkMode == LinkModeIntranet { if t.config.linkMode == LinkModeIntranet {
peerIP = t.config.peerLanIP peerIP = t.config.peerLanIP
@@ -343,62 +378,77 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("listen TCP error:%s", err) return nil, fmt.Errorf("listen TCP error:%s", err)
} }
gLog.Println(LvINFO, "TCP connection ok")
t.linkModeWeb = LinkModeIPv4 t.linkModeWeb = LinkModeIPv4
if t.config.linkMode == LinkModeIntranet { if t.config.linkMode == LinkModeIntranet {
t.linkModeWeb = LinkModeIntranet t.linkModeWeb = LinkModeIntranet
} }
if t.config.linkMode == LinkModeTCP6 {
t.linkModeWeb = LinkModeIPv6
}
gLog.i("%s TCP connection ok", t.linkModeWeb)
return ul, nil return ul, nil
} }
// client side // client side
if t.config.linkMode == LinkModeTCP4 { if t.config.linkMode == LinkModeTCP4 {
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
} else { //tcp punch should sleep for punch the same time } else { //tcp punch should sleep for punch the same time
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) gLog.d("peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
} else { } else {
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) ts := time.Duration(int64(t.punchTs) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
if ts > PunchTsDelay || ts < 0 { if ts > PunchTsDelay || ts < 0 {
ts = PunchTsDelay ts = PunchTsDelay
} }
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) gLog.d("sleep %d ms", ts/time.Millisecond)
time.Sleep(ts) time.Sleep(ts)
} }
} }
ul, err = dialTCP(peerIP, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode) host := peerIP
if t.config.linkMode == LinkModeTCP6 {
host = t.config.peerIPv6
}
ul, err = dialTCP(host, t.config.peerConeNatPort, t.coneLocalPort, t.config.linkMode)
if err != nil { if err != nil {
return nil, fmt.Errorf("TCP dial to %s:%d error:%s", t.config.peerIP, t.config.peerConeNatPort, err) return nil, fmt.Errorf("TCP dial to %s:%d error:%s", host, t.config.peerConeNatPort, err)
} }
handshakeBegin := time.Now() handshakeBegin := time.Now()
tidBuff := new(bytes.Buffer) tidBuff := new(bytes.Buffer)
binary.Write(tidBuff, binary.LittleEndian, t.id) binary.Write(tidBuff, binary.LittleEndian, t.id)
// fake_http_hostname := "speedtest.cn"
// user_agent := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
// ul.WriteMessage(MsgP2P, 100, fmt.Sprintf("GET / HTTP/1.1\r\nHost: %s\r\nUser-Agent: %s\r\nAccept: */*\r\n\r\n",
// fake_http_hostname, user_agent))
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
_, buff, err := ul.ReadBuffer() _, buff, err := ul.ReadBuffer()
if err != nil { if err != nil {
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, "hello ", string(buff)) gLog.d("hello %s", string(buff))
} }
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) gLog.i("rtt=%dms", time.Since(handshakeBegin)/time.Millisecond)
gLog.Println(LvINFO, "TCP connection ok")
t.linkModeWeb = LinkModeIPv4 t.linkModeWeb = LinkModeIPv4
if t.config.linkMode == LinkModeIntranet { if t.config.linkMode == LinkModeIntranet {
t.linkModeWeb = LinkModeIntranet t.linkModeWeb = LinkModeIntranet
} }
if t.config.linkMode == LinkModeTCP6 {
t.linkModeWeb = LinkModeIPv6
}
gLog.i("%s TCP connection ok", t.linkModeWeb)
return ul, nil return ul, nil
} }
func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) { func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s start ", t.config.PeerNode) gLog.d("connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode())
defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.PeerNode) defer gLog.d("connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode())
ts := time.Duration(int64(t.punchTs) + t.pn.dt + t.pn.ddtma*int64(time.Since(t.pn.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano()) ts := time.Duration(int64(t.punchTs) + GNetwork.dt + GNetwork.ddtma*int64(time.Since(GNetwork.hbTime)+PunchTsDelay)/int64(NetworkHeartbeatTime) - time.Now().UnixNano())
if ts > PunchTsDelay || ts < 0 { if ts > PunchTsDelay || ts < 0 {
ts = PunchTsDelay ts = PunchTsDelay
} }
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) gLog.d("sleep %d ms", ts/time.Millisecond)
time.Sleep(ts) time.Sleep(ts)
startTime := time.Now() startTime := time.Now()
t.linkModeWeb = LinkModeTCPPunch t.linkModeWeb = LinkModeTCPPunch
@@ -425,8 +475,8 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
return return
} }
_, buff, err := ul.ReadBuffer() _, buff, err := ul.ReadBuffer()
if err != nil { if err != nil || buff == nil {
gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) gLog.d("c2s ul.ReadBuffer error:%s", err)
return return
} }
req := P2PHandshakeReq{} req := P2PHandshakeReq{}
@@ -436,7 +486,7 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
if req.ID != t.id { if req.ID != t.id {
return return
} }
gLog.Printf(LvINFO, "handshakeS2C TCP ok. cost %dms", time.Since(startTime)/time.Millisecond) gLog.i("handshakeS2C TCP ok. cost %dms", time.Since(startTime)/time.Millisecond)
gotCh <- ul gotCh <- ul
close(gotCh) close(gotCh)
@@ -454,8 +504,8 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
} }
_, buff, err := ul.ReadBuffer() _, buff, err := ul.ReadBuffer()
if err != nil { if err != nil || buff == nil {
gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) gLog.d("s2c ul.ReadBuffer error:%s", err)
return return
} }
req := P2PHandshakeReq{} req := P2PHandshakeReq{}
@@ -486,91 +536,121 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
} }
func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) { func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s start ", t.config.PeerNode) gLog.d("connectUnderlayTCP6 %s start ", t.config.LogPeerNode())
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s end ", t.config.PeerNode) defer gLog.d("connectUnderlayTCP6 %s end ", t.config.LogPeerNode())
var ul *underlayTCP6 tidBuff := new(bytes.Buffer)
binary.Write(tidBuff, binary.LittleEndian, t.id)
if t.config.isUnderlayServer == 1 { if t.config.isUnderlayServer == 1 {
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout) // ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout)
if err != nil { tid := t.id
if compareVersion(t.config.peerVersion, PublicIPVersion) < 0 { // old version
ipBytes := net.ParseIP(t.config.peerIP).To4()
tid = uint64(binary.BigEndian.Uint32(ipBytes))
gLog.d("compatible with old client, use ip as key:%d", tid)
}
if v4l != nil {
c = v4l.getUnderlay(tid)
}
if c == nil {
return nil, fmt.Errorf("listen TCP6 error:%s", err) return nil, fmt.Errorf("listen TCP6 error:%s", err)
} }
_, buff, err := ul.ReadBuffer() _, buff, err := c.ReadBuffer()
if err != nil { if err != nil {
return nil, fmt.Errorf("read start msg error:%s", err) return nil, fmt.Errorf("read start msg error:%s", err)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, string(buff)) gLog.d("handshake flag:%s", string(buff))
} }
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2")) c.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
gLog.Println(LvDEBUG, "TCP6 connection ok") // ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
gLog.d("TCP6 connection ok")
t.linkModeWeb = LinkModeIPv6 t.linkModeWeb = LinkModeIPv6
return ul, nil return c, nil
} }
//else //else
t.pn.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout) GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6) gLog.d("TCP6 dial to %s", t.config.peerIPv6)
ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort) ul, err := dialTCP(fmt.Sprintf("[%s]", t.config.peerIPv6), t.config.peerConeNatPort, 0, LinkModeTCP6)
if err != nil { if err != nil || ul == nil {
return nil, fmt.Errorf("TCP6 dial to %s:%d error:%s", t.config.peerIPv6, 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() handshakeBegin := time.Now()
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello")) ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
_, buff, err := ul.ReadBuffer() // ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
if err != nil { _, buff, errR := ul.ReadBuffer()
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err) if errR != nil {
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", errR)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, string(buff)) gLog.d("handshake flag:%s", string(buff))
} }
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin)) gLog.i("rtt=%dms", time.Since(handshakeBegin))
gLog.Println(LvINFO, "TCP6 connection ok") gLog.i("TCP6 connection ok")
t.linkModeWeb = LinkModeIPv6 t.linkModeWeb = LinkModeIPv6
return ul, nil return ul, nil
} }
func (t *P2PTunnel) readLoop() { func (t *P2PTunnel) readLoop() {
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
gLog.Printf(LvDEBUG, "%d tunnel readloop start", t.id) gLog.d("%d tunnel readloop start", t.id)
for t.isRuning() { for t.isRuning() {
t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2)) t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2))
head, body, err := t.conn.ReadBuffer() head, body, err := t.conn.ReadBuffer()
if err != nil { if err != nil || head == nil {
if t.isRuning() { if t.isRuning() {
gLog.Printf(LvERROR, "%d tunnel read error:%s", t.id, err) gLog.d("%d tunnel read error:%s", t.id, err)
} }
break break
} }
if head.MainType != MsgP2P { if head.MainType != MsgP2P {
gLog.Printf(LvWARN, "%d head.MainType != MsgP2P", t.id) gLog.w("%d head.MainType(%d) != MsgP2P", head.MainType, t.id)
continue continue
} }
// gLog.d("%d tunnel read %d:%d len=%d", t.id, head.MainType, head.SubType, head.DataLen)
// TODO: replace some case implement to functions // TODO: replace some case implement to functions
switch head.SubType { switch head.SubType {
case MsgTunnelHeartbeat: case MsgTunnelHeartbeat:
t.hbMtx.Lock() t.hbMtx.Lock()
t.hbTime = time.Now() t.hbTime = time.Now()
t.hbMtx.Unlock() t.hbMtx.Unlock()
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil) memAppPeerID := new(bytes.Buffer)
gLog.Printf(LvDev, "%d read tunnel heartbeat", t.id) binary.Write(memAppPeerID, binary.LittleEndian, gConf.Network.nodeID)
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, memAppPeerID.Bytes())
gLog.dev("%d read tunnel heartbeat", t.id)
case MsgTunnelHeartbeatAck: case MsgTunnelHeartbeatAck:
t.hbMtx.Lock() t.hbMtx.Lock()
t.hbTime = time.Now() t.hbTime = time.Now()
t.hbMtx.Unlock() t.hbMtx.Unlock()
gLog.Printf(LvDev, "%d read tunnel heartbeat ack", t.id) if head.DataLen >= 8 {
memAppPeerID := binary.LittleEndian.Uint64(body[:8])
existApp, appok := GNetwork.apps.Load(memAppPeerID)
if appok {
app := existApp.(*p2pApp)
for i := 0; i < app.relayIdxStart; i++ {
if app.Tunnel(i) == t {
app.rtt[i].Store(int32(time.Since(t.whbTime) / time.Millisecond))
break
}
}
}
}
gLog.dev("%d read tunnel heartbeat ack, rtt=%dms", t.id, time.Since(t.whbTime)/time.Millisecond)
case MsgOverlayData: case MsgOverlayData:
if len(body) < overlayHeaderSize { if len(body) < overlayHeaderSize {
gLog.Printf(LvWARN, "%d len(body) < overlayHeaderSize", t.id) gLog.w("%d len(body) < overlayHeaderSize", t.id)
continue continue
} }
overlayID := binary.LittleEndian.Uint64(body[:8]) overlayID := binary.LittleEndian.Uint64(body[:8])
gLog.Printf(LvDev, "%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen) gLog.dev("%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen)
s, ok := t.overlayConns.Load(overlayID) s, ok := overlayConns.Load(overlayID)
if !ok { if !ok {
// debug level, when overlay connection closed, always has some packet not found tunnel // debug level, when overlay connection closed, always has some packet not found tunnel
gLog.Printf(LvDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID) gLog.d("%d tunnel not found overlay connection %d", t.id, overlayID)
continue continue
} }
overlayConn, ok := s.(*overlayConn) overlayConn, ok := s.(*overlayConn)
@@ -579,101 +659,86 @@ func (t *P2PTunnel) readLoop() {
} }
payload := body[overlayHeaderSize:] payload := body[overlayHeaderSize:]
var err error var err error
if overlayConn.appKey != 0 { if overlayConn.app.key != 0 {
payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize))) payload, _ = decryptBytes(overlayConn.app.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize)))
} }
_, err = overlayConn.Write(payload) _, err = overlayConn.Write(payload)
if err != nil { if err != nil {
gLog.Println(LvERROR, "overlay write error:", err) gLog.e("overlay write error:%s", err)
} }
case MsgNodeData: case MsgNodeDataMP:
t.handleNodeDataMP(head, body)
case MsgNodeDataMPAck:
t.handleNodeDataMPAck(head, body)
case MsgNodeData: // unused
t.handleNodeData(head, body, false) t.handleNodeData(head, body, false)
case MsgRelayNodeData: case MsgRelayNodeData: // unused
t.handleNodeData(head, body, true) t.handleNodeData(head, body, true)
case MsgRelayData: case MsgRelayData:
if len(body) < 8 { if len(body) < 8 {
continue continue
} }
tunnelID := binary.LittleEndian.Uint64(body[:8]) tunnelID := binary.LittleEndian.Uint64(body[:8])
gLog.Printf(LvDev, "relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize) gLog.dev("relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize)
if err := t.pn.relay(tunnelID, body[RelayHeaderSize:]); err != nil { if err := GNetwork.relay(tunnelID, body[RelayHeaderSize:]); err != nil {
gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.PeerNode, t.id, tunnelID, len(body), ErrRelayTunnelNotFound) gLog.d("%s:%d relay to %d len=%d error:%s", t.config.LogPeerNode(), t.id, tunnelID, len(body), ErrRelayTunnelNotFound)
} }
case MsgRelayHeartbeat: case MsgRelayHeartbeat: // only client side will write relay heartbeat, different with tunnel heartbeat
req := RelayHeartbeat{} req := RelayHeartbeat{}
if err := json.Unmarshal(body, &req); err != nil { if err := json.Unmarshal(body, &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) gLog.e("wrong %v:%s", reflect.TypeOf(req), err)
continue continue
} }
// TODO: debug relay heartbeat // TODO: debug relay heartbeat
gLog.Printf(LvDEBUG, "read MsgRelayHeartbeat from rtid:%d,appid:%d", req.RelayTunnelID, req.AppID) gLog.dev("read MsgRelayHeartbeat from rtid:%d,appid:%d", req.RelayTunnelID, req.AppID)
// update app hbtime // update app hbtime
t.pn.updateAppHeartbeat(req.AppID) GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, true)
req.From = gConf.Network.Node req.From = gConf.Network.Node
t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req) t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req)
case MsgRelayHeartbeatAck: case MsgRelayHeartbeatAck:
req := RelayHeartbeat{} req := RelayHeartbeat{}
err := json.Unmarshal(body, &req) err := json.Unmarshal(body, &req)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err) gLog.e("wrong RelayHeartbeat:%s", err)
continue continue
} }
// TODO: debug relay heartbeat // TODO: debug relay heartbeat
gLog.Printf(LvDEBUG, "read MsgRelayHeartbeatAck to appid:%d", req.AppID) gLog.dev("read MsgRelayHeartbeatAck to appid:%d", req.AppID)
t.pn.updateAppHeartbeat(req.AppID) GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, false)
case MsgOverlayConnectReq: req.From = gConf.Network.Node
req := OverlayConnectReq{} t.WriteMessage(req.RelayTunnelID2, MsgP2P, MsgRelayHeartbeatAck2, &req)
if err := json.Unmarshal(body, &req); err != nil { case MsgRelayHeartbeatAck2:
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) req := RelayHeartbeat{}
continue err := json.Unmarshal(body, &req)
}
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
if req.Token != gConf.Network.Token {
gLog.Println(LvERROR, "Access Denied:", req.Token)
continue
}
overlayID := req.ID
gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort)
oConn := overlayConn{
tunnel: t,
id: overlayID,
isClient: false,
rtid: req.RelayTunnelID,
appID: req.AppID,
appKey: GetKey(req.AppID),
running: true,
}
if req.Protocol == "udp" {
oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(req.DstIP), Port: req.DstPort})
} else {
oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), ReadMsgTimeout)
}
if err != nil { if err != nil {
gLog.Println(LvERROR, err) gLog.e("wrong RelayHeartbeat:%s", err)
continue continue
} }
gLog.dev("read MsgRelayHeartbeatAck2 to appid:%d", req.AppID)
// calc key bytes for encrypt GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, false)
if oConn.appKey != 0 { case MsgOverlayConnectReq: // TODO: send this msg withAppID, and app handle it
encryptKey := make([]byte, AESKeySize) // app connect only accept token(not relay totp token), avoid someone using the share relay node's token
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey) // targetApp := GNetwork.GetAPPByID(req.AppID)
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey) t.handleOverlayConnectReq(body, err)
oConn.appKeyBytes = encryptKey case MsgOverlayConnectRsp:
appID := binary.LittleEndian.Uint64(body[:8])
i, ok := GNetwork.apps.Load(appID)
if !ok {
gLog.e("MsgOverlayConnectRsp app not found %d", appID)
return
} }
app := i.(*p2pApp)
t.overlayConns.Store(oConn.id, &oConn) // ndmp := NodeDataMPHeader{fromNodeID: gConf.Network.nodeID, seq: seq}
go oConn.run() app.StoreMessage(head, body)
case MsgOverlayDisconnectReq: case MsgOverlayDisconnectReq:
req := OverlayDisconnectReq{} req := OverlayDisconnectReq{}
if err := json.Unmarshal(body, &req); err != nil { if err := json.Unmarshal(body, &req); err != nil {
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err) gLog.e("wrong %v:%s", reflect.TypeOf(req), err)
continue continue
} }
overlayID := req.ID overlayID := req.ID
gLog.Printf(LvDEBUG, "%d disconnect overlay connection %d", t.id, overlayID) gLog.d("%d disconnect overlay connection %d", t.id, overlayID)
i, ok := t.overlayConns.Load(overlayID) i, ok := overlayConns.Load(overlayID)
if ok { if ok {
oConn := i.(*overlayConn) oConn := i.(*overlayConn)
oConn.Close() oConn.Close()
@@ -682,7 +747,55 @@ func (t *P2PTunnel) readLoop() {
} }
} }
t.close() t.close()
gLog.Printf(LvDEBUG, "%d tunnel readloop end", t.id) gLog.d("%d tunnel readloop end", t.id)
}
func (*P2PTunnel) handleOverlayConnectReq(body []byte, err error) {
req := OverlayConnectReq{}
if err := json.Unmarshal(body, &req); err != nil {
gLog.e("wrong %v:%s", reflect.TypeOf(req), err)
return
}
if req.Token != gConf.Network.Token {
gLog.e("Access Denied,token=%d", req.Token)
return
}
overlayID := req.ID
gLog.d("App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort)
i, ok := GNetwork.apps.Load(req.AppID)
if !ok {
return
}
targetApp := i.(*p2pApp)
oConn := overlayConn{
app: targetApp,
id: overlayID,
isClient: false,
running: true,
}
// connect local service should use sys dns
sysResolver := &net.Resolver{}
ips, err := sysResolver.LookupIP(context.Background(), "ip4", req.DstIP)
if err != nil {
gLog.e("handleOverlayConnectReq dial error:%s", err)
return
}
if req.Protocol == "udp" {
oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: ips[0], Port: req.DstPort})
} else {
oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ips[0].String(), req.DstPort), ReadMsgTimeout)
}
if err != nil {
gLog.e("handleOverlayConnectReq dial error:%s", err)
return
}
overlayConns.Store(oConn.id, &oConn)
go oConn.run()
targetApp.WriteMessageWithAppID(MsgP2P, MsgOverlayConnectRsp, nil)
} }
func (t *P2PTunnel) writeLoop() { func (t *P2PTunnel) writeLoop() {
@@ -691,29 +804,39 @@ func (t *P2PTunnel) writeLoop() {
t.hbMtx.Unlock() t.hbMtx.Unlock()
tc := time.NewTicker(TunnelHeartbeatTime) tc := time.NewTicker(TunnelHeartbeatTime)
defer tc.Stop() defer tc.Stop()
gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop start", t.config.PeerNode, t.id) gLog.d("%s:%d tunnel writeLoop start", t.config.LogPeerNode(), t.id)
defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.PeerNode, t.id) defer gLog.d("%s:%d tunnel writeLoop end", t.config.LogPeerNode(), t.id)
writeHb := func() {
// tunnel send
t.whbTime = time.Now()
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
if err != nil {
gLog.d("%d write tunnel heartbeat error %s", t.id, err)
t.close()
return
}
gLog.dev("%d write tunnel heartbeat ok", t.id)
}
writeHb()
for t.isRuning() { for t.isRuning() {
select { select {
case buff := <-t.writeDataSmall: case buff := <-t.writeDataSmall:
t.conn.WriteBuffer(buff) t.conn.WriteBuffer(buff)
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) // gLog.d("write icmp %d", time.Now().Unix())
default: default:
select { select {
case buff := <-t.writeDataSmall: case buff := <-t.writeDataSmall:
t.conn.WriteBuffer(buff) t.conn.WriteBuffer(buff)
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix()) // gLog.d("write icmp %d", time.Now().Unix())
case buff := <-t.writeData: case buff := <-t.writeData:
t.conn.WriteBuffer(buff) err := t.conn.WriteBuffer(buff)
case <-tc.C:
// tunnel send
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "%d write tunnel heartbeat error %s", t.id, err) gLog.e("%d write tunnel error %s", t.id, err)
t.close() t.close()
return return
} }
gLog.Printf(LvDev, "%d write tunnel heartbeat ok", t.id) case <-tc.C:
writeHb()
} }
} }
} }
@@ -733,7 +856,7 @@ func (t *P2PTunnel) listen() error {
FromIP: gConf.Network.publicIP, FromIP: gConf.Network.publicIP,
ConeNatPort: t.coneNatPort, ConeNatPort: t.coneNatPort,
ID: t.id, ID: t.id,
PunchTs: uint64(time.Now().UnixNano() + int64(PunchTsDelay) - t.pn.dt), PunchTs: uint64(time.Now().UnixNano() + int64(PunchTsDelay) - GNetwork.dt),
Version: OpenP2PVersion, Version: OpenP2PVersion,
} }
t.punchTs = rsp.PunchTs t.punchTs = rsp.PunchTs
@@ -742,55 +865,75 @@ func (t *P2PTunnel) listen() error {
rsp.IPv6 = gConf.IPv6() rsp.IPv6 = gConf.IPv6()
} }
t.pn.push(t.config.PeerNode, MsgPushConnectRsp, rsp) GNetwork.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
gLog.Printf(LvDEBUG, "p2ptunnel wait for connecting") gLog.d("p2ptunnel wait for connecting")
t.tunnelServer = true
return t.start() return t.start()
} }
func (t *P2PTunnel) closeOverlayConns(appID uint64) {
t.overlayConns.Range(func(_, i interface{}) bool {
oConn := i.(*overlayConn)
if oConn.appID == appID {
oConn.Close()
}
return true
})
}
func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) { func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) {
gLog.Printf(LvDev, "%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay) gLog.dev("%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay)
ch := t.pn.nodeData ch := GNetwork.nodeData
// if body[9] == 1 { // TODO: deal relay // if body[9] == 1 { // TODO: deal relay
// ch = t.pn.nodeDataSmall // ch = GNetwork.nodeDataSmall
// gLog.Printf(LvDEBUG, "read icmp %d", time.Now().Unix()) // gLog.d("read icmp %d", time.Now().Unix())
// } // }
if isRelay { if isRelay {
fromPeerID := binary.LittleEndian.Uint64(body[:8]) // fromPeerID := binary.LittleEndian.Uint64(body[:8]) // unused
ch <- &NodeData{fromPeerID, body[8:]} // TODO: cache peerNodeID; encrypt/decrypt ch <- body[8:] // TODO: cache peerNodeID; encrypt/decrypt
} else { } else {
ch <- &NodeData{NodeNameToID(t.config.PeerNode), body} // TODO: cache peerNodeID; encrypt/decrypt ch <- body // TODO: cache peerNodeID; encrypt/decrypt
} }
} }
func (t *P2PTunnel) asyncWriteNodeData(mainType, subType uint16, data []byte) { func (t *P2PTunnel) handleNodeDataMP(head *openP2PHeader, body []byte) {
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...) gLog.dev("%s tid:%d tunnel read node data mp bodylen=%d", t.config.LogPeerNode(), t.id, head.DataLen) // Debug
// if len(data) < 192 { if head.DataLen < 16 {
if data[9] == 1 { // icmp return
select {
case t.writeDataSmall <- writeBytes:
// gLog.Printf(LvWARN, "%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall))
default:
gLog.Printf(LvWARN, "%s:%d t.writeDataSmall is full, drop it", t.config.PeerNode, t.id)
}
} else {
select {
case t.writeData <- writeBytes:
default:
gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.PeerNode, t.id)
}
} }
// TODO: reorder write tun
fromNodeID := binary.LittleEndian.Uint64(body[:8])
seq := binary.LittleEndian.Uint64(body[8:16])
i, ok := GNetwork.apps.Load(fromNodeID)
if !ok {
gLog.e("handleNodeDataMP peer not found,from=%s nodeID=%d, seq=%d", t.config.LogPeerNode(), fromNodeID, seq)
return
}
app := i.(*p2pApp)
// ndmp := NodeDataMPHeader{fromNodeID: gConf.Network.nodeID, seq: seq}
app.handleNodeDataMP(seq, body[16:], t)
}
func (t *P2PTunnel) handleNodeDataMPAck(head *openP2PHeader, body []byte) {
}
func (t *P2PTunnel) asyncWriteNodeData(id uint64, seq uint64, IPPacket []byte, relayHead []byte) {
all := new(bytes.Buffer)
if relayHead != nil {
all.Write(encodeHeader(MsgP2P, MsgRelayData, uint32(openP2PHeaderSize+len(relayHead)+16+len(IPPacket))))
all.Write(relayHead)
}
all.Write(encodeHeader(MsgP2P, MsgNodeDataMP, 16+uint32(len(IPPacket)))) // id+seq=16 bytes
binary.Write(all, binary.LittleEndian, id)
binary.Write(all, binary.LittleEndian, seq)
all.Write(IPPacket)
// if len(data) < 192 {
if IPPacket[9] == 1 { // icmp
select {
case t.writeDataSmall <- all.Bytes():
// gLog.w("%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall))
default:
gLog.w("%s:%d t.writeDataSmall is full, drop it", t.config.LogPeerNode(), t.id)
}
} else {
t.writeData <- all.Bytes()
// select {
// case t.writeData <- writeBytes:
// default:
// gLog.w("%s:%d t.writeData is full, drop it", t.config.LogPeerNode(), t.id)
// }
}
} }
func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, req interface{}) error { func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, req interface{}) error {
@@ -804,3 +947,41 @@ func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, r
return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead) return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead)
} }
func (t *P2PTunnel) WriteMessageWithAppID(appID uint64, rtid uint64, mainType uint16, subType uint16, req interface{}) error {
data, err := json.Marshal(req)
if err != nil {
return err
}
head := new(bytes.Buffer)
binary.Write(head, binary.LittleEndian, appID)
msgWithAppID := append(head.Bytes(), data...)
if rtid == 0 {
return t.conn.WriteBytes(mainType, subType, msgWithAppID)
}
relayHead := new(bytes.Buffer)
binary.Write(relayHead, binary.LittleEndian, rtid)
msg, _ := newMessageWithBuff(mainType, subType, msgWithAppID)
msgWithHead := append(relayHead.Bytes(), msg...)
return t.conn.WriteBytes(mainType, MsgRelayData, msgWithHead)
}
func (t *P2PTunnel) WriteBytes(rtid uint64, mainType uint16, subType uint16, data []byte) error {
if rtid == 0 {
return t.conn.WriteBytes(mainType, subType, data)
}
all := new(bytes.Buffer)
binary.Write(all, binary.LittleEndian, rtid)
all.Write(encodeHeader(mainType, subType, uint32(len(data))))
all.Write(data)
return t.conn.WriteBytes(mainType, MsgRelayData, all.Bytes())
}
// func (t *P2PTunnel) RTT() int {
// if t.isWriting.Load() && t.rtt.Load() < int32(time.Now().Add(time.Duration(-t.writingTs.Load())).Unix()/int64(time.Millisecond)) {
// return int(time.Now().Add(time.Duration(-t.writingTs.Load())).Unix() / int64(time.Millisecond))
// }
// return int(t.rtt.Load())
// }
+87
View File
@@ -0,0 +1,87 @@
package openp2p
import (
"fmt"
"net"
"os"
"time"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
)
// 定义ICMP回显请求和应答的结构
type ICMPMessage struct {
Type uint8
Code uint8
Checksum uint16
Ident uint16
Seq uint16
Data []byte
}
// Ping sends an ICMP Echo request to the specified host and returns the response time.
func Ping(host string) (time.Duration, error) {
// Resolve the IP address of the host
ipAddr, err := net.ResolveIPAddr("ip4", host)
if err != nil {
return 0, fmt.Errorf("failed to resolve host: %v", err)
}
// Create an ICMP listener
conn, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
return 0, fmt.Errorf("failed to create ICMP connection: %v", err)
}
defer conn.Close()
// Create an ICMP Echo request message
message := icmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &icmp.Echo{
ID: os.Getpid() & 0xffff,
Seq: 1,
Data: []byte("HELLO-R-U-THERE"),
},
}
// Marshal the message into binary form
messageBytes, err := message.Marshal(nil)
if err != nil {
return 0, fmt.Errorf("failed to marshal ICMP message: %v", err)
}
// Send the ICMP Echo request
start := time.Now()
if _, err := conn.WriteTo(messageBytes, ipAddr); err != nil {
return 0, fmt.Errorf("failed to send ICMP request: %v", err)
}
// Set a deadline for the response
err = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
if err != nil {
return 0, fmt.Errorf("failed to set read deadline: %v", err)
}
// Read the ICMP response
response := make([]byte, 1500)
n, _, err := conn.ReadFrom(response)
if err != nil {
return 0, fmt.Errorf("failed to read ICMP response: %v", err)
}
// Parse the ICMP response message
parsedMessage, err := icmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), response[:n])
if err != nil {
return 0, fmt.Errorf("failed to parse ICMP response: %v", err)
}
// Check if the response is an Echo reply
if parsedMessage.Type == ipv4.ICMPTypeEchoReply {
duration := time.Since(start)
return duration, nil
} else {
return 0, fmt.Errorf("unexpected ICMP message: %+v", parsedMessage)
}
}
+170 -59
View File
@@ -10,7 +10,7 @@ import (
"time" "time"
) )
const OpenP2PVersion = "3.18.4" const OpenP2PVersion = "3.25.11"
const ProductName string = "openp2p" const ProductName string = "openp2p"
const LeastSupportVersion = "3.0.0" const LeastSupportVersion = "3.0.0"
const SyncServerTimeVersion = "3.9.0" const SyncServerTimeVersion = "3.9.0"
@@ -18,13 +18,16 @@ const SymmetricSimultaneouslySendVersion = "3.10.7"
const PublicIPVersion = "3.11.2" const PublicIPVersion = "3.11.2"
const SupportIntranetVersion = "3.14.5" const SupportIntranetVersion = "3.14.5"
const SupportDualTunnelVersion = "3.15.5" const SupportDualTunnelVersion = "3.15.5"
const IPv6PunchVersion = "3.24.9"
const SupportUDP4DirectVersion = "3.24.16"
const SupportMultiDirectVersion = "3.25.1"
const ( const (
IfconfigPort1 = 27180 NATDetectPort1 = 27180
IfconfigPort2 = 27181 NATDetectPort2 = 27181
WsPort = 27183 WsPort = 27183
UDPPort1 = 27182 WsPort2 = 465
UDPPort2 = 27183 UDPPort1 = 27182
UDPPort2 = 27183
) )
type openP2PHeader struct { type openP2PHeader struct {
@@ -48,6 +51,12 @@ type overlayHeader struct {
id uint64 id uint64
} }
type NodeDataMPAck struct {
FromNodeID uint64
Seq uint64
Delay uint32 // delay write mergeack ms
}
var overlayHeaderSize = binary.Size(overlayHeader{}) var overlayHeaderSize = binary.Size(overlayHeader{})
func decodeHeader(data []byte) (*openP2PHeader, error) { func decodeHeader(data []byte) (*openP2PHeader, error) {
@@ -74,7 +83,7 @@ func encodeHeader(mainType uint16, subType uint16, len uint32) []byte {
return headBuf.Bytes() return headBuf.Bytes()
} }
// Message type // Message main type
const ( const (
MsgLogin = 0 MsgLogin = 0
MsgHeartbeat = 1 MsgHeartbeat = 1
@@ -108,41 +117,51 @@ const (
MsgPushReportGoroutine = 16 MsgPushReportGoroutine = 16
MsgPushReportMemApps = 17 MsgPushReportMemApps = 17
MsgPushServerSideSaveMemApp = 18 MsgPushServerSideSaveMemApp = 18
MsgPushCheckRemoteService = 19
MsgPushSpecTunnel = 20
MsgPushReportHeap = 21
MsgPushSDWanRefresh = 22
MsgPushNat4Detect = 23
) )
// MsgP2P sub type message // MsgP2P sub type message
const ( const (
MsgPunchHandshake = iota MsgPunchHandshake = 0
MsgPunchHandshakeAck MsgPunchHandshakeAck = 1
MsgTunnelHandshake MsgTunnelHandshake = 2
MsgTunnelHandshakeAck MsgTunnelHandshakeAck = 3
MsgTunnelHeartbeat MsgTunnelHeartbeat = 4
MsgTunnelHeartbeatAck MsgTunnelHeartbeatAck = 5
MsgOverlayConnectReq MsgOverlayConnectReq = 6
MsgOverlayConnectRsp MsgOverlayConnectRsp = 7
MsgOverlayDisconnectReq MsgOverlayDisconnectReq = 8
MsgOverlayData MsgOverlayData = 9
MsgRelayData MsgRelayData = 10
MsgRelayHeartbeat MsgRelayHeartbeat = 11
MsgRelayHeartbeatAck MsgRelayHeartbeatAck = 12
MsgNodeData MsgNodeData = 13
MsgRelayNodeData MsgRelayNodeData = 14
MsgNodeDataMP = 15
MsgNodeDataMPAck = 16
MsgRelayHeartbeatAck2 = 17
) )
// MsgRelay sub type message // MsgRelay sub type message
const ( const (
MsgRelayNodeReq = iota MsgRelayNodeReq = 0
MsgRelayNodeRsp MsgRelayNodeRsp = 1
) )
// MsgReport sub type message // MsgReport sub type message
const ( const (
MsgReportBasic = iota MsgReportBasic = 0
MsgReportQuery MsgReportQuery = 1
MsgReportConnect MsgReportConnect = 2
MsgReportApps MsgReportApps = 3
MsgReportLog MsgReportLog = 4
MsgReportMemApps MsgReportMemApps = 5
MsgReportResponse = 6
MsgReportBasicRsp = 7
) )
const ( const (
@@ -165,16 +184,12 @@ const (
MaxRetry = 10 MaxRetry = 10
Cone2ConeTCPPunchMaxRetry = 1 Cone2ConeTCPPunchMaxRetry = 1
Cone2ConeUDPPunchMaxRetry = 1 Cone2ConeUDPPunchMaxRetry = 1
PublicIPEchoTimeout = time.Second * 1 PublicIPEchoTimeout = time.Second * 5
NatTestTimeout = time.Second * 5 NatDetectTimeout = time.Second * 5
UDPReadTimeout = time.Second * 5 UDPReadTimeout = time.Second * 5
ClientAPITimeout = time.Second * 10 ClientAPITimeout = time.Second * 10
UnderlayConnectTimeout = time.Second * 10 UnderlayConnectTimeout = time.Second * 10
MaxDirectTry = 3 MaxDirectTry = 3
// sdwan
ReadTunBuffSize = 1600
ReadTunBuffNum = 10
) )
// NATNone has public ip // NATNone has public ip
@@ -206,13 +221,19 @@ const (
) )
const ( const (
MsgQueryPeerInfoReq = iota MsgQueryPeerInfoReq = 0
MsgQueryPeerInfoRsp MsgQueryPeerInfoRsp = 1
) )
const ( const (
MsgSDWANInfoReq = iota MsgSDWANInfoReq = 0
MsgSDWANInfoRsp MsgSDWANInfoRsp = 1
)
// MsgNATDetect
const (
MsgNAT = 0
MsgPublicIP = 1
) )
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) { func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
@@ -235,6 +256,21 @@ func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, er
return writeBytes, nil return writeBytes, nil
} }
func newMessageWithBuff(mainType uint16, subType uint16, data []byte) ([]byte, error) {
head := openP2PHeader{
uint32(len(data)),
mainType,
subType,
}
headBuf := new(bytes.Buffer)
err := binary.Write(headBuf, binary.LittleEndian, head)
if err != nil {
return nil, err
}
writeBytes := append(headBuf.Bytes(), data...)
return writeBytes, nil
}
func NodeNameToID(name string) uint64 { func NodeNameToID(name string) uint64 {
return crc64.Checksum([]byte(name), crc64.MakeTable(crc64.ISO)) return crc64.Checksum([]byte(name), crc64.MakeTable(crc64.ISO))
} }
@@ -287,6 +323,8 @@ type LoginRsp struct {
Token uint64 `json:"token,omitempty"` Token uint64 `json:"token,omitempty"`
Ts int64 `json:"ts,omitempty"` Ts int64 `json:"ts,omitempty"`
LoginMaxDelay int `json:"loginMaxDelay,omitempty"` // seconds LoginMaxDelay int `json:"loginMaxDelay,omitempty"` // seconds
Forcev6 int `json:"forcev6,omitempty"`
PublicIPPort int `json:"publicIPPort,omitempty"`
} }
type NatDetectReq struct { type NatDetectReq struct {
@@ -321,7 +359,8 @@ type TunnelMsg struct {
} }
type RelayNodeReq struct { type RelayNodeReq struct {
PeerNode string `json:"peerNode,omitempty"` PeerNode string `json:"peerNode,omitempty"`
ExcludeNodes string `json:"excludeNodes,omitempty"` //TODO: add exclude ip
} }
type RelayNodeRsp struct { type RelayNodeRsp struct {
@@ -331,13 +370,15 @@ type RelayNodeRsp struct {
} }
type AddRelayTunnelReq struct { type AddRelayTunnelReq struct {
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
RelayName string `json:"relayName,omitempty"` RelayName string `json:"relayName,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
RelayToken uint64 `json:"relayToken,omitempty"` RelayToken uint64 `json:"relayToken,omitempty"`
RelayMode string `json:"relayMode,omitempty"` RelayMode string `json:"relayMode,omitempty"`
AppID uint64 `json:"appID,omitempty"` // deprecated AppID uint64 `json:"appID,omitempty"` // deprecated
AppKey uint64 `json:"appKey,omitempty"` // deprecated AppKey uint64 `json:"appKey,omitempty"` // deprecated
UnderlayProtocol string `json:"underlayProtocol,omitempty"` // quic or kcp, default quic
PunchPriority int `json:"punchPriority,omitempty"`
} }
type APPKeySync struct { type APPKeySync struct {
@@ -346,9 +387,10 @@ type APPKeySync struct {
} }
type RelayHeartbeat struct { type RelayHeartbeat struct {
From string `json:"from,omitempty"` From string `json:"from,omitempty"`
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
AppID uint64 `json:"appID,omitempty"` RelayTunnelID2 uint64 `json:"relayTunnelID2,omitempty"`
AppID uint64 `json:"appID,omitempty"`
} }
type ReportBasic struct { type ReportBasic struct {
@@ -357,6 +399,7 @@ type ReportBasic struct {
LanIP string `json:"lanIP,omitempty"` LanIP string `json:"lanIP,omitempty"`
HasIPv4 int `json:"hasIPv4,omitempty"` HasIPv4 int `json:"hasIPv4,omitempty"`
IPv6 string `json:"IPv6,omitempty"` IPv6 string `json:"IPv6,omitempty"`
PublicIPPort int `json:"publicIPPort,omitempty"`
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"` HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
NetInfo NetInfo `json:"netInfo,omitempty"` NetInfo NetInfo `json:"netInfo,omitempty"`
@@ -407,13 +450,16 @@ type AppInfo struct {
} }
type ReportApps struct { type ReportApps struct {
Apps []AppInfo Apps []AppInfo
TunError string `json:"tunError,omitempty"`
} }
type ReportLogReq struct { type ReportLogReq struct {
FileName string `json:"fileName,omitempty"` FileName string `json:"fileName,omitempty"`
Offset int64 `json:"offset,omitempty"` Offset int64 `json:"offset,omitempty"`
Len int64 `json:"len,omitempty"` Len int64 `json:"len,omitempty"`
IsSetLogLevel int64 `json:"isSetLogLevel,omitempty"`
LogLevel int64 `json:"loglevel,omitempty"`
} }
type ReportLogRsp struct { type ReportLogRsp struct {
FileName string `json:"fileName,omitempty"` FileName string `json:"fileName,omitempty"`
@@ -426,6 +472,7 @@ type UpdateInfo struct {
Error int `json:"error,omitempty"` Error int `json:"error,omitempty"`
ErrorDetail string `json:"errorDetail,omitempty"` ErrorDetail string `json:"errorDetail,omitempty"`
Url string `json:"url,omitempty"` Url string `json:"url,omitempty"`
Url2 string `json:"url2,omitempty"`
} }
type NetInfo struct { type NetInfo struct {
@@ -457,8 +504,10 @@ type ProfileInfo struct {
} }
type EditNode struct { type EditNode struct {
NewName string `json:"newName,omitempty"` NewName string `json:"newName,omitempty"`
Bandwidth int `json:"bandwidth,omitempty"` Bandwidth int `json:"bandwidth,omitempty"`
Forcev6 int `json:"forcev6,omitempty"`
PublicIPPort int `json:"publicIPPort,omitempty"`
} }
type QueryPeerInfoReq struct { type QueryPeerInfoReq struct {
@@ -493,7 +542,18 @@ type SDWANInfo struct {
ForceRelay int32 `json:"forceRelay,omitempty"` ForceRelay int32 `json:"forceRelay,omitempty"`
PunchPriority int32 `json:"punchPriority,omitempty"` PunchPriority int32 `json:"punchPriority,omitempty"`
Enable int32 `json:"enable,omitempty"` Enable int32 `json:"enable,omitempty"`
Nodes []SDWANNode TunnelNum int32 `json:"tunnelNum,omitempty"`
Mtu int32 `json:"mtu,omitempty"`
Nodes []*SDWANNode
}
func (s *SDWANInfo) GetResourceByNodeName(nodeName string) string {
for _, node := range s.Nodes {
if node.Name == nodeName {
return node.Resource
}
}
return ""
} }
const ( const (
@@ -508,6 +568,32 @@ type ServerSideSaveMemApp struct {
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // rtid, if not 0 relay RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // rtid, if not 0 relay
RelayMode string `json:"relayMode,omitempty"` RelayMode string `json:"relayMode,omitempty"`
AppID uint64 `json:"appID,omitempty"` AppID uint64 `json:"appID,omitempty"`
AppKey uint64 `json:"appKey,omitempty"`
RelayIndex uint32 `json:"relayIndex,omitempty"`
TunnelNum uint32 `json:"tunnelNum,omitempty"`
SrcPort uint32 `json:"srcPort,omitempty"`
}
type CheckRemoteService struct {
Host string `json:"host,omitempty"`
Port uint32 `json:"port,omitempty"`
}
type SpecTunnel struct {
TunnelIndex uint32 `json:"tunnelIndex,omitempty"`
}
type Nat4Detect struct {
CustomData []*Nat4DetectItem
Num int32 `json:"num,omitempty"`
*Nat4DetectItem
}
type Nat4DetectItem struct {
Protocol string `json:"protocol,omitempty"`
Server string `json:"server,omitempty"`
ServerPort int32 `json:"serverPort,omitempty"`
LocalPort int32 `json:"localPort,omitempty"`
} }
const rootCA = `-----BEGIN CERTIFICATE----- const rootCA = `-----BEGIN CERTIFICATE-----
@@ -532,6 +618,31 @@ RVtXS+DplMClQ5QSlv3StwcWOsjyiAimNfLEU5xoEfq17yOJUTU1OTL4YOt16QUc
C1tnzFr3k/ioqFR7cnyzNrbjlfPOmO9l2WReEbMP3bvaSHm6EcpJKS8= C1tnzFr3k/ioqFR7cnyzNrbjlfPOmO9l2WReEbMP3bvaSHm6EcpJKS8=
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
const rootEdgeCA = `-----BEGIN CERTIFICATE-----
MIID/zCCAuegAwIBAgIUI53UqyuJSa74NFIKherg5WTjtl4wDQYJKoZIhvcNAQEL
BQAwgYYxCzAJBgNVBAYTAkNOMQswCQYDVQQIDAJHRDETMBEGA1UECgwKb3BlbnAy
cC5jbjETMBEGA1UECwwKb3BlbnAycC5jbjEbMBkGA1UEAwwSb3BlbnAycC5jbiBS
b290IENBMSMwIQYJKoZIhvcNAQkBFhRvcGVucDJwLmNuQGdtYWlsLmNvbTAeFw0y
NTA5MDMwNTExMTBaFw0zNTA5MDEwNTExMTBaMIGGMQswCQYDVQQGEwJDTjELMAkG
A1UECAwCR0QxEzARBgNVBAoMCm9wZW5wMnAuY24xEzARBgNVBAsMCm9wZW5wMnAu
Y24xGzAZBgNVBAMMEm9wZW5wMnAuY24gUm9vdCBDQTEjMCEGCSqGSIb3DQEJARYU
b3BlbnAycC5jbkBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC/aHC0opWx1MFkXYI+Mm0CkMi7nB5XaD3K/DGGtA/kadhayFSWb6Y2+UWW
s6OYBy7NmQRJgTedS4siQA6JEG4H3FBbz8URLt4TH/EP9+6QB0Z+P0arvUXNkl4k
7cALmblaiqjq2M199+FWKhDWH2vMr1htY9Y3ldivLRMeH76diKgf8NvsX+wGR8bZ
4MlJMFln0UeUYKIbekK7DmA5/9f2A/2Nrmi84PKGHU+0ZjB7gik/slW5zH0k7e+S
wNtTuf8+6+t/LcJK9dWsS6f5+DOWmLcIWs6s/VMP9ODEzlY/hKMFk53+H+AjAZY/
J/qhOxLXMNlNjdjwSEFPBY/vwVEnAgMBAAGjYzBhMB0GA1UdDgQWBBTXSSeIvz/R
6A1pz0H4xBlV1Vu9kTAfBgNVHSMEGDAWgBTXSSeIvz/R6A1pz0H4xBlV1Vu9kTAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC
AQEABqvvKwM+k2NfIFf9tzo1EsD4rQunyn6K5Zhf/kspb9++2Onw/lDlOErxSLLz
C5aXn+B48honQeYEL/cYhH4duVQb0Zk71iF/PKDxYvF79Xbx9k7Kzg6RryaH8ZfQ
pyEao+Uc6O895F+SLBog5aHIbz8gFNCRVaSAv3xpUIyQ/haxyHHapaLqt/ueNFVP
qEG+9R41q55rEYb2ltINhumS3gb4qOcKI5pHuAw42pF8SShqaBIfFXSZ4u9ib7/k
CvHN0kDYavV6NRiCSRF6wMxmaF70WpfqQhGdw0WyIzJfMOtSdvctjfNCoaWy2V2s
nLaJXgiPehxIVGNC9dk/ZZzI2g==
-----END CERTIFICATE-----`
const ISRGRootX1 = `-----BEGIN CERTIFICATE----- const ISRGRootX1 = `-----BEGIN CERTIFICATE-----
MIIEJjCCAw6gAwIBAgISAztStWq026ej0RCsk3ErbUdPMA0GCSqGSIb3DQEBCwUA MIIEJjCCAw6gAwIBAgISAztStWq026ej0RCsk3ErbUdPMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
+132 -41
View File
@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net" "net"
"reflect"
"runtime" "runtime"
"strings" "strings"
"sync" "sync"
@@ -43,8 +44,8 @@ type sdwanNode struct {
} }
type p2pSDWAN struct { type p2pSDWAN struct {
nodeName string
tun *optun tun *optun
tunErr string
sysRoute sync.Map // ip:sdwanNode sysRoute sync.Map // ip:sdwanNode
subnet *net.IPNet subnet *net.IPNet
gateway net.IP gateway net.IP
@@ -52,22 +53,55 @@ type p2pSDWAN struct {
internalRoute *IPTree internalRoute *IPTree
} }
func (s *p2pSDWAN) init(name string) error { func (s *p2pSDWAN) reset() {
gLog.i("reset sdwan when network disconnected")
// clear sysroute
delRoutesByGateway(s.gateway.String())
s.sysRoute.Range(func(key, value interface{}) bool {
s.sysRoute.Delete(key)
return true
})
// clear internel route
s.internalRoute = NewIPTree("")
// clear p2papp
for _, node := range gConf.getSDWAN().Nodes {
gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name})
GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name})
}
gConf.resetSDWAN()
}
func (s *p2pSDWAN) init() error {
gConf.Network.previousIP = gConf.Network.publicIP
if gConf.getSDWAN().Gateway == "" { if gConf.getSDWAN().Gateway == "" {
gLog.Println(LvDEBUG, "not in sdwan clear all ") gLog.d("sdwan init: not in sdwan clear all ")
} }
if s.internalRoute == nil { if s.internalRoute == nil {
s.internalRoute = NewIPTree("") s.internalRoute = NewIPTree("")
} }
s.nodeName = name if gw, sn, err := net.ParseCIDR(gConf.getSDWAN().Gateway); err == nil { // preserve old gateway
s.gateway, s.subnet, _ = net.ParseCIDR(gConf.getSDWAN().Gateway) s.gateway = gw
s.subnet = sn
}
for _, node := range gConf.getDelNodes() { for _, node := range gConf.getDelNodes() {
gLog.Println(LvDEBUG, "deal deleted node: ", node.Name) gLog.d("sdwan init: deal deleted node: %s", node.Name)
delRoute(node.IP, s.gateway.String()) gLog.d("sdwan init: delRoute: %s, %s ", node.IP, s.gateway.String())
// delRoute(node.IP, s.gateway.String()) // TODO: seems no need delelte each node
s.internalRoute.Del(node.IP, node.IP) s.internalRoute.Del(node.IP, node.IP)
ipNum, _ := inetAtoN(node.IP) ipNum, _ := inetAtoN(node.IP)
s.sysRoute.Delete(ipNum) s.sysRoute.Delete(ipNum)
// if node.Name == gConf.Network.Node {
// // this is local node, need rm all client-side apps
// GNetwork.apps.Range(func(id, i interface{}) bool {
// app := i.(*p2pApp)
// if app.config.is
// return true
// })
// continue
// }
gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name}) gConf.delete(AppConfig{SrcPort: 0, PeerNode: node.Name})
GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name}) GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name})
arr := strings.Split(node.Resource, ",") arr := strings.Split(node.Resource, ",")
@@ -88,24 +122,26 @@ func (s *p2pSDWAN) init(name string) error {
} }
s.internalRoute.Del(minIP.String(), maxIP.String()) s.internalRoute.Del(minIP.String(), maxIP.String())
delRoute(ipnet.String(), s.gateway.String()) delRoute(ipnet.String(), s.gateway.String())
gLog.d("sdwan init: resource delRoute: %s, %s ", ipnet.String(), s.gateway.String())
} }
} }
for _, node := range gConf.getAddNodes() { for _, node := range gConf.getAddNodes() {
gLog.Println(LvDEBUG, "deal add node: ", node.Name) gLog.d("sdwan init: deal add node: %s", node.Name)
ipNet := &net.IPNet{ ipNet := &net.IPNet{
IP: net.ParseIP(node.IP), IP: net.ParseIP(node.IP),
Mask: s.subnet.Mask, Mask: s.subnet.Mask,
} }
if node.Name == s.nodeName { if node.Name == gConf.Network.Node {
s.virtualIP = ipNet s.virtualIP = ipNet
gLog.Println(LvINFO, "start tun ", ipNet.String()) gLog.i("sdwan init: start tun %s", ipNet.String())
err := s.StartTun() err := s.StartTun()
if err != nil { if err != nil {
gLog.Println(LvERROR, "start tun error:", err) gLog.e("sdwan init: start tun error:%s", err)
return err return err
} }
gLog.Println(LvINFO, "start tun ok") gLog.i("sdwan init: start tun ok")
allowTunForward() allowTunForward()
gLog.d("sdwan init: addRoute %s %s %s", s.subnet.String(), s.gateway.String(), s.tun.tunName)
addRoute(s.subnet.String(), s.gateway.String(), s.tun.tunName) addRoute(s.subnet.String(), s.gateway.String(), s.tun.tunName)
// addRoute("255.255.255.255/32", s.gateway.String(), s.tun.tunName) // for broadcast // addRoute("255.255.255.255/32", s.gateway.String(), s.tun.tunName) // for broadcast
// addRoute("224.0.0.0/4", s.gateway.String(), s.tun.tunName) // for multicast // addRoute("224.0.0.0/4", s.gateway.String(), s.tun.tunName) // for multicast
@@ -120,22 +156,32 @@ func (s *p2pSDWAN) init(name string) error {
s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
} }
for _, node := range gConf.getAddNodes() { for _, node := range gConf.getAddNodes() {
if node.Name == s.nodeName { // not deal resource itself if node.Name == gConf.Network.Node { // not deal resource itself
continue continue
} }
if len(node.Resource) > 0 { if len(node.Resource) > 0 {
gLog.Printf(LvINFO, "deal add node: %s resource: %s", node.Name, node.Resource) gLog.i("sdwan init: deal add node: %s resource: %s", node.Name, node.Resource)
arr := strings.Split(node.Resource, ",") arr := strings.Split(node.Resource, ",")
for _, r := range arr { for _, r := range arr {
// add internal route // add internal route
_, ipnet, err := net.ParseCIDR(r) _, ipnet, err := net.ParseCIDR(r)
if err != nil { if err != nil {
fmt.Println("Error parsing CIDR:", err) fmt.Println("sdwan init: Error parsing CIDR:", err)
continue continue
} }
if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan
gLog.d("sdwan init: local ip %s in this resource %s, ignore", gConf.Network.localIP, ipnet.IP.String())
continue continue
} }
// local net could access this single ip
if ipnet.Mask[0] == 255 && ipnet.Mask[1] == 255 && ipnet.Mask[2] == 255 && ipnet.Mask[3] == 255 {
gLog.d("sdwan init: ping %s start", ipnet.IP.String())
if _, err := Ping(ipnet.IP.String()); err == nil {
gLog.d("sdwan init: ping %s ok, ignore this resource", ipnet.IP.String())
continue
}
gLog.d("sdwan init: ping %s failed", ipnet.IP.String())
}
minIP := ipnet.IP minIP := ipnet.IP
maxIP := make(net.IP, len(minIP)) maxIP := make(net.IP, len(minIP))
copy(maxIP, minIP) copy(maxIP, minIP)
@@ -144,12 +190,13 @@ func (s *p2pSDWAN) init(name string) error {
} }
s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)}) s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
// add sys route // add sys route
gLog.d("sdwan init: addRoute %s %s %s", ipnet.String(), s.gateway.String(), s.tun.tunName)
addRoute(ipnet.String(), s.gateway.String(), s.tun.tunName) addRoute(ipnet.String(), s.gateway.String(), s.tun.tunName)
} }
} }
} }
gConf.retryAllMemApp() gConf.retryAllMemApp()
gLog.Printf(LvINFO, "sdwan init ok") gLog.i("sdwan init ok")
return nil return nil
} }
@@ -162,28 +209,28 @@ func (s *p2pSDWAN) run() {
} }
func (s *p2pSDWAN) readNodeLoop() { func (s *p2pSDWAN) readNodeLoop() {
gLog.Printf(LvDEBUG, "sdwan readNodeLoop start") gLog.d("sdwan readNodeLoop start")
defer gLog.Printf(LvDEBUG, "sdwan readNodeLoop end") defer gLog.d("sdwan readNodeLoop end")
writeBuff := make([][]byte, 1) writeBuff := make([][]byte, 1)
for { for {
nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet
if nd == nil { if nd == nil {
gLog.Printf(LvDev, "waiting for node data") gLog.dev("waiting for node data")
continue continue
} }
head := PacketHeader{} head := PacketHeader{}
parseHeader(nd.Data, &head) parseHeader(nd, &head)
gLog.Printf(LvDev, "write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd.Data)) gLog.dev("write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd))
if PIHeaderSize == 0 { if PIHeaderSize == 0 {
writeBuff[0] = nd.Data writeBuff[0] = nd
} else { } else {
writeBuff[0] = make([]byte, PIHeaderSize+len(nd.Data)) writeBuff[0] = make([]byte, PIHeaderSize+len(nd))
copy(writeBuff[0][PIHeaderSize:], nd.Data) copy(writeBuff[0][PIHeaderSize:], nd)
} }
len, err := s.tun.Write(writeBuff, PIHeaderSize) len, err := s.tun.Write(writeBuff, PIHeaderSize)
if err != nil { if err != nil {
gLog.Printf(LvDEBUG, "write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err) gLog.d("write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err)
} }
} }
} }
@@ -199,9 +246,11 @@ func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) {
v, ok := s.internalRoute.Load(head.dst) v, ok := s.internalRoute.Load(head.dst)
if !ok || v == nil { if !ok || v == nil {
if isBroadcastOrMulticast(head.dst, s.subnet) { if isBroadcastOrMulticast(head.dst, s.subnet) {
gLog.Printf(LvDev, "multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String()) gLog.dev("multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String())
GNetwork.WriteBroadcast(p) GNetwork.WriteBroadcast(p)
return
} }
gLog.dev("internalRoute not found ip:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String())
return return
} else { } else {
node = v.(*sdwanNode) node = v.(*sdwanNode)
@@ -209,13 +258,13 @@ func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) {
err := GNetwork.WriteNode(node.id, p) err := GNetwork.WriteNode(node.id, p)
if err != nil { if err != nil {
gLog.Printf(LvDev, "write packet to %s fail: %s", node.name, err) gLog.dev("write packet to %s fail: %s", node.name, err)
} }
} }
func (s *p2pSDWAN) readTunLoop() { func (s *p2pSDWAN) readTunLoop() {
gLog.Printf(LvDEBUG, "sdwan readTunLoop start") gLog.d("sdwan readTunLoop start")
defer gLog.Printf(LvDEBUG, "sdwan readTunLoop end") defer gLog.d("sdwan readTunLoop end")
readBuff := make([][]byte, ReadTunBuffNum) readBuff := make([][]byte, ReadTunBuffNum)
for i := 0; i < ReadTunBuffNum; i++ { for i := 0; i < ReadTunBuffNum; i++ {
readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize) readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize)
@@ -225,16 +274,16 @@ func (s *p2pSDWAN) readTunLoop() {
for { for {
n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize) n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "read tun fail: ", err) gLog.e("read tun fail: %s", err)
return return
} }
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
if readBuffSize[i] > ReadTunBuffSize { if readBuffSize[i] > ReadTunBuffSize {
gLog.Printf(LvERROR, "read tun overflow: len=", readBuffSize[i]) gLog.e("read tun overflow: len=%d", readBuffSize[i])
continue continue
} }
parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih)
gLog.Printf(LvDev, "read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0]) gLog.dev("read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0])
s.routeTunPacket(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih) s.routeTunPacket(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih)
} }
} }
@@ -246,23 +295,25 @@ func (s *p2pSDWAN) StartTun() error {
tun := &optun{} tun := &optun{}
err := tun.Start(s.virtualIP.String(), &sdwan) err := tun.Start(s.virtualIP.String(), &sdwan)
if err != nil { if err != nil {
gLog.Println(LvERROR, "open tun fail:", err) gLog.e("open tun fail:%v", err)
s.tunErr = err.Error()
return err return err
} }
s.tun = tun s.tun = tun
s.tunErr = ""
go s.readTunLoop() go s.readTunLoop()
go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds
} }
err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev) err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway) gLog.e("setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway)
return err return err
} }
return nil return nil
} }
func handleSDWAN(subType uint16, msg []byte) error { func handleSDWAN(subType uint16, msg []byte) error {
gLog.Printf(LvDEBUG, "handle sdwan msg type:%d", subType) gLog.d("handle sdwan msg type:%d", subType)
var err error var err error
switch subType { switch subType {
case MsgSDWANInfoRsp: case MsgSDWANInfoRsp:
@@ -270,15 +321,25 @@ func handleSDWAN(subType uint16, msg []byte) error {
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil { if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
return ErrMsgFormat return ErrMsgFormat
} }
gLog.Println(LvINFO, "sdwan init:", prettyJson(rsp)) gLog.i("sdwan init:%s", prettyJson(rsp))
if runtime.GOOS == "android" {
AndroidSDWANConfig <- msg[openP2PHeaderSize:]
}
// GNetwork.sdwan.detail = &rsp // GNetwork.sdwan.detail = &rsp
if gConf.Network.previousIP != gConf.Network.publicIP || gConf.getSDWAN().CentralNode != rsp.CentralNode || gConf.getSDWAN().Gateway != rsp.Gateway {
GNetwork.sdwan.reset()
preAndroidSDWANConfig = "" // let androind app reset vpnservice
}
gConf.setSDWAN(rsp) gConf.setSDWAN(rsp)
err = GNetwork.sdwan.init(gConf.Network.Node) if runtime.GOOS == "android" {
if !compareResources(preAndroidSDWANConfig, string(msg[openP2PHeaderSize:])) { // when config change, notify android app
select {
case AndroidSDWANConfig <- msg[openP2PHeaderSize:]:
default:
}
preAndroidSDWANConfig = string(msg[openP2PHeaderSize:])
}
}
err = GNetwork.sdwan.init()
if err != nil { if err != nil {
gLog.Println(LvERROR, "sdwan init fail: ", err) gLog.e("sdwan init fail: %s", err)
if GNetwork.sdwan.tun != nil { if GNetwork.sdwan.tun != nil {
GNetwork.sdwan.tun.Stop() GNetwork.sdwan.tun.Stop()
GNetwork.sdwan.tun = nil GNetwork.sdwan.tun = nil
@@ -290,3 +351,33 @@ func handleSDWAN(subType uint16, msg []byte) error {
} }
return err return err
} }
// for android vpnservice
func compareResources(json1, json2 string) bool {
var net1, net2 SDWANInfo
if err := json.Unmarshal([]byte(json1), &net1); err != nil {
fmt.Println("Error parsing json1:", err)
fmt.Println("Error parsing json1:", string(json1))
return false
}
if err := json.Unmarshal([]byte(json2), &net2); err != nil {
fmt.Println("Error parsing json2:", err)
fmt.Println("Error parsing json1:", string(json2))
return false
}
// 获取所有资源并比较
resources1 := getResources(net1)
resources2 := getResources(net2)
return reflect.DeepEqual(resources1, resources2)
}
func getResources(network SDWANInfo) []string {
var resources []string
for _, node := range network.Nodes {
if node.Resource != "" {
resources = append(resources, node.Resource)
}
}
return resources
}
+6 -4
View File
@@ -1,6 +1,7 @@
package openp2p package openp2p
import ( import (
"log"
"testing" "testing"
"time" "time"
) )
@@ -15,7 +16,7 @@ func TestBandwidth(t *testing.T) {
for i := 0; i < writeNum; i++ { for i := 0; i < writeNum; i++ {
speedl.Add(oneBuffSize, true) speedl.Add(oneBuffSize, true)
} }
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime) log.Printf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second { if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error") t.Error("error")
} }
@@ -27,12 +28,12 @@ func TestSymmetric(t *testing.T) {
oneBuffSize := 300 oneBuffSize := 300
writeNum := 70 writeNum := 70
expectTime := (oneBuffSize*writeNum - 20000) / speed expectTime := (oneBuffSize*writeNum - 20000) / speed
t.Logf("expect %ds", expectTime) log.Printf("expect %ds", expectTime)
startTs := time.Now() startTs := time.Now()
for i := 0; i < writeNum; i++ { for i := 0; i < writeNum; i++ {
speedl.Add(oneBuffSize, true) speedl.Add(oneBuffSize, true)
} }
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime) log.Printf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second { if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error") t.Error("error")
} }
@@ -44,6 +45,7 @@ func TestSymmetric2(t *testing.T) {
oneBuffSize := 800 oneBuffSize := 800
writeNum := 40 writeNum := 40
expectTime := (oneBuffSize*writeNum - 30000) / speed expectTime := (oneBuffSize*writeNum - 30000) / speed
log.Printf("expect %ds", expectTime)
startTs := time.Now() startTs := time.Now()
for i := 0; i < writeNum; { for i := 0; i < writeNum; {
if speedl.Add(oneBuffSize, true) { if speedl.Add(oneBuffSize, true) {
@@ -52,7 +54,7 @@ func TestSymmetric2(t *testing.T) {
time.Sleep(time.Millisecond) time.Sleep(time.Millisecond)
} }
} }
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime) log.Printf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second { if time.Since(startTs) > time.Duration(expectTime+1)*time.Second || time.Since(startTs) < time.Duration(expectTime-1)*time.Second {
t.Error("error") t.Error("error")
} }
+7 -3
View File
@@ -22,7 +22,7 @@ func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP
if timeout > 0 { if timeout > 0 {
err = conn.SetReadDeadline(time.Now().Add(timeout)) err = conn.SetReadDeadline(time.Now().Add(timeout))
if err != nil { if err != nil {
gLog.Println(LvERROR, "SetReadDeadline error") gLog.e("SetReadDeadline error")
return nil, nil, nil, 0, err return nil, nil, nil, 0, err
} }
} }
@@ -35,9 +35,13 @@ func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP
} }
head = &openP2PHeader{} head = &openP2PHeader{}
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head) err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
if err != nil || head.DataLen > uint32(len(buff)-openP2PHeaderSize) { if err != nil {
gLog.Println(LvERROR, "parse p2pheader error:", err) gLog.e("parse p2pheader error:%s", err)
return nil, nil, nil, 0, err return nil, nil, nil, 0, err
} }
if head.DataLen > uint32(len(buff)-openP2PHeaderSize) {
gLog.e("parse p2pheader error:%d", ErrHeaderDataLen)
return nil, nil, nil, 0, ErrHeaderDataLen
}
return return
} }
+7 -4
View File
@@ -2,6 +2,7 @@ package openp2p
import ( import (
"io" "io"
"net"
"time" "time"
) )
@@ -18,6 +19,7 @@ type underlay interface {
SetReadDeadline(t time.Time) error SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error SetWriteDeadline(t time.Time) error
Protocol() string Protocol() string
RemoteAddr() net.Addr
} }
func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) { func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
@@ -27,7 +29,8 @@ func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
return nil, nil, err return nil, nil, err
} }
head, err := decodeHeader(headBuf) head, err := decodeHeader(headBuf)
if err != nil { if err != nil || head.MainType > 16 {
gLog.d("DefaultReadBuffer error:%v, %d", err, head.MainType)
return nil, nil, err return nil, nil, err
} }
dataBuf := make([]byte, head.DataLen) dataBuf := make([]byte, head.DataLen)
@@ -39,7 +42,7 @@ func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...) writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2)) ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock() ul.WLock()
_, err := ul.Write(writeBytes) err := writeFull(ul, writeBytes)
ul.WUnlock() ul.WUnlock()
return err return err
} }
@@ -47,7 +50,7 @@ func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error
func DefaultWriteBuffer(ul underlay, data []byte) error { func DefaultWriteBuffer(ul underlay, data []byte) error {
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2)) ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock() ul.WLock()
_, err := ul.Write(data) err := writeFull(ul, data)
ul.WUnlock() ul.WUnlock()
return err return err
} }
@@ -59,7 +62,7 @@ func DefaultWriteMessage(ul underlay, mainType uint16, subType uint16, packet in
} }
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2)) ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
ul.WLock() ul.WLock()
_, err = ul.Write(writeBytes) err = writeFull(ul, writeBytes)
ul.WUnlock() ul.WUnlock()
return err return err
} }
@@ -66,7 +66,7 @@ func (conn *underlayKCP) Accept() error {
} }
func listenKCP(addr string, idleTimeout time.Duration) (*underlayKCP, error) { func listenKCP(addr string, idleTimeout time.Duration) (*underlayKCP, error) {
gLog.Println(LvDEBUG, "kcp listen on ", addr) gLog.d("kcp listen on %s", addr)
listener, err := kcp.ListenWithOptions(addr, nil, 0, 0) listener, err := kcp.ListenWithOptions(addr, nil, 0, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("quic.ListenAddr error:%s", err) return nil, fmt.Errorf("quic.ListenAddr error:%s", err)
@@ -81,6 +81,7 @@ func listenKCP(addr string, idleTimeout time.Duration) (*underlayKCP, error) {
} }
func dialKCP(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayKCP, error) { func dialKCP(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayKCP, error) {
conn.SetDeadline(time.Now().Add(idleTimeout))
kConn, err := kcp.NewConn(remoteAddr.String(), nil, 0, 0, conn) kConn, err := kcp.NewConn(remoteAddr.String(), nil, 0, 0, conn)
if err != nil { if err != nil {
return nil, fmt.Errorf("quic.DialContext error:%s", err) return nil, fmt.Errorf("quic.DialContext error:%s", err)
+6 -4
View File
@@ -81,7 +81,7 @@ func (conn *underlayQUIC) Accept() error {
} }
func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) { func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
gLog.Println(LvDEBUG, "quic listen on ", addr) gLog.d("quic listen on %s", addr)
listener, err := quic.ListenAddr(addr, generateTLSConfig(), listener, err := quic.ListenAddr(addr, generateTLSConfig(),
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true}) &quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
if err != nil { if err != nil {
@@ -96,13 +96,15 @@ func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
return ul, nil return ul, nil
} }
func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, idleTimeout time.Duration) (*underlayQUIC, error) { func dialQuic(conn *net.UDPConn, remoteAddr *net.UDPAddr, timeout time.Duration) (*underlayQUIC, error) {
tlsConf := &tls.Config{ tlsConf := &tls.Config{
InsecureSkipVerify: true, InsecureSkipVerify: true,
NextProtos: []string{"openp2pv1"}, NextProtos: []string{"openp2pv1"},
} }
Connection, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf, ctx, cancel := context.WithTimeout(context.Background(), timeout)
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true}) defer cancel()
Connection, err := quic.DialContext(ctx, conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
&quic.Config{Versions: quicVersion, MaxIdleTimeout: TunnelIdleTimeout, DisablePathMTUDiscovery: true})
if err != nil { if err != nil {
return nil, fmt.Errorf("quic.DialContext error:%s", err) return nil, fmt.Errorf("quic.DialContext error:%s", err)
} }
+39 -46
View File
@@ -46,19 +46,25 @@ func (conn *underlayTCP) WUnlock() {
conn.writeMtx.Unlock() conn.writeMtx.Unlock()
} }
func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel) (*underlayTCP, error) { func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel) (underlay, error) {
if mode == LinkModeTCPPunch { if mode == LinkModeTCPPunch || mode == LinkModeTCP6 {
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 { if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion) gLog.d("peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
} else { } else {
ts := time.Duration(int64(t.punchTs) + t.pn.dt - time.Now().UnixNano()) ts := time.Duration(int64(t.punchTs) + GNetwork.dt - time.Now().UnixNano())
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond) gLog.d("sleep %d ms", ts/time.Millisecond)
time.Sleep(ts) time.Sleep(ts)
} }
gLog.Println(LvDEBUG, " send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port)) // gLog.d(" send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port))
c, err := reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout) 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), CheckActiveTimeout)
} else {
c, err = reuse.DialTimeout("tcp6", fmt.Sprintf("[::]:%d", localPort), fmt.Sprintf("[%s]:%d", t.config.peerIPv6, port), CheckActiveTimeout)
}
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "send tcp punch: ", err) // gLog.d("send tcp punch: ", err)
return nil, err return nil, err
} }
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c} utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}
@@ -67,69 +73,56 @@ func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel)
return nil, fmt.Errorf("read start msg error:%s", err) return nil, fmt.Errorf("read start msg error:%s", err)
} }
if buff != nil { if buff != nil {
gLog.Println(LvDEBUG, string(buff)) gLog.d("handshake flag:%s", string(buff))
} }
utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff) utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
return utcp, nil return utcp, nil
} }
t.pn.push(t.config.PeerNode, MsgPushUnderlayConnect, nil) GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
tid := t.id tid := t.id
if compareVersion(t.config.peerVersion, PublicIPVersion) < 0 { // old version if compareVersion(t.config.peerVersion, PublicIPVersion) < 0 { // old version
ipBytes := net.ParseIP(t.config.peerIP).To4() ipBytes := net.ParseIP(t.config.peerIP).To4()
tid = uint64(binary.BigEndian.Uint32(ipBytes)) tid = uint64(binary.BigEndian.Uint32(ipBytes))
gLog.Println(LvDEBUG, "compatible with old client, use ip as key:", tid) gLog.d("compatible with old client, use ip as key:%d", tid)
} }
var utcp *underlayTCP var ul underlay
if mode == LinkModeIntranet && gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { if v4l != nil {
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", localPort)) ul = v4l.getUnderlay(tid)
l, err := net.ListenTCP("tcp4", addr)
if err != nil {
gLog.Printf(LvERROR, "listen %d error:", localPort, err)
return nil, err
}
defer l.Close()
err = l.SetDeadline(time.Now().Add(UnderlayTCPConnectTimeout))
if err != nil {
gLog.Printf(LvERROR, "set listen timeout:", err)
return nil, err
}
c, err := l.Accept()
if err != nil {
return nil, err
}
utcp = &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}
} else {
if v4l != nil {
utcp = v4l.getUnderlayTCP(tid)
}
} }
if ul == nil {
if utcp == nil {
return nil, ErrConnectPublicV4 return nil, ErrConnectPublicV4
} }
return utcp, nil return ul, nil
} }
func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) { func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) {
var c net.Conn var c net.Conn
var err error var err error
if mode == LinkModeTCPPunch { network := "tcp"
gLog.Println(LvDev, " send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port)) localAddr := fmt.Sprintf("0.0.0.0:%d", localPort)
if c, err = reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout); err != nil { remoteAddr := fmt.Sprintf("%s:%d", host, port)
gLog.Println(LvDev, "send tcp punch: ", err) if mode == LinkModeTCP6 { // address need [ip]
} network = "tcp6"
localAddr = fmt.Sprintf("[::]:%d", localPort)
remoteAddr = fmt.Sprintf("[%s]:%d", host, port)
}
if mode == LinkModeTCP4 || mode == LinkModeIntranet { // random port
localAddr = fmt.Sprintf("0.0.0.0:%d", 0)
}
gLog.dev("send tcp punch: %s --> %s", localAddr, remoteAddr)
} else { c, err = reuse.DialTimeout(network, localAddr, remoteAddr, CheckActiveTimeout)
c, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout) if err != nil {
gLog.dev("send tcp punch: %v", err)
} }
if err != nil { if err != nil {
gLog.Printf(LvDev, "Dial %s:%d error:%s", host, port, err) gLog.dev("Dial %s:%d error:%s", host, port, err)
return nil, err return nil, err
} }
tc := c.(*net.TCPConn) tc := c.(*net.TCPConn)
tc.SetKeepAlive(true) tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(UnderlayTCPKeepalive) tc.SetKeepAlivePeriod(UnderlayTCPKeepalive)
gLog.Printf(LvDEBUG, "Dial %s:%d OK", host, port) gLog.d("Dial %s:%d OK", host, port)
return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil return &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}, nil
} }
+17
View File
@@ -0,0 +1,17 @@
package openp2p
import (
"testing"
)
func TestDialTCP(t *testing.T) {
InitForUnitTest(LvDEBUG)
// ul, err := dialTCP("[240e:3b1:6f6:d14:1c0b:9605:554d:351c]", 3389, 0, LinkModeTCP6)
// if err != nil || ul == nil {
// t.Error("dialTCP error:", err)
// }
ul, err := dialTCP("192.168.3.9", 3389, 0, LinkModeTCP6)
if err != nil || ul == nil {
t.Error("dialTCP error:", err)
}
}
+64 -43
View File
@@ -9,7 +9,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@@ -19,21 +18,21 @@ import (
) )
func update(host string, port int) error { func update(host string, port int) error {
gLog.Println(LvINFO, "update start") gLog.i("update start")
defer gLog.Println(LvINFO, "update end") defer gLog.i("update end")
caCertPool, err := x509.SystemCertPool() caCertPool, err := x509.SystemCertPool()
if err != nil { if err != nil {
gLog.Println(LvERROR, "Failed to load system root CAs:", err) gLog.e("Failed to load system root CAs:%s", err)
} else {
caCertPool = x509.NewCertPool() caCertPool = x509.NewCertPool()
} }
caCertPool.AppendCertsFromPEM([]byte(rootCA)) caCertPool.AppendCertsFromPEM([]byte(rootCA))
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
c := http.Client{ c := http.Client{
Transport: &http.Transport{ Transport: &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: caCertPool, TLSClientConfig: &tls.Config{RootCAs: caCertPool,
InsecureSkipVerify: false}, InsecureSkipVerify: gConf.TLSInsecureSkipVerify},
}, },
Timeout: time.Second * 30, Timeout: time.Second * 30,
} }
@@ -41,32 +40,36 @@ func update(host string, port int) error {
goarch := runtime.GOARCH goarch := runtime.GOARCH
rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s&user=%s&node=%s", host, port, OpenP2PVersion, goos, goarch, url.QueryEscape(gConf.Network.User), url.QueryEscape(gConf.Network.Node))) rsp, err := c.Get(fmt.Sprintf("https://%s:%d/api/v1/update?fromver=%s&os=%s&arch=%s&user=%s&node=%s", host, port, OpenP2PVersion, goos, goarch, url.QueryEscape(gConf.Network.User), url.QueryEscape(gConf.Network.Node)))
if err != nil { if err != nil {
gLog.Println(LvERROR, "update:query update list failed:", err) gLog.e("update:query update list failed:%s", err)
return err return err
} }
defer rsp.Body.Close() defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK { if rsp.StatusCode != http.StatusOK {
gLog.Println(LvERROR, "get update info error:", rsp.Status) gLog.e("get update info error:%s", rsp.Status)
return err return err
} }
rspBuf, err := ioutil.ReadAll(rsp.Body) rspBuf, err := io.ReadAll(rsp.Body)
if err != nil { if err != nil {
gLog.Println(LvERROR, "update:read update list failed:", err) gLog.e("update:read update list failed:%s", err)
return err return err
} }
updateInfo := UpdateInfo{} updateInfo := UpdateInfo{}
if err = json.Unmarshal(rspBuf, &updateInfo); err != nil { if err = json.Unmarshal(rspBuf, &updateInfo); err != nil {
gLog.Println(LvERROR, rspBuf, " update info decode error:", err) gLog.e("%s update info decode error:%s", string(rspBuf), err)
return err return err
} }
if updateInfo.Error != 0 { if updateInfo.Error != 0 {
gLog.Println(LvERROR, "update error:", updateInfo.Error, updateInfo.ErrorDetail) gLog.e("update error:%d,%s", updateInfo.Error, updateInfo.ErrorDetail)
return err return err
} }
err = updateFile(updateInfo.Url, "", "openp2p") err = updateFile(updateInfo.Url, "", "openp2p")
if err != nil { if err != nil {
gLog.Println(LvERROR, "update: download failed:", err) gLog.e("update: download failed:%s, retry...", err)
return err err = updateFile(updateInfo.Url2, "", "openp2p")
if err != nil {
gLog.e("update: download failed:%s", err)
return err
}
} }
return nil return nil
} }
@@ -74,66 +77,80 @@ func update(host string, port int) error {
func downloadFile(url string, checksum string, dstFile string) error { func downloadFile(url string, checksum string, dstFile string) error {
output, err := os.OpenFile(dstFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776) output, err := os.OpenFile(dstFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0776)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "OpenFile %s error:%s", dstFile, err) gLog.e("OpenFile %s error:%s", dstFile, err)
return err return err
} }
caCertPool, err := x509.SystemCertPool() caCertPool, err := x509.SystemCertPool()
if err != nil { if err != nil {
gLog.Println(LvERROR, "Failed to load system root CAs:", err) gLog.e("Failed to load system root CAs:%s", err)
} else {
caCertPool = x509.NewCertPool() caCertPool = x509.NewCertPool()
} }
caCertPool.AppendCertsFromPEM([]byte(rootCA)) caCertPool.AppendCertsFromPEM([]byte(rootCA))
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1)) caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
tr := &http.Transport{ tr := &http.Transport{
TLSClientConfig: &tls.Config{ TLSClientConfig: &tls.Config{
RootCAs: caCertPool, RootCAs: caCertPool,
InsecureSkipVerify: false}, InsecureSkipVerify: gConf.TLSInsecureSkipVerify},
} }
client := &http.Client{Transport: tr} client := &http.Client{Transport: tr,
Timeout: 60 * time.Second}
response, err := client.Get(url) response, err := client.Get(url)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "download url %s error:%s", url, err) gLog.e("download url %s error:%s", url, err)
output.Close() output.Close()
return err return err
} }
defer response.Body.Close() defer response.Body.Close()
n, err := io.Copy(output, response.Body) n, err := io.Copy(output, response.Body)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "io.Copy error:%s", err) gLog.e("io.Copy error:%s", err)
output.Close() output.Close()
return err return err
} }
output.Sync() output.Sync()
output.Close() output.Close()
gLog.Println(LvINFO, "download ", url, " ok") gLog.i("download %s ok", url)
gLog.Printf(LvINFO, "size: %d bytes", n) gLog.i("size: %d bytes", n)
return nil return nil
} }
func updateFile(url string, checksum string, dst string) error { func updateFile(url string, checksum string, dst string) error {
gLog.Println(LvINFO, "download ", url) gLog.i("download %s", url)
tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp" tempDir := os.TempDir()
tmpFile := filepath.Join(tempDir, "openp2p.tmp")
err := downloadFile(url, checksum, tmpFile) err := downloadFile(url, checksum, tmpFile)
if err != nil { if err != nil {
return err return err
} }
backupFile := os.Args[0] + "0" backupBase := filepath.Base(os.Args[0])
err = os.Rename(os.Args[0], backupFile) // the old daemon process was using the 0 file, so it will prevent override it var backupFile string
if runtime.GOOS == "windows" {
backupFile = filepath.Join(tempDir, backupBase+"0")
} else {
backupFile = os.Args[0] + "0" // linux can not mv running executable to /tmp, because they are different volumns
}
gLog.i("backup file %s --> %s", os.Args[0], backupFile)
err = moveFile(os.Args[0], backupFile)
if err != nil { if err != nil {
gLog.Printf(LvINFO, " rename %s error:%s, retry 1", os.Args[0], err) if runtime.GOOS == "windows" {
backupFile = os.Args[0] + "1" backupFile = filepath.Join(tempDir, backupBase+"1")
err = os.Rename(os.Args[0], backupFile) } else {
backupFile = os.Args[0] + "1" // 1st update will mv deamon process to 0, 2nd update mv to 0 will failed, mv to 1
}
gLog.i("backup file %s --> %s", os.Args[0], backupFile)
err = moveFile(os.Args[0], backupFile)
if err != nil { if err != nil {
gLog.Printf(LvINFO, " rename %s error:%s", os.Args[0], err) gLog.e(" rename %s error:%s", os.Args[0], err)
return err
} }
} }
// extract // extract
gLog.Println(LvINFO, "extract files") gLog.i("extract files")
err = extract(filepath.Dir(os.Args[0]), tmpFile) err = extract(filepath.Dir(os.Args[0]), tmpFile)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "extract error:%s. revert rename", err) gLog.e("extract error:%s. revert rename", err)
os.Rename(backupFile, os.Args[0]) moveFile(backupFile, os.Args[0])
return err return err
} }
os.Remove(tmpFile) os.Remove(tmpFile)
@@ -224,16 +241,20 @@ func extractTgz(dst, src string) error {
} }
func cleanTempFiles() { func cleanTempFiles() {
tmpFile := os.Args[0] + "0" tempDir := os.TempDir()
if _, err := os.Stat(tmpFile); err == nil { backupBase := filepath.Base(os.Args[0])
if err := os.Remove(tmpFile); err != nil { for i := 0; i < 2; i++ {
gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) tmpFile := fmt.Sprintf("%s%d", os.Args[0], i)
if _, err := os.Stat(tmpFile); err == nil {
if err := os.Remove(tmpFile); err != nil {
gLog.d(" remove %s error:%s", tmpFile, err)
}
} }
} tmpFile = fmt.Sprintf("%s%s%d", tempDir, backupBase, i)
tmpFile = os.Args[0] + "1" if _, err := os.Stat(tmpFile); err == nil {
if _, err := os.Stat(tmpFile); err == nil { if err := os.Remove(tmpFile); err != nil {
if err := os.Remove(tmpFile); err != nil { gLog.d(" remove %s error:%s", tmpFile, err)
gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err) }
} }
} }
} }
+8 -2
View File
@@ -63,13 +63,15 @@ func Discover() (nat NAT, err error) {
return return
} }
var n int var n int
socket.SetDeadline(time.Now().Add(3 * time.Second))
_, _, err = socket.ReadFromUDP(answerBytes) _, _, err = socket.ReadFromUDP(answerBytes)
if err != nil { if err != nil {
gLog.Println(LvDEBUG, "UPNP discover error:", err) gLog.d("UPNP discover error:%s", err)
return return
} }
for { for {
socket.SetDeadline(time.Now().Add(3 * time.Second))
n, _, err = socket.ReadFromUDP(answerBytes) n, _, err = socket.ReadFromUDP(answerBytes)
if err != nil { if err != nil {
break break
@@ -266,7 +268,11 @@ func soapRequest(url, function, message, domain string) (r *http.Response, err e
// log.Stderr("soapRequest ", req) // log.Stderr("soapRequest ", req)
r, err = http.DefaultClient.Do(req) client := &http.Client{
Timeout: 3 * time.Second,
}
r, err = client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
+5 -2
View File
@@ -5,9 +5,12 @@ import (
"syscall" "syscall"
) )
const ( var (
defaultInstallPath = "/usr/local/openp2p" defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p" )
const (
defaultBinName = "openp2p"
) )
func getOsName() (osName string) { func getOsName() (osName string) {
+5 -3
View File
@@ -5,14 +5,16 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"os" "os"
"runtime"
"strings" "strings"
"syscall" "syscall"
) )
const ( var (
defaultInstallPath = "/usr/local/openp2p" defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p" )
const (
defaultBinName = "openp2p"
) )
func getOsName() (osName string) { func getOsName() (osName string) {
+5 -2
View File
@@ -10,9 +10,12 @@ import (
"syscall" "syscall"
) )
const ( var (
defaultInstallPath = "/usr/local/openp2p" defaultInstallPath = "/usr/local/openp2p"
defaultBinName = "openp2p" )
const (
defaultBinName = "openp2p"
) )
func getOsName() (osName string) { func getOsName() (osName string) {
+6 -3
View File
@@ -11,9 +11,12 @@ import (
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )
const ( var (
defaultInstallPath = "C:\\Program Files\\OpenP2P" defaultInstallPath = "C:\\Program Files\\OpenP2P"
defaultBinName = "openp2p.exe" )
const (
defaultBinName = "openp2p.exe"
) )
func getOsName() (osName string) { func getOsName() (osName string) {
@@ -47,7 +50,7 @@ func setRLimit() error {
func setFirewall() { func setFirewall() {
fullPath, err := filepath.Abs(os.Args[0]) fullPath, err := filepath.Abs(os.Args[0])
if err != nil { if err != nil {
gLog.Println(LvERROR, "add firewall error:", err) gLog.e("add firewall error:%s", err)
return return
} }
isXP := false isXP := false
+101 -36
View File
@@ -1,91 +1,156 @@
package openp2p package openp2p
import ( import (
"context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net" "net"
"sync" "sync"
"time" "time"
"github.com/quic-go/quic-go"
) )
type v4Listener struct { type v4Listener struct {
conns sync.Map conns sync.Map
port int port int
acceptCh chan bool acceptCh chan bool
running bool
tcpListener *net.TCPListener
udpListener quic.Listener
wg sync.WaitGroup
} }
func (vl *v4Listener) start() error { func (vl *v4Listener) start() {
vl.running = true
v4l.acceptCh = make(chan bool, 500) v4l.acceptCh = make(chan bool, 500)
for { vl.wg.Add(1)
vl.listen() go func() {
time.Sleep(UnderlayTCPConnectTimeout) defer vl.wg.Done()
} for vl.running {
vl.listenTCP()
time.Sleep(UnderlayTCPConnectTimeout)
}
}()
vl.wg.Add(1)
go func() {
defer vl.wg.Done()
for vl.running {
vl.listenUDP()
time.Sleep(UnderlayTCPConnectTimeout)
}
}()
} }
func (vl *v4Listener) listen() error { func (vl *v4Listener) stop() {
gLog.Printf(LvINFO, "v4Listener listen %d start", vl.port) vl.running = false
defer gLog.Printf(LvINFO, "v4Listener listen %d end", vl.port) if vl.tcpListener != nil {
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", vl.port)) vl.tcpListener.Close()
l, err := net.ListenTCP("tcp4", addr) }
if vl.udpListener != nil {
vl.udpListener.Close()
}
vl.wg.Wait()
}
func (vl *v4Listener) listenTCP() error {
gLog.d("v4Listener listenTCP %d start", vl.port)
defer gLog.d("v4Listener listenTCP %d end", vl.port)
addr, _ := net.ResolveTCPAddr("tcp", fmt.Sprintf("0.0.0.0:%d", vl.port)) // system will auto listen both v4 and v6
var err error
vl.tcpListener, err = net.ListenTCP("tcp", addr)
if err != nil { if err != nil {
gLog.Printf(LvERROR, "v4Listener listen %d error:", vl.port, err) gLog.e("v4Listener listen %d error:", vl.port, err)
return err return err
} }
defer l.Close() defer vl.tcpListener.Close()
for { for {
c, err := l.Accept() c, err := vl.tcpListener.Accept()
if err != nil { if err != nil {
break break
} }
go vl.handleConnection(c) utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()}
go vl.handleConnection(utcp)
} }
vl.tcpListener = nil
return nil return nil
} }
func (vl *v4Listener) handleConnection(c net.Conn) {
gLog.Println(LvDEBUG, "v4Listener accept connection: ", c.RemoteAddr().String()) func (vl *v4Listener) listenUDP() error {
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()} gLog.d("v4Listener listenUDP %d start", vl.port)
utcp.SetReadDeadline(time.Now().Add(UnderlayTCPConnectTimeout)) defer gLog.d("v4Listener listenUDP %d end", vl.port)
_, buff, err := utcp.ReadBuffer() var err error
vl.udpListener, err = quic.ListenAddr(fmt.Sprintf("0.0.0.0:%d", vl.port), generateTLSConfig(),
&quic.Config{Versions: quicVersion, MaxIdleTimeout: TunnelIdleTimeout, DisablePathMTUDiscovery: true})
if err != nil { if err != nil {
gLog.Printf(LvERROR, "utcp.ReadBuffer error:", err) return err
} }
utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff) ctx, cancel := context.WithTimeout(context.Background(), UnderlayConnectTimeout)
defer cancel()
defer vl.udpListener.Close()
for {
sess, err := vl.udpListener.Accept(context.Background())
if err != nil {
break
}
stream, err := sess.AcceptStream(ctx)
if err != nil {
break
}
ul := &underlayQUIC{writeMtx: &sync.Mutex{}, Stream: stream, Connection: sess}
go vl.handleConnection(ul)
}
vl.udpListener = nil
return err
}
func (vl *v4Listener) handleConnection(ul underlay) {
gLog.d("v4Listener accept connection: %s", ul.RemoteAddr().String())
ul.SetReadDeadline(time.Now().Add(UnderlayTCPConnectTimeout))
_, buff, err := ul.ReadBuffer()
if err != nil || buff == nil {
gLog.e("v4Listener read MsgTunnelHandshake error:%s", err)
}
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
var tid uint64 var tid uint64
if string(buff) == "OpenP2P,hello" { // old client if string(buff) == "OpenP2P,hello" { // old client
// save remoteIP as key // save remoteIP as key
remoteAddr := c.RemoteAddr().(*net.TCPAddr).IP remoteAddr := ul.RemoteAddr().(*net.TCPAddr).IP
ipBytes := remoteAddr.To4() ipBytes := remoteAddr.To4()
tid = uint64(binary.BigEndian.Uint32(ipBytes)) // bytes not enough for uint64 tid = uint64(binary.BigEndian.Uint32(ipBytes)) // bytes not enough for uint64
gLog.Println(LvDEBUG, "hello ", string(buff)) gLog.d("hello %s", string(buff))
} else { } else {
if len(buff) < 8 { if len(buff) < 8 {
return return
} }
tid = binary.LittleEndian.Uint64(buff[:8]) tid = binary.LittleEndian.Uint64(buff[:8])
gLog.Println(LvDEBUG, "hello ", tid) gLog.d("hello %d", tid)
} }
// clear timeout connection // clear timeout connections
vl.conns.Range(func(idx, i interface{}) bool { vl.conns.Range(func(idx, i interface{}) bool {
ut := i.(*underlayTCP) if ut, ok := i.(*underlayTCP); ok {
if ut.connectTime.Before(time.Now().Add(-UnderlayTCPConnectTimeout)) { if ut.connectTime.Before(time.Now().Add(-UnderlayTCPConnectTimeout)) {
vl.conns.Delete(idx) vl.conns.Delete(idx)
}
} }
return true return true
}) })
vl.conns.Store(tid, utcp) vl.conns.Store(tid, ul)
if len(vl.acceptCh) == 0 { select {
vl.acceptCh <- true case vl.acceptCh <- true:
default:
gLog.e("msgQueue full, drop it")
} }
} }
func (vl *v4Listener) getUnderlayTCP(tid uint64) *underlayTCP { func (vl *v4Listener) getUnderlay(tid uint64) underlay {
for i := 0; i < 100; i++ { for i := 0; i < 100; i++ {
select { select {
case <-time.After(time.Millisecond * 50): case <-time.After(time.Millisecond * 50):
case <-vl.acceptCh: case <-vl.acceptCh:
} }
if u, ok := vl.conns.LoadAndDelete(tid); ok { if u, ok := vl.conns.LoadAndDelete(tid); ok {
return u.(*underlayTCP) return u.(underlay)
} }
} }
return nil return nil
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 327 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 23 KiB

+4 -2
View File
@@ -6,7 +6,9 @@ RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/* rm -rf /tmp/* /var/tmp/* /var/cache/apk/* /var/cache/distfiles/*
COPY get-client.sh / COPY get-client.sh /
ARG DOCKER_VER="latest" ARG VERSION
LABEL version=${VERSION}
# ARG DOCKER_VER="latest"
RUN echo $TARGETPLATFORM && chmod +x /get-client.sh && ./get-client.sh RUN echo $TARGETPLATFORM && chmod +x /get-client.sh && ./get-client.sh
ENTRYPOINT ["/openp2p"] ENTRYPOINT ["/usr/local/openp2p/openp2p"]
+5 -4
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
echo "Building version:${DOCKER_VER}" echo "Building version:${VERSION}"
echo "Running on platform: $TARGETPLATFORM" echo "Running on platform: $TARGETPLATFORM"
# TARGETPLATFORM=$(echo $TARGETPLATFORM | tr ',' '/') # TARGETPLATFORM=$(echo $TARGETPLATFORM | tr ',' '/')
echo "Running on platform: $TARGETPLATFORM" echo "Running on platform: $TARGETPLATFORM"
@@ -25,7 +25,7 @@ sysType="linux-amd64"
sysType="linux-mipsbe" sysType="linux-mipsbe"
fi fi
fi fi
url="https://openp2p.cn/download/v1/${DOCKER_VER}/openp2p-latest.$sysType.tar.gz" url="https://console.openpxp.com/download/v1/${VERSION}/openp2p-${VERSION}.$sysType.tar.gz"
echo "download $url start" echo "download $url start"
if [ -f /usr/bin/curl ]; then if [ -f /usr/bin/curl ]; then
@@ -38,8 +38,9 @@ if [ $? -ne 0 ]; then
exit 9 exit 9
fi fi
echo "download ok" echo "download ok"
tar -xzvf openp2p.tar.gz mkdir -p /usr/local/openp2p/
chmod +x openp2p tar -xzvf openp2p.tar.gz -C /usr/local/openp2p/
chmod +x /usr/local/openp2p/openp2p
pwd pwd
ls -l ls -l
exit 0 exit 0
+7 -7
View File
@@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
op "openp2p/core" op2p "openp2p/core"
"time" "time"
) )
func main() { func main() {
op.Run() op2p.Run()
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
go echoClient("5800-debug") go echoClient("5800-debug")
} }
@@ -15,28 +15,28 @@ func main() {
} }
func echoClient(peerNode string) { func echoClient(peerNode string) {
sendDatalen := op.ReadBuffLen sendDatalen := op2p.ReadBuffLen
sendBuff := make([]byte, sendDatalen) sendBuff := make([]byte, sendDatalen)
for i := 0; i < len(sendBuff); i++ { for i := 0; i < len(sendBuff); i++ {
sendBuff[i] = byte('A' + i/100) sendBuff[i] = byte('A' + i/100)
} }
// peerNode = "YOUR-PEER-NODE-NAME" // peerNode = "YOUR-PEER-NODE-NAME"
if err := op.GNetwork.ConnectNode(peerNode); err != nil { if err := op2p.GNetwork.ConnectNode(peerNode); err != nil {
fmt.Println("connect error:", err) fmt.Println("connect error:", err)
return return
} }
for i := 0; ; i++ { for i := 0; ; i++ {
sendBuff[1] = 'A' + byte(i%26) sendBuff[1] = 'A' + byte(i%26)
if err := op.GNetwork.WriteNode(op.NodeNameToID(peerNode), sendBuff[:sendDatalen]); err != nil { if err := op2p.GNetwork.WriteNode(op2p.NodeNameToID(peerNode), sendBuff[:sendDatalen]); err != nil {
fmt.Println("write error:", err) fmt.Println("write error:", err)
break break
} }
nd := op.GNetwork.ReadNode(time.Second * 10) nd := op2p.GNetwork.ReadNode(time.Second * 10)
if nd == nil { if nd == nil {
fmt.Printf("waiting for node data\n") fmt.Printf("waiting for node data\n")
time.Sleep(time.Second * 10) time.Sleep(time.Second * 10)
continue continue
} }
fmt.Printf("read %d len=%d data=%s\n", nd.NodeID, len(nd.Data), nd.Data[:16]) // only print 16 bytes fmt.Printf("read len=%d data=%s\n", len(nd), nd[:16]) // only print 16 bytes
} }
} }
+5 -5
View File
@@ -2,12 +2,12 @@ package main
import ( import (
"fmt" "fmt"
op "openp2p/core" op2p "openp2p/core"
"time" "time"
) )
func main() { func main() {
op.Run() op2p.Run()
echoServer() echoServer()
forever := make(chan bool) forever := make(chan bool)
<-forever <-forever
@@ -16,15 +16,15 @@ func main() {
func echoServer() { func echoServer() {
// peerID := fmt.Sprintf("%d", core.NodeNameToID(peerNode)) // peerID := fmt.Sprintf("%d", core.NodeNameToID(peerNode))
for { for {
nd := op.GNetwork.ReadNode(time.Second * 10) nd := op2p.GNetwork.ReadNode(time.Second * 10)
if nd == nil { if nd == nil {
fmt.Printf("waiting for node data\n") fmt.Printf("waiting for node data\n")
// time.Sleep(time.Second * 10) // time.Sleep(time.Second * 10)
continue continue
} }
// fmt.Printf("read %s len=%d data=%s\n", nd.Node, len(nd.Data), nd.Data[:16]) // fmt.Printf("read %s len=%d data=%s\n", nd.Node, len(nd.Data), nd.Data[:16])
nd.Data[0] = 'R' // echo server mark as replied nd[0] = 'R' // echo server mark as replied
if err := op.GNetwork.WriteNode(nd.NodeID, nd.Data); err != nil { if err := op2p.GNetwork.WriteNode(0, nd); err != nil {
fmt.Println("write error:", err) fmt.Println("write error:", err)
break break
} }
+15 -19
View File
@@ -4,39 +4,35 @@ go 1.20
require ( require (
github.com/emirpasic/gods v1.18.1 github.com/emirpasic/gods v1.18.1
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.5.3
github.com/huin/goupnp v1.3.0
github.com/jackpal/go-nat-pmp v1.0.2
github.com/openp2p-cn/go-reuseport v0.3.2 github.com/openp2p-cn/go-reuseport v0.3.2
github.com/openp2p-cn/service v1.0.0 github.com/openp2p-cn/service v1.0.0
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e
github.com/openp2p-cn/wireguard-go v0.0.20240223 github.com/openp2p-cn/wireguard-go v0.0.20241020
github.com/quic-go/quic-go v0.34.0 github.com/quic-go/quic-go v0.34.0
github.com/vishvananda/netlink v1.1.0 github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54
github.com/xtaci/kcp-go/v5 v5.5.17 golang.org/x/net v0.30.0
golang.org/x/sys v0.21.0 golang.org/x/sys v0.26.0
golang.zx2c4.com/wireguard/windows v0.5.3 golang.zx2c4.com/wireguard/windows v0.5.3
) )
require ( require (
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
github.com/kardianos/service v1.2.2 // indirect github.com/kardianos/service v1.2.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/klauspost/reedsolomon v1.11.8 // indirect
github.com/onsi/ginkgo/v2 v2.2.0 // indirect github.com/onsi/ginkgo/v2 v2.2.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-19 v0.3.2 // indirect
github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect
github.com/templexxx/cpu v0.1.0 // indirect github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
github.com/templexxx/xorsimd v0.4.2 // indirect golang.org/x/crypto v0.28.0 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect golang.org/x/mod v0.21.0 // indirect
golang.org/x/crypto v0.24.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/exp v0.0.0-20221205204356-47842c84f3db // indirect golang.org/x/tools v0.26.0 // indirect
golang.org/x/mod v0.18.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/tools v0.22.0 // indirect
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
google.golang.org/protobuf v1.28.1 // indirect gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 // indirect
) )
+109
View File
@@ -0,0 +1,109 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/kardianos/service v1.2.2 h1:ZvePhAHfvo0A7Mftk/tEzqEZ7Q4lgnR8sGz4xu1YX60=
github.com/kardianos/service v1.2.2/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI=
github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
github.com/onsi/gomega v1.20.1 h1:PA/3qinGoukvymdIDV8pii6tiZgC8kbmJO6Z5+b002Q=
github.com/openp2p-cn/go-reuseport v0.3.2 h1:TO78WsyJ1F6g7rLp3hpTKOBxtZTU5Lz+Y4Mj+fVUfZc=
github.com/openp2p-cn/go-reuseport v0.3.2/go.mod h1:+EwCusXz50jaYkPNZcCrK4cLoA9tr2jEiJC+bjzpWc8=
github.com/openp2p-cn/service v1.0.0 h1:1++FroLvW4Mc/PStFIAF0mzudVW6E8EAeqWyIESTGZA=
github.com/openp2p-cn/service v1.0.0/go.mod h1:U4VHekhSJldZ332W6bLviB1fipDrS4omY4dHVc/kgts=
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e h1:QqP3Va/nPj45wq0C8OmGiyZ4HhbTcV6yGuhcYCMgbjg=
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e/go.mod h1:RYVP3CTIvHD9IwQe2M3zy5iLKNjusRVDz/4gQuKcc/o=
github.com/openp2p-cn/wireguard-go v0.0.20241020 h1:cNgG8o2ctYT9YanqalfMQo+jVju7MrdJFI6WLZZRr7M=
github.com/openp2p-cn/wireguard-go v0.0.20241020/go.mod h1:ka26SCScyLEd+uFrnq6w4n65Sxq1W/xIJfXEXLLvJEc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qtls-go1-19 v0.3.2 h1:tFxjCFcTQzK+oMxG6Zcvp4Dq8dx4yD3dDiIiyc86Z5U=
github.com/quic-go/qtls-go1-19 v0.3.2/go.mod h1:ySOI96ew8lnoKPtSqx2BlI5wCpUVPT05RMAlajtnyOI=
github.com/quic-go/qtls-go1-20 v0.2.2 h1:WLOPx6OY/hxtTxKV1Zrq20FtXtDEkeY00CGQm8GEa3E=
github.com/quic-go/qtls-go1-20 v0.2.2/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2lU=
github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
golang.zx2c4.com/wireguard/windows v0.5.3 h1:On6j2Rpn3OEMXqBq00QEDC7bWSZrPIHKIus8eIuExIE=
golang.zx2c4.com/wireguard/windows v0.5.3/go.mod h1:9TEe8TJmtwyQebdFwAkEWOPr3prrtqm+REGFifP60hI=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 h1:QyA/pFgC67EZ5+0oRfiNFhfEGd3NqZM1A2HQEuPKC3c=
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93/go.mod h1:5DMfjtclAbTIjbXqO1qCe2K5GKKxWz2JHvCChuTcJEM=
+4
View File
@@ -1,5 +1,9 @@
package main package main
// On Windows env
// cd lib
// go build -o openp2p.dll -buildmode=c-shared openp2p.go
// caller example see example/dll
import ( import (
op "openp2p/core" op "openp2p/core"
) )
+239
View File
@@ -0,0 +1,239 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package nat provides access to common network port mapping protocols.
package openp2p
import (
"errors"
"fmt"
"net"
"strings"
"sync"
"time"
natpmp "github.com/jackpal/go-nat-pmp"
)
// Interface An implementation of nat.Interface can map local ports to ports
// accessible from the Internet.
type Interface interface {
// These methods manage a mapping between a port on the local
// machine to a port that can be connected to from the internet.
//
// protocol is "UDP" or "TCP". Some implementations allow setting
// a display name for the mapping. The mapping may be removed by
// the gateway when its lifetime ends.
AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error)
DeleteMapping(protocol string, extport, intport int) error
// ExternalIP should return the external (Internet-facing)
// address of the gateway device.
ExternalIP() (net.IP, error)
// String should return name of the method. This is used for logging.
String() string
}
// Parse parses a NAT interface description.
// The following formats are currently accepted.
// Note that mechanism names are not case-sensitive.
//
// "" or "none" return nil
// "extip:77.12.33.4" will assume the local machine is reachable on the given IP
// "any" uses the first auto-detected mechanism
// "upnp" uses the Universal Plug and Play protocol
// "pmp" uses NAT-PMP with an auto-detected gateway address
// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
func Parse(spec string) (Interface, error) {
var (
before, after, found = strings.Cut(spec, ":")
mech = strings.ToLower(before)
ip net.IP
)
if found {
ip = net.ParseIP(after)
if ip == nil {
return nil, errors.New("invalid IP address")
}
}
switch mech {
case "", "none", "off":
return nil, nil
case "any", "auto", "on":
return Any(), nil
case "extip", "ip":
if ip == nil {
return nil, errors.New("missing IP address")
}
return ExtIP(ip), nil
case "upnp":
return UPnP(), nil
case "pmp", "natpmp", "nat-pmp":
return PMP(ip), nil
default:
return nil, fmt.Errorf("unknown mechanism %q", before)
}
}
const (
DefaultMapTimeout = 10 * time.Minute
)
// Map adds a port mapping on m and keeps it alive until c is closed.
// This function is typically invoked in its own goroutine.
//
// Note that Map does not handle the situation where the NAT interface assigns a different
// external port than the requested one.
func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
// log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
refresh := time.NewTimer(DefaultMapTimeout)
defer func() {
refresh.Stop()
// log.Debug("Deleting port mapping")
m.DeleteMapping(protocol, extport, intport)
}()
if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
// log.Debug("Couldn't add port mapping", "err", err)
} else {
// log.Info("Mapped network port")
}
for {
select {
case _, ok := <-c:
if !ok {
return
}
case <-refresh.C:
// log.Trace("Refreshing port mapping")
if _, err := m.AddMapping(protocol, extport, intport, name, DefaultMapTimeout); err != nil {
// log.Debug("Couldn't add port mapping", "err", err)
}
refresh.Reset(DefaultMapTimeout)
}
}
}
// ExtIP assumes that the local machine is reachable on the given
// external IP address, and that any required ports were mapped manually.
// Mapping operations will not return an error but won't actually do anything.
type ExtIP net.IP
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
// These do nothing.
func (ExtIP) AddMapping(string, int, int, string, time.Duration) (uint16, error) { return 0, nil }
func (ExtIP) DeleteMapping(string, int, int) error { return nil }
// Any returns a port mapper that tries to discover any supported
// mechanism on the local network.
func Any() Interface {
// TODO: attempt to discover whether the local machine has an
// Internet-class address. Return ExtIP in this case.
return startautodisc("UPnP or NAT-PMP", func() Interface {
found := make(chan Interface, 2)
go func() { found <- discoverUPnP() }()
go func() { found <- discoverPMP() }()
for i := 0; i < cap(found); i++ {
if c := <-found; c != nil {
return c
}
}
return nil
})
}
// UPnP returns a port mapper that uses UPnP. It will attempt to
// discover the address of your router using UDP broadcasts.
func UPnP() Interface {
return startautodisc("UPnP", discoverUPnP)
}
// PMP returns a port mapper that uses NAT-PMP. The provided gateway
// address should be the IP of your router. If the given gateway
// address is nil, PMP will attempt to auto-discover the router.
func PMP(gateway net.IP) Interface {
if gateway != nil {
return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
}
return startautodisc("NAT-PMP", discoverPMP)
}
// autodisc represents a port mapping mechanism that is still being
// auto-discovered. Calls to the Interface methods on this type will
// wait until the discovery is done and then call the method on the
// discovered mechanism.
//
// This type is useful because discovery can take a while but we
// want return an Interface value from UPnP, PMP and Auto immediately.
type autodisc struct {
what string // type of interface being autodiscovered
once sync.Once
doit func() Interface
mu sync.Mutex
found Interface
}
func startautodisc(what string, doit func() Interface) Interface {
// TODO: monitor network configuration and rerun doit when it changes.
return &autodisc{what: what, doit: doit}
}
func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
if err := n.wait(); err != nil {
return 0, err
}
return n.found.AddMapping(protocol, extport, intport, name, lifetime)
}
func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
if err := n.wait(); err != nil {
return err
}
return n.found.DeleteMapping(protocol, extport, intport)
}
func (n *autodisc) ExternalIP() (net.IP, error) {
if err := n.wait(); err != nil {
return nil, err
}
return n.found.ExternalIP()
}
func (n *autodisc) String() string {
n.mu.Lock()
defer n.mu.Unlock()
if n.found == nil {
return n.what
}
return n.found.String()
}
// wait blocks until auto-discovery has been performed.
func (n *autodisc) wait() error {
n.once.Do(func() {
n.mu.Lock()
n.found = n.doit()
n.mu.Unlock()
})
if n.found == nil {
return fmt.Errorf("no %s router discovered", n.what)
}
return nil
}
+130
View File
@@ -0,0 +1,130 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package openp2p
import (
"fmt"
"net"
"strings"
"time"
natpmp "github.com/jackpal/go-nat-pmp"
)
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
// the common interface.
type pmp struct {
gw net.IP
c *natpmp.Client
}
func (n *pmp) String() string {
return fmt.Sprintf("NAT-PMP(%v)", n.gw)
}
func (n *pmp) ExternalIP() (net.IP, error) {
response, err := n.c.GetExternalAddress()
if err != nil {
return nil, err
}
return response.ExternalIPAddress[:], nil
}
func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) (uint16, error) {
if lifetime <= 0 {
return 0, fmt.Errorf("lifetime must not be <= 0")
}
// Note order of port arguments is switched between our
// AddMapping and the client's AddPortMapping.
res, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
if err != nil {
return 0, err
}
// NAT-PMP maps an alternative available port number if the requested port
// is already mapped to another address and returns success. Handling of
// alternate port numbers is done by the caller.
return res.MappedExternalPort, nil
}
func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
// To destroy a mapping, send an add-port with an internalPort of
// the internal port to destroy, an external port of zero and a
// time of zero.
_, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
return err
}
func discoverPMP() Interface {
// run external address lookups on all potential gateways
gws := potentialGateways()
found := make(chan *pmp, len(gws))
for i := range gws {
gw := gws[i]
go func() {
c := natpmp.NewClient(gw)
if _, err := c.GetExternalAddress(); err != nil {
found <- nil
} else {
found <- &pmp{gw, c}
}
}()
}
// return the one that responds first.
// discovery needs to be quick, so we stop caring about
// any responses after a very short timeout.
timeout := time.NewTimer(1 * time.Second)
defer timeout.Stop()
for range gws {
select {
case c := <-found:
if c != nil {
return c
}
case <-timeout.C:
return nil
}
}
return nil
}
// TODO: improve this. We currently assume that (on most networks)
// the router is X.X.X.1 in a local LAN range.
func potentialGateways() (gws []net.IP) {
ifaces, err := net.Interfaces()
if err != nil {
return nil
}
for _, iface := range ifaces {
ifaddrs, err := iface.Addrs()
if err != nil {
return gws
}
for _, addr := range ifaddrs {
if x, ok := addr.(*net.IPNet); ok {
if x.IP.IsPrivate() {
ip := x.IP.Mask(x.Mask).To4()
if ip != nil {
ip[3] = ip[3] | 0x01
gws = append(gws, ip)
}
}
}
}
}
return gws
}
+247
View File
@@ -0,0 +1,247 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package openp2p
import (
"errors"
"fmt"
"math"
"math/rand"
"net"
"strings"
"sync"
"time"
"github.com/huin/goupnp"
"github.com/huin/goupnp/dcps/internetgateway1"
"github.com/huin/goupnp/dcps/internetgateway2"
)
const (
soapRequestTimeout = 3 * time.Second
rateLimit = 200 * time.Millisecond
)
type upnp struct {
dev *goupnp.RootDevice
service string
client upnpClient
mu sync.Mutex
lastReqTime time.Time
rand *rand.Rand
}
type upnpClient interface {
GetExternalIPAddress() (string, error)
AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
DeletePortMapping(string, uint16, string) error
GetNATRSIPStatus() (sip bool, nat bool, err error)
}
func (n *upnp) natEnabled() bool {
var ok bool
var err error
n.withRateLimit(func() error {
_, ok, err = n.client.GetNATRSIPStatus()
return err
})
return err == nil && ok
}
func (n *upnp) ExternalIP() (addr net.IP, err error) {
var ipString string
n.withRateLimit(func() error {
ipString, err = n.client.GetExternalIPAddress()
return err
})
if err != nil {
return nil, err
}
ip := net.ParseIP(ipString)
if ip == nil {
return nil, errors.New("bad IP in response")
}
return ip, nil
}
func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) (uint16, error) {
ip, err := n.internalAddress()
if err != nil {
return 0, nil // TODO: Shouldn't we return the error?
}
protocol = strings.ToUpper(protocol)
lifetimeS := uint32(lifetime / time.Second)
n.DeleteMapping(protocol, extport, intport)
err = n.withRateLimit(func() error {
return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
})
if err == nil {
return uint16(extport), nil
}
return uint16(extport), n.withRateLimit(func() error {
p, err := n.addAnyPortMapping(protocol, extport, intport, ip, desc, lifetimeS)
if err == nil {
extport = int(p)
}
return err
})
}
func (n *upnp) addAnyPortMapping(protocol string, extport, intport int, ip net.IP, desc string, lifetimeS uint32) (uint16, error) {
if client, ok := n.client.(*internetgateway2.WANIPConnection2); ok {
return client.AddAnyPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
}
// It will retry with a random port number if the client does
// not support AddAnyPortMapping.
extport = n.randomPort()
err := n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
if err != nil {
return 0, err
}
return uint16(extport), nil
}
func (n *upnp) randomPort() int {
if n.rand == nil {
n.rand = rand.New(rand.NewSource(time.Now().UnixNano()))
}
return n.rand.Intn(math.MaxUint16-10000) + 10000
}
func (n *upnp) internalAddress() (net.IP, error) {
devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
if err != nil {
return nil, err
}
ifaces, err := net.Interfaces()
if err != nil {
return nil, err
}
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
return nil, err
}
for _, addr := range addrs {
if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
return x.IP, nil
}
}
}
return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
}
func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
return n.withRateLimit(func() error {
return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
})
}
func (n *upnp) String() string {
return "UPNP " + n.service
}
func (n *upnp) withRateLimit(fn func() error) error {
n.mu.Lock()
defer n.mu.Unlock()
lastreq := time.Since(n.lastReqTime)
if lastreq < rateLimit {
time.Sleep(rateLimit - lastreq)
}
err := fn()
n.lastReqTime = time.Now()
return err
}
// discoverUPnP searches for Internet Gateway Devices
// and returns the first one it can find on the local network.
func discoverUPnP() Interface {
found := make(chan *upnp, 2)
// IGDv1
go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
switch sc.Service.ServiceType {
case internetgateway1.URN_WANIPConnection_1:
return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
case internetgateway1.URN_WANPPPConnection_1:
return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
}
return nil
})
// IGDv2
go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
switch sc.Service.ServiceType {
case internetgateway2.URN_WANIPConnection_1:
return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
case internetgateway2.URN_WANIPConnection_2:
return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
case internetgateway2.URN_WANPPPConnection_1:
return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
}
return nil
})
for i := 0; i < cap(found); i++ {
if c := <-found; c != nil {
return c
}
}
return nil
}
// finds devices matching the given target and calls matcher for all
// advertised services of each device. The first non-nil service found
// is sent into out. If no service matched, nil is sent.
func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
devs, err := goupnp.DiscoverDevices(target)
if err != nil {
out <- nil
return
}
found := false
for i := 0; i < len(devs) && !found; i++ {
if devs[i].Root == nil {
continue
}
devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
if found {
return
}
// check for a matching IGD service
sc := goupnp.ServiceClient{
SOAPClient: service.NewSOAPClient(),
RootDevice: devs[i].Root,
Location: devs[i].Location,
Service: service,
}
sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
upnp := matcher(sc)
if upnp == nil {
return
}
upnp.dev = devs[i].Root
out <- upnp
found = true
})
}
if !found {
out <- nil
}
}