This commit is contained in:
snltty
2025-04-13 15:50:44 +08:00
parent 1ed9af43fa
commit 6b79061b40
35 changed files with 374 additions and 101 deletions

View File

@@ -37,7 +37,7 @@ jobs:
release_name: v1.7.2.${{ steps.date.outputs.today }} release_name: v1.7.2.${{ steps.date.outputs.today }}
draft: false draft: false
prerelease: false prerelease: false
body: "1. 内网穿透的计划任务\r\n2. 一些修复和优化\r\n3. 优化自动分配IP\r\n4. 优化网卡,排除不明数据包\r\n5. 优化管理UI适配移动端" body: "1. 内网穿透的计划任务\r\n2. 一些修复和优化\r\n3. 优化自动分配IP\r\n4. 优化网卡,排除不明数据包\r\n5. 优化管理UI适配移动端\r\n6. 虚拟网卡点对网IP映射"
- name: publish projects - name: publish projects
run: ./publish.bat "C:\\Android\\android-sdk" run: ./publish.bat "C:\\Android\\android-sdk"
- name: upload-win-x86-oss - name: upload-win-x86-oss

View File

@@ -460,14 +460,22 @@ namespace linker.app
public sealed class WebServerFileReader : IWebServerFileReader public sealed class WebServerFileReader : IWebServerFileReader
{ {
DateTime lastModified = DateTime.Now; ReceiveDataBuffer receiveDataBuffer = new ReceiveDataBuffer();
public byte[] Read(string root,string fileName, out DateTime lastModified) byte[] buffer = new byte[4 * 1024];
public byte[] Read(string root, string fileName, out DateTime lastModified)
{ {
lastModified = this.lastModified; lastModified = DateTime.Now;
fileName = Path.Join("public/web", fileName); fileName = Path.Join("public/web", fileName);
using Stream fileStream = FileSystem.Current.OpenAppPackageFileAsync(fileName).Result; using Stream fileStream = FileSystem.Current.OpenAppPackageFileAsync(fileName).Result;
using StreamReader reader = new StreamReader(fileStream); int length = 0;
return reader.ReadToEnd().ToBytes(); while ((length = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
receiveDataBuffer.AddRange(buffer, 0, length);
}
byte[] result = receiveDataBuffer.Data.ToArray();
receiveDataBuffer.Clear();
return result;
} }
} }

View File

@@ -41,7 +41,6 @@ namespace linker.libs
{ {
return FindValue(NetworkHelper.ToValue(ip), out value); return FindValue(NetworkHelper.ToValue(ip), out value);
} }
public bool FindValue(uint ip, out T value) public bool FindValue(uint ip, out T value)
{ {
if (ip2value.TryGetValue(ip, out value)) if (ip2value.TryGetValue(ip, out value))

View File

@@ -57,22 +57,41 @@ namespace linker.libs.web
//默认页面 //默认页面
if (path == "/") path = "index.html"; if (path == "/") path = "index.html";
Memory<byte> memory = Helper.EmptyArray;
DateTime last = DateTime.Now;
try try
{ {
byte[] bytes = fileReader.Read(root,path, out DateTime last); memory = fileReader.Read(root, path, out last);
if (memory.Length > 0)
{
response.ContentLength64 = memory.Length;
response.ContentType = GetContentType(path);
if (OperatingSystem.IsAndroid())
{
response.Headers.Set("Last-Modified", last.ToString("yyyy-MM-dd HH:mm:ss"));
}
else
{
response.Headers.Set("Last-Modified", last.ToString());
}
response.OutputStream.Write(memory.Span);
response.OutputStream.Flush();
response.OutputStream.Close();
}
else
{
response.StatusCode = (int)HttpStatusCode.NotFound;
}
}
catch (Exception ex)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(ex + $"");
response.ContentLength64 = bytes.Length; response.ContentLength64 = bytes.Length;
response.ContentType = GetContentType(path); response.ContentType = "text/plain; charset=utf-8";
response.Headers.Set("Last-Modified", last.ToString());
response.OutputStream.Write(bytes, 0, bytes.Length); response.OutputStream.Write(bytes, 0, bytes.Length);
response.OutputStream.Flush(); response.OutputStream.Flush();
response.OutputStream.Close(); response.OutputStream.Close();
} }
catch (Exception)
{
response.StatusCode = (int)HttpStatusCode.NotFound;
}
} }
catch (Exception) catch (Exception)
{ {
@@ -115,9 +134,9 @@ namespace linker.libs.web
{ {
public byte[] Read(string root, string fileName, out DateTime lastModified); public byte[] Read(string root, string fileName, out DateTime lastModified);
} }
public sealed class WebServerFileReader: IWebServerFileReader public sealed class WebServerFileReader : IWebServerFileReader
{ {
public byte[] Read(string root,string fileName, out DateTime lastModified) public byte[] Read(string root, string fileName, out DateTime lastModified)
{ {
fileName = Path.Join(root, fileName); fileName = Path.Join(root, fileName);
lastModified = File.GetLastWriteTimeUtc(fileName); lastModified = File.GetLastWriteTimeUtc(fileName);

View File

@@ -1,7 +1,6 @@
using linker.libs; using linker.libs;
using linker.libs.timer; using linker.libs.timer;
using linker.messenger.signin; using linker.messenger.signin;
using System;
namespace linker.messenger.decenter namespace linker.messenger.decenter
{ {
@@ -72,6 +71,7 @@ namespace linker.messenger.decenter
{ {
sync.AddData(decenterSyncInfo.Data); sync.AddData(decenterSyncInfo.Data);
sync.DataVersion.Increment(); sync.DataVersion.Increment();
versionMultipleManager.Increment(sync.Name);
} }
} }

View File

@@ -173,7 +173,7 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableTuntapInfo(string machineId, TuntapStatus status, IPAddress ip, byte prefixLength, string name, SerializableTuntapInfo(string machineId, TuntapStatus status, IPAddress ip, byte prefixLength, string name,
List<TuntapLanInfo> lans, IPAddress wan, string setupError, string natError, string systemInfo, List<TuntapForwardInfo> forwards, TuntapSwitch Switch) List<TuntapLanInfo> lans, IPAddress wan, string setupError, string natError, string systemInfo, List<TuntapForwardInfo> forwards, TuntapSwitch Switch)
{ {
var info = new TuntapInfo var info = new TuntapInfo
{ {
@@ -437,8 +437,14 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude] [MemoryPackInclude]
string Error => info.Error; string Error => info.Error;
[MemoryPackInclude, MemoryPackAllowSerialize]
IPAddress MapIP => info.MapIP;
[MemoryPackInclude, MemoryPackAllowSerialize]
byte MapPrefixLength => info.MapPrefixLength;
[MemoryPackConstructor] [MemoryPackConstructor]
SerializableTuntapLanInfo(IPAddress ip, byte prefixLength, bool disabled, bool exists, string error) SerializableTuntapLanInfo(IPAddress ip, byte prefixLength, bool disabled, bool exists, string error, IPAddress mapip, byte mapprefixLength)
{ {
var info = new TuntapLanInfo var info = new TuntapLanInfo
{ {
@@ -446,7 +452,9 @@ namespace linker.messenger.serializer.memorypack
Exists = exists, Exists = exists,
IP = ip, IP = ip,
PrefixLength = prefixLength, PrefixLength = prefixLength,
Error = error Error = error,
MapIP = mapip,
MapPrefixLength = mapprefixLength,
}; };
this.info = info; this.info = info;
} }

View File

@@ -106,7 +106,7 @@ namespace linker.messenger.store.file
LoggerHelper.Instance.Info("use store file"); LoggerHelper.Instance.Info("use store file");
FileConfig fileConfig = serviceProvider.GetService<FileConfig>(); FileConfig fileConfig = serviceProvider.GetService<FileConfig>();
fileConfig.Initialize(configDic); fileConfig.Save(configDic);
RunningConfig runningConfig = serviceProvider.GetService<RunningConfig>(); RunningConfig runningConfig = serviceProvider.GetService<RunningConfig>();
IApiServer apiServer = serviceProvider.GetService<IApiServer>(); IApiServer apiServer = serviceProvider.GetService<IApiServer>();

View File

@@ -5,9 +5,14 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using linker.libs.timer; using linker.libs.timer;
using System.Text.Json;
namespace linker.messenger.store.file namespace linker.messenger.store.file
{ {
public sealed class FileConfigInitParams
{
}
public sealed class FileConfig public sealed class FileConfig
{ {
private SemaphoreSlim slim = new SemaphoreSlim(1); private SemaphoreSlim slim = new SemaphoreSlim(1);
@@ -18,12 +23,9 @@ namespace linker.messenger.store.file
public ConfigInfo Data { get; private set; } = new ConfigInfo(); public ConfigInfo Data { get; private set; } = new ConfigInfo();
public FileConfig() public FileConfig()
{
}
public void Initialize(Dictionary<string, string> dic)
{ {
Init(); Init();
Load(dic); Load();
Save(); Save();
SaveTask(); SaveTask();
} }
@@ -42,14 +44,14 @@ namespace linker.messenger.store.file
object property = item.GetValue(Data); object property = item.GetValue(Data);
fsDic.Add(item.Name.ToLower(), new FileReadWrite fsDic.Add(item.Name.ToLower(), new FileReadWrite
{ {
Path = Path.Combine(Helper.currentDirectory,configPath, $"{item.Name.ToLower()}.json"), Path = Path.Combine(Helper.currentDirectory, configPath, $"{item.Name.ToLower()}.json"),
Property = item, Property = item,
PropertyObject = property, PropertyObject = property,
PropertyMethod = (IConfig)property, PropertyMethod = (IConfig)property,
}); });
} }
} }
private void Load(Dictionary<string, string> dic) private void Load()
{ {
slim.Wait(); slim.Wait();
try try
@@ -68,10 +70,6 @@ namespace linker.messenger.store.file
{ {
text = File.ReadAllText(item.Value.Path, encoding: System.Text.Encoding.UTF8); text = File.ReadAllText(item.Value.Path, encoding: System.Text.Encoding.UTF8);
} }
else if (dic != null && dic.TryGetValue(item.Value.Property.Name, out string base64))
{
text = Encoding.UTF8.GetString(Convert.FromBase64String(base64));
}
if (string.IsNullOrWhiteSpace(text)) if (string.IsNullOrWhiteSpace(text))
{ {
LoggerHelper.Instance.Error($"{item.Value.Path} empty"); LoggerHelper.Instance.Error($"{item.Value.Path} empty");
@@ -96,7 +94,7 @@ namespace linker.messenger.store.file
} }
} }
public void Save() public void Save(Dictionary<string, string> dic = null)
{ {
slim.Wait(); slim.Wait();
try try
@@ -111,6 +109,11 @@ namespace linker.messenger.store.file
continue; continue;
} }
string text = item.Value.PropertyMethod.Serialize(item.Value.Property.GetValue(Data)); string text = item.Value.PropertyMethod.Serialize(item.Value.Property.GetValue(Data));
if (dic != null && dic.TryGetValue(item.Value.Property.Name, out string base64))
{
string text2 = item.Value.PropertyMethod.Serialize(item.Value.PropertyMethod.Deserialize(Encoding.UTF8.GetString(Convert.FromBase64String(base64))));
text = MergeJson(text, text2);
}
File.WriteAllText($"{item.Value.Path}.temp", text, encoding: System.Text.Encoding.UTF8); File.WriteAllText($"{item.Value.Path}.temp", text, encoding: System.Text.Encoding.UTF8);
File.Move($"{item.Value.Path}.temp", item.Value.Path, true); File.Move($"{item.Value.Path}.temp", item.Value.Path, true);
} }
@@ -128,7 +131,10 @@ namespace linker.messenger.store.file
{ {
slim.Release(); slim.Release();
} }
} }
private void SaveTask() private void SaveTask()
{ {
TimerHelper.SetIntervalLong(() => TimerHelper.SetIntervalLong(() =>
@@ -140,6 +146,61 @@ namespace linker.messenger.store.file
} }
}, 3000); }, 3000);
} }
public static string MergeJson(string json1, string json2)
{
using var doc1 = JsonDocument.Parse(json1);
using var doc2 = JsonDocument.Parse(json2);
var output = new Dictionary<string, object>();
foreach (var property in doc1.RootElement.EnumerateObject())
{
output[property.Name] = GetValue(property.Value);
}
foreach (var property in doc2.RootElement.EnumerateObject())
{
output[property.Name] = GetValue(property.Value);
}
return output.ToJson();
object GetValue(JsonElement element)
{
return element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.GetDecimal(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Object => GetObjectValue(element),
JsonValueKind.Array => GetArrayValue(element),
JsonValueKind.Undefined => null,
_ => null
};
}
Dictionary<string, object> GetObjectValue(JsonElement element)
{
var dict = new Dictionary<string, object>();
foreach (var prop in element.EnumerateObject())
{
dict[prop.Name] = GetValue(prop.Value);
}
return dict;
}
List<object> GetArrayValue(JsonElement element)
{
var list = new List<object>();
foreach (var item in element.EnumerateArray())
{
list.Add(GetValue(item));
}
return list;
}
}
} }
public sealed class FileReadWrite public sealed class FileReadWrite
@@ -190,10 +251,13 @@ namespace linker.messenger.store.file
} }
public object Deserialize(string text) public object Deserialize(string text)
{ {
if (text.Contains("ApiPassword")) try
{ {
return text.DeJson<ConfigClientInfo>(); return text.DeJson<ConfigClientInfo>();
} }
catch (Exception)
{
}
return Encoding.UTF8.GetString(crypto.Decode(Convert.FromBase64String(text)).ToArray()).DeJson<ConfigClientInfo>(); return Encoding.UTF8.GetString(crypto.Decode(Convert.FromBase64String(text)).ToArray()).DeJson<ConfigClientInfo>();
} }
} }

View File

@@ -1,4 +1,5 @@
using linker.messenger.tuntap; using linker.messenger.signin;
using linker.messenger.tuntap;
using linker.messenger.tuntap.lease; using linker.messenger.tuntap.lease;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@@ -12,4 +13,9 @@ namespace linker.messenger.store.file
public TuntapConfigInfo Tuntap { get; set; } = new TuntapConfigInfo(); public TuntapConfigInfo Tuntap { get; set; } = new TuntapConfigInfo();
public ConcurrentDictionary<string, LeaseInfo> Leases { get; set; } = new ConcurrentDictionary<string, LeaseInfo>(); public ConcurrentDictionary<string, LeaseInfo> Leases { get; set; } = new ConcurrentDictionary<string, LeaseInfo>();
} }
public partial class ConfigServerInfo
{
public TuntapConfigServerInfo Tuntap { get; set; } = new TuntapConfigServerInfo();
}
} }

View File

@@ -1,16 +1,22 @@
using linker.messenger.tuntap.lease; using linker.messenger.tuntap;
using linker.messenger.tuntap.lease;
using LiteDB; using LiteDB;
namespace linker.messenger.store.file.tuntap namespace linker.messenger.store.file.tuntap
{ {
public sealed class LeaseServerStore : ILeaseServerStore public sealed class LeaseServerStore : ILeaseServerStore
{ {
public TuntapLeaseConfigServerInfo Info => fileConfig.Data.Server.Tuntap.Lease;
private readonly Storefactory dBfactory; private readonly Storefactory dBfactory;
private readonly ILiteCollection<LeaseCacheInfo> liteCollection; private readonly ILiteCollection<LeaseCacheInfo> liteCollection;
public LeaseServerStore(Storefactory dBfactory) private readonly FileConfig fileConfig;
public LeaseServerStore(FileConfig fileConfig,Storefactory dBfactory)
{ {
this.fileConfig = fileConfig;
this.dBfactory = dBfactory; this.dBfactory = dBfactory;
liteCollection = dBfactory.GetCollection<LeaseCacheInfo>("dhcp"); liteCollection = dBfactory.GetCollection<LeaseCacheInfo>("dhcp");
} }
public bool Add(LeaseCacheInfo info) public bool Add(LeaseCacheInfo info)
{ {

View File

@@ -341,11 +341,14 @@ namespace linker.messenger.tuntap
public sealed partial class TuntapLanInfo public sealed partial class TuntapLanInfo
{ {
public IPAddress IP { get; set; } public IPAddress IP { get; set; } = IPAddress.Any;
public byte PrefixLength { get; set; } = 24; public byte PrefixLength { get; set; } = 24;
public bool Disabled { get; set; } public bool Disabled { get; set; }
public bool Exists { get; set; } public bool Exists { get; set; }
public string Error { get; set; } = string.Empty; public string Error { get; set; } = string.Empty;
public IPAddress MapIP { get; set; } = IPAddress.Any;
public byte MapPrefixLength { get; set; } = 24;
} }
public enum TuntapStatus : byte public enum TuntapStatus : byte
@@ -396,5 +399,16 @@ namespace linker.messenger.tuntap
/// </summary> /// </summary>
InterfaceOrder = 128, InterfaceOrder = 128,
} }
public sealed class TuntapConfigServerInfo
{
public TuntapLeaseConfigServerInfo Lease { get; set; } = new TuntapLeaseConfigServerInfo();
}
public sealed class TuntapLeaseConfigServerInfo
{
public int IPDays { get; set; } = 7;
public int NetworkDays { get; set; } = 30;
}
} }

View File

@@ -5,6 +5,8 @@ using linker.messenger.exroute;
using linker.messenger.signin; using linker.messenger.signin;
using linker.tun; using linker.tun;
using linker.tunnel.connection; using linker.tunnel.connection;
using System.Linq;
using System.Net;
namespace linker.messenger.tuntap namespace linker.messenger.tuntap
{ {
@@ -53,6 +55,7 @@ namespace linker.messenger.tuntap
}; };
tuntapTransfer.OnSetupSuccess += () => tuntapTransfer.OnSetupSuccess += () =>
{ {
SetMaps();
AddForward(); AddForward();
}; };
tuntapTransfer.OnShutdownBefore += () => tuntapTransfer.OnShutdownBefore += () =>
@@ -70,7 +73,10 @@ namespace linker.messenger.tuntap
}; };
//配置有更新,去同步一下 //配置有更新,去同步一下
tuntapConfigTransfer.OnUpdate += () => { AddForward(); _ = CheckDevice(); tuntapDecenter.Refresh(); }; tuntapConfigTransfer.OnUpdate += () =>
{
SetMaps(); AddForward(); _ = CheckDevice(); tuntapDecenter.Refresh();
};
//隧道回调 //隧道回调
tuntapProxy.Callback = this; tuntapProxy.Callback = this;
@@ -123,7 +129,7 @@ namespace linker.messenger.tuntap
{ {
tuntapTransfer.Write(buffer); tuntapTransfer.Write(buffer);
} }
/// <summary> /// <summary>
/// 重启网卡 /// 重启网卡
@@ -147,6 +153,13 @@ namespace linker.messenger.tuntap
tuntapTransfer.Shutdown(); tuntapTransfer.Shutdown();
} }
private void SetMaps()
{
var maps = tuntapConfigTransfer.Info.Lans
.Where(c => c.MapIP.Equals(IPAddress.Any) == false && c.Disabled == false)
.Select(c => new LanMapInfo { IP = c.IP, ToIP = c.MapIP, PrefixLength = c.MapPrefixLength }).ToArray();
tuntapTransfer.SetMap(maps);
}
// <summary> // <summary>
/// 添加端口转发 /// 添加端口转发
/// </summary> /// </summary>

View File

@@ -83,7 +83,7 @@ namespace linker.messenger.tuntap
/// <returns></returns> /// <returns></returns>
public async Task InputPacket(LinkerTunDevicPacket packet) public async Task InputPacket(LinkerTunDevicPacket packet)
{ {
//LoggerHelper.Instance.Warning($"tuntap read to {new IPEndPoint(new IPAddress(packet.DistIPAddress.Span), packet.DistPort)} {packet.Length}"); //LoggerHelper.Instance.Warning($"tuntap read {new IPEndPoint(new IPAddress(packet.SourceIPAddress.Span), packet.SourcePort)}->{new IPEndPoint(new IPAddress(packet.DistIPAddress.Span), packet.DistPort)}->{packet.Length}");
//IPV4广播组播、IPV6 多播 //IPV4广播组播、IPV6 多播
if (packet.IPV4Broadcast || packet.IPV6Multicast) if (packet.IPV4Broadcast || packet.IPV6Multicast)
{ {

View File

@@ -174,6 +174,13 @@ namespace linker.messenger.tuntap
{ {
linkerTunDeviceAdapter.DelRoute(ips); linkerTunDeviceAdapter.DelRoute(ips);
} }
public void SetMap(LanMapInfo[] maps)
{
linkerTunDeviceAdapter.SetMap(maps);
}
public async Task<bool> CheckAvailable(bool order = false) public async Task<bool> CheckAvailable(bool order = false)
{ {
return await linkerTunDeviceAdapter.CheckAvailable(order).ConfigureAwait(false); return await linkerTunDeviceAdapter.CheckAvailable(order).ConfigureAwait(false);

View File

@@ -2,6 +2,8 @@
{ {
public interface ILeaseServerStore public interface ILeaseServerStore
{ {
public TuntapLeaseConfigServerInfo Info { get; }
public List<LeaseCacheInfo> Get(); public List<LeaseCacheInfo> Get();
public bool Add(LeaseCacheInfo info); public bool Add(LeaseCacheInfo info);
public bool Update(LeaseCacheInfo info); public bool Update(LeaseCacheInfo info);

View File

@@ -194,7 +194,9 @@ namespace linker.messenger.tuntap.lease
//空闲的IP //空闲的IP
IEnumerable<int> idleIPs = Enumerable.Range((int)firstIPValue, (int)length + 1).Except(cache.Users.Select(c => (int)c.IP)); IEnumerable<int> idleIPs = Enumerable.Range((int)firstIPValue, (int)length + 1).Except(cache.Users.Select(c => (int)c.IP));
//过期的IP //过期的IP
IEnumerable<int> expireIPs = cache.Users.Where(c => (DateTime.Now - c.LastTime).TotalDays > 7).Select(c => (int)c.IP); IEnumerable<int> expireIPs = cache.Users
.Where(c => (DateTime.Now - c.LastTime).TotalDays > leaseServerStore.Info.IPDays)
.OrderBy(c => c.LastTime).Select(c => (int)c.IP);
uint newIPValue = (uint)idleIPs.FirstOrDefault(); uint newIPValue = (uint)idleIPs.FirstOrDefault();
//没找到空闲的,但是有其它超时的,抢一个 //没找到空闲的,但是有其它超时的,抢一个
@@ -240,7 +242,7 @@ namespace linker.messenger.tuntap.lease
{ {
DateTime now = DateTime.Now; DateTime now = DateTime.Now;
var items = caches.Values.Where(c => (now - c.LastTime).TotalDays > 30).ToList(); var items = caches.Values.Where(c => (now - c.LastTime).TotalDays > leaseServerStore.Info.NetworkDays).ToList();
if (items.Count > 0) if (items.Count > 0)
{ {
foreach (var item in items) foreach (var item in items)

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.239" ProductVersion="0.0.0.239" publishDir="/dist/" dstrip="false" local="false" ignored="false"> <project ver="10" name="linker.tray.win" libEmbed="true" icon="..\linker\favicon.ico" ui="win" output="linker.tray.win.exe" CompanyName="snltty" FileDescription="linker.tray.win" LegalCopyright="Copyright (C) snltty 2024" ProductName="linker.tray.win" InternalName="linker.install.win" FileVersion="0.0.0.240" ProductVersion="0.0.0.240" publishDir="/dist/" dstrip="false" local="false" ignored="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="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/> <file name="favicon.ico" path="res\favicon.ico" comment="res\favicon.ico"/>

Binary file not shown.

View File

@@ -1 +1 @@
.el-radio-group[data-v-7061404c]{margin-right:.6rem}.wrap[data-v-7061404c]{padding-bottom:1rem}.el-form-item[data-v-2bef0d8e]{margin-bottom:1rem}.el-input-number--small[data-v-2bef0d8e]{width:10rem!important}.el-form-item[data-v-3d96703d]{margin-bottom:1rem}.el-input-number--small[data-v-3d96703d]{width:10rem!important}.head .search>div[data-v-5d11d068]{margin-right:1rem}.page[data-v-5d11d068]{padding:2rem 0;display:inline-block}.el-form-item[data-v-5d11d068]{margin-bottom:1rem}.el-input-number--small[data-v-5d11d068]{width:10rem!important}.head .search>div[data-v-22d5523e]{margin-right:1rem}.page[data-v-22d5523e]{padding:2rem 0;display:inline-block}.el-form-item[data-v-22d5523e]{margin-bottom:1rem}.el-input-number--small[data-v-22d5523e]{width:10rem!important}.el-form-item[data-v-c2557c92]{margin-bottom:1rem}.el-input-number--small[data-v-c2557c92]{width:10rem!important}.blue[data-v-5b81e49d]{color:#409eff}a.a-edit[data-v-5b81e49d]{margin-left:1rem}a.a-edit .el-icon[data-v-5b81e49d]{vertical-align:middle}.servers-wrap[data-v-597f32d0]{padding:1rem;font-size:1.3rem;color:#555}.servers-wrap a[data-v-597f32d0]{color:#333} .el-radio-group[data-v-7061404c]{margin-right:.6rem}.wrap[data-v-7061404c]{padding-bottom:1rem}.el-form-item[data-v-2bef0d8e]{margin-bottom:1rem}.el-input-number--small[data-v-2bef0d8e]{width:10rem!important}.el-form-item[data-v-3d96703d]{margin-bottom:1rem}.el-input-number--small[data-v-3d96703d]{width:10rem!important}.head .search>div[data-v-5d11d068]{margin-right:1rem}.page[data-v-5d11d068]{padding:2rem 0;display:inline-block}.el-form-item[data-v-5d11d068]{margin-bottom:1rem}.el-input-number--small[data-v-5d11d068]{width:10rem!important}.head .search>div[data-v-22d5523e]{margin-right:1rem}.page[data-v-22d5523e]{padding:2rem 0;display:inline-block}.el-form-item[data-v-22d5523e]{margin-bottom:1rem}.el-input-number--small[data-v-22d5523e]{width:10rem!important}.el-form-item[data-v-c2557c92]{margin-bottom:1rem}.el-input-number--small[data-v-c2557c92]{width:10rem!important}.blue[data-v-21fcf68e]{color:#409eff}a.a-edit[data-v-21fcf68e]{margin-left:1rem}a.a-edit .el-icon[data-v-21fcf68e]{vertical-align:middle}.servers-wrap[data-v-597f32d0]{padding:1rem;font-size:1.3rem;color:#555}.servers-wrap a[data-v-597f32d0]{color:#333}

View File

@@ -1 +1 @@
<!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.70d76e49.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.3aab4747.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html> <!doctype html><html lang=""><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="favicon.ico"><title>linker.web</title><link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/><script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script><script defer="defer" src="js/chunk-vendors.cfba5739.js"></script><script defer="defer" src="js/app.cfe18975.js"></script><link href="css/chunk-vendors.d8267b33.css" rel="stylesheet"><link href="css/app.3aab4747.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but linker.web doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -48,8 +48,8 @@ namespace linker.tun
Shutdown(); Shutdown();
return false; return false;
} }
fsRead = new FileStream(safeFileHandle, FileAccess.Read, 32 * 1024, true); fsRead = new FileStream(safeFileHandle, FileAccess.Read, 65 * 1024, true);
fsWrite = new FileStream(safeFileHandle, FileAccess.Write, 32 * 1024, true); fsWrite = new FileStream(safeFileHandle, FileAccess.Write, 65 * 1024, true);
interfaceLinux = GetLinuxInterfaceNum(); interfaceLinux = GetLinuxInterfaceNum();
return true; return true;
@@ -310,7 +310,7 @@ namespace linker.tun
} }
private readonly byte[] buffer = new byte[8 * 1024]; private readonly byte[] buffer = new byte[65 * 1024];
private readonly object writeLockObj = new object(); private readonly object writeLockObj = new object();
public byte[] Read(out int length) public byte[] Read(out int length)
{ {

View File

@@ -1,5 +1,10 @@
using linker.libs; using linker.libs;
using linker.libs.timer; using linker.libs.timer;
using System;
using System.Buffers.Binary;
using System.Collections.Concurrent;
using System.Collections.Frozen;
using System.Linq;
using System.Net; using System.Net;
namespace linker.tun namespace linker.tun
@@ -20,6 +25,10 @@ namespace linker.tun
public string NatError => natError; public string NatError => natError;
private FrozenDictionary<uint, uint> mapDic = new Dictionary<uint, uint>().ToFrozenDictionary();
private uint[] masks = Array.Empty<uint>();
private OperatingManager operatingManager = new OperatingManager(); private OperatingManager operatingManager = new OperatingManager();
public LinkerTunDeviceStatus Status public LinkerTunDeviceStatus Status
{ {
@@ -224,6 +233,8 @@ namespace linker.tun
byte[] buffer = linkerTunDevice.Read(out int length); byte[] buffer = linkerTunDevice.Read(out int length);
if (length == 0) if (length == 0)
{ {
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Warning($"tuntap read buffer 0");
await Task.Delay(1000); await Task.Delay(1000);
continue; continue;
} }
@@ -249,14 +260,58 @@ namespace linker.tun
/// </summary> /// </summary>
/// <param name="buffer"></param> /// <param name="buffer"></param>
/// <returns></returns> /// <returns></returns>
public bool Write(ReadOnlyMemory<byte> buffer) public unsafe bool Write(ReadOnlyMemory<byte> buffer)
{ {
if (linkerTunDevice != null && Status == LinkerTunDeviceStatus.Running) if (linkerTunDevice != null && Status == LinkerTunDeviceStatus.Running)
{ {
MapToRealIP(buffer);
return linkerTunDevice.Write(buffer); return linkerTunDevice.Write(buffer);
} }
return false; return false;
} }
private unsafe void MapToRealIP(ReadOnlyMemory<byte> buffer)
{
//只支持映射IPV4
if ((byte)(buffer.Span[0] >> 4 & 0b1111) != 4) return;
//映射表不为空
if (masks.Length == 0 || mapDic.Count == 0) return;
uint dist = BinaryPrimitives.ReadUInt32BigEndian(buffer.Span.Slice(16, 4));
for (int i = 0; i < masks.Length; i++)
{
//目标IP网络号存在映射表中找到映射后的真实网络号替换网络号得到最终真实的IP
if (mapDic.TryGetValue(dist & masks[i], out uint realNetwork))
{
//将原本的目标IP修改为映射的IP
fixed (byte* ptr = buffer.Span)
{
//修改目标IP
*(uint*)(ptr + 16) = BinaryPrimitives.ReverseEndianness(realNetwork | (dist & ~masks[i]));
//重新计算IP头校验和
*(ushort*)(ptr + 10) = 0;
*(ushort*)(ptr + 10) = Checksum((ushort*)ptr, (byte)((*ptr & 0b1111) * 4));
}
break;
}
}
}
/// <summary>
/// 设置IP映射列表
/// </summary>
/// <param name="maps"></param>
public void SetMap(LanMapInfo[] maps)
{
if (maps == null || maps.Length == 0)
{
mapDic = new Dictionary<uint, uint>().ToFrozenDictionary();
masks = Array.Empty<uint>();
return;
}
mapDic = maps.ToFrozenDictionary(x => NetworkHelper.ToNetworkValue(x.IP, x.PrefixLength), x => NetworkHelper.ToNetworkValue(x.ToIP, x.PrefixLength));
masks = maps.Select(x => NetworkHelper.ToPrefixValue(x.PrefixLength)).ToArray();
}
/// <summary> /// <summary>
/// 计算校验和 /// 计算校验和
@@ -290,4 +345,11 @@ namespace linker.tun
return await linkerTunDevice.CheckAvailable(order); return await linkerTunDevice.CheckAvailable(order);
} }
} }
public sealed class LanMapInfo
{
public IPAddress IP { get; set; }
public IPAddress ToIP { get; set; }
public byte PrefixLength { get; set; }
}
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap" <el-dialog v-model="state.show" :close-on-click-modal="false" append-to=".app-wrap"
:title="`设置[${state.machineName}]组网`" top="1vh" width="760"> :title="`设置[${state.machineName}]组网`" top="1vh" width="780">
<div> <div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="8rem"> <el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="8rem">
<el-form-item label="网卡名" prop="Name"> <el-form-item label="网卡名" prop="Name">
@@ -12,7 +12,7 @@
<el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" /> <el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" />
</el-form-item> </el-form-item>
<el-form-item label=""> <el-form-item label="" class="mgb-0">
<el-checkbox class="mgr-1" v-model="state.ruleForm.ShowDelay" label="显示延迟" size="large" /> <el-checkbox class="mgr-1" v-model="state.ruleForm.ShowDelay" label="显示延迟" size="large" />
<el-checkbox class="mgr-1" v-model="state.ruleForm.AutoConnect" label="自动连接" size="large" /> <el-checkbox class="mgr-1" v-model="state.ruleForm.AutoConnect" label="自动连接" size="large" />
<el-checkbox class="mgr-1" v-model="state.ruleForm.Multicast" label="禁用广播" size="large" /> <el-checkbox class="mgr-1" v-model="state.ruleForm.Multicast" label="禁用广播" size="large" />
@@ -145,6 +145,5 @@ export default {
.upgrade-wrap{ .upgrade-wrap{
border:1px solid #ddd; border:1px solid #ddd;
margin-bottom:2rem margin-bottom:2rem
padding:0 0 1rem 0;
} }
</style> </style>

View File

@@ -5,7 +5,7 @@
<span class="green" v-if="state.testing">testing</span> <span class="green" v-if="state.testing">testing</span>
</div> </div>
<div class="wrap"> <div class="wrap">
<el-table stripe :data="state.forwards" border size="small" width="100%" height="300px" @cell-dblclick="handleCellClick"> <el-table stripe :data="state.forwards" border size="small" width="100%" height="200px" @cell-dblclick="handleCellClick">
<el-table-column prop="ListenPort" label="源端口" width="60"> <el-table-column prop="ListenPort" label="源端口" width="60">
<template #default="scope"> <template #default="scope">
<template v-if="scope.row.ListenPortEditing"> <template v-if="scope.row.ListenPortEditing">
@@ -186,7 +186,7 @@ export default {
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.wrap{ .wrap{
padding-right:1rem; padding:0 1rem 1rem 0;
} }
.remark{ .remark{
white-space: nowrap; /* 文本不换行 */ white-space: nowrap; /* 文本不换行 */

View File

@@ -4,27 +4,77 @@
<span class="yellow">填写局域网IP使用NAT转发</span> <span class="yellow">填写局域网IP使用NAT转发</span>
</div> </div>
<div class="wrap"> <div class="wrap">
<template v-for="(item, index) in state.lans" :key="index"> <el-table stripe :data="state.lans" border size="small" width="100%" height="200px" @cell-dblclick="handleCellClick">
<div class="flex" style="margin-bottom:.6rem"> <el-table-column prop="IP" label="路由IP" width="120">
<div > <template #default="scope">
<el-input v-model="item.IP" style="width:14rem" /> <template v-if="scope.row.IPEditing">
<span>/</span> <el-input autofocus size="small" v-model="scope.row.IP"
<el-input @change="handleMaskChange(index)" v-model="item.PrefixLength" @blur="handleEditBlur(scope.row, 'IP')"></el-input>
style="width:4rem" /> </template>
</div> <template v-else>
<div class="pdl-10"> <strong v-if="scope.row.Error" :title="scope.row.Error" class="red">{{ scope.row.IP }}</strong>
<el-checkbox v-model="item.Disabled" label="禁用记录" style="vertical-align: middle;"/> <span v-else>{{ scope.row.IP }}</span>
</div> </template>
<div class="pdl-10"> </template>
<el-button type="danger" @click="handleDel(index)" size="small"><el-icon> </el-table-column>
<Delete /> <el-table-column prop="PrefixLength" label="路由掩码" width="80">
</el-icon></el-button> <template #default="scope">
<el-button type="primary" @click="handleAdd(index)" size="small"><el-icon> <template v-if="scope.row.PrefixLengthEditing">
<Plus /> <el-input autofocus size="small" v-model="scope.row.PrefixLength"
</el-icon></el-button> @blur="handleEditBlur(scope.row, 'PrefixLength')"></el-input>
</div> </template>
</div> <template v-else>
</template> <strong v-if="scope.row.Error" :title="scope.row.Error" class="red">{{ scope.row.PrefixLength }}</strong>
<span v-else>{{ scope.row.PrefixLength }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="MapIP" label="目标IP" width="120">
<template #default="scope">
<template v-if="scope.row.MapIPEditing">
<el-input autofocus size="small" v-model="scope.row.MapIP"
@blur="handleEditBlur(scope.row, 'MapIP')"></el-input>
</template>
<template v-else>
<strong v-if="scope.row.Error" :title="scope.row.Error" class="red">{{ scope.row.MapIP }}</strong>
<span v-else>{{ scope.row.MapIP }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="MapPrefixLength" label="目标掩码" width="80">
<template #default="scope">
<template v-if="scope.row.MapPrefixLengthEditing">
<el-input autofocus size="small" v-model="scope.row.MapPrefixLength"
@blur="handleEditBlur(scope.row, 'MapPrefixLength')"></el-input>
</template>
<template v-else>
<strong v-if="scope.row.Error" :title="scope.row.Error" class="red">{{ scope.row.MapPrefixLength }}</strong>
<span v-else>{{ scope.row.MapPrefixLength }}</span>
</template>
</template>
</el-table-column>
<el-table-column prop="Disabled" label="禁用">
<template #default="scope">
<el-checkbox v-model="scope.row.Disabled" label="禁用记录"/>
</template>
</el-table-column>
<el-table-column prop="Oper" label="操作" width="110">
<template #default="scope">
<div>
<el-popconfirm title="删除不可逆,是否确认?" @confirm="handleDel(scope.$index)">
<template #reference>
<el-button type="danger" size="small">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-popconfirm>
<el-button type="primary" size="small" @click="handleAdd(scope.$index)">
<el-icon><Plus /></el-icon>
</el-button>
</div>
</template>
</el-table-column>
</el-table>
</div> </div>
</div> </div>
</template> </template>
@@ -43,15 +93,33 @@ export default {
lans: tuntap.value.current.Lans.slice(0) lans: tuntap.value.current.Lans.slice(0)
}); });
if (state.lans.length == 0) { if (state.lans.length == 0) {
state.lans.push({ IP: '0.0.0.0', PrefixLength: 24 }); state.lans.push({ IP: '0.0.0.0', PrefixLength: 24,MapIP:'0.0.0.0',MapPrefixLength:24 });
} }
const handleMaskChange = (index) => { const handleCellClick = (row, column) => {
var value = +state.lans[index].PrefixLength; handleEdit(row, column.property);
if (value > 32 || value < 0 || isNaN(value)) { }
value = 24; const handleEdit = (row, p) => {
state.lans.forEach(c => {
c[`IPEditing`] = false;
c[`PrefixLengthEditing`] = false;
c[`MapIPEditing`] = false;
c[`MapPrefixLengthEditing`] = false;
})
row[`${p}Editing`] = true;
row[`__editing`] = true;
}
const handleEditBlur = (row, p) => {
row[`${p}Editing`] = false;
row[`__editing`] = false;
if(p == 'PrefixLength' || p == 'MapPrefixLength'){
var value = +row[p];
if (value > 32 || value < 0 || isNaN(value)) {
value = 24;
}
row[p] = value;
} }
state.lans[index].PrefixLength = value;
} }
const handleDel = (index) => { const handleDel = (index) => {
state.lans.splice(index, 1); state.lans.splice(index, 1);
@@ -68,13 +136,13 @@ export default {
} }
return { return {
state,handleMaskChange,handleDel,handleAdd,getData state,handleDel,handleAdd,getData,handleCellClick,handleEditBlur
} }
} }
} }
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.wrap{ .wrap{
padding-right:1rem; padding:0 1rem 1rem 0;
} }
</style> </style>

View File

@@ -2,9 +2,6 @@
using System.ServiceProcess; using System.ServiceProcess;
using System.Diagnostics; using System.Diagnostics;
using linker.messenger.entry; using linker.messenger.entry;
using linker.tunnel;
using linker.messenger.relay.client;
using linker.messenger.signin;
namespace linker namespace linker
{ {
@@ -27,10 +24,6 @@ namespace linker
//ThreadPool.SetMinThreads(1024, 1024); //ThreadPool.SetMinThreads(1024, 1024);
//ThreadPool.SetMaxThreads(65535, 65535); //ThreadPool.SetMaxThreads(65535, 65535);
//LoggerHelper.Instance.Warning(Process.GetCurrentProcess().MainModule.FileName);
//LoggerHelper.Instance.Warning(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName));
//LoggerHelper.Instance.Warning(Directory.GetCurrentDirectory());
string serviceDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName); string serviceDirectory = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
Directory.SetCurrentDirectory(serviceDirectory); Directory.SetCurrentDirectory(serviceDirectory);
@@ -59,6 +52,7 @@ namespace linker
LinkerMessengerEntry.Initialize(); LinkerMessengerEntry.Initialize();
LinkerMessengerEntry.Build(); LinkerMessengerEntry.Build();
LinkerMessengerEntry.Setup(ExcludeModule.None, configDic); LinkerMessengerEntry.Setup(ExcludeModule.None, configDic);
LoggerHelper.Instance.Warning($"current version : {VersionHelper.version}"); LoggerHelper.Instance.Warning($"current version : {VersionHelper.version}");

View File

@@ -24,7 +24,8 @@
2. 一些修复和优化 2. 一些修复和优化
3. 优化自动分配IP 3. 优化自动分配IP
4. 优化网卡,排除不明数据包 4. 优化网卡,排除不明数据包
5. 优化管理UI适配移动端</Description> 5. 优化管理UI适配移动端
6. 虚拟网卡点对网IP映射</Description>
<Copyright>snltty</Copyright> <Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl> <PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl> <RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,7 +1,8 @@
v1.7.2 v1.7.2
2025-04-11 17:28:49 2025-04-13 15:50:43
1. 内网穿透的计划任务 1. 内网穿透的计划任务
2. 一些修复和优化 2. 一些修复和优化
3. 优化自动分配IP 3. 优化自动分配IP
4. 优化网卡,排除不明数据包 4. 优化网卡,排除不明数据包
5. 优化管理UI适配移动端 5. 优化管理UI适配移动端
6. 虚拟网卡点对网IP映射