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