summaryrefslogtreecommitdiffstats
path: root/net/shunt/src/snoop.uc
blob: 5d8a38575c95e6cf0808442122c98ea1575235fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// shunt - passive DNS observer
//
// Opens an AF_PACKET socket with a BPF filter on DNS answers and turns a
// captured frame into a verdict: which policies want it, or why not.
//
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Dirk Brenken <dev@brenken.org>

import { decap } from 'shunt.frame';
import { parse, TYPE } from 'shunt.dns';

export const RECV_LEN = 4160;

export function open(dev) {
	let sock, bpf;

	try {
		sock = require('socket');
	}
	catch (e) {
		return { ok: false, err: 'socket module missing - install ucode-mod-socket' };
	}

	try {
		bpf = require('shunt.snoop_bpf').BPF;
	}
	catch (e) {
		return { ok: false,
			err: 'shunt.snoop_bpf missing - reinstall the shunt package' };
	}

	let s = sock.create(sock.AF_PACKET, sock.SOCK_RAW, 0);
	if (!s)
		return { ok: false, err: `create: ${sock.error()}` };

	if (!s.setopt(sock.SOL_SOCKET, sock.SO_ATTACH_FILTER,
		{ len: length(bpf), filter: bpf })) {
		let err = `SO_ATTACH_FILTER: ${sock.error()}`;
		s.close();
		return { ok: false, err };
	}

	if (!s.bind({ family: sock.AF_PACKET, interface: dev,
		protocol: 0x0003, address: '00:00:00:00:00:00' })) {
		let err = `bind: ${sock.error()}`;
		s.close();
		return { ok: false, err };
	}

	return { ok: true, sock: s };
};

// Returns { policies, qname, a, aaaa } or { drop: <verdict> }. The verdict
// strings are contract; the fixtures compare them verbatim.
export function observe(frame, matcher) {
	let f = decap(frame);
	if (!f.ok)
		return { drop: `frame:${f.err}` };

	let r = parse(f.payload);
	if (!r.ok)
		return { drop: `dns:${r.err}` };

	if (r.qtype != TYPE.A && r.qtype != TYPE.AAAA)
		return { drop: 'qtype' };

	if (!length(r.a) && !length(r.aaaa))
		return { drop: 'noaddr' };

	let policies = matcher.test(r.qname);
	if (policies == null)
		return { drop: 'nomatch' };

	return { policies, qname: r.qname, a: r.a, aaaa: r.aaaa };
};