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 */
58 void __redisSetError(redisContext *c, int type, const char *str);
59
redisContextCloseFd(redisContext * c)60 static void redisContextCloseFd(redisContext *c) {
61 if (c && c->fd >= 0) {
62 close(c->fd);
63 c->fd = -1;
64 }
65 }
66
__redisSetErrorFromErrno(redisContext * c,int type,const char * prefix)67 static 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
redisSetReuseAddr(redisContext * c)77 static 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
redisCreateSocket(redisContext * c,int type)87 static 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
redisSetBlocking(redisContext * c,int blocking)102 static 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
redisKeepAlive(redisContext * c,int interval)127 int 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
redisSetTcpNoDelay(redisContext * c)169 static 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
redisContextTimeoutMsec(redisContext * c,long * result)181 static int redisContextTimeoutMsec(redisContext *c, long *result)
182 {
183 const struct timeval *timeout = c->timeout;
184 long msec = -1;
185
186 /* Only use timeout when not NULL. */
187 if (timeout != NULL) {
188 if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) {
189 *result = msec;
190 return REDIS_ERR;
191 }
192
193 msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000);
194
195 if (msec < 0 || msec > INT_MAX) {
196 msec = INT_MAX;
197 }
198 }
199
200 *result = msec;
201 return REDIS_OK;
202 }
203
redisContextWaitReady(redisContext * c,long msec)204 static int redisContextWaitReady(redisContext *c, long msec) {
205 struct pollfd wfd[1];
206
207 wfd[0].fd = c->fd;
208 wfd[0].events = POLLOUT;
209
210 if (errno == EINPROGRESS) {
211 int res;
212
213 if ((res = poll(wfd, 1, msec)) == -1) {
214 __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
215 redisContextCloseFd(c);
216 return REDIS_ERR;
217 } else if (res == 0) {
218 errno = ETIMEDOUT;
219 __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
220 redisContextCloseFd(c);
221 return REDIS_ERR;
222 }
223
224 if (redisCheckSocketError(c) != REDIS_OK)
225 return REDIS_ERR;
226
227 return REDIS_OK;
228 }
229
230 __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
231 redisContextCloseFd(c);
232 return REDIS_ERR;
233 }
234
redisCheckSocketError(redisContext * c)235 int redisCheckSocketError(redisContext *c) {
236 int err = 0;
237 socklen_t errlen = sizeof(err);
238
239 if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
240 __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)");
241 return REDIS_ERR;
242 }
243
244 if (err) {
245 errno = err;
246 __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
247 return REDIS_ERR;
248 }
249
250 return REDIS_OK;
251 }
252
redisContextSetTimeout(redisContext * c,const struct timeval tv)253 int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
254 if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
255 __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
256 return REDIS_ERR;
257 }
258 if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
259 __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
260 return REDIS_ERR;
261 }
262 return REDIS_OK;
263 }
264
_redisContextConnectTcp(redisContext * c,const char * addr,int port,const struct timeval * timeout,const char * source_addr)265 static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
266 const struct timeval *timeout,
267 const char *source_addr) {
268 int s, rv, n;
269 char _port[6]; /* strlen("65535"); */
270 struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
271 int blocking = (c->flags & REDIS_BLOCK);
272 int reuseaddr = (c->flags & REDIS_REUSEADDR);
273 int reuses = 0;
274 long timeout_msec = -1;
275
276 servinfo = NULL;
277 c->connection_type = REDIS_CONN_TCP;
278 c->tcp.port = port;
279
280 /* We need to take possession of the passed parameters
281 * to make them reusable for a reconnect.
282 * We also carefully check we don't free data we already own,
283 * as in the case of the reconnect method.
284 *
285 * This is a bit ugly, but atleast it works and doesn't leak memory.
286 **/
287 if (c->tcp.host != addr) {
288 if (c->tcp.host)
289 free(c->tcp.host);
290
291 c->tcp.host = strdup(addr);
292 }
293
294 if (timeout) {
295 if (c->timeout != timeout) {
296 if (c->timeout == NULL)
297 c->timeout = malloc(sizeof(struct timeval));
298
299 memcpy(c->timeout, timeout, sizeof(struct timeval));
300 }
301 } else {
302 if (c->timeout)
303 free(c->timeout);
304 c->timeout = NULL;
305 }
306
307 if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
308 __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified");
309 goto error;
310 }
311
312 if (source_addr == NULL) {
313 free(c->tcp.source_addr);
314 c->tcp.source_addr = NULL;
315 } else if (c->tcp.source_addr != source_addr) {
316 free(c->tcp.source_addr);
317 c->tcp.source_addr = strdup(source_addr);
318 }
319
320 snprintf(_port, 6, "%d", port);
321 memset(&hints,0,sizeof(hints));
322 hints.ai_family = AF_INET;
323 hints.ai_socktype = SOCK_STREAM;
324
325 /* Try with IPv6 if no IPv4 address was found. We do it in this order since
326 * in a Redis client you can't afford to test if you have IPv6 connectivity
327 * as this would add latency to every connect. Otherwise a more sensible
328 * route could be: Use IPv6 if both addresses are available and there is IPv6
329 * connectivity. */
330 if ((rv = getaddrinfo(c->tcp.host,_port,&hints,&servinfo)) != 0) {
331 hints.ai_family = AF_INET6;
332 if ((rv = getaddrinfo(addr,_port,&hints,&servinfo)) != 0) {
333 __redisSetError(c,REDIS_ERR_OTHER,gai_strerror(rv));
334 return REDIS_ERR;
335 }
336 }
337 for (p = servinfo; p != NULL; p = p->ai_next) {
338 addrretry:
339 if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
340 continue;
341
342 c->fd = s;
343 if (redisSetBlocking(c,0) != REDIS_OK)
344 goto error;
345 if (c->tcp.source_addr) {
346 int bound = 0;
347 /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */
348 if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) {
349 char buf[128];
350 snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv));
351 __redisSetError(c,REDIS_ERR_OTHER,buf);
352 goto error;
353 }
354
355 if (reuseaddr) {
356 n = 1;
357 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
358 sizeof(n)) < 0) {
359 goto error;
360 }
361 }
362
363 for (b = bservinfo; b != NULL; b = b->ai_next) {
364 if (bind(s,b->ai_addr,b->ai_addrlen) != -1) {
365 bound = 1;
366 break;
367 }
368 }
369 freeaddrinfo(bservinfo);
370 if (!bound) {
371 char buf[128];
372 snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno));
373 __redisSetError(c,REDIS_ERR_OTHER,buf);
374 goto error;
375 }
376 }
377 if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
378 if (errno == EHOSTUNREACH) {
379 redisContextCloseFd(c);
380 continue;
381 } else if (errno == EINPROGRESS && !blocking) {
382 /* This is ok. */
383 } else if (errno == EADDRNOTAVAIL && reuseaddr) {
384 if (++reuses >= REDIS_CONNECT_RETRIES) {
385 goto error;
386 } else {
387 redisContextCloseFd(c);
388 goto addrretry;
389 }
390 } else {
391 if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
392 goto error;
393 }
394 }
395 if (blocking && redisSetBlocking(c,1) != REDIS_OK)
396 goto error;
397 if (redisSetTcpNoDelay(c) != REDIS_OK)
398 goto error;
399
400 c->flags |= REDIS_CONNECTED;
401 rv = REDIS_OK;
402 goto end;
403 }
404 if (p == NULL) {
405 char buf[128];
406 snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno));
407 __redisSetError(c,REDIS_ERR_OTHER,buf);
408 goto error;
409 }
410
411 error:
412 rv = REDIS_ERR;
413 end:
414 freeaddrinfo(servinfo);
415 return rv; // Need to return REDIS_OK if alright
416 }
417
redisContextConnectTcp(redisContext * c,const char * addr,int port,const struct timeval * timeout)418 int redisContextConnectTcp(redisContext *c, const char *addr, int port,
419 const struct timeval *timeout) {
420 return _redisContextConnectTcp(c, addr, port, timeout, NULL);
421 }
422
redisContextConnectBindTcp(redisContext * c,const char * addr,int port,const struct timeval * timeout,const char * source_addr)423 int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
424 const struct timeval *timeout,
425 const char *source_addr) {
426 return _redisContextConnectTcp(c, addr, port, timeout, source_addr);
427 }
428
redisContextConnectUnix(redisContext * c,const char * path,const struct timeval * timeout)429 int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
430 int blocking = (c->flags & REDIS_BLOCK);
431 struct sockaddr_un sa;
432 long timeout_msec = -1;
433
434 if (redisCreateSocket(c,AF_LOCAL) < 0)
435 return REDIS_ERR;
436 if (redisSetBlocking(c,0) != REDIS_OK)
437 return REDIS_ERR;
438
439 c->connection_type = REDIS_CONN_UNIX;
440 if (c->unix_sock.path != path)
441 c->unix_sock.path = strdup(path);
442
443 if (timeout) {
444 if (c->timeout != timeout) {
445 if (c->timeout == NULL)
446 c->timeout = malloc(sizeof(struct timeval));
447
448 memcpy(c->timeout, timeout, sizeof(struct timeval));
449 }
450 } else {
451 if (c->timeout)
452 free(c->timeout);
453 c->timeout = NULL;
454 }
455
456 if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
457 return REDIS_ERR;
458
459 sa.sun_family = AF_LOCAL;
460 strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
461 if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
462 if (errno == EINPROGRESS && !blocking) {
463 /* This is ok. */
464 } else {
465 if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
466 return REDIS_ERR;
467 }
468 }
469
470 /* Reset socket to be blocking after connect(2). */
471 if (blocking && redisSetBlocking(c,1) != REDIS_OK)
472 return REDIS_ERR;
473
474 c->flags |= REDIS_CONNECTED;
475 return REDIS_OK;
476 }
477