summaryrefslogtreecommitdiffstats
path: root/main.c
blob: fd77759a908ff99320d8665feebadedfdff03d7a (plain) (blame)
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
#include <sys/socket.h> /* udp(7) */
#include <netinet/in.h>
#include <netinet/udp.h>
#include <poll.h> /* poll(2) */
#include <sys/types.h> /* socket(2) */
#include <sys/socket.h>
#include <unistd.h> /* close(2) */
#include <stdio.h> /* perror(3) */
#include <sys/stat.h> /* open(2) */
#include <fcntl.h>
#include <errno.h> /* errno(3) */
#include <arpa/inet.h> /* byteorder(3) */
#include <netdb.h> /* getaddrinfo(3) */
#include <stdlib.h> /* atoi(3) */
#include <string.h> /* strchr(3) */
#include <time.h> /* clock_gettime(2) */
#include <signal.h> /* signal(2) */
#include <sys/prctl.h> /* prctl(2) */
#include <sys/wait.h> /* waitpid(2) */
#include "domain2name.c"
#include "host.c"
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MAXDOMAIN 255
#define QUERYDOMAIN "http://sijanec.eu/link?r=.sijanec.eu."
#define	EXPECTEDA 93.103.235.126
#define XSTR(x) #x
#define STR(x) XSTR(x)
#define HELP "find recursive DNS resolvers on IPv4 networks\n" \
	"%s [-a ip] [-b ip] [-d domain] [-h] [-o file] [-p port] [-t μs] [-w μs] net1 [net2 ...]\n" \
	"	-a Specify the IPv4 of the -d domain to be used instead of getaddrinfo(3).\n" \
	"	-b Bind on a specific interface, defined by IPv4. Default is any interface.\n" \
	"	-d Specify the domain name to be used in queries that has a single A record.\n" \
	"	-h Show this help and exit.\n" \
	"	-o Output PCAP to filename. Any existing file is truncated. No IP/UDP checksums.\n" \
	"	-p Set the source port number to use instead of a dynamically asigned one.\n" \
	"	-t Number of microseconds to wait between sent packets. (default 1000 - 64 KB/s)\n" \
	"	-w Finish after μs after recv'd last packet when done sending. (default 1000000)\n" \
	"Network addresses are optionally followed by slash and netmask, otherwise networks are\n" \
	"understood as single host addresses. Both network names and netmasks can be domains to\n" \
	"be looked up or IP dot-notation addresses. Mask can also be a bit prefix (/32 default).\n" \
	"When scanning the Internet please make sure that you specify your own domain instead\n" \
	"of the default one (dnsfind.sijanec.eu).\n" \
	"It would take a day to scan the entire address space with the default timings.\n"
/* DNS PACKET: HEADER QUESTION ANSWER AUTHORITY ADDITIONAL 	datatracker.ietf.org/doc/html/rfc1035
DEFINITIONS: (those appear somewhere in the packet, packet does not start with definitions!)
	LABLEN	 8 bits: first two bits zero, then 6 bits length of label
	POINTER	 8 bits: first two bits one, then 6 bits as offset from first byte of packet
	STRING	a single byte for defining length (0-256 - all eight bits) and then string of chars
	DOMAIN	 i.) one or more LABLEN followed by ASCII label (no dots) end with either LABLEN 0
		or a POINTER that points to some LABLEN somewhere else in the packet		-OR-:
		ii.) a POINTER that points to some LABLEN somewhere else in the packet
HEADER:		12 bytes
	XID	16 bits: random string to be matched in response to prevent cache poisoning
     /	QR	 1 bit : what type is this packet?	0 quest	1 response
    |	OPCODE   4 bits: type of query			0 query	1 iquer	2 sstat	3-15 reserved
1 byte	AA	 1 bit : is response authoritative?	0 no	1 yes
    |	TC	 1 bit : was response truncated?	0 no	1 yes
     \	RD       1 bit : does query desire recursion?	0 no	1 yes
     /	RA	 1 bit : does response server recurse?	0 no	1 yes
1 byte	Z	 3 bits: reserved for future		0 only option
     \	RCODE	 4 bits: error condition	0 ok	1 fmter	2 srvfa	3 nxdom	4 N/I	5 forbidden
    	QDCOUNT	16 bits: number of questions
	ANCOUNT 16 bits: number of answers
	NSCOUNT 16 bits: authority section (where to ask for actual response - NS RECORDS)
	ARCOUNT 16 bits: additional section (glue records)
QUESTION:
	QNAME	DOMAIN
	QTYPE	16 bits: 1 A 	2 NS	5 CNAME	6 SOA	10 NULL	12 PTR 13 HINFO	15 MX	16 TXT ...
	QCLASS	16 bits: 1 INTERNET	2 CSNET	(obsolete)	3 CHAOS	4 HESIOD	255 ANY ...
ANSWER:
	NAME	DOMAIN
	TYPE	same as QTYPE
	CLASS	same description as QCLASS, except class 255 ANY is not allowed here
	RDLEN	16 bits: length of RDATA field - this is a number 0-65536, no two zero bits
	RDATA	A: 4 bytes IP address		NS: DOMAIN	CNAME: DOMAIN
		SOA: NAME-ns1 NAME-email 32b-serial 32b-refresh 32b-retry 32b-expire 32b-nxdomainttl
		NULL: any data up to RDLEN 	PTR: DOMAIN	HINFO: STRING-CPU, STRING-OS
		MX: 16 bit preference, NAME-like domain		TXT: one or more STRING
*/
/* PCAP file format: GLOBALHEADER PACKETHEADER PACKETDATA PACKETHEADER2 PACKETDATA2 ...
GLOBAL HEADER:	24 bytes
	MAGIC	32 bits: 0xA1B2C3D4 timestamp is s and micros	0xA1B23C4D timestamp is s and nanos
	MAJOR	16 bits: version	2 https://tools.ietf.org/id/draft-gharris-opsawg-pcap-00.html
	MINOR	16 bits: version	4 _/	  /-------------------------------------------------\
	RESERV1	32 bits: unused and set to 0	 < http://en.wikipedia.org/wiki/Frame_check_sequence|
	RESERV2 32 bits: unused and set to 0	  \--------------------------------------------\  ^ |
	SNAPLEN	32 bits: larger or equal to size of largest capture of a single packet		|/ \|
	FCS	 4 bits: if last bit is 1, first 3 tell nr of bytes of FCS appended to every packet/
	LINKTYP	28 bits: pkt type //tcpdump.org/linktypes.html	101 IPv4/v6	1 ether		|   |
PACKET HEADER:	16 bytes
	SECONDS	32 bits: UNIX timestamp
	NANOSEC 32 bits: nanoseconds elapsed since the second, can also be microseconds - see MAGIC
	CAPTURE	32 bits: number of bytes captured from the packet following the header
	ORIGLEN	32 bits: number of bytes of the original packet size (can be more than CAPTURE)
*/
/* IPv4 PACKET: HEADER DATA				https://datatracker.ietf.org/doc/html/rfc791
HEADER:
 /--	VERSION	 4 bits:	4 IPv4	6 IPv6
| T	HEADLEN	 4 bits: >= 5.	Header size 32 bit words (header is padded), so it points to data.
|w	SRVTYPE	 8 bits:	3bPrecedence 1bLowDelay 1bHighThroughput 1bHighReliability 2bReserved
|e		Prec.: 0routine 1prio 2immediate 3flash 4flashoverride 5critic 110inetctrl 111netctrl
|n	LENGTH	16 bits: length including header and data. every host must accept at least 576.
|t	IDENTIF	16 bits: not so unique ID per src-dest persisted across fragmentations for reassembly
|y	FLAGS	 3 bits: bit 0 (1. bit): evil bit, bit 1: don't fragment, bit 2: more fragments
| B	FOFFSET	13 bits: where in complete datagram this fragment belongs in 64 bit words-first has 0
|y	TTL	 8 bits: every router decreases by one, when zero, packet is destroyed
|t	PROTO	 8 bits: datatracker.ietf.org/doc/html/rfc790#page-6	1 ICMP	6 TCP	17 UDP
|e	CHCKSUM	16 bits: 16 bit one's complement of the one's complement sum of 16b words in header
|s	SRCADDR	32 bits:
 \--	DSTADDR 32 bits:
	OPTIONS variable: depending on type, it may be single byte-type or byte-type, byte-len, data.
		Option type byte: 1b-copy to fragmented headers 2b-option class 5b-option number
		Option classes:  0 control	1 reserved	2 debugging	3 reserved
		Option byte zero denotes an end of options, NOOP is option number 1
	PADDING variable: zero bytes ensuring header is aligned into 32 bit words
*/
/* UDP PACKET: HEADER DATA					https://www.ietf.org/rfc/rfc768.txt
	SRCPORT	16 bits
	DSTPORT	16 bits
	LENGTH	16 bits: size of packet including header in bytes
	CHCKSUM	16 bits: same algo as IP, data: pseudoheader (srcip dstip 0x0011 LENGTH) header data
*/
#define MICROSECOND 0xA1B2C3D4
#define NANOSECOND 0xA1B23C4D
#define PCAPMAJ 2
#define PCAPMIN 4
#define ETHERNET = 1,
#define IP 101
struct pcap_global {
	uint32_t subsecond		__attribute__((packed));
	uint16_t major			__attribute__((packed));
	uint16_t minor			__attribute__((packed));
	uint32_t reserved[2]		__attribute__((packed));
	uint32_t snaplen		__attribute__((packed));
	uint32_t linktype		__attribute__((packed)); /* FCS in included here for order */
} __attribute__((packed));
struct pcap_packet {
	uint32_t seconds		__attribute__((packed));
	uint32_t subseconds		__attribute__((packed));
	uint32_t capture_length		__attribute__((packed));
	uint32_t original_length	__attribute__((packed));
} __attribute__((packed));
#define LOW_DELAY (1 << 0)
#define HIGH_THROUGHPUT (1 << 1)
#define HIGH_RELIABILITY (1 << 2)
#define ROUTINE (0 << 5)
#define PRIORITY (1 << 5)
#define IMMEDIATE (1 << 6)
#define FLASH (PRIORITY | IMMEDIATE)
#define FLASH_OVERRIDE (1 << 7)
#define CRITICAL (FLASH_OVERRIDE | PRIORITY)
#define INETCTRL (FLASH_OVERRIDE | IMMEDIATE)
#define NETCTRL (FLASH_OVERRIDE | FLASH)
#define HEADLENOR (1 << 6) /* always bitwiseOR the headlen with this to apply the version number */
#define EVIL (1 << 13)
#define DF (1 << 14)
#define MF (1 << 15)
#define ICMP 1
#define TCP 6
#define UDP 17
struct ip {
	uint8_t headlen; /* length of header in 32 bit words (min 5, max 15 = 60B). see HEADLENOR. */
	uint8_t srvtype; /* OR here: LOW_DELAY, HIGH_THROUGHPUT, HIGH_RELIABILITY, ROUTINE, ... */
	uint16_t length			__attribute__((packed)); /* header + data in 8 bit words */
	uint16_t identifier		__attribute__((packed));
	uint8_t foffset; /* fragment offset in 64 bit words. also or here any flags: EVIL, DF, MF */
	uint8_t ttl;			/* ignored for uint8_t */
	uint8_t protocol;		/* ignored for uint8_t */
	uint16_t checksum;		/* ignoref for uint8_t */
	struct in_addr src		__attribute__((packed));
	struct in_addr dst		__attribute__((packed)); /* ----------- 20 bytes */
	char options[];			/* ignored for char[] */
} __attribute__((packed));
enum type {
	A = 1,
	Ns,
	Md,
	Cname = 5, /* we skip the quite obsolete Mf record, luckily Mf (more fragments) is also 4 */
	Soa,
	Mb,
	Mg,
	Mr,
	Null,
	Wks,
	Ptr,
	Hinfo,
	Minfo,
	Mx,
	Txt
};
enum class {
	In = 1,
	Cs,
	Ch,
	He,
	Any = 255
};
#define RESPONSE (1 << 0)								/* :FLAGS */
#define QUESTION (0 << 0)
#define QUERY (1 << 1) /* must be set, even with response, this shows that it's normal query type */
#define IQUERY (1 << 2)
#define STATUS (QUERY | IQUERY)
#define AA (1 << 5)
#define TC (1 << 6)
#define RD (1 << 7)
#define RA (1 << 8)
#define SUCCESS (1 << 12)
#define FORMAT_ERROR (1 << 13)
#define SERVFAIL (SUCCESS | FORMAT_ERROR)
#define NXDOMAIN (1 << 14)
#define NI (NXDOMAIN | SUCCESS)
#define FORBIDDEN (NXDOMAIN | FORMAT_ERROR)
struct header {
	uint16_t xid			__attribute__((packed));
	uint16_t flags			__attribute__((packed));
	uint16_t qdcount		__attribute__((packed));
	uint16_t ancount		__attribute__((packed));
	uint16_t nscount		__attribute__((packed));
	uint16_t arcount		__attribute__((packed));
	char data[];			/* ignored for char[] */
} __attribute__((packed));
int logudp (int o /* fd */, struct sockaddr_in s, struct sockaddr_in d, char * u, size_t l /* d */) {
	if (o == -1)
		return -1;
	struct timespec t;
	if (clock_gettime(CLOCK_REALTIME, &t) == -1) {
		perror("clock_gettime(CLOCK_REALTIME, &t)");
		return -2;
	}
	struct pcap_packet p = {
		.seconds = t.tv_sec,
		.subseconds = t.tv_nsec,
		.capture_length = l,
		.original_length = l
	};
	struct ip i = {
		.headlen = 5 | HEADLENOR,
		.srvtype = ROUTINE,
		.length = htons(8+l),
		.identifier = 0x6969,
		.foffset = 0,
		.ttl = 69,
		.protocol = UDP,
		.checksum = 0, /* wireshark does not validate, at least not on debian 11 */
		.src = s.sin_addr,
		.dst = d.sin_addr
	};
#define LOGUDP_L (sizeof p + sizeof i + 4*2 + l)
	char * c, * b = alloca(LOGUDP_L); /* to do in one write (thread safe) */
	char * n = "\0"; /* as per udp rfc when no checksum was calculated (-: */
	uint16_t v = htons(l);
	c = (char *) memcpy(b, &p, sizeof p) + sizeof p;
	c = (char *) memcpy(c, &i, sizeof i) + sizeof i;
	c = (char *) memcpy(c, &s.sin_port, 2) + 2;
	c = (char *) memcpy(c, &d.sin_port, 2) + 2;
	c = (char *) memcpy(c, &v, 2) + 2;
	c = (char *) memcpy(c, &n, 2) + 2;
	c = (char *) memcpy(c, u, l) + l;
	if (write(o, b, LOGUDP_L) == -1) { /* atomic and thread safe, as per posix */
		perror("write(" STR(LOGUDP_L) ")");
		return -3;
	}
	return LOGUDP_L;
}
int finish = 0;
int c = -1; /* child process */
void handler () {
	if (++finish >= 3) {
		if (c != -1)
			kill(c, SIGHUP);
		exit(1);
	}
}
struct timespec lp = { /* last packet */
	.tv_sec = 0
};
void child_handler () {
	if (clock_gettime(CLOCK_MONOTONIC, &lp) == -1)
		perror("clock_gettime(CLOCK_MONOTONIC, &lp)");
}
int main (int argc, char ** argv) {
	int r = 2;
	struct in_addr a = {
		.s_addr = 0
	};
	struct sockaddr_in b = {
		.sin_family = AF_INET,
		.sin_port = 0,
		.sin_addr = {
			.s_addr = INADDR_ANY
		}
	};
	char * d = "dnsfind.sijanec.eu";
	int s = -1; /* socket */
	int o = -1; /* output file */
	struct in_net * n; /* networks */
	int l; /* count of networks */
	int i = 0; /* network index */
	int j = -1; /* host in network index */
	int t = 1000;
	int w = 1000000;
	struct in_addr h; /* host to scan */
	signal(SIGINT, handler);
	signal(SIGTERM, handler);
	while (1) {
		switch (getopt(argc, argv, ":a:b:d:ho:p:t:w:")) {
			case 'a':
				inet_aton(optarg, &a);
				break;
			case 'b':
				inet_aton(optarg, &b.sin_addr);
				break;
			case 'd':
				d = optarg;
				break;
			case 'h':
				printf(HELP, argv[0]);
				r = 0;
				goto r;
			case 'o':
				if ((o = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, 00664)) == -1) {
					perror("open(optarg, O_CREAT | O_TRUNC | O_WRONLY)");
					r = 3;
					goto r;
				}
				struct pcap_global g = {
					.subsecond = NANOSECOND,
					.major = PCAPMAJ,
					.minor = PCAPMIN,
					.reserved[0] = 0,
					.reserved[1] = 0,
					.snaplen = 65535,
					.linktype = IP
				};
				if (write(o, &g, sizeof g) == -1) {
					perror("write(o, &g, sizeof g)");
					r = 4;
					goto r;
				}
				break;
			case 'p':
				b.sin_port = htons(atoi(optarg));
				break;
			case 't':
				t = atoi(optarg);
				break;
			case 'w':
				w = atoi(optarg);
				break;
			case -1:
				if (!(l = argc-optind)) {
					fprintf(stderr, "specify targets to scan :: " HELP, argv[0]);
					r = 5;
					goto r;
				}
				n = alloca(l*sizeof *n);
				for (int i = optind; i < argc; i++) {
					int w = i-optind;
					n[w] = str2net(argv[i]);
				}
				goto o;
			case '?':
				fprintf(stderr, "unknown option :: " HELP, argv[0]);
				r = 6;
				goto r;
			case ':':
				fprintf(stderr, "missing option argument :: " HELP, argv[0]);
				r = 7;
				goto r;
			default:
				r = 8;
				goto r;
		}
	}
o:
	if (!a.s_addr) {
		int e;
		fprintf(stderr, "resolving %s ... ", d);
		if ((e = resolve(d, &a.s_addr))) {
			fprintf(stderr, "failed: %s\n", gai_strerror(e));
			r = 9;
			goto r;
		}
		fprintf(stderr, " %s\n", inet_ntoa(a));
	}
	if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
		perror("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)");
		r = 10;
		goto r;
	}
	if (bind(s, (struct sockaddr *) &b, sizeof(struct sockaddr))) {
		perror("bind(s, (struct sokaddr *) &b, sizeof(struct sockaddr))");
		r = 11;
		goto r;
	}
	if ((c = fork()) == -1) {
		perror("fork()");
		r = 12;
		goto r;
	}
	if (!c) {
		signal(SIGINT, child_handler);
		signal(SIGTERM, child_handler);
		while (1) {
			char b[512]; /* max dns packet */
			struct sockaddr_in f;
			socklen_t č = sizeof f;
			if (recvfrom(s,b,512,!lp.tv_sec?MSG_DONTWAIT:0,(struct sockaddr*)&f,&č)==-1){
				if (errno != EWOULDBLOCK) {
					perror("recvfrom(s,b,512,!lp.tv_sec?MSG_DONTWAIT:0,(str...");
					return 1;
				}
				struct timespec z;
				if (clock_gettime(CLOCK_MONOTONIC, &z) == -1) {
					perror("clock_gettime(CLOCK_MONOTONIC, &z)");
					return 2;
				}
				if ((z.tv_sec*1000000 + z.tv_nsec/1000)
						- (lp.tv_sec*1000000 + lp.tv_nsec/1000) > w) {
					fprintf(stderr, "no more packets are received. timeout.\n");
					return 0;
				}
				continue;
			}
			if (lp.tv_sec)
				if (clock_gettime(CLOCK_MONOTONIC, &lp) == -1) {
					perror("clock_gettime(CLOCK_MONOTONIC, &lp)");
					return 2;
				}
			fprintf(stderr, "received response from %s\n", inet_ntoa(f.sin_addr));
		}
		return 0;
	}
	while (!finish) {
		if (!(t = host(n[i], ++j).s_addr)) {
			if (++i >= l) {
				int s;
				fprintf(stderr, "finished sending, waiting for last replies\n");
				kill(c, SIGINT); /* child waits */
				if (wait(&s) == -1) {
					perror("wait(&s)");
					r = 13;
					goto r;
				}
				if (WIFEXITED(s) && !WEXITSTATUS(s)) {
					r = 0;
					goto r;
				}
				r = 14;
				goto r;
			}
			else
				h = host(n[i], (j = 0));
		}
		struct sockaddr_in e = {
			.sin_family = AF_INET,
			.sin_port = htons(53),
			.sin_addr = h
		};
		struct header h = {
			.xid = 0x6969, /* oh no, cache poisoning, whatever'll I do */
			.flags = htons(QUESTION | QUERY | RD),
			.qdcount = htons(1),
			.ancount = 0,
			.nscount = 0,
			.arcount = 0
		};
		int v = domain2name_len(d, strlen(d));
#define L (sizeof h + v + 2*2)
		char * u = alloca(L);
		char * c;
		uint16_t y = htons(A);
		uint16_t k = htons(In);
		c = (char *) memcpy(u, &h, sizeof h) + sizeof h;
		c += domain2name(c, d, strlen(d));
		c = (char *) memcpy(c, &y, 2) + 2;
		c = (char *) memcpy(c, &k, 2) + 2;
		logudp(o, b, e, u, L);
		sendto(s, u, L, 0, (struct sockaddr *) &e, sizeof(struct sockaddr));
		usleep(t);
	}
	if (finish)
		kill(c, SIGTERM); /* stop receiving */
r:
	if (!r && j != -1) {
		char * x = alloca(l*31+strlen("SCANNED  \n0"));
		strcpy(x, "SCANNED ");
		for (int m = 0; m < finish ? i : l; m++) {
			strcat(x, inet_ntoa(n[m].addr));
			strcat(x, "/");
			strcat(x, inet_ntoa(n[m].mask));
			strcat(x, " ");
		}
		strcat(x, "\n");
		write(STDIN_FILENO, x, strlen(x)); /* vsi tele strlen in strcat niso najboljša pot */
	}
	if (s != -1)
		if (close(s))
			perror("close(s)");
	if (o != -1)
		if (close(o))
			perror("close(o)");
	return r;
}