cURL / Mailing Lists / curl-library / Single Mail

curl-library

Http Message flow through two proxies

From: Ambuj Jain <ambuj_at_netenrich.com>
Date: Sun, 12 Nov 2006 11:15:21 +0530

Hi,

We like to use libcurl to go through two proxies over https protocol.
Howerver, with the existing curl 7.16.0, we can't do it because it
doesn’t allow to user to build a Http CONNECT packet, that need to be
send over the proxy tunnel established with very first proxy to create a
second proxy tunnel. We can do the same to create a custom CONNECT
request packet and sent it through raw socket (connected with first
proxy), that we can get by CURLINFO_LASTSOCKET option. But this is fine
if data transfer is being over http protocol. But if we want it over
secure layer, anyway we need to use curl internal API Curl_read() and
Curl_write() to get/send the data. For that we need connectindex of the
last curl session. We have written a code for doing same functionality
that allows two proxies tunnel connections by using both internal and
external curl API calls. Can you look into the code and help us to
implement the same only by using the external curl call.

Second issue is that the same code works fine if it is build with curl
7.15.5 but it crashes with curl 7.16.0 after sometimes (after some read
and write calls). I took the snapshot attached with mail when it happened.

Thanks,
Ambuj

#include <iostream>
#include <curl/curl.h>
#include "urldata.h"
using namespace std;

#define CURL_7_16_0 /* code for curl-7.16.0 */

#ifdef CURL_7_16_0
#undef CURL_7_15_5
#else
#define CURL_7_15_5 /* code for curl-7.15.5 */
#endif

extern "C" int Curl_read(struct connectdata *conn, curl_socket_t sockfd,
              char *buf, size_t buffersize,
              ssize_t *n);
extern "C" CURLcode Curl_write(struct connectdata *conn,
                    curl_socket_t sockfd,
                    void *mem, size_t len,
                    ssize_t *written);

long connectindex;
SOCKET sock;
int responseCode;
CURL *curl;
CURLcode res;
HANDLE readThread;

void receiveDataOverProxyTunnel(long sockFd, char * receivedData ,int numBytesToRead,int &nSize, long uniqueConnectIndex,bool blockedRead)
{
        struct SessionHandle* data = (struct SessionHandle*) curl;
        int connectIndex = uniqueConnectIndex;
        // get socket
        SOCKET mySock = sockFd;
        connectdata * data1;
        
        if(blockedRead) {
                // use select-based (blocked read)

        
#ifdef CURL_7_15_5
                data1=data->state.connects[connectIndex]
#else
                data1 = data->state.connc->connects[connectIndex];
#endif
                /* Get data from SSL layer if available */
                if (Curl_read(data1, mySock,receivedData, numBytesToRead, &nSize) == -1 && GetLastError() != WSAEWOULDBLOCK)
                        cout<<"Connect::receivedDataOverProxyTunnel :Curl_read() (non-blocking read) failed";
                if (nSize != 0)
                        return;
                
                // - set up the FD set
                fd_set readFDS;
                FD_ZERO(&readFDS);
                FD_SET(mySock, &readFDS);
                // - do select, wait for data to be available at socket
                int retVal = select(0, &readFDS, NULL, NULL, 0);
                if (retVal == SOCKET_ERROR) {
                        retVal = WSAGetLastError();
                        cout<<"Connect::receiveDataOverProxyTunnel: SOCKET ERROR";
                }
                else if (retVal > 0) {
                        // select returned, so read from socket
                        assert(FD_ISSET(mySock, &readFDS));
                        int retval = Curl_read(data1, mySock,
                                                receivedData, numBytesToRead, &nSize);
                        DWORD errnu=GetLastError();
                        if(retval == -1 && errnu == WSAEWOULDBLOCK)
                                return;
                        else
                                if(nSize == SOCKET_ERROR || nSize==0) {
                                        cout<<"Connect::receiveDataOverProxyTunnel: Curl_read() (blocked read) failed:";
                                }
                }
                else {
                        cout<<"Connect::receiveDataOverProxyTunnel:some other error";
                }
        }
        else {
                        // non-blocking read
                        int retval = Curl_read(data1, mySock,
                                receivedData, numBytesToRead, &nSize);
                        if(retval == -1 && GetLastError() != WSAEWOULDBLOCK){
                                cout<<"Connect::receivedDataOverProxyTunnel :Curl_read() (non-blocking read) failed";
                        }
        }
}

int
line_received( long sockFd, long connectIndex, char *buf, int size )
{
    char *dst = buf;
        if ( size == 0 )
        return 0; /* no error */
    size--;
        int nBytes = 0;
        
    while ( 0 < size ) {
                receiveDataOverProxyTunnel(sockFd,dst, 1, nBytes, connectIndex,true); /* recv one-by-one */
                switch ( nBytes ) {
        case SOCKET_ERROR:
           cout<<"recv() error\n";
            return -1; /* error */
        case 0:
            size = 0; /* end of stream */
            break;
        default:
            /* continue reading until last 1 char is EOL? */
            if ( *dst == '\n' ) {
                /* finished */
                size = 0;
            } else {
                /* more... */
                size--;
            }
            dst++;
        }
    }
    *dst = '\0';
    return 0;
}
void setConnectIndex() {
        // get the connect index
        connectindex = ((struct SessionHandle *)curl)->state.lastconnect;
        //to receive the last socket used by curl session
        curl_easy_getinfo(curl, CURLINFO_LASTSOCKET , &sock);
}
void sendDataOverProxyTunnel(char *buffer, int nBufferLength, int &bytesWritten,
                                                                                  long uniqueConnectIndex , long sock)
{
        // init
        struct SessionHandle* data = (struct SessionHandle*) curl;
         // - get the curl connectindex
         long connectIndex = uniqueConnectIndex;
        connectdata * data1;

#ifdef CURL_7_15_5
                data1=data->state.connects[connectIndex]
#else
                data1 = data->state.connc->connects[connectIndex];
#endif
        // - get socket
        SOCKET mySock = sock;
        int retVal;
        // set up select, for blocked write
        fd_set writeFDS;
        int dataToBeSend = nBufferLength;
        while ( dataToBeSend > 0)
        {
                FD_ZERO(&writeFDS);
                FD_SET(mySock, &writeFDS);
                // do select
                retVal = select(0, NULL, &writeFDS, NULL, 0);
                // select error
                if (retVal == SOCKET_ERROR) {
                        retVal = WSAGetLastError();
                }
                else if (retVal > 0) {
                        // select returned, so read from socket
                        assert(FD_ISSET(mySock, &writeFDS));
                        int retVal = Curl_write(data1, mySock,
                                                                        buffer + nBufferLength - dataToBeSend, dataToBeSend, &bytesWritten);

                        if((retVal == -1) || (bytesWritten == SOCKET_ERROR)) {
                                cout<<"Connect::sendDataOverProxyTunnel: Curl_write() failed:"<<GetLastError();
                        }
                        else
                        {
                                dataToBeSend = dataToBeSend - bytesWritten;
                                bytesWritten = nBufferLength - dataToBeSend;
                        }
                }
        }
}

int customConnectMethod()
{
        char buf[2048];
    int result;
         char protocol[10], host[50],filename[128];
        int nBytes;
        //Creating CONNECT Method packet
        sprintf(buf,"CONNECT %s:%d HTTP/1.1\r\n", "127.0.0.1", "4900");
        sscanf("https://kalyan.dummy.com/", "%15[^\n:]://%[^\n/]%[^\n]", protocol,host,filename);
        sprintf(buf + strlen(buf),"Host: %s\r\nAccept: */*\r\n",host);
        
        sprintf(buf + strlen(buf),"\r\n"); /* finish the header part */
        /* sending data over socket */
        sendDataOverProxyTunnel(buf, strlen(buf),nBytes,connectindex,sock);

        /* get response */
        if (line_received(sock, connectindex, buf, sizeof(buf) ) < 0){
        cout<<"failed to read http response for CONNECT.\n";
        return -1;
    }

    /* check status */
    if (!strchr(buf, ' ')) {
                cout<<"Unexpected http response: "<< buf<<endl;
                return -1;
    }
    result = atoi(strchr(buf,' '));
    switch ( result ) {
    case 200:
        /* Conguraturation, connected via http proxy server! */
        cout<<"connected, start user session.\n";
                nBytes = 0;
        break;
    case 302: /* redirect */
                cout<<"Connection Redirected";
                nBytes = -1;
        break;
                
        case 401: /* WWW-Auth required */
    case 407: /* Proxy-Auth required */
                cout<<"Proxy Authentication Required\n";
                nBytes = -1;
        break;

    default:
        /* Not allowed */
        cout<<"http proxy is not allowed.\n";
                 nBytes = -1;
        break;
    }
    /* skip to end of response header */
    do {
        if ( line_received(sock, connectindex, buf, sizeof(buf) ) ) {
            cout<<"Can't skip response headers\n";
            return -1;
        }
    } while ( strcmp(buf,"\r\n") != 0 );

        return nBytes;
}
DWORD WINAPI MsgRecvThread(LPVOID pThreadData)
{
        char * receivedData= new char[1024];
        int numBytesToRead;
        int nSize;
        while(1){
                receiveDataOverProxyTunnel(sock,receivedData,numBytesToRead,nSize,connectindex,true);
                cout<<"received data:"<<receivedData<<endl;
                // process the received data here.
        }
}
int main(void)
{
  
   curl = curl_easy_init(); // Start a libcurl easy session
   curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); // set to display verbose information
   
   if(curl) {
          
         curl_easy_setopt(curl, CURLOPT_URL, "https://kalyan.dummy.com/"); // set URL to deal with
         curl_easy_setopt(curl, CURLOPT_PROXY, "172.22.0.110:3128"); // first proxy
         curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, "proxy:password"); // First proxy user and password
         curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
         curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY , 1); // instruct to setup the connection olny, no data transfer

     res = curl_easy_perform(curl); // to create a socket and connect it with its peer
         setConnectIndex(); /* set connectindex and socket used by this connection */
         customConnectMethod(); /* send CONNECT request over this socket for second proxy */
         cout<<"response :"<<res;
         DWORD dwReadStatId;
         /* create thread to recv data from the channel */
         readThread= CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) MsgRecvThread,
                (LPVOID)0,0,&dwReadStatId);
         while(1){
                 char * buffer = new char[8 * 1024];
         int bytesWritten;
                 int nBufferLength;
                 // prepare the packet and sent it over channel
                 /* send raw data from already established channel */
                 sendDataOverProxyTunnel(buffer,nBufferLength,bytesWritten,connectindex,sock);
         }
 
     /* always cleanup */
     curl_easy_cleanup(curl);
   }
   return 0;
}

snap-shot.jpg
Received on 2006-11-12