diff --git a/include/coeio.h b/include/coeio.h index cee7be29284f654c22a0aa0170ae08b8e411d73b..9f19d77cde7e93c696de6af152709e56bd56b003 100644 --- a/include/coeio.h +++ b/include/coeio.h @@ -47,6 +47,10 @@ #define ERESOLVE -1 +#if defined(__cplusplus) +extern "C" { +#endif + /** * Asynchronous IO Tasks (libeio wrapper) * @@ -60,4 +64,7 @@ struct addrinfo * coeio_resolve(int socktype, const char *host, const char *port, ev_tstamp timeout); +#if defined(__cplusplus) +} +#endif #endif /* TARANTOOL_COEIO_H_INCLUDED */ diff --git a/include/fiber.h b/include/fiber.h index a89d8e8d63d1837d2007283ce645b5b3f156a3fa..c657b32a7c85724ef5f6bfa6b9e620018346f1ea 100644 --- a/include/fiber.h +++ b/include/fiber.h @@ -72,6 +72,8 @@ class FiberCancelException: public Exception { say_debug("FiberCancelException"); } }; + +extern "C" { #endif /* defined(__cplusplus) */ struct fiber { @@ -175,4 +177,7 @@ fiber_set_sid(struct fiber *f, uint32_t sid, uint64_t cookie) f->cookie = cookie; } +#if defined(__cplusplus) +} +#endif #endif /* TARANTOOL_FIBER_H_INCLUDED */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51fd9bcca5d6a73c6e63a30088fd1430d6d3586f..f6bf0d01436d9ff3b9ceff5b9c8df9ba582962de 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -64,6 +64,8 @@ set(lua_sources) lua_source(lua_sources lua/uuid.lua) lua_source(lua_sources lua/digest.lua) lua_source(lua_sources lua/session.lua) +lua_source(lua_sources lua/bsdsocket.lua) +lua_source(lua_sources lua/errno.lua) add_custom_target(generate_lua_sources WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src/box @@ -121,7 +123,9 @@ set (common_sources lua/lua_socket.cc lua/session.cc lua/cjson.cc + lua/errno.c fiob.c + bsdsocket.cc ${lua_sources} ) diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc index 5571340f1ba190e4491afb3f201683f55890255b..92308ab48672cd92d6583b26e9af087c37f84016 100644 --- a/src/box/box_lua.cc +++ b/src/box/box_lua.cc @@ -54,8 +54,15 @@ extern "C" { #include "scoped_guard.h" /* contents of box.lua, misc.lua, box.net.lua respectively */ -extern char box_lua[], box_net_lua[], misc_lua[]; -static const char *lua_sources[] = { box_lua, box_net_lua, misc_lua, NULL }; +extern char box_lua[], + box_net_lua[], + misc_lua[]; +static const char *lua_sources[] = { + box_lua, + box_net_lua, + misc_lua, + NULL +}; /** * All box connections share the same Lua state. We use diff --git a/src/bsdsocket.cc b/src/bsdsocket.cc new file mode 100644 index 0000000000000000000000000000000000000000..4766d93c499d93bf2c658ef42ed485396297db92 --- /dev/null +++ b/src/bsdsocket.cc @@ -0,0 +1,870 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <errno.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/types.h> +#include <string.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> + +extern "C" { +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> +#include <lj_obj.h> +#include <lj_ctype.h> +#include <lj_cdata.h> +#include <lj_cconv.h> +} + +#include <arpa/inet.h> +#include <lib/bit/bit.h> +#include <coeio.h> +#include <unistd.h> +#include <fiber.h> +#include <say.h> +#include <scoped_guard.h> + +#include "bsdsocket.h" + +extern char bsdsocket_lua[]; + +static const struct { char name[32]; int value; } domains[] = { + { "PF_UNIX", PF_UNIX }, + { "PF_LOCAL", PF_LOCAL }, + { "PF_INET", PF_INET }, + { "PF_INET6", PF_INET6 }, + { "PF_IPX", PF_IPX }, + { "PF_NETLINK", PF_NETLINK }, + { "PF_X25", PF_X25 }, + { "PF_AX25", PF_AX25 }, + { "PF_ATMPVC", PF_ATMPVC }, + { "PF_APPLETALK", PF_APPLETALK }, + { "PF_PACKET", PF_PACKET }, + + { "AF_UNIX", AF_UNIX }, + { "AF_LOCAL", AF_LOCAL }, + { "AF_INET", AF_INET }, + { "AF_INET6", AF_INET6 }, + { "AF_IPX", AF_IPX }, + { "AF_NETLINK", AF_NETLINK }, + { "AF_X25", AF_X25 }, + { "AF_AX25", AF_AX25 }, + { "AF_ATMPVC", AF_ATMPVC }, + { "AF_APPLETALK", AF_APPLETALK }, + { "AF_PACKET", AF_PACKET }, + { "", 0 } +}; + +static const struct { char name[32]; int value; } types[] = { + { "SOCK_STREAM", SOCK_STREAM }, + { "SOCK_DGRAM", SOCK_DGRAM }, + { "SOCK_SEQPACKET", SOCK_SEQPACKET }, + { "SOCK_RAW", SOCK_RAW }, + { "SOCK_RDM", SOCK_RDM }, + { "", 0 } +}; + +static const struct { char name[32]; int value; } send_flags[] = { +#ifdef MSG_OOB + {"MSG_OOB", MSG_OOB }, +#endif +#ifdef MSG_PEEK + {"MSG_PEEK", MSG_PEEK }, +#endif +#ifdef MSG_DONTROUTE + {"MSG_DONTROUTE", MSG_DONTROUTE }, +#endif +#ifdef MSG_TRYHARD + {"MSG_TRYHARD", MSG_TRYHARD }, +#endif +#ifdef MSG_CTRUNC + {"MSG_CTRUNC", MSG_CTRUNC }, +#endif +#ifdef MSG_PROXY + {"MSG_PROXY", MSG_PROXY }, +#endif +#ifdef MSG_TRUNC + {"MSG_TRUNC", MSG_TRUNC }, +#endif +#ifdef MSG_DONTWAIT + {"MSG_DONTWAIT", MSG_DONTWAIT }, +#endif +#ifdef MSG_EOR + {"MSG_EOR", MSG_EOR }, +#endif +#ifdef MSG_WAITALL + {"MSG_WAITALL", MSG_WAITALL }, +#endif +#ifdef MSG_FIN + {"MSG_FIN", MSG_FIN }, +#endif +#ifdef MSG_SYN + {"MSG_SYN", MSG_SYN }, +#endif +#ifdef MSG_CONFIRM + {"MSG_CONFIRM", MSG_CONFIRM }, +#endif +#ifdef MSG_RST + {"MSG_RST", MSG_RST }, +#endif +#ifdef MSG_ERRQUEUE + {"MSG_ERRQUEUE", MSG_ERRQUEUE }, +#endif +#ifdef MSG_NOSIGNAL + {"MSG_NOSIGNAL", MSG_NOSIGNAL }, +#endif +#ifdef MSG_MORE + {"MSG_MORE", MSG_MORE }, +#endif +#ifdef MSG_WAITFORONE + {"MSG_WAITFORONE", MSG_WAITFORONE }, +#endif +#ifdef MSG_FASTOPEN + {"MSG_FASTOPEN", MSG_FASTOPEN }, +#endif +#ifdef MSG_CMSG_CLOEXEC + {"MSG_CMSG_CLOEXEC", MSG_CMSG_CLOEXEC}, +#endif + { "", 0 } +}; + +static const struct { char name[32]; int value, type, rw; } so_opts[] = { + {"SO_ACCEPTCONN", SO_ACCEPTCONN, 1, 0, }, + {"SO_BINDTODEVICE", SO_BINDTODEVICE, 2, 1, }, + {"SO_BROADCAST", SO_BROADCAST, 1, 1, }, + {"SO_BSDCOMPAT", SO_BSDCOMPAT, 1, 1, }, + {"SO_DEBUG", SO_DEBUG, 1, 1, }, + {"SO_DOMAIN", SO_DOMAIN, 1, 0, }, + {"SO_ERROR", SO_ERROR, 1, 0, }, + {"SO_DONTROUTE", SO_DONTROUTE, 1, 1, }, + {"SO_KEEPALIVE", SO_KEEPALIVE, 1, 1, }, + {"SO_LINGER", SO_LINGER, 0, 0, }, + {"SO_MARK", SO_MARK, 1, 1, }, + {"SO_OOBINLINE", SO_OOBINLINE, 1, 1, }, + {"SO_PASSCRED", SO_PASSCRED, 1, 1, }, + {"SO_PEERCRED", SO_PEERCRED, 1, 0, }, + {"SO_PRIORITY", SO_PRIORITY, 1, 1, }, + {"SO_PROTOCOL", SO_PROTOCOL, 1, 0, }, + {"SO_RCVBUF", SO_RCVBUF, 1, 1, }, + {"SO_RCVBUFFORCE", SO_RCVBUFFORCE, 1, 1, }, + {"SO_RCVLOWAT", SO_RCVLOWAT, 1, 1, }, + {"SO_SNDLOWAT", SO_SNDLOWAT, 1, 1, }, + {"SO_RCVTIMEO", SO_RCVTIMEO, 1, 1, }, + {"SO_SNDTIMEO", SO_SNDTIMEO, 1, 1, }, + {"SO_REUSEADDR", SO_REUSEADDR, 1, 1, }, + {"SO_SNDBUF", SO_SNDBUF, 1, 1, }, + {"SO_SNDBUFFORCE", SO_SNDBUFFORCE, 1, 1, }, + {"SO_TIMESTAMP", SO_TIMESTAMP, 1, 1, }, + {"SO_TYPE", SO_TYPE, 1, 0, }, + + {"", 0, 0, 0, } +}; + +static const struct { char name[32]; int value; } ai_flags[] = { + {"AI_PASSIVE", AI_PASSIVE }, + {"AI_CANONNAME", AI_CANONNAME }, + {"AI_NUMERICHOST", AI_NUMERICHOST }, + {"AI_V4MAPPED", AI_V4MAPPED }, + {"AI_ALL", AI_ALL }, + {"AI_ADDRCONFIG", AI_ADDRCONFIG }, +#ifdef AI_IDN + {"AI_IDN", AI_IDN }, +#endif +#ifdef AI_CANONIDN + {"AI_CANONIDN", AI_CANONIDN }, +#endif +#ifdef AI_IDN_ALLOW_UNASSIGNED + {"AI_IDN_ALLOW_UNASSIGNED", AI_IDN_ALLOW_UNASSIGNED }, +#endif +#ifndef AI_IDN_USE_STD3_ASCII_RULES + {"AI_IDN_USE_STD3_ASCII_RULES", AI_IDN_USE_STD3_ASCII_RULES }, +#endif +#ifdef AI_NUMERICSERV + {"AI_NUMERICSERV", AI_NUMERICSERV }, +#endif + {"", 0 } +}; + +int +bsdsocket_protocol(const char *proto) +{ + struct protoent *p = getprotobyname(proto); + if (!p) { + errno = EINVAL; + return -1; + } + return p->p_proto; +} + + +struct dns_res { + struct sockaddr *sa; + socklen_t salen; +}; + + + +static struct dns_res * +local_resolve(const char *host, const char *port) +{ + static struct dns_res dns; + memset(&dns, 0, sizeof(dns)); + + if (strcmp(host, "unix/") == 0) { + static struct sockaddr_un uaddr; + uaddr.sun_family = AF_UNIX; + strncpy(uaddr.sun_path, port, sizeof(uaddr.sun_path)); + + dns.sa = (struct sockaddr *)&uaddr; + dns.salen = sizeof(uaddr); + return &dns; + } + + /* IPv4 */ + in_addr_t iaddr = inet_addr(host); + if (iaddr != (in_addr_t)(-1)) { + static struct sockaddr_in in; + in.sin_family = AF_INET; + in.sin_addr.s_addr = iaddr; + + in.sin_port = htons(atoi(port)); + + dns.sa = (struct sockaddr *)∈ + dns.salen = sizeof(in); + return &dns; + } + + /* IPv6 */ + char ipv6[16]; + if (inet_pton(AF_INET6, host, ipv6) == 1) { + struct sockaddr_in6 in6; + memset(&in6, 0, sizeof(in6)); + in6.sin6_family = AF_INET6; + in6.sin6_port = htonl(atol(port)); + memcpy(in6.sin6_addr.s6_addr, ipv6, 16); + + dns.sa = (struct sockaddr *)&in6; + dns.salen = sizeof(in6); + return &dns; + } + + errno = EINVAL; + return NULL; +} + +int +bsdsocket_sysconnect(int fh, const char *host, const char *port) +{ + struct dns_res *dns = local_resolve(host, port); + if (!dns) { + errno = EINVAL; + return -1; + } + return connect(fh, dns->sa, dns->salen);; +} + +int +bsdsocket_bind(int fh, const char *host, const char *port) +{ + struct dns_res *dns = local_resolve(host, port); + if (!dns) { + errno = EINVAL; + return -1; + } + return bind(fh, dns->sa, dns->salen);; +} + +int +bsdsocket_nonblock(int fh, int mode) +{ + int flags = fcntl(fh, F_GETFL, 0); + if (flags < 0) + return -1; + + /* GET */ + if (mode == 0x80) { + if (flags & O_NONBLOCK) + return 1; + else + return 0; + } + + if (mode) { + if (flags & O_NONBLOCK) + return 1; + flags |= O_NONBLOCK; + } else { + if ((flags & O_NONBLOCK) == 0) + return 0; + flags &= ~O_NONBLOCK; + } + flags = fcntl(fh, F_SETFL, flags); + if (flags < 0) + return -1; + + return mode ? 1 : 0; +} + +struct bsdsocket_io_wdata { + struct fiber *fiber; + int io; +}; + +static void +bsdsocket_io(ev_io *watcher, int revents) +{ + struct bsdsocket_io_wdata *wdata = + (struct bsdsocket_io_wdata *)watcher->data; + wdata->io = revents; + fiber_wakeup(wdata->fiber); +} + +static int +lbox_bsdsocket_iowait(struct lua_State *L) +{ + int fh = lua_tointeger(L, 1); + int events = lua_tointeger(L, 2); + ev_tstamp timeout = lua_tonumber(L, 3); + + + switch(events) { + case 0: + events = EV_READ; + break; + case 1: + events = EV_WRITE; + break; + case 2: + events = EV_READ | EV_WRITE; + break; + default: + abort(); + } + + struct ev_io io; + ev_io_init(&io, bsdsocket_io, fh, events); + struct bsdsocket_io_wdata wdata = { fiber, 0 }; + io.data = &wdata; + ev_set_priority(&io, EV_MAXPRI); + ev_io_start(&io); + + fiber_yield_timeout(timeout); + ev_io_stop(&io); + + int ret = 0; + if (wdata.io & EV_READ) + ret |= 1; + if (wdata.io & EV_WRITE) + ret |= 2; + + lua_pushinteger(L, ret); + return 1; +} + +static ssize_t +bsdsocket_getaddrinfo_cb(va_list ap) +{ + const char *host = va_arg(ap, const char *); + const char *port = va_arg(ap, const char *); + const struct addrinfo *hints = va_arg(ap, const struct addrinfo *); + struct addrinfo **res = va_arg(ap, struct addrinfo **); + int *res_errno = va_arg(ap, int *); + + int rc = getaddrinfo(host, port, hints, res); + + switch(rc) { + case EAI_NONAME: + *res = NULL; + case 0: + return 0; + + case EAI_SYSTEM: + *res_errno = errno; + return -1; + + + default: + *res_errno = rc; + return -1; + } +} + +static int +lbox_bsdsocket_push_family(struct lua_State *L, int family) +{ + switch(family) { + case AF_UNIX: + lua_pushliteral(L, "AF_UNIX"); + break; + case AF_INET: + lua_pushliteral(L, "AF_INET"); + break; + case AF_INET6: + lua_pushliteral(L, "AF_INET6"); + break; + case AF_IPX: + lua_pushliteral(L, "AF_IPX"); + break; + case AF_NETLINK: + lua_pushliteral(L, "AF_NETLINK"); + break; + case AF_X25: + lua_pushliteral(L, "AF_X25"); + break; + case AF_AX25: + lua_pushliteral(L, "AF_AX25"); + break; + case AF_ATMPVC: + lua_pushliteral(L, "AF_ATMPVC"); + break; + case AF_APPLETALK: + lua_pushliteral(L, "AF_APPLETALK"); + break; + case AF_PACKET: + lua_pushliteral(L, "AF_PACKET"); + break; + + default: + lua_pushinteger(L, family); + break; + } + return 1; +} + +static int +lbox_bsdsocket_push_protocol(struct lua_State *L, int protonumber) +{ + struct protoent *p = getprotobynumber(protonumber); + if (p) { + lua_pushstring(L, p->p_name); + } else { + lua_pushinteger(L, p->p_proto); + } + return 1; +} + +static int +lbox_bsdsocket_push_sotype(struct lua_State *L, int sotype) +{ + sotype &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC); + switch(sotype) { + case SOCK_STREAM: + lua_pushliteral(L, "SOCK_STREAM"); + break; + case SOCK_DGRAM: + lua_pushliteral(L, "SOCK_DGRAM"); + break; + case SOCK_SEQPACKET: + lua_pushliteral(L, "SOCK_SEQPACKET"); + break; + case SOCK_RAW: + lua_pushliteral(L, "SOCK_RAW"); + break; + case SOCK_RDM: + lua_pushliteral(L, "SOCK_RDM"); + break; + case SOCK_PACKET: + lua_pushliteral(L, "SOCK_PACKET"); + break; + default: + lua_pushinteger(L, sotype); + break; + } + return 1; +} + +static int +lbox_bsdsocket_push_addr(struct lua_State *L, + const struct sockaddr *addr, socklen_t alen) +{ + lua_newtable(L); + + lua_pushliteral(L, "family"); + lbox_bsdsocket_push_family(L, addr->sa_family); + lua_rawset(L, -3); + + switch(addr->sa_family) { + case AF_INET: + case AF_INET6: { + char shost[NI_MAXHOST]; + char sservice[NI_MAXSERV]; + int rc = getnameinfo(addr, + alen, + shost, sizeof(shost), + sservice, sizeof(sservice), + NI_NUMERICHOST|NI_NUMERICSERV + ); + + if (rc == 0) { + lua_pushliteral(L, "host"); + lua_pushstring(L, shost); + lua_rawset(L, -3); + + lua_pushliteral(L, "port"); + lua_pushinteger(L, atol(sservice)); + lua_rawset(L, -3); + } + + + break; + } + + case AF_UNIX: + lua_pushliteral(L, "host"); + lua_pushliteral(L, "unix/"); + lua_rawset(L, -3); + + lua_pushliteral(L, "port"); + lua_pushstring(L, + ((struct sockaddr_un *)addr)->sun_path); + lua_rawset(L, -3); + break; + + default: /* unknown family */ + lua_pop(L, 1); + lua_pushnil(L); + break; + } + + return 1; +} + +static int +lbox_bsdsocket_getaddrinfo(struct lua_State *L) +{ + assert(lua_gettop(L) == 4); + lua_pushvalue(L, 1); + const char *host = lua_tostring(L, -1); + lua_pushvalue(L, 2); + const char *port = lua_tostring(L, -1); + + ev_tstamp timeout = lua_tonumber(L, 3); + + struct addrinfo hints, *result = NULL; + memset(&hints, 0, sizeof(hints)); + + if (lua_istable(L, 4)) { + lua_getfield(L, 4, "family"); + if (lua_isnumber(L, -1)) + hints.ai_family = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 4, "type"); + if (lua_isnumber(L, -1)) + hints.ai_socktype = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 4, "protocol"); + if (lua_isnumber(L, -1)) + hints.ai_protocol = lua_tointeger(L, -1); + lua_pop(L, 1); + + lua_getfield(L, 4, "flags"); + if (lua_isnumber(L, -1)) + hints.ai_flags = lua_tointeger(L, -1); + lua_pop(L, 1); + } + + int res_errno = 0; + int dns_res = coeio_custom(bsdsocket_getaddrinfo_cb, timeout, + host, port, &hints, &result, &res_errno); + lua_pop(L, 2); /* host, port */ + + if (dns_res != 0) { + errno = res_errno; + lua_pushnil(L); + return 1; + } + + /* no results */ + if (!result) { + lua_newtable(L); + return 1; + } + + auto scope_guard = make_scoped_guard([&]{ + freeaddrinfo(result); + }); + + lua_newtable(L); + int i = 1; + for (struct addrinfo *rp = result; rp; rp = rp->ai_next, i++) { + lua_pushinteger(L, i); + + lbox_bsdsocket_push_addr(L, rp->ai_addr, rp->ai_addrlen); + + if (lua_isnil(L, -1)) { + lua_pop(L, 2); + i--; + continue; + } + + lua_pushliteral(L, "protocol"); + lbox_bsdsocket_push_protocol(L, rp->ai_protocol); + lua_rawset(L, -3); + + lua_pushliteral(L, "type"); + lbox_bsdsocket_push_sotype(L, rp->ai_socktype); + lua_rawset(L, -3); + + if (rp->ai_canonname) { + lua_pushliteral(L, "canonname"); + lua_pushstring(L, rp->ai_canonname); + lua_rawset(L, -3); + } + + + lua_rawset(L, -3); + + } + return 1; +} + +static void +lbox_bsdsocket_update_proto_type(struct lua_State *L, int fh) +{ + if (lua_isnil(L, -1)) + return; + + int save_errno = errno; + + int value; + socklen_t len = sizeof(value); + + if (getsockopt(fh, SOL_SOCKET, SO_PROTOCOL, &value, &len) == 0) { + lua_pushliteral(L, "protocol"); + lbox_bsdsocket_push_protocol(L, value); + lua_rawset(L, -3); + } + + len = sizeof(value); + if (getsockopt(fh, SOL_SOCKET, SO_TYPE, &value, &len) == 0) { + lua_pushliteral(L, "type"); + lbox_bsdsocket_push_sotype(L, value); + lua_rawset(L, -3); + } + errno = save_errno; + +} + +static int +lbox_bsdsocket_soname(struct lua_State *L) +{ + lua_pushvalue(L, 1); + int fh = lua_tointeger(L, -1); + lua_pop(L, 1); + + + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + if (getsockname(fh, (struct sockaddr *)&addr, &len) != 0) { + lua_pushnil(L); + return 1; + } + lbox_bsdsocket_push_addr(L, (const struct sockaddr *)&addr, len); + lbox_bsdsocket_update_proto_type(L, fh); + return 1; +} + + +static int +lbox_bsdsocket_peername(struct lua_State *L) +{ + lua_pushvalue(L, 1); + int fh = lua_tointeger(L, -1); + lua_pop(L, 1); + + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + if (getpeername(fh, (struct sockaddr *)&addr, &len) != 0) { + lua_pushnil(L); + return 1; + } + lbox_bsdsocket_push_addr(L, (const struct sockaddr *)&addr, len); + lbox_bsdsocket_update_proto_type(L, fh); + return 1; +} + + +static int +lbox_bsdsocket_recvfrom(struct lua_State *L) +{ + int fh = lua_tointeger(L, 1); + int size = lua_tointeger(L, 2); + int flags = lua_tointeger(L, 3); + + struct sockaddr_storage fa; + socklen_t len = sizeof(fa); + + char *buf = (char *)malloc(size); + if (!buf) { + errno = ENOMEM; + lua_pushnil(L); + return 1; + } + + auto scope_guard = make_scoped_guard([&]{ free(buf); }); + + ssize_t res = recvfrom(fh, buf, size, flags, + (struct sockaddr*)&fa, &len); + + if (res < 0) { + lua_pushnil(L); + return 1; + } + lua_pushlstring(L, buf, res); + lbox_bsdsocket_push_addr(L, (struct sockaddr *)&fa, len); + return 2; +} + + +int +bsdsocket_sendto(int fh, const char *host, const char *port, + const void *octets, size_t len, int flags) +{ + struct dns_res *dns = local_resolve(host, port); + if (!dns) { + errno = EINVAL; + return -1; + } + return sendto(fh, octets, len, flags, dns->sa, dns->salen); +} + +void +tarantool_lua_bsdsocket_init(struct lua_State *L) +{ + int top = lua_gettop(L); + + static const struct luaL_Reg internal_methods[] = { + { "iowait", lbox_bsdsocket_iowait }, + { "getaddrinfo", lbox_bsdsocket_getaddrinfo }, + { "name", lbox_bsdsocket_soname }, + { "peer", lbox_bsdsocket_peername }, + { "recvfrom", lbox_bsdsocket_recvfrom }, + { NULL, NULL } + }; + + + if (luaL_dostring(L, bsdsocket_lua)) + panic("Error loading Lua source (internal)/bsdsocket.lua: %s", + lua_tostring(L, -1)); + + lua_getfield(L, LUA_GLOBALSINDEX, "box"); + lua_getfield(L, -1, "socket"); + lua_getfield(L, -1, "internal"); + + + luaL_register(L, NULL, internal_methods); + + + /* domains table */ + lua_pushliteral(L, "DOMAIN"); + lua_newtable(L); + for (int i = 0; domains[i].name[0]; i++) { + lua_pushstring(L, domains[i].name); + lua_pushinteger(L, domains[i].value); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + /* SO_TYPE */ + lua_pushliteral(L, "SO_TYPE"); + lua_newtable(L); + for (int i = 0; types[i].name[0]; i++) { + lua_pushstring(L, types[i].name); + lua_pushinteger(L, types[i].value); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + /* SEND_FLAGS */ + lua_pushliteral(L, "SEND_FLAGS"); + lua_newtable(L); + for (int i = 0; send_flags[i].name[0]; i++) { + lua_pushstring(L, send_flags[i].name); + lua_pushinteger(L, send_flags[i].value); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + /* AI_FLAGS */ + lua_pushliteral(L, "AI_FLAGS"); + lua_newtable(L); + for (int i = 0; ai_flags[i].name[0]; i++) { + lua_pushstring(L, ai_flags[i].name); + lua_pushinteger(L, ai_flags[i].value); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + + /* SO_OPT */ + lua_pushliteral(L, "SO_OPT"); + lua_newtable(L); + for (int i = 0; so_opts[i].name[0]; i++) { + lua_pushstring(L, so_opts[i].name); + lua_newtable(L); + + lua_pushliteral(L, "iname"); + lua_pushinteger(L, so_opts[i].value); + lua_rawset(L, -3); + + lua_pushliteral(L, "type"); + lua_pushinteger(L, so_opts[i].type); + lua_rawset(L, -3); + + lua_pushliteral(L, "rw"); + lua_pushboolean(L, so_opts[i].rw); + lua_rawset(L, -3); + + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + /* constants */ + lua_pushliteral(L, "SOL_SOCKET"); + lua_pushinteger(L, SOL_SOCKET); + lua_rawset(L, -3); + + /* constants */ + lua_pushliteral(L, "INT_SIZE"); + lua_pushinteger(L, sizeof(int)); + lua_rawset(L, -3); + lua_pushliteral(L, "SIZE_T_SIZE"); + lua_pushinteger(L, sizeof(size_t)); + lua_rawset(L, -3); + + + lua_settop(L, top); +} + diff --git a/src/bsdsocket.h b/src/bsdsocket.h new file mode 100644 index 0000000000000000000000000000000000000000..05b822a4072c3d232c160790c873fb1453c6cf39 --- /dev/null +++ b/src/bsdsocket.h @@ -0,0 +1,53 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef TARANTOOL_BOX_RAWSOCKET_H_INCLUDED +#define TARANTOOL_BOX_RAWSOCKET_H_INCLUDED + +#include <stddef.h> + +struct lua_State; +void tarantool_lua_bsdsocket_init(struct lua_State *L); + +#if defined(__cplusplus) +extern "C" { +#endif + +int bsdsocket_protocol(const char *proto); +int bsdsocket_sysconnect(int fh, const char *host, const char *port); +int bsdsocket_bind(int fh, const char *host, const char *port); +int bsdsocket_nonblock(int fh, int mode); +int bsdsocket_sendto(int fh, const char *host, const char *port, + const void *octets, size_t len, int flags); + +#if defined(__cplusplus) +} +#endif + +#endif /* TARANTOOL_BOX_RAWSOCKET_H_INCLUDED */ diff --git a/src/ffisyms.cc b/src/ffisyms.cc index 4bf53e005c691d653e28807f610db28258aa01b1..e6a7332473688900f5a4d06dde97105eecb5fd7f 100644 --- a/src/ffisyms.cc +++ b/src/ffisyms.cc @@ -1,6 +1,11 @@ +#include "bsdsocket.h" /* * A special hack to cc/ld to keep symbols in an optimized binary. * Please add your symbols to this array if you plan to use it from LuaJIT FFI. */ void *ffi_symbols[] = { + (void *)bsdsocket_protocol, + (void *)bsdsocket_sysconnect, + (void *)bsdsocket_bind, + (void *)bsdsocket_nonblock }; diff --git a/src/lua/bsdsocket.lua b/src/lua/bsdsocket.lua new file mode 100644 index 0000000000000000000000000000000000000000..cc257a138f0e99aae940d5aa2f75b441b1c474bd --- /dev/null +++ b/src/lua/bsdsocket.lua @@ -0,0 +1,847 @@ +-- bsdsocket.lua (internal file) + +do + +local TIMEOUT_INFINITY = 500 * 365 * 86400 + +local ffi = require 'ffi' + +ffi.cdef[[ + int write(int fh, const char *octets, size_t len); + int read(int fh, char *buf, size_t len); + int listen(int fh, int backlog); + int socket(int domain, int type, int protocol); + int close(int s); + int shutdown(int s, int how); + int send(int s, const void *msg, size_t len, int flags); + int recv(int s, void *buf, size_t len, int flags); + int accept(int s, void *addr, void *addrlen); + + int bsdsocket_protocol(const char *proto); + int bsdsocket_sysconnect(int fh, const char *host, const char *port); + int bsdsocket_bind(int fh, const char *host, const char *port); + int bsdsocket_nonblock(int fh, int mode); + int bsdsocket_sendto(int fh, const char *host, const char *port, + const void *octets, size_t len, int flags); + + + int setsockopt(int s, int level, int iname, const void *opt, size_t optlen); + int getsockopt(int s, int level, int iname, void *ptr, size_t *optlen); + + + typedef struct { int active; int timeout; } linger_t; +]] + +local function sprintf(fmt, ...) + return string.format(fmt, ...) +end + +local function printf(fmt, ...) + print(sprintf(fmt, ...)) +end + + +local function errno(self) + if self['_errno'] == nil then + return 0 + else + return self['_errno'] + end +end + +local function errstr(self) + if self['_errno'] == nil then + return nil + else + return box.errno.strerror(self._errno) + end +end + +local function get_ivalue(table, key) + if type(key) == 'number' then + return key + end + return table[key] +end + +local function get_iflags(table, flags) + if flags == nil then + return 0 + end + local res = 0 + if type(flags) ~= 'table' then + flags = { flags } + end + for i, f in pairs(flags) do + if table[f] == nil then + return nil + end + res = bit.bor(res, table[f]) + end + return res +end + +local socket_methods = {} +socket_methods.errno = errno +socket_methods.error = errstr + +socket_methods.sysconnect = function(self, host, port) + self._errno = nil + + host = tostring(host) + port = tostring(port) + + local res = ffi.C.bsdsocket_sysconnect(self.fh, host, port) + if res == 0 then + return true + end + + local errno = box.errno() + if errno == box.errno.EINPROGRESS then + return true + end + + self._errno = errno + return false +end + +socket_methods.syswrite = function(self, octets) + self._errno = nil + local done = ffi.C.write(self.fh, octets, string.len(octets)) + if done < 0 then + self._errno = box.errno() + return nil + end + return done +end + +socket_methods.sysread = function(self, len) + self._errno = nil + local buf = ffi.new('char[?]', len) + local res = ffi.C.read(self.fh, buf, len) + + if res < 0 then + self._errno = box.errno() + return nil + end + + buf = ffi.string(buf, res) + return buf +end + +socket_methods.nonblock = function(self, nb) + self._errno = nil + + local res + + if nb == nil then + res = ffi.C.bsdsocket_nonblock(self.fh, 0x80) + elseif nb then + res = ffi.C.bsdsocket_nonblock(self.fh, 1) + else + res = ffi.C.bsdsocket_nonblock(self.fh, 0) + end + + if res < 0 then + self._errno = box.errno() + return nil + end + + if res == 1 then + return true + else + return false + end +end + +socket_methods.readable = function(self, timeout) + self._errno = nil + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + local wres = box.socket.internal.iowait(self.fh, 0, timeout) + + if wres == 0 then + self._errno = box.errno.ETIMEDOUT + return false + end + return true +end + +socket_methods.wait = function(self, timeout) + self._errno = nil + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + local wres = box.socket.internal.iowait(self.fh, 2, timeout) + + if wres == 0 then + self._errno = box.errno.ETIMEDOUT + return + end + + local res = '' + if bit.band(wres, 1) ~= 0 then + res = res .. 'R' + end + if bit.band(wres, 2) ~= 0 then + res = res .. 'W' + end + return res +end + +socket_methods.writable = function(self, timeout) + self._errno = nil + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + local wres = box.socket.internal.iowait(self.fh, 1, timeout) + + if wres == 0 then + self._errno = box.errno.ETIMEDOUT + return false + end + return true +end + +socket_methods.listen = function(self, backlog) + self._errno = nil + if backlog == nil then + backlog = 256 + end + local res = ffi.C.listen(self.fh, backlog) + if res < 0 then + self._errno = box.errno() + return false + end + return true +end + +socket_methods.bind = function(self, host, port) + self._errno = nil + + host = tostring(host) + port = tostring(port) + + local res = ffi.C.bsdsocket_bind(self.fh, host, port) + if res == 0 then + return true + end + self._errno = box.errno() + return false +end + +socket_methods.close = function(self) + self._errno = nil + if ffi.C.close(self.fh) < 0 then + self._errno = box.errno() + return false + else + return true + end +end + + +socket_methods.shutdown = function(self, how) + local hvariants = { + ['R'] = 0, + ['READ'] = 0, + ['W'] = 1, + ['WRITE'] = 1, + ['RW'] = 2, + ['READ_WRITE'] = 2, + + [0] = 0, + [1] = 1, + [2] = 2 + } + local ihow = hvariants[how] + + if ihow == nil then + ihow = 3 + end + self._errno = nil + if ffi.C.shutdown(self.fh, ihow) < 0 then + self._errno = box.errno() + return false + end + return true +end + +socket_methods.setsockopt = function(self, level, name, value) + local info = get_ivalue(box.socket.internal.SO_OPT, name) + + if info == nil then + error(sprintf("Unknown socket option name: %s", tostring(name))) + end + + if not info.rw then + error(sprintf("Socket option %s is read only", name)) + end + + self._errno = nil + + if type(level) == 'string' then + if level == 'SOL_SOCKET' then + level = box.socket.internal.SOL_SOCKET + else + level = ffi.C.bsdsocket_protocol(level) + if level == -1 then + self._errno = box.errno() + return false + end + end + else + level = tonumber(level) + end + + if type(value) == 'boolean' then + if value then + value = 1 + else + value = 0 + end + end + + if info.type == 1 then + local value = ffi.new("int[1]", value) + local res = ffi.C.setsockopt(self.fh, + level, info.iname, value, box.socket.internal.INT_SIZE) + + if res < 0 then + self._errno = box.errno() + return false + end + return true + end + + if info.type == 2 then + local res = ffi.C.setsockopt(self.fh, + level, info.iname, value, box.socket.internal.SIZE_T_SIZE) + if res < 0 then + self._errno = box.errno() + return false + end + return true + end + + if name == 'SO_LINGER' then + error("Use s:linger(active[, timeout])") + end + error(sprintf("Unsupported socket option: %s", name)) +end + +socket_methods.getsockopt = function(self, level, name) + local info = get_ivalue(box.socket.internal.SO_OPT, name) + + if info == nil then + error(sprintf("Unknown socket option name: %s", tostring(name))) + end + + self._errno = nil + + if type(level) == 'string' then + if level == 'SOL_SOCKET' then + level = box.socket.internal.SOL_SOCKET + else + level = ffi.C.bsdsocket_protocol(level) + if level == -1 then + self._errno = box.errno() + return nil + end + end + else + level = tonumber(level) + end + + + if info.type == 1 then + local value = ffi.new("int[1]", 0) + local len = ffi.new("size_t[1]", box.socket.internal.INT_SIZE) + local res = ffi.C.getsockopt(self.fh, level, info.iname, value, len) + + if res < 0 then + self._errno = box.errno() + return nil + end + + if len[0] ~= 4 then + error(sprintf("Internal error: unexpected optlen: %d", len[0])) + end + return value[0] + end + + if info.type == 2 then + local value = ffi.new("char[256]", { 0 }) + local len = ffi.new("size_t[1]", 256) + local res = ffi.C.getsockopt(self.fh, level, info.iname, value, len) + if res < 0 then + self._errno = box.errno() + return nil + end + return ffi.string(value, len[0]) + end + + if name == 'SO_LINGER' then + error("Use s:linger()") + end + error(sprintf("Unsupported socket option: %s", name)) +end + +socket_methods.linger = function(self, active, timeout) + + local info = box.socket.internal.SO_OPT.SO_LINGER + self._errno = nil + if active == nil then + local value = ffi.new("linger_t[1]") + local len = ffi.new("size_t[1]", 2 * box.socket.internal.INT_SIZE) + local res = ffi.C.getsockopt(self.fh, + box.socket.internal.SOL_SOCKET, info.iname, value, len) + if res < 0 then + self._errno = box.errno() + return nil + end + if value[0].active ~= 0 then + active = true + else + active = false + end + return active, value[0].timeout + end + + if timeout == nil then + timeout = 0 + end + + local iactive + if active then + iactive = 1 + else + iactive = 0 + end + + local value = ffi.new("linger_t[1]", + { { active = iactive, timeout = timeout } }) + local len = 2 * box.socket.internal.INT_SIZE + local res = ffi.C.setsockopt(self.fh, + box.socket.internal.SOL_SOCKET, info.iname, value, len) + if res < 0 then + self._errno = box.errno() + return nil + end + + return active, timeout +end + + +socket_methods.accept = function(self) + + self._errno = nil + + local fh = ffi.C.accept(self.fh, nil, nil) + + if fh < 1 then + self._errno = box.errno() + return nil + end + + local socket = { fh = fh } + setmetatable(socket, box.socket.internal.socket_mt) + return socket +end + + +socket_methods.read = function(self, size, timeout) + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + if self.rbuf == nil then + self.rbuf = '' + end + + if size == nil then + size = 4294967295 + end + + if string.len(self.rbuf) >= size then + self._errno = nil + local data = string.sub(self.rbuf, 1, size) + self.rbuf = string.sub(self.rbuf, size + 1) + return data + end + + while timeout > 0 do + local started = box.time() + + if not self:readable(timeout) then + self._errno = box.errno() + return nil + end + + timeout = timeout - ( box.time() - started ) + + local data = self:sysread(4096) + if data ~= nil then + self.rbuf = self.rbuf .. data + if string.len(self.rbuf) >= size then + data = string.sub(self.rbuf, 1, size) + self.rbuf = string.sub(self.rbuf, size + 1) + return data + end + + if string.len(data) == 0 then -- eof + data = self.rbuf + self.rbuf = nil + return data + end + elseif self:errno() ~= box.errno.EAGAIN then + self._errno = box.errno() + return nil + end + end + self._errno = box.errno.ETIMEDOUT + return nil +end + + +local function readline_check(self, eols, limit) + local rbuf = self.rbuf + if rbuf == nil then + return nil + end + if string.len(rbuf) == 0 then + return nil + end + for i, eol in pairs(eols) do + if string.len(rbuf) >= string.len(eol) then + local data = string.match(rbuf, "^(.*" .. eol .. ")") + if data ~= nil then + if string.len(data) > limit then + return string.sub(data, 1, limit) + end + return data + end + end + end + return nil +end + +socket_methods.readline = function(self, limit, eol, timeout) + + if type(limit) == 'table' then -- :readline({eol}[, timeout ]) + if eol ~= nil then + timeout = eol + eol = limit + limit = nil + else + eol = limit + limit = nil + end + elseif eol ~= nil then + if type(eol) ~= 'table' then + eol = { eol } + end + end + + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + if self.rbuf == nil then + self.rbuf = '' + end + + if limit == nil then + limit = 4294967295 + end + + self._errno = nil + local data = readline_check(self, eol, limit) + if data ~= nil then + self.rbuf = string.sub(self.rbuf, string.len(data) + 1) + return data + end + + local started = box.time() + while timeout > 0 do + local started = box.time() + + if not self:readable(timeout) then + self._errno = box.errno() + return nil + end + + timeout = timeout - ( box.time() - started ) + + local data = self:sysread(4096) + if data ~= nil then + self.rbuf = self.rbuf .. data + if string.len(self.rbuf) >= limit then + data = string.sub(self.rbuf, 1, limit) + self.rbuf = string.sub(self.rbuf, limit + 1) + return data + end + + if string.len(data) == 0 then + data = self.rbuf + self.rbuf = nil + return data + end + + data = readline_check(self, eol, limit) + if data ~= nil then + self.rbuf = string.sub(self.rbuf, string.len(data) + 1) + return data + end + elseif self:errno() ~= box.errno.EAGAIN then + self._errno = box.errno() + return nil + end + end +end + +socket_methods.write = function(self, octets, timeout) + if timeout == nil then + timeout = TIMEOUT_INFINITY + end + + local started = box.time() + while timeout > 0 and self:writable(timeout) do + timeout = timeout - ( box.time() - started ) + + local written = self:syswrite(octets) + if written == nil then + if self:errno() ~= box.errno.EAGAIN then + return false + end + end + + if written == string.len(octets) then + return true + end + if written > 0 then + octets = string.sub(octets, written + 1) + end + end +end + +socket_methods.send = function(self, octets, flags) + local iflags = get_iflags(box.socket.internal.SEND_FLAGS, flags) + + self._errno = nil + local res = ffi.C.send(self.fh, octets, string.len(octets), iflags) + if res == -1 then + self._errno = box.errno() + return false + end + return true +end + +socket_methods.recv = function(self, size, flags) + local iflags = get_iflags(box.socket.internal.SEND_FLAGS, flags) + if iflags == nil then + self._errno = box.errno.EINVAL + return nil + end + + if size == nil then + size = 512 + end + self._errno = nil + local buf = ffi.new("char[?]", size) + + local res = ffi.C.recv(self.fh, buf, size, iflags) + + if res == -1 then + self._errno = box.errno() + return nil + end + return ffi.string(buf, res) +end + + +socket_methods.recvfrom = function(self, size, flags) + local iflags = get_iflags(box.socket.internal.SEND_FLAGS, flags) + if iflags == nil then + self._errno = box.errno.EINVAL + return nil + end + self._errno = nil + local res, from = box.socket.internal.recvfrom(self.fh, size, iflags) + if res == nil then + self._errno = box.errno() + return nil + end + return res, from +end + +socket_methods.sendto = function(self, host, port, octets, flags) + local iflags = get_iflags(box.socket.internal.SEND_FLAGS, flags) + + if iflags == nil then + self._errno = box.errno.EINVAL + return nil + end + + self._errno = nil + if octets == nil or octets == '' then + return true + end + + host = tostring(host) + port = tostring(port) + octets = tostring(octets) + + local res = ffi.C.bsdsocket_sendto(self.fh, host, port, + octets, string.len(octets), iflags) + + if res < 0 then + self._errno = box.errno() + return false + end + return true +end + + +local function create_socket(self, domain, stype, proto) + + self._errno = nil + + local idomain = get_ivalue(box.socket.internal.DOMAIN, domain) + if idomain == nil then + self._errno = box.errno.EINVAL + return nil + end + + local itype = get_ivalue(box.socket.internal.SO_TYPE, stype) + if itype == nil then + self._errno = box.errno.EINVAL + return nil + end + + local iproto = ffi.C.bsdsocket_protocol(proto) + if iproto == -1 then + self._errno = box.errno() + return nil + end + + + local fh = ffi.C.socket(idomain, itype, iproto) + if fh < 0 then + self._errno = box.errno() + return nil + end + + local socket = { fh = fh } + setmetatable(socket, box.socket.internal.socket_mt) + return socket +end + + +local function getaddrinfo(host, port, timeout, opts) + local self = box.socket + + if type(timeout) == 'table' and opts == nil then + opts = timeout + timeout = TIMEOUT_INFINITY + elseif timeout == nil then + timeout = TIMEOUT_INFINITY + end + if port == nil then + port = 0 + end + local ga_opts = {} + if opts ~= nil then + if opts.type ~= nil then + local itype = get_ivalue(box.socket.internal.SO_TYPE, opts.type) + if itype == nil then + self._errno = box.errno.EINVAL + return nil + end + ga_opts.type = itype + end + + if opts.family ~= nil then + local ifamily = get_ivalue(box.socket.internal.DOMAIN, opts.family) + if ifamily == nil then + self._errno = box.errno.EINVAL + return nil + end + ga_opts.family = ifamily + end + + if opts.protocol ~= nil then + local iproto = ffi.C.bsdsocket_protocol(opts.protocol) + if iproto == -1 then + self._errno = box.errno() + return nil + end + ga_opts.protocol = iproto + end + + if opts.flags ~= nil then + ga_opts.flags = + get_iflags(box.socket.internal.AI_FLAGS, opts.flags) + if ga_opts.flags == nil then + self._errno = box.errno() + return nil + end + end + + end + + local r = box.socket.internal.getaddrinfo(host, port, timeout, ga_opts) + if r == nil then + self._errno = box.errno() + else + self._errno = nil + end + return r +end + +socket_methods.name = function(self) + local aka = box.socket.internal.name(self.fh) + if aka == nil then + self._errno = box.errno() + return nil + end + return aka +end + +if type(box.socket) == nil then + box.socket = {} +end + +box.socket.internal = { + socket_mt = { + __index = socket_methods, + __tostring = function(self) + local name = sprintf("fd %d", self.fh) + local aka = box.socket.internal.name(self.fh) + if aka ~= nil then + name = sprintf("%s, aka %s:%s", name, aka.host, aka.port) + end + local peer = box.socket.internal.peer(self.fh) + if peer ~= nil then + name = sprintf("%s, peer %s:%s", name, peer.host, peer.port) + end + return name + end + }, + +} + +setmetatable(box.socket, { + __call = create_socket, + __index = { + errno = errno, + error = errstr, + getaddrinfo = getaddrinfo, + } +}) + +end diff --git a/src/lua/errno.c b/src/lua/errno.c new file mode 100644 index 0000000000000000000000000000000000000000..fe60812f5f684ebae011763f10ed7be70ef06ec0 --- /dev/null +++ b/src/lua/errno.c @@ -0,0 +1,155 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "errno.h" +#include <errno.h> +#include <lua.h> +#include <lauxlib.h> +#include <lualib.h> +#include <say.h> + +extern char errno_lua[]; + + +void +tarantool_lua_errno_init(struct lua_State *L) +{ + static const struct { char name[32]; int value; } elist[] = { + { "E2BIG", E2BIG }, + { "EACCES", EACCES }, + { "EADDRINUSE", EADDRINUSE }, + { "EADDRNOTAVAIL", EADDRNOTAVAIL }, + { "EAFNOSUPPORT", EAFNOSUPPORT }, + { "EAGAIN", EAGAIN }, + { "EALREADY", EALREADY }, + { "EBADF", EBADF }, + { "EBADMSG", EBADMSG }, + { "EBUSY", EBUSY }, + { "ECANCELED", ECANCELED }, + { "ECHILD", ECHILD }, + { "ECONNABORTED", ECONNABORTED }, + { "ECONNREFUSED", ECONNREFUSED }, + { "ECONNRESET", ECONNRESET }, + { "EDEADLK", EDEADLK }, + { "EDESTADDRREQ", EDESTADDRREQ }, + { "EDOM", EDOM }, + { "EDQUOT", EDQUOT }, + { "EEXIST", EEXIST }, + { "EFAULT", EFAULT }, + { "EFBIG", EFBIG }, + { "EHOSTUNREACH", EHOSTUNREACH }, + { "EIDRM", EIDRM }, + { "EILSEQ", EILSEQ }, + { "EINPROGRESS", EINPROGRESS }, + { "EINTR", EINTR }, + { "EINVAL", EINVAL }, + { "EIO", EIO }, + { "EISCONN", EISCONN }, + { "EISDIR", EISDIR }, + { "ELOOP", ELOOP }, + { "EMFILE", EMFILE }, + { "EMLINK", EMLINK }, + { "EMSGSIZE", EMSGSIZE }, + { "EMULTIHOP", EMULTIHOP }, + { "ENAMETOOLONG", ENAMETOOLONG }, + { "ENETDOWN", ENETDOWN }, + { "ENETRESET", ENETRESET }, + { "ENETUNREACH", ENETUNREACH }, + { "ENFILE", ENFILE }, + { "ENOBUFS", ENOBUFS }, + { "ENODATA", ENODATA }, + { "ENODEV", ENODEV }, + { "ENOENT", ENOENT }, + { "ENOEXEC", ENOEXEC }, + { "ENOLCK", ENOLCK }, + { "ENOLINK", ENOLINK }, + { "ENOMEM", ENOMEM }, + { "ENOMSG", ENOMSG }, + { "ENOPROTOOPT", ENOPROTOOPT }, + { "ENOSPC", ENOSPC }, + { "ENOSR", ENOSR }, + { "ENOSTR", ENOSTR }, + { "ENOSYS", ENOSYS }, + { "ENOTCONN", ENOTCONN }, + { "ENOTDIR", ENOTDIR }, + { "ENOTEMPTY", ENOTEMPTY }, + { "ENOTSOCK", ENOTSOCK }, + { "ENOTSUP", ENOTSUP }, + { "ENOTTY", ENOTTY }, + { "ENXIO", ENXIO }, + { "EOPNOTSUPP", EOPNOTSUPP }, + { "EOVERFLOW", EOVERFLOW }, + { "EPERM", EPERM }, + { "EPIPE", EPIPE }, + { "EPROTO", EPROTO }, + { "EPROTONOSUPPORT", EPROTONOSUPPORT }, + { "EPROTOTYPE", EPROTOTYPE }, + { "ERANGE", ERANGE }, + { "EROFS", EROFS }, + { "ESPIPE", ESPIPE }, + { "ESRCH", ESRCH }, + { "ESTALE", ESTALE }, + { "ETIME", ETIME }, + { "ETIMEDOUT", ETIMEDOUT }, + { "ETXTBSY", ETXTBSY }, + { "EWOULDBLOCK", EWOULDBLOCK }, + { "EXDEV", EXDEV }, + { "", 0 } + }; + + int top = lua_gettop(L); + lua_getfield(L, LUA_GLOBALSINDEX, "box"); + lua_pushliteral(L, "errno"); + lua_newtable(L); + for (int i = 0; elist[i].name[0]; i++) { + lua_pushstring(L, elist[i].name); + lua_pushinteger(L, elist[i].value); + lua_rawset(L, -3); + } + lua_rawset(L, -3); + + if (luaL_dostring(L, errno_lua)) + panic("Error loading Lua source (internal)/errno.lua: %s", + lua_tostring(L, -1)); + + lua_settop(L, top); +} + +int +errno_get(void) +{ + return errno; +} + +int +errno_set(int new_errno) +{ + errno = new_errno; + return errno; +} diff --git a/src/lua/errno.h b/src/lua/errno.h new file mode 100644 index 0000000000000000000000000000000000000000..1bfade6e0f32a7aa317250de011ba00c55cb9ea8 --- /dev/null +++ b/src/lua/errno.h @@ -0,0 +1,44 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef TARANTOOL_LUA_ERRNO_H_INCLUDED +#define TARANTOOL_LUA_ERRNO_H_INCLUDED + +#ifdef __cplusplus +extern "C" { +#endif + + +struct lua_State; +void tarantool_lua_errno_init(struct lua_State *L); + +#ifdef __cplusplus +} +#endif +#endif /* TARANTOOL_LUA_ERRNO_H_INCLUDED */ + diff --git a/src/lua/errno.lua b/src/lua/errno.lua new file mode 100644 index 0000000000000000000000000000000000000000..c12da271c07e112296b0d5c6c206fdae0eaffc20 --- /dev/null +++ b/src/lua/errno.lua @@ -0,0 +1,28 @@ +-- errno.lua (internal file) + +do + +local ffi = require 'ffi' + +ffi.cdef[[ + char *strerror(int errnum); + int errno_get(); + int errno_set(int new_errno); +]] + +box.errno.strerror = function(errno) + return ffi.string(ffi.C.strerror(tonumber(errno))) +end + +setmetatable(box.errno, { + __newindex = function() error("Can't create new errno constants") end, + __call = function(self, new_errno) + local res + if new_errno then + return ffi.C.errno_set(new_errno) + end + return ffi.C.errno_get() + end +}) + +end diff --git a/src/lua/init.cc b/src/lua/init.cc index f50877eaf63d02f86889ca5d86044a5c4e896119..aacb9de91ad2e2db42442ce36691ff90fccbb5fd 100644 --- a/src/lua/init.cc +++ b/src/lua/init.cc @@ -61,6 +61,8 @@ extern "C" { #include <dirent.h> #include <stdio.h> #include "scoped_guard.h" +#include "errno.h" +#include "../bsdsocket.h" extern "C" { #include <cfg/tarantool_box_cfg.h> @@ -76,8 +78,11 @@ struct lua_State *tarantool_L; /* contents of src/lua/ files */ extern char uuid_lua[]; extern char session_lua[]; -extern char digest_lua[]; -static const char *lua_sources[] = { uuid_lua, digest_lua, session_lua, NULL }; +static const char *lua_sources[] = { + uuid_lua, + session_lua, + NULL +}; /** * Remember the output of the administrative console in the @@ -1230,12 +1235,14 @@ tarantool_lua_init() lua_register(L, "pcall", lbox_pcall); lua_register(L, "tonumber64", lbox_tonumber64); + tarantool_lua_errno_init(L); tarantool_lua_cjson_init(L); tarantool_lua_info_init(L); tarantool_lua_slab_init(L); tarantool_lua_stat_init(L); tarantool_lua_ipc_init(L); tarantool_lua_socket_init(L); + tarantool_lua_bsdsocket_init(L); tarantool_lua_session_init(L); tarantool_lua_error_init(L); @@ -1246,6 +1253,7 @@ tarantool_lua_init() *s, lua_tostring(L, -1)); } + mod_lua_init(L); /* clear possible left-overs of init */ diff --git a/test/box/bsdsocket.result b/test/box/bsdsocket.result new file mode 100644 index 0000000000000000000000000000000000000000..8eebd881aecd5f6b3287b2d3371a8e05a70895a4 --- /dev/null +++ b/test/box/bsdsocket.result @@ -0,0 +1,719 @@ +bsdsocket tests +lua type(box.socket) +--- + - table +... +lua box.socket:errno() +--- + - 0 +... +lua type(box.socket:error()) +--- + - nil +... +lua box.socket('PF_INET', 'SOCK_STREAM', 'tcp121222') +--- + - nil +... +lua box.socket:errno() ~= 0 +--- + - true +... +lua type(box.socket:error()) +--- + - string +... +lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua s:wait(.01) +--- + - RW +... +lua type(s) +--- + - table +... +lua s +--- + - fd 10, aka 0.0.0.0:0 +... +lua s:errno() +--- + - 0 +... +lua type(s:error()) +--- + - nil +... +lua s:sysconnect('127.0.0.1', box.cfg.primary_port) +--- + - true +... +lua s:nonblock(true) +--- + - true +... +lua s:nonblock() +--- + - true +... +lua s:nonblock(false) +--- + - false +... +lua s:nonblock() +--- + - false +... +lua s:nonblock(true) +--- + - true +... +lua s:readable(.01) +--- + - false +... +lua s:wait(.01) +--- + - W +... +lua s:readable(0) +--- + - false +... +lua s:errno() > 0 +--- + - true +... +lua s:error() +--- + - Connection timed out +... +lua s:writable(.00000000000001) +--- + - true +... +lua s:writable(0) +--- + - true +... +lua s:wait(.01) +--- + - W +... +lua s:syswrite(box.pack('iii', 65280, 0, 12334)) +--- + - 12 +... +lua s:readable(1) +--- + - true +... +lua s:wait(.01) +--- + - RW +... +lua box.unpack('iii', s:sysread(4096)) +--- + - 65280 + - 0 + - 12334 +... +lua s:syswrite(box.pack('iii', 65280, 0, 12335)) +--- + - 12 +... +lua s:readable(1) +--- + - true +... +lua string.len(s:sysread(4096)) +--- + - 12 +... +lua s:close() +--- + - true +... +lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) +--- + - true +... +lua s:error() +--- + - nil +... +lua s:nonblock(true) +--- + - true +... +lua s:bind('127.0.0.1', 3457) +--- + - true +... +lua s:error() +--- + - nil +... +lua s:listen(128) +--- + - true +... +lua sevres = {} +--- +... +lua type(box.fiber.wrap(function() s:readable() do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end)) +--- + - userdata +... +lua #sevres +--- + - 0 +... +lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua sc:sysconnect('127.0.0.1', 3457) +--- + - true +... +lua sc:nonblock(true) +--- + - true +... +lua sc:readable(.5) +--- + - true +... +lua sc:sysread(4096) +--- + - ok +... +lua string.match(tostring(sc), ', peer') ~= nil +--- + - true +... +lua #sevres +--- + - 1 +... +lua sevres[1].host +--- + - nil +... +lua s:setsockopt('SOL_SOCKET', 'SO_BROADCAST', false) +--- + - true +... +lua s:getsockopt('SOL_SOCKET', 'SO_TYPE') +--- + - 1 +... +lua s:error() +--- + - nil +... +lua s:setsockopt('SOL_SOCKET', 'SO_BSDCOMPAT', false) +--- + - true +... +lua s:setsockopt('SOL_SOCKET', 'SO_DEBUG', false) +--- + - true +... +lua s:getsockopt('SOL_SOCKET', 'SO_DEBUG') +--- + - 0 +... +lua s:setsockopt('SOL_SOCKET', 'SO_ACCEPTCONN', 1) +--- +error: '[string "-- bsdsocket.lua (internal file)..."]:282: Socket option SO_ACCEPTCONN is read only' +... +lua s:getsockopt('SOL_SOCKET', 'SO_RCVBUF') > 32 +--- + - true +... +lua s:error() +--- + - nil +... +lua s:linger() +--- + - false + - 0 +... +lua s:linger(true, 1) +--- + - true + - 1 +... +lua s:linger() +--- + - true + - 1 +... +lua s:linger(false, 1) +--- + - false + - 1 +... +lua s:linger() +--- + - false + - 1 +... +lua s:shutdown('R') +--- + - true +... +lua s:close() +--- + - true +... +lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua s:nonblock(true) +--- + - true +... +lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) +--- + - true +... +lua s:bind('127.0.0.1', 3457) +--- + - true +... +lua s:listen(128) +--- + - true +... +lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua sc:nonblock(true) +--- + - true +... +lua sc:writable() +--- + - true +... +lua sc:readable() +--- + - true +... +lua sc:sysconnect('127.0.0.1', 3457) +--- + - true +... +lua sc:writable(10) +--- + - true +... +lua sc:write('Hello, world') +--- + - true +... +lua sa = s:accept() +--- +... +lua sa:nonblock(1) +--- + - true +... +lua sa:read(8) +--- + - Hello, w +... +lua sa:read(3) +--- + - orl +... +lua sc:writable() +--- + - true +... +lua sc:write(', again') +--- + - true +... +lua sa:read(8) +--- + - d, again +... +lua sa:error() +--- + - nil +... +lua string.len(sa:read(0)) +--- + - 0 +... +lua type(sa:read(0)) +--- + - string +... +lua sa:read(1, .01) +--- + - nil +... +lua sc:writable() +--- + - true +... +lua sc:send('abc') +--- + - true +... +lua sa:read(3) +--- + - abc +... +lua sc:send('Hello') +--- + - true +... +lua sa:readable() +--- + - true +... +lua sa:recv() +--- + - Hello +... +lua sa:recv() +--- + - nil +... +lua sc:send('Hello') +--- + - true +... +lua sc:send(', world') +--- + - true +... +lua sc:send("\nnew line") +--- + - true +... +lua sa:readline({'\n'}, 1) +--- + - Hello, world + +... +lua sa:readline(1, {'ine'}, 1) +--- + - n +... +lua sa:readline({'ine'}, 1) +--- + - ew line +... +lua sa:readline({'ine'}, 0.1) +--- + - nil +... +lua sc:send('Hello, world') +--- + - true +... +lua sa:readline({','}, 1) +--- + - Hello, +... +lua sc:shutdown('W') +--- + - true +... +lua sa:read(100, 1) +--- + - world +... +lua sa:read(100, 1) +--- + - +... +lua sa:close() +--- + - true +... +lua sc:close() +--- + - true +... +lua s = box.socket('PF_UNIX', 'SOCK_STREAM', 'ip') +--- +... +lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true) +--- + - true +... +lua s ~= nil +--- + - true +... +lua s:nonblock() +--- + - false +... +lua s:nonblock(true) +--- + - true +... +lua s:nonblock() +--- + - true +... +lua s:bind('unix/', '/tmp/tarantool-test-socket') +--- + - true +... +lua tostring(sc) +--- + - fd 12, aka unix/:/tmp/tarantool-test-socket +... +lua s:listen(1234) +--- + - true +... +lua sc = box.socket('PF_UNIX', 'SOCK_STREAM', 'ip') +--- +... +lua sc:nonblock(true) +--- + - true +... +lua sc:sysconnect('unix/', '/tmp/tarantool-test-socket') +--- + - true +... +lua sc:error() +--- + - nil +... +lua s:readable() +--- + - true +... +lua sa = s:accept() +--- +... +lua sa:nonblock(true) +--- + - true +... +lua sa:send('Hello, world') +--- + - true +... +lua sc:recv() +--- + - Hello, world +... +lua sc:close() +--- + - true +... +lua sa:close() +--- + - true +... +lua s:close() +--- + - true +... +lua function aexitst(ai, host, port) for i, a in pairs(ai) do if a.host == host and a.port == port then return true end end return false end +--- +... +lua aexitst( box.socket.getaddrinfo('localhost', 'http', { protocol = 'tcp', type = 'SOCK_STREAM'}), '127.0.0.1', 80 ) +--- + - true +... +lua #(box.socket.getaddrinfo('mail.ru', 'http', {})) > 0 +--- + - true +... +lua #(box.socket.getaddrinfo('mail12211alklkl.ru', 'http', {})) == 0 +--- + - true +... +lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua tostring(sc) +--- + - fd 12, aka 0.0.0.0:0 +... +lua sc:getsockopt('SOL_SOCKET', 'SO_ERROR') +--- + - 0 +... +lua sc:nonblock(true) +--- + - true +... +lua sc:readable() +--- + - true +... +lua sc:sysconnect('127.0.0.1', 3458) +--- + - true +... +lua string.match(tostring(sc), ', peer') == nil +--- + - true +... +lua sc:writable() +--- + - true +... +lua string.match(tostring(sc), ', peer') == nil +--- + - true +... +lua box.errno.strerror(sc:getsockopt('SOL_SOCKET', 'SO_ERROR')) +--- + - Connection refused +... +lua box.cjson.encode(box.socket.getaddrinfo('ya.ru', '80', { flags = { 'AI_NUMERICSERV', 'AI_NUMERICHOST', } })) +--- + - {} +... +lua sc = box.socket('AF_INET', 'SOCK_STREAM', 'tcp') +--- +... +lua box.cjson.encode(sc:name()) +--- + - {"host":"0.0.0.0","family":"AF_INET","type":"SOCK_STREAM","protocol":"tcp","port":0} +... +lua sc:nonblock(true) +--- + - true +... +lua sc:close() +--- + - true +... +lua s = box.socket('AF_INET', 'SOCK_DGRAM', 'udp') +--- +... +lua s:bind('127.0.0.1', 3548) +--- + - true +... +lua sc = box.socket('AF_INET', 'SOCK_DGRAM', 'udp') +--- +... +lua sc:sendto('127.0.0.1', 3548, 'Hello, world') +--- + - true +... +lua s:readable(10) +--- + - true +... +lua s:recv(4096) +--- + - Hello, world +... +lua sc:sendto('127.0.0.1', 3548, 'Hello, world, 2') +--- + - true +... +lua s:readable(10) +--- + - true +... +lua local d, from = s:recvfrom(4096) print(' - ', from.port > 0) from.port = 'Random port' return box.cjson.encode{d, from} +--- + - true + - ["Hello, world, 2",{"host":"127.0.0.1","family":"AF_INET","port":"Random port"}] +... +lua s:close() +--- + - true +... +lua sc:close() +--- + - true +... +lua s = box.socket('AF_INET', 'SOCK_DGRAM', 'udp') +--- +... +lua s:nonblock(true) +--- + - true +... +lua s:bind('127.0.0.1') +--- + - true +... +lua s:name().port > 0 +--- + - true +... +lua sc = box.socket('AF_INET', 'SOCK_DGRAM', 'udp') +--- +... +lua sc:nonblock(true) +--- + - true +... +lua sc:sendto('127.0.0.1', s:name().port) +--- + - true +... +lua sc:sendto('127.0.0.1', s:name().port, 'Hello, World!') +--- + - true +... +lua s:readable(1) +--- + - true +... +lua data, from = s:recvfrom(10) +--- +... +lua data +--- + - Hello, Wor +... +lua s:sendto(from.host, from.port, 'Hello, hello!') +--- + - true +... +lua sc:readable(1) +--- + - true +... +lua data_r, from_r = sc:recvfrom(4096) +--- +... +lua data_r +--- + - Hello, hello! +... +lua from_r.host +--- + - 127.0.0.1 +... +lua from_r.port == s:name().port +--- + - true +... +lua s:close() +--- + - true +... +lua sc:close() +--- + - true +... diff --git a/test/box/bsdsocket.test b/test/box/bsdsocket.test new file mode 100644 index 0000000000000000000000000000000000000000..93287d3514550ffb9e904211b8a24d480c51c25c --- /dev/null +++ b/test/box/bsdsocket.test @@ -0,0 +1,245 @@ +# encoding: tarantool +# + +import os +import os.path + +print("bsdsocket tests") +exec admin "lua type(box.socket)" +exec admin "lua box.socket:errno()" +exec admin "lua type(box.socket:error())" +exec admin "lua box.socket('PF_INET', 'SOCK_STREAM', 'tcp121222')" +exec admin "lua box.socket:errno() ~= 0" +exec admin "lua type(box.socket:error())" + +exec admin "lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua s:wait(.01)" +exec admin "lua type(s)" +exec admin "lua s" +exec admin "lua s:errno()" +exec admin "lua type(s:error())" + + +exec admin "lua s:sysconnect('127.0.0.1', box.cfg.primary_port)" +exec admin "lua s:nonblock(true)" +exec admin "lua s:nonblock()" +exec admin "lua s:nonblock(false)" +exec admin "lua s:nonblock()" +exec admin "lua s:nonblock(true)" + +exec admin "lua s:readable(.01)" +exec admin "lua s:wait(.01)" +exec admin "lua s:readable(0)" +exec admin "lua s:errno() > 0" +exec admin "lua s:error()" +exec admin "lua s:writable(.00000000000001)" +exec admin "lua s:writable(0)" +exec admin "lua s:wait(.01)" + +exec admin "lua s:syswrite(box.pack('iii', 65280, 0, 12334))" +exec admin "lua s:readable(1)" +exec admin "lua s:wait(.01)" +exec admin "lua box.unpack('iii', s:sysread(4096))" + +exec admin "lua s:syswrite(box.pack('iii', 65280, 0, 12335))" +exec admin "lua s:readable(1)" +exec admin "lua string.len(s:sysread(4096))" +exec admin "lua s:close()" + + +exec admin "lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true)" +exec admin "lua s:error()" +exec admin "lua s:nonblock(true)" +exec admin "lua s:bind('127.0.0.1', 3457)" +exec admin "lua s:error()" +exec admin "lua s:listen(128)" +exec admin "lua sevres = {}" +exec admin "lua type(box.fiber.wrap(function() s:readable() do local sc = s:accept() table.insert(sevres, sc) sc:syswrite('ok') sc:close() end end))" +exec admin "lua #sevres" + + +exec admin "lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua sc:sysconnect('127.0.0.1', 3457)" +exec admin "lua sc:nonblock(true)" +exec admin "lua sc:readable(.5)" +exec admin "lua sc:sysread(4096)" +exec admin "lua string.match(tostring(sc), ', peer') ~= nil" +exec admin "lua #sevres" +exec admin "lua sevres[1].host" + +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_BROADCAST', false)" +exec admin "lua s:getsockopt('SOL_SOCKET', 'SO_TYPE')" +exec admin "lua s:error()" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_BSDCOMPAT', false)" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_DEBUG', false)" +exec admin "lua s:getsockopt('SOL_SOCKET', 'SO_DEBUG')" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_ACCEPTCONN', 1)" +exec admin "lua s:getsockopt('SOL_SOCKET', 'SO_RCVBUF') > 32" +exec admin "lua s:error()" + +exec admin "lua s:linger()" +exec admin "lua s:linger(true, 1)" +exec admin "lua s:linger()" +exec admin "lua s:linger(false, 1)" +exec admin "lua s:linger()" +exec admin "lua s:shutdown('R')" +exec admin "lua s:close()" + + +exec admin "lua s = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua s:nonblock(true)" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true)" +exec admin "lua s:bind('127.0.0.1', 3457)" +exec admin "lua s:listen(128)" + +exec admin "lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua sc:nonblock(true)" + +exec admin "lua sc:writable()" +exec admin "lua sc:readable()" +exec admin "lua sc:sysconnect('127.0.0.1', 3457)" +exec admin "lua sc:writable(10)" +exec admin "lua sc:write('Hello, world')" + +exec admin "lua sa = s:accept()" +exec admin "lua sa:nonblock(1)" +exec admin "lua sa:read(8)" +exec admin "lua sa:read(3)" +exec admin "lua sc:writable()" +exec admin "lua sc:write(', again')" +exec admin "lua sa:read(8)" +exec admin "lua sa:error()" +exec admin "lua string.len(sa:read(0))" +exec admin "lua type(sa:read(0))" +exec admin "lua sa:read(1, .01)" +exec admin "lua sc:writable()" + +exec admin "lua sc:send('abc')" +exec admin "lua sa:read(3)" + +exec admin "lua sc:send('Hello')" +exec admin "lua sa:readable()" +exec admin "lua sa:recv()" +exec admin "lua sa:recv()" + + +exec admin "lua sc:send('Hello')" +exec admin "lua sc:send(', world')" +exec admin "lua sc:send(\"\\nnew line\")" +exec admin "lua sa:readline({'\\n'}, 1)" +exec admin "lua sa:readline(1, {'ine'}, 1)" +exec admin "lua sa:readline({'ine'}, 1)" +exec admin "lua sa:readline({'ine'}, 0.1)" + +exec admin "lua sc:send('Hello, world')" +exec admin "lua sa:readline({','}, 1)" +exec admin "lua sc:shutdown('W')" +exec admin "lua sa:read(100, 1)" +exec admin "lua sa:read(100, 1)" +exec admin "lua sa:close()" +exec admin "lua sc:close()" + + +exec admin "lua s = box.socket('PF_UNIX', 'SOCK_STREAM', 'ip')" +exec admin "lua s:setsockopt('SOL_SOCKET', 'SO_REUSEADDR', true)" +exec admin "lua s ~= nil" +exec admin "lua s:nonblock()" +exec admin "lua s:nonblock(true)" +exec admin "lua s:nonblock()" +if os.path.exists('/tmp/tarantool-test-socket'): + os.unlink('/tmp/tarantool-test-socket') +exec admin "lua s:bind('unix/', '/tmp/tarantool-test-socket')" +exec admin "lua tostring(sc)" +exec admin "lua s:listen(1234)" + +exec admin "lua sc = box.socket('PF_UNIX', 'SOCK_STREAM', 'ip')" +exec admin "lua sc:nonblock(true)" +exec admin "lua sc:sysconnect('unix/', '/tmp/tarantool-test-socket')" +exec admin "lua sc:error()" + + +exec admin "lua s:readable()" +exec admin "lua sa = s:accept()" +exec admin "lua sa:nonblock(true)" +exec admin "lua sa:send('Hello, world')" +exec admin "lua sc:recv()" + + + +exec admin "lua sc:close()" +exec admin "lua sa:close()" +exec admin "lua s:close()" + +if os.path.exists('/tmp/tarantool-test-socket'): + os.unlink('/tmp/tarantool-test-socket') + +exec admin "lua function aexitst(ai, host, port) for i, a in pairs(ai) do if a.host == host and a.port == port then return true end end return false end" + +exec admin "lua aexitst( box.socket.getaddrinfo('localhost', 'http', { protocol = 'tcp', type = 'SOCK_STREAM'}), '127.0.0.1', 80 )" +exec admin "lua #(box.socket.getaddrinfo('mail.ru', 'http', {})) > 0" +exec admin "lua #(box.socket.getaddrinfo('mail12211alklkl.ru', 'http', {})) == 0" + + + +exec admin "lua sc = box.socket('PF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua tostring(sc)" +exec admin "lua sc:getsockopt('SOL_SOCKET', 'SO_ERROR')" +exec admin "lua sc:nonblock(true)" +exec admin "lua sc:readable()" +exec admin "lua sc:sysconnect('127.0.0.1', 3458)" +exec admin "lua string.match(tostring(sc), ', peer') == nil" +exec admin "lua sc:writable()" +exec admin "lua string.match(tostring(sc), ', peer') == nil" +exec admin "lua box.errno.strerror(sc:getsockopt('SOL_SOCKET', 'SO_ERROR'))" + +exec admin "lua box.cjson.encode(box.socket.getaddrinfo('ya.ru', '80', { flags = { 'AI_NUMERICSERV', 'AI_NUMERICHOST', } }))" + + +exec admin "lua sc = box.socket('AF_INET', 'SOCK_STREAM', 'tcp')" +exec admin "lua box.cjson.encode(sc:name())" +exec admin "lua sc:nonblock(true)" +exec admin "lua sc:close()" + +exec admin "lua s = box.socket('AF_INET', 'SOCK_DGRAM', 'udp')" +exec admin "lua s:bind('127.0.0.1', 3548)" +exec admin "lua sc = box.socket('AF_INET', 'SOCK_DGRAM', 'udp')" +exec admin "lua sc:sendto('127.0.0.1', 3548, 'Hello, world')" +exec admin "lua s:readable(10)" +exec admin "lua s:recv(4096)" + +exec admin "lua sc:sendto('127.0.0.1', 3548, 'Hello, world, 2')" +exec admin "lua s:readable(10)" +exec admin "lua local d, from = s:recvfrom(4096) print(' - ', from.port > 0) from.port = 'Random port' return box.cjson.encode{d, from}" + + +exec admin "lua s:close()" +exec admin "lua sc:close()" + + + + + +exec admin "lua s = box.socket('AF_INET', 'SOCK_DGRAM', 'udp')" +exec admin "lua s:nonblock(true)" +exec admin "lua s:bind('127.0.0.1')" +exec admin "lua s:name().port > 0" +exec admin "lua sc = box.socket('AF_INET', 'SOCK_DGRAM', 'udp')" +exec admin "lua sc:nonblock(true)" +exec admin "lua sc:sendto('127.0.0.1', s:name().port)" +exec admin "lua sc:sendto('127.0.0.1', s:name().port, 'Hello, World!')" +exec admin "lua s:readable(1)" +exec admin "lua data, from = s:recvfrom(10)" +exec admin "lua data" +exec admin "lua s:sendto(from.host, from.port, 'Hello, hello!')" +exec admin "lua sc:readable(1)" +exec admin "lua data_r, from_r = sc:recvfrom(4096)" +exec admin "lua data_r" +exec admin "lua from_r.host" +exec admin "lua from_r.port == s:name().port" +exec admin "lua s:close()" +exec admin "lua sc:close()" + + + + diff --git a/test/box/errno.result b/test/box/errno.result new file mode 100644 index 0000000000000000000000000000000000000000..cb5d65d5b94f540f2e155c2492b2bdd4d0e7d9e6 --- /dev/null +++ b/test/box/errno.result @@ -0,0 +1,26 @@ +lua type(box.errno) +--- + - table +... +lua box.errno.EINVAL > 0 +--- + - true +... +lua box.errno.EBADF > 0 +--- + - true +... +lua box.errno(box.errno.EINVAL) == box.errno.EINVAL, box.errno() == box.errno.EINVAL +--- + - true + - true +... +lua box.errno(box.errno.EBADF) ~= box.errno.EINVAL, box.errno() == box.errno.EBADF +--- + - true + - true +... +lua box.errno.strerror(box.errno.EINVAL) +--- + - Invalid argument +... diff --git a/test/box/errno.test b/test/box/errno.test new file mode 100644 index 0000000000000000000000000000000000000000..8d73fe372d0bcfdb9e7d09ade68052488f11a0ec --- /dev/null +++ b/test/box/errno.test @@ -0,0 +1,9 @@ +# encoding: tarantool +# + +exec admin "lua type(box.errno)" +exec admin "lua box.errno.EINVAL > 0" +exec admin "lua box.errno.EBADF > 0" +exec admin "lua box.errno(box.errno.EINVAL) == box.errno.EINVAL, box.errno() == box.errno.EINVAL" +exec admin "lua box.errno(box.errno.EBADF) ~= box.errno.EINVAL, box.errno() == box.errno.EBADF" +exec admin "lua box.errno.strerror(box.errno.EINVAL)"