]> ruderich.org/simon Gitweb - socket2unix/socket2unix.git/blob - src/socket2unix.c
Remove unnecessary DBG() output.
[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  (1 << 10)
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(((const struct sockaddr_in *)addr)->sin_port);
374     } else if (addr->sa_family == AF_INET6) {
375         af   = "v6";
376         port = ntohs(((const 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     struct list *entry = remove_sockfd(fd);
452     if (entry == NULL) {
453         DBG("close(%d): sockfd not found\n", fd);
454         return real_close(fd);
455     }
456     assert(fd == entry->orig_sockfd);
457
458     DBG("close(%d)\n", fd);
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     if (addr == NULL || addrlen < sizeof(addr->sa_family)
470             || addr->sa_family == AF_UNIX
471             || addr->sa_family == AF_LOCAL) {
472         return real_bind(sockfd, addr, addrlen);
473     }
474
475     struct list *entry = find_sockfd(sockfd);
476     if (!entry) {
477         DBG("bind(%d, ..): sockfd not found\n", sockfd);
478         return real_bind(sockfd, addr, addrlen);
479     }
480     assert(sockfd == entry->orig_sockfd);
481     DBG("bind(%d, ..): %s %s\n",
482         sockfd,
483         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
484
485     /* Copy struct sockaddr, we need it later in listen(). */
486     entry->orig_addr = xmalloc(addrlen);
487     memcpy(entry->orig_addr, addr, addrlen);
488     entry->orig_addrlen = addrlen;
489
490     return real_bind(sockfd, addr, addrlen);
491 }
492
493 int listen(int sockfd, int backlog) {
494     static int (*real_listen)(int, int);
495     LOAD_FUNCTION(real_listen, "listen");
496
497     if (!global_options) {
498         global_options = get_options();
499     }
500
501     if (global_options & OPTION_CLIENT_ONLY) {
502         DBG("listen(%d, %d): server hooking disabled\n", sockfd, backlog);
503         return real_listen(sockfd, backlog);
504     }
505
506     struct list *entry = find_sockfd(sockfd);
507     if (!entry) {
508         DBG("listen(%d, %d): sockfd not found\n", sockfd, backlog);
509         return real_listen(sockfd, backlog);
510     }
511     assert(sockfd == entry->orig_sockfd);
512     DBG("listen(%d, %d): %s %s\n",
513         sockfd, backlog,
514         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
515
516     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
517     if (unix_sockfd < 0) {
518         DIE("listen(): failed to create UNIX socket");
519     }
520
521     struct sockaddr_un sockaddr;
522     if (set_sockaddr_un(&sockaddr, entry->orig_addr,
523                                    entry->orig_addrlen) != 0) {
524         ERROR("listen(%d, ..) failed\n", sockfd);
525     }
526
527     DBG("listen(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
528
529     int attempts = 0;
530     while (attempts < 10) {
531         if (bind(unix_sockfd, (struct sockaddr *)&sockaddr,
532                               sizeof(sockaddr)) == 0) {
533             break;
534         }
535         if (errno != EADDRINUSE) {
536             DIE("listen(%d, ..): failed to bind to '%s'",
537                 sockfd, sockaddr.sun_path);
538         }
539
540         /* File already exists, unlink it if it's a socket. This has a race
541          * condition, but the worst case is that we delete a file created by
542          * the user at the path he told us to use. Tough luck .. */
543
544         struct stat buf;
545         if (lstat(sockaddr.sun_path, &buf) != 0) {
546             /* Looks like a race, better abort. */
547             DIE("listen(%d, ..): lstat on UNIX socket '%s' failed",
548                 sockfd, sockaddr.sun_path);
549         }
550
551         if (!S_ISSOCK(buf.st_mode)) {
552             ERROR("listen(%d, ..): path '%s' exits and is no socket\n",
553                   sockfd, sockaddr.sun_path);
554         }
555
556         WARN("listen(%d, ..): unlinking '%s'\n", sockfd, sockaddr.sun_path);
557         if (unlink(sockaddr.sun_path) != 0) {
558             DIE("listen(%d, ..): unlink '%s' failed",
559                 sockfd, sockaddr.sun_path);
560         }
561
562         attempts++;
563     }
564
565     if (attempts == 10) {
566         ERROR("listen(%d, ..): failed to create UNIX socket file\n", sockfd);
567     }
568
569     /* Replace the original socket of the program with our socket. */
570     if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
571         DIE("listen(): failed to replace socket");
572     }
573
574     if (real_listen(entry->orig_sockfd, backlog) != 0) {
575         DIE("listen(): failed to listen");
576     }
577
578     return 0;
579 }
580
581 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
582     static int (*real_accept)(int, struct sockaddr *, socklen_t *);
583     LOAD_FUNCTION(real_accept, "accept");
584
585     if (!global_options) {
586         global_options = get_options();
587     }
588
589     if (global_options & OPTION_CLIENT_ONLY) {
590         DBG("accept(%d, ..): server hooking disabled\n", sockfd);
591         return real_accept(sockfd, addr, addrlen);
592     }
593
594     struct list *entry = find_sockfd(sockfd);
595     if (!entry) {
596         DBG("accept(%d, ..): sockfd not found\n", sockfd);
597         return real_accept(sockfd, addr, addrlen);
598     }
599     assert(sockfd == entry->orig_sockfd);
600     DBG("accept(%d, ..): %s %s\n",
601         sockfd,
602         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
603
604     struct sockaddr_un sockaddr;
605     socklen_t size = sizeof(sockaddr);
606     int sock = real_accept(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
607                                                &size);
608     if (sock < 0) {
609         DIE("accept(%d, ..): failed to accept", sockfd);
610     }
611
612     if (addr == NULL || addrlen == NULL) {
613         return sock;
614     }
615     DBG("accept(%d, ..): caller requested sockaddr\n", sockfd);
616
617     if (*addrlen < size) {
618         WARN("accept(%d, ..): invalid addrlen from program", sockfd);
619         errno = EINVAL;
620         return -1;
621     }
622
623     /* This is not the protocol the program asked for (AF_* vs. AF_UNIX), but
624      * it should work most of the time. */
625     memcpy(addr, &sockaddr, size);
626     *addrlen = size;
627
628     /* TODO: is this enough? */
629
630     return sock;
631 }
632
633 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
634     static int (*real_connect)(int, const struct sockaddr *, socklen_t);
635     LOAD_FUNCTION(real_connect, "connect");
636
637     if (!global_options) {
638         global_options = get_options();
639     }
640
641     if (global_options & OPTION_SERVER_ONLY) {
642         DBG("connect(%d, ..): client hooking disabled\n", sockfd);
643         return real_connect(sockfd, addr, addrlen);
644     }
645
646     if (addr == NULL || addrlen < sizeof(addr->sa_family)
647             || addr->sa_family == AF_UNIX
648             || addr->sa_family == AF_LOCAL) {
649         return real_connect(sockfd, addr, addrlen);
650     }
651
652     struct list *entry = find_sockfd(sockfd);
653     if (!entry) {
654         DBG("connect(%d, ..): sockfd not found\n", sockfd);
655         return real_connect(sockfd, addr, addrlen);
656     }
657     assert(sockfd == entry->orig_sockfd);
658     DBG("connect(%d, ..): %s %s\n",
659         sockfd,
660         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
661
662     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
663     if (unix_sockfd < 0) {
664         DIE("bind(): failed to create UNIX socket");
665     }
666
667     /* Replace the original socket of the program with our socket. */
668     if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
669         DIE("connect(): failed to replace socket");
670     }
671
672     struct sockaddr_un sockaddr;
673     if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
674         ERROR("connect(%d, ..) failed\n", sockfd);
675     }
676
677     DBG("connect(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
678
679     if (real_connect(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
680                                          sizeof(sockaddr)) != 0) {
681         DIE("connect(%d, ..): failed to connect", sockfd);
682     }
683
684     return 0;
685 }
686
687
688 int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
689     static int (*real_getsockname)(int, struct sockaddr *, socklen_t *);
690     LOAD_FUNCTION(real_getsockname, "getsockname");
691
692     DBG("getsockname(%d, ..)\n", sockfd);
693
694     return real_getsockname(sockfd, addr, addrlen);
695 }
696
697 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
698     static int (*real_getpeername)(int, struct sockaddr *, socklen_t *);
699     LOAD_FUNCTION(real_getpeername, "getpeername");
700
701     DBG("getpeername(%d, ..)\n", sockfd);
702
703     return real_getpeername(sockfd, addr, addrlen);
704 }
705
706 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {
707     static int (*real_getsockopt)(int, int, int, void *, socklen_t *);
708     LOAD_FUNCTION(real_getsockopt, "getsockopt");
709
710     DBG("getsockopt(%d, %d %s, %d, ..)\n",
711         sockfd, level, level_to_name(level), optname);
712
713     return real_getsockopt(sockfd, level, optname, optval, optlen);
714 }
715 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {
716     static int (*real_setsockopt)(int, int, int, const void *, socklen_t);
717     LOAD_FUNCTION(real_setsockopt, "setsockopt");
718
719     DBG("setsockopt(%d, %d %s, %d, ..)\n",
720         sockfd, level, level_to_name(level), optname);
721
722     return real_setsockopt(sockfd, level, optname, optval, optlen);
723 }