windows-nt/Source/XPSP1/NT/net/tcpip/tpipv6/samples/genproxy/genproxy.c

632 lines
21 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
//
// 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);
}