add initial TCP server

This commit is contained in:
bert hubert
2018-04-03 18:03:59 +02:00
parent eb82401035
commit 3cb8e98440
2 changed files with 204 additions and 110 deletions

View File

@ -19,7 +19,7 @@ typedef std::string dnslabel;
enum class RCode
{
Noerror = 0, Servfail =2, Nxdomain =3, Notimp = 4, Refused = 5
Noerror = 0, Servfail = 2, Nxdomain = 3, Notimp = 4, Refused = 5
};
enum class DNSType : uint16_t
@ -48,11 +48,20 @@ struct RRSet
uint32_t ttl{3600};
};
struct CIStringCompare: public std::binary_function<string, string, bool>
{
bool operator()(const string& a, const string& b) const
{
return strcasecmp(a.c_str(), b.c_str()) < 0; // XXX locale pain, plus embedded zeros
}
};
struct DNSNode
{
const DNSNode* find(dnsname& name, dnsname& last, bool* passedZonecut=0) const;
DNSNode* add(dnsname name);
map<dnslabel, DNSNode> children;
map<dnslabel, DNSNode, CIStringCompare> children;
map<DNSType, RRSet > rrsets;
DNSNode* zone{0}; // if this is set, this node is a zone
@ -248,15 +257,122 @@ dnsname operator+(const dnsname& a, const dnsname& b)
return ret;
}
void udpThread(ComboAddress local, const DNSNode* zones)
bool processQuestion(const DNSNode& zones, DNSMessage& dm, const ComboAddress& local, const ComboAddress& remote, DNSMessage& response)
try
{
Socket udplistener(local.sin4.sin_family, SOCK_DGRAM);
SBind(udplistener, local);
dnsname name;
DNSType type;
dm.getQuestion(name, type);
cout<<"Received a query from "<<remote.toStringWithPort()<<" for "<<name<<" and type "<<(int)type<<endl;
response.dh = dm.dh;
response.dh.ad = 0;
response.dh.ra = 0;
response.dh.aa = 0;
response.dh.qr = 1;
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;
return true;
}
if(dm.dh.opcode != 0) {
cout<<"Query had non-zero opcode "<<dm.dh.opcode<<", sending NOTIMP"<<endl;
response.dh.rcode = (int)RCode::Notimp;
return true;
}
dnsname zone;
auto fnd = zones.find(name, zone);
if(fnd && fnd->zone) {
cout<<"---\nBest zone: "<<zone<<", name now "<<name<<", loaded: "<<(void*)fnd->zone<<endl;
response.dh.aa = 1;
auto bestzone = fnd->zone;
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 "<<(int)rr.first<<endl;
}
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;
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(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;
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 {
response.dh.rcode = (uint8_t)RCode::Refused;
}
return true;
}
catch(std::exception& e) {
cout<<"Error processing query: "<<e.what()<<endl;
return false;
}
void udpThread(ComboAddress local, Socket* sock, const DNSNode* zones)
{
for(;;) {
ComboAddress remote(local);
DNSMessage dm;
string message = SRecvfrom(udplistener, sizeof(dm), remote);
string message = SRecvfrom(*sock, sizeof(dm), remote);
if(message.size() < sizeof(dnsheader)) {
cerr<<"Dropping query from "<<remote.toStringWithPort()<<", too short"<<endl;
continue;
@ -268,116 +384,76 @@ void udpThread(ComboAddress local, const DNSNode* zones)
continue;
}
DNSMessage response;
if(processQuestion(*zones, dm, local, remote, response)) {
SSendto(*sock, response.serialize(), remote);
}
}
}
void tcpClientThread(ComboAddress local, ComboAddress remote, int s, const DNSNode* zones)
{
Socket sock(s);
cout<<"TCP Connection from "<<remote.toStringWithPort()<<endl;
for(;;) {
uint16_t len;
string message = SRead(sock, 2);
if(message.size() != 2)
break;
memcpy(&len, &message.at(1)-1, 2);
len=htons(len);
if(len > 512) {
cerr<<"Remote "<<remote.toStringWithPort()<<" sent question that was too big"<<endl;
return;
}
if(len < sizeof(dnsheader)) {
cerr<<"Dropping query from "<<remote.toStringWithPort()<<", too short"<<endl;
return;
}
cout<<"Reading "<<len<<" bytes"<<endl;
message = SRead(sock, len);
DNSMessage dm;
memcpy(&dm, message.c_str(), message.size());
if(dm.dh.qr) {
cerr<<"Dropping non-query from "<<remote.toStringWithPort()<<endl;
return;
}
dnsname name;
DNSType type;
dm.getQuestion(name, type);
cout<<"Received a query from "<<remote.toStringWithPort()<<" for "<<name<<" and type "<<(int)type<<endl;
DNSMessage response;
response.dh = dm.dh;
response.dh.ad = 0;
response.dh.ra = 0;
response.dh.aa = 0;
response.dh.qr = 1;
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);
if(fnd && fnd->zone) {
cout<<"---\nBest zone: "<<zone<<", name now "<<name<<", loaded: "<<(void*)fnd->zone<<endl;
response.dh.aa = 1;
auto bestzone = fnd->zone;
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 "<<(int)rr.first<<endl;
}
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;
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(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;
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]);
}
}
cout<<"Should do AXFR for "<<name<<endl;
return;
}
else {
response.dh.rcode = (uint8_t)RCode::Refused;
dm.payload.rewind();
DNSMessage response;
if(processQuestion(*zones, dm, local, remote, response)) {
string ser="00"+response.serialize();
cout<<"Should send a message of "<<ser.size()<<" bytes in response"<<endl;
len = htons(ser.length()-2);
ser[0] = *((char*)&len);
ser[1] = *(((char*)&len) + 1);
SWriten(sock, ser);
cout<<"Sent!"<<endl;
}
else
return;
}
SSendto(udplistener, response.serialize(), remote);
}
}
void loadZones(DNSNode& zones)
{
auto zone = zones.add({"powerdns", "org"});
@ -402,14 +478,27 @@ int main(int argc, char** argv)
{
ComboAddress local(argv[1], 53);
Socket udplistener(local.sin4.sin_family, SOCK_DGRAM);
SBind(udplistener, local);
Socket tcplistener(local.sin4.sin_family, SOCK_STREAM);
SSetsockopt(tcplistener, SOL_SOCKET, SO_REUSEPORT, 1);
SBind(tcplistener, local);
SListen(tcplistener, 10);
DNSNode zones;
loadZones(zones);
thread udpServer(udpThread, local, &zones);
// thread tcpServer(tcpThread, local, &zones);
udpServer.join();
// tcpServer.join();
thread udpServer(udpThread, local, &udplistener, &zones);
for(;;) {
ComboAddress remote;
int client = SAccept(tcplistener, remote);
thread t(tcpClientThread, local, remote, client, &zones);
t.detach();
}
udpServer.join();
}