]> ruderich.org/simon Gitweb - socket2unix/socket2unix.git/blob - src/socket2unix.c
693892d5c44c8190c2f5b39b38e0e89ef1cdf4d4
[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 *start = getenv("SOCKET2UNIX_OPTIONS");
234     if (!start) {
235         return OPTION_PARSED;
236     }
237
238     int options = OPTION_PARSED;
239
240     const char *end = start + strlen(start);
241     const char *pos, *curend;
242
243     for (pos = start; pos < end; pos = curend + 1) {
244         size_t length;
245
246         curend = strchr(pos, ',');
247         if (curend == NULL) {
248             curend = end;
249         }
250         length = (size_t)(curend - pos);
251
252         if (!strncmp("client_only", pos, length)) {
253             options |= OPTION_CLIENT_ONLY;
254         } else if (!strncmp("server_only", pos, length)) {
255             options |= OPTION_SERVER_ONLY;
256         } else {
257             char option[length + 1];
258             strncpy(option, pos, length);
259             option[length] = '\0';
260             ERROR("unknown option '%s' in SOCKET2UNIX_OPTIONS\n",
261                   option);
262         }
263     }
264
265     if ((options & OPTION_CLIENT_ONLY) && (options & OPTION_SERVER_ONLY)) {
266         ERROR("conflicting options 'client_only', 'server_only' "
267               "in SOCKET2UNIX_OPTIONS\n");
268     }
269
270     return options;
271 }
272
273 static const char *af_to_name(int af) {
274     if (af == AF_UNIX) {
275         return "AF_UNIX";
276     } else if (af == AF_LOCAL) {
277         return "AF_LOCAL";
278     } else if (af == AF_INET) {
279         return "AF_INET";
280     } else if (af == AF_INET6) {
281         return "AF_INET6";
282     } else if (af == AF_IPX) {
283         return "AF_IPX";
284 #ifdef AF_NETLINK
285     } else if (af == AF_NETLINK) {
286         return "AF_NETLINK";
287 #endif
288 #ifdef AF_X25
289     } else if (af == AF_X25) {
290         return "AF_X25";
291 #endif
292 #ifdef AF_AX25
293     } else if (af == AF_AX25) {
294         return "AF_AX25";
295 #endif
296 #ifdef AF_ATMPVC
297     } else if (af == AF_ATMPVC) {
298         return "AF_ATMPVC";
299 #endif
300     } else if (af == AF_APPLETALK) {
301         return "AF_APPLETALK";
302 #ifdef AF_PACKET
303     } else if (af == AF_PACKET) {
304         return "AF_PACKET";
305 #endif
306     } else {
307         return "AF_UNKNOWN";
308     }
309 }
310 static const char *sock_to_name(int sock) {
311     if (sock & SOCK_STREAM) {
312         return "SOCK_STREAM";
313     } else if (sock & SOCK_DGRAM) {
314         return "SOCK_DGRAM";
315     } else if (sock & SOCK_SEQPACKET) {
316         return "SOCK_SEQPACKET";
317     } else if (sock & SOCK_RAW) {
318         return "SOCK_RAW";
319     } else if (sock & SOCK_RDM) {
320         return "SOCK_RDM";
321 #ifdef SOCK_PACKET
322     } else if (sock & SOCK_PACKET) {
323         return "SOCK_PACKET";
324 #endif
325     } else {
326         return "SOCK_UNKNOWN";
327     }
328 }
329 /* for getsockopt()/setsockopt(). */
330 static const char *level_to_name(int level) {
331     if (level == SOL_SOCKET) {
332         return "SOL_SOCKET";
333 #ifdef SOL_IP
334     } else if (level == SOL_IP) {
335         return "SOL_IP";
336 #endif
337 #ifdef SOL_IPV6
338     } else if (level == SOL_IPV6) {
339         return "SOL_IPV6";
340 #endif
341     } else if (level == IPPROTO_TCP) {
342         return "IPPROTO_TCP";
343     } else if (level == IPPROTO_UDP) {
344         return "IPPROTO_UDP";
345     } else {
346         return "SOL_UNKNOWN";
347     }
348 }
349
350
351 static int set_sockaddr_un(struct sockaddr_un *sockaddr,
352                            const struct sockaddr *addr, socklen_t addrlen) {
353     /* Just in case ... */
354     if ((addr->sa_family == AF_INET
355                 && addrlen < sizeof(struct sockaddr_in))
356             || (addr->sa_family == AF_INET6
357                 && addrlen < sizeof(struct sockaddr_in6))) {
358         WARN("invalid addrlen from program\n");
359         return -1;
360     }
361
362     const char *socket_path = get_socket_path();
363
364     /* The program may open multiple sockets, e.g. IPv4 and IPv6 and on
365      * multiple ports. Create unique paths. */
366     const char *af;
367     int port;
368     if (addr->sa_family == AF_INET) {
369         af   = "v4";
370         port = ntohs(((const struct sockaddr_in *)addr)->sin_port);
371     } else if (addr->sa_family == AF_INET6) {
372         af   = "v6";
373         port = ntohs(((const struct sockaddr_in6 *)addr)->sin6_port);
374     } else {
375         af   = "unknown";
376         port = 0;
377         WARN("unknown sa_family '%s' (%d)\n",
378              af_to_name(addr->sa_family), addr->sa_family);
379     }
380
381     /* Initialize sockaddr_un. */
382     sockaddr->sun_family = AF_UNIX;
383     int written = snprintf(sockaddr->sun_path, sizeof(sockaddr->sun_path),
384                            "%s-%s-%d", socket_path, af, port);
385     /* The maximum length is quite short, check it. */
386     if (written >= (int)sizeof(sockaddr->sun_path)) {
387         ERROR("path '%s-%s-%d' too long for UNIX socket",
388               socket_path, af, port);
389     }
390
391     return 0;
392 }
393
394 static int replace_socket(int replaceefd, int replacerfd) {
395     static int (*real_close)(int);
396     LOAD_FUNCTION(real_close, "close");
397
398     /* Replace socket replaceefd with replacerfd. After dup2() both socket fds
399      * point to the same socket (replacerfd). */
400     if (dup2(replacerfd, replaceefd) < 0) {
401         return -1;
402     }
403     /* We don't need replacerfd anymore. The program will use our replacement
404      * and we don't need it for anything else. Use real_close() to prevent
405      * unnecessary debug messages. */
406     real_close(replacerfd);
407     return 0;
408 }
409
410
411 /* FUNCTIONS OVERWRITTEN BY LD_PRELOAD */
412
413 int socket(int domain, int type, int protocol) {
414     static int (*real_socket)(int, int, int);
415     LOAD_FUNCTION(real_socket, "socket");
416
417     /* We return the normal socket because we don't know yet if it's a client
418      * or a listen socket and therefore if we should replace it or not. This
419      * happens in listen() and connect(), see below. */
420
421     int sockfd = real_socket(domain, type, protocol);
422     if (sockfd < 0
423             || domain == AF_UNIX
424             || domain == AF_LOCAL) {
425         return sockfd;
426     }
427
428     DBG("socket(%s, %s, %d)\n",
429         af_to_name(domain), sock_to_name(type), protocol);
430
431     struct list *entry = xmalloc(sizeof(*entry));
432     memset(entry, 0, sizeof(*entry));
433
434     entry->orig_sockfd = sockfd;
435     entry->orig_domain = domain;
436     entry->orig_type   = type;
437
438     entry->next = socket_list.next;
439     socket_list.next = entry;
440
441     return sockfd;
442 }
443
444 int close(int fd) {
445     static int (*real_close)(int);
446     LOAD_FUNCTION(real_close, "close");
447
448     struct list *entry = remove_sockfd(fd);
449     if (entry == NULL) {
450         DBG("close(%d): sockfd not found\n", fd);
451         return real_close(fd);
452     }
453     assert(fd == entry->orig_sockfd);
454
455     DBG("close(%d)\n", fd);
456     free(entry->orig_addr);
457     free(entry);
458
459     return real_close(fd);
460 }
461
462 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
463     static int (*real_bind)(int, const struct sockaddr *, socklen_t);
464     LOAD_FUNCTION(real_bind, "bind");
465
466     if (addr == NULL || addrlen < sizeof(addr->sa_family)
467             || addr->sa_family == AF_UNIX
468             || addr->sa_family == AF_LOCAL) {
469         return real_bind(sockfd, addr, addrlen);
470     }
471
472     struct list *entry = find_sockfd(sockfd);
473     if (!entry) {
474         DBG("bind(%d, ..): sockfd not found\n", sockfd);
475         return real_bind(sockfd, addr, addrlen);
476     }
477     assert(sockfd == entry->orig_sockfd);
478     DBG("bind(%d, ..): %s %s\n",
479         sockfd,
480         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
481
482     /* Copy struct sockaddr, we need it later in listen(). */
483     entry->orig_addr = xmalloc(addrlen);
484     memcpy(entry->orig_addr, addr, addrlen);
485     entry->orig_addrlen = addrlen;
486
487     return real_bind(sockfd, addr, addrlen);
488 }
489
490 int listen(int sockfd, int backlog) {
491     static int (*real_listen)(int, int);
492     LOAD_FUNCTION(real_listen, "listen");
493
494     if (!global_options) {
495         global_options = get_options();
496     }
497
498     if (global_options & OPTION_CLIENT_ONLY) {
499         DBG("listen(%d, %d): server hooking disabled\n", sockfd, backlog);
500         return real_listen(sockfd, backlog);
501     }
502
503     struct list *entry = find_sockfd(sockfd);
504     if (!entry) {
505         DBG("listen(%d, %d): sockfd not found\n", sockfd, backlog);
506         return real_listen(sockfd, backlog);
507     }
508     assert(sockfd == entry->orig_sockfd);
509     DBG("listen(%d, %d): %s %s\n",
510         sockfd, backlog,
511         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
512
513     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
514     if (unix_sockfd < 0) {
515         DIE("listen(): failed to create UNIX socket");
516     }
517
518     struct sockaddr_un sockaddr;
519     if (set_sockaddr_un(&sockaddr, entry->orig_addr,
520                                    entry->orig_addrlen) != 0) {
521         ERROR("listen(%d, ..) failed\n", sockfd);
522     }
523
524     DBG("listen(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
525
526     int attempts = 0;
527     while (attempts < 10) {
528         if (bind(unix_sockfd, (struct sockaddr *)&sockaddr,
529                               sizeof(sockaddr)) == 0) {
530             break;
531         }
532         if (errno != EADDRINUSE) {
533             DIE("listen(%d, ..): failed to bind to '%s'",
534                 sockfd, sockaddr.sun_path);
535         }
536
537         /* File already exists, unlink it if it's a socket. This has a race
538          * condition, but the worst case is that we delete a file created by
539          * the user at the path he told us to use. Tough luck .. */
540
541         struct stat buf;
542         if (lstat(sockaddr.sun_path, &buf) != 0) {
543             /* Looks like a race, better abort. */
544             DIE("listen(%d, ..): lstat on UNIX socket '%s' failed",
545                 sockfd, sockaddr.sun_path);
546         }
547
548         if (!S_ISSOCK(buf.st_mode)) {
549             ERROR("listen(%d, ..): path '%s' exits and is no socket\n",
550                   sockfd, sockaddr.sun_path);
551         }
552
553         WARN("listen(%d, ..): unlinking '%s'\n", sockfd, sockaddr.sun_path);
554         if (unlink(sockaddr.sun_path) != 0) {
555             DIE("listen(%d, ..): unlink '%s' failed",
556                 sockfd, sockaddr.sun_path);
557         }
558
559         attempts++;
560     }
561
562     if (attempts == 10) {
563         ERROR("listen(%d, ..): failed to create UNIX socket file\n", sockfd);
564     }
565
566     /* Replace the original socket of the program with our socket. */
567     if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
568         DIE("listen(): failed to replace socket");
569     }
570
571     if (real_listen(entry->orig_sockfd, backlog) != 0) {
572         DIE("listen(): failed to listen");
573     }
574
575     return 0;
576 }
577
578 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
579     static int (*real_accept)(int, struct sockaddr *, socklen_t *);
580     LOAD_FUNCTION(real_accept, "accept");
581
582     if (!global_options) {
583         global_options = get_options();
584     }
585
586     if (global_options & OPTION_CLIENT_ONLY) {
587         DBG("accept(%d, ..): server hooking disabled\n", sockfd);
588         return real_accept(sockfd, addr, addrlen);
589     }
590
591     struct list *entry = find_sockfd(sockfd);
592     if (!entry) {
593         DBG("accept(%d, ..): sockfd not found\n", sockfd);
594         return real_accept(sockfd, addr, addrlen);
595     }
596     assert(sockfd == entry->orig_sockfd);
597     DBG("accept(%d, ..): %s %s\n",
598         sockfd,
599         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
600
601     struct sockaddr_un sockaddr;
602     socklen_t size = sizeof(sockaddr);
603     int sock = real_accept(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
604                                                &size);
605     if (sock < 0) {
606         DIE("accept(%d, ..): failed to accept", sockfd);
607     }
608
609     if (addr == NULL || addrlen == NULL) {
610         return sock;
611     }
612     DBG("accept(%d, ..): caller requested sockaddr\n", sockfd);
613
614     if (*addrlen < size) {
615         WARN("accept(%d, ..): invalid addrlen from program", sockfd);
616         errno = EINVAL;
617         return -1;
618     }
619
620     /* This is not the protocol the program asked for (AF_* vs. AF_UNIX), but
621      * it should work most of the time. */
622     memcpy(addr, &sockaddr, size);
623     *addrlen = size;
624
625     /* TODO: is this enough? */
626
627     return sock;
628 }
629
630 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) {
631     static int (*real_connect)(int, const struct sockaddr *, socklen_t);
632     LOAD_FUNCTION(real_connect, "connect");
633
634     if (!global_options) {
635         global_options = get_options();
636     }
637
638     if (global_options & OPTION_SERVER_ONLY) {
639         DBG("connect(%d, ..): client hooking disabled\n", sockfd);
640         return real_connect(sockfd, addr, addrlen);
641     }
642
643     if (addr == NULL || addrlen < sizeof(addr->sa_family)
644             || addr->sa_family == AF_UNIX
645             || addr->sa_family == AF_LOCAL) {
646         return real_connect(sockfd, addr, addrlen);
647     }
648
649     struct list *entry = find_sockfd(sockfd);
650     if (!entry) {
651         DBG("connect(%d, ..): sockfd not found\n", sockfd);
652         return real_connect(sockfd, addr, addrlen);
653     }
654     assert(sockfd == entry->orig_sockfd);
655     DBG("connect(%d, ..): %s %s\n",
656         sockfd,
657         af_to_name(entry->orig_domain), sock_to_name(entry->orig_type));
658
659     int unix_sockfd = socket(AF_UNIX, entry->orig_type, 0);
660     if (unix_sockfd < 0) {
661         DIE("bind(): failed to create UNIX socket");
662     }
663
664     /* Replace the original socket of the program with our socket. */
665     if (replace_socket(entry->orig_sockfd, unix_sockfd)) {
666         DIE("connect(): failed to replace socket");
667     }
668
669     struct sockaddr_un sockaddr;
670     if (set_sockaddr_un(&sockaddr, addr, addrlen) != 0) {
671         ERROR("connect(%d, ..) failed\n", sockfd);
672     }
673
674     DBG("connect(%d, ..): using path '%s'\n", sockfd, sockaddr.sun_path);
675
676     if (real_connect(entry->orig_sockfd, (struct sockaddr *)&sockaddr,
677                                          sizeof(sockaddr)) != 0) {
678         DIE("connect(%d, ..): failed to connect", sockfd);
679     }
680
681     return 0;
682 }
683
684
685 int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
686     static int (*real_getsockname)(int, struct sockaddr *, socklen_t *);
687     LOAD_FUNCTION(real_getsockname, "getsockname");
688
689     DBG("getsockname(%d, ..)\n", sockfd);
690
691     return real_getsockname(sockfd, addr, addrlen);
692 }
693
694 int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
695     static int (*real_getpeername)(int, struct sockaddr *, socklen_t *);
696     LOAD_FUNCTION(real_getpeername, "getpeername");
697
698     DBG("getpeername(%d, ..)\n", sockfd);
699
700     return real_getpeername(sockfd, addr, addrlen);
701 }
702
703 int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) {
704     static int (*real_getsockopt)(int, int, int, void *, socklen_t *);
705     LOAD_FUNCTION(real_getsockopt, "getsockopt");
706
707     DBG("getsockopt(%d, %d %s, %d, ..)\n",
708         sockfd, level, level_to_name(level), optname);
709
710     return real_getsockopt(sockfd, level, optname, optval, optlen);
711 }
712 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) {
713     static int (*real_setsockopt)(int, int, int, const void *, socklen_t);
714     LOAD_FUNCTION(real_setsockopt, "setsockopt");
715
716     DBG("setsockopt(%d, %d %s, %d, ..)\n",
717         sockfd, level, level_to_name(level), optname);
718
719     return real_setsockopt(sockfd, level, optname, optval, optlen);
720 }