632 lines
21 KiB
C
632 lines
21 KiB
C
//
|
|
// genproxy.c - Generic application level proxy for IPv6/IPv4
|
|
//
|
|
// This program accepts connections on a socket with a given address family
|
|
// and port, and forwards them on a socket of the other address family to
|
|
// a given address (default loopback) using the same port.
|
|
//
|
|
// Basically, it makes an unmodified IPv4 server look like an IPv6 server
|
|
// (or vice-versa). Typically, the proxy will run on the same machine as
|
|
// the server it is fronting, but that doesn't have to be the case.
|
|
//
|
|
// Copyright 1996 - 2000 Microsoft Corporation.
|
|
// All rights reserved.
|
|
//
|
|
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <wspiapi.h>
|
|
|
|
|
|
//
|
|
// What should the proxy server pretend to be?
|
|
// Default is an IPv6 web server.
|
|
//
|
|
#define DEFAULT_PROXY_FAMILY PF_INET6
|
|
#define DEFAULT_SOCKTYPE SOCK_STREAM
|
|
#define DEFAULT_PORT "http"
|
|
|
|
//
|
|
// Configuration parameters.
|
|
//
|
|
#define BUFFER_SIZE (4 * 1024) // Big enough?
|
|
|
|
|
|
typedef struct PerConnection PerConnection;
|
|
|
|
//
|
|
// Information we keep for each direction of a bi-directional connection.
|
|
//
|
|
typedef struct PerOperation {
|
|
BOOL Inbound; // Is this the "receive from client, send to server" side?
|
|
BOOL Receiving; // Is this operation a recv?
|
|
WSABUF Buffer;
|
|
WSAOVERLAPPED Overlapped;
|
|
PerConnection *Connection;
|
|
} PerOperation;
|
|
|
|
//
|
|
// Information we keep for each client connection.
|
|
//
|
|
typedef struct PerConnection {
|
|
int Number;
|
|
BOOL HalfOpen; // Has one side or the other stopped sending?
|
|
SOCKET Client;
|
|
SOCKET Server;
|
|
PerOperation Inbound;
|
|
PerOperation Outbound;
|
|
} PerConnection;
|
|
|
|
//
|
|
// Global variables
|
|
//
|
|
BOOL Verbose = FALSE;
|
|
|
|
//
|
|
// Create state information for a client.
|
|
//
|
|
PerConnection*
|
|
CreateConnectionState(Client, Server)
|
|
{
|
|
static TotalConnections = 1;
|
|
PerConnection *Conn;
|
|
|
|
//
|
|
// Allocate space for a PerConnection structure and two buffers.
|
|
//
|
|
Conn = (PerConnection *)malloc(sizeof(*Conn) + (2 * BUFFER_SIZE));
|
|
if (Conn == NULL)
|
|
return NULL;
|
|
|
|
//
|
|
// Fill everything in.
|
|
//
|
|
Conn->Number = TotalConnections++;
|
|
Conn->HalfOpen = FALSE;
|
|
Conn->Client = Client;
|
|
Conn->Server = Server;
|
|
Conn->Inbound.Inbound = TRUE; // Recv from client, send to server.
|
|
Conn->Inbound.Receiving = TRUE; // Start out receiving.
|
|
Conn->Inbound.Buffer.len = BUFFER_SIZE;
|
|
Conn->Inbound.Buffer.buf = (char *)(Conn + 1);
|
|
Conn->Inbound.Connection = Conn;
|
|
Conn->Outbound.Inbound = FALSE; // Recv from server, send to client.
|
|
Conn->Outbound.Receiving = TRUE; // Start out receiving.
|
|
Conn->Outbound.Buffer.len = BUFFER_SIZE;
|
|
Conn->Outbound.Buffer.buf = Conn->Inbound.Buffer.buf + BUFFER_SIZE;
|
|
Conn->Outbound.Connection = Conn;
|
|
|
|
printf("Created Connecton #%d\n", Conn->Number);
|
|
|
|
return Conn;
|
|
}
|
|
|
|
|
|
void Usage(char *ProgName) {
|
|
fprintf(stderr, "\nGeneric server proxy.\n");
|
|
fprintf(stderr, "\n%s [-f family] [-p port] [-s server] [-v]\n\n",
|
|
ProgName);
|
|
fprintf(stderr, " family\tFamily (PF_INET or PF_INET6) proxy exports. (default %s)\n",
|
|
(DEFAULT_PROXY_FAMILY == PF_INET) ? "PF_INET" : "PF_INET6");
|
|
fprintf(stderr, " port\t\tPort on which to bind. (default %s)\n",
|
|
DEFAULT_PORT);
|
|
fprintf(stderr, " server\tName or address to forward requests to. (default: loopback)\n");
|
|
WSACleanup();
|
|
exit(1);
|
|
}
|
|
|
|
|
|
LPSTR DecodeError(int ErrorCode)
|
|
{
|
|
static char Message[1024];
|
|
|
|
// If this program was multi-threaded, we'd want to use
|
|
// FORMAT_MESSAGE_ALLOCATE_BUFFER instead of a static buffer here.
|
|
// (And of course, free the buffer when we were done with it)
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS |
|
|
FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, ErrorCode,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
(LPSTR)Message, 1024, NULL);
|
|
return Message;
|
|
}
|
|
|
|
|
|
//
|
|
// Find out how many processors are on the system.
|
|
//
|
|
DWORD
|
|
GetNumberOfProcessors(void)
|
|
{
|
|
SYSTEM_INFO SystemInfo;
|
|
|
|
GetSystemInfo(&SystemInfo);
|
|
return SystemInfo.dwNumberOfProcessors;
|
|
}
|
|
|
|
|
|
//
|
|
// This routine waits for asynchronous operations to complete on
|
|
// a particular completion port and handles them.
|
|
//
|
|
// There should be one of these threads per processor on the machine.
|
|
//
|
|
WINAPI
|
|
CompletionPortHandler(LPVOID Param)
|
|
{
|
|
HANDLE CompletionPort = *(HANDLE *)Param;
|
|
PerConnection *Connection;
|
|
PerOperation *Operation;
|
|
OVERLAPPED *Overlapped;
|
|
DWORD BytesTransferred, AmountSent, AmountReceived, RecvFlags;
|
|
int RetVal;
|
|
|
|
while (1) {
|
|
//
|
|
// Wait for one of the asych operations to complete.
|
|
//
|
|
RetVal = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
|
|
(PULONG_PTR)&Connection,
|
|
&Overlapped, INFINITE);
|
|
//
|
|
// Retrieve the state of this operation.
|
|
//
|
|
Operation = CONTAINING_RECORD(Overlapped, PerOperation, Overlapped);
|
|
if (Operation->Connection != Connection) {
|
|
printf("Pointer mismatch in completion status!\n");
|
|
continue;
|
|
}
|
|
|
|
if (Verbose) {
|
|
printf("Handling %s %s on %s side of connection #%d\n",
|
|
RetVal ? "completed" : "aborted",
|
|
Operation->Receiving ? "recv" : "send",
|
|
Operation->Inbound ? "inbound" : "outbound",
|
|
Connection->Number);
|
|
}
|
|
|
|
if (RetVal == 0) {
|
|
if (GetLastError() == 64) {
|
|
printf("Connection #%d %s was reset\n", Connection->Number,
|
|
Operation->Inbound ? "inbound" : "outbound");
|
|
// Fall through, it'll be treated as a close...
|
|
} else {
|
|
fprintf(stderr, "GetQueuedCompletionStatus() failed with error %d: %s\n",
|
|
GetLastError(), DecodeError(GetLastError()));
|
|
// REVIEW: CloseConnection?
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (Operation->Receiving == TRUE) {
|
|
//
|
|
// We just completed a recv.
|
|
// Look for closed/closing connection.
|
|
//
|
|
if (BytesTransferred == 0) {
|
|
if (Operation->Inbound == TRUE) {
|
|
//
|
|
// The client has closed its side of the connection.
|
|
//
|
|
if (Connection->HalfOpen == FALSE) {
|
|
// Server is still around,
|
|
// tell it that the client quits.
|
|
shutdown(Connection->Server, SD_SEND);
|
|
Connection->HalfOpen = TRUE;
|
|
printf("Connection #%d Client quit sending\n",
|
|
Connection->Number);
|
|
} else {
|
|
// Server already quit sending, so close the sockets.
|
|
printf("Connection #%d Client quit too\n",
|
|
Connection->Number);
|
|
closesocket(Connection->Client);
|
|
closesocket(Connection->Server);
|
|
free(Connection);
|
|
}
|
|
} else {
|
|
//
|
|
// The server has closed its side of the connection.
|
|
//
|
|
if (Connection->HalfOpen == FALSE) {
|
|
// Client is still around,
|
|
// tell it that the server quits.
|
|
shutdown(Connection->Client, SD_SEND);
|
|
Connection->HalfOpen = TRUE;
|
|
printf("Connection #%d Server quit sending\n",
|
|
Connection->Number);
|
|
} else {
|
|
// Client already quit sending, so close the sockets.
|
|
printf("Connection #%d Server quit too\n",
|
|
Connection->Number);
|
|
closesocket(Connection->Client);
|
|
closesocket(Connection->Server);
|
|
free(Connection);
|
|
}
|
|
}
|
|
|
|
if (Verbose)
|
|
printf("Leaving Recv Handler\n");
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Connection is still active, and we received some data.
|
|
// Post a send request to forward it onward.
|
|
//
|
|
Operation->Receiving = FALSE;
|
|
Operation->Buffer.len = BytesTransferred;
|
|
RetVal = WSASend(Operation->Inbound ? Connection->Server :
|
|
Connection->Client, &Operation->Buffer, 1,
|
|
&AmountSent, 0, Overlapped, NULL);
|
|
if ((RetVal == SOCKET_ERROR) &&
|
|
(WSAGetLastError() != WSA_IO_PENDING)) {
|
|
//
|
|
// Something bad happened.
|
|
//
|
|
fprintf(stderr, "WSASend() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Connection->Client);
|
|
closesocket(Connection->Server);
|
|
free(Connection);
|
|
}
|
|
|
|
if (Verbose)
|
|
printf("Leaving Recv Handler\n");
|
|
|
|
} else {
|
|
//
|
|
// We just completed a send.
|
|
//
|
|
if (BytesTransferred != Operation->Buffer.len) {
|
|
fprintf(stderr, "WSASend() didn't send entire buffer!\n");
|
|
goto CloseConnection;
|
|
}
|
|
|
|
//
|
|
// Post another recv request since we but live to serve.
|
|
//
|
|
RecvFlags = 0;
|
|
Operation->Receiving = TRUE;
|
|
Operation->Buffer.len = BUFFER_SIZE;
|
|
RetVal = WSARecv(Operation->Inbound ? Connection->Client :
|
|
Connection->Server, &Operation->Buffer, 1,
|
|
&AmountReceived, &RecvFlags, Overlapped, NULL);
|
|
if ((RetVal == SOCKET_ERROR) &&
|
|
(WSAGetLastError() != WSA_IO_PENDING)) {
|
|
//
|
|
// Something bad happened.
|
|
//
|
|
fprintf(stderr, "WSARecv() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
CloseConnection:
|
|
closesocket(Connection->Client);
|
|
closesocket(Connection->Server);
|
|
free(Connection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// Start serving on this socket.
|
|
//
|
|
StartProxy(SOCKET Proxy, ADDRINFO *ServerAI)
|
|
{
|
|
char Hostname[NI_MAXHOST];
|
|
int FromLen, AmountReceived, RetVal;
|
|
SOCKADDR_STORAGE From;
|
|
PerConnection *Conn;
|
|
SOCKET Client, Server;
|
|
DWORD NumberOfWorkers, Flags;
|
|
HANDLE *CompletionPorts;
|
|
unsigned int Loop;
|
|
|
|
//
|
|
// Create a completion port and a worker thread to service it.
|
|
// Do this once for each processor on the system.
|
|
//
|
|
NumberOfWorkers = GetNumberOfProcessors();
|
|
CompletionPorts = malloc(sizeof(HANDLE) * NumberOfWorkers);
|
|
for (Loop = 0; Loop < NumberOfWorkers; Loop++) {
|
|
HANDLE WorkerThread;
|
|
|
|
CompletionPorts[Loop] = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
|
|
NULL, 0, 0);
|
|
if (CompletionPorts[Loop] == NULL) {
|
|
fprintf(stderr, "Couldn't create completion port, error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
WorkerThread = CreateThread(NULL, 0, CompletionPortHandler,
|
|
&CompletionPorts[Loop], 0, NULL);
|
|
if (WorkerThread == NULL) {
|
|
fprintf(stderr, "Couldn't create worker thread, error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//
|
|
// We now put the server into an eternal loop,
|
|
// serving requests as they arrive.
|
|
//
|
|
Loop = 0;
|
|
while(1) {
|
|
|
|
//
|
|
// Wait for a client to connect.
|
|
//
|
|
if (Verbose) {
|
|
printf("Before accept\n");
|
|
}
|
|
FromLen = sizeof(From);
|
|
Client = accept(Proxy, (LPSOCKADDR)&From, &FromLen);
|
|
if (Client == INVALID_SOCKET) {
|
|
if (WSAGetLastError() == 10022)
|
|
continue;
|
|
fprintf(stderr, "accept() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
break;
|
|
}
|
|
if (Verbose) {
|
|
if (getnameinfo((LPSOCKADDR)&From, FromLen, Hostname,
|
|
sizeof(Hostname), NULL, 0, NI_NUMERICHOST) != 0)
|
|
strcpy(Hostname, "<unknown>");
|
|
printf("\nAccepted connection from %s\n", Hostname);
|
|
}
|
|
|
|
//
|
|
// Connect to real server on client's behalf.
|
|
//
|
|
Server = socket(ServerAI->ai_family, ServerAI->ai_socktype,
|
|
ServerAI->ai_protocol);
|
|
if (Server == INVALID_SOCKET) {
|
|
fprintf(stderr,"Error opening real server socket, error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
continue;
|
|
}
|
|
|
|
if (connect(Server, ServerAI->ai_addr, ServerAI->ai_addrlen) == SOCKET_ERROR) {
|
|
fprintf(stderr, "connect() to server failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
closesocket(Server);
|
|
continue;
|
|
}
|
|
if (Verbose) {
|
|
FromLen = sizeof(From);
|
|
if (getpeername(Server, (LPSOCKADDR)&From, &FromLen) == SOCKET_ERROR) {
|
|
fprintf(stderr, "getpeername() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
} else {
|
|
if (getnameinfo((LPSOCKADDR)&From, FromLen, Hostname,
|
|
sizeof(Hostname), NULL, 0, NI_NUMERICHOST) != 0)
|
|
strcpy(Hostname, "<unknown>");
|
|
printf("Connected to server %s, port %d\n",
|
|
Hostname, ntohs(SS_PORT(&From)));
|
|
}
|
|
}
|
|
|
|
Conn = CreateConnectionState(Client, Server);
|
|
|
|
if (CreateIoCompletionPort((HANDLE) Client, CompletionPorts[Loop],
|
|
(ULONG_PTR)Conn, 0) == NULL) {
|
|
fprintf(stderr, "Couldn't attach completion port, error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
closesocket(Server);
|
|
free(Conn);
|
|
continue;
|
|
}
|
|
if (CreateIoCompletionPort((HANDLE) Server, CompletionPorts[Loop],
|
|
(ULONG_PTR)Conn, 0) == NULL) {
|
|
fprintf(stderr, "Couldn't attach completion port, error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
closesocket(Server);
|
|
free(Conn);
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Start things going by posting a recv on both client and server.
|
|
//
|
|
Flags = 0;
|
|
RetVal = WSARecv(Client, &(Conn->Inbound.Buffer), 1, &AmountReceived,
|
|
&Flags, &(Conn->Inbound.Overlapped), NULL);
|
|
|
|
if ((RetVal == SOCKET_ERROR) &&
|
|
(WSAGetLastError() != WSA_IO_PENDING)) {
|
|
//
|
|
// Something bad happened.
|
|
//
|
|
fprintf(stderr, "WSARecv() on Client failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
closesocket(Server);
|
|
free(Conn);
|
|
continue;
|
|
}
|
|
|
|
Flags = 0;
|
|
RetVal = WSARecv(Server, &(Conn->Outbound.Buffer), 1, &AmountReceived,
|
|
&Flags, &(Conn->Outbound.Overlapped), NULL);
|
|
if ((RetVal == SOCKET_ERROR) &&
|
|
(WSAGetLastError() != WSA_IO_PENDING)) {
|
|
//
|
|
// Something bad happened.
|
|
//
|
|
fprintf(stderr, "WSARecv() on Server failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
closesocket(Client);
|
|
closesocket(Server);
|
|
free(Conn);
|
|
continue;
|
|
}
|
|
|
|
if (++Loop == NumberOfWorkers)
|
|
Loop = 0;
|
|
}
|
|
|
|
//
|
|
// Only get here if something bad happened.
|
|
//
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
|
|
int __cdecl
|
|
main(int argc, char **argv)
|
|
{
|
|
int ServerFamily;
|
|
int ProxyFamily = DEFAULT_PROXY_FAMILY;
|
|
char *Port = DEFAULT_PORT;
|
|
char *Address = NULL;
|
|
int i, RetVal;
|
|
WSADATA wsaData;
|
|
ADDRINFO Hints, *AI;
|
|
SOCKET Proxy;
|
|
|
|
// Parse arguments
|
|
if (argc > 1) {
|
|
for (i = 1;i < argc; i++) {
|
|
if ((argv[i][0] == '-') || (argv[i][0] == '/') &&
|
|
(argv[i][1] != 0) && (argv[i][2] == 0)) {
|
|
switch(tolower(argv[i][1])) {
|
|
case 'f':
|
|
if (!argv[i+1])
|
|
Usage(argv[0]);
|
|
if (!strcmp(argv[i+1], "PF_INET"))
|
|
ProxyFamily = PF_INET;
|
|
else if (!strcmp(argv[i+1], "PF_INET6"))
|
|
ProxyFamily = PF_INET6;
|
|
else
|
|
Usage(argv[0]);
|
|
i++;
|
|
break;
|
|
|
|
case 's':
|
|
if (argv[i+1]) {
|
|
if (argv[i+1][0] != '-') {
|
|
Address = argv[++i];
|
|
break;
|
|
}
|
|
}
|
|
Usage(argv[0]);
|
|
break;
|
|
|
|
case 'p':
|
|
if (argv[i+1]) {
|
|
if (argv[i+1][0] != '-') {
|
|
Port = argv[++i];
|
|
break;
|
|
}
|
|
}
|
|
Usage(argv[0]);
|
|
break;
|
|
|
|
case 'v':
|
|
Verbose = TRUE;
|
|
break;
|
|
|
|
default:
|
|
Usage(argv[0]);
|
|
break;
|
|
}
|
|
} else
|
|
Usage(argv[0]);
|
|
}
|
|
}
|
|
|
|
ServerFamily = (ProxyFamily == PF_INET6) ? PF_INET : PF_INET6;
|
|
|
|
// Ask for Winsock version 2.2.
|
|
if ((RetVal = WSAStartup(MAKEWORD(2, 2), &wsaData)) != 0) {
|
|
fprintf(stderr, "WSAStartup failed with error %d: %s\n",
|
|
RetVal, DecodeError(RetVal));
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
if (Port == NULL) {
|
|
Usage(argv[0]);
|
|
}
|
|
|
|
//
|
|
// Determine parameters to use to create and bind the proxy's socket.
|
|
//
|
|
memset(&Hints, 0, sizeof(Hints));
|
|
Hints.ai_family = ProxyFamily;
|
|
Hints.ai_socktype = DEFAULT_SOCKTYPE;
|
|
Hints.ai_flags = AI_PASSIVE;
|
|
RetVal = getaddrinfo(NULL, Port, &Hints, &AI);
|
|
if (RetVal != 0) {
|
|
fprintf(stderr, "getaddrinfo failed with error %d: %s\n",
|
|
RetVal, gai_strerror(RetVal));
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
Proxy = socket(AI->ai_family, AI->ai_socktype, AI->ai_protocol);
|
|
if (Proxy == INVALID_SOCKET){
|
|
fprintf(stderr, "socket() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
freeaddrinfo(AI);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
if (bind(Proxy, AI->ai_addr, AI->ai_addrlen) == SOCKET_ERROR) {
|
|
fprintf(stderr,"bind() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
freeaddrinfo(AI);
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
if (listen(Proxy, 5) == SOCKET_ERROR) {
|
|
fprintf(stderr, "listen() failed with error %d: %s\n",
|
|
WSAGetLastError(), DecodeError(WSAGetLastError()));
|
|
freeaddrinfo(AI);
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
printf("'Listening' on port %s, protocol family %s\n",
|
|
Port, (AI->ai_family == PF_INET) ? "PF_INET" : "PF_INET6");
|
|
|
|
freeaddrinfo(AI);
|
|
|
|
//
|
|
// Determine the parameters to use to create and connect the
|
|
// sockets used to communicate with the real server.
|
|
//
|
|
memset(&Hints, 0, sizeof(Hints));
|
|
Hints.ai_family = ServerFamily;
|
|
Hints.ai_socktype = DEFAULT_SOCKTYPE;
|
|
RetVal = getaddrinfo(Address, Port, &Hints, &AI);
|
|
if (RetVal != 0) {
|
|
fprintf(stderr, "Cannot resolve address [%s] and port [%s], error %d: %s\n",
|
|
Address, Port, RetVal, gai_strerror(RetVal));
|
|
closesocket(Proxy);
|
|
WSACleanup();
|
|
return -1;
|
|
}
|
|
|
|
StartProxy(Proxy, AI);
|
|
}
|