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];
102 unsigned int rdlength;
111 unsigned int qdcount;
112 unsigned int ancount;
113 unsigned int nscount;
114 unsigned int arcount;
115 unsigned char payload[512];
119 void *dns_align(void *inp)
121 char *p = (char*)inp;
122 int offby = ((char *)p - (char *)0) % (sizeof(void *) > sizeof(long) ? sizeof(void *) : sizeof(long));
124 return p + ((sizeof(void *) > sizeof(long) ? sizeof(void *) : sizeof(long)) - offby);
130 * Optimized by brain, these were using integer division and modulus.
131 * We can use logic shifts and logic AND to replace these even divisions
132 * and multiplications, it should be a bit faster (probably not noticably,
133 * but of course, more impressive). Also made these inline.
136 inline void dns_fill_rr(s_rr_middle* rr, const unsigned char *input)
138 rr->type = (QueryType)((input[0] << 8) + input[1]);
139 rr->_class = (input[2] << 8) + input[3];
140 rr->ttl = (input[4] << 24) + (input[5] << 16) + (input[6] << 8) + input[7];
141 rr->rdlength = (input[8] << 8) + input[9];
144 inline void dns_fill_header(s_header *header, const unsigned char *input, const int l)
146 header->id[0] = input[0];
147 header->id[1] = input[1];
148 header->flags1 = input[2];
149 header->flags2 = input[3];
150 header->qdcount = (input[4] << 8) + input[5];
151 header->ancount = (input[6] << 8) + input[7];
152 header->nscount = (input[8] << 8) + input[9];
153 header->arcount = (input[10] << 8) + input[11];
154 memcpy(header->payload,&input[12],l);
157 inline void dns_empty_header(unsigned char *output, const s_header *header, const int l)
159 output[0] = header->id[0];
160 output[1] = header->id[1];
161 output[2] = header->flags1;
162 output[3] = header->flags2;
163 output[4] = header->qdcount >> 8;
164 output[5] = header->qdcount & 0xFF;
165 output[6] = header->ancount >> 8;
166 output[7] = header->ancount & 0xFF;
167 output[8] = header->nscount >> 8;
168 output[9] = header->nscount & 0xFF;
169 output[10] = header->arcount >> 8;
170 output[11] = header->arcount & 0xFF;
171 memcpy(&output[12],header->payload,l);
174 void dns_close(int fd)
177 if (ServerInstance && ServerInstance->SE)
178 ServerInstance->SE->DelFd(fd);
180 log(DEBUG,"DNS: dns_close on fd %d",fd);
197 srand((unsigned int) TIME);
198 memset(servers4,'\0',sizeof(in_addr) * 8);
199 f = fopen("/etc/resolv.conf","r");
202 while (fgets(buf,1024,f) != NULL) {
203 if (strncmp(buf,"nameserver",10) == 0)
206 while (buf[i] == ' ' || buf[i] == '\t')
210 if (dns_aton4_s(&buf[i],&addr4) != NULL)
211 memcpy(&servers4[i4++],&addr4,sizeof(in_addr));
218 void DNS::dns_init_2(const char* dnsserver)
222 srand((unsigned int) TIME);
223 memset(servers4,'\0',sizeof(in_addr) * 8);
224 if (dns_aton4_s(dnsserver,&addr4) != NULL)
225 memcpy(&servers4[i4++],&addr4,sizeof(in_addr));
229 int dns_send_requests(const s_header *h, const s_connection *s, const int l)
233 unsigned char payload[sizeof(s_header)];
235 dns_empty_header(payload,h,l);
240 /* otherwise send via standard ipv4 boringness */
241 memset(&addr4,0,sizeof(addr4));
242 memcpy(&addr4.sin_addr,&servers4[i],sizeof(addr4.sin_addr));
243 addr4.sin_family = AF_INET;
244 addr4.sin_port = htons(53);
245 if (sendto(s->fd, payload, l + 12, 0, (sockaddr *) &addr4, sizeof(addr4)) == -1)
253 s_connection *dns_add_query(s_header *h)
256 s_connection * s = new s_connection;
257 int id = rand() % 65536;
259 /* set header flags */
260 h->id[0] = s->id[0] = id >> 8; /* verified by dns_getresult_s() */
261 h->id[1] = s->id[1] = id & 0xFF;
262 h->flags1 = 0 | FLAGS1_MASK_RD;
269 s->fd = socket(PF_INET, SOCK_DGRAM, 0);
272 if (fcntl(s->fd, F_SETFL, O_NONBLOCK) != 0)
282 memset(&addr,0,sizeof(addr));
283 addr.sin_family = AF_INET;
285 addr.sin_addr.s_addr = INADDR_ANY;
286 if (bind(s->fd,(sockaddr *)&addr,sizeof(addr)) != 0)
298 /* create new connection object, add to linked list */
300 pthread_mutex_lock(&connmap_lock);
302 if (connections.find(s->fd) == connections.end())
303 connections[s->fd] = s;
305 pthread_mutex_unlock(&connmap_lock);
311 int dns_build_query_payload(const char * const name, const unsigned short rr, const unsigned short _class, unsigned char * const payload)
314 const char * tempchr, * tempchr2;
320 /* split name up into labels, create query */
321 while ((tempchr = strchr(tempchr2,'.')) != NULL)
323 l = tempchr - tempchr2;
324 if (payloadpos + l + 1 > 507)
326 payload[payloadpos++] = l;
327 memcpy(&payload[payloadpos],tempchr2,l);
329 tempchr2 = &tempchr[1];
331 l = strlen(tempchr2);
334 if (payloadpos + l + 2 > 507)
336 payload[payloadpos++] = l;
337 memcpy(&payload[payloadpos],tempchr2,l);
339 payload[payloadpos++] = '\0';
341 if (payloadpos > 508)
344 memcpy(&payload[payloadpos],&l,2);
346 memcpy(&payload[payloadpos + 2],&l,2);
347 return payloadpos + 4;
350 in_addr* DNS::dns_aton4(const char * const ipstring)
353 return dns_aton4_s(ipstring,&ip);
356 in_addr* DNS::dns_aton4_r(const char *ipstring) { /* ascii to numeric (reentrant): convert string to new 4part IP addr struct */
359 if(dns_aton4_s(ipstring,ip) == NULL)
367 in_addr* DNS::dns_aton4_s(const char *ipstring, in_addr *ip) { /* ascii to numeric (buffered): convert string to given 4part IP addr struct */
368 inet_aton(ipstring,ip);
372 int DNS::dns_getip4(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
380 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
383 s = dns_add_query(&h);
388 if (dns_send_requests(&h,s,l) == -1)
394 int DNS::dns_getip4list(const char *name) { /* build, add and send A query; retrieve result with dns_getresult() */
401 l = dns_build_query_payload(name,DNS_QRY_A,1,(unsigned char *)&h.payload);
404 s = dns_add_query(&h);
410 if (dns_send_requests(&h,s,l) == -1)
416 int DNS::dns_getname4(const in_addr *ip) { /* build, add and send PTR query; retrieve result with dns_getresult() */
423 c = (unsigned char *)&ip->s_addr;
425 sprintf(query,"%d.%d.%d.%d.in-addr.arpa",c[3],c[2],c[1],c[0]);
427 l = dns_build_query_payload(query,DNS_QRY_PTR,1,(unsigned char *)&h.payload);
430 s = dns_add_query(&h);
434 s->type = DNS_QRY_PTR;
435 if (dns_send_requests(&h,s,l) == -1)
441 char* DNS::dns_getresult(const int cfd) { /* retrieve result of DNS query */
442 log(DEBUG,"DNS: dns_getresult with cfd=%d",cfd);
443 return dns_getresult_s(cfd,this->localbuf);
446 char* DNS::dns_getresult_s(const int cfd, char *res) { /* retrieve result of DNS query (buffered) */
449 int l, i, q, curanswer, o;
451 unsigned char buffer[sizeof(s_header)];
457 /* FireDNS used a linked list for this. How ugly (and slow). */
460 /* XXX: STL really does NOT like being poked and prodded in more than
461 * one orifice by threaded apps. Make sure we remain nice to it, and
462 * lock a mutex around any access to the std::map.
464 pthread_mutex_lock(&connmap_lock);
466 connlist_iter n_iter = connections.find(cfd);
467 if (n_iter == connections.end())
469 log(DEBUG,"DNS: got a response for a query we didnt send with fd=%d",cfd);
471 pthread_mutex_unlock(&connmap_lock);
477 /* Remove the query from the list */
478 c = (s_connection*)n_iter->second;
479 /* We don't delete c here, because its done later when needed */
480 connections.erase(n_iter);
483 pthread_mutex_unlock(&connmap_lock);
486 l = recv(c->fd,buffer,sizeof(s_header),0);
493 dns_fill_header(&h,buffer,l - 12);
494 if (c->id[0] != h.id[0] || c->id[1] != h.id[1])
496 log(DEBUG,"DNS: id mismatch on query");
498 return NULL; /* ID mismatch */
500 if ((h.flags1 & FLAGS1_MASK_QR) == 0)
502 log(DEBUG,"DNS: didnt get a query result");
506 if ((h.flags1 & FLAGS1_MASK_OPCODE) != 0)
508 log(DEBUG,"DNS: got an OPCODE and didnt want one");
512 if ((h.flags2 & FLAGS2_MASK_RCODE) != 0)
514 log(DEBUG,"DNS lookup failed due to SERVFAIL");
520 log(DEBUG,"DNS: no answers!");
527 while ((unsigned)q < h.qdcount && i < l)
529 if (h.payload[i] > 63)
536 if (h.payload[i] == 0)
541 else i += h.payload[i] + 1;
545 while ((unsigned)curanswer < h.ancount)
548 while (q == 0 && i < l)
550 if (h.payload[i] > 63)
557 if (h.payload[i] == 0)
562 else i += h.payload[i] + 1; /* skip length and label */
570 dns_fill_rr(&rr,&h.payload[i]);
572 if (rr.type != c->type)
578 if (rr._class != c->_class)
586 if ((unsigned)curanswer == h.ancount)
588 if ((unsigned)i + rr.rdlength > (unsigned)l)
590 if (rr.rdlength > 1023)
596 log(DEBUG,"DNS: got a result of type DNS_QRY_PTR");
599 while (q == 0 && i < l && o + 256 < 1023)
601 if (h.payload[i] > 63)
603 log(DEBUG,"DNS: h.payload[i] > 63");
604 memcpy(&p,&h.payload[i],2);
605 i = ntohs(p) - 0xC000 - 12;
609 if (h.payload[i] == 0)
618 memcpy(&res[o],&h.payload[i + 1],h.payload[i]);
620 i += h.payload[i] + 1;
627 log(DEBUG,"DNS: got a result of type DNS_QRY_A");
630 dns_ip4list *alist = (dns_ip4list *) res; /* we have to trust that this is aligned */
631 while ((char *)alist - (char *)res < 700)
633 if (rr.type != DNS_QRY_A)
637 if (rr.rdlength != 4)
642 memcpy(&alist->ip,&h.payload[i],4);
643 if ((unsigned)++curanswer >= h.ancount)
647 while (q == 0 && i < l)
649 if (h.payload[i] > 63)
656 if (h.payload[i] == 0)
661 else i += h.payload[i] + 1;
669 dns_fill_rr(&rr,&h.payload[i]);
671 alist->next = (dns_ip4list *) dns_align(((char *) alist) + sizeof(dns_ip4list));
678 memcpy(res,&h.payload[i],rr.rdlength);
679 res[rr.rdlength] = '\0';
682 memcpy(res,&h.payload[i],rr.rdlength);
683 res[rr.rdlength] = '\0';
693 log(DEBUG,"Create blank DNS");
696 DNS::DNS(const std::string &dnsserver)
698 dns_init_2(dnsserver.c_str());
699 log(DEBUG,"Create DNS with server '%s'",dnsserver.c_str());
702 void DNS::SetNS(const std::string &dnsserver)
704 dns_init_2(dnsserver.c_str());
712 bool DNS::ReverseLookup(const std::string &ip, bool ins)
714 if (ServerInstance && ServerInstance->stats)
715 ServerInstance->stats->statsDns++;
716 binip = dns_aton4(ip.c_str());
722 this->myfd = dns_getname4(binip);
723 if (this->myfd == -1)
727 log(DEBUG,"DNS: ReverseLookup, fd=%d",this->myfd);
731 if (ServerInstance && ServerInstance->SE)
732 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
738 bool DNS::ForwardLookup(const std::string &host, bool ins)
740 if (ServerInstance && ServerInstance->stats)
741 ServerInstance->stats->statsDns++;
742 this->myfd = dns_getip4(host.c_str());
743 if (this->myfd == -1)
747 log(DEBUG,"DNS: ForwardLookup, fd=%d",this->myfd);
751 if (ServerInstance && ServerInstance->SE)
752 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_DNS);
758 bool DNS::ForwardLookupWithFD(const std::string &host, int &fd)
760 if (ServerInstance && ServerInstance->stats)
761 ServerInstance->stats->statsDns++;
762 this->myfd = dns_getip4(host.c_str());
764 if (this->myfd == -1)
768 log(DEBUG,"DNS: ForwardLookupWithFD, fd=%d",this->myfd);
769 if (ServerInstance && ServerInstance->SE)
770 ServerInstance->SE->AddFd(this->myfd,true,X_ESTAB_MODULE);
774 bool DNS::HasResult(int fd)
776 return (fd == this->myfd);
779 /* Only the multithreaded dns uses this poll() based
780 * check now. As its in another thread we dont have
781 * to worry about its performance that much.
783 bool DNS::HasResult()
785 log(DEBUG,"DNS: HasResult, fd=%d",this->myfd);
787 polls.fd = this->myfd;
788 polls.events = POLLIN;
789 int ret = poll(&polls,1,1);
790 log(DEBUG,"DNS: Hasresult returning %d",ret);
799 std::string DNS::GetResult()
801 log(DEBUG,"DNS: GetResult()");
802 result = dns_getresult(this->myfd);
805 if (ServerInstance && ServerInstance->stats)
806 ServerInstance->stats->statsDnsGood++;
807 dns_close(this->myfd);
813 if (ServerInstance && ServerInstance->stats)
814 ServerInstance->stats->statsDnsBad++;
815 if (this->myfd != -1)
817 dns_close(this->myfd);
824 std::string DNS::GetResultIP()
827 log(DEBUG,"DNS: GetResultIP()");
828 result = dns_getresult(this->myfd);
829 if (this->myfd != -1)
831 dns_close(this->myfd);
836 if (ServerInstance && ServerInstance->stats)
837 ServerInstance->stats->statsDnsGood++;
838 unsigned char a = (unsigned)result[0];
839 unsigned char b = (unsigned)result[1];
840 unsigned char c = (unsigned)result[2];
841 unsigned char d = (unsigned)result[3];
842 snprintf(r,1024,"%u.%u.%u.%u",a,b,c,d);
847 if (ServerInstance && ServerInstance->stats)
848 ServerInstance->stats->statsDnsBad++;
849 log(DEBUG,"DANGER WILL ROBINSON! NXDOMAIN for forward lookup, but we got a reverse lookup!");
858 /* This function is a thread function which can be thought of as a lightweight process
859 * to all you non-threaded people. In actuality its so much more, and pretty damn cool.
860 * With threaded dns enabled, each user which connects gets a thread attached to their
861 * user record when their DNS lookup starts. This function starts in parallel, and
862 * commences a blocking dns lookup. Because its a seperate thread, this occurs without
863 * actually blocking the main application. Once the dns lookup is completed, the thread
864 * checks if the user is still around by checking their fd against the reference table,
865 * and if they are, writes the hostname into the struct and terminates, after setting
866 * userrec::dns_done to true. Because this is multi-threaded it can make proper use of
867 * SMP setups (like the one i have here *grin*).
868 * This is in comparison to the non-threaded dns, which must monitor the thread sockets
869 * in a nonblocking fashion, consuming more resources to do so.
871 * NB: Yes this does scale, thank you. Even with large numbers of connecting clients
872 * in any one timeframe, they wont all connect *at the same time* therefore any argument
873 * of "but there will be thousands of threads it'll blow up" is moot, ive tested this and
874 * there will only ever be somewhere around the listen backlog in number of pending
875 * lookups at any one time. This is significant on any modern SMP system.
877 void* dns_task(void* arg)
879 userrec* u = (userrec*)arg;
882 log(DEBUG,"DNS thread for user %s",u->nick);
883 DNS dns1(Config->DNSServer);
884 DNS dns2(Config->DNSServer);
889 if (dns1.ReverseLookup(inet_ntoa(u->ip4),false))
891 /* FIX: Dont make these infinite! */
892 while ((!dns1.HasResult()) && (++iterations < 20))
897 if (dns1.GetFD() != -1)
899 host = dns1.GetResult();
902 if (dns2.ForwardLookup(host, false))
905 while ((!dns2.HasResult()) && (++iterations < 20))
910 if (dns2.GetFD() != -1)
912 ip = dns2.GetResultIP();
913 if (ip == std::string(inet_ntoa(u->ip4)))
915 if (host.length() < 65)
917 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
921 strcpy(u->host,host.c_str());
922 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
924 strcpy(u->dhost,host.c_str());
937 if ((fd_ref_table[thisfd] == u) && (fd_ref_table[thisfd]))
939 log(DEBUG,"THREAD EXIT");
944 Resolver::Resolver(const std::string &source, bool forward, const std::string &dnsserver = "") : input(source), fwd(forward), server(dnsserver)
946 if (this->server != "")
947 Query.SetNS(this->server);
949 Query.SetNS(Config->DNSServer);
953 Query.ForwardLookup(input.c_str(), false);
954 this->fd = Query.GetFD();
958 Query.ReverseLookup(input.c_str(), false);
959 this->fd = Query.GetFD();
963 log(DEBUG,"Resolver::Resolver: RESOLVER_NSDOWN");
964 this->OnError(RESOLVER_NSDOWN);
965 ModuleException e("Resolver: Nameserver is down");
967 /* We shouldnt get here really */
971 if (ServerInstance && ServerInstance->SE)
973 log(DEBUG,"Resolver::Resolver: this->fd=%d",this->fd);
974 ServerInstance->SE->AddFd(this->fd,true,X_ESTAB_CLASSDNS);
978 log(DEBUG,"Resolver::Resolver: RESOLVER_NOTREADY");
979 this->OnError(RESOLVER_NOTREADY);
980 ModuleException e("Resolver: Core not initialized yet");
982 /* We shouldnt get here really */
987 Resolver::~Resolver()
989 log(DEBUG,"Resolver::~Resolver");
990 if (ServerInstance && ServerInstance->SE)
991 ServerInstance->SE->DelFd(this->fd);
994 int Resolver::GetFd()
999 bool Resolver::ProcessResult()
1001 log(DEBUG,"Resolver::ProcessResult");
1003 result = Query.GetResultIP();
1005 result = Query.GetResult();
1009 log(DEBUG,"Resolver::OnLookupComplete(%s)",result.c_str());
1010 this->OnLookupComplete(result);
1015 log(DEBUG,"Resolver::OnError(RESOLVER_NXDOMAIN)");
1016 this->OnError(RESOLVER_NXDOMAIN);
1021 void Resolver::OnLookupComplete(const std::string &result)
1025 void Resolver::OnError(ResolverError e)
1029 void dns_deal_with_classes(int fd)
1031 log(DEBUG,"dns_deal_with_classes(%d)",fd);
1032 if ((fd > -1) && (dns_classes[fd]))
1034 log(DEBUG,"Valid fd %d",fd);
1035 dns_classes[fd]->ProcessResult();
1036 delete dns_classes[fd];
1037 dns_classes[fd] = NULL;
1041 bool dns_add_class(Resolver* r)
1043 log(DEBUG,"dns_add_class");
1044 if ((r) && (r->GetFd() > -1))
1046 if (!dns_classes[r->GetFd()])
1048 log(DEBUG,"dns_add_class: added class");
1049 dns_classes[r->GetFd()] = r;
1054 log(DEBUG,"Space occupied!");
1060 log(DEBUG,"Bad class");
1068 memset(dns_classes,0,sizeof(dns_classes));