2 * Simple LD_PRELOAD wrapper to "convert" network sockets to UNIX sockets;
3 * works for clients and servers. See README for details.
5 * Copyright (C) 2013 Simon Ruderich
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 /* Necessary for RTLD_NEXT. */
30 #include <netinet/in.h>
35 #include <sys/socket.h>
37 #include <sys/types.h>
44 #define LOG_LEVEL_ERROR 1
45 #define LOG_LEVEL_WARNING 2
46 #define LOG_LEVEL_DEBUG 3
47 #define LOG_LEVEL_MASK LOG_LEVEL_DEBUG
49 #define LOG_LEVEL_PERROR (1 << 10)
51 #define OPTION_PARSED (1 << 1)
52 /* Don't intercept listen(), accept(). */
53 #define OPTION_CLIENT_ONLY (1 << 2)
54 /* Don't intercept connect(). */
55 #define OPTION_SERVER_ONLY (1 << 3)
58 /* GLOBAL VARIABLES */
65 /* Used by listen(). */
66 struct sockaddr *orig_addr;
67 socklen_t orig_addrlen;
72 static struct list socket_list = {
73 .orig_sockfd = -1, /* must not match a valid sockfd */
76 static int global_options;
79 /* LOG FUNCTIONS/MACROS */
81 static int get_log_level(void);
83 static void log_helper(int action, const char *file, int line, const char *format, va_list ap) {
84 int saved_errno = errno;
88 log_level = get_log_level();
91 int level = action & LOG_LEVEL_MASK;
92 if (level > log_level) {
97 if (level == LOG_LEVEL_DEBUG) {
99 } else if (level == LOG_LEVEL_WARNING) {
101 } else if (level == LOG_LEVEL_ERROR) {
107 /* Prevent other threads from interrupting the printf()s. */
110 fprintf(stderr, "socket2unix [%s] ", prefix);
111 fprintf(stderr, "[%s:%3d] ", file, line);
112 vfprintf(stderr, format, ap);
114 if ((action & ~LOG_LEVEL_MASK) == LOG_LEVEL_PERROR) {
115 fprintf(stderr, ": ");
123 if (level == LOG_LEVEL_ERROR) {
124 fprintf(stderr, "Aborting.\n");
129 static void log_(int level, const char *file, int line, const char *format, ...)
130 __attribute__((format(printf, 4, 5)));
131 static void log_(int level, const char *file, int line, const char *format, ...) {
134 va_start(ap, format);
135 log_helper(level, file, line, format, ap);
140 log_(LOG_LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__)
142 log_(LOG_LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__)
144 log_(LOG_LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
147 log_(LOG_LEVEL_ERROR | LOG_LEVEL_PERROR, __FILE__, __LINE__, __VA_ARGS__)
152 /* Load the function name using dlsym() if necessary and store it in pointer.
153 * Terminate program on failure. */
154 #define LOAD_FUNCTION(pointer, name) \
155 if ((pointer) == NULL) { \
157 dlerror(); /* Clear possibly existing error. */ \
159 *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \
161 if ((error = dlerror()) != NULL) { \
162 ERROR("%s\n", error); \
167 /* OTHER FUNCTIONS */
169 static void *xmalloc(size_t size) {
170 void *x = malloc(size);
172 DIE("malloc(%zu)", size);
177 static struct list *find_sockfd(int sockfd) {
180 if (sockfd == socket_list.orig_sockfd) {
184 for (e = &socket_list; e != NULL; e = e->next) {
185 if (e->orig_sockfd == sockfd) {
191 static struct list *remove_sockfd(int sockfd) {
194 if (sockfd == socket_list.orig_sockfd) {
198 for (e = &socket_list, p = NULL; e != NULL; p = e, e = e->next) {
199 if (e->orig_sockfd == sockfd) {
207 static const char *get_socket_path(void) {
208 const char *path = getenv("SOCKET2UNIX_PATH");
210 ERROR("SOCKET2UNIX_PATH environment variable not defined\n");
212 if (path[0] != '/') {
213 ERROR("SOCKET2UNIX_PATH '%s' must be an absolute path\n", path);
217 static int get_log_level(void) {
218 const char *level = getenv("SOCKET2UNIX_DEBUG");
221 return LOG_LEVEL_DEBUG;
223 return LOG_LEVEL_WARNING;
226 int number = atoi(level);
227 if (number <= 0 || number > LOG_LEVEL_DEBUG) {
228 number = LOG_LEVEL_DEBUG;
232 static int get_options(void) {
233 const char *pos = getenv("SOCKET2UNIX_OPTIONS");
235 return OPTION_PARSED;
238 int options = OPTION_PARSED;
240 while (*pos != '\0') {
243 const char *end = strchr(pos, ',');
245 length = strlen(pos);
247 length = (size_t)(end - pos);
250 if (!strncmp("client_only", pos, length)) {
251 options |= OPTION_CLIENT_ONLY;
252 } else if (!strncmp("server_only", pos, length)) {
253 options |= OPTION_SERVER_ONLY;
255 char option[length + 1];
256 strncpy(option, pos, length);
257 option[length] = '\0';
258 ERROR("unknown option '%s' in SOCKET2UNIX_OPTIONS\n",
268 if ((options & OPTION_CLIENT_ONLY) && (options & OPTION_SERVER_ONLY)) {
269 ERROR("conflicting options 'client_only', 'server_only' "
270 "in SOCKET2UNIX_OPTIONS\n");
276 static const char *af_to_name(int af) {
279 } else if (af == AF_LOCAL) {
281 } else if (af == AF_INET) {
283 } else if (af == AF_INET6) {
285 } else if (af == AF_IPX) {
288 } else if (af == AF_NETLINK) {
292 } else if (af == AF_X25) {
296 } else if (af == AF_AX25) {
300 } else if (af == AF_ATMPVC) {
303 } else if (af == AF_APPLETALK) {
304 return "AF_APPLETALK";
306 } else if (af == AF_PACKET) {
313 static const char *sock_to_name(int sock) {
314 if (sock & SOCK_STREAM) {
315 return "SOCK_STREAM";
316 } else if (sock & SOCK_DGRAM) {
318 } else if (sock & SOCK_SEQPACKET) {
319 return "SOCK_SEQPACKET";
320 } else if (sock & SOCK_RAW) {
322 } else if (sock & SOCK_RDM) {
325 } else if (sock & SOCK_PACKET) {
326 return "SOCK_PACKET";
329 return "SOCK_UNKNOWN";
332 /* for getsockopt()/setsockopt(). */
333 static const char *level_to_name(int level) {
334 if (level == SOL_SOCKET) {
337 } else if (level == SOL_IP) {
341 } else if (level == SOL_IPV6) {
344 } else if (level == IPPROTO_TCP) {
345 return "IPPROTO_TCP";
346 } else if (level == IPPROTO_UDP) {
347 return "IPPROTO_UDP";
349 return "SOL_UNKNOWN";
354 static int set_sockaddr_un(struct sockaddr_un *sockaddr,
355 const struct sockaddr *addr, socklen_t addrlen) {
356 /* Just in case ... */
357 if ((addr->sa_family == AF_INET
358 && addrlen < sizeof(struct sockaddr_in))
359 || (addr->sa_family == AF_INET6
360 && addrlen < sizeof(struct sockaddr_in6))) {
361 WARN("invalid addrlen from program\n");
365 const char *socket_path = get_socket_path();
367 /* The program may open multiple sockets, e.g. IPv4 and IPv6 and on
368 * multiple ports. Create unique paths. */
371 if (addr->sa_family == AF_INET) {
373 port = ntohs(((const struct sockaddr_in *)addr)->sin_port);
374 } else if (addr->sa_family == AF_INET6) {
376 port = ntohs(((const struct sockaddr_in6 *)addr)->sin6_port);
380 WARN("unknown sa_family '%s' (%d)\n",
381 af_to_name(addr->sa_family), addr->sa_family);
384 /* Initialize sockaddr_un. */
385 sockaddr->sun_family = AF_UNIX;
386 int written = snprintf(sockaddr->sun_path, sizeof(sockaddr->sun_path),
387 "%s-%s-%d", socket_path, af, port);
388 /* The maximum length is quite short, check it. */
389 if (written >= (int)sizeof(sockaddr->sun_path)) {
390 ERROR("path '%s-%s-%d' too long for UNIX socket",
391 socket_path, af, port);
397 static int replace_socket(int replaceefd, int replacerfd) {
398 static int (*real_close)(int);
399 LOAD_FUNCTION(real_close, "close");
401 /* Replace socket replaceefd with replacerfd. After dup2() both socket fds
402 * point to the same socket (replacerfd). */
403 if (dup2(replacerfd, replaceefd) < 0) {
406 /* We don't need replacerfd anymore. The program will use our replacement
407 * and we don't need it for anything else. Use real_close() to prevent
408 * unnecessary debug messages. */
409 real_close(replacerfd);
414 /* FUNCTIONS OVERWRITTEN BY LD_PRELOAD */
416 int socket(int domain, int type, int protocol) {
417 static int (*real_socket)(int, int, int);
418 LOAD_FUNCTION(real_socket, "socket");
420 /* We return the normal socket because we don't know yet if it's a client
421 * or a listen socket and therefore if we should replace it or not. This
422 * happens in listen() and connect(), see below. */
424 int sockfd = real_socket(domain, type, protocol);
427 || domain == AF_LOCAL) {
431 DBG("socket(%s, %s, %d)\n",
432 af_to_name(domain), sock_to_name(type), protocol);
434 struct list *entry = xmalloc(sizeof(*entry));
435 memset(entry, 0, sizeof(*entry));
437 entry->orig_sockfd = sockfd;
438 entry->orig_domain = domain;
439 entry->orig_type = type;
441 entry->next = socket_list.next;
442 socket_list.next = entry;
448 static int (*real_close)(int);
449 LOAD_FUNCTION(real_close, "close");
451 DBG("close(%d)\n", fd);
453 struct list *entry = remove_sockfd(fd);
455 DBG("close(%d): sockfd not found\n", fd);
456 return real_close(fd);
458 assert(fd == entry->orig_sockfd);
459 free(entry->orig_addr);
462 return real_close(fd);
465 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
466 static int (*real_bind)(int, const struct sockaddr *, socklen_t);
467 LOAD_FUNCTION(real_bind, "bind");
469 DBG("bind(%d, ..)\n", sockfd);
471 if (addr == NULL || addrlen < sizeof(addr->sa_family)
472 || addr->sa_family == AF_UNIX
473 || addr->sa_family == AF_LOCAL) {
474 return real_bind(sockfd, addr, addrlen);
477 struct list *entry = find_sockfd(sockfd);
479 DBG("bind(%d, ..): sockfd not found\n", sockfd);
480 return real_bind(sockfd, addr, addrlen);
482 assert(sockfd == entry->orig_sockfd);
483 DBG("bind(%d, ..): %s %s\n",
485 af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
487 /* Copy struct sockaddr, we need it later in listen(). */
488 entry->orig_addr = xmalloc(addrlen);
489 memcpy(entry->orig_addr, addr, addrlen);
490 entry->orig_addrlen = addrlen;
492 return real_bind(sockfd, addr, addrlen);
495 int listen(int sockfd, int backlog) {
496 static int (*real_listen)(int, int);
497 LOAD_FUNCTION(real_listen, "listen");
499 if (!global_options) {
500 global_options = get_options();
503 if (global_options & OPTION_CLIENT_ONLY) {
504 DBG("listen(%d, %d): server hooking disabled\n", sockfd, backlog);
505 return real_listen(sockfd, backlog);
508 struct list *entry = find_sockfd(sockfd);
510 DBG("listen(%d, %d): sockfd not found\n", sockfd, backlog);
511 return real_listen(sockfd, backlog);
513 assert(sockfd == entry->orig_sockfd);
514 DBG("listen(%d, %d): %s %s\n",
516 af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
518 int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
519 if (unix_sockfd < 0) {
520 DIE("listen(): failed to create UNIX socket");
523 struct sockaddr_un sockaddr;
524 if (set_sockaddr_un(&sockaddr, entry->orig_addr,
525 entry->orig_addrlen) != 0) {
526 ERROR("listen(%d, ..) failed\n", sockfd);
529 DBG("listen(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
532 while (attempts < 10) {
533 if (bind(unix_sockfd, (struct sockaddr *)&sockaddr,
534 sizeof(sockaddr)) == 0) {
537 if (errno != EADDRINUSE) {
538 DIE("listen(%d, ..): failed to bind to '%s'",
539 sockfd, sockaddr.sun_path);
542 /* File already exists, unlink it if it's a socket. This has a race
543 * condition, but the worst case is that we delete a file created by
544 * the user at the path he told us to use. Tough luck .. */
547 if (lstat(sockaddr.sun_path, &buf) != 0) {
548 /* Looks like a race, better abort. */
549 DIE("listen(%d, ..): lstat on UNIX socket '%s' failed",
550 sockfd, sockaddr.sun_path);
553 if (!S_ISSOCK(buf.st_mode)) {
554 ERROR("listen(%d, ..): path '%s' exits and is no socket\n",
555 sockfd, sockaddr.sun_path);
558 WARN("listen(%d, ..): unlinking '%s'\n", sockfd, sockaddr.sun_path);
559 if (unlink(sockaddr.sun_path) != 0) {
560 DIE("listen(%d, ..): unlink '%s' failed",
561 sockfd, sockaddr.sun_path);
567 if (attempts == 10) {
568 ERROR("listen(%d, ..): failed to create UNIX socket file\n", sockfd);
571 /* Replace the original socket of the program with our socket. */
572 if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
573 DIE("listen(): failed to replace socket");
576 if (real_listen(entry->orig_sockfd, backlog) != 0) {
577 DIE("listen(): failed to listen");
583 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
584 static int (*real_accept)(int, struct sockaddr *, socklen_t *);
585 LOAD_FUNCTION(real_accept, "accept");
587 if (!global_options) {
588 global_options = get_options();
591 if (global_options & OPTION_CLIENT_ONLY) {
592 DBG("accept(%d, ..): server hooking disabled\n", sockfd);
593 return real_accept(sockfd, addr, addrlen);
596 DBG("accept(%d, ..)\n", sockfd);
598 struct list *entry = find_sockfd(sockfd);
600 DBG("accept(%d, ..): sockfd not found\n", sockfd);
601 return real_accept(sockfd, addr, addrlen);
603 assert(sockfd == entry->orig_sockfd);
604 DBG("accept(%d, ..): %s %s\n",
606 af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
608 struct sockaddr_un sockaddr;
609 socklen_t size = sizeof(sockaddr);
610 int sock = real_accept(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
613 DIE("accept(%d, ..): failed to accept", sockfd);
616 if (addr == NULL || addrlen == NULL) {
619 DBG("accept(%d, ..): caller requested sockaddr\n", sockfd);
621 if (*addrlen < size) {
622 WARN("accept(%d, ..): invalid addrlen from program", sockfd);
627 /* This is not the protocol the program asked for (AF_* vs. AF_UNIX), but
628 * it should work most of the time. */
629 memcpy(addr, &sockaddr, size);
632 /* TODO: is this enough? */
637 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
638 static int (*real_connect)(int, const struct sockaddr *, socklen_t);
639 LOAD_FUNCTION(real_connect, "connect");
641 if (!global_options) {
642 global_options = get_options();
645 if (global_options & OPTION_SERVER_ONLY) {
646 DBG("connect(%d, ..): client hooking disabled\n", sockfd);
647 return real_connect(sockfd, addr, addrlen);
650 DBG("connect(%d, ..)\n", sockfd);
652 if (addr == NULL || addrlen < sizeof(addr->sa_family)
653 || addr->sa_family == AF_UNIX
654 || addr->sa_family == AF_LOCAL) {
655 return real_connect(sockfd, addr, addrlen);
658 struct list *entry = find_sockfd(sockfd);
660 DBG("connect(%d, ..): sockfd not found\n", sockfd);
661 return real_connect(sockfd, addr, addrlen);
663 assert(sockfd == entry->orig_sockfd);
664 DBG("connect(%d, ..): %s %s\n",
666 af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
668 int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
669 if (unix_sockfd < 0) {
670 DIE("bind(): failed to create UNIX socket");
673 /* Replace the original socket of the program with our socket. */
674 if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
675 DIE("connect(): failed to replace socket");
678 struct sockaddr_un sockaddr;
679 if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
680 ERROR("connect(%d, ..) failed\n", sockfd);
683 DBG("connect(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
685 if (real_connect(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
686 sizeof(sockaddr)) != 0) {
687 DIE("connect(%d, ..): failed to connect", sockfd);
694 int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
695 static int (*real_getsockname)(int, struct sockaddr *, socklen_t *);
696 LOAD_FUNCTION(real_getsockname, "getsockname");
698 DBG("getsockname(%d, ..)\n", sockfd);
700 return real_getsockname(sockfd, addr, addrlen);
703 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
704 static int (*real_getpeername)(int, struct sockaddr *, socklen_t *);
705 LOAD_FUNCTION(real_getpeername, "getpeername");
707 DBG("getpeername(%d, ..)\n", sockfd);
709 return real_getpeername(sockfd, addr, addrlen);
712 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {
713 static int (*real_getsockopt)(int, int, int, void *, socklen_t *);
714 LOAD_FUNCTION(real_getsockopt, "getsockopt");
716 DBG("getsockopt(%d, %d %s, %d, ..)\n",
717 sockfd, level, level_to_name(level), optname);
719 return real_getsockopt(sockfd, level, optname, optval, optlen);
721 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {
722 static int (*real_setsockopt)(int, int, int, const void *, socklen_t);
723 LOAD_FUNCTION(real_setsockopt, "setsockopt");
725 DBG("setsockopt(%d, %d %s, %d, ..)\n",
726 sockfd, level, level_to_name(level), optname);
728 return real_setsockopt(sockfd, level, optname, optval, optlen);