Madao
Madao

Reputation: 109

How to act as proxy between client and server?

I am writing a program which is supposed to act as a simple proxy between a web server and a browser. The browser connects to my proxy program and my program connects to the web server. My proxy program should simply forward all data it receives from the browser to the web server and vice-versa, without modifying the data in any way and without performing any caching.

I have managed to get a reply from a web server but how would I direct that reply to my browser? Also is there any way to put this into some sort of infinite loop where I can recv and send at will?

Edit:

I've almost got it. I just need to know how to continuously read the sockets. The program closes unexpectedly after I get the Http redirect.

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
#include <string.h>

#define SERVER_PORT  8080
#define SA struct sockaddr
#define MAX 80

pthread_t ptid, ptidd;

#define TRUE             1
#define FALSE            0

struct sockets_struct {
    int server_sd;
    int client_sd;
    int new_sd;
}socks;

// Function designed to act as client. 
void *client_func(void *sockets)
{ 
    char   buffer[MAX];
    struct sockaddr_in servaddrr;
    struct sockets_struct *socks = (struct sockets_struct*)sockets;
    int    i, len, rc, on = 1;
    //bzero(&servaddrr, sizeof(servaddrr)); 
        
    // assign IP, PORT 
    servaddrr.sin_family = AF_INET; 
    servaddrr.sin_addr.s_addr = inet_addr("192.168.0.1"); 
    servaddrr.sin_port = htons(80);

    // connect the client socket to server socket 
    if (connect(socks->client_sd, (SA*)&servaddrr, sizeof(servaddrr)) != 0) { 
        printf(" client: connection with the server failed...\n"); 
        exit(0); 
    } 
    else
        printf(" client: connected to the remote server..\n");
        

    do {
        rc = recv(socks->client_sd, buffer, sizeof(buffer), 0);
        
        if (rc < 0) {
            if (errno != EWOULDBLOCK) {
                perror(" client: recv() failed\n");
            }
            break;
        }

        if (rc == 0) {
            printf(" client: Connection closed\n");
            break;
        }

        len = rc;
        printf(" client: %d bytes received\n", len);
        rc = send(socks->new_sd, buffer, len, 0);
        
        if (rc < 0) {
            perror(" client: send() failed");
            break;  
        }

    } while(TRUE);
}

// Function designed to act as server. 
void *server_func(void *sockets)
{
    int    len, rc, on = 1;
    int    desc_ready, end_server = FALSE, compress_array = FALSE;
    int    close_conn;
    char   buffer[80];
    struct sockaddr_in6   addr;
    int    timeout;
    struct pollfd fds[200];
    int    nfds = 1, current_size = 0, i, j;
    struct sockets_struct *socks = (struct sockets_struct*)sockets;
    
    rc = setsockopt(socks->server_sd, SOL_SOCKET, SO_REUSEADDR, 
                                            (char *)&on, sizeof(on));
    if (rc < 0) {
        perror(" server: setsockopt() failed\n");
        close(socks->server_sd);
        exit(-1);
    }

    rc = ioctl(socks->server_sd, FIONBIO, (char *)&on);
    
    if (rc < 0) {
        perror(" server: ioctl() failed\n");
        close(socks->server_sd);
        exit(-1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin6_family      = AF_INET;
    memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any));
    addr.sin6_port        = htons(SERVER_PORT);
    
    rc = bind(socks->server_sd, (struct sockaddr *)&addr, sizeof(addr));
    
    if (rc < 0) {
        perror(" server: bind() failed");
        close(socks->server_sd);
        exit(-1);
    }

    rc = listen(socks->server_sd, 32);
    
    if (rc < 0) {
        perror(" server: listen() failed");
        close(socks->server_sd);
        exit(-1);
    }

    memset(fds, 0 , sizeof(fds));
    fds[0].fd = socks->server_sd;
    fds[0].events = POLLIN;
    timeout = (3 * 60 * 1000);

    do {
        printf(" server: waiting on poll()...\n");
        
        rc = poll(fds, nfds, timeout);
        if (rc < 0) {
            perror(" server: poll() failed\n");
            break;
        }

        if (rc == 0) {
            printf(" server: poll() timed out.  End program.\n");
            break;
        }

        current_size = nfds;
        for (i = 0; i < current_size; i++) {
            if (fds[i].revents == 0)
                continue;

            if (fds[i].revents != POLLIN) {
                printf(" server: Error! revents = %d\n", fds[i].revents);
                end_server = TRUE;
                break;

            }
        
            if (fds[i].fd == socks->server_sd) {
                printf("  server: Listening socket is readable\n");
                socks->new_sd = accept(socks->server_sd, NULL, NULL);
                if (socks->new_sd < 0) {
                    if (errno != EWOULDBLOCK) {
                        perror("  server: accept() failed\n");
                        end_server = TRUE;
                    }
                    break;
                }

                printf("  server: new incoming connection - %d\n", socks->new_sd);
                fds[nfds].fd = socks->new_sd;
                fds[nfds].events = POLLIN;
                nfds++;
            }
            else {
                printf("  server: Descriptor %d is readable\n", fds[i].fd);
                close_conn = FALSE;

                do {
                    rc = recv(fds[i].fd, buffer, sizeof(buffer), 0);
                    if (rc < 0) {
                        if (errno != EWOULDBLOCK) {
                            perror("  recv() failed");
                            close_conn = TRUE;
                        }
                        break;
                    }

                    if (rc == 0) {
                        printf(" server: Connection closed\n");
                        close_conn = TRUE;
                        break;
                    }

                    len = rc;
                    printf(" server: %d bytes received \n", len);
                    rc = send(socks->client_sd, buffer, len, 0);
                    
                    if (rc < 0) {
                        perror(" server: send() failed\n");
                        close_conn = TRUE;
                        break;
                    }

                } while(TRUE);

                if (close_conn) {
                    close(fds[i].fd);
                    fds[i].fd = -1;
                    compress_array = TRUE;
                }

            }  /* End of existing connection is readable */
        } /* End of loop through pollable descriptors */

    } while (end_server == FALSE); /* End of serving running. */

}   

int main (int argc, char *argv[])
{
    socks.server_sd = socket(AF_INET, SOCK_STREAM, 0);
    socks.client_sd = socket(AF_INET, SOCK_STREAM, 0);

    if (socks.server_sd == -1) { 
        printf("socket \"server_sd\" creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket \"server_sd\" successfully created..\n");
        
    if (socks.client_sd == -1) { 
        printf("socket \"client_sd\" creation failed...\n"); 
        exit(0); 
    } 
    else
        printf("Socket \"client_sd\" successfully created..\n");
    
    pthread_create(&ptidd, NULL, &client_func, &socks);
    pthread_create(&ptid, NULL, &server_func, &socks);
    pthread_join(ptidd, NULL);
    
    return 0;

}

Upvotes: 0

Views: 1242

Answers (2)

Andreas Wenzel
Andreas Wenzel

Reputation: 24726

NOTE: This answer was written at a time before the OP edited the question and added threads to the code in the question.

The main problem I see with your algorithm is that you seem to assume that you will always receive all data from the client and server in one recv or read call. This cannot be relied upon, even if the web client (browser) only sends a single HTTP request (which is very unlikely, even if only one web page gets loaded).

I suggest you use the following algorithm instead:

  1. Wait for web client (browser) to establish connection to your program.
  2. Create a new socket which connects to web server.
  3. Wait for web server connection to be established. This step is not necessary with your program, as you are using a blocking connect call. It is only necessary if non-blocking or asynchronous sockets are used.
  4. Wait for new data to be available to be read on either of the two sockets, for example by using the function select. When this function returns, it will indicate on which sockets a non-blocking call to recv is possible.
  5. Read from the socket(s) that select reports as having data available to be read, and write this data to the other socket using the send function.
  6. Go to step 4.

However, this algorithm has one possible problem: It assumes that send will always be successful at writing all the bytes immediately, without blocking. Depending on the circumstances (for example the operating system's buffering of sockets) this may not always be the case. It may only be able to partially send the contents of the buffer at once. The documentation of the function send does not specify what will happen if the buffer of the send function is too large to be sent at once, i.e. whether it will block until all the data is sent or whether it will return as soon as it was able to perform a partial send.

Therefore, your algorithm should be able to deal with the case that the data is only partially sent, for example by also checking in step 4 whether it is possible to write more data if not all data was written in a previous call to send.

Also, beware that while your program is blocking on a send call, it will not process any communication in the other direction. For example, while your program is blocking on a send call while forwarding data from the client to the server, it will be unable to forward any data from the server to the client. I don't think that this can cause trouble with the HTTP protocol, because the client and server never send data simultaneously, as the server always waits for the client to finish its request and the client then waits for the server to finish its reply, before it sends another request. However, this may be an issue with other protocols. In particular, if you block communication completely in one direction, this may cause the client or server to get stuck on a blocking send or recv call, too. This could cause a deadlock in all three programs.

Therefore, you may want to consider using non-blocking sockets or asynchronous sockets instead, so that you can continue forwarding network traffic in both directions at all times.

Alternatively, you could continue using blocking socket calls and create two threads, one for forwarding data from the client to the server and one for forwarding data from the server to the client. That way, communication will never be blocked in any direction. But I would recommend using non-blocking sockets or asynchronous socket instead, as threads can get messy.

One thing your algorithm should also do is handle an orderly socket shutdown (indicated by recv returning 0) and error conditions. How to do this depends on what kind of sockets you are using, i.e. whether they are blocking, non-blocking or asynchronous.

Upvotes: 1

David Schwartz
David Schwartz

Reputation: 182743

You can either write a proxy that understands the data it's proxying or one that doesn't. Your question suggests that you want to write one that doesn't. That is definitely the easier approach.

So once all the connections are setup, you have two things to do. You need to read data from one connection and send it to the other. You also need to read data from the other connection and send it to the first one.

Using two threads is an easy way to do this. You can also fork a process for each direction. But the first way that everyone learns is a select or poll loop. You can punch "select loop proxy" into your favorite search engine to find lots of examples.

Upvotes: 2

Related Questions