From 9a390acd44203dc7847ad05d86ac07082336c7fe Mon Sep 17 00:00:00 2001 From: Tobias Klauser Date: Tue, 17 Feb 2015 17:25:28 +0100 Subject: llmnr-query: Add simple LLMNR query program Signed-off-by: Tobias Klauser --- .gitignore | 1 + Makefile | 40 +++++++--- llmnr-query.c | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ util.h | 13 ++++ 4 files changed, 279 insertions(+), 12 deletions(-) create mode 100644 llmnr-query.c diff --git a/.gitignore b/.gitignore index bfe4e9a..be74249 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.o llmnrd +llmnr-query diff --git a/Makefile b/Makefile index a94ea25..03acd43 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,15 @@ # # Copyright (C) 2014-2015 Tobias Klauser -P = llmnrd -OBJS = llmnr.o iface.o socket.o util.o main.o -LIBS = -lpthread +# llmnrd binary +D_P = llmnrd +D_OBJS = llmnr.o iface.o socket.o util.o main.o +D_LIBS = -lpthread + +# llmnr-query binary +Q_P = llmnr-query +Q_OBJS = llmnr-query.o util.o +Q_LIBS = CC = $(CROSS_COMPILE)gcc INSTALL = install @@ -22,17 +28,16 @@ LDQ = @echo " LD $@" && $(CC) prefix ?= /usr/local BINDIR = $(prefix)/bin +SBINDIR = $(prefix)/sbin DESTDIR = -all: $(P) +all: $(D_P) $(Q_P) -$(P): $(OBJS) - $(LDQ) $(LDFLAGS) -o $@ $(OBJS) $(LIBS) +$(D_P): $(D_OBJS) + $(LDQ) $(LDFLAGS) -o $@ $(D_OBJS) $(D_LIBS) -install_$(P): $(P) - @echo " INSTALL $(P)" - @$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) - @$(INSTALL) -m 755 $(P) $(BINDIR)/$(P) +$(Q_P): $(Q_OBJS) + $(LDQ) $(LDFLAGS) -o $@ $(Q_OBJS) $(Q_LIBS) %.o: %.c %.h $(CCQ) $(CFLAGS) -o $@ -c $< @@ -40,8 +45,19 @@ install_$(P): $(P) %.o: %.c $(CCQ) $(CFLAGS) -o $@ -c $< -install: install_$(P) +install_$(D_P): $(D_P) + @echo " INSTALL $(D_P)" + @$(INSTALL) -d -m 755 $(DESTDIR)$(SBINDIR) + @$(INSTALL) -m 755 $(D_P) $(SBINDIR)/$(D_P) + +install_$(Q_P): $(Q_P) + @echo " INSTALL $(Q_P)" + @$(INSTALL) -d -m 755 $(DESTDIR)$(BINDIR) + @$(INSTALL) -m 755 $(Q_P) $(BINDIR)/$(Q_P) + +install: install_$(D_P) install_$(Q_P) clean: @echo " CLEAN" - @rm -f $(OBJS) $(P) + @rm -f $(D_OBJS) $(D_P) + @rm -f $(Q_OBJS) $(Q_P) diff --git a/llmnr-query.c b/llmnr-query.c new file mode 100644 index 0000000..8a4a19d --- /dev/null +++ b/llmnr-query.c @@ -0,0 +1,237 @@ +/* + * Simple LLMNR query command. + * + * Copyright (C) 2015 Tobias Klauser + * + * This file is part of llmnrd. + * + * llmnrd is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * llmnrd is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with llmnrd. If not, see . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "compiler.h" +#include "llmnr-packet.h" +#include "log.h" +#include "pkt.h" + +static const char *short_ops = "c:i:p:T:h"; +static const struct option long_opts[] = { + { "count", required_argument, NULL, 'c' }, + { "interval", required_argument, NULL, 'i' }, + { "type", required_argument, NULL, 'T' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 }, +}; + +static void __noreturn usage_and_exit(int status) +{ + fprintf(stdout, "Usage: llmnr-query [OPTIONS...] \n" + "Options:\n" + " -c, --count number of queries to send (default: 1)\n" + " -i, --interval interval between queries in ms (default: 500)\n" + " -T, --type LLMNR query type, must be one of A, AAAA, ANY (default: A)\n" + " -h, --help show this help and exit"); + exit(status); +} + +static const char *query_type(uint16_t qtype) +{ + switch (qtype) { + case LLMNR_QTYPE_A: + return "A"; + case LLMNR_QTYPE_AAAA: + return "AAAA"; + case LLMNR_QTYPE_ANY: + return "ANY"; + default: + return ""; + } +} + +int main(int argc, char **argv) +{ + int c, sock; + const char *query_name; + size_t query_name_len; + unsigned long i, count = 1, interval = 500; + uint16_t qtype = LLMNR_QTYPE_A; + struct pkt *p; + + while ((c = getopt_long(argc, argv, short_ops, long_opts, NULL)) != -1) { + switch (c) { + case 'c': + count = strtoul(optarg, NULL, 0); + break; + case 'i': + interval = strtoul(optarg, NULL, 0); + break; + case 'T': + if (xstreq("A", optarg)) + qtype = LLMNR_QTYPE_A; + else if (xstreq("AAAA", optarg)) + qtype = LLMNR_QTYPE_AAAA; + else if (xstreq("ANY", optarg)) + qtype = LLMNR_QTYPE_ANY; + else { + printf("Invalid query type: %s\n", optarg); + usage_and_exit(EXIT_FAILURE); + } + break; + case 'h': + usage_and_exit(EXIT_SUCCESS); + default: + usage_and_exit(EXIT_FAILURE); + } + } + + if (optind >= argc) + usage_and_exit(EXIT_FAILURE); + + query_name = argv[optind]; + query_name_len = strlen(query_name); + if (query_name_len > UINT8_MAX) { + log_err("Query name too long\n"); + return -1; + } + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + log_err("Failed to open UDP socket: %s\n", strerror(errno)); + return -1; + } + + p = pkt_alloc(128); + + log_info("LLMNR query: %s IN %s\n", query_name, query_type(qtype)); + + for (i = 0; i < count; i++) { + struct llmnr_hdr *hdr; + struct sockaddr_in sin; + size_t query_pkt_len; + fd_set rfds; + struct timeval tv; + int ret; + + hdr = (struct llmnr_hdr *)pkt_put(p, sizeof(*hdr)); + hdr->id = htons(i % UINT16_MAX); + hdr->flags = 0; + hdr->qdcount = htons(1); + hdr->ancount = 0; + hdr->nscount = 0; + hdr->arcount = 0; + + pkt_put_u8(p, (uint8_t)query_name_len); + memcpy(pkt_put(p, query_name_len), query_name, query_name_len); + pkt_put_u8(p, 0); + + pkt_put_u16(p, htons(qtype)); + pkt_put_u16(p, htons(LLMNR_QCLASS_IN)); + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = inet_addr(LLMNR_IPV4_MCAST_ADDR); + sin.sin_port = htons(LLMNR_UDP_PORT); + + query_pkt_len = pkt_len(p) - sizeof(*hdr); + + if (sendto(sock, p->data, pkt_len(p), 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + log_err("Failed to send UDP packet: %s\n", strerror(errno)); + break; + } + + FD_ZERO(&rfds); + FD_SET(sock, &rfds); + + /* wait up to one second for a response */ + tv.tv_sec = 1; + tv.tv_usec = 0; + + ret = select(sock + 1, &rfds, NULL, NULL, &tv); + if (ret < 0) { + log_err("Failed to select() on socket: %s\n", strerror(errno)); + break; + } else if (ret) { + uint16_t j, ancount; + + pkt_reset(p); + if (recv(sock, p->data, p->size, 0) < 0) { + log_err("Failed to receive from socket: %s\n", strerror(errno)); + break; + } + + hdr = (struct llmnr_hdr *)pkt_put(p, sizeof(*hdr)); + ancount = htons(hdr->ancount); + + /* skip the original query */ + pkt_put(p, query_pkt_len); + + for (j = 0; j < ancount; ++j) { + uint8_t nl = *pkt_put(p, 1); + char addr[INET6_ADDRSTRLEN]; + uint16_t type, clss, addr_size; + uint32_t ttl; + char *name; + int af; + + /* compression? */ + if (nl & 0xC0) { + uint16_t ptr = (nl & 0x3F) << 8 | *pkt_put(p, 1); + name = (char *)p->data + ptr; + } else + name = (char *)pkt_put(p, nl + 1); + + type = htons(*(uint16_t *)pkt_put(p, sizeof(type))); + clss = htons(*(uint16_t *)pkt_put(p, sizeof(clss))); + ttl = htonl(*(uint32_t *)pkt_put(p, sizeof(ttl))); + addr_size = htons(*(uint16_t *)pkt_put(p, sizeof(addr_size))); + + if (addr_size == sizeof(struct in_addr)) { + af = AF_INET; + } else if (addr_size == sizeof(struct in6_addr)) { + af = AF_INET6; + } else { + log_warn("Unexpected address size received: %d\n", addr_size); + break; + } + + if (!inet_ntop(af, pkt_put(p, addr_size), addr, ARRAY_SIZE(addr))) + strncpy(addr, "", sizeof(addr)); + + log_info("LLMNR response: %s IN %s %s (TTL %d)\n", name, query_type(type), addr, ttl); + } + } else + log_info("No LLMNR response received within timeout\n"); + + if (i < count - 1) { + pkt_reset(p); + usleep(interval * 1000); + } + } + + pkt_free(p); + + close(sock); + + return 0; +} diff --git a/util.h b/util.h index 8a21467..13a8637 100644 --- a/util.h +++ b/util.h @@ -21,6 +21,7 @@ #define UTIL_H #include +#include #include #include @@ -55,4 +56,16 @@ void *xzalloc(size_t size); void *xrealloc(void *ptr, size_t size); char *xstrdup(const char *s); +static inline bool xstreq(const char *str1, const char *str2) +{ + size_t n = strlen(str1); + + if (n != strlen(str2)) + return false; + if (strncmp(str1, str2, n) != 0) + return false; + + return true; +} + #endif /* UTIL_H */ -- cgit v1.2.3-54-g00ecf