public void NetTest6_SocketAddressBasic() { Random random = new(); for (int i = 0; i <= 30; i++) { int[] IPInts = { random.Next(256), random.Next(256), random.Next(256), random.Next(128) }; Debug.WriteLine($"Random IP {IPInts[0]}.{IPInts[1]}.{IPInts[2]}.{IPInts[3]}"); IPAddress address = new( IPInts[0] + IPInts[1] * 256 + IPInts[2] * 256 * 256 + IPInts[3] * 256 * 256 * 256); int portInt = random.Next(65536); IPEndPoint ipEndpoint1 = new(address, portInt); SocketAddress socketAddress1 = ipEndpoint1.Serialize(); SocketAddress socketAddress2 = ipEndpoint1.Serialize(); Assert.NotNull(socketAddress1, "socketAddress1 is null"); Assert.NotNull(socketAddress2, "socketAddress2 is null"); Type typeOfSocketAddress = socketAddress1.GetType(); Assert.IsType(typeOfSocketAddress, Type.GetType("System.Net.SocketAddress"), "socketAddress1 Type is incorrect"); typeOfSocketAddress = socketAddress2.GetType(); Assert.IsType(typeOfSocketAddress, Type.GetType("System.Net.SocketAddress"), "socketAddress2 Type is incorrect"); Assert.Equal(socketAddress1.ToString(), socketAddress2.ToString(), "ToString returns differently for same data"); Assert.Equal(socketAddress1.GetHashCode(), socketAddress2.GetHashCode(), $"GetHashCode returns differently for same data"); Assert.True(socketAddress1.Family == AddressFamily.InterNetwork, "socketAddress1 Family is incorrect"); Assert.True(socketAddress2.Family == AddressFamily.InterNetwork, "socketAddress2 Family is incorrect"); // Recreate a different Socket socketAddress2 = new SocketAddress(AddressFamily.Chaos, 8); Assert.NotEqual(socketAddress1.GetHashCode(), socketAddress2.GetHashCode(), "GetHashCode returns same for " + socketAddress1.ToString() + " " + socketAddress1.GetHashCode() + " as " + socketAddress2.ToString() + " " + socketAddress2.GetHashCode()); } }
public static void Equals_Compare_Success() { SocketAddress sa1 = new SocketAddress(AddressFamily.InterNetwork, 64); SocketAddress sa2 = new SocketAddress(AddressFamily.InterNetwork, 64); SocketAddress sa3 = new SocketAddress(AddressFamily.InterNetworkV6, 64); SocketAddress sa4 = new SocketAddress(AddressFamily.InterNetwork, 60000); Assert.False(sa1.Equals(null)); Assert.False(sa1.Equals("")); Assert.Equal(sa1, sa2); Assert.Equal(sa2, sa1); Assert.Equal(sa1.GetHashCode(), sa2.GetHashCode()); Assert.NotEqual(sa1, sa3); Assert.NotEqual(sa1.GetHashCode(), sa3.GetHashCode()); Assert.NotEqual(sa1, sa4); }
protected override int ReceiveFrom(byte[] buffer, out int connectionHash) { // where-allocation nonalloc ReceiveFrom. int read = socket.ReceiveFrom_NonAlloc(buffer, 0, buffer.Length, SocketFlags.None, reusableClientEP); SocketAddress remoteAddress = reusableClientEP.temp; // where-allocation nonalloc GetHashCode connectionHash = remoteAddress.GetHashCode(); return(read); }
public static void Equals_Compare_Success() { SocketAddress sa1 = new SocketAddress(AddressFamily.InterNetwork, 64); SocketAddress sa2 = new SocketAddress(AddressFamily.InterNetwork, 64); SocketAddress sa3 = new SocketAddress(AddressFamily.InterNetworkV6, 64); SocketAddress sa4 = new SocketAddress(AddressFamily.InterNetwork, 60000); Assert.False(sa1.Equals(null)); Assert.False(sa1.Equals("")); Assert.Equal(sa1, sa2); Assert.Equal(sa2, sa1); Assert.Equal(sa1.GetHashCode(), sa2.GetHashCode()); Assert.NotEqual(sa1, sa3); Assert.NotEqual(sa1.GetHashCode(), sa3.GetHashCode()); Assert.NotEqual(sa1, sa4); }
public override EndPoint Create(SocketAddress socketAddress) { if (SocketAddress != socketAddress) { SocketAddress = socketAddress; unchecked { SocketAddress[0] += 1; SocketAddress[0] -= 1; } if (SocketAddress.GetHashCode() == 0) { throw new Exception($"0x0000001 fatal error ):"); } } if (IPEndPoint == null) { IPEndPoint = GetIPEndPoint(); } return(IPEndPoint); }
/// <summary> /// Returns hash for efficient lookup in list /// </summary> /// <returns></returns> public override int GetHashCode() { return(SocketAddress.GetHashCode() ^ CanonicalName.GetHashCode()); }
public override int GetHashCode() { return(socketAddress.GetHashCode()); }
public MFTestResults NetTest6_SocketAddressBasic() { /// <summary> /// 1. Creates 30 Random IPs between 0.0.0.0 and 255.255.255.127 /// 2. Verifies that they can be constructed as SocketAddress /// 3. Verifies that they have the correct data (GetAddressBytes) /// 4. Verifies ToString and GetHashcode /// </summary> /// bool testResult = true; try { Random random = new Random(); for (int i = 0; i <= 30; i++) { int[] IPInts = { random.Next(256), random.Next(256), random.Next(256), random.Next(128) }; Log.Comment("Random IP " + IPInts[0] + "." + IPInts[1] + "." + IPInts[2] + "." + IPInts[3]); IPAddress address = new IPAddress((long)( IPInts[0] + IPInts[1] * 256 + IPInts[2] * 256 * 256 + IPInts[3] * 256 * 256 * 256)); int portInt = random.Next(65536); IPEndPoint ipEndpoint1 = new IPEndPoint(address, portInt); SocketAddress socketAddress1 = ipEndpoint1.Serialize(); SocketAddress socketAddress2 = ipEndpoint1.Serialize(); if (socketAddress1 == null) { throw new Exception("socketAddress1 is null"); } if (socketAddress2 == null) { throw new Exception("socketAddress2 is null"); } Type typeOfSocketAddress = socketAddress1.GetType(); if (typeOfSocketAddress != Type.GetType("System.Net.SocketAddress")) { throw new Exception("socketAddress1 Type is incorrect"); } typeOfSocketAddress = socketAddress2.GetType(); if (typeOfSocketAddress != Type.GetType("System.Net.SocketAddress")) { throw new Exception("socketAddress2 Type is incorrect"); } if (socketAddress1.ToString() != socketAddress2.ToString()) { throw new Exception("ToString returns differently for same data"); } //21295 GetHashCode returns differently for cloned data if (socketAddress1.GetHashCode() != socketAddress2.GetHashCode()) { throw new Exception("GetHashCode returns differently for same data"); } if (socketAddress1.Family != AddressFamily.InterNetwork) { throw new Exception("socketAddress1 Family is incorrect"); } if (socketAddress2.Family != AddressFamily.InterNetwork) { throw new Exception("socketAddress2 Family is incorrect"); } /* * Pending Resolution of 17428 * * Log.Comment("Recreating socketAddress2 with new data"); * int portInt2 = portInt % 2 + 1; * long addressLong2 = (long)( * (IPInts[0] % 2 + 1) + (IPInts[1] % 2 + 1) * 256 + (IPInts[2] % 2 + 1) * 256 * 256 + (IPInts[3] % 2 + 1) * 256 * 256 * 256); + + IPEndPoint ipEndpoint2 = new IPEndPoint(addressLong2, portInt2); + socketAddress2 = ipEndpoint2.Serialize(); + socketAddress2.Family = AddressFamily.Chaos; */ socketAddress2 = new SocketAddress(AddressFamily.Chaos, 8); if (socketAddress1.GetHashCode() == socketAddress2.GetHashCode()) { throw new Exception("GetHashCode returns same for " + socketAddress1.ToString() + " as " + socketAddress2.ToString()); } if (socketAddress1.ToString() == socketAddress2.ToString()) { throw new Exception("ToString returns same for different data"); } } } catch (Exception e) { Log.Comment("Caught exception: " + e.Message); testResult = false; } return(testResult ? MFTestResults.Pass : MFTestResults.KnownFailure); }
// Create doesn't need to create anything. // SocketAddress object is already the one we returned in Serialize(). // ReceiveFrom_Internal simply wrote into it. public override EndPoint Create(SocketAddress socketAddress) { // original IPEndPoint.Create validates: if (socketAddress.Family != AddressFamily) { throw new ArgumentException($"Unsupported socketAddress.AddressFamily: {socketAddress.Family}. Expected: {AddressFamily}"); } if (socketAddress.Size < 8) { throw new ArgumentException($"Unsupported socketAddress.Size: {socketAddress.Size}. Expected: <8"); } // double check to guarantee that ReceiveFrom actually did write // into our 'temp' field. just in case that's ever changed. if (socketAddress != temp) { // well this is fun. // in the latest mono from the above github links, // the result of Serialize() is passed as 'ref' so ReceiveFrom // does in fact write into it. // // in Unity 2019 LTS's mono version, it does create a new one // each time. this is from ILSpy Receive_From: // // SocketPal.CheckDualModeReceiveSupport(this); // ValidateBlockingMode(); // if (NetEventSource.IsEnabled) // { // NetEventSource.Info(this, $"SRC{LocalEndPoint} size:{size} remoteEP:{remoteEP}", "ReceiveFrom"); // } // EndPoint remoteEP2 = remoteEP; // System.Net.Internals.SocketAddress socketAddress = SnapshotAndSerialize(ref remoteEP2); // System.Net.Internals.SocketAddress socketAddress2 = IPEndPointExtensions.Serialize(remoteEP2); // int bytesTransferred; // SocketError socketError = SocketPal.ReceiveFrom(_handle, buffer, offset, size, socketFlags, socketAddress.Buffer, ref socketAddress.InternalSize, out bytesTransferred); // SocketException ex = null; // if (socketError != 0) // { // ex = new SocketException((int)socketError); // UpdateStatusAfterSocketError(ex); // if (NetEventSource.IsEnabled) // { // NetEventSource.Error(this, ex, "ReceiveFrom"); // } // if (ex.SocketErrorCode != SocketError.MessageSize) // { // throw ex; // } // } // if (!socketAddress2.Equals(socketAddress)) // { // try // { // remoteEP = remoteEP2.Create(socketAddress); // } // catch // { // } // if (_rightEndPoint == null) // { // _rightEndPoint = remoteEP2; // } // } // if (ex != null) // { // throw ex; // } // if (NetEventSource.IsEnabled) // { // NetEventSource.DumpBuffer(this, buffer, offset, size, "ReceiveFrom"); // NetEventSource.Exit(this, bytesTransferred, "ReceiveFrom"); // } // return bytesTransferred; // // so until they upgrade their mono version, we are stuck with // some allocations. // // for now, let's pass the newly created on to our temp so at // least we reuse it next time. temp = socketAddress; // SocketAddress.GetHashCode() depends on SocketAddress.m_changed. // ReceiveFrom only sets the buffer, it does not seem to set m_changed. // we need to reset m_changed for two reasons: // * if m_changed is false, GetHashCode() returns the cahced m_hash // which is '0'. that would be a problem. // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L262 // * if we have a cached m_hash, but ReceiveFrom modified the buffer // then the GetHashCode() should change too. so we need to reset // either way. // // the only way to do that is by _actually_ modifying the buffer: // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/SocketAddress.cs#L99 // so let's do that. // -> unchecked in case it's byte.Max unchecked { temp[0] += 1; temp[0] -= 1; } // make sure this worked. // at least throw an Exception to make it obvious if the trick does // not work anymore, in case ReceiveFrom is ever changed. if (temp.GetHashCode() == 0) { throw new Exception($"SocketAddress GetHashCode() is 0 after ReceiveFrom. Does the m_changed trick not work anymore?"); } // in the future, enable this again: //throw new Exception($"Socket.ReceiveFrom(): passed SocketAddress={socketAddress} but expected {temp}. This should never happen. Did ReceiveFrom() change?"); } // ReceiveFrom sets seed_endpoint to the result of Create(): // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1764 // so let's return ourselves at least. // (seed_endpoint only seems to matter for BeginSend etc.) return(this); }
// we need to overwrite GetHashCode() for two reasons. // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPEndPoint.cs#L160 // * it uses m_Address. but our true SocketAddress is in m_temp. // m_Address might not be set at all. // * m_Address.GetHashCode() allocates: // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 public override int GetHashCode() => temp.GetHashCode();
public void TickIncoming() { while (socket != null && socket.Poll(0, SelectMode.SelectRead)) { try { // NOTE: ReceiveFrom allocates. // we pass our IPEndPoint to ReceiveFrom. // receive from calls newClientEP.Create(socketAddr). // IPEndPoint.Create always returns a new IPEndPoint. // https://github.com/mono/mono/blob/f74eed4b09790a0929889ad7fc2cf96c9b6e3757/mcs/class/System/System.Net.Sockets/Socket.cs#L1761 //int msgLength = socket.ReceiveFrom(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, ref newClientEP); //Log.Info($"KCP: server raw recv {msgLength} bytes = {BitConverter.ToString(buffer, 0, msgLength)}"); // where-allocation nonalloc ReceiveFrom. int msgLength = socket.ReceiveFrom_NonAlloc(rawReceiveBuffer, 0, rawReceiveBuffer.Length, SocketFlags.None, reusableClientEP); SocketAddress remoteAddress = reusableClientEP.temp; // calculate connectionId from endpoint // NOTE: IPEndPoint.GetHashCode() allocates. // it calls m_Address.GetHashCode(). // m_Address is an IPAddress. // GetHashCode() allocates for IPv6: // https://github.com/mono/mono/blob/bdd772531d379b4e78593587d15113c37edd4a64/mcs/class/referencesource/System/net/System/Net/IPAddress.cs#L699 // // => using only newClientEP.Port wouldn't work, because // different connections can have the same port. //int connectionId = newClientEP.GetHashCode(); // where-allocation nonalloc GetHashCode int connectionId = remoteAddress.GetHashCode(); // IMPORTANT: detect if buffer was too small for the received // msgLength. otherwise the excess data would be // silently lost. // (see ReceiveFrom documentation) if (msgLength <= rawReceiveBuffer.Length) { // is this a new connection? if (!connections.TryGetValue(connectionId, out KcpServerConnection connection)) { // IPEndPointNonAlloc is reused all the time. // we can't store that as the connection's endpoint. // we need a new copy! IPEndPoint newClientEP = reusableClientEP.DeepCopyIPEndPoint(); // for allocation free sending, we also need another // IPEndPointNonAlloc... IPEndPointNonAlloc reusableSendEP = new IPEndPointNonAlloc(newClientEP.Address, newClientEP.Port); // create a new KcpConnection // -> where-allocation IPEndPointNonAlloc is reused. // need to create a new one from the temp address. connection = new KcpServerConnection(socket, newClientEP, reusableSendEP, NoDelay, Interval, FastResend, CongestionWindow, SendWindowSize, ReceiveWindowSize, Timeout); // DO NOT add to connections yet. only if the first message // is actually the kcp handshake. otherwise it's either: // * random data from the internet // * or from a client connection that we just disconnected // but that hasn't realized it yet, still sending data // from last session that we should absolutely ignore. // // // TODO this allocates a new KcpConnection for each new // internet connection. not ideal, but C# UDP Receive // already allocated anyway. // // expecting a MAGIC byte[] would work, but sending the raw // UDP message without kcp's reliability will have low // probability of being received. // // for now, this is fine. // setup authenticated event that also adds to connections connection.OnAuthenticated = () => { // only send handshake to client AFTER we received his // handshake in OnAuthenticated. // we don't want to reply to random internet messages // with handshakes each time. connection.SendHandshake(); // add to connections dict after being authenticated. connections.Add(connectionId, connection); Log.Info($"KCP: server added connection({connectionId}): {newClientEP}"); // setup Data + Disconnected events only AFTER the // handshake. we don't want to fire OnServerDisconnected // every time we receive invalid random data from the // internet. // setup data event connection.OnData = (message) => { // call mirror event //Log.Info($"KCP: OnServerDataReceived({connectionId}, {BitConverter.ToString(message.Array, message.Offset, message.Count)})"); OnData.Invoke(connectionId, message); }; // setup disconnected event connection.OnDisconnected = () => { // flag for removal // (can't remove directly because connection is updated // and event is called while iterating all connections) connectionsToRemove.Add(connectionId); // call mirror event Log.Info($"KCP: OnServerDisconnected({connectionId})"); OnDisconnected.Invoke(connectionId); }; // finally, call mirror OnConnected event Log.Info($"KCP: OnServerConnected({connectionId})"); OnConnected.Invoke(connectionId); }; // now input the message & process received ones // connected event was set up. // tick will process the first message and adds the // connection if it was the handshake. connection.RawInput(rawReceiveBuffer, msgLength); connection.TickIncoming(); // again, do not add to connections. // if the first message wasn't the kcp handshake then // connection will simply be garbage collected. } // existing connection: simply input the message into kcp else { connection.RawInput(rawReceiveBuffer, msgLength); } } else { Log.Error($"KCP Server: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting connectionId={connectionId}."); Disconnect(connectionId); } } // this is fine, the socket might have been closed in the other end catch (SocketException) {} } // process inputs for all server connections // (even if we didn't receive anything. need to tick ping etc.) foreach (KcpServerConnection connection in connections.Values) { connection.TickIncoming(); } // remove disconnected connections // (can't do it in connection.OnDisconnected because Tick is called // while iterating connections) foreach (int connectionId in connectionsToRemove) { connections.Remove(connectionId); } connectionsToRemove.Clear(); }
public override int GetHashCode() => SocketAddress.GetHashCode();