1 /* +------------------------------------+
2 * | Inspire Internet Relay Chat Daemon |
3 * +------------------------------------+
5 * InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev.
7 * <brain@chatspike.net>
8 * <Craig@chatspike.net>
10 * Written by Craig Edwards, Craig McLure, and others.
11 * This program is free but copyrighted software; see
12 * the file COPYING for details.
14 * ---------------------------------------------------
18 dns.cpp - Nonblocking DNS functions.
19 Very loosely based on the firedns library,
20 Copyright (C) 2002 Ian Gulliver.
22 There have been so many modifications to this file
23 to make it fit into InspIRCd and make it object
24 orientated that you should not take this code as
25 being what firedns really looks like. It used to
26 look very different to this! :-P
34 #include <sys/types.h>
35 #include <sys/socket.h>
43 #include <sys/types.h>
44 #include <sys/socket.h>
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
51 #include "helperfuncs.h"
52 #include "inspircd_config.h"
53 #include "socketengine.h"
54 #include "configreader.h"
57 pthread_mutex_t connmap_lock = PTHREAD_MUTEX_INITIALIZER;
60 extern InspIRCd* ServerInstance;
61 extern ServerConfig* Config;
63 extern userrec* fd_ref_table[MAX_DESCRIPTORS];
65 enum QueryType { DNS_QRY_A = 1, DNS_QRY_PTR = 12 };
66 enum QueryFlags1 { FLAGS1_MASK_RD = 0x01, FLAGS1_MASK_TC = 0x02, FLAGS1_MASK_AA = 0x04, FLAGS1_MASK_OPCODE = 0x78, FLAGS1_MASK_QR = 0x80 };
67 enum QueryFlags2 { FLAGS2_MASK_RCODE = 0x0F, FLAGS2_MASK_Z = 0x70, FLAGS2_MASK_RA = 0x80 };
71 typedef std::map<int,s_connection*> connlist;
72 typedef connlist::iterator connlist_iter;
75 Resolver* dns_classes[MAX_DESCRIPTORS];
77 insp_inaddr servers[8];
98 unsigned int rdlength;
107 unsigned int qdcount;
108 unsigned int ancount;
109 unsigned int nscount;
110 unsigned int arcount;
111 unsigned char payload[512];
115 void *dns_align(void *inp)
117 char *p = (char*)inp;
118 int offby = ((char *)p - (char *)0) % (sizeof(void *) > sizeof(long) ? sizeof(void *) : sizeof(long));
120 return p + ((sizeof(void *) > sizeof(long) ? sizeof(void *) : sizeof(long)) - offby);
126 * Optimized by brain, these were using integer division and modulus.
127 * We can use logic shifts and logic AND to replace these even divisions
128 * and multiplications, it should be a bit faster (probably not noticably,
129 * but of course, more impressive). Also made these inline.
132 inline void dns_fill_rr(s_rr_middle* rr, const unsigned char *input)
134 rr->type = (QueryType)((input[0] << 8) + input[1]);
135 rr->_class = (input[2] << 8) + input[3];
136 rr->ttl = (input[4] << 24) + (input[5] << 16) + (input[6] << 8) + input[7];
137 rr->rdlength = (input[8] << 8) + input[9];
140 inline void dns_fill_header(s_header *header, const unsigned char *input, const int l)
142 header->id[0] = input[0];
143 header->id[1] = input[1];
144 header->flags1 = input[2];
145 header->flags2 = input[3];
146 header->qdcount = (input[4] << 8) + input[5];
147 header->ancount = (input[6] << 8) + input[7];
148 header->nscount = (input[8] << 8) + input[9];
149 header->arcount = (input[10] << 8) + input[11];
150 memcpy(header->payload,&input[12],l);
153 inline void dns_empty_header(unsigned char *output, const s_header *header, const int l)
155 output[0] = header->id[0];
156 output[1] = header->id[1];
157 output[2] = header->flags1;
158 output[3] = header->flags2;
159 output[4] = header->qdcount >> 8;
160 output[5] = header->qdcount & 0xFF;
161 output[6] = header->ancount >> 8;
162 output[7] = header->ancount & 0xFF;
163 output[8] = header->nscount >> 8;
164 output[9] = header->nscount & 0xFF;
165 output[10] = header->arcount >> 8;
166 output[11] = header->arcount & 0xFF;
167 memcpy(&output[12],header->payload,l);
170 void dns_close(int fd)
173 if (ServerInstance && ServerInstance->SE)
174 ServerInstance->SE->DelFd(fd);
176 log(DEBUG,"DNS: dns_close on fd %d",fd);
193 srand((unsigned int) TIME);
194 memset(servers,'\0',sizeof(insp_inaddr) * 8);
195 f = fopen("/etc/resolv.conf","r");
198 while (fgets(buf,1024,f) != NULL) {
199 if (strncmp(buf,"nameserver",10) == 0)
202 while (buf[i] == ' ' || buf[i] == '\t')
206 if (insp_aton(&buf[i],&addr) == 0)
207 memcpy(&servers[i4++],&addr,sizeof(insp_inaddr));
214 void DNS::dns_init_2(const char* dnsserver)
218 srand((unsigned int) TIME);
219 memset(servers,'\0',sizeof(insp_inaddr) * 8);
220 if (insp_aton(dnsserver,&addr) == 0)
221 memcpy(&servers[i4++],&addr,sizeof(insp_inaddr));
225 int dns_send_requests(const s_header *h, const s_connection *s, const int l)
229 unsigned char payload[sizeof(s_header)];
231 dns_empty_header(payload,h,l);
236 /* otherwise send via standard ipv4 boringness */
237 memset(&addr,0,sizeof(addr));
239 memcpy(&addr.sin6_addr,&servers[i],sizeof(addr.sin6_addr));
240 addr.sin6_family = AF_FAMILY;
241 addr.sin6_port = htons(53);
243 memcpy(&addr.sin_addr.s_addr,&servers[i],sizeof(addr.sin_addr));
244 addr.sin_family = AF_FAMILY;
245 addr.sin_port = htons(53);
247 if (sendto(s->fd, payload, l + 12, 0, (sockaddr *) &addr, sizeof(addr)) == -1)
249 log(DEBUG,"Error in sendto!");
256 s_connection *dns_add_query(s_header *h)
259 s_connection * s = new s_connection;
260 int id = rand() % 65536;
262 /* set header flags */
263 h->id[0] = s->id[0] = id >> 8; /* verified by dns_getresult_s() */
264 h->id[1] = s->id[1] = id & 0xFF;
265 h->flags1 = 0 | FLAGS1_MASK_RD;
272 s->fd = socket(PF_PROTOCOL, SOCK_DGRAM, 0);
275 log(DEBUG,"Set query socket nonblock");
276 if (fcntl(s->fd, F_SETFL, O_NONBLOCK) != 0)
287 memset(&addr,0,sizeof(addr));
288 addr.sin6_family = AF_FAMILY;
290 memset(&addr.sin6_addr,255,sizeof(in6_addr));
293 memset(&addr,0,sizeof(addr));
294 addr.sin_family = AF_FAMILY;
296 addr.sin_addr.s_addr = INADDR_ANY;
297 if (bind(s->fd,(sockaddr *)&addr,sizeof(addr)) != 0)
299 log(DEBUG,"Cant bind with source port = 0");
311 /* create new connection object, add to linked list */
313 pthread_mutex_lock(&connmap_lock);
315 if (connections.find(s->fd) == connections.end())
316 connections[s->fd] = s;
318 pthread_mutex_unlock(&connmap_lock);
324 int dns_build_query_payload(const char * const name, const unsigned short rr, const unsigned short _class, unsigned char * const payload)
327 const char * tempchr, * tempchr2;
333 /* split name up into labels, create query */
334 while ((tempchr = strchr(tempchr2,'.')) != NULL)
336 l = tempchr - tempchr2;
337 if (payloadpos + l + 1 > 507)
339 payload[payloadpos++] = l;
340 memcpy(&payload[payloadpos],tempchr2,l);
342 tempchr2 = &tempchr[1];
344 l = strlen(tempchr2);
347 if (payloadpos + l + 2 > 507)
349 payload[payloadpos++] = l;
350 memcpy(&payload[payloadpos],tempchr2,l);
352 payload[payloadpos++] = '\0';
354 if (payloadpos > 508)
357 memcpy(&payload[payloadpos],&l,2);
359 memcpy(&payload[payloadpos + 2],&l,2);
360 return payloadpos + 4;
363 int DNS::dns_getip4(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
371 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
374 s = dns_add_query(&h);
379 if (dns_send_requests(&h,s,l) == -1)
385 int DNS::dns_getip4list(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
392 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
395 s = dns_add_query(&h);
401 if (dns_send_requests(&h,s,l) == -1)
407 int DNS::dns_getname4(const insp_inaddr *ip)
408 { /* build, add and send PTR query; retrieve result with dns_getresult() */
412 log(DEBUG,"DNS::dns_getname4");
419 c = (unsigned char *)&ip->s_addr;
421 sprintf(query,"%d.%d.%d.%d.in-addr.arpa",c[3],c[2],c[1],c[0]);
423 l = dns_build_query_payload(query,DNS_QRY_PTR,1,(unsigned char *)&h.payload);
426 s = dns_add_query(&h);
430 s->type = DNS_QRY_PTR;
431 if (dns_send_requests(&h,s,l) == -1)
438 char* DNS::dns_getresult(const int cfd) { /* retrieve result of DNS query */
439 log(DEBUG,"DNS: dns_getresult with cfd=%d",cfd);
440 return dns_getresult_s(cfd,this->localbuf);
443 char* DNS::dns_getresult_s(const int cfd, char *res) { /* retrieve result of DNS query (buffered) */
446 int l, i, q, curanswer, o;
448 unsigned char buffer[sizeof(s_header)];
454 /* FireDNS used a linked list for this. How ugly (and slow). */
457 /* XXX: STL really does NOT like being poked and prodded in more than
458 * one orifice by threaded apps. Make sure we remain nice to it, and
459 * lock a mutex around any access to the std::map.
461 pthread_mutex_lock(&connmap_lock);
463 connlist_iter n_iter = connections.find(cfd);
464 if (n_iter == connections.end())
466 log(DEBUG,"DNS: got a response for a query we didnt send with fd=%d",cfd);
468 pthread_mutex_unlock(&connmap_lock);
474 /* Remove the query from the list */
475 c = (s_connection*)n_iter->second;
476 /* We don't delete c here, because its done later when needed */
477 connections.erase(n_iter);
480 pthread_mutex_unlock(&connmap_lock);
483 l = recv(c->fd,buffer,sizeof(s_header),0);
490 dns_fill_header(&h,buffer,l - 12);
491 if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
493 log(DEBUG,"DNS: id mismatch on query");
495 return NULL; /* ID mismatch */
497 if ((h.flags1 & FLAGS1_MASK_QR) == 0)
499 log(DEBUG,"DNS: didnt get a query result");
503 if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
505 log(DEBUG,"DNS: got an OPCODE and didnt want one");
509 if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
511 log(DEBUG,"DNS lookup failed due to SERVFAIL");
517 log(DEBUG,"DNS: no answers!");
524 while ((unsigned)q < h.qdcount && i < l)
526 if (h.payload[i] > 63)
533 if (h.payload[i] == 0)
538 else i += h.payload[i] + 1;
542 while ((unsigned)curanswer < h.ancount)
545 while (q == 0 && i < l)
547 if (h.payload[i] > 63)
554 if (h.payload[i] == 0)
559 else i += h.payload[i] + 1; /* skip length and label */
567 dns_fill_rr(&rr,&h.payload[i]);
569 if (rr.type != c->type)
575 if (rr._class != c->_class)
583 if ((unsigned)curanswer == h.ancount)
585 if ((unsigned)i + rr.rdlength > (unsigned)l)
587 if (rr.rdlength > 1023)
593 log(DEBUG,"DNS: got a result of type DNS_QRY_PTR");
596 while (q == 0 && i < l && o + 256 < 1023)
598 if (h.payload[i] > 63)
600 log(DEBUG,"DNS: h.payload[i] > 63");
601 memcpy(&p,&h.payload[i],2);
602 i = ntohs(p) - 0xC000 - 12;
606 if (h.payload[i] == 0)
615 memcpy(&res[o],&h.payload[i + 1],h.payload[i]);
617 i += h.payload[i] + 1;
624 log(DEBUG,"DNS: got a result of type DNS_QRY_A");
627 dns_ip4list *alist = (dns_ip4list *) res; /* we have to trust that this is aligned */
628 while ((char *)alist - (char *)res < 700)
630 if (rr.type != DNS_QRY_A)
634 if (rr.rdlength != 4)
639 memcpy(&alist->ip,&h.payload[i],4);
640 if ((unsigned)++curanswer >= h.ancount)
644 while (q == 0 && i < l)
646 if (h.payload[i] > 63)
653 if (h.payload[i] == 0)
658 else i += h.payload[i] + 1;
666 dns_fill_rr(&rr,&h.payload[i]);
668 alist->next = (dns_ip4list *) dns_align(((char *) alist) + sizeof(dns_ip4list));
675 memcpy(res,&h.payload[i],rr.rdlength);
676 res[rr.rdlength] = '\0';
679 memcpy(res,&h.payload[i],rr.rdlength);
680 res[rr.rdlength] = '\0';
690 log(DEBUG,"Create blank DNS");
693 DNS::DNS(const std::string &dnsserver)
695 dns_init_2(dnsserver.c_str());
696 log(DEBUG,"Create DNS with server '%s'",dnsserver.c_str());
699 void DNS::SetNS(const std::string &dnsserver)
701 dns_init_2(dnsserver.c_str());
709 bool DNS::ReverseLookup(const std::string &ip, bool ins)
711 if (ServerInstance && ServerInstance->stats)
712 ServerInstance->stats->statsDns++;
714 if (insp_aton(ip.c_str(), &binip) < 0)
719 this->myfd = dns_getname4(&binip);
720 if (this->myfd == -1)
724 log(DEBUG,"DNS: ReverseLookup, fd=%d",this->myfd);
728 if (ServerInstance && ServerInstance->SE)
729 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
735 bool DNS::ForwardLookup(const std::string &host, bool ins)
737 if (ServerInstance && ServerInstance->stats)
738 ServerInstance->stats->statsDns++;
739 this->myfd = dns_getip4(host.c_str());
740 if (this->myfd == -1)
744 log(DEBUG,"DNS: ForwardLookup, fd=%d",this->myfd);
748 if (ServerInstance && ServerInstance->SE)
749 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
755 bool DNS::ForwardLookupWithFD(const std::string &host, int &fd)
757 if (ServerInstance && ServerInstance->stats)
758 ServerInstance->stats->statsDns++;
759 this->myfd = dns_getip4(host.c_str());
761 if (this->myfd == -1)
765 log(DEBUG,"DNS: ForwardLookupWithFD, fd=%d",this->myfd);
766 if (ServerInstance && ServerInstance->SE)
767 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_MODULE);
771 bool DNS::HasResult(int fd)
773 return (fd == this->myfd);
776 /* Only the multithreaded dns uses this poll() based
777 * check now. As its in another thread we dont have
778 * to worry about its performance that much.
780 bool DNS::HasResult()
782 log(DEBUG,"DNS: HasResult, fd=%d",this->myfd);
784 polls.fd = this->myfd;
785 polls.events = POLLIN;
786 int ret = poll(&polls,1,1);
787 log(DEBUG,"DNS: Hasresult returning %d",ret);
796 std::string DNS::GetResult()
798 log(DEBUG,"DNS: GetResult()");
799 result = dns_getresult(this->myfd);
802 if (ServerInstance && ServerInstance->stats)
803 ServerInstance->stats->statsDnsGood++;
804 dns_close(this->myfd);
810 if (ServerInstance && ServerInstance->stats)
811 ServerInstance->stats->statsDnsBad++;
812 if (this->myfd != -1)
814 dns_close(this->myfd);
821 std::string DNS::GetResultIP()
824 log(DEBUG,"DNS: GetResultIP()");
825 result = dns_getresult(this->myfd);
826 if (this->myfd != -1)
828 dns_close(this->myfd);
833 if (ServerInstance && ServerInstance->stats)
834 ServerInstance->stats->statsDnsGood++;
835 unsigned char a = (unsigned)result[0];
836 unsigned char b = (unsigned)result[1];
837 unsigned char c = (unsigned)result[2];
838 unsigned char d = (unsigned)result[3];
839 snprintf(r,1024,"%u.%u.%u.%u",a,b,c,d);
844 if (ServerInstance && ServerInstance->stats)
845 ServerInstance->stats->statsDnsBad++;
846 log(DEBUG,"DANGER WILL ROBINSON! NXDOMAIN for forward lookup, but we got a reverse lookup!");
855 /* This function is a thread function which can be thought of as a lightweight process
856 * to all you non-threaded people. In actuality its so much more, and pretty damn cool.
857 * With threaded dns enabled, each user which connects gets a thread attached to their
858 * user record when their DNS lookup starts. This function starts in parallel, and
859 * commences a blocking dns lookup. Because its a seperate thread, this occurs without
860 * actually blocking the main application. Once the dns lookup is completed, the thread
861 * checks if the user is still around by checking their fd against the reference table,
862 * and if they are, writes the hostname into the struct and terminates, after setting
863 * userrec::dns_done to true. Because this is multi-threaded it can make proper use of
864 * SMP setups (like the one i have here *grin*).
865 * This is in comparison to the non-threaded dns, which must monitor the thread sockets
866 * in a nonblocking fashion, consuming more resources to do so.
868 * NB: Yes this does scale, thank you. Even with large numbers of connecting clients
869 * in any one timeframe, they wont all connect *at the same time* therefore any argument
870 * of "but there will be thousands of threads it'll blow up" is moot, ive tested this and
871 * there will only ever be somewhere around the listen backlog in number of pending
872 * lookups at any one time. This is significant on any modern SMP system.
874 void* dns_task(void* arg)
876 userrec* u = (userrec*)arg;
879 log(DEBUG,"DNS thread for user %s on ip %s",u->nick,insp_ntoa(u->ip4));
880 DNS dns1(Config->DNSServer);
881 DNS dns2(Config->DNSServer);
886 if (dns1.ReverseLookup(insp_ntoa(u->ip4),false))
888 /* FIX: Dont make these infinite! */
889 while ((!dns1.HasResult()) && (++iterations < 20))
894 if (dns1.GetFD() != -1)
896 host = dns1.GetResult();
899 if (dns2.ForwardLookup(host, false))
902 while ((!dns2.HasResult()) && (++iterations < 20))
907 if (dns2.GetFD() != -1)
909 ip = dns2.GetResultIP();
910 if (ip == std::string(insp_ntoa(u->ip4)))
912 if (host.length() < 65)
914 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
918 strcpy(u->host,host.c_str());
919 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
921 strcpy(u->dhost,host.c_str());
934 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
936 log(DEBUG,"THREAD EXIT");
941 Resolver::Resolver(const std::string &source, bool forward, const std::string &dnsserver = "") : input(source), fwd(forward), server(dnsserver)
943 if (this->server != "")
944 Query.SetNS(this->server);
946 Query.SetNS(Config->DNSServer);
950 Query.ForwardLookup(input.c_str(), false);
951 this->fd = Query.GetFD();
955 Query.ReverseLookup(input.c_str(), false);
956 this->fd = Query.GetFD();
960 log(DEBUG,"Resolver::Resolver: RESOLVER_NSDOWN");
961 this->OnError(RESOLVER_NSDOWN);
962 ModuleException e("Resolver: Nameserver is down");
964 /* We shouldnt get here really */
968 if (ServerInstance && ServerInstance->SE)
970 log(DEBUG,"Resolver::Resolver: this->fd=%d",this->fd);
971 ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_CLASSDNS);
975 log(DEBUG,"Resolver::Resolver: RESOLVER_NOTREADY");
976 this->OnError(RESOLVER_NOTREADY);
977 ModuleException e("Resolver: Core not initialized yet");
979 /* We shouldnt get here really */
984 Resolver::~Resolver()
986 log(DEBUG,"Resolver::~Resolver");
987 if (ServerInstance && ServerInstance->SE)
988 ServerInstance->SE->DelFd(this->fd);
991 int Resolver::GetFd()
996 bool Resolver::ProcessResult()
998 log(DEBUG,"Resolver::ProcessResult");
1000 result = Query.GetResultIP();
1002 result = Query.GetResult();
1006 log(DEBUG,"Resolver::OnLookupComplete(%s)",result.c_str());
1007 this->OnLookupComplete(result);
1012 log(DEBUG,"Resolver::OnError(RESOLVER_NXDOMAIN)");
1013 this->OnError(RESOLVER_NXDOMAIN);
1018 void Resolver::OnLookupComplete(const std::string &result)
1022 void Resolver::OnError(ResolverError e)
1026 void dns_deal_with_classes(int fd)
1028 log(DEBUG,"dns_deal_with_classes(%d)",fd);
1029 if ((fd > -1) && (dns_classes[fd]))
1031 log(DEBUG,"Valid fd %d",fd);
1032 dns_classes[fd]->ProcessResult();
1033 delete dns_classes[fd];
1034 dns_classes[fd] = NULL;
1038 bool dns_add_class(Resolver* r)
1040 log(DEBUG,"dns_add_class");
1041 if ((r) && (r->GetFd() > -1))
1043 if (!dns_classes[r->GetFd()])
1045 log(DEBUG,"dns_add_class: added class");
1046 dns_classes[r->GetFd()] = r;
1051 log(DEBUG,"Space occupied!");
1057 log(DEBUG,"Bad class");
1065 memset(dns_classes,0,sizeof(dns_classes));