Server LuaCsForBarotrauma
LidgrenServerPeer.cs
1 #nullable enable
2 using System;
3 using System.Collections.Generic;
4 using System.Collections.Immutable;
5 using System.Net;
6 using System.Linq;
8 using Barotrauma.Steam;
9 using Lidgren.Network;
10 
11 namespace Barotrauma.Networking
12 {
13  internal sealed class LidgrenServerPeer : ServerPeer<LidgrenConnection>
14  {
15  private readonly NetPeerConfiguration netPeerConfiguration;
16  private ImmutableDictionary<AuthenticationTicketKind, Authenticator>? authenticators;
17  private NetServer? netServer;
18 
19  private readonly List<NetIncomingMessage> incomingLidgrenMessages;
20 
21  public LidgrenServerPeer(Option<int> ownKey, ServerSettings settings, Callbacks callbacks) : base(callbacks, settings)
22  {
23  authenticators = null;
24  netServer = null;
25 
26  netPeerConfiguration = new NetPeerConfiguration("barotrauma")
27  {
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,
35  };
36 
37  netPeerConfiguration.DisableMessageType(
38  NetIncomingMessageType.DebugMessage
39  | NetIncomingMessageType.WarningMessage
40  | NetIncomingMessageType.Receipt
41  | NetIncomingMessageType.ErrorMessage
42  | NetIncomingMessageType.Error
43  | NetIncomingMessageType.UnconnectedData);
44 
45  netPeerConfiguration.EnableMessageType(NetIncomingMessageType.ConnectionApproval);
46 
47  incomingLidgrenMessages = new List<NetIncomingMessage>();
48 
49  ownerKey = ownKey;
50  }
51 
52  public override void Start()
53  {
54  if (netServer != null) { return; }
55 
56  authenticators = Authenticator.GetAuthenticatorsForHost(Option.None);
57 
58  incomingLidgrenMessages.Clear();
59 
60  netServer = new NetServer(netPeerConfiguration);
61 
62  netServer.Start();
63 
64  if (serverSettings.EnableUPnP)
65  {
66  InitUPnP();
67 
68  while (DiscoveringUPnP()) { }
69 
70  FinishUPnP();
71  }
72  }
73 
74  public override void Close()
75  {
76  if (netServer == null) { return; }
77 
78  for (int i = pendingClients.Count - 1; i >= 0; i--)
79  {
80  RemovePendingClient(pendingClients[i], PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
81  }
82 
83  for (int i = connectedClients.Count - 1; i >= 0; i--)
84  {
85  Disconnect(connectedClients[i].Connection, PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown));
86  }
87 
88  netServer.Shutdown(PeerDisconnectPacket.WithReason(DisconnectReason.ServerShutdown).ToLidgrenStringRepresentation());
89 
90  pendingClients.Clear();
91  connectedClients.Clear();
92 
93  netServer = null;
94 
95  callbacks.OnShutdown.Invoke();
96  }
97 
98  public override void Update(float deltaTime)
99  {
100  if (netServer is null) { return; }
101 
102  ToolBox.ThrowIfNull(incomingLidgrenMessages);
103 
104  netServer.ReadMessages(incomingLidgrenMessages);
105 
106  //process incoming connections first
107  foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType == NetIncomingMessageType.ConnectionApproval))
108  {
109  HandleConnection(inc);
110  }
111 
112  try
113  {
114  //after processing connections, go ahead with the rest of the messages
115  foreach (NetIncomingMessage inc in incomingLidgrenMessages.Where(m => m.MessageType != NetIncomingMessageType.ConnectionApproval))
116  {
117  switch (inc.MessageType)
118  {
119  case NetIncomingMessageType.Data:
120  HandleDataMessage(inc);
121  break;
122  case NetIncomingMessageType.StatusChanged:
123  HandleStatusChanged(inc);
124  break;
125  }
126  }
127  }
128 
129  catch (Exception e)
130  {
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);
133 #if DEBUG
134  DebugConsole.ThrowError(errorMsg);
135 #else
136  if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
137 #endif
138  }
139 
140  for (int i = 0; i < pendingClients.Count; i++)
141  {
142  PendingClient pendingClient = pendingClients[i];
143 
144  LidgrenConnection connection = (LidgrenConnection)pendingClient.Connection;
145 
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)
150  {
151  continue;
152  }
153 
154  UpdatePendingClient(pendingClient);
155  if (i >= pendingClients.Count || pendingClients[i] != pendingClient) { i--; }
156  }
157 
158  incomingLidgrenMessages.Clear();
159  }
160 
161  private void InitUPnP()
162  {
163  if (netServer is null) { return; }
164 
165  ToolBox.ThrowIfNull(netPeerConfiguration);
166 
167  netServer.UPnP.ForwardPort(netPeerConfiguration.Port, "barotrauma");
168  if (SteamManager.IsInitialized)
169  {
170  netServer.UPnP.ForwardPort(serverSettings.QueryPort, "barotrauma");
171  }
172  }
173 
174  private bool DiscoveringUPnP()
175  {
176  if (netServer == null) { return false; }
177 
178  return netServer.UPnP.Status == UPnPStatus.Discovering;
179  }
180 
181  private void FinishUPnP()
182  {
183  //do nothing
184  }
185 
186  private void HandleConnection(NetIncomingMessage inc)
187  {
188  if (netServer == null) { return; }
189 
190  var skipDeny = false;
191  {
192  var result = GameMain.LuaCs.Hook.Call<bool?>("lidgren.handleConnection", inc);
193  if (result != null) {
194  if (result.Value) skipDeny = true;
195  else return;
196  }
197  }
198 
199  if (!skipDeny && connectedClients.Count >= serverSettings.MaxPlayers)
200  {
201  inc.SenderConnection.Deny(PeerDisconnectPacket.WithReason(DisconnectReason.ServerFull).ToLidgrenStringRepresentation());
202  return;
203  }
204 
205  if (serverSettings.BanList.IsBanned(new LidgrenEndpoint(inc.SenderConnection.RemoteEndPoint), out string banReason))
206  {
207  //IP banned: deny immediately
208  inc.SenderConnection.Deny(PeerDisconnectPacket.Banned(banReason).ToLidgrenStringRepresentation());
209  return;
210  }
211 
212  PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == inc.SenderConnection);
213 
214  if (pendingClient is null)
215  {
216  pendingClient = new PendingClient(new LidgrenConnection(inc.SenderConnection));
217  pendingClients.Add(pendingClient);
218  }
219 
220  inc.SenderConnection.Approve();
221  }
222 
223  private void HandleDataMessage(NetIncomingMessage lidgrenMsg)
224  {
225  if (netServer == null) { return; }
226 
227  PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == lidgrenMsg.SenderConnection);
228 
229  IReadMessage inc = lidgrenMsg.ToReadMessage();
230 
231  var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
232 
233  if (packetHeader.IsConnectionInitializationStep() && pendingClient != null && initialization.HasValue)
234  {
235  ReadConnectionInitializationStep(pendingClient, inc, initialization.Value);
236  }
237  else if (!packetHeader.IsConnectionInitializationStep())
238  {
239  if (FindConnection(lidgrenMsg.SenderConnection) is not { } conn)
240  {
241  if (pendingClient != null)
242  {
243  RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired));
244  }
245  else if (lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnected &&
246  lidgrenMsg.SenderConnection.Status != NetConnectionStatus.Disconnecting)
247  {
248  lidgrenMsg.SenderConnection.Disconnect(PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationRequired).ToLidgrenStringRepresentation());
249  }
250 
251  return;
252  }
253 
254  if (pendingClient != null) { pendingClients.Remove(pendingClient); }
255 
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)))
259  {
260  Disconnect(conn, PeerDisconnectPacket.Banned(banReason));
261  return;
262  }
263 
264  var packet = INetSerializableStruct.Read<PeerPacketMessage>(inc);
265  callbacks.OnMessageReceived.Invoke(conn, packet.GetReadMessage(packetHeader.IsCompressed(), conn));
266  }
267 
268  LidgrenConnection? FindConnection(NetConnection ligdrenConn)
269  {
270  if (connectedClients.Find(c => c.Connection.NetConnection == ligdrenConn) is { Connection: LidgrenConnection conn })
271  {
272  return conn;
273  }
274  return null;
275  }
276  }
277 
278  private void HandleStatusChanged(NetIncomingMessage inc)
279  {
280  if (netServer == null) { return; }
281 
282  NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
283  switch (status)
284  {
285  case NetConnectionStatus.Disconnected:
286  LidgrenConnection? conn = connectedClients.Select(c => c.Connection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
287 
288  string disconnectMsg = inc.ReadString();
289  var peerDisconnectPacket =
290  PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg).Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown));
291  if (conn != null)
292  {
293  if (conn == OwnerConnection)
294  {
295  DebugConsole.NewMessage("Owner disconnected: closing the server...");
296  GameServer.Log("Owner disconnected: closing the server...", ServerLog.MessageType.ServerMessage);
297  Close();
298  }
299  else
300  {
301  Disconnect(conn, peerDisconnectPacket);
302  }
303  }
304  else
305  {
306  PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
307  if (pendingClient != null)
308  {
309  RemovePendingClient(pendingClient, peerDisconnectPacket);
310  }
311  }
312 
313  break;
314  }
315  }
316 
317  private void OnSteamAuthChange(Steamworks.SteamId steamId, Steamworks.SteamId ownerId, Steamworks.AuthResponse status)
318  {
319  if (netServer == null) { return; }
320 
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)}");
323 
324  if (pendingClient is null)
325  {
326  if (status == Steamworks.AuthResponse.OK) { return; }
327 
328  if (connectedClients.Find(c
329  => c.Connection.AccountInfo.AccountId.TryUnwrap<SteamId>(out var id) && id.Value == steamId)
330  is { Connection: LidgrenConnection connection })
331  {
332  Disconnect(connection, PeerDisconnectPacket.SteamAuthError(status));
333  }
334 
335  return;
336  }
337 
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))
342  {
343  RemovePendingClient(pendingClient, PeerDisconnectPacket.Banned(banReason));
344  return;
345  }
346 
347  if (status == Steamworks.AuthResponse.OK)
348  {
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;
354  }
355  else
356  {
357  RemovePendingClient(pendingClient, PeerDisconnectPacket.SteamAuthError(status));
358  }
359  }
360 
361  public override void Send(IWriteMessage msg, NetworkConnection conn, DeliveryMethod deliveryMethod, bool compressPastThreshold = true)
362  {
363  if (netServer == null) { return; }
364 
365  if (conn is not LidgrenConnection lidgrenConnection)
366  {
367  DebugConsole.ThrowError($"Tried to send message to connection of incorrect type: expected {nameof(LidgrenConnection)}, got {conn.GetType().Name}");
368  return;
369  }
370 
371  if (!connectedClients.Any(cc => cc.Connection == lidgrenConnection))
372  {
373  DebugConsole.ThrowError($"Tried to send message to unauthenticated connection: {lidgrenConnection.Endpoint.StringRepresentation}");
374  return;
375  }
376 
377  byte[] bufAux = msg.PrepareForSending(compressPastThreshold, out bool isCompressed, out _);
378 
379 #if DEBUG
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;
385 #endif
386 
387  var headers = new PeerPacketHeaders
388  {
389  DeliveryMethod = deliveryMethod,
390  PacketHeader = isCompressed ? PacketHeader.IsCompressed : PacketHeader.None,
391  Initialization = null
392  };
393  var body = new PeerPacketMessage
394  {
395  Buffer = bufAux
396  };
397  SendMsgInternal(lidgrenConnection, headers, body);
398  }
399 
400  public override void Disconnect(NetworkConnection conn, PeerDisconnectPacket peerDisconnectPacket)
401  {
402  if (netServer == null) { return; }
403 
404  if (conn is not LidgrenConnection lidgrenConn) { return; }
405 
406  if (connectedClients.FindIndex(cc => cc.Connection == lidgrenConn) is >= 0 and var ccIndex)
407  {
408  lidgrenConn.Status = NetworkConnectionStatus.Disconnected;
409  connectedClients.RemoveAt(ccIndex);
410  callbacks.OnDisconnect.Invoke(conn, peerDisconnectPacket);
411  if (conn.AccountInfo.AccountId.TryUnwrap(out var accountId))
412  {
413  authenticators?.Values.ForEach(authenticator => authenticator.EndAuthSession(accountId));
414  }
415  }
416 
417  lidgrenConn.NetConnection.Disconnect(peerDisconnectPacket.ToLidgrenStringRepresentation());
418  }
419 
420  protected override void SendMsgInternal(LidgrenConnection conn, PeerPacketHeaders headers, INetSerializableStruct? body)
421  {
422  IWriteMessage msgToSend = new WriteOnlyMessage();
423  msgToSend.WriteNetSerializableStruct(headers);
424  body?.Write(msgToSend);
425 
426  NetSendResult result = ForwardToLidgren(msgToSend, conn, headers.DeliveryMethod);
427  if (result != NetSendResult.Sent && result != NetSendResult.Queued)
428  {
429  DebugConsole.NewMessage($"Failed to send message to {conn.Endpoint}: {result}", Microsoft.Xna.Framework.Color.Yellow);
430  }
431  }
432 
433  protected override void CheckOwnership(PendingClient pendingClient)
434  {
435  if (OwnerConnection != null
436  || pendingClient.Connection is not LidgrenConnection l
437  || !IPAddress.IsLoopback(l.NetConnection.RemoteEndPoint.Address)
438  || !ownerKey.IsSome() || pendingClient.OwnerKey != ownerKey)
439  {
440  return;
441  }
442 
443  ownerKey = Option.None;
444  OwnerConnection = pendingClient.Connection;
445  callbacks.OnOwnerDetermined.Invoke(OwnerConnection);
446  }
447 
448  private enum AuthResult
449  {
450  Success,
451  Failure
452  }
453 
454  protected override void ProcessAuthTicket(ClientAuthTicketAndVersionPacket packet, PendingClient pendingClient)
455  {
456  if (pendingClient.AccountInfo.AccountId.IsSome())
457  {
458  if (pendingClient.AccountInfo.AccountId != packet.AccountId)
459  {
460  RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
461  }
462  return;
463  }
464 
465  void acceptClient(AccountInfo accountInfo)
466  {
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;
473  }
474 
475  void rejectClient()
476  {
477  RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
478  }
479 
480  if (authenticators is null &&
481  GameMain.Server.ServerSettings.RequireAuthentication)
482  {
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);
487  }
488 
489  if (authenticators is null
490  || !packet.AuthTicket.TryUnwrap(out var authTicket)
491  || !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
492  {
493 #if DEBUG
494  DebugConsole.NewMessage("Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
495  acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
496 #else
497  if (GameMain.Server.ServerSettings.RequireAuthentication)
498  {
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);
503  rejectClient();
504  }
505  else
506  {
507  acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
508  }
509 #endif
510  return;
511  }
512 
513  pendingClient.AuthSessionStarted = true;
514  TaskPool.Add($"{nameof(LidgrenServerPeer)}.ProcessAuth", authenticator.VerifyTicket(authTicket), t =>
515  {
516  if (!t.TryGetResult(out AccountInfo accountInfo)
517  || accountInfo.IsNone)
518  {
519  rejectClient();
520  return;
521  }
522 
523  acceptClient(accountInfo);
524  });
525  }
526 
527  private NetSendResult ForwardToLidgren(IWriteMessage msg, NetworkConnection connection, DeliveryMethod deliveryMethod)
528  {
529  ToolBox.ThrowIfNull(netServer);
530 
531  LidgrenConnection conn = (LidgrenConnection)connection;
532  return netServer.SendMessage(msg.ToLidgren(netServer), conn.NetConnection, deliveryMethod.ToLidgren());
533  }
534  }
535 }
static ImmutableDictionary< AuthenticationTicketKind, Authenticator > GetAuthenticatorsForHost(Option< Endpoint > ownerEndpointOption)