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 */
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 allocated
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 #ifdef DEBUG_PRETEND_IDENT
764 size_t delay
= lrand48() % IDENT_TIMEOUT
;
771 LOG_VVV("pretending ident query took %zu seconds\n", delay
);
773 #endif /* DEBUG_PRETEND_IDENT */
775 deadline
= time(&now
) + IDENT_TIMEOUT
;
777 fd
= socket(ss
->ss_family
, SOCK_STREAM
, 0);
779 LOG("%s: %s\n", "socket", strerror(errno
));
783 /* make non-blocking */
784 if (fcntl(fd
, F_SETFL
, O_NONBLOCK
) == -1) {
785 LOG("%s: %s\n", "fcntl", strerror(errno
));
789 /* get the sockaddr length, and the port address */
790 ss_addr_fields_(ss
, &sockaddr_len
, NULL
, NULL
, &vport
);
794 *vport
= htons(IDENT_PORT
);
796 r
= connect_nb_(fd
, (struct sockaddr
*)ss
, sockaddr_len
, deadline
);
798 LOG_V("%s: %s\n", "nb_connect_", strerror(errno
));
802 /* craft and send our meager request */
803 len
= snprintf(req_buf
, sizeof req_buf
, "%hu,%hu\r\n", port_remote
, port_local
);
804 r
= write_nb_(fd
, req_buf
, len
, deadline
);
806 LOG_V("%s: %s\n", "nb_write_", strerror(errno
));
810 /* read one full line as a response */
811 r
= read_line_nb_(fd
, buf
, sizeof buf
, &buf_ptr
, deadline
);
813 LOG_V("%s: %s\n", "nb_read_", strerror(errno
));
817 if (buf_ptr
== NULL
) {
818 LOG_VV("incomplete response\n");
824 if (ident_response_parse_(buf
, port_remote
, port_local
))
828 strncpy(out_buf
, buf
, out_buf_sz
);
832 shutdown(fd
, 2); /* be ruthless, discard any remaining data */
837 /* no success, just echo the port */
838 snprintf(out_buf
, out_buf_sz
, "%d", port_remote
);
847 Render the name of an address into the supplied buffer.
848 buf_sz ought to be at least NI_HOSTMAX (1025)
852 host_query_(char *buf
, size_t buf_sz
, struct sockaddr_storage
*ss
)
857 if (cache_find_(ss
, buf
, buf_sz
)) {
858 LOG_VVV("found '%s' in cache\n", buf
);
862 /* not in cache, try to resolve it */
863 r
= getnameinfo((struct sockaddr
*)ss
, sizeof *ss
, buf
, buf_sz
, NULL
, 0, NI_NAMEREQD
);
866 LOG("%s: %s\n", "getnameinfo", gai_strerror(r
));
870 /* not resolvable, render it numerically */
871 r
= getnameinfo((struct sockaddr
*)ss
, sizeof *ss
, buf
, buf_sz
, NULL
, 0, NI_NUMERICHOST
);
873 LOG("%s: %s\n", "getnameinfo", gai_strerror(r
));
874 snprintf(buf
, buf_sz
, "(unknown)");
876 LOG_VVV("failed to resolve '%s'\n", buf
);
879 cache_add_(ss
, buf
, strlen(buf
), time(NULL
), succeeded
);
885 The main processing loop for each thread.
889 resolve_thread_(void *data
)
891 size_t id
= *(size_t *)data
;
893 char hostname_buf
[2048]; /* seems silly-large, but is just the next power-of-two up from NI_MAXHOST (1025) */
894 char username_buf
[512]; /* identd user id might be this long */
896 struct sockaddr_storage ss
;
898 int port_remote
, port_local
;
899 sigset_t sigset_maskall
;
902 LOG_VV("<%zu> thread started\n", id
);
904 sigfillset(&sigset_maskall
);
905 pthread_sigmask(SIG_SETMASK
, &sigset_maskall
, NULL
);
907 for (/* */; ! g_want_shutdown_
; /* */) {
908 memset(&ss
, 0, sizeof ss
);
911 Attempt to fetch a line from stdin.
912 If EOF is encountered, or any error other than EAGAIN,
913 signal global shutdown and bail.
916 if (g_want_shutdown_
) {
921 LOG_VVV("<%zu> awaiting request\n", id
);
923 if (fgets(buf
, sizeof buf
, stdin
) == NULL
) {
924 if (feof_unlocked(stdin
)) {
925 g_want_shutdown_
= 1;
926 } else if (ferror_unlocked(stdin
)) {
927 if (errno
== EINTR
) {
928 clearerr_unlocked(stdin
);
932 LOG("<%zu> %s: %s\n", id
, "fgets", strerror(errno
));
933 g_want_shutdown_
= 1;
936 LOG("<%zu> %s: %s\n", id
, "fgets", "NULL but no error?");
937 g_want_shutdown_
= 1;
941 LOG_VVV("<%zu> '%s'\n", id
, buf
);
944 if (g_want_shutdown_
)
947 /* try again if line is empty */
948 if (*buf
== '\n' || *buf
== '\0')
951 /* explicit exit request */
952 if (strncmp(QUIT_COMMAND
, buf
, strlen(QUIT_COMMAND
)) == 0) {
953 g_want_shutdown_
= 1;
958 #ifdef WITH_RESOLVER_STATS
959 if (strncmp(DUMP_COMMAND
, buf
, strlen(DUMP_COMMAND
)) == 0) {
963 #endif /* WITH_RESOLVER_STATS */
965 /* locate the ports, and parse them first */
966 buf_ptr
= strchr(buf
, '(');
967 if (buf_ptr
== NULL
) {
968 LOG_V("<%zu> malformed request, no paren: %s", id
, buf
); /* expect \n in buf */
972 r
= sscanf(buf_ptr
, "(%d)%d", &port_remote
, &port_local
);
974 LOG_V("<%zu> malformed request, bad ports: %s (%d)", id
, buf_ptr
, r
);
979 || port_local
> (unsigned short)-1
981 || port_remote
> (unsigned short)-1) {
982 LOG_V("<%zu> port out of range: %s", id
, buf
);
986 /* truncate buffer at ports, and parse into an address */
989 /* locate the first delimiter to determine address family */
990 buf_ptr
= strpbrk(buf
, ".:");
991 if (buf_ptr
== NULL
) {
992 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
995 else if (*buf_ptr
== '.') {
997 ss
.ss_family
= AF_INET
;
998 vaddr
= &((struct sockaddr_in
*)&ss
)->sin_addr
;
1001 else if (*buf_ptr
== ':') {
1003 ss
.ss_family
= AF_INET6
;
1004 vaddr
= &((struct sockaddr_in6
*)&ss
)->sin6_addr
;
1006 #endif /* USE_IPV6 */
1008 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
1012 r
= inet_pton(ss
.ss_family
, buf
, vaddr
);
1014 LOG("%s: %s\n", "inet_pton", strerror(errno
));
1016 } else if (r
== 0) {
1017 LOG_V("<%zu> could not parse address: %s\n", id
, buf
);
1021 LOG_VVV("> %s %d %d\n", buf
, port_remote
, port_local
);
1023 ident_query_(username_buf
, sizeof username_buf
, &ss
, port_remote
, port_local
);
1024 host_query_(hostname_buf
, sizeof hostname_buf
, &ss
);
1027 fprintf(stdout
, "%s(%d)|%s(%s)\n", buf
, port_remote
, hostname_buf
, username_buf
);
1029 funlockfile(stdout
);
1031 LOG_VVV("<%zu> '%s(%d)|%s(%s)'\n", id
, buf
, port_remote
, hostname_buf
, username_buf
);
1034 LOG_VV("<%zu> thread exiting\n", id
);
1042 set_signal_(int sig
, void (*func
)(int))
1044 #ifdef _POSIX_VERSION
1045 struct sigaction act
, oact
;
1047 act
.sa_handler
= func
;
1048 sigemptyset(&act
.sa_mask
);
1051 act
.sa_flags
= SA_RESTART
;
1052 # else /* SA_RESTART */
1054 # endif /* SA_RESTART */
1056 if (sigaction(sig
, &act
, &oact
)) {
1057 LOG("%s: %s\n", "sigaction", strerror(errno
));
1059 #else /* _POSIX_VERSION */
1060 if (signal(signo
, sighandler
) == SIG_ERR
) {
1061 LOG("%s: %s\n", "signal", strerror(errno
));
1063 #endif /* _POSIX_VERSION */
1069 Convert a base-10 number in str to a size_t within range specified.
1070 Just a helper for processing command-line arguments.
1074 str_to_sizet_range_(const char *str
, size_t *val
, size_t min
, size_t max
)
1079 num
= strtoll(str
, &end
, 10);
1080 if (*str
== '\0' || *end
!= '\0') {
1081 fprintf(stderr
, "'%s' is not a valid number\n", str
);
1085 || (unsigned long long)num
< min
1086 || (unsigned long long)num
> max
) {
1087 fprintf(stderr
, "%lld is not between %zu and %zu\n", num
, min
, max
);
1096 /* same for unsigned int */
1099 str_to_uint_range_(const char *str
, unsigned int *val
, unsigned int min
, unsigned int max
)
1104 num
= strtoll(str
, &end
, 10);
1105 if (*str
== '\0' || *end
!= '\0') {
1106 fprintf(stderr
, "'%s' is not a valid number\n", str
);
1110 || (unsigned long long)num
< min
1111 || (unsigned long long)num
> max
) {
1112 fprintf(stderr
, "%lld is not between %u and %u\n", num
, min
, max
);
1116 *val
= (unsigned int)num
;
1122 main(int argc
, char **argv
)
1131 #ifdef HAVE_GETRLIMIT
1134 /* close anything not stdio */
1135 getrlimit(RLIMIT_NOFILE
, &lim
);
1136 for (i
= 3; i
< lim
.rlim_cur
; i
++) {
1138 LOG_VV("cleaned up fd %zu\n", i
);
1140 #endif /* HAVE_GETRLIMIT */
1142 #ifdef DEBUG_PRETEND_IDENT
1146 g_log_stream_
= stderr
;
1148 while ( (c
= getopt(argc
, argv
, "a:c:f:j:s:o:vh")) != EOF
) {
1151 if (str_to_uint_range_(optarg
, &g_opts_
.age_success
, 0, (unsigned int)-1))
1156 if (str_to_sizet_range_(optarg
, &g_opts_
.capacity
, 256, (size_t)-1))
1161 if (str_to_uint_range_(optarg
, &g_opts_
.age_fail
, 0, (unsigned int)-1))
1166 if (str_to_sizet_range_(optarg
, &g_opts_
.num_threads
, 1, (size_t)-1))
1171 if (str_to_sizet_range_(optarg
, &g_opts_
.hash_sz
, 1, (size_t)-1))
1176 g_opts_
.verbose
+= 1;
1180 g_opts_
.log_filename
= optarg
;
1181 g_opts_
.verbose
+= 1;
1185 usage_(argv
[0], USAGE_FLAG_SHOWFULL
);
1194 if (argc
- optind
!= 0) {
1199 if (g_opts_
.log_filename
) {
1200 g_log_stream_
= fopen(g_opts_
.log_filename
, "a+");
1201 if (g_log_stream_
== NULL
) {
1202 fprintf(stderr
, "could not open '%s': %s\n", g_opts_
.log_filename
, strerror(errno
));
1208 char lf
[] = "/tmp/resolver-debug.XXXXXX";
1209 int lf_fd
= mkstemp(lf
);
1212 fprintf(stderr
, "could not create debug logfile '%s': %s\n", lf
, strerror(errno
));
1215 g_log_stream_
= fdopen(lf_fd
, "a+");
1216 if (g_log_stream_
== NULL
) {
1217 fprintf(stderr
, "%s: %s\n", "fdopen", strerror(errno
));
1220 g_opts_
.verbose
= 3;
1223 g_cache_
= lru_cache_new(g_opts_
.hash_sz
, g_opts_
.capacity
, cache_entry_hash_feed_
, cache_entry_cmp_
);
1224 if (g_cache_
== NULL
) {
1228 threads
= calloc(g_opts_
.num_threads
, sizeof *threads
);
1229 if (threads
== NULL
) {
1230 LOG("%s: %s\n", "calloc", strerror(errno
));
1234 set_signal_(SIGPIPE
, SIG_IGN
);
1235 set_signal_(SIGHUP
, SIG_IGN
);
1237 for (i
= 0; i
< g_opts_
.num_threads
; i
++) {
1239 c
= pthread_create(&threads
[i
].pthr
, NULL
, resolve_thread_
, (void *)&threads
[i
].tid
);
1241 LOG("%s: %s\n", "pthread_create", strerror(c
));
1246 for (i
= 0; i
< g_opts_
.num_threads
; i
++) {
1248 c
= pthread_join(threads
[i
].pthr
, &retval
);
1250 LOG("%s: %s\n", "pthread_join", strerror(c
));
1258 LOG("Resolver exited.\n");
1260 fclose(g_log_stream_
);