From 0c9f420a3549df3fb331bb24157b65a3301641d4 Mon Sep 17 00:00:00 2001 From: Diego Nehab Date: Sat, 24 Jan 2004 00:18:19 +0000 Subject: [PATCH] New accept/connect code. Better error checking. Better tests. __tostring implemented. --- TODO | 3 + src/auxiliar.c | 30 ++++++- src/inet.c | 14 +-- src/inet.h | 2 +- src/socket.h | 10 ++- src/tcp.c | 54 +++++++++--- src/udp.c | 18 +++- src/usocket.c | 185 +++++++++++++++++++++++++-------------- src/wsocket.c | 215 ++++++++++++++++++++++++++++++++-------------- test/testclnt.lua | 89 ++++++++++++------- 10 files changed, 424 insertions(+), 196 deletions(-) diff --git a/TODO b/TODO index 835929f..f6a67f1 100644 --- a/TODO +++ b/TODO @@ -12,6 +12,9 @@ manual local connect add thanks to 'carlos cassino' and 'david burgess' add new ip- options and reuseaddr option + add listen to manual + bind method doesn't do listen anymore + bind doesn't turn an object into a server object: listen does. tests checar todos os metodos diff --git a/src/auxiliar.c b/src/auxiliar.c index 6888f9c..9a249b6 100644 --- a/src/auxiliar.c +++ b/src/auxiliar.c @@ -5,6 +5,7 @@ * RCS ID: $Id$ \*=========================================================================*/ #include +#include #include "luasocket.h" #include "auxiliar.h" @@ -12,6 +13,27 @@ /*=========================================================================*\ * Exported functions \*=========================================================================*/ +/*-------------------------------------------------------------------------*\ +* Prints the value of a class in a nice way +\*-------------------------------------------------------------------------*/ +int aux_meth_tostring(lua_State *L) +{ + char buf[32]; + if (!lua_getmetatable(L, 1)) goto error; + lua_pushstring(L, "__index"); + lua_gettable(L, -2); + if (!lua_istable(L, -1)) goto error; + lua_pushstring(L, "class"); + lua_gettable(L, -2); + if (!lua_isstring(L, -1)) goto error; + sprintf(buf, "%p", lua_touserdata(L, 1)); + lua_pushfstring(L, "socket: %s: %s", lua_tostring(L, -1), buf); + return 1; +error: + lua_pushnil(L); + return 1; +} + /*-------------------------------------------------------------------------*\ * Initializes the module \*-------------------------------------------------------------------------*/ @@ -26,14 +48,18 @@ void aux_open(lua_State *L) void aux_newclass(lua_State *L, const char *classname, luaL_reg *func) { luaL_newmetatable(L, classname); /* mt */ + /* set __tostring metamethod */ + lua_pushstring(L, "__tostring"); + lua_pushcfunction(L, aux_meth_tostring); + lua_rawset(L, -3); + /* create __index table to place methods */ lua_pushstring(L, "__index"); /* mt,"__index" */ lua_newtable(L); /* mt,"__index",it */ luaL_openlib(L, NULL, func, 0); -#ifdef LUASOCKET_DEBUG + /* put class name into class metatable */ lua_pushstring(L, "class"); /* mt,"__index",it,"class" */ lua_pushstring(L, classname); /* mt,"__index",it,"class",classname */ lua_rawset(L, -3); /* mt,"__index",it */ -#endif /* get __gc method from class and use it for garbage collection */ lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc" */ lua_pushstring(L, "__gc"); /* mt,"__index",it,"__gc","__gc" */ diff --git a/src/inet.c b/src/inet.c index f6c2a6f..dff4bf2 100644 --- a/src/inet.c +++ b/src/inet.c @@ -213,8 +213,7 @@ const char *inet_tryconnect(p_sock ps, const char *address, /*-------------------------------------------------------------------------*\ * Tries to bind socket to (address, port) \*-------------------------------------------------------------------------*/ -const char *inet_trybind(p_sock ps, const char *address, unsigned short port, - int backlog) +const char *inet_trybind(p_sock ps, const char *address, unsigned short port) { struct sockaddr_in local; const char *err; @@ -231,16 +230,9 @@ const char *inet_trybind(p_sock ps, const char *address, unsigned short port, addr = (struct in_addr **) hp->h_addr_list; memcpy(&local.sin_addr, *addr, sizeof(struct in_addr)); } - sock_setblocking(ps); err = sock_bind(ps, (SA *) &local, sizeof(local)); - if (err) { - sock_destroy(ps); - return err; - } else { - sock_setnonblocking(ps); - if (backlog >= 0) sock_listen(ps, backlog); - return NULL; - } + if (err) sock_destroy(ps); + return err; } /*-------------------------------------------------------------------------*\ diff --git a/src/inet.h b/src/inet.h index 28cf823..87da23a 100644 --- a/src/inet.h +++ b/src/inet.h @@ -30,7 +30,7 @@ const char *inet_trycreate(p_sock ps, int type); const char *inet_tryconnect(p_sock ps, const char *address, unsigned short port, p_tm tm); const char *inet_trybind(p_sock ps, const char *address, - unsigned short port, int backlog); + unsigned short port); int inet_meth_getpeername(lua_State *L, p_sock ps); int inet_meth_getsockname(lua_State *L, p_sock ps); diff --git a/src/socket.h b/src/socket.h index 7c84baa..2e7b6f9 100644 --- a/src/socket.h +++ b/src/socket.h @@ -39,7 +39,6 @@ typedef struct sockaddr SA; \*=========================================================================*/ int sock_open(void); void sock_destroy(p_sock ps); -void sock_listen(p_sock ps, int backlog); void sock_shutdown(p_sock ps, int how); int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, int timeout); @@ -51,10 +50,15 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, SA *addr, socklen_t *addr_len, int timeout); void sock_setnonblocking(p_sock ps); void sock_setblocking(p_sock ps); -int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, p_tm tm); +int sock_select(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, int timeout); + const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm); const char *sock_create(p_sock ps, int domain, int type, int protocol); const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len); -const char *sock_hoststrerror(void); +const char *sock_listen(p_sock ps, int backlog); +const char *sock_accept(p_sock ps, p_sock pa, SA *addr, + socklen_t *addr_len, p_tm tm); + +const char *sock_hoststrerror(); #endif /* SOCK_H */ diff --git a/src/tcp.c b/src/tcp.c index 01b07ee..4b3b0cc 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -22,6 +22,7 @@ \*=========================================================================*/ static int global_create(lua_State *L); static int meth_connect(lua_State *L); +static int meth_listen(lua_State *L); static int meth_bind(lua_State *L); static int meth_send(lua_State *L); static int meth_getsockname(lua_State *L); @@ -32,7 +33,8 @@ static int meth_accept(lua_State *L); static int meth_close(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); -static int meth_fd(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); /* tcp object methods */ @@ -41,6 +43,7 @@ static luaL_reg tcp[] = { {"send", meth_send}, {"receive", meth_receive}, {"bind", meth_bind}, + {"listen", meth_listen}, {"accept", meth_accept}, {"setpeername", meth_connect}, {"setsockname", meth_bind}, @@ -51,7 +54,8 @@ static luaL_reg tcp[] = { {"shutdown", meth_shutdown}, {"setoption", meth_setoption}, {"__gc", meth_close}, - {"fd", meth_fd}, + {"getfd", meth_getfd}, + {"setfd", meth_setfd}, {"dirty", meth_dirty}, {NULL, NULL} }; @@ -124,16 +128,24 @@ static int meth_setoption(lua_State *L) /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ -static int meth_fd(lua_State *L) +static int meth_getfd(lua_State *L) { - p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client,server}", 1); + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); lua_pushnumber(L, tcp->sock); return 1; } +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); + tcp->sock = (t_sock) luaL_checknumber(L, 2); + return 0; +} + static int meth_dirty(lua_State *L) { - p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{client,server}", 1); + p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1); lua_pushboolean(L, !buf_isempty(&tcp->buf)); return 1; } @@ -147,9 +159,9 @@ static int meth_accept(lua_State *L) p_tcp server = (p_tcp) aux_checkclass(L, "tcp{server}", 1); p_tm tm = tm_markstart(&server->tm); t_sock sock; - int err = sock_accept(&server->sock, &sock, NULL, NULL, tm); + const char *err = sock_accept(&server->sock, &sock, NULL, NULL, tm); /* if successful, push client socket */ - if (err == IO_DONE) { + if (!err) { p_tcp clnt = lua_newuserdata(L, sizeof(t_tcp)); aux_setclass(L, "tcp{client}", -1); /* initialize structure fields */ @@ -160,28 +172,25 @@ static int meth_accept(lua_State *L) return 1; } else { lua_pushnil(L); - io_pusherror(L, err); + lua_pushstring(L, err); return 2; } } /*-------------------------------------------------------------------------*\ -* Turns a master object into a server object +* Binds an object to an address \*-------------------------------------------------------------------------*/ static int meth_bind(lua_State *L) { p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); unsigned short port = (unsigned short) luaL_checknumber(L, 3); - int backlog = (int) luaL_optnumber(L, 4, 1); - const char *err = inet_trybind(&tcp->sock, address, port, backlog); + const char *err = inet_trybind(&tcp->sock, address, port); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } - /* turn master object into a server object if there was a listen */ - if (backlog >= 0) aux_setclass(L, "tcp{server}", 1); lua_pushnumber(L, 1); return 1; } @@ -217,6 +226,25 @@ static int meth_close(lua_State *L) return 0; } +/*-------------------------------------------------------------------------*\ +* Puts the sockt in listen mode +\*-------------------------------------------------------------------------*/ +static int meth_listen(lua_State *L) +{ + p_tcp tcp = (p_tcp) aux_checkclass(L, "tcp{master}", 1); + int backlog = (int) luaL_checknumber(L, 2); + const char *err = sock_listen(&tcp->sock, backlog); + if (err) { + lua_pushnil(L); + lua_pushstring(L, err); + return 2; + } + /* turn master object into a server object */ + aux_setclass(L, "tcp{server}", 1); + lua_pushnumber(L, 1); + return 1; +} + /*-------------------------------------------------------------------------*\ * Shuts the connection down partially \*-------------------------------------------------------------------------*/ diff --git a/src/udp.c b/src/udp.c index 115e749..14029f9 100644 --- a/src/udp.c +++ b/src/udp.c @@ -33,7 +33,8 @@ static int meth_close(lua_State *L); static int meth_shutdown(lua_State *L); static int meth_setoption(lua_State *L); static int meth_settimeout(lua_State *L); -static int meth_fd(lua_State *L); +static int meth_getfd(lua_State *L); +static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); /* udp object methods */ @@ -51,7 +52,8 @@ static luaL_reg udp[] = { {"shutdown", meth_shutdown}, {"setoption", meth_setoption}, {"__gc", meth_close}, - {"fd", meth_fd}, + {"getfd", meth_getfd}, + {"setfd", meth_setfd}, {"dirty", meth_dirty}, {NULL, NULL} }; @@ -194,13 +196,21 @@ static int meth_receivefrom(lua_State *L) /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ -static int meth_fd(lua_State *L) +static int meth_getfd(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); lua_pushnumber(L, udp->sock); return 1; } +/* this is very dangerous, but can be handy for those that are brave enough */ +static int meth_setfd(lua_State *L) +{ + p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); + udp->sock = (t_sock) luaL_checknumber(L, 2); + return 0; +} + static int meth_dirty(lua_State *L) { p_udp udp = (p_udp) aux_checkgroup(L, "udp{any}", 1); @@ -312,7 +322,7 @@ static int meth_setsockname(lua_State *L) p_udp udp = (p_udp) aux_checkclass(L, "udp{unconnected}", 1); const char *address = luaL_checkstring(L, 2); unsigned short port = (unsigned short) luaL_checknumber(L, 3); - const char *err = inet_trybind(&udp->sock, address, port, -1); + const char *err = inet_trybind(&udp->sock, address, port); if (err) { lua_pushnil(L); lua_pushstring(L, err); diff --git a/src/usocket.c b/src/usocket.c index 5afa1bf..bece354 100644 --- a/src/usocket.c +++ b/src/usocket.c @@ -20,9 +20,11 @@ #include "socket.h" -static const char *sock_createstrerror(void); -static const char *sock_bindstrerror(void); -static const char *sock_connectstrerror(void); +static const char *sock_createstrerror(int err); +static const char *sock_bindstrerror(int err); +static const char *sock_connectstrerror(int err); +static const char *sock_acceptstrerror(int err); +static const char *sock_listenstrerror(int err); /*-------------------------------------------------------------------------*\ * Initializes module @@ -46,11 +48,23 @@ int sock_open(void) void sock_destroy(p_sock ps) { if (*ps != SOCK_INVALID) { + sock_setblocking(ps); close(*ps); *ps = SOCK_INVALID; } } +/*-------------------------------------------------------------------------*\ +* Select with int timeout in ms +\*-------------------------------------------------------------------------*/ +int sock_select(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + return select(n, rfds, wfds, efds, timeout >= 0? &tv: NULL); +} + /*-------------------------------------------------------------------------*\ * Creates and sets up a socket \*-------------------------------------------------------------------------*/ @@ -58,7 +72,7 @@ const char *sock_create(p_sock ps, int domain, int type, int protocol) { int val = 1; t_sock sock = socket(domain, type, protocol); - if (sock == SOCK_INVALID) return sock_createstrerror(); + if (sock == SOCK_INVALID) return sock_createstrerror(errno); *ps = sock; sock_setnonblocking(ps); setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)); @@ -79,27 +93,22 @@ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm) /* if no error, we're done */ if (err == 0) return NULL; /* make sure the system is trying to connect */ - if (errno != EINPROGRESS) return io_strerror(IO_ERROR); + if (errno != EINPROGRESS) return sock_connectstrerror(errno); /* wait for a timeout or for the system's answer */ for ( ;; ) { - struct timeval tv; fd_set rfds, wfds, efds; - int timeout = tm_getretry(tm); - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&rfds); FD_SET(sock, &rfds); FD_ZERO(&wfds); FD_SET(sock, &wfds); FD_ZERO(&efds); FD_SET(sock, &efds); /* we run select to avoid busy waiting */ - err = select(sock+1, &rfds, &wfds, &efds, timeout >= 0? &tv: NULL); - /* if select was interrupted, try again */ - if (err < 0 && errno == EINTR) continue; + do err = sock_select(sock+1, &rfds, &wfds, &efds, tm_getretry(tm)); + while (err < 0 && errno == EINTR); /* if selects readable, try reading */ if (err > 0) { char dummy; /* recv will set errno to the value a blocking connect would set */ - if (recv(sock, &dummy, 0, 0) < 0 && errno != EAGAIN) - return sock_connectstrerror(); + if (recv(sock, &dummy, 0, 0) < 0 && errno != EWOULDBLOCK) + return sock_connectstrerror(errno); else return NULL; /* if no event happened, there was a timeout */ @@ -113,16 +122,24 @@ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm) \*-------------------------------------------------------------------------*/ const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) { - if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); - else return NULL; + const char *err = NULL; + sock_setblocking(ps); + if (bind(*ps, addr, addr_len) < 0) err = sock_bindstrerror(errno); + sock_setnonblocking(ps); + return err; } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ -void sock_listen(p_sock ps, int backlog) +const char* sock_listen(p_sock ps, int backlog) { - listen(*ps, backlog); + const char *err = NULL; + sock_setblocking(ps); + if (listen(*ps, backlog)) + err = sock_listenstrerror(errno); + sock_setnonblocking(ps); + return err; } /*-------------------------------------------------------------------------*\ @@ -130,35 +147,40 @@ void sock_listen(p_sock ps, int backlog) \*-------------------------------------------------------------------------*/ void sock_shutdown(p_sock ps, int how) { + sock_setblocking(ps); shutdown(*ps, how); + sock_setnonblocking(ps); } /*-------------------------------------------------------------------------*\ * Accept with timeout \*-------------------------------------------------------------------------*/ -int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, p_tm tm) +const char *sock_accept(p_sock ps, p_sock pa, SA *addr, + socklen_t *addr_len, p_tm tm) { t_sock sock = *ps; SA dummy_addr; socklen_t dummy_len = sizeof(dummy_addr); - if (sock == SOCK_INVALID) return IO_CLOSED; + if (sock == SOCK_INVALID) return io_strerror(IO_CLOSED); if (!addr) addr = &dummy_addr; if (!addr_len) addr_len = &dummy_len; for (;;) { - int timeout = tm_getretry(tm); - struct timeval tv; + int err; fd_set fds; *pa = accept(sock, addr, addr_len); - if (*pa != SOCK_INVALID) return IO_DONE; - if (timeout == 0) return IO_TIMEOUT; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; + /* if result is valid, we are done */ + if (*pa != SOCK_INVALID) return NULL; + /* find out if we failed for a fatal reason */ + if (errno != EWOULDBLOCK && errno != ECONNABORTED) + return sock_acceptstrerror(errno); + /* call select just to avoid busy-wait. */ FD_ZERO(&fds); FD_SET(sock, &fds); - /* call select just to avoid busy-wait. */ - select(sock+1, &fds, NULL, NULL, timeout >= 0? &tv: NULL); + do err = sock_select(sock+1, &fds, NULL, NULL, tm_getretry(tm)); + while (err < 0 && errno == EINTR); + if (err == 0) return io_strerror(IO_TIMEOUT); } - return IO_TIMEOUT; /* can't get here */ + return io_strerror(IO_TIMEOUT); /* can't get here */ } /*-------------------------------------------------------------------------*\ @@ -176,18 +198,15 @@ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, while (put < 0 && errno == EINTR); /* deal with failure */ if (put <= 0) { - struct timeval tv; fd_set fds; /* in any case, nothing has been sent */ *sent = 0; /* here we know the connection has been closed */ if (errno == EPIPE) return IO_CLOSED; /* run select to avoid busy wait */ - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - if (select(sock+1, NULL, &fds, NULL, timeout >= 0? &tv: NULL) <= 0) { + if (sock_select(sock+1, NULL, &fds, NULL, timeout) <= 0) { /* here the call was interrupted. calling again might work */ if (errno == EINTR) return IO_RETRY; /* here there was no data before timeout */ @@ -213,15 +232,12 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, do put = sendto(sock, data, count, 0, addr, addr_len); while (put < 0 && errno == EINTR); if (put <= 0) { - struct timeval tv; fd_set fds; *sent = 0; if (errno == EPIPE) return IO_CLOSED; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - if (select(sock+1, NULL, &fds, NULL, timeout >= 0? &tv: NULL) <= 0) { + if (sock_select(sock+1, NULL, &fds, NULL, timeout) <= 0) { if (errno == EINTR) return IO_RETRY; else return IO_TIMEOUT; } else return IO_DONE; @@ -242,16 +258,13 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) do taken = read(sock, data, count); while (taken < 0 && errno == EINTR); if (taken <= 0) { - struct timeval tv; fd_set fds; int ret; *got = 0; if (taken == 0) return IO_CLOSED; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(sock+1, &fds, NULL, NULL, timeout); if (ret < 0 && errno == EINTR) return IO_RETRY; if (ret == 0) return IO_TIMEOUT; else return IO_DONE; @@ -273,16 +286,13 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, do taken = recvfrom(sock, data, count, 0, addr, addr_len); while (taken < 0 && errno == EINTR); if (taken <= 0) { - struct timeval tv; fd_set fds; int ret; *got = 0; if (taken == 0) return IO_CLOSED; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(sock+1, &fds, NULL, NULL, timeout); if (ret < 0 && errno == EINTR) return IO_RETRY; if (ret == 0) return IO_TIMEOUT; else return IO_DONE; @@ -315,53 +325,98 @@ void sock_setnonblocking(p_sock ps) /*-------------------------------------------------------------------------*\ * Error translation functions \*-------------------------------------------------------------------------*/ +/* return error messages for the known errors reported by gethostbyname */ const char *sock_hoststrerror(void) { switch (h_errno) { case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; + case NO_ADDRESS: return "valid host but no ip found"; case NO_RECOVERY: return "name server error"; case TRY_AGAIN: return "name server unavailable, try again later"; default: return "unknown error"; } } -static const char *sock_createstrerror(void) +/* return error messages for the known errors reported by socket */ +static const char *sock_createstrerror(int err) { - switch (errno) { + switch (err) { + case EPROTONOSUPPORT: return "protocol not supported"; case EACCES: return "access denied"; - case EMFILE: return "descriptor table is full"; - case ENFILE: return "too many open files"; + case EMFILE: return "process file table is full"; + case ENFILE: return "kernel file table is full"; + case EINVAL: return "unknown protocol or family"; case ENOBUFS: return "insuffucient buffer space"; default: return "unknown error"; } } -static const char *sock_bindstrerror(void) +/* return error messages for the known errors reported by accept */ +static const char *sock_acceptstrerror(int err) { - switch (errno) { + switch (err) { + case EWOULDBLOCK: return io_strerror(IO_RETRY); case EBADF: return "invalid descriptor"; - case EINVAL: return "socket already bound"; - case EACCES: return "access denied"; - case ENOTSOCK: return "not a socket descriptor"; + case ENOBUFS: case ENOMEM: return "insuffucient buffer space"; + case ENOTSOCK: return "descriptor not a socket"; + case EOPNOTSUPP: return "not supported"; + case EINTR: return "call interrupted"; + case ECONNABORTED: return "connection aborted"; + case EINVAL: return "not listening"; + case EMFILE: return "process file table is full"; + case ENFILE: return "kernel file table is full"; + case EFAULT: return "invalid memory address"; + default: return "unknown error"; + } +} + + +/* return error messages for the known errors reported by bind */ +static const char *sock_bindstrerror(int err) +{ + switch (err) { + case EBADF: return "invalid descriptor"; + case ENOTSOCK: return "descriptor not a socket"; + case EADDRNOTAVAIL: return "address unavailable in local host"; case EADDRINUSE: return "address already in use"; - case EADDRNOTAVAIL: return "address unavailable"; + case EINVAL: return "already bound"; + case EACCES: return "access denied"; + case EFAULT: return "invalid memory address"; case ENOMEM: return "out of memory"; default: return "unknown error"; } } -static const char *sock_connectstrerror(void) +/* return error messages for the known errors reported by listen */ +static const char *sock_listenstrerror(int err) { - switch (errno) { + switch (err) { + case EADDRINUSE: return "local address already in use"; case EBADF: return "invalid descriptor"; - case ENOTSOCK: return "not a socket descriptor"; - case EADDRNOTAVAIL: return "address not availabe"; - case ETIMEDOUT: return "connection timed out"; - case ECONNREFUSED: return "connection refused"; - case EACCES: return "access denied"; - case ENETUNREACH: return "network is unreachable"; - case EADDRINUSE: return "address already in use"; + case ENOTSOCK: return "descriptor not a socket"; + case EOPNOTSUPP: return "not supported"; + default: return "unknown error"; + } +} + +/* return error messages for the known errors reported by connect */ +static const char *sock_connectstrerror(int err) +{ + switch (err) { + case EBADF: return "invalid descriptor"; + case EFAULT: return "invalid memory address"; + case ENOTSOCK: return "descriptor not a socket"; + case EADDRNOTAVAIL: return "address not available in local host"; + case EISCONN: return "already connected"; + case ECONNREFUSED: return "connection refused"; + case ETIMEDOUT: return io_strerror(IO_TIMEOUT); + case ENETUNREACH: return "network is unreachable"; + case EADDRINUSE: return "local address already in use"; + case EINPROGRESS: return "would block"; + case EALREADY: return "connect already in progress"; + case EAGAIN: return "not enough free ports"; + case EAFNOSUPPORT: return "address family not supported"; + case EPERM: return "broadcast not enabled or firewall block"; default: return "unknown error"; } } diff --git a/src/wsocket.c b/src/wsocket.c index f834503..f33e154 100644 --- a/src/wsocket.c +++ b/src/wsocket.c @@ -15,9 +15,11 @@ #include "socket.h" -static const char *sock_createstrerror(void); -static const char *sock_bindstrerror(void); -static const char *sock_connectstrerror(void); +static const char *sock_createstrerror(int err); +static const char *sock_bindstrerror(int err); +static const char *sock_connectstrerror(int err); +static const char *sock_acceptstrerror(int err); +static const char *sock_listenstrerror(int err); /*-------------------------------------------------------------------------*\ * Initializes module @@ -35,12 +37,24 @@ int sock_open(void) return 1; } +/*-------------------------------------------------------------------------*\ +* Select with int timeout in ms +\*-------------------------------------------------------------------------*/ +int sock_select(int n, fd_set *rfds, fd_set *wfds, fd_set *efds, int timeout) +{ + struct timeval tv; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + return select(n, rfds, wfds, efds, timeout >= 0? &tv: NULL); +} + /*-------------------------------------------------------------------------*\ * Close and inutilize socket \*-------------------------------------------------------------------------*/ void sock_destroy(p_sock ps) { if (*ps != SOCK_INVALID) { + sock_setblocking(ps); /* close can take a long time on WIN32 */ closesocket(*ps); *ps = SOCK_INVALID; } @@ -51,7 +65,9 @@ void sock_destroy(p_sock ps) \*-------------------------------------------------------------------------*/ void sock_shutdown(p_sock ps, int how) { + sock_setblocking(ps); shutdown(*ps, how); + sock_setnonblocking(ps); } /*-------------------------------------------------------------------------*\ @@ -61,10 +77,11 @@ const char *sock_create(p_sock ps, int domain, int type, int protocol) { int val = 1; t_sock sock = socket(domain, type, protocol); - if (sock == SOCK_INVALID) return sock_createstrerror(); + if (sock == SOCK_INVALID) + return sock_createstrerror(WSAGetLastError()); *ps = sock; - sock_setnonblocking(ps); setsockopt(*ps, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)); + sock_setnonblocking(ps); return NULL; } @@ -75,7 +92,6 @@ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm) { t_sock sock = *ps; int err, timeout = tm_getretry(tm); - struct timeval tv; fd_set efds, wfds; /* don't call on closed socket */ if (sock == SOCK_INVALID) return io_strerror(IO_CLOSED); @@ -84,27 +100,24 @@ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm) /* if no error, we're done */ if (err == 0) return NULL; /* make sure the system is trying to connect */ - if (WSAGetLastError() != WSAEWOULDBLOCK) return sock_connectstrerror(); + err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return sock_connectstrerror(err); /* wait for a timeout or for the system's answer */ - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&wfds); FD_SET(sock, &wfds); FD_ZERO(&efds); FD_SET(sock, &efds); /* we run select to wait */ - err = select(0, NULL, &wfds, &efds, timeout >= 0? &tv: NULL); + err = sock_select(0, NULL, &wfds, &efds, timeout); /* if select returned due to an event */ if (err > 0 ) { /* if was in efds, we failed */ - if (FD_ISSET(sock,&efds) || !FD_ISSET(sock,&wfds)) { - int why; - int len = sizeof(why); + if (FD_ISSET(sock, &efds)) { + int why, len = sizeof(why); /* find out why we failed */ getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *)&why, &len); - WSASetLastError(why); - return sock_connectstrerror(); - /* if was in wfds, we succeeded */ + return sock_connectstrerror(why); + /* otherwise it must be in wfds, so we succeeded */ } else return NULL; - /* if nothing happened, we timed out */ + /* if no event happened, we timed out */ } else return io_strerror(IO_TIMEOUT); } @@ -113,44 +126,60 @@ const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len, p_tm tm) \*-------------------------------------------------------------------------*/ const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len) { - if (bind(*ps, addr, addr_len) < 0) return sock_bindstrerror(); - else return NULL; + const char *err = NULL; + sock_setblocking(ps); + if (bind(*ps, addr, addr_len) < 0) + err = sock_bindstrerror(WSAGetLastError()); + sock_setnonblocking(ps); + return err; } /*-------------------------------------------------------------------------*\ * \*-------------------------------------------------------------------------*/ -void sock_listen(p_sock ps, int backlog) +const char *sock_listen(p_sock ps, int backlog) { - listen(*ps, backlog); + const char *err = NULL; + sock_setblocking(ps); + if (listen(*ps, backlog) < 0) + err = sock_listenstrerror(WSAGetLastError()); + sock_setnonblocking(ps); + return err; } /*-------------------------------------------------------------------------*\ * Accept with timeout \*-------------------------------------------------------------------------*/ -int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, p_tm tm) +const char *sock_accept(p_sock ps, p_sock pa, SA *addr, + socklen_t *addr_len, p_tm tm) { t_sock sock = *ps; SA dummy_addr; socklen_t dummy_len = sizeof(dummy_addr); - if (sock == SOCK_INVALID) return IO_CLOSED; + if (sock == SOCK_INVALID) return io_strerror(IO_CLOSED); if (!addr) addr = &dummy_addr; if (!addr_len) addr_len = &dummy_len; for (;;) { + fd_set rfds; int timeout = tm_getretry(tm); - struct timeval tv; - fd_set fds; + int err; + /* try to get client socket */ *pa = accept(sock, addr, addr_len); - if (*pa != SOCK_INVALID) return IO_DONE; - if (timeout == 0) return IO_TIMEOUT; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - FD_ZERO(&fds); - FD_SET(sock, &fds); - /* call select just to avoid busy-wait. */ - select(0, &fds, NULL, NULL, timeout >= 0? &tv: NULL); + /* if return is valid, we are done */ + if (*pa != SOCK_INVALID) return NULL; + /* optimization */ + if (timeout == 0) return io_strerror(IO_TIMEOUT); + /* otherwise find out why we failed */ + err = WSAGetLastError(); + /* if we failed because there was no connectoin, keep trying*/ + if (err != WSAEWOULDBLOCK) return sock_acceptstrerror(err); + /* call select to avoid busy wait */ + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + err = sock_select(0, &rfds, NULL, NULL, timeout); + if (err <= 0) return io_strerror(IO_TIMEOUT); } - return IO_TIMEOUT; /* can't get here */ + return io_strerror(IO_TIMEOUT); /* can't get here */ } /*-------------------------------------------------------------------------*\ @@ -172,13 +201,10 @@ int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, *sent = 0; /* run select to avoid busy wait */ if (WSAGetLastError() == WSAEWOULDBLOCK) { - struct timeval tv; fd_set fds; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(0, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(0, NULL, &fds, NULL, timeout); /* tell the caller to call us again because there is more data */ if (ret > 0) return IO_DONE; /* tell the caller there was no data before timeout */ @@ -211,13 +237,10 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent, *sent = 0; /* run select to avoid busy wait */ if (WSAGetLastError() == WSAEWOULDBLOCK) { - struct timeval tv; fd_set fds; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(0, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(0, NULL, &fds, NULL, timeout); /* tell the caller to call us again because there is more data */ if (ret > 0) return IO_DONE; /* tell the caller there was no data before timeout */ @@ -241,16 +264,13 @@ int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) if (sock == SOCK_INVALID) return IO_CLOSED; taken = recv(sock, data, (int) count, 0); if (taken <= 0) { - struct timeval tv; fd_set fds; int ret; *got = 0; if (taken == 0) return IO_CLOSED; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(0, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(0, &fds, NULL, NULL, timeout); if (ret > 0) return IO_DONE; else return IO_TIMEOUT; } else { @@ -270,16 +290,13 @@ int sock_recvfrom(p_sock ps, char *data, size_t count, size_t *got, if (sock == SOCK_INVALID) return IO_CLOSED; taken = recvfrom(sock, data, (int) count, 0, addr, addr_len); if (taken <= 0) { - struct timeval tv; fd_set fds; int ret; *got = 0; if (taken == 0) return IO_CLOSED; - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&fds); FD_SET(sock, &fds); - ret = select(0, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); + ret = sock_select(0, &fds, NULL, NULL, timeout); if (ret > 0) return IO_DONE; else return IO_TIMEOUT; } else { @@ -309,51 +326,117 @@ void sock_setnonblocking(p_sock ps) /*-------------------------------------------------------------------------*\ * Error translation functions \*-------------------------------------------------------------------------*/ +/* return error messages for the known errors reported by gethostbyname */ const char *sock_hoststrerror(void) { switch (WSAGetLastError()) { - case HOST_NOT_FOUND: return "host not found"; - case NO_ADDRESS: return "unable to resolve host name"; - case NO_RECOVERY: return "name server error"; - case TRY_AGAIN: return "name server unavailable, try again later."; + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAHOST_NOT_FOUND: return "host not found"; + case WSATRY_AGAIN: return "name server unavailable, try again later"; + case WSANO_RECOVERY: return "name server error"; + case WSANO_DATA: return "host not found"; + case WSAEINPROGRESS: return "another call in progress"; + case WSAEFAULT: return "invalid memory address"; + case WSAEINTR: return "call interrupted"; default: return "unknown error"; } } -static const char *sock_createstrerror(void) +/* return error messages for the known errors reported by socket */ +static const char *sock_createstrerror(int err) { - switch (WSAGetLastError()) { + switch (err) { case WSANOTINITIALISED: return "not initialized"; case WSAENETDOWN: return "network is down"; + case WSAEAFNOSUPPORT: return "address family not supported"; + case WSAEINPROGRESS: return "another call in progress"; case WSAEMFILE: return "descriptor table is full"; case WSAENOBUFS: return "insufficient buffer space"; + case WSAEPROTONOSUPPORT: return "protocol not supported"; + case WSAEPROTOTYPE: return "wrong protocol type"; + case WSAESOCKTNOSUPPORT: return "socket type not supported by family"; default: return "unknown error"; } } -static const char *sock_bindstrerror(void) +/* return error messages for the known errors reported by accept */ +static const char *sock_acceptstrerror(int err) { - switch (WSAGetLastError()) { + switch (err) { case WSANOTINITIALISED: return "not initialized"; case WSAENETDOWN: return "network is down"; - case WSAEADDRINUSE: return "address already in use"; - case WSAEINVAL: return "socket already bound"; - case WSAENOBUFS: return "too many connections"; - case WSAEFAULT: return "invalid address"; - case WSAENOTSOCK: return "not a socket descriptor"; + case WSAEFAULT: return "invalid memory address"; + case WSAEINTR: return "call interrupted"; + case WSAEINPROGRESS: return "another call in progress"; + case WSAEINVAL: return "not listening"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insufficient buffer space"; + case WSAENOTSOCK: return "descriptor not a socket"; + case WSAEOPNOTSUPP: return "not supported"; + case WSAEWOULDBLOCK: return "call would block"; default: return "unknown error"; } } -static const char *sock_connectstrerror(void) +/* return error messages for the known errors reported by bind */ +static const char *sock_bindstrerror(int err) { - switch (WSAGetLastError()) { + switch (err) { case WSANOTINITIALISED: return "not initialized"; case WSAENETDOWN: return "network is down"; + case WSAEACCES: return "broadcast not enabled for socket"; case WSAEADDRINUSE: return "address already in use"; - case WSAEADDRNOTAVAIL: return "address unavailable"; + case WSAEADDRNOTAVAIL: return "address not available in local host"; + case WSAEFAULT: return "invalid memory address"; + case WSAEINPROGRESS: return "another call in progress"; + case WSAEINVAL: return "already bound"; + case WSAENOBUFS: return "insuficient buffer space"; + case WSAENOTSOCK: return "descriptor not a socket"; + default: return "unknown error"; + } + +} + +/* return error messages for the known errors reported by listen */ +static const char *sock_listenstrerror(int err) +{ + switch (err) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "local address already in use"; + case WSAEINPROGRESS: return "another call in progress"; + case WSAEINVAL: return "not bound"; + case WSAEISCONN: return "already connected"; + case WSAEMFILE: return "descriptor table is full"; + case WSAENOBUFS: return "insuficient buffer space"; + case WSAENOTSOCK: return "descriptor not a socket"; + case WSAEOPNOTSUPP: return "not supported"; + default: return "unknown error"; + } +} + +/* return error messages for the known errors reported by connect */ +static const char *sock_connectstrerror(int err) +{ + switch (err) { + case WSANOTINITIALISED: return "not initialized"; + case WSAENETDOWN: return "network is down"; + case WSAEADDRINUSE: return "local address already in use"; + case WSAEINTR: return "call interrupted"; + case WSAEINPROGRESS: return "another call in progress"; + case WSAEALREADY: return "connect already in progress"; + case WSAEADDRNOTAVAIL: return "invalid remote address"; + case WSAEAFNOSUPPORT: return "address family not supported"; case WSAECONNREFUSED: return "connection refused"; + case WSAEFAULT: return "invalid memory address"; + case WSAEINVAL: return "socket is listening"; + case WSAEISCONN: return "socket already connected"; case WSAENETUNREACH: return "network is unreachable"; + case WSAENOTSOCK: return "descriptor not a socket"; + case WSAETIMEDOUT: return io_strerror(IO_TIMEOUT); + case WSAEWOULDBLOCK: return "would block"; + case WSAEACCES: return "broadcast not enabled"; default: return "unknown error"; } } diff --git a/test/testclnt.lua b/test/testclnt.lua index 6dccd0c..beb0157 100644 --- a/test/testclnt.lua +++ b/test/testclnt.lua @@ -117,7 +117,6 @@ function test_mixed(len) local p3 = "raw " .. string.rep("z", inter) .. "bytes" local p4 = "end" .. string.rep("w", inter) .. "bytes" local bp1, bp2, bp3, bp4 - pass(len .. " byte(s) patterns") remote (string.format("str = data:receive(%d)", string.len(p1)+string.len(p2)+string.len(p3)+string.len(p4))) sent, err = data:send(p1, p2, p3, p4) @@ -137,7 +136,6 @@ function test_asciiline(len) str = string.rep("x", math.mod(len, 10)) str10 = string.rep("aZb.c#dAe?", math.floor(len/10)) str = str .. str10 - pass(len .. " byte(s) line") remote "str = data:receive()" sent, err = data:send(str, "\n") if err then fail(err) end @@ -156,7 +154,6 @@ function test_rawline(len) str10 = string.rep(string.char(120,21,77,4,5,0,7,36,44,100), math.floor(len/10)) str = str .. str10 - pass(len .. " byte(s) line") remote "str = data:receive()" sent, err = data:send(str, "\n") if err then fail(err) end @@ -174,7 +171,6 @@ function test_raw(len) local s1, s2, back, err s1 = string.rep("x", half) s2 = string.rep("y", len-half) - pass(len .. " byte(s) block") remote (string.format("str = data:receive(%d)", len)) sent, err = data:send(s1) if err then fail(err) end @@ -271,12 +267,10 @@ end function empty_connect() reconnect() if data then data:close() data = nil end -print("before remote") remote [[ if data then data:close() data = nil end data = server:accept() ]] -print("after remote") data, err = socket.connect("", port) if not data then pass("ok") @@ -286,7 +280,7 @@ end ------------------------------------------------------------------------ function isclosed(c) - return c:fd() == -1 or c:fd() == (2^32-1) + return c:getfd() == -1 or c:getfd() == (2^32-1) end function active_close() @@ -354,13 +348,14 @@ end ------------------------------------------------------------------------ function accept_timeout() + io.write("accept with timeout (if it hangs, it failed): ") local s, e = socket.bind("*", 0, 0) assert(s, e) local t = socket.time() s:settimeout(1) local c, e = s:accept() assert(not c, "should not accept") - assert(e == "timeout", "wrong error message") + assert(e == "timeout", string.format("wrong error message (%s)", e)) t = socket.time() - t assert(t < 2, string.format("took to long to give up (%gs)", t)) s:close() @@ -369,21 +364,50 @@ end ------------------------------------------------------------------------ function connect_timeout() + io.write("connect with timeout (if it hangs, it failed): ") local t = socket.time() local c, e = socket.tcp() assert(c, e) c:settimeout(0.1) local r, e = c:connect("ibere.tecgraf.puc-rio.br", 80) - if r or e ~= "timeout" then - print("wrong error message (this test is flaky anyways)") - end - if socket.time() - t > 1 then - print("took to long to give up") - end - print("whatever") + assert(not r, "should not connect") + assert(e == "timeout", e) + assert(socket.time() - t < 2, "took to long to give up") c:close() end +------------------------------------------------------------------------ +function accept_errors() + io.write("not listenning: ") + local d, e = socket.bind("*", 0) + assert(d, e); + local c, e = socket.tcp(); + assert(c, e); + d:setfd(c:getfd()) + local r, e = d:accept() + assert(not r and e == "not listening", e) + print("ok") + io.write("not supported: ") + local c, e = socket.udp() + assert(c, e); + d:setfd(c:getfd()) + local r, e = d:accept() + assert(not r and e == "not supported", e) + print("ok") +end + +------------------------------------------------------------------------ +function connect_errors() + io.write("connection refused: ") + local c, e = socket.connect("localhost", 1); + assert(not c and e == "connection refused", e) + print("ok") + io.write("host not found: ") + local c, e = socket.connect("not.exist.com", 1); + assert(not c and e == "host not found", e) + print("ok") +end + ------------------------------------------------------------------------ function rebind_test() local c = socket.bind("localhost", 0) @@ -400,40 +424,44 @@ end ------------------------------------------------------------------------ test("method registration") test_methods(socket.tcp(), { - "connect", - "send", - "receive", + "accept", "bind", - "accept", - "setpeername", - "setsockname", + "close", + "connect", "getpeername", "getsockname", + "listen", + "receive", + "send", "setoption", + "setpeername", + "setsockname", "settimeout", "shutdown", - "close", }) + test_methods(socket.udp(), { + "close", "getpeername", "getsockname", - "setsockname", - "setpeername", - "send", - "sendto", "receive", "receivefrom", + "send", + "sendto", "setoption", + "setpeername", + "setsockname", "settimeout", "shutdown", - "close", }) test("select function") test_selectbugs() -test("empty host connect: ") +test("connect function") +connect_timeout() empty_connect() +connect_errors() test("rebinding: ") rebind_test() @@ -444,11 +472,10 @@ active_close() test("closed connection detection: ") test_closed() -test("accept with timeout (if it hangs, it failed:)") +test("accept function: ") accept_timeout() +accept_errors() -test("connect with timeout (if it hangs, it failed:)") -connect_timeout() test("mixed patterns") test_mixed(1)