From 18e52063f3e7b78559e9de20792b218c33fc44f4 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 26 Dec 2010 23:28:23 +0000 Subject: added initial tcp module git-svn-id: https://svn.spreadspace.org/gcsd/trunk@81 ac14a137-c7f1-4531-abe0-07747231d213 --- TODO | 7 +- src/Makefile | 1 + src/gcsd.c | 2 + src/l_tcp.c | 283 +++++++++++++++++++++++++++++++++++++++++++++ src/l_tcp.h | 41 +++++++ src/modules/tcp_listen.lua | 144 +++++++++++++++++++++++ 6 files changed, 475 insertions(+), 3 deletions(-) create mode 100644 src/l_tcp.c create mode 100644 src/l_tcp.h create mode 100644 src/modules/tcp_listen.lua 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 + * Christian Pointner + * + * 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 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 + * Christian Pointner + * + * 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 . + */ + +#ifndef GCSD_l_tcp_h_INCLUDED +#define GCSD_l_tcp_h_INCLUDED + +#include + +#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 +-- Christian Pointner +-- +-- 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 . +-- + +-- 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 -- cgit v1.2.3