summaryrefslogtreecommitdiff
path: root/src/l_tcp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/l_tcp.c')
-rw-r--r--src/l_tcp.c283
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;
+}