summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Pointner <equinox@spreadspace.org>2010-12-26 23:28:23 +0000
committerChristian Pointner <equinox@spreadspace.org>2010-12-26 23:28:23 +0000
commit18e52063f3e7b78559e9de20792b218c33fc44f4 (patch)
tree80276b96046fce99ad67b11b0949d5eb2ffc30c1
parentremoving getfd functions (diff)
added initial tcp module
git-svn-id: https://svn.spreadspace.org/gcsd/trunk@81 ac14a137-c7f1-4531-abe0-07747231d213
-rw-r--r--TODO7
-rw-r--r--src/Makefile1
-rw-r--r--src/gcsd.c2
-rw-r--r--src/l_tcp.c283
-rw-r--r--src/l_tcp.h41
-rw-r--r--src/modules/tcp_listen.lua144
6 files changed, 475 insertions, 3 deletions
diff --git a/TODO b/TODO
index 82501a9..a1a1d94 100644
--- a/TODO
+++ b/TODO
@@ -5,9 +5,10 @@
* finish API for command & response dispatch tables
* module API etc. clean-up, finalize, freeze
* add modules
- - tcp
- - udp
- - unix
+ - tcp_connect
+ - udp_listen, udp_connect
+ - unix_listen, unix_connect
- inotify
+ * finilize tcp_listen module
* exec module, add support for parameters
* improve module loader (allow multiple search paths)
diff --git a/src/Makefile b/src/Makefile
index 7d2bd52..9ba9c9c 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -45,6 +45,7 @@ C_OBJS := log.o \
sig_handler.o \
l_sig_handler.o \
l_rawio.o \
+ l_tcp.o \
l_util.o \
l_timer.o \
gcsd.o
diff --git a/src/gcsd.c b/src/gcsd.c
index acbab40..b2200b6 100644
--- a/src/gcsd.c
+++ b/src/gcsd.c
@@ -44,6 +44,7 @@
#include "string_list.h"
#include "log.h"
#include "l_rawio.h"
+#include "l_tcp.h"
#include "l_util.h"
#include "l_timer.h"
#include "l_log.h"
@@ -65,6 +66,7 @@ static const luaL_Reg gcsd_lualibs[] = {
{LUA_STRLIBNAME, luaopen_string},
{LUA_MATHLIBNAME, luaopen_math},
{LUA_RAWIOLIBNAME, luaopen_rawio},
+ {LUA_TCPLIBNAME, luaopen_tcp},
{LUA_UTILLIBNAME, luaopen_util},
{LUA_TIMERLIBNAME, luaopen_timer},
{LUA_LOGLIBNAME, luaopen_log},
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;
+}
diff --git a/src/l_tcp.h b/src/l_tcp.h
new file mode 100644
index 0000000..8e3c78e
--- /dev/null
+++ b/src/l_tcp.h
@@ -0,0 +1,41 @@
+/*
+ * 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/>.
+ */
+
+#ifndef GCSD_l_tcp_h_INCLUDED
+#define GCSD_l_tcp_h_INCLUDED
+
+#include <lua.h>
+
+#define LUA_TCPLIBNAME "tcp"
+LUALIB_API int luaopen_tcp(lua_State *L);
+
+#endif
diff --git a/src/modules/tcp_listen.lua b/src/modules/tcp_listen.lua
new file mode 100644
index 0000000..2adfe0f
--- /dev/null
+++ b/src/modules/tcp_listen.lua
@@ -0,0 +1,144 @@
+--
+-- 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/>.
+--
+
+-- tcp_listen module class
+local tcp_listen = {}
+tcp_listen.properties = { type=defines.IN_MODULE, name="tcp-listen", max_instances=-1 }
+tcp_listen.next_id = 0
+
+-- create new instance of tcp_listen module class
+function tcp_listen:new(config, runtype)
+ local inst = {}
+ inst.class = self
+ inst.config = config
+ inst.config.runtype = runtype
+ if(config.name == nil or config.name == "") then
+ inst.name = self.properties.name .. self.next_id
+ self.next_id = self.next_id + 1
+ else
+ inst.name = config.name
+ end
+
+ inst.listeners = tcp.server(config.addr, config.port, config.resolv_type)
+ for _, l in ipairs(inst.listeners) do
+ function l:read()
+ local new_client, addr = tcp.accept(self.fd)
+ if(not new_client) then
+ log.printf(ERROR, "inst.name: %s", addr)
+ return defines.KILL_MODULE
+ end
+
+ local client_handle = {}
+ client_handle.fd = new_client
+ client_handle.client_instance = nil
+ client_handle.in_buffer = ""
+ client_handle.out_buffer = ""
+ function client_handle:read()
+ -- TODO: which size should we request??
+ local buffer, err = tcp.recv(self.fd, 100)
+ if(buffer == nil) then
+ log.printf(log.ERROR, inst.name .. ": connection error: %s", err)
+ return defines.KILL_CLIENT
+ end
+ if(#buffer == 0) then
+ log.printf(log.INFO, inst.name .. ": connection closed")
+ return defines.KILL_CLIENT
+ end
+
+ self.in_buffer = self.in_buffer .. buffer
+ if(inst.config.runtype == defines.IN_MODULE) then
+ self.in_buffer = command_table:dispatch(self.in_buffer)
+ else
+ self.in_buffer = response_table:dispatch(self.in_buffer)
+ end
+
+ return defines.OK
+ end
+ function client_handle:write()
+ local len, err = tcp.send(self.fd, self.out_buffer)
+ if(len == nil) then
+ log.printf(log.ERROR, inst.name .. ": connection error: %s", err)
+ ret = defines.KILL_CLIENT
+ else
+ self.out_buffer = string.sub(self.out_buffer, len+1)
+ end
+ if(inst.config.runtype == defines.OUT_MODULE and self.out_buffer == "") then
+ command_queue:command_sent()
+ end
+ return defines.OK
+ end
+
+ local client = {}
+ client.module_instance = inst
+ client.addr = addr
+ client.name = inst.name .. "#" .. tcp.endtostring(addr)
+ function client:process_response() end
+ function client:process_timeout() end
+ function client:get_read_handles()
+ return { client_handle }
+ end
+ function client:get_write_handles()
+ if(client_handle.out_buffer ~= "") then
+ return { client_handle }
+ else
+ return {}
+ end
+ end
+ function client:cleanup()
+ rawio.close(client_handle.fd)
+ end
+ client_handle.client_instance = client
+ client_list:register(client)
+ return defines.OK
+ end
+ function l:write() return defines.OK end
+ end
+
+ function inst:cleanup()
+ client_list:unregister_by_module(self)
+ for _, l in ipairs(self.listeners) do
+ rawio.close(l.fd);
+ end
+ end
+ function inst:get_read_handles()
+ return self.listeners
+ end
+ function inst:get_write_handles()
+ return {}
+ end
+ setmetatable(inst, {})
+ getmetatable(inst).__gc = function() inst:cleanup() end
+
+ return inst
+end
+
+return tcp_listen