PageRenderTime 638ms CodeModel.GetById 300ms app.highlight 160ms RepoModel.GetById 172ms app.codeStats 0ms

/hiredis/net.c

http://github.com/nicolasff/webdis
C | 458 lines | 344 code | 60 blank | 54 comment | 132 complexity | 1cfa0ddb861a733815f69b2bb47237b5 MD5 | raw file
  1/* Extracted from anet.c to work properly with Hiredis error reporting.
  2 *
  3 * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
  4 * Copyright (c) 2010-2014, Pieter Noordhuis <pcnoordhuis at gmail dot com>
  5 * Copyright (c) 2015, Matt Stancliff <matt at genges dot com>,
  6 *                     Jan-Erik Rediger <janerik at fnordig dot com>
  7 *
  8 * All rights reserved.
  9 *
 10 * Redistribution and use in source and binary forms, with or without
 11 * modification, are permitted provided that the following conditions are met:
 12 *
 13 *   * Redistributions of source code must retain the above copyright notice,
 14 *     this list of conditions and the following disclaimer.
 15 *   * Redistributions in binary form must reproduce the above copyright
 16 *     notice, this list of conditions and the following disclaimer in the
 17 *     documentation and/or other materials provided with the distribution.
 18 *   * Neither the name of Redis nor the names of its contributors may be used
 19 *     to endorse or promote products derived from this software without
 20 *     specific prior written permission.
 21 *
 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 32 * POSSIBILITY OF SUCH DAMAGE.
 33 */
 34
 35#include "fmacros.h"
 36#include <sys/types.h>
 37#include <sys/socket.h>
 38#include <sys/select.h>
 39#include <sys/un.h>
 40#include <netinet/in.h>
 41#include <netinet/tcp.h>
 42#include <arpa/inet.h>
 43#include <unistd.h>
 44#include <fcntl.h>
 45#include <string.h>
 46#include <netdb.h>
 47#include <errno.h>
 48#include <stdarg.h>
 49#include <stdio.h>
 50#include <poll.h>
 51#include <limits.h>
 52#include <stdlib.h>
 53
 54#include "net.h"
 55#include "sds.h"
 56
 57/* Defined in hiredis.c */
 58void __redisSetError(redisContext *c, int type, const char *str);
 59
 60static void redisContextCloseFd(redisContext *c) {
 61    if (c && c->fd >= 0) {
 62        close(c->fd);
 63        c->fd = -1;
 64    }
 65}
 66
 67static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
 68    char buf[128] = { 0 };
 69    size_t len = 0;
 70
 71    if (prefix != NULL)
 72        len = snprintf(buf,sizeof(buf),"%s: ",prefix);
 73    __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
 74    __redisSetError(c,type,buf);
 75}
 76
 77static int redisSetReuseAddr(redisContext *c) {
 78    int on = 1;
 79    if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
 80        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
 81        redisContextCloseFd(c);
 82        return REDIS_ERR;
 83    }
 84    return REDIS_OK;
 85}
 86
 87static int redisCreateSocket(redisContext *c, int type) {
 88    int s;
 89    if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
 90        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
 91        return REDIS_ERR;
 92    }
 93    c->fd = s;
 94    if (type == AF_INET) {
 95        if (redisSetReuseAddr(c) == REDIS_ERR) {
 96            return REDIS_ERR;
 97        }
 98    }
 99    return REDIS_OK;
100}
101
102static int redisSetBlocking(redisContext *c, int blocking) {
103    int flags;
104
105    /* Set the socket nonblocking.
106     * Note that fcntl(2) for F_GETFL and F_SETFL can't be
107     * interrupted by a signal. */
108    if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
109        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
110        redisContextCloseFd(c);
111        return REDIS_ERR;
112    }
113
114    if (blocking)
115        flags &= ~O_NONBLOCK;
116    else
117        flags |= O_NONBLOCK;
118
119    if (fcntl(c->fd, F_SETFL, flags) == -1) {
120        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
121        redisContextCloseFd(c);
122        return REDIS_ERR;
123    }
124    return REDIS_OK;
125}
126
127int redisKeepAlive(redisContext *c, int interval) {
128    int val = 1;
129    int fd = c->fd;
130
131    if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
132        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
133        return REDIS_ERR;
134    }
135
136    val = interval;
137
138#ifdef _OSX
139    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
140        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
141        return REDIS_ERR;
142    }
143#else
144#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
145    val = interval;
146    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
147        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
148        return REDIS_ERR;
149    }
150
151    val = interval/3;
152    if (val == 0) val = 1;
153    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) {
154        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
155        return REDIS_ERR;
156    }
157
158    val = 3;
159    if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) {
160        __redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
161        return REDIS_ERR;
162    }
163#endif
164#endif
165
166    return REDIS_OK;
167}
168
169static int redisSetTcpNoDelay(redisContext *c) {
170    int yes = 1;
171    if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
172        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
173        redisContextCloseFd(c);
174        return REDIS_ERR;
175    }
176    return REDIS_OK;
177}
178
179#define __MAX_MSEC (((LONG_MAX) - 999) / 1000)
180
181static int redisContextWaitReady(redisContext *c, const struct timeval *timeout) {
182    struct pollfd   wfd[1];
183    long msec;
184
185    msec          = -1;
186    wfd[0].fd     = c->fd;
187    wfd[0].events = POLLOUT;
188
189    /* Only use timeout when not NULL. */
190    if (timeout != NULL) {
191        if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
192            __redisSetErrorFromErrno(c, REDIS_ERR_IO, NULL);
193            redisContextCloseFd(c);
194            return REDIS_ERR;
195        }
196
197        msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
198
199        if (msec < 0 || msec > INT_MAX) {
200            msec = INT_MAX;
201        }
202    }
203
204    if (errno == EINPROGRESS) {
205        int res;
206
207        if ((res = poll(wfd, 1, msec)) == -1) {
208            __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
209            redisContextCloseFd(c);
210            return REDIS_ERR;
211        } else if (res == 0) {
212            errno = ETIMEDOUT;
213            __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
214            redisContextCloseFd(c);
215            return REDIS_ERR;
216        }
217
218        if (redisCheckSocketError(c) != REDIS_OK)
219            return REDIS_ERR;
220
221        return REDIS_OK;
222    }
223
224    __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
225    redisContextCloseFd(c);
226    return REDIS_ERR;
227}
228
229int redisCheckSocketError(redisContext *c) {
230    int err = 0;
231    socklen_t errlen = sizeof(err);
232
233    if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
234        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
235        return REDIS_ERR;
236    }
237
238    if (err) {
239        errno = err;
240        __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
241        return REDIS_ERR;
242    }
243
244    return REDIS_OK;
245}
246
247int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
248    if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
249        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
250        return REDIS_ERR;
251    }
252    if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
253        __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
254        return REDIS_ERR;
255    }
256    return REDIS_OK;
257}
258
259static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
260                                   const struct timeval *timeout,
261                                   const char *source_addr) {
262    int s, rv, n;
263    char _port[6];  /* strlen("65535"); */
264    struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
265    int blocking = (c->flags & REDIS_BLOCK);
266    int reuseaddr = (c->flags & REDIS_REUSEADDR);
267    int reuses = 0;
268
269    c->connection_type = REDIS_CONN_TCP;
270    c->tcp.port = port;
271
272    /* We need to take possession of the passed parameters
273     * to make them reusable for a reconnect.
274     * We also carefully check we don't free data we already own,
275     * as in the case of the reconnect method.
276     *
277     * This is a bit ugly, but atleast it works and doesn't leak memory.
278     **/
279    if (c->tcp.host != addr) {
280        if (c->tcp.host)
281            free(c->tcp.host);
282
283        c->tcp.host = strdup(addr);
284    }
285
286    if (timeout) {
287        if (c->timeout != timeout) {
288            if (c->timeout == NULL)
289                c->timeout = malloc(sizeof(struct timeval));
290
291            memcpy(c->timeout, timeout, sizeof(struct timeval));
292        }
293    } else {
294        if (c->timeout)
295            free(c->timeout);
296        c->timeout = NULL;
297    }
298
299    if (source_addr == NULL) {
300        free(c->tcp.source_addr);
301        c->tcp.source_addr = NULL;
302    } else if (c->tcp.source_addr != source_addr) {
303        free(c->tcp.source_addr);
304        c->tcp.source_addr = strdup(source_addr);
305    }
306
307    snprintf(_port, 6, "%d", port);
308    memset(&hints,0,sizeof(hints));
309    hints.ai_family = AF_INET;
310    hints.ai_socktype = SOCK_STREAM;
311
312    /* Try with IPv6 if no IPv4 address was found. We do it in this order since
313     * in a Redis client you can't afford to test if you have IPv6 connectivity
314     * as this would add latency to every connect. Otherwise a more sensible
315     * route could be: Use IPv6 if both addresses are available and there is IPv6
316     * connectivity. */
317    if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
318         hints.ai_family = AF_INET6;
319         if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
320            __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
321            return REDIS_ERR;
322        }
323    }
324    for (p = servinfo; p != NULL; p = p->ai_next) {
325addrretry:
326        if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
327            continue;
328
329        c->fd = s;
330        if (redisSetBlocking(c,0) != REDIS_OK)
331            goto error;
332        if (c->tcp.source_addr) {
333            int bound = 0;
334            /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
335            if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
336                char buf[128];
337                snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
338                __redisSetError(c,REDIS_ERR_OTHER,buf);
339                goto error;
340            }
341
342            if (reuseaddr) {
343                n = 1;
344                if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
345                               sizeof(n)) < 0) {
346                    goto error;
347                }
348            }
349
350            for (b = bservinfo; b != NULL; b = b->ai_next) {
351                if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
352                    bound = 1;
353                    break;
354                }
355            }
356            freeaddrinfo(bservinfo);
357            if (!bound) {
358                char buf[128];
359                snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
360                __redisSetError(c,REDIS_ERR_OTHER,buf);
361                goto error;
362            }
363        }
364        if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
365            if (errno == EHOSTUNREACH) {
366                redisContextCloseFd(c);
367                continue;
368            } else if (errno == EINPROGRESS && !blocking) {
369                /* This is ok. */
370            } else if (errno == EADDRNOTAVAIL && reuseaddr) {
371                if (++reuses >= REDIS_CONNECT_RETRIES) {
372                    goto error;
373                } else {
374                    goto addrretry;
375                }
376            } else {
377                if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
378                    goto error;
379            }
380        }
381        if (blocking && redisSetBlocking(c,1) != REDIS_OK)
382            goto error;
383        if (redisSetTcpNoDelay(c) != REDIS_OK)
384            goto error;
385
386        c->flags |= REDIS_CONNECTED;
387        rv = REDIS_OK;
388        goto end;
389    }
390    if (p == NULL) {
391        char buf[128];
392        snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
393        __redisSetError(c,REDIS_ERR_OTHER,buf);
394        goto error;
395    }
396
397error:
398    rv = REDIS_ERR;
399end:
400    freeaddrinfo(servinfo);
401    return rv;  // Need to return REDIS_OK if alright
402}
403
404int redisContextConnectTcp(redisContext *c, const char *addr, int port,
405                           const struct timeval *timeout) {
406    return _redisContextConnectTcp(c, addr, port, timeout, NULL);
407}
408
409int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
410                               const struct timeval *timeout,
411                               const char *source_addr) {
412    return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
413}
414
415int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
416    int blocking = (c->flags & REDIS_BLOCK);
417    struct sockaddr_un sa;
418
419    if (redisCreateSocket(c,AF_LOCAL) < 0)
420        return REDIS_ERR;
421    if (redisSetBlocking(c,0) != REDIS_OK)
422        return REDIS_ERR;
423
424    c->connection_type = REDIS_CONN_UNIX;
425    if (c->unix_sock.path != path)
426        c->unix_sock.path = strdup(path);
427
428    if (timeout) {
429        if (c->timeout != timeout) {
430            if (c->timeout == NULL)
431                c->timeout = malloc(sizeof(struct timeval));
432
433            memcpy(c->timeout, timeout, sizeof(struct timeval));
434        }
435    } else {
436        if (c->timeout)
437            free(c->timeout);
438        c->timeout = NULL;
439    }
440
441    sa.sun_family = AF_LOCAL;
442    strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
443    if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
444        if (errno == EINPROGRESS && !blocking) {
445            /* This is ok. */
446        } else {
447            if (redisContextWaitReady(c,c->timeout) != REDIS_OK)
448                return REDIS_ERR;
449        }
450    }
451
452    /* Reset socket to be blocking after connect(2). */
453    if (blocking && redisSetBlocking(c,1) != REDIS_OK)
454        return REDIS_ERR;
455
456    c->flags |= REDIS_CONNECTED;
457    return REDIS_OK;
458}