C# code to play PCAP capture of HTTP request

20. January 2013

If you have been involved in troubleshooting web applications providing APIs to clients, you know how easier it is to have the actual request sent from client and be able to replay that against your server code to find the problem. Tools we use in my team is WireShark and NCAT. When QA team finds a problem, we always ask for the WireShark capture of the communication.

After finding the request in question in WireShark, you can right click on the packet and select "Follow TCP Stream" so it isolates all packets related only to this request. In the "Follow TCP Stream" you can change the drop down to only show request (and not the response) and use "Dave As" button to save this request to a file. This file can then be played back to destination of your choice to allow you to reproduce the issue easily. NCAT command line looks like this:

 

NCAT localhost 80 < PCAPfile

 

Where localhost can e changed to any destination you want, 80 is the port number and PCAPfile is the file you saved as explained in above section.

It is also possible to save same type of file from your ASP.net application. For example, you want to capture sample requests causing exceptions in your server code. You can use the HTTPRequest.SaveAs() method.

 

Request.SaveAs("c:\\temp\\HttpRequest.txt", true);

 

Such captured requests can then be played back using NCAT, as demonstrated above.

Here is a sample code I put together to read the PCAP file and play it back, similar to how NCAT does it, but it only works with HTTP requests.

public class PcapPlayer
    {

        public string Destination { get; set; }
        public string FileName { get; set; }

        public PcapPlayer(string destination, string filename)
        {
            Destination = destination;
            FileName = filename;
        }

        /// <summary>
        /// Reads the PCAP file and creates HTTPWebRequest and gets the response from server
        /// </summary>
        /// <returns>a string representing the results coming back from server</returns>
        public string Play()
        {
            string result = string.Empty;

            try
            {
                string url = Destination;

                if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
                    url = "http://" + url;

                HttpWebRequest request;

                using (FileStream reader = new FileStream(FileName, FileMode.Open))
                {
                    string line = ReadLine(reader);
                    string[] components = line.Split(' ');

                    url += components[1];

                    request = (HttpWebRequest)HttpWebRequest.Create(url);
                    request.Method = components[0];

                    if (components.Length > 2)
                    {
                        switch (components[2].ToLower())
                        {
                            case "http/1.0":
                                request.ProtocolVersion = HttpVersion.Version10;
                                break;
                            case "http/1.1":
                                request.ProtocolVersion = HttpVersion.Version11;
                                break;
                        }
                    }

                    while (reader.Position < reader.Length)
                    {
                        line = ReadLine(reader);

                        if (string.IsNullOrEmpty(line))
                            break;
                        else
                            SetHeader(line, request);
                    }

                    byte[] buffer = new byte[4096];
                    int bytesread = 0;
                    Stream Body = request.GetRequestStream();

                    while (reader.Position < reader.Length)
                    {
                        bytesread = reader.Read(buffer, 0, buffer.Length);
                        if (bytesread > 0)
                            Body.Write(buffer, 0, bytesread);
                    }

                    Body.Close();
                    reader.Close();
                }

                if (request != null)
                {
                    // Get the response
                    using (HttpWebResponse wRes = (HttpWebResponse)request.GetResponse())
                    {
                        result = GetResponse(wRes);
                        wRes.Close();
                    }
                }
            }
            catch (Exception ex)
            {
                result = GetExceptionMessage(ex);
            }

            return result;
        }

        /// <summary>
        /// Read from file until reaching a line break
        /// </summary>
        /// <param name="fstream">FileStream to read from</param>
        /// <returns>the line of text that was just read</returns>
        private string ReadLine(FileStream fstream)
        {
            long pos1 = fstream.Position;

            byte[] newline = Encoding.ASCII.GetBytes(Environment.NewLine);

            while (fstream.Position < fstream.Length)
            {
                if (fstream.ReadByte() == newline[0] && fstream.ReadByte() == newline[1])
                    break;
            }

            long pos2 = fstream.Position;
            fstream.Position = pos1;

            byte[] buffer = new byte[pos2 - pos1 - 2];
            fstream.Read(buffer, 0, buffer.Length);

            fstream.Position = pos2;

            return Encoding.ASCII.GetString(buffer, 0, buffer.Length);
        }

        /// <summary>
        /// Some known headers need to be parsed and assigned to Request properties
        /// </summary>
        /// <param name="line">Header Line to be processed</param>
        /// <param name="request">HttpWebRequest object which is being configured</param>
        private void SetHeader(string line, HttpWebRequest request)
        {
            string[] header = line.Split(':');
            string value = header[1].Trim();
            switch (header[0].ToLower())
            {
                case "content-type":
                    request.ContentType = value;
                    break;
                case "accept":
                    request.Accept = value;
                    break;
                case "expect":
                    if (value.Equals("100-continue", StringComparison.OrdinalIgnoreCase))
                        request.ServicePoint.Expect100Continue = true;
                    break;
                case "host":
                    request.Host = value;
                    break;
                case "user-agent":
                    request.UserAgent = value;
                    break;
                case "connection":
                    switch (value.ToLower())
                    {
                        case "keep-alive":
                            request.KeepAlive = true;
                            break;
                        case "close":
                            request.KeepAlive = false;
                            break;
                        default:
                            request.Connection = value;
                            break;
                    }
                    break;
                case "content-length":
                    request.ContentLength = long.Parse(value);
                    break;
                default:
                    request.Headers.Add(line);
                    break;
            }
        }

        /// <summary>
        /// Try to present most complete error message
        /// </summary>
        /// <param name="ex">Exception which was captured</param>
        /// <returns>string containing the error message</returns>
        private string GetExceptionMessage(Exception ex)
        {
            string result = string.Empty;
            if (ex is WebException)
            {
                WebResponse errResp = ((WebException)ex).Response;
                result = GetResponse((HttpWebResponse)errResp);
            }
            if (string.IsNullOrEmpty(result))
            {
                result = GetErrorStackTrace(ex);
            }
            return result;
        }

        /// <summary>
        /// Builds output based on the response and includes headers
        /// </summary>
        /// <param name="wRes">HttpWebResponse</param>
        /// <returns>string representing server response</returns>
        protected string GetResponse(HttpWebResponse wRes)
        {
            StringBuilder result = new StringBuilder();

            if (wRes != null)
            {
                result.Append("HTTP/").Append(wRes.ProtocolVersion).Append(" ").Append((int)wRes.StatusCode).Append(" ").Append(wRes.StatusDescription).AppendLine();

                foreach (string h in wRes.Headers.AllKeys)
                {
                    result.Append(h).Append(": ").Append(wRes.Headers[h]).AppendLine();
                }
                result.AppendLine();

                Stream ResponseStream = wRes.GetResponseStream();
                using (StreamReader stReader = new StreamReader(ResponseStream))
                {
                    result.Append(stReader.ReadToEnd());
                    stReader.Close();
                }
                ResponseStream.Close();
                wRes.Close();
            }

            return result.ToString();
        }

        public static string GetErrorStackTrace(Exception ex)
        {
            if (ex == null)
                return "";
            return ex.Message + "\r\n" + ex.StackTrace + "\r\n" + GetErrorStackTrace(ex.InnerException);
        }

    }

NCAT, PCAP, Request.SaveAs