/// <summary> /// Does STUN transaction. Returns transaction response or null if transaction failed. /// </summary> /// <param name="request">STUN message.</param> /// <param name="socket">Socket to use for send/receive.</param> /// <param name="remoteEndPoint">Remote end point.</param> /// <param name="timeout">Timeout in milli seconds.</param> /// <returns>Returns transaction response or null if transaction failed.</returns> private static STUN_Message DoTransaction(STUN_Message request, Socket socket, IPEndPoint remoteEndPoint, int timeout) { byte[] requestBytes = request.ToByteData(); DateTime startTime = DateTime.Now; // Retransmit with 500 ms. while (startTime.AddMilliseconds(timeout) > DateTime.Now) { try{ socket.SendTo(requestBytes, remoteEndPoint); // We got response. if (socket.Poll(500 * 1000, SelectMode.SelectRead)) { byte[] receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); // Parse message STUN_Message response = new STUN_Message(); response.Parse(receiveBuffer); // Check that transaction ID matches or not response what we want. if (Net_Utils.CompareArray(request.TransactionID, response.TransactionID)) { return(response); } } } catch { } } return(null); }
/// <summary> /// Does STUN transaction. Returns transaction response or null if transaction failed. /// </summary> /// <param name="request">STUN message.</param> /// <param name="socket">Socket to use for send/receive.</param> /// <param name="remoteEndPoint">Remote end point.</param> /// <returns>Returns transaction response or null if transaction failed.</returns> private static STUN_Message DoTransaction(STUN_Message request, Socket socket, IPEndPoint remoteEndPoint) { byte[] requestBytes = request.ToByteData(); DateTime startTime = DateTime.Now; // We do it only 2 sec and retransmit with 100 ms. while (startTime.AddSeconds(2) > DateTime.Now) { try{ socket.SendTo(requestBytes, remoteEndPoint); // We got response. if (socket.Poll(100, SelectMode.SelectRead)) { byte[] receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); // Parse message STUN_Message response = new STUN_Message(); response.Parse(receiveBuffer); // Check that transaction ID matches or not response what we want. if (request.TransactionID.Equals(response.TransactionID)) { return(response); } } } catch { } } return(null); }
/// <summary> /// Resolves socket local end point to public end point. /// </summary> /// <param name="stunServer">STUN server.</param> /// <param name="port">STUN server port. Default port is 3478.</param> /// <param name="socket">UDP socket to use.</param> /// <returns>Returns public IP end point.</returns> /// <exception cref="ArgumentNullException">Is raised when <b>stunServer</b> or <b>socket</b> is null reference.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> /// <exception cref="IOException">Is raised when no connection to STUN server.</exception> public static IPEndPoint GetPublicEP(string stunServer, int port, Socket socket) { if (stunServer == null) { throw new ArgumentNullException("stunServer"); } if (stunServer == "") { throw new ArgumentException("Argument 'stunServer' value must be specified."); } if (port < 1) { throw new ArgumentException("Invalid argument 'port' value."); } if (socket == null) { throw new ArgumentNullException("socket"); } if (socket.ProtocolType != ProtocolType.Udp) { throw new ArgumentException("Socket must be UDP socket !"); } IPEndPoint remoteEndPoint = new IPEndPoint(System.Net.Dns2.GetHostAddresses(stunServer)[0], port); try{ // Test I STUN_Message test1 = new STUN_Message(); test1.Type = STUN_MessageType.BindingRequest; STUN_Message test1response = DoTransaction(test1, socket, remoteEndPoint, 1000); // UDP blocked. if (test1response == null) { throw new IOException("Failed to STUN public IP address. STUN server name is invalid or firewall blocks STUN."); } return(test1response.SourceAddress); } catch { throw new IOException("Failed to STUN public IP address. STUN server name is invalid or firewall blocks STUN."); } finally{ // Junk all late responses. DateTime startTime = DateTime.Now; while (startTime.AddMilliseconds(200) > DateTime.Now) { // We got response. if (socket.Poll(1, SelectMode.SelectRead)) { byte[] receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); } } } }
public static IPEndPoint ParseReflection(byte[] receiveBuffer) { try { STUN_Message response = new STUN_Message(); response.Parse(receiveBuffer); return(response.ReflectedFrom); } catch { return(null); } }
/// <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> public static STUN_Result 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 STUN_Message test1 = new STUN_Message(); test1.Type = STUN_MessageType.BindingRequest; STUN_Message test1response = DoTransaction(test1, socket, remoteEndPoint); // UDP blocked. if (test1response == null) { return(new STUN_Result(STUN_NetType.UdpBlocked, null)); } else { // Test II STUN_Message test2 = new STUN_Message(); test2.Type = STUN_MessageType.BindingRequest; test2.ChangeRequest = new STUN_t_ChangeRequest(true, true); // No NAT. if (socket.LocalEndPoint.Equals(test1response.MappedAddress)) { STUN_Message test2Response = DoTransaction(test2, socket, remoteEndPoint); // Open Internet. if (test2Response != null) { return(new STUN_Result(STUN_NetType.OpenInternet, test1response.MappedAddress)); } // Symmetric UDP firewall. else { return(new STUN_Result(STUN_NetType.SymmetricUdpFirewall, test1response.MappedAddress)); } } // NAT else { STUN_Message test2Response = DoTransaction(test2, socket, remoteEndPoint); // Full cone NAT. if (test2Response != null) { return(new STUN_Result(STUN_NetType.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) STUN_Message test12 = new STUN_Message(); test12.Type = STUN_MessageType.BindingRequest; STUN_Message 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 STUN_Result(STUN_NetType.Symmetric, test1response.MappedAddress)); } else { // Test III STUN_Message test3 = new STUN_Message(); test3.Type = STUN_MessageType.BindingRequest; test3.ChangeRequest = new STUN_t_ChangeRequest(false, true); STUN_Message test3Response = DoTransaction(test3, socket, test1response.ChangedAddress); // Restricted if (test3Response != null) { return(new STUN_Result(STUN_NetType.RestrictedCone, test1response.MappedAddress)); } // Port restricted else { return(new STUN_Result(STUN_NetType.PortRestrictedCone, test1response.MappedAddress)); } } } } } } }
/// <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> public static STUN_Result 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(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 STUN_Message test1 = new STUN_Message(); test1.Type = STUN_MessageType.BindingRequest; STUN_Message test1response = DoTransaction(test1, socket, remoteEndPoint); // UDP blocked. if (test1response == null) { return new STUN_Result(STUN_NetType.UdpBlocked, null); } else { // Test II STUN_Message test2 = new STUN_Message(); test2.Type = STUN_MessageType.BindingRequest; test2.ChangeRequest = new STUN_t_ChangeRequest(true, true); // No NAT. if (socket.LocalEndPoint.Equals(test1response.MappedAddress)) { STUN_Message test2Response = DoTransaction(test2, socket, remoteEndPoint); // Open Internet. if (test2Response != null) { return new STUN_Result(STUN_NetType.OpenInternet, test1response.MappedAddress); } // Symmetric UDP firewall. else { return new STUN_Result(STUN_NetType.SymmetricUdpFirewall, test1response.MappedAddress); } } // NAT else { STUN_Message test2Response = DoTransaction(test2, socket, remoteEndPoint); // Full cone NAT. if (test2Response != null) { return new STUN_Result(STUN_NetType.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) STUN_Message test12 = new STUN_Message(); test12.Type = STUN_MessageType.BindingRequest; STUN_Message 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 STUN_Result(STUN_NetType.Symmetric, test1response.MappedAddress); } else { // Test III STUN_Message test3 = new STUN_Message(); test3.Type = STUN_MessageType.BindingRequest; test3.ChangeRequest = new STUN_t_ChangeRequest(false, true); STUN_Message test3Response = DoTransaction(test3, socket, test1response.ChangedAddress); // Restricted if (test3Response != null) { return new STUN_Result(STUN_NetType.RestrictedCone, test1response.MappedAddress); } // Port restricted else { return new STUN_Result(STUN_NetType.PortRestrictedCone, test1response.MappedAddress); } } } } } } }
/// <summary> /// Does STUN transaction. Returns transaction response or null if transaction failed. /// </summary> /// <param name="request">STUN message.</param> /// <param name="socket">Socket to use for send/receive.</param> /// <param name="remoteEndPoint">Remote end point.</param> /// <returns>Returns transaction response or null if transaction failed.</returns> private static STUN_Message DoTransaction(STUN_Message request, Socket socket, IPEndPoint remoteEndPoint) { byte[] requestBytes = request.ToByteData(); DateTime startTime = DateTime.Now; // We do it only 2 sec and retransmit with 100 ms. while (startTime.AddSeconds(2) > DateTime.Now) { try { socket.SendTo(requestBytes, remoteEndPoint); // We got response. if (socket.Poll(100, SelectMode.SelectRead)) { byte[] receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); // Parse message STUN_Message response = new STUN_Message(); response.Parse(receiveBuffer); // Check that transaction ID matches or not response what we want. if (request.TransactionID.Equals(response.TransactionID)) { return response; } } } catch {} } return null; }
public static STUN_Result TestStun(string host, int port, Socket socket) { if (string.IsNullOrEmpty(host)) { throw new ArgumentNullException(nameof(host)); } if (socket == null) { throw new ArgumentNullException(nameof(socket)); } if (port < 1 || port > 65535) { throw new ArgumentException("Port value must be >= 1 and <= 65535!"); } if (socket.ProtocolType != ProtocolType.Udp) { throw new ArgumentException("Socket must be UDP socket !"); } /* * 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 */ try { var remoteEndPoint = new IPEndPoint(Dns.GetHostAddresses(host)[0], port); var stunTest1 = new STUN_Message { Type = STUN_MessageType.BindingRequest }; STUN_Message test1response = DoTransaction(stunTest1, socket, remoteEndPoint, 1600); // UDP blocked. if (test1response == null) { return(new STUN_Result(STUN_NetType.UdpBlocked, null)); } var stunTest2 = new STUN_Message { Type = STUN_MessageType.BindingRequest, ChangeRequest = new STUN_t_ChangeRequest(true, true) }; // No NAT. if (socket.LocalEndPoint.Equals(test1response.MappedAddress)) { STUN_Message test2Response = DoTransaction(stunTest2, socket, remoteEndPoint, 1600); // Open Internet. if (test2Response != null) { return(new STUN_Result(STUN_NetType.OpenInternet, test1response.MappedAddress)); } // Symmetric UDP firewall. return(new STUN_Result(STUN_NetType.SymmetricUdpFirewall, test1response.MappedAddress)); } // NAT else { STUN_Message test2Response = DoTransaction(stunTest2, socket, remoteEndPoint, 1600); // Full cone NAT. if (test2Response != null) { return(new STUN_Result(STUN_NetType.FullCone, test1response.MappedAddress)); } /* * 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) STUN_Message test12 = new STUN_Message(); test12.Type = STUN_MessageType.BindingRequest; STUN_Message test12Response = DoTransaction(test12, socket, test1response.ChangedAddress, 1600); if (test12Response == null) { throw new Exception("STUN Test I(II) dind't get resonse !"); } // Symmetric NAT if (!test12Response.MappedAddress.Equals(test1response.MappedAddress)) { return(new STUN_Result(STUN_NetType.Symmetric, test1response.MappedAddress)); } // Test III STUN_Message test3 = new STUN_Message(); test3.Type = STUN_MessageType.BindingRequest; test3.ChangeRequest = new STUN_t_ChangeRequest(false, true); STUN_Message test3Response = DoTransaction(test3, socket, test1response.ChangedAddress, 1600); // Restricted if (test3Response != null) { return(new STUN_Result(STUN_NetType.RestrictedCone, test1response.MappedAddress)); } // Port restricted return(new STUN_Result(STUN_NetType.PortRestrictedCone, test1response.MappedAddress)); } } finally { // Junk all late responses. DateTime startTime = DateTime.Now; while (startTime.AddMilliseconds(200) > DateTime.Now) { // We got response. if (socket.Poll(1, SelectMode.SelectRead)) { byte[] receiveBuffer = new byte[512]; socket.Receive(receiveBuffer); } } } }