/// <summary> /// Gets NAT info from STUN server. /// </summary> /// <param name="host">STUN server name or IP.</param> /// <param name="port">STUN server port. Default port is 3478.</param> /// <param name="socket">UDP socket to use.</param> /// <returns>Returns UDP netwrok info.</returns> /// <exception cref="Exception">Throws exception if unexpected error happens.</exception> internal static StunResult Query(string host, int port, Socket socket) { if (host == null) { throw new ArgumentNullException("host"); } if (socket == null) { throw new ArgumentNullException("socket"); } if (port < 1) { throw new ArgumentException("Port value must be >= 1 !"); } if (socket.ProtocolType != ProtocolType.Udp) { throw new ArgumentException("Socket must be UDP socket !"); } IPEndPoint remoteEndPoint = new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0], port); socket.ReceiveTimeout = 3000; socket.SendTimeout = 3000; /* * In test I, the client sends a STUN Binding Request to a server, without any flags set in the * CHANGE-REQUEST attribute, and without the RESPONSE-ADDRESS attribute. This causes the server * to send the response back to the address and port that the request came from. * * In test II, the client sends a Binding Request with both the "change IP" and "change port" flags * from the CHANGE-REQUEST attribute set. * * In test III, the client sends a Binding Request with only the "change port" flag set. * +--------+ | Test | | I | +--------+ | | | V | /\ /\ | N / \ Y / \ Y +--------+ | UDP <-------/Resp\--------->/ IP \------------->| Test | | Blocked \ ? / \Same/ | II | \ / \? / +--------+ \/ \/ | | N | | V | V /\ +--------+ Sym. N / \ | Test | UDP <---/Resp\ | II | Firewall \ ? / +--------+ \ / | \/ | V |Y | /\ /\ | | Symmetric N / \ +--------+ N / \ V | NAT <--- / IP \<-----| Test |<--- /Resp\ Open | \Same/ | I | \ ? / Internet \? / +--------+ \ / \/ \/ | |Y | | | V | Full | Cone | V /\ +--------+ / \ Y | Test |------>/Resp\---->Restricted | III | \ ? / +--------+ \ / \/ |N | Port +------>Restricted | */ // Test I StunMessage test1 = new StunMessage(); test1.Type = StunMessageType.BindingRequest; StunMessage test1response = DoTransaction(test1, socket, remoteEndPoint); // UDP blocked. if (test1response == null) { return(new StunResult(StunNetType.UdpBlocked, null)); } else { // Test II StunMessage test2 = new StunMessage(); test2.Type = StunMessageType.BindingRequest; test2.ChangeRequest = new StunChangeRequest(true, true); // No NAT. if (socket.LocalEndPoint.Equals(test1response.MappedAddress)) { StunMessage test2Response = DoTransaction(test2, socket, remoteEndPoint); // Open Internet. if (test2Response != null) { return(new StunResult(StunNetType.OpenInternet, test1response.MappedAddress)); } // Symmetric UDP firewall. else { return(new StunResult(StunNetType.SymmetricUdpFirewall, test1response.MappedAddress)); } } // NAT else { StunMessage test2Response = DoTransaction(test2, socket, remoteEndPoint); // Full cone NAT. if (test2Response != null) { return(new StunResult(StunNetType.FullCone, test1response.MappedAddress)); } else { /* * If no response is received, it performs test I again, but this time, does so to * the address and port from the CHANGED-ADDRESS attribute from the response to test I. */ // Test I(II) StunMessage test12 = new StunMessage(); test12.Type = StunMessageType.BindingRequest; StunMessage test12Response = DoTransaction(test12, socket, test1response.ChangedAddress); if (test12Response == null) { throw new Exception("STUN Test I(II) dind't get resonse !"); } else { // Symmetric NAT if (!test12Response.MappedAddress.Equals(test1response.MappedAddress)) { return(new StunResult(StunNetType.Symmetric, test1response.MappedAddress)); } else { // Test III StunMessage test3 = new StunMessage(); test3.Type = StunMessageType.BindingRequest; test3.ChangeRequest = new StunChangeRequest(false, true); StunMessage test3Response = DoTransaction(test3, socket, test1response.ChangedAddress); // Restricted if (test3Response != null) { return(new StunResult(StunNetType.RestrictedCone, test1response.MappedAddress)); } // Port restricted else { return(new StunResult(StunNetType.PortRestrictedCone, test1response.MappedAddress)); } } } } } } }