/*=========================================================================*\ * TCP object * LuaSocket toolkit * * RCS ID: $Id: tcp.c,v 1.42 2009/05/27 09:31:35 diego Exp $ \*=========================================================================*/ #include #include "lua.h" #include "lauxlib.h" #include "auxiliar.h" #include "socket.h" #include "inet.h" #include "options.h" #include "tcp.h" /*=========================================================================*\ * Internal function prototypes \*=========================================================================*/ static int global_create(lua_State *L); static int global_connect6(lua_State *L); static int global_bind6(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_getstats(lua_State *L); static int meth_setstats(lua_State *L); static int meth_getsockname(lua_State *L); static int meth_getpeername(lua_State *L); static int meth_shutdown(lua_State *L); static int meth_receive(lua_State *L); 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_getfd(lua_State *L); static int meth_setfd(lua_State *L); static int meth_dirty(lua_State *L); /* tcp object methods */ static luaL_reg tcp_methods[] = { {"__gc", meth_close}, {"__tostring", auxiliar_tostring}, {"accept", meth_accept}, {"bind", meth_bind}, {"close", meth_close}, {"connect", meth_connect}, {"dirty", meth_dirty}, {"getfd", meth_getfd}, {"getpeername", meth_getpeername}, {"getsockname", meth_getsockname}, {"getstats", meth_getstats}, {"setstats", meth_setstats}, {"listen", meth_listen}, {"receive", meth_receive}, {"send", meth_send}, {"setfd", meth_setfd}, {"setoption", meth_setoption}, {"setpeername", meth_connect}, {"setsockname", meth_bind}, {"settimeout", meth_settimeout}, {"shutdown", meth_shutdown}, {NULL, NULL} }; /* socket option handlers */ static t_opt optset[] = { {"keepalive", opt_set_keepalive}, {"reuseaddr", opt_set_reuseaddr}, {"tcp-nodelay", opt_set_tcp_nodelay}, {"linger", opt_set_linger}, {NULL, NULL} }; /* functions in library namespace */ static luaL_reg func[] = { {"tcp", global_create}, {"connect6", global_connect6}, {"bind6", global_bind6}, {NULL, NULL} }; /*-------------------------------------------------------------------------*\ * Initializes module \*-------------------------------------------------------------------------*/ int tcp_open(lua_State *L) { /* create classes */ auxiliar_newclass(L, "tcp{master}", tcp_methods); auxiliar_newclass(L, "tcp{client}", tcp_methods); auxiliar_newclass(L, "tcp{server}", tcp_methods); /* create class groups */ auxiliar_add2group(L, "tcp{master}", "tcp{any}"); auxiliar_add2group(L, "tcp{client}", "tcp{any}"); auxiliar_add2group(L, "tcp{server}", "tcp{any}"); /* define library functions */ luaL_openlib(L, NULL, func, 0); return 0; } /*=========================================================================*\ * Lua methods \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Just call buffered IO methods \*-------------------------------------------------------------------------*/ static int meth_send(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_send(L, &tcp->buf); } static int meth_receive(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_receive(L, &tcp->buf); } static int meth_getstats(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_getstats(L, &tcp->buf); } static int meth_setstats(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); return buffer_meth_setstats(L, &tcp->buf); } /*-------------------------------------------------------------------------*\ * Just call option handler \*-------------------------------------------------------------------------*/ static int meth_setoption(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return opt_meth_setoption(L, optset, &tcp->sock); } /*-------------------------------------------------------------------------*\ * Select support methods \*-------------------------------------------------------------------------*/ static int meth_getfd(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); lua_pushnumber(L, (int) 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) auxiliar_checkgroup(L, "tcp{any}", 1); tcp->sock = (t_socket) luaL_checknumber(L, 2); return 0; } static int meth_dirty(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); lua_pushboolean(L, !buffer_isempty(&tcp->buf)); return 1; } /*-------------------------------------------------------------------------*\ * Waits for and returns a client object attempting connection to the * server object \*-------------------------------------------------------------------------*/ static int meth_accept(lua_State *L) { p_tcp server = (p_tcp) auxiliar_checkclass(L, "tcp{server}", 1); p_timeout tm = timeout_markstart(&server->tm); t_socket sock; int err = socket_accept(&server->sock, &sock, NULL, NULL, tm); /* if successful, push client socket */ if (err == IO_DONE) { p_tcp clnt = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); auxiliar_setclass(L, "tcp{client}", -1); /* initialize structure fields */ socket_setnonblocking(&sock); clnt->sock = sock; io_init(&clnt->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &clnt->sock); timeout_init(&clnt->tm, -1, -1); buffer_init(&clnt->buf, &clnt->io, &clnt->tm); return 1; } else { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } } /*-------------------------------------------------------------------------*\ * Binds an object to an address \*-------------------------------------------------------------------------*/ static int meth_bind(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); const char *address = luaL_checkstring(L, 2); unsigned short port = (unsigned short) luaL_checknumber(L, 3); const char *err = inet_trybind(&tcp->sock, address, port); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Turns a master tcp object into a client object. \*-------------------------------------------------------------------------*/ static int meth_connect(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); const char *address = luaL_checkstring(L, 2); unsigned short port = (unsigned short) luaL_checknumber(L, 3); p_timeout tm = timeout_markstart(&tcp->tm); const char *err = inet_tryconnect(&tcp->sock, address, port, tm); /* have to set the class even if it failed due to non-blocking connects */ auxiliar_setclass(L, "tcp{client}", 1); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Closes socket used by object \*-------------------------------------------------------------------------*/ static int meth_close(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); socket_destroy(&tcp->sock); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Puts the sockt in listen mode \*-------------------------------------------------------------------------*/ static int meth_listen(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{master}", 1); int backlog = (int) luaL_optnumber(L, 2, 32); int err = socket_listen(&tcp->sock, backlog); if (err != IO_DONE) { lua_pushnil(L); lua_pushstring(L, socket_strerror(err)); return 2; } /* turn master object into a server object */ auxiliar_setclass(L, "tcp{server}", 1); lua_pushnumber(L, 1); return 1; } /*-------------------------------------------------------------------------*\ * Shuts the connection down partially \*-------------------------------------------------------------------------*/ static int meth_shutdown(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkclass(L, "tcp{client}", 1); const char *how = luaL_optstring(L, 2, "both"); switch (how[0]) { case 'b': if (strcmp(how, "both")) goto error; socket_shutdown(&tcp->sock, 2); break; case 's': if (strcmp(how, "send")) goto error; socket_shutdown(&tcp->sock, 1); break; case 'r': if (strcmp(how, "receive")) goto error; socket_shutdown(&tcp->sock, 0); break; } lua_pushnumber(L, 1); return 1; error: luaL_argerror(L, 2, "invalid shutdown method"); return 0; } /*-------------------------------------------------------------------------*\ * Just call inet methods \*-------------------------------------------------------------------------*/ static int meth_getpeername(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return inet_meth_getpeername(L, &tcp->sock); } static int meth_getsockname(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return inet_meth_getsockname(L, &tcp->sock); } /*-------------------------------------------------------------------------*\ * Just call tm methods \*-------------------------------------------------------------------------*/ static int meth_settimeout(lua_State *L) { p_tcp tcp = (p_tcp) auxiliar_checkgroup(L, "tcp{any}", 1); return timeout_meth_settimeout(L, &tcp->tm); } /*=========================================================================*\ * Library functions \*=========================================================================*/ /*-------------------------------------------------------------------------*\ * Creates a master tcp object \*-------------------------------------------------------------------------*/ static int global_create(lua_State *L) { t_socket sock; const char *err = inet_trycreate(&sock, SOCK_STREAM); /* try to allocate a system socket */ if (!err) { /* allocate tcp object */ p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); /* set its type as master object */ auxiliar_setclass(L, "tcp{master}", -1); /* initialize remaining structure fields */ socket_setnonblocking(&sock); tcp->sock = sock; io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &tcp->sock); timeout_init(&tcp->tm, -1, -1); buffer_init(&tcp->buf, &tcp->io, &tcp->tm); return 1; } else { lua_pushnil(L); lua_pushstring(L, err); return 2; } } static const char *trybind6(const char *localaddr, const char *localserv, struct addrinfo *bindhints, p_tcp tcp) { struct addrinfo *iterator = NULL, *resolved = NULL; const char *err = NULL; /* translate luasocket special values to C */ if (strcmp(localaddr, "*") == 0) localaddr = NULL; if (!localserv) localserv = "0"; /* try resolving */ err = socket_gaistrerror(getaddrinfo(localaddr, localserv, bindhints, &resolved)); if (err) { if (resolved) freeaddrinfo(resolved); return err; } /* iterate over resolved addresses until one is good */ for (iterator = resolved; iterator; iterator = iterator->ai_next) { /* create a new socket each time because parameters * may have changed */ err = socket_strerror(socket_create(&tcp->sock, iterator->ai_family, iterator->ai_socktype, iterator->ai_protocol)); /* if failed to create socket, bail out */ if (err != NULL) { freeaddrinfo(resolved); return err; } /* all sockets are set as non-blocking initially */ socket_setnonblocking(&tcp->sock); /* try binding to local address */ err = socket_strerror(socket_bind(&tcp->sock, (SA *) iterator->ai_addr, iterator->ai_addrlen)); /* if faiiled, we try the next one */ if (err != NULL) socket_destroy(&tcp->sock); /* if success, we abort loop */ else break; } /* at this point, if err is not set, se succeeded */ if (err == NULL) { /* save family of chosen local address */ bindhints->ai_family = iterator->ai_family; } /* cleanup and return error */ freeaddrinfo(resolved); return err; } static int global_bind6(lua_State *L) { const char *localaddr = luaL_checkstring(L, 1); const char *localserv = luaL_checkstring(L, 2); int backlog = luaL_checkint(L, 3); p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); struct addrinfo bindhints; const char *err = NULL; /* initialize tcp structure */ io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &tcp->sock); timeout_init(&tcp->tm, -1, -1); buffer_init(&tcp->buf, &tcp->io, &tcp->tm); tcp->sock = SOCKET_INVALID; /* try binding to local address */ memset(&bindhints, 0, sizeof(bindhints)); bindhints.ai_socktype = SOCK_STREAM; bindhints.ai_family = PF_UNSPEC; bindhints.ai_flags = AI_PASSIVE; err = trybind6(localaddr, localserv, &bindhints, tcp); if (err == NULL) { /* all server sockets initially with reuseaddr set */ int val = 1; setsockopt(tcp->sock, SOL_SOCKET, SO_REUSEADDR, (char *) &val, sizeof(val)); /* set the backlog and listen */ err = socket_strerror(socket_listen(&tcp->sock, backlog)); if (err) { socket_destroy(&tcp->sock); lua_pushnil(L); lua_pushstring(L, err); return 2; } auxiliar_setclass(L, "tcp{server}", -1); return 1; } else { lua_pushnil(L); lua_pushstring(L, err); return 2; } } static const char *tryconnect6(const char *remoteaddr, const char *remoteserv, struct addrinfo *connecthints, p_tcp tcp) { struct addrinfo *iterator = NULL, *resolved = NULL; const char *err = NULL; /* try resolving */ err = socket_gaistrerror(getaddrinfo(remoteaddr, remoteserv, connecthints, &resolved)); if (err != NULL) { if (resolved) freeaddrinfo(resolved); return err; } /* iterate over all returned addresses trying to connect */ for (iterator = resolved; iterator; iterator = iterator->ai_next) { p_timeout tm = timeout_markstart(&tcp->tm); /* create new socket if one wasn't created by the bind stage */ if (tcp->sock == SOCKET_INVALID) { err = socket_strerror(socket_create(&tcp->sock, iterator->ai_family, iterator->ai_socktype, iterator->ai_protocol)); if (err != NULL) { freeaddrinfo(resolved); return err; } /* all sockets initially non-blocking */ socket_setnonblocking(&tcp->sock); } /* finally try connecting to remote address */ err = socket_strerror(socket_connect(&tcp->sock, (SA *) iterator->ai_addr, iterator->ai_addrlen, tm)); /* if success, break out of loop */ if (err == NULL) break; } freeaddrinfo(resolved); /* here, if err is set, we failed */ return err; } static int global_connect6(lua_State *L) { const char *remoteaddr = luaL_checkstring(L, 1); const char *remoteserv = luaL_checkstring(L, 2); const char *localaddr = luaL_optstring(L, 3, NULL); const char *localserv = luaL_optstring(L, 4, "0"); p_tcp tcp = (p_tcp) lua_newuserdata(L, sizeof(t_tcp)); struct addrinfo bindhints, connecthints; const char *err = NULL; /* initialize tcp structure */ io_init(&tcp->io, (p_send) socket_send, (p_recv) socket_recv, (p_error) socket_ioerror, &tcp->sock); timeout_init(&tcp->tm, -1, -1); buffer_init(&tcp->buf, &tcp->io, &tcp->tm); tcp->sock = SOCKET_INVALID; /* allow user to pick local address and port */ memset(&bindhints, 0, sizeof(bindhints)); bindhints.ai_socktype = SOCK_STREAM; bindhints.ai_family = PF_UNSPEC; bindhints.ai_flags = AI_PASSIVE; if (localaddr) { err = trybind6(localaddr, localserv, &bindhints, tcp); if (err) { lua_pushnil(L); lua_pushstring(L, err); return 2; } } /* try to connect to remote address and port */ memset(&connecthints, 0, sizeof(connecthints)); connecthints.ai_socktype = SOCK_STREAM; /* make sure we try to connect only to the same family */ connecthints.ai_family = bindhints.ai_family; err = tryconnect6(remoteaddr, remoteserv, &connecthints, tcp); if (err) { socket_destroy(&tcp->sock); lua_pushnil(L); lua_pushstring(L, err); return 2; } auxiliar_setclass(L, "tcp{client}", -1); return 1; }