summaryrefslogtreecommitdiffstats
path: root/main.c
blob: 02ca7ddad02470b4d1af869f2f03fe9e96f240d3 (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
#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 "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 ipv4] [-b ipv4] [-d domain] [-h] [-o filename] [-p port] network1 [network2 ...]\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" \
	"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"
/* 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 query	1 response
    |	OPCODE   4 bits: type of query			0 std	1 invrs	2 srvst	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
enum linktype {
	Ethernet = 1,
	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));
	unsigned int fcs : 4		__attribute__((packed));
	enum linktype linktype : 28	__attribute__((packed));
} __attribute__((packed));
#define microseconds subseconds
#define nanoseconds subseconds
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));
enum precedence {
	Routine,
	Priority,
	Immediate,
	Flash,
	Flash_override,
	Critical,
	Inetctrl,
	Netctrl
};
enum srvtype {
	Low_delay = 1 << 0,
	High_throughput = 1 << 1,
	High_reliability = 1 << 2,
	Reserved_0 = 1 << 3,
	Reserved_1 = 1 << 4
};
enum ip_flags {
	Evil = 1 << 0,
	Df = 1 << 1,
	Mf = 1 << 2
};
enum protocol {
	Icmp = 1,
	Tcp = 6,
	Udp = 17
};
struct ip {
	unsigned int version : 4	__attribute__((packed));
	unsigned int headlen : 4	__attribute__((packed)); /* measured in 32 bit words */
	enum precedence precedence : 3	__attribute__((packed)); /* without optionts it's min 5 */
	enum srvtype srvtype : 5	__attribute__((packed));
	uint16_t length			__attribute__((packed)); /* header + data in 8 bit words */
	uint16_t identifier		__attribute__((packed));
	enum ip_flags flags : 3		__attribute__((packed));
	unsigned int foffset : 13	__attribute__((packed)); /* fragment offset - 64 bit words */
	uint8_t ttl			/* ignored for uint8_t */;
	enum protocol protocol : 8	__attribute__((packed));
	uint16_t checksum		__attribute__((packed));
	struct in_addr src		__attribute__((packed));
	struct in_addr dst		__attribute__((packed)); /* ----------- 20 bytes */
	char options[]			/* ignored for char[] */;
} __attribute__((packed));
enum qr {
	Question,
	Response
};
enum opcode {
	Query,
	Iquery,
	Stauts
};
enum rcode {
	Success,
	Format_error,
	Servfail,
	Nxdomain,
	Ni,
	Forbidden
};
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
};
enum dns_flags {
	Aa = 1 << 0,
	Tc = 1 << 1,
	Rd = 1 << 2,
	Ra = 1 << 3,
	Z0 = 1 << 4,
	Z1 = 1 << 5,
	Z2 = 1 << 6
};
struct header {
	uint16_t xid			__attribute__((packed));
	enum qr qr : 1			__attribute__((packed));
	enum opcode opcode : 4		__attribute__((packed));
	enum dns_flags flags : 7	__attribute__((packed));
	enum rcode rcode : 4		__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));
struct question {
	char * qname			__attribute__((packed));
	enum type qtype : 16		__attribute__((packed));
	enum class qclass : 16		__attribute__((packed));
} __attribute__((packed));
struct answer {
	char * name 			__attribute__((packed));
	enum class class : 16		__attribute__((packed));
	uint16_t rdlen			__attribute__((packed));
	char * rdata			__attribute__((packed));
};
struct udp {
	struct sockaddr_in src;
	struct sockaddr_in dst;
	size_t len; /* of data only */
	char * data;
};
int logudp (int o /* file descriptor */, struct udp u) {
	struct timespec t;
	if (clock_gettime(CLOCK_REALTIME, &t) == -1) {
		perror("clock_gettime(CLOCK_REALTIME, &t)");
		return -1;
	}
	struct pcap_packet p = {
		.seconds = t.tv_sec,
		.subseconds = t.tv_nsec,
		.capture_length = u.len,
		.original_length = u.len
	};
	struct ip i = {
		.version = 4,
		.headlen = 5,
		.precedence = Routine,
		.srvtype = 0,
		.length = htons(8+u.len),
		.identifier = 0x6969,
		.flags = 0,
		.foffset = 0,
		.ttl = 69,
		.protocol = Udp,
		.checksum = 0, /* wireshark does not validate, at least not on debian 11 */
		.src = u.src.sin_addr,
		.dst = u.dst.sin_addr
	};
#define LOGUDP_L (sizeof p + sizeof i + 4*2 + u.len)
	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 l = htons(u.len);
	c = (char *) memcpy(b, &p, sizeof p) + sizeof p;
	c = (char *) memcpy(c, &i, sizeof i) + sizeof i;
	c = (char *) memcpy(c, &u.src.sin_port, 2) + 2;
	c = (char *) memcpy(c, &u.dst.sin_port, 2) + 2;
	c = (char *) memcpy(c, &l, 2) + 2;
	c = (char *) memcpy(c, &n, 2) + 2;
	c = (char *) memcpy(c, u.data, u.len) + u.len;
	if (write(o, b, LOGUDP_L) == -1) { /* atomic and thread safe, as per posix */
		perror("write(" STR(LOGUDP_L) ")");
		return -2;
	}
	return 0;
}
int main (int argc, char ** argv) {
	int r = 0;
	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 */
	char buf[512]; /* max dns packet */
	int c; /* child process */
	struct in_net * n; /* networks */
	int l; /* count of networks */
	while (1) {
		switch (getopt(argc, argv, ":a:b:d:h")) {
			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)) == -1) {
					perror("open(optarg, O_CREAT | O_TRUNC | O_WRONLY)");
					r = 1;
					goto r;
				}
				struct pcap_global g = {
					.subsecond = NANOSECOND,
					.major = PCAPMAJ,
					.minor = PCAPMIN,
					.reserved[0] = 0,
					.reserved[1] = 0,
					.snaplen = 65535,
					.fcs = 0,
					.linktype = Ip
				};
				if (write(o, &g, sizeof g) == -1) {
					perror("write(o, &g, sizeof g)");
					r = 2;
					goto r;
				}
				break;
			case 'p':
				b.sin_port = htons(atoi(optarg));
				break;
			case -1:
				if (!(l = argc-optind)) {
					fprintf(stderr, "specify targets to scan :: " HELP, argv[0]);
					r = 3;
					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;
		}
	}
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 = 8;
			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 = 8;
		goto r;
	}
	if (bind(s, (struct sockaddr *) &b, sizeof(struct sockaddr))) {
		perror("bind(s, (struct sokaddr *) &b, sizeof(struct sockaddr))");
		r = 10;
		goto r;
	}
	if ((c = fork()) == -1) {
		perror("fork()");
		r = 11;
		goto r;
	}
r:
	if (s != -1)
		if (close(s))
			perror("close(s)");
	if (o != -1)
		if (close(o))
			perror("close(o)");
	return r;
}