]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/inspircd.cpp
Added kqueue for listening sockets
[user/henk/code/inspircd.git] / src / inspircd.cpp
index e3162830580df72798b9856b54023ba1b7e7cacc..2a2696413451bf275d3ef72d0988af3cc82d66bf 100644 (file)
@@ -27,6 +27,11 @@ using namespace std;
 #include <sys/errno.h>
 #include <sys/ioctl.h>
 #include <sys/utsname.h>
+#ifdef USE_KQUEUE
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#endif
 #include <cstdio>
 #include <time.h>
 #include <string>
@@ -106,6 +111,10 @@ bool unlimitcore = false;
 
 time_t TIME = time(NULL);
 
+#ifdef USE_KQUEUE
+int kq, lkq;
+#endif
+
 namespace nspace
 {
 #ifdef GCC34
@@ -220,6 +229,8 @@ std::vector<userrec*> all_opers;
 
 static char already_sent[65536];
 
+char lowermap[255];
+
 void AddOper(userrec* user)
 {
        log(DEBUG,"Oper added to optimization list");
@@ -1305,19 +1316,10 @@ void WriteWallOps(userrec *source, bool local_only, char* text, ...)
 
 void strlower(char *n)
 {
-       if (!n)
-       {
-               return;
-       }
-       for (int i = 0; n[i] != 0; i++)
+       if (n)
        {
-               n[i] = tolower(n[i]);
-               if (n[i] == '[')
-                       n[i] = '{';
-               if (n[i] == ']')
-                       n[i] = '}';
-               if (n[i] == '\\')
-                       n[i] = '|';
+               for (char* t = n; *t; t++)
+                       *t = lowermap[*t];
        }
 }
 
@@ -1506,32 +1508,31 @@ void userlist(userrec *user,chanrec *c)
        }
 
        snprintf(list,MAXBUF,"353 %s = %s :", user->nick, c->name);
-       for (user_hash::const_iterator i = clientlist.begin(); i != clientlist.end(); i++)
+
+        std::vector<char*> *ulist = c->GetUsers();
+       for (int i = 0; i < ulist->size(); i++)
        {
-               if (has_channel(i->second,c))
+               char* o = (*ulist)[i];
+               userrec* otheruser = (userrec*)o;
+               if ((!has_channel(user,c)) && (strchr(otheruser->modes,'i')))
                {
-                       if (isnick(i->second->nick))
-                       {
-                               if ((!has_channel(i->second,c)) && (strchr(i->second->modes,'i')))
-                               {
-                                       /* user is +i, and source not on the channel, does not show
-                                        * nick in NAMES list */
-                                       continue;
-                               }
-                               strlcat(list,cmode(i->second,c),MAXBUF);
-                               strlcat(list,i->second->nick,MAXBUF);
-                               strlcat(list," ",MAXBUF);
-                               if (strlen(list)>(480-NICKMAX))
-                               {
-                                       /* list overflowed into
-                                        * multiple numerics */
-                                       WriteServ(user->fd,"%s",list);
-                                       snprintf(list,MAXBUF,"353 %s = %s :", user->nick, c->name);
-                               }
-                       }
+                       /* user is +i, and source not on the channel, does not show
+                        * nick in NAMES list */
+                       continue;
+               }
+               strlcat(list,cmode(otheruser,c),MAXBUF);
+               strlcat(list,otheruser->nick,MAXBUF);
+               strlcat(list," ",MAXBUF);
+               if (strlen(list)>(480-NICKMAX))
+               {
+                       /* list overflowed into
+                        * multiple numerics */
+                       WriteServ(user->fd,"%s",list);
+                       snprintf(list,MAXBUF,"353 %s = %s :", user->nick, c->name);
                }
        }
-       /* if whats left in the list isnt empty, send it */     if (list[strlen(list)-1] != ':')
+       /* if whats left in the list isnt empty, send it */
+       if (list[strlen(list)-1] != ':')
        {
                WriteServ(user->fd,"%s",list);
        }
@@ -2200,8 +2201,17 @@ void kill_link(userrec *user,const char* r)
        if (user->fd > -1)
        {
                FOREACH_MOD OnRawSocketClose(user->fd);
-               shutdown(user->fd,2);
-               close(user->fd);
+#ifdef USE_KQUEUE
+               struct kevent ke;
+               EV_SET(&ke, user->fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+               int i = kevent(kq, &ke, 1, 0, 0, NULL);
+               if (i == -1)
+               {
+                       log(DEBUG,"kqueue: Failed to remove user from queue!");
+               }
+#endif
+                shutdown(user->fd,2);
+                close(user->fd);
        }
        
        if (user->registered == 7) {
@@ -2255,6 +2265,15 @@ void kill_link_silent(userrec *user,const char* r)
         if (user->fd > -1)
         {
                FOREACH_MOD OnRawSocketClose(user->fd);
+#ifdef USE_KQUEUE
+                struct kevent ke;
+                EV_SET(&ke, user->fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
+                int i = kevent(kq, &ke, 1, 0, 0, NULL);
+                if (i == -1)
+                {
+                        log(DEBUG,"kqueue: Failed to remove user from queue!");
+                }
+#endif
                 shutdown(user->fd,2);
                 close(user->fd);
         }
@@ -2367,6 +2386,17 @@ int main(int argc, char** argv)
        }
        strlcpy(MyExecutable,argv[0],MAXBUF);
        
+       // initialize the lowercase mapping table
+       for (int cn = 0; cn < 256; cn++)
+               lowermap[cn] = cn;
+       // lowercase the uppercase chars
+       for (int cn = 65; cn < 91; cn++)
+               lowermap[cn] = tolower(cn);
+       // now replace the specific chars for scandanavian comparison
+       lowermap['['] = '{';
+       lowermap[']'] = '}';
+       lowermap['\\'] = '|';
+
        if (InspIRCd(argv,argc) == ERROR)
        {
                log(DEFAULT,"main: daemon function bailed");
@@ -2571,6 +2601,48 @@ void AddClient(int socket, char* host, int port, bool iscached, char* ip)
                }
        }
        fd_ref_table[socket] = clientlist[tempnick];
+
+#ifdef USE_KQUEUE
+       struct kevent ke;
+       log(DEBUG,"kqueue: Add user to events, kq=%d socket=%d",kq,socket);
+       EV_SET(&ke, socket, EVFILT_READ, EV_ADD, 0, 0, NULL);
+        int i = kevent(kq, &ke, 1, 0, 0, NULL);
+        if (i == -1)
+        {
+               switch (errno)
+               {
+                       case EACCES:
+                               log(DEBUG,"kqueue: EACCES");
+                       break;
+                       case EFAULT:
+                               log(DEBUG,"kqueue: EFAULT");
+                       break;
+                       case EBADF:
+                               log(DEBUG,"kqueue: EBADF=%d",ke.ident);
+                       break;
+                       case EINTR:
+                               log(DEBUG,"kqueue: EINTR");
+                       break;
+                       case EINVAL:
+                               log(DEBUG,"kqueue: EINVAL");
+                       break;
+                       case ENOENT:
+                               log(DEBUG,"kqueue: ENOENT");
+                       break;
+                       case ENOMEM:
+                               log(DEBUG,"kqueue: ENOMEM");
+                       break;
+                       case ESRCH:
+                               log(DEBUG,"kqueue: ESRCH");
+                       break;
+                       default:
+                               log(DEBUG,"kqueue: UNKNOWN!");
+                       break;
+               }
+                log(DEBUG,"kqueue: Failed to add user to queue!");
+        }
+
+#endif
 }
 
 // this function counts all users connected, wether they are registered or NOT.
@@ -2666,18 +2738,22 @@ long local_count()
 
 void ShowMOTD(userrec *user)
 {
+       char buf[65536];
         std::string WholeMOTD = "";
         if (!MOTD.size())
         {
                 WriteServ(user->fd,"422 %s :Message of the day file is missing.",user->nick);
                 return;
         }
-        WholeMOTD = std::string(":") + std::string(ServerName) + std::string(" 375 ") + std::string(user->nick) + std::string(" :- ") + std::string(ServerName) + " message of the day\r\n";
+        snprintf(buf,65535,":%s 375 %s :- %s message of the day\r\n", ServerName, user->nick, ServerName);
+       WholeMOTD = WholeMOTD + buf;
         for (int i = 0; i != MOTD.size(); i++)
         {
-                WholeMOTD = WholeMOTD + std::string(":") + std::string(ServerName) + std::string(" 372 ") + std::string(user->nick) + std::string(" :- ") + MOTD[i] + std::string("\r\n");
+               snprintf(buf,65535,":%s 372 %s :- %s\r\n", ServerName, user->nick, MOTD[i].c_str());
+                WholeMOTD = WholeMOTD + buf;
         }
-        WholeMOTD = WholeMOTD + std::string(":") + std::string(ServerName) + std::string(" 376 ") + std::string(user->nick) + std::string(" :End of message of the day.\r\n");
+       snprintf(buf,65535,":%s 376 %s :End of message of the day.\r\n", ServerName, user->nick);
+        WholeMOTD = WholeMOTD + buf;
         // only one write operation
         send(user->fd,WholeMOTD.c_str(),WholeMOTD.length(),0);
        statsSent += WholeMOTD.length();
@@ -2752,7 +2828,7 @@ void FullConnectUser(userrec* user)
         v << "MESHED WALLCHOPS MODES=13 CHANTYPES=# PREFIX=(ohv)@%+ MAP SAFELIST MAXCHANNELS=" << MAXCHANS;
         v << " MAXBANS=60 NICKLEN=" << NICKMAX;
         v << " TOPICLEN=307 KICKLEN=307 MAXTARGETS=20 AWAYLEN=307 CHANMODES=ohvb,k,l,psmnti NETWORK=";
-        v << std::string(Network);
+        v << Network;
         std::string data005 = v.str();
         FOREACH_MOD On005Numeric(data005);
         // anfl @ #ratbox, efnet reminded me that according to the RFC this cant contain more than 13 tokens per line...
@@ -2819,7 +2895,12 @@ std::string GetVersionString()
         s1 = savept;
         v2 = strtok_r(s1," ",&savept);
         s1 = savept;
-       snprintf(versiondata,MAXBUF,"%s Rev. %s %s :%s (O=%lu)",VERSION,v2,ServerName,SYSTEM,(unsigned long)OPTIMISATION);
+#ifdef USE_KQUEUE
+       char socketengine[] = "kqueue";
+#else
+       char socketengine[] = "select";
+#endif
+       snprintf(versiondata,MAXBUF,"%s Rev. %s %s :%s (O=%lu) [SE=%s]",VERSION,v2,ServerName,SYSTEM,(unsigned long)OPTIMISATION,socketengine);
        return versiondata;
 }
 
@@ -3735,9 +3816,10 @@ void erase_module(int j)
 
 bool UnloadModule(const char* filename)
 {
+       std::string filename_str = filename;
        for (int j = 0; j != module_names.size(); j++)
        {
-               if (module_names[j] == std::string(filename))
+               if (module_names[j] == filename_str)
                {
                        if (modules[j]->GetVersion().Flags & VF_STATIC)
                        {
@@ -3827,6 +3909,7 @@ bool LoadModule(const char* filename)
 {
        char modfile[MAXBUF];
        snprintf(modfile,MAXBUF,"%s/%s",ModPath,filename);
+       std::string filename_str = filename;
        if (!DirValid(modfile))
        {
                log(DEFAULT,"Module %s is not within the modules directory.",modfile);
@@ -3838,7 +3921,7 @@ bool LoadModule(const char* filename)
         {
                for (int j = 0; j < module_names.size(); j++)
                {
-                       if (module_names[j] == std::string(filename))
+                       if (module_names[j] == filename_str)
                        {
                                log(DEFAULT,"Module %s is already loaded, cannot load a module twice!",modfile);
                                snprintf(MODERR,MAXBUF,"Module already loaded");
@@ -4034,11 +4117,46 @@ int InspIRCd(char** argv, int argc)
                 }
         }
 
+       // BUGFIX: We cannot initialize this before forking, as the kqueue data is not inherited by child processes!
+#ifdef USE_KQUEUE
+        kq = kqueue();
+       lkq = kqueue();
+        if ((kq == -1) || (lkq == -1))
+        {
+                log(DEFAULT,"main: kqueue() failed!");
+                printf("ERROR: could not initialise kqueue event system. Shutting down.\n");
+                Exit(ERROR);
+        }
+#endif
+
+
+#ifdef USE_KQUEUE
+       log(DEFAULT,"kqueue socket engine is enabled. Filling listen list.");
+       for (count = 0; count < boundPortCount; count++)
+       {
+               struct kevent ke;
+               log(DEBUG,"kqueue: Add listening socket to events, kq=%d socket=%d",lkq,openSockfd[count]);
+               EV_SET(&ke, openSockfd[count], EVFILT_READ, EV_ADD, 0, 5, NULL);
+               int i = kevent(lkq, &ke, 1, 0, 0, NULL);
+               if (i == -1)
+               {
+                       log(DEFAULT,"main: add listen ports to kqueue failed!");
+                       printf("ERROR: could not initialise listening sockets in kqueue. Shutting down.\n");
+               }
+       }
+#else
+       log(DEFAULT,"Using standard select socket engine.");
+#endif
+
        WritePID(PID);
 
        length = sizeof (client);
        char tcp_msg[MAXBUF],tcp_host[MAXBUF];
 
+#ifdef USE_KQUEUE
+        struct kevent ke;
+        struct timespec ts;
+#endif
         fd_set serverfds;
         timeval tvs;
         tvs.tv_usec = 10000L;
@@ -4051,7 +4169,7 @@ int InspIRCd(char** argv, int argc)
         tval.tv_usec = 10000L;
         tval.tv_sec = 0;
         int total_in_this_set = 0;
-       int v = 0;
+       int i = 0, v = 0;
        bool expire_run = false;
          
        /* main loop, this never returns */
@@ -4156,10 +4274,10 @@ int InspIRCd(char** argv, int argc)
                        }
                }
        
-
        while (count2 != clientlist.end())
        {
                FD_ZERO(&sfd);
+
                total_in_this_set = 0;
 
                user_hash::iterator xcount = count2;
@@ -4182,7 +4300,8 @@ int InspIRCd(char** argv, int argc)
                        //
                        // This should be up to 64x faster than the
                        // old implementation.
-                       while (total_in_this_set < 64)
+#ifndef USE_KQUEUE
+                       while (total_in_this_set < 1024)
                        {
                                if (count2 != clientlist.end())
                                {
@@ -4233,14 +4352,76 @@ int InspIRCd(char** argv, int argc)
                                }
                                else break;
                        }
-   
                        endingiter = count2;
                                count2 = xcount; // roll back to where we were
+#else
+                       dns_poll();
+                       // KQUEUE: We don't go through a loop to fill the fd_set so instead we must manually do this loop every now and again.
+                       // TODO: We dont need to do all this EVERY loop iteration, tone down the visits to this if we're using kqueue.
+                       while (count2 != clientlist.end())
+                       {
+                               if (count2 != clientlist.end())
+                               {
+                                       curr = count2->second;
+                                       // we don't check the state of remote users.
+                                       if ((curr->fd != -1) && (curr->fd != FD_MAGIC_NUMBER))
+                                       {
+                                               // registration timeout -- didnt send USER/NICK/HOST in the time specified in
+                                               // their connection class.
+                                               if ((TIME > curr->timeout) && (curr->registered != 7))
+                                               {
+                                                       log(DEBUG,"InspIRCd: registration timeout: %s",curr->nick);
+                                                       kill_link(curr,"Registration timeout");
+                                                       goto label;
+                                               }
+                                               if ((TIME > curr->signon) && (curr->registered == 3) && (AllModulesReportReady(curr)))
+                                               {
+                                                       log(DEBUG,"signon exceed, registered=3, and modules ready, OK: %d %d",TIME,curr->signon);
+                                                       curr->dns_done = true;
+                                                       statsDnsBad++;
+                                                       FullConnectUser(curr);
+                                                       goto label;
+                                               }
+                                               if ((curr->dns_done) && (curr->registered == 3) && (AllModulesReportReady(curr)))
+                                               {
+                                                       log(DEBUG,"dns done, registered=3, and modules ready, OK");
+                                                       FullConnectUser(curr);
+                                                       goto label;
+                                               }
+                                               if ((TIME > curr->nping) && (isnick(curr->nick)) && (curr->registered == 7))
+                                               {
+                                                       if ((!curr->lastping) && (curr->registered == 7))
+                                                       {
+                                                               log(DEBUG,"InspIRCd: ping timeout: %s",curr->nick);
+                                                               kill_link(curr,"Ping timeout");
+                                                               goto label;
+                                                       }
+                                                       Write(curr->fd,"PING :%s",ServerName);
+                                                       log(DEBUG,"InspIRCd: pinging: %s",curr->nick);
+                                                       curr->lastping = 0;
+                                                       curr->nping = TIME+curr->pingmax;       // was hard coded to 120
+                                               }
+                                       }
+                               }
+                               else break;
+                               count2++;
+                       }
+                       // increment the counter right to the end of the list, as kqueue processes everything in one go
+#endif
         
                        v = 0;
 
-                       // tvals defined here
-
+#ifdef USE_KQUEUE
+                       ts.tv_sec = 0;
+                       ts.tv_nsec = 1000L;
+                       // for now, we only read 1 event. We could read soooo many more :)
+                       int i = kevent(kq, NULL, 0, &ke, 1, &ts);
+                       if (i > 0)
+                       {
+                               log(DEBUG,"kevent call: kq=%d, i=%d",kq,i);
+                               // KQUEUE: kevent gives us ONE fd which is ready to have something done to it. Do something to it.
+                               userrec* cu = fd_ref_table[ke.ident];
+#else
                        tval.tv_usec = 1000L;
                        selectResult2 = select(65535, &sfd, NULL, NULL, &tval);
                        
@@ -4248,13 +4429,21 @@ int InspIRCd(char** argv, int argc)
                        if (selectResult2 > 0)
                        for (user_hash::iterator count2a = xcount; count2a != endingiter; count2a++)
                        {
+                               // SELECT: we have to iterate...
+                               userrec* cu = count2a->second;
+#endif
 
 #ifdef _POSIX_PRIORITY_SCHEDULING
                                sched_yield();
 #endif
-                               userrec* cu = count2a->second;
                                result = EAGAIN;
+#ifdef USE_KQUEUE
+                               // KQUEUE: We already know we have a valid FD. No checks needed.
+                               if ((cu->fd != FD_MAGIC_NUMBER) && (cu->fd != -1))
+#else
+                               // SELECT: We don't know if our FD is valid.
                                if ((cu->fd != FD_MAGIC_NUMBER) && (cu->fd != -1) && (FD_ISSET (cu->fd, &sfd)))
+#endif
                                {
                                        log(DEBUG,"Data waiting on socket %d",cu->fd);
                                        int MOD_RESULT = 0;
@@ -4283,6 +4472,7 @@ int InspIRCd(char** argv, int argc)
                                                int currfd = current->fd;
                                                int floodlines = 0;
                                                // add the data to the users buffer
+                                               if (result > 0)
                                                if (!current->AddBuffer(data))
                                                {
                                                        // AddBuffer returned false, theres too much data in the user's buffer and theyre up to no good.
@@ -4392,14 +4582,18 @@ int InspIRCd(char** argv, int argc)
                                else
                                if (result == 0)
                                {
+#ifndef USE_KQUEUE
                                        if (count2->second)
                                        {
+#endif
                                                log(DEBUG,"InspIRCd: Exited: %s",cu->nick);
                                                kill_link(cu,"Client exited");
                                                // must bail here? kill_link removes the hash, corrupting the iterator
                                                log(DEBUG,"Bailing from client exit");
                                                goto label;
+#ifndef USE_KQUEUE
                                        }
+#endif
                                }
                                else if (result > 0)
                                {
@@ -4416,6 +4610,7 @@ int InspIRCd(char** argv, int argc)
         sched_yield();
 #endif
        
+#ifndef USE_KQUEUE
        // set up select call
        for (count = 0; count < boundPortCount; count++)
        {
@@ -4428,11 +4623,27 @@ int InspIRCd(char** argv, int argc)
        /* select is reporting a waiting socket. Poll them all to find out which */
        if (selectResult > 0)
        {
-               char target[MAXBUF], resolved[MAXBUF];
-               for (count = 0; count < boundPortCount; count++)                
+               for (count = 0; count < boundPortCount; count++)
                {
                        if (FD_ISSET (openSockfd[count], &selectFds))
                        {
+#else
+       ts.tv_sec = 0;
+       ts.tv_nsec = 30000L;
+       i = kevent(lkq, NULL, 0, &ke, 1, &ts);
+       if (i > 0)
+       {
+               log(DEBUG,"kqueue: Listening socket event, i=%d, ke.ident=%d",i,ke.ident);
+               // this isnt as efficient as it could be, we could create a reference table
+               // to reference bound ports by fd, but this isnt a big bottleneck as the actual
+               // number of listening ports on the average ircd is a small number (less than 20)
+               // compared to the number of clients (possibly over 2000)
+               for (count = 0; count < boundPortCount; count++)
+               {
+                       if (ke.ident == openSockfd[count])
+                       {
+#endif
+                               char target[MAXBUF], resolved[MAXBUF];
                                length = sizeof (client);
                                incomingSockfd = accept (openSockfd[count], (struct sockaddr *) &client, &length);