diff options
Diffstat (limited to 'src/l_tcp.c')
-rw-r--r-- | src/l_tcp.c | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/l_tcp.c b/src/l_tcp.c new file mode 100644 index 0000000..1a4da9f --- /dev/null +++ b/src/l_tcp.c @@ -0,0 +1,283 @@ +/* + * gcsd + * + * gcsd the generic command sequencer daemon can be used to serialize + * commands sent over various paralell communication channels to a + * single command output. It can be seen as a multiplexer for any + * kind of communication between a single resource and various clients + * which want to submit commands to it or query information from it. + * gcsd is written in C and Lua. The goal is to provide an easy to + * understand high level API based on Lua which can be used to + * implement the business logic of the so formed multiplexer daemon. + * + * + * Copyright (C) 2009-2010 Markus Grueneis <gimpf@spreadspace.org> + * Christian Pointner <equinox@spreadspace.org> + * + * This file is part of gcsd. + * + * gcsd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * gcsd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with gcsd. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <lua.h> +#include <lauxlib.h> + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#include "l_tcp.h" + +#include "datatypes.h" +#include "log.h" + +typedef struct { + socklen_t len_; + struct sockaddr_storage addr_; +} tcp_endpoint_t; +#define LUA_TCP_UDATA_NAME "tcp_endpoint_t" + + +static tcp_endpoint_t* newtcpendudata(lua_State *L) +{ + tcp_endpoint_t* end = (tcp_endpoint_t*)lua_newuserdata(L, sizeof(tcp_endpoint_t)); + memset(end, 0, sizeof(end)); + luaL_newmetatable(L, LUA_TCP_UDATA_NAME); + lua_setmetatable(L, -2); + return end; +} + +static char* tcp_endpoint_to_string(tcp_endpoint_t* e) +{ + size_t addrstr_len = 0; + char addrstr[INET6_ADDRSTRLEN + 1], portstr[6], *ret; + char addrport_sep = ':'; + + switch(e->addr_.ss_family) + { + case AF_INET: addrport_sep = ':'; break; + case AF_INET6: addrport_sep = '.'; break; + case AF_UNSPEC: return NULL; + default: asprintf(&ret, "unknown address type"); return ret; + } + + int errcode = getnameinfo((struct sockaddr *)&(e->addr_), e->len_, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); + if (errcode != 0) return NULL; + asprintf(&ret, "%s%c%s", addrstr, addrport_sep ,portstr); + return ret; +} + +static int init_listener(tcp_endpoint_t* end) +{ + int fd = socket(end->addr_.ss_family, SOCK_STREAM, 0); + if(fd < 0) { + log_printf(ERROR, "tcp: Error on opening socket: %s", strerror(errno)); + return -1; + } + + int on = 1; + int ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if(ret) { + log_printf(ERROR, "tcp: Error on setsockopt(): %s", strerror(errno)); + return -1; + } + if(end->addr_.ss_family == AF_INET6) { + if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on))) + log_printf(WARNING, "tcp: failed to set IPV6_V6ONLY socket option: %s", strerror(errno)); + } + + char* ls = tcp_endpoint_to_string(end); + ret = bind(fd, (struct sockaddr *)&(end->addr_), end->len_); + if(ret) { + log_printf(ERROR, "tcp: Error on bind(%s): %s", ls ? ls:"", strerror(errno)); + if(ls) free(ls); + return -1; + } + + ret = listen(fd, 0); + if(ret) { + log_printf(ERROR, "tcp: Error on listen(): %s", strerror(errno)); + if(ls) free(ls); + return -1; + } + + log_printf(NOTICE, "tcp: listening on: %s", ls ? ls:"(null)"); + if(ls) free(ls); + + return fd; +} + +static int l_tcp_server(lua_State *L) +{ + const char* addr = luaL_optstring(L, 1, "*"); + if(!strcmp(addr, "*") || !strlen(addr)) addr = NULL; + const char* port = luaL_checkstring(L, 2); + int af = luaL_optint(L, 3, 0); + + struct addrinfo hints, *res; + res = NULL; + memset (&hints, 0, sizeof (hints)); + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; + switch(af) { + case 4: hints.ai_family = AF_INET; break; + case 6: hints.ai_family = AF_INET6; break; + default: hints.ai_family = AF_UNSPEC; break; + } + + int errcode = getaddrinfo(addr, port, &hints, &res); + // TODO: better error handling (no lua error) + if(errcode != 0) + luaL_error(L, "tcp: resolver error: %s", gai_strerror(errcode)); + if(!res) + luaL_error(L, "tcp: no address found!"); + + lua_newtable(L); + + int idx = 1; + struct addrinfo* r = res; + while(r) { + lua_pushinteger(L, idx++); + lua_newtable(L); + + lua_pushliteral(L, "local_end"); + tcp_endpoint_t* end = newtcpendudata(L); + memcpy(&(end->addr_), r->ai_addr, r->ai_addrlen); + end->len_ = r->ai_addrlen; + lua_settable(L, -3); + + int fd = init_listener(end); + if(fd < 0) { + freeaddrinfo(res); + luaL_error(L, "tcp: Error at server init"); + } + + lua_pushliteral(L, "fd"); + lua_pushinteger(L, fd); + lua_settable(L, -3); + + lua_settable(L, -3); + r = r->ai_next; + } + freeaddrinfo(res); + + return 1; +} + +static int l_tcp_accept(lua_State *L) +{ + int fd = luaL_checkint(L, 1); + tcp_endpoint_t* remote_addr = newtcpendudata(L); + remote_addr->len_ = sizeof(remote_addr->addr_); + int new_client = accept(fd, (struct sockaddr *)&(remote_addr->addr_), &remote_addr->len_); + if(new_client == -1) { + lua_pushnil(L); + lua_pushstring(L, strerror(errno)); + return 2; + } + char* rs = tcp_endpoint_to_string(remote_addr); + log_printf(INFO, "new client from %s (fd=%d)", rs ? rs:"(null)", new_client); + if(rs) free(rs); + + lua_pushinteger(L, new_client); + lua_insert(L, -2); + return 2; +} + +static int l_tcp_client(lua_State *L) +{ + // TODO: implement this + return 0; +} + +static int l_tcp_connect(lua_State *L) +{ + // TODO: implement this + return 0; +} + +static int l_tcp_recv(lua_State *L) +{ + int fd = luaL_checkint(L,1); + size_t len = luaL_checkint(L,2); + char* data = malloc(len); + if(!data) { + lua_pushnil(L); + lua_pushstring(L, "bad alloc"); + return 2; + } + + int ret = recv(fd, data, len, 0); + + if(ret == -1) { + lua_pushnil(L); +// FIXXXXXME: strerror is not threadsafe!!! + lua_pushstring(L, strerror(errno)); + free(data); + return 2; + } + lua_pushlstring(L, data, ret); + free(data); + return 1; +} + +static int l_tcp_send(lua_State *L) +{ + int fd = luaL_checkint(L,1); + size_t len = 0; + const char* data = luaL_checklstring(L, 2, &len); + + int ret = send(fd, data, len, 0); + + if(ret == -1) { + lua_pushnil(L); +// FIXXXXXME: strerror is not threadsafe!!! + lua_pushstring(L, strerror(errno)); + return 2; + } + lua_pushinteger(L, ret); + return 1; +} + +static int l_tcp_endtostring(lua_State *L) +{ + tcp_endpoint_t* e = (tcp_endpoint_t*)luaL_checkudata(L, 1, LUA_TCP_UDATA_NAME); + char* es = tcp_endpoint_to_string(e); + if(!es) + luaL_error(L, "bad alloc"); + + lua_pushstring(L, es); + free(es); + return 1; +} + +static const struct luaL_reg tcp_funcs [] = { + { "server", l_tcp_server }, + { "accept", l_tcp_accept }, + { "client", l_tcp_client }, + { "connect", l_tcp_connect }, + { "recv", l_tcp_recv }, + { "send", l_tcp_send }, + { "endtostring", l_tcp_endtostring }, + { NULL, NULL } +}; + +LUALIB_API int luaopen_tcp(lua_State *L) +{ + luaL_register(L, LUA_TCPLIBNAME, tcp_funcs); + return 1; +} |