Probing the ASP.NET State Service Part II

clock Wednesday, 8 April 2009 07:50 by sunny

In my previous post, I took a first look at the ASP.NET state service communication protocol. In this post I'll describe the techniques I'm using to piece out and understand the protocol.

I needed to monitor the traffic between the web server and the state service so I installed WinDump, a Windows version of tcpdump.
With WinDump, I could capture the low level tcp traffic between the state service and the web server. The only caveat was that I had to monitor a state service on a different computer because WinDump doesn’t work with the loopback device.

After a while I decided WinDump was too low level for the task at hand. What I really needed was a tcp relay server.

A tcp relay server is a more involved version of an echo server that sits between a server and a client and relays transmitted data to and fro. With a tcp relay server, not only can I capture the transmitted data, I can also modify it.



To have the greatest flexibility, I decided to write one. My relay server is a console application. Here's the code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace RelayServer
{
    //State object
    public class StateObject
    {
        // Client  socket.
        public Socket WorkSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 1024;
        // Receive buffers.
        public byte[] Buffer = new byte[BufferSize];
        // Socket that connects to other party
        public Socket HandoffSocket = null;
    }

    class Program
    {
        static int relayPort = 24242; //Port to listen on for client connection
        static string serviceHost = "localhost"; //State service host
        static int servicePort = 42424; //State service port
        static StateObject pollStateObj = new StateObject(); //State object polled for disconnection

        static void Main(string[] args)
        {
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, relayPort);
            Socket clientListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            clientListener.Bind(localEndPoint);
            clientListener.Listen(100);
            clientListener.BeginAccept(new AsyncCallback(AcceptCallback), clientListener);

            while (!Console.KeyAvailable)
            {
                Thread.Sleep(200); //check every 200 milliseconds

                //is the state object instantiated and connected?
                if (pollStateObj.WorkSocket != null && 
                     (pollStateObj.WorkSocket.Connected || pollStateObj.HandoffSocket.Connected))
                {                    
                    //check if work (client) socket is disconnected
                    try
                    {
                        //Test for disconnection
                        bool isDisconnected = false;
                        lock (pollStateObj.WorkSocket)
                        {
                            isDisconnected = pollStateObj.WorkSocket.Available == 0 && 
                                pollStateObj.WorkSocket.Poll(1, SelectMode.SelectRead);
                        }

                        if (isDisconnected)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Client disconnected.");
                            DisconnectSockets(pollStateObj);
                            continue;
                        }
                    }
                    catch (ObjectDisposedException ex)
                    {
                        //do nothing
                        continue;
                    }
                    catch (SocketException ex)
                    {
                        //Was Socket remotely disconnected?
                        if (ex.ErrorCode == 10054)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Client disconnected.");
                        }
                        else if (ex.ErrorCode == 10053)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Client aborted connection.");
                        }
                        else
                        {
                            Console.ForegroundColor = ConsoleColor.White;
                            Console.WriteLine("\nError: " + ex + "\n");
                        }

                        DisconnectSockets(pollStateObj);
                        continue;
                    }
                    catch (Exception ex)
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.WriteLine("\nError: " + ex + "\n");
                        continue;
                    }

                    try
                    {
                        //Test for disconnection
                        lock (pollStateObj.HandoffSocket)
                        {
                            pollStateObj.HandoffSocket.Send(new byte[1], 0, SocketFlags.None);
                        }
                    }
                    catch (ObjectDisposedException ex)
                    {
                        //do nothing
                        continue;
                    }
                    catch (SocketException ex)
                    {
                        //Was Socket remotely disconnected?
                        if (ex.ErrorCode == 10054)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Server disconnected.");
                        }
                        else if (ex.ErrorCode == 10053)
                        {
                            Console.ForegroundColor = ConsoleColor.Red;
                            Console.WriteLine("Server aborted connection.");
                        }
                        else
                        {
                            Console.ForegroundColor = ConsoleColor.White;
                            Console.WriteLine("\nError: " + ex + "\n");
                        }

                        DisconnectSockets(pollStateObj);
                        continue;
                    }
                    catch (Exception ex)
                    {
                        Console.ForegroundColor = ConsoleColor.White;
                        Console.WriteLine("\nError: " + ex + "\n");
                        continue;
                    }
                }
            }
        }

        private static void AcceptCallback(IAsyncResult ar)
        {
            //Accept incoming connection
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = listener.EndAccept(ar);
            
            //Display Connection Status
            Console.ForegroundColor = ConsoleColor.Yellow;
            Console.WriteLine("Client is connected.");

            //Accept another incoming connection
            listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);

            // Create the state object.
            StateObject state = new StateObject();
            state.WorkSocket = handler;
            state.HandoffSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //Connect to other socket
            state.HandoffSocket.Connect(serviceHost, servicePort);

            //Create another state object to receive information from service
            StateObject recvState = new StateObject();
            recvState.WorkSocket = state.HandoffSocket;
            recvState.HandoffSocket = state.WorkSocket;

            //Set up the Service socket to receive information from service
            state.HandoffSocket.BeginReceive(recvState.Buffer, 0, StateObject.BufferSize, SocketFlags.None,
                new AsyncCallback(ReadCallback), recvState);

            //Set up the client socket to receive information from client
            handler.BeginReceive(state.Buffer, 0, StateObject.BufferSize, SocketFlags.None,
                new AsyncCallback(ReadCallback), state);

            //Reference this State object as the object to poll for disconnections
            pollStateObj = state;
        }

        private static void ReadCallback(IAsyncResult ar)
        {
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject state = (StateObject)ar.AsyncState;

            if (state.WorkSocket.Connected == false)
            {
                return;
            }

            try
            {
                // Read data from the client socket. 
                int bytesRead;
                lock (state.WorkSocket)
                {
                    bytesRead = state.WorkSocket.EndReceive(ar);
                }

                if (bytesRead > 0)
                {
                    //Transform received data
                    byte[] transformedData = TransformData(state.Buffer, bytesRead, state);

                    //Transmit transformed data to other Socket
                    if (transformedData.Length > 0)
                    {
                        lock (state.HandoffSocket)
                        {
                            state.HandoffSocket.BeginSend(transformedData, 0, transformedData.Length, 
                                SocketFlags.None, new AsyncCallback(SendCallback), state);
                        }
                    }
                }

                lock (state.WorkSocket)
                {
                    state.WorkSocket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0,
                        new AsyncCallback(ReadCallback), state);
                }
            }
            catch (ObjectDisposedException ex)
            {
                //do nothing
                return;
            }
            catch (SocketException ex)
            {
                if (ex.ErrorCode == 10054 || ex.ErrorCode == 10053)
                {
                    //do nothing
                    return;
                }
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("\nError: " + ex + "\n");

                return;
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("\nError: " + ex + "\n");
                return;
            }

        }

        private static void SendCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            StateObject state = (StateObject)ar.AsyncState;

            if (!state.HandoffSocket.Connected)
            {
                return;
            }

            // Complete send
            try
            {
                lock (state.HandoffSocket)
                {
                    state.HandoffSocket.EndSend(ar);
                }
            }
            catch (Exception ex)
            {
                Console.ForegroundColor = ConsoleColor.White;
                Console.WriteLine("\nError: " + ex + "\n");
                return;
            }
        }

        private static byte[] TransformData(byte[] Data, int Length, StateObject State)
        {
            //Just display Transmitted Data
            if (State.WorkSocket == pollStateObj.WorkSocket)
                Console.ForegroundColor = ConsoleColor.Cyan;
            else
                Console.ForegroundColor = ConsoleColor.Green;
            
            Console.Write(Encoding.UTF8.GetString(Data, 0, Length));

            byte[] output = new byte[Length];
            Array.Copy(Data, output, Length);
            return output;
        }

        private static void DisconnectSockets(StateObject state)
        {
            lock (state.WorkSocket)
            {                                        
                if (state.WorkSocket.Connected)
                {
                    state.WorkSocket.Shutdown(SocketShutdown.Both);
                }
                state.WorkSocket.Close();                    
            }

            lock (state.HandoffSocket)
            {
                if (state.HandoffSocket.Connected)
                {
                    state.HandoffSocket.Shutdown(SocketShutdown.Both);
                }
                state.HandoffSocket.Close();
            }
        }
    }
}

It's important to note that the tcp relay server must also relay connections and disconnections from either end, in addition to transmitted data.

To test the relay server:

1. Create a new ASP.NET web application in Visual Studio.

2. Add the following line in the system.web section of the Web.config file:

<sessionState mode="StateServer" stateConnectionString="tcpip=localhost:24242" cookieless="false" timeout="20"/>
<!-- -->

3. Add the following lines of code to the Page_Load event handler.

 Session.Add("Test2", "Test");
 Session.Abandon();

4. Run the relay server.

5. Start the local ASP.NET State Service (Located at Control Panel -> Administrative Tools -> Services) .

6. Run the ASP.NET Application

You'll see the gem below in the relay server console window.



This tool will be extremely useful as I spec out my implementation of the state service. For instance, if I want to see how the state service reacts if there is no Exclusive header, I'll simply modify the TransformData method to detect and remove the header.

I have also found Microsoft’s specification of the ASP.NET state service protocol. It doesn't look coherent but it will help me make my implementation compatible with their specifications.


Digg It!RedditDel.icio.usStumbleUponTechnorati

Comments

Add comment


(Will show your Gravatar icon)

biuquote
  • Comment
  • Preview
Loading