One Computer, Many IP Addresses

From Richard's Wiki
Jump to: navigation, search

You might want to test a load-balanced website, that is generate load from a bunch of different source IP addresses. Microsoft Test Load Agent product can do this, but it isn't covered by MSDN subscritption and we don't have it.

It turns out you can generate a bunch of source IP addresses on any client machine, even if it has only a single NIC card. And you can initiate requests within normal Visual Studio web test via your own extension.

First you need to configure a bunch of static IPs on your machine with the normal Windows IPV4 Protocol Advanced Properties dialog on the network manager thingy - make sure you don't clash with other IPs on your network. You can then attach HttpWebRequests to each of those IP addresses in code.

The key is to use the ServicePointManager (System.Net) to obtain a ServicePoint endpoint on an IP address you've configured on your machine. Details are given in http://www.devnewsgroups.net/dotnetframework/t31841-invoke-httpwebrequest-specific-ip-card-how.aspx

Example is (change the example IP "192.168.2.102" below to match one you've configured):

static void Get(Uri uri)
{
 HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
 request.UserAgent =
   "Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.7.8) " +
   "Gecko/20050511 Firefox/1.0.4";
 ServicePoint servicePoint = 
   ServicePointManager.FindServicePoint(uri);
 servicePoint.BindIPEndPointDelegate = new BindIPEndPoint(Bind);
 using (MemoryStream memoryStream = new MemoryStream(0x10000))
 using (HttpWebResponse response = 
  (HttpWebResponse) request.GetResponse())
 using (Stream responseStream = response.GetResponseStream())
 {
   // Add a breakpoint on the next line. When the breakpoint is 
   // being hit, do a "netstat -p tcp" on a command line and 
   // verify that port 8888 is being used.
   byte[] buffer = new byte[0x1000];
   int bytes;
   while ((bytes = responseStream.Read(buffer, 0, buffer.Length)) > 0)
   {
     memoryStream.Write(buffer, 0, bytes);
   }
   string text = Encoding.UTF8.GetString(memoryStream.ToArray());
   Console.WriteLine(text);
 }
}
static IPEndPoint Bind(ServicePoint servicePoint, IPEndPoint   
 remoteEndPoint, int retryCount) 
{
 IPAddress address = IPAddress.Parse("192.168.2.102");
 return new IPEndPoint(address, 8888);
}

Key fragments to do this in a web test (well, the whole thing, really) are:

   //========================================================================
   public class IpCycleTestHelper
   {
       private int _testNumber = 0;
       private string[] _ipAddresses;
       //--------------------------------------------------------------------
       public IpCycleTestHelper()
       {
           _ipAddresses = GetLocalIpAddresses();
       }
       //--------------------------------------------------------------------
       private static string[] GetLocalIpAddresses()
       {
           List<string> result = new List<string>();
           string localHostName = Dns.GetHostName();
           IPHostEntry hostEntry = Dns.GetHostEntry(localHostName);
           foreach (IPAddress ipAddr in hostEntry.AddressList)
           {
               Debug.Print("Found local IP = {0}", ipAddr.ToString());
               result.Add(ipAddr.ToString());
           }
           return result.ToArray();
       }
       //--------------------------------------------------------------------
       public int GetNextTestNumber()
       {
           _testNumber++;
           return _testNumber;
       }
       //--------------------------------------------------------------------
       public string GetNextIpAddress()
       {
           int ipIndex = _testNumber % _ipAddresses.Length;
           return _ipAddresses[ipIndex];
       }
   }

public class IpCycleWebTestPlugin : WebTestPlugin
   {
       private const string TEST_CONTEXT_NAME = "Test Context";
       private const int MAX_BIND_RETRIES = 100;
       private static IpCycleTestHelper _ipCycleTestHelper = new IpCycleTestHelper();
       //====================================================================
       private class TestContext
       {
           public int TestNumber { get; set; }
           public string IpAddress { get; set; }
       }
       //--------------------------------------------------------------------
       private TestContext GetTestContext(WebTest webTest)
       {
           if (!webTest.Context.ContainsKey(TEST_CONTEXT_NAME))
           {
               webTest.Context.Add(TEST_CONTEXT_NAME, new TestContext());
           }
           return webTest.Context[TEST_CONTEXT_NAME] as TestContext;
       }
       //--------------------------------------------------------------------
       public override void PreWebTest(object sender, PreWebTestEventArgs e)
       {
           base.PreWebTest(sender, e);
           TestContext testContext = GetTestContext(e.WebTest);
           testContext.TestNumber = _ipCycleTestHelper.GetNextTestNumber();
           testContext.IpAddress = _ipCycleTestHelper.GetNextIpAddress();
           Debug.Print("Starting Web Test {0}", testContext.TestNumber);
       }
       //--------------------------------------------------------------------
       public override void PreRequest(object sender, PreRequestEventArgs e)
       {
           base.PreRequest(sender, e);
           TestContext testContext = GetTestContext(e.WebTest);
           Debug.Print("Binding request, test = {0}, IP = {1}, request = {2}", testContext.TestNumber, testContext.IpAddress, e.Request.Url);
           ServicePoint servicePoint = ServicePointManager.FindServicePoint(e.Request.Url, e.WebTest.WebProxy);
           if (servicePoint != null && servicePoint.BindIPEndPointDelegate == null)
           {
               servicePoint.BindIPEndPointDelegate = new BindIPEndPoint(delegate(ServicePoint sp, IPEndPoint ep, int retryCount)
               {
                   if (retryCount == MAX_BIND_RETRIES)
                   {
                       Debug.Print("** Binding aborted after {0} attempts! test = {1}, IP = {2}, request = {3}", MAX_BIND_RETRIES, testContext.TestNumber, testContext.IpAddress, e.Request.Url);
                       return null;
                   }
                   IPAddress address = IPAddress.Parse(testContext.IpAddress);
                   return new IPEndPoint(address, 0);
               });
           }
       }
       //--------------------------------------------------------------------
       public override void PostRequest(object sender, PostRequestEventArgs e)
       {
           base.PostRequest(sender, e);
           ServicePoint servicePoint = ServicePointManager.FindServicePoint(e.Request.Url, e.WebTest.WebProxy);
           if (servicePoint != null)
           {
               servicePoint.BindIPEndPointDelegate = null;
           }
       }
   }