[tarantool-patches] Re: [server-dev] Re: Re: [PATCH v2 1/2] lua: return getaddrinfo() errors
Roman Khabibov
roman.habibov at tarantool.org
Tue Sep 10 15:54:47 MSK 2019
>>>> +-- gh-4138 Check getaddrinfo() error from socket:connect() only.
>>>> +-- Error code and error message returned by getaddrinfo() depends
>>>> +-- on system's gai_strerror(). So that there is no checking for
>>>> +-- certain error message.
>>>> +test_run:cmd("setopt delimiter ';'")
>>>> +---
>>>> +- true
>>>> +...
>>>> +function check_err(err)
>>>> + if err == 'getaddrinfo: nodename nor servname provided, or not known' or
>>>> + err == 'getaddrinfo: Servname not supported for ai_socktype' or
>>>> + err == 'getaddrinfo: Name or service not known' then
>>>> + return true
>>>> + end
>>>> + return false
>>>> +end;
>>>> +---
>>>> +...
>>>> +test_run:cmd("setopt delimiter ''");
>>>> +---
>>>> +- true
>>>> +...
>>>> +s, err = socket:connect('non_exists_hostname:3301')
>>>
>>> socket.connect() should be called, the colon here is the mistake.
>
> Please, look again to the code. One call is socket.connect(...), another
> is socket:connect().
>
> Just in case, I suggest to add a patch to your patchset to keep youself
> from such mistakes:
>
> | diff --git a/src/lua/socket.lua b/src/lua/socket.lua
> | index e4815adbb..a1c13d4db 100644
> | --- a/src/lua/socket.lua
> | +++ b/src/lua/socket.lua
> | @@ -1554,7 +1554,8 @@ local function lsocket_tcp()
> | end
> |
> | local function lsocket_connect(host, port)
> | - if host == nil or port == nil then
> | + if type(host) ~= 'string' or (type(port) ~= 'string' and
> | + type(port) ~= 'number') then
> | error("Usage: luasocket.connect(host, port)")
> | end
> | local s, err = tcp_connect(host, port)
> | @@ -1569,7 +1570,8 @@ local function lsocket_connect(host, port)
> | end
> |
> | local function lsocket_bind(host, port, backlog)
> | - if host == nil or port == nil then
> | + if type(host) ~= 'string' or (type(port) ~= 'string' and
> | + type(port) ~= 'number') then
> | error("Usage: luasocket.bind(host, port [, backlog])")
> | end
> | local function prepare(s) return backlog end
+s, err = socket.getaddrinfo('non_exists_hostname', 3301)
+check_err(err)
+s, err = socket.connect('non_exists_hostname', 3301)
+check_err(err)
+
Also, I added suggested patch.
>>> I think we should test both native and Lua Socket API, because both were
>>> changed.
>>>
>>> Should we ever change Lua Socket API? Please, verify it with a
>>> documentation and an actual behaviour of the external module. We emulate
>>> it, so we should be compatible.
>
> Are you look into this?
http://w3.impa.br/~diego/software/luasocket/tcp.html#connect
master:connect(address, port)
Attempts to connect a master object to a remote host, transforming it into a client object. Client objects support methods send, receive, getsockname, getpeername, settimeout, and close.
Address can be an IP address or a host name. Port must be an integer number in the range [1..64K).
In case of error, the method returns nil followed by a string describing the error. In case of success, the method returns 1.
I looked to this doc. It seems like our behaviour corresponds this API.
commit 9810ccd869183de068939cdce5a157ad35916d36
Author: Roman Khabibov <roman.habibov at tarantool.org>
Date: Tue Jul 30 15:49:13 2019 +0300
lua: return getaddrinfo() errors
Add getaddrinfo() errors into the several fuctions of socket. Now
getaddrinfo() can return a pair of values (nil and error message)
in case of error.
Closes #4138
@TarantoolBot document
Title: socket_object:connect
Connect to a remote host using tcp_connect() within. If it is no
error, return a socket object.
diff --git a/src/box/lua/net_box.lua b/src/box/lua/net_box.lua
index 31a8c16b7..beab3b0a7 100644
--- a/src/box/lua/net_box.lua
+++ b/src/box/lua/net_box.lua
@@ -150,9 +150,12 @@ local function next_id(id) return band(id + 1, 0x7FFFFFFF) end
local function establish_connection(host, port, timeout)
local timeout = timeout or DEFAULT_CONNECT_TIMEOUT
local begin = fiber.clock()
- local s = socket.tcp_connect(host, port, timeout)
+ local s, err = socket.tcp_connect(host, port, timeout)
if not s then
- return nil, errno.strerror(errno())
+ if not err then
+ return nil, errno.strerror(errno())
+ end
+ return nil, err
end
local msg = s:read({chunk = IPROTO_GREETING_SIZE},
timeout - (fiber.clock() - begin))
diff --git a/src/lua/socket.c b/src/lua/socket.c
index 130378caf..dbade2d27 100644
--- a/src/lua/socket.c
+++ b/src/lua/socket.c
@@ -774,6 +774,18 @@ lbox_getaddrinfo_result_wrapper(struct lua_State *L)
return 1;
}
+/**
+ * Wrap coio_getaddrinfo() and call it. Push returned values onto
+ * @a L Lua stack.
+ *
+ * @param L Lua stack.
+ *
+ * @retval 1 Number of returned values by Lua function if
+ * coio_getaddrinfo() success.
+ * @retval 2 Number of returned values by Lua function if
+ * coio_getaddrinfo() is failed (first is nil, second is error
+ * message).
+ */
static int
lbox_socket_getaddrinfo(struct lua_State *L)
{
@@ -816,7 +828,9 @@ lbox_socket_getaddrinfo(struct lua_State *L)
if (dns_res != 0) {
lua_pushnil(L);
- return 1;
+ struct error *err = diag_get()->last;
+ lua_pushstring(L, err->errmsg);
+ return 2;
}
/* no results */
diff --git a/src/lua/socket.lua b/src/lua/socket.lua
index a334ad45b..e4815adbb 100644
--- a/src/lua/socket.lua
+++ b/src/lua/socket.lua
@@ -1041,9 +1041,12 @@ local function tcp_connect(host, port, timeout)
end
local timeout = timeout or TIMEOUT_INFINITY
local stop = fiber.clock() + timeout
- local dns = getaddrinfo(host, port, timeout, { type = 'SOCK_STREAM',
+ local dns, err = getaddrinfo(host, port, timeout, { type = 'SOCK_STREAM',
protocol = 'tcp' })
- if dns == nil or #dns == 0 then
+ if dns == nil then
+ return nil, err
+ end
+ if #dns == 0 then
boxerrno(boxerrno.EINVAL)
return nil
end
@@ -1360,9 +1363,16 @@ local function lsocket_tcp_connect(self, host, port)
-- This function is broken by design
local ga_opts = { family = 'AF_INET', type = 'SOCK_STREAM' }
local timeout = deadline - fiber.clock()
- local dns = getaddrinfo(host, port, timeout, ga_opts)
+ local dns, err = getaddrinfo(host, port, timeout, ga_opts)
if dns == nil or #dns == 0 then
- self._errno = boxerrno.EINVAL
+ return nil, err
+ end
+ if dns == nil then
+ boxerrno(self._errno)
+ return nil, err
+ end
+ if #dns == 0 then
+ boxerrno(self._errno)
return nil, socket_error(self)
end
for _, remote in ipairs(dns) do
@@ -1547,9 +1557,12 @@ local function lsocket_connect(host, port)
if host == nil or port == nil then
error("Usage: luasocket.connect(host, port)")
end
- local s = tcp_connect(host, port)
+ local s, err = tcp_connect(host, port)
if not s then
- return nil, boxerrno.strerror()
+ if not err then
+ return nil, boxerrno.strerror()
+ end
+ return nil, err
end
setmetatable(s, lsocket_tcp_client_mt)
return s
diff --git a/test/app/socket.result b/test/app/socket.result
index fd299424c..87b1d0387 100644
--- a/test/app/socket.result
+++ b/test/app/socket.result
@@ -941,10 +941,20 @@ sc:close()
- true
...
-- tcp_connect
--- test timeout
-socket.tcp_connect('127.0.0.1', 80, 0.00000000001)
+-- Test timeout. In this test, tcp_connect can return the second
+-- output value from internal.getaddrinfo (usually on Mac OS, but
+-- theoretically it can happen on Linux too). Sometimes
+-- getaddrinfo() is timed out, sometimes connect. On Linux however
+-- getaddrinfo is fast enough to never give timeout error in
+-- the case. So, there are two sources of timeout errors that are
+-- reported differently. This difference has appeared after
+-- gh-4138 patch.
+s, err = socket.tcp_connect('127.0.0.1', 80, 0.00000000001)
---
-- null
+...
+s == nil
+---
+- true
...
-- AF_INET
s = socket('AF_INET', 'SOCK_STREAM', 'tcp')
@@ -1606,7 +1616,7 @@ fio.stat(path) == nil
{ socket.tcp_connect('abrakadabra#123') == nil, errno.strerror() }
---
- - true
- - Invalid argument
+ - Input/output error
...
-- wrong options for getaddrinfo
socket.getaddrinfo('host', 'port', { type = 'WRONG' }) == nil and errno() == errno.EINVAL
@@ -2812,6 +2822,41 @@ server:close()
---
- true
...
+-- gh-4138 Check getaddrinfo() error from socket:connect() only.
+-- Error code and error message returned by getaddrinfo() depends
+-- on system's gai_strerror().
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function check_err(err)
+ if err == 'getaddrinfo: nodename nor servname provided, or not known' or
+ err == 'getaddrinfo: Servname not supported for ai_socktype' or
+ err == 'getaddrinfo: Name or service not known' then
+ return true
+ end
+ return false
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+s, err = socket.getaddrinfo('non_exists_hostname', 3301)
+---
+...
+check_err(err)
+---
+- true
+...
+s, err = socket.connect('non_exists_hostname', 3301)
+---
+...
+check_err(err)
+---
+- true
+...
test_run:cmd("clear filter")
---
- true
diff --git a/test/app/socket.test.lua b/test/app/socket.test.lua
index c72d41763..08cb7fc9e 100644
--- a/test/app/socket.test.lua
+++ b/test/app/socket.test.lua
@@ -300,8 +300,16 @@ sc:close()
-- tcp_connect
--- test timeout
-socket.tcp_connect('127.0.0.1', 80, 0.00000000001)
+-- Test timeout. In this test, tcp_connect can return the second
+-- output value from internal.getaddrinfo (usually on Mac OS, but
+-- theoretically it can happen on Linux too). Sometimes
+-- getaddrinfo() is timed out, sometimes connect. On Linux however
+-- getaddrinfo is fast enough to never give timeout error in
+-- the case. So, there are two sources of timeout errors that are
+-- reported differently. This difference has appeared after
+-- gh-4138 patch.
+s, err = socket.tcp_connect('127.0.0.1', 80, 0.00000000001)
+s == nil
-- AF_INET
s = socket('AF_INET', 'SOCK_STREAM', 'tcp')
@@ -958,6 +966,25 @@ fiber.cancel(echo_fiber)
client:read(1, 5) == ''
server:close()
+-- gh-4138 Check getaddrinfo() error from socket:connect() only.
+-- Error code and error message returned by getaddrinfo() depends
+-- on system's gai_strerror().
+test_run:cmd("setopt delimiter ';'")
+function check_err(err)
+ if err == 'getaddrinfo: nodename nor servname provided, or not known' or
+ err == 'getaddrinfo: Servname not supported for ai_socktype' or
+ err == 'getaddrinfo: Name or service not known' then
+ return true
+ end
+ return false
+end;
+test_run:cmd("setopt delimiter ''");
+
+s, err = socket.getaddrinfo('non_exists_hostname', 3301)
+check_err(err)
+s, err = socket.connect('non_exists_hostname', 3301)
+check_err(err)
+
test_run:cmd("clear filter")
-- case: sicket receive inconsistent behavior
diff --git a/test/box/net.box.result b/test/box/net.box.result
index e3dabf7d9..0635ca9e7 100644
--- a/test/box/net.box.result
+++ b/test/box/net.box.result
@@ -3927,6 +3927,34 @@ test_run:grep_log('default', '00000040:.*')
---
- null
...
+-- gh-4138 Check getaddrinfo() error from connect() only. Error
+-- code and error message returned by getaddrinfo() depends on
+-- system's gai_strerror().
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function check_err(err)
+ if err == 'getaddrinfo: nodename nor servname provided, or not known' or
+ err == 'getaddrinfo: Servname not supported for ai_socktype' or
+ err == 'getaddrinfo: Name or service not known' then
+ return true
+ end
+ return false
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+s = remote.connect('non_exists_hostname:3301')
+---
+...
+check_err(s['error'])
+---
+- true
+...
box.cfg{log_level=log_level}
---
...
diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua
index 8e65ff470..9c9aee1ad 100644
--- a/test/box/net.box.test.lua
+++ b/test/box/net.box.test.lua
@@ -1582,4 +1582,21 @@ test_run:wait_log('default', '00000030:.*', nil, 10)
-- we expect nothing below, so don't wait
test_run:grep_log('default', '00000040:.*')
+-- gh-4138 Check getaddrinfo() error from connect() only. Error
+-- code and error message returned by getaddrinfo() depends on
+-- system's gai_strerror().
+test_run:cmd("setopt delimiter ';'")
+function check_err(err)
+ if err == 'getaddrinfo: nodename nor servname provided, or not known' or
+ err == 'getaddrinfo: Servname not supported for ai_socktype' or
+ err == 'getaddrinfo: Name or service not known' then
+ return true
+ end
+ return false
+end;
+test_run:cmd("setopt delimiter ''");
+
+s = remote.connect('non_exists_hostname:3301')
+check_err(s['error'])
+
box.cfg{log_level=log_level}
More information about the Tarantool-patches
mailing list