Skip to content

Commit

Permalink
socket: evaluate buffer size in recv / recvfrom
Browse files Browse the repository at this point in the history
When size parameter is not passed to socket:recv() or socket:recvfrom()
it will call a) or b) on the socket to evaluate size of the buffer to
store the receiving datagram. Before this commit a datagram will be
truncated to 512 bytes in the case.

a) Linux: recv(fd, NULL, 0 , MSG_TRUNC | MSG_PEEK)
b) Mac OS: getsockopt(fd, SOL_SOCKET, SO_NREAD, &val, &len)

It is recommended to set 'size' parameter (size of the input buffer)
explicitly based on known message format and known network conditions
(say, set it less then MTU to prevent IP fragmentation, which can be
inefficient) or pass it from a configuration option of a library / an
application. The reason is that explicit buffer size provided allows to
avoid extra syscall to evaluate necessary buffer size.

When 'size' parameter is set explicitly for recv / recvfrom on a UDP
socket and the next datagram length is larger then the size, the
returned message will be truncated to the size provided and the rest of
the datagram will be discarded. Of course, the tail will not be
discarded in case of a TCP socket and will be available to read by the
next recv / recvfrom call.

Fixes #3619.
  • Loading branch information
Totktonada committed Aug 27, 2018
1 parent b43c89b commit de71fd8
Show file tree
Hide file tree
Showing 4 changed files with 980 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/lua/socket.c
Expand Up @@ -268,6 +268,9 @@ static const struct lbox_sockopt_reg so_opts[] = {
#ifdef SO_PROTOCOL
{"SO_PROTOCOL", SO_PROTOCOL, 1, 0, },
#endif
#ifdef SO_NREAD /* available on Darwin */
{"SO_NREAD", SO_NREAD, 1, 0, },
#endif

{"SO_TYPE", SO_TYPE, 1, 0, },

Expand Down
68 changes: 62 additions & 6 deletions src/lua/socket.lua
Expand Up @@ -86,8 +86,12 @@ local function check_socket(socket)
end
end

local function make_socket(fd)
local socket = { _gc_socket = ffi.new(gc_socket_t, { fd = fd }) }
local function make_socket(fd, itype)
assert(itype ~= nil)
local socket = {
_gc_socket = ffi.new(gc_socket_t, { fd = fd }),
itype = itype,
}
return setmetatable(socket, socket_mt)
end

Expand Down Expand Up @@ -590,7 +594,7 @@ local function socket_accept(self)
self._errno = boxerrno()
return nil
end
local client = make_socket(client_fd)
local client = make_socket(client_fd, self.itype)
if not client:nonblock(true) then
client:close()
return
Expand Down Expand Up @@ -770,6 +774,38 @@ local function socket_send(self, octets, flags)
return tonumber(res)
end

local function socket_is_udp_internal(self)
assert(self.itype ~= nil)
return self.itype == internal.SO_TYPE['SOCK_DGRAM']
end

-- returns nil and sets EAGAIN when there are no datagrams in the receive queue
-- as well as when zero-length datagram is the next one
local function socket_dgram_length_internal(self)
local fd = check_socket(self)
if not socket_is_udp_internal(self) then
error("socket_dgram_length_internal() supports only a UDP socket")
end

local len
if jit.os == 'OSX' then
self._errno = nil
len = self:getsockopt('SOL_SOCKET', 'SO_NREAD')
else
local iflags = get_iflags(internal.SEND_FLAGS, {'MSG_TRUNC', 'MSG_PEEK'})
assert(iflags ~= nil)
self._errno = nil
len = tonumber(ffi.C.recv(fd, nil, 0, iflags))
end

if len == -1 or len == 0 then
self._errno = boxerrno()
return nil
end

return len
end

local function socket_recv(self, size, flags)
local fd = check_socket(self)
local iflags = get_iflags(internal.SEND_FLAGS, flags)
Expand All @@ -778,7 +814,15 @@ local function socket_recv(self, size, flags)
return nil
end

size = size or 512
if socket_is_udp_internal(self) then
size = size or socket_dgram_length_internal(self)
if size == nil then
return nil
end
else
size = size or 512
end

self._errno = nil
local buf = ffi.new("char[?]", size)

Expand All @@ -799,7 +843,15 @@ local function socket_recvfrom(self, size, flags)
return nil
end

size = size or 512
if socket_is_udp_internal(self) then
size = size or socket_dgram_length_internal(self)
if size == nil then
return nil
end
else
size = size or 512
end

self._errno = nil
local res, from = internal.recvfrom(fd, size, iflags)
if res == nil then
Expand Down Expand Up @@ -860,7 +912,7 @@ local function socket_new(domain, stype, proto)

local fd = ffi.C.socket(idomain, itype, iproto)
if fd >= 0 then
local socket = make_socket(fd)
local socket = make_socket(fd, itype)
if not socket:nonblock(true) then
socket:close()
else
Expand Down Expand Up @@ -1171,6 +1223,10 @@ socket_mt = {
name = socket_name;
peer = socket_peer;
fd = socket_fd;
internal = { -- for testing
is_udp = socket_is_udp_internal;
dgram_length = socket_dgram_length_internal;
};
};
__tostring = function(self)
local fd = check_socket(self)
Expand Down

0 comments on commit de71fd8

Please sign in to comment.