summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAnton Luka Šijanec <anton@sijanec.eu>2022-01-05 22:08:17 +0100
committerAnton Luka Šijanec <anton@sijanec.eu>2022-01-05 22:08:17 +0100
commit7bdd83308e2a7032502ad5a1273d9dcda2f586bf (patch)
tree2b04bfd6766fb60507c4983729870986de46d35b
parentprvi commit (diff)
downloaddnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.gz
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.bz2
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.lz
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.xz
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.tar.zst
dnsfind-7bdd83308e2a7032502ad5a1273d9dcda2f586bf.zip
-rw-r--r--main.c343
1 files changed, 274 insertions, 69 deletions
diff --git a/main.c b/main.c
index ec94e3f..e490d7b 100644
--- a/main.c
+++ b/main.c
@@ -1,4 +1,4 @@
-#include <sys/socket.h> /* udp(7) */ /* TODO: random XID */
+#include <sys/socket.h> /* udp(7) */
#include <netinet/in.h>
#include <netinet/udp.h>
#include <poll.h> /* poll(2) */
@@ -10,7 +10,9 @@
#include <fcntl.h>
#include <errno.h> /* errno(3) */
#include <arpa/inet.h> /* byteorder(3) */
-#include <string.h> /* strlen(3) */
+#include <netdb.h> /* getaddrinfo(3) */
+#include <stdlib.h> /* atoi(3) */
+#include <string.h> /* strchr(3) */
#include "domain2name.c"
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
@@ -18,18 +20,19 @@
#define QUERYDOMAIN "http://sijanec.eu/link?r=.sijanec.eu."
#define EXPECTEDA 93.103.235.126
#define HELP "find recursive DNS resolvers on IPv4 networks\n" \
- "%s [-h] [-d domain] [-a ipv4] network1 [network2 [network3 ...]]\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" \
- "Networks are specified as domain names or IPv4 addresses followed by a forward slash\n" \
- "and the netmask in decimal (0-32) or dot notation. For example: example.com/32. \n" \
+ " -o Output PCAP to filename. Any existing file is truncated.\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 (f@sijanec.eu-http://sijanec.eu/link?r=.sijanec.eu). Make sure it\n" \
- "somehow contains your contact information so the network/server administrator when\n" \
- "looking at the logs will be able to contact you.\n"
-/* DNS PACKET: HEADER (12 bytes) QUESTION ANSWER AUTHORITY ADDITIONAL
-
+ "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
@@ -37,8 +40,7 @@ DEFINITIONS: (those appear somewhere in the packet, packet does not start with d
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:
+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
@@ -65,11 +67,114 @@ ANSWER:
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
+*/
+#define MICROSECOND 0xA1B2C3D4
+#define NANOSECOND 0xA1B23C4D
+#define PCAPVERMAJ 2
+#define PCAPVERMIN 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));
+ enum precedence precedence : 3 __attribute__((packed));
+ enum srvtype srvtype : 5 __attribute__((packed));
+ uint16_t length __attribute__((packed));
+ uint16_t identifier __attribute__((packed));
+ enum ip_flags flags : 3 __attribute__((packed));
+ unsigned int foffset : 13 __attribute__((packed));
+ 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));
+ char options[] /* ignored for char[] */; /* options, padding and data */
+} __attribute__((packed));
enum qr {
- Query,
+ Question,
Response
};
enum opcode {
@@ -89,8 +194,7 @@ enum type {
A = 1,
Ns,
Md,
- Mf,
- Cname,
+ Cname = 5, /* we skip the quite obsolete Mf record, luckily Mf (more fragments) is also 4 */
Soa,
Mb,
Mg,
@@ -102,89 +206,190 @@ enum type {
Minfo,
Mx,
Txt
-}
+};
enum class {
In = 1,
Cs,
Ch,
He,
Any = 255
-}
-struct header {
- uint16_t xid;
- enum qr qr : 1;
- enum opcode opcode : 4;
- unsigned int aa : 1;
- unsigned int tc : 1;
- unsigned int rd : 1;
- unsigned int ra : 1;
- unsigned int z : 3;
- enum rcode rcode : 4;
- uint16_t qdcount;
- uint16_t ancount;
- uint16_t nscount;
- uint16_t arcount;
};
-struct question {
- char * qname;
- enum qtype qtype : 16;
- enum qclass qclass : 16;
+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;
- enum class class : 16;
- uint16_t rdlen;
- char * rdata;
+ char * name __attribute__((packed));
+ enum class class : 16 __attribute__((packed));
+ uint16_t rdlen __attribute__((packed));
+ char * rdata __attribute__((packed));
+};
+struct in_net {
+ struct in_addr addr;
+ struct in_addr mask;
+};
+int resolve (const char * d, uint32_t * r) {
+ struct addrinfo hints = {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_flags = 0,
+ .ai_protocol = 0,
+ .ai_canonname = NULL,
+ .ai_addr = NULL,
+ .ai_next = NULL
+ };
+ struct addrinfo * result;
+ int ret = getaddrinfo(d, NULL, &hints, &result);
+ *r = ((struct sockaddr_in *) result->ai_addr)->sin_addr.s_addr; /* ah yes, C */
+ freeaddrinfo(result);
+ return ret;
}
int main (int argc, char ** argv) {
int r = 0;
- while (t) {
- switch (getopt(argc, argv, ":a:d:h")) {
- /* TODO */
+ 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 -1:
- d = optarg;
- if (!d || !*d) {
- fprintf(stderr, "specify the domain :: " HELP, argv[0]);
- r = 4;
+ 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;
}
- if (strlen(d) > MAXDOMAIN) {
- fprintf(stderr, "domain is too long - max "#MAXDOMAIN".\n");
- r = 5;
+ write(o, "", 1);
+ 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 = 2;
goto r;
}
- t = 0;
- break;
+ n = alloca(l*sizeof(*n));
+ for (int i = optind; i < argc; i++) {
+ int w = i-optind;
+ char * m = strchr(argv[i], '/');
+ int e;
+ if (m)
+ *m++ = '\0';
+ else
+ m = "32";
+ fprintf(stderr, "network %d: resolving %s ... ", w, argv[i]);
+ if ((e = resolve(argv[i], &n[w].addr.s_addr))) {
+ fprintf(stderr, "failed: %s\n", gai_strerror(e));
+ r = 3;
+ goto r;
+ }
+ fprintf(stderr, " %s mask %s ...", inet_ntoa(n[w].addr), m);
+ char * p;
+ int x = strtoll(m, &p, 10);
+ n[w].mask.s_addr = 0;
+ for (int j = 0; j < x && j < 32; j++)
+ n[w].mask.s_addr = n[w].mask.s_addr >> 1 | 1 << 31;
+ n[w].mask.s_addr = htonl(n[w].mask.s_addr);
+ if (*p)
+ if ((e = resolve(m, &n[w].mask.s_addr))) {
+ fprintf(stderr, "no: %s\n", gai_strerror(e));
+ r = 4;
+ goto r;
+ }
+ fprintf(stderr, " %s\n", inet_ntoa(n[w].mask));
+ }
+ goto o;
case '?':
fprintf(stderr, "unknown option :: " HELP, argv[0]);
- if (db != -1)
- close(db);
- r = 6;
+ r = 5;
goto r;
case ':':
fprintf(stderr, "missing option argument :: " HELP, argv[0]);
- r = 7;
+ r = 6;
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 = 7;
+ 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 = 9;
+ goto r;
+ }
+ if ((c = fork()) == -1) {
+ perror("fork()");
+ r = 10;
+ goto r;
+ }
r:
if (s != -1)
if (close(s))
perror("close(s)");
- }
+ if (o != -1)
+ if (close(o))
+ perror("close(o)");
return r;
}
-int handle () {
- char buf[512]; /* we don't support EDNS0 here, so max DNS packet is 512 bytes */
- recvfrom();
-}
-int query () {
- sendto();
-}
-int erase (char * dbfn, int dbfd, char * db) {
-
-}