public override NetSendResult Enqueue(NetOutgoingMessage message) { int queueLen = QueuedSends.Count + 1; int left = GetAllowedSends(); if (queueLen > left || (message.ByteLength > _connection.CurrentMTU && _connection._peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.DropAboveMTU)) { _connection.Peer.Recycle(message); return(NetSendResult.Dropped); } if (message.BitLength >= ushort.MaxValue && _connection._peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.IgnoreMTU) { _connection.Peer.LogError(NetLogMessage.FromValues(NetLogCode.MessageSizeExceeded, endPoint: _connection, value: message.BitLength, maxValue: ushort.MaxValue)); return(NetSendResult.Dropped); } QueuedSends.Enqueue(message); return(NetSendResult.Sent); }
/// <summary> /// Create a connection to a remote endpoint. /// </summary> public virtual NetConnection Connect(IPEndPoint remoteEndPoint, NetOutgoingMessage?hailMessage) { if (remoteEndPoint == null) { throw new ArgumentNullException(nameof(remoteEndPoint)); } if (Configuration.DualStack) { remoteEndPoint = NetUtility.MapToIPv6(remoteEndPoint); } if (Status == NetPeerStatus.NotRunning) { throw new LidgrenException("Must call Start() first."); } if (ConnectionLookup.ContainsKey(remoteEndPoint)) { throw new LidgrenException("Already connected to that endpoint!"); } if (Handshakes.TryGetValue(remoteEndPoint, out NetConnection? hs)) { // already trying to connect to that endpoint; make another try switch (hs.Status) { case NetConnectionStatus.InitiatedConnect: // send another connect hs._connectRequested = true; break; case NetConnectionStatus.RespondedConnect: // send another response hs.SendConnectResponse(NetTime.Now, false); break; default: // weird LogWarning(NetLogMessage.FromValues(NetLogCode.UnexpectedHandshakeStatus, value: (int)hs.Status)); break; } return(hs); } var conn = new NetConnection(this, remoteEndPoint); conn.Status = NetConnectionStatus.InitiatedConnect; conn.LocalHailMessage = hailMessage; // handle on network thread conn._connectRequested = true; conn._connectionInitiator = true; Handshakes.TryAdd(remoteEndPoint, conn); return(conn); }
/// <summary> /// Approves this connection; sending a connection response to the remote host. /// </summary> /// <param name="localHail">The local hail message that will be set as RemoteHailMessage on the remote host</param> public void Approve(NetOutgoingMessage?localHail) { if (Status != NetConnectionStatus.RespondedAwaitingApproval) { Peer.LogWarning(NetLogMessage.FromValues(NetLogCode.UnexpectedApprove, endPoint: this, value: (int)Status)); return; } LocalHailMessage = localHail; _handshakeAttempts = 0; SendConnectResponse(NetTime.Now, false); }
internal bool SendMTUPacket(int byteCount, IPEndPoint target) { if (Socket == null) { throw new InvalidOperationException("No socket bound."); } try { Socket.DontFragment = true; int bytesSent = Socket.SendTo(_sendBuffer, 0, byteCount, SocketFlags.None, target); if (byteCount != bytesSent) { LogWarning(NetLogMessage.FromValues(NetLogCode.FullSendFailure, endPoint: target, value: bytesSent, maxValue: byteCount)); } Statistics.PacketSent(byteCount, 1); } catch (SocketException sx) { switch (sx.SocketErrorCode) { case SocketError.MessageSize: return(false); case SocketError.WouldBlock: // send buffer full? LogError(new NetLogMessage(NetLogCode.SocketWouldBlock, sx, target)); return(true); case SocketError.ConnectionReset: return(true); default: LogError(new NetLogMessage(NetLogCode.SendFailure, sx, target)); break; } } catch (Exception ex) { LogError(new NetLogMessage(NetLogCode.SendFailure, ex, target)); } finally { Socket.DontFragment = false; } return(true); }
private void FinalizeMTU(int size) { if (_expandMTUStatus == ExpandMTUStatus.Finished) { return; } _expandMTUStatus = ExpandMTUStatus.Finished; int previousMTU = CurrentMTU; CurrentMTU = size; if (CurrentMTU != _peerConfiguration._maximumTransmissionUnit) { Peer.LogDebug(NetLogMessage.FromValues(NetLogCode.ExpandedMTU, endPoint: this, value: previousMTU, maxValue: CurrentMTU)); } }
private void WriteLocalHail(NetOutgoingMessage om) { if (LocalHailMessage == null) { return; } int bitsToAppend = LocalHailMessage.BitLength - LocalHailMessage.BitPosition; if (bitsToAppend > 0) { int resultSize = om.ByteLength + NetBitWriter.BytesForBits(bitsToAppend); int maxMtuSize = _peerConfiguration._maximumTransmissionUnit - 10; if (resultSize > maxMtuSize) { int maxHailSize = maxMtuSize - om.ByteLength; Peer.LogError(NetLogMessage.FromValues(NetLogCode.HailMessageTooLarge, value: resultSize, maxValue: maxHailSize)); } om.Write(LocalHailMessage); } }
/// <summary> /// Called when host/client receives a NatIntroduction message from a master server /// </summary> internal void HandleNatIntroduction(int offset) { AssertIsOnLibraryThread(); // read intro NetIncomingMessage tmp = SetupReadHelperMessage(offset, 1000); // never mind length byte hostByte = tmp.ReadByte(); IPEndPoint remoteInternal = tmp.ReadIPEndPoint(); IPEndPoint remoteExternal = tmp.ReadIPEndPoint(); string token = tmp.ReadString(); bool isHost = hostByte != 0; LogDebug(NetLogMessage.FromValues(NetLogCode.NATIntroductionReceived, value: hostByte)); if (!isHost && !Configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) { return; // no need to punch - we're not listening for nat intros! } // send internal punch var internalPunch = CreateMessage(1); internalPunch._messageType = NetMessageType.NatPunchMessage; internalPunch.Write(hostByte); internalPunch.Write(token); UnsentUnconnectedMessages.Enqueue((remoteInternal, internalPunch)); LogDebug(new NetLogMessage(NetLogCode.NATPunchSent, endPoint: remoteInternal)); // send external punch var externalPunch = CreateMessage(1); externalPunch._messageType = NetMessageType.NatPunchMessage; externalPunch.Write(hostByte); externalPunch.Write(token); UnsentUnconnectedMessages.Enqueue((remoteExternal, externalPunch)); LogDebug(new NetLogMessage(NetLogCode.NATPunchSent, endPoint: remoteExternal)); }
// TODO: replace byte[] with Memory<byte> in the future (held back by Socket.SendTo) // https://github.com/dotnet/runtime/issues/33418 internal NetSocketResult ActuallySendPacket(byte[] data, int byteCount, IPEndPoint target) { Socket?socket = Socket; Debug.Assert(socket != null); bool broadcasting = false; try { IPAddress?ba = NetUtility.GetBroadcastAddress(); // TODO: refactor this check outta here if (target.Address.Equals(ba)) { // Some networks do not allow // a global broadcast so we use the BroadcastAddress from the configuration // this can be resolved to a local broadcast addresss e.g 192.168.x.255 _targetCopy.Address = Configuration.BroadcastAddress; _targetCopy.Port = target.Port; socket.EnableBroadcast = true; broadcasting = true; } else if ( Configuration.DualStack && Configuration.LocalAddress.AddressFamily == AddressFamily.InterNetworkV6) { // Maps to IPv6 for Dual Mode NetUtility.MapToIPv6(target, _targetCopy); } else { _targetCopy.Port = target.Port; _targetCopy.Address = target.Address; } int bytesSent = socket.SendTo(data, 0, byteCount, SocketFlags.None, _targetCopy); if (byteCount != bytesSent) { LogWarning(NetLogMessage.FromValues(NetLogCode.FullSendFailure, endPoint: target, value: bytesSent, maxValue: byteCount)); } //LogDebug("Sent " + numBytes + " bytes"); } catch (SocketException sx) { switch (sx.SocketErrorCode) { case SocketError.WouldBlock: // send buffer full? LogDebug(new NetLogMessage(NetLogCode.SocketWouldBlock, sx)); return(new NetSocketResult(false, false)); case SocketError.ConnectionReset: // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" return(new NetSocketResult(false, true)); default: LogError(new NetLogMessage(NetLogCode.SendFailure, sx, target)); break; } } catch (Exception ex) { LogError(new NetLogMessage(NetLogCode.SendFailure, ex, target)); } finally { if (broadcasting) { socket.EnableBroadcast = false; } } return(new NetSocketResult(true, false)); }
internal void ReceivedHandshake(TimeSpan now, NetMessageType type, int offset, int payloadLength) { Peer.AssertIsOnLibraryThread(); switch (type) { case NetMessageType.Connect: if (Status == NetConnectionStatus.ReceivedInitiation) { // Whee! Server full has already been checked var(success, hail, hailLength) = ValidateHandshakeData(offset, payloadLength); if (success) { if (hail != null) { RemoteHailMessage = Peer.CreateIncomingMessage(NetIncomingMessageType.Data); RemoteHailMessage.SetBuffer(hail, true); RemoteHailMessage.ByteLength = hailLength; } else { RemoteHailMessage = null; } if (_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) { // ok, let's not add connection just yet var appMsg = Peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval); appMsg.ReceiveTime = now; appMsg.SenderConnection = this; appMsg.SenderEndPoint = RemoteEndPoint; if (RemoteHailMessage != null) { appMsg.Write(RemoteHailMessage.GetBuffer().AsSpan(0, RemoteHailMessage.ByteLength)); } SetStatus(NetConnectionStatus.RespondedAwaitingApproval); Peer.ReleaseMessage(appMsg); return; } SendConnectResponse(now, true); } return; } if (Status == NetConnectionStatus.RespondedAwaitingApproval) { Peer.LogWarning(new NetLogMessage(NetLogCode.IgnoringMultipleConnects, endPoint: this)); return; } if (Status == NetConnectionStatus.RespondedConnect) { // our ConnectResponse must have been lost SendConnectResponse(now, true); return; } Peer.LogDebug(NetLogMessage.FromValues(NetLogCode.UnhandledConnect, endPoint: this, value: (int)Status)); break; case NetMessageType.ConnectResponse: HandleConnectResponse(offset, payloadLength); break; case NetMessageType.ConnectionEstablished: switch (Status) { case NetConnectionStatus.Connected: // ok... break; case NetConnectionStatus.Disconnected: case NetConnectionStatus.Disconnecting: case NetConnectionStatus.None: // too bad, almost made it break; case NetConnectionStatus.ReceivedInitiation: // uh, a little premature... ignore break; case NetConnectionStatus.InitiatedConnect: // weird, should have been RespondedConnect... break; case NetConnectionStatus.RespondedConnect: // awesome NetIncomingMessage msg = Peer.SetupReadHelperMessage(offset, payloadLength); InitializeRemoteTimeOffset(msg.ReadTimeSpan()); Peer.AcceptConnection(this); InitializePing(); SetStatus(NetConnectionStatus.Connected); return; } break; case NetMessageType.InvalidHandshake: case NetMessageType.WrongAppIdentifier: case NetMessageType.ConnectTimedOut: case NetMessageType.TimedOut: case NetMessageType.Disconnect: NetOutgoingMessage?reason = null; try { reason = Peer.CreateReadHelperOutMessage(offset, payloadLength); } catch { } ExecuteDisconnect(reason, false); break; case NetMessageType.Discovery: Peer.HandleIncomingDiscoveryRequest(now, RemoteEndPoint, offset, payloadLength); return; case NetMessageType.DiscoveryResponse: Peer.HandleIncomingDiscoveryResponse(now, RemoteEndPoint, offset, payloadLength); return; case NetMessageType.Ping: // silently ignore return; default: Peer.LogDebug(NetLogMessage.FromValues(NetLogCode.UnhandledHandshakeMessage, value: (int)Status)); break; } }
// heartbeat called when connection still is in m_handshakes of NetPeer internal void UnconnectedHeartbeat(TimeSpan now) { Peer.AssertIsOnLibraryThread(); if (_disconnectRequested) { ExecuteDisconnect(_disconnectMessage, true); } if (_connectRequested) { switch (Status) { case NetConnectionStatus.Connected: case NetConnectionStatus.RespondedConnect: // reconnect ExecuteDisconnect(NetMessageType.ConnectTimedOut); break; case NetConnectionStatus.InitiatedConnect: // send another connect attempt SendConnect(now); break; case NetConnectionStatus.Disconnected: Peer.LogError(NetLogMessage.FromValues(NetLogCode.DisconnectedPeer)); break; case NetConnectionStatus.Disconnecting: // let disconnect finish first break; case NetConnectionStatus.None: default: SendConnect(now); break; } return; } if (now - _lastHandshakeSendTime > _peerConfiguration._resendHandshakeInterval) { if (_handshakeAttempts >= _peerConfiguration._maximumHandshakeAttempts) { // failed to connect ExecuteDisconnect(NetMessageType.ConnectTimedOut); return; } // resend handshake switch (Status) { case NetConnectionStatus.InitiatedConnect: SendConnect(now); break; case NetConnectionStatus.RespondedConnect: SendConnectResponse(now, true); break; case NetConnectionStatus.RespondedAwaitingApproval: // awaiting approval _lastHandshakeSendTime = now; // postpone handshake resend break; case NetConnectionStatus.None: case NetConnectionStatus.ReceivedInitiation: default: Peer.LogWarning(NetLogMessage.FromValues(NetLogCode.UnexpectedHandshakeStatus, value: (int)Status)); break; } } }