From bd50dfdf965dabcf6bd55425d0f15f6536a2243e Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sun, 7 Sep 2025 10:05:15 +0200 Subject: [PATCH] udhnssnoop: move the code directly into the repo Signed-off-by: John Crispin --- feeds/ucentral/udnssnoop/Makefile | 6 - feeds/ucentral/udnssnoop/src/.gitignore | 4 + feeds/ucentral/udnssnoop/src/CMakeLists.txt | 16 ++ feeds/ucentral/udnssnoop/src/dns.c | 215 +++++++++++++++ feeds/ucentral/udnssnoop/src/dns.h | 86 ++++++ feeds/ucentral/udnssnoop/src/main.c | 282 ++++++++++++++++++++ 6 files changed, 603 insertions(+), 6 deletions(-) create mode 100644 feeds/ucentral/udnssnoop/src/.gitignore create mode 100644 feeds/ucentral/udnssnoop/src/CMakeLists.txt create mode 100644 feeds/ucentral/udnssnoop/src/dns.c create mode 100644 feeds/ucentral/udnssnoop/src/dns.h create mode 100644 feeds/ucentral/udnssnoop/src/main.c diff --git a/feeds/ucentral/udnssnoop/Makefile b/feeds/ucentral/udnssnoop/Makefile index 985b29a86..dd88375a8 100644 --- a/feeds/ucentral/udnssnoop/Makefile +++ b/feeds/ucentral/udnssnoop/Makefile @@ -6,12 +6,6 @@ PKG_RELEASE:=1 PKG_LICENSE:=GPL-2.0 PKG_MAINTAINER:=John Crispin -PKG_SOURCE_URL=https://github.com/blogic/udnssnoop.git -PKG_MIRROR_HASH:=afd17cc6aed4a151bc0f437b84491d751932a39f93f429418200e9e8be53dfad -PKG_SOURCE_PROTO:=git -PKG_SOURCE_DATE:=2021-04-12 -PKG_SOURCE_VERSION:=67e1e5f0bfc12222aa59c54e7066b1c00a680e56 - include $(INCLUDE_DIR)/package.mk include $(INCLUDE_DIR)/cmake.mk diff --git a/feeds/ucentral/udnssnoop/src/.gitignore b/feeds/ucentral/udnssnoop/src/.gitignore new file mode 100644 index 000000000..bc8dbb94e --- /dev/null +++ b/feeds/ucentral/udnssnoop/src/.gitignore @@ -0,0 +1,4 @@ +CMakeCache.txt +CMakeFiles/ +cmake_install.cmake +udnssnoop diff --git a/feeds/ucentral/udnssnoop/src/CMakeLists.txt b/feeds/ucentral/udnssnoop/src/CMakeLists.txt new file mode 100644 index 000000000..921cb5152 --- /dev/null +++ b/feeds/ucentral/udnssnoop/src/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(udnssnoop C) +INCLUDE(GNUInstallDirs) +ADD_DEFINITIONS(-Os -ggdb -Wall -Werror --std=gnu99 -Wmissing-declarations) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +SET(SOURCES main.c dns.c) +SET(LIBS ubox ubus resolv) + +ADD_EXECUTABLE(udnssnoop ${SOURCES}) +TARGET_LINK_LIBRARIES(udnssnoop ${LIBS}) +INSTALL(TARGETS udnssnoop + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR} +) diff --git a/feeds/ucentral/udnssnoop/src/dns.c b/feeds/ucentral/udnssnoop/src/dns.c new file mode 100644 index 000000000..2bc549900 --- /dev/null +++ b/feeds/ucentral/udnssnoop/src/dns.c @@ -0,0 +1,215 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include "dns.h" + +static char name_buffer[MAX_NAME_LEN + 1]; + +static int +scan_name(const uint8_t *buffer, int len) +{ + int offset = 0; + + while (len && (*buffer != '\0')) { + int l = *buffer; + + if (IS_COMPRESSED(l)) + return offset + 2; + + if (l + 1 > len) return -1; + len -= l + 1; + offset += l + 1; + buffer += l + 1; + } + + if (!len || !offset || (*buffer != '\0')) + return -1; + + return offset + 1; +} + +static struct dns_header* +dns_consume_header(uint8_t **data, int *len) +{ + struct dns_header *h = (struct dns_header *) *data; + + if (*len < sizeof(struct dns_header)) + return NULL; + + h->id = be16_to_cpu(h->id); + h->flags = be16_to_cpu(h->flags); + h->questions = be16_to_cpu(h->questions); + h->answers = be16_to_cpu(h->answers); + h->authority = be16_to_cpu(h->authority); + h->additional = be16_to_cpu(h->additional); + + *len -= sizeof(struct dns_header); + *data += sizeof(struct dns_header); + + return h; +} + +static struct dns_question* +dns_consume_question(uint8_t **data, int *len) +{ + struct dns_question *q = (struct dns_question *) *data; + + if (*len < sizeof(struct dns_question)) + return NULL; + + q->type = be16_to_cpu(q->type); + q->class = be16_to_cpu(q->class); + + *len -= sizeof(struct dns_question); + *data += sizeof(struct dns_question); + + return q; +} + +static struct dns_answer* +dns_consume_answer(uint8_t **data, int *len) +{ + struct dns_answer *a = (struct dns_answer *) *data; + + if (*len < sizeof(struct dns_answer)) + return NULL; + + a->type = be16_to_cpu(a->type); + a->class = be16_to_cpu(a->class); + a->ttl = be32_to_cpu(a->ttl); + a->rdlength = be16_to_cpu(a->rdlength); + + *len -= sizeof(struct dns_answer); + *data += sizeof(struct dns_answer); + + return a; +} + +static char * +dns_consume_name(const uint8_t *base, int blen, uint8_t **data, int *len) +{ + int nlen = scan_name(*data, *len); + + if (nlen < 1) + return NULL; + + if (dn_expand(base, base + blen, *data, name_buffer, MAX_NAME_LEN) < 0) { + perror("dns_consume_name/dn_expand"); + return NULL; + } + + *len -= nlen; + *data += nlen; + + return name_buffer; +} + +static int +parse_answer(uint8_t *buffer, int len, uint8_t **b, int *rlen) +{ + char *name = dns_consume_name(buffer, len, b, rlen); + struct dns_answer *a; + uint8_t *rdata; + char ipbuf[33]; + + if (*rlen < 0) { + fprintf(stderr, "dropping: bad answer - bad length\n"); + return -1; + } + + a = dns_consume_answer(b, rlen); + if (!a) { + fprintf(stderr, "dropping: bad answer - bad buffer\n"); + return -1; + } + + if (!name) { + return 0; + } + + if ((a->class & ~CLASS_FLUSH) != CLASS_IN) { + fprintf(stderr, "dropping: class\n"); + return -1; + } + + rdata = *b; + if (a->rdlength > *rlen) { + fprintf(stderr, "dropping: bad answer - bad rlen\n"); + return -1; + } + + *rlen -= a->rdlength; + *b += a->rdlength; + + switch (a->type) { + case TYPE_A: + if (a->rdlength != 4) + return 0; + + if (!inet_ntop(AF_INET, rdata, ipbuf, sizeof(ipbuf))) + return 0; + break; + + case TYPE_AAAA: + if (a->rdlength != 16) + return 0; + + if (!inet_ntop(AF_INET6, rdata, ipbuf, sizeof(ipbuf))) + return 0; + break; + + default: + return 0; + } + + ubus_notify_qosify(name, ipbuf, a->type, a->ttl); + printf("%s %s %" PRIu32 "\n", name, ipbuf, a->ttl); + + return 0; +} + +void +dns_handle_packet(uint8_t *buffer, int len) +{ + struct dns_header *h; + uint8_t *b = buffer; + int rlen = len; + + h = dns_consume_header(&b, &rlen); + if (!h) { + fprintf(stderr, "dropping: bad header\n"); + return; + } + + if (!(h->flags & FLAG_RESPONSE)) + return; + + if (!h->answers) + return; + + while (h->questions-- > 0) { + char *name = dns_consume_name(buffer, len, &b, &rlen); + struct dns_question *q; + + if (!name || rlen < 0) + return; + + q = dns_consume_question(&b, &rlen); + if (!q) { + fprintf(stderr, "dropping: bad question\n"); + return; + } + } + + while (h->answers-- > 0) + if (parse_answer(buffer, len, &b, &rlen)) + return; + +/* while (h->authority-- > 0) + if (parse_answer(buffer, len, &b, &rlen)) + return; + + while (h->additional-- > 0) + if (parse_answer(buffer, len, &b, &rlen)) + return; +*/ +} diff --git a/feeds/ucentral/udnssnoop/src/dns.h b/feeds/ucentral/udnssnoop/src/dns.h new file mode 100644 index 000000000..0c181f628 --- /dev/null +++ b/feeds/ucentral/udnssnoop/src/dns.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef _DNS_H__ +#define _DNS_H__ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define FLAG_RESPONSE 0x8000 +#define FLAG_AUTHORATIVE 0x0400 + +#define TYPE_A 0x0001 +#define TYPE_PTR 0x000C +#define TYPE_TXT 0x0010 +#define TYPE_AAAA 0x001c +#define TYPE_SRV 0x0021 +#define TYPE_ANY 0x00ff + +#define IS_COMPRESSED(x) ((x & 0xc0) == 0xc0) + +#define CLASS_FLUSH 0x8000 +#define CLASS_UNICAST 0x8000 +#define CLASS_IN 0x0001 + +#define MAX_NAME_LEN 8096 +#define MAX_DATA_LEN 8096 + +struct vlan_hdr { + uint16_t h_vlan_TCI; + uint16_t h_vlan_encapsulated_proto; +}; + +struct dns_header { + uint16_t id; + uint16_t flags; + uint16_t questions; + uint16_t answers; + uint16_t authority; + uint16_t additional; +} __attribute__((packed)); + +struct dns_srv_data { + uint16_t priority; + uint16_t weight; + uint16_t port; +} __attribute__((packed)); + +struct dns_answer { + uint16_t type; + uint16_t class; + uint32_t ttl; + uint16_t rdlength; +} __attribute__((packed)); + +struct dns_question { + uint16_t type; + uint16_t class; +} __attribute__((packed)); + +void dns_handle_packet(uint8_t *buffer, int len); +void ubus_notify_qosify(char *name, char *address, int type, int ttl); + +#endif diff --git a/feeds/ucentral/udnssnoop/src/main.c b/feeds/ucentral/udnssnoop/src/main.c new file mode 100644 index 000000000..2fa2b229c --- /dev/null +++ b/feeds/ucentral/udnssnoop/src/main.c @@ -0,0 +1,282 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +/* code is derived from hapd proxy_arp snooping */ + +#include "dns.h" + + /* sudo tcpdump -s 3000 -dd greater 96 and '(ip or ip6)' and '(udp or tcp)' and '(port 53)' */ +static struct sock_filter dns_sock_filter_insns[] = { + { 0x80, 0, 0, 0x00000000 }, + { 0x35, 0, 19, 0x00000060 }, + { 0x28, 0, 0, 0x0000000c }, + { 0x15, 0, 9, 0x00000800 }, + { 0x30, 0, 0, 0x00000017 }, + { 0x15, 0, 15, 0x00000011 }, + { 0x28, 0, 0, 0x00000014 }, + { 0x45, 13, 0, 0x00001fff }, + { 0xb1, 0, 0, 0x0000000e }, + { 0x48, 0, 0, 0x0000000e }, + { 0x15, 9, 0, 0x00000035 }, + { 0x48, 0, 0, 0x00000010 }, + { 0x15, 7, 8, 0x00000035 }, + { 0x15, 0, 7, 0x000086dd }, + { 0x30, 0, 0, 0x00000014 }, + { 0x15, 0, 5, 0x00000011 }, + { 0x28, 0, 0, 0x00000036 }, + { 0x15, 2, 0, 0x00000035 }, + { 0x28, 0, 0, 0x00000038 }, + { 0x15, 0, 1, 0x00000035 }, + { 0x6, 0, 0, 0x00000bb8 }, + { 0x6, 0, 0, 0x00000000 }, +}; + +static const struct sock_fprog sock_filter = { + .len = ARRAY_SIZE(dns_sock_filter_insns), + .filter = dns_sock_filter_insns, +}; + +static struct ubus_auto_conn conn; +static struct uloop_fd fd; +static struct blob_buf b; +static char *ifname; +static int qosify; + +static struct ubus_object_type ubus_object_type = { + .name = "dnssnoop" +}; + +static void ubus_state_handler(struct ubus_context *ctx, struct ubus_object *obj) +{ +} + +struct ubus_object ubus_object = { + .name = "dnssnoop", + .type = &ubus_object_type, + .subscribe_cb = ubus_state_handler, +}; + + +static int +proto_is_vlan(uint16_t h_proto) +{ + return !!(h_proto == ETH_P_8021Q || + h_proto == ETH_P_8021AD); +} + +static int +consume_buffer(uint8_t **buf, int *len, int size) +{ + if (size > *len) + return -1; + + *buf += size; + *len -= size; + + return 0; +} + +static void +packet_handle(uint8_t *buf, int len) +{ + struct ethhdr *eth = (struct ethhdr *)buf; + uint16_t h_proto; + + if (consume_buffer(&buf, &len, sizeof(*eth))) + return; + + h_proto = eth->h_proto; + + if (proto_is_vlan(ntohs(h_proto))) { + struct vlan_hdr *vlanh = (struct vlan_hdr *)buf; + + if (consume_buffer(&buf, &len, sizeof(struct vlan_hdr))) + return; + + h_proto = vlanh->h_vlan_encapsulated_proto; + } + + switch (ntohs(eth->h_proto)) { + case ETH_P_IP: + if (consume_buffer(&buf, &len, sizeof(struct ip))) + return; + break; + case ETH_P_IPV6: + if (consume_buffer(&buf, &len, sizeof(struct ip6_hdr))) + return; + break; + default: + return; + } + + if (consume_buffer(&buf, &len, sizeof(struct udphdr))) + return; + + dns_handle_packet(buf, len); +} + +static void +socket_fd_cb(struct uloop_fd *fd, unsigned int events) +{ + uint8_t buf[8192]; + + do { + int len = recvfrom(fd->fd, buf, sizeof(buf), MSG_DONTWAIT, NULL, NULL); + + if (len <= 0) { + switch (errno) { + case EINTR: + case EAGAIN: + return; + default: + exit(1); + } + break; + } + packet_handle(buf, len); + } while (true); +} + +static int +socket_open(void) +{ + int sock; + + sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sock == -1) { + ULOG_ERR("failed to open socket on %s\n", ifname); + return -1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname))) { + ULOG_ERR("failed to bind socket to %s\n", ifname); + close(sock); + return -1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, + &sock_filter, sizeof(struct sock_fprog))) { + ULOG_ERR("failed to attach filter to %s\n", ifname); + close(sock); + return -1; + } + + return sock; +} + +static void +snoop_start(void) +{ + int sock = socket_open(); + if (sock == -1) { + ULOG_ERR("failed to open socket on %s\n", ifname); + return; + } + fd.cb = socket_fd_cb; + fd.fd = sock; + uloop_fd_add(&fd, ULOOP_READ); +} + +void +ubus_notify_qosify(char *name, char *address, int type, int ttl) +{ + if (!qosify) + return; + + blob_buf_init(&b, 0); + switch (type) { + case TYPE_AAAA: + blobmsg_add_string(&b, "type", "AAAA"); + break; + case TYPE_A: + blobmsg_add_string(&b, "type", "A"); + break; + default: + return; + } + blobmsg_add_string(&b, "name", name); + blobmsg_add_string(&b, "address", address); + blobmsg_add_u32(&b, "ttl", ttl); + + ubus_invoke(&conn.ctx, qosify, "add_dns_host", b.head, NULL, NULL, 200); +} + +static void +ubus_handle_status(struct ubus_context *ctx, struct ubus_event_handler *ev, + const char *type, struct blob_attr *msg) +{ + enum { + EVENT_ID, + EVENT_PATH, + __EVENT_MAX + }; + + static const struct blobmsg_policy status_policy[__EVENT_MAX] = { + [EVENT_ID] = { .name = "id", .type = BLOBMSG_TYPE_INT32 }, + [EVENT_PATH] = { .name = "path", .type = BLOBMSG_TYPE_STRING }, + }; + + struct blob_attr *tb[__EVENT_MAX]; + uint32_t id; + char *path; + + blobmsg_parse(status_policy, __EVENT_MAX, tb, blob_data(msg), blob_len(msg)); + if (!tb[EVENT_ID] || !tb[EVENT_PATH]) + return; + + path = blobmsg_get_string(tb[EVENT_PATH]); + id = blobmsg_get_u32(tb[EVENT_ID]); + + if (strcmp(path, "qosify")) + return; + + if (!strcmp("ubus.object.remove", type)) + qosify = 0; + + if (!strcmp("ubus.object.add", type)) + qosify = id; +} + +static void +ubus_lookup_cb(struct ubus_context *ctx, struct ubus_object_data *obj, + void *priv) +{ + if (strcmp(obj->path, "qosify")) + return; + + qosify = obj->id; +} + +static struct ubus_event_handler ubus_status_handler = { .cb = ubus_handle_status }; + +static void +ubus_connect_handler(struct ubus_context *ctx) +{ + ULOG_NOTE("connected to ubus\n"); + ubus_add_object(ctx, &ubus_object); + + ubus_register_event_handler(ctx, &ubus_status_handler, "ubus.object.add"); + ubus_register_event_handler(ctx, &ubus_status_handler, "ubus.object.remove"); + + ubus_lookup(ctx, NULL, ubus_lookup_cb, NULL); +} + +int +main(int argc, char **argv) +{ + if (argc != 2) + return -1; + + ifname = argv[1]; + + ulog_open(ULOG_STDIO | ULOG_SYSLOG, LOG_DAEMON, "udnssnoop"); + + uloop_init(); + + conn.cb = ubus_connect_handler; + ubus_auto_connect(&conn); + snoop_start(); + uloop_run(); + uloop_done(); + + return 0; +}