Click here to Skip to main content
15,896,557 members
Articles / Programming Languages / C#

STUN Client

Rate me:
Please Sign up or sign in to vote.
4.83/5 (36 votes)
20 Apr 2007CPOL 330.6K   14.9K   85  
STUN client C# implementation with sample application
using System;
using System.Collections.Generic;
using System.Text;
using System.Timers;

using LumiSoft.Net.AUTH;
using LumiSoft.Net.SIP.Header;

namespace LumiSoft.Net.SIP
{
    /// <summary>
    /// Implements SIP registrar server. Defined in rfc 3261.
    /// </summary>
    public class SIP_Registrar
    {        
        private SIP_ServerProxyCore        m_pProxy         = null;
        private SIP_RegistrationCollection m_pRegistrations = null;
        private Timer                      m_pTimer         = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="proxy">Reference to owner proxy.</param>
        public SIP_Registrar(SIP_ServerProxyCore proxy)
        {
            m_pProxy = proxy;

            m_pRegistrations = new SIP_RegistrationCollection();

            m_pTimer = new Timer(15000);
            m_pTimer.Elapsed += new ElapsedEventHandler(m_pTimer_Elapsed);
            m_pTimer.Enabled = true;
        }


        #region method m_pTimer_Elapsed

        private void m_pTimer_Elapsed(object sender,ElapsedEventArgs e)
        {
            m_pRegistrations.RemoveExpired();
        }

        #endregion


        #region method GetReferedContact

        /// <summary>
        /// Gets specified registration prefered contact. Returns null if no such registration or no contacts available.
        /// </summary>
        /// <param name="registrationName">Registration name.</param>
        /// <returns></returns>
        public SIP_RegistrationContact GetReferedContact(string registrationName)
        {
            // Try to contact recipient.
            SIP_Registration r = this[registrationName];
            if(r != null){
                return r.GetPrefferedContact();
            }

            return null;
        }

        #endregion


        #region mehtod Register

        /// <summary>
        /// Handles REGISTER method.
        /// </summary>
        /// <param name="request">SIP REGISTER request.</param>
        public void Register(SIP_Request request)
        {
            /* RFC 3261 10.3 Processing REGISTER Requests.
                1. The registrar inspects the Request-URI to determine whether it
                    has access to bindings for the domain identified in the Request-URI.
                      
                2. To guarantee that the registrar supports any necessary extensions, 
                   the registrar MUST process the Require header field.
                     
                3. A registrar SHOULD authenticate the UAC.
                     
                4. The registrar SHOULD determine if the authenticated user is
                   authorized to modify registrations for this address-of-record.
                     
                5. The registrar extracts the address-of-record from the To header
                   field of the request.  If the address-of-record is not valid
                   for the domain in the Request-URI, the registrar MUST send a
                   404 (Not Found) response and skip the remaining steps.
                     
                6. The registrar checks whether the request contains the Contact
                   header field.  If not, it skips to the last step.  If the
                   Contact header field is present, the registrar checks if there
                   is one Contact field value that contains the special value "*"
                   and an Expires field.  If the request has additional Contact
                   fields or an expiration time other than zero, the request is
                   invalid, and the server MUST return a 400 (Invalid Request).
                     
                7. The registrar now processes each contact address in the Contact
                   eader field in turn.
                     
                   If Expire paremeter specified, check that it isnt smaller than server minimum
                   allowed expire value. If smaller return error 423 (Interval Too Brief) and add 
                   header field Min-Expires. If no expire parameter, user server default value.                          
                     
                8. The registrar returns a 200 (OK) response.  The response MUST contain Contact 
                   header field values enumerating all current bindings. Each Contact value MUST 
                   feature an "expires" parameter indicating its expiration interval chosen by the
                   registrar. The response SHOULD include a Date header field.

            */

            SocketEx socket = request.Socket;

            // 3. ------------------------------------------------------------------------- 
            // User didn't supplied credentials.
            if(request.Authorization.Count == 0){
                SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
                
                // Send response
                m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse);
                return;
            }
                        
            // Check that client supplied supported authentication method.
            string authenticationData = "";
            // digest
            if(request.Authorization.GetFirst().ValueX.Method.ToLower() == "digest"){
                authenticationData = request.Authorization.GetFirst().ValueX.AuthData;
            }
            // Not supported authentication.
            else{
                m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.Not_Implemented + " authentication method"));
                return;
            }
       
            Auth_HttpDigest auth = new Auth_HttpDigest(authenticationData,request.Method);
            // Check nonce validity
            if(!m_pProxy.Stack.DigestNonceManager.NonceExists(auth.Nonce)){
                SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");

                // Send response
                m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse);
                return;
            }
            // Valid nonce, consume it so that nonce can't be used any more. 
            else{
                m_pProxy.Stack.DigestNonceManager.RemoveNonce(auth.Nonce);
            }

            SIP_ServerProxyCore.AuthenticateEventArgs eArgs = m_pProxy.OnAuthenticate(auth);
            // Authenticate failed.
            if(!eArgs.Authenticated){
                SIP_Response notAuthenticatedResponse = request.CreateResponse(SIP_ResponseCodes.Unauthorized);
                notAuthenticatedResponse.WWWAuthenticate.Add("digest realm=\"\",qop=\"auth\",nonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"");
                
                // Send response
                m_pProxy.Stack.TransportLayer.SendResponse(socket,notAuthenticatedResponse);
                return;
            }
            //----------------------------------------------------------------------------

            // 5. Get TO:.
            string registrationName = SipUtils.ParseAddress(request.To.ToStringValue()).ToLower();

            // 4. Check if user can register specified address.
            // User is not allowed to register specified address.
            if(!m_pProxy.OnCanRegister(auth.UserName,registrationName)){
                m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.Forbidden));
                return;
            }
                                        
            //--- 6. And 7. --------------------------------------------------------
            SIP_t_ContactParam[] contacts = request.Contact.GetAllValues();
            if(request.Header.Contains("Contact:")){                
                SIP_t_ContactParam starContact = null;
                foreach(SIP_t_ContactParam contact in contacts){
                    // We have STAR contact, store it.
                    if(starContact == null && contact.IsStarContact){
                        starContact = contact;                               
                    }

                    //--- Handle minimum expires time --------------------------------------------
                    // Get contact expires time, if not specified, get header expires time.
                    int expires = contact.Expires;
                    if(expires < 1){
                        expires = request.Expires;
                    }
                    // We don't check that for STAR contact and if contact expires parameter = 0.
                    if(!contact.IsStarContact && contact.Expires != 0 && expires < m_pProxy.Stack.MinimumExpireTime){
                        // RFC 3261 20.23 must add Min-Expires
                        SIP_Response sipExpiresResponse = request.CreateResponse(SIP_ResponseCodes.Interval_Too_Brief);

                        // The response SHOULD include a Date header field.
                        sipExpiresResponse.Header.Add("Date:",DateTime.Now.ToString("r"));
                                               
                        // Send response
                        m_pProxy.Stack.TransportLayer.SendResponse(socket,sipExpiresResponse);
                        return;
                    }
                    //---------------------------------------------------------------------------
                }

                // We have STAR contact. Check that STAR contact meets all RFC rules.
                if(starContact != null){
                    // We may have only 1 STAR Contact and expires must be 0.
                    if(contacts.Length > 1 || starContact.Expires != 0){                        
                        m_pProxy.Stack.TransportLayer.SendResponse(socket,request.CreateResponse(SIP_ResponseCodes.Bad_Request + ". Invalid STAR Contact: combination or parameter. For more info see RFC 3261 10.3.6."));
                        return;
                    }
                    // We have valid STAR Contact:.
                    //else{
                    //}
                }
            }
                    
            // Add or get SIP registration
            SIP_Registration registration = null;
            lock(m_pRegistrations){
                if(!m_pRegistrations.Contains(registrationName)){
                    // Add SIP registration.
                    registration = new SIP_Registration(auth.UserName,registrationName);
                    m_pRegistrations.Add(registration);
                }
                // Update SIP registration contacts
                else{
                    registration = m_pRegistrations[registrationName];
                }
            }

            // Update registration contacts
            registration.UpdateContacts(contacts,request.Expires);
            //--------------------------------------------------------------------

            // 8. --- Make and send SIP respone ----------------------------------
            SIP_Response sipResponse = request.CreateResponse(SIP_ResponseCodes.Ok);
            
            // The response SHOULD include a Date header field.
            sipResponse.Date = DateTime.Now;

            // List Registered Contacts
            sipResponse.Header.RemoveAll("Contact:");
            foreach(SIP_RegistrationContact contact in registration.Contacts){
                sipResponse.Header.Add("Contact:",contact.Contact.ToStringValue());
            }

            // Add Authentication-Info:, then client knows next nonce.
            sipResponse.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pProxy.Stack.DigestNonceManager.CreateNonce() + "\"");
                                               
            // Send response
            m_pProxy.Stack.TransportLayer.SendResponse(socket,sipResponse);
            //-----------------------------------------------------------------------
        }

        #endregion


        #region Properties Implementaion

        /// <summary>
        /// Gets current SIP registrations.
        /// </summary>
        public SIP_Registration[] Registrations
        {
            get{
                lock(m_pRegistrations){
                    SIP_Registration[] retVal = new SIP_Registration[m_pRegistrations.Count];
                    m_pRegistrations.Values.CopyTo(retVal,0);

                    return retVal;
                }
            }
        }

        /// <summary>
        /// Gets SIP registration with specified registration name. Returns null if specified registration doesn't exist.
        /// </summary>
        /// <param name="registrationName">SIP registration name.</param>
        /// <returns></returns>
        public SIP_Registration this[string registrationName]
        {
            get{ return m_pRegistrations[registrationName.ToLower()]; }
        }

        #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)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions