/* * Abstract: Replaces the old hostlookup system with a threaded version, * thus removing the "lag" at new connections. * * This is the 2nd version of the threaded dns lookup howto, the first version * used a rather buggy system, which could accidently cause the mud to crash. * Even though this was rare, it's still anoying. This system has been implanted * in the latest releases of both version 1.0.x and 1.2.x of dystopia. * * This howto was written for godwars based muds, but it should be possible * to use it with any merc deriviative without to much work. It has been tested * on gw_96 downloaded from KaVirs homepage (www.kavir.org). It took roughly 5 * minutes to add all the changes, so this is actually a minor modification. * * Code/Howto by Brian Graversen */ ******************************* ******************************* **** Initial Modifications **** ******************************* ******************************* /* * File: Makefile */ ** Add this to the L_FLAGS (if you use linux): -lpthread ** Add this to the L_FLAGS (if you use BSD and perhaps other unices): -pthread /* * File: merc.h */ ** Add this to the includes at the beginning of the file: #include ** Add a new set of defines somewhere: #define STATUS_LOOKUP 0 // New Descriptor, in lookup by default. #define STATUS_DONE 1 // The lookup is done. #define STATUS_WAIT 2 // Closed while in thread. #define STATUS_CLOSED 3 // Closed, ready to be recycled. ** Add a new structure type somewhere: struct dummy_arg { DUMMY_ARG *next; DESCRIPTOR_DATA *d; char *buf; sh_int status; }; ** Make sure to typedefine it somewhere typedef struct dummy_arg DUMMY_ARG; ** Add these globel variables somewhere: extern DUMMY_ARG * dummy_free; extern DUMMY_ARG * dummy_list; extern pthread_mutex_t memory_mutex; ** Edit the descriptor_data structure so it contains: sh_int lookup_status; ********************************** ********************************** **** Editing the lookup stuff **** ********************************** ********************************** /* * File: comm.c */ ** Initialize the global variables somewhere (above main() perhaps) DUMMY_ARG * dummy_free; DUMMY_ARG * dummy_list; pthread_mutex_t memory_mutex = PTHREAD_MUTEX_INITIALIZER; ** Write a new new_descroptor() function to replace the old code /* This is just a prototype, you may have to compare this with * your own new_descriptor() function, so you don't forget anything. * This function can replace the one in gw_96 without any changes. * * It is important to notice that all banning has been removed from * this function, since the hostlookup has not been completed yet. */ void new_descriptor( int control ) { static DESCRIPTOR_DATA d_zero; char buf[MAX_STRING_LENGTH]; DESCRIPTOR_DATA *dnew; struct sockaddr_in sock; int desc; int size; pthread_attr_t attr; pthread_t thread_lookup; DUMMY_ARG *dummyarg; /* initialize threads */ pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); /* New Dummy Argument */ if (dummy_free == NULL) { dummyarg = alloc_perm(sizeof(*dummyarg)); } else { dummyarg = dummy_free; dummy_free = dummy_free->next; } dummyarg->status = 1; dummyarg->next = dummy_list; dummy_list = dummyarg; size = sizeof(sock); getsockname( control, (struct sockaddr *) &sock, &size ); if (( desc = accept( control, (struct sockaddr *) &sock, &size) ) < 0) { perror( "New_descriptor: accept" ); return; } #if !defined(FNDELAY) #define FNDELAY O_NDELAY #endif if (fcntl( desc, F_SETFL, FNDELAY ) == -1) { perror( "New_descriptor: fcntl: FNDELAY"); return; } /* * Cons a new descriptor. */ if ( descriptor_free == NULL ) { dnew = alloc_perm( sizeof(*dnew) ); } else { dnew = descriptor_free; descriptor_free = descriptor_free->next; } /* initialize descriptor data */ *dnew = d_zero; dnew->descriptor = desc; dnew->connected = CON_GET_NAME; dnew->lookup_status = STATUS_LOOKUP; // important dnew->outsize = 2000; dnew->outbuf = alloc_mem( dnew->outsize ); size = sizeof(sock); if ( getpeername( desc, (struct sockaddr *) &sock, &size ) < 0 ) { perror( "New_descriptor: getpeername" ); dnew->host = str_dup( "(unknown)" ); } else { int addr; addr = ntohl( sock.sin_addr.s_addr ); sprintf( buf, "%d.%d.%d.%d", ( addr >> 24 ) & 0xFF, ( addr >> 16 ) & 0xFF, ( addr >> 8 ) & 0xFF, ( addr ) & 0xFF); dnew->host = str_dup(buf); // set the temporary ip as the host. /* Set up the dummyarg for use in lookup_address */ dummyarg->buf = str_dup((char *) &sock.sin_addr); dummyarg->d = dnew; /* create a thread to do the lookup */ pthread_create( &thread_lookup, &attr, (void*)&lookup_address, (void*) dummyarg); } /* * Init descriptor data. */ dnew->next = descriptor_list; descriptor_list = dnew; /* send gretting */ { extern char * help_greeting; if ( help_greeting[0] == '.' ) write_to_buffer( dnew, help_greeting+1, 0 ); else write_to_buffer( dnew, help_greeting , 0 ); } } ** Write new close_socket() and close_socket2() functions. /* * The following is a prototype of close_socket(), which * can replace the one used in gw_96 without any changes, * other codebases may need modifications of this function, * so remember to compare it with the one you already have. * close_socket2() should also be replaced with a similar * function, this is easy, so I'll leave that to you. */ /* Nothing much is done here, except preparing the socket * to get closed. The actual closing is done elsewhere. * This is because we do not want to close sockets that * are currently active in another thread. */ void close_socket( DESCRIPTOR_DATA *dclose ) { CHAR_DATA *ch; if (dclose->lookup_status > STATUS_DONE) return; dclose->lookup_status += 2; if (dclose->outtop > 0) process_output( dclose, FALSE ); if ( dclose->snoop_by != NULL ) write_to_buffer( dclose->snoop_by, "Your victim has left the game.\n\r", 0 ); if (dclose->character != NULL && dclose->connected == CON_PLAYING && IS_NPC(dclose->character)) do_return(dclose->character,""); /* * Clear snoops */ { DESCRIPTOR_DATA *d; for ( d = descriptor_list; d != NULL; d = d->next ) if (d->snoop_by == dclose) d->snoop_by = NULL; } if ((ch = dclose->character) != NULL) { sprintf( log_buf, "Closing link to %s.", ch->name ); log_string( log_buf ); if (dclose->connected == CON_PLAYING) { if (IS_NPC(ch) || ch->pcdata->obj_vnum == 0) act( "$n has lost $s link.", ch, NULL, NULL, TO_ROOM ); ch->desc = NULL; } else { free_char( dclose->character ); } } if (d_next == dclose) d_next = d_next->next; dclose->connected = CON_NOT_PLAYING; return; } ** Add four new functions, one to do the lookup, ** another function to check for bans, ** a function to recycle closed descriptors, and ** finally a function to recycle dummy_args. ** Remember to define them all in merc.h so they ** can be called from other files. /* does the lookup, changes the hostname, and dies */ void lookup_address(DUMMY_ARG *darg) { struct hostent *from = 0; struct hostent ent; char buf[16384]; // enough ?? int err; gethostbyaddr_r( darg->buf, sizeof(darg->buf), AF_INET, &ent, buf, 16384, &from, &err); if (from && from->h_name) { free_string(darg->d->host); darg->d->host = str_dup(from->h_name); } darg->d->lookup_status++; free_string(darg->buf); darg->status = 0; pthread_exit(0); } /* returns true if the site is banned */ bool check_banned( DESCRIPTOR_DATA *dnew ) { BAN_DATA *pban; for (pban = ban_list; pban != NULL; pban = pban->next) if (!str_suffix(pban->name, dnew->host)) return TRUE; return FALSE; } /* recycles closed descriptors */ void recycle_descriptors() { DESCRIPTOR_DATA *dclose; DESCRIPTOR_DATA *dclose_next; for (dclose = descriptor_list; dclose; dclose = dclose_next) { dclose_next = dclose->next; if (dclose->lookup_status != STATUS_CLOSED) continue; /* * First let's get it out of the descriptor list. */ if ( dclose == descriptor_list ) { descriptor_list = descriptor_list->next; } else { DESCRIPTOR_DATA *d; for (d = descriptor_list; d && d->next != dclose; d = d->next) ; if (d != NULL) d->next = dclose->next; else { bug( "Recycle_descriptors: dclose not found.", 0 ); continue; } } /* * Clear out that memory */ free_string( dclose->host ); free_mem( dclose->outbuf, dclose->outsize ); /* * Bye bye mr. Descriptor. */ close( dclose->descriptor ); /* * And then we recycle */ dclose->next = descriptor_free; descriptor_free = dclose; } } /* recycles used dummy arguments */ void recycle_dummys() { DUMMY_ARG *dummy; DUMMY_ARG *dummy_next; for (dummy = dummy_list; dummy; dummy = dummy_next) { dummy_next = dummy->next; if (dummy->status == 1) continue; // being used if (dummy == dummy_list) { dummy_list = dummy_list->next; } else { DUMMY_ARG *prev; /* we find the prev dummy arg */ for (prev = dummy_list; prev && prev->next != dummy; prev = prev->next) ; if (prev != NULL) prev->next = dummy->next; else { bug( "Recycle_dymmys: dummy not found.", 0 ); continue; } /* recycle */ dummy->next = dummy_free; dummy_free = dummy; } } } ********************************** ********************************** **** Handling New Connections **** **** And recycling **** ********************************** ********************************** /* * File: comm.c */ ** In nanny() we should edit case CON_GET_NAME, so only ** descriptors that have passed the lookup can enter the ** game, as well as check for banned sites. ** after this check if ( argument[0] == '\0' ) { close_socket( d ); return; } ** Add these two checks: if (d->lookup_status != STATUS_DONE) { write_to_buffer( d, "Making a dns lookup, please have patience.\n\rWhat be thy name?", 0 ); return; } if (check_banned(d)) { write_to_buffer( d, "Your site has been banned from this mud\n\r", 0 ); close_socket( d ); return; } /* * update.c */ ** Edit update_handler() so it calls this function every pass ** (put it somewhere in the top, but make sure it is called ** every time update_handler is called, so do NOT put it in ** one of the pulse's. recycle_descriptors(); ** Now somewhere in update_handler() add this call, ** where you put it doesn't matter, perhaps in pulse_area ** since it doesn't need to be called that often. recycle_dummys(); ******************************* ******************************* **** Locking Memory Access **** ******************************* ******************************* /* * File: db.c */ ** Edit the functions alloc_mem(), free_mem() and alloc_perm(). ** in each of the functions you must begin with: pthread_mutex_lock(&memory_mutex); ** and make sure to end the function with: pthread_mutex_unlock(&memory_mutex); ** This will lock and unlock access to the memory list, ** so only one thread can use it at a given time. ** Also ***IMPORTANT** since alloc_mem() calls alloc_perm() ** if it doesn't have any free memory of the needed size, ** you **MUST** unlock and lock the mutex before and after ** the call to alloc_perm(). All in all, you should add ** 8 mutex calls in db.c (4 locks and 4 unlocks). ***************** ***************** **** Roundup **** ***************** ***************** Let's take a quick look at possible crashes in the code, the most important thing to remember is that d->host might not be allocated, so we have to worry when accessing this. In comm.c we should make the following changes : First make sure nanny() can handle CON_NOT_PLAYING (just break on those) Then in read_from_descriptor() make these changes : (after) if ( d->incomm[0] != '\0' ) return TRUE; (add this) if (d->connected == CON_NOT_PLAYING) return TRUE; (change these lines) else sprintf( log_buf, "%s input overflow!", d->host ); (to these) else if (d->lookup_status == STATUS_DONE) { sprintf( log_buf, "%s input overflow!", d->host ); log_string( log_buf ); } And in the function read_from_buffer() we should do the same, to avoid crashes if we try to access d->host when it's not allocated. (change) else sprintf( log_buf, "%s input spamming!", d->host ); (to) else if (d->lookup_status == STATUS_DONE) { sprintf( log_buf, "%s input overflow!", d->host ); log_string( log_buf ); } also, in interp.c the function interpret() should have a little extra check to avoid crashes when after freeing a character. (add this at the very beginning of interpret() ) if (!ch) return; Now look through your code, whenever theres a function that does something to d->host (d being a descriptor), then you need to make sure that d->lookup_status == STATUS_DONE. One example would be the immortal command 'users' which lists all active connections to the mud.