// sends a connection challenge packet to the endpoint private void sendConnectionChallenge(NetcodePrivateConnectToken connectToken, EndPoint endpoint) { log("Sending connection challenge", NetcodeLogLevel.Debug); var challengeToken = new NetcodeChallengeToken(); challengeToken.ClientID = connectToken.ClientID; challengeToken.UserData = connectToken.UserData; ulong challengeSequence = nextChallengeSequenceNumber++; byte[] tokenBytes = BufferPool.GetBuffer(300); using (var tokenWriter = ByteArrayReaderWriter.Get(tokenBytes)) challengeToken.Write(tokenWriter); byte[] encryptedToken = BufferPool.GetBuffer(300); int encryptedTokenBytes; try { encryptedTokenBytes = PacketIO.EncryptChallengeToken(challengeSequence, tokenBytes, challengeKey, encryptedToken); } catch { BufferPool.ReturnBuffer(tokenBytes); BufferPool.ReturnBuffer(encryptedToken); return; } var challengePacket = new NetcodeConnectionChallengeResponsePacket(); challengePacket.ChallengeTokenSequence = challengeSequence; challengePacket.ChallengeTokenBytes = encryptedToken; var cryptIdx = encryptionManager.FindEncryptionMapping(endpoint, time); if (cryptIdx == -1) { return; } var cryptKey = encryptionManager.GetSendKey(cryptIdx); serializePacket(new NetcodePacketHeader() { PacketType = NetcodePacketType.ConnectionChallenge }, (writer) => { challengePacket.Write(writer); }, endpoint, cryptKey); BufferPool.ReturnBuffer(tokenBytes); BufferPool.ReturnBuffer(encryptedToken); }
// process an incoming connection request packet private void processConnectionRequest(ByteArrayReaderWriter reader, int size, EndPoint sender) { log("Got connection request", NetcodeLogLevel.Debug); var connectionRequestPacket = new NetcodeConnectionRequestPacket(); if (!connectionRequestPacket.Read(reader, size - (int)reader.ReadPosition, protocolID)) { log("Failed to read request", NetcodeLogLevel.Debug); return; } // expiration timestamp should be greater than current timestamp if (connectionRequestPacket.Expiration <= (ulong)Math.Truncate(time)) { log("Connect token expired", NetcodeLogLevel.Debug); connectionRequestPacket.Release(); return; } var privateConnectToken = new NetcodePrivateConnectToken(); if (!privateConnectToken.Read(connectionRequestPacket.ConnectTokenBytes, privateKey, protocolID, connectionRequestPacket.Expiration, connectionRequestPacket.TokenSequenceNum)) { log("Failed to read private token", NetcodeLogLevel.Debug); connectionRequestPacket.Release(); return; } // if this server's public IP is not in the list of endpoints, packet is not valid bool serverAddressInEndpoints = privateConnectToken.ConnectServers.Any(x => x.Endpoint.CompareEndpoint(this.listenEndpoint, this.Port)); if (!serverAddressInEndpoints) { log("Server address not listen in token", NetcodeLogLevel.Debug); return; } // if a client from packet source IP / port is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.RemoteEndpoint.Equals(sender))) { log("Client {0} already connected", NetcodeLogLevel.Debug, sender.ToString()); return; } // if a client with the same id as the connect token is already connected, ignore the packet if (clientSlots.Any(x => x != null && x.ClientID == privateConnectToken.ClientID)) { log("Client ID {0} already connected", NetcodeLogLevel.Debug, privateConnectToken.ClientID); return; } // if the connect token has already been used by a different endpoint, ignore the packet // otherwise, add the token hmac and endpoint to the used token history // compares the last 16 bytes (token mac) byte[] token_mac = BufferPool.GetBuffer(Defines.MAC_SIZE); System.Array.Copy(connectionRequestPacket.ConnectTokenBytes, Defines.NETCODE_CONNECT_TOKEN_PRIVATE_BYTES - Defines.MAC_SIZE, token_mac, 0, Defines.MAC_SIZE); if (!findOrAddConnectToken(sender, token_mac, time)) { log("Token already used", NetcodeLogLevel.Debug); BufferPool.ReturnBuffer(token_mac); return; } BufferPool.ReturnBuffer(token_mac); // if we have no slots, we need to respond with a connection denied packet var nextSlot = getFreeClientSlot(); if (nextSlot == -1) { denyConnection(sender, privateConnectToken.ServerToClientKey); log("Server is full, denying connection", NetcodeLogLevel.Info); return; } // add encryption mapping for this endpoint as well as timeout // packets received from this endpoint are to be decrypted with the client-to-server key // packets sent to this endpoint are to be encrypted with the server-to-client key // if no messages are received within timeout from this endpoint, it is disconnected (unless timeout is negative) if (!encryptionManager.AddEncryptionMapping(sender, privateConnectToken.ServerToClientKey, privateConnectToken.ClientToServerKey, time, time + 30, privateConnectToken.TimeoutSeconds, 0)) { log("Failed to add encryption mapping", NetcodeLogLevel.Error); return; } // finally, send a connection challenge packet sendConnectionChallenge(privateConnectToken, sender); }
internal byte[] GenerateConnectToken(IPEndPoint[] addressList, double time, int expirySeconds, int serverTimeout, ulong sequence, ulong clientID, byte[] userData) { if (userData.Length > 256) { throw new ArgumentOutOfRangeException("User data cannot be larger than 256 bytes"); } if (addressList == null) { throw new NullReferenceException("Address list cannot be null"); } else if (addressList.Length == 0) { throw new ArgumentOutOfRangeException("Address list cannot be empty"); } else if (addressList.Length > Defines.MAX_SERVER_ADDRESSES) { throw new ArgumentOutOfRangeException("Address list cannot contain more than " + Defines.MAX_SERVER_ADDRESSES + " entries"); } NetcodePrivateConnectToken privateConnectToken = new NetcodePrivateConnectToken(); privateConnectToken.ClientID = clientID; privateConnectToken.TimeoutSeconds = serverTimeout; // generate random crypto keys byte[] clientToServerKey = new byte[32]; byte[] serverToClientKey = new byte[32]; KeyUtils.GenerateKey(clientToServerKey); KeyUtils.GenerateKey(serverToClientKey); privateConnectToken.ClientToServerKey = clientToServerKey; privateConnectToken.ServerToClientKey = serverToClientKey; privateConnectToken.UserData = new byte[256]; Buffer.BlockCopy(userData, 0, privateConnectToken.UserData, 0, userData.Length); privateConnectToken.ConnectServers = new ConnectTokenServerEntry[addressList.Length]; for (int i = 0; i < privateConnectToken.ConnectServers.Length; i++) { privateConnectToken.ConnectServers[i] = new ConnectTokenServerEntry() { AddressType = addressList[i].AddressFamily == AddressFamily.InterNetwork ? NetcodeAddressType.IPv4 : NetcodeAddressType.IPv6, Endpoint = addressList[i] }; } byte[] privateConnectTokenBytes = new byte[1024]; using (var writer = ByteArrayReaderWriter.Get(privateConnectTokenBytes)) { privateConnectToken.Write(writer); } ulong createTimestamp = (ulong)Math.Truncate(time); ulong expireTimestamp = expirySeconds >= 0 ? (createTimestamp + (ulong)expirySeconds) : 0xFFFFFFFFFFFFFFFFUL; byte[] encryptedPrivateToken = new byte[1024]; PacketIO.EncryptPrivateConnectToken(privateConnectTokenBytes, protocolID, expireTimestamp, sequence, privateKey, encryptedPrivateToken); NetcodePublicConnectToken publicToken = new NetcodePublicConnectToken(); publicToken.ProtocolID = protocolID; publicToken.CreateTimestamp = createTimestamp; publicToken.ExpireTimestamp = expireTimestamp; publicToken.ConnectTokenSequence = sequence; publicToken.PrivateConnectTokenBytes = encryptedPrivateToken; publicToken.ConnectServers = privateConnectToken.ConnectServers; publicToken.ClientToServerKey = clientToServerKey; publicToken.ServerToClientKey = serverToClientKey; publicToken.TimeoutSeconds = serverTimeout; byte[] publicTokenBytes = new byte[2048]; using (var writer = ByteArrayReaderWriter.Get(publicTokenBytes)) { publicToken.Write(writer); } return(publicTokenBytes); }