2 * Quick-and-dirty userspace port forwarder.
4 * There are a bunch of things that should be cleaned up here:
5 * - the (limited) user-interface strings are hardcoded; should use a look-up table.
6 * - should have some administrative logging (perhaps syslog)
7 * - code to handle certain plausible (but unlikely) conditions remains a "TODO".
8 * - if there are a lot of simultaneous connections, the linear search after a select() could be a problem
9 * - the qdSocket library functions need some performance work
11 * On the plus side, the code is small, and reasonably efficient on a
12 * light-duty server. I.e., "it works for me."
16 #include <arpa/inet.h>
20 #include <netinet/in.h>
24 #include <sys/socket.h>
26 #include <sys/types.h>
29 #include "qderrhandler.h"
32 #define MAX_ACCEPTED 50 // maximal number of concurrent connections
34 void usage(const char * myName)
36 printf("Usage: %s myPort host:otherPort\n", myName);
37 printf(" Forwards tcp received on myPort to host:otherPort.\n");
40 void fillFds(int * highest_sd, struct qdSocket incoming[], struct qdSocket outgoing[], fd_set * readFds, fd_set * writeFds)
47 // Special case: the listening server socket, on which we read but never write
48 FD_SET(incoming[0].sd, readFds);
49 *highest_sd = incoming[0].sd;
51 for (idx = 1; idx < MAX_ACCEPTED; ++idx) {
54 qdSockCheckClose(&(incoming[idx]));
55 sd = incoming[idx].sd;
56 if (sd > *highest_sd) {
64 qdSockCheckClose(&(outgoing[idx]));
65 sd = outgoing[idx].sd;
66 if (sd > *highest_sd) {
76 void writeError(struct qdSocket *to, char * buf)
78 ssize_t len = strlen(buf);
81 written = qdSockWrite(to, buf, len);
83 printf("WARNING: Tried to report the following on sd %ld, but could not: %s\n",
84 (long)(to->sd), (buf ? buf : "<NULL>"));
88 void forwardFromTo(struct qdSocket *from, struct qdSocket *to)
90 char buf[QD_SOCK_BUF_SIZE];
93 ssize_t writeBufAvail;
95 maxToRead = QD_SOCK_BUF_SIZE - (to->toWrite);
98 //printf("WARNING: write buffer full on sd %ld. Pending read on sd %ld deferred.\n",
99 // (long)(to->sd), (long)(from->sd));
103 memset(buf, 0x00, QD_SOCK_BUF_SIZE);
105 n = read(from->sd, buf, maxToRead);
111 qdSockWrite(to, buf, n);
114 // printf("\nRead-and-write: \"%s\"\n", buf);
117 void readSockets(struct qdSocket incoming[], struct qdSocket outgoing[], fd_set * readFds, const char *otherHost, int otherPort)
121 // Special case: the listening server socket
122 if (FD_ISSET(incoming[0].sd, readFds)) {
123 doAccept(incoming, outgoing, otherHost, otherPort);
126 for (idx = 1; idx < MAX_ACCEPTED; ++idx) {
127 if (FD_ISSET(incoming[idx].sd, readFds)) {
128 forwardFromTo(&(incoming[idx]), &(outgoing[idx]));
130 if (FD_ISSET(outgoing[idx].sd, readFds)) {
131 forwardFromTo(&(outgoing[idx]), &(incoming[idx]));
136 void writeSockets(struct qdSocket incoming[], struct qdSocket outgoing[], fd_set * writeFds)
140 assert(! FD_ISSET(incoming[0].sd, writeFds));
142 for (idx = 1; idx < MAX_ACCEPTED; ++idx) {
143 if (FD_ISSET(incoming[idx].sd, writeFds)) {
144 qdSockFlush(incoming + idx);
146 if (FD_ISSET(outgoing[idx].sd, writeFds)) {
147 qdSockFlush(outgoing + idx);
152 void setNonBlocking(int sd)
156 opt = fcntl(sd, F_GETFL);
158 qdAbort("fcntl(sd, F_GETFL) failed", opt);
161 opt = fcntl(sd, F_SETFL, opt);
163 qdAbort("fcntl(sd, F_SETFL) failed", opt);
167 int doAccept(struct qdSocket incoming[], struct qdSocket outgoing[], const char *otherHost, int otherPort )
172 int sin_size = sizeof(struct sockaddr_in);
173 struct sockaddr_in theirAddr;
176 new_sd = accept(incoming[0].sd, (struct sockaddr *)&theirAddr, &sin_size);
178 qdAbort("Could not accept() incoming connection.", new_sd);
181 p = inet_ntoa(theirAddr.sin_addr);
183 printf("Connection received from %s:%d, using sd %d.\n",
188 setNonBlocking(new_sd);
190 for (i = 0; i < MAX_ACCEPTED; ++i) {
191 if (0 == incoming[i].sd) {
192 incoming[i].sd = new_sd;
199 struct qdSocket temp;
202 writeError(&temp, "Sorry, the server is too busy at the moment (too many open connections). Please wait a while and then try again.");
207 assert (0 == outgoing[i].sd);
209 ret = qdSockConnect(&(outgoing[i]), otherHost, otherPort);
211 // Could not connect. Most likely, otherHost:otherPort is (temporarily?) unavailable
212 // We intentionally do not explain which host and port are unavailable, to avoid leaking
213 // information about the internal network structure to outside parties.
214 writeError(&(incoming[i]), "Sorry, the server is currently down. Please wait a while and then try again.");
215 qdSockClose(&(incoming[i]));
223 void forward(int myPort, const char *otherHost, int otherPort)
229 int numSignalled = 0;
231 struct sockaddr_in myAddr;
232 struct qdSocket * incoming;
233 struct qdSocket * outgoing;
237 struct timeval timeout;
239 incoming = (struct qdSocket *)malloc(MAX_ACCEPTED * sizeof(struct qdSocket));
240 outgoing = (struct qdSocket *)malloc(MAX_ACCEPTED * sizeof(struct qdSocket));
242 if (NULL == incoming) {
243 qdAbort("Failed to calloc() incoming. Out of memory?", 0);
245 if (NULL == outgoing) {
246 qdAbort("Failed to calloc() outgoing. Out of memory?", 0);
249 for (idx = 0; idx < MAX_ACCEPTED; ++idx) {
250 qdSockInit(&(incoming[idx]));
251 qdSockInit(&(outgoing[idx]));
254 server_sd = socket(AF_INET, SOCK_STREAM, 0);
256 qdAbort("Could not allocate socket descriptor.", server_sd);
259 ret = setsockopt(server_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
261 qdAbort("Could not set socket options.", ret);
265 incoming[0].sd = server_sd;
267 memset(&myAddr, 0x00, sizeof(struct sockaddr_in));
268 myAddr.sin_family = AF_INET;
269 myAddr.sin_port = htons(myPort);
270 myAddr.sin_addr.s_addr = htonl(INADDR_ANY);
272 ret = bind(server_sd, (struct sockaddr *)&myAddr, sizeof(myAddr));
274 qdAbort("Could not bind() socket.", ret);
277 printf("Listening on %d...\n", myPort);
279 ret = listen(server_sd, MAX_ACCEPTED); // allow a backlog of up to MAX_ACCEPTED pending connections
281 qdAbort("Could not listen() on socket.", ret);
284 highest_sd = server_sd;
285 incoming[0].sd = server_sd;
288 fillFds(&highest_sd, incoming, outgoing, &readFds, &writeFds);
293 numSignalled = select(highest_sd + 1, &readFds, &writeFds, NULL, &timeout);
294 if (0 == numSignalled) {
295 // printf("."); fflush(stdout);
298 readSockets(incoming, outgoing, &readFds, otherHost, otherPort);
299 writeSockets(incoming, outgoing, &writeFds);
307 int main(int argc, char **argv)
311 char * otherHost = "";
319 myPort = atoi(argv[1]);
321 p = strrchr(argv[2], ':');
330 otherHost = argv[2]; // note that :port has been truncated off
332 forward(myPort, otherHost, otherPort);
333 return 0; // unreachable