1

So I have been writing a proxy server (following Beej's guide to network programming) and now I have stumble upon a problem It seems I can't solve.. Driving me crazy..

The problem is it drops some packets due to "Connection reset by peer" when it is going to forward packets to the webclient.

Here is the full code.

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <string>
#include <unistd.h>

using namespace std;

string replace_string(string str, const string from, const string to) {
    size_t start_pos = str.find(from);
    if(start_pos == string::npos)
        return str;
    str.replace(start_pos, from.length(), to);
    return str;
}

void sigchld_handler(int s)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
class Server
{
private:
    string PORT;
    int MAXDATASIZE;
public:
    Server()
    {
        PORT = "3490";
        MAXDATASIZE = 4096;

    }



    // get sockaddr, IPv4 or IPv6:
    void *get_in_addr(struct sockaddr *sa)
    {
        if (sa->sa_family == AF_INET) {
            return &(((struct sockaddr_in*)sa)->sin_addr);
        }

        return &(((struct sockaddr_in6*)sa)->sin6_addr);
    }

    int start_server()
    {

        // first, load up address structs with getaddrinfo():
        int socketfd, new_socketfd;
        struct addrinfo hints, *servinfo, *p;
        struct sockaddr_storage their_addr;
        socklen_t their_addr_size, addr_size;
        struct sigaction sa;

        char s[INET6_ADDRSTRLEN];

        int rv;
        int yes = 1;
        int BACKLOG = 10;
        int numbytes;
        unsigned char buf[MAXDATASIZE];

        memset(&hints, 0, sizeof hints); // Make sure it is cleared
        hints.ai_family = AF_UNSPEC;     // use IPv4 or IPv6, whichever
        hints.ai_socktype = SOCK_STREAM; // TCP socket
        hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

        if ((rv = getaddrinfo(NULL, PORT.c_str(), &hints, &servinfo)) != 0) {
            cerr << "getaddrinfo: " << gai_strerror(rv) << endl;
            return -1;
        }

        // make a socket, bind it asap
        for(p = servinfo; p != NULL; p = p->ai_next)
        {
            if ((socketfd = socket(p->ai_family, p->ai_socktype,
                                   p->ai_protocol)) == -1) {
                //perror("server: socket");
                cerr << "ERROR: Server: socket" << endl;
                continue;
            }

            if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                           sizeof(int)) == -1) {
                //perror("setsockopt");
                cerr << "ERROR: setsockopt" << endl;
                exit(1);
            }

            if (bind(socketfd, p->ai_addr, p->ai_addrlen) == -1) {
                close(socketfd);
                //perror("server: bind");
                cerr << "ERROR: server: bind" << endl;
                continue;
            }
            break;  
        }
        //If the binding failed
        if ( p == NULL)
        {
            cerr << "Server: failed to bind" << endl;
            return -1;
        }

        //Free space, we do not need it anymore.
        freeaddrinfo(servinfo);

        //Listen to the socket (port)
        if ( listen(socketfd, BACKLOG) == -1)
        {
            cerr << "ERROR: listen" << endl;
        }

        // killing zombie processs (all dead processes)
        sa.sa_handler = sigchld_handler; 
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        if (sigaction(SIGCHLD, &sa, NULL) == -1) {
            perror("sigaction");
            exit(1);
        }

        cout << "Server: waiting for connections ...\n"<<endl;

        //Main loop 
        for(;;)
        {  
            their_addr_size = sizeof their_addr;          
            //Create a new socket when we got a connection
            if ((new_socketfd = accept(socketfd, (struct sockaddr *)&their_addr, &their_addr_size))== -1) {
                continue;
            }

            //Converts IP address to 127.0.0.1 (or IPv6 format..) format instead of binary 
            inet_ntop(their_addr.ss_family,get_in_addr((struct sockaddr *)&their_addr), s, sizeof s);

            if ((numbytes = recv(new_socketfd, buf, MAXDATASIZE-1, 0)) == -1) {
                cerr << "ERROR: Failed to receive from user-agent" << endl;
                exit(1);
            }
            else
            {
                /*Parse the header and change some things.*/

                string buffer_str(reinterpret_cast<char*>(buf), MAXDATASIZE);   // Convert to unsigned char array to make it easier to handle                              
                buffer_str = replace_string(buffer_str, "Proxy-Connection: keep-alive","Connection: close"); // Make it connection Close instead of Keep-Alive
                buffer_str = replace_string(buffer_str, "Connection: keep-alive","Connection: close"); // Make it connection Close instead of Keep-Alive
                // removes host from GET, and saves it
                int first = buffer_str.find_first_of("/");                      
                int last =  buffer_str.find_first_of("/", first+2);
                int size_of_buf=buffer_str.size();
                unsigned char host_request[MAXDATASIZE];
                for (int a=0;a<=size_of_buf;a++)
                    host_request[a]=buffer_str[a];
                host_request[size_of_buf]='\0';
                string host_name = "";
                for(int i=first+2;i<last;++i) 
                    host_name += buffer_str[i];
                buffer_str.erase(4, buffer_str.find_first_of("/", first+2)-4);



                /*Set up the socket for the proxy-host */

                int host_sockfd, host_numbytes;  
                unsigned char host_buf[MAXDATASIZE];
                struct addrinfo host_hints, *host_servinfo, *host_p;
                int host_rv;
                char host_s[INET6_ADDRSTRLEN];


                memset(&host_hints, 0, sizeof(host_hints));
                host_hints.ai_family = AF_UNSPEC;
                host_hints.ai_socktype = SOCK_STREAM;

                if ((rv = getaddrinfo(host_name.c_str(), "80", &host_hints, &host_servinfo)) != 0) {
                    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
                    return 1;
                }

                // loop through all the results and connect to the first we can
                for(host_p = host_servinfo; host_p != NULL; host_p = host_p->ai_next) {
                    if ((host_sockfd = socket(host_p->ai_family, host_p->ai_socktype,
                                              host_p->ai_protocol)) == -1) {
                        perror("client: socket");
                        continue;
                    }

                    if (connect(host_sockfd, host_p->ai_addr, host_p->ai_addrlen) == -1) {
                        close(host_sockfd);
                        perror("client: connect");
                        continue;
                    }

                    break;
                }

                if (host_p == NULL) {
                    fprintf(stderr, "client: failed to connect\n");
                    return 2;
                }

                inet_ntop(host_p->ai_family, get_in_addr((struct sockaddr *)host_p->ai_addr),s, sizeof s); //Converts the IP address from binary to IPv4/6-format
                //printf("client: connecting to %s\n", s);

                freeaddrinfo(host_servinfo); // all done with this structure

                /*Send the GET request to the server*/
                send_message(host_sockfd, host_request, sizeof(host_request), "Webserver");
                if (!fork())
                {
                    memset(&host_buf, 0, MAXDATASIZE);
                    while (recv(host_sockfd, &host_buf, MAXDATASIZE, 0) > 0 ) {
                        close(socketfd);

                        send_message(new_socketfd, (unsigned char *)host_buf, MAXDATASIZE, "Browser");
                        memset(&host_buf, 0, MAXDATASIZE);
                    }

                }

                close(host_sockfd);
            }


            //cout << "server: got connection from " << s << endl;

            close(new_socketfd);  // parent doesn't need this

        }

        return 1;
    }
    int send_message(int &socket,unsigned char msg[], int length, string too)
    {
        usleep(10);
        if (send(socket, msg, length, 0) == -1)
        {
            cerr << "ERROR: sending to " << too << endl;
            cerr << "We will lose a packet due to" << endl;
            perror("send");
            cerr << "-------------------MEDDELANDE-------------------\n\n" << msg << endl << 
                "--------------------------------------------------\n\n";
            return -1;
        }
        return 1;
    }
};

int main()
{
    Server srv;
    srv.start_server();
}

Any ideas? Thanks in advance!

Deanie
  • 2,316
  • 2
  • 19
  • 35
Dan Andreasson
  • 15,380
  • 5
  • 29
  • 30
  • "Connection reset by peer" probably means the user closed the browser window or clicked the "Stop" button. – Barmar Feb 07 '13 at 11:28
  • I am running it on localhost at the moment. I am not closing the browser or clicking stop – Dan Andreasson Feb 07 '13 at 11:29
  • @thang How big would you recommend the packets to be? – Dan Andreasson Feb 07 '13 at 11:30
  • why don't you snoop it with wireshark and post the results.. – thang Feb 07 '13 at 11:31
  • it's when the other side sent RST (http://stackoverflow.com/questions/1434451/connection-reset-by-peer) – thang Feb 07 '13 at 11:32
  • from my experience, this can be due to firewall... or the other side crashing – thang Feb 07 '13 at 11:32
  • @thang No, 'connection reset by peer' means it sent an RST, not a FIN. Or are you just joking again? In either case please stop it. The solution is to close the connection and forget about it. There will be another one along in a minute. – user207421 Feb 07 '13 at 11:33
  • @EJP When would you suggest I should close the connection? – Dan Andreasson Feb 07 '13 at 11:36
  • you should figure out why you're getting RST first... could just be that someone terminated the web client while packets are pending. in which case, you don't have to do anything, and there's not much you can do. just shutdown the connection and close the socket. – thang Feb 07 '13 at 11:36
  • @DanAndreasson As soon as you get any error on it other than a read timeout. – user207421 Feb 07 '13 at 11:42
  • @EJP That does not fix my problem. When I am connected to the proxy, any website I enter looks entirly messed up, pictures screwed up and content missing. – Dan Andreasson Feb 07 '13 at 11:48
  • @DanAndreasson So you have another problem in addition to how to handle resets. I can't MAI much sense of of your data copy loop. It closes the listening socket every time you receive data, and it doesn't make any use of the read count returned by recv(); – user207421 Feb 07 '13 at 17:46
  • @EJP What should I do with the read count returned by recv()? – Dan Andreasson Feb 07 '13 at 20:46
  • Use it as the `length` argument to send(): `while ((rc = recv(infd, buffer, 0, BUFFERSIZE, 0)) > 0) { send(outfd, buffer, 0, rc, 0); }`. Otherwise you are writing random junk at the end of each buffer. You can also get rid of the `memset()` in that loop, for the same reason. – user207421 Feb 08 '13 at 00:26

2 Answers2

2

The code does not look much like the Beejs guide I remember. C++ is not my strongest point, but to me it seems you are not keeping track of the amount of data written or read from sockets. No wonder data is missing.

There is a close() inside your while (why?) and a sleep() in your send_message etc. Whenever you find yourself doing a sleep() to get things working even a little bit, its generally a bug.

EDIT: To be more constructive, I suggest you copy sendall() from Beejs guide. Track how much data recv() gives you and send it with sendall.

For a possible speed improvement, track the amount of both received and sent data and buffer some so you dont block waiting for send buffer to become available.

EDIT2: Also add an exit to the child process. Now it runs into the parent code, when its done with the recv & send loop. It looks like you meant it to stay inside the if(!fork()) but currently it does not.

thuovila
  • 1,960
  • 13
  • 21
  • http://pastebin.com/HDgdjruE Something like that? It is still not working good :( – Dan Andreasson Feb 07 '13 at 20:45
  • 1
    I didnt have time to look through all the code now, but two quick observations. 1) `recv()` can return 0. It means there is nothing more to write to the client and you should exit the child in an orderly fashion. See man-page for details on return values. 2) Take the return value of `recv()` directly into a variable. Now you are doing strlen on the receive buffer. That will not work reliably. The transmitted data might contain what equals a "null" for strlen.The exits() seem to be in okayish places, though the condition for the first should consider 0 also (as mentioned earlier). – thuovila Feb 08 '13 at 08:21
0

I have solved the problem, with the help of some guys here!

The first thing I had to change was to swap sizeof with strlen in the recv().

Secondly I didn't store the size of the data received by recv, so when I forwarded the received data from the webserver to the webbrowser I said that the packet contained MAXDATASIZE. The fix for this problem was to store the data received and then just give that size as a parameter in the send()

Last I had to change every unsigned chars to just chars.

Thank everyone who helped me come to this solutions! Especially Thuovila!

Dan Andreasson
  • 15,380
  • 5
  • 29
  • 30
  • Im glad you figured it out. The last code (on pastebin.org) was so long I never got around to going through it in detail. As for the recv() I would still recommend getting the length of data like this: `int bytes_received = recv(...)` and also handle values of 0 and < 0. – thuovila Feb 12 '13 at 10:11
  • That is what I am doing now. I am working on tweaking the code a bit, divide it into functions and so forth. I shall publish the code when it is finished. Thanks once again for the help! – Dan Andreasson Feb 12 '13 at 19:42