/// <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 void Query(string host, int port, Socket socket) { if (host == null) { throw new System.ArgumentNullException("host"); } if (socket == null) { throw new System.ArgumentNullException("socket"); } if (port < 1) { throw new System.ArgumentNullException("Port value must be >= 1 !"); } if (socket.ProtocolType != ProtocolType.Udp) { throw new System.ArgumentNullException("Socket must be UDP socket !"); } IPEndPoint remoteEndPoint = new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0], port); socket.ReceiveTimeout = 3000; socket.SendTimeout = 3000; // Test I STUN_Message test1 = new STUN_Message(); test1.Type = STUN_MessageType.BindingRequest; transaction.doTransaction(test1, socket, remoteEndPoint); transactionFunc = test1ResponeFunc; timer.enabled = true; }
public void doTransaction(STUN_Message pRequest) { doTransaction(pRequest, socket, remoteEndPoint); }
public void doTransaction(STUN_Message pRequest, Socket pSocket, IPEndPoint pRemoteEndPoint) { request = pRequest; socket = pSocket; remoteEndPoint = pRemoteEndPoint; startTime = Time.realtimeSinceStartup; socket.SendTo(requestBytes, remoteEndPoint); }
TransactionFunc transactionRespone(STUN_Message pRequest,TransactionFunc pFunc ) { transaction.doTransaction(pRequest); return pFunc; }
public STUN_Message continueTransaction() { 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; } } return null; }
TransactionFunc test3ResponeFunc(STUN_Message pRequest) { // Restricted if (pRequest != null) { return succeedRespone( new STUN_Result(STUN_NetType.RestrictedCone, test1ResponeMessage.MappedAddress)); } // Port restricted else { return succeedRespone( new STUN_Result(STUN_NetType.PortRestrictedCone, test1ResponeMessage.MappedAddress)); } }
TransactionFunc testI_IIResponeFunc(STUN_Message pRequest) { if (pRequest == null) { Debug.LogError("STUN Test I(II) dind't get resonse !"); stunFail(); return null; } else { // Symmetric NAT if (!test1ResponeMessage.MappedAddress.Equals(pRequest.MappedAddress)) { return succeedRespone( new STUN_Result(STUN_NetType.Symmetric, pRequest.MappedAddress)); } else { // Test III STUN_Message test3 = new STUN_Message(); test3.Type = STUN_MessageType.BindingRequest; test3.ChangeRequest = new STUN_t_ChangeRequest(false, true); return transactionRespone(test3, test3ResponeFunc); } } }
TransactionFunc noNATResponeFunc(STUN_Message pRequest) { STUN_Result lResult; // Open Internet. if (pRequest != null) { lResult = new STUN_Result(STUN_NetType.OpenInternet, pRequest.MappedAddress); } // Symmetric UDP firewall. else { lResult = new STUN_Result(STUN_NetType.SymmetricUdpFirewall, pRequest.MappedAddress); } return succeedRespone(lResult); }
TransactionFunc test1ResponeFunc(STUN_Message test1response) { // UDP blocked. if (test1response == null) { result = new STUN_Result(STUN_NetType.UdpBlocked, null); stunFail(); return null; } else { test1ResponeMessage = test1response; // Test II STUN_Message test2 = new STUN_Message(); test2.Type = STUN_MessageType.BindingRequest; test2.ChangeRequest = new STUN_t_ChangeRequest(true, true); // No NAT. if (transaction.socket.LocalEndPoint.Equals(test1response.MappedAddress)) { return transactionRespone(test2, noNATResponeFunc); } // NAT else { return transactionRespone(test2, NATResponeFunc); } } }
TransactionFunc NATResponeFunc(STUN_Message pRequest) { // Full cone NAT. if (pRequest != null) { return succeedRespone( new STUN_Result(STUN_NetType.FullCone, pRequest.MappedAddress)); } else { STUN_Message test12 = new STUN_Message(); test12.Type = STUN_MessageType.BindingRequest; return transactionRespone(test12, testI_IIResponeFunc); } }
/// <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); /* 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{ // Test I STUN_Message test1 = new STUN_Message(); test1.Type = STUN_MessageType.BindingRequest; STUN_Message test1response = DoTransaction(test1,socket,remoteEndPoint,1600); // 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,1600); // 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,1600); // 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,1600); 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,1600); // 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); } } } } } } } 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); } } } }
/// <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> /// 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.Dns.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); } } } }
/// <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; }