3 using System.Collections.Generic;
4 using System.Collections.Immutable;
13 internal sealed
class LidgrenServerPeer : ServerPeer<LidgrenConnection>
15 private readonly NetPeerConfiguration netPeerConfiguration;
16 private ImmutableDictionary<AuthenticationTicketKind, Authenticator>? authenticators;
17 private NetServer? netServer;
19 private readonly List<NetIncomingMessage> incomingLidgrenMessages;
21 public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings, Callbacks callbacks) : base(callbacks, settings)
23 authenticators =
null;
26 netPeerConfiguration =
new NetPeerConfiguration(
"barotrauma")
28 AcceptIncomingConnections =
true,
29 AutoExpandMTU =
false,
30 MaximumConnections = NetConfig.MaxPlayers * 2,
31 EnableUPnP = serverSettings.EnableUPnP,
32 Port = serverSettings.Port,
33 DualStack = GameSettings.CurrentConfig.UseDualModeSockets,
34 LocalAddress = serverSettings.ListenIPAddress,
37 netPeerConfiguration.DisableMessageType(
38 NetIncomingMessageType.DebugMessage
39 | NetIncomingMessageType.WarningMessage
40 | NetIncomingMessageType.Receipt
41 | NetIncomingMessageType.ErrorMessage
42 | NetIncomingMessageType.Error
43 | NetIncomingMessageType.UnconnectedData);
45 netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
47 incomingLidgrenMessages =
new List<NetIncomingMessage>();
52 public override void Start()
54 if (netServer !=
null) {
return; }
58 incomingLidgrenMessages.Clear();
60 netServer =
new NetServer(netPeerConfiguration);
64 if (serverSettings.EnableUPnP)
68 while (DiscoveringUPnP()) { }
74 public override void Close()
76 if (netServer ==
null) {
return; }
78 for (
int i = pendingClients.Count - 1; i >= 0; i--)
80 RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
83 for (
int i = connectedClients.Count - 1; i >= 0; i--)
85 Disconnect(connectedClients[i].Connection, PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
88 netServer.Shutdown(PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown).ToLidgrenStringRepresentation());
90 pendingClients.Clear();
91 connectedClients.Clear();
95 callbacks.OnShutdown.Invoke();
98 public override void Update(
float deltaTime)
100 if (netServer is
null) {
return; }
102 ToolBox.ThrowIfNull(incomingLidgrenMessages);
104 netServer.ReadMessages(incomingLidgrenMessages);
107 foreach (NetIncomingMessage inc
in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
109 HandleConnection(inc);
115 foreach (NetIncomingMessage inc
in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
117 switch (inc.MessageType)
119 case NetIncomingMessageType.Data:
120 HandleDataMessage(inc);
122 case NetIncomingMessageType.StatusChanged:
123 HandleStatusChanged(inc);
131 string errorMsg =
"Server failed to read an incoming message. {" + e +
"}\n" + e.StackTrace.CleanupStackTrace();
132 GameAnalyticsManager.AddErrorEventOnce($
"LidgrenServerPeer.Update:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
134 DebugConsole.ThrowError(errorMsg);
136 if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
140 for (
int i = 0; i < pendingClients.Count; i++)
142 PendingClient pendingClient = pendingClients[i];
144 LidgrenConnection connection = (LidgrenConnection)pendingClient.Connection;
146 if (connection.NetConnection.Status == NetConnectionStatus.InitiatedConnect ||
147 connection.NetConnection.Status == NetConnectionStatus.ReceivedInitiation ||
148 connection.NetConnection.Status == NetConnectionStatus.RespondedAwaitingApproval ||
149 connection.NetConnection.Status == NetConnectionStatus.RespondedConnect)
154 UpdatePendingClient(pendingClient);
155 if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
158 incomingLidgrenMessages.Clear();
161 private void InitUPnP()
163 if (netServer is
null) {
return; }
165 ToolBox.ThrowIfNull(netPeerConfiguration);
167 netServer.UPnP.ForwardPort(netPeerConfiguration.Port,
"barotrauma");
168 if (SteamManager.IsInitialized)
170 netServer.UPnP.ForwardPort(serverSettings.QueryPort,
"barotrauma");
174 private bool DiscoveringUPnP()
176 if (netServer ==
null) {
return false; }
178 return netServer.UPnP.Status == UPnPStatus.Discovering;
181 private void FinishUPnP()
186 private void HandleConnection(NetIncomingMessage inc)
188 if (netServer ==
null) {
return; }
190 var skipDeny =
false;
192 var result = GameMain.LuaCs.Hook.Call<
bool?>(
"lidgren.handleConnection", inc);
193 if (result !=
null) {
194 if (result.Value) skipDeny =
true;
199 if (!skipDeny && connectedClients.Count >= serverSettings.MaxPlayers)
201 inc.SenderConnection.Deny(PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull).ToLidgrenStringRepresentation());
205 if (serverSettings.BanList.IsBanned(
new LidgrenEndpoint(inc.SenderConnection.RemoteEndPoint), out
string banReason))
208 inc.SenderConnection.Deny(PeerDisconnectPacket.Banned(banReason).ToLidgrenStringRepresentation());
212 PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == inc.SenderConnection);
214 if (pendingClient is
null)
216 pendingClient =
new PendingClient(
new LidgrenConnection(inc.SenderConnection));
217 pendingClients.Add(pendingClient);
220 inc.SenderConnection.Approve();
223 private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
225 if (netServer ==
null) {
return; }
227 PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == lidgrenMsg.SenderConnection);
229 IReadMessage inc = lidgrenMsg.ToReadMessage();
231 var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
233 if (packetHeader.IsConnectionInitializationStep() && pendingClient !=
null && initialization.HasValue)
235 ReadConnectionInitializationStep(pendingClient, inc, initialization.Value);
237 else if (!packetHeader.IsConnectionInitializationStep())
239 if (FindConnection(lidgrenMsg.SenderConnection) is not { } conn)
241 if (pendingClient !=
null)
243 RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired));
245 else if (lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnected &&
246 lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnecting)
248 lidgrenMsg.SenderConnection.Disconnect(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired).ToLidgrenStringRepresentation());
254 if (pendingClient !=
null) { pendingClients.Remove(pendingClient); }
256 if (serverSettings.BanList.IsBanned(conn.Endpoint, out
string banReason)
257 || (conn.AccountInfo.AccountId.TryUnwrap(out var accountId) && serverSettings.BanList.IsBanned(accountId, out banReason))
258 || conn.AccountInfo.OtherMatchingIds.Any(
id => serverSettings.BanList.IsBanned(
id, out banReason)))
260 Disconnect(conn, PeerDisconnectPacket.Banned(banReason));
264 var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
265 callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
268 LidgrenConnection? FindConnection(NetConnection ligdrenConn)
270 if (connectedClients.Find(c => c.Connection.NetConnection == ligdrenConn) is { Connection: LidgrenConnection conn })
278 private void HandleStatusChanged(NetIncomingMessage inc)
280 if (netServer ==
null) {
return; }
282 NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
285 case NetConnectionStatus.Disconnected:
286 LidgrenConnection? conn = connectedClients.Select(c => c.Connection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
288 string disconnectMsg = inc.ReadString();
289 var peerDisconnectPacket =
290 PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg).Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown));
293 if (conn == OwnerConnection)
295 DebugConsole.NewMessage(
"Owner disconnected: closing the server...");
296 GameServer.Log(
"Owner disconnected: closing the server...",
ServerLog.MessageType.ServerMessage);
301 Disconnect(conn, peerDisconnectPacket);
306 PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
307 if (pendingClient !=
null)
309 RemovePendingClient(pendingClient, peerDisconnectPacket);
317 private void OnSteamAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
319 if (netServer ==
null) {
return; }
321 PendingClient? pendingClient = pendingClients.Find(c => c.AccountInfo.AccountId.TryUnwrap<SteamId>(out var
id) &&
id.Value == steamId);
322 DebugConsole.Log($
"{steamId} validation: {status}, {(pendingClient != null)}");
324 if (pendingClient is
null)
326 if (status == Steamworks.AuthResponse.OK) {
return; }
328 if (connectedClients.Find(c
329 => c.Connection.AccountInfo.AccountId.TryUnwrap<SteamId>(out var
id) &&
id.Value == steamId)
330 is { Connection: LidgrenConnection connection })
332 Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status));
338 LidgrenConnection pendingConnection = (LidgrenConnection)pendingClient.Connection;
339 if (serverSettings.BanList.IsBanned(pendingConnection.Endpoint, out
string banReason)
340 || serverSettings.BanList.IsBanned(
new SteamId(steamId), out banReason)
341 || serverSettings.BanList.IsBanned(
new SteamId(ownerId), out banReason))
343 RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
347 if (status == Steamworks.AuthResponse.OK)
349 pendingClient.Connection.SetAccountInfo(
new AccountInfo(
new SteamId(steamId),
new SteamId(ownerId)));
350 pendingClient.InitializationStep = ShouldAskForPassword(serverSettings, pendingClient.Connection)
351 ? ConnectionInitialization.Password
352 : ConnectionInitialization.ContentPackageOrder;
353 pendingClient.UpdateTime = Timing.TotalTime;
357 RemovePendingClient(pendingClient, PeerDisconnectPacket.SteamAuthError(status));
361 public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod,
bool compressPastThreshold =
true)
363 if (netServer ==
null) {
return; }
365 if (conn is not LidgrenConnection lidgrenConnection)
367 DebugConsole.ThrowError($
"Tried to send message to connection of incorrect type: expected {nameof(LidgrenConnection)}, got {conn.GetType().Name}");
371 if (!connectedClients.Any(cc => cc.Connection == lidgrenConnection))
373 DebugConsole.ThrowError($
"Tried to send message to unauthenticated connection: {lidgrenConnection.Endpoint.StringRepresentation}");
377 byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out
bool isCompressed, out _);
380 ToolBox.ThrowIfNull(netPeerConfiguration);
381 netPeerConfiguration.SimulatedDuplicatesChance = GameMain.Server.SimulatedDuplicatesChance;
382 netPeerConfiguration.SimulatedMinimumLatency = GameMain.Server.SimulatedMinimumLatency;
383 netPeerConfiguration.SimulatedRandomLatency = GameMain.Server.SimulatedRandomLatency;
384 netPeerConfiguration.SimulatedLoss = GameMain.Server.SimulatedLoss;
387 var headers =
new PeerPacketHeaders
389 DeliveryMethod = deliveryMethod,
390 PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
391 Initialization =
null
393 var body =
new PeerPacketMessage
397 SendMsgInternal(lidgrenConnection, headers, body);
400 public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
402 if (netServer ==
null) {
return; }
404 if (conn is not LidgrenConnection lidgrenConn) {
return; }
406 if (connectedClients.FindIndex(cc => cc.Connection == lidgrenConn) is >= 0 and var ccIndex)
409 connectedClients.RemoveAt(ccIndex);
410 callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
411 if (conn.AccountInfo.AccountId.TryUnwrap(out var accountId))
413 authenticators?.Values.ForEach(authenticator => authenticator.EndAuthSession(accountId));
417 lidgrenConn.NetConnection.Disconnect(peerDisconnectPacket.ToLidgrenStringRepresentation());
420 protected override void SendMsgInternal(LidgrenConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
422 IWriteMessage msgToSend =
new WriteOnlyMessage();
423 msgToSend.WriteNetSerializableStruct(headers);
424 body?.Write(msgToSend);
426 NetSendResult result = ForwardToLidgren(msgToSend, conn, headers.DeliveryMethod);
427 if (result != NetSendResult.Sent && result != NetSendResult.Queued)
429 DebugConsole.NewMessage($
"Failed to send message to {conn.Endpoint}: {result}", Microsoft.Xna.Framework.Color.Yellow);
433 protected override void CheckOwnership(PendingClient pendingClient)
435 if (OwnerConnection !=
null
436 || pendingClient.Connection is not LidgrenConnection l
437 || !IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address)
438 || !ownerKey.IsSome() || pendingClient.OwnerKey != ownerKey)
443 ownerKey = Option.None;
444 OwnerConnection = pendingClient.Connection;
445 callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
448 private enum AuthResult
454 protected override void ProcessAuthTicket(ClientAuthTicketAndVersionPacket packet, PendingClient pendingClient)
456 if (pendingClient.AccountInfo.AccountId.IsSome())
458 if (pendingClient.AccountInfo.AccountId != packet.AccountId)
460 RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
465 void acceptClient(AccountInfo accountInfo)
467 pendingClient.Connection.SetAccountInfo(accountInfo);
468 pendingClient.Name = packet.Name;
469 pendingClient.OwnerKey = packet.OwnerKey;
470 pendingClient.InitializationStep = ShouldAskForPassword(serverSettings, pendingClient.Connection)
471 ? ConnectionInitialization.Password
472 : ConnectionInitialization.ContentPackageOrder;
477 RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
480 if (authenticators is
null &&
481 GameMain.Server.ServerSettings.RequireAuthentication)
483 DebugConsole.NewMessage(
484 "The server is configured to require authentication from clients, but there are no authenticators available. " +
485 $
"If you're for example trying to host a server in a local network without being connected to Steam or Epic Online Services, please set {nameof(GameMain.Server.ServerSettings.RequireAuthentication)} to false in the server settings.",
486 Microsoft.Xna.Framework.Color.Yellow);
489 if (authenticators is
null
490 || !packet.AuthTicket.TryUnwrap(out var authTicket)
491 || !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
494 DebugConsole.NewMessage(
"Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
495 acceptClient(
new AccountInfo(
new UnauthenticatedAccountId(packet.Name)));
497 if (GameMain.Server.ServerSettings.RequireAuthentication)
499 DebugConsole.NewMessage(
500 "A client attempted to join without an authentication ticket, but the server is configured to require authentication. " +
501 $
"If you're for example trying to host a server in a local network without being connected to Steam or Epic Online Services, please set {nameof(GameMain.Server.ServerSettings.RequireAuthentication)} to false in the server settings.",
502 Microsoft.Xna.Framework.Color.Yellow);
507 acceptClient(
new AccountInfo(
new UnauthenticatedAccountId(packet.Name)));
513 pendingClient.AuthSessionStarted =
true;
514 TaskPool.Add($
"{nameof(LidgrenServerPeer)}.ProcessAuth", authenticator.VerifyTicket(authTicket), t =>
516 if (!t.TryGetResult(out AccountInfo accountInfo)
517 || accountInfo.IsNone)
523 acceptClient(accountInfo);
527 private NetSendResult ForwardToLidgren(IWriteMessage msg, NetworkConnection connection, DeliveryMethod deliveryMethod)
529 ToolBox.ThrowIfNull(netServer);
531 LidgrenConnection conn = (LidgrenConnection)connection;
532 return netServer.SendMessage(msg.ToLidgren(netServer), conn.NetConnection, deliveryMethod.ToLidgren());
static ImmutableDictionary< AuthenticationTicketKind, Authenticator > GetAuthenticatorsForHost(Option< Endpoint > ownerEndpointOption)