]> ruderich.org/simon Gitweb - socket2unix/socket2unix.git/blob - src/socket2unix.c
Allow disabling of hooks for client or server functionality.
[socket2unix/socket2unix.git] / src / socket2unix.c
1 /*
2  * Simple LD_PRELOAD wrapper to "convert" network sockets to UNIX sockets;
3  * works for clients and servers. See README for details.
4  *
5  * Copyright (C) 2013  Simon Ruderich
6  *
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.
11  *
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.
16  *
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/>.
19  */
20
21
22 /* Necessary for RTLD_NEXT. */
23 #define _GNU_SOURCE
24
25 #include <config.h>
26
27 #include <assert.h>
28 #include <dlfcn.h>
29 #include <errno.h>
30 #include <netinet/in.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <sys/socket.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <sys/un.h>
39 #include <unistd.h>
40
41
42 /* CONSTANTS */
43
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
48
49 #define LOG_LEVEL_PERROR  42
50
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)
56
57
58 /* GLOBAL VARIABLES */
59
60 struct list {
61     int orig_sockfd;
62     int orig_domain;
63     int orig_type;
64
65     /* Used by listen(). */
66     struct sockaddr *orig_addr;
67     socklen_t orig_addrlen;
68
69     struct list *next;
70 };
71
72 static struct list socket_list = {
73     .orig_sockfd = -1, /* must not match a valid sockfd */
74 };
75
76 static int global_options;
77
78
79 /* LOG FUNCTIONS/MACROS */
80
81 static int get_log_level(void);
82
83 static void log_helper(int action, const char *file, int line, const char *format, va_list ap) {
84     int saved_errno = errno;
85
86     static int log_level;
87     if (!log_level) {
88         log_level = get_log_level();
89     }
90
91     int level = action & LOG_LEVEL_MASK;
92     if (level > log_level) {
93         return;
94     }
95
96     const char *prefix;
97     if (level == LOG_LEVEL_DEBUG) {
98         prefix = "DEBUG";
99     } else if (level == LOG_LEVEL_WARNING) {
100         prefix = "WARN ";
101     } else if (level == LOG_LEVEL_ERROR) {
102         prefix = "ERROR";
103     } else {
104         prefix = "UNKNOWN";
105     }
106
107     /* Prevent other threads from interrupting the printf()s. */
108     flockfile(stderr);
109
110     fprintf(stderr, "socket2unix [%s] ", prefix);
111     fprintf(stderr, "[%s:%3d] ", file, line);
112     vfprintf(stderr, format, ap);
113
114     if ((action & ~LOG_LEVEL_MASK) == LOG_LEVEL_PERROR) {
115         fprintf(stderr, ": ");
116
117         errno = saved_errno;
118         perror("");
119     }
120
121     funlockfile(stderr);
122
123     if (level == LOG_LEVEL_ERROR) {
124         fprintf(stderr, "Aborting.\n");
125         exit(EXIT_FAILURE);
126     }
127 }
128
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, ...) {
132     va_list ap;
133
134     va_start(ap, format);
135     log_helper(level, file, line, format, ap);
136     va_end(ap);
137 }
138
139 #define ERROR(...) \
140     log_(LOG_LEVEL_ERROR,   __FILE__, __LINE__, __VA_ARGS__)
141 #define WARN(...) \
142     log_(LOG_LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__)
143 #define DBG(...) \
144     log_(LOG_LEVEL_DEBUG,   __FILE__, __LINE__, __VA_ARGS__)
145
146 #define DIE(...) \
147     log_(LOG_LEVEL_ERROR | LOG_LEVEL_PERROR, __FILE__, __LINE__, __VA_ARGS__)
148
149
150 /* LD_PRELOAD */
151
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) { \
156         char *error; \
157         dlerror(); /* Clear possibly existing error. */ \
158         \
159         *(void **) (&(pointer)) = dlsym(RTLD_NEXT, (name)); \
160         \
161         if ((error = dlerror()) != NULL) { \
162             ERROR("%s\n", error); \
163         } \
164     }
165
166
167 /* OTHER FUNCTIONS */
168
169 static void *xmalloc(size_t size) {
170     void *x = malloc(size);
171     if (!x) {
172         DIE("malloc(%zu)", size);
173     }
174     return x;
175 }
176
177 static struct list *find_sockfd(int sockfd) {
178     struct list *e;
179
180     if (sockfd == socket_list.orig_sockfd) {
181         return NULL;
182     }
183
184     for (e = &socket_list; e != NULL; e = e->next) {
185         if (e->orig_sockfd == sockfd) {
186             return e;
187         }
188     }
189     return NULL;
190 }
191 static struct list *remove_sockfd(int sockfd) {
192     struct list *e, *p;
193
194     if (sockfd == socket_list.orig_sockfd) {
195         return NULL;
196     }
197
198     for (e = &socket_list, p = NULL; e != NULL; p = e, e = e->next) {
199         if (e->orig_sockfd == sockfd) {
200             p->next = e->next;
201             return e;
202         }
203     }
204     return NULL;
205 }
206
207 static const char *get_socket_path(void) {
208     const char *path = getenv("SOCKET2UNIX_PATH");
209     if (!path) {
210         ERROR("SOCKET2UNIX_PATH environment variable not defined\n");
211     }
212     if (path[0] != '/') {
213         ERROR("SOCKET2UNIX_PATH '%s' must be an absolute path\n", path);
214     }
215     return path;
216 }
217 static int get_log_level(void) {
218     const char *level = getenv("SOCKET2UNIX_DEBUG");
219     if (!level) {
220 #ifdef DEBUG
221         return LOG_LEVEL_DEBUG;
222 #else
223         return LOG_LEVEL_WARNING;
224 #endif
225     }
226     int number = atoi(level);
227     if (number <= 0 || number > LOG_LEVEL_DEBUG) {
228         number = LOG_LEVEL_DEBUG;
229     }
230     return number;
231 }
232 static int get_options(void) {
233     const char *pos = getenv("SOCKET2UNIX_OPTIONS");
234     if (!pos) {
235         return OPTION_PARSED;
236     }
237
238     int options = OPTION_PARSED;
239
240     while (*pos != '\0') {
241         size_t length;
242
243         const char *end = strchr(pos, ',');
244         if (end == NULL) {
245             length = strlen(pos);
246         } else {
247             length = (size_t)(end - pos);
248         }
249
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;
254         } else {
255             char option[length + 1];
256             strncpy(option, pos, length);
257             option[length] = '\0';
258             ERROR("unknown option '%s' in SOCKET2UNIX_OPTIONS\n",
259                   option);
260         }
261
262         if (end == NULL) {
263             break;
264         }
265         pos = end + 1;
266     }
267
268     if ((options & OPTION_CLIENT_ONLY) && (options & OPTION_SERVER_ONLY)) {
269         ERROR("conflicting options 'client_only', 'server_only' "
270               "in SOCKET2UNIX_OPTIONS\n");
271     }
272
273     return options;
274 }
275
276 static const char *af_to_name(int af) {
277     if (af == AF_UNIX) {
278         return "AF_UNIX";
279     } else if (af == AF_LOCAL) {
280         return "AF_LOCAL";
281     } else if (af == AF_INET) {
282         return "AF_INET";
283     } else if (af == AF_INET6) {
284         return "AF_INET6";
285     } else if (af == AF_IPX) {
286         return "AF_IPX";
287 #ifdef AF_NETLINK
288     } else if (af == AF_NETLINK) {
289         return "AF_NETLINK";
290 #endif
291 #ifdef AF_X25
292     } else if (af == AF_X25) {
293         return "AF_X25";
294 #endif
295 #ifdef AF_AX25
296     } else if (af == AF_AX25) {
297         return "AF_AX25";
298 #endif
299 #ifdef AF_ATMPVC
300     } else if (af == AF_ATMPVC) {
301         return "AF_ATMPVC";
302 #endif
303     } else if (af == AF_APPLETALK) {
304         return "AF_APPLETALK";
305 #ifdef AF_PACKET
306     } else if (af == AF_PACKET) {
307         return "AF_PACKET";
308 #endif
309     } else {
310         return "AF_UNKNOWN";
311     }
312 }
313 static const char *sock_to_name(int sock) {
314     if (sock & SOCK_STREAM) {
315         return "SOCK_STREAM";
316     } else if (sock & SOCK_DGRAM) {
317         return "SOCK_DGRAM";
318     } else if (sock & SOCK_SEQPACKET) {
319         return "SOCK_SEQPACKET";
320     } else if (sock & SOCK_RAW) {
321         return "SOCK_RAW";
322     } else if (sock & SOCK_RDM) {
323         return "SOCK_RDM";
324 #ifdef SOCK_PACKET
325     } else if (sock & SOCK_PACKET) {
326         return "SOCK_PACKET";
327 #endif
328     } else {
329         return "SOCK_UNKNOWN";
330     }
331 }
332 /* for getsockopt()/setsockopt(). */
333 static const char *level_to_name(int level) {
334     if (level == SOL_SOCKET) {
335         return "SOL_SOCKET";
336 #ifdef SOL_IP
337     } else if (level == SOL_IP) {
338         return "SOL_IP";
339 #endif
340 #ifdef SOL_IPV6
341     } else if (level == SOL_IPV6) {
342         return "SOL_IPV6";
343 #endif
344     } else if (level == IPPROTO_TCP) {
345         return "IPPROTO_TCP";
346     } else if (level == IPPROTO_UDP) {
347         return "IPPROTO_UDP";
348     } else {
349         return "SOL_UNKNOWN";
350     }
351 }
352
353
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");
362         return -1;
363     }
364
365     const char *socket_path = get_socket_path();
366
367     /* The program may open multiple sockets, e.g. IPv4 and IPv6 and on
368      * multiple ports. Create unique paths. */
369     const char *af;
370     int port;
371     if (addr->sa_family == AF_INET) {
372         af   = "v4";
373         port = ntohs(((struct sockaddr_in *)addr)->sin_port);
374     } else if (addr->sa_family == AF_INET6) {
375         af   = "v6";
376         port = ntohs(((struct sockaddr_in6 *)addr)->sin6_port);
377     } else {
378         af   = "unknown";
379         port = 0;
380         WARN("unknown sa_family '%s' (%d)\n",
381              af_to_name(addr->sa_family), addr->sa_family);
382     }
383
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);
392     }
393
394     return 0;
395 }
396
397 static int replace_socket(int replaceefd, int replacerfd) {
398     static int (*real_close)(int);
399     LOAD_FUNCTION(real_close, "close");
400
401     /* Replace socket replaceefd with replacerfd. After dup2() both socket fds
402      * point to the same socket (replacerfd). */
403     if (dup2(replacerfd, replaceefd) < 0) {
404         return -1;
405     }
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);
410     return 0;
411 }
412
413
414 /* FUNCTIONS OVERWRITTEN BY LD_PRELOAD */
415
416 int socket(int domain, int type, int protocol) {
417     static int (*real_socket)(int, int, int);
418     LOAD_FUNCTION(real_socket, "socket");
419
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. */
423
424     int sockfd = real_socket(domain, type, protocol);
425     if (sockfd < 0
426             || domain == AF_UNIX
427             || domain == AF_LOCAL) {
428         return sockfd;
429     }
430
431     DBG("socket(%s, %s, %d)\n",
432         af_to_name(domain), sock_to_name(type), protocol);
433
434     struct list *entry = xmalloc(sizeof(*entry));
435     memset(entry, 0, sizeof(*entry));
436
437     entry->orig_sockfd = sockfd;
438     entry->orig_domain = domain;
439     entry->orig_type   = type;
440
441     entry->next = socket_list.next;
442     socket_list.next = entry;
443
444     return sockfd;
445 }
446
447 int close(int fd) {
448     static int (*real_close)(int);
449     LOAD_FUNCTION(real_close, "close");
450
451     DBG("close(%d)\n", fd);
452
453     struct list *entry = remove_sockfd(fd);
454     if (entry == NULL) {
455         DBG("close(%d): sockfd not found\n", fd);
456         return real_close(fd);
457     }
458     assert(fd == entry->orig_sockfd);
459     free(entry->orig_addr);
460     free(entry);
461
462     return real_close(fd);
463 }
464
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");
468
469     DBG("bind(%d, ..)\n", sockfd);
470
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);
475     }
476
477     struct list *entry = find_sockfd(sockfd);
478     if (!entry) {
479         DBG("bind(%d, ..): sockfd not found\n", sockfd);
480         return real_bind(sockfd, addr, addrlen);
481     }
482     assert(sockfd == entry->orig_sockfd);
483     DBG("bind(%d, ..): %s %s\n",
484         sockfd,
485         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
486
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;
491
492     return real_bind(sockfd, addr, addrlen);
493 }
494
495 int listen(int sockfd, int backlog) {
496     static int (*real_listen)(int, int);
497     LOAD_FUNCTION(real_listen, "listen");
498
499     if (!global_options) {
500         global_options = get_options();
501     }
502
503     if (global_options & OPTION_CLIENT_ONLY) {
504         DBG("listen(%d, %d): server hooking disabled\n", sockfd, backlog);
505         return real_listen(sockfd, backlog);
506     }
507
508     struct list *entry = find_sockfd(sockfd);
509     if (!entry) {
510         DBG("listen(%d, %d): sockfd not found\n", sockfd, backlog);
511         return real_listen(sockfd, backlog);
512     }
513     assert(sockfd == entry->orig_sockfd);
514     DBG("listen(%d, %d): %s %s\n",
515         sockfd, backlog,
516         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
517
518     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
519     if (unix_sockfd < 0) {
520         DIE("listen(): failed to create UNIX socket");
521     }
522
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);
527     }
528
529     DBG("listen(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
530
531     int attempts = 0;
532     while (attempts < 10) {
533         if (bind(unix_sockfd, (struct sockaddr *)&sockaddr,
534                               sizeof(sockaddr)) == 0) {
535             break;
536         }
537         if (errno != EADDRINUSE) {
538             DIE("listen(%d, ..): failed to bind to '%s'",
539                 sockfd, sockaddr.sun_path);
540         }
541
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 .. */
545
546         struct stat buf;
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);
551         }
552
553         if (!S_ISSOCK(buf.st_mode)) {
554             ERROR("listen(%d, ..): path '%s' exits and is no socket\n",
555                   sockfd, sockaddr.sun_path);
556         }
557
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);
562         }
563
564         attempts++;
565     }
566
567     if (attempts == 10) {
568         ERROR("listen(%d, ..): failed to create UNIX socket file\n", sockfd);
569     }
570
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");
574     }
575
576     if (real_listen(entry->orig_sockfd, backlog) != 0) {
577         DIE("listen(): failed to listen");
578     }
579
580     return 0;
581 }
582
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");
586
587     if (!global_options) {
588         global_options = get_options();
589     }
590
591     if (global_options & OPTION_CLIENT_ONLY) {
592         DBG("accept(%d, ..): server hooking disabled\n", sockfd);
593         return real_accept(sockfd, addr, addrlen);
594     }
595
596     DBG("accept(%d, ..)\n", sockfd);
597
598     struct list *entry = find_sockfd(sockfd);
599     if (!entry) {
600         DBG("accept(%d, ..): sockfd not found\n", sockfd);
601         return real_accept(sockfd, addr, addrlen);
602     }
603     assert(sockfd == entry->orig_sockfd);
604     DBG("accept(%d, ..): %s %s\n",
605         sockfd,
606         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
607
608     struct sockaddr_un sockaddr;
609     socklen_t size = sizeof(sockaddr);
610     int sock = real_accept(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
611                                                &size);
612     if (sock < 0) {
613         DIE("accept(%d, ..): failed to accept", sockfd);
614     }
615
616     if (addr == NULL || addrlen == NULL) {
617         return sock;
618     }
619     DBG("accept(%d, ..): caller requested sockaddr\n", sockfd);
620
621     if (*addrlen < size) {
622         WARN("accept(%d, ..): invalid addrlen from program", sockfd);
623         errno = EINVAL;
624         return -1;
625     }
626
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);
630     *addrlen = size;
631
632     /* TODO: is this enough? */
633
634     return sock;
635 }
636
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");
640
641     if (!global_options) {
642         global_options = get_options();
643     }
644
645     if (global_options & OPTION_SERVER_ONLY) {
646         DBG("connect(%d, ..): client hooking disabled\n", sockfd);
647         return real_connect(sockfd, addr, addrlen);
648     }
649
650     DBG("connect(%d, ..)\n", sockfd);
651
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);
656     }
657
658     struct list *entry = find_sockfd(sockfd);
659     if (!entry) {
660         DBG("connect(%d, ..): sockfd not found\n", sockfd);
661         return real_connect(sockfd, addr, addrlen);
662     }
663     assert(sockfd == entry->orig_sockfd);
664     DBG("connect(%d, ..): %s %s\n",
665         sockfd,
666         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
667
668     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
669     if (unix_sockfd < 0) {
670         DIE("bind(): failed to create UNIX socket");
671     }
672
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");
676     }
677
678     struct sockaddr_un sockaddr;
679     if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
680         ERROR("connect(%d, ..) failed\n", sockfd);
681     }
682
683     DBG("connect(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
684
685     if (real_connect(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
686                                          sizeof(sockaddr)) != 0) {
687         DIE("connect(%d, ..): failed to connect", sockfd);
688     }
689
690     return 0;
691 }
692
693
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");
697
698     DBG("getsockname(%d, ..)\n", sockfd);
699
700     return real_getsockname(sockfd, addr, addrlen);
701 }
702
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");
706
707     DBG("getpeername(%d, ..)\n", sockfd);
708
709     return real_getpeername(sockfd, addr, addrlen);
710 }
711
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");
715
716     DBG("getsockopt(%d, %d %s, %d, ..)\n",
717         sockfd, level, level_to_name(level), optname);
718
719     return real_getsockopt(sockfd, level, optname, optval, optlen);
720 }
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");
724
725     DBG("setsockopt(%d, %d %s, %d, ..)\n",
726         sockfd, level, level_to_name(level), optname);
727
728     return real_setsockopt(sockfd, level, optname, optval, optlen);
729 }