From 526f73a67a35be31a4cd571bd0580fff3cc9f365 Mon Sep 17 00:00:00 2001 From: Mario Klebsch Date: Fri, 6 Aug 2021 21:20:34 +0200 Subject: [PATCH 1/5] * Added support for aliases (CNAME records) * Added support for reverse address lookup --- llmnr.c | 318 +++++++++++++++++++++++++++++++++++++++++++------------ llmnr.h | 1 + llmnrd.c | 7 +- 3 files changed, 257 insertions(+), 69 deletions(-) diff --git a/llmnr.c b/llmnr.c index facba70..f9485ff 100644 --- a/llmnr.c +++ b/llmnr.c @@ -55,28 +55,168 @@ void llmnr_init(const char *hostname, bool ipv6) llmnr_ipv6 = ipv6; } -static bool llmnr_name_matches(const uint8_t *query) +struct llmnr_alias_t { - uint8_t n = llmnr_hostname[0]; + struct llmnr_alias_t * next; + char name[2]; // allocate additional space, always need len prefix and terminating \0. +}; - /* length */ - if (query[0] != n) - return false; - /* NULL byte */ - if (query[1 + n] != 0) - return false; +static struct llmnr_alias_t *llmnr_aliases = NULL; - return strncasecmp((const char *)&query[1], &llmnr_hostname[1], n) == 0; +void llmnr_add_alias(const char *alias) +{ + struct llmnr_alias_t *a = (struct llmnr_alias_t*)malloc(sizeof(*a)+strlen(alias)); + if (!a) + return; + a->name[0] = strlen(alias); + strcpy(&a->name[1], alias); + a->next = llmnr_aliases; + llmnr_aliases = a; +} + + +static size_t get_dns_name_length(const uint8_t *query, size_t query_len) +{ + size_t ret = 0; + while (ret+1 < query_len) { + uint8_t len = query[ret]; + if (len > LLMNR_LABEL_MAX_SIZE) + return 0; + if (!len) + return ret+1; + ret += 1+len; + } + return 0; +} + +static int dns_is_reverse_query(const uint8_t *query) +{ + size_t len = strlen((const char*)query); + uint16_t query_type; + uint16_t query_class; + + // check wether query ends on ".arpa" + if (len < 5 || query[len-5]!= 4 || strcasecmp((const char*)&query[len-4], "arpa") != 0) + return 0; + + memcpy(&query_type, &query[len+1], 2); + memcpy(&query_class, &query[len+3], 2); + + if (query_class != htons(LLMNR_CLASS_IN)) + return 0; + + if ( query_type != htons(LLMNR_TYPE_PTR) && + query_type != htons(LLMNR_QTYPE_ANY) ) + return 0; + + // check wether query ends on ".in-addr.arpa" + if (len > 13 && query[len-13]==7 && strncasecmp( (const char*)&query[len-12], "in-addr", 7) ==0) + return AF_INET; + + // check wether query ends on ".ip6.arpa" + if (len == 73 && query[len-9]==3 && strncasecmp((const char*) &query[len-8], "ip6", 3) ==0) + return AF_INET6; + + return 0; +} + +#define MATCH_NAME 1 +#define MATCH_ALIAS 2 +#define MATCH_ADDR 3 + +static int llmnr_name_matches(int ifindex, const uint8_t *query) +{ + struct llmnr_alias_t *a; + uint8_t n; + + int af = dns_is_reverse_query(query); + + if (af) { + int i; + unsigned x; + char buffer[4]; + uint8_t query_addr[16]; + struct sockaddr_storage addrs[16]; + int n; + + if (af == AF_INET) { + for (i=4; i--;) { + uint8_t n = query[0]; + if (n < 1 || n > 3) + return 0; + memcpy(buffer, query+1, n); + buffer[n]='\0'; + if (sscanf(buffer, "%d", &x) != 1) + return 0; + query_addr[i]=x; + query += n+1; + } + } else if (af == AF_INET6) { + for (i=16; i--;) { + if (query[0] != 1 || query[2] != 1) + return 0; + buffer[0]=query[3]; + buffer[1]=query[1]; + buffer[2]='\0'; + if (sscanf(buffer, "%x", &x) != 1) + return 0; + query_addr[i]=x; + query +=4; + } + } else + return 0; + + n = iface_addr_lookup(ifindex, af, addrs, ARRAY_SIZE(addrs)); + /* Don't respond if no address was found for the given interface */ + if (n == 0) + return 0; + + for (i = 0; i < n; i++) { + void *addr; + size_t addr_size; + + if (addrs[i].ss_family != af) + continue; + + if (af == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&addrs[i]; + addr = &sin->sin_addr; + addr_size = sizeof(sin->sin_addr); + } else if (af == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addrs[i]; + addr = &sin6->sin6_addr; + addr_size = sizeof(sin6->sin6_addr); + } else + return 0; + + if (memcmp(query_addr, addr, addr_size)==0) + return MATCH_ADDR; + } + } + + + n = llmnr_hostname[0]; + if (query[0] == n && query[1 + n] == 0 && strncasecmp((const char *)&query[1], &llmnr_hostname[1], n) == 0) + return MATCH_NAME; + + for (a=llmnr_aliases; a; a = a->next) { + uint8_t n = a->name[0]; + + if (query[0]==n && query[1 + n] == 0 && strncasecmp((const char *)&query[1], &a->name[1], n)==0) + return MATCH_ALIAS; + } + + return 0; } static void llmnr_respond(unsigned int ifindex, const struct llmnr_hdr *hdr, const uint8_t *query, size_t query_len, int sock, - const struct sockaddr_storage *sst) + const struct sockaddr_storage *sst, int match) { uint16_t qtype, qclass; - uint8_t name_len = query[0]; + uint8_t query_name_len = get_dns_name_length(query, query_len); /* skip name length & additional '\0' byte */ - const uint8_t *query_name_end = query + name_len + 2; + const uint8_t *query_name_end = query + query_name_len; size_t i, n, response_len; unsigned char family = AF_UNSPEC; /* @@ -86,9 +226,12 @@ static void llmnr_respond(unsigned int ifindex, const struct llmnr_hdr *hdr, struct sockaddr_storage addrs[16]; struct pkt *p; struct llmnr_hdr *r; + size_t cname_n; + size_t cname_len; + uint16_t name_ptr; /* 4 bytes expected for QTYPE and QCLASS */ - if ((query_len - name_len - 2) < (sizeof(qtype) + sizeof(qclass))) + if ((query_len - query_name_len) < (sizeof(qtype) + sizeof(qclass))) return; memcpy(&qtype, query_name_end, sizeof(qtype)); @@ -104,31 +247,38 @@ static void llmnr_respond(unsigned int ifindex, const struct llmnr_hdr *hdr, if (!llmnr_ipv6 && qtype == LLMNR_QTYPE_AAAA) return; - switch (qtype) { - case LLMNR_QTYPE_A: - family = AF_INET; - break; - case LLMNR_QTYPE_AAAA: - family = AF_INET6; - break; - case LLMNR_QTYPE_ANY: - family = AF_UNSPEC; - break; - default: - return; - } - - n = iface_addr_lookup(ifindex, family, addrs, ARRAY_SIZE(addrs)); - /* Don't respond if no address was found for the given interface */ - if (n == 0) - return; + if (match == MATCH_ADDR) { + response_len = 2 + 2 + 2 + 4 + 2 + 1 + llmnr_hostname[0] + 1 ; + n = 1; + } else { + switch (qtype) { + case LLMNR_QTYPE_A: + family = AF_INET; + break; + case LLMNR_QTYPE_AAAA: + family = AF_INET6; + break; + case LLMNR_QTYPE_ANY: + family = AF_UNSPEC; + break; + default: + return; + } + n = iface_addr_lookup(ifindex, family, addrs, ARRAY_SIZE(addrs)); + /* Don't respond if no address was found for the given interface */ + if (n == 0) + return; + response_len = n * (2 + 2 + 2 + 4 + 2 + sizeof(struct in6_addr)); + } + + cname_n = (match==MATCH_ALIAS) ? 1 : 0 ; + cname_len = (match==MATCH_ALIAS) ? (2 + 2 + 2+ 4 + 2 + llmnr_hostname[0] + 2) : 0; /* * This is the max response length (i.e. using all IPv6 addresses and * no message compression). We might not use all of it. */ - response_len = n * (1 + name_len + 1 + 2 + 2 + 4 + 2 + sizeof(struct in6_addr)); - p = pkt_alloc(sizeof(*hdr) + query_len + response_len); + p = pkt_alloc(sizeof(*hdr) + query_len + cname_len + response_len); /* fill the LLMNR header */ r = (struct llmnr_hdr *)pkt_put(p, sizeof(*r)); @@ -136,50 +286,80 @@ static void llmnr_respond(unsigned int ifindex, const struct llmnr_hdr *hdr, /* response flag */ r->flags = htons(LLMNR_F_QR); r->qdcount = hdr->qdcount; - r->ancount = htons(n); + r->ancount = htons(n + cname_n); r->nscount = 0; r->arcount = 0; + /* get pointer to question name */ + name_ptr=pkt_len(p); /* copy the original question */ memcpy(pkt_put(p, query_len), query, query_len); - /* append an RR for each address */ - for (i = 0; i < n; i++) { - void *addr; - size_t addr_size; - uint16_t type; - - if (addrs[i].ss_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *)&addrs[i]; - addr = &sin->sin_addr; - addr_size = sizeof(sin->sin_addr); - type = LLMNR_TYPE_A; - } else if (addrs[i].ss_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addrs[i]; - addr = &sin6->sin6_addr; - addr_size = sizeof(sin6->sin6_addr); - type = LLMNR_TYPE_AAAA; - } else - continue; - - /* NAME */ - if (i == 0) - memcpy(pkt_put(p, llmnr_hostname[0] + 2), llmnr_hostname, llmnr_hostname[0] + 2); - else { - /* message compression (RFC 1035, section 4.1.3) */ - uint16_t ptr = 0xC000 | (sizeof(*hdr) + query_len); - pkt_put_u16(p, ntohs(ptr)); - } + if (match==MATCH_ADDR) { + /* message compression (RFC 1035, section 4.1.3) */ + pkt_put_u16(p, ntohs(0xC000 | name_ptr)); /* TYPE */ - pkt_put_u16(p, htons(type)); + pkt_put_u16(p, htons(LLMNR_TYPE_PTR)); /* CLASS */ pkt_put_u16(p, htons(LLMNR_CLASS_IN)); /* TTL */ pkt_put_u32(p, htonl(LLMNR_TTL_DEFAULT)); /* RDLENGTH */ - pkt_put_u16(p, htons(addr_size)); + pkt_put_u16(p, htons(llmnr_hostname[0] + 2)); /* RDATA */ - memcpy(pkt_put(p, addr_size), addr, addr_size); + memcpy(pkt_put(p, llmnr_hostname[0] + 2), llmnr_hostname, llmnr_hostname[0] + 2); + } else { + if (match==MATCH_ALIAS) { + /* message compression (RFC 1035, section 4.1.3) */ + pkt_put_u16(p, ntohs(0xC000 | name_ptr)); + /* TYPE */ + pkt_put_u16(p, htons(LLMNR_TYPE_CNAME)); + /* CLASS */ + pkt_put_u16(p, htons(LLMNR_CLASS_IN)); + /* TTL */ + pkt_put_u32(p, htonl(LLMNR_TTL_DEFAULT)); + /* RDLENGTH */ + pkt_put_u16(p, htons(llmnr_hostname[0] + 2)); + /* RDATA */ + /* update pointer to CNAME target */ + name_ptr = pkt_len(p); + memcpy(pkt_put(p, llmnr_hostname[0] + 2), llmnr_hostname, llmnr_hostname[0] + 2); + } + + /* append an RR for each address */ + for (i = 0; i < n; i++) { + void *addr; + size_t addr_size; + uint16_t type; + + if (addrs[i].ss_family == AF_INET) { + struct sockaddr_in *sin = (struct sockaddr_in *)&addrs[i]; + addr = &sin->sin_addr; + addr_size = sizeof(sin->sin_addr); + type = LLMNR_TYPE_A; + } else if (addrs[i].ss_family == AF_INET6) { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addrs[i]; + addr = &sin6->sin6_addr; + addr_size = sizeof(sin6->sin6_addr); + type = LLMNR_TYPE_AAAA; + } else + continue; + + /* NAME */ + /* message compression (RFC 1035, section 4.1.3) */ + pkt_put_u16(p, ntohs(0xC000 | name_ptr)); + + /* TYPE */ + pkt_put_u16(p, htons(type)); + /* CLASS */ + pkt_put_u16(p, htons(LLMNR_CLASS_IN)); + /* TTL */ + pkt_put_u32(p, htonl(LLMNR_TTL_DEFAULT)); + /* RDLENGTH */ + pkt_put_u16(p, htons(addr_size)); + /* RDATA */ + memcpy(pkt_put(p, addr_size), addr, addr_size); + } } if (sendto(sock, p->data, pkt_len(p), 0, (struct sockaddr *)sst, sizeof(*sst)) < 0) @@ -196,6 +376,7 @@ static void llmnr_packet_process(int ifindex, const uint8_t *pktbuf, size_t len, const uint8_t *query; size_t query_len; uint8_t name_len; + int match; /* Query too short? */ if (len < sizeof(struct llmnr_hdr)) @@ -211,15 +392,16 @@ static void llmnr_packet_process(int ifindex, const uint8_t *pktbuf, size_t len, query = pktbuf + sizeof(struct llmnr_hdr); query_len = len - sizeof(struct llmnr_hdr); - name_len = query[0]; + name_len = get_dns_name_length(query, query_len); /* Invalid name in query? */ - if (name_len == 0 || name_len >= query_len || name_len > LLMNR_LABEL_MAX_SIZE || query[1 + name_len] != 0) + if (name_len == 0 || name_len+4u > query_len ) return; /* Authoritative? */ - if (llmnr_name_matches(query)) - llmnr_respond(ifindex, hdr, query, query_len, sock, sst); + match = llmnr_name_matches(ifindex, query); + if (match) + llmnr_respond(ifindex, hdr, query, query_len, sock, sst, match); } void llmnr_recv(int sock) diff --git a/llmnr.h b/llmnr.h index 6380ebf..ee4e7e1 100644 --- a/llmnr.h +++ b/llmnr.h @@ -22,6 +22,7 @@ #include void llmnr_set_hostname(const char *hostname); +void llmnr_add_alias(const char *alias); void llmnr_init(const char *hostname, bool ipv6); void llmnr_recv(int sock); diff --git a/llmnrd.c b/llmnrd.c index ebac84e..630108c 100644 --- a/llmnrd.c +++ b/llmnrd.c @@ -48,8 +48,9 @@ static bool llmnrd_running = true; static int llmnrd_sock_ipv4 = -1; static int llmnrd_sock_ipv6 = -1; -static const char *short_opts = "H:i:p:6dshV"; +static const char *short_opts = "a:H:i:p:6dshV"; static const struct option long_opts[] = { + { "alias", required_argument, NULL, 'a' }, { "hostname", required_argument, NULL, 'H' }, { "interface", required_argument, NULL, 'i' }, { "port", required_argument, NULL, 'p' }, @@ -66,6 +67,7 @@ static void __noreturn usage_and_exit(int status) fprintf(stdout, "Usage: llmnrd [OPTIONS]\n" "Options:\n" " -H, --hostname NAME set hostname to respond with (default: system hostname)\n" + " -a, --alias NAME add NAME as alias, can be given multiple times\n" " -i, --interface DEV bind socket to a specific interface, e.g. eth0\n" " -p, --port NUM set port number to listen on (default: %d)\n" " -6, --ipv6 enable LLMNR name resolution over IPv6\n" @@ -205,6 +207,9 @@ int main(int argc, char **argv) openlog("llmnrd", LOG_PID, LOG_DAEMON); log_to_syslog(); break; + case 'a': + llmnr_add_alias(optarg); + break; case 'H': hostname = xstrdup(optarg); break; From 5a47cce6a5ccbfae6a69ebab0c8c919b5cf2a3b9 Mon Sep 17 00:00:00 2001 From: Mario Klebsch Date: Fri, 6 Aug 2021 21:46:36 +0200 Subject: [PATCH 2/5] Removed blank line --- llmnr.c | 1 - 1 file changed, 1 deletion(-) diff --git a/llmnr.c b/llmnr.c index f9485ff..0a69878 100644 --- a/llmnr.c +++ b/llmnr.c @@ -194,7 +194,6 @@ static int llmnr_name_matches(int ifindex, const uint8_t *query) } } - n = llmnr_hostname[0]; if (query[0] == n && query[1 + n] == 0 && strncasecmp((const char *)&query[1], &llmnr_hostname[1], n) == 0) return MATCH_NAME; From 41b554e9dc8445a12fa5ccb7aad9a8f1e33e5a2d Mon Sep 17 00:00:00 2001 From: Mario Klebsch Date: Tue, 10 Aug 2021 09:54:41 +0200 Subject: [PATCH 3/5] Updated README.md and changed version number to include a suffix indicating the branch. --- Makefile | 2 +- README.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 79f4af1..5a118a2 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # # Copyright (C) 2014-2019 Tobias Klauser -VERSION = 0.7 +VERSION = 0.7-mkl # llmnrd binary D_P = llmnrd diff --git a/README.md b/README.md index 38adefd..c1a30a3 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,9 @@ Contributors llmnrd is authored and maintained by Tobias Klauser +This is a branch maintained by Mario Klebsch +adding support for reverse lookup and CNAME records. + The following people contributed patches and ideas, found and reported bugs or otherwise helped in the development of llmnrd: @@ -105,6 +108,7 @@ otherwise helped in the development of llmnrd: * @Schimmelreiter * @svimik * @tbetker +* Mario Klebsch Thanks a lot! From db53093fc28507a06ecd96d937fae59cba3b925f Mon Sep 17 00:00:00 2001 From: Mario Klebsch Date: Tue, 10 Aug 2021 10:03:33 +0200 Subject: [PATCH 4/5] Pass aliases as command line argumentas, not as options --- llmnrd.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/llmnrd.c b/llmnrd.c index 630108c..1e37523 100644 --- a/llmnrd.c +++ b/llmnrd.c @@ -48,9 +48,8 @@ static bool llmnrd_running = true; static int llmnrd_sock_ipv4 = -1; static int llmnrd_sock_ipv6 = -1; -static const char *short_opts = "a:H:i:p:6dshV"; +static const char *short_opts = "H:i:p:6dshV"; static const struct option long_opts[] = { - { "alias", required_argument, NULL, 'a' }, { "hostname", required_argument, NULL, 'H' }, { "interface", required_argument, NULL, 'i' }, { "port", required_argument, NULL, 'p' }, @@ -64,17 +63,18 @@ static const struct option long_opts[] = { static void __noreturn usage_and_exit(int status) { - fprintf(stdout, "Usage: llmnrd [OPTIONS]\n" + fprintf(stdout, "Usage: llmnrd [OPTIONS] [ ... ]\n" "Options:\n" " -H, --hostname NAME set hostname to respond with (default: system hostname)\n" - " -a, --alias NAME add NAME as alias, can be given multiple times\n" " -i, --interface DEV bind socket to a specific interface, e.g. eth0\n" " -p, --port NUM set port number to listen on (default: %d)\n" " -6, --ipv6 enable LLMNR name resolution over IPv6\n" " -d, --daemonize run as daemon in the background\n" " -s, --syslog send all log output to syslog\n" " -h, --help show this help and exit\n" - " -V, --version show version information and exit\n", + " -V, --version show version information and exit\n" + "\n" + " Add an alias (CNAME record)\n", LLMNR_UDP_PORT); exit(status); } @@ -207,9 +207,6 @@ int main(int argc, char **argv) openlog("llmnrd", LOG_PID, LOG_DAEMON); log_to_syslog(); break; - case 'a': - llmnr_add_alias(optarg); - break; case 'H': hostname = xstrdup(optarg); break; @@ -236,6 +233,9 @@ int main(int argc, char **argv) } } + for (c=1; c < argc; c++) + llmnr_add_alias(argv[c]); + register_signal(SIGINT, signal_handler); register_signal(SIGQUIT, signal_handler); register_signal(SIGTERM, signal_handler); From d941665236fa521f7409f6e8eff6006eb9f7033d Mon Sep 17 00:00:00 2001 From: Mario Klebsch Date: Tue, 10 Aug 2021 10:57:34 +0200 Subject: [PATCH 5/5] Updated man page: * Added ALIAS arguments * addes --syslog option --- doc/llmnrd.8 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/llmnrd.8 b/doc/llmnrd.8 index 1b31ceb..3d7c9a9 100644 --- a/doc/llmnrd.8 +++ b/doc/llmnrd.8 @@ -2,13 +2,16 @@ .SH NAME llmnrd \- LLMNR (RFC 4795) responder daemon .SH SYNOPSIS -\fBllmnrd\fR [\fIoptions\fR] +\fBllmnrd\fR [\fI\,OPTION\/\fR]... [\fI\,ALIAS\/\fR]... + .SH DESCRIPTION .B llmnrd is a user space daemon implementing the Link-Local Multicast Name Resolution (LLMNR) protocol according to RFC 4795. It will respond to name resolution queries sent by Windows clients in networks where no DNS server is available. Both IPv4 and IPv6 are supported. + +If aliases are spcified on the command line, llmnrd will generete apropriate CNAME records. .SH OPTIONS .TP .B -H , --hostname @@ -27,6 +30,9 @@ Enable LLMNR name resolution over IPv6. .B -d, --daemonize Run llmnrd as daemon in the background. .TP +.B -s, --syslog +Send all log output to syslog +.TP .B -h, --help Show usage information. .TP