// // 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 #include #include #include #include #include // // 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, ""); 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, ""); 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); }