Commit d2848c0b14ecf965f59017e969071e908ea0d173

Authored by Georg Hopp
1 parent 0fd281dd

add info text about file handle passing and ported more stuff from my old server structure

  1 +File Descriptor Passing
  2 +
  3 +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.
  4 +
  5 +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.
  6 +
  7 +
  8 + int send_fd(int socket, int fd_to_send)
  9 + {
  10 + struct msghdr socket_message;
  11 + struct iovec io_vector[1];
  12 + struct cmsghdr * control_message = NULL;
  13 + char message_buffer[1];
  14 +
  15 + /*
  16 + storage space needed for an ancillary element with
  17 + a paylod of length is CMSG_SPACE(sizeof(length))
  18 + */
  19 + char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];
  20 + int available_ancillary_element_buffer_space;
  21 +
  22 + /* at least one vector of one byte must be sent */
  23 + message_buffer[0] = 'F';
  24 + io_vector[0].iov_base = message_buffer;
  25 + io_vector[0].iov_len = 1;
  26 +
  27 + /* initialize socket message */
  28 + memset(&socket_message, 0, sizeof(struct msghdr));
  29 + socket_message.msg_iov = io_vector;
  30 + socket_message.msg_iovlen = 1;
  31 +
  32 + /* provide space for the ancillary data */
  33 + available_ancillary_element_buffer_space = CMSG_SPACE(sizeof(int));
  34 + memset(ancillary_element_buffer, 0, available_ancillary_element_buffer_space);
  35 + socket_message.msg_control = ancillary_element_buffer;
  36 + socket_message.msg_controllen = available_ancillary_element_buffer_space;
  37 +
  38 + /* initialize a single ancillary data element for fd passing */
  39 + control_message = CMSG_FIRSTHDR(&socket_message);
  40 + control_message->cmsg_level = SOL_SOCKET;
  41 + control_message->cmsg_type = SCM_RIGHTS;
  42 + control_message->cmsg_len = CMSG_LEN(sizeof(int));
  43 + *((int *) CMSG_DATA(control_message)) = fd_to_send;
  44 +
  45 + return sendmsg(socket, &socket_message, 0);
  46 + }
  47 +
  48 +
  49 +
  50 + int recv_fd(int socket)
  51 + {
  52 + int sent_fd, available_ancillary_element_buffer_space;
  53 + struct msghdr socket_message;
  54 + struct iovec io_vector[1];
  55 + struct cmsghdr *control_message = NULL;
  56 + char message_buffer[1];
  57 + char ancillary_element_buffer[CMSG_SPACE(sizeof(int))];
  58 +
  59 + /* start clean */
  60 + memset(&socket_message, 0, sizeof(struct msghdr));
  61 + memset(ancillary_element_buffer, 0, CMSG_SPACE(sizeof(int)));
  62 +
  63 + /* setup a place to fill in message contents */
  64 + io_vector[0].iov_base = message_buffer;
  65 + io_vector[0].iov_len = 1;
  66 + socket_message.msg_iov = io_vector;
  67 + socket_message.msg_iovlen = 1;
  68 +
  69 + /* provide space for the ancillary data */
  70 + socket_message.msg_control = ancillary_element_buffer;
  71 + socket_message.msg_controllen = CMSG_SPACE(sizeof(int));
  72 +
  73 + if(recvmsg(socket, &socket_message, MSG_CMSG_CLOEXEC) < 0)
  74 + return -1;
  75 +
  76 + if(message_buffer[0] != 'F')
  77 + {
  78 + /* this did not originate from the above function */
  79 + return -1;
  80 + }
  81 +
  82 + if((socket_message.msg_flags & MSG_CTRUNC) == MSG_CTRUNC)
  83 + {
  84 + /* we did not provide enough space for the ancillary element array */
  85 + return -1;
  86 + }
  87 +
  88 + /* iterate ancillary elements */
  89 + for(control_message = CMSG_FIRSTHDR(&socket_message);
  90 + control_message != NULL;
  91 + control_message = CMSG_NXTHDR(&socket_message, control_message))
  92 + {
  93 + if( (control_message->cmsg_level == SOL_SOCKET) &&
  94 + (control_message->cmsg_type == SCM_RIGHTS) )
  95 + {
  96 + sent_fd = *((int *) CMSG_DATA(control_message));
  97 + return sent_fd;
  98 + }
  99 + }
  100 +
  101 + return -1;
  102 + }
  103 +
... ...
... ... @@ -16,15 +16,13 @@ AC_PROG_CC
16 16 AC_PROG_CC_C99
17 17 AC_PROG_LIBTOOL
18 18 AM_PROG_CC_C_O
19   -AC_PROG_RANLIB
20 19
21 20 # Checks for libraries.
22 21 AC_CHECK_LIB([json], [json_object_new_object], [],
23 22 [AC_MSG_ERROR([json-c library not found], [1])])
24 23
25 24 # Checks for header files.
26   -AC_CHECK_HEADERS([stdarg.h string.h stdlib.h stdio.h unistd.h syslog.h
27   - sys/types.h json/json.h])
  25 +AC_CHECK_HEADERS([stdarg.h string.h stdlib.h stdio.h unistd.h syslog.h sys/types.h json/json.h])
28 26
29 27 # Checks for typedefs, structures, and compiler characteristics.
30 28 AC_HEADER_STDBOOL
... ... @@ -33,8 +31,8 @@ AC_TYPE_PID_T
33 31 AC_TYPE_SIZE_T
34 32
35 33 # Checks for library functions.
36   -AC_FUNC_FORK
37   -AC_FUNC_MALLOC
  34 +#AC_FUNC_FORK
  35 +#AC_FUNC_MALLOC
38 36 AC_CHECK_FUNCS([memset])
39 37
40 38 AC_CONFIG_FILES([Makefile tests/Makefile])
... ...
1 1 #ifndef __LOGGER_H__
2 2 #define __LOGGER_H__
3 3
4   -#include <cclass.h>
  4 +#include "cclass.h"
5 5
6 6
7 7 #define LOGGER_EMERG 0
... ...
  1 +#ifndef __SERVER_H__
  2 +#define __SERVER_H__
  3 +
  4 +#include <stdio.h> /* for printf() and fprintf() */
  5 +#include <sys/select.h> /* for select system call and related */
  6 +
  7 +#include "socket.h"
  8 +#include "logger.h"
  9 +#include "cclass.h"
  10 +
  11 +
  12 +CLASS(SERVER) {
  13 + LOGGER logger;
  14 + SOCK sock;
  15 + unsigned int max_fd;
  16 + fd_set fdset;
  17 +
  18 + struct {
  19 + SOCK sock;
  20 + char * wbuf;
  21 + char * rbuf;
  22 + unsigned int rpos;
  23 + unsigned int wpos;
  24 + } conns[FD_SETSIZE];
  25 +};
  26 +
  27 +
  28 +void server_run(SERVER this);
  29 +void server_close(SERVER this);
  30 +void server_shutdown(SERVER this);
  31 +
  32 +#endif // __SERVER_H__
  33 +
  34 +// vim: set ts=4 sw=4:
... ...
  1 +#ifndef __SOCKET_H__
  2 +#define __SOCKET_H__
  3 +
  4 +#include <arpa/inet.h> /* for in_port_t */
  5 +
  6 +#include "logger.h"
  7 +#include "cclass.h"
  8 +
  9 +
  10 +CLASS(SOCK) {
  11 + LOGGER logger;
  12 + in_port_t port;
  13 + struct sockaddr_in addr;
  14 + int handle; /* socket handle for server */
  15 +};
  16 +
  17 +void sock_initServer(SOCK this, int backlog);
  18 +SOCK sock_accept(SOCK this, char remoteAddr[16]);
  19 +
  20 +#endif /* __SOCKET_H__ */
  21 +
  22 +// vim: set ts=4 sw=4:
... ...
  1 +#include <sys/select.h> /* for select system call and related */
  2 +#include <string.h> /* for memset and stuff */
  3 +#include <stdlib.h> /* for getopt */
  4 +
  5 +#include "server.h"
  6 +#include "socket.h"
  7 +#include "server.h"
  8 +#include "logger.h"
  9 +#include "cclass.h"
  10 +
  11 +
  12 +INIT_CLASS(SERVER);
  13 +
  14 +__construct(SERVER)
  15 +{
  16 + in_port_t port;
  17 + unsigned int backlog;
  18 +
  19 + this->logger = va_arg(* params, LOGGER);
  20 + port = va_arg(* params, int);
  21 + backlog = va_arg(* params, unsigned int);
  22 +
  23 + FD_ZERO(&(this->fdset));
  24 +
  25 + this->sock = new(SOCK, port);
  26 + sock_initServer(this->sock, backlog);
  27 +
  28 + this->max_fd = this->sock->handle;
  29 + FD_SET(this->sock->handle, &(this->fdset));
  30 +}
  31 +
  32 +__destruct(SERVER)
  33 +{
  34 + int i;
  35 +
  36 + for (i=3; i<=this->max_fd; i++) {
  37 + if (FD_ISSET(i, &(this->fdset)) && i != this->sock->handle) {
  38 + /*
  39 + * @TODO do some finalization...buffer handling...etc.
  40 + */
  41 + delete(&(this->conns[i]).sock);
  42 + FD_CLR(i, &(this->fdset));
  43 + }
  44 + }
  45 +
  46 + delete(&this->sock);
  47 +}
  48 +
  49 +__jsonConst(SERVER) {}
  50 +__toJson(SERVER) {}
  51 +__clear(SERVER) {}
  52 +
  53 +// vim: set ts=4 sw=4:
... ...
  1 +#include <stdio.h> /* for printf() and fprintf() */
  2 +#include <sys/types.h> /* SO_REUSEADDR */
  3 +#include <sys/socket.h> /* for socket(), bind(), and connect() */
  4 +#include <arpa/inet.h> /* for sockaddr_in and inet_ntoa() */
  5 +#include <stdlib.h> /* for atoi() and exit() */
  6 +#include <string.h> /* for memset() */
  7 +#include <unistd.h> /* for close() */
  8 +#include <errno.h> /* for errno */
  9 +#include <stdarg.h>
  10 +
  11 +#include "logger.h"
  12 +#include "cclass.h"
  13 +#include "socket.h"
  14 +
  15 +
  16 +INIT_CLASS(SOCK);
  17 +
  18 +__construct(SOCK)
  19 +{
  20 + this->logger = va_arg(* params, struct _logger *);
  21 + this->port = va_arg(* params, int);
  22 +}
  23 +
  24 +__destruct(SOCK)
  25 +{
  26 + if (0 != this->handle) {
  27 + shutdown(this->handle, SHUT_RDWR);
  28 + close(this->handle);
  29 + }
  30 +}
  31 +
  32 +__jsonConst(SOCK) {}
  33 +__toJson(SOCK) {}
  34 +__clear(SOCK) {}
  35 +
  36 +
  37 +void
  38 +sock_initServer(SOCK this, int backlog)
  39 +{
  40 + struct sockaddr_in addr; /* Local address */
  41 + int reUse = 1; /* TODO: make this configurable */
  42 +
  43 + /* Create socket for incoming connections */
  44 + if (-1 == (this->handle = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))) {
  45 + logger_log(this->logger, LOGGER_CRIT,
  46 + "error opening socket: %s - service terminated",
  47 + strerror(errno));
  48 + exit(EXIT_FAILURE);
  49 + }
  50 +
  51 + /* Make the socket REUSE a TIME_WAT socket */
  52 + setsockopt(this->handle, SOL_SOCKET, SO_REUSEADDR, &reUse, sizeof (reUse));
  53 +
  54 + /* Construct local address structure */
  55 + memset(&addr, 0, sizeof(addr)); /* Zero out structure */
  56 +
  57 + addr.sin_family = AF_INET; /* Internet address family */
  58 + addr.sin_addr.s_addr = htonl(INADDR_ANY); /* Any incoming interface */
  59 + addr.sin_port = htons(this->port); /* Local port */
  60 +
  61 + /* Bind to the local address */
  62 + if (-1 == bind(this->handle, (struct sockaddr *) &addr, sizeof(addr))) {
  63 + logger_log(this->logger, LOGGER_CRIT,
  64 + "error binding socket: %s - service terminated",
  65 + strerror(errno));
  66 + exit(EXIT_FAILURE);
  67 + }
  68 +
  69 + /* Mark the socket so it will listen for incoming connections */
  70 + if (-1 == listen(this->handle, backlog)) {
  71 + logger_log(this->logger, LOGGER_CRIT,
  72 + "error binding socket: %s - service terminated",
  73 + strerror(errno));
  74 + exit(EXIT_FAILURE);
  75 + }
  76 +}
  77 +
  78 +SOCK
  79 +sock_accept(SOCK this, char remoteAddr[16])
  80 +{
  81 + SOCK sock; /* Socket for client */
  82 + struct sockaddr_in addr; /* Client address */
  83 + unsigned int len; /* Length of client address data structure */
  84 +
  85 + /* Set the size of the in-out parameter */
  86 + len = sizeof(addr);
  87 +
  88 + sock = new(SOCK, this->logger, this->port);
  89 + /**
  90 + * @TODO: change port to remote port on success
  91 + */
  92 +
  93 + /* Wait for a client to connect */
  94 + if (-1 == (sock->handle = accept(this->handle, (struct sockaddr *) &addr, &len))) {
  95 + logger_log(this->logger, LOGGER_WARNING,
  96 + "error acception connection: %s", strerror(errno));
  97 + } else {
  98 + strncpy (remoteAddr, inet_ntoa(addr.sin_addr), sizeof(remoteAddr)-1);
  99 + }
  100 +
  101 + /* clntSock is connected to a client! */
  102 + /**
  103 + * @TODO add verbosity level to logger
  104 + */
  105 +// if (0 != this->logger->verbose) {
  106 + logger_log(this->logger, LOGGER_INFO,
  107 + "handling client %s\n", inet_ntoa(addr.sin_addr));
  108 +// }
  109 +
  110 + return sock;
  111 +}
  112 +
  113 +// vim: set ts=4 sw=4:
... ...
1 1 ACLOCAL_AMFLAGS = -I m4
2 2
3 3 TESTS_ENVIRONMENT = valgrind --error-exitcode=123 --leak-check=full --quiet
4   -TESTS = cclassTest loggerTest
5   -check_PROGRAMS = cclassTest loggerTest
  4 +TESTS = cclassTest loggerTest socketTest serverTest
  5 +check_PROGRAMS = cclassTest loggerTest socketTest serverTest
6 6
7 7 cclassTest_SOURCES = runtest.c cclassTest.c mock/class.c ../src/cclass.c
8 8 cclassTest_LDADD = $(LIBOBJS)
... ... @@ -12,4 +12,12 @@ loggerTest_SOURCES = runtest.c loggerTest.c ../src/cclass.c ../src/logger.c
12 12 loggerTest_LDADD = $(LIBOBJS)
13 13 loggerTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I .
14 14
  15 +socketTest_SOURCES = runtest.c socketTest.c ../src/cclass.c ../src/logger.c ../src/socket.c
  16 +socketTest_LDADD = $(LIBOBJS)
  17 +socketTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I .
  18 +
  19 +serverTest_SOURCES = runtest.c serverTest.c ../src/cclass.c ../src/logger.c ../src/socket.c ../src/server.c
  20 +serverTest_LDADD = $(LIBOBJS)
  21 +serverTest_CFLAGS = -Wall -ggdb -O0 -I ../include -I .. -I .
  22 +
15 23 EXTRA_DIST = runtest.h mock/class.h
... ...
... ... @@ -18,14 +18,15 @@
18 18 */
19 19
20 20 #include <stdio.h>
  21 +#include <stdlib.h>
21 22
22 23 #include "runtest.h"
23 24 #include "cclass.h"
24 25 #include "logger.h"
25 26
26 27
27   -int level = -1;
28   -const char * msg = NULL;
  28 +int level = -1;
  29 +char * msg = NULL;
29 30
30 31 static void
31 32 logfnct_mock(int _level, const char * _msg)
... ... @@ -75,7 +76,7 @@ int (* const tearDown)() = __tearDown;
75 76
76 77 static
77 78 int
78   -testDummy()
  79 +testLogger()
79 80 {
80 81 logger_log(logger, LOGGER_ERR, "foo %d %s", 123, "bar");
81 82
... ... @@ -86,7 +87,7 @@ testDummy()
86 87 }
87 88
88 89 const testfunc tests[] = {
89   - testDummy
  90 + testLogger
90 91 };
91 92 const size_t count = FUNCS_COUNT(tests);
92 93
... ...
  1 +#include <stdio.h>
  2 +#include <stdlib.h>
  3 +
  4 +#include "runtest.h"
  5 +#include "logger.h"
  6 +#include "cclass.h"
  7 +#include "server.h"
  8 +
  9 +
  10 +#define TEST_PORT 11212
  11 +
  12 +
  13 +int level = -1;
  14 +char * msg = NULL;
  15 +
  16 +static void
  17 +logfnct_mock(int _level, const char * _msg)
  18 +{
  19 + level = _level;
  20 + msg = malloc(strlen(_msg) + 1);
  21 + strcpy(msg, _msg);
  22 +}
  23 +
  24 +const char testname[] = "serverTest";
  25 +LOGGER logger = NULL;
  26 +SERVER server = NULL;
  27 +
  28 +static
  29 +int
  30 +__setUp()
  31 +{
  32 + logger = new(LOGGER, NULL);
  33 + logger_add(logger, logfnct_mock);
  34 +
  35 + server = new(SERVER, logger, TEST_PORT);
  36 +
  37 + ASSERT_INSTANCE_OF(SERVER, server);
  38 + ASSERT_INSTANCE_OF(LOGGER, server->logger);
  39 + ASSERT_INSTANCE_OF(SOCK, server->sock);
  40 + ASSERT_EQUAL(TEST_PORT, server->sock->port);
  41 + ASSERT_EQUAL(server->max_fd, server->sock->handle);
  42 +
  43 + return TEST_OK;
  44 +}
  45 +int (* const setUp)() = __setUp;
  46 +
  47 +static
  48 +int
  49 +__tearDown()
  50 +{
  51 + level = -1;
  52 +
  53 + if (NULL != msg) {
  54 + free(msg);
  55 + msg = NULL;
  56 + }
  57 +
  58 + if (NULL != logger) {
  59 + ASSERT_OBJECT(logger);
  60 + delete(&logger);
  61 + }
  62 +
  63 + if (NULL != server) {
  64 + ASSERT_OBJECT(server);
  65 + delete(&server);
  66 + }
  67 +
  68 + return TEST_OK;
  69 +}
  70 +int (* const tearDown)() = __tearDown;
  71 +
  72 +static
  73 +int
  74 +testDummy()
  75 +{
  76 + return TEST_OK;
  77 +}
  78 +
  79 +static
  80 +int
  81 +testAccept()
  82 +{
  83 + /*
  84 + * @TODO ...
  85 + */
  86 + return TEST_OK;
  87 +}
  88 +
  89 +const testfunc tests[] = {
  90 + testDummy
  91 +};
  92 +const size_t count = FUNCS_COUNT(tests);
  93 +
  94 +// vim: set ts=4 sw=4:
... ...
  1 +#include <stdio.h>
  2 +#include <stdlib.h>
  3 +
  4 +#include "runtest.h"
  5 +#include "logger.h"
  6 +#include "cclass.h"
  7 +#include "socket.h"
  8 +
  9 +
  10 +#define TEST_PORT 11212
  11 +
  12 +
  13 +int level = -1;
  14 +char * msg = NULL;
  15 +
  16 +static void
  17 +logfnct_mock(int _level, const char * _msg)
  18 +{
  19 + level = _level;
  20 + msg = malloc(strlen(_msg) + 1);
  21 + strcpy(msg, _msg);
  22 +}
  23 +
  24 +const char testname[] = "socketTest";
  25 +LOGGER logger = NULL;
  26 +SOCK sock = NULL;
  27 +
  28 +static
  29 +int
  30 +__setUp()
  31 +{
  32 + logger = new(LOGGER, NULL);
  33 + logger_add(logger, logfnct_mock);
  34 +
  35 + sock = new(SOCK, logger, TEST_PORT);
  36 +
  37 + ASSERT_INSTANCE_OF(SOCK, sock);
  38 + ASSERT_INSTANCE_OF(LOGGER, sock->logger);
  39 + ASSERT_EQUAL(TEST_PORT, sock->port);
  40 +
  41 + return TEST_OK;
  42 +}
  43 +int (* const setUp)() = __setUp;
  44 +
  45 +static
  46 +int
  47 +__tearDown()
  48 +{
  49 + level = -1;
  50 +
  51 + if (NULL != msg) {
  52 + free(msg);
  53 + msg = NULL;
  54 + }
  55 +
  56 + if (NULL != logger) {
  57 + ASSERT_OBJECT(logger);
  58 + delete(&logger);
  59 + }
  60 +
  61 + if (NULL != sock) {
  62 + ASSERT_OBJECT(sock);
  63 + delete(&sock);
  64 + }
  65 +
  66 + return TEST_OK;
  67 +}
  68 +int (* const tearDown)() = __tearDown;
  69 +
  70 +static
  71 +int
  72 +testInitServer()
  73 +{
  74 + sock_initServer(sock, 10);
  75 +
  76 + ASSERT_NOT_NULL(sock->handle);
  77 +
  78 + return TEST_OK;
  79 +}
  80 +
  81 +static
  82 +int
  83 +testAccept()
  84 +{
  85 + /*
  86 + * @TODO ...
  87 + */
  88 +}
  89 +
  90 +const testfunc tests[] = {
  91 + testInitServer
  92 +};
  93 +const size_t count = FUNCS_COUNT(tests);
  94 +
  95 +// vim: set ts=4 sw=4:
... ...
Please register or login to post a comment