SimpleWebTransport.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. using System;
  2. using System.Net;
  3. using System.Security.Authentication;
  4. using UnityEngine;
  5. using UnityEngine.Serialization;
  6. namespace Mirror.SimpleWeb
  7. {
  8. [DisallowMultipleComponent]
  9. [HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/websockets-transport")]
  10. public class SimpleWebTransport : Transport, PortTransport
  11. {
  12. public const string NormalScheme = "ws";
  13. public const string SecureScheme = "wss";
  14. [Tooltip("Protect against allocation attacks by keeping the max message size small. Otherwise an attacker might send multiple fake packets with 2GB headers, causing the server to run out of memory after allocating multiple large packets.")]
  15. public int maxMessageSize = 16 * 1024;
  16. [FormerlySerializedAs("handshakeMaxSize")]
  17. [Tooltip("Max size for http header send as handshake for websockets")]
  18. public int maxHandshakeSize = 3000;
  19. [FormerlySerializedAs("serverMaxMessagesPerTick")]
  20. [Tooltip("Caps the number of messages the server will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")]
  21. public int serverMaxMsgsPerTick = 10000;
  22. [FormerlySerializedAs("clientMaxMessagesPerTick")]
  23. [Tooltip("Caps the number of messages the client will process per tick. Allows LateUpdate to finish to let the reset of unity continue in case more messages arrive before they are processed")]
  24. public int clientMaxMsgsPerTick = 1000;
  25. [Tooltip("Send would stall forever if the network is cut off during a send, so we need a timeout (in milliseconds)")]
  26. public int sendTimeout = 5000;
  27. [Tooltip("How long without a message before disconnecting (in milliseconds)")]
  28. public int receiveTimeout = 20000;
  29. [Tooltip("disables nagle algorithm. lowers CPU% and latency but increases bandwidth")]
  30. public bool noDelay = true;
  31. [Header("Obsolete SSL settings")]
  32. [Tooltip("Requires wss connections on server, only to be used with SSL cert.json, never with reverse proxy.\nNOTE: if sslEnabled is true clientUseWss is forced true, even if not checked.")]
  33. public bool sslEnabled;
  34. [Tooltip("Protocols that SSL certificate is created to support.")]
  35. public SslProtocols sslProtocols = SslProtocols.Tls12;
  36. [Tooltip("Path to json file that contains path to cert and its password\nUse Json file so that cert password is not included in client builds\nSee Assets/Mirror/Transports/.cert.example.Json")]
  37. public string sslCertJson = "./cert.json";
  38. [Header("Server settings")]
  39. [Tooltip("Port to use for server")]
  40. public ushort port = 7778;
  41. public ushort Port
  42. {
  43. get
  44. {
  45. #if UNITY_WEBGL
  46. if (clientWebsocketSettings.ClientPortOption == WebsocketPortOption.SpecifyPort)
  47. return clientWebsocketSettings.CustomClientPort;
  48. else
  49. return port;
  50. #else
  51. return port;
  52. #endif
  53. }
  54. set
  55. {
  56. #if UNITY_WEBGL
  57. if (clientWebsocketSettings.ClientPortOption == WebsocketPortOption.SpecifyPort)
  58. clientWebsocketSettings.CustomClientPort = value;
  59. else
  60. port = value;
  61. #else
  62. port = value;
  63. #endif
  64. }
  65. }
  66. [Tooltip("Groups messages in queue before calling Stream.Send")]
  67. public bool batchSend = true;
  68. [Tooltip("Waits for 1ms before grouping and sending messages.\n" +
  69. "This gives time for mirror to finish adding message to queue so that less groups need to be made.\n" +
  70. "If WaitBeforeSend is true then BatchSend Will also be set to true")]
  71. public bool waitBeforeSend = true;
  72. [Header("Client settings")]
  73. [Tooltip("Sets connect scheme to wss. Useful when client needs to connect using wss when TLS is outside of transport.\nNOTE: if sslEnabled is true clientUseWss is also true")]
  74. public bool clientUseWss;
  75. public ClientWebsocketSettings clientWebsocketSettings;
  76. [Header("Logging")]
  77. [Tooltip("Choose minimum severity level for logging\nFlood level requires Debug build")]
  78. [SerializeField] Log.Levels minimumLogLevel = Log.Levels.Warn;
  79. /// <summary>
  80. /// <para>Gets _logLevels field</para>
  81. /// <para>Sets _logLevels and Log.level fields</para>
  82. /// </summary>
  83. public Log.Levels LogLevels
  84. {
  85. get => minimumLogLevel;
  86. set
  87. {
  88. minimumLogLevel = value;
  89. Log.minLogLevel = minimumLogLevel;
  90. }
  91. }
  92. SimpleWebClient client;
  93. SimpleWebServer server;
  94. TcpConfig TcpConfig => new TcpConfig(noDelay, sendTimeout, receiveTimeout);
  95. void Awake()
  96. {
  97. Log.minLogLevel = minimumLogLevel;
  98. }
  99. public override string ToString() => $"SWT [{port}]";
  100. void OnValidate()
  101. {
  102. Log.minLogLevel = minimumLogLevel;
  103. }
  104. public override bool Available() => true;
  105. public override int GetMaxPacketSize(int channelId = 0) => maxMessageSize;
  106. public override void Shutdown()
  107. {
  108. client?.Disconnect();
  109. client = null;
  110. server?.Stop();
  111. server = null;
  112. }
  113. #region Client
  114. string GetClientScheme() => (sslEnabled || clientUseWss) ? SecureScheme : NormalScheme;
  115. public override bool ClientConnected()
  116. {
  117. // not null and not NotConnected (we want to return true if connecting or disconnecting)
  118. return client != null && client.ConnectionState != ClientState.NotConnected;
  119. }
  120. public override void ClientConnect(string hostname)
  121. {
  122. UriBuilder builder = new UriBuilder
  123. {
  124. Scheme = GetClientScheme(),
  125. Host = hostname,
  126. };
  127. switch (clientWebsocketSettings.ClientPortOption)
  128. {
  129. case WebsocketPortOption.SpecifyPort:
  130. builder.Port = clientWebsocketSettings.CustomClientPort;
  131. break;
  132. case WebsocketPortOption.MatchWebpageProtocol:
  133. // not including a port in the builder allows the webpage to drive the port
  134. // https://github.com/MirrorNetworking/Mirror/pull/3477
  135. break;
  136. default: // default case handles ClientWebsocketPortOption.DefaultSameAsServerPort
  137. builder.Port = port;
  138. break;
  139. }
  140. ClientConnect(builder.Uri);
  141. }
  142. public override void ClientConnect(Uri uri)
  143. {
  144. // connecting or connected
  145. if (ClientConnected())
  146. {
  147. Log.Warn("[SWT-ClientConnect]: Already Connected");
  148. return;
  149. }
  150. client = SimpleWebClient.Create(maxMessageSize, clientMaxMsgsPerTick, TcpConfig);
  151. if (client == null)
  152. return;
  153. client.onConnect += OnClientConnected.Invoke;
  154. client.onDisconnect += () =>
  155. {
  156. OnClientDisconnected.Invoke();
  157. // clear client here after disconnect event has been sent
  158. // there should be no more messages after disconnect
  159. client = null;
  160. };
  161. client.onData += (ArraySegment<byte> data) => OnClientDataReceived.Invoke(data, Channels.Reliable);
  162. // We will not invoke OnClientError if minLogLevel is set to None
  163. // We only send the full exception if minLogLevel is set to Verbose
  164. switch (Log.minLogLevel)
  165. {
  166. case Log.Levels.Flood:
  167. case Log.Levels.Verbose:
  168. client.onError += (Exception e) =>
  169. {
  170. OnClientError.Invoke(TransportError.Unexpected, e.ToString());
  171. ClientDisconnect();
  172. };
  173. break;
  174. case Log.Levels.Info:
  175. case Log.Levels.Warn:
  176. case Log.Levels.Error:
  177. client.onError += (Exception e) =>
  178. {
  179. OnClientError.Invoke(TransportError.Unexpected, e.Message);
  180. ClientDisconnect();
  181. };
  182. break;
  183. }
  184. client.Connect(uri);
  185. }
  186. public override void ClientDisconnect()
  187. {
  188. // don't set client null here of messages wont be processed
  189. client?.Disconnect();
  190. }
  191. public override void ClientSend(ArraySegment<byte> segment, int channelId)
  192. {
  193. if (!ClientConnected())
  194. {
  195. Log.Error("[SWT-ClientSend]: Not Connected");
  196. return;
  197. }
  198. if (segment.Count > maxMessageSize)
  199. {
  200. Log.Error("[SWT-ClientSend]: Message greater than max size");
  201. return;
  202. }
  203. if (segment.Count == 0)
  204. {
  205. Log.Error("[SWT-ClientSend]: Message count was zero");
  206. return;
  207. }
  208. client.Send(segment);
  209. // call event. might be null if no statistics are listening etc.
  210. OnClientDataSent?.Invoke(segment, Channels.Reliable);
  211. }
  212. // messages should always be processed in early update
  213. public override void ClientEarlyUpdate()
  214. {
  215. client?.ProcessMessageQueue(this);
  216. }
  217. #endregion
  218. #region Server
  219. string GetServerScheme() => sslEnabled ? SecureScheme : NormalScheme;
  220. public override Uri ServerUri()
  221. {
  222. UriBuilder builder = new UriBuilder
  223. {
  224. Scheme = GetServerScheme(),
  225. Host = Dns.GetHostName(),
  226. Port = port
  227. };
  228. return builder.Uri;
  229. }
  230. public override bool ServerActive()
  231. {
  232. return server != null && server.Active;
  233. }
  234. public override void ServerStart()
  235. {
  236. if (ServerActive())
  237. Log.Warn("[SWT-ServerStart]: Server Already Started");
  238. SslConfig config = SslConfigLoader.Load(sslEnabled, sslCertJson, sslProtocols);
  239. server = new SimpleWebServer(serverMaxMsgsPerTick, TcpConfig, maxMessageSize, maxHandshakeSize, config);
  240. server.onConnect += OnServerConnected.Invoke;
  241. server.onDisconnect += OnServerDisconnected.Invoke;
  242. server.onData += (int connId, ArraySegment<byte> data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable);
  243. // We will not invoke OnServerError if minLogLevel is set to None
  244. // We only send the full exception if minLogLevel is set to Verbose
  245. switch (Log.minLogLevel)
  246. {
  247. case Log.Levels.Flood:
  248. case Log.Levels.Verbose:
  249. server.onError += (connId, exception) =>
  250. {
  251. OnServerError(connId, TransportError.Unexpected, exception.ToString());
  252. ServerDisconnect(connId);
  253. };
  254. break;
  255. case Log.Levels.Info:
  256. case Log.Levels.Warn:
  257. case Log.Levels.Error:
  258. server.onError += (connId, exception) =>
  259. {
  260. OnServerError(connId, TransportError.Unexpected, exception.Message);
  261. ServerDisconnect(connId);
  262. };
  263. break;
  264. }
  265. SendLoopConfig.batchSend = batchSend || waitBeforeSend;
  266. SendLoopConfig.sleepBeforeSend = waitBeforeSend;
  267. server.Start(port);
  268. }
  269. public override void ServerStop()
  270. {
  271. if (ServerActive())
  272. {
  273. server.Stop();
  274. server = null;
  275. }
  276. }
  277. public override void ServerDisconnect(int connectionId)
  278. {
  279. if (ServerActive())
  280. server.KickClient(connectionId);
  281. }
  282. public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
  283. {
  284. if (!ServerActive())
  285. {
  286. Log.Error("[SWT-ServerSend]: Server Not Active");
  287. return;
  288. }
  289. if (segment.Count > maxMessageSize)
  290. {
  291. Log.Error("[SWT-ServerSend]: Message greater than max size");
  292. return;
  293. }
  294. if (segment.Count == 0)
  295. {
  296. Log.Error("[SWT-ServerSend]: Message count was zero");
  297. return;
  298. }
  299. server.SendOne(connectionId, segment);
  300. // call event. might be null if no statistics are listening etc.
  301. OnServerDataSent?.Invoke(connectionId, segment, Channels.Reliable);
  302. }
  303. public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId);
  304. public Request ServerGetClientRequest(int connectionId) => server.GetClientRequest(connectionId);
  305. // messages should always be processed in early update
  306. public override void ServerEarlyUpdate()
  307. {
  308. server?.ProcessMessageQueue(this);
  309. }
  310. #endregion
  311. }
  312. }