• Testing Lync server 2013 external URLs from Lync clients (external web service, meet, office webapps, lyncdiscover and dialin)

    Hi there,

     

    In this blog post, I’ll be talking about another powershell based tool which could be used to check name resolution, TCP layer connectivity and SSL/TLS negotiation for various URLs that are accessible through your reverse proxy which are:

     

    - External web services URL

    - Meet URL

    - Office WebApps URL

    - Lyncdiscover URL

    - Dialin URL

     

    When the tool is run, you can see if the given URL is successfully resolved to a name. If name resolution succeeds, a TCP session is attempted to the target and if the TCP session successfully created an SSL/TLS negotiation is done with the target URL. And if that succeeds as well, the subject names and subject alternative names in the returned certificate are dumped by the tool. Also expiration time of each certificate is dumped.

     

    You’ll have to provide each URL manually (actually I was planning to read all such details from topology file removing the requirement of manual input but due to some issues I decided to enter the information manual for the time being).

     

    Such URL details could be easily obtained from your topology file:

     

    - External Web services FQDN:


     

    - Meet URL and Dialin URL:


     

    - Office WebApps URL:


     

    - Lyncdiscover URL:

    Lyncdiscover URL is built by adding “lyncdiscover” to the beginning of sip domain. Examples:

     

    lyncdiscover.contoso.com

    lyncdiscover.fabrikam.com

     

    => Here is the full Powershell script source:

     

     

    #------------SCRIPT STARTS HERE-----------

     

     

    # ResolveLyncNames() resolves various Lync names to IP addresses

     

    function ResolveLyncNames {

     

    param($LyncFqdn)

     

    try {

    $ipaddr   = [System.Net.Dns]::GetHostAddresses($LyncFqdn)

    Write-Host "Successfully resolved $LyncFqdn to $ipaddr" -ForegroundColor Green

    return 1

    }

     

    catch {

     

    $exception = New-Object system.net.sockets.socketexception

    $errorcode = $exception.ErrorCode

    write-host "Name resolution failed, error code:$errorcode" -foregroundcolor Red

    write-host "Error details: $exception" -foregroundcolor Red

    return 0

    }

     

     

     

    }

     

     

    # TLSConnectionCheck() establishes TLS session to remote host at the given TCP port

     

    function TLSConnectionCheck  {

     

    param ($remotehost,$remoteport)

     

    try {

     

    $TCPConnection = New-Object System.Net.Sockets.Tcpclient($remotehost, $remoteport)

    Write-Host "TCP connection has succeeded" -ForegroundColor Green

    $TCPStream     = $TCPConnection.GetStream()

     

     

    try {

     

    $SSLStream     = New-Object System.Net.Security.SslStream($TCPStream)

    Write-Host "SSL connection has succeeded" -ForegroundColor Green

     

    try {

    $SSLStream.AuthenticateAsClient($remotehost)

    Write-Host "SSL authentication has succeeded" -ForegroundColor Green

    }

    catch {

    Write-Host "There's a problem with SSL authentication to $remotehost" -ForegroundColor Red

    return

    }

     

    $certificate = $SSLStream.get_remotecertificate()

     

    $certificateX509 = New-Object system.security.cryptography.x509certificates.x509certificate2($certificate)

    $SANextensions = New-Object system.security.cryptography.x509certificates.x509Certificate2Collection($certificateX509)

    $SANextensions = $SANextensions.Extensions | Where-Object {$_.Oid.FriendlyName -eq "subject alternative name"}

     

    Write-Host "=> Remote host certificate details:"

     

    Write-Host "Issuer:          " $SSLStream.RemoteCertificate.Issuer -ForegroundColor Green

    Write-Host "Subject name:    " $SSLStream.RemoteCertificate.Subject -ForegroundColor Green

    Write-Host "Expiration time: " $SSLStream.RemoteCertificate.GetExpirationDateString() -ForegroundColor Green

    Write-Host "Serial number:   " $SSLStream.RemoteCertificate.GetSerialNumberString() -ForegroundColor Green

    Write-Host "=> Subject alternative names:          "

    $SANextensions.Format(1)

     

    Write-Host "Please make sure that Subject name and Subject alternative names in the certificate are compatible with the names given in http://technet.microsoft.com/en-us/library/gg429704.aspx" -BackgroundColor DarkCyan

    Write-Host "Wildcard names in SAN are supported for meet, dialin and Lyncdiscover URLs. Please see http://technet.microsoft.com/en-us/library/hh202161.aspx for more details" -BackgroundColor DarkCyan

     

    }

     

    catch {

     

    Write-Host "$remotehost doesn't support SSL connections at TCP port $remoteport" -foregroundcolor Red

     

    }

     

    }

     

    catch {

     

    $exception = New-Object system.net.sockets.socketexception

    $errorcode = $exception.ErrorCode

    write-host "TCP connection to $remotehost failed, error code:$errorcode" -foregroundcolor Red

    write-host "Error details: $exception" -foregroundcolor Red

     

     

    }

    }

     

     

     

    #

    # TESTING

    #

     

     

    write-host "Please enter your external Web services FQDN. This could be seen from your Lync topology. You can skip it by pressing Enter. Example: webext.contoso.com" -ForegroundColor Green

    $LyncExternalWebserviceFqdn = Read-Host

    write-host "Please enter your meet URL. This could be seen from your Lync topology. You can skip it by pressing Enter. Example: meet.contoso.com" -ForegroundColor Green

    $SimpleURL_meet             = Read-Host

    write-host "Please enter your Office WebApp server FQDN. This could be seen from your Lync topology. You can skip it by pressing Enter. Example: wac.contoso.com" -ForegroundColor Green

    $LyncWac                    = Read-Host

    write-host "Please enter your Lyncdiscover URL. You can skip it by pressing Enter. Example: lyncdiscover.contoso.com" -ForegroundColor Green

    $SimpleURl_lyncdiscover     = Read-Host

    write-host "Please enter your dialin URL. This could be seen from your Lync topology. You can skip it by pressing Enter. Example: dialin.contoso.com" -ForegroundColor Green

    $SimpleURL_dialin           = Read-Host

     

     

     

    # ==============================================================================================

    # Resolve and access external web service fqdn

    # ==============================================================================================

     

    Write-Host "================================================================================================================"

    Write-Host "TEST 1: External Web service FQDN name resolution and access tests" -ForegroundColor yellow

    Write-Host "================================================================================================================"

     

     

    $LyncExternalWebserviceFqdnport = 443

     

     

    If([string]::IsNullOrEmpty($LyncExternalWebserviceFqdn)) {

     

     Write-Host "External Web services URL is NULL. Bypassing External web services accessibility check"

     

    }

     

     

    else  {

     

     

    Write-Host "=> Attempting to resolve External Web Service fqdn ("$LyncExternalWebserviceFqdn" )"

    if(ResolveLyncNames($LyncExternalWebserviceFqdn)) {

     

    Write-Host "=> Attempting to connect to External Web Service fqdn ("$LyncExternalWebserviceFqdn" ) at TCP port $LyncExternalWebserviceFqdnport"

    TLSConnectionCheck $LyncExternalWebserviceFqdn $LyncExternalWebserviceFqdnport

    }

     

     

     

    # ==============================================================================================

    # Resolve and access Meet URL

    # ==============================================================================================

     

    Write-Host "================================================================================================================"

    Write-Host "TEST 2: Meet URL name resolution and access tests" -ForegroundColor yellow

    Write-Host "================================================================================================================"

     

     

    If([string]::IsNullOrEmpty($SimpleURL_meet)) {

     

     Write-Host "Meet URL is NULL. Bypassing Meet URL accessibility check"

     

    }

     

    else {

     

    $SimpleURL_meet_port = 443

     

    Write-Host "=> Attempting to resolve Meet URL ("$SimpleURL_meet" )"

    if(ResolveLyncNames($SimpleURL_meet)) {

     

    Write-Host "=> Attempting to connect to Meet URL ("$SimpleURL_meet" ) at TCP port $SimpleURL_meet_port"

    TLSConnectionCheck $SimpleURL_meet $SimpleURL_meet_port

    }

     

     

    }

     

     

    # ==============================================================================================

    # Resolve and access Office WebApps URL

    # ==============================================================================================

     

     

    Write-Host "================================================================================================================"

    Write-Host "TEST 3: Office WebApps server FQDN name resolution and access tests" -ForegroundColor yellow

    Write-Host "================================================================================================================"

     

     

    $LyncWacport = 443

     

     

    If([string]::IsNullOrEmpty($LyncWac)) {

     

     Write-Host "Office WebApps URL is NULL. Bypassing Office WebApps services accessibility check"

     

    }

     

     

    else  {

     

     

    Write-Host "=> Attempting to resolve Office WebApps server fqdn ("$LyncWac" )"

    if(ResolveLyncNames($LyncWac)) {

     

    Write-Host "=> Attempting to connect to resolve Office WebApps server fqdn ("$LyncWac" ) at TCP port $LyncWacport"

    TLSConnectionCheck $LyncWac $LyncWacport

    }

     

     

     

    # ==============================================================================================

    # Resolve and access Lyncdiscover URL

    # ==============================================================================================

     

    Write-Host "================================================================================================================"

    Write-Host "TEST 4: Lyncdiscover URL name resolution and access tests" -ForegroundColor yellow

    Write-Host "================================================================================================================"

     

     

    If([string]::IsNullOrEmpty($SimpleURl_lyncdiscover)) {

     

     Write-Host "Lyncdiscover URL is NULL. Bypassing Lyncdiscover URL accessibility check"

     

    }

     

    else {

     

     

    $SimpleURL_lyncdiscover_port = 443

     

    Write-Host "=> Attempting to resolve Admin URL ("$SimpleURl_lyncdiscover" )"

    if(ResolveLyncNames($SimpleURl_lyncdiscover)) {

     

    Write-Host "=> Attempting to connect to Admin URL ("$SimpleURl_lyncdiscover" ) at TCP port $SimpleURL_lyncdiscover_port"

    TLSConnectionCheck $SimpleURl_lyncdiscover $SimpleURL_lyncdiscover_port

    }

     

     

    }

     

     

    # ==============================================================================================

    # Resolve and access Dialin URL

    # ==============================================================================================

     

    Write-Host "================================================================================================================"

    Write-Host "TEST 5: Dialin URL name resolution and access tests" -ForegroundColor yellow

    Write-Host "================================================================================================================"

     

     

    If([string]::IsNullOrEmpty($SimpleURL_dialin)) {

     

     Write-Host "Dialin URL is NULL. Bypassing Dialin URL accessibility check"

     

    }

     

    else {

     

    $SimpleURL_dialin_port = 443

     

     

    Write-Host "=> Attempting to resolve Dialin URL ("$SimpleURL_dialin" )"

    if(ResolveLyncNames($SimpleURL_dialin)) {

     

    Write-Host "=> Attempting to connect to Dialin URL ("$SimpleURL_dialin" ) at TCP port $SimpleURL_dialin_port"

    TLSConnectionCheck $SimpleURL_dialin $SimpleURL_dialin_port

    }

     

    }

     

     

    #------------SCRIPT ENDS HERE-----------


    => You can also find a sample output below:



     

    Hope this helps

     

    Thanks,

    Murat

     

  • Testing Exchange EWS service accessibility from internal/external Lync clients

    Hi there,

     

    In this blog post, I’ll be talking about a powershell based tool which could be used to check Exchange EWS connectivity especially from external Lync clients where the client might not be able to access Exchange EWS and hence cannot display meetings and similar.

     

    The powershell tool uses Exchange EWS managed API to connect to EWS service, so you have to install the API to the test client from where you plan to run the script. The library could be downloaded at:

     

    http://www.microsoft.com/en-us/download/details.aspx?id=35371 Microsoft Exchange Web Services Managed API 2.0

     

    => Here is the full Powershell script source:

     

     

    #------------SCRIPT STARTS HERE-----------

     

     

     

            function TestExternalEWS () {

     

            param($emailaddress)

     

            Add-Type -Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll'

         

            write-host "=> Retrieving your credentials for EWS connectivity (Domain\User and password) ..." -ForegroundColor Green

            $autod = New-Object Microsoft.Exchange.WebServices.Autodiscover.AutodiscoverService

           

     

            $Credentials = (Get-Credential)

           

           

           

            $creds=New-Object System.Net.NetworkCredential($Credentials.UserName.ToString(),$Credentials.GetNetworkCredential().password.ToString())

     

            $autod.Credentials=$creds

            $autod.EnableScpLookup = $false

            $autod.RedirectionUrlValidationCallback = {$true}

            $autod.TraceEnabled = $TraceEnabled

           

            write-host "=> Retrieving external EWS URL via Autodiscover service for the given smtp address '$emailaddress' ..." -ForegroundColor Green

            Write-Host

           

           

                    try {

           

                    $response = $autod.GetUserSettings(

                          $emailaddress,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalMailboxServer,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalEcpUrl,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalEwsUrl,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalOABUrl,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalUMUrl,

                          [Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalWebClientUrls

                        )

     

     

                    $ExternalEwsUrl = $response.Settings[[Microsoft.Exchange.WebServices.Autodiscover.UserSettingName]::ExternalEwsUrl]

               

                     

                     if($ExternalEwsUrl -eq $NULL) {

               

                        write-host "=> Successfully contacted Exchange Autodiscover service but couldn't retrieve autodiscovery settings for the given smtp address '$emailaddress'" -ForegroundColor Red

                        write-host "Error code:" $response.errormessage

                        Write-Host 

                        return

                        }

                   

                        else {

                       

                        write-host "=> Successfully contacted Autodiscover service and retrieved the external EWS URL for the given smtp address '$emailaddress'" -ForegroundColor green

                        write-host  $externalEwsUrl -ForegroundColor Magenta

                        write-host

                       

                        }

                   

                   

                   

            

                    }

     

                    catch {

     

     

     

                    write-host "There was an error calling GetUserSettings() function, the error returned:" $_.exception.message -ForegroundColor Red

                    Write-Host

                    return

           

                    }

     

    # Got the EWS URL information, now testing EWS access

     

     

                    write-host "=> Now making a test call (retrieving given user's OOF settings) to Exchange Web Service to test external EWS connectivity against '$externalEwsUrl' ..." -ForegroundColor Green

                    Write-Host

           

                   $service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1)

                   $uri=[system.URI] $ExternalEwsUrl

                   $service.Url = $uri

           

                    try {

                       

                        $oofsettings = $service.GetUserOofSettings($emailaddress)

                        Write-host "=> Successfully retrieved OOF settings." -foreground Green

                        write-host

                        write-host "OOF state: " $oofsettings.State

                        write-host "External OOF reply:"

                        write-host "============================================================================================================================================="

                        write-host $oofsettings.externalreply

                        write-host "============================================================================================================================================="

           

                    }

     

                    catch {

     

                        write-host "There was an error calling GetUserOofSettings() function, the error returned:" $_.exception.message -ForegroundColor Red

                        Write-Host

                    }

           

     

                    }

     

     

                    if(($args[0] -eq $NULL))

     

                    {

     

                    Write-host "Please specify e-mail address to test"

                    Write-Host "Example: testlyncext.ps1 user-email@contoso.com"

                   

                    return

     

                    }

     

     

                   

                    $emailaddress = $args[0]

     

                    TestExternalEWS($emailaddress)

     

     

     

    #------------SCRIPT ENDS HERE-----------

     

     

    You can find a sample output below:

     

     

    Hope this helps

    Thanks,

    Murat

     

  • How it works on the wire: IIS HTTP Client certificate authentication

    I collaborated with a colleague recently where the IE client was failing to authenticate to IIS and I was requested to analyze a few network traces collected while reproducing the issue. The main issue was that the client (IE) wasn’t able to successfully authenticate to IIS server when certificate authentication was configured on IIS side and was getting the HTTP error 403.7.

    First of all, I asked my colleague to setup a lab environment with IIS client certificate authentication configured so that we should be able to see how it works under normal circumstances. After the requested logs were collected, we had a chance to analyze the working traffic. Also I should note that we used server certificate with its private key to decrypt the SSL traffic.

     

    => Here is how it works in summary:

    1) Client establishes an SSL session to the server.

    2) Client then sends the initial HTTP Get request inside this SSL tunnel

    3) Server responds to that request with a new SSL re-negotiate request since client certificate authentication is configured on the IIS server side.

    4) Client establishes a new SSL session inside the original SSL session. This time IIS server also asks the client to send a certificate (IIS server also provides the client with the list of possible CAs from which the client certificate should be obtained)

    5) Client sends its certificate and then the second SSL negotiation finishes successfully and the server returns HTTP 200 response right after the second SSL negotiation is finished successfully.

     

    => The TCP 3-way handshake to the IIS server:

     

    No.     Time                    Delta    Source                Destination           Protocol Ack number Info

         68 2013-12-26 13:03:51.654 0.000    10.10.10.16           10.10.10.15           TCP                 49685 > 443 [SYN] Seq=923564226 Win=8192 Len=0 MSS=1460 WS=4 SACK_PERM=1

         69 2013-12-26 13:03:51.654 0.000    10.10.10.15           10.10.10.16           TCP      923564227  443 > 49685 [SYN, ACK] Seq=4236088449 Ack=923564227 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1

         71 2013-12-26 13:03:51.657 0.003    10.10.10.16           10.10.10.15           TCP      4236088450 49685 > 443 [ACK] Seq=923564227 Ack=4236088450 Win=131400 Len=0

     

    => The initial SSL negotiation between the client and the IIS server:

         72 2013-12-26 13:03:51.657 0.000    10.10.10.16           10.10.10.15           TLSv1    4236088450 Client Hello

    => Please note that IIS server doesn’t ask the client send a certificate at the initial SSL negotiation as there’s no “Certificate request”  in the server response

     

         73 2013-12-26 13:03:51.657 0.000    10.10.10.15           10.10.10.16           TLSv1    923564384  Server Hello, Certificate, Server Hello Done

         74 2013-12-26 13:03:51.660 0.002    10.10.10.16           10.10.10.15           TCP      4236089827 49685 > 443 [ACK] Seq=923564384 Ack=4236089827 Win=130020 Len=0

         75 2013-12-26 13:03:51.660 0.000    10.10.10.16           10.10.10.15           TLSv1    4236089827 Client Key Exchange, Change Cipher Spec, Finished

         76 2013-12-26 13:03:51.664 0.004    10.10.10.15           10.10.10.16           TLSv1    923564710  Change Cipher Spec, Finished

         77 2013-12-26 13:03:51.666 0.002    10.10.10.16           10.10.10.15           TCP      4236089886 49685 > 443 [ACK] Seq=923564710 Ack=4236089886 Win=129964 Len=0

     

    Note: To be able to see traffic in clear text from this point on, you need to decrypt the SSL traffic in a way or another.  I used the method that I mentioned in a previous blog post of mine:

    http://blogs.technet.com/b/nettracer/archive/2013/10/12/decrypting-ssl-tls-sessions-with-wireshark-reloaded.aspx

     

    => The initial GET request (sent inside the SSL channel)

     

         78 2013-12-26 13:03:51.666 0.000    10.10.10.16           10.10.10.15           HTTP     4236089886 GET /1.aspx HTTP/1.1

        

    => Instead of sending an HTTP response back to the client, the IIS server asks the client to re-negotiate SSL:

         79 2013-12-26 13:03:51.666 0.000    10.10.10.15           10.10.10.16           TLSv1    923565216  Hello Request

         80 2013-12-26 13:03:51.668 0.001    10.10.10.16           10.10.10.15           TCP      4236089923 49685 > 443 [ACK] Seq=923565216 Ack=4236089923 Win=131400 Len=0

         81 2013-12-26 13:03:51.668 0.000    10.10.10.16           10.10.10.15           TLSv1    4236089923 Client Hello

    => This time the IIS server requires the client to send a certificate (as opposed to initial SSL negotiation) by adding “Certificate Request” to the response that it sends to client. It also includes the names of the accepted CA names from which a client certificate should be issued:

     

         82 2013-12-26 13:03:51.669 0.000    10.10.10.15           10.10.10.16           TLSv1    923565413  Server Hello, Certificate, Certificate Request, Server Hello Done

     

         83 2013-12-26 13:03:51.676 0.007    10.10.10.16           10.10.10.15           TCP      4236092088 49685 > 443 [ACK] Seq=923565413 Ack=4236092088 Win=131400 Len=0

         84 2013-12-26 13:03:51.684 0.008    10.10.10.16           10.10.10.15           TCP      4236092088 [TCP segment of a reassembled PDU]

     

    => The client chooses an appropriate certificate from its certificate store (it should be a client certificate that should be issued by one of those CAs pointed to by the server and it also should have “client authentication” key usage. And then it sends it to the server within the response packet:

     

         85 2013-12-26 13:03:51.684 0.000    10.10.10.16           10.10.10.15           TLSv1    4236092088 Certificate, Client Key Exchange, Certificate Verify

      

        86 2013-12-26 13:03:51.684 0.000    10.10.10.15           10.10.10.16           TCP      923567204  443 > 49685 [ACK] Seq=4236092088 Ack=923567204 Win=131328 Len=0

     

    => The second SSL negotiation is finished successfully at this point:

     

         87 2013-12-26 13:03:51.691 0.006    10.10.10.15           10.10.10.16           TLSv1    923567204  Change Cipher Spec, Finished

         88 2013-12-26 13:03:51.694 0.002    10.10.10.16           10.10.10.15           TCP      4236092178 49685 > 443 [ACK] Seq=923567204 Ack=4236092178 Win=131308 Len=0

     

    => Then the server returns the response to the initial HTTP Get request:

     

         89 2013-12-26 13:03:51.695 0.001    10.10.10.15           10.10.10.16           HTTP     923567204  HTTP/1.1 200 OK  (text/html)

     

     

    Note: The web page includes the word “Hello”

     

    => Then the client tries to retrieve favicon.ico file which doesn't exist on the server and finally the session is gracefully terminated by the client:

         90 2013-12-26 13:03:51.701 0.005    10.10.10.16           10.10.10.15           TCP      4236092599 49685 > 443 [ACK] Seq=923567204 Ack=4236092599 Win=130888 Len=0

         91 2013-12-26 13:03:51.795 0.094    10.10.10.16           10.10.10.15           HTTP     4236092599 GET /favicon.ico HTTP/1.1

         92 2013-12-26 13:03:51.834 0.039    10.10.10.15           10.10.10.16           HTTP     923567582  HTTP/1.1 404 Not Found  (text/html)

         93 2013-12-26 13:03:51.844 0.009    10.10.10.16           10.10.10.15           TCP      4236094044 49685 > 443 [ACK] Seq=923567582 Ack=4236094044 Win=131400 Len=0

         94 2013-12-26 13:03:51.844 0.000    10.10.10.16           10.10.10.15           TCP      4236094044 49685 > 443 [FIN, ACK] Seq=923567582 Ack=4236094044 Win=131400 Len=0

         95 2013-12-26 13:03:51.844 0.000    10.10.10.15           10.10.10.16           TCP      923567583  443 > 49685 [FIN, ACK] Seq=4236094044 Ack=923567583 Win=130816 Len=0

         96 2013-12-26 13:03:51.851 0.007    10.10.10.16           10.10.10.15           TCP      4236094045 49685 > 443 [ACK] Seq=923567583 Ack=4236094045 Win=131400 Len=0

     

    After that, we collected the same set of logs from the customer environment and we realized that the IIS server wasn’t returning the CA name of which issued the client certificate  to the client in the accepted CAs list even though that root CA certificate existed in the trusted root certificates store on the IIS server. So the IE client wasn’t sending the user certificate located at the user certificate store on its end.

    Then we realized that the amount of root CA names were about 16 KB which seems to be an upper limit. After deleting some of the expired and unused root CAs from the IIS server the issue was resolved. You can find more details on this issue at the below article:

     

    http://support.microsoft.com/kb/933430/EN-US Clients cannot make connections if you require client certificates on a Web site or if you use IAS in Windows Server 2003

     

    The hotfix increases the Schannel security buffer to 16k. If you exceed this limit, you will still have issues that are described in the symptoms section of this article. This change has also been included with Windows Server 2008 and Windows Server 2008 R2. The workarounds described below will apply to Windows Server 2008 and Windows Server 2008 R2 as well.

     

    But we were already hitting the 16 KB limit and hence the only solution seemed to be removing some expired or unused root certificates from the IIS server.

     

    Hope this helps

    Thanks,

    Murat

  • DNS SRV lookup from Powershell

    [After posting this blog, I have realized that Powershell 3.0 already provides a built-in cmdlet called Resolve-DnsName which could help you make any kind of DNS queries. But I still wanted to leave this blog post active for those who might want to see how it could be done with Win32 API. Also Powershell 3.0 is available only on Windows 8/Windows server 2012 and forward which means you still need another way of doing your own SRV queries for earlier OS versions]

     

    Hi there,

     

    While working on another tool that I mentioned in the previous blog post, I needed to lookup SRV records from powershell script. Under normal circumstances, we should be able to call any .Net API through powershell and considering that there’s a Dns class already implemented in the System.net namespace, this shouldn’t be a tough thing. But when you check the Dns class, you’ll see that it doesn’t allow you to make SRV queries:

     

    http://msdn.microsoft.com/en-us/library/system.net.dns_methods.aspx Dns Methods

     

    After some research I have concluded that it might be easier and faster to implement it with Win32 code.  The below code just returns the host part of DNS SRV response as I only needed that part but it’s just a matter of uncommenting the printf() statements in the below code. Here is the full code that could be complied from Visual studio:

     

    // dnssrvclient.cpp : Defines the entry point for the console application.
    //

    #include "stdio.h"
    #include "winsock2.h"
    #include "windows.h"
    #include "windns.h"


    int wmain( int argc, wchar_t *argv[ ], wchar_t *envp[ ] )

    {


    PWSTR resultname;

    PDNS_RECORD ppQueryResultsSet = NULL;

    // ppQueryResultsSet = (PDNS_RECORD) malloc(sizeof(DNS_RECORD));
    // memset(ppQueryResultsSet,'\0',sizeof(DNS_RECORD));

    int retvalue;

    // printf("Sending SRV query for the name '%S'. Please wait...\n",argv[1]);

    retvalue = DnsQuery(
    argv[1],
    DNS_TYPE_SRV,
    DNS_QUERY_BYPASS_CACHE,
    NULL,
    &ppQueryResultsSet,
    NULL

    );
    if(!retvalue)

    {
    resultname = ppQueryResultsSet->Data.SRV.pNameTarget;
    printf("%S",resultname);
    // printf("SRV query succeeded.\n");
    // printf("SRV record name:\t %S \n", resultname);
    // printf("SRV record port:\t %d \n", ppQueryResultsSet->Data.SRV.wPort);
    // printf("SRV record priority:\t %d \n", ppQueryResultsSet->Data.SRV.wPriority);

    return retvalue;
    }

    else
    {

    // printf("The SRV record couldn't be resolved, error code: %d",retvalue);

    return retvalue;
    }

    }

    Note: If you decide to compile the above code, please make sure that Windows Kit (SDK) is already installed on the Visual Studio client and make sure that you add the dnsapi.lib as an additonal options from linker properties.

     

     

    Basically the application calls the DnsQuery() API with parameters stating that it would be an SRV query and the local DNS client cache will be bypassed when resolving the name. Once the result is returned, the application simply returns the Data.SRV.pNameTarget which holds the name part of SRV query response. You can see below an example run of the application code:

     

    C:\> dnssrvclient.exe  _sip._tls.contoso.com

    sip.contoso.com

     

    Note: _sip._tls.contoso.com is the SRV record being queried and sip.contoso.com is the result of this SRV lookup. If the query fails, the application returns a non-zero value

     

    If you need to use the application from a powershell script, you can use a code similar below:

    Note: The powershell code snipet below assumes that dnssrvclient.exe exists within the same folder.

     

    $result = .\dnssrvclient.exe _sip._tls.contoso.com
    if($LASTEXITCODE)

    {

    Write-Host "DNS SRV query failed, error code: $LASTEXITCODE" -ForegroundColor Red

    }

    else

    {

    write-host "DNS SRV query succeeded: $result" -ForegroundColor Green

    }

      

    A successful run will be like below:

     

    DNS SRV query succeeded: sip.contoso.com

     

    When SRV lookup fails, you should a response similar to below:

     

    DNS SRV query failed, error code: 9003

     

    Hope this helps

     

    Thanks,

    Murat

     

     

     

     

     

     
  • Testing STUN/TURN server connectivity from external networks through UDP 3478 by using Powershell

    Hi there,

     

    While working on a Lync connectivity test tool, I developed a STUN/TURN server connectivity test script in powershell which might be handy when you want to make sure that your external clients will be able to communicate with STUN/TURN server via UDP 3478 (STUN/TURN connectivity through TCP will also be added later on). Actually we have a resource kit tool called MsTurnPing which provides similar functionality (and more): 

    http://technet.microsoft.com/en-us/library/jj945604.aspx Lync Server 2013 Resource Kit Tools Documentation

    MsTurnPing

    --------------------------------------------------------------------------------

    The MSTurnPing tool allows an administrator of Microsoft Lync Server 2013 communications software to check the status of the servers running the Audio/Video Edge and Audio/Video Authentication services as well as the servers that are running Bandwidth Policy Services in the topology.

     

    Description

    --------------------------------------------------------------------------------

    The MSTurnPing tool allows an administrator of Lync Server 2013 communications software to check the status of the servers running the Audio/Video Edge and Audio/Video Authentication services as well as the servers that are running Bandwidth Policy Services in the topology.

     The tool allows the administrator to perform the following tests:

    1. A/V Edge Server test: The tool performs tests against all A/V Edge Servers in the topology by doing the following:

    ◦ Verifying that the Lync Server Audio/Video Authentication service is started and can issue proper credentials.

     ◦ Verifying that the Lync Server Audio/Video Edge service is started and can allocate the resources on the external edge successfully.

     2. Bandwidth Policy Service test: The tool performs tests against all the servers that are running the Bandwidth Policy Services in the topology by doing the following:

    ◦ Verifying that the Lync Server Bandwidth Policy Service (Authentication) is started and can issue proper credentials.

     ◦ Verifying that the Lync Server Bandwidth Policy Service (Core) is started and can perform the bandwidth check successfully.

     

    This tool must be run from a computer that is part of the topology and has the local store installed.

     

    Unfortunately MsTurnPing tool doesn’t allow you to do the testing from an external client.

    Now let me further explain what the powershell script does to test TURN/STUN connectivity test:

     

    1) First of all, to do the testing you need to provide your AV edge server’s name/IP address information as a parameter to the powershell script:

    Example:

    PS C:\> turn.ps1 av.contoso.com

     

    2) Once the powershell script runs, it does:

    - Resolve the name

    - If the given name is successfully resolved, it then sends a UDP datagram with a STUN allocate payload

    - It waits on a response on the UDP socket for a timeout period and if there’s a STUN allocate response it prints “Received response from STUN Server!”

    - If no response is received within the given timeout period, it prints an error.

     

    Some example outputs:

     

    If you get such a failure, one of the first things that you can do is to run the below commands on your Edge server(s) and see if listens on UDP port 3478 on the internal IP and on the AV Edge IP:

     

    C:\Users\Administrator.CONTOSO>netstat -ano | find ":3478"

      UDP    10.2.2.10:3478         *:*                                    1952

      UDP    10.3.3.12:3478         *:*                                    1952

     

    C:\Users\Administrator.CONTOSO>tasklist /svc | find "1952"

    MediaRelaySvc.exe             1952 RTCMEDIARELAY

     

    If you don’t know what is your AV Edge server name/IP address, you can check it from the topology file:

     

     

     

    => Here is the full Powershell script source:

     

    #------------SCRIPT STARTS HERE-----------

     

    function ResolveLyncNames {

     

    param($LyncFqdn)

     

    try {

    $ipaddr   = [System.Net.Dns]::GetHostAddresses($LyncFqdn)

    Write-Host "Successfully resolved $LyncFqdn to $ipaddr" -ForegroundColor Green

    return $ipaddr

    }

     

    catch {

     

    $exception = New-Object system.net.sockets.socketexception

    $errorcode = $exception.ErrorCode

    write-host "Requested name '$LyncFqdn' could not be resolved, error code:$errorcode" -foregroundcolor Red

    write-host "Error details: $exception" -foregroundcolor Red

    return 0

    }

     

    }

      

     

    if($args[0] -eq $NULL)

     

    {

     

    Write-host "Please specify a STUN server name or IP address to test"

    Write-Host "Example: turn.ps1 av.contoso.com"

     

    return

    }

     

    $stunserver1 = $args[0]

     

    if(ResolveLyncNames($stunserver1)) {

     

     

    try {

     

    $j = Start-Job -ScriptBlock {

    $stunserver2 = $args[0]

    $udpclient=new-Object System.Net.Sockets.UdpClient

    $udpclient.Connect($stunserver2,3478)

     

    [Byte[]] $payload =

    0x00,0x03,0x00,0x64,0x21,0x12,0xa4,0x42,0xf1,0x3c,0x08,0x4b,

    0x80,0x18,0x17,0x72,0x47,0x49,0x30,0x65,0x00,0x0f,0x00,0x04,

    0x72,0xc6,0x4b,0xc6,0x80,0x08,0x00,0x04,0x00,0x00,0x00,0x04,

    0x00,0x06,0x00,0x38,0x02,0x00,0x00,0x24,0x79,0xb7,0x95,0x2d,

    0x01,0xce,0xa8,0x6e,0x4e,0x3d,0x76,0x22,0x64,0xaa,0xc7,0xb6,

    0x2b,0xb8,0x78,0xde,0xee,0x8a,0xc0,0x88,0x84,0x49,0x1b,0x7c,

    0x00,0x00,0x00,0x00,0x82,0x8b,0x73,0x75,0xdd,0x0c,0xae,0x66,

    0x95,0xed,0xef,0x56,0xd0,0x4b,0x54,0xa4,0x7c,0xbd,0xfa,0x98,

    0x00,0x10,0x00,0x04,0x00,0x00,0x00,0xc8,0x80,0x06,0x00,0x04,

    0x00,0x00,0x00,0x01,0x80,0x55,0x00,0x04,0x00,0x01,0x00,0x00

      

    $bytesSent=$udpclient.Send($payload,$payload.length)

    $listenport = $udpclient.client.localendpoint.port

     

    $endpoint  = new-object System.Net.IPEndPoint ([IPAddress]::Any,$listenport)

    $content   = $udpclient.Receive([ref]$endpoint)

     

    $Encoding = "ASCII"

     

    switch ( $Encoding.ToUpper() )

    {

    "ASCII" { $EncodingType = "System.Text.ASCIIEncoding" }

    "UNICODE" { $EncodingType = "System.Text.UnicodeEncoding" }

    "UTF7" { $EncodingType = "System.Text.UTF7Encoding" }

    "UTF8" { $EncodingType = "System.Text.UTF8Encoding" }

    "UTF32" { $EncodingType = "System.Text.UTF32Encoding" }

    Default { $EncodingType = "System.Text.ASCIIEncoding" }

    }

    $Encode = new-object $EncodingType

     

    if ($Encode.GetString($content).Contains("The request did not contain a Message-Integrity attribute")) {

    write-host "Received response from STUN Server!" -ForegroundColor Green

    }

    else {

    write-host "STUN Server either is not reachable or doesn't respond" -ForegroundColor Red

    }

     

    $udpclient.Close()

     

    } -ArgumentList $stunserver1

     

    write-host "Sending TURN server port allocation request at UDP port 3478, it will be checked after 10 seconds to see if a response is received or not ..." -ForegroundColor Green

     

    Start-Sleep -Seconds 10

     

    if( $j.JobStateInfo.State -ne "completed" )

     

    {

     

    Write-Host "The request timed out, STUN Server '$stunserver1' is not reachable or doesn't respond to the request." -ForegroundColor Red

    Write-host "Please check if UDP 3478 is allowed between the client and the STUN server" -ForegroundColor Red

    Write-Host "Also please check if STUN server (MediaRelaySvc.exe) is running on the Edge server and listening on UDP 3478" -ForegroundColor Red

     

    }

     

    else {

    $results = Receive-Job -Job $j

     

    $results

     

    }

     

    }

     

    catch {

     

    $_.exception.message

     }

    }

      

    #------------SCRIPT ENDS HERE-----------

     

     

     

    Hope this helps

     

    Thanks,

    Murat