tdns work
This commit is contained in:
252
tdns/tdns.cc
252
tdns/tdns.cc
@ -11,24 +11,25 @@
|
||||
#include "sclasses.hh"
|
||||
#include "dns.hh"
|
||||
#include "safearray.hh"
|
||||
#include <thread>
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef uint16_t dnstype;
|
||||
typedef std::string dnslabel;
|
||||
|
||||
enum class RCode
|
||||
{
|
||||
Refused=5
|
||||
Noerror = 0, Servfail =2, Nxdomain =3, Notimp = 4, Refused = 5
|
||||
};
|
||||
|
||||
enum class DNSType
|
||||
enum class DNSType : uint16_t
|
||||
{
|
||||
A = 1,
|
||||
NS = 2,
|
||||
CNAME = 5,
|
||||
SOA=6,
|
||||
AAAA = 28
|
||||
A = 1, NS = 2, CNAME = 5, SOA=6, AAAA = 28, IXFR = 251, AXFR = 252, ANY = 255
|
||||
};
|
||||
|
||||
enum class DNSSection
|
||||
{
|
||||
Question, Answer, Authority, Additional
|
||||
};
|
||||
|
||||
typedef deque<dnslabel> dnsname;
|
||||
@ -41,20 +42,26 @@ static std::ostream & operator<<(std::ostream &os, const dnsname& d)
|
||||
return os;
|
||||
}
|
||||
|
||||
struct RRSet
|
||||
{
|
||||
vector<string> contents;
|
||||
uint32_t ttl{3600};
|
||||
};
|
||||
|
||||
struct DNSNode
|
||||
{
|
||||
DNSNode* find(dnsname& name, dnsname& last, bool* passedZonecut=0);
|
||||
const DNSNode* find(dnsname& name, dnsname& last, bool* passedZonecut=0) const;
|
||||
DNSNode* add(dnsname name);
|
||||
map<dnslabel, DNSNode> children;
|
||||
map<dnstype, vector<string> > rrsets;
|
||||
map<DNSType, RRSet > rrsets;
|
||||
|
||||
DNSNode* zone{0}; // if this is set, this node is a zone
|
||||
};
|
||||
|
||||
DNSNode* DNSNode::find(dnsname& name, dnsname& last, bool* passedZonecut)
|
||||
const DNSNode* DNSNode::find(dnsname& name, dnsname& last, bool* passedZonecut) const
|
||||
{
|
||||
cout<<"find for '"<<name<<"', last is now '"<<last<<"'"<<endl;
|
||||
if(passedZonecut && rrsets.count((int)DNSType::NS)) {
|
||||
if(!last.empty() && passedZonecut && rrsets.count(DNSType::NS)) {
|
||||
*passedZonecut=true;
|
||||
}
|
||||
|
||||
@ -106,20 +113,18 @@ DNSNode* DNSNode::add(dnsname name)
|
||||
return iter->second.add(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct DNSMessage
|
||||
{
|
||||
struct dnsheader dh=dnsheader{};
|
||||
SafeArray<500> payload;
|
||||
|
||||
dnsname getName();
|
||||
void putName(const dnsname& name);
|
||||
void getQuestion(dnsname& name, dnstype& type);
|
||||
void setQuestion(const dnsname& name, dnstype type);
|
||||
void putRR(const dnsname& name, uint16_t type, uint32_t ttl, const std::string& rr);
|
||||
std::string serialize() const;
|
||||
} __attribute__((packed));
|
||||
void getQuestion(dnsname& name, DNSType& type);
|
||||
void setQuestion(const dnsname& name, DNSType type);
|
||||
void putRR(DNSSection section, const dnsname& name, DNSType type, uint32_t ttl, const std::string& rr);
|
||||
|
||||
string serialize() const;
|
||||
}; // __attribute__((packed));
|
||||
|
||||
dnsname DNSMessage::getName()
|
||||
{
|
||||
@ -136,35 +141,50 @@ dnsname DNSMessage::getName()
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
void DNSMessage::getQuestion(dnsname& name, dnstype& type)
|
||||
void DNSMessage::getQuestion(dnsname& name, DNSType& type)
|
||||
{
|
||||
name=getName();
|
||||
type=payload.getUInt16();
|
||||
type=(DNSType)payload.getUInt16();
|
||||
}
|
||||
|
||||
void DNSMessage::putName(const dnsname& name)
|
||||
void putName(auto& payload, const dnsname& name)
|
||||
{
|
||||
for(const auto& l : name) {
|
||||
if(l.size() > 63)
|
||||
throw std::runtime_error("Can't emit a label larger than 63 characters");
|
||||
payload.putUInt8(l.size());
|
||||
payload.putBlob(l);
|
||||
}
|
||||
payload.putUInt8(0);
|
||||
}
|
||||
|
||||
void DNSMessage::putRR(const dnsname& name, uint16_t type, uint32_t ttl, const std::string& content)
|
||||
void DNSMessage::putRR(DNSSection section, const dnsname& name, DNSType type, uint32_t ttl, const std::string& content)
|
||||
{
|
||||
putName(name);
|
||||
payload.putUInt16(type); payload.putUInt16(1);
|
||||
putName(payload, name);
|
||||
payload.putUInt16((int)type); payload.putUInt16(1);
|
||||
payload.putUInt32(ttl);
|
||||
payload.putUInt16(content.size()); // check for overflow!
|
||||
payload.putBlob(content);
|
||||
|
||||
switch(section) {
|
||||
case DNSSection::Question:
|
||||
throw runtime_error("Can't add questions to a DNS Message with putRR");
|
||||
case DNSSection::Answer:
|
||||
dh.ancount = htons(ntohs(dh.ancount) + 1);
|
||||
break;
|
||||
case DNSSection::Authority:
|
||||
dh.nscount = htons(ntohs(dh.nscount) + 1);
|
||||
break;
|
||||
case DNSSection::Additional:
|
||||
dh.arcount = htons(ntohs(dh.arcount) + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DNSMessage::setQuestion(const dnsname& name, dnstype type)
|
||||
void DNSMessage::setQuestion(const dnsname& name, DNSType type)
|
||||
{
|
||||
putName(name);
|
||||
payload.putUInt16(type);
|
||||
putName(payload, name);
|
||||
payload.putUInt16((uint16_t)type);
|
||||
payload.putUInt16(1); // class
|
||||
}
|
||||
|
||||
@ -172,7 +192,7 @@ string DNSMessage::serialize() const
|
||||
{
|
||||
return string((const char*)this, (const char*)this + sizeof(dnsheader) + payload.payloadpos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static_assert(sizeof(DNSMessage) == 516, "dnsmessage size must be 516");
|
||||
|
||||
@ -187,6 +207,39 @@ std::string serializeDNSName(const dnsname& dn)
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string serializeSOARecord(const dnsname& mname, const dnsname& rname, uint32_t serial, uint32_t minimum=3600, uint32_t refresh=10800, uint32_t retry=3600, uint32_t expire=604800)
|
||||
{
|
||||
SafeArray<256> sa;
|
||||
putName(sa, mname);
|
||||
putName(sa, rname);
|
||||
sa.putUInt32(serial);
|
||||
sa.putUInt32(refresh);
|
||||
sa.putUInt32(retry);
|
||||
sa.putUInt32(expire);
|
||||
sa.putUInt32(minimum);
|
||||
|
||||
return sa.serialize();
|
||||
}
|
||||
|
||||
std::string serializeARecord(const std::string& src)
|
||||
{
|
||||
ComboAddress ca(src);
|
||||
if(ca.sin4.sin_family != AF_INET)
|
||||
throw std::runtime_error("Could not convert '"+src+"' to an IPv4 address");
|
||||
auto p = (const char*)&ca.sin4.sin_addr.s_addr;
|
||||
return std::string(p, p+4);
|
||||
}
|
||||
|
||||
std::string serializeAAAARecord(const std::string& src)
|
||||
{
|
||||
ComboAddress ca(src);
|
||||
if(ca.sin4.sin_family != AF_INET6)
|
||||
throw std::runtime_error("Could not convert '"+src+"' to an IPv6 address");
|
||||
auto p = (const char*)ca.sin6.sin6_addr.s6_addr;
|
||||
return std::string(p, p+16);
|
||||
}
|
||||
|
||||
|
||||
dnsname operator+(const dnsname& a, const dnsname& b)
|
||||
{
|
||||
dnsname ret=a;
|
||||
@ -195,27 +248,11 @@ dnsname operator+(const dnsname& a, const dnsname& b)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
void udpThread(ComboAddress local, const DNSNode* zones)
|
||||
{
|
||||
ComboAddress local(argv[1], 53);
|
||||
Socket udplistener(local.sin4.sin_family, SOCK_DGRAM);
|
||||
SBind(udplistener, local);
|
||||
|
||||
DNSNode zones;
|
||||
auto zone = zones.add({"powerdns", "org"});
|
||||
zone->zone = new DNSNode(); // XXX ICK
|
||||
zone->zone->rrsets[(dnstype)DNSType::SOA]={"hello"};
|
||||
zone->zone->rrsets[(dnstype)DNSType::A]={"\x01\x02\x03\x04"};
|
||||
|
||||
zone->zone->add({"www"})->rrsets[(dnstype)DNSType::CNAME]={serializeDNSName({"server1","powerdns","com"})};
|
||||
|
||||
// zone->zone->add({"*"})->rrsets[(dnstype)DNSType::A]={"\x05\x06\x07\x08"};
|
||||
|
||||
zone->zone->add({"fra"})->rrsets[(dnstype)DNSType::NS]={serializeDNSName({"ns1","fra","powerdns","org"})};
|
||||
|
||||
zone->zone->add({"ns1", "fra"})->rrsets[(dnstype)DNSType::A]={"\x05\x06\x07\x08"};
|
||||
|
||||
|
||||
for(;;) {
|
||||
ComboAddress remote(local);
|
||||
DNSMessage dm;
|
||||
@ -226,15 +263,17 @@ int main(int argc, char** argv)
|
||||
}
|
||||
memcpy(&dm, message.c_str(), message.size());
|
||||
|
||||
if(dm.dh.qr || dm.dh.opcode) {
|
||||
if(dm.dh.qr) {
|
||||
cerr<<"Dropping non-query from "<<remote.toStringWithPort()<<endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
dnsname name;
|
||||
dnstype type;
|
||||
DNSType type;
|
||||
dm.getQuestion(name, type);
|
||||
cout<<"Received a query from "<<remote.toStringWithPort()<<" for "<<name<<" and type "<<type<<endl;
|
||||
cout<<"Received a query from "<<remote.toStringWithPort()<<" for "<<name<<" and type "<<(int)type<<endl;
|
||||
|
||||
|
||||
DNSMessage response;
|
||||
response.dh = dm.dh;
|
||||
response.dh.ad = 0;
|
||||
@ -244,8 +283,23 @@ int main(int argc, char** argv)
|
||||
response.dh.ancount = response.dh.arcount = response.dh.nscount = 0;
|
||||
response.setQuestion(name, type);
|
||||
|
||||
|
||||
if(type == DNSType::AXFR) {
|
||||
cout<<"Query was for AXFR or IXFR over UDP, can't do that"<<endl;
|
||||
response.dh.rcode = (int)RCode::Servfail;
|
||||
SSendto(udplistener, response.serialize(), remote);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(dm.dh.opcode != 0) {
|
||||
cout<<"Query had non-zero opcode "<<dm.dh.opcode<<", sending NOTIMP"<<endl;
|
||||
response.dh.rcode = (int)RCode::Notimp;
|
||||
SSendto(udplistener, response.serialize(), remote);
|
||||
continue;
|
||||
}
|
||||
|
||||
dnsname zone;
|
||||
auto fnd = zones.find(name, zone);
|
||||
auto fnd = zones->find(name, zone);
|
||||
if(fnd && fnd->zone) {
|
||||
cout<<"---\nBest zone: "<<zone<<", name now "<<name<<", loaded: "<<(void*)fnd->zone<<endl;
|
||||
|
||||
@ -255,42 +309,65 @@ int main(int argc, char** argv)
|
||||
dnsname searchname(name), lastnode;
|
||||
bool passedZonecut=false;
|
||||
auto node = bestzone->find(searchname, lastnode, &passedZonecut);
|
||||
if(passedZonecut)
|
||||
response.dh.aa = false;
|
||||
|
||||
if(!node) {
|
||||
cout<<"Found nothing in zone '"<<zone<<"' for lhs '"<<name<<"'"<<endl;
|
||||
}
|
||||
else if(!searchname.empty()) {
|
||||
cout<<"This was a partial match, searchname now "<<searchname<<endl;
|
||||
|
||||
for(const auto& rr: node->rrsets) {
|
||||
cout<<" Have type "<<rr.first<<endl;
|
||||
cout<<" Have type "<<(int)rr.first<<endl;
|
||||
}
|
||||
if(node->rrsets.count((int)DNSType::NS)) {
|
||||
for(const auto& rr : node->rrsets[(int)DNSType::NS]) {
|
||||
response.putRR(lastnode+zone, (int)DNSType::NS, 3600, rr);
|
||||
response.dh.nscount = htons(ntohs(response.dh.ancount)+1);
|
||||
auto iter = node->rrsets.find(DNSType::NS);
|
||||
if(iter != node->rrsets.end() && passedZonecut) {
|
||||
cout<<"Have delegation"<<endl;
|
||||
const auto& rrset = iter->second;
|
||||
for(const auto& rr : rrset.contents) {
|
||||
response.putRR(DNSSection::Answer, lastnode+zone, DNSType::NS, rrset.ttl, rr);
|
||||
}
|
||||
// should do additional processing here
|
||||
}
|
||||
else {
|
||||
cout<<"This is an NXDOMAIN situation"<<endl;
|
||||
const auto& rrset = fnd->zone->rrsets[DNSType::SOA];
|
||||
response.dh.rcode = (int)RCode::Nxdomain;
|
||||
response.putRR(DNSSection::Authority, zone, DNSType::SOA, rrset.ttl, rrset.contents[0]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
cout<<"Found something in zone '"<<zone<<"' for lhs '"<<name<<"', searchname now '"<<searchname<<"', lastnode '"<<lastnode<<"', passedZonecut="<<passedZonecut<<endl;
|
||||
|
||||
if(passedZonecut)
|
||||
response.dh.aa = false;
|
||||
if(node->rrsets.count(type)) {
|
||||
cout<<"Had qtype too!"<<endl;
|
||||
for(const auto& rr : node->rrsets[type]) {
|
||||
response.putRR(lastnode+zone, type, 3600, rr);
|
||||
response.dh.ancount = htons(ntohs(response.dh.ancount)+1);
|
||||
auto iter = node->rrsets.cbegin();
|
||||
if(type == DNSType::ANY) {
|
||||
for(const auto& t : node->rrsets) {
|
||||
const auto& rrset = t.second;
|
||||
for(const auto& rr : rrset.contents) {
|
||||
response.putRR(DNSSection::Answer, lastnode+zone, t.first, rrset.ttl, rr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(node->rrsets.count((int)DNSType::CNAME)) {
|
||||
else if(iter = node->rrsets.find(type), iter != node->rrsets.end()) {
|
||||
const auto& rrset = iter->second;
|
||||
for(const auto& rr : rrset.contents) {
|
||||
response.putRR(DNSSection::Answer, lastnode+zone, type, rrset.ttl, rr);
|
||||
}
|
||||
}
|
||||
else if(iter = node->rrsets.find(DNSType::CNAME), iter != node->rrsets.end()) {
|
||||
cout<<"We do have a CNAME!"<<endl;
|
||||
for(const auto& rr : node->rrsets[(int)DNSType::CNAME]) {
|
||||
response.putRR(lastnode+zone, (int)DNSType::CNAME, 3600, rr);
|
||||
response.dh.ancount = htons(ntohs(response.dh.ancount)+1);
|
||||
const auto& rrset = iter->second;
|
||||
for(const auto& rr : rrset.contents) {
|
||||
response.putRR(DNSSection::Answer, lastnode+zone, DNSType::CNAME, rrset.ttl, rr);
|
||||
}
|
||||
cout<<" We should actually follow this, at least within our zone"<<endl;
|
||||
}
|
||||
else {
|
||||
cout<<"Node exists, qtype doesn't, NOERROR situation, inserting SOA"<<endl;
|
||||
const auto& rrset = fnd->zone->rrsets[DNSType::SOA];
|
||||
response.putRR(DNSSection::Answer, zone, DNSType::SOA, rrset.ttl, rrset.contents[0]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -298,4 +375,41 @@ int main(int argc, char** argv)
|
||||
}
|
||||
SSendto(udplistener, response.serialize(), remote);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void loadZones(DNSNode& zones)
|
||||
{
|
||||
auto zone = zones.add({"powerdns", "org"});
|
||||
zone->zone = new DNSNode(); // XXX ICK
|
||||
zone->zone->rrsets[DNSType::SOA]={{serializeSOARecord({"ns1", "powerdns", "org"}, {"admin", "powerdns", "org"}, 1)}};
|
||||
zone->zone->rrsets[DNSType::A]={{serializeARecord("1.2.3.4")}, 300};
|
||||
zone->zone->rrsets[DNSType::AAAA]={{serializeAAAARecord("::1"), serializeAAAARecord("2001::1")}, 900};
|
||||
zone->zone->rrsets[DNSType::NS]={{serializeDNSName({"ns1", "powerdns", "org"})}, 300};
|
||||
|
||||
zone->zone->add({"www"})->rrsets[DNSType::CNAME]={{serializeDNSName({"server1","powerdns","org"})}};
|
||||
|
||||
zone->zone->add({"server1"})->rrsets[DNSType::A]={{serializeARecord("213.244.168.210")}};
|
||||
|
||||
// zone->zone->add({"*"})->rrsets[(dnstype)DNSType::A]={"\x05\x06\x07\x08"};
|
||||
|
||||
zone->zone->add({"fra"})->rrsets[DNSType::NS]={{serializeDNSName({"ns1","fra","powerdns","org"})}};
|
||||
|
||||
zone->zone->add({"ns1", "fra"})->rrsets[DNSType::A]={{serializeARecord("12.13.14.15")}, 86400};
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
ComboAddress local(argv[1], 53);
|
||||
|
||||
DNSNode zones;
|
||||
|
||||
loadZones(zones);
|
||||
|
||||
thread udpServer(udpThread, local, &zones);
|
||||
// thread tcpServer(tcpThread, local, &zones);
|
||||
|
||||
udpServer.join();
|
||||
// tcpServer.join();
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user