Limited Socks Proxy.
/* * Implements a minimal subset of RFC 1928. Will listen to standard or * given port or 1080 and handles requests. Clients are handled in * separate threads. No authentication (only method 0), only TCP 4, * only connect requests. */ #include <cstdlib> #include <cstring> #include <iostream> #include <iomanip> #include <string> #include <memory> #include <algorithm> #include <thread> #include <utility> #include <cleansocks.h> #include <cleanip.h> using namespace cleansocks; /* * We need to use failure codes from socket to determine the response * code to the client. Unfortunately, these are system-dependent. * So we have two versions to map the codes to socks error responses. */ #ifdef WIN32 unsigned char errmap(int err) { switch(err) { case WSAECONNREFUSED: return 5; case WSAEHOSTUNREACH: case WSAEHOSTDOWN: return 4; case WSAENETDOWN: case WSAENETUNREACH: return 3; default: return 1; } } #else unsigned char errmap(int err) { switch(err) { case ECONNREFUSED: return 5; case ENONET: case EHOSTDOWN: case EHOSTUNREACH: return 4; case ENETUNREACH: return 3; default: return 1; } } #endif /* * Holds a variable field result. I should probably just use a vector, but * that seems like such overkill. Can't use a template array since the * size varies, and don't want use a native dynamic since it doesn't know * its size. */ struct vf { // Apparently, the mingw that comes with cleansocks on Windows is too // old to have std::make_unique. //vf(int s): size(s), data(std::make_unique<unsigned char[]>(s)) { } vf(int s): size(s), data (std::unique_ptr<unsigned char[]>(new unsigned char[s])) { } int size; std::unique_ptr<unsigned char[]> data; unsigned char * begin() { return data.get(); } unsigned char * end() { return begin() + size; } }; /* * Read a variable-length field. Read the length byte, then the specified * amount of data. Returns the result wrapped in a struct vf (see just above). */ vf readvar(TCPsocket from) { unsigned char size = 0; recv(from, &size, 1); vf ret(size); recv(from, ret.begin(), ret.size, cleansocks::CSMSG_WAITALL); return ret; } /* * Thread that forwards between sockets from from to to, until from * returns eof. Then closes to and returns. */ void fwd(TCPsocket from, TCPsocket to) { unsigned char buf[1024]; // Any size. int nread; try { while((nread = recv(from, buf, sizeof buf)) > 0) { send(to, buf, nread); } shutdownsend(to); } catch(socket_sys_error &e) { // Seems to have a lot of connection crashes. Maybe // hosts don't bother much with nice shutdown? //std::cout << "Good heavens! " << e.what() << std::endl; } } /* * Runs in a thread to serve a client. Peforms the interaction with the * client: Reads and responds to the selection, and reads the command * (which can only be connect) and attempts to perform it. Generates * a response (success or failure), then, if successful, forwards the * stream between the client and the server. */ void serve_client(TCPsocket reader) { // Read the selection method. Two parts unsigned char ver; int cnt = recv(reader, &ver, sizeof ver); auto meths = readvar(reader); // We only accept version 5 and method 0. if(ver != 5 || std::find(meths.begin(), meths.end(), 0) == meths.end()) { // This appears to be the only option for an error response. unsigned char bad[] = { 5, 0xff }; send(reader, bad, sizeof bad); close(reader); return; } // Approve, and select no authentication. { unsigned char ok[] = { 5, 0 }; send(reader, ok, sizeof ok); } // Read the first four bytes of the request. unsigned char prefix[4]; recv(reader, prefix, sizeof prefix, CSMSG_WAITALL); // Reply code for the response. // Change from 255 when something happens. unsigned char reason = 0; // Well, okay so far. // Read and interpred the address based on the type. IPaddress addr; if(prefix[3] == 1) { // IP4 address. Will be sent in network byte order. uint32_t proxy_this; recv(reader, &proxy_this, sizeof proxy_this, CSMSG_WAITALL); addr.net(proxy_this); // Note the request. std::cout << "Request to proxy IP4 address " << addr << std::endl; } else if(prefix[3] == 4) { // IP6 address. We'll discard it, since we don't // support IP6. unsigned char addrbytes[16]; recv(reader, addrbytes, 16, CSMSG_WAITALL); // Note the request. std::cout << "Request to proxy IP6 address:"; char sep = ' '; for(auto b: addrbytes) { char buf[3]; snprintf(buf, sizeof buf, "%02x", b); std::cout << sep << buf; sep = ':'; } std::cout << std::endl; reason = 8; // Unsupp. address type. } else if(prefix[3] == 3) { // Host name. Not implemented. auto hostname = readvar(reader); // Note the request. std::cout << "Request to proxy by domain name " << std::string(reinterpret_cast<char *> (hostname.begin()), hostname.size) << std::endl; reason = 8; // Too lazy. } else { std::cout << "Unrecognized address type " << prefix[3] << std::endl; reason = 8; } // Read the port. Again, network order. uint16_t portno; recv(reader, &portno, sizeof portno, CSMSG_WAITALL); IPport port; port.net(portno); std::cout << "Request port number: " << port << std::endl; // Try to connect, if that was the command. Otherwise, we'll // just give an error response. The connect case also fetches the // local endpoint data. if(prefix[1] != 1) reason = 7; // Only doing connect. TCPsocket connected; IPendpoint localend; if(reason == 0) { // Okay, now we're going to try to connect. try { connect(connected, IPendpoint(addr, port)); getsockname(connected,localend); std::cout << "Connected to " << addr << ":" << port << " from " << localend << std::endl; } catch(socket_sys_error &e) { reason = errmap(e.code()); std::cout << "Connect to " << addr << ":" << port << " failed: " << e.what() << std::endl; } } // Fail if we're gonna unsigned char reply[] = { 5, reason, 0, 1, 0, 0, 0, 0, 0, 0 }; if(reason != 0) { send(reader, reply, sizeof reply); close(reader); return; } // Add the local endpoint info to the message, and send it // as a successful response. uint32_t locip = localend.addr().net(); uint16_t locport = localend.port().net(); std::memcpy(reply+4, &locip, sizeof locip); std::memcpy(reply+8, &locport, sizeof locport); send(reader, reply, sizeof reply); // Forward the stream, each way. Use threads so that a read for // one direction doesn't block while there is data available on // the read for the other. std::thread otherway(fwd, reader, connected); fwd(connected, reader); otherway.join(); close(reader); close(connected); } /* * This just catches networking errors in a call to serve_client, so that an * exception on the client service thread will not crash the whole server. */ void server_catcher(TCPsocket from) { try { serve_client(from); } catch(cleansocks::socket_error & e) { std::cerr << "Client crashes " << e.what() << std::endl; } } int main(int argc, char **argv) { // Get the listening port IPport p; if(argc > 1) p = atoi(argv[1]); else { std::cout << "Enter listen port number: "; uint16_t pno; if(std::cin >> pno) p = pno; else p = lookup_service("socks"); } try { // Listen. TCPsocket listener; //bind(listener, IPendpoint(IPaddress::any(),p)); bind(listener, IPendpoint("127.0.0.1",p)); listen(listener); // Accept connections and let a detached thread handle them. while(true) { IPendpoint client; TCPsocket reader = accept(listener, client); std::cout << "Accepted connection from " << client << std::endl; std::thread(server_catcher, reader).detach(); } } catch(cleansocks::socket_error & e) { std::cerr << "Networking error: " << e.what() << std::endl; } catch(std::runtime_error & e) { std::cerr << "General error: " << e.what() << std::endl; } catch(...) { std::cerr << "Unknown exception" << std::endl; } }