Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9e4cd6b3c | ||
|
|
07836fc162 | ||
|
|
12393f00c5 | ||
|
|
ef4bc1e1e3 | ||
|
|
a1621bcfdd | ||
|
|
47220fe38b | ||
|
|
d3e8ee2a32 | ||
|
|
8e303e93f8 | ||
|
|
471aa5e6ea | ||
|
|
a4c6668760 | ||
|
|
dfaff2c327 | ||
|
|
57fe6986b0 | ||
|
|
6639f40d70 | ||
|
|
d827fd108d | ||
|
|
4daeeaab1a |
@@ -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"
|
||||
@@ -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!"
|
||||
@@ -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
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -1,10 +1,8 @@
|
||||
__debug_bin
|
||||
__debug_bin.exe
|
||||
# .vscode
|
||||
test/
|
||||
openp2p.exe*
|
||||
*.log*
|
||||
go.sum
|
||||
*.tar.gz
|
||||
*.zip
|
||||
*.exe
|
||||
@@ -21,4 +19,10 @@ wintun.dll
|
||||
app/.idea/
|
||||
*_debug_bin*
|
||||
cmd/openp2p
|
||||
vendor/
|
||||
vendor/
|
||||
config.json
|
||||
openp2p
|
||||
lib/openp2p.dll
|
||||
cmd/config.json0
|
||||
test/docker/Dockerfile
|
||||
test/docker/get-client.sh
|
||||
|
||||
@@ -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. 优化客户端卡死问题
|
||||
@@ -1,21 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 OpenP2P.cn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 OpenP2P.cn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
[查看详细](#安全性)
|
||||
### 4. 轻量
|
||||
文件大小2MB+,运行内存2MB+;它可以仅跑在应用层,或者配合wintun驱动使用组网功能
|
||||
文件大小不到10MB,cpu占用极低;它可以仅跑在应用层,或者配合kmod-tun/wintun驱动使用组网功能
|
||||
### 5. 跨平台
|
||||
因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le
|
||||
### 6. 高效
|
||||
@@ -33,11 +33,11 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
|
||||
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
||||
(另外一个快速入门视频 <https://www.bilibili.com/video/BV1Et4y1P7bF/>)
|
||||
### 1.注册
|
||||
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
||||
前往<https://console.openp2p.cn> 使用邮箱注册新用户,暂无需任何认证
|
||||
|
||||

|
||||
### 2.安装
|
||||
分别在本地和远程电脑下载后双击运行,一键安装
|
||||
分别在本地和远程电脑下载后双击运行,一键安装(如果是windows用户,在浏览器下载后请勿修改文件名!!!)
|
||||
|
||||

|
||||
|
||||
@@ -46,7 +46,7 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
||||

|
||||
|
||||

|
||||
### 3.新建P2P应用
|
||||
### 3.新建端口转发(P2PApp)
|
||||
|
||||

|
||||
|
||||
@@ -54,12 +54,12 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
||||
|
||||

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

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

|
||||
|
||||
@@ -82,8 +82,8 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
||||

|
||||
### 客户端架构
|
||||

|
||||
### P2PApp
|
||||
它是项目里最重要的概念,一个P2PApp就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理P2PApp
|
||||
### 端口转发(P2PApp)
|
||||
它是项目里最重要的概念,一个端口转发(P2PApp)就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理端口转发(P2PApp)
|
||||

|
||||
## 安全性
|
||||
加入OpenP2P共享网络的节点,只能凭授权访问。共享节点只会中转数据,别人无法访问内网任何资源。
|
||||
@@ -123,8 +123,8 @@ CGO_ENABLED=0 env GOOS=linux GOARCH=amd64 go build -o openp2p --ldflags '-s -w '
|
||||
1. ~~支持IPv6~~(100%)
|
||||
2. ~~支持随系统自动启动,安装成系统服务~~(100%)
|
||||
3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%)
|
||||
4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%)
|
||||
5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备
|
||||
4. ~~建立网站,用户可以在网站管理所有端口转发(P2PApp)和设备。查看设备在线状态,升级,增删查改重启端口转发(P2PApp)等~~(100%)
|
||||
5. 建立公众号,用户可在微信公众号管理所有端口转发(P2PApp)和设备
|
||||
6. 客户端提供WebUI
|
||||
7. ~~支持自有服务器,开源服务器程序~~(100%)
|
||||
8. 共享节点调度模型优化,对不同的运营商优化
|
||||
|
||||
@@ -19,7 +19,7 @@ The code is open source, the P2P tunnel uses TLS1.3+AES double encryption, and t
|
||||
[details](#Safety)
|
||||
|
||||
### 4. Lightweight
|
||||
2MB+ filesize, 2MB+ memory. It 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
|
||||
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.
|
||||
(Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||
### 1.Register
|
||||
Go to <https://console.openp2p.cn> register a new user
|
||||
Go to <https://console.openp2p.cn> register a new user using email
|
||||
|
||||

|
||||
### 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!!!)
|
||||
|
||||

|
||||
|
||||
@@ -49,7 +49,7 @@ By default, Windows will block programs that have not been signed by the Microso
|
||||
|
||||

|
||||
|
||||
### 3.New P2PApp
|
||||
### 3.New Port ForWard (P2PApp)
|
||||
|
||||

|
||||
|
||||
@@ -57,12 +57,12 @@ By default, Windows will block programs that have not been signed by the Microso
|
||||
|
||||

|
||||
|
||||
### 4.Use P2PApp
|
||||
You can see the P2P application you just created on the "MyHomePC" device, just connect to the "local listening port" shown in the figure below.
|
||||
### 4.Use Port ForWard (P2PApp)
|
||||
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.
|
||||
|
||||

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

|
||||
|
||||
@@ -86,8 +86,8 @@ Especially suitable for large traffic intranet access.
|
||||

|
||||
### Client architecture
|
||||

|
||||
### 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)
|
||||
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).
|
||||
|
||||

|
||||
|
||||
@@ -131,8 +131,8 @@ Short-Term:
|
||||
1. ~~Support IPv6.~~(100%)
|
||||
2. ~~Support auto run when system boot, setup system service.~~(100%)
|
||||
3. ~~Provide free servers to some low-performance network.~~(100%)
|
||||
4. ~~Build website, users can manage all P2PApp and devices via it. View devices' online status, upgrade, restart or CURD P2PApp .~~(100%)
|
||||
5. Provide wechat official account, user can manage P2PApp nodes and deivce as same as website.
|
||||
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 Port ForWard (P2PApp) nodes and deivce as same as website.
|
||||
6. Provide WebUI on client side.
|
||||
7. ~~Support private server, open source server program.~~(100%)
|
||||
8. Optimize our share scheduling model for different network operators.
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import java.io.IOException
|
||||
import android.net.VpnService
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
@@ -22,6 +21,13 @@ import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.coroutines.*
|
||||
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)
|
||||
|
||||
@@ -32,9 +38,10 @@ data class Network(
|
||||
val gateway: String,
|
||||
val Nodes: List<Node>
|
||||
)
|
||||
|
||||
class OpenP2PService : VpnService() {
|
||||
companion object {
|
||||
private val LOG_TAG = OpenP2PService::class.simpleName
|
||||
private val LOG_TAG = "OpenP2PService"
|
||||
}
|
||||
|
||||
inner class LocalBinder : Binder() {
|
||||
@@ -44,12 +51,17 @@ class OpenP2PService : VpnService() {
|
||||
private val binder = LocalBinder()
|
||||
private lateinit var network: openp2p.P2PNetwork
|
||||
private lateinit var mToken: String
|
||||
private var running:Boolean =true
|
||||
private var sdwanRunning:Boolean =false
|
||||
private var running: Boolean = true
|
||||
private var sdwanRunning: Boolean = false
|
||||
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() {
|
||||
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) {
|
||||
createNotificationChannel("kim.hsl", "ForegroundService")
|
||||
} else {
|
||||
@@ -64,7 +76,7 @@ class OpenP2PService : VpnService() {
|
||||
|
||||
val notification = channelId?.let {
|
||||
NotificationCompat.Builder(this, it)
|
||||
// .setSmallIcon(R.mipmap.app_icon)
|
||||
// .setSmallIcon(R.mipmap.app_icon)
|
||||
.setContentTitle("My Awesome App")
|
||||
.setContentText("Doing some work...")
|
||||
.setContentIntent(pendingIntent).build()
|
||||
@@ -76,7 +88,7 @@ class OpenP2PService : VpnService() {
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i(
|
||||
Logger.i(
|
||||
LOG_TAG,
|
||||
"onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id
|
||||
)
|
||||
@@ -86,20 +98,20 @@ class OpenP2PService : VpnService() {
|
||||
|
||||
override fun onBind(p0: Intent?): IBinder? {
|
||||
val token = p0?.getStringExtra("token")
|
||||
Log.i(LOG_TAG, "onBind token=$token")
|
||||
Logger.i(LOG_TAG, "onBind token=$token")
|
||||
startOpenP2P(token)
|
||||
return binder
|
||||
}
|
||||
|
||||
private fun startOpenP2P(token : String?): Boolean {
|
||||
private fun startOpenP2P(token: String?): Boolean {
|
||||
if (sdwanRunning) {
|
||||
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())
|
||||
Log.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token")
|
||||
if (oldToken=="0" && token==null){
|
||||
Logger.i(LOG_TAG, "startOpenP2P oldtoken=$oldToken newtoken=$token")
|
||||
if (oldToken == "0" && token == null) {
|
||||
return false
|
||||
}
|
||||
sdwanRunning = true
|
||||
@@ -112,7 +124,7 @@ class OpenP2PService : VpnService() {
|
||||
1
|
||||
) // /storage/emulated/0/Android/data/cn.openp2p/files/
|
||||
val isConnect = network.connect(30000) // ms
|
||||
Log.i(LOG_TAG, "login result: " + isConnect.toString());
|
||||
Logger.i(LOG_TAG, "login result: " + isConnect.toString());
|
||||
do {
|
||||
Thread.sleep(1000)
|
||||
} while (network.connect(30000) && running)
|
||||
@@ -123,152 +135,257 @@ class OpenP2PService : VpnService() {
|
||||
|
||||
private fun refreshSDWAN() {
|
||||
GlobalScope.launch {
|
||||
Log.i(OpenP2PService.LOG_TAG, "refreshSDWAN start");
|
||||
Logger.i(OpenP2PService.LOG_TAG, "refreshSDWAN start");
|
||||
while (true) {
|
||||
Log.i(OpenP2PService.LOG_TAG, "waiting new sdwan config");
|
||||
val buf = ByteArray(4096)
|
||||
Logger.i(OpenP2PService.LOG_TAG, "waiting new sdwan config");
|
||||
val buf = ByteArray(32 * 1024)
|
||||
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
|
||||
vpnInterface?.close()
|
||||
vpnInterface = null
|
||||
Thread.sleep(10000)
|
||||
runSDWAN(buf.copyOfRange(0,buffLen.toInt() ))
|
||||
sdwanJob?.join()
|
||||
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() {
|
||||
val inputStream = FileInputStream(vpnInterface?.fileDescriptor).channel
|
||||
if (inputStream==null){
|
||||
Log.i(OpenP2PService.LOG_TAG, "open FileInputStream error: ");
|
||||
if (inputStream == null) {
|
||||
Logger.i(OpenP2PService.LOG_TAG, "open FileInputStream error: ");
|
||||
return
|
||||
}
|
||||
Log.d(LOG_TAG, "read tun loop start")
|
||||
Logger.i(LOG_TAG, "read tun loop start")
|
||||
val buffer = ByteBuffer.allocate(4096)
|
||||
val byteArrayRead = ByteArray(4096)
|
||||
while (sdwanRunning) {
|
||||
buffer.clear()
|
||||
val readBytes = inputStream.read(buffer)
|
||||
if (readBytes <= 0) {
|
||||
// Log.i(OpenP2PService.LOG_TAG, "inputStream.read error: ")
|
||||
delay(1)
|
||||
continue
|
||||
withContext(Dispatchers.IO) {
|
||||
val readBytes = inputStream.read(buffer)
|
||||
if (readBytes > 0) {
|
||||
buffer.flip()
|
||||
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>()
|
||||
for (i in 0 until nodesArray.length()) {
|
||||
nodesList.add(nodesArray.getJSONObject(i))
|
||||
}
|
||||
private suspend fun runSDWAN(buf: ByteArray) {
|
||||
// val localIps = listOf(
|
||||
// "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()
|
||||
Log.i(OpenP2PService.LOG_TAG, "getAndroidNodeName:${myNodeName}");
|
||||
val nodeList = nodesList.map {
|
||||
val nodeName = it.getString("name")
|
||||
val nodeIp = it.getString("ip")
|
||||
if (nodeName==myNodeName){
|
||||
Logger.i(OpenP2PService.LOG_TAG, "runSDWAN start:${buf.decodeToString()}");
|
||||
try {
|
||||
var builder = Builder()
|
||||
val jsonObject = JSONObject(buf.decodeToString())
|
||||
// debug sdwan info
|
||||
// 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)
|
||||
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("/")
|
||||
if (parts?.size == 2) {
|
||||
val ipAddress = parts[0]
|
||||
val subnetMask = parts[1]
|
||||
builder.addRoute(ipAddress, subnetMask.toInt())
|
||||
Log.i(OpenP2PService.LOG_TAG, "sdwan addRoute:${ipAddress},${subnetMask.toInt()}");
|
||||
}
|
||||
val nodeResource = it.optString("resource", null)
|
||||
if (!nodeResource.isNullOrEmpty()) {
|
||||
// 可能是多个网段,用逗号分隔
|
||||
val resourceList = nodeResource.split(",")
|
||||
for (resource in resourceList) {
|
||||
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)
|
||||
println(network)
|
||||
Log.i(OpenP2PService.LOG_TAG, "onBind");
|
||||
builder.addDnsServer("223.5.5.5")
|
||||
builder.addDnsServer("2400:3200::1") // alicloud dns v6 & v4
|
||||
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}")
|
||||
Node(nodeName, nodeIp, nodeResource)
|
||||
}
|
||||
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() {
|
||||
Log.i(LOG_TAG, "onDestroy - Thread ID = " + Thread.currentThread().id)
|
||||
super.onDestroy()
|
||||
Logger.i(LOG_TAG, "onDestroy - Canceling service scope")
|
||||
serviceScope.cancel() // 取消所有与服务相关的协程
|
||||
}
|
||||
|
||||
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()
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
fun isConnected(): Boolean {
|
||||
if (!::network.isInitialized) return false
|
||||
return network.connect(1000)
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
running=false
|
||||
running = false
|
||||
stopSelf()
|
||||
Openp2p.stop()
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
private fun createNotificationChannel(channelId: String, channelName: String): String? {
|
||||
val chan = NotificationChannel(
|
||||
@@ -281,4 +398,55 @@ class OpenP2PService : VpnService() {
|
||||
service.createNotificationChannel(chan)
|
||||
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
|
||||
}
|
||||
@@ -1,45 +1,91 @@
|
||||
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.FileWriter
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import java.io.IOException
|
||||
import android.net.VpnService
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import cn.openp2p.ui.login.LoginActivity
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import openp2p.Openp2p
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONObject
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
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())
|
||||
val logMessage = "$timestamp: $message\n"
|
||||
// 初始化日志文件
|
||||
fun init(logDir: File, logFileName: String = "app.log") {
|
||||
if (!logDir.exists()) logDir.mkdirs()
|
||||
logFile = File(logDir, logFileName)
|
||||
|
||||
try {
|
||||
val fileWriter = FileWriter(logFile, true)
|
||||
fileWriter.append(logMessage)
|
||||
fileWriter.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
bufferedWriter = BufferedWriter(FileWriter(logFile, true))
|
||||
} catch (e: IOException) {
|
||||
Log.e(LOG_TAG, "Failed to initialize BufferedWriter: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
// 写日志(线程安全)
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,14 @@ allprojects {
|
||||
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) {
|
||||
delete rootProject.buildDir
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
@@ -16,4 +16,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
kotlin.code.style=official
|
||||
org.gradle.caching=true
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
op "openp2p/core"
|
||||
op2p "openp2p/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op.Run()
|
||||
op2p.Run()
|
||||
}
|
||||
|
||||
@@ -2,18 +2,22 @@ package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -128,19 +132,19 @@ func netInfo() *NetInfo {
|
||||
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
|
||||
r, err := client.Get("https://ifconfig.co/json")
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||
gLog.d("netInfo error:%s", err)
|
||||
continue
|
||||
}
|
||||
defer r.Body.Close()
|
||||
buf := make([]byte, 1024*64)
|
||||
n, err := r.Body.Read(buf)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||
if err != nil && err != io.EOF {
|
||||
gLog.d("error reading response body: %s", err)
|
||||
continue
|
||||
}
|
||||
rsp := NetInfo{}
|
||||
if err = json.Unmarshal(buf[:n], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong NetInfo:%s", err)
|
||||
gLog.d("wrong NetInfo:%s", err)
|
||||
continue
|
||||
}
|
||||
return &rsp
|
||||
@@ -280,3 +284,122 @@ func calculateChecksum(data []byte) uint16 {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -127,3 +127,59 @@ func TestNodeID(t *testing.T) {
|
||||
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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,10 @@ type AppConfig struct {
|
||||
}
|
||||
|
||||
const (
|
||||
PunchPriorityTCPFirst = 1
|
||||
PunchPriorityUDPDisable = 1 << 1
|
||||
PunchPriorityTCPDisable = 1 << 2
|
||||
PunchPriorityUDPFirst = 0
|
||||
PunchPriorityTCPFirst = 1
|
||||
PunchPriorityTCPOnly = 1 << 1
|
||||
PunchPriorityUDPOnly = 1 << 2
|
||||
)
|
||||
|
||||
func (c *AppConfig) ID() uint64 {
|
||||
@@ -77,14 +78,17 @@ type Config struct {
|
||||
Network NetworkConfig `json:"network"`
|
||||
Apps []*AppConfig `json:"apps"`
|
||||
|
||||
LogLevel int
|
||||
MaxLogSize int
|
||||
daemonMode bool
|
||||
mtx sync.Mutex
|
||||
sdwanMtx sync.Mutex
|
||||
sdwan SDWANInfo
|
||||
delNodes []*SDWANNode
|
||||
addNodes []*SDWANNode
|
||||
LogLevel int
|
||||
MaxLogSize int
|
||||
TLSInsecureSkipVerify bool
|
||||
Forcev6 bool
|
||||
daemonMode bool
|
||||
mtx sync.RWMutex
|
||||
fileMtx sync.Mutex
|
||||
sdwanMtx sync.Mutex
|
||||
sdwan SDWANInfo
|
||||
delNodes []*SDWANNode
|
||||
addNodes []*SDWANNode
|
||||
}
|
||||
|
||||
func (c *Config) getSDWAN() SDWANInfo {
|
||||
@@ -115,6 +119,10 @@ func (c *Config) resetSDWAN() {
|
||||
func (c *Config) setSDWAN(s SDWANInfo) {
|
||||
c.sdwanMtx.Lock()
|
||||
defer c.sdwanMtx.Unlock()
|
||||
allNew := false
|
||||
if c.sdwan.GetResourceByNodeName(c.Network.Node) != s.GetResourceByNodeName(c.Network.Node) {
|
||||
allNew = true
|
||||
}
|
||||
// get old-new
|
||||
c.delNodes = []*SDWANNode{}
|
||||
for _, oldNode := range c.sdwan.Nodes {
|
||||
@@ -125,7 +133,7 @@ func (c *Config) setSDWAN(s SDWANInfo) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if isDeleted {
|
||||
if isDeleted || allNew {
|
||||
c.delNodes = append(c.delNodes, oldNode)
|
||||
}
|
||||
}
|
||||
@@ -139,11 +147,17 @@ func (c *Config) setSDWAN(s SDWANInfo) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if isNew {
|
||||
if isNew || allNew {
|
||||
c.addNodes = append(c.addNodes, newNode)
|
||||
}
|
||||
}
|
||||
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) {
|
||||
@@ -160,26 +174,16 @@ func (c *Config) switchApp(app AppConfig, enabled int) {
|
||||
c.save()
|
||||
}
|
||||
|
||||
// TODO: move to p2pnetwork
|
||||
func (c *Config) retryApp(peerNode string) {
|
||||
GNetwork.apps.Range(func(id, i interface{}) bool {
|
||||
app := i.(*p2pApp)
|
||||
if app.config.PeerNode == peerNode {
|
||||
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
|
||||
app.config.retryNum = 0
|
||||
app.config.nextRetryTime = time.Now()
|
||||
app.retryRelayNum = 0
|
||||
app.nextRetryRelayTime = time.Now()
|
||||
app.hbMtx.Lock()
|
||||
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
|
||||
app.hbMtx.Unlock()
|
||||
app.Retry(true)
|
||||
}
|
||||
if app.config.RelayNode == peerNode {
|
||||
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
|
||||
app.retryRelayNum = 0
|
||||
app.nextRetryRelayTime = time.Now()
|
||||
app.hbMtx.Lock()
|
||||
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
|
||||
app.hbMtx.Unlock()
|
||||
app.Retry(false)
|
||||
gLog.d("retry app relay=%s", app.config.LogPeerNode())
|
||||
}
|
||||
return true
|
||||
})
|
||||
@@ -188,14 +192,7 @@ func (c *Config) retryApp(peerNode string) {
|
||||
func (c *Config) retryAllApp() {
|
||||
GNetwork.apps.Range(func(id, i interface{}) bool {
|
||||
app := i.(*p2pApp)
|
||||
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
|
||||
app.config.retryNum = 0
|
||||
app.config.nextRetryTime = time.Now()
|
||||
app.retryRelayNum = 0
|
||||
app.nextRetryRelayTime = time.Now()
|
||||
app.hbMtx.Lock()
|
||||
defer app.hbMtx.Unlock()
|
||||
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
|
||||
app.Retry(true)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -206,22 +203,22 @@ func (c *Config) retryAllMemApp() {
|
||||
if app.config.SrcPort != 0 {
|
||||
return true
|
||||
}
|
||||
gLog.Println(LvDEBUG, "retry app ", app.config.LogPeerNode())
|
||||
app.config.retryNum = 0
|
||||
app.config.nextRetryTime = time.Now()
|
||||
app.retryRelayNum = 0
|
||||
app.nextRetryRelayTime = time.Now()
|
||||
app.hbMtx.Lock()
|
||||
defer app.hbMtx.Unlock()
|
||||
app.hbTimeRelay = time.Now().Add(-TunnelHeartbeatTime * 3)
|
||||
if app.tunnelNum != int(gConf.sdwan.TunnelNum) {
|
||||
gLog.d("memapp %s tunnelNum changed from %d to %d, delete it and not retry", app.config.LogPeerNode(), app.tunnelNum, gConf.sdwan.TunnelNum)
|
||||
GNetwork.DeleteApp(app.config)
|
||||
return true
|
||||
}
|
||||
app.Retry(true)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Config) add(app AppConfig, override bool) {
|
||||
if app.AppName == "" {
|
||||
app.AppName = fmt.Sprintf("%d", app.ID())
|
||||
}
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
defer c.save()
|
||||
if override {
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if c.Apps[i].PeerNode == app.PeerNode && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort {
|
||||
@@ -231,12 +228,14 @@ func (c *Config) add(app AppConfig, override bool) {
|
||||
}
|
||||
}
|
||||
c.Apps = append(c.Apps, &app)
|
||||
if app.SrcPort != 0 {
|
||||
c.save()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) delete(app AppConfig) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
defer c.save()
|
||||
for i := 0; i < len(c.Apps); i++ {
|
||||
if (app.SrcPort != 0 && c.Apps[i].Protocol == app.Protocol && c.Apps[i].SrcPort == app.SrcPort) || // normal app
|
||||
(app.SrcPort == 0 && c.Apps[i].SrcPort == 0 && c.Apps[i].PeerNode == app.PeerNode) { // memapp
|
||||
@@ -245,37 +244,51 @@ func (c *Config) delete(app AppConfig) {
|
||||
} else {
|
||||
c.Apps = append(c.Apps[:i], c.Apps[i+1:]...)
|
||||
}
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
if app.SrcPort != 0 {
|
||||
c.save()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) save() {
|
||||
// c.mtx.Lock()
|
||||
// defer c.mtx.Unlock() // internal call
|
||||
c.fileMtx.Lock()
|
||||
defer c.fileMtx.Unlock()
|
||||
if c.Network.Token == 0 {
|
||||
gLog.e("c.Network.Token == 0 skip save")
|
||||
return
|
||||
}
|
||||
data, _ := json.MarshalIndent(c, "", " ")
|
||||
err := os.WriteFile("config.json", data, 0644)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "save config.json error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) saveCache() {
|
||||
// c.mtx.Lock()
|
||||
// defer c.mtx.Unlock() // internal call
|
||||
if c.Network.Token == 0 {
|
||||
data, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil || len(data) < 16 {
|
||||
gLog.e("MarshalIndent config.json error:%v, len=%d", err, len(data))
|
||||
return
|
||||
}
|
||||
data, _ := json.MarshalIndent(c, "", " ")
|
||||
err := os.WriteFile("config.json0", data, 0644)
|
||||
err = os.WriteFile("config.json0", data, 0644)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "save config.json0 error:", err)
|
||||
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() {
|
||||
gConf.LogLevel = int(LvINFO)
|
||||
gConf.MaxLogSize = 1024 * 1024
|
||||
@@ -286,19 +299,19 @@ func init() {
|
||||
}
|
||||
|
||||
func (c *Config) load() error {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.fileMtx.Lock()
|
||||
defer c.fileMtx.Unlock()
|
||||
data, err := os.ReadFile("config.json")
|
||||
if err != nil {
|
||||
return c.loadCache()
|
||||
return err
|
||||
}
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
err = json.Unmarshal(data, &c)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "parse config.json error:", err)
|
||||
// try cache
|
||||
return c.loadCache()
|
||||
gLog.e("parse config.json error:", err)
|
||||
return err
|
||||
}
|
||||
// load ok. cache it
|
||||
var filteredApps []*AppConfig // filter memapp
|
||||
for _, app := range c.Apps {
|
||||
if app.SrcPort != 0 {
|
||||
@@ -306,19 +319,7 @@ func (c *Config) load() error {
|
||||
}
|
||||
}
|
||||
c.Apps = filteredApps
|
||||
c.saveCache()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) loadCache() error {
|
||||
data, err := os.ReadFile("config.json0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, &c)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "parse config.json0 error:", err)
|
||||
}
|
||||
c.Network.natType = NATUnknown
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -326,7 +327,6 @@ func (c *Config) loadCache() error {
|
||||
func (c *Config) setToken(token uint64) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
defer c.save()
|
||||
if token != 0 {
|
||||
c.Network.Token = token
|
||||
}
|
||||
@@ -334,16 +334,19 @@ func (c *Config) setToken(token uint64) {
|
||||
func (c *Config) setUser(user string) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
defer c.save()
|
||||
c.Network.User = user
|
||||
}
|
||||
func (c *Config) setNode(node string) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
defer c.save()
|
||||
c.Network.Node = node
|
||||
c.Network.nodeID = NodeNameToID(c.Network.Node)
|
||||
}
|
||||
func (c *Config) setForcev6(force bool) {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
c.Forcev6 = force
|
||||
}
|
||||
func (c *Config) nodeID() uint64 {
|
||||
c.mtx.Lock()
|
||||
defer c.mtx.Unlock()
|
||||
@@ -379,22 +382,27 @@ type NetworkConfig struct {
|
||||
mac string
|
||||
os string
|
||||
publicIP string
|
||||
previousIP string // for publicIP change detect
|
||||
natType int
|
||||
hasIPv4 int
|
||||
publicIPv6 string // must lowwer-case not save json
|
||||
hasUPNPorNATPMP int
|
||||
ShareBandwidth int
|
||||
// server info
|
||||
ServerHost string
|
||||
ServerPort int
|
||||
UDPPort1 int
|
||||
UDPPort2 int
|
||||
TCPPort int
|
||||
ServerHost string
|
||||
ServerIP string
|
||||
ServerPort int
|
||||
natDetectPort1 int
|
||||
natDetectPort2 int
|
||||
PublicIPPort int // both tcp and udp
|
||||
specTunnel int
|
||||
}
|
||||
|
||||
func parseParams(subCommand string, cmd string) {
|
||||
fset := flag.NewFlagSet(subCommand, flag.ExitOnError)
|
||||
installPath := fset.String("installpath", "", "custom install path")
|
||||
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 ")
|
||||
// serverHost := flag.String("serverhost", "127.0.0.1", "server host ") // for debug
|
||||
token := fset.Uint64("token", 0, "token")
|
||||
@@ -404,7 +412,7 @@ func parseParams(subCommand string, cmd string) {
|
||||
whiteList := fset.String("whitelist", "", "whitelist for p2pApp ")
|
||||
dstPort := fset.Int("dstport", 0, "destination port ")
|
||||
srcPort := fset.Int("srcport", 0, "source port ")
|
||||
tcpPort := fset.Int("tcpport", 0, "tcp port for upnp or publicip")
|
||||
publicIPPort := fset.Int("publicipport", 0, "public ip port for upnp or publicip")
|
||||
protocol := fset.String("protocol", "tcp", "tcp or udp")
|
||||
underlayProtocol := fset.String("underlay_protocol", "quic", "quic or kcp")
|
||||
punchPriority := fset.Int("punch_priority", 0, "bitwise DisableTCP|DisableUDP|UDPFirst 0:tcp and udp both enable, tcp first")
|
||||
@@ -423,12 +431,10 @@ func parseParams(subCommand string, cmd string) {
|
||||
fset.Parse(os.Args[2:])
|
||||
}
|
||||
} else {
|
||||
gLog.Println(LvINFO, "cmd=", cmd)
|
||||
args := strings.Split(cmd, " ")
|
||||
fset.Parse(args)
|
||||
}
|
||||
|
||||
gLog.setMaxSize(int64(*maxLogSize))
|
||||
config := AppConfig{Enabled: 1}
|
||||
config.PeerNode = *peerNode
|
||||
config.DstHost = *dstIP
|
||||
@@ -440,6 +446,19 @@ func parseParams(subCommand string, cmd string) {
|
||||
config.PunchPriority = *punchPriority
|
||||
config.AppName = *appName
|
||||
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 {
|
||||
gConf.load() // load old config. otherwise will clear all apps
|
||||
}
|
||||
@@ -465,17 +484,26 @@ func parseParams(subCommand string, cmd string) {
|
||||
if f.Name == "maxlogsize" {
|
||||
gConf.MaxLogSize = *maxLogSize
|
||||
}
|
||||
if f.Name == "tcpport" {
|
||||
gConf.Network.TCPPort = *tcpPort
|
||||
if f.Name == "publicipport" {
|
||||
gConf.Network.PublicIPPort = *publicIPPort
|
||||
}
|
||||
if f.Name == "token" {
|
||||
gConf.setToken(*token)
|
||||
}
|
||||
if f.Name == "serverport" {
|
||||
gConf.Network.ServerPort = *serverPort
|
||||
}
|
||||
if f.Name == "insecure" {
|
||||
gConf.TLSInsecureSkipVerify = *insecure
|
||||
}
|
||||
})
|
||||
// set default value
|
||||
if gConf.Network.ServerHost == "" {
|
||||
gConf.Network.ServerHost = *serverHost
|
||||
}
|
||||
if gConf.Network.ServerPort == 0 {
|
||||
gConf.Network.ServerPort = *serverPort
|
||||
}
|
||||
if *node != "" {
|
||||
gConf.setNode(*node)
|
||||
} else {
|
||||
@@ -487,12 +515,12 @@ func parseParams(subCommand string, cmd string) {
|
||||
gConf.setNode(defaultNodeName())
|
||||
}
|
||||
}
|
||||
if gConf.Network.TCPPort == 0 {
|
||||
if *tcpPort == 0 {
|
||||
p := int(gConf.nodeID()%15000 + 50000)
|
||||
tcpPort = &p
|
||||
if gConf.Network.PublicIPPort == 0 {
|
||||
if *publicIPPort == 0 {
|
||||
p := int(gConf.nodeID()%8192 + 1025)
|
||||
publicIPPort = &p
|
||||
}
|
||||
gConf.Network.TCPPort = *tcpPort
|
||||
gConf.Network.PublicIPPort = *publicIPPort
|
||||
}
|
||||
if *token == 0 {
|
||||
envToken := os.Getenv("OPENP2P_TOKEN")
|
||||
@@ -502,13 +530,13 @@ func parseParams(subCommand string, cmd string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
gConf.Network.ServerPort = *serverPort
|
||||
gConf.Network.UDPPort1 = UDPPort1
|
||||
gConf.Network.UDPPort2 = UDPPort2
|
||||
|
||||
gConf.Network.natDetectPort1 = NATDetectPort1
|
||||
gConf.Network.natDetectPort2 = NATDetectPort2
|
||||
gLog.setLevel(LogLevel(gConf.LogLevel))
|
||||
gLog.setMaxSize(int64(gConf.MaxLogSize))
|
||||
if *notVerbose {
|
||||
gLog.setMode(LogFile)
|
||||
}
|
||||
// gConf.mtx.Unlock()
|
||||
gConf.save()
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/openp2p-cn/service"
|
||||
@@ -15,34 +15,32 @@ type daemon struct {
|
||||
}
|
||||
|
||||
func (d *daemon) Start(s service.Service) error {
|
||||
gLog.Println(LvINFO, "daemon start")
|
||||
gLog.i("system service start")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *daemon) Stop(s service.Service) error {
|
||||
gLog.Println(LvINFO, "service stop")
|
||||
gLog.i("system service stop")
|
||||
d.running = false
|
||||
if d.proc != nil {
|
||||
gLog.Println(LvINFO, "stop worker")
|
||||
gLog.i("stop worker")
|
||||
d.proc.Kill()
|
||||
}
|
||||
if service.Interactive() {
|
||||
gLog.Println(LvINFO, "stop daemon")
|
||||
gLog.i("stop daemon")
|
||||
os.Exit(0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *daemon) run() {
|
||||
gLog.Println(LvINFO, "daemon run start")
|
||||
defer gLog.Println(LvINFO, "daemon run end")
|
||||
gLog.close()
|
||||
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
|
||||
binPath, _ := os.Executable()
|
||||
mydir, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
gLog.Println(LvINFO, mydir)
|
||||
conf := &service.Config{
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
@@ -64,34 +62,55 @@ func (d *daemon) run() {
|
||||
args = append(args, "-nv")
|
||||
for {
|
||||
// start worker
|
||||
tmpDump := filepath.Join("log", "dump.log.tmp")
|
||||
dumpFile := filepath.Join("log", "dump.log")
|
||||
f, err := os.Create(filepath.Join(tmpDump))
|
||||
tmpDump := filepath.Join(filepath.Dir(binPath), "log", "dump.log.tmp")
|
||||
dumpFile := filepath.Join(filepath.Dir(binPath), "log", "dump.log")
|
||||
// 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 {
|
||||
gLog.Printf(LvERROR, "start worker error:%s", err)
|
||||
gLog.e("OpenFile %s error:%s", tmpDump, err)
|
||||
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}}
|
||||
lastRebootTime := time.Now()
|
||||
p, err := os.StartProcess(binPath, args, execSpec)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "start worker error:%s", err)
|
||||
gLog.e("start worker error:%s", err)
|
||||
return
|
||||
}
|
||||
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()
|
||||
time.Sleep(time.Second)
|
||||
err = os.Rename(tmpDump, dumpFile)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "rename dump error:%s", err)
|
||||
gLog.e("rename dump error:%s", err)
|
||||
}
|
||||
if !d.running {
|
||||
return
|
||||
}
|
||||
if time.Since(lastRebootTime) < time.Second*10 {
|
||||
gLog.Printf(LvERROR, "worker stop, restart it after 10s")
|
||||
gLog.e("worker stop, restart it after 10s")
|
||||
time.Sleep(time.Second * 10)
|
||||
}
|
||||
|
||||
@@ -99,13 +118,7 @@ func (d *daemon) run() {
|
||||
}
|
||||
|
||||
func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) error {
|
||||
svcConfig := &service.Config{
|
||||
Name: ProductName,
|
||||
DisplayName: ProductName,
|
||||
Description: ProductName,
|
||||
Executable: exeAbsPath,
|
||||
Arguments: args,
|
||||
}
|
||||
svcConfig := getServiceConfig(exeAbsPath, args)
|
||||
|
||||
s, e := service.New(d, svcConfig)
|
||||
if e != nil {
|
||||
@@ -118,3 +131,56 @@ func (d *daemon) Control(ctrlComm string, exeAbsPath string, args []string) erro
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,7 @@ var (
|
||||
ErrBuildTunnelBusy = errors.New("build tunnel busy")
|
||||
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")
|
||||
)
|
||||
|
||||
@@ -4,12 +4,14 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/openp2p-cn/totp"
|
||||
@@ -21,120 +23,141 @@ func handlePush(subType uint16, msg []byte) error {
|
||||
if err != nil {
|
||||
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 {
|
||||
case MsgPushConnectReq:
|
||||
err = handleConnectReq(msg)
|
||||
case MsgPushRsp:
|
||||
rsp := PushRsp{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong pushRsp:%s", err)
|
||||
gLog.e("Unmarshal pushRsp:%s", err)
|
||||
return err
|
||||
}
|
||||
if rsp.Error == 0 {
|
||||
gLog.Printf(LvDEBUG, "push ok, detail:%s", rsp.Detail)
|
||||
gLog.dev("push ok, detail:%s", rsp.Detail)
|
||||
} 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:
|
||||
req := AddRelayTunnelReq{}
|
||||
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
|
||||
}
|
||||
config := AppConfig{}
|
||||
config.PeerNode = req.RelayName
|
||||
config.peerToken = req.RelayToken
|
||||
config.relayMode = req.RelayMode
|
||||
config.PunchPriority = req.PunchPriority
|
||||
config.UnderlayProtocol = req.UnderlayProtocol
|
||||
go func(r AddRelayTunnelReq) {
|
||||
t, errDt := GNetwork.addDirectTunnel(config, 0)
|
||||
if errDt == nil {
|
||||
t, errDt := GNetwork.addDirectTunnel(config, 0, nil)
|
||||
if errDt == nil && t != nil {
|
||||
// notify peer relay ready
|
||||
msg := TunnelMsg{ID: t.id}
|
||||
GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, msg)
|
||||
appConfig := config
|
||||
appConfig.PeerNode = req.From
|
||||
} else {
|
||||
gLog.Printf(LvERROR, "addDirectTunnel error:%s", errDt)
|
||||
gLog.w("addDirectTunnel error:%s", errDt)
|
||||
GNetwork.push(r.From, MsgPushAddRelayTunnelRsp, "error") // compatible with old version client, trigger unmarshal error
|
||||
}
|
||||
}(req)
|
||||
case MsgPushServerSideSaveMemApp:
|
||||
req := ServerSideSaveMemApp{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
gLog.e("Unmarshal %v:%s", reflect.TypeOf(req), 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
|
||||
i, ok := GNetwork.allTunnels.Load(req.TunnelID)
|
||||
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.
|
||||
if !ok {
|
||||
gLog.Println(LvERROR, "handle MsgPushServerSideSaveMemApp error:", ErrMemAppTunnelNotFound)
|
||||
gLog.e("handle MsgPushServerSideSaveMemApp error:%s", ErrMemAppTunnelNotFound)
|
||||
return ErrMemAppTunnelNotFound
|
||||
}
|
||||
}
|
||||
existTunnel = i.(*P2PTunnel)
|
||||
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 {
|
||||
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.id = req.AppID
|
||||
app.setRelayTunnelID(req.RelayTunnelID)
|
||||
app.relayMode = req.RelayMode
|
||||
app.hbTimeRelay = time.Now()
|
||||
if req.RelayTunnelID == 0 {
|
||||
app.setDirectTunnel(existTunnel)
|
||||
} else {
|
||||
app.setRelayTunnel(existTunnel)
|
||||
app.key = req.AppKey
|
||||
app.PreCalcKeyBytes()
|
||||
app.relayMode[req.RelayIndex] = req.RelayMode
|
||||
app.hbTime[req.RelayIndex] = time.Now()
|
||||
app.SetTunnel(existTunnel, int(req.RelayIndex))
|
||||
if req.RelayTunnelID != 0 {
|
||||
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 {
|
||||
appConfig := existTunnel.config
|
||||
appConfig.SrcPort = 0
|
||||
appConfig.SrcPort = int(req.SrcPort)
|
||||
appConfig.Protocol = ""
|
||||
appConfig.AppName = fmt.Sprintf("%d", peerID)
|
||||
appConfig.PeerNode = req.From
|
||||
app := p2pApp{
|
||||
id: req.AppID,
|
||||
config: appConfig,
|
||||
relayMode: req.RelayMode,
|
||||
running: true,
|
||||
hbTimeRelay: time.Now(),
|
||||
app = &p2pApp{
|
||||
id: req.AppID,
|
||||
config: appConfig,
|
||||
running: true,
|
||||
// asyncWriteChan: make(chan []byte, WriteDataChanSize),
|
||||
key: req.AppKey,
|
||||
}
|
||||
if req.RelayTunnelID == 0 {
|
||||
app.setDirectTunnel(existTunnel)
|
||||
} else {
|
||||
app.setRelayTunnel(existTunnel)
|
||||
app.setRelayTunnelID(req.RelayTunnelID)
|
||||
app.PreCalcKeyBytes()
|
||||
tunnelNum := 2
|
||||
if req.TunnelNum > uint32(tunnelNum) {
|
||||
tunnelNum = int(req.TunnelNum)
|
||||
}
|
||||
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 {
|
||||
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
|
||||
case MsgPushAPPKey:
|
||||
req := APPKeySync{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
return err
|
||||
}
|
||||
SaveKey(req.AppID, req.AppKey)
|
||||
case MsgPushUpdate:
|
||||
gLog.Println(LvINFO, "MsgPushUpdate")
|
||||
gLog.i("MsgPushUpdate")
|
||||
err := update(gConf.Network.ServerHost, gConf.Network.ServerPort)
|
||||
if err == nil {
|
||||
os.Exit(0)
|
||||
if !isAndroid() {
|
||||
os.Exit(9) // 9 tell daemon this exit because of update
|
||||
}
|
||||
|
||||
}
|
||||
return err
|
||||
case MsgPushRestart:
|
||||
gLog.Println(LvINFO, "MsgPushRestart")
|
||||
os.Exit(0)
|
||||
gLog.i("MsgPushRestart")
|
||||
if !isAndroid() {
|
||||
os.Exit(0)
|
||||
}
|
||||
return err
|
||||
case MsgPushReportApps:
|
||||
err = handleReportApps()
|
||||
@@ -144,43 +167,62 @@ func handlePush(subType uint16, msg []byte) error {
|
||||
err = handleLog(msg)
|
||||
case MsgPushReportGoroutine:
|
||||
err = handleReportGoroutine()
|
||||
case MsgPushReportHeap:
|
||||
err = handleReportHeap()
|
||||
case MsgPushCheckRemoteService:
|
||||
err = handleCheckRemoteService(msg)
|
||||
case MsgPushEditApp:
|
||||
err = handleEditApp(msg)
|
||||
case MsgPushEditNode:
|
||||
gLog.Println(LvINFO, "MsgPushEditNode")
|
||||
gLog.i("MsgPushEditNode")
|
||||
req := EditNode{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
gConf.setNode(req.NewName)
|
||||
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)
|
||||
case MsgPushSwitchApp:
|
||||
gLog.Println(LvINFO, "MsgPushSwitchApp")
|
||||
gLog.i("MsgPushSwitchApp")
|
||||
app := AppInfo{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &app); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(app), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
config := AppConfig{Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
|
||||
gLog.Println(LvINFO, app.AppName, " switch to ", app.Enabled)
|
||||
config := AppConfig{PeerNode: app.PeerNode, Enabled: app.Enabled, SrcPort: app.SrcPort, Protocol: app.Protocol}
|
||||
gLog.i("%s switch to %d", app.AppName, app.Enabled)
|
||||
gConf.switchApp(config, app.Enabled)
|
||||
if app.Enabled == 0 {
|
||||
// disable APP
|
||||
GNetwork.DeleteApp(config)
|
||||
}
|
||||
case MsgPushDstNodeOnline:
|
||||
gLog.Println(LvINFO, "MsgPushDstNodeOnline")
|
||||
req := PushDstNodeOnline{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
gLog.Println(LvINFO, "retry peerNode ", req.Node)
|
||||
gLog.i("%s online, 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:
|
||||
i, ok := GNetwork.msgMap.Load(pushHead.From)
|
||||
if !ok {
|
||||
@@ -192,11 +234,50 @@ func handlePush(subType uint16, msg []byte) error {
|
||||
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) {
|
||||
gLog.Println(LvINFO, "MsgPushEditApp")
|
||||
gLog.i("MsgPushEditApp")
|
||||
newApp := AppInfo{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &newApp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(newApp), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
oldConf := AppConfig{Enabled: 1}
|
||||
@@ -212,13 +293,16 @@ func handleEditApp(msg []byte) (err error) {
|
||||
gConf.delete(oldConf)
|
||||
}
|
||||
|
||||
// AddApp
|
||||
newConf := oldConf
|
||||
newConf.Protocol = newApp.Protocol
|
||||
newConf.SrcPort = newApp.SrcPort
|
||||
newConf.RelayNode = newApp.SpecRelayNode
|
||||
newConf.PunchPriority = newApp.PunchPriority
|
||||
gConf.add(newConf, false)
|
||||
if newApp.SrcPort != 0 { // delete app
|
||||
// AddApp
|
||||
newConf := oldConf
|
||||
newConf.Protocol = newApp.Protocol
|
||||
newConf.SrcPort = newApp.SrcPort
|
||||
newConf.RelayNode = newApp.SpecRelayNode
|
||||
newConf.PunchPriority = newApp.PunchPriority
|
||||
gConf.add(newConf, false)
|
||||
}
|
||||
|
||||
if newApp.Protocol0 != "" && newApp.SrcPort0 != 0 { // not edit
|
||||
GNetwork.DeleteApp(oldConf) // DeleteApp may cost some times, execute at the end
|
||||
}
|
||||
@@ -228,13 +312,12 @@ func handleEditApp(msg []byte) (err error) {
|
||||
func handleConnectReq(msg []byte) (err error) {
|
||||
req := PushConnectReq{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize+PushHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
gLog.e("Unmarshal %v:%s", reflect.TypeOf(req), err)
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "%s is connecting...", req.From)
|
||||
gLog.Println(LvDEBUG, "push connect response to ", req.From)
|
||||
gLog.d("%s is connecting... push connect response", req.From)
|
||||
if compareVersion(req.Version, LeastSupportVersion) < 0 {
|
||||
gLog.Println(LvERROR, ErrVersionNotCompatible.Error(), ":", req.From)
|
||||
gLog.e("%s:%s", ErrVersionNotCompatible.Error(), req.From)
|
||||
rsp := PushConnectRsp{
|
||||
Error: 10,
|
||||
Detail: ErrVersionNotCompatible.Error(),
|
||||
@@ -247,7 +330,7 @@ func handleConnectReq(msg []byte) (err error) {
|
||||
// verify totp token or token
|
||||
t := totp.TOTP{Step: totp.RelayTOTPStep}
|
||||
if t.Verify(req.Token, gConf.Network.Token, time.Now().Unix()-GNetwork.dt/int64(time.Second)) { // localTs may behind, auto adjust ts
|
||||
gLog.Printf(LvINFO, "Access Granted")
|
||||
gLog.d("handleConnectReq Access Granted")
|
||||
config := AppConfig{}
|
||||
config.peerNatType = req.NatType
|
||||
config.peerConeNatPort = req.ConeNatPort
|
||||
@@ -263,16 +346,16 @@ func handleConnectReq(msg []byte) (err error) {
|
||||
config.UnderlayProtocol = req.UnderlayProtocol
|
||||
// share relay node will limit bandwidth
|
||||
if req.Token != gConf.Network.Token {
|
||||
gLog.Printf(LvINFO, "set share bandwidth %d mbps", gConf.Network.ShareBandwidth)
|
||||
gLog.i("set share bandwidth %d mbps", gConf.Network.ShareBandwidth)
|
||||
config.shareBandwidth = gConf.Network.ShareBandwidth
|
||||
}
|
||||
// go GNetwork.AddTunnel(config, req.ID)
|
||||
go func() {
|
||||
GNetwork.addDirectTunnel(config, req.ID)
|
||||
GNetwork.addDirectTunnel(config, req.ID, nil)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
gLog.Println(LvERROR, "Access Denied:", req.From)
|
||||
gLog.e("handleConnectReq Access Denied:%s", req.From)
|
||||
rsp := PushConnectRsp{
|
||||
Error: 1,
|
||||
Detail: fmt.Sprintf("connect to %s error: Access Denied", gConf.Network.Node),
|
||||
@@ -283,10 +366,10 @@ func handleConnectReq(msg []byte) (err error) {
|
||||
}
|
||||
|
||||
func handleReportApps() (err error) {
|
||||
gLog.Println(LvINFO, "MsgPushReportApps")
|
||||
gLog.i("MsgPushReportApps")
|
||||
req := ReportApps{}
|
||||
gConf.mtx.Lock()
|
||||
defer gConf.mtx.Unlock()
|
||||
gConf.mtx.RLock()
|
||||
defer gConf.mtx.RUnlock()
|
||||
|
||||
for _, config := range gConf.Apps {
|
||||
appActive := 0
|
||||
@@ -296,28 +379,24 @@ func handleReportApps() (err error) {
|
||||
linkMode := LinkModeUDPPunch
|
||||
var connectTime string
|
||||
var retryTime string
|
||||
var app *p2pApp
|
||||
i, ok := GNetwork.apps.Load(config.ID())
|
||||
if ok {
|
||||
app = i.(*p2pApp)
|
||||
if app.isActive() {
|
||||
app := GNetwork.findApp(config)
|
||||
if app != nil {
|
||||
|
||||
if app.IsActive() {
|
||||
appActive = 1
|
||||
}
|
||||
if app.config.SrcPort == 0 { // memapp
|
||||
continue
|
||||
}
|
||||
specRelayNode = app.config.RelayNode
|
||||
if !app.isDirect() { // TODO: should always report relay node for app edit
|
||||
relayNode = app.relayNode
|
||||
relayMode = app.relayMode
|
||||
t, tidx := app.AvailableTunnel()
|
||||
if tidx != 0 { // TODO: should always report relay node for app edit
|
||||
relayNode = app.relayNode[tidx]
|
||||
relayMode = app.relayMode[tidx]
|
||||
}
|
||||
|
||||
if app.Tunnel() != nil {
|
||||
linkMode = app.Tunnel().linkModeWeb
|
||||
if t != nil {
|
||||
linkMode = t.linkModeWeb
|
||||
}
|
||||
retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700")
|
||||
connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700")
|
||||
|
||||
}
|
||||
appInfo := AppInfo{
|
||||
AppName: config.AppName,
|
||||
@@ -348,10 +427,8 @@ func handleReportApps() (err error) {
|
||||
}
|
||||
|
||||
func handleReportMemApps() (err error) {
|
||||
gLog.Println(LvINFO, "handleReportMemApps")
|
||||
gLog.i("handleReportMemApps")
|
||||
req := ReportApps{}
|
||||
gConf.mtx.Lock()
|
||||
defer gConf.mtx.Unlock()
|
||||
GNetwork.sdwan.sysRoute.Range(func(key, value interface{}) bool {
|
||||
node := value.(*sdwanNode)
|
||||
appActive := 0
|
||||
@@ -361,13 +438,16 @@ func handleReportMemApps() (err error) {
|
||||
|
||||
i, ok := GNetwork.apps.Load(node.id)
|
||||
var app *p2pApp
|
||||
var t *P2PTunnel
|
||||
var tidx int
|
||||
if ok {
|
||||
app = i.(*p2pApp)
|
||||
if app.isActive() {
|
||||
t, tidx = app.AvailableTunnel()
|
||||
if app.IsActive() {
|
||||
appActive = 1
|
||||
}
|
||||
if !app.isDirect() {
|
||||
relayMode = app.relayMode
|
||||
if tidx != 0 {
|
||||
relayMode = app.relayMode[tidx]
|
||||
}
|
||||
retryTime = app.RetryTime().Local().Format("2006-01-02T15:04:05-0700")
|
||||
connectTime = app.ConnectTime().Local().Format("2006-01-02T15:04:05-0700")
|
||||
@@ -384,12 +464,13 @@ func handleReportMemApps() (err error) {
|
||||
appInfo.Protocol = app.config.Protocol
|
||||
appInfo.Whitelist = app.config.Whitelist
|
||||
appInfo.SrcPort = app.config.SrcPort
|
||||
if !app.isDirect() {
|
||||
appInfo.RelayNode = app.relayNode
|
||||
|
||||
if tidx != 0 {
|
||||
appInfo.RelayNode = app.relayNode[tidx]
|
||||
}
|
||||
|
||||
if app.Tunnel() != nil {
|
||||
appInfo.LinkMode = app.Tunnel().linkModeWeb
|
||||
if t != nil {
|
||||
appInfo.LinkMode = t.linkModeWeb
|
||||
}
|
||||
appInfo.DstHost = app.config.DstHost
|
||||
appInfo.DstPort = app.config.DstPort
|
||||
@@ -402,17 +483,19 @@ func handleReportMemApps() (err error) {
|
||||
req.Apps = append(req.Apps, appInfo)
|
||||
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)
|
||||
}
|
||||
|
||||
func handleLog(msg []byte) (err error) {
|
||||
gLog.Println(LvDEBUG, "MsgPushReportLog")
|
||||
gLog.d("MsgPushReportLog")
|
||||
const defaultLen = 1024 * 128
|
||||
const maxLen = 1024 * 1024
|
||||
req := ReportLogReq{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
if req.FileName == "" {
|
||||
@@ -420,9 +503,12 @@ func handleLog(msg []byte) (err error) {
|
||||
} else {
|
||||
req.FileName = sanitizeFileName(req.FileName)
|
||||
}
|
||||
if req.IsSetLogLevel == 1 {
|
||||
gLog.setLevel(LogLevel(req.LogLevel))
|
||||
}
|
||||
f, err := os.Open(filepath.Join("log", req.FileName))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "read log file error:", err)
|
||||
gLog.e("read log file error:%s", err)
|
||||
return err
|
||||
}
|
||||
fi, err := f.Stat()
|
||||
@@ -445,7 +531,7 @@ func handleLog(msg []byte) (err error) {
|
||||
readLength, err := f.Read(buff)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "read log content error:", err)
|
||||
gLog.e("read log content error:%s", err)
|
||||
return err
|
||||
}
|
||||
rsp := ReportLogRsp{}
|
||||
@@ -457,17 +543,27 @@ func handleLog(msg []byte) (err error) {
|
||||
}
|
||||
|
||||
func handleReportGoroutine() (err error) {
|
||||
gLog.Println(LvDEBUG, "handleReportGoroutine")
|
||||
gLog.d("handleReportGoroutine")
|
||||
buf := make([]byte, 1024*128)
|
||||
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.Println(LvDEBUG, "handleCheckRemoteService")
|
||||
gLog.d("handleCheckRemoteService")
|
||||
req := CheckRemoteService{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
gLog.e("Unmarshal %v:%s %s", reflect.TypeOf(req), err, string(msg[openP2PHeaderSize:]))
|
||||
return err
|
||||
}
|
||||
rsp := PushRsp{Error: 0}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
gLog.Printf(LvDEBUG, "handshakeC2C %s:%d:%d to %s:%d", gConf.Network.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
|
||||
defer gLog.Printf(LvDEBUG, "handshakeC2C end")
|
||||
gLog.d("handshakeC2C %s:%d:%d to %s:%d", gConf.Network.Node, t.coneLocalPort, t.coneNatPort, t.config.peerIP, t.config.peerConeNatPort)
|
||||
defer gLog.d("handshakeC2C end")
|
||||
conn, err := net.ListenUDP("udp", t.localHoleAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -20,12 +20,12 @@ func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
defer conn.Close()
|
||||
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshake error:", err)
|
||||
gLog.d("handshakeC2C write MsgPunchHandshake error:%s", err)
|
||||
return err
|
||||
}
|
||||
ra, head, buff, _, err := UDPRead(conn, HandshakeTimeout)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2C read MsgPunchHandshake error:", err)
|
||||
gLog.d("handshakeC2C read MsgPunchHandshake error:%s", err)
|
||||
return err
|
||||
}
|
||||
t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", ra.String())
|
||||
@@ -39,29 +39,29 @@ func handshakeC2C(t *P2PTunnel) (err error) {
|
||||
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.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
_, head, _, _, err = UDPRead(conn, HandshakeTimeout)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
|
||||
gLog.d("handshakeC2C write MsgPunchHandshakeAck error:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
|
||||
gLog.Printf(LvDEBUG, "read %d handshake ack ", t.id)
|
||||
gLog.d("read tunnelID:%d handshake ack ", t.id)
|
||||
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2C write MsgPunchHandshakeAck error", err)
|
||||
gLog.d("handshakeC2C write MsgPunchHandshakeAck error:%s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
gLog.Printf(LvINFO, "handshakeC2C ok")
|
||||
gLog.i("handshakeC2C ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
func handshakeC2S(t *P2PTunnel) error {
|
||||
gLog.Printf(LvDEBUG, "handshakeC2S start")
|
||||
defer gLog.Printf(LvDEBUG, "handshakeC2S end")
|
||||
gLog.d("tid:%d handshakeC2S start", t.id)
|
||||
defer gLog.d("tid:%d handshakeC2S end", t.id)
|
||||
if !buildTunnelMtx.TryLock() {
|
||||
// time.Sleep(time.Second * 3)
|
||||
return ErrBuildTunnelBusy
|
||||
@@ -77,7 +77,7 @@ func handshakeC2S(t *P2PTunnel) error {
|
||||
defer conn.Close()
|
||||
|
||||
go func() error {
|
||||
gLog.Printf(LvDEBUG, "send symmetric handshake to %s from %d:%d start", t.config.peerIP, t.coneLocalPort, t.coneNatPort)
|
||||
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++ {
|
||||
// time.Sleep(SymmetricHandshakeInterval)
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", t.config.peerIP, randPorts[i]+2))
|
||||
@@ -86,29 +86,29 @@ func handshakeC2S(t *P2PTunnel) error {
|
||||
}
|
||||
_, err = UDPWrite(conn, dst, MsgP2P, MsgPunchHandshake, P2PHandshakeReq{ID: t.id})
|
||||
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
|
||||
}
|
||||
}
|
||||
gLog.Println(LvDEBUG, "send symmetric handshake end")
|
||||
gLog.d("tid:%d send symmetric handshake end", t.id)
|
||||
return nil
|
||||
}()
|
||||
err = conn.SetReadDeadline(time.Now().Add(HandshakeTimeout))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "SymmetricHandshakeAckTimeout SetReadDeadline error")
|
||||
gLog.d("tid:%d SymmetricHandshakeAckTimeout SetReadDeadline error", t.id)
|
||||
return err
|
||||
}
|
||||
// read response of the punching hole ok port
|
||||
buff := make([]byte, 1024)
|
||||
_, dst, err := conn.ReadFrom(buff)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handshakeC2S wait timeout")
|
||||
gLog.d("tid:%d handshakeC2S wait timeout", t.id)
|
||||
return err
|
||||
}
|
||||
head := &openP2PHeader{}
|
||||
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "parse p2pheader error:", err)
|
||||
gLog.e("tid:%d parse p2pheader error:%s", t.id, err)
|
||||
return err
|
||||
}
|
||||
t.remoteHoleAddr, _ = net.ResolveUDPAddr("udp", dst.String())
|
||||
@@ -122,12 +122,12 @@ func handshakeC2S(t *P2PTunnel) error {
|
||||
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.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
for {
|
||||
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeC2S handshake error")
|
||||
gLog.d("tid:%d handshakeC2S handshake error", t.id)
|
||||
return err
|
||||
}
|
||||
var tunnelID uint64
|
||||
@@ -146,35 +146,35 @@ func handshakeC2S(t *P2PTunnel) error {
|
||||
}
|
||||
}
|
||||
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck {
|
||||
gLog.Printf(LvDEBUG, "handshakeC2S read %d handshake ack %s", t.id, t.remoteHoleAddr.String())
|
||||
gLog.d("tid:%d handshakeC2S read handshake ack %s", t.id, t.remoteHoleAddr.String())
|
||||
_, err = UDPWrite(conn, t.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
return err
|
||||
} 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
|
||||
}
|
||||
|
||||
func handshakeS2C(t *P2PTunnel) error {
|
||||
gLog.Printf(LvDEBUG, "handshakeS2C start")
|
||||
defer gLog.Printf(LvDEBUG, "handshakeS2C end")
|
||||
gLog.d("tid:%d handshakeS2C start", t.id)
|
||||
defer gLog.d("tid:%d handshakeS2C end", t.id)
|
||||
if !buildTunnelMtx.TryLock() {
|
||||
// time.Sleep(time.Second * 3)
|
||||
return ErrBuildTunnelBusy
|
||||
}
|
||||
defer buildTunnelMtx.Unlock()
|
||||
startTime := time.Now()
|
||||
gotCh := make(chan *net.UDPAddr, 5)
|
||||
gotCh := make(chan *net.UDPAddr, 50)
|
||||
// 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
|
||||
for i := 0; i < SymmetricHandshakeNum; i++ {
|
||||
// time.Sleep(SymmetricHandshakeInterval)
|
||||
go func(t *P2PTunnel) error {
|
||||
conn, err := net.ListenUDP("udp", nil) // TODO: system allocated port really random?
|
||||
if err != nil {
|
||||
gLog.Printf(LvDEBUG, "listen error")
|
||||
gLog.d("tid:%d listen error", t.id)
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
@@ -198,13 +198,13 @@ func handshakeS2C(t *P2PTunnel) error {
|
||||
}
|
||||
|
||||
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.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
// may read several MsgPunchHandshake
|
||||
for {
|
||||
_, head, buff, _, err = UDPRead(conn, HandshakeTimeout)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "handshakeS2C handshake error")
|
||||
gLog.d("tid:%d handshakeS2C handshake error", t.id)
|
||||
return err
|
||||
}
|
||||
if len(buff) > openP2PHeaderSize {
|
||||
@@ -218,36 +218,35 @@ func handshakeS2C(t *P2PTunnel) error {
|
||||
if head.MainType == MsgP2P && head.SubType == MsgPunchHandshakeAck && tunnelID == t.id {
|
||||
break
|
||||
} 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 {
|
||||
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.remoteHoleAddr, MsgP2P, MsgPunchHandshakeAck, P2PHandshakeReq{ID: t.id})
|
||||
gotIt = true
|
||||
la, _ := net.ResolveUDPAddr("udp", conn.LocalAddr().String())
|
||||
gotCh <- la
|
||||
return nil
|
||||
} else {
|
||||
gLog.Println(LvDEBUG, "handshakeS2C read msg but not MsgPunchHandshakeAck")
|
||||
gLog.d("tid:%d handshakeS2C read msg but not MsgPunchHandshakeAck", t.id)
|
||||
}
|
||||
return nil
|
||||
}(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
|
||||
gLog.Println(LvDEBUG, "handshakeS2C ready, notify peer connect")
|
||||
gLog.d("tid:%d handshakeS2C ready, notify peer connect", t.id)
|
||||
GNetwork.push(t.config.PeerNode, MsgPushHandshakeStart, TunnelMsg{ID: t.id})
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(HandshakeTimeout):
|
||||
return fmt.Errorf("wait handshake timeout")
|
||||
return fmt.Errorf("tid:%d wait handshake timeout", t.id)
|
||||
case la := <-gotCh:
|
||||
t.localHoleAddr = la
|
||||
gLog.Println(LvDEBUG, "symmetric handshake ok", la)
|
||||
gLog.Printf(LvINFO, "handshakeS2C ok. cost %dms", time.Since(startTime)/time.Millisecond)
|
||||
gLog.i("tid:%d handshakeS2C ok. cost %dms", t.id, time.Since(startTime)/time.Millisecond)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,26 +11,14 @@ import (
|
||||
)
|
||||
|
||||
func install() {
|
||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]")
|
||||
gLog.Println(LvINFO, "install start")
|
||||
defer gLog.Println(LvINFO, "install end")
|
||||
// auto uninstall
|
||||
err := os.MkdirAll(defaultInstallPath, 0775)
|
||||
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "MkdirAll %s error:%s", defaultInstallPath, err)
|
||||
return
|
||||
}
|
||||
err = os.Chdir(defaultInstallPath)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "cd error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
uninstall()
|
||||
// save config file
|
||||
gLog.i("openp2p start. version: %s", OpenP2PVersion)
|
||||
gLog.i("Contact: QQ group 16947733, Email [email protected]")
|
||||
gLog.i("install start")
|
||||
defer gLog.i("install end")
|
||||
parseParams("install", "")
|
||||
// auto uninstall
|
||||
uninstall(false)
|
||||
gLog.i("install path: %s", defaultInstallPath)
|
||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
d := daemon{}
|
||||
// copy files
|
||||
@@ -38,38 +26,42 @@ func install() {
|
||||
binPath, _ := os.Executable()
|
||||
src, errFiles := os.Open(binPath) // can not use args[0], on Windows call openp2p is ok(=openp2p.exe)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", os.Args[0], errFiles)
|
||||
gLog.e("os.Open %s error:%s", os.Args[0], errFiles)
|
||||
return
|
||||
}
|
||||
|
||||
dst, errFiles := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0775)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "os.OpenFile %s error:%s", targetPath, errFiles)
|
||||
return
|
||||
time.Sleep(time.Second * 5) // maybe windows defender occupied the file, retry
|
||||
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)
|
||||
if errFiles != nil {
|
||||
gLog.Printf(LvERROR, "io.Copy error:%s", errFiles)
|
||||
gLog.e("io.Copy error:%s", errFiles)
|
||||
return
|
||||
}
|
||||
src.Close()
|
||||
dst.Close()
|
||||
|
||||
// install system service
|
||||
gLog.Println(LvINFO, "targetPath:", targetPath)
|
||||
err = d.Control("install", targetPath, []string{"-d"})
|
||||
err := d.Control("install", targetPath, []string{"-d"})
|
||||
if err == nil {
|
||||
gLog.Println(LvINFO, "install system service ok.")
|
||||
gLog.i("install system service ok.")
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
err = d.Control("start", targetPath, []string{"-d"})
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "start openp2p service error:", err)
|
||||
gLog.e("start openp2p service error:%s", err)
|
||||
} 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() {
|
||||
@@ -79,7 +71,7 @@ func installByFilename() {
|
||||
}
|
||||
serverHost := params[1]
|
||||
token := params[2]
|
||||
gLog.Println(LvINFO, "install start")
|
||||
gLog.i("install start")
|
||||
targetPath := os.Args[0]
|
||||
args := []string{"install"}
|
||||
args = append(args, "-serverhost")
|
||||
@@ -94,31 +86,38 @@ func installByFilename() {
|
||||
cmd.Env = env
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "install by filename, start process error:", err)
|
||||
gLog.e("install by filename, start process error:%s", err)
|
||||
return
|
||||
}
|
||||
gLog.Println(LvINFO, "install end")
|
||||
gLog.Println(LvINFO, "Visit WebUI on https://console.openp2p.cn")
|
||||
gLog.i("install end")
|
||||
gLog.i("Visit WebUI on https://console.openp2p.cn")
|
||||
fmt.Println("Press the Any Key to exit")
|
||||
fmt.Scanln()
|
||||
os.Exit(0)
|
||||
}
|
||||
func uninstall() {
|
||||
gLog.Println(LvINFO, "uninstall start")
|
||||
defer gLog.Println(LvINFO, "uninstall end")
|
||||
|
||||
func uninstall(rmFiles bool) {
|
||||
gLog.i("uninstall start")
|
||||
defer gLog.i("uninstall end")
|
||||
d := daemon{}
|
||||
err := d.Control("stop", "", nil)
|
||||
if err != nil { // service maybe not install
|
||||
return
|
||||
gLog.d("stop service error:%s", err)
|
||||
}
|
||||
err = d.Control("uninstall", "", nil)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "uninstall system service error:", err)
|
||||
gLog.d("uninstall system service error:%s", err)
|
||||
} else {
|
||||
gLog.Println(LvINFO, "uninstall system service ok.")
|
||||
gLog.i("uninstall system service ok.")
|
||||
}
|
||||
time.Sleep(time.Second * 3)
|
||||
binPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||
os.Remove(binPath + "0")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,38 +3,33 @@ package openp2p
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
type LogLevel int32
|
||||
|
||||
var gLog *logger
|
||||
|
||||
const (
|
||||
LvDev LogLevel = -1
|
||||
LvDEBUG LogLevel = iota
|
||||
LvINFO
|
||||
LvWARN
|
||||
LvERROR
|
||||
LvDEBUG LogLevel = 0
|
||||
LvINFO LogLevel = 1
|
||||
LvWARN LogLevel = 2
|
||||
LvERROR LogLevel = 3
|
||||
)
|
||||
|
||||
var (
|
||||
logFileNames map[LogLevel]string
|
||||
loglevel map[LogLevel]string
|
||||
)
|
||||
|
||||
func init() {
|
||||
logFileNames = make(map[LogLevel]string)
|
||||
loglevel = make(map[LogLevel]string)
|
||||
logFileNames[0] = ".log"
|
||||
loglevel[LvDEBUG] = "DEBUG"
|
||||
loglevel[LvINFO] = "INFO"
|
||||
loglevel[LvWARN] = "WARN"
|
||||
loglevel[LvERROR] = "ERROR"
|
||||
loglevel[LvDev] = "Dev"
|
||||
const logFileNames string = ".log"
|
||||
|
||||
var loglevel = map[LogLevel]string{
|
||||
LvDEBUG: "DEBUG",
|
||||
LvINFO: "INFO",
|
||||
LvWARN: "WARN",
|
||||
LvERROR: "ERROR",
|
||||
LvDev: "Dev",
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -43,62 +38,54 @@ const (
|
||||
)
|
||||
|
||||
type logger struct {
|
||||
loggers map[LogLevel]*log.Logger
|
||||
files map[LogLevel]*os.File
|
||||
level LogLevel
|
||||
logDir string
|
||||
mtx *sync.Mutex
|
||||
lineEnding string
|
||||
pid int
|
||||
maxLogSize int64
|
||||
mode int
|
||||
stdLogger *log.Logger
|
||||
logger *log.Logger
|
||||
files *os.File
|
||||
level atomic.Int32
|
||||
logDir string
|
||||
mtx sync.Mutex
|
||||
lineEnding string
|
||||
pid int
|
||||
maxLogSize atomic.Int64
|
||||
mode int
|
||||
stdLogger *log.Logger
|
||||
checkFileRunning bool
|
||||
}
|
||||
|
||||
func NewLogger(path string, filePrefix string, level LogLevel, maxLogSize int64, mode int) *logger {
|
||||
loggers := make(map[LogLevel]*log.Logger)
|
||||
logfiles := make(map[LogLevel]*os.File)
|
||||
var (
|
||||
logdir string
|
||||
)
|
||||
if path == "" {
|
||||
logdir = "log/"
|
||||
} else {
|
||||
logdir = path + "/log/"
|
||||
logdir := filepath.Join(path, "log")
|
||||
if err := os.MkdirAll(logdir, 0755); err != nil && mode&LogFile != 0 {
|
||||
return nil
|
||||
}
|
||||
os.MkdirAll(logdir, 0777)
|
||||
for lv := range logFileNames {
|
||||
logFilePath := logdir + filePrefix + logFileNames[lv]
|
||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Chmod(logFilePath, 0644)
|
||||
logfiles[lv] = f
|
||||
loggers[lv] = log.New(f, "", log.LstdFlags|log.Lmicroseconds)
|
||||
logFilePath := filepath.Join(logdir, filePrefix+logFileNames)
|
||||
f, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if err != nil && mode&LogFile != 0 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var le string
|
||||
stdLog := log.New(f, "", log.LstdFlags|log.Lmicroseconds)
|
||||
le := "\n"
|
||||
if runtime.GOOS == "windows" {
|
||||
le = "\r\n"
|
||||
} else {
|
||||
le = "\n"
|
||||
}
|
||||
pLog := &logger{loggers, logfiles, level, logdir, &sync.Mutex{}, le, os.Getpid(), maxLogSize, mode, log.New(os.Stdout, "", 0)}
|
||||
pLog := &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)
|
||||
go pLog.checkFile()
|
||||
return pLog
|
||||
}
|
||||
|
||||
func (l *logger) setLevel(level LogLevel) {
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
l.level = level
|
||||
l.level.Store(int32(level))
|
||||
}
|
||||
|
||||
func (l *logger) setMaxSize(size int64) {
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
l.maxLogSize = size
|
||||
l.maxLogSize.Store(size)
|
||||
}
|
||||
|
||||
func (l *logger) setMode(mode int) {
|
||||
@@ -107,49 +94,61 @@ func (l *logger) setMode(mode int) {
|
||||
l.mode = mode
|
||||
}
|
||||
|
||||
func (l *logger) close() {
|
||||
l.checkFileRunning = false
|
||||
l.files.Close()
|
||||
}
|
||||
|
||||
func (l *logger) checkFile() {
|
||||
if l.maxLogSize <= 0 {
|
||||
if l.maxLogSize.Load() <= 0 {
|
||||
return
|
||||
}
|
||||
l.checkFileRunning = true
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
for {
|
||||
for l.checkFileRunning {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
l.mtx.Lock()
|
||||
for lv, logFile := range l.files {
|
||||
f, e := logFile.Stat()
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
if f.Size() <= l.maxLogSize {
|
||||
continue
|
||||
}
|
||||
logFile.Close()
|
||||
fname := f.Name()
|
||||
backupPath := l.logDir + fname + ".0"
|
||||
os.Remove(backupPath)
|
||||
os.Rename(l.logDir+fname, backupPath)
|
||||
newFile, e := os.OpenFile(l.logDir+fname, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||
if e == nil {
|
||||
l.loggers[lv].SetOutput(newFile)
|
||||
l.files[lv] = newFile
|
||||
}
|
||||
f, e := l.files.Stat()
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
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{}) {
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
if level < l.level {
|
||||
if level < LogLevel(l.level.Load()) {
|
||||
return
|
||||
}
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
|
||||
pidAndLevel := []interface{}{l.pid, loglevel[level]}
|
||||
params = append(pidAndLevel, params...)
|
||||
if l.mode&LogFile != 0 {
|
||||
l.loggers[0].Printf("%d %s "+format+l.lineEnding, params...)
|
||||
l.logger.Printf("%d %s "+format+l.lineEnding, params...)
|
||||
}
|
||||
if l.mode&LogConsole != 0 {
|
||||
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{}) {
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
if level < l.level {
|
||||
if level < LogLevel(l.level.Load()) {
|
||||
return
|
||||
}
|
||||
l.mtx.Lock()
|
||||
defer l.mtx.Unlock()
|
||||
pidAndLevel := []interface{}{l.pid, " ", loglevel[level], " "}
|
||||
params = append(pidAndLevel, params...)
|
||||
params = append(params, l.lineEnding)
|
||||
if l.mode&LogFile != 0 {
|
||||
l.loggers[0].Print(params...)
|
||||
l.logger.Print(params...)
|
||||
}
|
||||
if l.mode&LogConsole != 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -9,53 +9,60 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
upnp "openp2p/pkg/upnp"
|
||||
|
||||
reuse "github.com/openp2p-cn/go-reuseport"
|
||||
)
|
||||
|
||||
func natTCP(serverHost string, serverPort int) (publicIP string, publicPort int, localPort int) {
|
||||
// dialer := &net.Dialer{
|
||||
// LocalAddr: &net.TCPAddr{
|
||||
// IP: net.ParseIP("0.0.0.0"),
|
||||
// Port: localPort,
|
||||
// },
|
||||
// }
|
||||
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", 0), fmt.Sprintf("%s:%d", serverHost, serverPort), NatTestTimeout)
|
||||
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||
// log.Println(LvINFO, conn.LocalAddr())
|
||||
func natDetectTCP(serverHost string, serverPort int, lp int) (publicIP string, publicPort int, localPort int, err error) {
|
||||
gLog.dev("natDetectTCP start")
|
||||
defer gLog.dev("natDetectTCP end")
|
||||
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("0.0.0.0:%d", lp), fmt.Sprintf("%s:%d", serverHost, serverPort), NatDetectTimeout)
|
||||
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
|
||||
}
|
||||
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")
|
||||
defer gLog.Println(LvDEBUG, "natTest end")
|
||||
|
||||
func natDetectUDP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
|
||||
gLog.dev("natDetectUDP start")
|
||||
defer gLog.dev("natDetectUDP end")
|
||||
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "natTest listen udp error:", err)
|
||||
gLog.e("natDetectUDP listen udp error:%s", err)
|
||||
return "", 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
@@ -71,7 +78,7 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
deadline := time.Now().Add(NatTestTimeout)
|
||||
deadline := time.Now().Add(NatDetectTimeout)
|
||||
err = conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
@@ -79,7 +86,7 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
|
||||
buffer := make([]byte, 1024)
|
||||
nRead, _, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "NAT detect error:", err)
|
||||
gLog.e("NAT detect error:%s", err)
|
||||
return "", 0, err
|
||||
}
|
||||
natRsp := NatDetectRsp{}
|
||||
@@ -88,19 +95,27 @@ func natTest(serverHost string, serverPort int, localPort int) (publicIP string,
|
||||
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.
|
||||
localPort := int(rand.Uint32()%15000 + 50000)
|
||||
|
||||
ip1, port1, err := natTest(host, udp1, localPort)
|
||||
ip1, port1, err := natDetectUDP(host, detectPort1, localPort)
|
||||
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
|
||||
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
|
||||
_, port2, err := natDetectUDP(host, detectPort2, localPort) // 2rd nat test not need testing publicip
|
||||
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
|
||||
if port1 == port2 {
|
||||
natType = NATCone
|
||||
@@ -113,11 +128,11 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
|
||||
return
|
||||
}
|
||||
var echoConn *net.UDPConn
|
||||
gLog.Println(LvDEBUG, "echo server start")
|
||||
gLog.d("echo server start")
|
||||
var err error
|
||||
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
||||
if err != nil { // listen error
|
||||
gLog.Println(LvERROR, "echo server listen error:", err)
|
||||
gLog.e("echo server listen error:%s", err)
|
||||
return
|
||||
}
|
||||
defer echoConn.Close()
|
||||
@@ -125,34 +140,18 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
|
||||
for i := 0; i < 2; i++ {
|
||||
if i == 1 {
|
||||
// test upnp or nat-pmp
|
||||
gLog.Println(LvDEBUG, "upnp test start")
|
||||
nat, err := Discover()
|
||||
if err != nil || nat == nil {
|
||||
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
||||
break
|
||||
}
|
||||
ext, err := nat.GetExternalAddress()
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
|
||||
break
|
||||
}
|
||||
gLog.Println(LvINFO, "PublicIP:", ext)
|
||||
|
||||
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30) // 30 seconds fot upnp testing
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||
break
|
||||
} else {
|
||||
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800) // 7 days for tcp connection
|
||||
}
|
||||
gLog.d("upnp test start")
|
||||
// 7 days for udp connection
|
||||
// 7 days for tcp connection
|
||||
setUPNP(echoPort)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
defer conn.Close()
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort))
|
||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", gConf.Network.ServerIP, gConf.Network.ServerPort))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
@@ -169,21 +168,21 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
|
||||
echoConn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
||||
nRead, _, err := echoConn.ReadFromUDP(buf)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
|
||||
gLog.d("publicIPTest echoConn read timeout:%s", err)
|
||||
continue
|
||||
}
|
||||
natRsp := NatDetectRsp{}
|
||||
err = json.Unmarshal(buf[openP2PHeaderSize:nRead], &natRsp)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "PublicIP detect error:", err)
|
||||
gLog.d("publicIPTest Unmarshal error:%s", err)
|
||||
continue
|
||||
}
|
||||
if natRsp.Port == echoPort {
|
||||
if i == 1 {
|
||||
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
||||
gLog.d("UPNP or NAT-PMP:YES")
|
||||
hasUPNPorNATPMP = 1
|
||||
} else {
|
||||
gLog.Println(LvDEBUG, "public ip:YES")
|
||||
gLog.d("public ip:YES")
|
||||
hasPublicIP = 1
|
||||
}
|
||||
break
|
||||
@@ -191,3 +190,25 @@ func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATP
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package openp2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -25,15 +26,33 @@ func Run() {
|
||||
install()
|
||||
return
|
||||
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
|
||||
}
|
||||
} else {
|
||||
installByFilename()
|
||||
}
|
||||
parseParams("", "")
|
||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]")
|
||||
gLog.i("openp2p start. version: %s", OpenP2PVersion)
|
||||
gLog.i("Contact: QQ group 16947733, Email [email protected]")
|
||||
|
||||
if gConf.daemonMode {
|
||||
d := daemon{}
|
||||
@@ -41,18 +60,18 @@ func Run() {
|
||||
return
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, &gConf)
|
||||
gLog.i("node=%s, serverHost=%s, serverPort=%d", gConf.Network.Node, gConf.Network.ServerHost, gConf.Network.ServerPort)
|
||||
setFirewall()
|
||||
err := setRLimit()
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "setRLimit error:", err)
|
||||
gLog.i("setRLimit error:%s", err)
|
||||
}
|
||||
GNetwork = P2PNetworkInstance()
|
||||
P2PNetworkInstance()
|
||||
if ok := GNetwork.Connect(30000); !ok {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
gLog.e("P2PNetwork login error")
|
||||
return
|
||||
}
|
||||
// gLog.Println(LvINFO, "waiting for connection...")
|
||||
// gLog.i("waiting for connection...")
|
||||
forever := make(chan bool)
|
||||
<-forever
|
||||
}
|
||||
@@ -76,16 +95,16 @@ func RunAsModule(baseDir string, token string, bw int, logLevel int) *P2PNetwork
|
||||
}
|
||||
// gLog.setLevel(LogLevel(logLevel))
|
||||
gConf.setShareBandwidth(bw)
|
||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||
gLog.Println(LvINFO, "Contact: QQ group 16947733, Email [email protected]")
|
||||
gLog.Println(LvINFO, &gConf)
|
||||
gLog.i("openp2p start. version: %s", OpenP2PVersion)
|
||||
gLog.i("Contact: QQ group 16947733, Email [email protected]")
|
||||
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 {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
gLog.e("P2PNetwork login error")
|
||||
return nil
|
||||
}
|
||||
// gLog.Println(LvINFO, "waiting for connection...")
|
||||
// gLog.i("waiting for connection...")
|
||||
return GNetwork
|
||||
}
|
||||
|
||||
@@ -99,11 +118,11 @@ func RunCmd(cmd string) {
|
||||
setFirewall()
|
||||
err := setRLimit()
|
||||
if err != nil {
|
||||
gLog.Println(LvINFO, "setRLimit error:", err)
|
||||
gLog.i("setRLimit error:%s", err)
|
||||
}
|
||||
GNetwork = P2PNetworkInstance()
|
||||
P2PNetworkInstance()
|
||||
if ok := GNetwork.Connect(30000); !ok {
|
||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||
gLog.e("P2PNetwork login error")
|
||||
return
|
||||
}
|
||||
forever := make(chan bool)
|
||||
@@ -116,6 +135,14 @@ func GetToken(baseDir string) string {
|
||||
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() {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,10 @@ import (
|
||||
"github.com/openp2p-cn/wireguard-go/tun"
|
||||
)
|
||||
|
||||
const optunMTU = 1420
|
||||
|
||||
var AndroidSDWANConfig chan []byte
|
||||
var preAndroidSDWANConfig string
|
||||
|
||||
type optun struct {
|
||||
tunName string
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
ReadTunBuffSize = 2048
|
||||
ReadTunBuffNum = 16
|
||||
)
|
||||
|
||||
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) {
|
||||
head := PacketHeader{}
|
||||
parseHeader(data, &head)
|
||||
gLog.Printf(LvDev, "AndroidRead tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len)
|
||||
// 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)
|
||||
copy(buf, data)
|
||||
AndroidReadTun <- buf
|
||||
}
|
||||
|
||||
func AndroidWrite(buf []byte) int {
|
||||
p := <-AndroidWriteTun
|
||||
copy(buf, p)
|
||||
return len(p)
|
||||
func AndroidWrite(buf []byte, timeoutMs int) int {
|
||||
timeout := time.Duration(timeoutMs) * time.Millisecond
|
||||
select {
|
||||
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 {
|
||||
p := <-AndroidSDWANConfig
|
||||
copy(buf, p)
|
||||
gLog.Printf(LvINFO, "AndroidSDWANConfig=%s", p)
|
||||
gLog.i("AndroidSDWANConfig=%s", p)
|
||||
return len(p)
|
||||
}
|
||||
|
||||
func GetAndroidNodeName() string {
|
||||
gLog.Printf(LvINFO, "GetAndroidNodeName=%s", gConf.Network.Node)
|
||||
gLog.i("GetAndroidNodeName=%s", gConf.Network.Node)
|
||||
return gConf.Network.Node
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
tunIfaceName = "utun"
|
||||
PIHeaderSize = 4 // utun has no IFF_NO_PI
|
||||
tunIfaceName = "utun"
|
||||
PIHeaderSize = 4 // utun has no IFF_NO_PI
|
||||
ReadTunBuffSize = 2048
|
||||
ReadTunBuffNum = 16
|
||||
)
|
||||
|
||||
func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
var err error
|
||||
t.tunName = tunIfaceName
|
||||
t.dev, err = tun.CreateTUN(t.tunName, 1420)
|
||||
t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,10 +74,10 @@ func delRoutesByGateway(gateway string) error {
|
||||
cmd := exec.Command("route", "delete", fields[0], gateway)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
|
||||
gLog.e("Delete route %s error:%s", fields[0], err)
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LvINFO, "Delete route ok: %s %s\n", fields[0], gateway)
|
||||
gLog.i("Delete route ok: %s %s\n", fields[0], gateway)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -7,8 +7,7 @@ package openp2p
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/openp2p-cn/wireguard-go/tun"
|
||||
"github.com/vishvananda/netlink"
|
||||
@@ -17,6 +16,9 @@ import (
|
||||
const (
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
// sdwan
|
||||
ReadTunBuffSize = 2048
|
||||
ReadTunBuffNum = 16
|
||||
)
|
||||
|
||||
var previousIP = ""
|
||||
@@ -24,10 +26,14 @@ var previousIP = ""
|
||||
func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
var err error
|
||||
t.tunName = tunIfaceName
|
||||
t.dev, err = tun.CreateTUN(t.tunName, 1420)
|
||||
t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -44,8 +50,8 @@ func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netlink.LinkSetMTU(ifce, 1375)
|
||||
netlink.LinkSetTxQLen(ifce, 100)
|
||||
netlink.LinkSetMTU(ifce, int(gConf.getSDWAN().Mtu))
|
||||
netlink.LinkSetTxQLen(ifce, 1000)
|
||||
netlink.LinkSetUp(ifce)
|
||||
|
||||
ln, err := netlink.ParseIPNet(localAddr)
|
||||
@@ -108,26 +114,24 @@ func delRoute(dst, gw string) error {
|
||||
}
|
||||
|
||||
func delRoutesByGateway(gateway string) error {
|
||||
cmd := exec.Command("route", "-n")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
ipGW := net.ParseIP(gateway)
|
||||
if ipGW == nil {
|
||||
return fmt.Errorf("invalid gateway IP: %s", gateway)
|
||||
}
|
||||
|
||||
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()
|
||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list routes: %v", err)
|
||||
}
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Gw != nil && route.Gw.Equal(ipGW) || (route.Dst != nil && route.Dst.IP.Equal(ipGW)) {
|
||||
err := netlink.RouteDel(&route)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
|
||||
gLog.e("Failed to delete route: %v, error: %v", route, err)
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LvINFO, "Delete route ok: %s %s %s\n", fields[0], fields[1], gateway)
|
||||
gLog.i("Deleted route: %v", route)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -3,40 +3,133 @@
|
||||
|
||||
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 (
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
tunIfaceName = "optun"
|
||||
PIHeaderSize = 0
|
||||
ReadTunBuffSize = 2048
|
||||
ReadTunBuffNum = 16
|
||||
)
|
||||
|
||||
var previousIP = ""
|
||||
|
||||
func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
var err error
|
||||
t.tunName = tunIfaceName
|
||||
t.dev, err = tun.CreateTUN(t.tunName, 1420)
|
||||
|
||||
t.dev, err = tun.CreateTUN(t.tunName, int(detail.Mtu))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setTunAddr(t.tunName, localAddr, detail.Gateway, t.dev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *optun) Read(bufs [][]byte, sizes []int, offset int) (n int, err error) {
|
||||
return t.dev.Read(bufs, sizes, offset)
|
||||
}
|
||||
|
||||
func (t *optun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
return t.dev.Write(bufs, offset)
|
||||
}
|
||||
|
||||
func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error {
|
||||
ifce, err := netlink.LinkByName(ifname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netlink.LinkSetMTU(ifce, 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 {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
func addTunAddr(localAddr, remoteAddr string) error {
|
||||
return nil
|
||||
_, networkid, err := net.ParseCIDR(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import (
|
||||
const (
|
||||
tunIfaceName = "optun"
|
||||
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 {
|
||||
@@ -42,9 +45,9 @@ func (t *optun) Start(localAddr string, detail *SDWANInfo) error {
|
||||
Data3: 0x4567,
|
||||
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
|
||||
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, 1420)
|
||||
t.dev, err = tun.CreateTUNWithRequestedGUID(t.tunName, uuid, int(detail.Mtu))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -67,12 +70,12 @@ func setTunAddr(ifname, localAddr, remoteAddr string, wintun interface{}) error
|
||||
link := winipcfg.LUID(nativeTunDevice.LUID())
|
||||
ip, err := netip.ParsePrefix(localAddr)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr)
|
||||
gLog.e("ParsePrefix error:%s, luid:%d,localAddr:%s", err, nativeTunDevice.LUID(), localAddr)
|
||||
return err
|
||||
}
|
||||
err = link.SetIPAddresses([]netip.Prefix{ip})
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip})
|
||||
gLog.e("SetIPAddresses error:%s, netip.Prefix:%+v", err, []netip.Prefix{ip})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -133,10 +136,10 @@ func delRoutesByGateway(gateway string) error {
|
||||
cmd := exec.Command("route", "delete", fields[0], "mask", fields[1], gateway)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "Delete route %s error:%s", fields[0], err)
|
||||
gLog.e("Delete route %s error:%s", fields[0], err)
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LvINFO, "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
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -21,18 +22,24 @@ func (e *DeadlineExceededError) Error() string { return "i/o timeout" }
|
||||
func (e *DeadlineExceededError) Timeout() 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
|
||||
type overlayConn struct {
|
||||
tunnel *P2PTunnel // TODO: del
|
||||
app *p2pApp
|
||||
connTCP net.Conn
|
||||
id uint64
|
||||
rtid uint64
|
||||
running bool
|
||||
isClient bool
|
||||
appID uint64 // TODO: del
|
||||
appKey uint64 // TODO: del
|
||||
appKeyBytes []byte // TODO: del
|
||||
app *p2pApp
|
||||
connTCP net.Conn
|
||||
id uint64
|
||||
running bool
|
||||
isClient bool
|
||||
// for udp
|
||||
connUDP *net.UDPConn
|
||||
remoteAddr net.Addr
|
||||
@@ -41,42 +48,31 @@ type overlayConn struct {
|
||||
}
|
||||
|
||||
func (oConn *overlayConn) run() {
|
||||
gLog.Printf(LvDEBUG, "%d overlayConn run start", oConn.id)
|
||||
defer gLog.Printf(LvDEBUG, "%d overlayConn run end", oConn.id)
|
||||
gLog.d("oid:%d overlayConn run start", oConn.id)
|
||||
defer gLog.d("oid:%d overlayConn run end", oConn.id)
|
||||
oConn.lastReadUDPTs = time.Now()
|
||||
buffer := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
|
||||
reuseBuff := buffer[:ReadBuffLen]
|
||||
encryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
|
||||
tunnelHead := new(bytes.Buffer)
|
||||
relayHead := new(bytes.Buffer)
|
||||
binary.Write(relayHead, binary.LittleEndian, oConn.rtid)
|
||||
binary.Write(tunnelHead, binary.LittleEndian, oConn.id)
|
||||
for oConn.running && oConn.tunnel.isRuning() {
|
||||
overlayHead := new(bytes.Buffer)
|
||||
|
||||
binary.Write(overlayHead, binary.LittleEndian, oConn.id)
|
||||
for oConn.running && oConn.app.running {
|
||||
readBuff, dataLen, err := oConn.Read(reuseBuff)
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
continue
|
||||
}
|
||||
// overlay tcp connection normal close, debug log
|
||||
gLog.Printf(LvDEBUG, "overlayConn %d read error:%s,close it", oConn.id, err)
|
||||
gLog.d("oid:%d overlayConn read error:%s,close it", oConn.id, err)
|
||||
break
|
||||
}
|
||||
payload := readBuff[:dataLen]
|
||||
if oConn.appKey != 0 {
|
||||
payload, _ = encryptBytes(oConn.appKeyBytes, encryptData, readBuff[:dataLen], dataLen)
|
||||
}
|
||||
writeBytes := append(tunnelHead.Bytes(), payload...)
|
||||
// TODO: app.write
|
||||
if oConn.rtid == 0 {
|
||||
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgOverlayData, writeBytes)
|
||||
gLog.Printf(LvDev, "write overlay data to tid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.id, len(writeBytes))
|
||||
} else {
|
||||
// write raley data
|
||||
all := append(relayHead.Bytes(), encodeHeader(MsgP2P, MsgOverlayData, uint32(len(writeBytes)))...)
|
||||
all = append(all, writeBytes...)
|
||||
oConn.tunnel.conn.WriteBytes(MsgP2P, MsgRelayData, all)
|
||||
gLog.Printf(LvDev, "write relay data to tid:%d,rtid:%d,oid:%d bodylen=%d", oConn.tunnel.id, oConn.rtid, oConn.id, len(writeBytes))
|
||||
if oConn.app.key != 0 {
|
||||
payload, _ = encryptBytes(oConn.app.appKeyBytes, encryptData, readBuff[:dataLen], dataLen)
|
||||
}
|
||||
writeBytes := append(overlayHead.Bytes(), payload...)
|
||||
oConn.app.WriteBytes(writeBytes)
|
||||
}
|
||||
if oConn.connTCP != nil {
|
||||
oConn.connTCP.Close()
|
||||
@@ -84,10 +80,10 @@ func (oConn *overlayConn) run() {
|
||||
if oConn.connUDP != nil {
|
||||
oConn.connUDP.Close()
|
||||
}
|
||||
oConn.tunnel.overlayConns.Delete(oConn.id)
|
||||
overlayConns.Delete(oConn.id)
|
||||
// notify peer disconnect
|
||||
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) {
|
||||
@@ -145,7 +141,8 @@ func (oConn *overlayConn) Write(buff []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
if oConn.connTCP != nil {
|
||||
n, err = oConn.connTCP.Write(buff)
|
||||
err = writeFull(oConn.connTCP, buff)
|
||||
n = len(buff)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -4,167 +4,233 @@ import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"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 {
|
||||
config AppConfig
|
||||
listener net.Listener
|
||||
listenerUDP *net.UDPConn
|
||||
directTunnel *P2PTunnel
|
||||
relayTunnel *P2PTunnel
|
||||
tunnelMtx sync.Mutex
|
||||
iptree *IPTree // for whitelist
|
||||
rtid uint64 // relay tunnelID
|
||||
relayNode string
|
||||
relayMode string // public/private
|
||||
hbTimeRelay time.Time
|
||||
hbMtx sync.Mutex
|
||||
running bool
|
||||
id uint64
|
||||
key uint64 // aes
|
||||
wg sync.WaitGroup
|
||||
relayHead *bytes.Buffer
|
||||
once sync.Once
|
||||
// for relayTunnel
|
||||
retryRelayNum int
|
||||
retryRelayTime time.Time
|
||||
nextRetryRelayTime time.Time
|
||||
errMsg string
|
||||
connectTime time.Time
|
||||
config AppConfig
|
||||
listener net.Listener
|
||||
listenerUDP *net.UDPConn
|
||||
|
||||
tunnelMtx sync.Mutex
|
||||
iptree *IPTree // for whitelist
|
||||
|
||||
hbMtx sync.Mutex
|
||||
running bool
|
||||
id uint64
|
||||
key uint64 // aes
|
||||
appKeyBytes []byte // pre-calc
|
||||
wg sync.WaitGroup
|
||||
msgChan chan appMsgCtx
|
||||
once sync.Once
|
||||
tunnelNum int
|
||||
relayIdxStart int
|
||||
allTunnels []*P2PTunnel
|
||||
retryNum []int
|
||||
retryTime []time.Time
|
||||
nextRetryTime []time.Time
|
||||
rtt []atomic.Int32
|
||||
relayHead []*bytes.Buffer
|
||||
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()
|
||||
defer app.tunnelMtx.Unlock()
|
||||
if app.directTunnel != nil {
|
||||
return app.directTunnel
|
||||
|
||||
func (app *p2pApp) Tunnel(idx int) *P2PTunnel {
|
||||
if idx > app.tunnelNum-1 {
|
||||
return nil
|
||||
}
|
||||
return app.relayTunnel
|
||||
}
|
||||
|
||||
func (app *p2pApp) DirectTunnel() *P2PTunnel {
|
||||
app.tunnelMtx.Lock()
|
||||
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()
|
||||
defer app.tunnelMtx.Unlock()
|
||||
app.directTunnel = t
|
||||
}
|
||||
app.allTunnels[idx] = t
|
||||
|
||||
func (app *p2pApp) RelayTunnel() *P2PTunnel {
|
||||
app.tunnelMtx.Lock()
|
||||
defer app.tunnelMtx.Unlock()
|
||||
return app.relayTunnel
|
||||
}
|
||||
|
||||
func (app *p2pApp) setRelayTunnel(t *P2PTunnel) {
|
||||
app.tunnelMtx.Lock()
|
||||
defer app.tunnelMtx.Unlock()
|
||||
app.relayTunnel = t
|
||||
}
|
||||
|
||||
func (app *p2pApp) isDirect() bool {
|
||||
return app.directTunnel != nil
|
||||
}
|
||||
|
||||
func (app *p2pApp) RelayTunnelID() uint64 {
|
||||
if app.isDirect() {
|
||||
return 0
|
||||
}
|
||||
return app.rtid
|
||||
app.rtt[idx].Store(DefaultRtt)
|
||||
app.unAckTs[idx].Store(0)
|
||||
app.writeTs[idx].Store(0)
|
||||
}
|
||||
|
||||
func (app *p2pApp) ConnectTime() time.Time {
|
||||
if app.isDirect() {
|
||||
if app.allTunnels[0] != nil {
|
||||
return app.config.connectTime
|
||||
}
|
||||
return app.connectTime
|
||||
}
|
||||
|
||||
func (app *p2pApp) RetryTime() time.Time {
|
||||
if app.isDirect() {
|
||||
if app.allTunnels[0] != nil {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *p2pApp) directRetryLimit() int {
|
||||
if app.config.peerIP == gConf.Network.publicIP && compareVersion(app.config.peerVersion, SupportIntranetVersion) >= 0 {
|
||||
return retryLimit
|
||||
func (app *p2pApp) daemonDirectTunnel(idx int) error {
|
||||
if !GNetwork.online {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
gLog.Printf(LvINFO, "detect app %s appid:%d disconnect, reconnecting the %d times...", app.config.LogPeerNode(), app.id, app.config.retryNum)
|
||||
if app.retryNum[idx] > 0 { // first time not show reconnect log
|
||||
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.nextRetryTime = time.Now().Add(retryInterval)
|
||||
|
||||
app.config.connectTime = time.Now()
|
||||
err := app.buildDirectTunnel()
|
||||
err := app.buildDirectTunnel(idx)
|
||||
if err != nil {
|
||||
app.config.errMsg = err.Error()
|
||||
if err == ErrPeerOffline && app.config.retryNum > 2 { // stop retry, waiting for online
|
||||
app.config.retryNum = retryLimit
|
||||
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.LogPeerNode())
|
||||
if err == ErrPeerOffline && app.retryNum[idx] > 2 { // stop retry, waiting for online
|
||||
app.retryNum[idx] = retryLimit
|
||||
gLog.i("appid:%d checkDirectTunnel %s offline, it will auto reconnect when peer node online", app.id, app.config.LogPeerNode())
|
||||
}
|
||||
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() {
|
||||
go app.listen()
|
||||
// memapp also need
|
||||
go app.relayHeartbeatLoop()
|
||||
for i := app.relayIdxStart; i < app.tunnelNum; i++ {
|
||||
go app.relayHeartbeatLoop(i)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (app *p2pApp) buildDirectTunnel() error {
|
||||
func (app *p2pApp) buildDirectTunnel(idx int) error {
|
||||
relayNode := ""
|
||||
peerNatType := NATUnknown
|
||||
peerIP := ""
|
||||
@@ -172,12 +238,13 @@ func (app *p2pApp) buildDirectTunnel() error {
|
||||
var t *P2PTunnel
|
||||
var err error
|
||||
pn := GNetwork
|
||||
// TODO: optimize requestPeerInfo call frequency
|
||||
initErr := pn.requestPeerInfo(&app.config)
|
||||
if initErr != nil {
|
||||
gLog.Printf(LvERROR, "%s requestPeerInfo error:%s", app.config.LogPeerNode(), initErr)
|
||||
gLog.w("appid:%d buildDirectTunnel %s requestPeerInfo error:%s", app.id, app.config.LogPeerNode(), initErr)
|
||||
return initErr
|
||||
}
|
||||
t, err = pn.addDirectTunnel(app.config, 0)
|
||||
t, err = pn.addDirectTunnel(app.config, 0, app.Tunnel(idx^1))
|
||||
if t != nil {
|
||||
peerNatType = t.config.peerNatType
|
||||
peerIP = t.config.peerIP
|
||||
@@ -212,64 +279,78 @@ func (app *p2pApp) buildDirectTunnel() error {
|
||||
AppID: app.id,
|
||||
AppKey: app.key,
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sync appkey direct to %s", app.config.LogPeerNode())
|
||||
gLog.d("appid:%d buildDirectTunnel sync appkey to %s", app.id, app.config.LogPeerNode())
|
||||
pn.push(app.config.PeerNode, MsgPushAPPKey, &syncKeyReq)
|
||||
app.setDirectTunnel(t)
|
||||
app.SetTunnel(t, idx)
|
||||
|
||||
// if memapp notify peer addmemapp
|
||||
if app.config.SrcPort == 0 {
|
||||
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: gConf.Network.Node, TunnelID: t.id, RelayTunnelID: 0, AppID: app.id}
|
||||
pn.push(app.config.PeerNode, MsgPushServerSideSaveMemApp, &req)
|
||||
gLog.Printf(LvDEBUG, "push %s ServerSideSaveMemApp: %s", app.config.LogPeerNode(), prettyJson(req))
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id)
|
||||
// if app.config.SrcPort == 0 {
|
||||
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, &req2)
|
||||
gLog.d("appid:%d buildDirectTunnel push %s ServerSideSaveMemApp: %s", app.id, app.config.LogPeerNode(), prettyJson(req2))
|
||||
|
||||
// }
|
||||
gLog.d("appid:%d buildDirectTunnel ok. %s use tid %d", app.id, app.config.AppName, t.id)
|
||||
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.SrcPort == 0 && (gConf.sdwan.CentralNode == app.config.PeerNode || gConf.sdwan.CentralNode == gConf.Network.Node) { // memapp central node not build relay tunnel
|
||||
return nil
|
||||
}
|
||||
if gConf.sdwan.CentralNode != "" && idx != app.relayIdxStart { // if central node exist only need one relayTunnel
|
||||
return nil
|
||||
}
|
||||
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()
|
||||
return nil
|
||||
}
|
||||
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
|
||||
}
|
||||
if time.Now().Add(-time.Minute * 15).After(app.retryRelayTime) { // run normally 15min, reset retrynum
|
||||
app.retryRelayNum = 1
|
||||
if time.Now().Add(-time.Minute * 15).After(app.retryTime[idx]) { // run normally 15min, reset retrynum
|
||||
app.retryNum[idx] = 1
|
||||
}
|
||||
if app.retryRelayNum > 0 { // first time not show reconnect log
|
||||
gLog.Printf(LvINFO, "detect app %s appid:%d relay disconnect, reconnecting the %d times...", app.config.LogPeerNode(), app.id, app.retryRelayNum)
|
||||
if app.retryNum[idx] > 0 { // first time not show reconnect log
|
||||
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.retryRelayNum++
|
||||
app.retryRelayTime = time.Now()
|
||||
app.nextRetryRelayTime = time.Now().Add(retryInterval)
|
||||
app.SetTunnel(nil, idx) // reset relayTunnel
|
||||
app.retryNum[idx]++
|
||||
app.retryTime[idx] = time.Now()
|
||||
app.connectTime = time.Now()
|
||||
err := app.buildRelayTunnel()
|
||||
err := app.buildRelayTunnel(idx)
|
||||
if err != nil {
|
||||
app.errMsg = err.Error()
|
||||
if err == ErrPeerOffline && app.retryRelayNum > 2 { // stop retry, waiting for online
|
||||
app.retryRelayNum = retryLimit
|
||||
gLog.Printf(LvINFO, " %s offline, it will auto reconnect when peer node online", app.config.LogPeerNode())
|
||||
if err == ErrPeerOffline && app.retryNum[idx] > 2 { // stop retry, waiting for online
|
||||
app.retryNum[idx] = retryLimit
|
||||
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() {
|
||||
go app.listen()
|
||||
// memapp also need
|
||||
go app.relayHeartbeatLoop()
|
||||
for i := 1; i < app.tunnelNum; i++ {
|
||||
go app.relayHeartbeatLoop(i)
|
||||
}
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *p2pApp) buildRelayTunnel() error {
|
||||
func (app *p2pApp) buildRelayTunnel(idx int) error {
|
||||
var rtid uint64
|
||||
relayNode := ""
|
||||
relayMode := ""
|
||||
@@ -282,11 +363,18 @@ func (app *p2pApp) buildRelayTunnel() error {
|
||||
config := app.config
|
||||
initErr := pn.requestPeerInfo(&config)
|
||||
if initErr != nil {
|
||||
gLog.Printf(LvERROR, "%s init error:%s", config.LogPeerNode(), initErr)
|
||||
gLog.w("appid:%d buildRelayTunnel %s init error:%s", app.id, config.LogPeerNode(), initErr)
|
||||
return initErr
|
||||
}
|
||||
|
||||
t, rtid, relayMode, err = pn.addRelayTunnel(config)
|
||||
ExcludeNodes := ""
|
||||
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 {
|
||||
relayNode = t.config.PeerNode
|
||||
}
|
||||
@@ -294,45 +382,48 @@ func (app *p2pApp) buildRelayTunnel() error {
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
req := ReportConnect{
|
||||
Error: errMsg,
|
||||
Protocol: config.Protocol,
|
||||
SrcPort: config.SrcPort,
|
||||
NatType: gConf.Network.natType,
|
||||
PeerNode: config.PeerNode,
|
||||
DstPort: config.DstPort,
|
||||
DstHost: config.DstHost,
|
||||
PeerNatType: peerNatType,
|
||||
PeerIP: peerIP,
|
||||
ShareBandwidth: gConf.Network.ShareBandwidth,
|
||||
RelayNode: relayNode,
|
||||
Version: OpenP2PVersion,
|
||||
if app.Tunnel(0) == nil {
|
||||
req := ReportConnect{
|
||||
Error: errMsg,
|
||||
Protocol: config.Protocol,
|
||||
SrcPort: config.SrcPort,
|
||||
NatType: gConf.Network.natType,
|
||||
PeerNode: config.PeerNode,
|
||||
DstPort: config.DstPort,
|
||||
DstHost: config.DstHost,
|
||||
PeerNatType: peerNatType,
|
||||
PeerIP: peerIP,
|
||||
ShareBandwidth: gConf.Network.ShareBandwidth,
|
||||
RelayNode: relayNode,
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
pn.write(MsgReport, MsgReportConnect, &req)
|
||||
}
|
||||
pn.write(MsgReport, MsgReportConnect, &req)
|
||||
if err != nil {
|
||||
if err != nil || t == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if rtid != 0 || t.conn.Protocol() == "tcp" {
|
||||
// sync appkey
|
||||
syncKeyReq := APPKeySync{
|
||||
AppID: app.id,
|
||||
AppKey: app.key,
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sync appkey relay to %s", config.LogPeerNode())
|
||||
gLog.d("appid:%d buildRelayTunnel sync appkey relay to %s", app.id, config.LogPeerNode())
|
||||
pn.push(config.PeerNode, MsgPushAPPKey, &syncKeyReq)
|
||||
app.setRelayTunnelID(rtid)
|
||||
app.setRelayTunnel(t)
|
||||
app.relayNode = relayNode
|
||||
app.relayMode = relayMode
|
||||
app.hbTimeRelay = time.Now()
|
||||
app.SetRelayTunnelID(rtid, idx)
|
||||
app.SetTunnel(t, idx)
|
||||
app.relayNode[idx] = relayNode
|
||||
app.relayMode[idx] = relayMode
|
||||
app.hbTime[idx] = time.Now()
|
||||
|
||||
// if memapp notify peer addmemapp
|
||||
if config.SrcPort == 0 {
|
||||
req := ServerSideSaveMemApp{From: gConf.Network.Node, Node: relayNode, TunnelID: rtid, RelayTunnelID: t.id, AppID: app.id, RelayMode: relayMode}
|
||||
pn.push(config.PeerNode, MsgPushServerSideSaveMemApp, &req)
|
||||
gLog.Printf(LvDEBUG, "push %s relay ServerSideSaveMemApp: %s", config.LogPeerNode(), prettyJson(req))
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "%s use tunnel %d", app.config.AppName, t.id)
|
||||
// if config.SrcPort == 0 {
|
||||
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, &req2)
|
||||
gLog.d("appid:%d buildRelayTunnel push %s relay ServerSideSaveMemApp: %s", app.id, config.LogPeerNode(), prettyJson(req2))
|
||||
// }
|
||||
gLog.d("appid:%d buildRelayTunnel %s use tunnel %d", app.id, app.config.AppName, t.id)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -341,47 +432,80 @@ func (app *p2pApp) buildOfficialTunnel() error {
|
||||
}
|
||||
|
||||
// cache relayHead, refresh when rtid change
|
||||
func (app *p2pApp) RelayHead() *bytes.Buffer {
|
||||
if app.relayHead == nil {
|
||||
app.relayHead = new(bytes.Buffer)
|
||||
binary.Write(app.relayHead, binary.LittleEndian, app.rtid)
|
||||
func (app *p2pApp) RelayHead(idx int) *bytes.Buffer {
|
||||
if app.relayHead[idx] == nil {
|
||||
app.relayHead[idx] = new(bytes.Buffer)
|
||||
binary.Write(app.relayHead[idx], binary.LittleEndian, app.rtid[idx])
|
||||
}
|
||||
return app.relayHead
|
||||
return app.relayHead[idx]
|
||||
}
|
||||
|
||||
func (app *p2pApp) setRelayTunnelID(rtid uint64) {
|
||||
app.rtid = rtid
|
||||
app.relayHead = new(bytes.Buffer)
|
||||
binary.Write(app.relayHead, binary.LittleEndian, app.rtid)
|
||||
func (app *p2pApp) SetRelayTunnelID(rtid uint64, idx int) {
|
||||
app.rtid[idx] = rtid
|
||||
app.relayHead[idx] = new(bytes.Buffer)
|
||||
binary.Write(app.relayHead[idx], binary.LittleEndian, app.rtid[idx])
|
||||
}
|
||||
|
||||
func (app *p2pApp) isActive() bool {
|
||||
if app.Tunnel() == nil {
|
||||
// gLog.Printf(LvDEBUG, "isActive app.tunnel==nil")
|
||||
func (app *p2pApp) IsActive() bool {
|
||||
if t, _ := app.AvailableTunnel(); t == nil {
|
||||
// gLog.d("isActive app.tunnel==nil")
|
||||
return false
|
||||
}
|
||||
if app.isDirect() { // direct mode app heartbeat equals to tunnel heartbeat
|
||||
return app.Tunnel().isActive()
|
||||
if app.Tunnel(0) != nil { // direct mode app heartbeat equals to tunnel heartbeat
|
||||
return app.Tunnel(0).isActive()
|
||||
}
|
||||
// relay mode calc app heartbeat
|
||||
app.hbMtx.Lock()
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
func (app *p2pApp) updateHeartbeat() {
|
||||
// only for relay tunnel heartbeat update
|
||||
func (app *p2pApp) UpdateHeartbeat(rtid uint64) {
|
||||
app.hbMtx.Lock()
|
||||
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 {
|
||||
gLog.Printf(LvDEBUG, "tcp accept on port %d start", app.config.SrcPort)
|
||||
defer gLog.Printf(LvDEBUG, "tcp accept on port %d end", app.config.SrcPort)
|
||||
gLog.d("appid:%d tcp accept on port %d start", app.id, app.config.SrcPort)
|
||||
defer gLog.d("appid:%d tcp accept on port %d end", app.id, app.config.SrcPort)
|
||||
var err error
|
||||
listenAddr := ""
|
||||
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))
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "listen error:%s", err)
|
||||
gLog.e("appid:%d listen tcp error:%s", app.id, err)
|
||||
return err
|
||||
}
|
||||
defer app.listener.Close()
|
||||
@@ -397,12 +521,13 @@ func (app *p2pApp) listenTCP() error {
|
||||
conn, err := app.listener.Accept()
|
||||
if err != nil {
|
||||
if app.running {
|
||||
gLog.Printf(LvERROR, "%d accept error:%s", app.id, err)
|
||||
gLog.e("appid:%d accept error:%s", app.id, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if app.Tunnel() == nil {
|
||||
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort)
|
||||
t, tidx := app.AvailableTunnel()
|
||||
if t == nil {
|
||||
gLog.d("appid:%d srcPort=%d, app.Tunnel()==nil, not ready", app.id, app.config.SrcPort)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
@@ -411,32 +536,20 @@ func (app *p2pApp) listenTCP() error {
|
||||
remoteIP := conn.RemoteAddr().(*net.TCPAddr).IP.String()
|
||||
if !app.iptree.Contains(remoteIP) && !IsLocalhost(remoteIP) {
|
||||
conn.Close()
|
||||
gLog.Printf(LvERROR, "%s not in whitelist, access denied", remoteIP)
|
||||
gLog.e("appid:%d %s not in whitelist, access denied", app.id, remoteIP)
|
||||
continue
|
||||
}
|
||||
}
|
||||
oConn := overlayConn{
|
||||
tunnel: app.Tunnel(),
|
||||
app: app,
|
||||
connTCP: conn,
|
||||
id: rand.Uint64(),
|
||||
isClient: true,
|
||||
appID: app.id,
|
||||
appKey: app.key,
|
||||
running: true,
|
||||
}
|
||||
if !app.isDirect() {
|
||||
oConn.rtid = app.rtid
|
||||
}
|
||||
// pre-calc key bytes for encrypt
|
||||
if oConn.appKey != 0 {
|
||||
encryptKey := make([]byte, AESKeySize)
|
||||
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
|
||||
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
|
||||
oConn.appKeyBytes = encryptKey
|
||||
}
|
||||
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
|
||||
gLog.Printf(LvDEBUG, "Accept TCP overlayID:%d, %s", oConn.id, oConn.connTCP.RemoteAddr())
|
||||
|
||||
overlayConns.Store(oConn.id, &oConn)
|
||||
gLog.d("appid:%d Accept TCP overlayID:%d, %s", app.id, oConn.id, oConn.connTCP.RemoteAddr())
|
||||
// tell peer connect
|
||||
req := OverlayConnectReq{ID: oConn.id,
|
||||
Token: gConf.Network.Token,
|
||||
@@ -445,48 +558,52 @@ func (app *p2pApp) listenTCP() error {
|
||||
Protocol: app.config.Protocol,
|
||||
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()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *p2pApp) listenUDP() error {
|
||||
gLog.Printf(LvDEBUG, "udp accept on port %d start", app.config.SrcPort)
|
||||
defer gLog.Printf(LvDEBUG, "udp accept on port %d end", app.config.SrcPort)
|
||||
gLog.d("appid:%d udp accept on port %d start", app.id, app.config.SrcPort)
|
||||
defer gLog.d("appid:%d udp accept on port %d end", app.id, app.config.SrcPort)
|
||||
var err error
|
||||
app.listenerUDP, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: app.config.SrcPort})
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "listen error:%s", err)
|
||||
gLog.e("appid:%d listen udp error:%s", app.id, err)
|
||||
return err
|
||||
}
|
||||
defer app.listenerUDP.Close()
|
||||
buffer := make([]byte, 64*1024+PaddingSize)
|
||||
udpID := make([]byte, 8)
|
||||
for {
|
||||
for app.running {
|
||||
app.listenerUDP.SetReadDeadline(time.Now().Add(UDPReadTimeout))
|
||||
len, remoteAddr, err := app.listenerUDP.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
if ne, ok := err.(net.Error); ok && ne.Timeout() {
|
||||
continue
|
||||
} else {
|
||||
gLog.Printf(LvERROR, "udp read failed:%s", err)
|
||||
gLog.e("appid:%d udp read failed:%s", app.id, err)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if app.Tunnel() == nil {
|
||||
gLog.Printf(LvDEBUG, "srcPort=%d, app.Tunnel()==nil, not ready", app.config.SrcPort)
|
||||
t, tidx := app.AvailableTunnel()
|
||||
if t == nil {
|
||||
gLog.d("appid:%d srcPort=%d, app.Tunnel()==nil, not ready", app.id, app.config.SrcPort)
|
||||
time.Sleep(time.Second)
|
||||
continue
|
||||
}
|
||||
dupData := bytes.Buffer{} // should uses memory pool
|
||||
dupData.Write(buffer[:len+PaddingSize])
|
||||
// load from app.tunnel.overlayConns by remoteAddr error, new udp connection
|
||||
// load from app.overlayConns by remoteAddr error, new udp connection
|
||||
remoteIP := strings.Split(remoteAddr.String(), ":")[0]
|
||||
port, _ := strconv.Atoi(strings.Split(remoteAddr.String(), ":")[1])
|
||||
a := net.ParseIP(remoteIP)
|
||||
@@ -497,31 +614,19 @@ func (app *p2pApp) listenUDP() error {
|
||||
udpID[4] = byte(port)
|
||||
udpID[5] = byte(port >> 8)
|
||||
id := binary.LittleEndian.Uint64(udpID) // convert remoteIP:port to uint64
|
||||
s, ok := app.Tunnel().overlayConns.Load(id)
|
||||
s, ok := overlayConns.Load(id)
|
||||
if !ok {
|
||||
oConn := overlayConn{
|
||||
tunnel: app.Tunnel(),
|
||||
app: app,
|
||||
connUDP: app.listenerUDP,
|
||||
remoteAddr: remoteAddr,
|
||||
udpData: make(chan []byte, 1000),
|
||||
id: id,
|
||||
isClient: true,
|
||||
appID: app.id,
|
||||
appKey: app.key,
|
||||
running: true,
|
||||
}
|
||||
if !app.isDirect() {
|
||||
oConn.rtid = app.rtid
|
||||
}
|
||||
// calc key bytes for encrypt
|
||||
if oConn.appKey != 0 {
|
||||
encryptKey := make([]byte, AESKeySize)
|
||||
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
|
||||
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
|
||||
oConn.appKeyBytes = encryptKey
|
||||
}
|
||||
app.Tunnel().overlayConns.Store(oConn.id, &oConn)
|
||||
gLog.Printf(LvDEBUG, "Accept UDP overlayID:%d", oConn.id)
|
||||
overlayConns.Store(oConn.id, &oConn)
|
||||
gLog.d("appid:%d Accept UDP overlayID:%d", app.id, oConn.id)
|
||||
// tell peer connect
|
||||
req := OverlayConnectReq{ID: oConn.id,
|
||||
Token: gConf.Network.Token,
|
||||
@@ -530,17 +635,19 @@ func (app *p2pApp) listenUDP() error {
|
||||
Protocol: app.config.Protocol,
|
||||
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()
|
||||
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)
|
||||
if !ok {
|
||||
continue
|
||||
@@ -555,8 +662,8 @@ func (app *p2pApp) listen() error {
|
||||
if app.config.SrcPort == 0 {
|
||||
return nil
|
||||
}
|
||||
gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d START", app.config.Protocol, app.config.SrcPort)
|
||||
defer gLog.Printf(LvINFO, "LISTEN ON PORT %s:%d END", app.config.Protocol, app.config.SrcPort)
|
||||
gLog.i("appid:%d LISTEN ON PORT %s:%d START", app.id, 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)
|
||||
defer app.wg.Done()
|
||||
for app.running {
|
||||
@@ -573,7 +680,7 @@ func (app *p2pApp) listen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *p2pApp) close() {
|
||||
func (app *p2pApp) Close() {
|
||||
app.running = false
|
||||
if app.listener != nil {
|
||||
app.listener.Close()
|
||||
@@ -581,36 +688,193 @@ func (app *p2pApp) close() {
|
||||
if app.listenerUDP != nil {
|
||||
app.listenerUDP.Close()
|
||||
}
|
||||
if app.DirectTunnel() != nil {
|
||||
app.DirectTunnel().closeOverlayConns(app.id)
|
||||
}
|
||||
if app.RelayTunnel() != nil {
|
||||
app.RelayTunnel().closeOverlayConns(app.id)
|
||||
}
|
||||
closeOverlayConns(app.id)
|
||||
app.wg.Wait()
|
||||
}
|
||||
|
||||
// TODO: many relay app on the same P2PTunnel will send a lot of relay heartbeat
|
||||
func (app *p2pApp) relayHeartbeatLoop() {
|
||||
func (app *p2pApp) relayHeartbeatLoop(idx int) {
|
||||
app.wg.Add(1)
|
||||
defer app.wg.Done()
|
||||
gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid:%d start", app.config.LogPeerNode(), app.id, app.rtid)
|
||||
defer gLog.Printf(LvDEBUG, "%s appid:%d relayHeartbeat to rtid%d end", app.config.LogPeerNode(), app.id, app.rtid)
|
||||
gLog.d("appid:%d %s relayHeartbeat to rtid:%d start", app.id, app.config.LogPeerNode(), app.rtid[idx])
|
||||
defer gLog.d("appid:%d %s relayHeartbeat to rtid%d end", app.id, app.config.LogPeerNode(), app.rtid[idx])
|
||||
|
||||
for app.running {
|
||||
if app.RelayTunnel() == nil || !app.RelayTunnel().isRuning() {
|
||||
if app.Tunnel(idx) == nil || !app.Tunnel(idx).isRuning() {
|
||||
time.Sleep(TunnelHeartbeatTime)
|
||||
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}
|
||||
err := app.RelayTunnel().WriteMessage(app.rtid, MsgP2P, MsgRelayHeartbeat, &req)
|
||||
err := app.Tunnel(idx).WriteMessage(app.rtid[idx], MsgP2P, MsgRelayHeartbeat, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "%s appid:%d rtid:%d write relay tunnel heartbeat error %s", app.config.LogPeerNode(), app.id, app.rtid, err)
|
||||
return
|
||||
gLog.e("appid:%d %s rtid:%d write relay tunnel heartbeat error %s", app.id, app.config.LogPeerNode(), app.rtid[idx], err)
|
||||
app.SetTunnel(nil, idx)
|
||||
continue
|
||||
}
|
||||
app.whbTime[idx] = time.Now()
|
||||
// TODO: debug relay heartbeat
|
||||
gLog.Printf(LvDEBUG, "%s appid:%d rtid:%d write relay tunnel heartbeat ok", app.config.LogPeerNode(), 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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/binary"
|
||||
@@ -9,7 +10,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
// _ "net/http/pprof"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -21,25 +27,25 @@ import (
|
||||
|
||||
var (
|
||||
v4l *v4Listener
|
||||
instance *P2PNetwork
|
||||
onceP2PNetwork sync.Once
|
||||
onceV4Listener sync.Once
|
||||
)
|
||||
|
||||
const (
|
||||
retryLimit = 20
|
||||
retryInterval = 10 * time.Second
|
||||
DefaultLoginMaxDelaySeconds = 60
|
||||
MsgQueueSize = 256
|
||||
)
|
||||
|
||||
// golang not support float64 const
|
||||
var (
|
||||
ma20 float64 = 1.0 / 20
|
||||
ma10 float64 = 1.0 / 10
|
||||
ma5 float64 = 1.0 / 5
|
||||
)
|
||||
|
||||
type NodeData struct {
|
||||
NodeID uint64
|
||||
NodeID uint64 // unused
|
||||
Data []byte
|
||||
}
|
||||
|
||||
@@ -52,6 +58,7 @@ type P2PNetwork struct {
|
||||
writeMtx sync.Mutex
|
||||
reqGatewayMtx sync.Mutex
|
||||
hbTime time.Time
|
||||
initTime time.Time
|
||||
// for sync server time
|
||||
t1 int64 // nanoSeconds
|
||||
preRtt int64 // nanoSeconds
|
||||
@@ -61,12 +68,13 @@ type P2PNetwork struct {
|
||||
msgMap sync.Map //key: nodeID
|
||||
// msgMap map[uint64]chan pushMsg //key: nodeID
|
||||
allTunnels sync.Map // key: tid
|
||||
apps sync.Map //key: config.ID(); value: *p2pApp
|
||||
apps sync.Map //key: peerid when memapp for sdwan node data indicate app/random uint64 when portforward; value: *p2pApp
|
||||
limiter *SpeedLimiter
|
||||
nodeData chan *NodeData
|
||||
nodeData chan []byte
|
||||
sdwan *p2pSDWAN
|
||||
tunnelCloseCh chan *P2PTunnel
|
||||
loginMaxDelaySeconds int
|
||||
peerNodeMutex sync.Map
|
||||
}
|
||||
|
||||
type msgCtx struct {
|
||||
@@ -74,34 +82,93 @@ type msgCtx struct {
|
||||
ts time.Time
|
||||
}
|
||||
|
||||
func P2PNetworkInstance() *P2PNetwork {
|
||||
if instance == nil {
|
||||
func P2PNetworkInstance() {
|
||||
if GNetwork == nil {
|
||||
onceP2PNetwork.Do(func() {
|
||||
instance = &P2PNetwork{
|
||||
GNetwork = &P2PNetwork{
|
||||
restartCh: make(chan bool, 1),
|
||||
tunnelCloseCh: make(chan *P2PTunnel, 100),
|
||||
nodeData: make(chan *NodeData, 10000),
|
||||
nodeData: make(chan []byte, 10000),
|
||||
online: false,
|
||||
running: true,
|
||||
limiter: newSpeedLimiter(gConf.Network.ShareBandwidth*1024*1024/8, 1),
|
||||
dt: 0,
|
||||
ddt: 0,
|
||||
loginMaxDelaySeconds: DefaultLoginMaxDelaySeconds,
|
||||
initTime: time.Now(),
|
||||
}
|
||||
instance.msgMap.Store(uint64(0), make(chan msgCtx, 50)) // for gateway
|
||||
instance.StartSDWAN()
|
||||
instance.init()
|
||||
go instance.run()
|
||||
GNetwork.msgMap.Store(uint64(0), make(chan msgCtx, MsgQueueSize)) // for gateway
|
||||
GNetwork.StartSDWAN()
|
||||
v4l = &v4Listener{port: gConf.Network.PublicIPPort}
|
||||
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() {
|
||||
for {
|
||||
instance.refreshIPv6()
|
||||
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()
|
||||
// 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() {
|
||||
@@ -113,28 +180,41 @@ func (pn *P2PNetwork) run() {
|
||||
case <-heartbeatTimer.C:
|
||||
pn.t1 = time.Now().UnixNano()
|
||||
pn.write(MsgHeartbeat, 0, "")
|
||||
case <-pn.restartCh:
|
||||
gLog.Printf(LvDEBUG, "got restart channel")
|
||||
pn.sdwan.reset()
|
||||
case isRestartDelay := <-pn.restartCh:
|
||||
gLog.i("got restart channel")
|
||||
// pn.sdwan.reset()
|
||||
pn.online = false
|
||||
pn.wgReconnect.Wait() // wait read/autorunapp goroutine end
|
||||
delay := ClientAPITimeout + time.Duration(rand.Int()%pn.loginMaxDelaySeconds)*time.Second
|
||||
time.Sleep(delay)
|
||||
waitDone := make(chan struct{})
|
||||
go func() {
|
||||
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()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
gLog.e("P2PNetwork init error:%s", err)
|
||||
}
|
||||
gConf.retryAllApp()
|
||||
|
||||
case t := <-pn.tunnelCloseCh:
|
||||
gLog.Printf(LvDEBUG, "got tunnelCloseCh %s", t.config.LogPeerNode())
|
||||
gLog.d("got tunnelCloseCh %s", t.config.LogPeerNode())
|
||||
pn.apps.Range(func(id, i interface{}) bool {
|
||||
app := i.(*p2pApp)
|
||||
if app.DirectTunnel() == t {
|
||||
app.setDirectTunnel(nil)
|
||||
}
|
||||
if app.RelayTunnel() == t {
|
||||
app.setRelayTunnel(nil)
|
||||
for i := 0; i < app.tunnelNum; i++ {
|
||||
if app.Tunnel(i) == t {
|
||||
app.SetTunnel(nil, i)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
@@ -163,46 +243,54 @@ func (pn *P2PNetwork) Connect(timeout int) bool {
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) runAll() {
|
||||
gConf.mtx.Lock() // lock for copy gConf.Apps and the modification of config(it's pointer)
|
||||
defer gConf.mtx.Unlock()
|
||||
gConf.mtx.RLock() // lock for coRUpy gConf.Apps and the modification of config(it's pointer)
|
||||
defer gConf.mtx.RUnlock()
|
||||
allApps := gConf.Apps // read a copy, other thread will modify the gConf.Apps
|
||||
for _, config := range allApps {
|
||||
if config.AppName == "" {
|
||||
config.AppName = fmt.Sprintf("%d", config.ID())
|
||||
}
|
||||
if config.Enabled == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := pn.apps.Load(config.ID()); ok {
|
||||
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
|
||||
}
|
||||
|
||||
config.peerToken = gConf.Network.Token
|
||||
gConf.mtx.Unlock() // AddApp will take a period of time, let outside modify gConf
|
||||
// config.peerToken = gConf.Network.Token // move to AddApp
|
||||
gConf.mtx.RUnlock() // AddApp will take a period of time, let outside modify gConf
|
||||
pn.AddApp(*config)
|
||||
gConf.mtx.Lock()
|
||||
gConf.mtx.RLock()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) autorunApp() {
|
||||
gLog.Println(LvINFO, "autorunApp start")
|
||||
gLog.i("autorunApp start")
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
for pn.running && pn.online {
|
||||
time.Sleep(time.Second)
|
||||
pn.runAll()
|
||||
}
|
||||
gLog.Println(LvINFO, "autorunApp end")
|
||||
gLog.i("autorunApp end")
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, string, error) {
|
||||
gLog.Printf(LvINFO, "addRelayTunnel to %s start", config.LogPeerNode())
|
||||
defer gLog.Printf(LvINFO, "addRelayTunnel to %s end", config.LogPeerNode())
|
||||
func (pn *P2PNetwork) addRelayTunnel(config AppConfig, excludeNodes string) (*P2PTunnel, uint64, string, error) {
|
||||
gLog.d("addRelayTunnel to %s start", config.LogPeerNode())
|
||||
defer gLog.d("addRelayTunnel to %s end", config.LogPeerNode())
|
||||
var relayTunnel *P2PTunnel
|
||||
relayConfig := AppConfig{
|
||||
PeerNode: config.RelayNode,
|
||||
peerToken: config.peerToken,
|
||||
relayMode: "private"}
|
||||
peerToken: config.peerToken,
|
||||
PunchPriority: config.PunchPriority,
|
||||
UnderlayProtocol: config.UnderlayProtocol,
|
||||
relayMode: "private",
|
||||
}
|
||||
if config.RelayNode != excludeNodes {
|
||||
relayConfig.PeerNode = config.RelayNode
|
||||
// TODO: verify relay node is online
|
||||
}
|
||||
if relayConfig.PeerNode == "" {
|
||||
// find existing relay tunnel
|
||||
pn.apps.Range(func(id, i interface{}) bool {
|
||||
@@ -210,16 +298,20 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri
|
||||
if app.config.PeerNode != config.PeerNode {
|
||||
return true
|
||||
}
|
||||
if app.RelayTunnel() == nil {
|
||||
return true
|
||||
for i := 1; i < app.tunnelNum; i++ { // index 1 for relay tunnel
|
||||
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
|
||||
gLog.Printf(LvDEBUG, "found existing relay tunnel %s", relayConfig.LogPeerNode())
|
||||
return false
|
||||
return true
|
||||
})
|
||||
if relayConfig.PeerNode == "" { // request relay node
|
||||
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)
|
||||
pn.reqGatewayMtx.Unlock()
|
||||
if head == nil {
|
||||
@@ -230,105 +322,154 @@ func (pn *P2PNetwork) addRelayTunnel(config AppConfig) (*P2PTunnel, uint64, stri
|
||||
return nil, 0, "", errors.New("unmarshal MsgRelayNodeRsp error")
|
||||
}
|
||||
if rsp.RelayName == "" || rsp.RelayToken == 0 {
|
||||
gLog.Printf(LvERROR, "MsgRelayNodeReq error")
|
||||
gLog.e("MsgRelayNodeReq error")
|
||||
return nil, 0, "", errors.New("MsgRelayNodeReq error")
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "got relay node:%s", relayConfig.LogPeerNode())
|
||||
|
||||
relayConfig.PeerNode = rsp.RelayName
|
||||
relayConfig.peerToken = rsp.RelayToken
|
||||
relayConfig.relayMode = rsp.Mode
|
||||
gLog.d("got relay node:%s", relayConfig.LogPeerNode())
|
||||
}
|
||||
|
||||
}
|
||||
///
|
||||
t, err := pn.addDirectTunnel(relayConfig, 0)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "direct connect error:", err)
|
||||
return nil, 0, "", ErrConnectRelayNode // relay offline will stop retry
|
||||
if relayTunnel == nil {
|
||||
var err error
|
||||
relayTunnel, err = pn.addDirectTunnel(relayConfig, 0, nil)
|
||||
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
|
||||
req := AddRelayTunnelReq{
|
||||
From: gConf.Network.Node,
|
||||
RelayName: relayConfig.PeerNode,
|
||||
RelayToken: relayConfig.peerToken,
|
||||
RelayMode: relayConfig.relayMode,
|
||||
RelayTunnelID: t.id,
|
||||
From: gConf.Network.Node,
|
||||
RelayName: relayConfig.PeerNode,
|
||||
RelayToken: relayConfig.peerToken,
|
||||
RelayMode: relayConfig.relayMode,
|
||||
RelayTunnelID: relayTunnel.id,
|
||||
PunchPriority: relayConfig.PunchPriority,
|
||||
UnderlayProtocol: relayConfig.UnderlayProtocol,
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "push %s the relay node(%s)", config.LogPeerNode(), relayConfig.LogPeerNode())
|
||||
|
||||
gLog.d("push %s the relay node(%s)", config.LogPeerNode(), relayConfig.LogPeerNode())
|
||||
pn.push(config.PeerNode, MsgPushAddRelayTunnelReq, &req)
|
||||
|
||||
// wait relay ready
|
||||
head, body := pn.read(config.PeerNode, MsgPush, MsgPushAddRelayTunnelRsp, PeerAddRelayTimeount)
|
||||
if head == nil {
|
||||
gLog.Printf(LvERROR, "read MsgPushAddRelayTunnelRsp error")
|
||||
gLog.e("read MsgPushAddRelayTunnelRsp error")
|
||||
return nil, 0, "", errors.New("read MsgPushAddRelayTunnelRsp error")
|
||||
}
|
||||
rspID := TunnelMsg{}
|
||||
if err = json.Unmarshal(body, &rspID); err != nil {
|
||||
gLog.Println(LvDEBUG, ErrPeerConnectRelay)
|
||||
if err := json.Unmarshal(body, &rspID); err != nil {
|
||||
gLog.d("Unmarshal error:%s", ErrPeerConnectRelay)
|
||||
return nil, 0, "", ErrPeerConnectRelay
|
||||
}
|
||||
return t, rspID.ID, relayConfig.relayMode, err
|
||||
return relayTunnel, rspID.ID, relayConfig.relayMode, nil
|
||||
}
|
||||
|
||||
// use *AppConfig to save status
|
||||
func (pn *P2PNetwork) AddApp(config AppConfig) error {
|
||||
gLog.Printf(LvINFO, "addApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
|
||||
defer gLog.Printf(LvINFO, "addApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
|
||||
config.peerToken = gConf.Network.Token
|
||||
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 {
|
||||
return errors.New("P2PNetwork offline")
|
||||
}
|
||||
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?
|
||||
if _, ok := pn.apps.Load(config.ID()); ok {
|
||||
return errors.New("P2PApp already exist")
|
||||
existApp := pn.findApp(&config)
|
||||
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{
|
||||
// tunnel: t,
|
||||
id: rand.Uint64(),
|
||||
key: rand.Uint64(),
|
||||
config: config,
|
||||
iptree: NewIPTree(config.Whitelist),
|
||||
running: true,
|
||||
hbTimeRelay: time.Now(),
|
||||
id: rand.Uint64(),
|
||||
key: rand.Uint64(),
|
||||
config: config,
|
||||
iptree: NewIPTree(config.Whitelist),
|
||||
running: true,
|
||||
// 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 {
|
||||
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)
|
||||
gLog.Printf(LvDEBUG, "Store app %d", config.ID())
|
||||
go app.checkP2PTunnel()
|
||||
app.Start(true)
|
||||
pn.apps.Store(app.id, &app) // TODO: store appid
|
||||
gLog.d("Store app %d", app.id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) DeleteApp(config AppConfig) {
|
||||
gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d start", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
|
||||
defer gLog.Printf(LvINFO, "DeleteApp %s to %s:%s:%d end", config.AppName, config.LogPeerNode(), config.DstHost, config.DstPort)
|
||||
// close the apps of this config
|
||||
i, ok := pn.apps.Load(config.ID())
|
||||
if ok {
|
||||
app := i.(*p2pApp)
|
||||
gLog.Printf(LvINFO, "app %s exist, delete it", app.config.AppName)
|
||||
app.close()
|
||||
pn.apps.Delete(config.ID())
|
||||
}
|
||||
func (pn *P2PNetwork) findApp(config *AppConfig) (app *p2pApp) {
|
||||
pn.apps.Range(func(id, i interface{}) bool {
|
||||
tempApp := i.(*p2pApp)
|
||||
if config.SrcPort == 0 { // sdwan app
|
||||
if tempApp.config.SrcPort == config.SrcPort &&
|
||||
tempApp.config.PeerNode == config.PeerNode {
|
||||
app = tempApp
|
||||
return false
|
||||
}
|
||||
} 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
|
||||
// find existing tunnel to peer
|
||||
pn.allTunnels.Range(func(id, i interface{}) bool {
|
||||
tmpt := i.(*P2PTunnel)
|
||||
if tmpt.config.PeerNode == peerNode {
|
||||
gLog.Println(LvINFO, "tunnel already exist ", peerNode)
|
||||
if tmpt.config.PeerNode == peerNode && tmpt != ignoredTunnel {
|
||||
gLog.d("tunnel already exist %s", tmpt.config.LogPeerNode())
|
||||
isActive := tmpt.checkActive()
|
||||
// inactive, close it
|
||||
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()
|
||||
} else {
|
||||
t = tmpt
|
||||
@@ -340,65 +481,90 @@ func (pn *P2PNetwork) findTunnel(peerNode string) (t *P2PTunnel) {
|
||||
return t
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunnel, err error) {
|
||||
gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d start", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
|
||||
defer gLog.Printf(LvDEBUG, "addDirectTunnel %s%d to %s:%s:%d tid:%d end", config.Protocol, config.SrcPort, config.LogPeerNode(), config.DstHost, config.DstPort, tid)
|
||||
func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
|
||||
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.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
|
||||
// client side tid=0, assign random uint64
|
||||
if tid == 0 {
|
||||
tid = rand.Uint64()
|
||||
isClient = true
|
||||
}
|
||||
if _, ok := pn.msgMap.Load(NodeNameToID(config.PeerNode)); !ok {
|
||||
pn.msgMap.Store(NodeNameToID(config.PeerNode), make(chan msgCtx, 50))
|
||||
|
||||
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
|
||||
if !isClient {
|
||||
t, err = pn.newTunnel(config, tid, isClient)
|
||||
t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel)
|
||||
return t, err // always return
|
||||
}
|
||||
|
||||
// client side
|
||||
// peer info
|
||||
initErr := pn.requestPeerInfo(&config)
|
||||
if initErr != nil {
|
||||
gLog.Printf(LvERROR, "%s init error:%s", config.LogPeerNode(), initErr)
|
||||
|
||||
gLog.w("%s init error:%s", config.LogPeerNode(), initErr)
|
||||
return nil, initErr
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "config.peerNode=%s,config.peerVersion=%s,config.peerIP=%s,config.peerLanIP=%s,gConf.Network.publicIP=%s,config.peerIPv6=%s,config.hasIPv4=%d,config.hasUPNPorNATPMP=%d,gConf.Network.hasIPv4=%d,gConf.Network.hasUPNPorNATPMP=%d,config.peerNatType=%d,gConf.Network.natType=%d,",
|
||||
config.LogPeerNode(), config.peerVersion, config.peerIP, config.peerLanIP, gConf.Network.publicIP, config.peerIPv6, config.hasIPv4, config.hasUPNPorNATPMP, gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, config.peerNatType, gConf.Network.natType)
|
||||
|
||||
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
|
||||
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.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
|
||||
}
|
||||
}
|
||||
thisTunnelForcev6 := false
|
||||
// try TCP6
|
||||
if IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) {
|
||||
gLog.Println(LvINFO, "try TCP6")
|
||||
if !strings.Contains(gConf.Network.Node, "openp2pS2STest") && IsIPv6(config.peerIPv6) && IsIPv6(gConf.IPv6()) && (config.PunchPriority&PunchPriorityUDPOnly == 0) {
|
||||
gLog.i("try TCP6")
|
||||
config.linkMode = LinkModeTCP6
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// try UDP6? maybe no
|
||||
|
||||
// try TCP4
|
||||
if config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
gLog.Println(LvINFO, "try TCP4")
|
||||
config.linkMode = LinkModeTCP4
|
||||
// try IPv4
|
||||
if !thisTunnelForcev6 && !strings.Contains(gConf.Network.Node, "openp2pS2STest") && (config.hasIPv4 == 1 || gConf.Network.hasIPv4 == 1 || config.hasUPNPorNATPMP == 1 || gConf.Network.hasUPNPorNATPMP == 1) {
|
||||
if config.PunchPriority&PunchPriorityUDPOnly != 0 && compareVersion(config.peerVersion, SupportUDP4DirectVersion) >= 0 {
|
||||
gLog.i("try UDP4")
|
||||
config.linkMode = LinkModeUDP4
|
||||
} else {
|
||||
gLog.i("try TCP4")
|
||||
config.linkMode = LinkModeTCP4
|
||||
}
|
||||
|
||||
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
config.isUnderlayServer = 1
|
||||
} else {
|
||||
config.isUnderlayServer = 0
|
||||
}
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
@@ -406,16 +572,16 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
|
||||
var primaryPunchFunc func() (*P2PTunnel, error)
|
||||
var secondaryPunchFunc func() (*P2PTunnel, error)
|
||||
funcUDP := func() (t *P2PTunnel, err error) {
|
||||
if config.PunchPriority&PunchPriorityUDPDisable != 0 {
|
||||
if thisTunnelForcev6 || config.PunchPriority&PunchPriorityTCPOnly != 0 {
|
||||
return
|
||||
}
|
||||
// try UDPPunch
|
||||
for i := 0; i < Cone2ConeUDPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
|
||||
if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
|
||||
gLog.Println(LvINFO, "try UDP4 Punch")
|
||||
gLog.i("try UDP4 Punch")
|
||||
config.linkMode = LinkModeUDPPunch
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -426,17 +592,17 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
|
||||
return
|
||||
}
|
||||
funcTCP := func() (t *P2PTunnel, err error) {
|
||||
if config.PunchPriority&PunchPriorityTCPDisable != 0 {
|
||||
if thisTunnelForcev6 || config.PunchPriority&PunchPriorityUDPOnly != 0 {
|
||||
return
|
||||
}
|
||||
// try TCPPunch
|
||||
for i := 0; i < Cone2ConeTCPPunchMaxRetry; i++ { // when both 2 nats has restrict firewall, simultaneous punching needs to be very precise, it takes a few tries
|
||||
if config.peerNatType == NATCone || gConf.Network.natType == NATCone {
|
||||
gLog.Println(LvINFO, "try TCP4 Punch")
|
||||
gLog.i("try TCP4 Punch")
|
||||
config.linkMode = LinkModeTCPPunch
|
||||
config.isUnderlayServer = 0
|
||||
if t, err = pn.newTunnel(config, tid, isClient); err == nil {
|
||||
gLog.Println(LvINFO, "TCP4 Punch ok")
|
||||
if t, err = pn.newTunnel(config, tid, isClient, ignoredTunnel); err == nil {
|
||||
gLog.i("TCP4 Punch ok")
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
@@ -447,8 +613,8 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
|
||||
primaryPunchFunc = funcTCP
|
||||
secondaryPunchFunc = funcUDP
|
||||
} else {
|
||||
primaryPunchFunc = funcTCP
|
||||
secondaryPunchFunc = funcUDP
|
||||
primaryPunchFunc = funcUDP
|
||||
secondaryPunchFunc = funcTCP
|
||||
}
|
||||
if t, err = primaryPunchFunc(); t != nil && err == nil {
|
||||
return t, err
|
||||
@@ -461,9 +627,9 @@ func (pn *P2PNetwork) addDirectTunnel(config AppConfig, tid uint64) (t *P2PTunne
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t *P2PTunnel, err error) {
|
||||
if isClient { // only client side find existing tunnel
|
||||
if existTunnel := pn.findTunnel(config.PeerNode); existTunnel != nil {
|
||||
func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool, ignoredTunnel *P2PTunnel) (t *P2PTunnel, err error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -472,40 +638,70 @@ func (pn *P2PNetwork) newTunnel(config AppConfig, tid uint64, isClient bool) (t
|
||||
config: config,
|
||||
id: tid,
|
||||
writeData: make(chan []byte, WriteDataChanSize),
|
||||
writeDataSmall: make(chan []byte, WriteDataChanSize/30),
|
||||
writeDataSmall: make(chan []byte, WriteDataChanSize),
|
||||
}
|
||||
t.initPort()
|
||||
if isClient {
|
||||
if err = t.connect(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel connect error:", err)
|
||||
gLog.d("p2pTunnel connect error:%s", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if err = t.listen(); err != nil {
|
||||
gLog.Println(LvERROR, "p2pTunnel listen error:", err)
|
||||
gLog.d("p2pTunnel listen error:%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// store it when success
|
||||
gLog.Printf(LvDEBUG, "store tunnel %d", tid)
|
||||
gLog.d("store tunnel %d", tid)
|
||||
pn.allTunnels.Store(tid, t)
|
||||
return
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) init() error {
|
||||
gLog.Println(LvINFO, "P2PNetwork init start")
|
||||
defer gLog.Println(LvINFO, "P2PNetwork init end")
|
||||
gLog.i("P2PNetwork init start")
|
||||
defer gLog.i("P2PNetwork init end")
|
||||
pn.initTime = time.Now()
|
||||
pn.wgReconnect.Add(1)
|
||||
defer pn.wgReconnect.Done()
|
||||
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 {
|
||||
// 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 {
|
||||
gLog.Println(LvDEBUG, "detect NAT type error:", err)
|
||||
gLog.d("detect NAT type error:%s", err)
|
||||
break
|
||||
}
|
||||
if gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 { // if already has ipv4 or upnp no need test again
|
||||
gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.TCPPort)
|
||||
gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP = publicIPTest(gConf.Network.publicIP, gConf.Network.PublicIPPort)
|
||||
}
|
||||
|
||||
// for testcase
|
||||
@@ -513,49 +709,66 @@ func (pn *P2PNetwork) init() error {
|
||||
gConf.Network.natType = NATSymmetric
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pS2STest debug")
|
||||
gLog.i("openp2pS2STest debug")
|
||||
|
||||
}
|
||||
if strings.Contains(gConf.Network.Node, "openp2pC2CTest") {
|
||||
gConf.Network.natType = NATCone
|
||||
gConf.Network.hasIPv4 = 0
|
||||
gConf.Network.hasUPNPorNATPMP = 0
|
||||
gLog.Println(LvINFO, "openp2pC2CTest debug")
|
||||
gLog.i("openp2pC2CTest debug")
|
||||
}
|
||||
|
||||
if gConf.Network.hasIPv4 == 1 || gConf.Network.hasUPNPorNATPMP == 1 {
|
||||
onceV4Listener.Do(func() {
|
||||
v4l = &v4Listener{port: gConf.Network.TCPPort}
|
||||
go v4l.start()
|
||||
})
|
||||
}
|
||||
gLog.Printf(LvINFO, "hasIPv4:%d, UPNP:%d, NAT type:%d, publicIP:%s", gConf.Network.hasIPv4, gConf.Network.hasUPNPorNATPMP, gConf.Network.natType, gConf.Network.publicIP)
|
||||
gatewayURL := fmt.Sprintf("%s:%d", gConf.Network.ServerHost, gConf.Network.ServerPort)
|
||||
// public ip and intranet connect
|
||||
v4l.start()
|
||||
pn.refreshIPv6()
|
||||
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)
|
||||
uri := "/api/v1/login"
|
||||
caCertPool, errCert := x509.SystemCertPool()
|
||||
if errCert != nil {
|
||||
gLog.Println(LvERROR, "Failed to load system root CAs:", errCert)
|
||||
gLog.e("Failed to load system root CAs:%s", errCert)
|
||||
caCertPool = x509.NewCertPool()
|
||||
}
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
|
||||
config := tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
InsecureSkipVerify: false} // let's encrypt root cert "DST Root CA X3" expired at 2021/09/29. many old system(windows server 2008 etc) will not trust our cert
|
||||
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.HandshakeTimeout = ClientAPITimeout
|
||||
websocket.DefaultDialer.HandshakeTimeout = ClientAPITimeout * 3
|
||||
u := url.URL{Scheme: "wss", Host: gatewayURL, Path: uri}
|
||||
q := u.Query()
|
||||
q.Add("node", gConf.Network.Node)
|
||||
q.Add("token", fmt.Sprintf("%d", gConf.Network.Token))
|
||||
q.Add("version", OpenP2PVersion)
|
||||
q.Add("ipv4", gConf.Network.publicIP)
|
||||
q.Add("ipv6", gConf.IPv6())
|
||||
q.Add("nattype", fmt.Sprintf("%d", gConf.Network.natType))
|
||||
q.Add("sharebandwidth", fmt.Sprintf("%d", gConf.Network.ShareBandwidth))
|
||||
u.RawQuery = q.Encode()
|
||||
var ws *websocket.Conn
|
||||
ws, _, err = websocket.DefaultDialer.Dial(u.String(), nil)
|
||||
d := websocket.Dialer{
|
||||
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 {
|
||||
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
|
||||
}
|
||||
pn.running = true
|
||||
@@ -565,7 +778,7 @@ func (pn *P2PNetwork) init() error {
|
||||
if len(localAddr) == 2 {
|
||||
gConf.Network.localIP = localAddr[0]
|
||||
} else {
|
||||
err = errors.New("get local ip failed")
|
||||
gLog.e("get local ip failed:%s", ws.LocalAddr().String())
|
||||
break
|
||||
}
|
||||
go pn.readLoop()
|
||||
@@ -577,11 +790,12 @@ func (pn *P2PNetwork) init() error {
|
||||
LanIP: gConf.Network.localIP,
|
||||
OS: gConf.Network.os,
|
||||
HasIPv4: gConf.Network.hasIPv4,
|
||||
PublicIPPort: gConf.Network.PublicIPPort,
|
||||
HasUPNPorNATPMP: gConf.Network.hasUPNPorNATPMP,
|
||||
Version: OpenP2PVersion,
|
||||
}
|
||||
rsp := netInfo()
|
||||
gLog.Println(LvDEBUG, "netinfo:", rsp)
|
||||
gLog.d("netinfo:%v", rsp)
|
||||
if rsp != nil && rsp.Country != "" {
|
||||
if IsIPv6(rsp.IP.String()) {
|
||||
gConf.setIPv6(rsp.IP.String())
|
||||
@@ -591,18 +805,28 @@ func (pn *P2PNetwork) init() error {
|
||||
pn.refreshIPv6()
|
||||
}
|
||||
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()
|
||||
pn.write(MsgSDWAN, MsgSDWANInfoReq, nil)
|
||||
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
||||
initOK = true
|
||||
gLog.d("P2PNetwork init ok")
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
// init failed, retry
|
||||
pn.close()
|
||||
gLog.Println(LvERROR, "P2PNetwork init error:", err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -610,37 +834,43 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
head := openP2PHeader{}
|
||||
err := binary.Read(bytes.NewReader(msg[:openP2PHeaderSize]), binary.LittleEndian, &head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handleMessage error:", err)
|
||||
gLog.e("handleMessage error:%s", err)
|
||||
return
|
||||
}
|
||||
gLog.dev("handleMessage %+v", head)
|
||||
switch head.MainType {
|
||||
case MsgLogin:
|
||||
// gLog.Println(LevelINFO,string(msg))
|
||||
rsp := LoginRsp{}
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(rsp), err)
|
||||
gLog.e("wrong %v:%s", reflect.TypeOf(rsp), err)
|
||||
return
|
||||
}
|
||||
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
|
||||
} else {
|
||||
gConf.setToken(rsp.Token)
|
||||
gConf.setUser(rsp.User)
|
||||
gConf.setForcev6(rsp.Forcev6 != 0)
|
||||
if rsp.PublicIPPort != 0 {
|
||||
gConf.Network.PublicIPPort = rsp.PublicIPPort
|
||||
}
|
||||
if len(rsp.Node) >= MinNodeNameLen {
|
||||
gConf.setNode(rsp.Node)
|
||||
}
|
||||
gConf.save()
|
||||
if rsp.LoginMaxDelay > 0 {
|
||||
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:
|
||||
gLog.Printf(LvDev, "P2PNetwork heartbeat ok")
|
||||
gLog.dev("P2PNetwork heartbeat ok")
|
||||
pn.hbTime = time.Now()
|
||||
rtt := pn.hbTime.UnixNano() - pn.t1
|
||||
if rtt > int64(PunchTsDelay) || (pn.preRtt > 0 && rtt > pn.preRtt*5) {
|
||||
gLog.Printf(LvINFO, "rtt=%d too large ignore", rtt)
|
||||
gLog.d("rtt=%dms too large ignore", rtt/int64(time.Millisecond))
|
||||
return // invalid hb rsp
|
||||
}
|
||||
pn.preRtt = rtt
|
||||
@@ -658,7 +888,7 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
}
|
||||
}
|
||||
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:
|
||||
handlePush(head.SubType, msg)
|
||||
case MsgSDWAN:
|
||||
@@ -667,7 +897,11 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
i, ok := pn.msgMap.Load(uint64(0))
|
||||
if ok {
|
||||
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
|
||||
@@ -675,20 +909,53 @@ func (pn *P2PNetwork) handleMessage(msg []byte) {
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) readLoop() {
|
||||
gLog.Printf(LvDEBUG, "P2PNetwork readLoop start")
|
||||
gLog.d("P2PNetwork readLoop start")
|
||||
pn.wgReconnect.Add(1)
|
||||
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 {
|
||||
pn.conn.SetReadDeadline(time.Now().Add(NetworkHeartbeatTime + 10*time.Second))
|
||||
_, msg, err := pn.conn.ReadMessage()
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "P2PNetwork read error:%s", err)
|
||||
pn.close()
|
||||
break
|
||||
select {
|
||||
case result := <-readChan:
|
||||
if result == nil {
|
||||
// 处理错误
|
||||
pn.close(false)
|
||||
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 {
|
||||
@@ -701,9 +968,10 @@ func (pn *P2PNetwork) write(mainType uint16, subType uint16, packet interface{})
|
||||
}
|
||||
pn.writeMtx.Lock()
|
||||
defer pn.writeMtx.Unlock()
|
||||
pn.conn.SetWriteDeadline(time.Now().Add(NetworkHeartbeatTime))
|
||||
if err = pn.conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
|
||||
gLog.Printf(LvERROR, "write msgType %d,%d error:%s", mainType, subType, err)
|
||||
pn.close()
|
||||
gLog.e("write msgType %d,%d error:%s", mainType, subType, err)
|
||||
pn.close(false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -719,13 +987,13 @@ func (pn *P2PNetwork) relay(to uint64, body []byte) error {
|
||||
}
|
||||
var err error
|
||||
if err = tunnel.conn.WriteBuffer(body); err != nil {
|
||||
gLog.Printf(LvERROR, "relay to %d len=%d error:%s", to, len(body), err)
|
||||
gLog.dev("relay to %d len=%d error:%s", to, len(body), err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error {
|
||||
// gLog.Printf(LvDEBUG, "push msgType %d to %s", subType, to)
|
||||
// gLog.d("push msgType %d to %s", subType, to)
|
||||
if !pn.online {
|
||||
return errors.New("client offline")
|
||||
}
|
||||
@@ -746,14 +1014,15 @@ func (pn *P2PNetwork) push(to string, subType uint16, packet interface{}) error
|
||||
pushMsg = append(pushMsg, data...)
|
||||
pn.writeMtx.Lock()
|
||||
defer pn.writeMtx.Unlock()
|
||||
pn.conn.SetWriteDeadline(time.Now().Add(NetworkHeartbeatTime))
|
||||
if err = pn.conn.WriteMessage(websocket.BinaryMessage, pushMsg); err != nil {
|
||||
gLog.Printf(LvERROR, "push to %s error:%s", to, err)
|
||||
pn.close()
|
||||
gLog.e("push to %s error:%s", to, err)
|
||||
pn.close(false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) close() {
|
||||
func (pn *P2PNetwork) close(isRestartDelay bool) {
|
||||
if pn.running {
|
||||
if pn.conn != nil {
|
||||
pn.conn.Close()
|
||||
@@ -761,7 +1030,7 @@ func (pn *P2PNetwork) close() {
|
||||
pn.running = false
|
||||
}
|
||||
select {
|
||||
case pn.restartCh <- true:
|
||||
case pn.restartCh <- isRestartDelay:
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -775,30 +1044,30 @@ func (pn *P2PNetwork) read(node string, mainType uint16, subType uint16, timeout
|
||||
}
|
||||
i, ok := pn.msgMap.Load(nodeID)
|
||||
if !ok {
|
||||
gLog.Printf(LvERROR, "read msg error: %s not found", node)
|
||||
gLog.e("read msg error: %s not found", node)
|
||||
return
|
||||
}
|
||||
ch := i.(chan msgCtx)
|
||||
for {
|
||||
select {
|
||||
case <-time.After(timeout):
|
||||
gLog.Printf(LvERROR, "read msg error %d:%d timeout", mainType, subType)
|
||||
gLog.e("read msg error %d:%d timeout", mainType, subType)
|
||||
return
|
||||
case msg := <-ch:
|
||||
head = &openP2PHeader{}
|
||||
err := binary.Read(bytes.NewReader(msg.data[:openP2PHeaderSize]), binary.LittleEndian, head)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "read msg error:", err)
|
||||
gLog.e("read msg error:%s", err)
|
||||
break
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
time.Sleep(time.Second)
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
continue
|
||||
}
|
||||
if mainType == MsgPush {
|
||||
@@ -811,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 {
|
||||
app := i.(*p2pApp)
|
||||
if app.id == appID {
|
||||
app.updateHeartbeat()
|
||||
if updateRelayTs {
|
||||
app.UpdateRelayHeartbeatTs(rtid)
|
||||
} else {
|
||||
app.UpdateHeartbeat(rtid)
|
||||
}
|
||||
|
||||
}
|
||||
return true
|
||||
})
|
||||
@@ -823,23 +1097,34 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
|
||||
|
||||
// ipv6 will expired need to refresh.
|
||||
func (pn *P2PNetwork) refreshIPv6() {
|
||||
|
||||
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}
|
||||
r, err := client.Get("http://ipv6.ddnspod.com/")
|
||||
r, err := client.Get(url)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "refreshIPv6 error:", err)
|
||||
gLog.d("refreshIPv6 error:%s", err)
|
||||
continue
|
||||
}
|
||||
defer r.Body.Close()
|
||||
buf := make([]byte, 1024)
|
||||
n, err := r.Body.Read(buf)
|
||||
if n <= 0 {
|
||||
gLog.Println(LvINFO, "refreshIPv6 error:", err, n)
|
||||
gLog.e("refreshIPv6 error:%s", err)
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -853,7 +1138,7 @@ func (pn *P2PNetwork) requestPeerInfo(config *AppConfig) error {
|
||||
head, body := pn.read("", MsgQuery, MsgQueryPeerInfoRsp, ClientAPITimeout)
|
||||
pn.reqGatewayMtx.Unlock()
|
||||
if head == nil {
|
||||
gLog.Println(LvERROR, "requestPeerInfo error")
|
||||
gLog.e("requestPeerInfo error")
|
||||
return ErrNetwork // network error, should not be ErrPeerOffline
|
||||
}
|
||||
rsp := QueryPeerInfoRsp{}
|
||||
@@ -883,7 +1168,7 @@ func (pn *P2PNetwork) StartSDWAN() {
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
peerNodeID := fmt.Sprintf("%d", NodeNameToID(node))
|
||||
@@ -893,6 +1178,7 @@ func (pn *P2PNetwork) ConnectNode(node string) error {
|
||||
config.PeerNode = node
|
||||
sdwan := gConf.getSDWAN()
|
||||
config.PunchPriority = int(sdwan.PunchPriority)
|
||||
// config.UnderlayProtocol = "kcp"
|
||||
if node != sdwan.CentralNode && gConf.Network.Node != sdwan.CentralNode { // neither is centralnode
|
||||
config.RelayNode = sdwan.CentralNode
|
||||
config.ForceRelay = int(sdwan.ForceRelay)
|
||||
@@ -912,22 +1198,24 @@ func (pn *P2PNetwork) WriteNode(nodeID uint64, buff []byte) error {
|
||||
}
|
||||
var err error
|
||||
app := i.(*p2pApp)
|
||||
if app.Tunnel() == nil {
|
||||
return errors.New("peer tunnel nil")
|
||||
}
|
||||
// TODO: move to app.write
|
||||
gLog.Printf(LvDev, "%d tunnel write node data bodylen=%d, relay=%t", app.Tunnel().id, len(buff), !app.isDirect())
|
||||
if app.isDirect() { // direct
|
||||
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgNodeData, buff)
|
||||
} else { // relay
|
||||
fromNodeIDHead := new(bytes.Buffer)
|
||||
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
|
||||
all := app.RelayHead().Bytes()
|
||||
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
|
||||
all = append(all, fromNodeIDHead.Bytes()...)
|
||||
all = append(all, buff...)
|
||||
app.Tunnel().asyncWriteNodeData(MsgP2P, MsgRelayData, all)
|
||||
err = app.WriteNodeDataMP(buff)
|
||||
if err != nil {
|
||||
gLog.dev("appID:%d WriteNodeDataMP %s", app.id, err)
|
||||
}
|
||||
// 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
|
||||
}
|
||||
@@ -942,32 +1230,23 @@ func (pn *P2PNetwork) WriteBroadcast(buff []byte) error {
|
||||
// binary.BigEndian.PutUint16(buff[10:12], ipChecksum)
|
||||
// binary.BigEndian.PutUint16(buff[26:28], 0x082e)
|
||||
app := i.(*p2pApp)
|
||||
if app.Tunnel() == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if app.config.SrcPort != 0 { // normal portmap app
|
||||
return true
|
||||
}
|
||||
if app.config.peerIP == gConf.Network.publicIP { // mostly in a lan
|
||||
return true
|
||||
}
|
||||
if app.isDirect() { // direct
|
||||
app.Tunnel().conn.WriteBytes(MsgP2P, MsgNodeData, buff)
|
||||
} else { // relay
|
||||
fromNodeIDHead := new(bytes.Buffer)
|
||||
binary.Write(fromNodeIDHead, binary.LittleEndian, gConf.nodeID())
|
||||
all := app.RelayHead().Bytes()
|
||||
all = append(all, encodeHeader(MsgP2P, MsgRelayNodeData, uint32(len(buff)+overlayHeaderSize))...)
|
||||
all = append(all, fromNodeIDHead.Bytes()...)
|
||||
all = append(all, buff...)
|
||||
app.Tunnel().conn.WriteBytes(MsgP2P, MsgRelayData, all)
|
||||
err := app.WriteNodeDataMP(buff)
|
||||
if err != nil {
|
||||
gLog.dev("appID:%d WriteNodeDataMP %s", app.id, err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pn *P2PNetwork) ReadNode(tm time.Duration) *NodeData {
|
||||
func (pn *P2PNetwork) ReadNode(tm time.Duration) []byte {
|
||||
select {
|
||||
case nd := <-pn.nodeData:
|
||||
return nd
|
||||
|
||||
@@ -2,6 +2,7 @@ package openp2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
@@ -14,22 +15,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const WriteDataChanSize int = 3000
|
||||
const WriteDataChanSize int = 8192
|
||||
|
||||
var buildTunnelMtx sync.Mutex
|
||||
|
||||
const (
|
||||
StatusIdle = 0
|
||||
StatusWriting = 1
|
||||
)
|
||||
|
||||
type P2PTunnel struct {
|
||||
conn underlay
|
||||
hbTime time.Time
|
||||
hbMtx sync.Mutex
|
||||
whbTime time.Time
|
||||
config AppConfig
|
||||
localHoleAddr *net.UDPAddr // local hole address
|
||||
remoteHoleAddr *net.UDPAddr // remote hole address
|
||||
overlayConns sync.Map // both TCP and UDP
|
||||
id uint64 // client side alloc rand.uint64 = server side
|
||||
|
||||
running bool
|
||||
runMtx sync.Mutex
|
||||
tunnelServer bool // different from underlayServer
|
||||
coneLocalPort int
|
||||
coneNatPort int
|
||||
linkModeWeb string // use config.linkmode
|
||||
@@ -40,30 +46,33 @@ type P2PTunnel struct {
|
||||
|
||||
func (t *P2PTunnel) initPort() {
|
||||
t.running = true
|
||||
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param
|
||||
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 || t.config.linkMode == LinkModeIntranet {
|
||||
t.coneLocalPort = gConf.Network.TCPPort
|
||||
t.coneNatPort = gConf.Network.TCPPort // symmetric doesn't need coneNatPort
|
||||
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 == LinkModeUDP4 || t.config.linkMode == LinkModeIntranet {
|
||||
t.coneLocalPort = gConf.Network.PublicIPPort
|
||||
t.coneNatPort = gConf.Network.PublicIPPort // symmetric doesn't need coneNatPort
|
||||
}
|
||||
if t.config.linkMode == LinkModeUDPPunch {
|
||||
// prepare one random cone hole manually
|
||||
_, natPort, _ := natTest(gConf.Network.ServerHost, gConf.Network.UDPPort1, localPort)
|
||||
_, natPort, _ := natDetectUDP(gConf.Network.ServerIP, NATDetectPort1, localPort)
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = natPort
|
||||
}
|
||||
if t.config.linkMode == LinkModeTCPPunch {
|
||||
// prepare one random cone hole by system automatically
|
||||
_, natPort, localPort2 := natTCP(gConf.Network.ServerHost, IfconfigPort1)
|
||||
_, natPort, localPort2, _ := natDetectTCP(gConf.Network.ServerIP, NATDetectPort1, 0)
|
||||
t.coneLocalPort = localPort2
|
||||
t.coneNatPort = natPort
|
||||
}
|
||||
if t.config.linkMode == LinkModeTCP6 && compareVersion(t.config.peerVersion, IPv6PunchVersion) >= 0 {
|
||||
t.coneLocalPort = localPort
|
||||
t.coneNatPort = localPort
|
||||
}
|
||||
t.localHoleAddr = &net.UDPAddr{IP: net.ParseIP(gConf.Network.localIP), Port: t.coneLocalPort}
|
||||
gLog.Printf(LvDEBUG, "prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
|
||||
gLog.d("prepare punching port %d:%d", t.coneLocalPort, t.coneNatPort)
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connect() error {
|
||||
gLog.Printf(LvDEBUG, "start p2pTunnel to %s ", t.config.LogPeerNode())
|
||||
t.tunnelServer = false
|
||||
gLog.d("start p2pTunnel to %s ", t.config.LogPeerNode())
|
||||
appKey := uint64(0)
|
||||
req := PushConnectReq{
|
||||
Token: t.config.peerToken,
|
||||
@@ -91,7 +100,7 @@ func (t *P2PTunnel) connect() error {
|
||||
}
|
||||
rsp := PushConnectRsp{}
|
||||
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
|
||||
}
|
||||
// gLog.Println(LevelINFO, rsp)
|
||||
@@ -108,7 +117,7 @@ func (t *P2PTunnel) connect() error {
|
||||
t.punchTs = rsp.PunchTs
|
||||
err := t.start()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "handshake error:", err)
|
||||
gLog.d("handshake error:%s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -133,7 +142,7 @@ func (t *P2PTunnel) isActive() bool {
|
||||
defer t.hbMtx.Unlock()
|
||||
res := time.Now().Before(t.hbTime.Add(TunnelHeartbeatTime * 2))
|
||||
if !res {
|
||||
gLog.Printf(LvDEBUG, "%d tunnel isActive false", t.id)
|
||||
gLog.d("%d tunnel isActive false", t.id)
|
||||
}
|
||||
return res
|
||||
}
|
||||
@@ -154,7 +163,7 @@ func (t *P2PTunnel) checkActive() bool {
|
||||
t.hbMtx.Unlock()
|
||||
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
|
||||
}
|
||||
|
||||
@@ -169,7 +178,7 @@ func (t *P2PTunnel) close() {
|
||||
t.conn.Close()
|
||||
}
|
||||
GNetwork.allTunnels.Delete(t.id)
|
||||
gLog.Printf(LvINFO, "%d p2ptunnel close %s ", t.id, t.config.LogPeerNode())
|
||||
gLog.i("%d p2ptunnel close %s ", t.id, t.config.LogPeerNode())
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) start() error {
|
||||
@@ -180,7 +189,7 @@ func (t *P2PTunnel) start() error {
|
||||
}
|
||||
err := t.connectUnderlay()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, err)
|
||||
gLog.d("connectUnderlay error:%s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -195,16 +204,16 @@ func (t *P2PTunnel) handshake() error {
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
gLog.d("sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
}
|
||||
gLog.Println(LvDEBUG, "handshake to ", t.config.LogPeerNode())
|
||||
gLog.d("handshake to %s", t.config.LogPeerNode())
|
||||
var err error
|
||||
if gConf.Network.natType == NATCone && t.config.peerNatType == NATCone {
|
||||
err = handshakeC2C(t)
|
||||
@@ -219,19 +228,25 @@ func (t *P2PTunnel) handshake() error {
|
||||
return errors.New("unknown error")
|
||||
}
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "punch handshake error:", err)
|
||||
gLog.d("punch handshake error:%s", err)
|
||||
return err
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "handshake to %s ok", t.config.LogPeerNode())
|
||||
gLog.d("handshake to %s ok", t.config.LogPeerNode())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlay() (err error) {
|
||||
switch t.config.linkMode {
|
||||
case LinkModeTCP6:
|
||||
t.conn, err = t.connectUnderlayTCP6()
|
||||
if compareVersion(t.config.peerVersion, IPv6PunchVersion) >= 0 {
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
} else {
|
||||
t.conn, err = t.connectUnderlayTCP6()
|
||||
}
|
||||
case LinkModeTCP4:
|
||||
t.conn, err = t.connectUnderlayTCP()
|
||||
case LinkModeUDP4:
|
||||
t.conn, err = t.connectUnderlayUDP()
|
||||
case LinkModeTCPPunch:
|
||||
if gConf.Network.natType == NATSymmetric || t.config.peerNatType == NATSymmetric {
|
||||
t.conn, err = t.connectUnderlayTCPSymmetric()
|
||||
@@ -257,24 +272,35 @@ func (t *P2PTunnel) connectUnderlay() (err error) {
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayUDP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayUDP %s end ", t.config.LogPeerNode())
|
||||
gLog.d("connectUnderlayUDP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.d("connectUnderlayUDP %s end ", t.config.LogPeerNode())
|
||||
var ul underlay
|
||||
underlayProtocol := t.config.UnderlayProtocol
|
||||
if underlayProtocol == "" {
|
||||
underlayProtocol = "quic"
|
||||
}
|
||||
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
|
||||
go GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
if t.config.UnderlayProtocol == "kcp" {
|
||||
ul, err = listenKCP(t.localHoleAddr.String(), TunnelIdleTimeout)
|
||||
if t.config.linkMode == LinkModeUDP4 {
|
||||
if v4l != nil {
|
||||
ul = v4l.getUnderlay(t.id)
|
||||
}
|
||||
if ul == nil {
|
||||
return nil, fmt.Errorf("listen UDP4 error")
|
||||
}
|
||||
gLog.d("UDP4 connection ok")
|
||||
} else {
|
||||
ul, err = listenQuic(t.localHoleAddr.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 {
|
||||
gLog.Printf(LvINFO, "listen %s error:%s", underlayProtocol, err)
|
||||
gLog.i("listen %s error:%s", underlayProtocol, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -284,54 +310,64 @@ func (t *P2PTunnel) connectUnderlayUDP() (c underlay, err error) {
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
gLog.d("handshake flag:%s", string(buff))
|
||||
}
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Printf(LvDEBUG, "%s connection ok", underlayProtocol)
|
||||
gLog.d("%s connection ok", underlayProtocol)
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
//else
|
||||
conn, errL := net.ListenUDP("udp", t.localHoleAddr)
|
||||
//client side
|
||||
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 {
|
||||
time.Sleep(time.Millisecond * 10)
|
||||
conn, errL = net.ListenUDP("udp", t.localHoleAddr)
|
||||
conn, errL = net.ListenUDP("udp", listenAddr)
|
||||
if errL != nil {
|
||||
return nil, fmt.Errorf("%s listen error:%s", underlayProtocol, errL)
|
||||
}
|
||||
}
|
||||
GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
gLog.Printf(LvDEBUG, "%s dial to %s", underlayProtocol, t.remoteHoleAddr.String())
|
||||
gLog.d("%s dial to %s", underlayProtocol, t.remoteHoleAddr.String())
|
||||
if t.config.UnderlayProtocol == "kcp" {
|
||||
ul, errL = dialKCP(conn, t.remoteHoleAddr, TunnelIdleTimeout)
|
||||
// ul, errL = dialKCP(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
|
||||
} else {
|
||||
ul, errL = dialQuic(conn, t.remoteHoleAddr, TunnelIdleTimeout)
|
||||
ul, errL = dialQuic(conn, t.remoteHoleAddr, UnderlayConnectTimeout)
|
||||
}
|
||||
|
||||
if errL != nil {
|
||||
return nil, fmt.Errorf("%s dial to %s error:%s", underlayProtocol, t.remoteHoleAddr.String(), errL)
|
||||
}
|
||||
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
|
||||
if err != nil {
|
||||
ul.Close()
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
gLog.d("handshake flag:%s", string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Printf(LvINFO, "%s connection ok", underlayProtocol)
|
||||
gLog.i("rtt=%dms", time.Since(handshakeBegin)/time.Millisecond)
|
||||
gLog.i("%s connection ok", underlayProtocol)
|
||||
t.linkModeWeb = LinkModeUDPPunch
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP %s end ", t.config.LogPeerNode())
|
||||
var ul *underlayTCP
|
||||
gLog.d("connectUnderlayTCP %s start ", t.config.LogPeerNode())
|
||||
defer gLog.d("connectUnderlayTCP %s end ", t.config.LogPeerNode())
|
||||
var ul underlay
|
||||
peerIP := t.config.peerIP
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
peerIP = t.config.peerLanIP
|
||||
@@ -342,11 +378,15 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("listen TCP error:%s", err)
|
||||
}
|
||||
gLog.Println(LvINFO, "TCP connection ok")
|
||||
|
||||
t.linkModeWeb = LinkModeIPv4
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
t.linkModeWeb = LinkModeIntranet
|
||||
}
|
||||
if t.config.linkMode == LinkModeTCP6 {
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
}
|
||||
gLog.i("%s TCP connection ok", t.linkModeWeb)
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
@@ -355,49 +395,60 @@ func (t *P2PTunnel) connectUnderlayTCP() (c underlay, err error) {
|
||||
GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
} else { //tcp punch should sleep for punch the same time
|
||||
if compareVersion(t.config.peerVersion, SyncServerTimeVersion) < 0 {
|
||||
gLog.Printf(LvDEBUG, "peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
|
||||
gLog.d("peer version %s less than %s", t.config.peerVersion, SyncServerTimeVersion)
|
||||
} else {
|
||||
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 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
gLog.d("sleep %d ms", ts/time.Millisecond)
|
||||
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 {
|
||||
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()
|
||||
tidBuff := new(bytes.Buffer)
|
||||
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
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, "hello ", string(buff))
|
||||
gLog.d("hello %s", string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvINFO, "TCP connection ok")
|
||||
gLog.i("rtt=%dms", time.Since(handshakeBegin)/time.Millisecond)
|
||||
t.linkModeWeb = LinkModeIPv4
|
||||
if t.config.linkMode == LinkModeIntranet {
|
||||
t.linkModeWeb = LinkModeIntranet
|
||||
}
|
||||
if t.config.linkMode == LinkModeTCP6 {
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
}
|
||||
gLog.i("%s TCP connection ok", t.linkModeWeb)
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode())
|
||||
gLog.d("connectUnderlayTCPSymmetric %s start ", t.config.LogPeerNode())
|
||||
defer gLog.d("connectUnderlayTCPSymmetric %s end ", t.config.LogPeerNode())
|
||||
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 {
|
||||
ts = PunchTsDelay
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sleep %d ms", ts/time.Millisecond)
|
||||
gLog.d("sleep %d ms", ts/time.Millisecond)
|
||||
time.Sleep(ts)
|
||||
startTime := time.Now()
|
||||
t.linkModeWeb = LinkModeTCPPunch
|
||||
@@ -424,8 +475,8 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
return
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "c2s ul.ReadBuffer error:", err)
|
||||
if err != nil || buff == nil {
|
||||
gLog.d("c2s ul.ReadBuffer error:%s", err)
|
||||
return
|
||||
}
|
||||
req := P2PHandshakeReq{}
|
||||
@@ -435,7 +486,7 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
if req.ID != t.id {
|
||||
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
|
||||
close(gotCh)
|
||||
@@ -453,8 +504,8 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
}
|
||||
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "s2c ul.ReadBuffer error:", err)
|
||||
if err != nil || buff == nil {
|
||||
gLog.d("s2c ul.ReadBuffer error:%s", err)
|
||||
return
|
||||
}
|
||||
req := P2PHandshakeReq{}
|
||||
@@ -485,91 +536,121 @@ func (t *P2PTunnel) connectUnderlayTCPSymmetric() (c underlay, err error) {
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) connectUnderlayTCP6() (c underlay, err error) {
|
||||
gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s start ", t.config.LogPeerNode())
|
||||
defer gLog.Printf(LvDEBUG, "connectUnderlayTCP6 %s end ", t.config.LogPeerNode())
|
||||
var ul *underlayTCP6
|
||||
gLog.d("connectUnderlayTCP6 %s start ", t.config.LogPeerNode())
|
||||
defer gLog.d("connectUnderlayTCP6 %s end ", t.config.LogPeerNode())
|
||||
tidBuff := new(bytes.Buffer)
|
||||
binary.Write(tidBuff, binary.LittleEndian, t.id)
|
||||
if t.config.isUnderlayServer == 1 {
|
||||
GNetwork.push(t.config.PeerNode, MsgPushUnderlayConnect, nil)
|
||||
ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout)
|
||||
if err != nil {
|
||||
// ul, err = listenTCP6(t.coneNatPort, UnderlayConnectTimeout)
|
||||
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)
|
||||
}
|
||||
_, buff, err := ul.ReadBuffer()
|
||||
_, buff, err := c.ReadBuffer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
gLog.d("handshake flag:%s", string(buff))
|
||||
}
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.Println(LvDEBUG, "TCP6 connection ok")
|
||||
c.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
|
||||
// ul.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, []byte("OpenP2P,hello2"))
|
||||
gLog.d("TCP6 connection ok")
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return ul, nil
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//else
|
||||
GNetwork.read(t.config.PeerNode, MsgPush, MsgPushUnderlayConnect, ReadMsgTimeout)
|
||||
gLog.Println(LvDEBUG, "TCP6 dial to ", t.config.peerIPv6)
|
||||
ul, err = dialTCP6(t.config.peerIPv6, t.config.peerConeNatPort)
|
||||
gLog.d("TCP6 dial to %s", t.config.peerIPv6)
|
||||
ul, err := dialTCP(fmt.Sprintf("[%s]", t.config.peerIPv6), t.config.peerConeNatPort, 0, LinkModeTCP6)
|
||||
if err != nil || ul == nil {
|
||||
return nil, fmt.Errorf("TCP6 dial to %s:%d error:%s", t.config.peerIPv6, t.config.peerConeNatPort, err)
|
||||
}
|
||||
handshakeBegin := time.Now()
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
|
||||
ul.WriteBytes(MsgP2P, MsgTunnelHandshake, tidBuff.Bytes()) // tunnelID
|
||||
// ul.WriteBytes(MsgP2P, MsgTunnelHandshake, []byte("OpenP2P,hello"))
|
||||
_, buff, errR := ul.ReadBuffer()
|
||||
if errR != nil {
|
||||
return nil, fmt.Errorf("read MsgTunnelHandshake error:%s", errR)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
gLog.d("handshake flag:%s", string(buff))
|
||||
}
|
||||
|
||||
gLog.Println(LvINFO, "rtt=", time.Since(handshakeBegin))
|
||||
gLog.Println(LvINFO, "TCP6 connection ok")
|
||||
gLog.i("rtt=%dms", time.Since(handshakeBegin))
|
||||
gLog.i("TCP6 connection ok")
|
||||
t.linkModeWeb = LinkModeIPv6
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) readLoop() {
|
||||
decryptData := make([]byte, ReadBuffLen+PaddingSize) // 16 bytes for padding
|
||||
gLog.Printf(LvDEBUG, "%d tunnel readloop start", t.id)
|
||||
gLog.d("%d tunnel readloop start", t.id)
|
||||
for t.isRuning() {
|
||||
t.conn.SetReadDeadline(time.Now().Add(TunnelHeartbeatTime * 2))
|
||||
head, body, err := t.conn.ReadBuffer()
|
||||
if err != nil {
|
||||
if err != nil || head == nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
// gLog.d("%d tunnel read %d:%d len=%d", t.id, head.MainType, head.SubType, head.DataLen)
|
||||
// TODO: replace some case implement to functions
|
||||
switch head.SubType {
|
||||
case MsgTunnelHeartbeat:
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
t.hbMtx.Unlock()
|
||||
t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeatAck, nil)
|
||||
gLog.Printf(LvDev, "%d read tunnel heartbeat", t.id)
|
||||
memAppPeerID := new(bytes.Buffer)
|
||||
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:
|
||||
t.hbMtx.Lock()
|
||||
t.hbTime = time.Now()
|
||||
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:
|
||||
if len(body) < overlayHeaderSize {
|
||||
gLog.Printf(LvWARN, "%d len(body) < overlayHeaderSize", t.id)
|
||||
gLog.w("%d len(body) < overlayHeaderSize", t.id)
|
||||
continue
|
||||
}
|
||||
overlayID := binary.LittleEndian.Uint64(body[:8])
|
||||
gLog.Printf(LvDev, "%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen)
|
||||
s, ok := t.overlayConns.Load(overlayID)
|
||||
gLog.dev("%d tunnel read overlay data %d bodylen=%d", t.id, overlayID, head.DataLen)
|
||||
s, ok := overlayConns.Load(overlayID)
|
||||
if !ok {
|
||||
// debug level, when overlay connection closed, always has some packet not found tunnel
|
||||
gLog.Printf(LvDEBUG, "%d tunnel not found overlay connection %d", t.id, overlayID)
|
||||
gLog.d("%d tunnel not found overlay connection %d", t.id, overlayID)
|
||||
continue
|
||||
}
|
||||
overlayConn, ok := s.(*overlayConn)
|
||||
@@ -578,101 +659,86 @@ func (t *P2PTunnel) readLoop() {
|
||||
}
|
||||
payload := body[overlayHeaderSize:]
|
||||
var err error
|
||||
if overlayConn.appKey != 0 {
|
||||
payload, _ = decryptBytes(overlayConn.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize)))
|
||||
if overlayConn.app.key != 0 {
|
||||
payload, _ = decryptBytes(overlayConn.app.appKeyBytes, decryptData, body[overlayHeaderSize:], int(head.DataLen-uint32(overlayHeaderSize)))
|
||||
}
|
||||
_, err = overlayConn.Write(payload)
|
||||
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)
|
||||
case MsgRelayNodeData:
|
||||
case MsgRelayNodeData: // unused
|
||||
t.handleNodeData(head, body, true)
|
||||
case MsgRelayData:
|
||||
if len(body) < 8 {
|
||||
continue
|
||||
}
|
||||
tunnelID := binary.LittleEndian.Uint64(body[:8])
|
||||
gLog.Printf(LvDev, "relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize)
|
||||
gLog.dev("relay data to %d, len=%d", tunnelID, head.DataLen-RelayHeaderSize)
|
||||
if err := GNetwork.relay(tunnelID, body[RelayHeaderSize:]); err != nil {
|
||||
gLog.Printf(LvERROR, "%s:%d relay to %d len=%d error:%s", t.config.LogPeerNode(), t.id, tunnelID, len(body), ErrRelayTunnelNotFound)
|
||||
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{}
|
||||
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
|
||||
}
|
||||
// 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
|
||||
GNetwork.updateAppHeartbeat(req.AppID)
|
||||
GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, true)
|
||||
req.From = gConf.Network.Node
|
||||
t.WriteMessage(req.RelayTunnelID, MsgP2P, MsgRelayHeartbeatAck, &req)
|
||||
case MsgRelayHeartbeatAck:
|
||||
req := RelayHeartbeat{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "wrong RelayHeartbeat:%s", err)
|
||||
gLog.e("wrong RelayHeartbeat:%s", err)
|
||||
continue
|
||||
}
|
||||
// TODO: debug relay heartbeat
|
||||
gLog.Printf(LvDEBUG, "read MsgRelayHeartbeatAck to appid:%d", req.AppID)
|
||||
GNetwork.updateAppHeartbeat(req.AppID)
|
||||
case MsgOverlayConnectReq:
|
||||
req := OverlayConnectReq{}
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
gLog.Printf(LvERROR, "wrong %v:%s", reflect.TypeOf(req), err)
|
||||
continue
|
||||
}
|
||||
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
|
||||
if req.Token != gConf.Network.Token {
|
||||
gLog.Println(LvERROR, "Access Denied:", req.Token)
|
||||
continue
|
||||
}
|
||||
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LvDEBUG, "App:%d overlayID:%d connect %s:%d", req.AppID, overlayID, req.DstIP, req.DstPort)
|
||||
oConn := overlayConn{
|
||||
tunnel: t,
|
||||
id: overlayID,
|
||||
isClient: false,
|
||||
rtid: req.RelayTunnelID,
|
||||
appID: req.AppID,
|
||||
appKey: GetKey(req.AppID),
|
||||
running: true,
|
||||
}
|
||||
if req.Protocol == "udp" {
|
||||
oConn.connUDP, err = net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP(req.DstIP), Port: req.DstPort})
|
||||
} else {
|
||||
oConn.connTCP, err = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", req.DstIP, req.DstPort), ReadMsgTimeout)
|
||||
|
||||
}
|
||||
gLog.dev("read MsgRelayHeartbeatAck to appid:%d", req.AppID)
|
||||
GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, false)
|
||||
req.From = gConf.Network.Node
|
||||
t.WriteMessage(req.RelayTunnelID2, MsgP2P, MsgRelayHeartbeatAck2, &req)
|
||||
case MsgRelayHeartbeatAck2:
|
||||
req := RelayHeartbeat{}
|
||||
err := json.Unmarshal(body, &req)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, err)
|
||||
gLog.e("wrong RelayHeartbeat:%s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// calc key bytes for encrypt
|
||||
if oConn.appKey != 0 {
|
||||
encryptKey := make([]byte, AESKeySize)
|
||||
binary.LittleEndian.PutUint64(encryptKey, oConn.appKey)
|
||||
binary.LittleEndian.PutUint64(encryptKey[8:], oConn.appKey)
|
||||
oConn.appKeyBytes = encryptKey
|
||||
gLog.dev("read MsgRelayHeartbeatAck2 to appid:%d", req.AppID)
|
||||
GNetwork.updateAppHeartbeat(req.AppID, req.RelayTunnelID, false)
|
||||
case MsgOverlayConnectReq: // TODO: send this msg withAppID, and app handle it
|
||||
// app connect only accept token(not relay totp token), avoid someone using the share relay node's token
|
||||
// targetApp := GNetwork.GetAPPByID(req.AppID)
|
||||
t.handleOverlayConnectReq(body, err)
|
||||
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
|
||||
}
|
||||
|
||||
t.overlayConns.Store(oConn.id, &oConn)
|
||||
go oConn.run()
|
||||
app := i.(*p2pApp)
|
||||
// ndmp := NodeDataMPHeader{fromNodeID: gConf.Network.nodeID, seq: seq}
|
||||
app.StoreMessage(head, body)
|
||||
case MsgOverlayDisconnectReq:
|
||||
req := OverlayDisconnectReq{}
|
||||
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
|
||||
}
|
||||
overlayID := req.ID
|
||||
gLog.Printf(LvDEBUG, "%d disconnect overlay connection %d", t.id, overlayID)
|
||||
i, ok := t.overlayConns.Load(overlayID)
|
||||
gLog.d("%d disconnect overlay connection %d", t.id, overlayID)
|
||||
i, ok := overlayConns.Load(overlayID)
|
||||
if ok {
|
||||
oConn := i.(*overlayConn)
|
||||
oConn.Close()
|
||||
@@ -681,7 +747,55 @@ func (t *P2PTunnel) readLoop() {
|
||||
}
|
||||
}
|
||||
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() {
|
||||
@@ -690,29 +804,39 @@ func (t *P2PTunnel) writeLoop() {
|
||||
t.hbMtx.Unlock()
|
||||
tc := time.NewTicker(TunnelHeartbeatTime)
|
||||
defer tc.Stop()
|
||||
gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop start", t.config.LogPeerNode(), t.id)
|
||||
defer gLog.Printf(LvDEBUG, "%s:%d tunnel writeLoop end", t.config.LogPeerNode(), t.id)
|
||||
gLog.d("%s:%d tunnel writeLoop start", t.config.LogPeerNode(), 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() {
|
||||
select {
|
||||
case buff := <-t.writeDataSmall:
|
||||
t.conn.WriteBuffer(buff)
|
||||
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix())
|
||||
// gLog.d("write icmp %d", time.Now().Unix())
|
||||
default:
|
||||
select {
|
||||
case buff := <-t.writeDataSmall:
|
||||
t.conn.WriteBuffer(buff)
|
||||
// gLog.Printf(LvDEBUG, "write icmp %d", time.Now().Unix())
|
||||
// gLog.d("write icmp %d", time.Now().Unix())
|
||||
case buff := <-t.writeData:
|
||||
t.conn.WriteBuffer(buff)
|
||||
case <-tc.C:
|
||||
// tunnel send
|
||||
err := t.conn.WriteBytes(MsgP2P, MsgTunnelHeartbeat, nil)
|
||||
err := t.conn.WriteBuffer(buff)
|
||||
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()
|
||||
return
|
||||
}
|
||||
gLog.Printf(LvDev, "%d write tunnel heartbeat ok", t.id)
|
||||
case <-tc.C:
|
||||
writeHb()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -742,54 +866,74 @@ func (t *P2PTunnel) listen() error {
|
||||
}
|
||||
|
||||
GNetwork.push(t.config.PeerNode, MsgPushConnectRsp, rsp)
|
||||
gLog.Printf(LvDEBUG, "p2ptunnel wait for connecting")
|
||||
t.tunnelServer = true
|
||||
gLog.d("p2ptunnel wait for connecting")
|
||||
return t.start()
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) closeOverlayConns(appID uint64) {
|
||||
t.overlayConns.Range(func(_, i interface{}) bool {
|
||||
oConn := i.(*overlayConn)
|
||||
if oConn.appID == appID {
|
||||
oConn.Close()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (t *P2PTunnel) handleNodeData(head *openP2PHeader, body []byte, isRelay bool) {
|
||||
gLog.Printf(LvDev, "%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay)
|
||||
gLog.dev("%d tunnel read node data bodylen=%d, relay=%t", t.id, head.DataLen, isRelay)
|
||||
ch := GNetwork.nodeData
|
||||
// if body[9] == 1 { // TODO: deal relay
|
||||
// ch = GNetwork.nodeDataSmall
|
||||
// gLog.Printf(LvDEBUG, "read icmp %d", time.Now().Unix())
|
||||
// gLog.d("read icmp %d", time.Now().Unix())
|
||||
// }
|
||||
if isRelay {
|
||||
fromPeerID := binary.LittleEndian.Uint64(body[:8])
|
||||
ch <- &NodeData{fromPeerID, body[8:]} // TODO: cache peerNodeID; encrypt/decrypt
|
||||
// fromPeerID := binary.LittleEndian.Uint64(body[:8]) // unused
|
||||
ch <- body[8:] // TODO: cache peerNodeID; encrypt/decrypt
|
||||
} 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) {
|
||||
writeBytes := append(encodeHeader(mainType, subType, uint32(len(data))), data...)
|
||||
// if len(data) < 192 {
|
||||
if data[9] == 1 { // icmp
|
||||
select {
|
||||
case t.writeDataSmall <- writeBytes:
|
||||
// gLog.Printf(LvWARN, "%s:%d t.writeDataSmall write %d", t.config.PeerNode, t.id, len(t.writeDataSmall))
|
||||
default:
|
||||
gLog.Printf(LvWARN, "%s:%d t.writeDataSmall is full, drop it", t.config.LogPeerNode(), t.id)
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case t.writeData <- writeBytes:
|
||||
default:
|
||||
gLog.Printf(LvWARN, "%s:%d t.writeData is full, drop it", t.config.LogPeerNode(), t.id)
|
||||
}
|
||||
func (t *P2PTunnel) handleNodeDataMP(head *openP2PHeader, body []byte) {
|
||||
gLog.dev("%s tid:%d tunnel read node data mp bodylen=%d", t.config.LogPeerNode(), t.id, head.DataLen) // Debug
|
||||
if head.DataLen < 16 {
|
||||
return
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -803,3 +947,41 @@ func (t *P2PTunnel) WriteMessage(rtid uint64, mainType uint16, subType uint16, r
|
||||
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())
|
||||
// }
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const OpenP2PVersion = "3.21.12"
|
||||
const OpenP2PVersion = "3.25.11"
|
||||
const ProductName string = "openp2p"
|
||||
const LeastSupportVersion = "3.0.0"
|
||||
const SyncServerTimeVersion = "3.9.0"
|
||||
@@ -18,13 +18,16 @@ const SymmetricSimultaneouslySendVersion = "3.10.7"
|
||||
const PublicIPVersion = "3.11.2"
|
||||
const SupportIntranetVersion = "3.14.5"
|
||||
const SupportDualTunnelVersion = "3.15.5"
|
||||
|
||||
const IPv6PunchVersion = "3.24.9"
|
||||
const SupportUDP4DirectVersion = "3.24.16"
|
||||
const SupportMultiDirectVersion = "3.25.1"
|
||||
const (
|
||||
IfconfigPort1 = 27180
|
||||
IfconfigPort2 = 27181
|
||||
WsPort = 27183
|
||||
UDPPort1 = 27182
|
||||
UDPPort2 = 27183
|
||||
NATDetectPort1 = 27180
|
||||
NATDetectPort2 = 27181
|
||||
WsPort = 27183
|
||||
WsPort2 = 465
|
||||
UDPPort1 = 27182
|
||||
UDPPort2 = 27183
|
||||
)
|
||||
|
||||
type openP2PHeader struct {
|
||||
@@ -48,6 +51,12 @@ type overlayHeader struct {
|
||||
id uint64
|
||||
}
|
||||
|
||||
type NodeDataMPAck struct {
|
||||
FromNodeID uint64
|
||||
Seq uint64
|
||||
Delay uint32 // delay write mergeack ms
|
||||
}
|
||||
|
||||
var overlayHeaderSize = binary.Size(overlayHeader{})
|
||||
|
||||
func decodeHeader(data []byte) (*openP2PHeader, error) {
|
||||
@@ -74,7 +83,7 @@ func encodeHeader(mainType uint16, subType uint16, len uint32) []byte {
|
||||
return headBuf.Bytes()
|
||||
}
|
||||
|
||||
// Message type
|
||||
// Message main type
|
||||
const (
|
||||
MsgLogin = 0
|
||||
MsgHeartbeat = 1
|
||||
@@ -109,42 +118,50 @@ const (
|
||||
MsgPushReportMemApps = 17
|
||||
MsgPushServerSideSaveMemApp = 18
|
||||
MsgPushCheckRemoteService = 19
|
||||
MsgPushSpecTunnel = 20
|
||||
MsgPushReportHeap = 21
|
||||
MsgPushSDWanRefresh = 22
|
||||
MsgPushNat4Detect = 23
|
||||
)
|
||||
|
||||
// MsgP2P sub type message
|
||||
const (
|
||||
MsgPunchHandshake = iota
|
||||
MsgPunchHandshakeAck
|
||||
MsgTunnelHandshake
|
||||
MsgTunnelHandshakeAck
|
||||
MsgTunnelHeartbeat
|
||||
MsgTunnelHeartbeatAck
|
||||
MsgOverlayConnectReq
|
||||
MsgOverlayConnectRsp
|
||||
MsgOverlayDisconnectReq
|
||||
MsgOverlayData
|
||||
MsgRelayData
|
||||
MsgRelayHeartbeat
|
||||
MsgRelayHeartbeatAck
|
||||
MsgNodeData
|
||||
MsgRelayNodeData
|
||||
MsgPunchHandshake = 0
|
||||
MsgPunchHandshakeAck = 1
|
||||
MsgTunnelHandshake = 2
|
||||
MsgTunnelHandshakeAck = 3
|
||||
MsgTunnelHeartbeat = 4
|
||||
MsgTunnelHeartbeatAck = 5
|
||||
MsgOverlayConnectReq = 6
|
||||
MsgOverlayConnectRsp = 7
|
||||
MsgOverlayDisconnectReq = 8
|
||||
MsgOverlayData = 9
|
||||
MsgRelayData = 10
|
||||
MsgRelayHeartbeat = 11
|
||||
MsgRelayHeartbeatAck = 12
|
||||
MsgNodeData = 13
|
||||
MsgRelayNodeData = 14
|
||||
MsgNodeDataMP = 15
|
||||
MsgNodeDataMPAck = 16
|
||||
MsgRelayHeartbeatAck2 = 17
|
||||
)
|
||||
|
||||
// MsgRelay sub type message
|
||||
const (
|
||||
MsgRelayNodeReq = iota
|
||||
MsgRelayNodeRsp
|
||||
MsgRelayNodeReq = 0
|
||||
MsgRelayNodeRsp = 1
|
||||
)
|
||||
|
||||
// MsgReport sub type message
|
||||
const (
|
||||
MsgReportBasic = iota
|
||||
MsgReportQuery
|
||||
MsgReportConnect
|
||||
MsgReportApps
|
||||
MsgReportLog
|
||||
MsgReportMemApps
|
||||
MsgReportResponse
|
||||
MsgReportBasic = 0
|
||||
MsgReportQuery = 1
|
||||
MsgReportConnect = 2
|
||||
MsgReportApps = 3
|
||||
MsgReportLog = 4
|
||||
MsgReportMemApps = 5
|
||||
MsgReportResponse = 6
|
||||
MsgReportBasicRsp = 7
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -167,16 +184,12 @@ const (
|
||||
MaxRetry = 10
|
||||
Cone2ConeTCPPunchMaxRetry = 1
|
||||
Cone2ConeUDPPunchMaxRetry = 1
|
||||
PublicIPEchoTimeout = time.Second * 3
|
||||
NatTestTimeout = time.Second * 5
|
||||
PublicIPEchoTimeout = time.Second * 5
|
||||
NatDetectTimeout = time.Second * 5
|
||||
UDPReadTimeout = time.Second * 5
|
||||
ClientAPITimeout = time.Second * 10
|
||||
UnderlayConnectTimeout = time.Second * 10
|
||||
MaxDirectTry = 3
|
||||
|
||||
// sdwan
|
||||
ReadTunBuffSize = 1600
|
||||
ReadTunBuffNum = 10
|
||||
)
|
||||
|
||||
// NATNone has public ip
|
||||
@@ -208,19 +221,19 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
MsgQueryPeerInfoReq = iota
|
||||
MsgQueryPeerInfoRsp
|
||||
MsgQueryPeerInfoReq = 0
|
||||
MsgQueryPeerInfoRsp = 1
|
||||
)
|
||||
|
||||
const (
|
||||
MsgSDWANInfoReq = iota
|
||||
MsgSDWANInfoRsp
|
||||
MsgSDWANInfoReq = 0
|
||||
MsgSDWANInfoRsp = 1
|
||||
)
|
||||
|
||||
// MsgNATDetect
|
||||
const (
|
||||
MsgNAT = iota
|
||||
MsgPublicIP
|
||||
MsgNAT = 0
|
||||
MsgPublicIP = 1
|
||||
)
|
||||
|
||||
func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, error) {
|
||||
@@ -243,6 +256,21 @@ func newMessage(mainType uint16, subType uint16, packet interface{}) ([]byte, er
|
||||
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 {
|
||||
return crc64.Checksum([]byte(name), crc64.MakeTable(crc64.ISO))
|
||||
}
|
||||
@@ -295,6 +323,8 @@ type LoginRsp struct {
|
||||
Token uint64 `json:"token,omitempty"`
|
||||
Ts int64 `json:"ts,omitempty"`
|
||||
LoginMaxDelay int `json:"loginMaxDelay,omitempty"` // seconds
|
||||
Forcev6 int `json:"forcev6,omitempty"`
|
||||
PublicIPPort int `json:"publicIPPort,omitempty"`
|
||||
}
|
||||
|
||||
type NatDetectReq struct {
|
||||
@@ -329,7 +359,8 @@ type TunnelMsg 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 {
|
||||
@@ -339,13 +370,15 @@ type RelayNodeRsp struct {
|
||||
}
|
||||
|
||||
type AddRelayTunnelReq struct {
|
||||
From string `json:"from,omitempty"`
|
||||
RelayName string `json:"relayName,omitempty"`
|
||||
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
|
||||
RelayToken uint64 `json:"relayToken,omitempty"`
|
||||
RelayMode string `json:"relayMode,omitempty"`
|
||||
AppID uint64 `json:"appID,omitempty"` // deprecated
|
||||
AppKey uint64 `json:"appKey,omitempty"` // deprecated
|
||||
From string `json:"from,omitempty"`
|
||||
RelayName string `json:"relayName,omitempty"`
|
||||
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
|
||||
RelayToken uint64 `json:"relayToken,omitempty"`
|
||||
RelayMode string `json:"relayMode,omitempty"`
|
||||
AppID uint64 `json:"appID,omitempty"` // deprecated
|
||||
AppKey uint64 `json:"appKey,omitempty"` // deprecated
|
||||
UnderlayProtocol string `json:"underlayProtocol,omitempty"` // quic or kcp, default quic
|
||||
PunchPriority int `json:"punchPriority,omitempty"`
|
||||
}
|
||||
|
||||
type APPKeySync struct {
|
||||
@@ -354,9 +387,10 @@ type APPKeySync struct {
|
||||
}
|
||||
|
||||
type RelayHeartbeat struct {
|
||||
From string `json:"from,omitempty"`
|
||||
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
|
||||
AppID uint64 `json:"appID,omitempty"`
|
||||
From string `json:"from,omitempty"`
|
||||
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"`
|
||||
RelayTunnelID2 uint64 `json:"relayTunnelID2,omitempty"`
|
||||
AppID uint64 `json:"appID,omitempty"`
|
||||
}
|
||||
|
||||
type ReportBasic struct {
|
||||
@@ -365,6 +399,7 @@ type ReportBasic struct {
|
||||
LanIP string `json:"lanIP,omitempty"`
|
||||
HasIPv4 int `json:"hasIPv4,omitempty"`
|
||||
IPv6 string `json:"IPv6,omitempty"`
|
||||
PublicIPPort int `json:"publicIPPort,omitempty"`
|
||||
HasUPNPorNATPMP int `json:"hasUPNPorNATPMP,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
NetInfo NetInfo `json:"netInfo,omitempty"`
|
||||
@@ -415,13 +450,16 @@ type AppInfo struct {
|
||||
}
|
||||
|
||||
type ReportApps struct {
|
||||
Apps []AppInfo
|
||||
Apps []AppInfo
|
||||
TunError string `json:"tunError,omitempty"`
|
||||
}
|
||||
|
||||
type ReportLogReq struct {
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
Offset int64 `json:"offset,omitempty"`
|
||||
Len int64 `json:"len,omitempty"`
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
Offset int64 `json:"offset,omitempty"`
|
||||
Len int64 `json:"len,omitempty"`
|
||||
IsSetLogLevel int64 `json:"isSetLogLevel,omitempty"`
|
||||
LogLevel int64 `json:"loglevel,omitempty"`
|
||||
}
|
||||
type ReportLogRsp struct {
|
||||
FileName string `json:"fileName,omitempty"`
|
||||
@@ -434,6 +472,7 @@ type UpdateInfo struct {
|
||||
Error int `json:"error,omitempty"`
|
||||
ErrorDetail string `json:"errorDetail,omitempty"`
|
||||
Url string `json:"url,omitempty"`
|
||||
Url2 string `json:"url2,omitempty"`
|
||||
}
|
||||
|
||||
type NetInfo struct {
|
||||
@@ -465,8 +504,10 @@ type ProfileInfo struct {
|
||||
}
|
||||
|
||||
type EditNode struct {
|
||||
NewName string `json:"newName,omitempty"`
|
||||
Bandwidth int `json:"bandwidth,omitempty"`
|
||||
NewName string `json:"newName,omitempty"`
|
||||
Bandwidth int `json:"bandwidth,omitempty"`
|
||||
Forcev6 int `json:"forcev6,omitempty"`
|
||||
PublicIPPort int `json:"publicIPPort,omitempty"`
|
||||
}
|
||||
|
||||
type QueryPeerInfoReq struct {
|
||||
@@ -501,9 +542,20 @@ type SDWANInfo struct {
|
||||
ForceRelay int32 `json:"forceRelay,omitempty"`
|
||||
PunchPriority int32 `json:"punchPriority,omitempty"`
|
||||
Enable int32 `json:"enable,omitempty"`
|
||||
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 (
|
||||
SDWANModeFullmesh = "fullmesh"
|
||||
SDWANModeCentral = "central"
|
||||
@@ -516,6 +568,10 @@ type ServerSideSaveMemApp struct {
|
||||
RelayTunnelID uint64 `json:"relayTunnelID,omitempty"` // rtid, if not 0 relay
|
||||
RelayMode string `json:"relayMode,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 {
|
||||
@@ -523,6 +579,23 @@ type CheckRemoteService struct {
|
||||
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-----
|
||||
MIIDhTCCAm0CFHm0cd8dnGCbUW/OcS56jf0gvRk7MA0GCSqGSIb3DQEBCwUAMH4x
|
||||
CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJHRDETMBEGA1UECgwKb3BlbnAycC5jbjET
|
||||
@@ -545,6 +618,31 @@ RVtXS+DplMClQ5QSlv3StwcWOsjyiAimNfLEU5xoEfq17yOJUTU1OTL4YOt16QUc
|
||||
C1tnzFr3k/ioqFR7cnyzNrbjlfPOmO9l2WReEbMP3bvaSHm6EcpJKS8=
|
||||
-----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-----
|
||||
MIIEJjCCAw6gAwIBAgISAztStWq026ej0RCsk3ErbUdPMA0GCSqGSIb3DQEBCwUA
|
||||
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -43,8 +44,8 @@ type sdwanNode struct {
|
||||
}
|
||||
|
||||
type p2pSDWAN struct {
|
||||
nodeName string
|
||||
tun *optun
|
||||
tunErr string
|
||||
sysRoute sync.Map // ip:sdwanNode
|
||||
subnet *net.IPNet
|
||||
gateway net.IP
|
||||
@@ -53,39 +54,54 @@ type p2pSDWAN struct {
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) reset() {
|
||||
gLog.Println(LvINFO, "reset sdwan when network disconnected")
|
||||
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.getAddNodes() {
|
||||
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(name string) error {
|
||||
|
||||
func (s *p2pSDWAN) init() error {
|
||||
gConf.Network.previousIP = gConf.Network.publicIP
|
||||
if gConf.getSDWAN().Gateway == "" {
|
||||
gLog.Println(LvDEBUG, "sdwan init: not in sdwan clear all ")
|
||||
gLog.d("sdwan init: not in sdwan clear all ")
|
||||
}
|
||||
if s.internalRoute == nil {
|
||||
s.internalRoute = NewIPTree("")
|
||||
}
|
||||
|
||||
s.nodeName = name
|
||||
if gw, sn, err := net.ParseCIDR(gConf.getSDWAN().Gateway); err == nil { // preserve old gateway
|
||||
s.gateway = gw
|
||||
s.subnet = sn
|
||||
}
|
||||
|
||||
for _, node := range gConf.getDelNodes() {
|
||||
gLog.Println(LvDEBUG, "sdwan init: deal deleted node: ", node.Name)
|
||||
gLog.Printf(LvDEBUG, "sdwan init: delRoute: %s, %s ", node.IP, s.gateway.String())
|
||||
delRoute(node.IP, s.gateway.String())
|
||||
gLog.d("sdwan init: deal deleted node: %s", node.Name)
|
||||
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)
|
||||
ipNum, _ := inetAtoN(node.IP)
|
||||
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})
|
||||
GNetwork.DeleteApp(AppConfig{SrcPort: 0, PeerNode: node.Name})
|
||||
arr := strings.Split(node.Resource, ",")
|
||||
@@ -106,26 +122,26 @@ func (s *p2pSDWAN) init(name string) error {
|
||||
}
|
||||
s.internalRoute.Del(minIP.String(), maxIP.String())
|
||||
delRoute(ipnet.String(), s.gateway.String())
|
||||
gLog.Printf(LvDEBUG, "sdwan init: resource delRoute: %s, %s ", ipnet.String(), s.gateway.String())
|
||||
gLog.d("sdwan init: resource delRoute: %s, %s ", ipnet.String(), s.gateway.String())
|
||||
}
|
||||
}
|
||||
for _, node := range gConf.getAddNodes() {
|
||||
gLog.Println(LvDEBUG, "sdwan init: deal add node: ", node.Name)
|
||||
gLog.d("sdwan init: deal add node: %s", node.Name)
|
||||
ipNet := &net.IPNet{
|
||||
IP: net.ParseIP(node.IP),
|
||||
Mask: s.subnet.Mask,
|
||||
}
|
||||
if node.Name == s.nodeName {
|
||||
if node.Name == gConf.Network.Node {
|
||||
s.virtualIP = ipNet
|
||||
gLog.Println(LvINFO, "sdwan init: start tun ", ipNet.String())
|
||||
gLog.i("sdwan init: start tun %s", ipNet.String())
|
||||
err := s.StartTun()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "sdwan init: start tun error:", err)
|
||||
gLog.e("sdwan init: start tun error:%s", err)
|
||||
return err
|
||||
}
|
||||
gLog.Println(LvINFO, "sdwan init: start tun ok")
|
||||
gLog.i("sdwan init: start tun ok")
|
||||
allowTunForward()
|
||||
gLog.Printf(LvDEBUG, "sdwan init: addRoute %s %s %s", s.subnet.String(), s.gateway.String(), s.tun.tunName)
|
||||
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("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
|
||||
@@ -140,11 +156,11 @@ func (s *p2pSDWAN) init(name string) error {
|
||||
s.internalRoute.AddIntIP(ip, ip, &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
|
||||
}
|
||||
for _, node := range gConf.getAddNodes() {
|
||||
if node.Name == s.nodeName { // not deal resource itself
|
||||
if node.Name == gConf.Network.Node { // not deal resource itself
|
||||
continue
|
||||
}
|
||||
if len(node.Resource) > 0 {
|
||||
gLog.Printf(LvINFO, "sdwan init: deal add node: %s resource: %s", node.Name, node.Resource)
|
||||
gLog.i("sdwan init: deal add node: %s resource: %s", node.Name, node.Resource)
|
||||
arr := strings.Split(node.Resource, ",")
|
||||
for _, r := range arr {
|
||||
// add internal route
|
||||
@@ -154,17 +170,17 @@ func (s *p2pSDWAN) init(name string) error {
|
||||
continue
|
||||
}
|
||||
if ipnet.Contains(net.ParseIP(gConf.Network.localIP)) { // local ip and resource in the same lan
|
||||
gLog.Printf(LvDEBUG, "sdwan init: local ip %s in this resource %s, ignore", gConf.Network.localIP, ipnet.IP.String())
|
||||
gLog.d("sdwan init: local ip %s in this resource %s, ignore", gConf.Network.localIP, ipnet.IP.String())
|
||||
continue
|
||||
}
|
||||
// local net could access this single ip
|
||||
if ipnet.Mask[0] == 255 && ipnet.Mask[1] == 255 && ipnet.Mask[2] == 255 && ipnet.Mask[3] == 255 {
|
||||
gLog.Printf(LvDEBUG, "sdwan init: ping %s start", ipnet.IP.String())
|
||||
gLog.d("sdwan init: ping %s start", ipnet.IP.String())
|
||||
if _, err := Ping(ipnet.IP.String()); err == nil {
|
||||
gLog.Printf(LvDEBUG, "sdwan init: ping %s ok, ignore this resource", ipnet.IP.String())
|
||||
gLog.d("sdwan init: ping %s ok, ignore this resource", ipnet.IP.String())
|
||||
continue
|
||||
}
|
||||
gLog.Printf(LvDEBUG, "sdwan init: ping %s failed", ipnet.IP.String())
|
||||
gLog.d("sdwan init: ping %s failed", ipnet.IP.String())
|
||||
}
|
||||
minIP := ipnet.IP
|
||||
maxIP := make(net.IP, len(minIP))
|
||||
@@ -174,13 +190,13 @@ func (s *p2pSDWAN) init(name string) error {
|
||||
}
|
||||
s.internalRoute.Add(minIP.String(), maxIP.String(), &sdwanNode{name: node.Name, id: NodeNameToID(node.Name)})
|
||||
// add sys route
|
||||
gLog.Printf(LvDEBUG, "sdwan init: addRoute %s %s %s", ipnet.String(), s.gateway.String(), s.tun.tunName)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
gConf.retryAllMemApp()
|
||||
gLog.Printf(LvINFO, "sdwan init ok")
|
||||
gLog.i("sdwan init ok")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -193,28 +209,28 @@ func (s *p2pSDWAN) run() {
|
||||
}
|
||||
|
||||
func (s *p2pSDWAN) readNodeLoop() {
|
||||
gLog.Printf(LvDEBUG, "sdwan readNodeLoop start")
|
||||
defer gLog.Printf(LvDEBUG, "sdwan readNodeLoop end")
|
||||
gLog.d("sdwan readNodeLoop start")
|
||||
defer gLog.d("sdwan readNodeLoop end")
|
||||
writeBuff := make([][]byte, 1)
|
||||
for {
|
||||
nd := GNetwork.ReadNode(time.Second * 10) // TODO: read multi packet
|
||||
if nd == nil {
|
||||
gLog.Printf(LvDev, "waiting for node data")
|
||||
gLog.dev("waiting for node data")
|
||||
continue
|
||||
}
|
||||
head := PacketHeader{}
|
||||
parseHeader(nd.Data, &head)
|
||||
gLog.Printf(LvDev, "write tun dst ip=%s,len=%d", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len(nd.Data))
|
||||
parseHeader(nd, &head)
|
||||
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 {
|
||||
writeBuff[0] = nd.Data
|
||||
writeBuff[0] = nd
|
||||
} else {
|
||||
writeBuff[0] = make([]byte, PIHeaderSize+len(nd.Data))
|
||||
copy(writeBuff[0][PIHeaderSize:], nd.Data)
|
||||
writeBuff[0] = make([]byte, PIHeaderSize+len(nd))
|
||||
copy(writeBuff[0][PIHeaderSize:], nd)
|
||||
}
|
||||
|
||||
len, err := s.tun.Write(writeBuff, PIHeaderSize)
|
||||
if err != nil {
|
||||
gLog.Printf(LvDEBUG, "write tun dst ip=%s,len=%d,error:%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String(), len, err)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,9 +246,11 @@ func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) {
|
||||
v, ok := s.internalRoute.Load(head.dst)
|
||||
if !ok || v == nil {
|
||||
if isBroadcastOrMulticast(head.dst, s.subnet) {
|
||||
gLog.Printf(LvDev, "multicast ip=%s", net.IP{byte(head.dst >> 24), byte(head.dst >> 16), byte(head.dst >> 8), byte(head.dst)}.String())
|
||||
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)
|
||||
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
|
||||
} else {
|
||||
node = v.(*sdwanNode)
|
||||
@@ -240,13 +258,13 @@ func (s *p2pSDWAN) routeTunPacket(p []byte, head *PacketHeader) {
|
||||
|
||||
err := GNetwork.WriteNode(node.id, p)
|
||||
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() {
|
||||
gLog.Printf(LvDEBUG, "sdwan readTunLoop start")
|
||||
defer gLog.Printf(LvDEBUG, "sdwan readTunLoop end")
|
||||
gLog.d("sdwan readTunLoop start")
|
||||
defer gLog.d("sdwan readTunLoop end")
|
||||
readBuff := make([][]byte, ReadTunBuffNum)
|
||||
for i := 0; i < ReadTunBuffNum; i++ {
|
||||
readBuff[i] = make([]byte, ReadTunBuffSize+PIHeaderSize)
|
||||
@@ -256,16 +274,16 @@ func (s *p2pSDWAN) readTunLoop() {
|
||||
for {
|
||||
n, err := s.tun.Read(readBuff, readBuffSize, PIHeaderSize)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "read tun fail: ", err)
|
||||
gLog.e("read tun fail: %s", err)
|
||||
return
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
if readBuffSize[i] > ReadTunBuffSize {
|
||||
gLog.Printf(LvERROR, "read tun overflow: len=", readBuffSize[i])
|
||||
gLog.e("read tun overflow: len=%d", readBuffSize[i])
|
||||
continue
|
||||
}
|
||||
parseHeader(readBuff[i][PIHeaderSize:readBuffSize[i]+PIHeaderSize], &ih)
|
||||
gLog.Printf(LvDev, "read tun dst ip=%s,len=%d", net.IP{byte(ih.dst >> 24), byte(ih.dst >> 16), byte(ih.dst >> 8), byte(ih.dst)}.String(), readBuffSize[0])
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -277,23 +295,25 @@ func (s *p2pSDWAN) StartTun() error {
|
||||
tun := &optun{}
|
||||
err := tun.Start(s.virtualIP.String(), &sdwan)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "open tun fail:", err)
|
||||
gLog.e("open tun fail:%v", err)
|
||||
s.tunErr = err.Error()
|
||||
return err
|
||||
}
|
||||
s.tun = tun
|
||||
s.tunErr = ""
|
||||
go s.readTunLoop()
|
||||
go s.readNodeLoop() // multi-thread read will cause packets out of order, resulting in slower speeds
|
||||
}
|
||||
err := setTunAddr(s.tun.tunName, s.virtualIP.String(), sdwan.Gateway, s.tun.dev)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway)
|
||||
gLog.e("setTunAddr error:%s,%s,%s,%s", err, s.tun.tunName, s.virtualIP.String(), sdwan.Gateway)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleSDWAN(subType uint16, msg []byte) error {
|
||||
gLog.Printf(LvDEBUG, "handle sdwan msg type:%d", subType)
|
||||
gLog.d("handle sdwan msg type:%d", subType)
|
||||
var err error
|
||||
switch subType {
|
||||
case MsgSDWANInfoRsp:
|
||||
@@ -301,15 +321,25 @@ func handleSDWAN(subType uint16, msg []byte) error {
|
||||
if err = json.Unmarshal(msg[openP2PHeaderSize:], &rsp); err != nil {
|
||||
return ErrMsgFormat
|
||||
}
|
||||
gLog.Println(LvINFO, "sdwan init:", prettyJson(rsp))
|
||||
if runtime.GOOS == "android" {
|
||||
AndroidSDWANConfig <- msg[openP2PHeaderSize:]
|
||||
}
|
||||
gLog.i("sdwan init:%s", prettyJson(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)
|
||||
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 {
|
||||
gLog.Println(LvERROR, "sdwan init fail: ", err)
|
||||
gLog.e("sdwan init fail: %s", err)
|
||||
if GNetwork.sdwan.tun != nil {
|
||||
GNetwork.sdwan.tun.Stop()
|
||||
GNetwork.sdwan.tun = nil
|
||||
@@ -321,3 +351,33 @@ func handleSDWAN(subType uint16, msg []byte) error {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ func TestBandwidth(t *testing.T) {
|
||||
for i := 0; i < writeNum; i++ {
|
||||
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 {
|
||||
t.Error("error")
|
||||
}
|
||||
@@ -27,12 +28,12 @@ func TestSymmetric(t *testing.T) {
|
||||
oneBuffSize := 300
|
||||
writeNum := 70
|
||||
expectTime := (oneBuffSize*writeNum - 20000) / speed
|
||||
t.Logf("expect %ds", expectTime)
|
||||
log.Printf("expect %ds", expectTime)
|
||||
startTs := time.Now()
|
||||
for i := 0; i < writeNum; i++ {
|
||||
speedl.Add(oneBuffSize, true)
|
||||
}
|
||||
t.Logf("cost %ds, expect %ds", time.Since(startTs)/time.Second, expectTime)
|
||||
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 {
|
||||
t.Error("error")
|
||||
}
|
||||
@@ -44,6 +45,7 @@ func TestSymmetric2(t *testing.T) {
|
||||
oneBuffSize := 800
|
||||
writeNum := 40
|
||||
expectTime := (oneBuffSize*writeNum - 30000) / speed
|
||||
log.Printf("expect %ds", expectTime)
|
||||
startTs := time.Now()
|
||||
for i := 0; i < writeNum; {
|
||||
if speedl.Add(oneBuffSize, true) {
|
||||
@@ -52,7 +54,7 @@ func TestSymmetric2(t *testing.T) {
|
||||
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 {
|
||||
t.Error("error")
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func UDPRead(conn *net.UDPConn, timeout time.Duration) (ra net.Addr, head *openP
|
||||
if timeout > 0 {
|
||||
err = conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "SetReadDeadline error")
|
||||
gLog.e("SetReadDeadline error")
|
||||
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{}
|
||||
err = binary.Read(bytes.NewReader(buff[:openP2PHeaderSize]), binary.LittleEndian, head)
|
||||
if err != nil || head.DataLen > uint32(len(buff)-openP2PHeaderSize) {
|
||||
gLog.Println(LvERROR, "parse p2pheader error:", err)
|
||||
if err != nil {
|
||||
gLog.e("parse p2pheader error:%s", 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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package openp2p
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,7 @@ type underlay interface {
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
Protocol() string
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
|
||||
@@ -28,6 +30,7 @@ func DefaultReadBuffer(ul underlay) (*openP2PHeader, []byte, error) {
|
||||
}
|
||||
head, err := decodeHeader(headBuf)
|
||||
if err != nil || head.MainType > 16 {
|
||||
gLog.d("DefaultReadBuffer error:%v, %d", err, head.MainType)
|
||||
return nil, nil, err
|
||||
}
|
||||
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...)
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err := ul.Write(writeBytes)
|
||||
err := writeFull(ul, writeBytes)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
@@ -47,7 +50,7 @@ func DefaultWriteBytes(ul underlay, mainType, subType uint16, data []byte) error
|
||||
func DefaultWriteBuffer(ul underlay, data []byte) error {
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err := ul.Write(data)
|
||||
err := writeFull(ul, data)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
@@ -59,7 +62,7 @@ func DefaultWriteMessage(ul underlay, mainType uint16, subType uint16, packet in
|
||||
}
|
||||
ul.SetWriteDeadline(time.Now().Add(TunnelHeartbeatTime / 2))
|
||||
ul.WLock()
|
||||
_, err = ul.Write(writeBytes)
|
||||
err = writeFull(ul, writeBytes)
|
||||
ul.WUnlock()
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ func (conn *underlayKCP) Accept() 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)
|
||||
if err != nil {
|
||||
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) {
|
||||
conn.SetDeadline(time.Now().Add(idleTimeout))
|
||||
kConn, err := kcp.NewConn(remoteAddr.String(), nil, 0, 0, conn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic.DialContext error:%s", err)
|
||||
@@ -81,7 +81,7 @@ func (conn *underlayQUIC) Accept() 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(),
|
||||
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
|
||||
if err != nil {
|
||||
@@ -96,13 +96,15 @@ func listenQuic(addr string, idleTimeout time.Duration) (*underlayQUIC, error) {
|
||||
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{
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"openp2pv1"},
|
||||
}
|
||||
Connection, err := quic.DialContext(context.Background(), conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
|
||||
&quic.Config{Versions: quicVersion, MaxIdleTimeout: idleTimeout, DisablePathMTUDiscovery: true})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
Connection, err := quic.DialContext(ctx, conn, remoteAddr, conn.LocalAddr().String(), tlsConf,
|
||||
&quic.Config{Versions: quicVersion, MaxIdleTimeout: TunnelIdleTimeout, DisablePathMTUDiscovery: true})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("quic.DialContext error:%s", err)
|
||||
}
|
||||
|
||||
@@ -46,19 +46,25 @@ func (conn *underlayTCP) WUnlock() {
|
||||
conn.writeMtx.Unlock()
|
||||
}
|
||||
|
||||
func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel) (*underlayTCP, error) {
|
||||
if mode == LinkModeTCPPunch {
|
||||
func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel) (underlay, error) {
|
||||
if mode == LinkModeTCPPunch || mode == LinkModeTCP6 {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
gLog.Println(LvDEBUG, " 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)
|
||||
// gLog.d(" send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port))
|
||||
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 {
|
||||
gLog.Println(LvDEBUG, "send tcp punch: ", err)
|
||||
// gLog.d("send tcp punch: ", err)
|
||||
return nil, err
|
||||
}
|
||||
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c}
|
||||
@@ -67,7 +73,7 @@ func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel)
|
||||
return nil, fmt.Errorf("read start msg error:%s", err)
|
||||
}
|
||||
if buff != nil {
|
||||
gLog.Println(LvDEBUG, string(buff))
|
||||
gLog.d("handshake flag:%s", string(buff))
|
||||
}
|
||||
utcp.WriteBytes(MsgP2P, MsgTunnelHandshakeAck, buff)
|
||||
return utcp, nil
|
||||
@@ -77,59 +83,46 @@ func listenTCP(host string, port int, localPort int, mode string, t *P2PTunnel)
|
||||
if compareVersion(t.config.peerVersion, PublicIPVersion) < 0 { // old version
|
||||
ipBytes := net.ParseIP(t.config.peerIP).To4()
|
||||
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
|
||||
if mode == LinkModeIntranet && gConf.Network.hasIPv4 == 0 && gConf.Network.hasUPNPorNATPMP == 0 {
|
||||
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", localPort))
|
||||
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)
|
||||
}
|
||||
var ul underlay
|
||||
if v4l != nil {
|
||||
ul = v4l.getUnderlay(tid)
|
||||
}
|
||||
|
||||
if utcp == nil {
|
||||
if ul == nil {
|
||||
return nil, ErrConnectPublicV4
|
||||
}
|
||||
return utcp, nil
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
func dialTCP(host string, port int, localPort int, mode string) (*underlayTCP, error) {
|
||||
var c net.Conn
|
||||
var err error
|
||||
if mode == LinkModeTCPPunch {
|
||||
gLog.Println(LvDev, " send tcp punch: ", fmt.Sprintf("0.0.0.0:%d", localPort), "-->", fmt.Sprintf("%s:%d", host, port))
|
||||
if c, err = reuse.DialTimeout("tcp", fmt.Sprintf("0.0.0.0:%d", localPort), fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout); err != nil {
|
||||
gLog.Println(LvDev, "send tcp punch: ", err)
|
||||
}
|
||||
network := "tcp"
|
||||
localAddr := fmt.Sprintf("0.0.0.0:%d", localPort)
|
||||
remoteAddr := fmt.Sprintf("%s:%d", host, port)
|
||||
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 = net.DialTimeout("tcp", fmt.Sprintf("%s:%d", host, port), CheckActiveTimeout)
|
||||
c, err = reuse.DialTimeout(network, localAddr, remoteAddr, CheckActiveTimeout)
|
||||
if err != nil {
|
||||
gLog.dev("send tcp punch: %v", err)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
tc := c.(*net.TCPConn)
|
||||
tc.SetKeepAlive(true)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -19,21 +18,21 @@ import (
|
||||
)
|
||||
|
||||
func update(host string, port int) error {
|
||||
gLog.Println(LvINFO, "update start")
|
||||
defer gLog.Println(LvINFO, "update end")
|
||||
gLog.i("update start")
|
||||
defer gLog.i("update end")
|
||||
caCertPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "Failed to load system root CAs:", err)
|
||||
} else {
|
||||
gLog.e("Failed to load system root CAs:%s", err)
|
||||
caCertPool = x509.NewCertPool()
|
||||
}
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
|
||||
|
||||
c := http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{RootCAs: caCertPool,
|
||||
InsecureSkipVerify: false},
|
||||
InsecureSkipVerify: gConf.TLSInsecureSkipVerify},
|
||||
},
|
||||
Timeout: time.Second * 30,
|
||||
}
|
||||
@@ -41,32 +40,36 @@ func update(host string, port int) error {
|
||||
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)))
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "update:query update list failed:", err)
|
||||
gLog.e("update:query update list failed:%s", err)
|
||||
return err
|
||||
}
|
||||
defer rsp.Body.Close()
|
||||
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
|
||||
}
|
||||
rspBuf, err := ioutil.ReadAll(rsp.Body)
|
||||
rspBuf, err := io.ReadAll(rsp.Body)
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "update:read update list failed:", err)
|
||||
gLog.e("update:read update list failed:%s", err)
|
||||
return err
|
||||
}
|
||||
updateInfo := UpdateInfo{}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
err = updateFile(updateInfo.Url, "", "openp2p")
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "update: download failed:", err)
|
||||
return err
|
||||
gLog.e("update: download failed:%s, retry...", err)
|
||||
err = updateFile(updateInfo.Url2, "", "openp2p")
|
||||
if err != nil {
|
||||
gLog.e("update: download failed:%s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -74,66 +77,80 @@ func update(host string, port int) 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)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "OpenFile %s error:%s", dstFile, err)
|
||||
gLog.e("OpenFile %s error:%s", dstFile, err)
|
||||
return err
|
||||
}
|
||||
caCertPool, err := x509.SystemCertPool()
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "Failed to load system root CAs:", err)
|
||||
} else {
|
||||
gLog.e("Failed to load system root CAs:%s", err)
|
||||
caCertPool = x509.NewCertPool()
|
||||
}
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(rootEdgeCA))
|
||||
caCertPool.AppendCertsFromPEM([]byte(ISRGRootX1))
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
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)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "download url %s error:%s", url, err)
|
||||
gLog.e("download url %s error:%s", url, err)
|
||||
output.Close()
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
n, err := io.Copy(output, response.Body)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "io.Copy error:%s", err)
|
||||
gLog.e("io.Copy error:%s", err)
|
||||
output.Close()
|
||||
return err
|
||||
}
|
||||
output.Sync()
|
||||
output.Close()
|
||||
gLog.Println(LvINFO, "download ", url, " ok")
|
||||
gLog.Printf(LvINFO, "size: %d bytes", n)
|
||||
gLog.i("download %s ok", url)
|
||||
gLog.i("size: %d bytes", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateFile(url string, checksum string, dst string) error {
|
||||
gLog.Println(LvINFO, "download ", url)
|
||||
tmpFile := filepath.Dir(os.Args[0]) + "/openp2p.tmp"
|
||||
gLog.i("download %s", url)
|
||||
tempDir := os.TempDir()
|
||||
tmpFile := filepath.Join(tempDir, "openp2p.tmp")
|
||||
err := downloadFile(url, checksum, tmpFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupFile := os.Args[0] + "0"
|
||||
err = os.Rename(os.Args[0], backupFile) // the old daemon process was using the 0 file, so it will prevent override it
|
||||
backupBase := filepath.Base(os.Args[0])
|
||||
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 {
|
||||
gLog.Printf(LvINFO, " rename %s error:%s, retry 1", os.Args[0], err)
|
||||
backupFile = os.Args[0] + "1"
|
||||
err = os.Rename(os.Args[0], backupFile)
|
||||
if runtime.GOOS == "windows" {
|
||||
backupFile = filepath.Join(tempDir, backupBase+"1")
|
||||
} 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 {
|
||||
gLog.Printf(LvINFO, " rename %s error:%s", os.Args[0], err)
|
||||
gLog.e(" rename %s error:%s", os.Args[0], err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
// extract
|
||||
gLog.Println(LvINFO, "extract files")
|
||||
gLog.i("extract files")
|
||||
err = extract(filepath.Dir(os.Args[0]), tmpFile)
|
||||
if err != nil {
|
||||
gLog.Printf(LvERROR, "extract error:%s. revert rename", err)
|
||||
os.Rename(backupFile, os.Args[0])
|
||||
gLog.e("extract error:%s. revert rename", err)
|
||||
moveFile(backupFile, os.Args[0])
|
||||
return err
|
||||
}
|
||||
os.Remove(tmpFile)
|
||||
@@ -224,16 +241,20 @@ func extractTgz(dst, src string) error {
|
||||
}
|
||||
|
||||
func cleanTempFiles() {
|
||||
tmpFile := os.Args[0] + "0"
|
||||
if _, err := os.Stat(tmpFile); err == nil {
|
||||
if err := os.Remove(tmpFile); err != nil {
|
||||
gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err)
|
||||
tempDir := os.TempDir()
|
||||
backupBase := filepath.Base(os.Args[0])
|
||||
for i := 0; i < 2; i++ {
|
||||
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 = os.Args[0] + "1"
|
||||
if _, err := os.Stat(tmpFile); err == nil {
|
||||
if err := os.Remove(tmpFile); err != nil {
|
||||
gLog.Printf(LvDEBUG, " remove %s error:%s", tmpFile, err)
|
||||
tmpFile = fmt.Sprintf("%s%s%d", tempDir, backupBase, i)
|
||||
if _, err := os.Stat(tmpFile); err == nil {
|
||||
if err := os.Remove(tmpFile); err != nil {
|
||||
gLog.d(" remove %s error:%s", tmpFile, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,13 +63,15 @@ func Discover() (nat NAT, err error) {
|
||||
return
|
||||
}
|
||||
var n int
|
||||
socket.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
_, _, err = socket.ReadFromUDP(answerBytes)
|
||||
if err != nil {
|
||||
gLog.Println(LvDEBUG, "UPNP discover error:", err)
|
||||
gLog.d("UPNP discover error:%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
socket.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
n, _, err = socket.ReadFromUDP(answerBytes)
|
||||
if err != nil {
|
||||
break
|
||||
@@ -266,7 +268,11 @@ func soapRequest(url, function, message, domain string) (r *http.Response, err e
|
||||
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,9 +5,12 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
defaultInstallPath = "/usr/local/openp2p"
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
func getOsName() (osName string) {
|
||||
|
||||
@@ -5,14 +5,16 @@ import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
defaultInstallPath = "/usr/local/openp2p"
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
func getOsName() (osName string) {
|
||||
|
||||
@@ -10,9 +10,12 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
defaultInstallPath = "/usr/local/openp2p"
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBinName = "openp2p"
|
||||
)
|
||||
|
||||
func getOsName() (osName string) {
|
||||
|
||||
@@ -11,9 +11,12 @@ import (
|
||||
"golang.org/x/sys/windows/registry"
|
||||
)
|
||||
|
||||
const (
|
||||
var (
|
||||
defaultInstallPath = "C:\\Program Files\\OpenP2P"
|
||||
defaultBinName = "openp2p.exe"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBinName = "openp2p.exe"
|
||||
)
|
||||
|
||||
func getOsName() (osName string) {
|
||||
@@ -47,7 +50,7 @@ func setRLimit() error {
|
||||
func setFirewall() {
|
||||
fullPath, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
gLog.Println(LvERROR, "add firewall error:", err)
|
||||
gLog.e("add firewall error:%s", err)
|
||||
return
|
||||
}
|
||||
isXP := false
|
||||
|
||||
@@ -1,91 +1,156 @@
|
||||
package openp2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
)
|
||||
|
||||
type v4Listener struct {
|
||||
conns sync.Map
|
||||
port int
|
||||
acceptCh chan bool
|
||||
conns sync.Map
|
||||
port int
|
||||
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)
|
||||
for {
|
||||
vl.listen()
|
||||
time.Sleep(UnderlayTCPConnectTimeout)
|
||||
}
|
||||
vl.wg.Add(1)
|
||||
go func() {
|
||||
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 {
|
||||
gLog.Printf(LvINFO, "v4Listener listen %d start", vl.port)
|
||||
defer gLog.Printf(LvINFO, "v4Listener listen %d end", vl.port)
|
||||
addr, _ := net.ResolveTCPAddr("tcp4", fmt.Sprintf("0.0.0.0:%d", vl.port))
|
||||
l, err := net.ListenTCP("tcp4", addr)
|
||||
func (vl *v4Listener) stop() {
|
||||
vl.running = false
|
||||
if vl.tcpListener != nil {
|
||||
vl.tcpListener.Close()
|
||||
}
|
||||
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 {
|
||||
gLog.Printf(LvERROR, "v4Listener listen %d error:", vl.port, err)
|
||||
gLog.e("v4Listener listen %d error:", vl.port, err)
|
||||
return err
|
||||
}
|
||||
defer l.Close()
|
||||
defer vl.tcpListener.Close()
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
c, err := vl.tcpListener.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go vl.handleConnection(c)
|
||||
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()}
|
||||
go vl.handleConnection(utcp)
|
||||
}
|
||||
vl.tcpListener = nil
|
||||
return nil
|
||||
}
|
||||
func (vl *v4Listener) handleConnection(c net.Conn) {
|
||||
gLog.Println(LvDEBUG, "v4Listener accept connection: ", c.RemoteAddr().String())
|
||||
utcp := &underlayTCP{writeMtx: &sync.Mutex{}, Conn: c, connectTime: time.Now()}
|
||||
utcp.SetReadDeadline(time.Now().Add(UnderlayTCPConnectTimeout))
|
||||
_, buff, err := utcp.ReadBuffer()
|
||||
|
||||
func (vl *v4Listener) listenUDP() error {
|
||||
gLog.d("v4Listener listenUDP %d start", vl.port)
|
||||
defer gLog.d("v4Listener listenUDP %d end", vl.port)
|
||||
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 {
|
||||
gLog.Println(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
|
||||
if string(buff) == "OpenP2P,hello" { // old client
|
||||
// save remoteIP as key
|
||||
remoteAddr := c.RemoteAddr().(*net.TCPAddr).IP
|
||||
remoteAddr := ul.RemoteAddr().(*net.TCPAddr).IP
|
||||
ipBytes := remoteAddr.To4()
|
||||
tid = uint64(binary.BigEndian.Uint32(ipBytes)) // bytes not enough for uint64
|
||||
gLog.Println(LvDEBUG, "hello ", string(buff))
|
||||
gLog.d("hello %s", string(buff))
|
||||
} else {
|
||||
if len(buff) < 8 {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
ut := i.(*underlayTCP)
|
||||
if ut.connectTime.Before(time.Now().Add(-UnderlayTCPConnectTimeout)) {
|
||||
vl.conns.Delete(idx)
|
||||
if ut, ok := i.(*underlayTCP); ok {
|
||||
if ut.connectTime.Before(time.Now().Add(-UnderlayTCPConnectTimeout)) {
|
||||
vl.conns.Delete(idx)
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
vl.conns.Store(tid, utcp)
|
||||
if len(vl.acceptCh) == 0 {
|
||||
vl.acceptCh <- true
|
||||
vl.conns.Store(tid, ul)
|
||||
select {
|
||||
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++ {
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 50):
|
||||
case <-vl.acceptCh:
|
||||
}
|
||||
if u, ok := vl.conns.LoadAndDelete(tid); ok {
|
||||
return u.(*underlayTCP)
|
||||
return u.(underlay)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 361 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 340 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 201 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 297 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 327 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 273 KiB |
@@ -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/*
|
||||
|
||||
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
|
||||
|
||||
ENTRYPOINT ["/openp2p"]
|
||||
ENTRYPOINT ["/usr/local/openp2p/openp2p"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
|
||||
echo "Building version:${DOCKER_VER}"
|
||||
echo "Building version:${VERSION}"
|
||||
echo "Running on platform: $TARGETPLATFORM"
|
||||
# TARGETPLATFORM=$(echo $TARGETPLATFORM | tr ',' '/')
|
||||
echo "Running on platform: $TARGETPLATFORM"
|
||||
@@ -25,7 +25,7 @@ sysType="linux-amd64"
|
||||
sysType="linux-mipsbe"
|
||||
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"
|
||||
|
||||
if [ -f /usr/bin/curl ]; then
|
||||
@@ -38,8 +38,9 @@ if [ $? -ne 0 ]; then
|
||||
exit 9
|
||||
fi
|
||||
echo "download ok"
|
||||
tar -xzvf openp2p.tar.gz
|
||||
chmod +x openp2p
|
||||
mkdir -p /usr/local/openp2p/
|
||||
tar -xzvf openp2p.tar.gz -C /usr/local/openp2p/
|
||||
chmod +x /usr/local/openp2p/openp2p
|
||||
pwd
|
||||
ls -l
|
||||
exit 0
|
||||
|
||||
@@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
op "openp2p/core"
|
||||
op2p "openp2p/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op.Run()
|
||||
op2p.Run()
|
||||
for i := 0; i < 10; i++ {
|
||||
go echoClient("5800-debug")
|
||||
}
|
||||
@@ -15,28 +15,28 @@ func main() {
|
||||
}
|
||||
|
||||
func echoClient(peerNode string) {
|
||||
sendDatalen := op.ReadBuffLen
|
||||
sendDatalen := op2p.ReadBuffLen
|
||||
sendBuff := make([]byte, sendDatalen)
|
||||
for i := 0; i < len(sendBuff); i++ {
|
||||
sendBuff[i] = byte('A' + i/100)
|
||||
}
|
||||
// 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)
|
||||
return
|
||||
}
|
||||
for i := 0; ; i++ {
|
||||
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)
|
||||
break
|
||||
}
|
||||
nd := op.GNetwork.ReadNode(time.Second * 10)
|
||||
nd := op2p.GNetwork.ReadNode(time.Second * 10)
|
||||
if nd == nil {
|
||||
fmt.Printf("waiting for node data\n")
|
||||
time.Sleep(time.Second * 10)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
op "openp2p/core"
|
||||
op2p "openp2p/core"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
op.Run()
|
||||
op2p.Run()
|
||||
echoServer()
|
||||
forever := make(chan bool)
|
||||
<-forever
|
||||
@@ -16,15 +16,15 @@ func main() {
|
||||
func echoServer() {
|
||||
// peerID := fmt.Sprintf("%d", core.NodeNameToID(peerNode))
|
||||
for {
|
||||
nd := op.GNetwork.ReadNode(time.Second * 10)
|
||||
nd := op2p.GNetwork.ReadNode(time.Second * 10)
|
||||
if nd == nil {
|
||||
fmt.Printf("waiting for node data\n")
|
||||
// time.Sleep(time.Second * 10)
|
||||
continue
|
||||
}
|
||||
// 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
|
||||
if err := op.GNetwork.WriteNode(nd.NodeID, nd.Data); err != nil {
|
||||
nd[0] = 'R' // echo server mark as replied
|
||||
if err := op2p.GNetwork.WriteNode(0, nd); err != nil {
|
||||
fmt.Println("write error:", err)
|
||||
break
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@ go 1.20
|
||||
|
||||
require (
|
||||
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/service v1.0.0
|
||||
github.com/openp2p-cn/totp v0.0.0-20230421034602-0f3320ffb25e
|
||||
github.com/openp2p-cn/wireguard-go v0.0.20241020
|
||||
github.com/quic-go/quic-go v0.34.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.26.0
|
||||
golang.zx2c4.com/wireguard/windows v0.5.3
|
||||
)
|
||||
@@ -20,22 +23,16 @@ require (
|
||||
github.com/golang/mock v1.7.0-rc.1 // indirect
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // 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/pkg/errors v0.9.1 // 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/templexxx/cpu v0.1.0 // indirect
|
||||
github.com/templexxx/xorsimd v0.4.2 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f // indirect
|
||||
golang.org/x/crypto v0.28.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.30.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect
|
||||
gvisor.dev/gvisor v0.0.0-20241128011400-745828301c93 // indirect
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||