mirror of
https://github.com/snltty/linker.git
synced 2025-09-27 05:25:57 +08:00
傻瓜共享版
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,5 +5,6 @@ obj
|
||||
node_modules
|
||||
/public/*
|
||||
/x64/*
|
||||
linker.share.win
|
||||
|
||||
TestResults
|
@@ -26,6 +26,7 @@ namespace linker.ics
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
publicCon.EnableSharing(tagSHARINGCONNECTIONTYPE.ICSSHARINGTYPE_PUBLIC);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
BIN
src/linker.install.win/dist/linker简易安装程序.exe
vendored
BIN
src/linker.install.win/dist/linker简易安装程序.exe
vendored
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 17 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
@@ -1,199 +0,0 @@
|
||||
import ctypes
|
||||
import os
|
||||
import zipfile
|
||||
import requests
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox, ttk
|
||||
from PIL import Image, ImageTk
|
||||
import winshell
|
||||
from win32com.client import Dispatch
|
||||
import threading
|
||||
import webbrowser
|
||||
import pythoncom
|
||||
from io import BytesIO
|
||||
import sys
|
||||
|
||||
|
||||
class InstallerApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("Linker 安装程序")
|
||||
self.root.geometry("400x400")
|
||||
self.root.resizable(False, False) # 禁止调整窗口大小
|
||||
|
||||
# 检查管理员权限
|
||||
if not self.is_admin():
|
||||
messagebox.showerror("权限不足", "当前用户没有管理员权限,请以管理员身份运行程序。")
|
||||
self.root.quit()
|
||||
|
||||
# 默认安装路径
|
||||
self.default_install_dir = r"C:\Program Files (x86)\linker"
|
||||
self.install_dir = self.default_install_dir
|
||||
|
||||
# 设置背景色
|
||||
self.root.config(bg="#f4f4f4")
|
||||
|
||||
# 加载 Logo
|
||||
self.load_logo()
|
||||
|
||||
# 快速安装按钮
|
||||
self.btn_install = tk.Button(self.root, text="快速安装", command=self.start_installation,
|
||||
bg="#007BFF", fg="white", font=("Arial", 14), width=20, height=2)
|
||||
self.btn_install.pack(pady=10)
|
||||
|
||||
# 安装目录选择按钮
|
||||
self.btn_select_path = tk.Button(self.root, text="选择安装目录", command=self.select_install_path,
|
||||
bg="#007BFF", fg="white", font=("Arial", 10), width=20)
|
||||
self.btn_select_path.pack(pady=10)
|
||||
|
||||
# 协议复选框
|
||||
self.agree_var = tk.IntVar()
|
||||
self.agree_checkbox = tk.Checkbutton(self.root, text="我已阅读并同意", variable=self.agree_var,
|
||||
bg="#f4f4f4", font=("Arial", 10))
|
||||
self.agree_checkbox.pack(pady=10)
|
||||
|
||||
# 用户协议链接
|
||||
self.terms_label = tk.Label(self.root, text="《用户许可协议》", fg="blue", cursor="hand2", bg="#f4f4f4")
|
||||
self.terms_label.pack(pady=5)
|
||||
self.terms_label.bind("<Button-1>", self.open_terms) # 点击跳转到协议网页
|
||||
|
||||
# 安装进度条
|
||||
self.progress = ttk.Progressbar(self.root, orient="horizontal", length=300, mode="determinate")
|
||||
self.progress.pack(pady=10)
|
||||
|
||||
# 状态标签
|
||||
self.label_status = tk.Label(self.root, text="", bg="#f4f4f4", font=("Arial", 10))
|
||||
self.label_status.pack(pady=5)
|
||||
|
||||
def is_admin(self):
|
||||
"""检查当前程序是否以管理员身份运行"""
|
||||
try:
|
||||
return ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
except:
|
||||
return False
|
||||
|
||||
def load_logo(self):
|
||||
"""加载 logo(本地优先,失败后从网络加载)"""
|
||||
if getattr(sys, 'frozen', False):
|
||||
base_path = sys._MEIPASS # PyInstaller 运行环境
|
||||
else:
|
||||
base_path = os.path.dirname(__file__) # 普通 Python 运行环境
|
||||
|
||||
local_logo_path = os.path.join(base_path, 'img', 'logo.png')
|
||||
remote_logo_url = "https://linker-doc.snltty.com/img/logo.png"
|
||||
|
||||
try:
|
||||
if os.path.exists(local_logo_path):
|
||||
self.logo = Image.open(local_logo_path)
|
||||
else:
|
||||
response = requests.get(remote_logo_url, timeout=10)
|
||||
response.raise_for_status()
|
||||
img_data = BytesIO(response.content)
|
||||
self.logo = Image.open(img_data)
|
||||
|
||||
# 调整大小
|
||||
self.logo = self.logo.resize((100, 100), Image.Resampling.LANCZOS)
|
||||
self.logo = ImageTk.PhotoImage(self.logo)
|
||||
self.logo_label = tk.Label(self.root, image=self.logo, bg="#f4f4f4")
|
||||
self.logo_label.pack(pady=20) # 确保 Logo 正确显示
|
||||
|
||||
except Exception as e:
|
||||
print(f"加载 logo 失败: {e}")
|
||||
self.logo = None
|
||||
|
||||
def select_install_path(self):
|
||||
"""选择安装路径"""
|
||||
self.install_dir = filedialog.askdirectory(initialdir=self.default_install_dir, title="选择安装目录")
|
||||
if not self.install_dir:
|
||||
self.install_dir = self.default_install_dir
|
||||
|
||||
def open_terms(self, event):
|
||||
"""打开用户许可协议"""
|
||||
webbrowser.open("https://linker-doc.snltty.com/docs/1%E3%80%81%E9%A6%96%E9%A1%B5")
|
||||
|
||||
def start_installation(self):
|
||||
"""开始安装"""
|
||||
if not self.agree_var.get():
|
||||
messagebox.showerror("错误", "请先勾选‘我已阅读并同意《用户许可协议》’")
|
||||
return
|
||||
|
||||
self.progress["value"] = 0
|
||||
self.btn_install.config(state=tk.DISABLED)
|
||||
|
||||
# 启动安装
|
||||
threading.Thread(target=self.install_process, daemon=True).start()
|
||||
|
||||
def install_process(self):
|
||||
"""执行安装流程"""
|
||||
zip_path = os.path.join(self.install_dir, "linker.zip")
|
||||
|
||||
if not os.path.exists(self.install_dir):
|
||||
os.makedirs(self.install_dir)
|
||||
|
||||
self.update_status("正在下载 ZIP 文件...")
|
||||
self.download_zip("https://static.qbcode.cn/downloads/linker/v1.6.9/linker-win-x64.zip", zip_path)
|
||||
self.progress["value"] = 30
|
||||
|
||||
self.update_status("正在解压文件...")
|
||||
extract_folder = self.extract_zip(zip_path, self.install_dir)
|
||||
self.progress["value"] = 70
|
||||
|
||||
os.remove(zip_path)
|
||||
|
||||
linker_exe = self.find_linker_exe(extract_folder)
|
||||
if linker_exe:
|
||||
self.update_status("创建快捷方式...")
|
||||
pythoncom.CoInitialize()
|
||||
self.create_shortcut(linker_exe, "linker")
|
||||
self.progress["value"] = 100
|
||||
messagebox.showinfo("完成", "安装完成!")
|
||||
else:
|
||||
messagebox.showerror("错误", "linker.tray.win.exe 未找到,安装失败!")
|
||||
|
||||
self.btn_install.config(state=tk.NORMAL)
|
||||
|
||||
def download_zip(self, url, save_path):
|
||||
"""下载 ZIP 文件"""
|
||||
try:
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
with open(save_path, 'wb') as file:
|
||||
for chunk in response.iter_content(1024):
|
||||
file.write(chunk)
|
||||
except requests.exceptions.RequestException as e:
|
||||
messagebox.showerror("下载错误", f"下载失败: {e}")
|
||||
self.btn_install.config(state=tk.NORMAL)
|
||||
|
||||
def extract_zip(self, zip_path, extract_to):
|
||||
"""解压 ZIP 文件"""
|
||||
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||
zip_ref.extractall(extract_to)
|
||||
return extract_to
|
||||
|
||||
def find_linker_exe(self, extract_folder):
|
||||
"""寻找 linker.tray.win.exe"""
|
||||
for root, dirs, files in os.walk(extract_folder):
|
||||
if "linker.tray.win.exe" in files:
|
||||
return os.path.join(root, "linker.tray.win.exe")
|
||||
return None
|
||||
|
||||
def create_shortcut(self, target_path, shortcut_name):
|
||||
"""创建快捷方式"""
|
||||
desktop = winshell.desktop()
|
||||
shortcut_path = os.path.join(desktop, f"{shortcut_name}.lnk")
|
||||
shell = Dispatch('WScript.Shell')
|
||||
shortcut = shell.CreateShortcut(shortcut_path)
|
||||
shortcut.TargetPath = target_path
|
||||
shortcut.WorkingDirectory = os.path.dirname(target_path)
|
||||
shortcut.Save()
|
||||
|
||||
def update_status(self, message):
|
||||
"""更新状态标签"""
|
||||
self.label_status.config(text=message)
|
||||
self.root.update_idletasks()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = InstallerApp(root)
|
||||
root.mainloop()
|
@@ -1,46 +0,0 @@
|
||||
|
||||
added_files = [
|
||||
('img/logo.png','img/')
|
||||
]
|
||||
|
||||
# 配置打包参数
|
||||
a = Analysis(
|
||||
['main1.0.py'],
|
||||
pathex=['D:/py/linker/.venv/Lib/site-packages'],
|
||||
binaries=[],
|
||||
datas=added_files ,
|
||||
hiddenimports=['requests', 'pywin32', 'winshell', 'tkinterweb'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='linker简易安装程序',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
icon='D:\py\linker\src\linker.install.win\img\linker.ico'
|
||||
)
|
||||
|
Binary file not shown.
@@ -1,4 +0,0 @@
|
||||
加载所需要的模块
|
||||
pip install -r requirements.txt
|
||||
打包
|
||||
pyinstaller .\main1.0.spec
|
@@ -189,7 +189,7 @@ namespace linker.messenger.entry
|
||||
if ((modules & ExcludeModule.Socks5) != ExcludeModule.Socks5)
|
||||
serviceProvider.UseSocks5Client();
|
||||
if ((modules & ExcludeModule.Tuntap) != ExcludeModule.Tuntap)
|
||||
serviceProvider.UseTuntapClient();
|
||||
serviceProvider.UseTuntapClient(configDic);
|
||||
if ((modules & ExcludeModule.Updater) != ExcludeModule.Updater)
|
||||
serviceProvider.UseUpdaterClient();
|
||||
serviceProvider.UseExRoute().UseAccessClient().UseDecenterClient().UsePcpClient().UseRelayClient().UseSyncClient().UseTunnelClient().UseFlowClient();
|
||||
|
@@ -73,7 +73,7 @@ namespace linker.messenger.flow.messenger
|
||||
sForwardFlow.Update();
|
||||
SForwardFlowRequestInfo info = serializer.Deserialize<SForwardFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
|
||||
if (sForwardServerStore.SecretKey == info.SecretKey)
|
||||
if (sForwardServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
info.GroupId = string.Empty;
|
||||
}
|
||||
@@ -97,7 +97,7 @@ namespace linker.messenger.flow.messenger
|
||||
{
|
||||
relayFlow.Update();
|
||||
RelayFlowRequestInfo info = serializer.Deserialize<RelayFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if (relayServerStore.SecretKey == info.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
info.GroupId = string.Empty;
|
||||
}
|
||||
|
@@ -292,7 +292,7 @@ namespace linker.messenger.relay.messenger
|
||||
public async Task UpdateNodeForward(IConnection connection)
|
||||
{
|
||||
RelayServerNodeUpdateWrapInfo info = serializer.Deserialize<RelayServerNodeUpdateWrapInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if (info.SecretKey == relayServerStore.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
await relayServerTransfer.UpdateNodeReport(info.Info).ConfigureAwait(false);
|
||||
connection.Write(Helper.TrueArray);
|
||||
@@ -337,7 +337,7 @@ namespace linker.messenger.relay.messenger
|
||||
public void AccessCdkey(IConnection connection)
|
||||
{
|
||||
string secretKey = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
connection.Write(relayServerStore.SecretKey == secretKey ? Helper.TrueArray : Helper.FalseArray);
|
||||
connection.Write(relayServerStore.ValidateSecretKey(secretKey) ? Helper.TrueArray : Helper.FalseArray);
|
||||
}
|
||||
/// <summary>
|
||||
/// 添加CDKEY
|
||||
@@ -352,7 +352,7 @@ namespace linker.messenger.relay.messenger
|
||||
connection.Write(Helper.FalseArray);
|
||||
return;
|
||||
}
|
||||
if (relayServerStore.SecretKey != info.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
connection.Write(Helper.FalseArray);
|
||||
return;
|
||||
@@ -376,7 +376,7 @@ namespace linker.messenger.relay.messenger
|
||||
connection.Write(Helper.FalseArray);
|
||||
return;
|
||||
}
|
||||
if (relayServerStore.SecretKey == info.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
await relayServerCdkeyStore.Del(info.Id).ConfigureAwait(false);
|
||||
}
|
||||
@@ -401,7 +401,7 @@ namespace linker.messenger.relay.messenger
|
||||
connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
|
||||
return;
|
||||
}
|
||||
if (relayServerStore.SecretKey != info.SecretKey && string.IsNullOrWhiteSpace(info.UserId))
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey) && string.IsNullOrWhiteSpace(info.UserId))
|
||||
{
|
||||
connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
|
||||
return;
|
||||
@@ -427,7 +427,7 @@ namespace linker.messenger.relay.messenger
|
||||
connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { }));
|
||||
return;
|
||||
}
|
||||
if (relayServerStore.SecretKey != info.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(info.SecretKey))
|
||||
{
|
||||
connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { }));
|
||||
return;
|
||||
|
@@ -2,10 +2,7 @@
|
||||
{
|
||||
public interface IRelayServerStore
|
||||
{
|
||||
/// <summary>
|
||||
/// 中继密钥
|
||||
/// </summary>
|
||||
public string SecretKey { get; }
|
||||
public bool ValidateSecretKey(string secretKey);
|
||||
|
||||
/// <summary>
|
||||
/// 设置中继密钥
|
||||
|
@@ -15,7 +15,7 @@ namespace linker.messenger.relay.server.validator
|
||||
|
||||
public async Task<string> Validate(linker.messenger.relay.client.transport.RelayInfo relayInfo, SignCacheInfo fromMachine, SignCacheInfo toMachine)
|
||||
{
|
||||
if (relayInfo.SecretKey != relayServerStore.SecretKey)
|
||||
if (relayServerStore.ValidateSecretKey(relayInfo.SecretKey) == false)
|
||||
{
|
||||
return $"SecretKey validate fail";
|
||||
}
|
||||
|
@@ -2,10 +2,6 @@
|
||||
{
|
||||
public interface ISForwardServerStore
|
||||
{
|
||||
/// <summary>
|
||||
/// 穿透密钥
|
||||
/// </summary>
|
||||
public string SecretKey { get; }
|
||||
/// <summary>
|
||||
/// 缓冲区大小
|
||||
/// </summary>
|
||||
@@ -19,6 +15,7 @@
|
||||
/// </summary>
|
||||
public int[] TunnelPortRange { get; }
|
||||
|
||||
public bool ValidateSecretKey(string key);
|
||||
/// <summary>
|
||||
/// 穿透密钥
|
||||
/// </summary>
|
||||
|
@@ -17,7 +17,7 @@ namespace linker.messenger.sforward.server.validator
|
||||
|
||||
public async Task<string> Validate(SignCacheInfo signCacheInfo, SForwardAddInfo sForwardAddInfo)
|
||||
{
|
||||
if (sForwardServerStore.SecretKey != sForwardAddInfo.SecretKey)
|
||||
if (sForwardServerStore.ValidateSecretKey(sForwardAddInfo.SecretKey))
|
||||
{
|
||||
return $"sforward secretKey 【{sForwardAddInfo.SecretKey}】 valid fail";
|
||||
}
|
||||
|
@@ -7,12 +7,9 @@ namespace linker.messenger.signin
|
||||
/// </summary>
|
||||
public interface ISignInServerStore : IStore<SignCacheInfo>
|
||||
{
|
||||
/// <summary>
|
||||
/// 信标密钥
|
||||
/// </summary>
|
||||
public string SecretKey { get; }
|
||||
|
||||
public int CleanDays { get; }
|
||||
|
||||
public bool ValidateSecretKey(string key);
|
||||
/// <summary>
|
||||
/// 设置信标密钥
|
||||
/// </summary>
|
||||
|
@@ -51,13 +51,11 @@
|
||||
/// <returns></returns>
|
||||
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(signInServerStore.SecretKey) == false)
|
||||
{
|
||||
if (signInfo.Args.TryGetValue("signin-secretkey", out string secretkey) == false || secretkey != signInServerStore.SecretKey)
|
||||
signInfo.Args.TryGetValue("signin-secretkey", out string secretkey);
|
||||
if (signInServerStore.ValidateSecretKey(secretkey) == false)
|
||||
{
|
||||
return $"server secretkey validate fail";
|
||||
}
|
||||
}
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
return string.Empty;
|
||||
}
|
||||
|
@@ -4,13 +4,17 @@ namespace linker.messenger.store.file.relay
|
||||
{
|
||||
public sealed class RelayServerStore : IRelayServerStore
|
||||
{
|
||||
public string SecretKey => config.Data.Server.Relay.SecretKey;
|
||||
private readonly FileConfig config;
|
||||
public RelayServerStore(FileConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public bool ValidateSecretKey(string secretKey)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(config.Data.Server.Relay.SecretKey) || config.Data.Server.Relay.SecretKey == secretKey;
|
||||
}
|
||||
|
||||
public void SetSecretKey(string secretKey)
|
||||
{
|
||||
config.Data.Server.Relay.SecretKey = secretKey;
|
||||
@@ -21,5 +25,7 @@ namespace linker.messenger.store.file.relay
|
||||
config.Data.Update();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,6 @@ namespace linker.messenger.store.file.sforward
|
||||
{
|
||||
public sealed class SForwardServerStore : ISForwardServerStore
|
||||
{
|
||||
public string SecretKey => fileConfig.Data.Server.SForward.SecretKey;
|
||||
|
||||
public byte BufferSize => fileConfig.Data.Server.SForward.BufferSize;
|
||||
|
||||
public int WebPort => fileConfig.Data.Server.SForward.WebPort;
|
||||
@@ -18,6 +16,10 @@ namespace linker.messenger.store.file.sforward
|
||||
this.fileConfig = fileConfig;
|
||||
}
|
||||
|
||||
public bool ValidateSecretKey(string key)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(fileConfig.Data.Server.SForward.SecretKey) || fileConfig.Data.Server.SForward.SecretKey == key;
|
||||
}
|
||||
public bool SetSecretKey(string key)
|
||||
{
|
||||
fileConfig.Data.Server.SForward.SecretKey = key;
|
||||
|
@@ -5,7 +5,6 @@ namespace linker.messenger.store.file.signIn
|
||||
{
|
||||
public sealed class SignInServerStore : ISignInServerStore
|
||||
{
|
||||
public string SecretKey => fileConfig.Data.Server.SignIn.SecretKey;
|
||||
public int CleanDays => fileConfig.Data.Server.SignIn.CleanDays;
|
||||
|
||||
private readonly Storefactory dBfactory;
|
||||
@@ -17,6 +16,11 @@ namespace linker.messenger.store.file.signIn
|
||||
liteCollection = dBfactory.GetCollection<SignCacheInfo>("signs");
|
||||
this.fileConfig = fileConfig;
|
||||
}
|
||||
|
||||
public bool ValidateSecretKey(string key)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(fileConfig.Data.Server.SignIn.SecretKey) || fileConfig.Data.Server.SignIn.SecretKey == key;
|
||||
}
|
||||
public void SetSecretKey(string secretKey)
|
||||
{
|
||||
fileConfig.Data.Server.SignIn.SecretKey = secretKey;
|
||||
|
@@ -5,14 +5,16 @@ namespace linker.messenger.store.file.updater
|
||||
{
|
||||
public sealed class UpdaterServerStore : IUpdaterServerStore
|
||||
{
|
||||
public string SecretKey => fileConfig.Data.Server.Updater.SecretKey;
|
||||
|
||||
private readonly FileConfig fileConfig;
|
||||
public UpdaterServerStore(FileConfig fileConfig)
|
||||
{
|
||||
this.fileConfig = fileConfig;
|
||||
}
|
||||
|
||||
public bool ValidateSecretKey(string key)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(fileConfig.Data.Server.Updater.SecretKey) || fileConfig.Data.Server.Updater.SecretKey == key;
|
||||
}
|
||||
public void SetSecretKey(string key)
|
||||
{
|
||||
fileConfig.Data.Server.Updater.SecretKey = key;
|
||||
|
@@ -1,11 +1,17 @@
|
||||
using linker.messenger.api;
|
||||
using linker.libs;
|
||||
using linker.libs.extends;
|
||||
using linker.messenger.api;
|
||||
using linker.messenger.decenter;
|
||||
using linker.messenger.exroute;
|
||||
using linker.messenger.signin;
|
||||
using linker.messenger.tunnel;
|
||||
using linker.messenger.tuntap.lease;
|
||||
using linker.messenger.tuntap.messenger;
|
||||
using linker.tun;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
namespace linker.messenger.tuntap
|
||||
{
|
||||
public static class Entry
|
||||
@@ -35,7 +41,7 @@ namespace linker.messenger.tuntap
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
public static ServiceProvider UseTuntapClient(this ServiceProvider serviceProvider)
|
||||
public static ServiceProvider UseTuntapClient(this ServiceProvider serviceProvider, Dictionary<string, string> configDic)
|
||||
{
|
||||
TuntapProxy tuntapProxy = serviceProvider.GetService<TuntapProxy>();
|
||||
TuntapTransfer tuntapTransfer = serviceProvider.GetService<TuntapTransfer>();
|
||||
@@ -63,6 +69,54 @@ namespace linker.messenger.tuntap
|
||||
DecenterClientTransfer decenterClientTransfer = serviceProvider.GetService<DecenterClientTransfer>();
|
||||
decenterClientTransfer.AddDecenters(new List<IDecenter> { serviceProvider.GetService<TuntapDecenter>() });
|
||||
|
||||
if (configDic.TryGetValue("Tuntap", out string base64))
|
||||
{
|
||||
ITuntapClientStore tuntapClientStore = serviceProvider.GetService<ITuntapClientStore>();
|
||||
ILeaseClientStore leaseClientStore = serviceProvider.GetService<ILeaseClientStore>();
|
||||
ISignInClientStore signInClientStore = serviceProvider.GetService<ISignInClientStore>();
|
||||
try
|
||||
{
|
||||
JsonElement doc = JsonDocument.Parse(Encoding.UTF8.GetString(Convert.FromBase64String(base64))).RootElement;
|
||||
if (doc.TryGetProperty("IP", out JsonElement ip))
|
||||
{
|
||||
tuntapClientStore.Info.IP = IPAddress.Parse(ip.GetString());
|
||||
}
|
||||
if (doc.TryGetProperty("PrefixLength", out JsonElement prefixLength))
|
||||
{
|
||||
tuntapClientStore.Info.PrefixLength = prefixLength.GetByte();
|
||||
}
|
||||
if (doc.TryGetProperty("Lans", out JsonElement lans))
|
||||
{
|
||||
tuntapClientStore.Info.Lans = lans.GetString().DeJson<List<TuntapLanInfo>>();
|
||||
}
|
||||
if (doc.TryGetProperty("Name", out JsonElement name))
|
||||
{
|
||||
tuntapClientStore.Info.Name = name.GetString();
|
||||
}
|
||||
if (doc.TryGetProperty("Running", out JsonElement running))
|
||||
{
|
||||
tuntapClientStore.Info.Running = running.GetBoolean();
|
||||
}
|
||||
if (doc.TryGetProperty("Switch", out JsonElement _switch))
|
||||
{
|
||||
tuntapClientStore.Info.Switch = (TuntapSwitch)_switch.GetInt32();
|
||||
}
|
||||
if (doc.TryGetProperty("Forwards", out JsonElement forwards))
|
||||
{
|
||||
tuntapClientStore.Info.Forwards = forwards.GetString().DeJson<List<TuntapForwardInfo>>();
|
||||
}
|
||||
if (doc.TryGetProperty("Lease", out JsonElement lease))
|
||||
{
|
||||
leaseClientStore.Set(signInClientStore.Group.Id, lease.GetString().DeJson<LeaseInfo>());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerHelper.Instance.Error(ex);
|
||||
}
|
||||
tuntapClientStore.Confirm();
|
||||
}
|
||||
|
||||
return serviceProvider;
|
||||
}
|
||||
|
||||
|
@@ -2,10 +2,8 @@
|
||||
{
|
||||
public interface IUpdaterServerStore
|
||||
{
|
||||
/// <summary>
|
||||
/// 更新密钥
|
||||
/// </summary>
|
||||
public string SecretKey { get; }
|
||||
|
||||
public bool ValidateSecretKey(string key);
|
||||
/// <summary>
|
||||
/// 设置更新密钥
|
||||
/// </summary>
|
||||
|
@@ -151,7 +151,7 @@ namespace linker.messenger.updater
|
||||
public void ConfirmServer(IConnection connection)
|
||||
{
|
||||
UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if (updaterServerStore.SecretKey == confirm.SecretKey)
|
||||
if (updaterServerStore.ValidateSecretKey(confirm.SecretKey))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(confirm.Version))
|
||||
{
|
||||
@@ -172,7 +172,7 @@ namespace linker.messenger.updater
|
||||
public void ExitServer(IConnection connection)
|
||||
{
|
||||
UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
|
||||
if (updaterServerStore.SecretKey == confirm.SecretKey)
|
||||
if (updaterServerStore.ValidateSecretKey(confirm.SecretKey))
|
||||
{
|
||||
Environment.Exit(1);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ namespace linker.messenger.updater
|
||||
}
|
||||
|
||||
//需要密钥
|
||||
if (confirm.All && updaterServerStore.SecretKey != confirm.SecretKey)
|
||||
if (confirm.All && updaterServerStore.ValidateSecretKey(confirm.SecretKey) == false)
|
||||
{
|
||||
connection.Write(Helper.FalseArray);
|
||||
return;
|
||||
|
@@ -1,9 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<project ver="10" name="linker.share.win" libEmbed="true" icon="res\favicon.ico" ui="win" output="linker.share.win.exe" CompanyName="snltty" FileDescription="linker.share.win" LegalCopyright="Copyright (C) snltty 2025" ProductName="linker.share.win" InternalName="linker.share.win" FileVersion="0.0.0.1" ProductVersion="0.0.0.1" publishDir="/dist/" dstrip="false">
|
||||
<project ver="10" name="linker.share.win" libEmbed="true" icon="res\favicon.ico" ui="win" output="linker.share.win.exe" CompanyName="snltty" FileDescription="linker.share.win" LegalCopyright="Copyright (C) snltty 2025" ProductName="linker.share.win" InternalName="linker.share.win" FileVersion="0.0.0.3" ProductVersion="0.0.0.3" publishDir="/dist/" dstrip="false">
|
||||
<file name="main.aardio" path="main.aardio" comment="main.aardio"/>
|
||||
<folder name="资源文件" path="res" embed="true" local="false" ignored="false">
|
||||
<file name="connect.aardio" path="res\connect.aardio" comment="res\connect.aardio"/>
|
||||
<file name="share.aardio" path="res\share.aardio" comment="res\share.aardio"/>
|
||||
<file name="info.aardio" path="res\info.aardio" comment="res\info.aardio"/>
|
||||
</folder>
|
||||
<folder name="窗体文件" path="dlg" comment="目录" embed="true"/>
|
||||
</project>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
//RUNAS//
|
||||
import win.ui;
|
||||
/*DSG{{*/
|
||||
mainForm = win.form(text="linker.share.win";right=250;bottom=291;border="thin";max=false;topmost=1)
|
||||
mainForm = win.form(text="linker局域网共享";right=352;bottom=312;border="thin";max=false;topmost=1)
|
||||
mainForm.add(
|
||||
mainTab={cls="tab";left=1;top=-2;right=250;bottom=254;aw=1;edge=1;z=1}
|
||||
mainTab={cls="tab";left=8;top=1;right=346;bottom=306;z=1}
|
||||
)
|
||||
/*}}*/
|
||||
|
||||
@@ -16,6 +16,7 @@ if(!atom){
|
||||
|
||||
mainForm.mainTab.loadForm("\res\connect.aardio");
|
||||
mainForm.mainTab.loadForm("\res\share.aardio");
|
||||
mainForm.mainTab.loadForm("\res\info.aardio");
|
||||
|
||||
mainForm.show();
|
||||
return win.loopMessage();
|
@@ -1,7 +1,16 @@
|
||||
import win.ui;
|
||||
/*DSG{{*/
|
||||
var winform = win.form(text="连接共享";right=759;bottom=469)
|
||||
winform.add()
|
||||
var winform = win.form(text="连接共享";right=330;bottom=276)
|
||||
winform.add(
|
||||
button={cls="button";text="删除";left=223;top=10;right=269;bottom=31;z=3};
|
||||
button2={cls="button";text="新增";left=275;top=10;right=321;bottom=31;z=4};
|
||||
button3={cls="button";text="开启连接";left=120;top=234;right=208;bottom=269;z=8};
|
||||
checkbox={cls="checkbox";text="自动开启连接";left=10;top=242;right=105;bottom=261;z=7};
|
||||
combobox={cls="combobox";left=67;top=11;right=217;bottom=32;edge=1;items={};mode="dropdown";z=2};
|
||||
groupbox={cls="groupbox";text="在线的";left=10;top=37;right=321;bottom=228;edge=1;z=5};
|
||||
listview2={cls="listview";left=17;top=56;right=314;bottom=221;edge=1;z=6};
|
||||
static={cls="static";text="选择共享";left=10;top=12;right=67;bottom=29;transparent=1;z=1}
|
||||
)
|
||||
/*}}*/
|
||||
|
||||
winform.show();
|
||||
|
@@ -1,13 +1,286 @@
|
||||
import win.ui;
|
||||
/*DSG{{*/
|
||||
var winform = win.form(text="发出共享";right=243;bottom=254)
|
||||
var winform = win.form(text="发出共享";right=330;bottom=283)
|
||||
winform.add(
|
||||
button={cls="button";text="生成";left=195;top=12;right=234;bottom=35;z=3};
|
||||
edit={cls="edit";text="Edit";left=44;top=14;right=189;bottom=35;edge=1;z=1};
|
||||
static={cls="static";text="密钥";left=11;top=16;right=48;bottom=35;transparent=1;z=2}
|
||||
btnCopy={cls="button";text="复制共享密钥";left=229;top=240;right=321;bottom=267;z=16};
|
||||
btnNewSecretkey={cls="button";text="新ID";left=276;top=41;right=321;bottom=64;z=14};
|
||||
btnSave={cls="button";text="保存";left=276;top=12;right=321;bottom=35;z=19};
|
||||
btnStart={cls="button";text="开启共享";left=122;top=237;right=210;bottom=272;z=15};
|
||||
cbLan={cls="combobox";left=80;top=102;right=205;bottom=123;edge=1;items={};mode="dropdown";z=4};
|
||||
cbLanPrefixlength={cls="combobox";left=248;top=102;right=314;bottom=123;edge=1;items={};mode="dropdown";z=7};
|
||||
cbRoutePrefixlength={cls="combobox";left=248;top=131;right=314;bottom=152;edge=1;items={};mode="dropdown";z=9};
|
||||
ckAuto={cls="checkbox";text="自动开启共享";left=11;top=246;right=115;bottom=265;z=17};
|
||||
groupbox={cls="groupbox";text="共享网段";left=11;top=75;right=321;bottom=228;edge=1;z=3};
|
||||
static={cls="static";text="服务器";left=11;top=16;right=54;bottom=33;transparent=1;z=2};
|
||||
static3={cls="static";text="内网网段";left=19;top=105;right=72;bottom=122;transparent=1;z=5};
|
||||
static4={cls="static";text="掩码";left=216;top=105;right=247;bottom=119;transparent=1;z=6};
|
||||
static5={cls="static";text="掩码";left=216;top=135;right=247;bottom=152;transparent=1;z=8};
|
||||
static6={cls="static";text="映射路由";left=19;top=134;right=72;bottom=154;transparent=1;z=11};
|
||||
static9={cls="static";text="唯一ID";left=11;top=46;right=52;bottom=63;transparent=1;z=13};
|
||||
textMsg={cls="edit";left=19;top=163;right=314;bottom=219;autohscroll=false;edge=1;multiline=1;readonly=1;z=18};
|
||||
textRoute={cls="ipaddress";left=80;top=132;right=205;bottom=153;edge=1;z=10};
|
||||
textSecretkey={cls="edit";left=58;top=42;right=273;bottom=63;edge=1;z=12};
|
||||
textServer={cls="edit";left=58;top=13;right=273;bottom=34;edge=1;z=1}
|
||||
)
|
||||
/*}}*/
|
||||
|
||||
import dotNet;
|
||||
compiler = dotNet.createCompiler("C#");
|
||||
compiler.Source = /******
|
||||
using System;
|
||||
namespace LinkerLibsSpace
|
||||
{
|
||||
public class LinkerLibs
|
||||
{
|
||||
public string GetGuid()
|
||||
{
|
||||
return Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
******/
|
||||
assembly = compiler.CompileOrFail();
|
||||
assembly.import("LinkerLibsSpace");
|
||||
winform.linkerLibs = LinkerLibsSpace.LinkerLibs();
|
||||
|
||||
|
||||
if(mainForm != null)
|
||||
{
|
||||
winform.mainForm = mainForm;
|
||||
winform.mainForm.shareForm =winform;
|
||||
}
|
||||
|
||||
import crypt.aes;
|
||||
import crypt.bin;
|
||||
import web.json;
|
||||
winform.aes = crypt.aes();
|
||||
winform.aes.setPassword("BC7F0C7C-6A1E-4792-A17F-0008D300C076");
|
||||
winform.shareSettingTab = {
|
||||
"server"="",
|
||||
"secretkey" = winform.linkerLibs.GetGuid(),
|
||||
"lan"="",
|
||||
"lanPrefixlength"="24",
|
||||
"route"="192.168.188.0",
|
||||
"routePrefixlength"="24",
|
||||
"auto"=false
|
||||
}
|
||||
winform.loadShareSetting = function()
|
||||
{
|
||||
configStr = string.load("~/configs/share.json");
|
||||
if(configStr)
|
||||
{
|
||||
json = web.json.parse(winform.aes.decrypt(configStr));
|
||||
winform.shareSettingTab.server = json.server;
|
||||
winform.shareSettingTab.secretkey = json.secretkey;
|
||||
winform.shareSettingTab.lan = json.lan;
|
||||
winform.shareSettingTab.lanPrefixlength = json.lanPrefixlength;
|
||||
winform.shareSettingTab.route = json.route;
|
||||
winform.shareSettingTab.routePrefixlength = json.routePrefixlength;
|
||||
winform.shareSettingTab.auto = json.auto;
|
||||
}
|
||||
winform.btnSave.oncommand = function()
|
||||
{
|
||||
winform.saveShareSetting();
|
||||
}
|
||||
}
|
||||
winform.saveShareSetting = function()
|
||||
{
|
||||
import console;
|
||||
winform.shareSettingTab.server = winform.textServer.text;
|
||||
winform.shareSettingTab.secretkey = winform.textSecretkey.text;
|
||||
winform.shareSettingTab.lan = winform.cbLan.selText;
|
||||
winform.shareSettingTab.lanPrefixlength = winform.cbLanPrefixlength.selText;
|
||||
winform.shareSettingTab.route = winform.textRoute.text;
|
||||
winform.shareSettingTab.routePrefixlength = winform.cbRoutePrefixlength.selText;
|
||||
winform.shareSettingTab.auto = winform.ckAuto.checked;
|
||||
config = winform.aes.encrypt(web.json.stringify(winform.shareSettingTab));
|
||||
import fsys;
|
||||
try{fsys.delete("~/configs/share.json");}catch(e){}
|
||||
string.save("~/configs/share.json",config ,true);
|
||||
win.msgbox("已保存配置",,0x01);
|
||||
}
|
||||
|
||||
|
||||
winform.initLans = function()
|
||||
{
|
||||
import com.wmi;
|
||||
|
||||
winform.prefixlengthDefault = "24";
|
||||
winform.prefixlengthTab = {32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16};
|
||||
winform.cbLanPrefixlength.items = winform.prefixlengthTab;
|
||||
winform.cbLanPrefixlength.selText = winform.shareSettingTab.lanPrefixlength;
|
||||
winform.cbLanPrefixlength.onEditChange = function(){
|
||||
winform.cbLanPrefixlength.selText = winform.prefixlengthDefault;
|
||||
}
|
||||
winform.cbRoutePrefixlength.items = winform.prefixlengthTab;
|
||||
winform.cbRoutePrefixlength.selText = winform.shareSettingTab.routePrefixlength;
|
||||
winform.cbRoutePrefixlength.onEditChange = function(){
|
||||
winform.cbRoutePrefixlength.selText = winform.prefixlengthDefault;
|
||||
}
|
||||
|
||||
lansTab = {};
|
||||
for item in com.wmi.eachProperties("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=true") {
|
||||
table.push(lansTab,item.IPAddress[1]);
|
||||
}
|
||||
winform.cbLan.items = lansTab;
|
||||
|
||||
if(#winform.shareSettingTab.lan)winform.cbLan.selText = winform.shareSettingTab.lan
|
||||
else winform.cbLan.selText = lansTab[1];
|
||||
winform.cbLan.onEditChange = function(){
|
||||
winform.cbLan.selText = lansTab[1];
|
||||
}
|
||||
winform.textRoute.text = winform.shareSettingTab.route;
|
||||
|
||||
function setText()
|
||||
{
|
||||
winform.textMsg.text = "对方使用【"+winform.textRoute.text+"/"+winform.cbRoutePrefixlength.selText+"】访问你的【"+winform.cbLan.selText+"/"+winform.cbLanPrefixlength.selText+"】,这能有效的解决网段冲突,和隐藏你的真实内网网段";
|
||||
}
|
||||
winform.cbLan.onListChange = function(){
|
||||
setText();
|
||||
}
|
||||
winform.cbLanPrefixlength.onListChange = function(){
|
||||
setText();
|
||||
}
|
||||
winform.cbRoutePrefixlength.onListChange = function(){
|
||||
setText();
|
||||
}
|
||||
winform.textRoute.onChange = function(){
|
||||
if(owner.onModified)owner.onModified(true);
|
||||
setText();
|
||||
}
|
||||
setText();
|
||||
}
|
||||
|
||||
winform.initSecretkey = function()
|
||||
{
|
||||
winform.textSecretkey.text = winform.shareSettingTab.secretkey;
|
||||
winform.btnNewSecretkey.oncommand = function(){
|
||||
result = win.msgbox("旧的共享密钥将不可用,确认使用一个新ID吗",,0x01/*MB_YESNOCANCEL*/ );
|
||||
if(result == 1)
|
||||
{
|
||||
winform.textSecretkey.text = winform.linkerLibs.GetGuid();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
winform.initAuto = function()
|
||||
{
|
||||
winform.ckAuto.checked = winform.shareSettingTab.auto;
|
||||
}
|
||||
winform.initServer = function()
|
||||
{
|
||||
winform.textServer.text = winform.shareSettingTab.server;
|
||||
}
|
||||
|
||||
winform.getShareKey = function()
|
||||
{
|
||||
json = {
|
||||
"Client"={
|
||||
CApi = {ApiPort:18003,ApiPassword="snltty",WebPort:0},
|
||||
Servers={{Name="linker",Host=winform.textServer.text}},
|
||||
Groups={{Name="Linker",Id="Linker",Password=winform.textSecretkey.text}}
|
||||
},
|
||||
"Common"= { Modes={"client"},Install=true },
|
||||
"Tuntap" = {
|
||||
Running=true,
|
||||
Lans={
|
||||
{
|
||||
IP= winform.cbLan.selText,
|
||||
PrefixLength = winform.cbLanPrefixlength.selText,
|
||||
MapIP = winform.textRoute.text,
|
||||
MapPrefixLength = winform.cbRoutePrefixlength.selText
|
||||
}
|
||||
},
|
||||
Lease={
|
||||
IP="10.18.0.0",
|
||||
PrefixLength=16,
|
||||
Name="linkers"
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!#json.Client.Servers[1].Host)
|
||||
{
|
||||
winform.textServer.showErrorTip("必填","请填写服务器")
|
||||
return null;
|
||||
}
|
||||
if(!#json.Client.Groups[1].Password)
|
||||
{
|
||||
winform.textSecretkey.showErrorTip("必选","请填写唯一id")
|
||||
return null;
|
||||
}
|
||||
if(!#json.Tuntap.Lans[1].IP)
|
||||
{
|
||||
winform.cbLan.showErrorTip("必选","请选择一个局域网IP")
|
||||
return null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
winform.initSHareKey = function()
|
||||
{
|
||||
winform.btnCopy.oncommand = function()
|
||||
{
|
||||
import win.clip;
|
||||
json = winform.getShareKey();
|
||||
if(json)
|
||||
{
|
||||
encodeText = winform.aes.encrypt(web.json.stringify(json));
|
||||
base64Text = crypt.bin.encodeBase64(encodeText);
|
||||
win.clip.write(base64Text);
|
||||
win.msgbox("已复制到剪贴板",,0x01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
winform.startProc = null;
|
||||
winform.runStart = function()
|
||||
{
|
||||
import process.popen;
|
||||
import console;
|
||||
|
||||
winform.btnStart.text = "操作中..";
|
||||
if(winform.startProc)
|
||||
{
|
||||
winform.startProc.terminate();
|
||||
winform.startProc = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
json = winform.getShareKey();
|
||||
if(json)
|
||||
{
|
||||
params = {
|
||||
"--config-client",
|
||||
crypt.bin.encodeBase64(web.json.stringify(json.Client))
|
||||
"--config-common",
|
||||
crypt.bin.encodeBase64(web.json.stringify(json.Common))
|
||||
"--config-tuntap",
|
||||
crypt.bin.encodeBase64(web.json.stringify(json.Tuntap))
|
||||
}
|
||||
winform.startProc = process.popen("~/linker.exe",process.joinArguments(params));
|
||||
}
|
||||
}
|
||||
winform.btnStart.text = winform.startProc ? "关闭共享" : "开启共享";
|
||||
}
|
||||
winform.initStart = function()
|
||||
{
|
||||
if(winform.ckAuto.checked)
|
||||
{
|
||||
winform.runStart();
|
||||
}
|
||||
winform.btnStart.oncommand = function()
|
||||
{
|
||||
winform.runStart();
|
||||
}
|
||||
}
|
||||
|
||||
winform.loadShareSetting();
|
||||
winform.initLans();
|
||||
winform.initSecretkey();
|
||||
winform.initAuto();
|
||||
winform.initServer();
|
||||
winform.initSHareKey();
|
||||
winform.initStart();
|
||||
|
||||
winform.show();
|
||||
win.loopMessage();
|
||||
return winform;
|
||||
|
@@ -2,19 +2,23 @@
|
||||
using linker.libs.extends;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace linker.tun
|
||||
{
|
||||
/// <summary>
|
||||
/// osx网卡实现,未测试
|
||||
/// </summary>
|
||||
internal sealed class LinkerOsxTunDevice : ILinkerTunDevice
|
||||
{
|
||||
|
||||
private string name = string.Empty;
|
||||
public string Name => name;
|
||||
public bool Running => fs != null;
|
||||
public bool Running => safeFileHandle != null;
|
||||
public bool AppNat => false;
|
||||
|
||||
private FileStream fs = null;
|
||||
private FileStream fsRead = null;
|
||||
private FileStream fsWrite = null;
|
||||
private IPAddress address;
|
||||
private byte prefixLength = 24;
|
||||
private SafeFileHandle safeFileHandle;
|
||||
@@ -28,42 +32,79 @@ namespace linker.tun
|
||||
|
||||
public bool Setup(string name, IPAddress address, byte prefixLength, out string error)
|
||||
{
|
||||
this.name = name;
|
||||
this.name = "utun0";
|
||||
error = string.Empty;
|
||||
|
||||
interfaceOsx = GetOsxInterfaceNum();
|
||||
this.address = address;
|
||||
this.prefixLength = prefixLength;
|
||||
|
||||
safeFileHandle = File.OpenHandle($"/dev/{Name}", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous);
|
||||
fs = new FileStream(safeFileHandle, FileAccess.ReadWrite, 1500);
|
||||
IntPtr arg = Marshal.AllocHGlobal(4);
|
||||
Marshal.WriteInt32(arg, 0);
|
||||
try
|
||||
{
|
||||
interfaceOsx = GetOsxInterfaceNum();
|
||||
safeFileHandle = File.OpenHandle($"/dev/utun", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous);
|
||||
|
||||
int ret = OsxAPI.Ioctl(safeFileHandle, arg);
|
||||
if (ret < 0)
|
||||
{
|
||||
Shutdown();
|
||||
error = $"open utun failed: {Marshal.GetLastWin32Error()}";
|
||||
return false;
|
||||
}
|
||||
this.name = $"utun{Marshal.ReadInt32(arg)}";
|
||||
|
||||
fsRead = new FileStream(safeFileHandle, FileAccess.Read, 65 * 1024, true);
|
||||
fsWrite = new FileStream(safeFileHandle, FileAccess.Write, 65 * 1024, true);
|
||||
|
||||
IPAddress network = NetworkHelper.ToNetworkIP(address, NetworkHelper.ToPrefixValue(prefixLength));
|
||||
|
||||
CommandHelper.Osx(string.Empty, new string[] {
|
||||
$"route delete -net {network}/{prefixLength} {address}",
|
||||
$"ifconfig {Name} {address} {address} up",
|
||||
$"route add -net {network}/{prefixLength} {address}",
|
||||
});
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Shutdown();
|
||||
error = $"open utun failed: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(arg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
if (fs != null)
|
||||
try
|
||||
{
|
||||
safeFileHandle?.Dispose();
|
||||
|
||||
interfaceOsx = string.Empty;
|
||||
fs.Close();
|
||||
fs.Dispose();
|
||||
fs = null;
|
||||
|
||||
safeFileHandle?.Dispose();
|
||||
safeFileHandle = null;
|
||||
|
||||
try { fsRead?.Flush(); } catch (Exception) { }
|
||||
try { fsRead?.Close(); fsRead?.Dispose(); } catch (Exception) { }
|
||||
fsRead = null;
|
||||
|
||||
try { fsWrite?.Flush(); } catch (Exception) { }
|
||||
try { fsWrite?.Close(); fsWrite?.Dispose(); } catch (Exception) { }
|
||||
fsWrite = null;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
IPAddress network = NetworkHelper.ToNetworkIP(address, NetworkHelper.ToPrefixValue(this.prefixLength));
|
||||
CommandHelper.Osx(string.Empty, new string[] { $"route delete -net {network}/{prefixLength} {address}" });
|
||||
}
|
||||
public void Refresh()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void AddRoute(LinkerTunDeviceRouteItem[] ips)
|
||||
@@ -93,7 +134,7 @@ namespace linker.tun
|
||||
|
||||
public void SetMtu(int value)
|
||||
{
|
||||
CommandHelper.Osx(string.Empty, new string[] { $"ifconfig {Name} mtu {value}" });
|
||||
CommandHelper.Osx(string.Empty, new string[] { $"ifconfig {Name} mtu {value} up" });
|
||||
}
|
||||
|
||||
public void SetSystemNat(out string error)
|
||||
@@ -134,34 +175,28 @@ namespace linker.tun
|
||||
}
|
||||
|
||||
|
||||
private byte[] buffer = new byte[2 * 1024];
|
||||
private byte[] buffer = new byte[65 * 1024];
|
||||
private readonly object writeLockObj = new object();
|
||||
public byte[] Read(out int length)
|
||||
{
|
||||
length = 0;
|
||||
try
|
||||
{
|
||||
length = fs.Read(buffer.AsSpan(4));
|
||||
if (safeFileHandle == null) return Helper.EmptyArray;
|
||||
|
||||
length = fsRead.Read(buffer.AsSpan(4));
|
||||
length.ToBytes(buffer);
|
||||
length += 4;
|
||||
return buffer;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return Helper.EmptyArray;
|
||||
}
|
||||
|
||||
public bool Write(ReadOnlyMemory<byte> buffer)
|
||||
{
|
||||
try
|
||||
if (safeFileHandle == null) return true;
|
||||
lock (writeLockObj)
|
||||
{
|
||||
fs.Write(buffer.Span);
|
||||
fsWrite.Write(buffer.Span);
|
||||
fsWrite.Flush();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private string GetOsxInterfaceNum()
|
||||
@@ -187,7 +222,6 @@ namespace linker.tun
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
|
||||
public async Task<bool> CheckAvailable(bool order = false)
|
||||
{
|
||||
return await Task.FromResult(true).ConfigureAwait(false);
|
||||
|
23
src/linker.tun/OsxTun.cs
Normal file
23
src/linker.tun/OsxTun.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace linker.tun
|
||||
{
|
||||
internal static class OsxAPI
|
||||
{
|
||||
|
||||
// 定义 macOS 的 ioctl 命令(来自 <net/if_utun.h>)
|
||||
private const uint UTUN_CONTROL = 0x80000000; // 'u' << 24
|
||||
private const uint UTUN_CTRL_SET_IFNAME = (UTUN_CONTROL | 1);
|
||||
|
||||
// P/Invoke 声明
|
||||
[DllImport("libc", EntryPoint = "ioctl", SetLastError = true)]
|
||||
private static extern int Ioctl(SafeFileHandle fd, uint request, IntPtr arg);
|
||||
|
||||
public static int Ioctl(SafeFileHandle fd, IntPtr arg)
|
||||
{
|
||||
return Ioctl(fd, UTUN_CTRL_SET_IFNAME, arg);
|
||||
}
|
||||
}
|
||||
}
|
@@ -34,7 +34,7 @@ namespace linker.tunnel.transport
|
||||
|
||||
public bool DisableSSL => false;
|
||||
|
||||
public byte Order => 3;
|
||||
public byte Order => 5;
|
||||
|
||||
/// <summary>
|
||||
/// 连接成功
|
||||
|
@@ -32,7 +32,7 @@ namespace linker.tunnel.transport
|
||||
|
||||
public bool DisableSSL => false;
|
||||
|
||||
public byte Order => 2;
|
||||
public byte Order => 4;
|
||||
|
||||
/// <summary>
|
||||
/// 连接成功
|
||||
|
@@ -35,7 +35,7 @@ namespace linker.tunnel.transport
|
||||
|
||||
public bool DisableSSL => false;
|
||||
|
||||
public byte Order => 5;
|
||||
public byte Order => 2;
|
||||
|
||||
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };
|
||||
|
||||
|
@@ -38,7 +38,7 @@ namespace linker.tunnel.transport
|
||||
|
||||
public bool DisableSSL => false;
|
||||
|
||||
public byte Order => 1;
|
||||
public byte Order => 3;
|
||||
|
||||
|
||||
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };
|
||||
|
@@ -33,7 +33,7 @@ namespace linker.tunnel.transport
|
||||
|
||||
public bool DisableSSL => false;
|
||||
|
||||
public byte Order => 4;
|
||||
public byte Order => 1;
|
||||
|
||||
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };
|
||||
|
||||
|
@@ -88,6 +88,11 @@ namespace linker
|
||||
configDic.Add("Common", args[i + 1]);
|
||||
i++;
|
||||
}
|
||||
else if (args[i] == "--config-tuntap")
|
||||
{
|
||||
configDic.Add("Tuntap", args[i + 1]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return configDic;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
v1.7.6
|
||||
2025-04-27 11:23:46
|
||||
2025-04-28 16:09:25
|
||||
1. 一些优化
|
||||
2. 安卓APP勉强能用,支持分身,下拉刷新,在线升级
|
||||
5. 如果你设备很多,请尝试升级其中一个成功重启后再升级其它
|
Reference in New Issue
Block a user