diff options
author | Adam <Adam@anope.org> | 2010-09-09 23:43:11 -0400 |
---|---|---|
committer | Adam <Adam@anope.org> | 2010-09-09 23:43:11 -0400 |
commit | 46813ccb8c6ab572b8a9ff0a39afb1d92dc4482b (patch) | |
tree | 562da502a102230ce207bbe921fdc978ee71e20c /src/dns.cpp | |
parent | fdd196e50b4616ac377bd0ee0ae5ce6c57b657ee (diff) |
Added an asynchronous DNS system and m_dnsbl, which checks clients against DNS blacklists.
Rewrote internal handling of IPs, we now properly support users using IPv6.
Fixed a few problems with the UnrealIRCd protocol module.
Diffstat (limited to 'src/dns.cpp')
-rw-r--r-- | src/dns.cpp | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/src/dns.cpp b/src/dns.cpp new file mode 100644 index 000000000..51cd14938 --- /dev/null +++ b/src/dns.cpp @@ -0,0 +1,550 @@ +#include "services.h" + +DNSManager *DNSEngine; + +static inline unsigned short GetRandomID() +{ + return random(); +} + +DNSRequest::DNSRequest(const Anope::string &addr, QueryType qt, bool cache) : address(addr), QT(qt) +{ + if (!DNSEngine) + DNSEngine = new DNSManager(); + if (DNSEngine->packets.size() == 65535) + throw SocketException("DNS queue full"); + + Log(LOG_DEBUG_2) << "Resolver: Processing request to lookup " << addr << ", of type " << qt; + + if (cache && DNSEngine->CheckCache(this)) + { + delete this; + return; + } + + DNSPacket *p = new DNSPacket(); + + while (DNSEngine->requests.count((p->id = GetRandomID()))); + + DNSEngine->requests[p->id] = this; + DNSEngine->packets.push_back(p); + + p->flags = DNS_QUERYFLAGS_RD; + if (!p->AddQuestion(addr, qt)) + { + delete this; + return; + } + + SocketEngine->MarkWritable(DNSEngine->sock); + + this->timeout = new DNSRequestTimeout(this, Config->DNSTimeout); +} + +DNSRequest::~DNSRequest() +{ + if (!this->timeout->done) + { + delete this->timeout; + } +} + +void DNSRequest::OnError(DNSError error, const Anope::string &message) +{ +} + +inline DNSPacket::DNSPacket() +{ + this->id = this->flags = this->qdcount = this->ancount = this->nscount = this->arcount = this->payload_count = 0; + memset(&this->payload, 0, sizeof(this->payload)); +} + +bool DNSPacket::AddQuestion(const Anope::string &name, const QueryType qt) +{ + unsigned char temp_buffer[512] = ""; + unsigned short buffer_pos = 0; + + Anope::string working_name = name; + + switch (qt) + { + case DNS_QUERY_PTR: + { + if (working_name.find(':') != Anope::string::npos) + { + in6_addr ip; + if (!inet_pton(AF_INET6, working_name.c_str(), &ip)) + { + Log(LOG_DEBUG_2) << "Resolver: Received an invalid IP for PTR lookup (" << working_name << "): " << strerror(errno); + return false; + } + + static const char *const hex = "0123456789abcdef"; + char reverse_ip[128]; + unsigned reverse_ip_count = 0; + for (int i = 15; i >= 0; --i) + { + reverse_ip[reverse_ip_count++] = hex[ip.s6_addr[i] & 0xF]; + reverse_ip[reverse_ip_count++] = '.'; + reverse_ip[reverse_ip_count++] = hex[ip.s6_addr[i] >> 4]; + reverse_ip[reverse_ip_count++] = '.'; + } + reverse_ip[reverse_ip_count++] = 0; + + working_name = reverse_ip; + working_name += "ip6.arpa"; + Log(LOG_DEBUG_2) << "IP changed to in6.arpa format: " << working_name; + } + else + { + in_addr ip; + if (!inet_pton(AF_INET, working_name.c_str(), &ip)) + { + Log(LOG_DEBUG_2) << "Resolver: Received an invalid IP for PTR lookup (" << working_name << "): " << strerror(errno); + return false; + } + unsigned long reverse_ip = ((ip.s_addr & 0xFF) << 24) | ((ip.s_addr & 0xFF00) << 8) | ((ip.s_addr & 0xFF0000) >> 8) | ((ip.s_addr & 0xFF000000) >> 24); + char ipbuf[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, &reverse_ip, ipbuf, sizeof(ipbuf))) + { + Log(LOG_DEBUG_2) << "Resolver: Reformatted IP " << working_name << " back into an invalid IP: " << strerror(errno); + return false; + } + + working_name = ipbuf; + working_name += ".in-addr.arpa"; + Log(LOG_DEBUG_2) << "IP changed to in-addr.arpa format: " << working_name; + } + } + case DNS_QUERY_CNAME: + case DNS_QUERY_A: + case DNS_QUERY_AAAA: + { + sepstream sep(working_name, '.'); + Anope::string token; + while (sep.GetToken(token)) + { + temp_buffer[buffer_pos++] = token.length(); + memcpy(&temp_buffer[buffer_pos], token.c_str(), token.length()); + buffer_pos += token.length(); + } + break; + } + default: + Log(LOG_DEBUG_2) << "Resolver: Received an unknown query type format " << qt; + return false; + } + + temp_buffer[buffer_pos++] = 0; + + short i = htons(qt); + memcpy(&temp_buffer[buffer_pos], &i, 2); + buffer_pos += 2; + + i = htons(1); + memcpy(&temp_buffer[buffer_pos], &i, 2); + buffer_pos += 2; + + Log(LOG_DEBUG_3) << "Resolver: Packet payload to: " << temp_buffer; + Log(LOG_DEBUG_3) << "Resolver: Bytes used: " << buffer_pos << ", payload count " << this->payload_count; + + if (buffer_pos > (sizeof(this->payload) - this->payload_count)) + return false; + + memmove(this->payload + this->payload_count, temp_buffer, buffer_pos); + this->payload_count += buffer_pos; + + this->qdcount++; + + return true; +} + +inline void DNSPacket::FillPacket(const unsigned char *input, const size_t length) +{ + this->id = (input[0] << 8) | input[1]; + this->flags = (input[2] << 8) | input[3]; + this->qdcount = (input[4] << 8) | input[5]; + this->ancount = (input[6] << 8) | input[7]; + this->nscount = (input[8] << 8) | input[9]; + this->arcount = (input[10] << 8) | input[11]; + memcpy(this->payload, &input[12], length); + this->payload_count = length; +} + +inline void DNSPacket::FillBuffer(unsigned char *buffer) +{ + buffer[0] = this->id >> 8; + buffer[1] = this->id & 0xFF; + buffer[2] = this->flags >> 8; + buffer[3] = this->flags & 0xFF; + buffer[4] = this->qdcount >> 8; + buffer[5] = this->qdcount & 0xFF; + buffer[6] = this->ancount >> 8; + buffer[7] = this->ancount & 0xFF; + buffer[8] = this->nscount >> 8; + buffer[9] = this->nscount & 0xFF; + buffer[10] = this->arcount >> 8; + buffer[11] = this->arcount & 0xFF; + memcpy(&buffer[12], this->payload, this->payload_count); +} + +inline DNSRecord::DNSRecord() +{ + this->type = DNS_QUERY_NONE; + this->record_class = this->ttl = this->rdlength = 0; + this->created = time(NULL); +} + +DNSSocket::DNSSocket(const Anope::string &nTargetHost, int nPort) : ClientSocket(nTargetHost, nPort, "", false, SOCK_DGRAM) +{ + this->server_addr.pton(AF_INET, TargetHost, Port); +} + +DNSSocket::~DNSSocket() +{ + Log() << "Resolver: Lost connection to nameserver"; + DNSEngine->sock = NULL; +} + +int DNSSocket::SendTo(const unsigned char *buf, size_t len) const +{ + return sendto(this->GetSock(), buf, len, 0, &this->server_addr.sa, this->server_addr.size()); +} + +int DNSSocket::RecvFrom(char *buf, size_t len, sockaddrs &addrs) const +{ + socklen_t x = sizeof(addrs); + return recvfrom(this->GetSock(), buf, len, 0, &addrs.sa, &x); +} + +bool DNSSocket::ProcessRead() +{ + Log(LOG_DEBUG_2) << "Resolver: Reading from UDP socket"; + + sockaddrs from_server; + unsigned char packet_buffer[524]; + int length = this->RecvFrom(reinterpret_cast<char *>(&packet_buffer), sizeof(packet_buffer), from_server); + + if (length < 0) + return false; + + if (this->server_addr != from_server) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer from the wrong nameserver, Bad NAT or DNS forging attempt? '" << this->server_addr.addr() << "' != '" << from_server.addr() << "'"; + return true; + } + + if (length < 12) + { + Log(LOG_DEBUG_2) << "Resolver: Received a corrupted packet"; + return true; + } + + /* Remove header length */ + length -= 12; + + DNSPacket recv_packet; + recv_packet.FillPacket(packet_buffer, length); + + std::map<short, DNSRequest *>::iterator it = DNSEngine->requests.find(recv_packet.id); + if (it == DNSEngine->requests.end()) + { + Log(LOG_DEBUG_2) << "Resolver: Received an answer for something we didn't request"; + return true; + } + DNSRequest *request = it->second; + DNSEngine->requests.erase(it); + + if (!(recv_packet.flags & DNS_QUERYFLAGS_QR)) + { + Log(LOG_DEBUG_2) << "Resolver: Received a non-answer"; + request->OnError(DNS_ERROR_NOT_AN_ANSWER, "Received a non-answer"); + } + else if (recv_packet.flags & DNS_QUERYFLAGS_OPCODE) + { + Log(LOG_DEBUG_2) << "Resolver: Received a nonstandard query"; + request->OnError(DNS_ERROR_NONSTANDARD_QUERY, "Received a nonstandard query"); + } + else if (recv_packet.flags & DNS_QUERYFLAGS_RCODE) + { + switch (recv_packet.flags & DNS_QUERYFLAGS_RCODE) + { + case 1: + Log(LOG_DEBUG_2) << "Resolver: Format error"; + request->OnError(DNS_ERROR_FORMAT_ERROR, "Format error"); + break; + case 2: + Log(LOG_DEBUG_2) << "Resolver: Server failure"; + request->OnError(DNS_ERROR_SERVER_FAILURE, "Server failure"); + break; + case 3: + Log(LOG_DEBUG_2) << "Resolver: Domain name not found"; + request->OnError(DNS_ERROR_DOMAIN_NOT_FOUND, "Domain not found"); + break; + case 4: + Log(LOG_DEBUG_2) << "Resolver: Not Implemented - The name server does not support the requested kind of query."; + request->OnError(DNS_ERROR_NOT_IMPLEMENTED, "Not Implemented - The name server does not support the requested kind of query."); + break; + case 5: + Log(LOG_DEBUG_2) << "Resolver: Server refused to perform the specificed operation"; + request->OnError(DNS_ERROR_REFUSED, "Server refused to perform the specified operation"); + break; + default: + Log(LOG_DEBUG_2) << "Resolver: Unknown error"; + request->OnError(DNS_ERROR_UNKNOWN, "Unknown error"); + } + } + else if (recv_packet.ancount < 1) + { + Log(LOG_DEBUG_2) << "Resolver: No resource records returned"; + request->OnError(DNS_ERROR_NO_RECORDS, "No resource records returned"); + } + else + { + unsigned packet_pos = 0; + + /* Skip over the questions in this reponse */ + for (unsigned qdcount = 0; qdcount < recv_packet.qdcount; ++qdcount) + { + for (unsigned offset = recv_packet.payload[packet_pos]; offset; packet_pos += offset + 1, offset = recv_packet.payload[packet_pos]) + { + if (offset & 0xC0) + { + packet_pos += 2; + offset = 0; + } + } + + packet_pos += 5; + } + + for (unsigned ancount = 0; ancount < recv_packet.ancount; ++ancount) + { + Anope::string name; + unsigned packet_pos_ptr = packet_pos; + for (unsigned offset = recv_packet.payload[packet_pos_ptr]; offset; packet_pos_ptr += offset + 1, offset = recv_packet.payload[packet_pos_ptr]) + { + if (offset & 0xC0) + { + offset = (offset & 0x3F) << 8 | recv_packet.payload[++packet_pos_ptr]; + packet_pos_ptr = offset - 12; + offset = recv_packet.payload[packet_pos_ptr]; + } + for (unsigned i = 1; i <= offset; ++i) + name += recv_packet.payload[packet_pos_ptr + i]; + name += "."; + } + name.erase(name.begin() + name.length() - 1); + + if (packet_pos_ptr < packet_pos) + packet_pos += 2; + else + packet_pos = packet_pos_ptr + 1; + + DNSRecord *rr = new DNSRecord; + rr->name = name; + + Log(LOG_DEBUG_2) << "Resolver: Received answer for " << name; + + rr->type = static_cast<QueryType>(recv_packet.payload[packet_pos] << 8 | (recv_packet.payload[packet_pos + 1] & 0xFF)); + packet_pos += 2; + + rr->record_class = recv_packet.payload[packet_pos] << 8 | (recv_packet.payload[packet_pos + 1] & 0xFF); + packet_pos += 2; + + rr->ttl = (recv_packet.payload[packet_pos] << 24) | (recv_packet.payload[packet_pos + 1] << 16) | (recv_packet.payload[packet_pos + 2] << 8) | recv_packet.payload[packet_pos + 3]; + packet_pos += 4; + + rr->rdlength = (recv_packet.payload[packet_pos] << 8 | recv_packet.payload[packet_pos + 1]); + packet_pos += 2; + + switch (rr->type) + { + case DNS_QUERY_A: + { + unsigned long ip = (recv_packet.payload[packet_pos]) | (recv_packet.payload[packet_pos + 1]) << 8 | (recv_packet.payload[packet_pos + 2] << 16) | (recv_packet.payload[packet_pos + 3] << 24); + packet_pos += 4; + + char ipbuf[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, &ip, ipbuf, sizeof(ipbuf))) + { + Log(LOG_DEBUG_2) << "Resolver: Received an invalid IP for DNS_QUERY_A: " << strerror(errno); + request->OnError(DNS_ERROR_FORMAT_ERROR, "Received an invalid IP from the nameserver: " + stringify(strerror(errno))); + delete rr; + rr = NULL; + } + else + rr->result = ipbuf; + break; + } + case DNS_QUERY_AAAA: + { + unsigned char ip[16]; + for (int i = 0; i < 16; ++i) + ip[i] = recv_packet.payload[packet_pos + i]; + packet_pos += 16; + + char ipbuf[INET6_ADDRSTRLEN]; + if (!inet_ntop(AF_INET6, &ip, ipbuf, sizeof(ipbuf))) + { + Log(LOG_DEBUG_2) << "Resolver: Received an invalid IP for DNS_QUERY_A: " << strerror(errno); + request->OnError(DNS_ERROR_FORMAT_ERROR, "Received an invalid IP from the nameserver: " + stringify(strerror(errno))); + delete rr; + rr = NULL; + } + else + rr->result = ipbuf; + break; + } + case DNS_QUERY_CNAME: + case DNS_QUERY_PTR: + { + name.clear(); + packet_pos_ptr = packet_pos; + for (unsigned offset = recv_packet.payload[packet_pos_ptr]; offset; packet_pos_ptr += offset + 1, offset = recv_packet.payload[packet_pos_ptr]) + { + if (offset & 0xC0) + { + offset = (offset & 0x3F) << 8 | recv_packet.payload[++packet_pos_ptr]; + packet_pos_ptr = offset - 12; + offset = recv_packet.payload[packet_pos_ptr]; + } + for (unsigned i = 1; i <= offset; ++i) + name += recv_packet.payload[packet_pos_ptr + i]; + name += "."; + } + name.erase(name.begin() + name.length() - 1); + + if (packet_pos_ptr < packet_pos) + packet_pos += 2; + else + packet_pos = packet_pos_ptr + 1; + + rr->result = name; + break; + } + default: + delete rr; + request->OnError(DNS_ERROR_INVALID_TYPE, "Invalid query type"); + rr = NULL; + } + + if (rr && request->QT != rr->type) + { + delete rr; + rr = NULL; + } + + if (rr) + { + request->OnLookupComplete(rr); + DNSEngine->AddCache(rr); + } + } + } + + delete request; + return true; +} + +bool DNSSocket::ProcessWrite() +{ + Log(LOG_DEBUG_2) << "Resolver: Writing to UDP socket"; + + bool cont = true; + for (unsigned i = DNSEngine->packets.size(); i > 0; --i) + { + DNSPacket *r = DNSEngine->packets[i - 1]; + + unsigned char buffer[524]; + r->FillBuffer(buffer); + + cont = this->SendTo(buffer, r->payload_count + 12) >= 0; + + if (!cont) + break; + + delete r; + DNSEngine->packets.erase(DNSEngine->packets.begin() + i - 1); + } + SocketEngine->ClearWritable(this); + return cont; +} + +DNSManager::DNSManager() : Timer(3600, time(NULL), true) +{ + this->sock = NULL; + + try + { + this->sock = new DNSSocket(Config->NameServer, DNSManager::DNSPort); + } + catch (const SocketException &ex) + { + throw SocketException("Unable to connect to nameserver: " + ex.GetReason()); + } +} + +DNSManager::~DNSManager() +{ + delete this->sock; +} + +void DNSManager::AddCache(DNSRecord *rr) +{ + this->cache.insert(std::make_pair(rr->name, rr)); +} + +bool DNSManager::CheckCache(DNSRequest *request) +{ + std::multimap<Anope::string, DNSRecord *>::iterator it = this->cache.find(request->address); + if (it != this->cache.end()) + { + std::multimap<Anope::string, DNSRecord *>::iterator it_end = this->cache.upper_bound(request->address); + + time_t now = time(NULL); + for (; it != it_end; ++it) + { + DNSRecord *rec = it->second; + if (rec->created + rec->ttl >= now) + { + request->OnLookupComplete(rec); + } + } + + return true; + } + + return false; +} + +void DNSManager::Tick(time_t now) +{ + for (std::multimap<Anope::string, DNSRecord *>::iterator it = this->cache.begin(), it_end = this->cache.end(); it != it_end; ++it) + { + DNSRecord *req = it->second; + + if (req->created + req->ttl < now) + { + delete req; + this->cache.erase(it); + } + } +} + +DNSRequestTimeout::DNSRequestTimeout(DNSRequest *r, time_t timeout) : Timer(timeout), request(r) +{ + this->done = false; +} + +DNSRequestTimeout::~DNSRequestTimeout() +{ +} + +void DNSRequestTimeout::Tick(time_t) +{ + this->done = true; + this->request->OnError(DNS_ERROR_TIMEOUT, "Request timed out"); + delete this->request; +} + |