This commit is contained in:
snltty
2024-10-17 17:30:30 +08:00
parent c5a12185e7
commit 078d61d5b0
36 changed files with 769 additions and 150 deletions

View File

@@ -37,7 +37,7 @@ jobs:
release_name: v1.4.9.${{ steps.date.outputs.today }}
draft: false
prerelease: false
body: "1. 使用原生成器替代反射\r\n2. 统计所有服务端,客户端数量\r\n3. 创建docker镜像清单自动识别平台\r\n4. docker镜像增加arm\r\n5. 优化了arm平台下获取设备ID的方法\r\n6. 增加了查看流量信息的权限\r\n7. 有服务器更新密钥的客户端,可以更新本服务器的所有客户端"
body: "1. 新增多分组管理,方便分组切换\r\n2. 增加分组密码,提高分组隔离安全性\r\n3. 优化配置同步,可自由选择同步哪些信息\r\n4. 自动分配虚拟网卡IP\r\n5. 线程池优化\r\n6. 一些UI优化"
- name: upload-win-x86-oss
id: upload-win-x86-oss
uses: tvrcgo/oss-action@v0.1.1

View File

@@ -37,6 +37,9 @@
- [x] 权限管理,主客户端拥有完全权限,可导出、配置子客户端配置,分配其管理权限
- [x] 自定义验证,通过`HTTP POST`让你可以自定义认证是否允许`连接信标``中继``内网穿透`
- [x] 流量统计,统计服务器`信标``中继``内网穿透` 的流量情况
- [ ] 网络配置主客户端设置网络所有客户端自动分配IP
- [ ] 分布式,中继,内网穿透分布式,可实现负载均衡
## 界面预览

View File

@@ -2,7 +2,7 @@
sidebar_position: 5
---
# 5、组网权限
# 5、导出配置和管理权限
:::tip[说明]
1. 按 <a href="../2、首次运行/2.1、安装">首次运行,安装</a>,此设备拥有完全管理权限,可导出配置,用以作为组网子设备运行

View File

@@ -157,7 +157,8 @@ namespace linker.libs
return Array.Empty<IPAddress>();
}
public static byte GetPrefixLength(uint ip)
public static byte PrefixValue2Length(uint ip)
{
byte maskLength = 32;
for (int i = 0; i < sizeof(uint); i++)
@@ -170,34 +171,60 @@ namespace linker.libs
}
return maskLength;
}
public static uint GetPrefixIP(byte prefixLength)
public static uint PrefixLength2Value(byte prefixLength)
{
//最多<<31 所以0需要单独计算
if (prefixLength < 1) return 0;
return 0xffffffff << (32 - prefixLength);
}
public static IPAddress GetPrefixIp(uint prefixIP)
public static IPAddress PrefixValue2IP(uint prefixIP)
{
return new IPAddress(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(prefixIP)));
}
public static IPAddress ToNetworkIp(IPAddress ip, uint prefixIP)
public static uint IP2Value(IPAddress ip)
{
return ToNetworkIp(BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes()), prefixIP);
return BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes());
}
public static IPAddress ToNetworkIp(uint ip, uint prefixIP)
public static IPAddress Value2IP(uint value)
{
return new IPAddress(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(value)));
}
public static IPAddress NetworkIP2IP(IPAddress ip, uint prefixIP)
{
return NetworkValue2Ip(BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes()), prefixIP);
}
public static uint NetworkIP2Value(IPAddress ip, uint prefixIP)
{
return NetworkValue2Value(BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes()), prefixIP);
}
public static IPAddress NetworkValue2Ip(uint ip, uint prefixIP)
{
return new IPAddress(BinaryPrimitives.ReverseEndianness(ip & prefixIP).ToBytes());
}
public static uint NetworkValue2Value(uint ip, uint prefixIP)
{
return ip & prefixIP;
}
public static uint BroadcastValue2Value(uint ip, uint prefixIP)
{
return ip | ~prefixIP;
}
public static IPAddress ToGatewayIP(IPAddress ip, byte prefixLength)
{
uint network = BinaryPrimitives.ReadUInt32BigEndian(ToNetworkIp(ip, NetworkHelper.GetPrefixIP(prefixLength)).GetAddressBytes());
uint network = BinaryPrimitives.ReadUInt32BigEndian(NetworkIP2IP(ip, NetworkHelper.PrefixLength2Value(prefixLength)).GetAddressBytes());
IPAddress gateway = new IPAddress(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(network + 1)));
return gateway;
}
public static IPAddress ToGatewayIP(uint ip, uint prefixIP)
{
uint network = BinaryPrimitives.ReadUInt32BigEndian(ToNetworkIp(ip, prefixIP).GetAddressBytes());
uint network = BinaryPrimitives.ReadUInt32BigEndian(NetworkValue2Ip(ip, prefixIP).GetAddressBytes());
IPAddress gateway = new IPAddress(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(network + 1)));
return gateway;
}

View File

@@ -138,7 +138,7 @@ namespace linker.tun
error = string.Empty;
try
{
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(prefixLength));
IPAddress network = NetworkHelper.NetworkIP2IP(address, NetworkHelper.PrefixLength2Value(prefixLength));
CommandHelper.Linux(string.Empty, new string[] {
$"sysctl -w net.ipv4.ip_forward=1",
@@ -170,7 +170,7 @@ namespace linker.tun
$"iptables -D FORWARD -o {Name} -m state --state ESTABLISHED,RELATED -j ACCEPT"
});
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(prefixLength));
IPAddress network = NetworkHelper.NetworkIP2IP(address, NetworkHelper.PrefixLength2Value(prefixLength));
string iptableLineNumbers = CommandHelper.Linux(string.Empty, new string[] { $"iptables -t nat -L --line-numbers | grep {network}/{prefixLength} | cut -d' ' -f1" });
if (string.IsNullOrWhiteSpace(iptableLineNumbers) == false)
{
@@ -223,8 +223,8 @@ namespace linker.tun
{
string[] commands = ips.Select(item =>
{
uint prefixValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress network = NetworkHelper.ToNetworkIp(item.Address, prefixValue);
uint prefixValue = NetworkHelper.PrefixLength2Value(item.PrefixLength);
IPAddress network = NetworkHelper.NetworkIP2IP(item.Address, prefixValue);
return $"ip route add {network}/{item.PrefixLength} via {ip} dev {Name} metric 1 ";
}).ToArray();
@@ -237,8 +237,8 @@ namespace linker.tun
{
string[] commands = ip.Select(item =>
{
uint prefixValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress network = NetworkHelper.ToNetworkIp(item.Address, prefixValue);
uint prefixValue = NetworkHelper.PrefixLength2Value(item.PrefixLength);
IPAddress network = NetworkHelper.NetworkIP2IP(item.Address, prefixValue);
return $"ip route del {network}/{item.PrefixLength}";
}).ToArray();
CommandHelper.Linux(string.Empty, commands);

View File

@@ -35,7 +35,7 @@ namespace linker.tun
safeFileHandle = File.OpenHandle($"/dev/{Name}", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.Asynchronous);
fs = new FileStream(safeFileHandle, FileAccess.ReadWrite, 1500);
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(prefixLength));
IPAddress network = NetworkHelper.NetworkIP2IP(address, NetworkHelper.PrefixLength2Value(prefixLength));
CommandHelper.Osx(string.Empty, new string[] {
$"route delete -net {network}/{prefixLength} {address}",
$"ifconfig {Name} {address} {address} up",
@@ -55,7 +55,7 @@ namespace linker.tun
fs.Dispose();
fs = null;
}
IPAddress network = NetworkHelper.ToNetworkIp(address, NetworkHelper.GetPrefixIP(this.prefixLength));
IPAddress network = NetworkHelper.NetworkIP2IP(address, NetworkHelper.PrefixLength2Value(this.prefixLength));
CommandHelper.Osx(string.Empty, new string[] { $"route delete -net {network}/{prefixLength} {address}" });
}
@@ -63,7 +63,7 @@ namespace linker.tun
{
string[] commands = ips.Select(item =>
{
IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, item.PrefixLength);
IPAddress _ip = NetworkHelper.NetworkIP2IP(item.Address, item.PrefixLength);
return $"route add -net {_ip}/{item.PrefixLength} {ip}";
}).ToArray();
if (commands.Length > 0)
@@ -75,7 +75,7 @@ namespace linker.tun
{
string[] commands = ip.Select(item =>
{
IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, item.PrefixLength);
IPAddress _ip = NetworkHelper.NetworkIP2IP(item.Address, item.PrefixLength);
return $"route delete -net {_ip}/{item.PrefixLength}";
}).ToArray();
if (commands.Length > 0)

View File

@@ -152,7 +152,7 @@ namespace linker.tun
try
{
CommandHelper.PowerShell($"start-service WinNat", [], out error);
IPAddress network = NetworkHelper.ToNetworkIp(this.address, NetworkHelper.GetPrefixIP(prefixLength));
IPAddress network = NetworkHelper.NetworkIP2IP(this.address, NetworkHelper.PrefixLength2Value(prefixLength));
CommandHelper.PowerShell($"New-NetNat -Name {Name} -InternalIPInterfaceAddressPrefix {network}/{prefixLength}", [], out error);
if (string.IsNullOrWhiteSpace(error) == false)
@@ -214,9 +214,9 @@ namespace linker.tun
{
string[] commands = ips.Select(item =>
{
uint maskValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress mask = NetworkHelper.GetPrefixIp(maskValue);
IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, maskValue);
uint maskValue = NetworkHelper.PrefixLength2Value(item.PrefixLength);
IPAddress mask = NetworkHelper.PrefixValue2IP(maskValue);
IPAddress _ip = NetworkHelper.NetworkIP2IP(item.Address, maskValue);
return $"route add {_ip} mask {mask} {ip} metric 5 if {interfaceNumber}";
}).ToArray();
@@ -230,9 +230,9 @@ namespace linker.tun
{
string[] commands = ip.Select(item =>
{
uint maskValue = NetworkHelper.GetPrefixIP(item.PrefixLength);
IPAddress mask = NetworkHelper.GetPrefixIp(maskValue);
IPAddress _ip = NetworkHelper.ToNetworkIp(item.Address, maskValue);
uint maskValue = NetworkHelper.PrefixLength2Value(item.PrefixLength);
IPAddress mask = NetworkHelper.PrefixValue2IP(maskValue);
IPAddress _ip = NetworkHelper.NetworkIP2IP(item.Address, maskValue);
return $"route delete {_ip}";
}).ToArray();
if (commands.Length > 0)

View File

@@ -17,9 +17,9 @@ export const getAccesss = (machineid) => {
export const setAccess = (data) => {
return sendWebsocketMsg('configclient/SetAccess', data);
}
export const setSecretKeyAsync = (data) => {
return sendWebsocketMsg('configclient/SecretKeyAsync', data);
export const getSyncNames = () => {
return sendWebsocketMsg('configclient/SyncNames');
}
export const setServerAsync = (data) => {
return sendWebsocketMsg('configclient/ServerAsync', data);
export const setSync = (data) => {
return sendWebsocketMsg('configclient/Sync', data);
}

View File

@@ -1,71 +1,81 @@
<template>
<div>
<el-card shadow="never">
<template #header>
<div class="card-header flex">
<span>同步密钥</span>
<span class="flex-1"></span>
<el-button type="success" @click="handleSyncSecretKey">确定同步</el-button>
<el-card shadow="never" style="max-width: 540px;margin:0 auto;">
<template #header>选择你需要同步的项将这些配置同步到本组所有客户端</template>
<div>
<el-checkbox v-model="state.checkAll" :indeterminate="state.isIndeterminate" @change="handleCheckAllChange">全选</el-checkbox>
<el-checkbox-group v-model="state.checkeds" @change="handleCheckedsChange">
<el-row>
<template v-for="name in state.names">
<el-col :span="8">
<el-checkbox :key="name.name" :label="name.label" :value="name.name">{{ name.label }}</el-checkbox>
</el-col>
</template>
</el-row>
</el-checkbox-group>
</div>
<template #footer>
<div class="t-c">
<el-button type="success" @click="handleSync">确定同步</el-button>
</div>
</template>
<div>
同步信标服务器中继服务器服务器代理穿透的密钥到所有客户端
</div>
</el-card>
<el-card shadow="never" style="margin-top:2rem">
<template #header>
<div class="card-header flex">
<span>同步服务器配置</span>
<span class="flex-1"></span>
<el-button type="success" @click="handleSyncServer">确定同步</el-button>
</div>
</template>
<div>
同步信标服务器端口服务器中继服务器列表到所有客户端
</div>
</el-card>
</div>
</template>
<script>
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { reactive } from 'vue'
import { setSecretKeyAsync, setServerAsync } from '@/apis/config';
import { onMounted, reactive } from 'vue'
import { getSyncNames, setSync } from '@/apis/config';
export default {
label:'同步配置',
name:'async',
order:7,
setup(props) {
const globalData = injectGlobalData();
const state = reactive({});
const state = reactive({
names:[],
checkAll:false,
isIndeterminate:false,
checkeds:[],
});
const handleSyncSecretKey = ()=>{
const json = {
SignSecretKey:globalData.value.config.Client.ServerInfo.SecretKey,
RelaySecretKey:globalData.value.config.Client.Relay.Servers[0].SecretKey,
SForwardSecretKey:globalData.value.config.Client.SForward.SecretKey
}
setSecretKeyAsync(json).then(()=>{
ElMessage.success('已操作');
}).catch(()=>{
ElMessage.error('操作失败');
});;
const handleCheckAllChange = (val)=>{
state.checkeds = val ? state.names.map(c=>c.name) : [];
state.isIndeterminate = false
}
const handleSyncServer = ()=>{
const json = {
SignServers:globalData.value.config.Client.Servers,
RelayServers:globalData.value.config.Client.Relay.Servers,
TunnelServers:globalData.value.config.Client.Tunnel.Servers
}
setServerAsync(json).then(()=>{
ElMessage.success('已操作');
}).catch(()=>{
ElMessage.error('操作失败');
});;
const handleCheckedsChange = (value)=>{
const checkedCount = value.length
state.checkAll = checkedCount === state.names.length
state.isIndeterminate = checkedCount > 0 && checkedCount < state.names.length;
}
return {state,handleSyncSecretKey,handleSyncServer}
const labels = {
'SignInSecretKey':'当前信标密钥',
'GroupSecretKey':'当前分组密码',
'RelaySecretKey':'当前中继密钥',
'SForwardSecretKey':'当前服务器穿透密钥',
'UpdaterSecretKey':'服务器更新密钥',
'TunnelTransports':'打洞协议列表'
}
onMounted(()=>{
getSyncNames().then(res=>{
state.names = res.map(c=>{
return {name:c,label:labels[c]}
});
});
});
const handleSync = ()=>{
if(state.checkeds.length == 0) {
ElMessage.error('至少选择一个');
return;
}
setSync(state.checkeds).then(res=>{
ElMessage.success('已操作');
});
}
return {state,handleCheckAllChange,handleCheckedsChange,handleSync}
}
}
</script>

View File

@@ -21,13 +21,12 @@
<Title>linker</Title>
<Authors>snltty</Authors>
<Company>snltty</Company>
<Description>1. 使用原生成器替代反射
2. 统计所有服务端,客户端数量
3. 创建docker镜像清单自动识别平台
4. docker镜像增加arm
5. 优化了arm平台下获取设备ID的方法
6. 增加了查看流量信息的权限
7. 有服务器更新密钥的客户端,可以更新本服务器的所有客户端</Description>
<Description>1. 新增多分组管理,方便分组切换
2. 增加分组密码,提高分组隔离安全性
3. 优化配置同步,可自由选择同步哪些信息
4. 自动分配虚拟网卡IP
5. 线程池优化
6. 一些UI优化</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -35,6 +35,9 @@ namespace linker.plugins.client
serviceCollection.AddSingleton<SignInArgsSecretKeyClient>();
serviceCollection.AddSingleton<SignInArgsGroupPasswordClient>();
serviceCollection.AddSingleton<ConfigSyncSignInSecretKey>();
serviceCollection.AddSingleton<ConfigSyncGroupSecretKey>();
}
public void UseClient(ServiceProvider serviceProvider, FileConfig config)

View File

@@ -0,0 +1,51 @@
using linker.config;
using linker.plugins.config;
using MemoryPack;
namespace linker.plugins.client
{
public sealed class ConfigSyncSignInSecretKey : IConfigSync
{
public string Name => "SignInSecretKey";
private readonly FileConfig fileConfig;
public ConfigSyncSignInSecretKey(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.Servers.FirstOrDefault().SecretKey);
}
public void SetData(Memory<byte> data)
{
string value = MemoryPackSerializer.Deserialize<string>(data.Span);
foreach (var item in fileConfig.Data.Client.Servers)
{
item.SecretKey = value;
}
fileConfig.Data.Update();
}
}
public sealed class ConfigSyncGroupSecretKey : IConfigSync
{
public string Name => "GroupSecretKey";
private readonly FileConfig fileConfig;
public ConfigSyncGroupSecretKey(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.Group.Password);
}
public void SetData(Memory<byte> data)
{
fileConfig.Data.Client.Group.Password = MemoryPackSerializer.Deserialize<string>(data.Span);
fileConfig.Data.Update();
}
}
}

View File

@@ -6,7 +6,6 @@ using MemoryPack;
using System.Net;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
namespace linker.client.config
{
@@ -209,10 +208,6 @@ namespace linker.config
public string Host { get; set; } = string.Empty;
public string SecretKey { get; set; } = string.Empty;
public string ToStr()
{
return $"{Host}-{SecretKey}";
}
}

View File

@@ -225,15 +225,23 @@ namespace linker.plugins.config
}
public List<ConfigSyncNameInfo> SyncNames(ApiControllerParamsInfo param)
public List<string> SyncNames(ApiControllerParamsInfo param)
{
return configSyncTreansfer.GetNames();
}
[ClientApiAccessAttribute(ClientApiAccess.Sync)]
public bool Sync(ApiControllerParamsInfo param)
public async Task<bool> Sync(ApiControllerParamsInfo param)
{
string[] names = param.Content.DeJson<string[]>();
configSyncTreansfer.Sync(names);
if (names.Length == 1)
{
await configSyncTreansfer.Sync(names[0]);
}
else
{
configSyncTreansfer.Sync(names);
}
return true;
}
}

View File

@@ -10,7 +10,6 @@ namespace linker.plugins.config
public interface IConfigSync
{
public string Name { get; }
public string Label { get; }
public Memory<byte> GetData();
public void SetData(Memory<byte> data);
}
@@ -23,11 +22,6 @@ namespace linker.plugins.config
public Memory<byte> Data { get; set; }
}
public sealed class ConfigSyncNameInfo
{
public string Name { get; set; } = string.Empty;
public string Label { get; set; } = string.Empty;
}
public sealed partial class ConfigSyncTreansfer
{
private readonly SemaphoreSlim slim = new SemaphoreSlim(1);
@@ -45,9 +39,9 @@ namespace linker.plugins.config
LoggerHelper.Instance.Info($"load config sync transport:{string.Join(",", syncs.Select(c => c.Name))}");
}
public List<ConfigSyncNameInfo> GetNames()
public List<string> GetNames()
{
return syncs.Select(c => new ConfigSyncNameInfo { Label = c.Label, Name = c.Name }).ToList();
return syncs.Select(c => c.Name).ToList();
}
public void Sync(string[] names)
@@ -63,7 +57,7 @@ namespace linker.plugins.config
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)ConfigMessengerIds.SyncForward,
Payload = c.GetData(),
Payload = MemoryPackSerializer.Serialize(new ConfigAsyncInfo { Name = c.Name, Data = c.GetData() }),
});
});
@@ -75,6 +69,19 @@ namespace linker.plugins.config
slim.Release();
});
}
public async Task Sync(string name)
{
var sync = syncs.FirstOrDefault(c => c.Name == name);
if(sync != null)
{
await messengerSender.SendOnly(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)ConfigMessengerIds.SyncForward,
Payload = MemoryPackSerializer.Serialize(new ConfigAsyncInfo { Name = sync.Name, Data = sync.GetData() }),
});
}
}
public void Sync(ConfigAsyncInfo info)
{
var sync = syncs.FirstOrDefault(c => c.Name == info.Name);

View File

@@ -0,0 +1,31 @@
using linker.config;
using linker.plugins.config;
using MemoryPack;
namespace linker.plugins.relay
{
public sealed class ConfigSyncRelaySecretKey : IConfigSync
{
public string Name => "RelaySecretKey";
private readonly FileConfig fileConfig;
public ConfigSyncRelaySecretKey(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.Relay.Servers.FirstOrDefault().SecretKey);
}
public void SetData(Memory<byte> data)
{
string value = MemoryPackSerializer.Deserialize<string>(data.Span);
foreach (var item in fileConfig.Data.Client.Relay.Servers)
{
item.SecretKey = value;
}
fileConfig.Data.Update();
}
}
}

View File

@@ -1,5 +1,4 @@
using linker.config;
using linker.plugins.relay.messenger;
using linker.libs.api;
using linker.libs.extends;
using linker.plugins.client;

View File

@@ -29,6 +29,8 @@ namespace linker.plugins.relay
serviceCollection.AddSingleton<RelayTransfer>();
serviceCollection.AddSingleton<RelayFlow>();
serviceCollection.AddSingleton<ConfigSyncRelaySecretKey>();
}
public void AddServer(ServiceCollection serviceCollection, FileConfig config)

View File

@@ -0,0 +1,27 @@
using linker.config;
using linker.plugins.config;
using MemoryPack;
namespace linker.plugins.sforward
{
public sealed class ConfigSyncSForwardSecretKey : IConfigSync
{
public string Name => "SForwardSecretKey";
private readonly FileConfig fileConfig;
public ConfigSyncSForwardSecretKey(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.SForward.SecretKey);
}
public void SetData(Memory<byte> data)
{
fileConfig.Data.Client.SForward.SecretKey = MemoryPackSerializer.Deserialize<string>(data.Span);
fileConfig.Data.Update();
}
}
}

View File

@@ -30,7 +30,9 @@ namespace linker.plugins.sforward
serviceCollection.AddSingleton<SForwardFlow>();
serviceCollection.AddSingleton<ConfigSyncSForwardSecretKey>();
}
public void AddServer(ServiceCollection serviceCollection, FileConfig config)

View File

@@ -405,8 +405,8 @@ namespace linker.plugins.sforward.messenger
if (sForwardInfo.RemotePortMin != 0 && sForwardInfo.RemotePortMax != 0)
{
uint plus = (uint)(sForwardProxyInfo.RemotePort - sForwardInfo.RemotePortMin);
uint newIP = BinaryPrimitives.ReadUInt32BigEndian(localEP.Address.GetAddressBytes()) + plus;
localEP.Address = new IPAddress(BitConverter.GetBytes(BinaryPrimitives.ReverseEndianness(newIP)));
uint newIP = NetworkHelper.IP2Value(localEP.Address) + plus;
localEP.Address = NetworkHelper.Value2IP(newIP);
}
return localEP;
}

View File

@@ -129,7 +129,7 @@ namespace linker.plugins.signin.messenger
DateTime now = DateTime.Now;
var groups = Clients.Values.GroupBy(c => c.GroupId)
.Where(group => group.All(info => info.Connected == false && (now - info.LastSignIn).TotalMilliseconds > 7 * 24 * 60 * 60 * 1000))
.Where(group => group.All(info => info.Connected == false && (now - info.LastSignIn).TotalDays > 7))
.Select(group => group.Key).ToList();
if (groups.Count > 0)

View File

@@ -0,0 +1,28 @@
using linker.config;
using linker.plugins.config;
using linker.tunnel.transport;
using MemoryPack;
namespace linker.plugins.tunnel
{
public sealed class ConfigSyncTunnelTransports : IConfigSync
{
public string Name => "TunnelTransports";
private readonly FileConfig fileConfig;
public ConfigSyncTunnelTransports(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.Tunnel.Transports);
}
public void SetData(Memory<byte> data)
{
fileConfig.Data.Client.Tunnel.Transports = MemoryPackSerializer.Deserialize<List<TunnelTransportItemInfo>>(data.Span);
fileConfig.Data.Update();
}
}
}

View File

@@ -79,11 +79,11 @@ namespace linker.plugins.tunnel
{
if (c.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
uint ip = BinaryPrimitives.ReadUInt32BigEndian(c.GetAddressBytes());
uint ip = NetworkHelper.IP2Value(c);
foreach (var item in excludeips)
{
uint maskValue = NetworkHelper.GetPrefixIP(item.Mask);
uint ip1 = BinaryPrimitives.ReadUInt32BigEndian(item.IPAddress.GetAddressBytes());
uint maskValue = NetworkHelper.PrefixLength2Value(item.Mask);
uint ip1 = NetworkHelper.IP2Value(item.IPAddress);
if ((ip & maskValue) == (ip1 & maskValue))
{
return false;

View File

@@ -65,6 +65,8 @@ namespace linker.plugins.tunnel
serviceCollection.AddSingleton<TunnelUpnpTransfer>();
serviceCollection.AddSingleton<ConfigSyncTunnelTransports>();
LoggerHelper.Instance.Info($"tunnel route level getting.");
config.Data.Client.Tunnel.RouteLevel = NetworkHelper.GetRouteLevel(config.Data.Client.ServerInfo.Host, out List<IPAddress> ips);
config.Data.Client.Tunnel.RouteIPs = ips.ToArray();

View File

@@ -1,22 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace linker.plugins.tuntap
{
public sealed class DHCPTreansfer
{
public void AddNetwork()
{
}
public IPAddress GetIP()
{
return IPAddress.Any;
}
}
}

View File

@@ -1,5 +1,6 @@
using linker.config;
using linker.plugins.tuntap.client;
using linker.plugins.tuntap.lease;
using linker.plugins.tuntap.messenger;
using linker.startup;
using linker.tun;
@@ -37,6 +38,8 @@ namespace linker.plugins.tuntap
{
serviceCollection.AddSingleton<TuntapServerMessenger>();
serviceCollection.AddSingleton<LeaseServerTreansfer>();
}
public void UseClient(ServiceProvider serviceProvider, FileConfig config)
@@ -47,6 +50,7 @@ namespace linker.plugins.tuntap
public void UseServer(ServiceProvider serviceProvider, FileConfig config)
{
LeaseServerTreansfer leaseTreansfer = serviceProvider.GetService<LeaseServerTreansfer>();
}
}
}

View File

@@ -50,9 +50,9 @@ namespace linker.plugins.tuntap.client
}
}).Select(c =>
{
uint maskValue = NetworkHelper.GetPrefixIP(c.PrefixLength);
IPAddress mask = NetworkHelper.GetPrefixIp(maskValue);
IPAddress _ip = NetworkHelper.ToNetworkIp(c.Address, maskValue);
uint maskValue = NetworkHelper.PrefixLength2Value(c.PrefixLength);
IPAddress mask = NetworkHelper.PrefixValue2IP(maskValue);
IPAddress _ip = NetworkHelper.NetworkIP2IP(c.Address, maskValue);
return new
{
IP = c.Address,

View File

@@ -91,6 +91,7 @@ namespace linker.plugins.tuntap.client
}
});
}
/// <summary>
/// 运行网卡
@@ -424,7 +425,7 @@ namespace linker.plugins.tuntap.client
tuntapProxy.SetIPs(ips);
foreach (var item in tuntapInfos.Values)
{
tuntapProxy.SetIP(item.MachineId, BinaryPrimitives.ReadUInt32BigEndian(item.IP.GetAddressBytes()));
tuntapProxy.SetIP(item.MachineId, NetworkHelper.IP2Value(item.IP));
}
CheckLanIPs();
}
@@ -464,7 +465,7 @@ namespace linker.plugins.tuntap.client
.Concat(runningConfig.Data.Tuntap.LanIPs.Where(c => c != null))
//路由上的IP
.Concat(config.Data.Client.Tunnel.RouteIPs)
.Select(c => BinaryPrimitives.ReadUInt32BigEndian(c.GetAddressBytes()))
.Select(c => NetworkHelper.IP2Value(c))
.ToArray();
}
private List<TuntapVeaLanIPAddressList> ParseIPs(List<TuntapInfo> infos)
@@ -496,9 +497,9 @@ namespace linker.plugins.tuntap.client
}
private TuntapVeaLanIPAddress ParseIPAddress(IPAddress ip, byte maskLength, string machineid)
{
uint ipInt = BinaryPrimitives.ReadUInt32BigEndian(ip.GetAddressBytes());
uint ipInt = NetworkHelper.IP2Value(ip);
//掩码十进制
uint maskValue = NetworkHelper.GetPrefixIP(maskLength);
uint maskValue = NetworkHelper.PrefixLength2Value(maskLength);
return new TuntapVeaLanIPAddress
{
IPAddress = ipInt,

View File

@@ -0,0 +1,46 @@
using linker.store;
using LiteDB;
using System.Net;
using linker.libs;
using MemoryPack;
using System.Collections.Concurrent;
using linker.plugins.client;
using linker.plugins.messenger;
using linker.plugins.tuntap.messenger;
using linker.client.config;
namespace linker.plugins.tuntap.lease
{
public sealed class LeaseClientTreansfer
{
private readonly MessengerSender messengerSender;
private readonly ClientSignInState clientSignInState;
private readonly RunningConfig runningConfig;
public LeaseClientTreansfer(MessengerSender messengerSender, ClientSignInState clientSignInState, RunningConfig runningConfig)
{
this.messengerSender = messengerSender;
this.clientSignInState = clientSignInState;
this.runningConfig = runningConfig;
}
public async Task<IPAddress> LeaseIp()
{
MessageResponeInfo resp = await messengerSender.SendReply(new MessageRequestWrap
{
Connection = clientSignInState.Connection,
MessengerId = (ushort)TuntapMessengerIds.Lease,
Payload = MemoryPackSerializer.Serialize(runningConfig.Data.Tuntap.IP)
});
if(resp.Code == MessageResponeCodes.OK)
{
IPAddress ip = MemoryPackSerializer.Deserialize<IPAddress>(resp.Data.Span);
if (ip.Equals(IPAddress.Any) == false)
{
return IPAddress.Any;
}
}
return runningConfig.Data.Tuntap.IP;
}
}
}

View File

@@ -0,0 +1,283 @@
using linker.store;
using LiteDB;
using System.Net;
using linker.libs;
using MemoryPack;
using System.Collections.Concurrent;
namespace linker.plugins.tuntap.lease
{
public sealed class LeaseServerTreansfer
{
private readonly Storefactory dBfactory;
private readonly ILiteCollection<LeaseCacheInfo> liteCollection;
private ConcurrentDictionary<string, LeaseCacheInfo> caches { get; set; } = new ConcurrentDictionary<string, LeaseCacheInfo>();
public LeaseServerTreansfer(Storefactory dBfactory)
{
this.dBfactory = dBfactory;
liteCollection = dBfactory.GetCollection<LeaseCacheInfo>("dhcp");
foreach (var item in liteCollection.FindAll())
{
caches.TryAdd(item.Key, item);
}
ClearTask();
}
/// <summary>
/// 添加网络配置
/// </summary>
/// <param name="key"></param>
/// <param name="info"></param>
public void Add(string key, LeaseInfo info)
{
if (info.PrefixLength < 16 || info.PrefixLength >= 32)
{
return;
}
if (caches.TryGetValue(key, out LeaseCacheInfo cache) == false)
{
cache = new LeaseCacheInfo { Key = key };
cache.Id = ObjectId.NewObjectId();
liteCollection.Insert(cache);
caches.TryAdd(key, cache);
}
uint oldIP = cache.IP;
uint oldPrefix = cache.PrefixValue;
cache.IP = NetworkHelper.IP2Value(info.IP);
cache.PrefixValue = NetworkHelper.PrefixLength2Value((byte)info.PrefixLength);
//网络配置有变化,清理分配,让他们重新申请
if (oldIP != cache.IP || oldPrefix != cache.PrefixValue)
{
cache.Users.Clear();
}
liteCollection.Update(cache);
dBfactory.Confirm();
}
/// <summary>
/// 获取配置
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public LeaseInfo Get(string key)
{
if (caches.TryGetValue(key, out LeaseCacheInfo cache) == false)
{
cache = new LeaseCacheInfo { Key = key };
}
return new LeaseInfo { IP = NetworkHelper.Value2IP(cache.IP), PrefixLength = NetworkHelper.PrefixValue2Length(cache.PrefixValue) };
}
/// <summary>
/// 租赁
/// </summary>
/// <param name="userId">用户id</param>
/// <param name="key">配置项</param>
/// <param name="ip">静态IP</param>
/// <returns>两种种结果0.0.0.0 和 ip</returns>
public IPAddress Lease(string userId, string key, IPAddress ip)
{
if (string.IsNullOrWhiteSpace(key) || caches.TryGetValue(key, out LeaseCacheInfo cache) == false)
{
return ip;
}
lock (cache)
{
IPAddress result = IPAddress.Any;
if (ip.Equals(IPAddress.Any) == false)
{
result = StaticIP(userId, ip, cache);
}
if (result.Equals(IPAddress.Any))
{
result = DynamicIP(userId, cache);
}
return result;
}
}
private IPAddress StaticIP(string userId, IPAddress ip, LeaseCacheInfo cache)
{
uint value = NetworkHelper.IP2Value(ip);
//不是同一个网络
if (NetworkHelper.NetworkValue2Value(value, cache.PrefixValue) != NetworkHelper.NetworkValue2Value(cache.IP, cache.PrefixValue))
{
return IPAddress.Any;
}
LeaseCacheUserInfo self = cache.Users.FirstOrDefault(c => c.Id == userId);
LeaseCacheUserInfo other = cache.Users.FirstOrDefault(c => c.IP == value && c.Id != userId);
//这个IP没人用
if (other == null)
{
//我自己有记录更新IP即可
if (self != null)
{
self.IP = value;
self.LastTime = DateTime.Now;
}
else
{
cache.Users.Add(new LeaseCacheUserInfo { Id = userId, IP = value, LastTime = DateTime.Now });
}
liteCollection.Update(cache);
dBfactory.Confirm();
}
//有人用
else
{
//用之前申请到的IP
if (self != null)
{
return NetworkHelper.Value2IP(self.IP);
}
//对方没过期,那就不能申占用了
if ((DateTime.Now - other.LastTime).Days <= 7)
{
return IPAddress.Any;
}
other.IP = value;
other.LastTime = DateTime.Now;
other.Id = userId;
liteCollection.Update(cache);
dBfactory.Confirm();
}
return ip;
}
private IPAddress DynamicIP(string userId, LeaseCacheInfo cache)
{
//网络号
uint network = NetworkHelper.NetworkValue2Value(cache.IP, cache.PrefixValue);
//广播
uint broadcast = NetworkHelper.BroadcastValue2Value(cache.IP, cache.PrefixValue);
//第一个可用IP
uint firstValue = network + 1;
//最后一个可用IP
uint lastValue = broadcast - 1;
//IP数
uint length = lastValue - network;
IEnumerable<int> ips = Enumerable.Range((int)firstValue, (int)length + 1).Except(cache.Users.Select(c => (int)c.IP));
if (ips.Any() == false)
{
return IPAddress.Any;
}
uint ipValue = (uint)ips.FirstOrDefault();
cache.Users.Add(new LeaseCacheUserInfo { Id = userId, IP = ipValue, LastTime = DateTime.Now });
liteCollection.Update(cache);
dBfactory.Confirm();
return NetworkHelper.Value2IP(ipValue);
}
/// <summary>
/// 租期
/// </summary>
/// <param name="userid"></param>
/// <param name="key"></param>
public void LeaseExp(string userid, string key)
{
if (caches.TryGetValue(key, out LeaseCacheInfo cache))
{
cache.LastTime = DateTime.Now;
LeaseCacheUserInfo user = cache.Users.FirstOrDefault(c => c.Id == userid);
if (user != null)
{
user.LastTime = DateTime.Now;
}
liteCollection.Update(cache);
dBfactory.Confirm();
}
}
private void ClearTask()
{
TimerHelper.SetInterval(() =>
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Debug($"start cleaning up dhcp that have exceeded the 30-day timeout period");
}
try
{
DateTime now = DateTime.Now;
var items = caches.Values.Where(c => (now - c.LastTime).TotalDays > 30).ToList();
if (items.Count > 0)
{
foreach (var item in items)
{
caches.TryRemove(item.Key, out _);
liteCollection.Delete(item.Id);
}
dBfactory.Confirm();
}
}
catch (Exception ex)
{
LoggerHelper.Instance.Debug($"cleaning up dhcp error {ex}");
}
return true;
}, 5 * 60 * 1000);
}
}
[MemoryPackable]
public sealed partial class LeaseInfo
{
/// <summary>
/// 网络号
/// </summary>
[MemoryPackAllowSerialize]
public IPAddress IP { get; set; } = IPAddress.Any;
/// <summary>
/// 前缀,掩码长度
/// </summary>
public int PrefixLength { get; set; } = 32;
}
public sealed class LeaseCacheInfo
{
public ObjectId Id { get; set; }
public string Key { get; set; }
/// <summary>
/// 网络号
/// </summary>
public uint IP { get; set; }
/// <summary>
/// 前缀,掩码
/// </summary>
public uint PrefixValue { get; set; }
/// <summary>
/// 最后活动时间
/// </summary>
public DateTime LastTime { get; set; } = DateTime.Now;
public List<LeaseCacheUserInfo> Users { get; set; } = new List<LeaseCacheUserInfo>();
}
public sealed class LeaseCacheUserInfo
{
public string Id { get; set; }
public uint IP { get; set; }
public DateTime LastTime { get; set; } = DateTime.Now;
}
}

View File

@@ -3,7 +3,9 @@ using linker.plugins.messenger;
using linker.plugins.signin.messenger;
using linker.plugins.tuntap.client;
using linker.plugins.tuntap.config;
using linker.plugins.tuntap.lease;
using MemoryPack;
using System.Net;
namespace linker.plugins.tuntap.messenger
{
@@ -61,6 +63,14 @@ namespace linker.plugins.tuntap.messenger
connection.Write(MemoryPackSerializer.Serialize(_info));
}
/// <summary>
/// 重新租赁
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)TuntapMessengerIds.LeaseChange)]
public void LeaseChange(IConnection connection)
{
}
}
@@ -69,12 +79,14 @@ namespace linker.plugins.tuntap.messenger
private readonly MessengerSender messengerSender;
private readonly SignCaching signCaching;
private readonly FileConfig config;
private readonly LeaseServerTreansfer leaseTreansfer;
public TuntapServerMessenger(MessengerSender messengerSender, SignCaching signCaching, FileConfig config)
public TuntapServerMessenger(MessengerSender messengerSender, SignCaching signCaching, FileConfig config, LeaseServerTreansfer leaseTreansfer)
{
this.messengerSender = messengerSender;
this.signCaching = signCaching;
this.config = config;
this.leaseTreansfer = leaseTreansfer;
}
/// <summary>
@@ -179,5 +191,71 @@ namespace linker.plugins.tuntap.messenger
}
}
/// <summary>
/// 添加网络配置
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)TuntapMessengerIds.LeaseAdd)]
public void LeaseAdd(IConnection connection)
{
LeaseInfo info = MemoryPackSerializer.Deserialize<LeaseInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{
leaseTreansfer.Add(cache.GroupId, info);
}
}
/// <summary>
/// 添加网络配置
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)TuntapMessengerIds.LeaseGet)]
public void LeaseGet(IConnection connection)
{
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{
connection.Write(MemoryPackSerializer.Serialize(leaseTreansfer.Get(cache.GroupId)));
}
}
/// <summary>
/// 租赁IP
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
[MessengerId((ushort)TuntapMessengerIds.Lease)]
public void Lease(IConnection connection)
{
IPAddress info = MemoryPackSerializer.Deserialize<IPAddress>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{
IPAddress result = leaseTreansfer.Lease(cache.MachineId, cache.GroupId, info);
connection.Write(MemoryPackSerializer.Serialize(result));
}
}
/// <summary>
/// 网络配置发生变化,需要重新租赁
/// </summary>
/// <param name="connection"></param>
[MessengerId((ushort)TuntapMessengerIds.LeaseChangeForward)]
public void LeaseChangeForward(IConnection connection)
{
TuntapInfo tuntapInfo = MemoryPackSerializer.Deserialize<TuntapInfo>(connection.ReceiveRequestWrap.Payload.Span);
if (signCaching.TryGet(connection.Id, out SignCacheInfo cache))
{
uint requiestid = connection.ReceiveRequestWrap.RequestId;
List<SignCacheInfo> caches = signCaching.Get(cache.GroupId);
IEnumerable<Task<bool>> tasks = caches.Where(c => c.MachineId != connection.Id && c.Connected).Select(c => messengerSender.SendOnly(new MessageRequestWrap
{
Connection = c.Connection,
MessengerId = (ushort)TuntapMessengerIds.Config,
Payload = connection.ReceiveRequestWrap.Payload,
Timeout = 1000,
}));
Task.WhenAll(tasks);
}
}
}
}

View File

@@ -14,6 +14,13 @@
Config = 2206,
ConfigForward = 2207,
LeaseAdd = 2208,
LeaseGet = 2209,
Lease = 2210,
LeaseChange = 2211,
LeaseChangeForward = 2212,
None = 2299
}
}

View File

@@ -0,0 +1,27 @@
using linker.config;
using linker.plugins.config;
using MemoryPack;
namespace linker.plugins.updater
{
public sealed class ConfigSyncUpdaterSecretKey : IConfigSync
{
public string Name => "UpdaterSecretKey";
private readonly FileConfig fileConfig;
public ConfigSyncUpdaterSecretKey(FileConfig fileConfig)
{
this.fileConfig = fileConfig;
}
public Memory<byte> GetData()
{
return MemoryPackSerializer.Serialize(fileConfig.Data.Client.Updater.SecretKey);
}
public void SetData(Memory<byte> data)
{
fileConfig.Data.Client.Updater.SecretKey = MemoryPackSerializer.Deserialize<string>(data.Span);
fileConfig.Data.Update();
}
}
}

View File

@@ -27,6 +27,8 @@ namespace linker.plugins.updater
serviceCollection.AddSingleton<UpdaterClientMessenger>();
serviceCollection.AddSingleton<UpdaterClientApiController>();
serviceCollection.AddSingleton<ConfigSyncUpdaterSecretKey>();
}
public void AddServer(ServiceCollection serviceCollection, FileConfig config)

View File

@@ -1,9 +1,8 @@
v1.4.9
2024-10-16 17:33:40
1. 使用原生成器替代反射
2. 统计所有服务端,客户端数量
3. 创建docker镜像清单自动识别平台
4. docker镜像增加arm
5. 优化了arm平台下获取设备ID的方法
6. 增加了查看流量信息的权限
7. 有服务器更新密钥的客户端,可以更新本服务器的所有客户端
2024-10-17 17:30:30
1. 新增多分组管理,方便分组切换
2. 增加分组密码,提高分组隔离安全性
3. 优化配置同步,可自由选择同步哪些信息
4. 自动分配虚拟网卡IP
5. 线程池优化
6. 一些UI优化