diff --git a/FD_PASSING.txt b/FD_PASSING.txt new file mode 100644 index 0000000..a59353d --- /dev/null +++ b/FD_PASSING.txt @@ -0,0 +1,103 @@ +File Descriptor Passing + +File descriptors can be sent from one process to another by two means. One way is by inheritance, the other is by passing through a unix domain socket. There are three reasons I know of why one might do this. The first is that on platforms that don't have a credentials passing mechanism but do have a file descriptor passing mechanism, an authentication scheme based on file system privilege demonstration could be used instead. The second is if one process has file system privileges that the other does not. The third is scenarios where a server will hand a connection's file descriptor to another already started helper process of some kind. Again this area is different from OS to OS. On Linux this is done with a socket feature known as ancillary data. + +It works by one side sending some data to the other (at least 1 byte) with attached ancillary data. Normally this facility is used for odd features of various underlying network protocols, such as TCP/IP's out of band data. This is accomplished with the lower level socket function sendmsg() that accepts both arrays of IO vectors and control data message objects as members of its struct msghdr parameter. Ancillary, also known as control, data in sockets takes the form of a struct cmsghdr. The members of this structure can mean different things based on what type of socket it is used with. Making it even more squirrelly is that most of these structures need to be modified with macros. Here are two example functions based on the ones available in Warren Gay's book mention at the end of this article. A socket's peer that read data sent to it by send_fd() without using recv_fd() would just get a single capital F. + + + int send_fd(int socket, int fd_to_send) + { + struct msghdr socket_message; + struct iovec io_vector[1]; + struct cmsghdr * control_message = NULL; + char message_buffer[1]; + + /* + storage space needed for an ancillary element with + a paylod of length is CMSG_SPACE(sizeof(length)) + */ + char ancillary_element_buffer[CMSG_SPACE(sizeof(int))]; + int available_ancillary_element_buffer_space; + + /* at least one vector of one byte must be sent */ + message_buffer[0] = 'F'; + io_vector[0].iov_base = message_buffer; + io_vector[0].iov_len = 1; + + /* initialize socket message */ + memset(&socket_message, 0, sizeof(struct msghdr)); + socket_message.msg_iov = io_vector; + socket_message.msg_iovlen = 1; + + /* provide space for the ancillary data */ + available_ancillary_element_buffer_space = CMSG_SPACE(sizeof(int)); + memset(ancillary_element_buffer, 0, available_ancillary_element_buffer_space); + socket_message.msg_control = ancillary_element_buffer; + socket_message.msg_controllen = available_ancillary_element_buffer_space; + + /* initialize a single ancillary data element for fd passing */ + control_message = CMSG_FIRSTHDR(&socket_message); + control_message->cmsg_level = SOL_SOCKET; + control_message->cmsg_type = SCM_RIGHTS; + control_message->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *) CMSG_DATA(control_message)) = fd_to_send; + + return sendmsg(socket, &socket_message, 0); + } + + + + int recv_fd(int socket) + { + int sent_fd, available_ancillary_element_buffer_space; + struct msghdr socket_message; + struct iovec io_vector[1]; + struct cmsghdr *control_message = NULL; + char message_buffer[1]; + char ancillary_element_buffer[CMSG_SPACE(sizeof(int))]; + + /* start clean */ + memset(&socket_message, 0, sizeof(struct msghdr)); + memset(ancillary_element_buffer, 0, CMSG_SPACE(sizeof(int))); + + /* setup a place to fill in message contents */ + io_vector[0].iov_base = message_buffer; + io_vector[0].iov_len = 1; + socket_message.msg_iov = io_vector; + socket_message.msg_iovlen = 1; + + /* provide space for the ancillary data */ + socket_message.msg_control = ancillary_element_buffer; + socket_message.msg_controllen = CMSG_SPACE(sizeof(int)); + + if(recvmsg(socket, &socket_message, MSG_CMSG_CLOEXEC) < 0) + return -1; + + if(message_buffer[0] != 'F') + { + /* this did not originate from the above function */ + return -1; + } + + if((socket_message.msg_flags & MSG_CTRUNC) == MSG_CTRUNC) + { + /* we did not provide enough space for the ancillary element array */ + return -1; + } + + /* iterate ancillary elements */ + for(control_message = CMSG_FIRSTHDR(&socket_message); + control_message != NULL; + control_message = CMSG_NXTHDR(&socket_message, control_message)) + { + if( (control_message->cmsg_level == SOL_SOCKET) && + (control_message->cmsg_type == SCM_RIGHTS) ) + { + sent_fd = *((int *) CMSG_DATA(control_message)); + return sent_fd; + } + } + + return -1; + } + diff --git a/configure.ac b/configure.ac index 6dd80a5..daa42c7 100644 --- a/configure.ac +++ b/configure.ac @@ -16,15 +16,13 @@ AC_PROG_CC AC_PROG_CC_C99 AC_PROG_LIBTOOL AM_PROG_CC_C_O -AC_PROG_RANLIB # Checks for libraries. AC_CHECK_LIB([json], [json_object_new_object], [], [AC_MSG_ERROR([json-c library not found], [1])]) # Checks for header files. -AC_CHECK_HEADERS([stdarg.h string.h stdlib.h stdio.h unistd.h syslog.h - sys/types.h json/json.h]) +AC_CHECK_HEADERS([stdarg.h string.h stdlib.h stdio.h unistd.h syslog.h sys/types.h json/json.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL @@ -33,8 +31,8 @@ AC_TYPE_PID_T AC_TYPE_SIZE_T # Checks for library functions. -AC_FUNC_FORK -AC_FUNC_MALLOC +#AC_FUNC_FORK +#AC_FUNC_MALLOC AC_CHECK_FUNCS([memset]) AC_CONFIG_FILES([Makefile tests/Makefile]) diff --git a/include/logger.h b/include/logger.h index c7c84cf..67fb033 100644 --- a/include/logger.h +++ b/include/logger.h @@ -1,7 +1,7 @@ #ifndef __LOGGER_H__ #define __LOGGER_H__ -#include +#include "cclass.h" #define LOGGER_EMERG 0 diff --git a/include/server.h b/include/server.h new file mode 100644 index 0000000..3c31322 --- /dev/null +++ b/include/server.h @@ -0,0 +1,34 @@ +#ifndef __SERVER_H__ +#define __SERVER_H__ + +#include /* for printf() and fprintf() */ +#include /* for select system call and related */ + +#include "socket.h" +#include "logger.h" +#include "cclass.h" + + +CLASS(SERVER) { + LOGGER logger; + SOCK sock; + unsigned int max_fd; + fd_set fdset; + + struct { + SOCK sock; + char * wbuf; + char * rbuf; + unsigned int rpos; + unsigned int wpos; + } conns[FD_SETSIZE]; +}; + + +void server_run(SERVER this); +void server_close(SERVER this); +void server_shutdown(SERVER this); + +#endif // __SERVER_H__ + +// vim: set ts=4 sw=4: diff --git a/include/socket.h b/include/socket.h new file mode 100644 index 0000000..7e5d4b3 --- /dev/null +++ b/include/socket.h @@ -0,0 +1,22 @@ +#ifndef __SOCKET_H__ +#define __SOCKET_H__ + +#include /* for in_port_t */ + +#include "logger.h" +#include "cclass.h" + + +CLASS(SOCK) { + LOGGER logger; + in_port_t port; + struct sockaddr_in addr; + int handle; /* socket handle for server */ +}; + +void sock_initServer(SOCK this, int backlog); +SOCK sock_accept(SOCK this, char remoteAddr[16]); + +#endif /* __SOCKET_H__ */ + +// vim: set ts=4 sw=4: diff --git a/src/server.c b/src/server.c new file mode 100644 index 0000000..bb12637 --- /dev/null +++ b/src/server.c @@ -0,0 +1,53 @@ +#include /* for select system call and related */ +#include /* for memset and stuff */ +#include /* for getopt */ + +#include "server.h" +#include "socket.h" +#include "server.h" +#include "logger.h" +#include "cclass.h" + + +INIT_CLASS(SERVER); + +__construct(SERVER) +{ + in_port_t port; + unsigned int backlog; + + this->logger = va_arg(* params, LOGGER); + port = va_arg(* params, int); + backlog = va_arg(* params, unsigned int); + + FD_ZERO(&(this->fdset)); + + this->sock = new(SOCK, port); + sock_initServer(this->sock, backlog); + + this->max_fd = this->sock->handle; + FD_SET(this->sock->handle, &(this->fdset)); +} + +__destruct(SERVER) +{ + int i; + + for (i=3; i<=this->max_fd; i++) { + if (FD_ISSET(i, &(this->fdset)) && i != this->sock->handle) { + /* + * @TODO do some finalization...buffer handling...etc. + */ + delete(&(this->conns[i]).sock); + FD_CLR(i, &(this->fdset)); + } + } + + delete(&this->sock); +} + +__jsonConst(SERVER) {} +__toJson(SERVER) {} +__clear(SERVER) {} + +// vim: set ts=4 sw=4: diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..3331734 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,113 @@ +#include /* for printf() and fprintf() */ +#include /* SO_REUSEADDR */ +#include /* for socket(), bind(), and connect() */ +#include /* for sockaddr_in and inet_ntoa() */ +#include /* for atoi() and exit() */ +#include /* for memset() */ +#include /* for close() */ +#include /* for errno */ +#include + +#include "logger.h" +#include "cclass.h" +#include "socket.h" + + +INIT_CLASS(SOCK); + +__construct(SOCK) +{ + this->logger = va_arg(* params, struct _logger *); + this->port = va_arg(* params, int); +} + +__destruct(SOCK) +{ + if (0 != this->handle) { + shutdown(this->handle, SHUT_RDWR); + close(this->handle); + } +} + +__jsonConst(SOCK) {} +__toJson(SOCK) {} +__clear(SOCK) {} + + +void +sock_initServer(SOCK this, int backlog) +{ + struct sockaddr_in addr; /* Local address */ + int reUse = 1; /* TODO: make this configurable */ + + /* Create socket for incoming connections */ + if (-1 == (this->handle = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) { + logger_log(this->logger, LOGGER_CRIT, + "error opening socket: %s - service terminated", + strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Make the socket REUSE a TIME_WAT socket */ + setsockopt(this->handle, SOL_SOCKET, SO_REUSEADDR, &reUse, sizeof (reUse)); + + /* Construct local address structure */ + memset(&addr, 0, sizeof(addr)); /* Zero out structure */ + + addr.sin_family = AF_INET; /* Internet address family */ + addr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */ + addr.sin_port = htons(this->port); /* Local port */ + + /* Bind to the local address */ + if (-1 == bind(this->handle, (struct sockaddr *) &addr, sizeof(addr))) { + logger_log(this->logger, LOGGER_CRIT, + "error binding socket: %s - service terminated", + strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Mark the socket so it will listen for incoming connections */ + if (-1 == listen(this->handle, backlog)) { + logger_log(this->logger, LOGGER_CRIT, + "error binding socket: %s - service terminated", + strerror(errno)); + exit(EXIT_FAILURE); + } +} + +SOCK +sock_accept(SOCK this, char remoteAddr[16]) +{ + SOCK sock; /* Socket for client */ + struct sockaddr_in addr; /* Client address */ + unsigned int len; /* Length of client address data structure */ + + /* Set the size of the in-out parameter */ + len = sizeof(addr); + + sock = new(SOCK, this->logger, this->port); + /** + * @TODO: change port to remote port on success + */ + + /* Wait for a client to connect */ + if (-1 == (sock->handle = accept(this->handle, (struct sockaddr *) &addr, &len))) { + logger_log(this->logger, LOGGER_WARNING, + "error acception connection: %s", strerror(errno)); + } else { + strncpy (remoteAddr, inet_ntoa(addr.sin_addr), sizeof(remoteAddr)-1); + } + + /* clntSock is connected to a client! */ + /** + * @TODO add verbosity level to logger + */ +// if (0 != this->logger->verbose) { + logger_log(this->logger, LOGGER_INFO, + "handling client %s\n", inet_ntoa(addr.sin_addr)); +// } + + return sock; +} + +// vim: set ts=4 sw=4: diff --git a/tests/Makefile.am b/tests/Makefile.am index 9e865e3..7d4277c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,8 +1,8 @@ ACLOCAL_AMFLAGS = -I m4 TESTS_ENVIRONMENT = valgrind --error-exitcode=123 --leak-check=full --quiet -TESTS = cclassTest loggerTest -check_PROGRAMS = cclassTest loggerTest +TESTS = cclassTest loggerTest socketTest serverTest +check_PROGRAMS = cclassTest loggerTest socketTest serverTest cclassTest_SOURCES = runtest.c cclassTest.c mock/class.c ../src/cclass.c cclassTest_LDADD = $(LIBOBJS) @@ -12,4 +12,12 @@ loggerTest_SOURCES = runtest.c loggerTest.c ../src/cclass.c ../src/logger.c loggerTest_LDADD = $(LIBOBJS) loggerTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I . +socketTest_SOURCES = runtest.c socketTest.c ../src/cclass.c ../src/logger.c ../src/socket.c +socketTest_LDADD = $(LIBOBJS) +socketTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I . + +serverTest_SOURCES = runtest.c serverTest.c ../src/cclass.c ../src/logger.c ../src/socket.c ../src/server.c +serverTest_LDADD = $(LIBOBJS) +serverTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I . + EXTRA_DIST = runtest.h mock/class.h diff --git a/tests/loggerTest.c b/tests/loggerTest.c index 7292570..2727a47 100644 --- a/tests/loggerTest.c +++ b/tests/loggerTest.c @@ -18,14 +18,15 @@ */ #include +#include #include "runtest.h" #include "cclass.h" #include "logger.h" -int level = -1; -const char * msg = NULL; +int level = -1; +char * msg = NULL; static void logfnct_mock(int _level, const char * _msg) @@ -75,7 +76,7 @@ int (* const tearDown)() = __tearDown; static int -testDummy() +testLogger() { logger_log(logger, LOGGER_ERR, "foo %d %s", 123, "bar"); @@ -86,7 +87,7 @@ testDummy() } const testfunc tests[] = { - testDummy + testLogger }; const size_t count = FUNCS_COUNT(tests); diff --git a/tests/serverTest.c b/tests/serverTest.c new file mode 100644 index 0000000..416fce4 --- /dev/null +++ b/tests/serverTest.c @@ -0,0 +1,94 @@ +#include +#include + +#include "runtest.h" +#include "logger.h" +#include "cclass.h" +#include "server.h" + + +#define TEST_PORT 11212 + + +int level = -1; +char * msg = NULL; + +static void +logfnct_mock(int _level, const char * _msg) +{ + level = _level; + msg = malloc(strlen(_msg) + 1); + strcpy(msg, _msg); +} + +const char testname[] = "serverTest"; +LOGGER logger = NULL; +SERVER server = NULL; + +static +int +__setUp() +{ + logger = new(LOGGER, NULL); + logger_add(logger, logfnct_mock); + + server = new(SERVER, logger, TEST_PORT); + + ASSERT_INSTANCE_OF(SERVER, server); + ASSERT_INSTANCE_OF(LOGGER, server->logger); + ASSERT_INSTANCE_OF(SOCK, server->sock); + ASSERT_EQUAL(TEST_PORT, server->sock->port); + ASSERT_EQUAL(server->max_fd, server->sock->handle); + + return TEST_OK; +} +int (* const setUp)() = __setUp; + +static +int +__tearDown() +{ + level = -1; + + if (NULL != msg) { + free(msg); + msg = NULL; + } + + if (NULL != logger) { + ASSERT_OBJECT(logger); + delete(&logger); + } + + if (NULL != server) { + ASSERT_OBJECT(server); + delete(&server); + } + + return TEST_OK; +} +int (* const tearDown)() = __tearDown; + +static +int +testDummy() +{ + return TEST_OK; +} + +static +int +testAccept() +{ + /* + * @TODO ... + */ + return TEST_OK; +} + +const testfunc tests[] = { + testDummy +}; +const size_t count = FUNCS_COUNT(tests); + +// vim: set ts=4 sw=4: diff --git a/tests/socketTest.c b/tests/socketTest.c new file mode 100644 index 0000000..a4da5db --- /dev/null +++ b/tests/socketTest.c @@ -0,0 +1,95 @@ +#include +#include + +#include "runtest.h" +#include "logger.h" +#include "cclass.h" +#include "socket.h" + + +#define TEST_PORT 11212 + + +int level = -1; +char * msg = NULL; + +static void +logfnct_mock(int _level, const char * _msg) +{ + level = _level; + msg = malloc(strlen(_msg) + 1); + strcpy(msg, _msg); +} + +const char testname[] = "socketTest"; +LOGGER logger = NULL; +SOCK sock = NULL; + +static +int +__setUp() +{ + logger = new(LOGGER, NULL); + logger_add(logger, logfnct_mock); + + sock = new(SOCK, logger, TEST_PORT); + + ASSERT_INSTANCE_OF(SOCK, sock); + ASSERT_INSTANCE_OF(LOGGER, sock->logger); + ASSERT_EQUAL(TEST_PORT, sock->port); + + return TEST_OK; +} +int (* const setUp)() = __setUp; + +static +int +__tearDown() +{ + level = -1; + + if (NULL != msg) { + free(msg); + msg = NULL; + } + + if (NULL != logger) { + ASSERT_OBJECT(logger); + delete(&logger); + } + + if (NULL != sock) { + ASSERT_OBJECT(sock); + delete(&sock); + } + + return TEST_OK; +} +int (* const tearDown)() = __tearDown; + +static +int +testInitServer() +{ + sock_initServer(sock, 10); + + ASSERT_NOT_NULL(sock->handle); + + return TEST_OK; +} + +static +int +testAccept() +{ + /* + * @TODO ... + */ +} + +const testfunc tests[] = { + testInitServer +}; +const size_t count = FUNCS_COUNT(tests); + +// vim: set ts=4 sw=4: