public static STUNQueryResult Query(Socket socket, IPEndPoint server, STUNQueryType queryType, int ReceiveTimeout) { STUNNatMappingBehavior mappingBehavior = STUNNatMappingBehavior.NoMapping; STUNNatFilteringBehavior filteringBehavior = STUNNatFilteringBehavior.EndpointIndependentFiltering; var result = new STUNQueryResult(); // the query result result.Socket = socket; result.ServerEndPoint = server; result.NATType = STUNNATType.Unspecified; result.QueryType = queryType; var transID = STUNMessage.GenerateTransactionIDNewStun(); // get a random trans id var message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request // send the request to server socket.SendTo(message.GetBytes(), server); // we set result local endpoint after calling SendTo, // because if socket is unbound, the system will bind it after SendTo call. result.LocalEndPoint = socket.LocalEndPoint as IPEndPoint; // wait for response var responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout); // didn't receive anything if (responseBuffer == null) { result.QueryError = STUNQueryError.Timeout; return(result); } // try to parse message if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } // check trans id if (!STUNUtils.ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } // finds error-code attribute, used in case of binding error var errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; // if server responded our request with error if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { if (errorAttr == null) { // we count a binding error without error-code attribute as bad response (no?) result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } // return if receive something else than binding response if (message.MessageType != STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.BadResponse; return(result); } var xorAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute) as STUNXorMappedAddressAttribute; if (xorAddressAttribute == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.PublicEndPoint = xorAddressAttribute.EndPoint; // stop querying and return the public ip if user just wanted to know public ip if (queryType == STUNQueryType.PublicIP) { result.QueryError = STUNQueryError.Success; return(result); } var otherAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNOtherAddressAttribute) as STUNOtherAddressAttribute; var changedAddressAttribute = message.Attributes.FirstOrDefault(p => p is STUNChangedAddressAttribute) as STUNChangedAddressAttribute; // Check is next test should be performed and is support rfc5780 test if (otherAddressAttribute == null) { if (changedAddressAttribute == null) { result.QueryError = STUNQueryError.NotSupported; return(result); } otherAddressAttribute = new STUNOtherAddressAttribute(); otherAddressAttribute.EndPoint = changedAddressAttribute.EndPoint; } // Make test 2 - bind different ip address but primary port message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request IPEndPoint secondaryServer = new IPEndPoint(otherAddressAttribute.EndPoint.Address, server.Port); socket.SendTo(message.GetBytes(), secondaryServer); responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout); // Secondary server presented but is down if (responseBuffer == null) { result.QueryError = STUNQueryError.Timeout; return(result); } if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } var xorAddressAttribute2 = message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute) as STUNXorMappedAddressAttribute; if (xorAddressAttribute2 == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (xorAddressAttribute.EndPoint.Equals(xorAddressAttribute2.EndPoint)) { if (xorAddressAttribute.EndPoint.Equals(socket.LocalEndPoint) || Dns.GetHostAddresses(Dns.GetHostName()).Contains(xorAddressAttribute.EndPoint.Address) ) { mappingBehavior = STUNNatMappingBehavior.NoMapping; } else { mappingBehavior = STUNNatMappingBehavior.EndpointIndependentMapping; } } else // Make test 3 { IPEndPoint secondaryServerPort = new IPEndPoint(otherAddressAttribute.EndPoint.Address, otherAddressAttribute.EndPoint.Port); message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request socket.SendTo(message.GetBytes(), secondaryServerPort); responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout); if (responseBuffer == null) { result.QueryError = STUNQueryError.Timeout; return(result); } if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } var xorAddressAttribute3 = message.Attributes.FirstOrDefault(p => p is STUNXorMappedAddressAttribute) as STUNXorMappedAddressAttribute; if (xorAddressAttribute3 == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (xorAddressAttribute3.EndPoint.Equals(xorAddressAttribute2.EndPoint)) { mappingBehavior = STUNNatMappingBehavior.AddressDependMapping; } else { mappingBehavior = STUNNatMappingBehavior.AddressAndPortDependMapping; } } // Now make a filtering behavioral test // We already made a test 1 for mapping behavioral // so jump to test 2 // Send message to primary server. // Try receive from another server and port message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); message.Attributes.Add(new STUNChangeRequestAttribute(true, true)); socket.SendTo(message.GetBytes(), server); responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout); if (responseBuffer != null) { filteringBehavior = STUNNatFilteringBehavior.EndpointIndependentFiltering; } else // Test 3 - send request to original server with change port attribute { message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); message.Attributes.Add(new STUNChangeRequestAttribute(false, true)); socket.SendTo(message.GetBytes(), server); responseBuffer = STUNUtils.Receive(socket, ReceiveTimeout); if (responseBuffer != null) { filteringBehavior = STUNNatFilteringBehavior.AddressDependFiltering; } else { filteringBehavior = STUNNatFilteringBehavior.AddressAndPortDependFiltering; } } result.FilteringBehavior = filteringBehavior; if (mappingBehavior == STUNNatMappingBehavior.NoMapping) { result.NATType = STUNNATType.OpenInternet; } else if (mappingBehavior == STUNNatMappingBehavior.EndpointIndependentMapping) { if (filteringBehavior == STUNNatFilteringBehavior.EndpointIndependentFiltering) { result.NATType = STUNNATType.FullCone; } else if (filteringBehavior == STUNNatFilteringBehavior.AddressDependFiltering) { result.NATType = STUNNATType.Restricted; } else // (filteringBehavior == STUNNatFilteringBehavior.AddressAndPortDependFiltering) { result.NATType = STUNNATType.PortRestricted; } } else { result.NATType = STUNNATType.Symmetric; } return(result); }
/// <param name="socket">A UDP <see cref="Socket"/> that will use for query. You can also use <see cref="UdpClient.Client"/></param> /// <param name="server">Server address</param> /// <param name="queryType">Query type</param> public static STUNQueryResult Query(Socket socket, IPEndPoint server, STUNQueryType queryType) { var result = new STUNQueryResult(); // the query result var transID = STUNMessage.GenerateTransactionID(); // get a random trans id var message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); // create a bind request result.Socket = socket; result.ServerEndPoint = server; result.NATType = STUNNATType.Unspecified; result.QueryType = queryType; // send the request to server socket.SendTo(message.GetBytes(), server); // we set result local endpoint after calling SendTo, // because if socket is unbound, the system will bind it after SendTo call. result.LocalEndPoint = socket.LocalEndPoint as IPEndPoint; // wait for response var responseBuffer = Receive(socket); // didn't receive anything if (responseBuffer == null) { result.QueryError = STUNQueryError.Timedout; return(result); } // try to parse message if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } // check trans id if (!ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } // finds error-code attribute, used in case of binding error var errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; // if server responsed our request with error if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { if (errorAttr == null) { // we count a binding error without error-code attribute as bad response (no?) result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } // return if receive something else binding response if (message.MessageType != STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.BadResponse; return(result); } // not used for now. var changedAddr = message.Attributes.FirstOrDefault(p => p is STUNChangedAddressAttribute) as STUNChangedAddressAttribute; // find mapped address attribue in message // this attribue should present var mappedAddressAttr = message.Attributes.FirstOrDefault(p => p is STUNMappedAddressAttribute) as STUNMappedAddressAttribute; if (mappedAddressAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } else { result.PublicEndPoint = mappedAddressAttr.EndPoint; } // stop querying and return the public ip if user just wanted to know public ip if (queryType == STUNQueryType.PublicIP) { result.QueryError = STUNQueryError.Success; return(result); } // if our local ip and port equals to mapped address if (mappedAddressAttr.EndPoint.Equals(socket.LocalEndPoint)) { // we send to a binding request again but with change-request attribute // that tells to server to response us with different endpoint message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); message.Attributes.Add(new STUNChangeRequestAttribute(true, true)); socket.SendTo(message.GetBytes(), server); responseBuffer = Receive(socket); // if we didnt receive a response if (responseBuffer == null) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.SymmetricUDPFirewall; return(result); } if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (!ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } if (message.MessageType == STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.OpenInternet; return(result); } if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; if (errorAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } // the message type is wrong result.QueryError = STUNQueryError.BadResponse; return(result); } message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); message.Attributes.Add(new STUNChangeRequestAttribute(true, true)); var testmsg = new STUNMessage(STUNMessageTypes.BindingRequest, null); testmsg.Parse(message.GetBytes()); socket.SendTo(message.GetBytes(), server); responseBuffer = Receive(socket); if (responseBuffer != null) { if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (!ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } if (message.MessageType == STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.FullCone; return(result); } if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; if (errorAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } result.QueryError = STUNQueryError.BadResponse; return(result); } // if user only wanted to know the NAT is open or not if (queryType == STUNQueryType.OpenNAT) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.Unspecified; return(result); } // we now need changed-address attribute // because we send our request to this address instead of the first server address if (changedAddr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } else { server = changedAddr.EndPoint; } message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); socket.SendTo(message.GetBytes(), server); responseBuffer = Receive(socket); if (responseBuffer == null) { result.QueryError = STUNQueryError.Timedout; return(result); } if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (!ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { if (errorAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } if (message.MessageType != STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.BadResponse; return(result); } mappedAddressAttr = message.Attributes.FirstOrDefault(p => p is STUNMappedAddressAttribute) as STUNMappedAddressAttribute; if (mappedAddressAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } if (!mappedAddressAttr.EndPoint.Equals(result.PublicEndPoint)) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.Symmetric; result.PublicEndPoint = null; return(result); } message = new STUNMessage(STUNMessageTypes.BindingRequest, transID); message.Attributes.Add(new STUNChangeRequestAttribute(false, true)); // change port but not ip socket.SendTo(message.GetBytes(), server); responseBuffer = Receive(socket); if (responseBuffer == null) { result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.PortRestricted; return(result); } if (!message.TryParse(responseBuffer)) { result.QueryError = STUNQueryError.Timedout; return(result); } if (ByteArrayCompare(message.TransactionID, transID)) { result.QueryError = STUNQueryError.BadTransactionID; return(result); } errorAttr = message.Attributes.FirstOrDefault(p => p is STUNErrorCodeAttribute) as STUNErrorCodeAttribute; if (message.MessageType == STUNMessageTypes.BindingErrorResponse) { if (errorAttr == null) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.ServerError; result.ServerError = errorAttr.Error; result.ServerErrorPhrase = errorAttr.Phrase; return(result); } if (message.MessageType != STUNMessageTypes.BindingResponse) { result.QueryError = STUNQueryError.BadResponse; return(result); } result.QueryError = STUNQueryError.Success; result.NATType = STUNNATType.Restricted; return(result); }