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 (dns_aton4_s(&buf[i],&addr) != NULL)
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 (dns_aton4_s(dnsserver,&addr) != NULL)
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,&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)
255 s_connection *dns_add_query(s_header *h)
258 s_connection * s = new s_connection;
259 int id = rand() % 65536;
261 /* set header flags */
262 h->id[0] = s->id[0] = id >> 8; /* verified by dns_getresult_s() */
263 h->id[1] = s->id[1] = id & 0xFF;
264 h->flags1 = 0 | FLAGS1_MASK_RD;
271 s->fd = socket(PF_INET, SOCK_DGRAM, 0);
274 if (fcntl(s->fd, F_SETFL, O_NONBLOCK) != 0)
284 memset(&addr,0,sizeof(addr));
286 addr.sin6_family = AF_FAMILY;
288 memset(&addr.sin6_addr,255,sizeof(in6_addr));
290 addr.sin_family = AF_FAMILY;
292 addr.sin_addr.s_addr = INADDR_ANY;
294 if (bind(s->fd,(sockaddr *)&addr,sizeof(addr)) != 0)
306 /* create new connection object, add to linked list */
308 pthread_mutex_lock(&connmap_lock);
310 if (connections.find(s->fd) == connections.end())
311 connections[s->fd] = s;
313 pthread_mutex_unlock(&connmap_lock);
319 int dns_build_query_payload(const char * const name, const unsigned short rr, const unsigned short _class, unsigned char * const payload)
322 const char * tempchr, * tempchr2;
328 /* split name up into labels, create query */
329 while ((tempchr = strchr(tempchr2,'.')) != NULL)
331 l = tempchr - tempchr2;
332 if (payloadpos + l + 1 > 507)
334 payload[payloadpos++] = l;
335 memcpy(&payload[payloadpos],tempchr2,l);
337 tempchr2 = &tempchr[1];
339 l = strlen(tempchr2);
342 if (payloadpos + l + 2 > 507)
344 payload[payloadpos++] = l;
345 memcpy(&payload[payloadpos],tempchr2,l);
347 payload[payloadpos++] = '\0';
349 if (payloadpos > 508)
352 memcpy(&payload[payloadpos],&l,2);
354 memcpy(&payload[payloadpos + 2],&l,2);
355 return payloadpos + 4;
358 insp_inaddr* DNS::dns_aton4(const char * const ipstring)
360 static insp_inaddr ip;
361 return dns_aton4_s(ipstring,&ip);
364 insp_inaddr* DNS::dns_aton4_r(const char *ipstring) { /* ascii to numeric (reentrant): convert string to new 4part IP addr struct */
366 ip = new insp_inaddr;
367 if(dns_aton4_s(ipstring,ip) == NULL)
375 insp_inaddr* DNS::dns_aton4_s(const char *ipstring, insp_inaddr *ip) { /* ascii to numeric (buffered): convert string to given 4part IP addr struct */
376 insp_aton(ipstring,ip);
380 int DNS::dns_getip4(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
388 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
391 s = dns_add_query(&h);
396 if (dns_send_requests(&h,s,l) == -1)
402 int DNS::dns_getip4list(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
409 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
412 s = dns_add_query(&h);
418 if (dns_send_requests(&h,s,l) == -1)
424 int DNS::dns_getname4(const insp_inaddr *ip)
425 { /* build, add and send PTR query; retrieve result with dns_getresult() */
435 c = (unsigned char *)&ip->s_addr;
437 sprintf(query,"%d.%d.%d.%d.in-addr.arpa",c[3],c[2],c[1],c[0]);
439 l = dns_build_query_payload(query,DNS_QRY_PTR,1,(unsigned char *)&h.payload);
442 s = dns_add_query(&h);
446 s->type = DNS_QRY_PTR;
447 if (dns_send_requests(&h,s,l) == -1)
454 char* DNS::dns_getresult(const int cfd) { /* retrieve result of DNS query */
455 log(DEBUG,"DNS: dns_getresult with cfd=%d",cfd);
456 return dns_getresult_s(cfd,this->localbuf);
459 char* DNS::dns_getresult_s(const int cfd, char *res) { /* retrieve result of DNS query (buffered) */
462 int l, i, q, curanswer, o;
464 unsigned char buffer[sizeof(s_header)];
470 /* FireDNS used a linked list for this. How ugly (and slow). */
473 /* XXX: STL really does NOT like being poked and prodded in more than
474 * one orifice by threaded apps. Make sure we remain nice to it, and
475 * lock a mutex around any access to the std::map.
477 pthread_mutex_lock(&connmap_lock);
479 connlist_iter n_iter = connections.find(cfd);
480 if (n_iter == connections.end())
482 log(DEBUG,"DNS: got a response for a query we didnt send with fd=%d",cfd);
484 pthread_mutex_unlock(&connmap_lock);
490 /* Remove the query from the list */
491 c = (s_connection*)n_iter->second;
492 /* We don't delete c here, because its done later when needed */
493 connections.erase(n_iter);
496 pthread_mutex_unlock(&connmap_lock);
499 l = recv(c->fd,buffer,sizeof(s_header),0);
506 dns_fill_header(&h,buffer,l - 12);
507 if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
509 log(DEBUG,"DNS: id mismatch on query");
511 return NULL; /* ID mismatch */
513 if ((h.flags1 & FLAGS1_MASK_QR) == 0)
515 log(DEBUG,"DNS: didnt get a query result");
519 if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
521 log(DEBUG,"DNS: got an OPCODE and didnt want one");
525 if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
527 log(DEBUG,"DNS lookup failed due to SERVFAIL");
533 log(DEBUG,"DNS: no answers!");
540 while ((unsigned)q < h.qdcount && i < l)
542 if (h.payload[i] > 63)
549 if (h.payload[i] == 0)
554 else i += h.payload[i] + 1;
558 while ((unsigned)curanswer < h.ancount)
561 while (q == 0 && i < l)
563 if (h.payload[i] > 63)
570 if (h.payload[i] == 0)
575 else i += h.payload[i] + 1; /* skip length and label */
583 dns_fill_rr(&rr,&h.payload[i]);
585 if (rr.type != c->type)
591 if (rr._class != c->_class)
599 if ((unsigned)curanswer == h.ancount)
601 if ((unsigned)i + rr.rdlength > (unsigned)l)
603 if (rr.rdlength > 1023)
609 log(DEBUG,"DNS: got a result of type DNS_QRY_PTR");
612 while (q == 0 && i < l && o + 256 < 1023)
614 if (h.payload[i] > 63)
616 log(DEBUG,"DNS: h.payload[i] > 63");
617 memcpy(&p,&h.payload[i],2);
618 i = ntohs(p) - 0xC000 - 12;
622 if (h.payload[i] == 0)
631 memcpy(&res[o],&h.payload[i + 1],h.payload[i]);
633 i += h.payload[i] + 1;
640 log(DEBUG,"DNS: got a result of type DNS_QRY_A");
643 dns_ip4list *alist = (dns_ip4list *) res; /* we have to trust that this is aligned */
644 while ((char *)alist - (char *)res < 700)
646 if (rr.type != DNS_QRY_A)
650 if (rr.rdlength != 4)
655 memcpy(&alist->ip,&h.payload[i],4);
656 if ((unsigned)++curanswer >= h.ancount)
660 while (q == 0 && i < l)
662 if (h.payload[i] > 63)
669 if (h.payload[i] == 0)
674 else i += h.payload[i] + 1;
682 dns_fill_rr(&rr,&h.payload[i]);
684 alist->next = (dns_ip4list *) dns_align(((char *) alist) + sizeof(dns_ip4list));
691 memcpy(res,&h.payload[i],rr.rdlength);
692 res[rr.rdlength] = '\0';
695 memcpy(res,&h.payload[i],rr.rdlength);
696 res[rr.rdlength] = '\0';
706 log(DEBUG,"Create blank DNS");
709 DNS::DNS(const std::string &dnsserver)
711 dns_init_2(dnsserver.c_str());
712 log(DEBUG,"Create DNS with server '%s'",dnsserver.c_str());
715 void DNS::SetNS(const std::string &dnsserver)
717 dns_init_2(dnsserver.c_str());
725 bool DNS::ReverseLookup(const std::string &ip, bool ins)
727 if (ServerInstance && ServerInstance->stats)
728 ServerInstance->stats->statsDns++;
729 binip = dns_aton4(ip.c_str());
735 this->myfd = dns_getname4(binip);
736 if (this->myfd == -1)
740 log(DEBUG,"DNS: ReverseLookup, fd=%d",this->myfd);
744 if (ServerInstance && ServerInstance->SE)
745 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
751 bool DNS::ForwardLookup(const std::string &host, bool ins)
753 if (ServerInstance && ServerInstance->stats)
754 ServerInstance->stats->statsDns++;
755 this->myfd = dns_getip4(host.c_str());
756 if (this->myfd == -1)
760 log(DEBUG,"DNS: ForwardLookup, fd=%d",this->myfd);
764 if (ServerInstance && ServerInstance->SE)
765 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
771 bool DNS::ForwardLookupWithFD(const std::string &host, int &fd)
773 if (ServerInstance && ServerInstance->stats)
774 ServerInstance->stats->statsDns++;
775 this->myfd = dns_getip4(host.c_str());
777 if (this->myfd == -1)
781 log(DEBUG,"DNS: ForwardLookupWithFD, fd=%d",this->myfd);
782 if (ServerInstance && ServerInstance->SE)
783 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_MODULE);
787 bool DNS::HasResult(int fd)
789 return (fd == this->myfd);
792 /* Only the multithreaded dns uses this poll() based
793 * check now. As its in another thread we dont have
794 * to worry about its performance that much.
796 bool DNS::HasResult()
798 log(DEBUG,"DNS: HasResult, fd=%d",this->myfd);
800 polls.fd = this->myfd;
801 polls.events = POLLIN;
802 int ret = poll(&polls,1,1);
803 log(DEBUG,"DNS: Hasresult returning %d",ret);
812 std::string DNS::GetResult()
814 log(DEBUG,"DNS: GetResult()");
815 result = dns_getresult(this->myfd);
818 if (ServerInstance && ServerInstance->stats)
819 ServerInstance->stats->statsDnsGood++;
820 dns_close(this->myfd);
826 if (ServerInstance && ServerInstance->stats)
827 ServerInstance->stats->statsDnsBad++;
828 if (this->myfd != -1)
830 dns_close(this->myfd);
837 std::string DNS::GetResultIP()
840 log(DEBUG,"DNS: GetResultIP()");
841 result = dns_getresult(this->myfd);
842 if (this->myfd != -1)
844 dns_close(this->myfd);
849 if (ServerInstance && ServerInstance->stats)
850 ServerInstance->stats->statsDnsGood++;
851 unsigned char a = (unsigned)result[0];
852 unsigned char b = (unsigned)result[1];
853 unsigned char c = (unsigned)result[2];
854 unsigned char d = (unsigned)result[3];
855 snprintf(r,1024,"%u.%u.%u.%u",a,b,c,d);
860 if (ServerInstance && ServerInstance->stats)
861 ServerInstance->stats->statsDnsBad++;
862 log(DEBUG,"DANGER WILL ROBINSON! NXDOMAIN for forward lookup, but we got a reverse lookup!");
871 /* This function is a thread function which can be thought of as a lightweight process
872 * to all you non-threaded people. In actuality its so much more, and pretty damn cool.
873 * With threaded dns enabled, each user which connects gets a thread attached to their
874 * user record when their DNS lookup starts. This function starts in parallel, and
875 * commences a blocking dns lookup. Because its a seperate thread, this occurs without
876 * actually blocking the main application. Once the dns lookup is completed, the thread
877 * checks if the user is still around by checking their fd against the reference table,
878 * and if they are, writes the hostname into the struct and terminates, after setting
879 * userrec::dns_done to true. Because this is multi-threaded it can make proper use of
880 * SMP setups (like the one i have here *grin*).
881 * This is in comparison to the non-threaded dns, which must monitor the thread sockets
882 * in a nonblocking fashion, consuming more resources to do so.
884 * NB: Yes this does scale, thank you. Even with large numbers of connecting clients
885 * in any one timeframe, they wont all connect *at the same time* therefore any argument
886 * of "but there will be thousands of threads it'll blow up" is moot, ive tested this and
887 * there will only ever be somewhere around the listen backlog in number of pending
888 * lookups at any one time. This is significant on any modern SMP system.
890 void* dns_task(void* arg)
892 userrec* u = (userrec*)arg;
895 log(DEBUG,"DNS thread for user %s",u->nick);
896 DNS dns1(Config->DNSServer);
897 DNS dns2(Config->DNSServer);
902 if (dns1.ReverseLookup(insp_ntoa(u->ip4),false))
904 /* FIX: Dont make these infinite! */
905 while ((!dns1.HasResult()) && (++iterations < 20))
910 if (dns1.GetFD() != -1)
912 host = dns1.GetResult();
915 if (dns2.ForwardLookup(host, false))
918 while ((!dns2.HasResult()) && (++iterations < 20))
923 if (dns2.GetFD() != -1)
925 ip = dns2.GetResultIP();
926 if (ip == std::string(insp_ntoa(u->ip4)))
928 if (host.length() < 65)
930 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
934 strcpy(u->host,host.c_str());
935 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
937 strcpy(u->dhost,host.c_str());
950 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
952 log(DEBUG,"THREAD EXIT");
957 Resolver::Resolver(const std::string &source, bool forward, const std::string &dnsserver = "") : input(source), fwd(forward), server(dnsserver)
959 if (this->server != "")
960 Query.SetNS(this->server);
962 Query.SetNS(Config->DNSServer);
966 Query.ForwardLookup(input.c_str(), false);
967 this->fd = Query.GetFD();
971 Query.ReverseLookup(input.c_str(), false);
972 this->fd = Query.GetFD();
976 log(DEBUG,"Resolver::Resolver: RESOLVER_NSDOWN");
977 this->OnError(RESOLVER_NSDOWN);
978 ModuleException e("Resolver: Nameserver is down");
980 /* We shouldnt get here really */
984 if (ServerInstance && ServerInstance->SE)
986 log(DEBUG,"Resolver::Resolver: this->fd=%d",this->fd);
987 ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_CLASSDNS);
991 log(DEBUG,"Resolver::Resolver: RESOLVER_NOTREADY");
992 this->OnError(RESOLVER_NOTREADY);
993 ModuleException e("Resolver: Core not initialized yet");
995 /* We shouldnt get here really */
1000 Resolver::~Resolver()
1002 log(DEBUG,"Resolver::~Resolver");
1003 if (ServerInstance && ServerInstance->SE)
1004 ServerInstance->SE->DelFd(this->fd);
1007 int Resolver::GetFd()
1012 bool Resolver::ProcessResult()
1014 log(DEBUG,"Resolver::ProcessResult");
1016 result = Query.GetResultIP();
1018 result = Query.GetResult();
1022 log(DEBUG,"Resolver::OnLookupComplete(%s)",result.c_str());
1023 this->OnLookupComplete(result);
1028 log(DEBUG,"Resolver::OnError(RESOLVER_NXDOMAIN)");
1029 this->OnError(RESOLVER_NXDOMAIN);
1034 void Resolver::OnLookupComplete(const std::string &result)
1038 void Resolver::OnError(ResolverError e)
1042 void dns_deal_with_classes(int fd)
1044 log(DEBUG,"dns_deal_with_classes(%d)",fd);
1045 if ((fd > -1) && (dns_classes[fd]))
1047 log(DEBUG,"Valid fd %d",fd);
1048 dns_classes[fd]->ProcessResult();
1049 delete dns_classes[fd];
1050 dns_classes[fd] = NULL;
1054 bool dns_add_class(Resolver* r)
1056 log(DEBUG,"dns_add_class");
1057 if ((r) && (r->GetFd() > -1))
1059 if (!dns_classes[r->GetFd()])
1061 log(DEBUG,"dns_add_class: added class");
1062 dns_classes[r->GetFd()] = r;
1067 log(DEBUG,"Space occupied!");
1073 log(DEBUG,"Bad class");
1081 memset(dns_classes,0,sizeof(dns_classes));