3 #if defined(SOLARIS) && !defined(_POSIX_SOURCE)
17 #include <sys/select.h>
18 #include <sys/types.h>
19 #include <sys/socket.h>
20 #include <netinet/in.h>
21 #include <arpa/inet.h>
26 #include <sys/queue.h>
29 # include <sys/resource.h>
30 #endif /* HAVE_GETRLIMIT */
32 #include "lru_cache.h"
34 #if !defined(O_NONBLOCK) || defined(ULTRIX)
35 # ifdef FNDELAY /* SunOS */
36 # define O_NONBLOCK FNDELAY
38 # ifdef O_NDELAY /* SysV */
39 # define O_NONBLOCK O_NDELAY
40 # endif /* O_NDELAY */
44 #define NUM_THREADS 5 /* default number of threads to start */
45 #define CACHE_CAPACITY 8192 /* default number of addresses to cache */
46 #define CACHE_HASH_SZ 1021 /* default number of hash slots, prime recommended */
47 #define CACHE_AGE_SUCCESS 60 /* default seconds to maintain a cached success */
48 #define CACHE_AGE_FAIL 1800 /* seconds to maintain a cached fail */
49 #define BUF_SZ 1024 /* size of input and output buffers */
51 #define IDENT_PORT 113 /* port number of the ident service */
52 #define IDENT_TIMEOUT 60 /* number of seconds to wait for an ident connection */
54 #define QUIT_COMMAND "QUIT" /* shut down if this input is encountered */
55 #ifdef WITH_RESOLVER_STATS
56 # define DUMP_COMMAND "DUMP" /* report on internal state */
57 #endif /* WITH_RESOLVER_STATS */
66 unsigned int age_success
;
67 unsigned int age_fail
;
81 N.B. The hostname in a cache entry is a flexible array!
82 If this is ever updated to c99 style, also update how it's
83 allocated in cache_add_.
86 lru_entry_t lru_
; /* must be first */
88 struct sockaddr_storage ss
;
94 static pthread_rwlock_t g_cache_rwlock_
= PTHREAD_RWLOCK_INITIALIZER
;
95 static struct lru_cache
*g_cache_
= NULL
;
97 static volatile unsigned int g_want_shutdown_
= 0;
99 static FILE *g_log_stream_
= NULL
;
101 #define LOG(...) do { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } while (0)
102 #define LOG_V(...) do { if (g_opts_.verbose > 0) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
103 #define LOG_VV(...) do { if (g_opts_.verbose > 1) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
104 #define LOG_VVV(...) do { if (g_opts_.verbose > 2) { fprintf(g_log_stream_, __VA_ARGS__); fflush(g_log_stream_); } } while (0)
108 #define USAGE_FLAG_SHOWFULL (1<<0)
111 usage_(const char *prog
, unsigned int flags
)
113 FILE *f
= (flags
& USAGE_FLAG_SHOWFULL
) ? stdout
: stderr
;
114 char *x
= strrchr(prog
, '/');
119 if (flags
& USAGE_FLAG_SHOWFULL
)
121 "%s -- asynchronous caching hostname and identd lookup tool\n\n"
123 "\tReads lines from stdin, in the format of an IP address,\n"
124 "followed by a remote port number in parenthesis, followed by a local\n"
125 "port number, followed by a newline.\n\n"
127 "\tFor each valid line read, it will attempt to resolve the\n"
128 "hostname of the IP address, and the identd response from the IP for\n"
129 "the port pairing.\n\n",
132 fprintf(f
, "Usage: %s [-h] [-v] [-a <cache_age>] [-f <cache_age>] [-j <num_threads>] [-c <cache_capacity>] [-s <hash_slots>] [-o <log_filename>]\n", prog
);
134 if (flags
& USAGE_FLAG_SHOWFULL
) {
137 "\t -h -- this screen\n"
138 "\t -v -- increase verbosity\n"
139 "\t -o <log_filename> -- write errors and notices to this file (default is to stderr) (implies -v)\n"
140 "\t -j <num_threads> -- use this many threads (default: %zu)\n"
141 "\t -c <cache_capacity> -- cache up to this many addresses (default: %zu)\n"
142 "\t -s <hash_slots> -- use this many bins for hashing (default: %zu)\n"
143 "\t -a <cache_age> -- cache successful queries for this many seconds (default: %u)\n"
144 "\t -f <cache_age> -- cache failed queries for this many seconds (default: %u)\n",
157 #define PTHREAD_OR_DIE__(__fn__,...) do { \
159 r = __fn__(__VA_ARGS__); \
161 LOG("%s: %s\n", #__fn__, strerror(r)); \
166 #define UNLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_unlock, (__l__))
167 #define RDLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_rdlock, (__l__))
168 #define WRLOCK(__l__) PTHREAD_OR_DIE__(pthread_rwlock_wrlock, (__l__))
173 Access AF-specific fields inside a generic sockaddr_storage struct.
177 ss_addr_fields_(const struct sockaddr_storage
*ss
,
178 socklen_t
*sockaddr_len
, void **vaddr
, size_t *addr_len
, unsigned short **port
)
182 if (ss
->ss_family
== AF_INET
) {
183 struct sockaddr_in
*sa
= (struct sockaddr_in
*)ss
;
184 struct in_addr
*a
= &sa
->sin_addr
;
189 *addr_len
= sizeof a
->s_addr
;
191 *sockaddr_len
= sizeof *sa
;
193 *port
= &sa
->sin_port
;
194 } else if (ss
->ss_family
== AF_INET6
) {
195 struct sockaddr_in6
*sa
= (struct sockaddr_in6
*)ss
;
196 struct in6_addr
*a
= &sa
->sin6_addr
;
199 *vaddr
= &a
->s6_addr
;
201 *addr_len
= sizeof a
->s6_addr
;
203 *sockaddr_len
= sizeof *sa
;
205 *port
= &sa
->sin6_port
;
207 LOG_V("unknown AF %d\n", ss
->ss_family
);
223 this is the lru_entry_cmp_fn
224 Two entries are equal if their addresses match.
228 cache_entry_cmp_(lru_entry_t
*a
, lru_entry_t
*b
)
230 struct cache_entry
*ea
= (struct cache_entry
*)a
,
231 *eb
= (struct cache_entry
*)b
;
238 /* Keep things simple: different families are not equal. */
239 if (ea
->ss
.ss_family
!= eb
->ss
.ss_family
)
242 ss_addr_fields_(&ea
->ss
, NULL
, &va
, &addr_len
, NULL
);
246 ss_addr_fields_(&eb
->ss
, NULL
, &vb
, NULL
, NULL
);
250 return memcmp(va
, vb
, addr_len
);
256 this is the lru_hash_feed_fn
257 an entry is hashed by its address
261 cache_entry_hash_feed_(lru_entry_t
*e
, char **buf
, size_t *sz
)
263 struct cache_entry
*entry
= (struct cache_entry
*)e
;
267 assert(entry
!= NULL
);
269 /* just hash the address */
270 ss_addr_fields_(&entry
->ss
, NULL
, &vaddr
, &addr_len
, NULL
);
278 Search the cache for an entry.
279 If it exists, and has not expired, move it to the front of queue.
280 If it has expired, expunge.
284 cache_find_(struct sockaddr_storage
*ss
, char *buf
, size_t buf_sz
)
286 struct cache_entry
*entry
, match
;
291 memset(&match
, 0, sizeof match
);
292 memcpy(&match
.ss
, ss
, sizeof *ss
);
294 RDLOCK(&g_cache_rwlock_
);
297 entry
= (struct cache_entry
*)lru_cache_locate(g_cache_
, (lru_entry_t
*)&match
);
302 If an entry exists in the cache, it will need to be updated, so
303 exchange the read lock for a write lock, and look it up again on
304 the off chance another thread got rid of it before we could.
307 UNLOCK(&g_cache_rwlock_
);
308 WRLOCK(&g_cache_rwlock_
);
313 /* Drop the entry if it is too old. */
314 if (time(NULL
) > entry
->timestamp
+ (time_t)(entry
->succeeded
? g_opts_
.age_success
: g_opts_
.age_fail
)) {
315 LOG_VV("expired %s\n", entry
->hostname
);
316 lru_cache_extract(g_cache_
, (lru_entry_t
*)entry
);
322 /* Peek at the front of the queue, and only reinsert if this entry is not there already. */
323 if ((lru_entry_t
*)entry
!= g_cache_
->newest
) {
324 struct cache_entry
*removed
;
325 lru_cache_extract(g_cache_
, (lru_entry_t
*)entry
);
326 lru_cache_insert(g_cache_
, (lru_entry_t
*)entry
, (lru_entry_t
**)&removed
);
327 /* No need to check removed, the queue can never overflow here. */
331 strncpy(buf
, entry
->hostname
, buf_sz
);
332 if (buf
[buf_sz
- 1] != '\0')
333 buf
[buf_sz
- 1] = '\0';
337 UNLOCK(&g_cache_rwlock_
);
338 return (entry
!= NULL
);
344 Update the cache to include the given data.
345 hostname_len is the strlen of the hostname
349 cache_add_(struct sockaddr_storage
*ss
, const char *hostname
, size_t hostname_len
, time_t timestamp
, int succeeded
)
351 struct cache_entry
*entry
, *old
, match
;
354 assert(hostname
!= NULL
|| hostname_len
== 0);
356 memset(&match
, 0, sizeof match
);
357 memcpy(&match
.ss
, ss
, sizeof *ss
);
360 When allocating an entry, the size of the struct already includes
361 the extra byte for the nul at the end of the hostname.
363 entry
= malloc(sizeof *entry
+ hostname_len
);
365 LOG("%s: %s\n", "malloc", strerror(errno
));
369 entry
->timestamp
= timestamp
;
370 entry
->succeeded
= succeeded
;
371 memcpy(&entry
->ss
, ss
, sizeof *ss
);
372 memcpy(&entry
->hostname
, hostname
, hostname_len
);
373 entry
->hostname
[hostname_len
] = '\0';
375 WRLOCK(&g_cache_rwlock_
);
377 /* assure we're not duplicating entries */
378 old
= (struct cache_entry
*)lru_cache_locate(g_cache_
, (lru_entry_t
*)entry
);
380 lru_cache_extract(g_cache_
, (lru_entry_t
*)old
);
384 lru_cache_insert(g_cache_
, (lru_entry_t
*)entry
, (lru_entry_t
**)&old
);
388 UNLOCK(&g_cache_rwlock_
);
393 #ifdef WITH_RESOLVER_STATS
397 cache_entry_dump_(lru_entry_t
*e
, size_t idx
, void *data
)
400 struct cache_entry
*entry
= (struct cache_entry
*)e
;
401 time_t *now
= (time_t *)data
;
405 assert(data
!= NULL
);
407 r
= getnameinfo((struct sockaddr
*)&(entry
->ss
), sizeof entry
->ss
, buf
, sizeof buf
, NULL
, 0, NI_NUMERICHOST
);
409 LOG("%s: %s\n", "getnameinfo", gai_strerror(r
));
410 snprintf(buf
, sizeof buf
, "(unknown)");
412 fprintf(g_log_stream_
, "%05zu: %s -> %s [%ld]\n", idx
, buf
, entry
->hostname
, *now
- entry
->timestamp
);
417 cache_statdump_(void) {
418 time_t now
= time(NULL
);
421 RDLOCK(&g_cache_rwlock_
);
422 flockfile(g_log_stream_
);
424 fprintf(g_log_stream_
, "-- cache newest to oldest --\n");
425 lru_cache_foreach(g_cache_
, cache_entry_dump_
, &now
);
426 fprintf(g_log_stream_
, "----------------------------\n");
428 fprintf(g_log_stream_
, "----- hash slot depths -----\n");
429 for (i
= 0; i
< g_cache_
->hash_sz
; i
++) {
430 if (g_cache_
->hash
[i
].tally
) {
431 fprintf(g_log_stream_
, "[%zu]: %zu\n", i
, g_cache_
->hash
[i
].tally
);
434 fprintf(g_log_stream_
, "----------------------------\n");
436 funlockfile(g_log_stream_
);
437 UNLOCK(&g_cache_rwlock_
);
440 #endif /* WITH_RESOLVER_STATS */
444 /* empty everything */
445 void cache_free_(void)
447 struct cache_entry
*entry
;
449 while ((entry
= (struct cache_entry
*)g_cache_
->newest
)) {
450 lru_cache_extract(g_cache_
, g_cache_
->newest
);
458 connect wrapper, with timeout
459 deadline is the absolute time after which it will give up
463 connect_nb_(int fd
, struct sockaddr
*sa
, socklen_t sa_len
, time_t deadline
)
466 struct timeval timeout_tv
;
467 time_t now
= time(NULL
);
471 LOG_VVV("connecting fd %d\n", fd
);
473 /* attempt to connect, expect EINPROGRESS or immediate success */
474 while ( (r
= connect(fd
, sa
, sa_len
)) ) {
475 if (errno
== EINPROGRESS
)
480 LOG("%s: %s\n", "connect", strerror(errno
));
484 /* wait for it to become writable, which indicates connect completion */
488 timeout_tv
.tv_sec
= deadline
- now
;
489 timeout_tv
.tv_usec
= 0;
491 r
= select(fd
+ 1, NULL
, &fds
, NULL
, &timeout_tv
);
493 LOG("%s: %s\n", "select", strerror(errno
));
494 if (errno
!= EINTR
&& errno
!= EAGAIN
)
498 && time(&now
) < deadline
);
499 if (now
>= deadline
) {
501 LOG("%s: %s\n", "connect", strerror(errno
));
505 /* connect completed, check for errors */
508 if (getsockopt(fd
, SOL_SOCKET
, SO_ERROR
, &r
, &r_len
)) {
509 LOG("%s: %s\n", "getsockopt", strerror(errno
));
514 LOG("%s: %s\n", "connect", strerror(errno
));
524 write a buffer to a socket, with timeout
528 write_nb_(int fd
, const char *buf
, size_t buf_len
, time_t deadline
)
531 struct timeval timeout_tv
;
532 time_t now
= time(NULL
);
533 ssize_t w_len
, total
;
538 LOG_VVV("writing '%*s' to fd %d\n", (int)buf_len
, buf
, fd
);
542 timeout_tv
.tv_sec
= deadline
- now
;
543 timeout_tv
.tv_usec
= 0;
545 r
= select(fd
+ 1, NULL
, &fds
, NULL
, &timeout_tv
);
547 LOG("%s: %s\n", "select", strerror(errno
));
548 if (errno
== EINTR
|| errno
== EAGAIN
)
555 if (FD_ISSET(fd
, &fds
)) {
556 w_len
= write(fd
, buf
, buf_len
);
558 LOG("%s: %s\n", "write", strerror(errno
));
559 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
563 LOG_VVV("wrote %zd bytes (of %zu) to fd %d\n", w_len
, buf_len
, fd
);
570 && time(&now
) < deadline
);
571 if (now
>= deadline
) {
573 LOG("%s: %s\n", "write", strerror(errno
));
577 LOG_VVV("fd %d wrote %zd bytes\n", fd
, total
);
585 reads at least a line, with a timeout
586 sets *line_end to the first '\n' or '\r' encountered
590 read_line_nb_(int fd
, char *buf
, size_t buf_len
, char **line_end
, time_t deadline
)
593 struct timeval timeout_tv
;
594 time_t now
= time(NULL
);
595 ssize_t r_len
, total
;
603 timeout_tv
.tv_sec
= deadline
- now
;
604 timeout_tv
.tv_usec
= 0;
606 r
= select(fd
+ 1, &fds
, NULL
, NULL
, &timeout_tv
);
608 LOG("%s: %s\n", "select", strerror(errno
));
609 if (errno
== EINTR
|| errno
== EAGAIN
)
616 if (FD_ISSET(fd
, &fds
)) {
617 r_len
= read(fd
, buf
, buf_len
);
619 LOG("%s: %s\n", "read", strerror(errno
));
620 if (errno
== EAGAIN
|| errno
== EWOULDBLOCK
|| errno
== EINTR
)
625 LOG_VVV("fd %d EOF\n", fd
);
629 LOG_VVV("read %zu bytes from fd %d\n", r_len
, fd
);
633 if (*buf
== '\r' || *buf
== '\n') {
634 LOG_VVV("fd %d line-length %zu\n", fd
, total
- r_len
);
642 } while (*line_end
== NULL
644 && time(&now
) < deadline
);
645 if (now
>= deadline
) {
647 LOG("%s: %s\n", "read", strerror(errno
));
657 remove trim characters from start and end of a token
661 strtok_trim_(char *str
, const char *sep
, const char *trim
, char **endp
)
665 tok
= strtok_r(str
, sep
, endp
);
670 while (*tok
&& strchr(trim
, *tok
)) {
675 while (ep
> tok
&& strchr(trim
, *ep
)) {
687 destructively parse response for validity, leaves username in buffer on success
689 an ident response is like:
690 ' port , port : USERID : os : userid'
692 ' port , port : ERROR : error'
696 ident_response_parse_(char *buf
, unsigned short port_remote
, unsigned short port_local
)
701 /* first, ensure response prefix matches request */
704 if ((tok
= strtok_trim_(buf
, ",", " \t", &last
)) == NULL
)
706 if (sscanf(tok
, "%d", &port
) != 1)
708 if (port
!= port_remote
)
712 if ((tok
= strtok_trim_(NULL
, ":", " \t", &last
)) == NULL
)
714 if (sscanf(tok
, "%d", &port
) != 1)
716 if (port
!= port_local
)
720 if ((tok
= strtok_trim_(NULL
, ":", " \t", &last
)) == NULL
)
723 LOG_VVV("ident resonse: %s %s\n", tok
, last
);
725 if (strcasecmp(tok
, "USERID") != 0)
728 /* os type, ignored */
729 if ((tok
= strtok_trim_(NULL
, ":", NULL
, &last
)) == NULL
)
732 LOG_VVV("ident user: %s\n", last
);
734 /* anything remaining is user id */
735 /* move it to the front of the buffer */
736 /* strcpy is safe here because we could not have passed in an unterminated string */
745 Query a host's identity service for user information.
746 This overwrites ss->sin*_port.
750 ident_query_(char *out_buf
, size_t out_buf_sz
, struct sockaddr_storage
*ss
, unsigned short port_remote
, unsigned short port_local
)
759 time_t now
, deadline
;
760 unsigned short *vport
;
761 socklen_t sockaddr_len
;
763 deadline
= time(&now
) + IDENT_TIMEOUT
;
765 fd
= socket(ss
->ss_family
, SOCK_STREAM
, 0);
767 LOG("%s: %s\n", "socket", strerror(errno
));
771 /* make non-blocking */
772 if (fcntl(fd
, F_SETFL
, O_NONBLOCK
) == -1) {
773 LOG("%s: %s\n", "fcntl", strerror(errno
));
777 /* get the sockaddr length, and the port address */
778 ss_addr_fields_(ss
, &sockaddr_len
, NULL
, NULL
, &vport
);
782 *vport
= htons(IDENT_PORT
);
784 r
= connect_nb_(fd
, (struct sockaddr
*)ss
, sockaddr_len
, deadline
);
786 LOG_V("%s: %s\n", "nb_connect_", strerror(errno
));
790 /* craft and send our meager request */
791 len
= snprintf(req_buf
, sizeof req_buf
, "%hu,%hu\r\n", port_remote
, port_local
);
792 r
= write_nb_(fd
, req_buf
, len
, deadline
);
794 LOG_V("%s: %s\n", "nb_write_", strerror(errno
));
798 /* read one full line as a response */
799 r
= read_line_nb_(fd
, buf
, sizeof buf
, &buf_ptr
, deadline
);
801 LOG_V("%s: %s\n", "nb_read_", strerror(errno
));
805 if (buf_ptr
== NULL
) {
806 LOG_VV("incomplete response\n");
812 if (ident_response_parse_(buf
, port_remote
, port_local
))
816 strncpy(out_buf
, buf
, out_buf_sz
);
820 shutdown(fd
, 2); /* be ruthless, discard any remaining data */
825 /* no success, just echo the port */
826 snprintf(out_buf
, out_buf_sz
, "%d", port_remote
);
835 Render the name of an address into the supplied buffer.
836 buf_sz ought to be at least NI_HOSTMAX (1025)
840 host_query_(char *buf
, size_t buf_sz
, struct sockaddr_storage
*ss
)
845 if (cache_find_(ss
, buf
, buf_sz
)) {
846 LOG_VVV("found '%s' in cache\n", buf
);
850 /* not in cache, try to resolve it */
851 r
= getnameinfo((struct sockaddr
*)ss
, sizeof *ss
, buf
, buf_sz
, NULL
, 0, NI_NAMEREQD
);
854 LOG("%s: %s\n", "getnameinfo", gai_strerror(r
));
858 /* not resolvable, render it numerically */
859 r
= getnameinfo((struct sockaddr
*)ss
, sizeof *ss
, buf
, buf_sz
, NULL
, 0, NI_NUMERICHOST
);
861 LOG("%s: %s\n", "getnameinfo", gai_strerror(r
));
862 snprintf(buf
, buf_sz
, "(unknown)");
864 LOG_VVV("failed to resolve '%s'\n", buf
);
867 cache_add_(ss
, buf
, strlen(buf
), time(NULL
), succeeded
);
873 The main processing loop for each thread.
877 resolve_thread_(void *data
)
879 size_t id
= *(size_t *)data
;
881 char hostname_buf
[2048]; /* seems silly-large, but is just the next power-of-two up from NI_MAXHOST (1025) */
882 char username_buf
[512]; /* identd user id might be this long */
884 struct sockaddr_storage ss
;
886 int port_remote
, port_local
;
887 sigset_t sigset_maskall
;
890 LOG_VV("<%zu> thread started\n", id
);
892 sigfillset(&sigset_maskall
);
893 pthread_sigmask(SIG_SETMASK
, &sigset_maskall
, NULL
);
895 for (/* */; ! g_want_shutdown_
; /* */) {
896 memset(&ss
, 0, sizeof ss
);
899 Attempt to fetch a line from stdin.
900 If EOF is encountered, or any error other than EAGAIN,
901 signal global shutdown and bail.
904 if (g_want_shutdown_
) {
909 LOG_VVV("<%zu> awaiting request\n", id
);
911 if (fgets(buf
, sizeof buf
, stdin
) == NULL
) {
912 if (feof_unlocked(stdin
)) {
913 g_want_shutdown_
= 1;
914 } else if (ferror_unlocked(stdin
)) {
915 if (errno
== EINTR
) {
916 clearerr_unlocked(stdin
);
920 LOG("<%zu> %s: %s\n", id
, "fgets", strerror(errno
));
921 g_want_shutdown_
= 1;
924 LOG("<%zu> %s: %s\n", id
, "fgets", "NULL but no error?");
925 g_want_shutdown_
= 1;
929 LOG_VVV("<%zu> '%s'\n", id
, buf
);
932 if (g_want_shutdown_
)
935 /* try again if line is empty */
936 if (*buf
== '\n' || *buf
== '\0')
939 /* explicit exit request */
940 if (strncmp(QUIT_COMMAND
, buf
, strlen(QUIT_COMMAND
)) == 0) {
941 g_want_shutdown_
= 1;
946 #ifdef WITH_RESOLVER_STATS
947 if (strncmp(DUMP_COMMAND
, buf
, strlen(DUMP_COMMAND
)) == 0) {
951 #endif /* WITH_RESOLVER_STATS */
953 /* locate the ports, and parse them first */
954 buf_ptr
= strchr(buf
, '(');
955 if (buf_ptr
== NULL
) {
956 LOG_V("<%zu> malformed request, no paren: %s", id
, buf
); /* expect \n in buf */
960 r
= sscanf(buf_ptr
, "(%d)%d", &port_remote
, &port_local
);
962 LOG_V("<%zu> malformed request, bad ports: %s (%d)", id
, buf_ptr
, r
);
967 || port_local
> (unsigned short)-1
969 || port_remote
> (unsigned short)-1) {
970 LOG_V("<%zu> port out of range: %s", id
, buf
);
974 /* truncate buffer at ports, and parse into an address */
977 /* locate the first delimiter to determine address family */
978 buf_ptr
= strpbrk(buf
, ".:");
979 if (buf_ptr
== NULL
) {
980 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
983 else if (*buf_ptr
== '.') {
985 ss
.ss_family
= AF_INET
;
986 vaddr
= &((struct sockaddr_in
*)&ss
)->sin_addr
;
989 else if (*buf_ptr
== ':') {
991 ss
.ss_family
= AF_INET6
;
992 vaddr
= &((struct sockaddr_in6
*)&ss
)->sin6_addr
;
994 #endif /* USE_IPV6 */
996 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
1000 r
= inet_pton(ss
.ss_family
, buf
, vaddr
);
1002 LOG("%s: %s\n", "inet_pton", strerror(errno
));
1004 } else if (r
== 0) {
1005 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
1009 LOG_VVV("> %s %d %d\n", buf
, port_remote
, port_local
);
1011 ident_query_(username_buf
, sizeof username_buf
, &ss
, port_remote
, port_local
);
1012 host_query_(hostname_buf
, sizeof hostname_buf
, &ss
);
1015 fprintf(stdout
, "%s(%d)|%s(%s)\n", buf
, port_remote
, hostname_buf
, username_buf
);
1017 funlockfile(stdout
);
1019 LOG_VVV("<%zu> '%s(%d)|%s(%s)'\n", id
, buf
, port_remote
, hostname_buf
, username_buf
);
1022 LOG_VV("<%zu> thread exiting\n", id
);
1030 set_signal_(int sig
, void (*func
)(int))
1032 #ifdef _POSIX_VERSION
1033 struct sigaction act
, oact
;
1035 act
.sa_handler
= func
;
1036 sigemptyset(&act
.sa_mask
);
1039 act
.sa_flags
= SA_RESTART
;
1040 # else /* SA_RESTART */
1042 # endif /* SA_RESTART */
1044 if (sigaction(sig
, &act
, &oact
)) {
1045 LOG("%s: %s\n", "sigaction", strerror(errno
));
1047 #else /* _POSIX_VERSION */
1048 if (signal(signo
, sighandler
) == SIG_ERR
) {
1049 LOG("%s: %s\n", "signal", strerror(errno
));
1051 #endif /* _POSIX_VERSION */
1057 Convert a base-10 number in str to a size_t within range specified.
1058 Just a helper for processing command-line arguments.
1062 str_to_sizet_range_(const char *str
, size_t *val
, size_t min
, size_t max
)
1067 num
= strtoll(str
, &end
, 10);
1068 if (*str
== '\0' || *end
!= '\0') {
1069 fprintf(stderr
, "'%s' is not a valid number\n", str
);
1073 || (unsigned long long)num
< min
1074 || (unsigned long long)num
> max
) {
1075 fprintf(stderr
, "%lld is not between %zu and %zu\n", num
, min
, max
);
1084 /* same for unsigned int */
1087 str_to_uint_range_(const char *str
, unsigned int *val
, unsigned int min
, unsigned int max
)
1092 num
= strtoll(str
, &end
, 10);
1093 if (*str
== '\0' || *end
!= '\0') {
1094 fprintf(stderr
, "'%s' is not a valid number\n", str
);
1098 || (unsigned long long)num
< min
1099 || (unsigned long long)num
> max
) {
1100 fprintf(stderr
, "%lld is not between %u and %u\n", num
, min
, max
);
1104 *val
= (unsigned int)num
;
1110 main(int argc
, char **argv
)
1119 #ifdef HAVE_GETRLIMIT
1122 /* close anything not stdio */
1123 getrlimit(RLIMIT_NOFILE
, &lim
);
1124 for (i
= 3; i
< lim
.rlim_cur
; i
++) {
1126 LOG_VV("cleaned up fd %zu\n", i
);
1128 #endif /* HAVE_GETRLIMIT */
1130 g_log_stream_
= stderr
;
1132 while ( (c
= getopt(argc
, argv
, "a:c:f:j:s:o:vh")) != EOF
) {
1135 if (str_to_uint_range_(optarg
, &g_opts_
.age_success
, 0, (unsigned int)-1))
1140 if (str_to_sizet_range_(optarg
, &g_opts_
.capacity
, 256, (size_t)-1))
1145 if (str_to_uint_range_(optarg
, &g_opts_
.age_fail
, 0, (unsigned int)-1))
1150 if (str_to_sizet_range_(optarg
, &g_opts_
.num_threads
, 1, (size_t)-1))
1155 if (str_to_sizet_range_(optarg
, &g_opts_
.hash_sz
, 1, (size_t)-1))
1160 g_opts_
.verbose
+= 1;
1164 g_opts_
.log_filename
= optarg
;
1165 g_opts_
.verbose
+= 1;
1169 usage_(argv
[0], USAGE_FLAG_SHOWFULL
);
1178 if (argc
- optind
!= 0) {
1183 if (g_opts_
.log_filename
) {
1184 g_log_stream_
= fopen(g_opts_
.log_filename
, "a+");
1185 if (g_log_stream_
== NULL
) {
1186 fprintf(stderr
, "could not open '%s': %s\n", g_opts_
.log_filename
, strerror(errno
));
1192 /* forces muck-spawned resolver to log debug messages */
1193 char lf
[] = "/tmp/resolver-debug.XXXXXX";
1194 int lf_fd
= mkstemp(lf
);
1197 fprintf(stderr
, "could not create debug logfile '%s': %s\n", lf
, strerror(errno
));
1200 g_log_stream_
= fdopen(lf_fd
, "a+");
1201 if (g_log_stream_
== NULL
) {
1202 fprintf(stderr
, "%s: %s\n", "fdopen", strerror(errno
));
1205 g_opts_
.verbose
= 3;
1208 g_cache_
= lru_cache_new(g_opts_
.hash_sz
, g_opts_
.capacity
, cache_entry_hash_feed_
, cache_entry_cmp_
);
1209 if (g_cache_
== NULL
) {
1213 threads
= calloc(g_opts_
.num_threads
, sizeof *threads
);
1214 if (threads
== NULL
) {
1215 LOG("%s: %s\n", "calloc", strerror(errno
));
1219 set_signal_(SIGPIPE
, SIG_IGN
);
1220 set_signal_(SIGHUP
, SIG_IGN
);
1222 for (i
= 0; i
< g_opts_
.num_threads
; i
++) {
1224 c
= pthread_create(&threads
[i
].pthr
, NULL
, resolve_thread_
, (void *)&threads
[i
].tid
);
1226 LOG("%s: %s\n", "pthread_create", strerror(c
));
1231 for (i
= 0; i
< g_opts_
.num_threads
; i
++) {
1233 c
= pthread_join(threads
[i
].pthr
, &retval
);
1235 LOG("%s: %s\n", "pthread_join", strerror(c
));
1243 LOG("Resolver exited.\n");
1245 fclose(g_log_stream_
);