Floating IP (or Virtual IP) on Windows XP and newer

Discussion in 'Windows XP Networking' started by Jeff55, Nov 24, 2009.

  1. Jeff55

    Jeff55 Guest

    We are trying to create a redundant environment where we have two
    machines configured to handle our application and our application on
    the two machines negotiates which one is active and which copy is
    standby. We would like to create an IP address which the client
    software uses to connect to the application but we cannot figure out
    how to activate it either via a script or a C program on the machine
    which is hosting the live instance. The machines are configured with
    DHCP and this virtual address has been reserved on the DHCP server so
    it never gets assigned.

    TIA
     
    Jeff55, Nov 24, 2009
    #1
    1. Advertisements

  2. Jeff55

    Anteaus Guest

    The netsh command will allow you to assign an IP address to an interfsce,
    plus a lot more besides. The syntax is a little cryptic but there are plenty
    sites with details of how to use it.

    Bear in mind that assigning the same IP to a different computer may confuse
    a 'smart' switch, requring a hub reboot (or long wait) for the connection to
    reestablish.

    "Jeff55" wrote:

    > We are trying to create a redundant environment where we have two
    > machines configured to handle our application and our application on
    > the two machines negotiates which one is active and which copy is
    > standby. We would like to create an IP address which the client
    > software uses to connect to the application but we cannot figure out
    > how to activate it either via a script or a C program on the machine
    > which is hosting the live instance. The machines are configured with
    > DHCP and this virtual address has been reserved on the DHCP server so
    > it never gets assigned.
    >
    > TIA
    >
    > .
    >
     
    Anteaus, Nov 25, 2009
    #2
    1. Advertisements

  3. Jeff55

    Jeff55 Guest

    Thank you.

    We've been playing with netsh but I was hoping to find some some way
    to
    do it from C. I've been playing with AddIPAddress but this fails when
    DHCP
    is enabled. netsh fails too with DHCP but if we disable that first,
    then it works.

    On Nov 25, 2:41 am, Anteaus <> wrote:
    > The netsh command will allow you to assign an IP address to an interfsce,
    > plus a lot more besides. The syntax is a little cryptic but there are plenty
    > sites with details of how to use it.
    >
    > Bear in mind that assigning the same IP to a different computer may confuse
    > a 'smart' switch, requring a hub reboot (or long wait) for the connectionto
    > reestablish.
    >
    > "Jeff55" wrote:
    > > We are trying to create a redundant environment where we have two
    > > machines configured to handle our application and our application on
    > > the two machines negotiates which one is active and which copy is
    > > standby. We would like to create an IP address which the client
    > > software uses to connect to the application but we cannot figure out
    > > how to activate it either via a script or a C program on the machine
    > > which is hosting the live instance. The machines are configured with
    > > DHCP and this virtual address has been reserved on the DHCP server so
    > > it never gets assigned.

    >
    > > TIA

    >
    > > .
     
    Jeff55, Nov 25, 2009
    #3
  4. Jeff55

    Jeff55 Guest

    Oh, I spoke too soon. DHCP is a problem regardless of whether we try
    to call AddIPAddress or use netsh. If DHCP is enabled on the interface
    we can't add a 2nd address. This is on XP so maybe the behavior
    is different now. If we disable DHCP we can add the 2nd address
    however
    the address is disabled if DHCP is re-enabled. We can leave DHCP
    disabled
    until the application shuts down but if the box reboots, we don't get
    a chance
    to re-enable DHCP so it is disabled after the reboot.

    On Nov 25, 2:41 am, Anteaus <> wrote:
    > The netsh command will allow you to assign an IP address to an interfsce,
    > plus a lot more besides. The syntax is a little cryptic but there are plenty
    > sites with details of how to use it.
    >
    > Bear in mind that assigning the same IP to a different computer may confuse
    > a 'smart' switch, requring a hub reboot (or long wait) for the connectionto
    > reestablish.
    >
    > "Jeff55" wrote:
    > > We are trying to create a redundant environment where we have two
    > > machines configured to handle our application and our application on
    > > the two machines negotiates which one is active and which copy is
    > > standby. We would like to create an IP address which the client
    > > software uses to connect to the application but we cannot figure out
    > > how to activate it either via a script or a C program on the machine
    > > which is hosting the live instance. The machines are configured with
    > > DHCP and this virtual address has been reserved on the DHCP server so
    > > it never gets assigned.

    >
    > > TIA

    >
    > > .
     
    Jeff55, Nov 26, 2009
    #4
  5. Jeff55

    Jeff55 Guest

    Seems we were confused and DHCP doesn't actually interfere with adding
    an address
    via AddIPAddress however it does block netsh from adding a 2nd
    address.

    For anyone interested here is a C program built mostly from examples
    in the MSDN
    which uses AddIPAddress and DeleteIPAddress and will list the
    interfaces and/or
    the current set of ip addresses.

    ---------------------------------------------------------------------------------------------------------------------------------
    /* Program: iphelper..c
    * This is a collection of code pulled together from various
    * examples in the MSDN to create a program that can add and
    * delete IP alias addresses using AddIPAddress and DeleteIPAddress
    *
    * cc [-Zi] -c iphelper.c
    * link [/debug] iphelper.obj
    */
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <iphlpapi.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <ctype.h>

    #pragma comment(lib, "iphlpapi.lib")
    #pragma comment(lib, "ws2_32.lib")

    #define MALLOC(x) HeapAlloc(GetProcessHeap(), 0, (x))
    #define FREE(x) HeapFree(GetProcessHeap(), 0, (x))

    /* Note: could also use malloc() and free() */
    int listInterfaces();
    int listAddresses();
    int addAddress(UINT,UINT,UINT);
    int delAddress(UINT);
    int findAdapter(UINT);
    int findIndex(UINT);

    dohelp(char *prog) {
    printf("usage: %s -a IPAddress NewIPAddress SubnetMask\n", prog);
    printf("usage: %s -d IPAddress\n", prog);
    printf("usage: %s -li list interfaces\n", prog);
    printf("usage: %s -la list addresses\n", prog);
    }

    int __cdecl main(int argc, char **argv)
    {
    UINT iaNewIPAddress;
    UINT iaIPAddress;
    UINT iaIPMask;
    char opt;
    int rc;
    ULONG context;

    if (argc < 2 || *argv[1] != '-') {
    dohelp(argv[0]);
    exit(1);
    }
    opt = argv[1][1];
    opt = tolower(opt);
    if (opt != 'l' && opt != 'a' && opt != 'd') {
    printf("invalid option -%c\n",opt);
    dohelp(argv[0]);
    exit(1);
    }
    if (opt == 'l') {
    opt = argv[1][2];
    opt = tolower(opt);
    if (opt == 'i')
    listInterfaces();
    else if (opt == 'a')
    listAddresses();
    else {
    printf("invalid list type -l%c\n",opt);
    dohelp(argv[0]);
    }
    exit(0);
    }

    if ((opt == 'd' && argc != 3) ||
    (opt == 'a' && argc != 5)) {
    printf("Insufficient arguments for -%c\n",opt);
    dohelp(argv[0]);
    exit(1);
    }


    if (opt == 'a') {
    iaIPAddress = inet_addr(argv[2]);
    if (iaIPAddress == INADDR_NONE) {
    printf("1st argument is not a valid ip address\n");
    dohelp(argv[0]);
    exit(1);
    }

    iaNewIPAddress = inet_addr(argv[3]);
    if (iaNewIPAddress == INADDR_NONE) {
    printf("New IP address is invalid\n");
    dohelp(argv[0]);
    exit(1);
    }
    iaIPMask = inet_addr(argv[4]);
    if (iaIPMask == INADDR_NONE) {
    printf("Invalid subnet mask\n");
    dohelp(argv[0]);
    exit(1);
    }
    rc = addAddress(iaIPAddress, iaNewIPAddress, iaIPMask);
    }

    if (opt == 'd') {
    iaIPAddress = inet_addr(argv[2]);
    if (iaIPAddress == INADDR_NONE) {
    printf("1st argument is not a valid ip address\n");
    dohelp(argv[0]);
    exit(1);
    }

    rc = delAddress(iaIPAddress);
    }
    exit(rc);
    }

    /* Note: could also use malloc() and free() */

    addAddress(UINT iaTargIPAddress, UINT iaIPAddress, UINT iaIPMask)
    {
    /* Variables used by GetIpAddrTable */
    DWORD ifIndex;

    /* Variables where handles to the added IP are returned */
    ULONG NTEContext = 0;
    ULONG NTEInstance = 0;
    DWORD dwRetVal = 0;

    /* Variables used to return error message */
    LPVOID lpMsgBuf;


    ifIndex = findAdapter(iaTargIPAddress);
    if (ifIndex == -1) {
    printf("Failed to find existing adaptor for target address\n");
    return 1; /* failure */
    }

    if ((dwRetVal = AddIPAddress(iaIPAddress,
    iaIPMask,
    ifIndex,
    &NTEContext, &NTEInstance)) == NO_ERROR) {
    printf("\tIPv4 address was successfully added.\n");
    } else {
    printf("AddIPAddress failed with error: %d\n", dwRetVal);

    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
    dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default
    language
    (LPTSTR) & lpMsgBuf, 0, NULL)) {
    printf("\tError: %s", lpMsgBuf);
    LocalFree(lpMsgBuf);
    return 1;
    }
    }
    return 0;
    }

    delAddress(UINT iAddr)
    {
    DWORD dwRetVal = 0;
    int context;

    context = findIndex(iAddr);
    if (context == -1) {
    printf("Failed to find address to delete\n");
    return 1;
    }

    if ((dwRetVal = DeleteIPAddress(context)) != NO_ERROR) {
    printf("DeleteIPAddress call failed with %d\n", dwRetVal);
    }
    return 0;
    }


    listInterfaces() {
    /* Declare and initialize variables */

    // It is possible for an adapter to have multiple
    // IPv4 addresses, gateways, and secondary WINS servers
    // assigned to the adapter.
    //
    // Note that this sample code only prints out the
    // first entry for the IP address/mask, and gateway, and
    // the primary and secondary WINS server for each adapter.

    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    DWORD dwRetVal = 0;
    UINT i;

    /* variables used to print DHCP time info */
    struct tm newtime;
    char buffer[32];
    errno_t error;

    ULONG ulOutBufLen = sizeof (IP_ADAPTER_INFO);
    pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(sizeof
    (IP_ADAPTER_INFO));
    if (pAdapterInfo == NULL) {
    printf("Error allocating memory needed to call GetAdaptersinfo
    \n");
    return 1;
    }
    // Make an initial call to GetAdaptersInfo to get
    // the necessary size into the ulOutBufLen variable
    if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) ==
    ERROR_BUFFER_OVERFLOW) {
    FREE(pAdapterInfo);
    pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(ulOutBufLen);
    if (pAdapterInfo == NULL) {
    printf("Error allocating memory needed to call GetAdaptersinfo\n");
    return 1;
    }
    }

    if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) ==
    NO_ERROR) {
    pAdapter = pAdapterInfo;
    while (pAdapter) {
    printf("\tComboIndex: \t%d\n", pAdapter->ComboIndex);
    printf("\tAdapter Name: \t%s\n", pAdapter->AdapterName);
    printf("\tAdapter Desc: \t%s\n", pAdapter->Description);
    printf("\tAdapter Addr: \t");
    for (i = 0; i < pAdapter->AddressLength; i++) {
    if (i == (pAdapter->AddressLength - 1))
    printf("%.2X\n", (int) pAdapter->Address);
    else
    printf("%.2X-", (int) pAdapter->Address);
    }
    printf("\tIndex: \t%d\n", pAdapter->Index);
    printf("\tType: \t");
    switch (pAdapter->Type) {
    case MIB_IF_TYPE_OTHER:
    printf("Other\n");
    break;
    case MIB_IF_TYPE_ETHERNET:
    printf("Ethernet\n");
    break;
    case MIB_IF_TYPE_TOKENRING:
    printf("Token Ring\n");
    break;
    case MIB_IF_TYPE_FDDI:
    printf("FDDI\n");
    break;
    case MIB_IF_TYPE_PPP:
    printf("PPP\n");
    break;
    case MIB_IF_TYPE_LOOPBACK:
    printf("Lookback\n");
    break;
    case MIB_IF_TYPE_SLIP:
    printf("Slip\n");
    break;
    default:
    printf("Unknown type %ld\n", pAdapter->Type);
    break;
    }

    printf("\tIP Address: \t%s\n",
    pAdapter->IpAddressList.IpAddress.String);
    printf("\tIP Mask: \t%s\n", pAdapter->IpAddressList.IpMask.String);

    printf("\tGateway: \t%s\n", pAdapter->GatewayList.IpAddress.String);
    printf("\t***\n");

    if (pAdapter->DhcpEnabled) {
    printf("\tDHCP Enabled: Yes\n");
    printf("\t DHCP Server: \t%s\n",
    pAdapter->DhcpServer.IpAddress.String);

    printf("\t Lease Obtained: ");
    /* Display local time */
    error = _localtime32_s(&newtime, (__time32_t*) &pAdapter-
    >LeaseObtained);

    if (error)
    printf("Invalid Argument to _localtime32_s\n");
    else {
    // Convert to an ASCII representation
    error = asctime_s(buffer, 32, &newtime);
    if (error)
    printf("Invalid Argument to asctime_s\n");
    else
    /* asctime_s returns the string terminated by \n\0 */
    printf("%s", buffer);
    }

    printf("\t Lease Expires: ");
    error = _localtime32_s(&newtime, (__time32_t*) &pAdapter-
    >LeaseExpires);

    if (error)
    printf("Invalid Argument to _localtime32_s\n");
    else {
    // Convert to an ASCII representation
    error = asctime_s(buffer, 32, &newtime);
    if (error)
    printf("Invalid Argument to asctime_s\n");
    else
    /* asctime_s returns the string terminated by \n\0 */
    printf("%s", buffer);
    }
    } else
    printf("\tDHCP Enabled: No\n");

    if (pAdapter->HaveWins) {
    printf("\tHave Wins: Yes\n");
    printf("\t Primary Wins Server: %s\n",
    pAdapter->PrimaryWinsServer.IpAddress.String);
    printf("\t Secondary Wins Server: %s\n",
    pAdapter->SecondaryWinsServer.IpAddress.String);
    } else
    printf("\tHave Wins: No\n");
    pAdapter = pAdapter->Next;
    printf("\n");
    }
    } else {
    printf("GetAdaptersInfo failed with error: %d\n", dwRetVal);

    }
    if (pAdapterInfo)
    FREE(pAdapterInfo);

    return 0;
    }
    int findAdapter(UINT iaAddress) {
    int i;
    int ifIndex;

    /* Variables used by GetIpAddrTable */
    PMIB_IPADDRTABLE pIPAddrTable;
    DWORD dwSize = 0;
    DWORD dwRetVal = 0;
    IN_ADDR IPAddr;

    /* Variables used to return error message */
    LPVOID lpMsgBuf;

    // Before calling AddIPAddress we use GetIpAddrTable to get
    // an adapter to which we can add the IP.
    pIPAddrTable = (MIB_IPADDRTABLE *) MALLOC(sizeof
    (MIB_IPADDRTABLE));

    if (pIPAddrTable) {
    // Make an initial call to GetIpAddrTable to get the
    // necessary size into the dwSize variable
    if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) ==
    ERROR_INSUFFICIENT_BUFFER) {
    FREE(pIPAddrTable);
    pIPAddrTable = (MIB_IPADDRTABLE *) MALLOC(dwSize);

    }
    if (pIPAddrTable == NULL) {
    printf("Memory allocation failed for GetIpAddrTable\n");
    return -1;
    }
    }
    // Make a second call to GetIpAddrTable to get the
    // actual data we want
    if ( (dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 )) !=
    NO_ERROR ) {
    printf("GetIpAddrTable failed with error %d\n", dwRetVal);
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
    dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default
    language
    (LPTSTR) & lpMsgBuf, 0, NULL)) {
    printf("\tError: %s", lpMsgBuf);
    LocalFree(lpMsgBuf);
    }
    return -1;
    }

    ifIndex = -1; /* not found */
    for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) {
    if ((u_long) pIPAddrTable->table.dwAddr == iaAddress) {
    ifIndex = pIPAddrTable->table.dwIndex;
    goto done;
    }
    }
    done:
    if (pIPAddrTable) {
    FREE(pIPAddrTable);
    pIPAddrTable = NULL;
    }

    return ifIndex;
    }

    int listAddresses() {
    int i;
    /* Variables used by GetIpAddrTable */
    PMIB_IPADDRTABLE pIPAddrTable;
    DWORD dwSize = 0;
    DWORD dwRetVal = 0;
    IN_ADDR IPAddr;

    /* Variables used to return error message */
    LPVOID lpMsgBuf;

    // Before calling AddIPAddress we use GetIpAddrTable to get
    // an adapter to which we can add the IP.
    pIPAddrTable = (MIB_IPADDRTABLE *) MALLOC(sizeof
    (MIB_IPADDRTABLE));

    if (pIPAddrTable) {
    // Make an initial call to GetIpAddrTable to get the
    // necessary size into the dwSize variable
    if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) ==
    ERROR_INSUFFICIENT_BUFFER) {
    FREE(pIPAddrTable);
    pIPAddrTable = (MIB_IPADDRTABLE *) MALLOC(dwSize);

    }
    if (pIPAddrTable == NULL) {
    printf("Memory allocation failed for GetIpAddrTable\n");
    return 0;
    }
    }
    // Make a second call to GetIpAddrTable to get the
    // actual data we want
    if ( (dwRetVal = GetIpAddrTable( pIPAddrTable, &dwSize, 0 )) !=
    NO_ERROR ) {
    printf("GetIpAddrTable failed with error %d\n", dwRetVal);
    if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
    FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
    dwRetVal, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default
    language
    (LPTSTR) & lpMsgBuf, 0, NULL)) {
    printf("\tError: %s", lpMsgBuf);
    LocalFree(lpMsgBuf);
    }
    return 0;
    }

    printf("\tNum Entries: %ld\n", pIPAddrTable->dwNumEntries);
    for (i=0; i < (int) pIPAddrTable->dwNumEntries; i++) {
    printf("\n\tInterface Index[%d]:\t%ld\n", i, pIPAddrTable->table
    .dwIndex);
    IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table.dwAddr;
    printf("\tIP Address[%d]: \t%s\n", i, inet_ntoa(IPAddr) );
    IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table.dwMask;
    printf("\tSubnet Mask[%d]: \t%s\n", i, inet_ntoa(IPAddr) );
    IPAddr.S_un.S_addr = (u_long) pIPAddrTable->table
    .dwBCastAddr;
    printf("\tBroadCast[%d]: \t%s (%ld%)\n", i, inet_ntoa
    (IPAddr), pIPAddrTable->table.dwBCastAddr);
    printf("\tReassembly size[%d]:\t%ld\n", i, pIPAddrTable->table
    .dwReasmSize);
    printf("\tType and State[%d]:", i);
    if (pIPAddrTable->table.wType & MIB_IPADDR_PRIMARY)
    printf("\tPrimary IP Address");
    if (pIPAddrTable->table.wType & MIB_IPADDR_DYNAMIC)
    printf("\tDynamic IP Address");
    if (pIPAddrTable->table.wType & MIB_IPADDR_DISCONNECTED)
    printf("\tAddress is on disconnected interface");
    if (pIPAddrTable->table.wType & MIB_IPADDR_DELETED)
    printf("\tAddress is being deleted");
    if (pIPAddrTable->table.wType & MIB_IPADDR_TRANSIENT)
    printf("\tTransient address");
    printf("\n");
    }

    if (pIPAddrTable) {
    FREE(pIPAddrTable);
    pIPAddrTable = NULL;
    }

    return 0;
    }

    int findIndex(UINT iFindAddr) {
    /* Declare and initialize variables */

    // It is possible for an adapter to have multiple
    // IPv4 addresses, gateways, and secondary WINS servers
    // assigned to the adapter.
    //
    // Note that this sample code only prints out the
    // first entry for the IP address/mask, and gateway, and
    // the primary and secondary WINS server for each adapter.

    IP_ADDR_STRING * pIPAddr;
    PIP_ADAPTER_INFO pAdapterInfo;
    PIP_ADAPTER_INFO pAdapter = NULL;
    DWORD dwRetVal = 0;
    UINT i;
    UINT iAddr;
    int retContext;

    /* variables used to print DHCP time info */
    struct tm newtime;
    char buffer[32];
    errno_t error;

    ULONG ulOutBufLen = sizeof (IP_ADAPTER_INFO);
    pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(sizeof
    (IP_ADAPTER_INFO));
    if (pAdapterInfo == NULL) {
    printf("Error allocating memory needed to call GetAdaptersinfo\n");
    return -1;
    }
    // Make an initial call to GetAdaptersInfo to get
    // the necessary size into the ulOutBufLen variable
    if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) ==
    ERROR_BUFFER_OVERFLOW) {
    FREE(pAdapterInfo);
    pAdapterInfo = (IP_ADAPTER_INFO *) MALLOC(ulOutBufLen);
    if (pAdapterInfo == NULL) {
    printf("Error allocating memory needed to call GetAdaptersinfo
    \n");
    return -1;
    }
    }
    retContext = -1;
    if ((dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen)) ==
    NO_ERROR) {
    pAdapter = pAdapterInfo;
    while (pAdapter) {
    pIPAddr = &pAdapter->IpAddressList;
    while (pIPAddr) {
    iAddr = inet_addr(pIPAddr->IpAddress.String);
    if (iAddr == iFindAddr) {
    retContext = pIPAddr->Context;
    goto done;
    }
    pIPAddr = pIPAddr->Next;
    }
    pAdapter = pAdapter->Next;
    }
    } else {
    printf("GetAdaptersInfo failed with error: %d\n", dwRetVal);
    }
    done:
    if (pAdapterInfo)
    FREE(pAdapterInfo);

    return retContext;
    }
     
    Jeff55, Nov 27, 2009
    #5
    1. Advertisements

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. James O. Henry

    Windows Virtual Machine

    James O. Henry, Jul 19, 2003, in forum: Windows XP Networking
    Replies:
    1
    Views:
    206
    Dusty Harper {MS}
    Jul 20, 2003
  2. Scott Mooney

    Ghosted XP doesn't work on newer computer

    Scott Mooney, Feb 5, 2004, in forum: Windows XP Networking
    Replies:
    1
    Views:
    180
    knappf
    Mar 31, 2004
  3. Annonymous Coward
    Replies:
    5
    Views:
    288
    Hans-Georg Michna
    Nov 11, 2004
  4. Cindy
    Replies:
    1
    Views:
    1,129
    smlunatick
    Nov 27, 2007
  5. Tony
    Replies:
    5
    Views:
    291
    James Egan
    May 11, 2008
Loading...

Share This Page