#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;
}