2015-03-11

Http time consumption

I did this as a refresher for raw sockets, and the TCP/IP stack. Written in C#, the following class can be used to tally the amount of time spent waiting for a site. It measures the time between the request, an HTTP GET, and the end of the response, the TCP FIN.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;           // for IPEndPoint
using System.Net.Sockets;   // for socket
using System.IO;

namespace User.Network
{
    class SocketMonitor
    {
        // members
        private bool _bIsRunning;
        private DateTime _dtLastReset;
        private DateTime _dtTimeStart;
        private DateTime _dtTimeEnd;
        private TimeSpan _tsCumulative;
        private TimeSpan _tsMax;
        private TimeSpan _tsMin;
        private TimeSpan _tsLast;

        // properties
        public IPAddress ipaTarget { get; private set; }
        public int iPort { get; private set; }

        public DateTime dtLastReset
        {
            get
            {
                return _dtLastReset;
            }
            private set
            {
                _dtLastReset = value;
            }
        }

        public TimeSpan tsCumulative
        {
            get
            {
                return _tsCumulative;
            }
            private set
            {
                _tsCumulative = value;
            }
        }

        public TimeSpan tsMax
        {
            get
            {
                return _tsMax;
            }
            private set
            {
                _tsMax = value;
            }
        }

        public TimeSpan tsMin
        {
            get
            {
                return _tsMin;
            }
            private set
            {
                _tsMin = value;
            }
        }

        public TimeSpan tsLast
        {
            get
            {
                return _tsLast;
            }
            private set
            {
                _tsLast = value;
            }
        }

        // constructor
        public SocketMonitor(IPAddress target, int port)
        {
            ipaTarget = target;
            iPort = port;
            _bIsRunning = false;
            _dtTimeStart = DateTime.MinValue;
            _dtTimeEnd = DateTime.MinValue;
            Reset();
        }

        // member methods
        // timer control
        public void Reset()
        {
            tsCumulative = TimeSpan.Zero;
            tsMax = TimeSpan.Zero;
            tsMin = TimeSpan.MaxValue;
            tsLast = TimeSpan.Zero;
            dtLastReset = DateTime.Now;
        }// of method Reset()

        private void Start()
        {
            if (!_bIsRunning)
            {
                _bIsRunning = true;
                _dtTimeStart = DateTime.Now;
            }
        }// of method Start()

        private void Stop()
        {
            if(_bIsRunning)
            {
                _bIsRunning = false;
                _dtTimeEnd = DateTime.Now;
                TimeSpan duration = _dtTimeEnd - _dtTimeStart;
                if (duration > TimeSpan.Zero)
                {
                    tsLast = duration;
                    tsCumulative += duration;
                    if (duration > tsMax) tsMax = duration;
                    if (duration < tsMin) tsMin = duration;
                    Console.WriteLine("{0} Total: {1}, Max: {2}, Min: {3}, Last: {4}",
                        DateTime.Now.ToShortTimeString(), tsCumulative, tsMax, tsMin, tsLast);
                }
            }
        }// of method Stop()

        // executable
        // much of this taken from here: https://social.msdn.microsoft.com/Forums/en-US/6d19f326-0d8f-4672-90f9-29d3497fc803/raw-sockets-promiscuous-mode-only-sees-datagrams-tofrom-single-machine?forum=ncl
        public void Run()
        {
            int len_receive_buf = 4096;
            byte[] receive_buf = new byte[len_receive_buf];
            int cout_receive_bytes;
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
            socket.Blocking = false;
            socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true);
            socket.ReceiveBufferSize = 65535;
            // the following linq frag was found here: http://stackoverflow.com/questions/1069103/how-to-get-my-own-ip-address-in-c
            IPAddress thisMachine = Dns.GetHostEntry(Dns.GetHostName()).AddressList.
                Where(o => o.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork).First();
            IPEndPoint ipEP = new IPEndPoint(thisMachine, 0); //(IPAddress.Any, 0);
            socket.Bind(ipEP);
            // found this here: http://www.progamercity.net/code-tut/5370-c-creating-simple-network-sniffer.html
            byte[] byTrue = new byte[4] { 1, 0, 0, 0 };
            byte[] byOut = new byte[4];
            socket.IOControl(IOControlCode.ReceiveAll, byTrue, byOut);
            while (true)
            {
                IAsyncResult ar =
                    socket.BeginReceive(receive_buf, 0, len_receive_buf, SocketFlags.None, null, this);
                cout_receive_bytes = socket.EndReceive(ar);
                Receive(receive_buf, cout_receive_bytes);
            }
        }// of method Run()

        private void Receive(byte[] buf, int len)
        {
            if ((len >= 20) && ((buf[0] >> 4) == 4))
            {
                //check IPv4 only; header info
                int IHL = buf[0] & 0xf; // number of words in header
                int byIHL = IHL * 4; // number of bytes in header
                IPAddress ipSender = new IPAddress((long)BitConverter.ToUInt32(buf, 12));
                IPAddress ipReceiver = new IPAddress((long)BitConverter.ToUInt32(buf, 16));
                // here are the protocol numbers: http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
                if (buf[9] == 6)
                {
                    // tcp = 6; header info
                    int sourcePort = buf[byIHL] << 8 | buf[byIHL + 1];
                    int destPort = buf[byIHL + 2] << 8 | buf[byIHL + 3];
                    int dataOffset = buf[byIHL + 12] >> 4; // number of words in TCP header
                    int flags = (buf[byIHL + 12] & 1) << 8 | buf[byIHL + 13];
                    int byDataOffset = (dataOffset * 4) + byIHL; // offset past the IP & tcp headers
                    if ((ipReceiver.ToString() == ipaTarget.ToString()) && (destPort == iPort))
                    {
                        // from us -- looking for the GET in the payload
                        if (len > byDataOffset)
                        {
                            // a payload exists
                            byte[] payload = new byte[len - byDataOffset];
                            for (int index = 0; index < len - byDataOffset; index++)
                            {
                                payload[index] = buf[index + byDataOffset];
                            }
                            string outline1 = Encoding.ASCII.GetString(payload);

                            if (outline1.TrimStart().ToUpper().StartsWith("GET"))
                            {
                                // found the get... start the timer
                                Start();
                            }
                        }
                    }

                    if ((ipSender.ToString() == ipaTarget.ToString()) && (sourcePort == iPort))
                    {
                        // to us -- looking for a FIN flag (flags, bit 0)
                        if ((flags & 1) == 1)
                        {
                            // found the fin... stop the timer & save
                            Stop();
                        }
                    }
                }
            }
        }// of method Receive(byte[], int)

    }
}
Have fun!
Steve



Breakpoints

Getting breakpoints to work with Classic ASP and ASP.NET 2.0, with IIS running locally.
  1. Ensure web.config has debug="True"
  2. Ensure that you are attaching to the correct w3wp.exe process
  3. Ensure that the App Pool configuration that is associated with this site in IIS has Maximum Worker Processes set to 1 (found in the Process Model section).
  4. Ensure that the ASP configuration for the site in IIS allows Client and Server debugging (found in the Debugging Properties section).
  5. Ensure that machine.config does not have retail="True" in the deployment tag. The default for this attribute is false, which will permit debugging.