/* * 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 . */ #define _GNU_SOURCE #include #include #include #include #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) { 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: return strdup("unknown address type"); } int errcode = getnameinfo((struct sockaddr *)&(e->addr_), e->len_, addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST | NI_NUMERICSERV); if (errcode != 0) return NULL; int len = asprintf(&ret, "%s%c%s", addrstr, addrport_sep ,portstr); if(len == -1) return NULL; return ret; } static struct addrinfo* tcp_resolve_endpoint(const char* addr, const char* port, int af, int passive) { struct addrinfo hints, *res; res = NULL; memset (&hints, 0, sizeof (hints)); hints.ai_socktype = SOCK_STREAM; if(passive) 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); if(errcode != 0 || !res) { log_printf(ERROR, "tcp: resolver error: %s", errcode ? gai_strerror(errcode):"no address found!"); return NULL; } return res; } 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; } free(ls); ret = listen(fd, 0); if(ret) { log_printf(ERROR, "tcp: error on listen(): %s", strerror(errno)); if(ls) free(ls); return -1; } 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 *res = tcp_resolve_endpoint(addr, port, af, 1); if(!res) { lua_pushnil(L); lua_pushstring(L, "error at tcp-server initialization"); return 2; } 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); lua_pushnil(L); lua_pushstring(L, "error at tcp-server initialization"); return 2; } 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 init_client(tcp_endpoint_t remote, tcp_endpoint_t source) { int fd = socket(remote.addr_.ss_family, SOCK_STREAM, 0); if(fd < 0) { log_printf(INFO, "error on socket(): %s", strerror(errno)); return -1; } int on = 1; if(setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on))) { log_printf(ERROR, "error on setsockopt(): %s", strerror(errno)); close(fd); return -1; } if(fcntl(fd, F_SETFL, O_NONBLOCK)) { log_printf(ERROR, "error on fcntl(): %s", strerror(errno)); close(fd); return -1; } if(source.addr_.ss_family != AF_UNSPEC) { if(bind(fd, (struct sockaddr *)&(source.addr_), source.len_)==-1) { log_printf(INFO, "error on bind(): %s", strerror(errno)); close(fd); return -1; } } return fd; } static int l_tcp_client(lua_State *L) { const char* raddr = luaL_checkstring(L, 1); const char* rport = luaL_checkstring(L, 2); int af = luaL_optint(L, 3, 0); const char* saddr = luaL_optstring(L, 4, NULL); tcp_endpoint_t remote, source; memset(&remote, 0, sizeof(tcp_endpoint_t)); memset(&source, 0, sizeof(tcp_endpoint_t)); source.addr_.ss_family = AF_UNSPEC; struct addrinfo *res = tcp_resolve_endpoint(raddr, rport, af, 0); if(!res) { lua_pushnil(L); lua_pushstring(L, "error at tcp-client initialization"); return 2; } memcpy(&(remote.addr_), res->ai_addr, res->ai_addrlen); remote.len_ = res->ai_addrlen; freeaddrinfo(res); if(saddr) { res = tcp_resolve_endpoint(saddr, NULL, af, 0); if(!res) { lua_pushnil(L); lua_pushstring(L, "error at tcp-client initialization"); return 2; } memcpy(&(source.addr_), res->ai_addr, res->ai_addrlen); source.len_ = res->ai_addrlen; freeaddrinfo(res); } int fd = init_client(remote, source); if(fd < 0) { lua_pushnil(L); lua_pushstring(L, "error at tcp-client initialization"); return 2; } lua_newtable(L); lua_pushliteral(L, "remote_end"); tcp_endpoint_t* re = newtcpendudata(L); memcpy(re, &remote, sizeof(tcp_endpoint_t)); lua_settable(L, -3); if(source.addr_.ss_family != AF_UNSPEC) { lua_pushliteral(L, "source_end"); tcp_endpoint_t* se = newtcpendudata(L); memcpy(se, &source, sizeof(tcp_endpoint_t)); lua_settable(L, -3); } lua_pushliteral(L, "fd"); lua_pushinteger(L, fd); lua_settable(L, -3); if(connect(fd, (struct sockaddr *)&(remote.addr_), remote.len_)==-1) { if(errno == EINPROGRESS) { lua_pushboolean(L, 0); return 2; } close(fd); log_printf(INFO, "error on connect(): %s", strerror(errno)); lua_pushnil(L); lua_pushstring(L, "connect() failed"); return 2; } lua_pushboolean(L, 1); return 2; } static int l_tcp_connect(lua_State *L) { int fd = luaL_checkint(L, 1); int error = 0; socklen_t len = sizeof(error); if(getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len)==-1) { log_printf(ERROR, "error on getsockopt(): %s", strerror(errno)); lua_pushnil(L); lua_pushstring(L, "can't read SO_ERROR"); return 2; } if(error) { log_printf(ERROR, "error on connect(): %s", strerror(error)); lua_pushnil(L); lua_pushstring(L, "connect() failed"); return 2; } lua_pushboolean(L, 1); return 1; } 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) { #if LUA_VERSION_NUM > 501 lua_newtable(L); luaL_setfuncs(L, tcp_funcs, 0); lua_pushvalue(L, -1); lua_setglobal(L, LUA_TCPLIBNAME); #else luaL_register(L, LUA_TCPLIBNAME, tcp_funcs); #endif return 1; }