From f0f075abae47994279864fcdfe76c9e31b65bc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 4 Jan 2022 00:33:23 +0100 Subject: prvi commit --- .gitignore | 1 + domain2name.c | 40 +++++++++++ domain2name_test.c | 16 +++++ domain2name_testcase.c | 14 ++++ main.c | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ test.c | 8 +++ 6 files changed, 269 insertions(+) create mode 100644 .gitignore create mode 100644 domain2name.c create mode 100644 domain2name_test.c create mode 100644 domain2name_testcase.c create mode 100644 main.c create mode 100644 test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cba7efc --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +a.out diff --git a/domain2name.c b/domain2name.c new file mode 100644 index 0000000..46fd2a3 --- /dev/null +++ b/domain2name.c @@ -0,0 +1,40 @@ +int domain2name_len (const char * s, int l) { + int r = 1; /* ending terminator */ + int o = 0; /* label offset */ + for (int i = 0; i < l; i++) { + if (s[i] == '.') { + if (!o) /* double period or starting period, label is empty */ + break; + o = 0; + continue; + } + if (!o) /* label has started */ + r++; + r++; + o++; + } + return r; +} +int domain2name (char * d, const char * s, int l) { /* l is length of s */ + char * c = d; /* where to write the label size when done with label */ + char * w = d; + int o = 0; /* label offset */ + for (int i = 0; i <= l /* yes, we go one more ... */; i++) { + if (i == l /* ... here */ || s[i] == '.') { /* end of label or end of last label */ + if (!o) /* double period or starting period, label is empty */ + break; + if (o <= 63) /* max length of label (six bits) */ + *c = o; + o = 0; + continue; + } + if (!o++) /* label has started */ + c = w++; /* to be filled with length */ + if (o <= 63) + *w++ = s[i]; + if (o == 64) /* if this label is too long, instead of writing it, we silently cap */ + *c = 63; /* it at 63 bytes */ + } + *w++ = '\0'; /* luckily this makes domain2name kind of safe for handling as a string (: */ + return w-d; /* we return number of bytes written */ +} /* d must be allocated for at least _len. */ diff --git a/domain2name_test.c b/domain2name_test.c new file mode 100644 index 0000000..f0a6898 --- /dev/null +++ b/domain2name_test.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include "domain2name.c" +int main (int argc, char * const argv[]) { + if (argc != 2) { + fprintf(stderr, "%s domain\n", argv[0]); + return 1; + } + int r, s = domain2name_len(argv[1], strlen(argv[1])); + char * b = alloca(s); + r = domain2name(b, argv[1], strlen(argv[1])); + printf("%4dB %4dB (-:\n", s, r); /* xxd has 16 bytes per row */ + printf("%s", b); + fflush(stdout); +} diff --git a/domain2name_testcase.c b/domain2name_testcase.c new file mode 100644 index 0000000..dfb7334 --- /dev/null +++ b/domain2name_testcase.c @@ -0,0 +1,14 @@ +#define D2NL(x,y) assert(domain2name_len(x, strlen(x)) == y); assert(domain2name_len(x ".", strlen(x ".")) == y); assert(domain2name_len("." x, strlen("." x)) == 1) +D2NL("", 1); +D2NL("localhost", 11); +D2NL("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 2002); +D2NL("a", 3); +D2NL("sijanec.eu", 12); +D2NL("splet.sijanec.eu", 18); +D2NL("splet.sijanec..eu", 15); +D2NL(".", 1); +D2NL("a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a", 2001); +D2NL("a...........................", 3); +D2NL("splet..sijanec..eu", 7); +D2NL("splet..sijanec.eu", 7); +D2NL("193.2.1.66.sijanec.eu", 23); diff --git a/main.c b/main.c new file mode 100644 index 0000000..ec94e3f --- /dev/null +++ b/main.c @@ -0,0 +1,190 @@ +#include /* udp(7) */ /* TODO: random XID */ +#include +#include +#include /* poll(2) */ +#include /* socket(2) */ +#include +#include /* close(2) */ +#include /* perror(3) */ +#include /* open(2) */ +#include +#include /* errno(3) */ +#include /* byteorder(3) */ +#include /* strlen(3) */ +#include "domain2name.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 HELP "find recursive DNS resolvers on IPv4 networks\n" \ + "%s [-h] [-d domain] [-a ipv4] network1 [network2 [network3 ...]]\n" \ + " -a Specify the IPv4 of the -d domain to be used instead of getaddrinfo(3).\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" \ + "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 + +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: + 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 + + +*/ +enum qr { + Query, + Response +}; +enum opcode { + Query, + Iquery, + Stauts +}; +enum rcode { + Success, + Format_error, + Servfail, + Nxdomain, + Ni, + Forbidden +}; +enum type { + A = 1, + Ns, + Md, + Mf, + Cname, + Soa, + Mb, + Mg, + Mr, + Null, + Wks, + Ptr, + Hinfo, + 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; +}; +struct answer { + char * name; + enum class class : 16; + uint16_t rdlen; + char * rdata; +} +int main (int argc, char ** argv) { + int r = 0; + while (t) { + switch (getopt(argc, argv, ":a:d:h")) { + /* TODO */ + 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; + goto r; + } + if (strlen(d) > MAXDOMAIN) { + fprintf(stderr, "domain is too long - max "#MAXDOMAIN".\n"); + r = 5; + goto r; + } + t = 0; + break; + case '?': + fprintf(stderr, "unknown option :: " HELP, argv[0]); + if (db != -1) + close(db); + r = 6; + goto r; + case ':': + fprintf(stderr, "missing option argument :: " HELP, argv[0]); + r = 7; + goto r; + } + } +r: + if (s != -1) + if (close(s)) + perror("close(s)"); + } + 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) { + +} diff --git a/test.c b/test.c new file mode 100644 index 0000000..eaa490e --- /dev/null +++ b/test.c @@ -0,0 +1,8 @@ +#include +#include +#include +#include "domain2name.c" +int main (void) { +#include "domain2name_testcase.c" + return 0; +} -- cgit v1.2.3