Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ulixee
GitHub Repository: ulixee/secret-agent
Path: blob/main/mitm/test/dns.test.ts
1030 views
1
import { LookupAddress, promises as nodeDns } from 'dns';
2
import { Helpers } from '@secret-agent/testing';
3
import BrowserEmulator from '@secret-agent/default-browser-emulator';
4
import CorePlugins from '@secret-agent/core/lib/CorePlugins';
5
import { IBoundLog } from '@secret-agent/interfaces/ILog';
6
import Log from '@secret-agent/commons/Logger';
7
import CorePlugin from '@secret-agent/plugin-utils/lib/CorePlugin';
8
import Core from '@secret-agent/core';
9
import DnsOverTlsSocket from '../lib/DnsOverTlsSocket';
10
import { Dns } from '../lib/Dns';
11
import RequestSession from '../handlers/RequestSession';
12
13
const { log } = Log(module);
14
15
const CloudFlare = {
16
host: '1.1.1.1',
17
servername: 'cloudflare-dns.com',
18
};
19
20
const Google = {
21
host: '8.8.8.8',
22
servername: 'dns.google',
23
};
24
25
const Quad9 = {
26
host: '9.9.9.9',
27
servername: 'dns.quad9.net',
28
};
29
30
let dns: Dns;
31
let requestSession: RequestSession;
32
beforeAll(() => {
33
Core.use(
34
class CustomPlugin extends CorePlugin {
35
static id = 'test';
36
37
onTlsConfiguration(settings) {
38
settings.tlsClientHelloId = 'chrome-83';
39
}
40
41
onDnsConfiguration(settings) {
42
settings.dnsOverTlsConnection = Quad9;
43
}
44
},
45
);
46
const selectBrowserMeta = BrowserEmulator.selectBrowserMeta();
47
const plugins = new CorePlugins({ selectBrowserMeta }, log as IBoundLog);
48
49
requestSession = new RequestSession('dns.test', plugins, null);
50
Helpers.onClose(() => requestSession.close(), true);
51
dns = new Dns(requestSession);
52
});
53
54
afterAll(() => {
55
dns.close();
56
return Helpers.afterAll();
57
});
58
59
describe('DnsOverTlsSocket', () => {
60
let cloudflareDnsSocket: DnsOverTlsSocket;
61
beforeAll(() => {
62
cloudflareDnsSocket = new DnsOverTlsSocket(
63
{ dnsOverTlsConnection: CloudFlare },
64
requestSession,
65
);
66
});
67
afterAll(() => {
68
cloudflareDnsSocket.close();
69
});
70
71
test('should be able to lookup dns records', async () => {
72
const response = await cloudflareDnsSocket.lookupARecords('dataliberationfoundation.org');
73
expect(response.answers).toHaveLength(1);
74
});
75
76
test('should be able to reuse the socket', async () => {
77
const response = await cloudflareDnsSocket.lookupARecords('ulixee.org');
78
expect(response.answers).toHaveLength(2);
79
});
80
81
test('should be able to lookup multiple records at once', async () => {
82
const response = await Promise.all([
83
cloudflareDnsSocket.lookupARecords('headers.ulixee.org'),
84
cloudflareDnsSocket.lookupARecords('tls.ulixee.org'),
85
cloudflareDnsSocket.lookupARecords('stateofscraping.org'),
86
]);
87
expect(response).toHaveLength(3);
88
});
89
90
test('should be able to lookup with google', async () => {
91
let socket: DnsOverTlsSocket;
92
try {
93
socket = new DnsOverTlsSocket({ dnsOverTlsConnection: Google }, requestSession);
94
const response = await socket.lookupARecords('ulixee.org');
95
expect(response.answers).toHaveLength(2);
96
} finally {
97
socket.close();
98
}
99
});
100
101
test('should be able to lookup a record after a miss', async () => {
102
const item1 = await cloudflareDnsSocket.lookupARecords('double-agent.collect');
103
expect(item1).toBeTruthy();
104
// @ts-ignore - trigger internal eof
105
cloudflareDnsSocket.mitmSocket.emit('eof');
106
const response = await Promise.all([
107
cloudflareDnsSocket.lookupARecords('sub.double-agent.collect'),
108
cloudflareDnsSocket.lookupARecords(' double-agent-external.collect'),
109
]);
110
expect(response).toHaveLength(2);
111
});
112
});
113
114
test('should cache and round robin results', async () => {
115
const domain = 'stateofscraping.org';
116
const spy = jest.spyOn<any, any>(dns, 'lookupDnsEntry');
117
const ip = await dns.lookupIp(domain);
118
expect(ip).toBeTruthy();
119
expect(Dns.dnsEntries.get(domain).isResolved).toBeTruthy();
120
121
const cached = await Dns.dnsEntries.get(domain).promise;
122
expect(cached.aRecords).toHaveLength(2);
123
124
const ip2 = await dns.lookupIp(domain);
125
expect(ip2).toBeTruthy();
126
// should round robin
127
expect(ip).not.toBe(ip2);
128
expect(spy).toHaveBeenCalledTimes(1);
129
});
130
131
test('should lookup in the local machine if not found in DoT', async () => {
132
const lookupSpy = jest.spyOn(nodeDns, 'lookup').mockImplementationOnce(async () => {
133
return [
134
<LookupAddress>{
135
address: '127.0.0.1',
136
family: 4,
137
},
138
] as any;
139
});
140
const domain = 'double-agent.collect';
141
const systemLookupSpy = jest.spyOn<any, any>(dns, 'systemLookup');
142
143
const ip = await dns.lookupIp(domain);
144
expect(ip).toBeTruthy();
145
expect(Dns.dnsEntries.get(domain).isResolved).toBeTruthy();
146
147
const cached = await Dns.dnsEntries.get(domain).promise;
148
expect(cached.aRecords).toHaveLength(1);
149
expect(lookupSpy).toHaveBeenCalledTimes(1);
150
expect(systemLookupSpy).toHaveBeenCalledTimes(1);
151
});
152
153
test('should properly expose errors if nothing is found', async () => {
154
const lookupSpy = jest.spyOn(nodeDns, 'lookup').mockClear();
155
const dotLookup = jest
156
.spyOn<any, any>(dns, 'lookupDnsEntry')
157
.mockClear()
158
.mockImplementationOnce(() => {
159
throw new Error('Not found');
160
});
161
const systemLookupSpy = jest.spyOn<any, any>(dns, 'systemLookup').mockClear();
162
163
let unhandledErrorCalled = false;
164
const handler = () => {
165
unhandledErrorCalled = true;
166
};
167
process.once('unhandledRejection', handler);
168
169
try {
170
await dns.lookupIp('not-real-123423423443433434343-fake-domain.com');
171
} catch (error) {
172
// eslint-disable-next-line jest/no-try-expect
173
expect(error.message).toMatch('Not found');
174
}
175
176
await new Promise(resolve => setTimeout(resolve, 100));
177
expect(unhandledErrorCalled).toBe(false);
178
expect(dotLookup).toHaveBeenCalledTimes(1);
179
expect(lookupSpy).toHaveBeenCalledTimes(1);
180
expect(systemLookupSpy).toHaveBeenCalledTimes(1);
181
process.off('unhandledRejection', handler);
182
});
183
184