0

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;

}
Madao
  • 109
  • 10
  • Do I understand your question correctly that your program acting as a middleman between a web client (browser) and a web server, so that the web client connects to your program and your program connects to a web server. After these connections are established, your program is supposed to forward all data it receives from the web server to the web client and vice-versa? – Andreas Wenzel Jul 17 '20 at 13:26
  • Yes that's exactly what I'm trying to do. – Madao Jul 17 '20 at 13:27
  • 3
    You need to pay close attention to how many bytes you received. You ask for 8080 bytes but if the client only sends 200, then you need to only send 200 to the server! (Not 200 bytes and then 7880 extra 0 bytes) – user253751 Jul 17 '20 at 13:37
  • 1
    Is there a particular reason that you are using the function combination `recv`and `write`? Normally, one would either use the socket functions `recv` and `send` or the more general functions `read` and `write`, but using `recv` and `write` seems like a strange combination to me. I am a bit unfamiliar with UNIX programming, though. – Andreas Wenzel Jul 17 '20 at 13:48
  • They are essentially the same. I'll correct this eventually. It was just testing. – Madao Jul 17 '20 at 13:55
  • 1
    Are you trying to implement a proxy that's not aware of the protocol for the data it's proxying or one that is? The process for doing these two things is completely different and your code is kind of in the middle somewhere. That *definitely* won't work. – David Schwartz Jul 17 '20 at 15:32
  • I'm not going to implement protocol I just want raw. – Madao Jul 17 '20 at 15:35
  • 1
    @Madao: I have now edited your question in order to make it clearer. If you don't like my changes, feel free to revert them. – Andreas Wenzel Jul 17 '20 at 17:53
  • Why did you edit your question by adding threads to your code? By doing this, you invalidated my answer. If this is supposed to be a solution to your problem, then please don't edit your question with the solution, but rather post it as your own answer. There is nothing wrong with answering your own question. See this official help page for further information: [Can I answer my own question?](https://stackoverflow.com/help/self-answer) – Andreas Wenzel Jul 20 '20 at 15:26
  • Your answer is still valid. I just want to show what direction I'm trying to go with it. That code is broken anyways. – Madao Jul 20 '20 at 23:02

2 Answers2

2

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.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
1

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.

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • Thank you so much for this. – Madao Jul 17 '20 at 17:17
  • The HTTP protocol doesn't prohibit the client and server from sending data simultaneously. Many protocols layered on top of HTTP (such as websockets) can transfer data in both directions simultaneously. It comes down to whether the OP's program is intended to be a toy or intended to be a reliable tool. Writing the latter is much more complicated. (The HTTP protocol specification has a list of requirements for proxies. See section 8.1.3 of the HTTP/1.1 specification.) – David Schwartz Jul 17 '20 at 18:36