http_async_client: add parameters to enable/set tcp keepalive
[sip-router] / src / modules / http_async_client / http_multi.c
1 /**
2  * Copyright 2016 (C) Federico Cabiddu <federico.cabiddu@gmail.com>
3  * Copyright 2016 (C) Giacomo Vacca <giacomo.vacca@gmail.com>
4  * Copyright 2016 (C) Orange - Camille Oudot <camille.oudot@orange.com>
5  *
6  * This file is part of Kamailio, a free SIP server.
7  *
8  * This file is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version
12  *
13  *
14  * This file is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  */
24
25 /*! \file
26  * \brief  Kamailio http_async_client :: multi interface
27  * \ingroup http_async_client
28  */
29
30
31 #include "../../core/dprint.h"
32 #include "../../core/mem/mem.h"
33 #include "../../core/ut.h"
34 #include "../../core/hashes.h"
35 #include "http_multi.h"
36
37 extern int hash_size;
38 /*! global http multi table */
39 struct http_m_table *hm_table = 0;
40 struct http_m_global *g = 0;
41
42 /* 0: shm, 1:system malloc */
43 int curl_memory_manager = 0;
44
45 /* Update the event timer after curl_multi library calls */
46 int multi_timer_cb(CURLM *multi, long timeout_ms, struct http_m_global *g)
47 {
48         struct timeval timeout;
49         (void)multi; /* unused */
50
51         timeout.tv_sec = timeout_ms/1000;
52         timeout.tv_usec = (timeout_ms%1000)*1000;
53         LM_DBG("multi_timer_cb: Setting timeout to %ld ms\n", timeout_ms);
54         evtimer_add(g->timer_event, &timeout);
55         return 0;
56 }
57 /* Called by libevent when our timeout expires */
58 void timer_cb(int fd, short kind, void *userp)
59 {
60         struct http_m_global *g = (struct http_m_global *)userp;
61         CURLMcode rc;
62         (void)fd;
63         (void)kind;
64
65         char error[CURL_ERROR_SIZE];
66
67         LM_DBG("timeout on socket %d\n", fd);
68
69         rc = curl_multi_socket_action(g->multi,
70                                   CURL_SOCKET_TIMEOUT, 0, &g->still_running);
71         if (check_mcode(rc, error) < 0) {
72                 LM_ERR("curl_multi_socket_action error: %s", error);
73         }
74
75         check_multi_info(g);
76 }
77 /* Called by libevent when we get action on a multi socket */
78 void event_cb(int fd, short kind, void *userp)
79 {
80         struct http_m_global *g;
81         CURLMcode rc;
82         CURL *easy = (CURL*) userp;
83         struct http_m_cell *cell;
84
85         cell = http_m_cell_lookup(easy);
86         if (cell == NULL) {
87                 LM_INFO("Cell for handler %p not found in table\n", easy);
88                 return;
89         }
90
91         g = cell->global;
92         int action =
93                 (kind & EV_READ ? CURL_CSELECT_IN : 0) |
94                 (kind & EV_WRITE ? CURL_CSELECT_OUT : 0);
95
96         LM_DBG("activity %d on socket %d: action %d\n", kind, fd, action);
97         if (kind == EV_TIMEOUT) {
98                 LM_DBG("handle %p timeout on socket %d (cell=%p, param=%p)\n", cell->easy, fd, cell, cell->param);
99                 update_stat(timeouts, 1);
100                 const char *error = "TIMEOUT";
101
102                 strncpy(cell->error, error, strlen(error)+1);
103
104                 reply_error(cell);
105
106                 easy = cell->easy;
107                 /* we are going to remove the cell and the handle here:
108                    pass NULL as sockptr */
109                 curl_multi_assign(g->multi, cell->sockfd, NULL);
110
111                 LM_DBG("cleaning up cell %p\n", cell);
112                 if (cell->evset && cell->ev) {
113                         LM_DBG("freeing event %p\n", cell->ev);
114                         event_del(cell->ev);
115                         event_free(cell->ev);
116                         cell->ev=NULL;
117                         cell->evset=0;
118                 }
119                 unlink_http_m_cell(cell);
120                 free_http_m_cell(cell);
121
122                 LM_DBG("removing handle %p\n", easy);
123                 curl_multi_remove_handle(g->multi, easy);
124                 curl_easy_cleanup(easy);
125                 rc = curl_multi_socket_action(g->multi,
126                                   CURL_SOCKET_TIMEOUT, 0, &g->still_running);
127
128         } else {
129                 LM_DBG("performing action %d on socket %d\n", action, fd);
130                 rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
131                 LM_DBG("action %d on socket %d performed\n", action, fd);
132
133                 if (rc == CURLM_CALL_MULTI_PERFORM) {
134                         LM_DBG("received CURLM_CALL_MULTI_PERFORM, performing action again\n");
135                         rc = curl_multi_socket_action(g->multi, fd, action, &g->still_running);
136                 }
137                 if (check_mcode(rc, cell->error) < 0) {
138                         LM_ERR("error: %s\n", cell->error);
139                         reply_error(cell);
140                         curl_multi_remove_handle(g->multi, easy);
141                         curl_easy_cleanup(easy);
142                 }
143         }
144
145         check_multi_info(g);
146         if ( g->still_running <= 0 ) {
147                 LM_DBG("last transfer done, kill timeout\n");
148                 if (evtimer_pending(g->timer_event, NULL)) {
149                         evtimer_del(g->timer_event);
150                 }
151         }
152 }
153
154 /* CURLMOPT_SOCKETFUNCTION */
155 int sock_cb(CURL *e, curl_socket_t s, int what, void *cbp, void *sockp)
156 {
157         struct http_m_global *g = (struct http_m_global*) cbp;
158         struct http_m_cell *cell = (struct http_m_cell*)sockp;
159         const char *whatstr[]={ "none", "IN", "OUT", "INOUT", "REMOVE" };
160
161         LM_DBG("socket callback: s=%d e=%p what=%s\n", s, e, whatstr[what]);
162         if (what == CURL_POLL_REMOVE) {
163                 /* if cell is NULL the handle has been removed by the event callback for timeout */
164                 if (cell) {
165                         if (cell->evset && cell->ev) {
166                                 LM_DBG("freeing event %p\n", cell->ev);
167                                 event_del(cell->ev);
168                                 event_free(cell->ev);
169                                 cell->ev=NULL;
170                                 cell->evset=0;
171                         }
172                 }
173                 else {
174                         LM_DBG("REMOVE action without cell, handler timed out.\n");
175                 }
176         }
177         else {
178                 if (!cell) {
179                         LM_DBG("Adding data: %s\n", whatstr[what]);
180                         addsock(s, e, what, g);
181                 }
182                 else {
183                         LM_DBG("Changing action from %s to %s\n",
184                         whatstr[cell->action], whatstr[what]);
185                         setsock(cell, s, e, what);
186                 }
187         }
188         return 0;
189 }
190 int check_mcode(CURLMcode code, char *error)
191 {
192         const char *s;
193         if ( CURLM_OK != code && CURLM_CALL_MULTI_PERFORM != code ) {
194                 switch (code) {
195                         case     CURLM_BAD_HANDLE:         s="CURLM_BAD_HANDLE";         break;
196                         case     CURLM_BAD_EASY_HANDLE:    s="CURLM_BAD_EASY_HANDLE";    break;
197                         case     CURLM_OUT_OF_MEMORY:      s="CURLM_OUT_OF_MEMORY";      break;
198                         case     CURLM_INTERNAL_ERROR:     s="CURLM_INTERNAL_ERROR";     break;
199                         case     CURLM_UNKNOWN_OPTION:     s="CURLM_UNKNOWN_OPTION";     break;
200                         case     CURLM_LAST:               s="CURLM_LAST";               break;
201                         case     CURLM_BAD_SOCKET:         s="CURLM_BAD_SOCKET";           break;
202                         default: s="CURLM_unknown";
203                           break;
204                 }
205                 LM_ERR("ERROR: %s\n", s);
206                 strncpy(error, s, strlen(s)+1);
207                 return -1;
208         }
209         return 0;
210 }
211
212 /* CURLOPT_DEBUGFUNCTION */
213 int debug_cb(CURL *handle,
214                    curl_infotype type,
215                    char *data,
216                    size_t size,
217                    void *userptr)
218 {
219         char *prefix;
220         switch (type) {
221         case CURLINFO_TEXT:
222                 prefix = "[cURL]";
223                 break;
224         case CURLINFO_HEADER_IN:
225                 prefix = "[cURL hdr in]";
226                 break;
227         case CURLINFO_HEADER_OUT:
228                 prefix = "[cURL hdr out]";
229                 break;
230         case CURLINFO_DATA_IN:
231         case CURLINFO_DATA_OUT:
232         case CURLINFO_SSL_DATA_OUT:
233         case CURLINFO_SSL_DATA_IN:
234         default:
235                 return 0;
236                 break;
237         }
238         LM_INFO("%s %.*s"/* cURL includes final \n */, prefix, (int)size, data);
239         return 0;
240 }
241 /* CURLOPT_WRITEFUNCTION */
242 size_t write_cb(void *ptr, size_t size, size_t nmemb, void *data)
243 {
244         size_t realsize = size * nmemb;
245         struct http_m_cell *cell;
246         CURL *easy = (CURL*) data;
247         int old_len;
248
249         LM_DBG("data received: %.*s [%d]\n", (int)realsize, (char*)ptr, (int)realsize);
250
251         cell = http_m_cell_lookup(easy);
252         if (cell == NULL) {
253                 LM_ERR("Cell for handler %p not found in table\n", easy);
254                 return -1;
255         }
256
257         if (cell->reply == NULL) {
258                 cell->reply = (struct http_m_reply*)shm_malloc(sizeof(struct http_m_reply));
259                 if (cell->reply == NULL) {
260                         LM_ERR("Cannot allocate shm memory for reply\n");
261                         return -1;
262                 }
263                 memset( cell->reply, 0, sizeof(struct http_m_reply) );
264                 cell->reply->result = (str *)shm_malloc(sizeof(str));
265                 if (cell->reply->result == NULL) {
266                         LM_ERR("Cannot allocate shm memory for reply's result\n");
267                         shm_free(cell->reply);
268                         return -1;
269                 }
270                 memset( cell->reply->result, 0, sizeof(str) );
271         }
272
273         old_len = cell->reply->result->len;
274         cell->reply->result->len += realsize;
275         cell->reply->result->s = (char*)shm_reallocxf(cell->reply->result->s, cell->reply->result->len);
276         if (cell->reply->result->s == NULL) {
277                 LM_ERR("Cannot allocate shm memory for reply's result\n");
278                 shm_free(cell->reply->result);
279                 shm_free(cell->reply);
280                 cell->reply = NULL;
281                 return -1;
282         }
283         strncpy(cell->reply->result->s + old_len, ptr, cell->reply->result->len - old_len);
284
285         if (cell->easy == NULL ) { /* TODO: when does this happen? */
286                 LM_DBG("cell %p easy handler is null\n", cell);
287         }
288         else {
289                 LM_DBG("getting easy handler info (%p)\n", cell->easy);
290                 curl_easy_getinfo(cell->easy, CURLINFO_HTTP_CODE, &cell->reply->retcode);
291         }
292
293         return realsize;
294 }
295
296 void reply_error(struct http_m_cell *cell)
297 {
298         struct http_m_reply *reply;
299         LM_DBG("replying error for  cell=%p\n", cell);
300
301         reply = (struct http_m_reply*)pkg_malloc(sizeof(struct http_m_reply));
302         if (reply == NULL) {
303                 LM_ERR("Cannot allocate pkg memory for reply's result\n");
304                 return;
305         }
306         memset( reply, 0, sizeof(struct http_m_reply) );
307         reply->result = NULL;
308         reply->retcode = 0;
309
310         if (cell) {
311                 strncpy(reply->error, cell->error, strlen(cell->error));
312                 reply->error[strlen(cell->error)] = '\0';
313         } else {
314                 reply->error[0] = '\0';
315         }
316
317         if (cell) {
318                 cell->cb(reply, cell->param);
319         }
320
321         pkg_free(reply);
322
323         return;
324 }
325
326 static void *curl_shm_malloc(size_t size)
327 {
328     void *p = shm_malloc(size);
329     return p;
330 }
331 static void curl_shm_free(void *ptr)
332 {
333         if (ptr)
334                 shm_free(ptr);
335 }
336
337 static void *curl_shm_realloc(void *ptr, size_t size)
338 {
339     void *p = shm_realloc(ptr, size);
340
341     return p;
342 }
343
344 static void *curl_shm_calloc(size_t nmemb, size_t size)
345 {
346     void *p = shm_malloc(nmemb * size);
347     if (p)
348         memset(p, '\0', nmemb * size);
349
350     return p;
351 }
352
353 static char *curl_shm_strdup(const char *cp)
354 {
355     char *rval;
356     int len;
357
358     len = strlen(cp) + 1;
359     rval = shm_malloc(len);
360     if (!rval)
361         return NULL;
362
363     memcpy(rval, cp, len);
364     return rval;
365 }
366
367 void set_curl_mem_callbacks(void)
368 {
369         CURLcode rc;
370
371         switch (curl_memory_manager) {
372                 case 0:
373                         LM_DBG("Setting shm memory callbacks for cURL\n");
374                         rc = curl_global_init_mem(CURL_GLOBAL_ALL,
375                                         curl_shm_malloc,
376                                         curl_shm_free,
377                                         curl_shm_realloc,
378                                         curl_shm_strdup,
379                                         curl_shm_calloc);
380                         if (rc != 0) {
381                                 LM_ERR("Cannot set memory callbacks for cURL: %d\n", rc);
382                         }
383                         break;
384                 case 1:
385                         LM_DBG("Initilizing cURL with sys malloc\n");
386                         rc = curl_global_init(CURL_GLOBAL_ALL);
387                         if (rc != 0) {
388                                 LM_ERR("Cannot initialize cURL: %d\n", rc);
389                         }
390                         break;
391                 default:
392                         LM_ERR ("invalid memory manager: %d\n", curl_memory_manager);
393                         break;
394         }
395
396 }
397
398 int init_http_multi(struct event_base *evbase, struct http_m_global *wg)
399 {
400         g = wg;
401         g->evbase = evbase;
402
403         set_curl_mem_callbacks();
404
405         g->multi = curl_multi_init();
406         LM_DBG("curl_multi %p initialized on global %p (evbase %p)\n", g->multi, g, evbase);
407
408         g->timer_event = evtimer_new(g->evbase, timer_cb, g);
409
410         /* setup the generic multi interface options we want */
411         curl_multi_setopt(g->multi, CURLMOPT_SOCKETFUNCTION, sock_cb);
412         curl_multi_setopt(g->multi, CURLMOPT_SOCKETDATA, g);
413         curl_multi_setopt(g->multi, CURLMOPT_TIMERFUNCTION, multi_timer_cb);
414         curl_multi_setopt(g->multi, CURLMOPT_TIMERDATA, g);
415
416         return init_http_m_table(hash_size);
417 }
418
419 int new_request(str *query, http_m_params_t *query_params, http_multi_cbe_t cb, void *param)
420 {
421
422         LM_DBG("received query %.*s with timeout %d, tls_verify_peer %d, tls_verify_host %d (param=%p)\n", 
423                         query->len, query->s, query_params->timeout, query_params->tls_verify_peer, query_params->tls_verify_host, param);
424         
425         CURL *easy;
426         CURLMcode rc;
427
428         struct http_m_cell *cell;
429
430         update_stat(requests, 1);
431
432         easy = NULL;
433         cell = NULL;
434
435         easy = curl_easy_init();
436         if (!easy) {
437                 LM_ERR("curl_easy_init() failed!\n");
438                 update_stat(errors, 1);
439                 return -1;
440         }
441
442         cell = build_http_m_cell(easy);
443         if (!cell) {
444                 LM_ERR("cannot create cell!\n");
445                 update_stat(errors, 1);
446                 LM_DBG("cleaning up curl handler %p\n", easy);
447                 curl_easy_cleanup(easy);
448                 return -1;
449         }
450
451         link_http_m_cell(cell);
452
453         cell->global = g;
454         cell->easy=easy;
455         cell->error[0] = '\0';
456         cell->params = *query_params;
457         cell->param = param;
458         cell->cb = cb;
459         cell->url = (char*)shm_malloc(query->len + 1);
460         if (cell->url==0) {
461                 LM_ERR("no more shm mem\n");
462                 goto error;
463         }
464         strncpy(cell->url, query->s, query->len);
465         cell->url[query->len] = '\0';
466
467         curl_easy_setopt(cell->easy, CURLOPT_URL, cell->url);
468         curl_easy_setopt(cell->easy, CURLOPT_WRITEFUNCTION, write_cb);
469         curl_easy_setopt(cell->easy, CURLOPT_WRITEDATA, easy);
470         if (curl_verbose) {
471                 curl_easy_setopt(cell->easy, CURLOPT_VERBOSE, 1L);
472                 curl_easy_setopt(cell->easy, CURLOPT_DEBUGFUNCTION, debug_cb);
473         }
474         curl_easy_setopt(cell->easy, CURLOPT_ERRORBUFFER, cell->error);
475         curl_easy_setopt(cell->easy, CURLOPT_PRIVATE, cell);
476         curl_easy_setopt(cell->easy, CURLOPT_SSL_VERIFYPEER, cell->params.tls_verify_peer);
477         curl_easy_setopt(cell->easy, CURLOPT_SSL_VERIFYHOST, cell->params.tls_verify_host?2:0);
478         curl_easy_setopt(cell->easy, CURLOPT_SSLVERSION, tls_version);
479
480         if (cell->params.tls_client_cert) {
481                 curl_easy_setopt(cell->easy, CURLOPT_SSLCERT, cell->params.tls_client_cert);
482         }
483
484         if (cell->params.tls_client_key) {
485                 curl_easy_setopt(cell->easy, CURLOPT_SSLKEY, cell->params.tls_client_key);
486         }
487
488         if (cell->params.tls_ca_path) {
489                 curl_easy_setopt(cell->easy, CURLOPT_CAPATH, cell->params.tls_ca_path);
490         }
491
492         curl_easy_setopt(cell->easy, CURLOPT_HEADER, 1);
493         if (cell->params.headers) {
494                 curl_easy_setopt(cell->easy, CURLOPT_HTTPHEADER, cell->params.headers);
495         }
496
497         if (cell->params.body.s && cell->params.body.len) {
498                 curl_easy_setopt(cell->easy, CURLOPT_POSTFIELDSIZE, (long)cell->params.body.len);
499                 curl_easy_setopt(cell->easy, CURLOPT_COPYPOSTFIELDS, cell->params.body.s);
500         }
501
502         switch (cell->params.method) {
503         case 1:
504                 curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "GET");
505                 break;
506         case 2:
507                 curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "POST");
508                 break;
509         case 3:
510                 curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "PUT");
511                 break;
512         case 4:
513                 curl_easy_setopt(cell->easy, CURLOPT_CUSTOMREQUEST, "DELETE");
514                 break;
515         default:
516                 break;
517         }
518
519         if (cell->params.username) {
520                 curl_easy_setopt(cell->easy, CURLOPT_USERNAME, cell->params.username);
521                 curl_easy_setopt(cell->easy, CURLOPT_HTTPAUTH, cell->params.authmethod);
522
523                 LM_DBG("set username to %s [authmethod %u]\n", cell->params.username, cell->params.authmethod);
524         }
525
526         if (cell->params.password) {
527                 curl_easy_setopt(cell->easy, CURLOPT_PASSWORD, cell->params.password);
528         }
529     
530     /* enable tcp keepalives for the handler */
531         if (cell->params.tcp_keepalive) {
532                 LM_DBG("Enabling TCP keepalives\n");
533                 curl_easy_setopt(cell->easy, CURLOPT_TCP_KEEPALIVE, 1L);
534                 
535                 if (cell->params.tcp_ka_idle) {
536                         curl_easy_setopt(cell->easy, CURLOPT_TCP_KEEPIDLE, cell->params.tcp_ka_idle);
537                         LM_DBG("CURLOPT_TCP_KEEPIDLE set to %d\n", cell->params.tcp_ka_idle);
538                 }
539
540                 if (cell->params.tcp_ka_interval) {
541                         curl_easy_setopt(cell->easy, CURLOPT_TCP_KEEPINTVL, cell->params.tcp_ka_interval);
542                         LM_DBG("CURLOPT_TCP_KEEPINTERVAL set to %d\n", cell->params.tcp_ka_interval);
543                 }
544         }
545         
546     LM_DBG("Adding easy %p to multi %p (%.*s)\n", cell->easy, g->multi, query->len, query->s);
547         rc = curl_multi_add_handle(g->multi, cell->easy);
548         if (check_mcode(rc, cell->error) < 0) {
549                 LM_ERR("error adding curl handler: %s\n", cell->error);
550                 goto error;
551         }
552         /* note that the add_handle() will set a time-out to trigger very soon so
553          *      that the necessary socket_action() call will be called by this app */
554         return 0;
555
556 error:
557         update_stat(errors, 1);
558     if (easy) {
559                 LM_DBG("cleaning up curl handler %p\n", easy);
560                 curl_easy_cleanup(easy);
561     }
562     free_http_m_cell(cell);
563     return -1;
564 }
565
566 /* Check for completed transfers, and remove their easy handles */
567 void check_multi_info(struct http_m_global *g)
568 {
569         char *eff_url;
570         CURLMsg *msg;
571         int msgs_left;
572         CURL *easy;
573         CURLcode res;
574
575         struct http_m_cell *cell;
576
577         LM_DBG("REMAINING: %d\n", g->still_running);
578         while ((msg = curl_multi_info_read(g->multi, &msgs_left))) {
579                 if (msg->msg == CURLMSG_DONE) {
580                         easy = msg->easy_handle;
581                         res = msg->data.result;
582                         curl_easy_getinfo(easy, CURLINFO_PRIVATE, &cell);
583                         curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &eff_url);
584                         LM_DBG("DONE: %s => (%d) %s\n", eff_url, res, cell->error);
585
586                         cell = http_m_cell_lookup(easy);
587                         if (msg->data.result != 0) {
588                                 LM_ERR("handle %p returned error %d: %s\n", easy, res, cell->error);
589                                 update_stat(errors, 1);
590                                 reply_error(cell);
591                         } else {
592                                 cell->reply->error[0] = '\0';
593                                 cell->cb(cell->reply, cell->param);
594
595                                 LM_DBG("reply: [%d] %.*s [%d]\n", (int)cell->reply->retcode, cell->reply->result->len, cell->reply->result->s, cell->reply->result->len);
596                                 update_stat(replies, 1);
597                         }
598
599                         if (cell != 0) {
600                                 LM_DBG("cleaning up cell %p\n", cell);
601                                 unlink_http_m_cell(cell);
602                                 free_http_m_cell(cell);
603                         }
604
605                         LM_DBG("Removing handle %p\n", easy);
606                         curl_multi_remove_handle(g->multi, easy);
607                         curl_easy_cleanup(easy);
608                 }
609         }
610 }
611
612 /* set cell's socket information and assign an event to the socket */
613 void setsock(struct http_m_cell *cell, curl_socket_t s, CURL*e, int act)
614 {
615
616         struct timeval timeout;
617
618         int kind =
619                 (act&CURL_POLL_IN?EV_READ:0)|(act&CURL_POLL_OUT?EV_WRITE:0)|EV_PERSIST;
620         struct http_m_global *g = cell->global;
621         cell->sockfd = s;
622         cell->action = act;
623         cell->easy = e;
624         if (cell->evset && cell->ev) {
625                 event_del(cell->ev);
626                 event_free(cell->ev);
627                 cell->ev=NULL;
628                 cell->evset=0;
629         }
630         cell->ev = event_new(g->evbase, cell->sockfd, kind, event_cb, e);
631         LM_DBG("added event %p to socket %d\n", cell->ev, cell->sockfd);
632         cell->evset = 1;
633
634
635         timeout.tv_sec = cell->params.timeout/1000;
636         timeout.tv_usec = (cell->params.timeout%1000)*1000;
637
638         event_add(cell->ev, &timeout);
639 }
640
641
642
643 /* assign a socket to the multi handler */
644 void addsock(curl_socket_t s, CURL *easy, int action, struct http_m_global *g)
645 {
646         struct http_m_cell *cell;
647
648         cell = http_m_cell_lookup(easy);
649         if (!cell)
650                 return;
651         setsock(cell, s, cell->easy, action);
652         curl_multi_assign(g->multi, s, cell);
653 }
654