mirror of https://github.com/ericonr/dnsquery.git
227 lines
4.8 KiB
C
227 lines
4.8 KiB
C
/* DNS query
|
|
*
|
|
* based off of:
|
|
* - https://datatracker.ietf.org/doc/html/rfc1035
|
|
* - https://datatracker.ietf.org/doc/html/rfc3596
|
|
*
|
|
* understanding compression required some study of strace(1) output
|
|
* from drill(1) and getent(1)
|
|
*
|
|
* this utility doesn't check for buffer overflows in any capacity,
|
|
* and is therefore extremely vulnerable to any form of malicious input
|
|
*
|
|
*/
|
|
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdnoreturn.h>
|
|
#include <string.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#define BYTE1(x) (x & 0xff)
|
|
#define BYTE2(x) ((x>>8)&0xff)
|
|
|
|
static noreturn void e(const char *str)
|
|
{
|
|
fputs(str, stderr);
|
|
fputc('\n', stderr);
|
|
exit(1);
|
|
}
|
|
|
|
static noreturn void ep(const char *str, int v)
|
|
{
|
|
fprintf(stderr, "%s: %d\n", str, v);
|
|
exit(1);
|
|
}
|
|
|
|
static void iter_name(const uint8_t *buf, size_t buf_len, char *name, size_t *name_pos, size_t *index, int recursion)
|
|
{
|
|
if (recursion > 5) {
|
|
e("too much compression recursion!");
|
|
}
|
|
|
|
size_t l, ln = *name_pos, i = *index;
|
|
while ((l = buf[i++])) {
|
|
/* compression */
|
|
if (l & 0xc0) {
|
|
uint16_t offset = ((uint16_t)(l & ~0xc0) << 8) | buf[i++];
|
|
size_t j = offset;
|
|
iter_name(buf, buf_len, name, &ln, &j, recursion+1);
|
|
break;
|
|
} else {
|
|
memcpy(name+ln, buf+i, l);
|
|
i+=l;
|
|
ln+=l+1;
|
|
name[ln-1] = '.';
|
|
}
|
|
}
|
|
*name_pos = ln;
|
|
*index = i;
|
|
}
|
|
|
|
#define QTYPE_A 1
|
|
#define QTYPE_AAAA 28
|
|
#define QCLASS_IN 1
|
|
static void add_q(uint8_t *q, size_t *index, const char *qname, uint16_t qtype, uint16_t qclass, uint16_t *qdcount_written)
|
|
{
|
|
size_t i = *index;
|
|
|
|
for (;;) {
|
|
const char *dot = strchr(qname, '.');
|
|
|
|
/* fragment length */
|
|
size_t f = dot ? dot - qname : strlen(qname);
|
|
if (f >= UINT8_MAX) e("bad qname");
|
|
|
|
q[i++] = f;
|
|
memcpy(q+i, qname, f);
|
|
i+=f;
|
|
|
|
if (dot) qname = dot+1;
|
|
else break;
|
|
}
|
|
/* terminator */
|
|
q[i++] = 0;
|
|
|
|
q[i++] = BYTE2(qtype);
|
|
q[i++] = BYTE1(qtype);
|
|
q[i++] = BYTE2(qclass);
|
|
q[i++] = BYTE1(qclass);
|
|
*index = i;
|
|
|
|
*qdcount_written += 1;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *query = "google.com", *server = "127.0.0.1";
|
|
int c, get_aaaa = 0;
|
|
while ((c = getopt(argc, argv, "46d:s:")) != -1) {
|
|
if (c == '4') get_aaaa = 0;
|
|
else if (c == '6') get_aaaa = 1;
|
|
else if (c == 'd') query = optarg;
|
|
else if (c == 's' && strcmp(optarg, "localhost")) server = optarg;
|
|
else e("usage: dnsquery [-4|-6] [-d domain] [-s server]");
|
|
}
|
|
|
|
uint8_t q[256], r[1024];
|
|
|
|
/* query id */
|
|
getentropy(q, 2);
|
|
|
|
/* QR(1) OPCODE(4) AA(1) TC(1) RD(1)
|
|
* query | standard query */
|
|
q[2] = (0 << 7) | (0 << 3) | (0 << 2) | (0 << 1) | 1;
|
|
/* RA(1) Z(3) RCODE(4) */
|
|
q[3] = 0;
|
|
|
|
uint16_t ancount = 0, nscount = 0, arcount = 0;
|
|
q[6] = BYTE2(ancount);
|
|
q[7] = BYTE1(ancount);
|
|
q[8] = BYTE2(nscount);
|
|
q[9] = BYTE1(nscount);
|
|
q[10] = BYTE2(arcount);
|
|
q[11] = BYTE1(arcount);
|
|
|
|
size_t i = 12;
|
|
|
|
uint16_t qdcount = 0;
|
|
add_q(q, &i, query, get_aaaa ? QTYPE_AAAA : QTYPE_A, QCLASS_IN, &qdcount);
|
|
q[4] = BYTE2(qdcount);
|
|
q[5] = BYTE1(qdcount);
|
|
|
|
int s = socket(AF_INET, SOCK_DGRAM, 0);
|
|
struct sockaddr_in addr = {.sin_family=AF_INET, .sin_port=htons(53), .sin_addr=inet_addr(server)};
|
|
void *ap = &addr;
|
|
sendto(s, q, i, 0, ap, sizeof addr);
|
|
|
|
ssize_t ret = recv(s, r, sizeof r, 0);
|
|
close(s);
|
|
if (ret < 0) {
|
|
perror("recv");
|
|
return 1;
|
|
}
|
|
|
|
if (memcmp(q, r, 2)) {
|
|
e("bad ID");
|
|
}
|
|
|
|
uint8_t rcode = r[3] & 0xf;
|
|
if (rcode) {
|
|
ep("bad rcode", rcode);
|
|
}
|
|
|
|
qdcount = ((uint16_t)r[4] << 8) | r[5];
|
|
ancount = ((uint16_t)r[6] << 8) | r[7];
|
|
if (ancount == 0) {
|
|
e("no answers");
|
|
}
|
|
|
|
/* begin parsing RRs */
|
|
i = 12;
|
|
|
|
for (size_t j=0; j < qdcount+ancount; j++) {
|
|
char name[64];
|
|
size_t ln = 0;
|
|
iter_name(r, ret, name, &ln, &i, 0);
|
|
name[ln-1] = '\0';
|
|
puts(name);
|
|
|
|
/* don't do anything else with query RRs */
|
|
if (j < qdcount) {
|
|
i+=4;
|
|
continue;
|
|
}
|
|
|
|
uint16_t type = ((uint16_t)r[i] << 8) | r[i+1];
|
|
i+=2;
|
|
uint16_t class = ((uint16_t)r[i] << 8) | r[i+1];
|
|
i+=2;
|
|
uint32_t ttl = ((uint32_t)r[i] << 24) | ((uint32_t)r[i+1] << 16) |
|
|
((uint32_t)r[i+2] << 8) | r[i+3];
|
|
i+=4;
|
|
printf("ttl: %u\n", (unsigned)ttl);
|
|
|
|
uint16_t rdlength = ((uint16_t)r[i] << 8) | r[i+1];
|
|
i+=2;
|
|
|
|
if (class == QCLASS_IN) {
|
|
/* desired length */
|
|
uint16_t len_d;
|
|
/* formatting */
|
|
int af;
|
|
const char *ip_class;
|
|
char ip_s[INET6_ADDRSTRLEN];
|
|
|
|
if (type == QTYPE_A) {
|
|
len_d = 4;
|
|
af = AF_INET;
|
|
ip_class = "IPv4";
|
|
} else if (type == QTYPE_AAAA) {
|
|
len_d = 16;
|
|
af = AF_INET6;
|
|
ip_class = "IPv6";
|
|
} else {
|
|
ep("unknown qtype", type);
|
|
}
|
|
if (rdlength != len_d) ep("bad record length", rdlength);
|
|
|
|
/* address comes over the wire in network order */
|
|
if (!inet_ntop(af, r+i, ip_s, sizeof ip_s)) e("bad address");
|
|
i+=len_d;
|
|
|
|
printf("%s: %s\n", ip_class, ip_s);
|
|
continue;
|
|
}
|
|
e("can't interpret answer");
|
|
}
|
|
|
|
return 0;
|
|
}
|