summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Klauser <tklauser@distanz.ch>2015-02-17 17:25:28 +0100
committerTobias Klauser <tklauser@distanz.ch>2015-02-17 17:25:28 +0100
commit9a390acd44203dc7847ad05d86ac07082336c7fe (patch)
treeacf10a650982b9b5c90e49ee35b661f46bc84bb3
parent4bcd6157b2b44e700dd824d58248f4a98b2c536e (diff)
llmnr-query: Add simple LLMNR query program
Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
-rw-r--r--.gitignore1
-rw-r--r--Makefile40
-rw-r--r--llmnr-query.c237
-rw-r--r--util.h13
4 files changed, 279 insertions, 12 deletions
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 <tklauser@distanz.ch>
-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 <tklauser@distanz.ch>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <arpa/inet.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#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...] <query-name>\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 "<unknown>";
+ }
+}
+
+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, "<invalid>", 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 <stdarg.h>
+#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -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 */