傻瓜共享版

This commit is contained in:
snltty
2025-04-28 16:09:26 +08:00
parent f212cbc9ee
commit 5af63ed003
39 changed files with 507 additions and 353 deletions

1
.gitignore vendored
View File

@@ -5,5 +5,6 @@ obj
node_modules node_modules
/public/* /public/*
/x64/* /x64/*
linker.share.win
TestResults TestResults

View File

@@ -26,6 +26,7 @@ namespace linker.ics
{ {
try try
{ {
publicCon.EnableSharing(tagSHARINGCONNECTIONTYPE.ICSSHARINGTYPE_PUBLIC); publicCon.EnableSharing(tagSHARINGCONNECTIONTYPE.ICSSHARINGTYPE_PUBLIC);
} }
catch (Exception ex) catch (Exception ex)

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

View File

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

View File

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

View File

@@ -1,4 +0,0 @@
加载所需要的模块
pip install -r requirements.txt
打包
pyinstaller .\main1.0.spec

View File

@@ -189,7 +189,7 @@ namespace linker.messenger.entry
if ((modules & ExcludeModule.Socks5) != ExcludeModule.Socks5) if ((modules & ExcludeModule.Socks5) != ExcludeModule.Socks5)
serviceProvider.UseSocks5Client(); serviceProvider.UseSocks5Client();
if ((modules & ExcludeModule.Tuntap) != ExcludeModule.Tuntap) if ((modules & ExcludeModule.Tuntap) != ExcludeModule.Tuntap)
serviceProvider.UseTuntapClient(); serviceProvider.UseTuntapClient(configDic);
if ((modules & ExcludeModule.Updater) != ExcludeModule.Updater) if ((modules & ExcludeModule.Updater) != ExcludeModule.Updater)
serviceProvider.UseUpdaterClient(); serviceProvider.UseUpdaterClient();
serviceProvider.UseExRoute().UseAccessClient().UseDecenterClient().UsePcpClient().UseRelayClient().UseSyncClient().UseTunnelClient().UseFlowClient(); serviceProvider.UseExRoute().UseAccessClient().UseDecenterClient().UsePcpClient().UseRelayClient().UseSyncClient().UseTunnelClient().UseFlowClient();

View File

@@ -73,7 +73,7 @@ namespace linker.messenger.flow.messenger
sForwardFlow.Update(); sForwardFlow.Update();
SForwardFlowRequestInfo info = serializer.Deserialize<SForwardFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span); SForwardFlowRequestInfo info = serializer.Deserialize<SForwardFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (sForwardServerStore.SecretKey == info.SecretKey) if (sForwardServerStore.ValidateSecretKey(info.SecretKey))
{ {
info.GroupId = string.Empty; info.GroupId = string.Empty;
} }
@@ -97,7 +97,7 @@ namespace linker.messenger.flow.messenger
{ {
relayFlow.Update(); relayFlow.Update();
RelayFlowRequestInfo info = serializer.Deserialize<RelayFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span); RelayFlowRequestInfo info = serializer.Deserialize<RelayFlowRequestInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (relayServerStore.SecretKey == info.SecretKey) if (relayServerStore.ValidateSecretKey(info.SecretKey))
{ {
info.GroupId = string.Empty; info.GroupId = string.Empty;
} }
@@ -152,7 +152,7 @@ namespace linker.messenger.flow.messenger
private DateTime start = DateTime.Now; private DateTime start = DateTime.Now;
public FlowClientMessenger( MessengerFlow messengerFlow,ISerializer serializer) public FlowClientMessenger(MessengerFlow messengerFlow, ISerializer serializer)
{ {
this.messengerFlow = messengerFlow; this.messengerFlow = messengerFlow;
this.serializer = serializer; this.serializer = serializer;

View File

@@ -292,7 +292,7 @@ namespace linker.messenger.relay.messenger
public async Task UpdateNodeForward(IConnection connection) public async Task UpdateNodeForward(IConnection connection)
{ {
RelayServerNodeUpdateWrapInfo info = serializer.Deserialize<RelayServerNodeUpdateWrapInfo>(connection.ReceiveRequestWrap.Payload.Span); 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); await relayServerTransfer.UpdateNodeReport(info.Info).ConfigureAwait(false);
connection.Write(Helper.TrueArray); connection.Write(Helper.TrueArray);
@@ -337,7 +337,7 @@ namespace linker.messenger.relay.messenger
public void AccessCdkey(IConnection connection) public void AccessCdkey(IConnection connection)
{ {
string secretKey = serializer.Deserialize<string>(connection.ReceiveRequestWrap.Payload.Span); 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> /// <summary>
/// 添加CDKEY /// 添加CDKEY
@@ -352,7 +352,7 @@ namespace linker.messenger.relay.messenger
connection.Write(Helper.FalseArray); connection.Write(Helper.FalseArray);
return; return;
} }
if (relayServerStore.SecretKey != info.SecretKey) if (relayServerStore.ValidateSecretKey(info.SecretKey))
{ {
connection.Write(Helper.FalseArray); connection.Write(Helper.FalseArray);
return; return;
@@ -376,7 +376,7 @@ namespace linker.messenger.relay.messenger
connection.Write(Helper.FalseArray); connection.Write(Helper.FalseArray);
return; return;
} }
if (relayServerStore.SecretKey == info.SecretKey) if (relayServerStore.ValidateSecretKey(info.SecretKey))
{ {
await relayServerCdkeyStore.Del(info.Id).ConfigureAwait(false); await relayServerCdkeyStore.Del(info.Id).ConfigureAwait(false);
} }
@@ -401,7 +401,7 @@ namespace linker.messenger.relay.messenger
connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { })); connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
return; 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 { })); connection.Write(serializer.Serialize(new RelayServerCdkeyPageResultInfo { }));
return; return;
@@ -427,7 +427,7 @@ namespace linker.messenger.relay.messenger
connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { })); connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { }));
return; return;
} }
if (relayServerStore.SecretKey != info.SecretKey) if (relayServerStore.ValidateSecretKey(info.SecretKey))
{ {
connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { })); connection.Write(serializer.Serialize(new RelayServerCdkeyTestResultInfo { }));
return; return;

View File

@@ -2,10 +2,7 @@
{ {
public interface IRelayServerStore public interface IRelayServerStore
{ {
/// <summary> public bool ValidateSecretKey(string secretKey);
/// 中继密钥
/// </summary>
public string SecretKey { get; }
/// <summary> /// <summary>
/// 设置中继密钥 /// 设置中继密钥

View File

@@ -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) 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"; return $"SecretKey validate fail";
} }

View File

@@ -2,10 +2,6 @@
{ {
public interface ISForwardServerStore public interface ISForwardServerStore
{ {
/// <summary>
/// 穿透密钥
/// </summary>
public string SecretKey { get; }
/// <summary> /// <summary>
/// 缓冲区大小 /// 缓冲区大小
/// </summary> /// </summary>
@@ -19,6 +15,7 @@
/// </summary> /// </summary>
public int[] TunnelPortRange { get; } public int[] TunnelPortRange { get; }
public bool ValidateSecretKey(string key);
/// <summary> /// <summary>
/// 穿透密钥 /// 穿透密钥
/// </summary> /// </summary>

View File

@@ -17,7 +17,7 @@ namespace linker.messenger.sforward.server.validator
public async Task<string> Validate(SignCacheInfo signCacheInfo, SForwardAddInfo sForwardAddInfo) 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"; return $"sforward secretKey 【{sForwardAddInfo.SecretKey}】 valid fail";
} }

View File

@@ -7,12 +7,9 @@ namespace linker.messenger.signin
/// </summary> /// </summary>
public interface ISignInServerStore : IStore<SignCacheInfo> public interface ISignInServerStore : IStore<SignCacheInfo>
{ {
/// <summary>
/// 信标密钥
/// </summary>
public string SecretKey { get; }
public int CleanDays { get; } public int CleanDays { get; }
public bool ValidateSecretKey(string key);
/// <summary> /// <summary>
/// 设置信标密钥 /// 设置信标密钥
/// </summary> /// </summary>

View File

@@ -51,12 +51,10 @@
/// <returns></returns> /// <returns></returns>
public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache) public async Task<string> Validate(SignInfo signInfo, SignCacheInfo cache)
{ {
if (string.IsNullOrWhiteSpace(signInServerStore.SecretKey) == false) signInfo.Args.TryGetValue("signin-secretkey", out string secretkey);
if (signInServerStore.ValidateSecretKey(secretkey) == false)
{ {
if (signInfo.Args.TryGetValue("signin-secretkey", out string secretkey) == false || secretkey != signInServerStore.SecretKey) return $"server secretkey validate fail";
{
return $"server secretkey validate fail";
}
} }
await Task.CompletedTask.ConfigureAwait(false); await Task.CompletedTask.ConfigureAwait(false);
return string.Empty; return string.Empty;

View File

@@ -4,13 +4,17 @@ namespace linker.messenger.store.file.relay
{ {
public sealed class RelayServerStore : IRelayServerStore public sealed class RelayServerStore : IRelayServerStore
{ {
public string SecretKey => config.Data.Server.Relay.SecretKey;
private readonly FileConfig config; private readonly FileConfig config;
public RelayServerStore(FileConfig config) public RelayServerStore(FileConfig config)
{ {
this.config = 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) public void SetSecretKey(string secretKey)
{ {
config.Data.Server.Relay.SecretKey = secretKey; config.Data.Server.Relay.SecretKey = secretKey;
@@ -21,5 +25,7 @@ namespace linker.messenger.store.file.relay
config.Data.Update(); config.Data.Update();
return true; return true;
} }
} }
} }

View File

@@ -4,8 +4,6 @@ namespace linker.messenger.store.file.sforward
{ {
public sealed class SForwardServerStore : ISForwardServerStore public sealed class SForwardServerStore : ISForwardServerStore
{ {
public string SecretKey => fileConfig.Data.Server.SForward.SecretKey;
public byte BufferSize => fileConfig.Data.Server.SForward.BufferSize; public byte BufferSize => fileConfig.Data.Server.SForward.BufferSize;
public int WebPort => fileConfig.Data.Server.SForward.WebPort; public int WebPort => fileConfig.Data.Server.SForward.WebPort;
@@ -18,6 +16,10 @@ namespace linker.messenger.store.file.sforward
this.fileConfig = fileConfig; 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) public bool SetSecretKey(string key)
{ {
fileConfig.Data.Server.SForward.SecretKey = key; fileConfig.Data.Server.SForward.SecretKey = key;

View File

@@ -5,7 +5,6 @@ namespace linker.messenger.store.file.signIn
{ {
public sealed class SignInServerStore : ISignInServerStore public sealed class SignInServerStore : ISignInServerStore
{ {
public string SecretKey => fileConfig.Data.Server.SignIn.SecretKey;
public int CleanDays => fileConfig.Data.Server.SignIn.CleanDays; public int CleanDays => fileConfig.Data.Server.SignIn.CleanDays;
private readonly Storefactory dBfactory; private readonly Storefactory dBfactory;
@@ -17,6 +16,11 @@ namespace linker.messenger.store.file.signIn
liteCollection = dBfactory.GetCollection<SignCacheInfo>("signs"); liteCollection = dBfactory.GetCollection<SignCacheInfo>("signs");
this.fileConfig = fileConfig; 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) public void SetSecretKey(string secretKey)
{ {
fileConfig.Data.Server.SignIn.SecretKey = secretKey; fileConfig.Data.Server.SignIn.SecretKey = secretKey;

View File

@@ -5,14 +5,16 @@ namespace linker.messenger.store.file.updater
{ {
public sealed class UpdaterServerStore : IUpdaterServerStore public sealed class UpdaterServerStore : IUpdaterServerStore
{ {
public string SecretKey => fileConfig.Data.Server.Updater.SecretKey;
private readonly FileConfig fileConfig; private readonly FileConfig fileConfig;
public UpdaterServerStore(FileConfig fileConfig) public UpdaterServerStore(FileConfig fileConfig)
{ {
this.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) public void SetSecretKey(string key)
{ {
fileConfig.Data.Server.Updater.SecretKey = key; fileConfig.Data.Server.Updater.SecretKey = key;

View File

@@ -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.decenter;
using linker.messenger.exroute; using linker.messenger.exroute;
using linker.messenger.signin;
using linker.messenger.tunnel; using linker.messenger.tunnel;
using linker.messenger.tuntap.lease; using linker.messenger.tuntap.lease;
using linker.messenger.tuntap.messenger; using linker.messenger.tuntap.messenger;
using linker.tun; using linker.tun;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Text;
using System.Text.Json;
namespace linker.messenger.tuntap namespace linker.messenger.tuntap
{ {
public static class Entry public static class Entry
@@ -35,7 +41,7 @@ namespace linker.messenger.tuntap
return serviceCollection; 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>(); TuntapProxy tuntapProxy = serviceProvider.GetService<TuntapProxy>();
TuntapTransfer tuntapTransfer = serviceProvider.GetService<TuntapTransfer>(); TuntapTransfer tuntapTransfer = serviceProvider.GetService<TuntapTransfer>();
@@ -54,15 +60,63 @@ namespace linker.messenger.tuntap
IApiServer apiServer = serviceProvider.GetService<IApiServer>(); IApiServer apiServer = serviceProvider.GetService<IApiServer>();
apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<TuntapApiController>() }); apiServer.AddPlugins(new List<libs.api.IApiController> { serviceProvider.GetService<TuntapApiController>() });
ExRouteTransfer exRouteTransfer= serviceProvider.GetService<ExRouteTransfer>(); ExRouteTransfer exRouteTransfer = serviceProvider.GetService<ExRouteTransfer>();
exRouteTransfer.AddExRoutes(new List<IExRoute> { serviceProvider.GetService<TuntapExRoute>() }); exRouteTransfer.AddExRoutes(new List<IExRoute> { serviceProvider.GetService<TuntapExRoute>() });
TunnelClientExcludeIPTransfer tunnelClientExcludeIPTransfer = serviceProvider.GetService<TunnelClientExcludeIPTransfer>(); TunnelClientExcludeIPTransfer tunnelClientExcludeIPTransfer = serviceProvider.GetService<TunnelClientExcludeIPTransfer>();
tunnelClientExcludeIPTransfer.AddTunnelExcludeIPs(new List<ITunnelClientExcludeIP> { serviceProvider.GetService<TuntapTunnelExcludeIP>() }); tunnelClientExcludeIPTransfer.AddTunnelExcludeIPs(new List<ITunnelClientExcludeIP> { serviceProvider.GetService<TuntapTunnelExcludeIP>() });
DecenterClientTransfer decenterClientTransfer= serviceProvider.GetService<DecenterClientTransfer>(); DecenterClientTransfer decenterClientTransfer = serviceProvider.GetService<DecenterClientTransfer>();
decenterClientTransfer.AddDecenters(new List<IDecenter> { serviceProvider.GetService<TuntapDecenter>() }); 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; return serviceProvider;
} }

View File

@@ -2,10 +2,8 @@
{ {
public interface IUpdaterServerStore public interface IUpdaterServerStore
{ {
/// <summary>
/// 更新密钥 public bool ValidateSecretKey(string key);
/// </summary>
public string SecretKey { get; }
/// <summary> /// <summary>
/// 设置更新密钥 /// 设置更新密钥
/// </summary> /// </summary>

View File

@@ -132,7 +132,7 @@ namespace linker.messenger.updater
MachineId = string.Empty, MachineId = string.Empty,
Current = info.Current, Current = info.Current,
Length = info.Length, Length = info.Length,
Status = info.Status, Status = info.Status,
Version = info.Version Version = info.Version
}; };
connection.Write(serializer.Serialize(result)); connection.Write(serializer.Serialize(result));
@@ -151,7 +151,7 @@ namespace linker.messenger.updater
public void ConfirmServer(IConnection connection) public void ConfirmServer(IConnection connection)
{ {
UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span); UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (updaterServerStore.SecretKey == confirm.SecretKey) if (updaterServerStore.ValidateSecretKey(confirm.SecretKey))
{ {
if (string.IsNullOrWhiteSpace(confirm.Version)) if (string.IsNullOrWhiteSpace(confirm.Version))
{ {
@@ -172,7 +172,7 @@ namespace linker.messenger.updater
public void ExitServer(IConnection connection) public void ExitServer(IConnection connection)
{ {
UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span); UpdaterConfirmServerInfo confirm = serializer.Deserialize<UpdaterConfirmServerInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (updaterServerStore.SecretKey == confirm.SecretKey) if (updaterServerStore.ValidateSecretKey(confirm.SecretKey))
{ {
Environment.Exit(1); 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); connection.Write(Helper.FalseArray);
return; return;

View File

@@ -1,9 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?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"/> <file name="main.aardio" path="main.aardio" comment="main.aardio"/>
<folder name="资源文件" path="res" embed="true" local="false" ignored="false"> <folder name="资源文件" path="res" embed="true" local="false" ignored="false">
<file name="connect.aardio" path="res\connect.aardio" comment="res\connect.aardio"/> <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="share.aardio" path="res\share.aardio" comment="res\share.aardio"/>
<file name="info.aardio" path="res\info.aardio" comment="res\info.aardio"/>
</folder> </folder>
<folder name="窗体文件" path="dlg" comment="目录" embed="true"/> <folder name="窗体文件" path="dlg" comment="目录" embed="true"/>
</project> </project>

View File

@@ -1,9 +1,9 @@
//RUNAS// //RUNAS//
import win.ui; import win.ui;
/*DSG{{*/ /*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( 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\connect.aardio");
mainForm.mainTab.loadForm("\res\share.aardio"); mainForm.mainTab.loadForm("\res\share.aardio");
mainForm.mainTab.loadForm("\res\info.aardio");
mainForm.show(); mainForm.show();
return win.loopMessage(); return win.loopMessage();

View File

@@ -1,7 +1,16 @@
import win.ui; import win.ui;
/*DSG{{*/ /*DSG{{*/
var winform = win.form(text="连接共享";right=759;bottom=469) var winform = win.form(text="连接共享";right=330;bottom=276)
winform.add() 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(); winform.show();

View File

@@ -1,13 +1,286 @@
import win.ui; import win.ui;
/*DSG{{*/ /*DSG{{*/
var winform = win.form(text="发出共享";right=243;bottom=254) var winform = win.form(text="发出共享";right=330;bottom=283)
winform.add( winform.add(
button={cls="button";text="生成";left=195;top=12;right=234;bottom=35;z=3}; btnCopy={cls="button";text="复制共享密钥";left=229;top=240;right=321;bottom=267;z=16};
edit={cls="edit";text="Edit";left=44;top=14;right=189;bottom=35;edge=1;z=1}; btnNewSecretkey={cls="button";text="新ID";left=276;top=41;right=321;bottom=64;z=14};
static={cls="static";text="密钥";left=11;top=16;right=48;bottom=35;transparent=1;z=2} 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(); winform.show();
win.loopMessage(); win.loopMessage();
return winform; return winform;

View File

@@ -2,19 +2,23 @@
using linker.libs.extends; using linker.libs.extends;
using Microsoft.Win32.SafeHandles; using Microsoft.Win32.SafeHandles;
using System.Net; using System.Net;
using System.Threading; using System.Runtime.InteropServices;
namespace linker.tun namespace linker.tun
{ {
/// <summary>
/// osx网卡实现未测试
/// </summary>
internal sealed class LinkerOsxTunDevice : ILinkerTunDevice internal sealed class LinkerOsxTunDevice : ILinkerTunDevice
{ {
private string name = string.Empty; private string name = string.Empty;
public string Name => name; public string Name => name;
public bool Running => fs != null; public bool Running => safeFileHandle != null;
public bool AppNat => false; public bool AppNat => false;
private FileStream fs = null; private FileStream fsRead = null;
private FileStream fsWrite = null;
private IPAddress address; private IPAddress address;
private byte prefixLength = 24; private byte prefixLength = 24;
private SafeFileHandle safeFileHandle; private SafeFileHandle safeFileHandle;
@@ -28,42 +32,79 @@ namespace linker.tun
public bool Setup(string name, IPAddress address, byte prefixLength, out string error) public bool Setup(string name, IPAddress address, byte prefixLength, out string error)
{ {
this.name = name; this.name = "utun0";
error = string.Empty; error = string.Empty;
interfaceOsx = GetOsxInterfaceNum();
this.address = address; this.address = address;
this.prefixLength = prefixLength; this.prefixLength = prefixLength;
safeFileHandle = File.OpenHandle($"/dev/{Name}", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous); IntPtr arg = Marshal.AllocHGlobal(4);
fs = new FileStream(safeFileHandle, FileAccess.ReadWrite, 1500); Marshal.WriteInt32(arg, 0);
try
{
interfaceOsx = GetOsxInterfaceNum();
safeFileHandle = File.OpenHandle($"/dev/utun", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous);
IPAddress network = NetworkHelper.ToNetworkIP(address, NetworkHelper.ToPrefixValue(prefixLength)); int ret = OsxAPI.Ioctl(safeFileHandle, arg);
CommandHelper.Osx(string.Empty, new string[] { if (ret < 0)
$"route delete -net {network}/{prefixLength} {address}", {
$"ifconfig {Name} {address} {address} up", Shutdown();
$"route add -net {network}/{prefixLength} {address}", error = $"open utun failed: {Marshal.GetLastWin32Error()}";
}); return false;
return true; }
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() public void Shutdown()
{ {
if (fs != null) try
{ {
safeFileHandle?.Dispose();
interfaceOsx = string.Empty; interfaceOsx = string.Empty;
fs.Close();
fs.Dispose(); safeFileHandle?.Dispose();
fs = null; 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)); IPAddress network = NetworkHelper.ToNetworkIP(address, NetworkHelper.ToPrefixValue(this.prefixLength));
CommandHelper.Osx(string.Empty, new string[] { $"route delete -net {network}/{prefixLength} {address}" }); CommandHelper.Osx(string.Empty, new string[] { $"route delete -net {network}/{prefixLength} {address}" });
} }
public void Refresh() public void Refresh()
{ {
} }
public void AddRoute(LinkerTunDeviceRouteItem[] ips) public void AddRoute(LinkerTunDeviceRouteItem[] ips)
@@ -93,7 +134,7 @@ namespace linker.tun
public void SetMtu(int value) 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) 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) public byte[] Read(out int length)
{ {
length = 0; length = 0;
try if (safeFileHandle == null) return Helper.EmptyArray;
{
length = fs.Read(buffer.AsSpan(4)); length = fsRead.Read(buffer.AsSpan(4));
length.ToBytes(buffer); length.ToBytes(buffer);
length += 4; length += 4;
return buffer; return buffer;
}
catch (Exception)
{
}
return Helper.EmptyArray;
} }
public bool Write(ReadOnlyMemory<byte> buffer) 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; return true;
} }
catch (Exception)
{
}
return false;
} }
private string GetOsxInterfaceNum() private string GetOsxInterfaceNum()
@@ -187,7 +222,6 @@ namespace linker.tun
return string.Empty; return string.Empty;
} }
public async Task<bool> CheckAvailable(bool order = false) public async Task<bool> CheckAvailable(bool order = false)
{ {
return await Task.FromResult(true).ConfigureAwait(false); return await Task.FromResult(true).ConfigureAwait(false);

23
src/linker.tun/OsxTun.cs Normal file
View 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);
}
}
}

View File

@@ -34,7 +34,7 @@ namespace linker.tunnel.transport
public bool DisableSSL => false; public bool DisableSSL => false;
public byte Order => 3; public byte Order => 5;
/// <summary> /// <summary>
/// 连接成功 /// 连接成功

View File

@@ -32,7 +32,7 @@ namespace linker.tunnel.transport
public bool DisableSSL => false; public bool DisableSSL => false;
public byte Order => 2; public byte Order => 4;
/// <summary> /// <summary>
/// 连接成功 /// 连接成功

View File

@@ -35,7 +35,7 @@ namespace linker.tunnel.transport
public bool DisableSSL => false; public bool DisableSSL => false;
public byte Order => 5; public byte Order => 2;
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { }; public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };

View File

@@ -38,7 +38,7 @@ namespace linker.tunnel.transport
public bool DisableSSL => false; public bool DisableSSL => false;
public byte Order => 1; public byte Order => 3;
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { }; public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };

View File

@@ -33,7 +33,7 @@ namespace linker.tunnel.transport
public bool DisableSSL => false; public bool DisableSSL => false;
public byte Order => 4; public byte Order => 1;
public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { }; public Action<ITunnelConnection> OnConnected { get; set; } = (state) => { };

View File

@@ -88,6 +88,11 @@ namespace linker
configDic.Add("Common", args[i + 1]); configDic.Add("Common", args[i + 1]);
i++; i++;
} }
else if (args[i] == "--config-tuntap")
{
configDic.Add("Tuntap", args[i + 1]);
i++;
}
} }
return configDic; return configDic;
} }

View File

@@ -1,5 +1,5 @@
v1.7.6 v1.7.6
2025-04-27 11:23:46 2025-04-28 16:09:25
1. 一些优化 1. 一些优化
2. 安卓APP勉强能用支持分身下拉刷新在线升级 2. 安卓APP勉强能用支持分身下拉刷新在线升级
5. 如果你设备很多,请尝试升级其中一个成功重启后再升级其它 5. 如果你设备很多,请尝试升级其中一个成功重启后再升级其它