summaryrefslogtreecommitdiffstats
path: root/net/shunt/src/poll.uc
blob: 83677918cfcb9a00bf94fe8fc579ea8424ea87fb (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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// shunt - active resolution
//
// Collects the resolvable names from the policies and turns query results
// into set writes, following CNAME chains from the queried name.
//
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Dirk Brenken <dev@brenken.org>

import { normalize } from 'shunt.match';
import { set_name } from 'shunt.nft';

export function names(policies) {
	let seen = {}, out = [];

	for (let p in (policies ?? [])) {
		for (let raw in (p?.domains ?? [])) {
			let n = normalize(raw);

			if (!length(n) || index(n, '*') >= 0)
				continue;
			if (seen[n])
				continue;

			seen[n] = true;
			push(out, n);
		}
	}

	return out;
};

const CHAIN_MAX = 8;

// resolv keys records by their own owner name, so a CNAME answer hides the
// address under the canonical name. Walk from the name that was asked for.
export function addresses(by_name, name) {
	let seen = {};
	let cur = normalize(name);
	let a = [], aaaa = [];

	for (let hop = 0; hop < CHAIN_MAX; hop++) {
		if (!length(cur ?? '') || seen[cur])
			break;

		seen[cur] = true;

		let e = by_name[cur];
		if (!e)
			break;

		for (let v in (e.A ?? []))
			push(a, v);
		for (let v in (e.AAAA ?? []))
			push(aaaa, v);

		cur = normalize((e.CNAME ?? [])[0] ?? '');
	}

	return { a, aaaa };
};

export function index_results(results) {
	let by = {};

	for (let k in (results ?? {})) {
		let n = normalize(k);

		if (length(n))
			by[n] = results[k];
	}

	return by;
};

export function plan(results, matcher, names) {
	let writes = [];
	let by = index_results(results);

	for (let raw in (names ?? [])) {
		let name = normalize(raw);

		let policies = matcher.test(name);
		if (policies == null)
			continue;

		let got = addresses(by, name);

		for (let policy in policies) {
			for (let a in got.a)
				push(writes, { set: set_name('d', 4, policy), addr: a });

			for (let a in got.aaaa)
				push(writes, { set: set_name('d', 6, policy), addr: a });
		}
	}

	return writes;
};