Line | Count | Source (jump to first uncovered line) |
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 <fcntl.h> |
38 | | #include <string.h> |
39 | | #include <errno.h> |
40 | | #include <stdarg.h> |
41 | | #include <stdio.h> |
42 | | #include <limits.h> |
43 | | #include <stdlib.h> |
44 | | |
45 | | #include "net.h" |
46 | | #include "sds.h" |
47 | | #include "sockcompat.h" |
48 | | #include "win32.h" |
49 | | |
50 | | /* Defined in hiredis.c */ |
51 | | void __redisSetError(redisContext *c, int type, const char *str); |
52 | | |
53 | | int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout); |
54 | | |
55 | 0 | void redisNetClose(redisContext *c) { |
56 | 0 | if (c && c->fd != REDIS_INVALID_FD) { |
57 | 0 | close(c->fd); |
58 | 0 | c->fd = REDIS_INVALID_FD; |
59 | 0 | } |
60 | 0 | } |
61 | | |
62 | 0 | ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) { |
63 | 0 | ssize_t nread = recv(c->fd, buf, bufcap, 0); |
64 | 0 | if (nread == -1) { |
65 | 0 | if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { |
66 | | /* Try again later */ |
67 | 0 | return 0; |
68 | 0 | } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) { |
69 | | /* especially in windows */ |
70 | 0 | __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout"); |
71 | 0 | return -1; |
72 | 0 | } else { |
73 | 0 | __redisSetError(c, REDIS_ERR_IO, strerror(errno)); |
74 | 0 | return -1; |
75 | 0 | } |
76 | 0 | } else if (nread == 0) { |
77 | 0 | __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection"); |
78 | 0 | return -1; |
79 | 0 | } else { |
80 | 0 | return nread; |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | 0 | ssize_t redisNetWrite(redisContext *c) { |
85 | 0 | ssize_t nwritten; |
86 | |
|
87 | 0 | nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); |
88 | 0 | if (nwritten < 0) { |
89 | 0 | if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { |
90 | | /* Try again */ |
91 | 0 | return 0; |
92 | 0 | } else { |
93 | 0 | __redisSetError(c, REDIS_ERR_IO, strerror(errno)); |
94 | 0 | return -1; |
95 | 0 | } |
96 | 0 | } |
97 | | |
98 | 0 | return nwritten; |
99 | 0 | } |
100 | | |
101 | 0 | static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) { |
102 | 0 | int errorno = errno; /* snprintf() may change errno */ |
103 | 0 | char buf[128] = { 0 }; |
104 | 0 | size_t len = 0; |
105 | |
|
106 | 0 | if (prefix != NULL) |
107 | 0 | len = snprintf(buf,sizeof(buf),"%s: ",prefix); |
108 | 0 | strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len); |
109 | 0 | __redisSetError(c,type,buf); |
110 | 0 | } |
111 | | |
112 | 0 | static int redisSetReuseAddr(redisContext *c) { |
113 | 0 | int on = 1; |
114 | 0 | if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { |
115 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
116 | 0 | redisNetClose(c); |
117 | 0 | return REDIS_ERR; |
118 | 0 | } |
119 | 0 | return REDIS_OK; |
120 | 0 | } |
121 | | |
122 | 0 | static int redisCreateSocket(redisContext *c, int type) { |
123 | 0 | redisFD s; |
124 | 0 | if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) { |
125 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
126 | 0 | return REDIS_ERR; |
127 | 0 | } |
128 | 0 | c->fd = s; |
129 | 0 | if (type == AF_INET) { |
130 | 0 | if (redisSetReuseAddr(c) == REDIS_ERR) { |
131 | 0 | return REDIS_ERR; |
132 | 0 | } |
133 | 0 | } |
134 | 0 | return REDIS_OK; |
135 | 0 | } |
136 | | |
137 | 0 | static int redisSetBlocking(redisContext *c, int blocking) { |
138 | 0 | #ifndef _WIN32 |
139 | 0 | int flags; |
140 | | |
141 | | /* Set the socket nonblocking. |
142 | | * Note that fcntl(2) for F_GETFL and F_SETFL can't be |
143 | | * interrupted by a signal. */ |
144 | 0 | if ((flags = fcntl(c->fd, F_GETFL)) == -1) { |
145 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)"); |
146 | 0 | redisNetClose(c); |
147 | 0 | return REDIS_ERR; |
148 | 0 | } |
149 | | |
150 | 0 | if (blocking) |
151 | 0 | flags &= ~O_NONBLOCK; |
152 | 0 | else |
153 | 0 | flags |= O_NONBLOCK; |
154 | |
|
155 | 0 | if (fcntl(c->fd, F_SETFL, flags) == -1) { |
156 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)"); |
157 | 0 | redisNetClose(c); |
158 | 0 | return REDIS_ERR; |
159 | 0 | } |
160 | | #else |
161 | | u_long mode = blocking ? 0 : 1; |
162 | | if (ioctl(c->fd, FIONBIO, &mode) == -1) { |
163 | | __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)"); |
164 | | redisNetClose(c); |
165 | | return REDIS_ERR; |
166 | | } |
167 | | #endif /* _WIN32 */ |
168 | 0 | return REDIS_OK; |
169 | 0 | } |
170 | | |
171 | 0 | int redisKeepAlive(redisContext *c, int interval) { |
172 | 0 | int val = 1; |
173 | 0 | redisFD fd = c->fd; |
174 | |
|
175 | 0 | #ifndef _WIN32 |
176 | 0 | if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){ |
177 | 0 | __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); |
178 | 0 | return REDIS_ERR; |
179 | 0 | } |
180 | | |
181 | 0 | val = interval; |
182 | |
|
183 | | #if defined(__APPLE__) && defined(__MACH__) |
184 | | if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) { |
185 | | __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); |
186 | | return REDIS_ERR; |
187 | | } |
188 | | #else |
189 | 0 | #if defined(__GLIBC__) && !defined(__FreeBSD_kernel__) |
190 | 0 | if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) { |
191 | 0 | __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); |
192 | 0 | return REDIS_ERR; |
193 | 0 | } |
194 | | |
195 | 0 | val = interval/3; |
196 | 0 | if (val == 0) val = 1; |
197 | 0 | if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &val, sizeof(val)) < 0) { |
198 | 0 | __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); |
199 | 0 | return REDIS_ERR; |
200 | 0 | } |
201 | | |
202 | 0 | val = 3; |
203 | 0 | if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &val, sizeof(val)) < 0) { |
204 | 0 | __redisSetError(c,REDIS_ERR_OTHER,strerror(errno)); |
205 | 0 | return REDIS_ERR; |
206 | 0 | } |
207 | 0 | #endif |
208 | 0 | #endif |
209 | | #else |
210 | | int res; |
211 | | |
212 | | res = win32_redisKeepAlive(fd, interval * 1000); |
213 | | if (res != 0) { |
214 | | __redisSetError(c, REDIS_ERR_OTHER, strerror(res)); |
215 | | return REDIS_ERR; |
216 | | } |
217 | | #endif |
218 | 0 | return REDIS_OK; |
219 | 0 | } |
220 | | |
221 | 0 | int redisSetTcpNoDelay(redisContext *c) { |
222 | 0 | int yes = 1; |
223 | 0 | if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { |
224 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); |
225 | 0 | redisNetClose(c); |
226 | 0 | return REDIS_ERR; |
227 | 0 | } |
228 | 0 | return REDIS_OK; |
229 | 0 | } |
230 | | |
231 | 0 | #define __MAX_MSEC (((LONG_MAX) - 999) / 1000) |
232 | | |
233 | | static int redisContextTimeoutMsec(redisContext *c, long *result) |
234 | 0 | { |
235 | 0 | const struct timeval *timeout = c->connect_timeout; |
236 | 0 | long msec = -1; |
237 | | |
238 | | /* Only use timeout when not NULL. */ |
239 | 0 | if (timeout != NULL) { |
240 | 0 | if (timeout->tv_usec > 1000000 || timeout->tv_sec > __MAX_MSEC) { |
241 | 0 | __redisSetError(c, REDIS_ERR_IO, "Invalid timeout specified"); |
242 | 0 | *result = msec; |
243 | 0 | return REDIS_ERR; |
244 | 0 | } |
245 | | |
246 | 0 | msec = (timeout->tv_sec * 1000) + ((timeout->tv_usec + 999) / 1000); |
247 | |
|
248 | 0 | if (msec < 0 || msec > INT_MAX) { |
249 | 0 | msec = INT_MAX; |
250 | 0 | } |
251 | 0 | } |
252 | | |
253 | 0 | *result = msec; |
254 | 0 | return REDIS_OK; |
255 | 0 | } |
256 | | |
257 | 0 | static int redisContextWaitReady(redisContext *c, long msec) { |
258 | 0 | struct pollfd wfd[1]; |
259 | |
|
260 | 0 | wfd[0].fd = c->fd; |
261 | 0 | wfd[0].events = POLLOUT; |
262 | |
|
263 | 0 | if (errno == EINPROGRESS) { |
264 | 0 | int res; |
265 | |
|
266 | 0 | if ((res = poll(wfd, 1, msec)) == -1) { |
267 | 0 | __redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)"); |
268 | 0 | redisNetClose(c); |
269 | 0 | return REDIS_ERR; |
270 | 0 | } else if (res == 0) { |
271 | 0 | errno = ETIMEDOUT; |
272 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
273 | 0 | redisNetClose(c); |
274 | 0 | return REDIS_ERR; |
275 | 0 | } |
276 | | |
277 | 0 | if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) { |
278 | 0 | redisCheckSocketError(c); |
279 | 0 | return REDIS_ERR; |
280 | 0 | } |
281 | | |
282 | 0 | return REDIS_OK; |
283 | 0 | } |
284 | | |
285 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
286 | 0 | redisNetClose(c); |
287 | 0 | return REDIS_ERR; |
288 | 0 | } |
289 | | |
290 | 0 | int redisCheckConnectDone(redisContext *c, int *completed) { |
291 | 0 | int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen); |
292 | 0 | if (rc == 0) { |
293 | 0 | *completed = 1; |
294 | 0 | return REDIS_OK; |
295 | 0 | } |
296 | 0 | int error = errno; |
297 | 0 | if (error == EINPROGRESS) { |
298 | | /* must check error to see if connect failed. Get the socket error */ |
299 | 0 | int fail, so_error; |
300 | 0 | socklen_t optlen = sizeof(so_error); |
301 | 0 | fail = getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &so_error, &optlen); |
302 | 0 | if (fail == 0) { |
303 | 0 | if (so_error == 0) { |
304 | | /* Socket is connected! */ |
305 | 0 | *completed = 1; |
306 | 0 | return REDIS_OK; |
307 | 0 | } |
308 | | /* connection error; */ |
309 | 0 | errno = so_error; |
310 | 0 | error = so_error; |
311 | 0 | } |
312 | 0 | } |
313 | 0 | switch (error) { |
314 | 0 | case EISCONN: |
315 | 0 | *completed = 1; |
316 | 0 | return REDIS_OK; |
317 | 0 | case EALREADY: |
318 | 0 | case EWOULDBLOCK: |
319 | 0 | *completed = 0; |
320 | 0 | return REDIS_OK; |
321 | 0 | default: |
322 | 0 | return REDIS_ERR; |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | 0 | int redisCheckSocketError(redisContext *c) { |
327 | 0 | int err = 0, errno_saved = errno; |
328 | 0 | socklen_t errlen = sizeof(err); |
329 | |
|
330 | 0 | if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) { |
331 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"getsockopt(SO_ERROR)"); |
332 | 0 | return REDIS_ERR; |
333 | 0 | } |
334 | | |
335 | 0 | if (err == 0) { |
336 | 0 | err = errno_saved; |
337 | 0 | } |
338 | |
|
339 | 0 | if (err) { |
340 | 0 | errno = err; |
341 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL); |
342 | 0 | return REDIS_ERR; |
343 | 0 | } |
344 | | |
345 | 0 | return REDIS_OK; |
346 | 0 | } |
347 | | |
348 | 0 | int redisContextSetTimeout(redisContext *c, const struct timeval tv) { |
349 | 0 | const void *to_ptr = &tv; |
350 | 0 | size_t to_sz = sizeof(tv); |
351 | |
|
352 | 0 | if (redisContextUpdateCommandTimeout(c, &tv) != REDIS_OK) { |
353 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
354 | 0 | return REDIS_ERR; |
355 | 0 | } |
356 | 0 | if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { |
357 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); |
358 | 0 | return REDIS_ERR; |
359 | 0 | } |
360 | 0 | if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) { |
361 | 0 | __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)"); |
362 | 0 | return REDIS_ERR; |
363 | 0 | } |
364 | 0 | return REDIS_OK; |
365 | 0 | } |
366 | | |
367 | 0 | int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) { |
368 | | /* Same timeval struct, short circuit */ |
369 | 0 | if (c->connect_timeout == timeout) |
370 | 0 | return REDIS_OK; |
371 | | |
372 | | /* Allocate context timeval if we need to */ |
373 | 0 | if (c->connect_timeout == NULL) { |
374 | 0 | c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout)); |
375 | 0 | if (c->connect_timeout == NULL) |
376 | 0 | return REDIS_ERR; |
377 | 0 | } |
378 | | |
379 | 0 | memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout)); |
380 | 0 | return REDIS_OK; |
381 | 0 | } |
382 | | |
383 | 0 | int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) { |
384 | | /* Same timeval struct, short circuit */ |
385 | 0 | if (c->command_timeout == timeout) |
386 | 0 | return REDIS_OK; |
387 | | |
388 | | /* Allocate context timeval if we need to */ |
389 | 0 | if (c->command_timeout == NULL) { |
390 | 0 | c->command_timeout = hi_malloc(sizeof(*c->command_timeout)); |
391 | 0 | if (c->command_timeout == NULL) |
392 | 0 | return REDIS_ERR; |
393 | 0 | } |
394 | | |
395 | 0 | memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout)); |
396 | 0 | return REDIS_OK; |
397 | 0 | } |
398 | | |
399 | | static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, |
400 | | const struct timeval *timeout, |
401 | 0 | const char *source_addr) { |
402 | 0 | redisFD s; |
403 | 0 | int rv, n; |
404 | 0 | char _port[6]; /* strlen("65535"); */ |
405 | 0 | struct addrinfo hints, *servinfo, *bservinfo, *p, *b; |
406 | 0 | int blocking = (c->flags & REDIS_BLOCK); |
407 | 0 | int reuseaddr = (c->flags & REDIS_REUSEADDR); |
408 | 0 | int reuses = 0; |
409 | 0 | long timeout_msec = -1; |
410 | |
|
411 | 0 | servinfo = NULL; |
412 | 0 | c->connection_type = REDIS_CONN_TCP; |
413 | 0 | c->tcp.port = port; |
414 | | |
415 | | /* We need to take possession of the passed parameters |
416 | | * to make them reusable for a reconnect. |
417 | | * We also carefully check we don't free data we already own, |
418 | | * as in the case of the reconnect method. |
419 | | * |
420 | | * This is a bit ugly, but atleast it works and doesn't leak memory. |
421 | | **/ |
422 | 0 | if (c->tcp.host != addr) { |
423 | 0 | hi_free(c->tcp.host); |
424 | |
|
425 | 0 | c->tcp.host = hi_strdup(addr); |
426 | 0 | if (c->tcp.host == NULL) |
427 | 0 | goto oom; |
428 | 0 | } |
429 | | |
430 | 0 | if (timeout) { |
431 | 0 | if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) |
432 | 0 | goto oom; |
433 | 0 | } else { |
434 | 0 | hi_free(c->connect_timeout); |
435 | 0 | c->connect_timeout = NULL; |
436 | 0 | } |
437 | | |
438 | 0 | if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { |
439 | 0 | goto error; |
440 | 0 | } |
441 | | |
442 | 0 | if (source_addr == NULL) { |
443 | 0 | hi_free(c->tcp.source_addr); |
444 | 0 | c->tcp.source_addr = NULL; |
445 | 0 | } else if (c->tcp.source_addr != source_addr) { |
446 | 0 | hi_free(c->tcp.source_addr); |
447 | 0 | c->tcp.source_addr = hi_strdup(source_addr); |
448 | 0 | } |
449 | |
|
450 | 0 | snprintf(_port, 6, "%d", port); |
451 | 0 | memset(&hints,0,sizeof(hints)); |
452 | 0 | hints.ai_family = AF_INET; |
453 | 0 | hints.ai_socktype = SOCK_STREAM; |
454 | | |
455 | | /* DNS lookup. To use dual stack, set both flags to prefer both IPv4 and |
456 | | * IPv6. By default, for historical reasons, we try IPv4 first and then we |
457 | | * try IPv6 only if no IPv4 address was found. */ |
458 | 0 | if (c->flags & REDIS_PREFER_IPV6 && c->flags & REDIS_PREFER_IPV4) |
459 | 0 | hints.ai_family = AF_UNSPEC; |
460 | 0 | else if (c->flags & REDIS_PREFER_IPV6) |
461 | 0 | hints.ai_family = AF_INET6; |
462 | 0 | else |
463 | 0 | hints.ai_family = AF_INET; |
464 | |
|
465 | 0 | rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); |
466 | 0 | if (rv != 0 && hints.ai_family != AF_UNSPEC) { |
467 | | /* Try again with the other IP version. */ |
468 | 0 | hints.ai_family = (hints.ai_family == AF_INET) ? AF_INET6 : AF_INET; |
469 | 0 | rv = getaddrinfo(c->tcp.host, _port, &hints, &servinfo); |
470 | 0 | } |
471 | 0 | if (rv != 0) { |
472 | 0 | __redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv)); |
473 | 0 | return REDIS_ERR; |
474 | 0 | } |
475 | 0 | for (p = servinfo; p != NULL; p = p->ai_next) { |
476 | 0 | addrretry: |
477 | 0 | if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD) |
478 | 0 | continue; |
479 | | |
480 | 0 | c->fd = s; |
481 | 0 | if (redisSetBlocking(c,0) != REDIS_OK) |
482 | 0 | goto error; |
483 | 0 | if (c->tcp.source_addr) { |
484 | 0 | int bound = 0; |
485 | | /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ |
486 | 0 | if ((rv = getaddrinfo(c->tcp.source_addr, NULL, &hints, &bservinfo)) != 0) { |
487 | 0 | char buf[128]; |
488 | 0 | snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); |
489 | 0 | __redisSetError(c,REDIS_ERR_OTHER,buf); |
490 | 0 | goto error; |
491 | 0 | } |
492 | | |
493 | 0 | if (reuseaddr) { |
494 | 0 | n = 1; |
495 | 0 | if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n, |
496 | 0 | sizeof(n)) < 0) { |
497 | 0 | freeaddrinfo(bservinfo); |
498 | 0 | goto error; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | for (b = bservinfo; b != NULL; b = b->ai_next) { |
503 | 0 | if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { |
504 | 0 | bound = 1; |
505 | 0 | break; |
506 | 0 | } |
507 | 0 | } |
508 | 0 | freeaddrinfo(bservinfo); |
509 | 0 | if (!bound) { |
510 | 0 | char buf[128]; |
511 | 0 | snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); |
512 | 0 | __redisSetError(c,REDIS_ERR_OTHER,buf); |
513 | 0 | goto error; |
514 | 0 | } |
515 | 0 | } |
516 | | |
517 | | /* For repeat connection */ |
518 | 0 | hi_free(c->saddr); |
519 | 0 | c->saddr = hi_malloc(p->ai_addrlen); |
520 | 0 | if (c->saddr == NULL) |
521 | 0 | goto oom; |
522 | | |
523 | 0 | memcpy(c->saddr, p->ai_addr, p->ai_addrlen); |
524 | 0 | c->addrlen = p->ai_addrlen; |
525 | |
|
526 | 0 | if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { |
527 | 0 | if (errno == EHOSTUNREACH) { |
528 | 0 | redisNetClose(c); |
529 | 0 | continue; |
530 | 0 | } else if (errno == EINPROGRESS) { |
531 | 0 | if (blocking) { |
532 | 0 | goto wait_for_ready; |
533 | 0 | } |
534 | | /* This is ok. |
535 | | * Note that even when it's in blocking mode, we unset blocking |
536 | | * for `connect()` |
537 | | */ |
538 | 0 | } else if (errno == EADDRNOTAVAIL && reuseaddr) { |
539 | 0 | if (++reuses >= REDIS_CONNECT_RETRIES) { |
540 | 0 | goto error; |
541 | 0 | } else { |
542 | 0 | redisNetClose(c); |
543 | 0 | goto addrretry; |
544 | 0 | } |
545 | 0 | } else { |
546 | 0 | wait_for_ready: |
547 | 0 | if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) |
548 | 0 | goto error; |
549 | 0 | if (redisSetTcpNoDelay(c) != REDIS_OK) |
550 | 0 | goto error; |
551 | 0 | } |
552 | 0 | } |
553 | 0 | if (blocking && redisSetBlocking(c,1) != REDIS_OK) |
554 | 0 | goto error; |
555 | | |
556 | 0 | c->flags |= REDIS_CONNECTED; |
557 | 0 | rv = REDIS_OK; |
558 | 0 | goto end; |
559 | 0 | } |
560 | 0 | if (p == NULL) { |
561 | 0 | char buf[128]; |
562 | 0 | snprintf(buf,sizeof(buf),"Can't create socket: %s",strerror(errno)); |
563 | 0 | __redisSetError(c,REDIS_ERR_OTHER,buf); |
564 | 0 | goto error; |
565 | 0 | } |
566 | | |
567 | 0 | oom: |
568 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
569 | 0 | error: |
570 | 0 | rv = REDIS_ERR; |
571 | 0 | end: |
572 | 0 | if(servinfo) { |
573 | 0 | freeaddrinfo(servinfo); |
574 | 0 | } |
575 | |
|
576 | 0 | return rv; // Need to return REDIS_OK if alright |
577 | 0 | } |
578 | | |
579 | | int redisContextConnectTcp(redisContext *c, const char *addr, int port, |
580 | 0 | const struct timeval *timeout) { |
581 | 0 | return _redisContextConnectTcp(c, addr, port, timeout, NULL); |
582 | 0 | } |
583 | | |
584 | | int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, |
585 | | const struct timeval *timeout, |
586 | 0 | const char *source_addr) { |
587 | 0 | return _redisContextConnectTcp(c, addr, port, timeout, source_addr); |
588 | 0 | } |
589 | | |
590 | 0 | int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { |
591 | 0 | #ifndef _WIN32 |
592 | 0 | int blocking = (c->flags & REDIS_BLOCK); |
593 | 0 | struct sockaddr_un *sa; |
594 | 0 | long timeout_msec = -1; |
595 | |
|
596 | 0 | if (redisCreateSocket(c,AF_UNIX) < 0) |
597 | 0 | return REDIS_ERR; |
598 | 0 | if (redisSetBlocking(c,0) != REDIS_OK) |
599 | 0 | return REDIS_ERR; |
600 | | |
601 | 0 | c->connection_type = REDIS_CONN_UNIX; |
602 | 0 | if (c->unix_sock.path != path) { |
603 | 0 | hi_free(c->unix_sock.path); |
604 | |
|
605 | 0 | c->unix_sock.path = hi_strdup(path); |
606 | 0 | if (c->unix_sock.path == NULL) |
607 | 0 | goto oom; |
608 | 0 | } |
609 | | |
610 | 0 | if (timeout) { |
611 | 0 | if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR) |
612 | 0 | goto oom; |
613 | 0 | } else { |
614 | 0 | hi_free(c->connect_timeout); |
615 | 0 | c->connect_timeout = NULL; |
616 | 0 | } |
617 | | |
618 | 0 | if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) |
619 | 0 | return REDIS_ERR; |
620 | | |
621 | | /* Don't leak sockaddr if we're reconnecting */ |
622 | 0 | if (c->saddr) hi_free(c->saddr); |
623 | |
|
624 | 0 | sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un))); |
625 | 0 | if (sa == NULL) |
626 | 0 | goto oom; |
627 | | |
628 | 0 | c->addrlen = sizeof(struct sockaddr_un); |
629 | 0 | sa->sun_family = AF_UNIX; |
630 | 0 | strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); |
631 | 0 | if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) { |
632 | 0 | if (errno == EINPROGRESS && !blocking) { |
633 | | /* This is ok. */ |
634 | 0 | } else { |
635 | 0 | if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) |
636 | 0 | return REDIS_ERR; |
637 | 0 | } |
638 | 0 | } |
639 | | |
640 | | /* Reset socket to be blocking after connect(2). */ |
641 | 0 | if (blocking && redisSetBlocking(c,1) != REDIS_OK) |
642 | 0 | return REDIS_ERR; |
643 | | |
644 | 0 | c->flags |= REDIS_CONNECTED; |
645 | 0 | return REDIS_OK; |
646 | | #else |
647 | | /* We currently do not support Unix sockets for Windows. */ |
648 | | /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */ |
649 | | errno = EPROTONOSUPPORT; |
650 | | return REDIS_ERR; |
651 | | #endif /* _WIN32 */ |
652 | 0 | oom: |
653 | 0 | __redisSetError(c, REDIS_ERR_OOM, "Out of memory"); |
654 | 0 | return REDIS_ERR; |
655 | 0 | } |