Thursday, September 26, 2013

cURL HTTP Pipelining

One of my friend asked me if there was a tool to send pipelined http request from linux command line. Thanks to cURL libcurl, I developed a small tool to do the same. Have put in some basic validations and some restrictions like the maximum no of pipeline request is 32 etc

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/* 
 * Ahamed 
 * Using cURL libcurl
 *
 * For sending pipelined http requests
 * Usage : ./send_pipelined_http <no of pipelined requests> <http://x.x.x.x>
 * Some basic validations added.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

static void usage();

#define ERR_LEN 512
#define MIN_URL_LEN 14
#define MAX_URL_LEN 32 
#define MIN_PIPELINE_REQ 1
#define MAX_PIPELINE_REQ 32
#define err(x, y) {printf("\nError: %s...\nExiting...\n", x); if(y)exit(0); usage();}

static unsigned int pipeline_req;
static char url[MAX_URL_LEN];
static char err_str[ERR_LEN];

static void handle_curl_error(CURLcode ret, char *func)
{
  if(ret != CURLE_OK){
    fprintf(stderr, "%s() failed: %s\n", func,
            curl_multi_strerror(ret));
  }
  return;
}

static void usage()
{
  printf("\nDeveloped by Ahamed using cURL API via libcurl\n");
  printf("Usage : ./send_pipelined_http <no of req> <http://x.x.x.x>\n");
  printf("        Pipeline Request - Min %d : Max : %d\n", 
                  MIN_PIPELINE_REQ, MAX_PIPELINE_REQ);
  printf("        Hostname not supported yet. Please provide IP address.\n");
  printf("        URL - Min Length %d : Max Length : %d\n\n", 
                  MIN_URL_LEN, MAX_URL_LEN);
  exit(0);
}

static void parse_args(int argc, char **argv)
{

  if(argc <= 2)
    err("Insufficient arguments", 0);

  if(!argv)
    err("Something unexpected happened", 1);

  pipeline_req = atol(argv[1]);
  if(pipeline_req > MAX_PIPELINE_REQ || pipeline_req < MIN_PIPELINE_REQ)
    err("Invalid Pipeline Request", 0);
  
  if(strlen(argv[2]) > MAX_URL_LEN || strlen(argv[2]) < MIN_URL_LEN)
    err("Invalid URL length", 0);

  strncpy(url, argv[2], MAX_URL_LEN);
  
  return;
}
 
int main(int argc, char **argv)
{

  int i=0;
  parse_args(argc, argv);

  CURLM *m_curl;
  CURLMcode res;

  m_curl = curl_multi_init();
  curl_multi_setopt(m_curl, CURLMOPT_PIPELINING, 1L);

  CURL *curl[MAX_PIPELINE_REQ];
  for(i=0; i<pipeline_req; i++){

    curl[i] = curl_easy_init();
    if(!curl[i])
      err("Something went wrong", 0);

    res = curl_easy_setopt(curl[i], CURLOPT_URL, url);
    handle_curl_error(res, "curl_easy_setopt");

    res = curl_easy_setopt(curl[i], CURLOPT_FOLLOWLOCATION, 1L);
    handle_curl_error(res, "curl_easy_setopt");

    res = curl_multi_add_handle(m_curl, curl[i]);
    handle_curl_error(res, "curl_multi_add_handle");
  }

  int ret = 1;
  do {
    res = curl_multi_perform(m_curl, &ret);
  } while(ret);

  handle_curl_error(res, (char*)__FUNCTION__);

  for(i=0; i<pipeline_req; i++){
    curl_multi_remove_handle(m_curl, curl[i]); 
    curl_easy_cleanup(curl[i]);
  }
  curl_multi_cleanup(m_curl);

  return 0;
}

root@Imperfecto_maximuS> gcc send_pipelined_http.c -lcurl -o send_pipelined_http
root@Imperfecto_maximuS> ./send_pipelined_http

Error: Insufficient arguments...
Exiting...

Developed by Ahamed(ahamed.en@gmail.com) using cURL API via libcurl
Usage : ./send_pipelined_http <no of req> <http://x.x.x.x>
        Pipeline Request - Min 1 : Max : 32
        Hostname not supported yet. Please provide IP address.
        URL - Min Length 14 : Max Length : 32

root@Imperfecto_maximuS>  ./send_pipelined_http 3 http://11.1.1.99
<html><body><h1>This is sample page</h1>\0<p>for testing created...</p></body></html>
<html><body><h1>This is sample page</h1>\0<p>for testing created...</p></body></html>
<html><body><h1>This is sample page</h1>\0<p>for testing created...</p></body></html>

16 comments:

  1. Hi, Thanks for the program, I am getting the below error when executing. I am using gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)
    and CURL version curl 7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5


    [build@iforge1 venkat]$ gcc pipeline.c -lcurl -o pipeline
    pipeline.c: In function âmainâ:
    pipeline.c:80: error: âCURLMOPT_PIPELININGâ undeclared (first use in this function)
    pipeline.c:80: error: (Each undeclared identifier is reported only once
    pipeline.c:80: error: for each function it appears in.)


    ReplyDelete
    Replies
    1. Venkat,
      Sorry I just got to see the comment. Request you to install the latest curl version available and try.

      Delete
    2. Hi Ahmed, Thanks for the response. I complied the code successfully, but I get this issue during runtime. Looking to solve this issue,
      [root@hello_38 venkat]# ./pipeline 3 http://www.venkat.com
      ./pipeline: error while loading shared libraries: libcurl.so.4: cannot open shared object file: No such file or directory

      Delete
    3. Running "ldconfig" solved the issue. Cheers !! But I suppose these are not piplined connection !! A pipelined connection uses same TCP socket from client. When I did a pcap and checked they are using different TCP sockets to send the requests and not the same. It actually send concurrent connections with three different client TCP sockets !!

      Delete
    4. Well I will be damned! Let me double check that.
      Since I am harnessing the cURL library I believe it should work!
      From pcap how did you know it was using 3 different sockets?

      Delete
    5. I checked it again, all the requests are send via only one TCP connection and they are send before the response for the first arrives.

      Delete
  2. Its not working can you pls help me getting below error
    send_pipelined_http.c:10:19: stdio.h: No such file or directory
    send_pipelined_http.c:11:20: stdlib.h: No such file or directory
    send_pipelined_http.c:12:20: string.h: No such file or directory
    send_pipelined_http.c:13:23: curl/curl.h: No such file or directory
    send_pipelined_http.c:28: error: parse error before "ret"
    send_pipelined_http.c: In function `handle_curl_error':
    send_pipelined_http.c:30: error: `ret' undeclared (first use in this function)
    send_pipelined_http.c:30: error: (Each undeclared identifier is reported only once
    send_pipelined_http.c:30: error: for each function it appears in.)
    send_pipelined_http.c:30: error: `CURLE_OK' undeclared (first use in this function)
    send_pipelined_http.c:31: error: `stderr' undeclared (first use in this function)
    send_pipelined_http.c:31: error: `func' undeclared (first use in this function)
    send_pipelined_http.c: In function `main':
    send_pipelined_http.c:76: error: `CURLM' undeclared (first use in this function)
    send_pipelined_http.c:76: error: `m_curl' undeclared (first use in this function)
    send_pipelined_http.c:77: error: `CURLMcode' undeclared (first use in this function)
    send_pipelined_http.c:77: error: parse error before "res"
    send_pipelined_http.c:80: error: `CURLMOPT_PIPELINING' undeclared (first use in this function)
    send_pipelined_http.c:82: error: `CURL' undeclared (first use in this function)
    send_pipelined_http.c:82: error: `curl' undeclared (first use in this function)
    send_pipelined_http.c:89: error: `res' undeclared (first use in this function)
    send_pipelined_http.c:89: error: `CURLOPT_URL' undeclared (first use in this function)
    send_pipelined_http.c:92: error: `CURLOPT_FOLLOWLOCATION' undeclared (first use in this function)

    ReplyDelete
  3. Requests are not pipelined.

    ReplyDelete
    Replies
    1. I have tested it and from the capture you can see that it is pipelined. Can you share your capture?

      Delete
    2. Hi! How can I check that it is realy pipelined? How to get capture like yours?

      Delete
    3. You can do a tcpdump and capture the packets.

      Delete
    4. Hi Ahamed,

      Thank you for sharing your code. I also see that requests are not pipelined (one http get per socket). Here is a capture:

      13:54:06.299436 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [S], seq 866784912, win 42340, options [mss 1460,sackOK,TS val 191092763 ecr 0,nop,wscale 11], length 0
      13:54:06.299470 IP 1.1.1.1.http > 1.1.1.2.40010: Flags [S.], seq 2231060603, ack 866784913, win 65535, options [mss 1460,nop,wscale 3,nop,nop,TS val 4166 ecr 0], length 0
      13:54:06.299488 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [.], ack 1, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299503 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [S], seq 2603313572, win 42340, options [mss 1460,sackOK,TS val 191092763 ecr 0,nop,wscale 11], length 0
      13:54:06.299510 IP 1.1.1.1.http > 1.1.1.2.40010: Flags [.], ack 1, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299522 IP 1.1.1.1.http > 1.1.1.2.40012: Flags [S.], seq 2240101762, ack 2603313573, win 65535, options [mss 1460,nop,wscale 3,nop,nop,TS val 4166 ecr 0], length 0
      13:54:06.299532 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [.], ack 1, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299541 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [P.], seq 1:51, ack 1, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 50: HTTP: GET /toto HTTP/1.1
      13:54:06.299550 IP 1.1.1.1.http > 1.1.1.2.40012: Flags [.], ack 1, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299561 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [P.], seq 1:51, ack 1, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 50: HTTP: GET /toto HTTP/1.1
      13:54:06.299565 IP 1.1.1.1.http > 1.1.1.2.40010: Flags [P.], seq 1:80, ack 51, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 79: HTTP: HTTP/1.1 200 OK
      13:54:06.299571 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [.], ack 80, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299577 IP 1.1.1.1.http > 1.1.1.2.40012: Flags [P.], seq 1:80, ack 51, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 79: HTTP: HTTP/1.1 200 OK
      13:54:06.299582 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [.], ack 80, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299620 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [F.], seq 51, ack 80, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299632 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [F.], seq 51, ack 80, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299640 IP 1.1.1.1.http > 1.1.1.2.40010: Flags [.], ack 52, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299650 IP 1.1.1.1.http > 1.1.1.2.40010: Flags [F.], seq 80, ack 52, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299661 IP 1.1.1.2.40010 > 1.1.1.1.http: Flags [.], ack 81, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0
      13:54:06.299663 IP 1.1.1.1.http > 1.1.1.2.40012: Flags [.], ack 52, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299668 IP 1.1.1.1.http > 1.1.1.2.40012: Flags [F.], seq 80, ack 52, win 16425, options [nop,nop,TS val 4166 ecr 191092763], length 0
      13:54:06.299675 IP 1.1.1.2.40012 > 1.1.1.1.http: Flags [.], ack 81, win 21, options [nop,nop,TS val 191092763 ecr 4166], length 0

      Delete
    5. Hello, it has been a while since I worked on this. Let me know if you are still looking for a solution, i will re-validate this.

      Delete
    6. Hi Ahamed, You can see that there are two connections opened. one with port :40010 and one with port:40012. There are 2 connection opened from client side.

      Idea is both the request should go in same connection.

      Delete
  4. I compiled this code today on FreeBSD using the latest libCurl (7.60.0) and I see it making three connections to the server, and then looping forever in the "res = curl_multi_perform(m_curl, &ret);" loop with ret == 2.

    Murf

    ReplyDelete