diff --git a/README.markdown b/README.markdown
index 4e8f9bd3cf..937ff1ce31 100644
--- a/README.markdown
+++ b/README.markdown
@@ -2926,6 +2926,7 @@ Nginx API for Lua
* [udpsock:settimeout](#udpsocksettimeout)
* [ngx.socket.stream](#ngxsocketstream)
* [ngx.socket.tcp](#ngxsockettcp)
+* [tcpsock:bind](#tcpsockbind)
* [tcpsock:connect](#tcpsockconnect)
* [tcpsock:sslhandshake](#tcpsocksslhandshake)
* [tcpsock:send](#tcpsocksend)
@@ -6383,6 +6384,7 @@ ngx.socket.tcp
Creates and returns a TCP or stream-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object:
+* [bind](#tcpsockbind)
* [connect](#tcpsockconnect)
* [sslhandshake](#tcpsocksslhandshake)
* [send](#tcpsocksend)
@@ -6419,6 +6421,42 @@ See also [ngx.socket.udp](#ngxsocketudp).
[Back to TOC](#nginx-api-for-lua)
+tcpsock:bind
+------------
+**syntax:** *ok, err = tcpsock:bind(address)*
+
+**context:** *rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua**
+
+Just like the standard [proxy_bind](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_bind) directive, this api makes the outgoing connection to a upstream server originate from the specified local IP address.
+
+Only IP addresses can be specified as the `address` argument.
+
+Here is an example for connecting to a TCP server from the specified local IP address:
+
+```nginx
+
+ location /test {
+ content_by_lua_block {
+ local sock = ngx.socket.tcp()
+ -- assume "192.168.1.10" is the local ip address
+ local ok, err = sock:bind("192.168.1.10")
+ if not ok then
+ ngx.say("failed to bind")
+ return
+ end
+ local ok, err = sock:connect("192.168.1.67", 80)
+ if not ok then
+ ngx.say("failed to connect server: ", err)
+ return
+ end
+ ngx.say("successfully connected!")
+ sock:close()
+ }
+ }
+```
+
+[Back to TOC](#nginx-api-for-lua)
+
tcpsock:connect
---------------
**syntax:** *ok, err = tcpsock:connect(host, port, options_table?)*
diff --git a/doc/HttpLuaModule.wiki b/doc/HttpLuaModule.wiki
index f6824ef12b..79a110d9d5 100644
--- a/doc/HttpLuaModule.wiki
+++ b/doc/HttpLuaModule.wiki
@@ -5347,6 +5347,7 @@ This API function was first added to the v0.10.1 release.
Creates and returns a TCP or stream-oriented unix domain socket object (also known as one type of the "cosocket" objects). The following methods are supported on this object:
+* [[#tcpsock:bind|bind]]
* [[#tcpsock:connect|connect]]
* [[#tcpsock:sslhandshake|sslhandshake]]
* [[#tcpsock:send|send]]
@@ -5381,6 +5382,38 @@ This feature was first introduced in the v0.5.0rc1 release.
See also [[#ngx.socket.udp|ngx.socket.udp]].
+== tcpsock:bind ==
+'''syntax:''' ''ok, err = tcpsock:bind(address)''
+
+'''context:''' ''rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*''
+
+Just like the standard [[HttpProxyModule#proxy_bind|proxy_bind]] directive, this api makes the outgoing connection to a upstream server originate from the specified local IP address.
+
+Only IP addresses can be specified as the address argument.
+
+Here is an example for connecting to a TCP server from the specified local IP address:
+
+
+ location /test {
+ content_by_lua_block {
+ local sock = ngx.socket.tcp()
+ -- assume "192.168.1.10" is the local ip address
+ local ok, err = sock:bind("192.168.1.10")
+ if not ok then
+ ngx.say("failed to bind")
+ return
+ end
+ local ok, err = sock:connect("192.168.1.67", 80)
+ if not ok then
+ ngx.say("failed to connect server: ", err)
+ return
+ end
+ ngx.say("successfully connected!")
+ sock:close()
+ }
+ }
+
+
== tcpsock:connect ==
'''syntax:''' ''ok, err = tcpsock:connect(host, port, options_table?)''
diff --git a/src/ngx_http_lua_socket_tcp.c b/src/ngx_http_lua_socket_tcp.c
index 1a6594d168..dbbc1d8df9 100644
--- a/src/ngx_http_lua_socket_tcp.c
+++ b/src/ngx_http_lua_socket_tcp.c
@@ -19,6 +19,7 @@
static int ngx_http_lua_socket_tcp(lua_State *L);
+static int ngx_http_lua_socket_tcp_bind(lua_State *L);
static int ngx_http_lua_socket_tcp_connect(lua_State *L);
#if (NGX_HTTP_SSL)
static int ngx_http_lua_socket_tcp_sslhandshake(lua_State *L);
@@ -138,7 +139,8 @@ static void ngx_http_lua_socket_tcp_close_connection(ngx_connection_t *c);
enum {
SOCKET_CTX_INDEX = 1,
SOCKET_TIMEOUT_INDEX = 2,
- SOCKET_KEY_INDEX = 3
+ SOCKET_KEY_INDEX = 3,
+ SOCKET_BIND_INDEX = 4 /* only in upstream cosocket */
};
@@ -266,7 +268,10 @@ ngx_http_lua_inject_socket_tcp_api(ngx_log_t *log, lua_State *L)
/* {{{tcp object metatable */
lua_pushlightuserdata(L, &ngx_http_lua_tcp_socket_metatable_key);
- lua_createtable(L, 0 /* narr */, 11 /* nrec */);
+ lua_createtable(L, 0 /* narr */, 12 /* nrec */);
+
+ lua_pushcfunction(L, ngx_http_lua_socket_tcp_bind);
+ lua_setfield(L, -2, "bind");
lua_pushcfunction(L, ngx_http_lua_socket_tcp_connect);
lua_setfield(L, -2, "connect");
@@ -388,7 +393,7 @@ ngx_http_lua_socket_tcp(lua_State *L)
| NGX_HTTP_LUA_CONTEXT_TIMER
| NGX_HTTP_LUA_CONTEXT_SSL_CERT);
- lua_createtable(L, 3 /* narr */, 1 /* nrec */);
+ lua_createtable(L, 4 /* narr */, 1 /* nrec */);
lua_pushlightuserdata(L, &ngx_http_lua_tcp_socket_metatable_key);
lua_rawget(L, LUA_REGISTRYINDEX);
lua_setmetatable(L, -2);
@@ -399,6 +404,61 @@ ngx_http_lua_socket_tcp(lua_State *L)
}
+static int
+ngx_http_lua_socket_tcp_bind(lua_State *L)
+{
+ ngx_http_request_t *r;
+ ngx_http_lua_ctx_t *ctx;
+ int n;
+ u_char *text;
+ size_t len;
+ ngx_addr_t *local;
+
+ n = lua_gettop(L);
+
+ if (n != 2) {
+ return luaL_error(L, "expecting 2 arguments, but got %d",
+ lua_gettop(L));
+ }
+
+ r = ngx_http_lua_get_req(L);
+ if (r == NULL) {
+ return luaL_error(L, "no request found");
+ }
+
+ ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
+ if (ctx == NULL) {
+ return luaL_error(L, "no ctx found");
+ }
+
+ ngx_http_lua_check_context(L, ctx, NGX_HTTP_LUA_CONTEXT_REWRITE
+ | NGX_HTTP_LUA_CONTEXT_ACCESS
+ | NGX_HTTP_LUA_CONTEXT_CONTENT
+ | NGX_HTTP_LUA_CONTEXT_TIMER
+ | NGX_HTTP_LUA_CONTEXT_SSL_CERT);
+
+ luaL_checktype(L, 1, LUA_TTABLE);
+
+ text = (u_char *) luaL_checklstring(L, 2, &len);
+
+ local = ngx_http_lua_parse_addr(L, text, len);
+ if (local == NULL) {
+ lua_pushnil(L);
+ lua_pushfstring(L, "bad address");
+ return 2;
+ }
+
+ /* TODO: we may reuse the userdata here */
+ lua_rawseti(L, 1, SOCKET_BIND_INDEX);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "lua tcp socket bind ip: %V", &local->name);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+
static int
ngx_http_lua_socket_tcp_connect(lua_State *L)
{
@@ -416,6 +476,7 @@ ngx_http_lua_socket_tcp_connect(lua_State *L)
ngx_int_t rc;
ngx_http_lua_loc_conf_t *llcf;
ngx_peer_connection_t *pc;
+ ngx_addr_t *local;
int timeout;
unsigned custom_pool;
int key_index;
@@ -574,6 +635,14 @@ ngx_http_lua_socket_tcp_connect(lua_State *L)
dd("lua peer connection log: %p", pc->log);
+ lua_rawgeti(L, 1, SOCKET_BIND_INDEX);
+ local = lua_touserdata(L, -1);
+ lua_pop(L, 1);
+
+ if (local) {
+ u->peer.local = local;
+ }
+
lua_rawgeti(L, 1, SOCKET_TIMEOUT_INDEX);
timeout = (ngx_int_t) lua_tointeger(L, -1);
lua_pop(L, 1);
diff --git a/src/ngx_http_lua_util.c b/src/ngx_http_lua_util.c
index 69eccaede4..8330b35357 100644
--- a/src/ngx_http_lua_util.c
+++ b/src/ngx_http_lua_util.c
@@ -4097,4 +4097,75 @@ ngx_http_lua_cleanup_free(ngx_http_request_t *r, ngx_http_cleanup_pt *cleanup)
}
+ngx_addr_t *
+ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len)
+{
+ ngx_addr_t *addr;
+ size_t socklen;
+ in_addr_t inaddr;
+ ngx_uint_t family;
+ struct sockaddr_in *sin;
+#if (NGX_HAVE_INET6)
+ struct in6_addr inaddr6;
+ struct sockaddr_in6 *sin6;
+
+ /*
+ * prevent MSVC8 warning:
+ * potentially uninitialized local variable 'inaddr6' used
+ */
+ ngx_memzero(&inaddr6, sizeof(struct in6_addr));
+#endif
+
+ inaddr = ngx_inet_addr(text, len);
+
+ if (inaddr != INADDR_NONE) {
+ family = AF_INET;
+ socklen = sizeof(struct sockaddr_in);
+
+#if (NGX_HAVE_INET6)
+ } else if (ngx_inet6_addr(text, len, inaddr6.s6_addr) == NGX_OK) {
+ family = AF_INET6;
+ socklen = sizeof(struct sockaddr_in6);
+
+#endif
+ } else {
+ return NULL;
+ }
+
+ addr = lua_newuserdata(L, sizeof(ngx_addr_t) + socklen + len);
+ if (addr == NULL) {
+ luaL_error(L, "no memory");
+ return NULL;
+ }
+
+ addr->sockaddr = (struct sockaddr *) ((u_char *) addr + sizeof(ngx_addr_t));
+
+ ngx_memzero(addr->sockaddr, socklen);
+
+ addr->sockaddr->sa_family = (u_char) family;
+ addr->socklen = socklen;
+
+ switch (family) {
+
+#if (NGX_HAVE_INET6)
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *) addr->sockaddr;
+ ngx_memcpy(sin6->sin6_addr.s6_addr, inaddr6.s6_addr, 16);
+ break;
+#endif
+
+ default: /* AF_INET */
+ sin = (struct sockaddr_in *) addr->sockaddr;
+ sin->sin_addr.s_addr = inaddr;
+ break;
+ }
+
+ addr->name.data = (u_char *) addr->sockaddr + socklen;
+ addr->name.len = len;
+ ngx_memcpy(addr->name.data, text, len);
+
+ return addr;
+}
+
+
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
diff --git a/src/ngx_http_lua_util.h b/src/ngx_http_lua_util.h
index f0e8923c24..797d2249d0 100644
--- a/src/ngx_http_lua_util.h
+++ b/src/ngx_http_lua_util.h
@@ -245,6 +245,8 @@ ngx_http_cleanup_t *ngx_http_lua_cleanup_add(ngx_http_request_t *r,
void ngx_http_lua_cleanup_free(ngx_http_request_t *r,
ngx_http_cleanup_pt *cleanup);
+ngx_addr_t *ngx_http_lua_parse_addr(lua_State *L, u_char *text, size_t len);
+
#define ngx_http_lua_check_if_abortable(L, ctx) \
if ((ctx)->no_abort) { \
diff --git a/t/141-tcp-socket-bind.t b/t/141-tcp-socket-bind.t
new file mode 100644
index 0000000000..7a615e5430
--- /dev/null
+++ b/t/141-tcp-socket-bind.t
@@ -0,0 +1,360 @@
+# vim:set ft= ts=4 sw=4 et fdm=marker:
+
+use Test::Nginx::Socket::Lua;
+
+# more times than usual(2) for test case 6
+repeat_each(4);
+
+plan tests => repeat_each() * (blocks() * 3 + 7);
+
+our $HtmlDir = html_dir;
+
+# we'd better find a better way to get local ip
+my $local_ip = `ifconfig | grep -oE '([0-9]{1,3}\\.?){4}' | grep '\\.' | grep -v '127.0.0.1' | head -n 1`;
+chomp $local_ip;
+
+$ENV{TEST_NGINX_HTML_DIR} = $HtmlDir;
+$ENV{TEST_NGINX_NOT_EXIST_IP} ||= '8.8.8.8';
+$ENV{TEST_NGINX_INVALID_IP} ||= '127.0.0.1:8899';
+$ENV{TEST_NGINX_SERVER_IP} ||= $local_ip;
+
+no_long_string();
+#no_diff();
+
+#log_level 'warn';
+log_level 'debug';
+
+no_shuffle();
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: upstream sockets bind 127.0.0.1
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local ip = "127.0.0.1"
+ local port = ngx.var.port
+
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:bind(ip)
+ if not ok then
+ ngx.say("failed to bind", err)
+ return
+ end
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+
+ local bytes, err = sock:send("GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n")
+ if not bytes then
+ ngx.say("failed to send request: ", err)
+ return
+ end
+
+ ngx.say("request sent")
+
+ local reader = sock:receiveuntil("\r\n0\r\n\r\n")
+ local data, err = reader()
+
+ if not data then
+ ngx.say("failed to receive response body: ", err)
+ return
+ end
+
+ ngx.say("received response")
+ local remote_ip = string.match(data, "(bind: %d+%.%d+%.%d+%.%d+)")
+ ngx.say(remote_ip)
+
+ ngx.say("done")
+ }
+ }
+
+ location /foo {
+ echo bind: $remote_addr;
+ }
+--- request
+GET /t
+--- response_body
+connected: 1
+request sent
+received response
+bind: 127.0.0.1
+done
+--- no_error_log
+["[error]",
+"bind(127.0.0.1) failed"]
+--- error_log eval
+"lua tcp socket bind ip: 127.0.0.1"
+
+
+
+=== TEST 2: upstream sockets bind server ip, not 127.0.0.1
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local ip = "$TEST_NGINX_SERVER_IP"
+ local port = ngx.var.port
+
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:bind(ip)
+ if not ok then
+ ngx.say("failed to bind", err)
+ return
+ end
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+
+ local bytes, err = sock:send("GET /foo HTTP/1.1\r\nHost: localhost\r\nConnection: keepalive\r\n\r\n")
+ if not bytes then
+ ngx.say("failed to send request: ", err)
+ return
+ end
+
+ ngx.say("request sent")
+
+ local reader = sock:receiveuntil("\r\n0\r\n\r\n")
+ local data, err = reader()
+
+ if not data then
+ ngx.say("failed to receive response body: ", err)
+ return
+ end
+
+ ngx.say("received response")
+ local remote_ip = string.match(data, "(bind: %d+%.%d+%.%d+%.%d+)")
+ if remote_ip == "bind: $TEST_NGINX_SERVER_IP" then
+ ngx.say("ip matched")
+ end
+
+ ngx.say("done")
+ }
+ }
+
+ location /foo {
+ echo bind: $remote_addr;
+ }
+--- request
+GET /t
+--- response_body
+connected: 1
+request sent
+received response
+ip matched
+done
+--- no_error_log eval
+["[error]",
+"bind($ENV{TEST_NGINX_SERVER_IP}) failed"]
+--- error_log eval
+"lua tcp socket bind ip: $ENV{TEST_NGINX_SERVER_IP}"
+
+
+
+=== TEST 3: add setkeepalive
+--- http_config eval
+ "lua_package_path '$::HtmlDir/?.lua;./?.lua';"
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local test = require "test"
+ local t1 = test.go()
+ local t2 = test.go()
+ ngx.say("t2 - t1: ", t2 - t1)
+ }
+ }
+--- user_files
+>>> test.lua
+local _M = {}
+
+function _M.go()
+ local ip = "127.0.0.1"
+ local port = ngx.var.port
+
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:bind(ip)
+ if not ok then
+ ngx.say("failed to bind", err)
+ return
+ end
+
+ ngx.say("bind: ", ip)
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+
+ local reused = sock:getreusedtimes()
+
+ local ok, err = sock:setkeepalive()
+ if not ok then
+ ngx.say("failed to set reusable: ", err)
+ end
+
+ return reused
+end
+
+return _M
+--- request
+GET /t
+--- response_body
+bind: 127.0.0.1
+connected: 1
+bind: 127.0.0.1
+connected: 1
+t2 - t1: 1
+--- no_error_log
+["[error]",
+"bind(127.0.0.1) failed"]
+--- error_log eval
+"lua tcp socket bind ip: 127.0.0.1"
+
+
+
+=== TEST 4: upstream sockets bind not exist ip
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local ip = "$TEST_NGINX_NOT_EXIST_IP"
+ local port = ngx.var.port
+
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:bind(ip)
+ if not ok then
+ ngx.say("failed to bind", err)
+ return
+ end
+
+ ngx.say("bind: ", ip)
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+ }
+ }
+--- request
+GET /t
+--- response_body
+bind: 8.8.8.8
+failed to connect: cannot assign requested address
+--- error_log eval
+["bind(8.8.8.8) failed",
+"lua tcp socket bind ip: 8.8.8.8"]
+
+
+
+=== TEST 5: upstream sockets bind invalid ip
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local ip = "$TEST_NGINX_INVALID_IP"
+ local port = ngx.var.port
+
+ local sock = ngx.socket.tcp()
+ local ok, err = sock:bind(ip)
+ if not ok then
+ ngx.say("failed to bind: ", err)
+ return
+ end
+
+ ngx.say("bind: ", ip)
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+ }
+ }
+--- request
+GET /t
+--- response_body
+failed to bind: bad address
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: tcpsock across request after bind
+--- http_config
+ init_worker_by_lua_block {
+ -- this is not the recommend way, just for test
+ local function tcp()
+ local sock = ngx.socket.tcp()
+
+ ---[[
+ local ok, err = sock:bind("127.0.0.1")
+ if not ok then
+ ngx.log(ngx.ERR, "failed to bind")
+ end
+ --]]
+
+ package.loaded.share_sock = sock
+ end
+
+ local ok, err = ngx.timer.at(0, tcp)
+ if not ok then
+ ngx.log(ngx.ERR, "failed to create timer")
+ end
+ }
+--- config
+ server_tokens off;
+ location /t {
+ set $port $TEST_NGINX_SERVER_PORT;
+ content_by_lua_block {
+ local port = ngx.var.port
+
+ -- make sure share_sock is created
+ ngx.sleep(0.002)
+
+ local sock = package.loaded.share_sock
+
+ local ok, err = sock:connect("127.0.0.1", port)
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+
+ ngx.say("connected: ", ok)
+
+ sock:close()
+ collectgarbage("collect")
+ }
+ }
+--- request
+GET /t
+--- response_body
+connected: 1
+--- no_error_log
+[error]