Click here to Skip to main content
Click here to Skip to main content
Articles » Languages » C# » General » Downloads
 
Add your own
alternative version

STUN Client

, 20 Apr 2007 CPOL
STUN client C# implementation with sample application
Stun.zip
Stun
Net
docs
dns
dns_records.jpg
dns_records.vsd
Net
_junk
_Obsolete
_Stuff
AUTH
bin
Release
LumiSoft.Net.dll
Data
Dns
Client
FTP
Client
Server
HTTP
Server
ICMP
IMAP
Client
Server
IO
Log
LumiSoft.Net
Mime
vCard
Net.csproj.user
Net.suo
NNTP
Client
POP3
Client
Server
SDP
ServersCore
SIP
Client
Message
Proxy
Stack
SMTP
Client
Server
STUN
Client
Message
STUN_Client_app
STUN_Client_app.suo
STUN_Client_app
bin
Debug
LumiSoft.Net.dll
STUN_Client_app.exe
STUN_Client_app.vshost.exe
dep
LumiSoft.Net.dll
Properties
Settings.settings
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

using LumiSoft.Net.STUN.Message;

namespace LumiSoft.Net.STUN.Client
{
    /// <summary>
    /// This class implements STUN client. Defined in RFC 3489.
    /// </summary>
    /// <example>
    /// <code>
    /// // Create new socket for STUN client.
    /// Socket socket = new Socket(AddressFamily.InterNetwork,SocketType.Dgram,ProtocolType.Udp);
    /// socket.Bind(new IPEndPoint(IPAddress.Any,0));
    /// 
    /// // Query STUN server
    /// STUN_Result result = STUN_Client.Query("stunserver.org",3478,socket);
    /// if(result.NetType != STUN_NetType.UdpBlocked){
    ///     // UDP blocked or !!!! bad STUN server
    /// }
    /// else{
    ///     IPEndPoint publicEP = result.PublicEndPoint;
    ///     // Do your stuff
    /// }
    /// </code>
    /// </example>
    public class STUN_Client
    {
        #region static method Query

        /// <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);
                                }
                            }
                        }
                    }
                }
            }
        }

        #endregion


        #region method GetSharedSecret

        private void GetSharedSecret()
        {
            /*
                *) Open TLS connection to STUN server.
                *) Send Shared Secret request.
            */

            /*
            using(SocketEx socket = new SocketEx()){
                socket.RawSocket.ReceiveTimeout = 5000;
                socket.RawSocket.SendTimeout = 5000;

                socket.Connect(host,port);
                socket.SwitchToSSL_AsClient();                

                // Send Shared Secret request.
                STUN_Message sharedSecretRequest = new STUN_Message();
                sharedSecretRequest.Type = STUN_MessageType.SharedSecretRequest;
                socket.Write(sharedSecretRequest.ToByteData());
                
                // TODO: Parse message

                // We must get  "Shared Secret" or "Shared Secret Error" response.

                byte[] receiveBuffer = new byte[256];
                socket.RawSocket.Receive(receiveBuffer);

                STUN_Message sharedSecretRequestResponse = new STUN_Message();
                if(sharedSecretRequestResponse.Type == STUN_MessageType.SharedSecretResponse){
                }
                // Shared Secret Error or Unknown response, just try again.
                else{
                    // TODO: Unknown response
                }
            }*/
        }

        #endregion

        #region method DoTransaction

        /// <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;
        }

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ivar Lumi

Estonia Estonia
No Biography provided

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150123.1 | Last Updated 20 Apr 2007
Article Copyright 2007 by Ivar Lumi
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid