diff --git a/.gitignore b/.gitignore index a3c7c3179..cf04c4932 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ pihole-FTL* # Versioning files (generated by Makefile) -version.h +version.c version~ # CMake files generated during compilation diff --git a/CMakeLists.txt b/CMakeLists.txt index b04cb263b..1042bb230 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,6 @@ set(CMAKE_C_STANDARD 17) project(PIHOLE_FTL C) -set(DNSMASQ_VERSION pi-hole-v2.91test10) +set(DNSMASQ_VERSION pi-hole-v2.91rc2) add_subdirectory(src) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1c77458fb..e2c2002e2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -264,11 +264,10 @@ set(sources timers.h vector.c vector.h + version.c version.h ) -set_source_files_properties(version.h PROPERTIES GENERATED TRUE) - add_custom_target( gen_version ALL COMMAND ${CMAKE_COMMAND} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -P ${CMAKE_CURRENT_SOURCE_DIR}/gen_version.cmake diff --git a/src/api/info.c b/src/api/info.c index 16df56ba4..693f14397 100644 --- a/src/api/info.c +++ b/src/api/info.c @@ -823,9 +823,9 @@ int get_version_obj(struct ftl_conn *api, cJSON *version) fclose(fp); // Add remaining properties to ftl object - JSON_REF_STR_IN_OBJECT(ftl_local, "branch", GIT_BRANCH); + JSON_REF_STR_IN_OBJECT(ftl_local, "branch", git_branch()); JSON_REF_STR_IN_OBJECT(ftl_local, "version", get_FTL_version()); - JSON_REF_STR_IN_OBJECT(ftl_local, "date", GIT_DATE); + JSON_REF_STR_IN_OBJECT(ftl_local, "date", git_date()); cJSON *core = JSON_NEW_OBJECT(); JSON_ADD_NULL_IF_NOT_EXISTS(core_local, "branch"); diff --git a/src/args.c b/src/args.c index c914dd3d4..2b4ed0382 100644 --- a/src/args.c +++ b/src/args.c @@ -810,10 +810,10 @@ void parse_args(int argc, char *argv[]) yellow, bold, normal); printf("Version: %s%s%s%s\n", green, bold, get_FTL_version(), normal); - printf("Branch: " GIT_BRANCH "\n"); - printf("Commit: " GIT_HASH " (" GIT_DATE ")\n"); - printf("Architecture: " FTL_ARCH "\n"); - printf("Compiler: " FTL_CC "\n"); + printf("Branch: %s\n", git_branch()); + printf("Commit: %s (%s)",git_hash(), git_date()); + printf("Architecture: %s\n", ftl_arch()); + printf("Compiler: %s\n", ftl_cc()); #if defined(__GLIBC__) && defined(__GLIBC_MINOR__) printf("GLIBC version: %d.%d\n\n", __GLIBC__, __GLIBC_MINOR__); #else @@ -920,20 +920,20 @@ void parse_args(int argc, char *argv[]) if(strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "tag") == 0) { - printf("%s\n",GIT_TAG); + printf("%s\n", git_tag()); exit(EXIT_SUCCESS); } if(strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "branch") == 0) { - printf("%s\n",GIT_BRANCH); + printf("%s\n", git_branch()); exit(EXIT_SUCCESS); } if(strcmp(argv[i], "--hash") == 0) { - printf("%s\n",GIT_HASH); + printf("%s\n", git_hash()); exit(EXIT_SUCCESS); } diff --git a/src/database/message-table.c b/src/database/message-table.c index d924035f2..8d37b520f 100644 --- a/src/database/message-table.c +++ b/src/database/message-table.c @@ -1597,7 +1597,7 @@ void log_verify_message(const char *expected, const char *actual) log_crit("%s", buf); // Log to database - add_message(VERIFY_MESSAGE, buf, expected, actual, GIT_HASH, FTL_ARCH); + add_message(VERIFY_MESSAGE, buf, expected, actual, git_hash(), ftl_arch()); } diff --git a/src/database/query-table.c b/src/database/query-table.c index 0b07d52d7..7cd94a00d 100644 --- a/src/database/query-table.c +++ b/src/database/query-table.c @@ -1230,7 +1230,7 @@ void DB_read_queries(void) if(type < 100) { // Mapped query type - if(type >= TYPE_A && type < TYPE_MAX) + if(type >= TYPE_NONE && type < TYPE_MAX) query->type = type; else { diff --git a/src/dnsmasq/dnsmasq.h b/src/dnsmasq/dnsmasq.h index 7428b7f62..ec21aeb7e 100644 --- a/src/dnsmasq/dnsmasq.h +++ b/src/dnsmasq/dnsmasq.h @@ -282,7 +282,8 @@ struct event_desc { #define OPT_CACHE_RR 71 #define OPT_LOCALHOST_SERVICE 72 #define OPT_LOG_PROTO 73 -#define OPT_LAST 74 +#define OPT_NO_0x20 74 +#define OPT_LAST 75 #define OPTION_BITS (sizeof(unsigned int)*8) #define OPTION_SIZE ( (OPT_LAST/OPTION_BITS)+((OPT_LAST%OPTION_BITS)!=0) ) @@ -788,12 +789,13 @@ struct dyndir { #define FREC_DO_QUESTION 64 #define FREC_HAS_PHEADER 128 #define FREC_GONE_TO_TCP 256 +#define FREC_ANSWER 512 struct frec { struct frec_src { union mysockaddr source; union all_addr dest; - unsigned int iface, log_id; + unsigned int iface, log_id, encode_bitmap, *encode_bigmap; int fd; unsigned short orig_id, udp_pkt_size; struct frec_src *next; @@ -804,7 +806,6 @@ struct frec { int forwardall, flags; time_t time; u32 forward_timestamp; - unsigned int encode_bitmap; int forward_delay; struct blockdata *stash; /* saved query or saved reply, whilst we validate */ size_t stash_len; diff --git a/src/dnsmasq/forward.c b/src/dnsmasq/forward.c index b2b424f1d..3d341db7e 100644 --- a/src/dnsmasq/forward.c +++ b/src/dnsmasq/forward.c @@ -102,11 +102,10 @@ int send_from(int fd, int nowild, char *packet, size_t len, /* If interface is still in DAD, EINVAL results - ignore that. */ if (errno != EINVAL) { - const int errnum = errno; - my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); /********** Pi-hole modification **********/ - FTL_connection_error("failed to send UDP reply", to, errnum); + FTL_connection_error("failed to send UDP reply", to, -1); /******************************************/ + my_syslog(LOG_ERR, _("failed to send packet: %s"), strerror(errno)); } #endif return 0; @@ -206,6 +205,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, FREC_HAS_PHEADER | FREC_DNSKEY_QUERY | FREC_DS_QUERY | FREC_NO_CACHE))) { struct frec_src *src; + struct dns_header *saved_header; + unsigned int casediff = 0; + unsigned int *bitvector = NULL; for (src = &forward->frec_src; src; src = src->next) if (src->orig_id == ntohs(header->id) && @@ -230,6 +232,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, { daemon->frec_src_count++; daemon->free_frec_src->next = NULL; + daemon->free_frec_src->encode_bigmap = NULL; } /* If we've been spammed with many duplicates, return REFUSED. */ @@ -246,6 +249,54 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, free_frec(forward); goto reply; } + + /* Find a bitmap of case differences between the query send upstream and this one, + so we can reply to each query with the correct case pattern. + Since we need this to get back the exact case pattern of each query when doing + query combining, we have to handle the (rare) case that there are case differences + beyond the first 32 letters. + If that happens we have to allocate memory to save it, and the casediff variable + holds the length of that array. + Mismatches beyond 32 letters are rare because most queries are all lowercase and + we only scramble the first 32 letters for security reasons. + + Note the two names are guaranteed to be the same length and differ only in the case + of letters. */ + if ((saved_header = blockdata_retrieve(forward->stash, forward->stash_len, NULL)) && + extract_request(saved_header, forward->stash_len, daemon->workspacename, NULL)) + { + unsigned int i, gobig = 0; + char *s1, *s2; + +#define BITS_IN_INT (sizeof(unsigned int) * 8) + + big_redo: + for (s1 = daemon->namebuff, s2 = daemon->workspacename, i = 0; *s1; s1++, s2++) + { + char c1 = *s1, c2 = *s2; + + if ((c1 >= 'a' && c1 <= 'z') || (c1 >= 'A' && c1 <= 'Z')) + { + if ((c1 & 0x20) ^ (c2 & 0x20)) + { + if (bitvector) + bitvector[i/BITS_IN_INT] |= 1<<(i%BITS_IN_INT); + else if (i >= BITS_IN_INT) + gobig = 1; /* More than 32 */ + else + casediff |= 1<free_frec_src; daemon->free_frec_src = src->next; @@ -257,6 +308,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, src->log_id = daemon->log_id; src->iface = dst_iface; src->fd = udpfd; + src->encode_bitmap = casediff; + src->encode_bigmap = bitvector; + src->udp_pkt_size = (unsigned short)replylimit; /* closely spaced identical queries cannot be a try and a retry, so @@ -338,9 +392,9 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, forward->new_id = get_id(); header->id = ntohs(forward->new_id); - forward->encode_bitmap = rand32(); + forward->frec_src.encode_bitmap = option_bool(OPT_NO_0x20) ? 0 : rand32(); p = (unsigned char *)(header+1); - if (!extract_name(header, plen, &p, NULL, EXTR_NAME_FLIP, forward->encode_bitmap)) + if (!extract_name(header, plen, &p, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1)) goto reply; /* Keep copy of query for retries and move to TCP */ @@ -523,9 +577,7 @@ static void forward_query(int udpfd, union mysockaddr *udpaddr, } /**** Pi-hole modification ****/ else - { - FTL_connection_error("failed to send UDP request", &srv->addr, errno); - } + FTL_connection_error("failed to send UDP request", &srv->addr, -1); /******************************/ } @@ -1038,7 +1090,7 @@ static void dnssec_validate(struct frec *forward, struct dns_header *header, new->flags &= ~(FREC_DNSKEY_QUERY | FREC_DS_QUERY); new->flags |= flags; new->forwardall = 0; - new->encode_bitmap = 0; + new->frec_src.encode_bitmap = 0; forward->next_dependent = NULL; new->dependent = forward; /* to find query awaiting new one. */ @@ -1161,7 +1213,7 @@ void reply_query(int fd, time_t now) GETSHORT(rrtype, p); GETSHORT(class, p); - if (!(forward = lookup_frec(daemon->namebuff, class, rrtype, ntohs(header->id), 0, 0))) + if (!(forward = lookup_frec(daemon->namebuff, class, rrtype, ntohs(header->id), FREC_ANSWER, 0))) return; filter_servers(forward->sentto->arrayposn, F_SERVER, &first, &last); @@ -1270,7 +1322,7 @@ void reply_query(int fd, time_t now) /* Flip the bits back in the query name. */ p = (unsigned char *)(header+1); - if (!extract_name(header, n, &p, NULL, EXTR_NAME_FLIP, forward->encode_bitmap)) + if (!extract_name(header, n, &p, (char *)&forward->frec_src.encode_bitmap, EXTR_NAME_FLIP, 1)) return; #ifdef HAVE_DNSSEC @@ -1292,6 +1344,51 @@ void reply_query(int fd, time_t now) return_reply(now, forward, header, n, STAT_OK); } +static void xor_array(unsigned int *arg1, unsigned int *arg2, unsigned int len) +{ + unsigned int i; + + for (i = 0; i < len; i++) + arg1[i] ^= arg2[i]; +} + +/* Call extract_name() to flip case of query in packet according to the XOR of the bit maps help in arg1 and arg2 */ +static void flip_queryname(struct dns_header *header, ssize_t len, struct frec_src *arg1, struct frec_src *arg2) +{ + unsigned char *p = (unsigned char *)(header+1); + unsigned int *arg1p, *arg2p, arg1len, arg2len, *swapp, swap; + + /* Two cases: bitmap is single 32 bit int, or it's arbitrary-length array of 32bit ints. + The two args may be different and of different lengths. + The shorter gets notionally extended with zeros. */ + + if (arg1->encode_bigmap) + arg1p = arg1->encode_bigmap, arg1len = arg1->encode_bitmap; + else + arg1p = &arg1->encode_bitmap, arg1len = 1; + + if (arg2->encode_bigmap) + arg2p = arg2->encode_bigmap, arg2len = arg2->encode_bitmap; + else + arg2p = &arg2->encode_bitmap, arg2len = 1; + + /* make arg1 the longer, if they differ. */ + if (arg2len > arg1len) + { + swap = arg1len; + swapp = arg1p; + arg1len = arg2len; + arg1p = arg2p; + arg2len = swap; + arg2p = swapp; + } + + /* XOR on shorter length, flip on longer, operate on longer */ + xor_array(arg1p, arg2p, arg2len); + extract_name(header, len, &p, (char *)arg1p, EXTR_NAME_FLIP, arg1len); + xor_array(arg1p, arg2p, arg2len); /* restore */ +} + void return_reply(time_t now, struct frec *forward, struct dns_header *header, ssize_t n, int status) { int check_rebind = 0, no_cache_dnssec = 0, cache_secure = 0, bogusanswer = 0; @@ -1381,10 +1478,10 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s !(forward->flags & FREC_HAS_PHEADER), &forward->frec_src.source, ((unsigned char *)header) + daemon->edns_pktsz, ede))) { - struct frec_src *src; + struct frec_src *src, *prev; int do_trunc; - - for (do_trunc = 0, src = &forward->frec_src; src; src = src->next) + + for (do_trunc = 0, prev = NULL, src = &forward->frec_src; src; prev = src, src = src->next) { header->id = htons(src->orig_id); @@ -1398,6 +1495,13 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s } #endif + /* You will have to draw diagrams and scratch your head to convince yourself + that this works. Bear in mind that the flip to upstream state has already been undone, + for the original query so nothing needs to be done, but subsequent queries' flips + were recorded relative to the flipped name sent upstream. */ + if (prev) + flip_queryname(header, nn, prev, src); + if (src->fd != -1) { /* Only send packets that fit what the requestor allows. @@ -1421,7 +1525,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s } } } - + /* The packet is too big for one or more requestors, send them a truncated answer. */ if (do_trunc) { @@ -1433,7 +1537,7 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s header->arcount = htons(0); header->hb3 |= HB3_TC; new = resize_packet(header, nn, pheader, hlen); - + daemon->log_display_id = forward->frec_src.log_id; daemon->log_source_addr = &forward->frec_src.source; log_query(F_UPSTREAM, NULL, NULL, "truncated", 0); @@ -1441,19 +1545,28 @@ void return_reply(time_t now, struct frec *forward, struct dns_header *header, s /* Pi-hole modification */ int first_ID = -1; - for (src = &forward->frec_src; src; src = src->next) - if (src->fd != -1 && nn > src->udp_pkt_size) - { - header->id = htons(src->orig_id); - send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, new, - &src->source, &src->dest, src->iface); - + /* This gets the name back to the state it was in when we started. */ + flip_queryname(header, nn, prev, &forward->frec_src); + + for (src = &forward->frec_src, prev = NULL; src; prev = src, src = src->next) + { + /* If you didn't undertand this above, you won't understand it here either. */ + if (prev) + flip_queryname(header, nn, prev, src); + + if (src->fd != -1 && nn > src->udp_pkt_size) + { + header->id = htons(src->orig_id); + send_from(src->fd, option_bool(OPT_NOWILD) || option_bool (OPT_CLEVERBIND), daemon->packet, new, + &src->source, &src->dest, src->iface); + #ifdef HAVE_DUMPFILE - dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)new, NULL, &src->source, src->fd); + dump_packet_udp(DUMP_REPLY, daemon->packet, (size_t)new, NULL, &src->source, src->fd); #endif - /* Pi-hole modification */ - FTL_multiple_replies(src->log_id, &first_ID); - } + /* Pi-hole modification */ + FTL_multiple_replies(src->log_id, &first_ID); + } + } } } @@ -2018,6 +2131,9 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, unsigned char *p; struct blockdata *saved_question; struct timeval tv; + + // Pi-hole + char where = 0; (void)mark; (void)have_mark; @@ -2051,7 +2167,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, break; } - serv = daemon->serverarray[start]; + *servp = serv = daemon->serverarray[start]; retry: blockdata_retrieve(saved_question, qsize, header); @@ -2096,19 +2212,21 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, data_sent = 1; else if (errno == ETIMEDOUT || errno == EHOSTUNREACH || errno == EINPROGRESS || errno == ECONNREFUSED) fatal = 1; - /**** Pi-hole modification ****/ - if (errno != 0) - FTL_connection_error("failed to send TCP(fast-open) packet", &serv->addr, errno); - /******************************/ #endif /* If fastopen failed due to lack of reply, then there's no point in trying again in non-FASTOPEN mode. */ - if (fatal || (!data_sent && connect(serv->tcpfd, &serv->addr.sa, sa_len(&serv->addr)) == -1)) + if (fatal || (!data_sent && (where = 1) && connect(serv->tcpfd, &serv->addr.sa, sa_len(&serv->addr)) == -1)) { + int port; + + failed: /**** Pi-hole modification ****/ - FTL_connection_error("failed to send TCP(connect) packet", &serv->addr, errno); + FTL_connection_error("TCP connection failed", &serv->addr, where); /******************************/ + + port = prettyprint_addr(&serv->addr, daemon->addrbuff); + my_syslog(LOG_DEBUG|MS_DEBUG, _("TCP connection failed to %s#%d"), daemon->addrbuff, port); close(serv->tcpfd); serv->tcpfd = -1; continue; @@ -2120,25 +2238,21 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, /* We us the _ONCE veriant of read_write() here because we've set a timeout on the tcp socket and wish to abort if the whole data is not read/written within the timeout. */ - if ((!data_sent && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) || - !read_write(serv->tcpfd, (unsigned char *)length, sizeof (*length), RW_READ_ONCE) || - !read_write(serv->tcpfd, payload, (rsize = ntohs(*length)), RW_READ_ONCE)) + if ((!data_sent && (where = 2) && !read_write(serv->tcpfd, (unsigned char *)packet, qsize + sizeof(u16), RW_WRITE_ONCE)) || + ((where = 3) && !read_write(serv->tcpfd, (unsigned char *)length, sizeof (*length), RW_READ_ONCE)) || + ((where = 4) && !read_write(serv->tcpfd, payload, (rsize = ntohs(*length)), RW_READ_ONCE))) { - const int errnum = errno; - close(serv->tcpfd); - serv->tcpfd = -1; /* We get data then EOF, reopen connection to same server, else try next. This avoids DoS from a server which accepts connections and then closes them. */ if (serv->flags & SERV_GOT_TCP) - goto retry; + { + close(serv->tcpfd); + serv->tcpfd = -1; + goto retry; + } else - /**** Pi-hole modification ****/ - { - FTL_connection_error("failed to send TCP(read_write) packet", &serv->addr, errnum); - continue; - } - /******************************/ + goto failed; } /* If the question section of the reply doesn't match the question we sent, then @@ -2146,7 +2260,7 @@ static ssize_t tcp_talk(int first, int last, int start, unsigned char *packet, sending replies containing questions and bogus answers. Try another server, or give up */ p = (unsigned char *)(header+1); - if (extract_name(header, rsize, &p, daemon->namebuff, EXTR_NAME_NOCASE, 4) != 1) + if (extract_name(header, rsize, &p, daemon->namebuff, EXTR_NAME_COMPARE, 4) != 1) continue; GETSHORT(rtype, p); GETSHORT(rclass, p); @@ -2796,7 +2910,8 @@ unsigned char *tcp_request(int confd, time_t now, } blockdata_free(saved_question); - + check_log_writer(1); + return packet; } @@ -3075,8 +3190,15 @@ static void free_frec(struct frec *f) { struct frec_src *last; - /* add back to freelist if not the record builtin to every frec. */ - for (last = f->frec_src.next; last && last->next; last = last->next) ; + /* add back to freelist if not the record builtin to every frec, + also free any bigmaps they's been decorated with. */ + for (last = f->frec_src.next; last && last->next; last = last->next) + if (last->encode_bigmap) + { + free(last->encode_bigmap); + last->encode_bigmap = NULL; + } + if (last) { last->next = daemon->free_frec_src; @@ -3093,7 +3215,7 @@ static void free_frec(struct frec *f) blockdata_free(f->stash); f->stash = NULL; } - + #ifdef HAVE_DNSSEC /* Anything we're waiting on is pointless now, too */ if (f->blocking_query) @@ -3216,6 +3338,16 @@ static struct frec *lookup_frec(char *target, int class, int rrtype, int id, int { struct frec *f; struct dns_header *header; + int compare_mode = EXTR_NAME_COMPARE; + + /* Only compare case-sensitive when matching frec to a recieved answer, + NOT when looking for a duplicated question. */ + if (flags & FREC_ANSWER) + { + flags &= ~FREC_ANSWER; + if (!option_bool(OPT_NO_0x20)) + compare_mode = EXTR_NAME_NOCASE; + } for (f = daemon->frec_list; f; f = f->next) if (f->sentto && @@ -3224,22 +3356,35 @@ static struct frec *lookup_frec(char *target, int class, int rrtype, int id, int (header = blockdata_retrieve(f->stash, f->stash_len, NULL))) { unsigned char *p = (unsigned char *)(header+1); - int hclass, hrrtype; + int hclass, hrrtype, rc; /* Case sensitive compare for DNS-0x20 encoding. */ - if (extract_name(header, f->stash_len, &p, target, EXTR_NAME_NOCASE, 4) != 1) - continue; - - GETSHORT(hrrtype, p); - GETSHORT(hclass, p); - - /* type checked by flags for DNSSEC queries. */ - if (rrtype != -1 && rrtype != hrrtype) - continue; - - if (class != hclass) - continue; - + if ((rc = extract_name(header, f->stash_len, &p, target, compare_mode, 4))) + { + GETSHORT(hrrtype, p); + GETSHORT(hclass, p); + + /* type checked by flags for DNSSEC queries. */ + if (rrtype != -1 && rrtype != hrrtype) + continue; + + if (class != hclass) + continue; + } + + if (rc != 1) + { + static int warned = 0; + + if (rc == 3 && !warned) + { + my_syslog(LOG_WARNING, _("Case mismatch in DNS reply - check bit 0x20 encoding.")); + warned = 1; + } + + continue; + } + return f; } diff --git a/src/dnsmasq/option.c b/src/dnsmasq/option.c index 4f8bcf14d..9c850a9be 100644 --- a/src/dnsmasq/option.c +++ b/src/dnsmasq/option.c @@ -197,6 +197,7 @@ struct myoption { #define LOPT_MAX_PROCS 384 #define LOPT_DNSSEC_LIMITS 385 #define LOPT_PXE_OPT 386 +#define LOPT_NO_ENCODE 387 #ifdef HAVE_GETOPT_LONG static const struct option opts[] = @@ -251,6 +252,7 @@ static const struct myoption opts[] = { "local-ttl", 1, 0, 'T' }, { "no-negcache", 0, 0, 'N' }, { "no-round-robin", 0, 0, LOPT_NORR }, + { "no-0x20-encode", 0, 0, LOPT_NO_ENCODE }, { "cache-rr", 1, 0, LOPT_CACHE_RR }, { "addn-hosts", 1, 0, 'H' }, { "hostsdir", 1, 0, LOPT_HOST_INOTIFY }, @@ -595,6 +597,7 @@ static struct { { LOPT_UMBRELLA, ARG_ONE, "[=]", gettext_noop("Send Cisco Umbrella identifiers including remote IP."), NULL }, { LOPT_QUIET_TFTP, OPT_QUIET_TFTP, NULL, gettext_noop("Do not log routine TFTP."), NULL }, { LOPT_NORR, OPT_NORR, NULL, gettext_noop("Suppress round-robin ordering of DNS records."), NULL }, + { LOPT_NO_ENCODE, OPT_NO_0x20, NULL, gettext_noop("Suppress DNS bit 0x20 encoding."), NULL }, { LOPT_NO_IDENT, OPT_NO_IDENT, NULL, gettext_noop("Do not add CHAOS TXT records."), NULL }, { LOPT_CACHE_RR, ARG_DUP, "", gettext_noop("Cache this DNS resource record type."), NULL }, { LOPT_MAX_PROCS, ARG_ONE, "", gettext_noop("Maximum number of concurrent tcp connections."), NULL }, diff --git a/src/dnsmasq/rfc1035.c b/src/dnsmasq/rfc1035.c index 5b3c0ab17..13ebe03b8 100644 --- a/src/dnsmasq/rfc1035.c +++ b/src/dnsmasq/rfc1035.c @@ -20,25 +20,37 @@ /* EXTR_NAME_EXTRACT -> extract name EXTR_NAME_COMPARE -> compare name, case insensitive EXTR_NAME_NOCASE -> compare name, case sensitive - EXTR_NAME_FLIP -> flip 0x20 bits in packet, controlled by bitmap in parm. name may be NULL + EXTR_NAME_FLIP -> flip 0x20 bits in packet. + + For flip, name is an array of ints, whose size + is given in parm, which forms the bitmap. Bits beyond the size + are assumed to be zero. return = 0 -> error return = 1 -> extract OK, compare OK, flip OK return = 2 -> extract OK, compare failed. + return = 3 -> extract OK, compare failed but only on case. */ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, char *name, int func, unsigned int parm) { unsigned char *cp = (unsigned char *)name, *p = *pp, *p1 = NULL; unsigned int j, l, namelen = 0, hops = 0; + unsigned int bigmap_counter = 0, bigmap_posn = 0, bigmap_size, bitmap; int retvalue = 1, case_insens = 1, isExtract = 0, flip = 0, extrabytes = (int)parm; - + unsigned int *bigmap; + if (func == EXTR_NAME_EXTRACT) isExtract = 1, *cp = 0; else if (func == EXTR_NAME_NOCASE) case_insens = 0; else if (func == EXTR_NAME_FLIP) - flip = 1, extrabytes = 0; + { + flip = 1, extrabytes = 0; + bigmap = (unsigned int *)name; + name = NULL; + bigmap_size = parm; + } while (1) { @@ -116,12 +128,18 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, { unsigned char c = *p; - /* parm is unsigned. We only flip up to the first 32 alpha-chars. */ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - if (parm & 1) + /* Get the next int of the bitmap */ + if (bigmap_posn < bigmap_size && bigmap_counter-- == 0) + { + bitmap = bigmap[bigmap_posn++]; + bigmap_counter = (sizeof(unsigned int) * 8) - 1; + } + + if (bitmap & 1) *p ^= 0x20; - parm >>= 1; + bitmap >>= 1; } } else @@ -141,9 +159,21 @@ int extract_name(struct dns_header *header, size_t plen, unsigned char **pp, if (case_insens && c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; + + if (!case_insens && retvalue != 2 && c1 != c2) + { + if (c1 >= 'A' && c1 <= 'Z') + c1 += 'a' - 'A'; + + if (c2 >= 'A' && c2 <= 'Z') + c2 += 'a' - 'A'; + + if (c1 == c2) + retvalue = 3; + } if (c1 != c2) - retvalue = 2; + retvalue = 2; } } @@ -2412,7 +2442,7 @@ size_t answer_request(struct dns_header *header, char *limit, size_t qlen, if (!(ansp = skip_questions(header, qlen))) return 0; /* bad packet */ anscount = nscount = addncount = 0; - log_query(F_CONFIG, "reply", NULL, "truncated", 0); + log_query(0, "reply", NULL, "truncated", 0); } if (nxdomain) diff --git a/src/dnsmasq/tftp.c b/src/dnsmasq/tftp.c index 831d2f241..637a566f2 100644 --- a/src/dnsmasq/tftp.c +++ b/src/dnsmasq/tftp.c @@ -360,7 +360,7 @@ void tftp_request(struct listener *listen, time_t now) } p = packet + 2; - end = packet + len; + end = packet + 2 + len; if (ntohs(*((unsigned short *)packet)) != OP_RRQ || !(filename = next(&p, end)) || diff --git a/src/dnsmasq_interface.c b/src/dnsmasq_interface.c index 0a3b413ea..b1fdf3d14 100644 --- a/src/dnsmasq_interface.c +++ b/src/dnsmasq_interface.c @@ -3801,17 +3801,31 @@ void get_dnsmasq_metrics_obj(cJSON *json) cJSON_AddNumberToObject(json, get_metric_name(i), daemon->metrics[i]); } -void FTL_connection_error(const char *reason, const union mysockaddr *addr, const int errnum) +void FTL_connection_error(const char *reason, const union mysockaddr *addr, const char where) { + // Backup errno + const int errnum = errno; + // Get the error message const char *error = strerror(errnum); // Set log priority int priority = LOG_ERR; + // Additional information (if available) + const char *extra = ""; + if(where == 1) + extra = " while connecting to upstream"; + else if(where == 2) + extra = " while sending data upstream"; + else if(where == 3) + extra = " while receiving payload length from upstream"; + else if(where == 4) + extra = " while receiving payload data from upstream"; + // If this is a TCP connection error and errno == 0, this isn't a // connection error but the remote side closed the connection - if(errnum == 0 && strstr(reason, "TCP(read_write)") != NULL) + if(errnum == 0 && strcmp(reason, "TCP connection failed") == 0) { error = "Connection prematurely closed by remote server"; priority = LOG_INFO; @@ -3826,7 +3840,7 @@ void FTL_connection_error(const char *reason, const union mysockaddr *addr, cons // Get query ID, may be negative if this is a TCP query const int id = daemon->log_display_id > 0 ? daemon->log_display_id : -daemon->log_display_id; // Log to FTL.log - log_debug(DEBUG_QUERIES, "Connection error (%s#%u, ID %d): %s (%s)", ip, port, id, reason, error); + log_debug(DEBUG_QUERIES, "Connection error (%s#%u, ID %d): %s (%s)%s", ip, port, id, reason, error, extra); // Log to pihole.log my_syslog(priority, "%s: %s", reason, error); @@ -3837,7 +3851,10 @@ void FTL_connection_error(const char *reason, const union mysockaddr *addr, cons static time_t last = 0; if(time(NULL) - last > 5) { + // Update last time last = time(NULL); + + // Build server string char *server = NULL; if(ip[0] != '\0') { @@ -3849,10 +3866,34 @@ void FTL_connection_error(const char *reason, const union mysockaddr *addr, cons server[len - 1] = '\0'; } } - log_connection_error(server, reason, error); + + // Extend reason with extra information (if available) + char *reason_extended = (char *)reason; + bool allocated = false; + if(extra[0] != '\0') + { + const size_t len = strlen(reason) + strlen(extra) + 3; + reason_extended = calloc(len, sizeof(char)); + if(reason_extended != NULL) + { + snprintf(reason_extended, len, "%s%s", reason, extra); + reason_extended[len - 1] = '\0'; + allocated = true; + } + } + + // Log connection error + log_connection_error(server, reason_extended, error); + + // Free allocated memory if(server != NULL) free(server); + if(allocated) + free(reason_extended); } + + // Restore errno for dnsmaq logging routines + errno = errnum; } /** diff --git a/src/dnsmasq_interface.h b/src/dnsmasq_interface.h index 2e99a3116..250d67cca 100644 --- a/src/dnsmasq_interface.h +++ b/src/dnsmasq_interface.h @@ -49,7 +49,7 @@ void FTL_TCP_worker_terminating(bool finished); bool FTL_unlink_DHCP_lease(const char *ipaddr, const char **hint); -void FTL_connection_error(const char *reason, const union mysockaddr *addr, const int errnum); +void FTL_connection_error(const char *reason, const union mysockaddr *addr, const char where); bool get_dnsmasq_debug(void) __attribute__ ((pure)); diff --git a/src/gen_version.cmake b/src/gen_version.cmake index a41303325..5d1006c2a 100644 --- a/src/gen_version.cmake +++ b/src/gen_version.cmake @@ -99,7 +99,7 @@ message(" - Commit date: ${GIT_DATE}") # configure the version file, but output to a temporary location configure_file( - ${CMAKE_CURRENT_SOURCE_DIR}/version.h.in + ${CMAKE_CURRENT_SOURCE_DIR}/version.c.in ${CMAKE_CURRENT_BINARY_DIR}/version~ @ONLY ) @@ -109,7 +109,7 @@ execute_process( COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_CURRENT_BINARY_DIR}/version~ - ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${CMAKE_CURRENT_BINARY_DIR}/version.c RESULT_VARIABLE VERSION_NEEDS_UPDATING @@ -123,6 +123,6 @@ if(VERSION_NEEDS_UPDATING) COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/version~ - ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${CMAKE_CURRENT_BINARY_DIR}/version.c ) endif() diff --git a/src/log.c b/src/log.c index f516bd6c5..20cdbda35 100644 --- a/src/log.c +++ b/src/log.c @@ -498,10 +498,10 @@ void log_counter_info(void) void log_FTL_version(const bool crashreport) { - log_info("FTL branch: %s", GIT_BRANCH); + log_info("FTL branch: %s", git_branch()); log_info("FTL version: %s", get_FTL_version()); - log_info("FTL commit: %s", GIT_HASH); - log_info("FTL date: %s", GIT_DATE); + log_info("FTL commit: %s", git_hash()); + log_info("FTL date: %s", git_date()); if(crashreport) { char *username_now = getUserName(); @@ -510,7 +510,7 @@ void log_FTL_version(const bool crashreport) } else log_info("FTL user: %s", username); - log_info("Compiled for %s using %s", FTL_ARCH, FTL_CC); + log_info("Compiled for %s using %s", ftl_arch(), ftl_cc()); } static char *FTLversion = NULL; @@ -519,21 +519,21 @@ const char __attribute__ ((malloc)) *get_FTL_version(void) // Obtain FTL version if not already determined if(FTLversion == NULL) { - if(strlen(GIT_TAG) > 1 ) + if(strlen(git_tag()) > 1 ) { - if (strlen(GIT_VERSION) > 1) + if (strlen(git_version()) > 1) { // Copy version string if this is a tagged release - FTLversion = strdup(GIT_VERSION); + FTLversion = strdup(git_version()); } } - else if(strlen(GIT_HASH) > 0) + else if(strlen(git_hash()) > 0) { // Build special version string when there is a hash FTLversion = calloc(13, sizeof(char)); // Build version by appending 7 characters of the hash to "vDev-" - snprintf(FTLversion, 13, "vDev-%.7s", GIT_HASH); + snprintf(FTLversion, 13, "vDev-%.7s", git_hash()); } else { diff --git a/src/version.c b/src/version.c new file mode 100644 index 000000000..12614eafe --- /dev/null +++ b/src/version.c @@ -0,0 +1,44 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2025 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Version-related hard-coded strings +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "version.h" + +const char * __attribute__ ((const)) git_version(void) +{ + return "v5.25.2-2605-g04807af5"; +} + +const char * __attribute__ ((const)) git_date(void) +{ + return "2025-02-06 16:01:57 +0000"; +} +const char * __attribute__ ((const)) git_branch(void) +{ + return "update/dnsmasq"; +} +const char * __attribute__ ((const)) git_tag(void) +{ + return "v5.25.2"; +} + +const char * __attribute__ ((const)) git_hash(void) +{ + return "04807af5"; +} + +const char * __attribute__ ((const)) ftl_arch(void) +{ + return "x86_64 (compiled locally)"; +} + +const char * __attribute__ ((const)) ftl_cc(void) +{ + return "cc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"; +} diff --git a/src/version.c.in b/src/version.c.in new file mode 100644 index 000000000..5d94f294d --- /dev/null +++ b/src/version.c.in @@ -0,0 +1,44 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2025 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Version-related hard-coded strings +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#include "version.h" + +const char * __attribute__ ((const)) git_version(void) +{ + return "@GIT_VERSION@"; +} + +const char * __attribute__ ((const)) git_date(void) +{ + return "@GIT_DATE@"; +} +const char * __attribute__ ((const)) git_branch(void) +{ + return "@GIT_BRANCH@"; +} +const char * __attribute__ ((const)) git_tag(void) +{ + return "@GIT_TAG@"; +} + +const char * __attribute__ ((const)) git_hash(void) +{ + return "@GIT_HASH@"; +} + +const char * __attribute__ ((const)) ftl_arch(void) +{ + return "@FTL_ARCH@"; +} + +const char * __attribute__ ((const)) ftl_cc(void) +{ + return "@FTL_CC@"; +} diff --git a/src/version.h b/src/version.h new file mode 100644 index 000000000..6be658b25 --- /dev/null +++ b/src/version.h @@ -0,0 +1,22 @@ +/* Pi-hole: A black hole for Internet advertisements +* (c) 2025 Pi-hole, LLC (https://pi-hole.net) +* Network-wide ad blocking via your own hardware. +* +* FTL Engine +* Version-related prototype declarations +* +* This file is copyright under the latest version of the EUPL. +* Please see LICENSE file for your rights under this license. */ + +#ifndef VERSION_H +#define VERSION_H + +const char *git_version(void) __attribute__ ((const)); +const char *git_date(void) __attribute__ ((const)); +const char *git_branch(void) __attribute__ ((const)); +const char *git_tag(void) __attribute__ ((const)); +const char *git_hash(void) __attribute__ ((const)); +const char *ftl_arch(void) __attribute__ ((const)); +const char *ftl_cc(void) __attribute__ ((const)); + +#endif // VERSION_H diff --git a/src/version.h.in b/src/version.h.in deleted file mode 100644 index e9cd74fbe..000000000 --- a/src/version.h.in +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef VERSION_H -#define VERSION_H - -#define GIT_VERSION "@GIT_VERSION@" -#define GIT_DATE "@GIT_DATE@" -#define GIT_BRANCH "@GIT_BRANCH@" -#define GIT_TAG "@GIT_TAG@" -#define GIT_HASH "@GIT_HASH@" -#define FTL_ARCH "@FTL_ARCH@" -#define FTL_CC "@FTL_CC@" - -#endif // VERSION_H diff --git a/test/dnsmasq_warnings b/test/dnsmasq_warnings index 861dfb331..3e9f6284e 100644 --- a/test/dnsmasq_warnings +++ b/test/dnsmasq_warnings @@ -108,6 +108,8 @@ src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries reached (max: %d)"), daemon->ftabsize); src/dnsmasq/forward.c my_syslog(LOG_WARNING, _("Maximum number of concurrent DNS queries to %s reached (max: %d)"), domain, daemon->ftabsize); +src/dnsmasq/forward.c + my_syslog(LOG_WARNING, _("Case mismatch in DNS reply - check bit 0x20 encoding.")); src/dnsmasq/lease.c my_syslog(MS_DHCP | LOG_WARNING, _("ignoring invalid line in lease database: %s %s %s %s ..."), daemon->dhcp_buff3, daemon->dhcp_buff2,