Skip to content

Commit 4a69cd3

Browse files
committed
Merge upstream/main and resolve conflicts
2 parents 9439a3f + a19606c commit 4a69cd3

File tree

18 files changed

+318
-74
lines changed

18 files changed

+318
-74
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Publish ConnectX.Client NuGet Package
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/setup-dotnet@v4
12+
with:
13+
dotnet-version: 9.0
14+
15+
- uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
submodules: recursive
19+
20+
- name: Get current datetime
21+
id: currentdatetime
22+
uses: Kaven-Universe/github-action-current-date-time@v1
23+
with:
24+
format: "YYYY.MMDD.HHmmssSSS"
25+
26+
- name: Restore packages
27+
run: dotnet restore ./ConnectX.Client/ConnectX.Client.csproj
28+
29+
- name: Build solution
30+
run: dotnet build ./ConnectX.Client/ConnectX.Client.csproj -c Release --no-restore
31+
32+
- name: Pack solution
33+
run: dotnet pack ./ConnectX.Client/ConnectX.Client.csproj -c Release -p:PackageVersion="${{ steps.currentdatetime.outputs.time }}" --no-build --no-restore -o .
34+
35+
- name: Publish to NuGet
36+
run: dotnet nuget push *.nupkg -k ${{secrets.NUGET_KEY}} -s https://api.nuget.org/v3/index.json

ConnectX.Client/ConnectX.Client.csproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<PackageReference Include="CommunityToolkit.HighPerformance" Version="8.4.0" />
1111
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="ConsoleTables" Version="2.6.2" />
1212
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.4" />
13+
<PackageReference Include="Snappier" Version="1.2.0" />
1314
<PackageReference Include="ZeroTier.Sockets" Version="1.8.4" />
1415
</ItemGroup>
1516

@@ -25,4 +26,13 @@
2526
<ProjectReference Include="..\Hive.Framework\Hive.Network.Udp\Hive.Network.Udp.csproj" />
2627
</ItemGroup>
2728

29+
<PropertyGroup>
30+
<IncludeSymbols>true</IncludeSymbols>
31+
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
32+
<PackageLicenseExpression>MIT</PackageLicenseExpression>
33+
<PackageProjectUrl>https://github.com/Corona-Studio/ConnectX</PackageProjectUrl>
34+
<Title>$(AssemblyName)</Title>
35+
<Nullable>enable</Nullable>
36+
</PropertyGroup>
37+
2838
</Project>

ConnectX.Client/Interfaces/IZeroTierNodeLinkHolder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ public interface IZeroTierNodeLinkHolder : IHostedService
99
public const ushort RandomPortLower = 30000;
1010
public const ushort RandomPortUpper = 40000;
1111

12+
bool IsZeroTierInitialized { get; }
1213
Node? Node { get; }
14+
1315
Task<bool> JoinNetworkAsync(ulong networkId, CancellationToken cancellationToken);
1416
Task LeaveNetworkAsync(CancellationToken cancellationToken);
1517

ConnectX.Client/Managers/GenericProxyManager.cs

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
using System.Net;
1+
using System.Collections.Concurrent;
2+
using System.Net;
23
using System.Net.Sockets;
34
using ConnectX.Client.Messages.Proxy;
5+
using ConnectX.Client.Models;
46
using ConnectX.Client.Proxy;
7+
using ConnectX.Client.Route;
8+
using ConnectX.Client.Transmission;
59
using ConnectX.Shared.Helpers;
610
using ConnectX.Shared.Interfaces;
711
using Hive.Both.General.Dispatchers;
@@ -14,15 +18,17 @@ namespace ConnectX.Client.Managers;
1418
public abstract class GenericProxyManager : BackgroundService
1519
{
1620
//使用一个二元组确定一个连接
17-
private readonly Dictionary<TunnelIdentifier, GenericProxyPair> _proxies = [];
18-
private readonly Dictionary<TunnelIdentifier, Socket> _acceptedSockets = [];
19-
private readonly Dictionary<ValueTuple<Guid, ushort>, GenericProxyAcceptor> _acceptors = [];
21+
private readonly ConcurrentDictionary<TunnelIdentifier, GenericProxyPair> _proxies = [];
22+
private readonly ConcurrentDictionary<TunnelIdentifier, Socket> _acceptedSockets = [];
23+
private readonly ConcurrentDictionary<ValueTuple<Guid, ushort>, GenericProxyAcceptor> _acceptors = [];
2024

2125
private readonly IHostApplicationLifetime _lifetime;
2226
private readonly IServiceProvider _serviceProvider;
2327
protected readonly ILogger Logger;
2428

2529
protected GenericProxyManager(
30+
RouterPacketDispatcher packetDispatcher,
31+
RelayPacketDispatcher relayPacketDispatcher,
2632
IHostApplicationLifetime lifetime,
2733
IServiceProvider serviceProvider,
2834
ILogger logger)
@@ -31,6 +37,9 @@ protected GenericProxyManager(
3137
_serviceProvider = serviceProvider;
3238

3339
Logger = logger;
40+
41+
packetDispatcher.OnReceive<ProxyDisconnectReq>(RecycleClientProxy);
42+
relayPacketDispatcher.OnReceive<ProxyDisconnectReq>(RecycleClientProxy);
3443
}
3544

3645
public override void Dispose()
@@ -45,6 +54,20 @@ public override void Dispose()
4554
GC.SuppressFinalize(this);
4655
}
4756

57+
private void RecycleClientProxy(ProxyDisconnectReq req, PacketContext ctx)
58+
{
59+
var key = new TunnelIdentifier(req.ClientId, req.ServerRealPort, req.ClientRealPort);
60+
61+
if (_proxies.TryRemove(key, out var pair))
62+
{
63+
Logger.LogClientProxyDisconnectedBecauseRequested(key);
64+
pair.Dispose();
65+
return;
66+
}
67+
68+
Logger.LogFailedToShutdownClientProxyBecauseNotFound(key);
69+
}
70+
4871
public void ReceivedProxyConnectReq(
4972
MessageContext<ProxyConnectReq> ctx,
5073
ISender sender)
@@ -133,7 +156,11 @@ public virtual GenericProxyPair CreateClientProxy(
133156
dispatcher,
134157
sender);
135158

136-
_proxies.Add(key, pair);
159+
_proxies.AddOrUpdate(key, _ => pair, (_, old) =>
160+
{
161+
old.Dispose();
162+
return pair;
163+
});
137164

138165
Logger.LogCreateProxy(key);
139166

@@ -164,7 +191,9 @@ public virtual GenericProxyPair CreateClientProxy(
164191
socket,
165192
key,
166193
_lifetime.ApplicationStopping);
194+
167195
proxy.OnRealServerDisconnected += OnProxyDisconnected;
196+
proxy.OnRealServerDisconnected += NotifyServerProxyDisconnect;
168197

169198
var pair = new GenericProxyPair(
170199
partnerId,
@@ -174,10 +203,29 @@ public virtual GenericProxyPair CreateClientProxy(
174203
dispatcher,
175204
sender);
176205

177-
_proxies.Add(key, pair);
206+
_proxies.AddOrUpdate(key, _ => pair, (_, old) =>
207+
{
208+
old.Dispose();
209+
return pair;
210+
});
178211
proxy.Start();
179212

180213
return proxy;
214+
215+
void NotifyServerProxyDisconnect(TunnelIdentifier id, GenericProxyBase proxy)
216+
{
217+
proxy.OnRealServerDisconnected -= NotifyServerProxyDisconnect;
218+
219+
// 真客户端离开了房间或掉线,通知房主回收对应的 proxy
220+
sender.SendData(new ProxyDisconnectReq
221+
{
222+
ClientId = id.PartnerId,
223+
ClientRealPort = id.LocalRealPort,
224+
ServerRealPort = id.RemoteRealPort
225+
});
226+
227+
Logger.LogNotifyServerProxyNeedToDisconnect(id);
228+
}
181229
}
182230

183231
private void OnProxyDisconnected(TunnelIdentifier id, GenericProxyBase proxy)
@@ -206,11 +254,11 @@ public virtual GenericProxyAcceptor CreateAcceptor(
206254

207255
var key = (partnerId, remoteRealServerPort);
208256

209-
if (_acceptors.Remove(key, out var oldAcceptor))
257+
if (_acceptors.TryRemove(key, out var oldAcceptor))
210258
{
211259
if (oldAcceptor.IsRunning)
212260
{
213-
_acceptors.Add(key, oldAcceptor);
261+
_acceptors.AddOrUpdate((partnerId, remoteRealServerPort), _ => oldAcceptor, (_, _) => oldAcceptor);
214262
throw new InvalidOperationException($"There has been a acceptor with same key: {partnerId}-{remoteRealServerPort}");
215263
}
216264

@@ -224,7 +272,7 @@ public virtual GenericProxyAcceptor CreateAcceptor(
224272
localMapPort,
225273
_lifetime.ApplicationStopping);
226274

227-
_acceptors.Add((partnerId, remoteRealServerPort), acceptor);
275+
_acceptors.AddOrUpdate((partnerId, remoteRealServerPort), _ => acceptor, (_, _) => acceptor);
228276

229277
// 当有真客户端连接的时候,发送一个McConnectReq,当收到回复后启动Proxy(启动的逻辑在OnReceive里)
230278
acceptor.OnRealClientConnected += (_, socket) =>
@@ -240,7 +288,7 @@ public virtual GenericProxyAcceptor CreateAcceptor(
240288
(ushort)remoteEndPoint.Port,
241289
remoteRealServerPort);
242290

243-
_acceptedSockets.Add(id, socket);
291+
_acceptedSockets.AddOrUpdate(id, _ => socket, (_, _) => socket);
244292

245293
sender.SendData(new ProxyConnectReq
246294
{
@@ -351,4 +399,13 @@ public static partial void LogErrorProxyPairWithSameKey(this ILogger logger, Gui
351399

352400
[LoggerMessage(LogLevel.Information, "[GEN_PROXY_MANAGER] Create acceptor {Key}")]
353401
public static partial void LogCreateAcceptor(this ILogger logger, (Guid, ushort) key);
402+
403+
[LoggerMessage(LogLevel.Information, "[GEN_PROXY_MANAGER] Notify server proxy need to disconnect {Id}")]
404+
public static partial void LogNotifyServerProxyNeedToDisconnect(this ILogger logger, TunnelIdentifier id);
405+
406+
[LoggerMessage(LogLevel.Error, "[GEN_PROXY_MANAGER] Failed to shutdown client proxy because not found {key}")]
407+
public static partial void LogFailedToShutdownClientProxyBecauseNotFound(this ILogger logger, TunnelIdentifier key);
408+
409+
[LoggerMessage(LogLevel.Information, "[GEN_PROXY_MANAGER] Client proxy disconnected because requested {key}")]
410+
public static partial void LogClientProxyDisconnectedBecauseRequested(this ILogger logger, TunnelIdentifier key);
354411
}

ConnectX.Client/Managers/ProxyManager.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Collections.Concurrent;
2-
using ConnectX.Client.Interfaces;
32
using ConnectX.Client.Messages.Proxy;
43
using ConnectX.Client.Proxy;
4+
using ConnectX.Client.Route;
55
using ConnectX.Client.Transmission;
66
using ConnectX.Shared.Helpers;
77
using Hive.Both.General.Dispatchers;
@@ -16,11 +16,13 @@ public sealed class ProxyManager : GenericProxyManager
1616
private readonly ConcurrentBag<(IDispatcher, HandlerId)> _registeredHandlers = [];
1717

1818
public ProxyManager(
19+
RouterPacketDispatcher packetDispatcher,
20+
RelayPacketDispatcher relayPacketDispatcher,
1921
PartnerManager partnerManager,
2022
IHostApplicationLifetime lifetime,
2123
IServiceProvider serviceProvider,
2224
ILogger<ProxyManager> logger)
23-
: base(lifetime, serviceProvider, logger)
25+
: base(packetDispatcher, relayPacketDispatcher, lifetime, serviceProvider, logger)
2426
{
2527
_partnerManager = partnerManager;
2628
}

ConnectX.Client/Managers/RoomInfoManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
4646
{
4747
if (CurrentGroupInfo == null)
4848
{
49-
// Reset flags to original state
49+
// Reset flags to the original state
5050
needToRefreshRoomInfo = false;
5151
lastRefreshTime = DateTime.MinValue;
5252

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Hive.Codec.Shared;
2+
using MemoryPack;
3+
4+
namespace ConnectX.Client.Messages.Proxy;
5+
6+
[MessageDefine]
7+
[MemoryPackable]
8+
public partial class ProxyDisconnectReq
9+
{
10+
public required Guid ClientId { get; init; }
11+
public required ushort ClientRealPort { get; init; }
12+
public required ushort ServerRealPort { get; init; }
13+
}

ConnectX.Client/Proxy/FakeServerMultiCaster.cs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Net;
1+
using System.Collections.Frozen;
2+
using System.Net;
23
using System.Net.Sockets;
34
using System.Text;
45
using ConnectX.Client.Interfaces;
@@ -47,12 +48,24 @@ public FakeServerMultiCaster(
4748
}
4849

4950
public event Action<string, int>? OnListenedLanServer;
51+
52+
private static readonly FrozenSet<string> VirtualKeywords = FrozenSet.Create(
53+
"virtual", "vmware", "loopback",
54+
"pseudo", "tunneling", "tap",
55+
"container", "hyper-v", "bluetooth",
56+
"docker");
5057

5158
private static IPAddress GetLocalIpAddress()
5259
{
60+
var candidates = new List<IPAddress>();
61+
5362
var networkInterfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces()
54-
.Where(ni => ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up
55-
&& ni.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback);
63+
.Where(ni =>
64+
ni.OperationalStatus == System.Net.NetworkInformation.OperationalStatus.Up &&
65+
ni.NetworkInterfaceType != System.Net.NetworkInformation.NetworkInterfaceType.Loopback &&
66+
!VirtualKeywords.Contains(ni.Description.ToLowerInvariant()) &&
67+
!VirtualKeywords.Contains(ni.Name.ToLowerInvariant())
68+
);
5669

5770
foreach (var ni in networkInterfaces)
5871
{
@@ -61,12 +74,19 @@ private static IPAddress GetLocalIpAddress()
6174
{
6275
if (addr.Address.AddressFamily == AddressFamily.InterNetwork)
6376
{
64-
return addr.Address;
77+
var ip = addr.Address;
78+
if (ip.ToString().StartsWith("192.168."))
79+
return ip;
80+
81+
candidates.Add(ip);
6582
}
6683
}
6784
}
6885

69-
throw new Exception("No network adapters with an IPv4 address in the system!");
86+
if (candidates.Count > 0)
87+
return candidates[0];
88+
89+
throw new Exception("No suitable IPv4 address found.");
7090
}
7191

7292
private void OnReceiveMcMulticastMessage(McMulticastMessage message, PacketContext context)
@@ -88,6 +108,8 @@ private void OnReceiveMcMulticastMessage(McMulticastMessage message, PacketConte
88108

89109
var multicastOption = new MulticastOption(MulticastAddress, IPAddress.Any);
90110
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, multicastOption);
111+
112+
_logger.LogSocketSetupForWindows(MulticastAddress.ToString());
91113
}
92114

93115
if (OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())
@@ -99,16 +121,21 @@ private void OnReceiveMcMulticastMessage(McMulticastMessage message, PacketConte
99121
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, localIp.GetAddressBytes());
100122
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 255);
101123
socket.Bind(new IPEndPoint(localIp, 0));
124+
125+
_logger.LogSocketSetupForLinuxMacOS(localIp.ToString());
102126
}
103127

104128
ArgumentNullException.ThrowIfNull(socket);
105129

106130
using var multicastSocket = socket;
107131

108132
var mess = $"[MOTD]{message.Name}[/MOTD][AD]{proxy.LocalMappingPort}[/AD]";
109-
var buf = Encoding.Default.GetBytes(mess);
110-
111-
multicastSocket.SendTo(buf, MulticastIpe);
133+
var buf = Encoding.Default.GetBytes(mess).AsSpan();
134+
var sentLen = 0;
135+
136+
while (sentLen < buf.Length)
137+
sentLen += multicastSocket.SendTo(buf[sentLen..], MulticastIpe);
138+
112139
_logger.LogMulticastMessageToClient(proxy.LocalMappingPort, message.Name);
113140
}
114141

@@ -235,4 +262,10 @@ public static partial void LogSendLanServerToPartner(this ILogger logger, string
235262

236263
[LoggerMessage(LogLevel.Error, "Failed to receive multicast message.")]
237264
public static partial void LogFailedToReceiveMulticastMessage(this ILogger logger, Exception exception);
265+
266+
[LoggerMessage(LogLevel.Debug, "Socket setup for Windows, multicast address is {MulticastAddress}")]
267+
public static partial void LogSocketSetupForWindows(this ILogger logger, string multicastAddress);
268+
269+
[LoggerMessage(LogLevel.Debug, "Socket setup for Linux/MacOS, local IP is {LocalIp}")]
270+
public static partial void LogSocketSetupForLinuxMacOS(this ILogger logger, string localIp);
238271
}

ConnectX.Client/Proxy/GenericProxyAcceptor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public Task StartAcceptAsync()
6262
var tmp = await _acceptSocket.AcceptAsync(_cancellationToken);
6363

6464
tmp.NoDelay = true;
65-
tmp.LingerState = new LingerOption(true, 3);
65+
//tmp.LingerState = new LingerOption(true, 3);
6666

6767
if (tmp.RemoteEndPoint is not IPEndPoint remoteEndPoint) continue;
6868

0 commit comments

Comments
 (0)