udhnssnoop: move the code directly into the repo

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2025-09-07 10:05:15 +02:00
parent cbf0e536df
commit bd50dfdf96
6 changed files with 603 additions and 6 deletions

View File

@@ -6,12 +6,6 @@ PKG_RELEASE:=1
PKG_LICENSE:=GPL-2.0
PKG_MAINTAINER:=John Crispin <john@phrozen.org>
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

View File

@@ -0,0 +1,4 @@
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
udnssnoop

View File

@@ -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}
)

View File

@@ -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;
*/
}

View File

@@ -0,0 +1,86 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#ifndef _DNS_H__
#define _DNS_H__
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <linux/filter.h>
#include <linux/udp.h>
#include <netinet/ip6.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <libubox/avl-cmp.h>
#include <libubox/utils.h>
#include <libubox/uloop.h>
#include <libubox/ulog.h>
#include <libubus.h>
#include <uci.h>
#include <uci_blob.h>
#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

View File

@@ -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;
}