Welcome to MSDN Blogs Sign in | Join | Help

Feroze Daud's WebLog

Test-UMConnectivity Primer

Exchange 2007 Ships with a tool called test-umconnectivity that can be used to verify the health of your Exchange Unified Messaging installation.

It has a couple of modes of operation:

 

Local Voice

In this mode, the task will run on a machine that has the Exchange Unified Messaging role installed. It will run diagnostics against the local UM server.

The way to run this, is to invoke test-umconnectivity without any parameters.

 

In this mode, the task verifies that the UMService is listening and responding to incoming INVITES.

 

[PS] D:\users\Administrator>test-umconnectivity

UmserverAcceptingCallAnsweringMessages : True
CallAnswerQueuedMessages               : 0
CurrCalls                              : 0
UmIPAddress                            : 10.197.228.62
Latency                                : 2121
OutBoundSIPCallSuccess                 : True
EntireOperationSuccess                 : True
ReasonForFailure                       :
Identity                               : 62caff65-7a68-46ab-b2a3-ef52a31e431e
IsValid                                : True

 

Remote Voice

This is an end-to-end test. It verifies that the connectivity from UMServer to Gateway to PSTN is working fine. In this mode, the task does the following:

1) Makes an outbound call to a given phone number using a gateway.

2) The gateway will then send the call to the PBX.

3) The call will then come back to the gateway from the PBX.

4) The Gateway sends the call to a UM server that is linked to the gateway.

5) UM server will respond to the incoming INVITE with some DTMF tones, that are then verified by the task.

 

This test can be run from any machine that has the Exchange 2007 admin tools installed. It can also be run from any machine that has the Microsoft Operations Manager (MOM) installed, along with the Exchange Management Pack.

For example, I have a UM server setup with an IPGateway. I also have an autoattendant configured with an extension 74808. I want to make sure that my autoattendant is answering calls.

[PS] D:\users\Administrator>get-umipgateway

Name                 Address       HuntGroups    OutCallsAllowed  Status
----                 -------       ----------    ---------------  ------
UNSECURE_GATEWAY     10.198.250... {74809:UNS... True             Enabled
SECURE_GATEWAY       securegw.e... {}            True             Enabled
sipphonegw           157.56.146.19 {:UNSECURE... True             Enabled

 

[PS] D:\users\Administrator>get-umautoattendant

Name              UMDialPlan      PilotIdentifier SpeechEnabled  Status
                                  List
----              ----------      --------------- -------------  ------
aa1               UNSECURE_DIA... {74808}         True           Enabled

 

[PS] D:\users\Administrator>test-umconnectivity -UMIPGateway unsecure_gateway -Phone 74808

CurrCalls              : 0
UmIPAddress            : 10.197.228.62
Latency                : 43236
OutBoundSIPCallSuccess : True
EntireOperationSuccess : True
ReasonForFailure       :
Identity               : 385bbfdf-26fc-460e-9ccb-a1f14b750c0b
IsValid                : True

 

The output of the command shows which UM server answered the call, the call latency, etc.

 

TuiLogon

In this mode, the task will test that the UM server can connect to a mailbox in a site. This is used to make sure that the UM sever has good connectivity to mailbox servers that have the mailboxes for UM enabled user that the UM server is servicing.

In order to run this command, we need a UM enabled mailbox with an extension. Also, this command needs to be run on a UMServer.

 

[PS] D:\users\Administrator>test-umconnectivity -tuiLogon:1 -UMDialPlan:unsecure_dialplan -phone:74808 -pin:147258

Posted Saturday, December 20, 2008 1:41 PM by Feroze Daud | 0 Comments

Filed under:

Encapsulating Enumeration

While writing a unit test for a particular feature, I was faced with an interesting problem. My unit test has different scenarios. These scenarios test that a certain data item is propagated all the way down from the called function.

For eg, I have a method that I need to test - lets call it CFoo:DomSometing().

We pass in a data item to this method - so as part of the changes I change the method to CFoo::DoSomething(int data)

I need to make sure, in my tests, that the 'data' item is properly consumed by the function.

It turns out that for different functions that I call as part of each of my scenario, the way of checking is different. For eg, for one scenario, I have to call the function 'n' times, whereas for another scenario, I have to call the function for a particular duration.

I want to encapsulate all of this, so that I can write one test function that does the following

 

bool RunTest ( RunTestDelegate testMethod, int data, ILoopIterator iterator)
{
    foreach (int i in iterator.GetValues())
    {
         // call the test method
         testMethod (data);
    }
}

 

Here, the RunTestDelegate will be used to encapsulate each method that I need to test, so that I dont have to write one RunTestXXX method per each method I want to test.

The question is: how do you implement the ILoopIterator? Clearly, the first requirement is that it should be returning an IEnumerable<int> because that is what the foreach loop is iterating over.

Also, as discussed above, we need it so that we can iterate over both a range of values, as well as for a time duration.

In order to solve this, I implemented the interface as follows:


interface ILoopIterator
{
    IEnumerable<int> GetValues();
}

class LoopIterator : ILoopIterator
{
    private int count;
    private TimeSpan duration;
    private bool isCounted;
    private LoopIterator (TimeSpan duration)
    {
        this.duration = duration;
        this.count = -1;
        this.isCounted = false;
    }

    private LoopIterator (int iterationCount)
    {
        this.count = iterationCount;
        this.isCounted = true;
        this.duration = TimeSpan.MaxValue;
    }

    public static LoopIterator CreateTimed(TimeSpan duration)
    {
        return new LoopIterator (duration);
    }

    public static LoopIterator CreateCounted(int iterationCount)
    {
        return new LoopIterator (iterationCount);
    }

    public IEnumerable<int> GetValues()
    {
        if (this.isCounted)
        {
            for (int i = 0; i < this.count; i++)
            {
                yield return i;
            }
        }
        else
        {
            int i = 0;
            DateTime end = DateTime.Now + this.duration;
            while (DateTime.Now < end)
            {
                yield return i;
                ++i;
            }
        }
    }
}

This code used the .NET/2.0 C# features - i.e generics & yield statement for implementing enumerators.

The caller will use a different factory method, depending on how he wants the iteration to be performed.

For duration based iteration, use LoopIterator .CreateTimed(TimeSpan duration)

For normal iteration (like a for loop) use LoopIterator .CreateCounted(int count)

The utility of this pattern, is that the client (which is using ILoopIterator ) does not need to change!

Posted Monday, November 03, 2008 5:26 PM by Feroze Daud | 2 Comments

Filed under: ,

AutoAttendant: What do the various Greetings mean

This post is going to give an explanation of what the various greetings mean, for the autoattendant.

The posting will be divided into two sections. First, I will describe the common structure. Next, I will describe how this manifests itself at runtime for the DTMF AutoAttendant, and the ASR AutoAttendant respectively.

 

Greeting Configuration for the AutoAttendant

The greetings are configured for the AutoAttendant using the set-umautoattendant command in Powershell, or by using the Exchange Management Console MMC snapin that ships with Exchange Server 2007. When using set-umautoattendant, the following parameters are available to customize the greetings

 

Prompt Description
InfoAnnouncementFilename Informational Announcement
InfoAnnouncementEnabled Informational Announcement Enabled Flag
BusinessHoursWelcomeGreetingFilename
AfterHoursWelcomeGreetingFilename
Welcome Greeting Announcement
BusinessHoursWelcomeGreetingEnabled
AfterHoursWelcomeGreetingEnabled
Welcome Greeting Announcement Enabled
BusinessHoursMainMenuCustomPromptFilename
AfterHoursMainMenuCustomPromptFilename
Custom Menu Announcement
BusinessHoursMainMenuCustomEnabled
AfterHoursMainMenuCustomEnabled
Custom Menu Enabled

 

If you want to set the Autoattendant greetings using the Exchange Management Console MMC snapin, then you should navigate to the Greetings tab of the autoattendant properties page.

 

 

DTMF AutoAttendant Greeting ConfigurationMain Menu Greetings.

The Greetings for the Main Menu of the autoattendant are played in the following sequence.

 

First, the autoattendant plays the WelcomeGreeting defined on the AutoAttendant. If no greeting is defined, then the default greeting is played. The default welcome greeting is "Welcome to Microsoft Exchange Automated Attendant".

If the call was received during Holiday Hours (as defined in the holiday schedule), then the greeting associated with that holiday is played. After this, the Informational Announcement is played (if defined).

If a custom menu is defined, then the prompt  defined in the AutoAttendant's BusinessHoursMainMenuCustomPromptFilename is played. The custom menu can be defined by setting the BusinessHoursCustomMenuKeyMapping. To enable the custom menu, set the BusinessHoursCustomMenuKeyMappingEnabled property to true.

If a custom menu prompt filename is not defined, then the server will do a best effort to render a TTS (Text to Speech) greeting using the CustomMenu options that were defined.

Custom Menu TTS

When a CustomMenu prompt filename is not defined, then the system will TTS the custom menu prompt. In order to TTS the prompt, it will sort the Custom menu in increasing order by the Key that has been defined. Then it will use greeting templates defined for the language of the autoattendant to generate Greeting segments, one for each menu item. The greeting segments are then concatenated to give the final greeting.

For a normal (non-timeout) menu option, the following greeting template is used to generate the greeting segment.

For <description> Press <key>

And if you have a timeout option defined in the custom menu, then the following template is used.

Or, stay on the line for <description>

The following table shows the greeting segment that are generated from the custom menu definition. 

Key Description Greeting Segment
1 Sales For Sales Press One
2 Support For SupportPress Two
Timeout Directions Or, stay on the line for Directions

 

Once we have the greeting segment, the final greeting is generated by concatenating each individual greeting segment:

For Sales Press One. For Support, Press Two. Or, stay on the line for directions.

 

 Autoattendant Greeting Scenarios

The following section describes how the autoattendant sounds when different combinaions of greetings are configured, along with some of the other options that effect greeting playback.

 

Default Configuration

This is the case where an autoattendant was created, and no settings were modified on the autoattendant. The autoattendant will sound as follows:

Welcome to Microsoft Exchange Automated Attendant.

Custom Welcome Greeting configured

You can change the welcomg greeting of the autoattendant by specifying a Business/AfterHours WelcomeGreetingFilename, and setting the corresponding Business/AfterHours WelcomeGreetingEnabled flag to TRUE. Once you do this, the system will sound as follows:

Welcome to Contoso Corporation.

Custom Informational Greeting configured

You can configure an informational greeting to let the callers know about extraordinary situations like unforeseen closures etc.. This greeting can be configured by setting the InfoAnnouncementFilename & InfoAnnouncementEnabled properties of the autoattendant. If you set this greeting, then the system will sound like this:

Welcome to contoso corporation. We are closed due to year end Inventory processing.

 If you set the InfoAnnouncementEnabled property value to Uninteruptible, then this greeting cannot be interrupted by the caller. This means that the entire main menu greetings will become uninterruptible. This includes the welcome greeting, informational greeting, and the holiday greeting as well.

 

 Holiday Greetings

 You can also configure a holidy schedule for the autoattendant. When a call comes in during a holiday, the autoattendant will play a greeting specified for that holiday. Normally, it doesnt make sense to have both an Informational greeting and holiday greeting active at the same time, so we will assume for this scenario, that the informational greeting is not configured.

You can specify a holday schedule and greeting for the autoattendant by setting the autoattendant's HolidaySchedule property using Powershell. Or, if you are using the MMC, you can navigate to the Times tab, and set the Holiday Schedule there.

 If you specify a holiday schedule for Christmas, for example, and also specify a greeting for that holiday, then this is how the main menu will sound when a user calls in.

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for Christmas. Normal hours will resume from the 26th of december.

Custom Menu Greeting

 If a custom menu is defined, then the next greeting that will be played is the custom menu greeting. As explained earlier, the autoattendant will use the greeting defined in the Business/AfterHoursMainMenuCustomPromptFilename. If no greeting is defined, then a TTS greeting will be played to the caller.

If you assume that the custom menu has three options: Sales, Support, Directions as described in the section above, then this is what the caller will hear when no custom menu greeting is defined:

Welcome to Contoso Corporation. For Sales Press One. For Support, Press Two. Or, stay on the line for directions.

Additionally, if Transfer to operator and directory search are enabled for the autoattendant, the autoattendant will give the caller these options as well:

Welcome to Contoso Corporation. For Sales Press One. For Support, Press Two. Or, stay on the line for directions. To contact someone, press the Pound key. To speak to an operator, press Zero.

 If the call came in during a holiday, then the holiday greeting will be played after the welcome greeting. In that case, this is what the callers will hear:

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for christmas. Normal hours will resume from the 26th. of December.

For Sales Press One. For Support, Press Two. Or, stay on the line for directions. To contact someone, press the Pound key. To speak to an operator, press Zero.

However, if you define a greeting for the custom menu, then this is what the caller will hear:

Welcome to Contoso Corporation. The office will be closed on the 24th and 25th of December for christmas. Normal hours will resume from the 26th. of December.

To talk to our sales department, press One. For support issues, press Two. Or, you can stay on the line for directions.

If you compare this greeting flow with the flowchard given in the begining, you will noticed that the custom menu greeting did not give the caller an option for transferring to the operator, or doing directory search by pressing the Pound key. If you want to give callers this option, you must record these into your custom menu greeting.

 If you dont want to give callers this option, you can disable them on the autoattendant.

Posted Monday, October 22, 2007 10:17 AM by Feroze Daud | 1 Comments

Filed under:

PowerShell script to get the reason why a Unified Messaging worker process recycled

If you are the administrator of an Exchange Unified Messaging server, you might want to know why the UM worker process is recycling and how often. The following one line PowerShell script will print out all the reasons why the process recycled:

 

 get-eventlog application | where { ($_.EventId -ge 1049 -and $_.EventId -le 1055) -or $_.EventId -eq 1092 } | ft -wrap

If you run this command, you will get an output like so:

 

 

Index Time          Type Source                EventID Message                                                             
----- ----          ---- ------                ------- -------                                                             
56821 Mar 12 20:35  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    9                    essaging                      gured maximum lifetime was exceeded.                                
56773 Mar 12 20:05  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    4                    essaging                       "5065".                                                            
56772 Mar 12 20:04  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    5                    essaging                       WorkerProcess because the working set (589 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             
52146 Mar 11 03:55  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    1                    essaging                      gured maximum lifetime was exceeded.                                
52099 Mar 11 03:25  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    2                    essaging                       "5067".                                                            
52098 Mar 11 03:24  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    1                    essaging                       WorkerProcess because the working set (777 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             
49992 Mar 10 02:55  Info MSExchange Unified M     1055 The Unified Messaging Worker Process was terminated because the confi
    1                    essaging                      gured maximum lifetime was exceeded.                                
49948 Mar 10 02:25  Info MSExchange Unified M     1000 The Unified Messaging Worker Process was started successfully on port
    6                    essaging                       "5065".                                                            
49947 Mar 10 02:24  Info MSExchange Unified M     1051 The Microsoft Exchange Unified Messaging service has created a new UM
    7                    essaging                       WorkerProcess because the working set (644 MB) has exceeded the conf
                                                       igured maximum: 500 MB"                                             

Posted Thursday, March 15, 2007 10:24 PM by Feroze Daud | 0 Comments

Filed under:

Viewing eventlog files in the absence of resource strings.

Many a time, I had to look at eventlog files sent by a customer, or those from a test run that happened some time back. These files (.evt) are offline copies of eventlogs. To view them, you open them up with eventviewer.

However, you might sometimes notice that the display of these logs does not give the descriptive information that was logged. It will only show the parameters used when logging the message. For eg, if you create an eventlog entry with the followin format string:

"This operation failed with error %s"

then, the display will only show the parameter that was passed to the format string.

To work around this, you can use the /AUXSOURCE= flag to point eventviewer at another machine that might have the required resources to load the resource strings. This is done by invoking the eventviewer in the following manner:

mmc.exe /a eventvwr.msc /AUXSOURCE=<machine name or ip>

Posted Friday, September 22, 2006 7:27 PM by Feroze Daud | 0 Comments

Attaching VS to a process on startup.

I have been stumped from time to time on how to attach VS debuggers to a process on process startup. I knew how to do it with Windbg, by setting ImageFileExecutionOptions for the target process. However I did not know how to do it for VS.

Well, I need to fret no more. A colleague forwarded me th is blog entry by Greg, in which he has some good info on how to use ImageFileExecutionOptions to attach to a process on startup.

http://blogs.msdn.com/greggm/archive/2005/02/21/377663.aspx

Posted Tuesday, April 04, 2006 12:05 PM by Feroze Daud | 0 Comments

Filed under:

Ping Part IV: Adventures in Socket programming using System.Net

In this part, we will add some networking code to the code we have thus far. When we get done, we should have a working Ping utility.

 

Take the program that we wrote in the Ping: Part III and add the following code.

 

using System;

using System.Text;

using System.Globalization;

using System.Net;

using System.Net.Sockets;

using System.Net.NetworkInformation;

 

namespace ping

{

      public class ICMP_PACKET

      {

 

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  ...

            }

 

 

 

            public ICMP_PACKET(byte[] data, int offset, int count)

            {

                  this.i_type = data[offset++];

                  this.i_code = data[offset++];

                  this.i_cksum = ByteToUint(data, offset);

                  offset += 2;

                  this.i_id = ByteToUint(data, offset);

                  offset += 2;

                  this.i_seq = ByteToUint(data, offset);

                  offset += 2;

                  this.data = new byte[count - headerLength];

                  Array.Copy(data, offset, this.data, 0, this.data.Length);

            }

 

            public static UInt16 ByteToUint(byte [] networkBytes, int offset)

            {

                  return BitConverter.ToUInt16(new byte[] { networkBytes[offset+1], networkBytes[offset] }, 0);

            }

      }

 

 

class Program

{

            static void Main(string[] args)

            {

                  if (args.Length != 1)

                  {

                        Usage();

                  }

 

                  if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))

                  {

                        Usage();

                  }

 

                  string target = args[0];

 

                  IPAddress [] heTarget = Dns.GetHostAddresses(target);

                  IPAddress ipTarget = null;

                  foreach (IPAddress ip in heTarget)

                  {

                        if (ip.AddressFamily == AddressFamily.InterNetwork)

                        {

                              ipTarget = ip;

                              break;

                        }

                  }

 

                  IPAddress[] heSource = Dns.GetHostAddresses(Dns.GetHostName());

                  IPAddress ipSource = null;

                  foreach (IPAddress ip in heSource)

                  {

                        if (ip.AddressFamily == AddressFamily.InterNetwork)

                        {

                              ipSource = ip;

                              break;

                        }

                  }

 

                  IPEndPoint epLocal = new IPEndPoint(ipSource, 0);

 

                  Socket pingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);

                  pingSocket.Bind(epLocal);

 

                  byte [] data = Encoding.ASCII.GetBytes("1234567890abcdef");

 

                  Console.WriteLine("Ping {0}({1}) with {2} bytes of data...",target, ipTarget.ToString(), data.Length);

 

                  Console.WriteLine();

 

                  for (int i = 0; i < 3; i++)

                  {

 

                        ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(111, 222, data);

 

                        IPEndPoint epRemote = new IPEndPoint(ipTarget, 0);

 

                        pingSocket.SendTo(packet.Serialize(), epRemote);

 

                        byte[] receiveData = new byte[1024];

                        EndPoint epResponse = (EndPoint)new IPEndPoint(0, 0);

                        int read = pingSocket.ReceiveFrom(receiveData, ref epResponse);

 

                        ICMP_PACKET recvPacket = new ICMP_PACKET(receiveData, 20, read);

 

                        Console.WriteLine("Reply from:{0} bytes = {1}", ((IPEndPoint)epResponse).Address.ToString(), recvPacket.PayloadLength);

 

                  }

 

 

            }

 

...

 

 

      }

 

 

A couple of important points that I wanted to bring to your attention:

 

1)      I have uncommented the code which checks the validity of command-line parameters in Program::Main.

2)      We have added a new constructor ICMP_PACKET(byte [], int, int). This is to parse the response returned by the server.

3)      The ByteToUInt(byte [], int) method is used to convert from Network Order to Host Order. This is needed because the bytes on the wire are in Network Order, which is BigEndian

4)      In Main(), when we resolve the IPAddress of the source and destination, we need to make sure that we work with the correct IP address. Since this program is limited to IPv4, we need to make sure that we do not accidently work with IPv6 addresses. The IPv6 address may be returned when the host that you are trying to resolve is IPv6 enabled.

5)      When we read the response from the socket, we get back more bytes that the size of the ICMP packet sent by the server. This is because we opened the Socket with SocketType.Raw. In this mode, we will also get back the IPv4 header that is encapsulating the ICMP packet. So, when we process the response packet, we need to make sure that we skip past the IP header. That is why we skip the first 20 bytes of the packet when we try to create an ICMP_PACKET with the returned data.

 

 

If you compile and run this program, you will be able to ping a host without any problems.

 

As utilities go, this is a very barebones utility. It does not handle errors from the network very well. For the interested, here are some things that you can do to this to make it more robust:

 

  1. Be ready to handle exceptions from Socket.SendTo() and Socket.ReceiveFrom().
  2. The utility does not handle timeouts. It would be a simple matter to add code and set a timeout on the Receive() call. That way, if the response doesnt come back within a certain time interval, you can print an error ( or an asterisk like the Ping utility that comes with the OS).
  3. Give a command line option to change the #bytes sent in the payload.

 

Well, that is it then. Now we have a working Ping utility. Hopefully you have learned how to convert a protocol specification into a working program.

 

Posted Wednesday, October 26, 2005 2:53 PM by Feroze Daud | 0 Comments

Ping Part III: Adventures in Socket programming using System.Net

 

In Part II of this article, we saw how we are going to use the ICMP protocol to implement a simple Ping client. We also saw a skeleton of this program which showed how to translate the ICMP packet specification into a C# structure.

 

In this part, we will write a routine to calculate the checksum of the packet, and a routine to serialize the packet into a byte array. Recall from PartII that the request and reply packets have a particular encoding on the wire. We will have to write a routine that will encode the packet into a byte array, so that the array can be sent on the wire.

 

Take the skeleton that we created in Part II and add the following (lines in blue):

 

using System;

using System.Text;

using System.Globalization;

 

namespace ping

{

      public class ICMP_PACKET

      {

            ...

 

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  this.i_type = kind;

                  this.i_code = code;

                  this.i_id = ToNetworkOrder(id);

                  this.i_seq = ToNetworkOrder(seq);

                  this.data = data;

                  this.i_cksum = Checksum();

            }

 

            public static ICMP_PACKET CreateRequestPacket(UInt16 id, UInt16 seq, byte[] data)

            {

                  return new ICMP_PACKET(8, 0, id, seq, data);

            }

 

            public static byte[] GetBytesInNetworkOrder(UInt16 number)

            {

                  byte[] bytes = BitConverter.GetBytes(number);

                  if (BitConverter.IsLittleEndian)

                        return new byte[] { bytes[1], bytes[0] };

                  else

                        return bytes;

            }

 

            public static UInt16 ToNetworkOrder(UInt16 number)

            {

                  byte[] networkBytes = GetBytesInNetworkOrder(number);

                  return BitConverter.ToUInt16(networkBytes, 0);

            }

 

 

            public UInt16 Checksum()

            {

                  int cksum_buffer_length = (int)(Length / 2);

 

                  byte [] packetBytes = Serialize(this);

 

                  //

                  // first, convert the serialized bytes into a UInt16 array

                  // we will use the UInt16 array to do the checksum

                  //

                  UInt16[] checksumBuffer = new UInt16[cksum_buffer_length];

 

                  int index = 0;

                  int i = 0;

                  while (index < Length)

                  {

                        checksumBuffer[i] = BitConverter.ToUInt16(packetBytes,index);

                        index += 2;

                        ++i;

                  }

 

                  int checksum = 0;

                  for (i = 0; i < checksumBuffer.Length; i++)

                  {

                        checksum += Convert.ToInt32(checksumBuffer[i]);

                  }

 

                  checksum = (checksum >> 16) + (checksum & 0xffff);

                  checksum += (checksum >> 16);

 

                  return (UInt16)(~checksum);

            }

 

            public byte[] Serialize()

            {

                  return Serialize(this);

            }

 

            public static byte[] Serialize(ICMP_PACKET packet)

            {

                  // first, find out how many bytes to allocate for the serialized packet

                  int packet_size = packet.Length;

 

                  bool isLittleEndian = BitConverter.IsLittleEndian;

 

                  UInt16 cksum = packet.i_cksum;

                  UInt16 id = packet.i_id;

                  UInt16 seq = packet.i_seq;

 

 

                  //allocate the byte array

                  byte[] packetArray = new byte[packet_size];

 

                  // now serialize the packet into the array.

                  int index = 0;

                  packetArray[index++] = packet.i_type;

                  packetArray[index++] = packet.i_code;

                  // the checksum is 16 bits.

                  byte[] temp = BitConverter.GetBytes(cksum);

                  // copy it into the packetArray at the required offset

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // similarly, copy the rest.

                  temp = BitConverter.GetBytes(id);

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // copy seq#

                  temp = BitConverter.GetBytes(seq);

                  Array.Copy(temp, 0, packetArray, index, temp.Length);

                  index += 2;

                  // copy payload

                  if (packet.PayloadLength > 0)

                  {

                        Array.Copy(packet.data, 0, packetArray, index, packet.PayloadLength);

                  }

 

                  return packetArray;

            }

      };

 

      class Program

      {

            static void Main(string[] args)

            {

                  //if (args.Length != 1)

                  //{

                  //    Usage();

                  //}

 

                  //if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

                  //|| 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

                  //|| 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))

                  //{

                  //    Usage();

                  //}

 

                  byte [] data = new byte[32];

                  int j = 0;

                  for (int i = 0; i < 32; i++)

                  {

                        data[i] = (byte)((int)'a' + j);

                        j = (j + 1) % 23;

                  }

 

                  ICMP_PACKET packet = ICMP_PACKET.CreateRequestPacket(0x200, 0x9603, data);

 

                  byte[] serialized = packet.Serialize();

 

                  for (int i = 0; i < serialized.Length; i++)

                  {

                        Console.Write("{0:X} ", serialized[i]);

                  }

 

            }

 

            ...

 

 

      }

}

 

 

A couple of important points that I wanted to bring to your attention:

 

1)      I have commented out the code which checks the validity of command-line parameters. While we are developing this application, we will sometimes be running this program with no command line arguments, so I didn’t want that code there. We will put it back in later.

2)      As you might recall, the Platform/CPU stores data in a certain order. This is called Endianness. Intel (X86) platform, on which I am writing this program is Little-Endian. However it turns out that the byte-order on the network is always Big-Endian. Hence we need to convert multi-byte numbers from LittleEndian to Big-Endian before we send them on the network. Therefore, I have written the functions GetBytesInNetworkOrder() and ToNetworkOrder() to do the conversion from Little-Endian to Big-Endian.

3)      The functions Serialize() and Checksum() are used to serialize the packet into a byte array, and calculate the checksum respectively. Note that as per the RFC, while calculating the checksum, we should assume that the i_cksum field of the packet structure has a value of zero. Once the checksum is calculated, we write the value into the structure, and then serialize the structure to get the final bytes on the wire.

4)      In Part II of this series, I showed you the network trace of the Ping.exe routine that comes with the OS. In this part, I created a request packet with the same data, to make sure that our serialization routine and checksum routines are right. You can compile the program, and run it to verify.

 

 

D:\ping>bin\debug\ping.exe

8 0 B5 58 2 0 96 3 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 7

5 76 77 61 62 63 64 65 66 67 68 69

 

If you compare this output with the Ethereal trace of the actual Ping command that I ran in Part II, you will see that they match. This tells us that the Checksum and serialization routines that we wrote are correct.

 

 

 


 

Posted Monday, October 24, 2005 11:48 AM by Feroze Daud | 0 Comments

Ping Part II: Adventures in Socket programming using System.Net

If you see the ping utility that comes with your OS, you will notice that it has many options. However, the one we are going to develop will just take one argument:

 

C:\ping>ping <hostname> | <ipaddress>

        Example: ping www.contoso.com

        Example: ping 127.0.0.1

 

 Let us start out by looking at requirements of the Ping client. Basically, the task of the tool is to find out if a specified server is alive, and on the network. The way it achieves this is by sending an “echo” packet to the server. The server responds with an “echo” response. If the server responds within a certain time interval, then we can assume that there is network connectivity from the client to the server. If it doesn’t respond, then it could indicate a variety of things that could be wrong, for eg:

 

  • The destination server is not up.
  • The destination server is up but does not have networking enabled.
  • The destination server is up and on the network, but is configured to drop Ping requests
  • Etc.

 

We will not concern ourselves with “why” a server is not responding to the Ping request, as it could be another topic in itself.

 

Where was I? Oh yeah, the Ping client. It so happens that we have a protocol (ICMP) that has the echo request and reply operations that we need to implement the Ping client. Read the ICMP RFC to get information about all the features offered by the ICMP protocol. As we know, the ICMP protocol is at the same layer as the IP protocol in the OSI layering scheme.

 

For the ping utility, we will be using the ICMP Echo Request and response messages. The client will send the ICMP echo request message, and the server will reply with an Echo response message. If you look at the Rfc, it describes how an RFC message looks like:

 

Offset

Field Name

Size (octets)

Description

0

Type

1

Specified type of the operation. It should be set to 8 for request message and 0 for reply messages

8

Code

1

This field is Zero.

16

Checksum

2

One’s complement checksum of the ICMP message

32

Identifier

2

Used to correlate request and reply

48

Sequence Number

2

Used to correlate request and reply

64

Data

Variable

Optional data to be sent with the request. This must be sent back by the server.

 

 

Looking at the table above, we can come up with a C# class that corresponds to what the packet will look like:

 

public class ICMP_PACKET

{

      public Byte i_type;    // type of message

      public Byte i_code;    // sub code

      public UInt16 i_cksum;   // ones complement checksum of header

      public UInt16 i_id;      // identifier

      public UInt16 i_seq;     // sequence number 

      public Byte[] data;

}

 

 

I used Ethereal to sniff the network while doing a Ping, to see what a Ping request/response looks like on the wire. The request packet looks like this:

 

0020  59 99 08 00 b5 58 02 00  96 03 61 62 63 64 65 66   Y....X.. ..abcdef

0030  67 68 69 6a 6b 6c 6d 6e  6f 70 71 72 73 74 75 76    ghijklmn opqrstuv

0040  77 61 62 63 64 65 66 67  68 69                                 wabcdefghi     

 

 

Let us dissect the packet:

 

i_type = 0x08 ( Echo Request)

i_code = 0x00

i_cksum = 0xb558

i_id  = 0x0200

i_seq = 0x9603

data = 32 bytes ( abcedf…. )

 

The ICMP echo reply looks as follows:

 

0020  59 9a 00 00 bd 58 02 00  96 03 61 62 63 64 65 66   Y....X.. ..abcdef

0030  67 68 69 6a 6b 6c 6d 6e  6f 70 71 72 73 74 75 76    ghijklmn opqrstuv

0040  77 61 62 63 64 65 66 67  68 69                                 wabcdefghi     

 

Let us dissect the packet:

 

i_type = 0x00 ( Echo Reply)

i_code = 0x00

i_cksum = 0xbd58

i_id  = 0x0200

i_seq = 0x9603

data = 32 bytes ( abcedf…. )

 

 

Given what we have learned so far, we can now come up with a skeleton for the program that we will develop:

 

using System;

using System.Text;

using System.Globalization;

 

namespace ping

{

public class ICMP_PACKET

      {

            Byte i_type;    // type of message

            Byte i_code;    // sub code

            UInt16 i_cksum;   // ones complement checksum of header

            UInt16 i_id;      // identifier

            UInt16 i_seq;     // sequence number 

            Byte[] data;

 

            private const int headerLength = 8;

 

            // Total size of the packet (header + payload)

            public int Length

            {

                  get { return HeaderLength + PayloadLength; }

            }

 

            // Size of the packet header (excluding data)

            public int HeaderLength

            {

                  get { return headerLength; }

            }

 

            // Size of the payload

            public int PayloadLength

            {

                  get { return (data == null) ? 0 : data.Length; }

            }

 

            public byte PacketType

            {

                  get { return i_type; }

            }

 

            public byte Code

            {

                  get { return i_code; }

            }

 

            public UInt16 Identifier

            {

                  get { return i_id; }

            }

 

            public UInt16 SequenceNumber

            {

                  get { return i_seq; }

            }

 

            public ICMP_PACKET(Byte kind, Byte code, UInt16 id, UInt16 seq, byte[] data)

            {

                  this.i_type = kind;

                  this.i_code = code;

                  this.i_id = id;

                  this.i_seq = seq;

                  this.data = data;

                  this.i_cksum = 0;

            }

 

            public static ICMP_PACKET CreateRequestPacket(UInt16 id, UInt16 seq, byte[] data)

            {

                  return new ICMP_PACKET(8, 0, id, seq, data);

            }

      };

 

      class Program

      {

            static void Main(string[] args)

            {

                  if (args.Length != 1)

                  {

                        Usage();

                  }

 

                  if (0 == String.Compare(args[0], "/?", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-h", true, CultureInfo.InvariantCulture)

                  || 0 == String.Compare(args[0], "-?", true, CultureInfo.InvariantCulture))

                  {

                        Usage();

                  }

 

            }

 

            static void Usage()

            {

                  Console.WriteLine("ping <hostname> | <ipaddress>");

                  Console.WriteLine("\tExample: ping www.contoso.com");

                  Console.WriteLine("\tExample: ping 127.0.0.1");

                  Environment.Exit(0);

            }

 

      }

}

Posted Sunday, October 23, 2005 8:24 AM by Feroze Daud | 3 Comments

PING: Adventures in Socket programming using System.Net

After a long hiatus, I am back to posing to my blog. I am going to start out with a series on Socket programming.

 

In this series, I will show you how to implement a simple Ping client using classes from the System.Net.Sockets  namespace. It should be an exciting journey.

For starters, you can familiarize yourself with the RFC for ICMP (Internet Control Message Protocol) which is the protocol used by the PING client. The RFC for that is here: (http://www.ietf.org/rfc/rfc0792.txt).

 

 

Until next time,

 

Cheers.

Posted Thursday, October 20, 2005 9:18 AM by Feroze Daud | 2 Comments

Decoding Printer Tracking Technology

In a previous blog post, I wrote about how printer manufacturers are embedding tracking information in their printers.

Well, today, via Slashdot, I learned that folks at EFF have figured out the encoding in atleast one printer (those made by Xerox).

The following link has more details...  http://www.eff.org/news/archives/2005_10.php#004063

Posted Tuesday, October 18, 2005 1:56 PM by Feroze Daud | 0 Comments

Tracing in System.Net (contd..)

Durgaprasad, the test lead for System.Net has a very informative entry on using Tracing facilities in whidbey.

http://blogs.msdn.com/dgorti/archive/2005/09/18/471003.aspx

Posted Thursday, September 22, 2005 4:54 PM by Feroze Daud | 0 Comments

Enabling Tracing for System.Net

In whidbey, System.Net has a cool retail tracing implementation. It writes most calls made to public API's to a TraceListener. You can setup your own tracelisteners, or use the config file to setup a default listener.

This facility is very useful in debugging problems with applications.

Here is the example config file which enables Verbose tracing.

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
<system.diagnostics>
 <trace autoflush="true" />

 <sources>
 <source name="System.Net">

  <listeners>
   <add name="System.Net"/>
  </listeners>
 </source>
 <source name="System.Net.Sockets">
  <listeners>
   <add name="System.Net"/>
  </listeners>
 </source>
 <source name="System.Net.Cache">
  <listeners>
   <add name="System.Net"/>
  </listeners>
 </source>
 </sources>
        <sharedListeners>
                <add
                  name="System.Net"
                  type="System.Diagnostics.TextWriterTraceListener"
                  initializeData="System.Net.trace.log"
                />
        </sharedListeners>
 <switches>
                <add name="System.Net" value="Verbose" />
  <add name="System.Net.Sockets" value="Verbose" />               
  <add name="System.Net.Cache" value="Verbose" />
 </switches>
</system.diagnostics>
</configuration>

NOTE: If you are doing logging inside of the ASP.NET process, make sure to give the ASP.NET process identity WRITE permissions to the directory where you want the log to be written.

Posted Thursday, May 12, 2005 1:09 PM by Feroze Daud | 0 Comments

Filed under:

HttpWebRequest and Host Headers

A user posted on the newsgroups asking about how to set Host headers in HttpWebRequest.

HttpWebRequest does not allow the host header to be set by the user. It is set automatically from the URI supplied to HttpWebRequest constructor.

However, it turns out that there is a roundabout way to accomplish this. This is shown in the following reply to the original post:

http://groups-beta.google.com/group/microsoft.public.dotnet.framework/browse_thread/thread/17c5369520221c8/4e768c733ad2f451?q=HttpWebRequest+and+Host+Header&rnum=1#4e768c733ad2f451

This solution works, but there is a subtle difference in the request sent on the wire. To illustrate that, look at the following program:


using System;
using System.Net;

public class EP {
 public static void Main()
 {
  test("http://www.company.com/test.aspx");
  test("http://www.contoso.com/test.aspx");
  
 }

 public static void test(string uri)
 {
  WebRequest req = WebRequest.Create(uri);
  req.Proxy = new WebProxy("http://myserver", false);

  WebResponse resp = req.GetResponse();

  resp.Close();
 }
}


To get this program to work, you need to do the following:

  1. Have a server named "myserver" on which you get IIS/ASP.NET running. If you dont want to run ASP.NET that is fine as well.
  2. On the server, go into the IIS console, and create two websites. Set the host header on the first website to be www.company.com and the other to be www.contoso.com
  3. Compile and run the above program.

If you look at the request sent on the wire, you will notice that this program sends the following request to the server "myserver"

GET http://www.contoso.com/test.aspx HTTP/1.1
Host: http://www.contoso.com

Whereas the actual request you want sent is:

GET /test.aspx HTTP/1.1
Host: www.contoso.com

NOTE: Even though the request URI sent on the wire is an absolute URI, and is different from what should actually be sent if the client had control of the host header, it will still work on the server, because as per RFC/2616, a server is supposed to honor all requests which use an absolute URI instead of a relative URI to identify the resource.

 

Posted Thursday, March 31, 2005 2:46 PM by Feroze Daud | 1 Comments

Filed under:

Forcing reuse of connections when doing Windows Authentication

When doing authentication, HttpWebRequest manages connections differently, depending on the authentication mode being used. If it uses Windows Integrated Authentication, it will close the connection after every request completes. The reasons for this are complex, and I wont go into them at this point.

However, there are cases where you dont want this to happen. For example, imagine that you have a 3-tier architecture, where the client is talking to an asp.net application, which in turn is hitting a backend webserver. The asp.net webapplication (henceforth called the MiddleTier) is using HttpWebRequest to talk to the back-end, and the back-end is secured by Windows Integrated Authentication.

If the middle-tier web application ends up getting a lot of requests, it will inturn issue a lot of requests to the back-end. The default behavior of webrequest is to create a new connection for each request, and over time, the middle-tier might end up running out of wildcard TCP ports, causing the HttpWebRequest to fail with a "Unable to connect to the remote server" exception.

To mitigate this, you want to use a property on the webrequest called UnsafeAuthenticatedConnectionSharing . Setting this property will cause HttpWebRequest to reuse authenticated connections (making sure that it honors ServicePoint.ConnectionLimit).

Security Note: You dont want to use this property lightly. It has security consequences.

Posted Monday, November 22, 2004 7:40 PM by Feroze Daud | 0 Comments

Filed under:

More Posts Next page »
 
© 2009 Microsoft Corporation. All rights reserved. Terms of Use  |  Trademarks  |  Privacy Statement
Page view tracker