From 37d25a392c9b9468e064a67c504939c9c4ea0031 Mon Sep 17 00:00:00 2001 From: Matt Stancliff Date: Tue, 8 Apr 2014 13:15:36 -0400 Subject: Add ability to bind source address on connect Some environments require binding to specific source addresses instead of letting the system determine which IP a connection should originate from. Closes #233 --- async.c | 8 ++++++++ async.h | 1 + hiredis.c | 8 ++++++++ hiredis.h | 1 + net.c | 38 ++++++++++++++++++++++++++++++++++++-- net.h | 2 ++ 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/async.c b/async.c index f3b57e3..7fd17f2 100644 --- a/async.c +++ b/async.c @@ -165,6 +165,14 @@ redisAsyncContext *redisAsyncConnect(const char *ip, int port) { return ac; } +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, + char *source_addr) { + redisContext *c = redisConnectBindNonBlock(ip,port,source_addr); + redisAsyncContext *ac = redisAsyncInitialize(c); + __redisAsyncCopyError(ac); + return ac; +} + redisAsyncContext *redisAsyncConnectUnix(const char *path) { redisContext *c; redisAsyncContext *ac; diff --git a/async.h b/async.h index 268274e..1df2ff0 100644 --- a/async.h +++ b/async.h @@ -102,6 +102,7 @@ typedef struct redisAsyncContext { /* Functions that proxy to hiredis */ redisAsyncContext *redisAsyncConnect(const char *ip, int port); +redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,char *source); redisAsyncContext *redisAsyncConnectUnix(const char *path); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); diff --git a/hiredis.c b/hiredis.c index 9b74b5b..beed8e9 100644 --- a/hiredis.c +++ b/hiredis.c @@ -1049,6 +1049,14 @@ redisContext *redisConnectNonBlock(const char *ip, int port) { return c; } +redisContext *redisConnectBindNonBlock(const char *ip, int port, + char *source_addr) { + redisContext *c = redisContextInit(); + c->flags &= ~REDIS_BLOCK; + redisContextConnectBindTcp(c,ip,port,NULL,source_addr); + return c; +} + redisContext *redisConnectUnix(const char *path) { redisContext *c; diff --git a/hiredis.h b/hiredis.h index c65098b..6d24213 100644 --- a/hiredis.h +++ b/hiredis.h @@ -175,6 +175,7 @@ typedef struct redisContext { redisContext *redisConnect(const char *ip, int port); redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv); redisContext *redisConnectNonBlock(const char *ip, int port); +redisContext *redisConnectBindNonBlock(const char *ip, int port, char *source); redisContext *redisConnectUnix(const char *path); redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv); redisContext *redisConnectUnixNonBlock(const char *path); diff --git a/net.c b/net.c index c26f4cc..6cbae48 100644 --- a/net.c +++ b/net.c @@ -250,10 +250,12 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) { return REDIS_OK; } -int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout) { +static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, + char *source_addr) { int s, rv; char _port[6]; /* strlen("65535"); */ - struct addrinfo hints, *servinfo, *p; + struct addrinfo hints, *servinfo, *bservinfo, *p, *b; int blocking = (c->flags & REDIS_BLOCK); snprintf(_port, 6, "%d", port); @@ -280,6 +282,28 @@ int redisContextConnectTcp(redisContext *c, const char *addr, int port, const st c->fd = s; if (redisSetBlocking(c,0) != REDIS_OK) goto error; + if (source_addr) { + int bound = 0; + /* Using getaddrinfo saves us from self-determining IPv4 vs IPv6 */ + if ((rv = getaddrinfo(source_addr, NULL, &hints, &bservinfo)) != 0) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't get addr: %s",gai_strerror(rv)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + for (b = bservinfo; b != NULL; b = b->ai_next) { + if (bind(s,b->ai_addr,b->ai_addrlen) != -1) { + bound = 1; + break; + } + } + if (!bound) { + char buf[128]; + snprintf(buf,sizeof(buf),"Can't bind socket: %s",strerror(errno)); + __redisSetError(c,REDIS_ERR_OTHER,buf); + goto error; + } + } if (connect(s,p->ai_addr,p->ai_addrlen) == -1) { if (errno == EHOSTUNREACH) { redisContextCloseFd(c); @@ -314,6 +338,16 @@ end: return rv; // Need to return REDIS_OK if alright } +int redisContextConnectTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout) { + return _redisContextConnectTcp(c, addr, port, timeout, NULL); +} + +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, char *source_addr) { + return _redisContextConnectTcp(c, addr, port, timeout, source_addr); +} + int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) { int blocking = (c->flags & REDIS_BLOCK); struct sockaddr_un sa; diff --git a/net.h b/net.h index af7da11..370a1ff 100644 --- a/net.h +++ b/net.h @@ -42,6 +42,8 @@ int redisCheckSocketError(redisContext *c); int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextConnectTcp(redisContext *c, const char *addr, int port, const struct timeval *timeout); +int redisContextConnectBindTcp(redisContext *c, const char *addr, int port, + const struct timeval *timeout, char *source_addr); int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout); int redisKeepAlive(redisContext *c, int interval); -- cgit v1.2.3