// This file may be redistributed and modified only under the terms of // the GNU General Public License (See COPYING for details). // Copyright (C) 2000,2001 Alistair Riddoch #include "CommClient.h" #include "CommServer.h" #include "ServerRouting_methods.h" #include "protocol_instructions.h" #include #include #include #include #include extern "C" { #include } using Atlas::Message::Object; static const bool debug_flag = false; static inline char *pack_uint32(uint32_t data, char *buffer, unsigned int *size) { uint32_t netorder; netorder = htonl(data); memcpy(buffer, &netorder, sizeof(uint32_t)); *size += sizeof(uint32_t); return buffer+sizeof(uint32_t); } static inline char *unpack_uint32(uint32_t *dest, char *buffer) { uint32_t netorder; memcpy(&netorder, buffer, sizeof(uint32_t)); *dest = ntohl(netorder); return buffer+sizeof(uint32_t); } CommServer::CommServer(const std::string & ruleset, const std::string & ident) : metaserverTime(-1), useMetaserver(true), identity(ident), server(*new ServerRouting(*this, ruleset, ident)) { } CommServer::~CommServer() { client_set_t::const_iterator I = clients.begin(); for(; I != clients.end(); I++) { delete *I; } delete &server; } bool CommServer::setup(int port) { // Nasty low level socket code to set up listen socket. This should be // replaced with a socket class library. struct sockaddr_in sin; serverPort = port; serverFd = socket(PF_INET, SOCK_STREAM, 0); if (serverFd < 0) { return false; } int flag=1; setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = 0L; if (bind(serverFd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { close(serverFd); return false; } listen(serverFd, 5); // If we are not going to use the metaserver, then out work here is // complete. if (!useMetaserver) { return true; } // Establish socket for communication with the metaserver memset(&meta_sa, 0, sizeof(meta_sa)); meta_sa.sin_family = AF_INET; meta_sa.sin_port = htons(metaserverPort); std::string mserver("metaserver.worldforge.org"); if (global_conf->findItem("cyphesis", "metaserver")) { mserver = global_conf->getItem("cyphesis", "metaserver"); } debug(std::cout << "Connecting to metaserver..." << std::endl << std::flush;); struct hostent * ms_addr = gethostbyname(mserver.c_str()); if (ms_addr == NULL) { log(WARNING, "metaserver lookup failed. Disabling metaserver"); useMetaserver = false; return true; } memcpy(&meta_sa.sin_addr, ms_addr->h_addr_list[0], ms_addr->h_length); metaFd = socket(AF_INET, SOCK_DGRAM, 0); if (metaFd < 0) { log(WARNING, "Could not connect to metaserver. Disabling metaserver"); useMetaserver = false; perror("socket"); } return true; } bool CommServer::accept() { // Low level socket code to accept a new client connection, and create // the associated commclient object. struct sockaddr_in sin; unsigned int addr_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_port = htons(serverPort); sin.sin_addr.s_addr = 0L; debug(std::cout << "Accepting.." << std::endl << std::flush;); int asockfd = ::accept(serverFd, (struct sockaddr *)&sin, &addr_len); if (asockfd < 0) { return false; } debug(std::cout << "Accepted" << std::endl << std::flush;); CommClient * newcli = new CommClient(*this, asockfd, sin.sin_port); newcli->setup(); // Add this new client to the list. clients.insert(newcli); return true; } inline void CommServer::idle() { // Update the time, and get the core server object to process // stuff. time_t ctime = time(NULL); if ((ctime > (metaserverTime + 5 * 60)) && useMetaserver) { debug(std::cout << "Sending keepalive" << std::endl << std::flush;); metaserverTime = ctime; metaserverKeepalive(); } // server.idle() is inlined, and simply calls the world idle method, // which is not directly accessible from here. server.idle(); } void CommServer::loop() { // This is the main code loop. // Classic select code for checking incoming data or client connections. // It may be beneficial to re-write this code to use the poll(2) system // call. fd_set sock_fds; int highest; struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; FD_ZERO(&sock_fds); FD_SET(serverFd, &sock_fds); if (useMetaserver) { FD_SET(metaFd, &sock_fds); highest = std::max(serverFd, metaFd); } else { highest = serverFd; } client_set_t::const_iterator I; for(I = clients.begin(); I != clients.end(); I++) { if (!(*I)->isOpen()) { continue; } int client_fd = (*I)->getFd(); FD_SET(client_fd, &sock_fds); if (client_fd > highest) { highest = client_fd; } } highest++; int rval = select(highest, &sock_fds, NULL, NULL, &tv); if (rval < 0) { return; } std::set obsoleteConnections; for(I = clients.begin(); I != clients.end(); I++) { CommClient * client = *I; if (!client->isOpen()) { obsoleteConnections.insert(client); continue; } if (FD_ISSET(client->getFd(), &sock_fds)) { if (client->peek() != EOF) { if (client->read()) { debug(std::cout << "Removing client due to failed negotiation" << std::endl << std::flush;); obsoleteConnections.insert(client); } } else if (client->eof()) { obsoleteConnections.insert(client); } else { // It is not clear why this happens. log(WARNING, "Client peek failed, but eof() is not set"); obsoleteConnections.insert(client); } } } if (FD_ISSET(serverFd, &sock_fds)) { debug(std::cout << "selected on server" << std::endl << std::flush;); accept(); } if (useMetaserver && FD_ISSET(metaFd, &sock_fds)) { debug(std::cout << "selected on metaserver" << std::endl << std::flush;); metaserverReply(); } std::set::const_iterator J = obsoleteConnections.begin(); for(; J != obsoleteConnections.end(); ++J) { removeClient(*J); } // Once we have done all socket related stuff, proceed with processing // the world. idle(); } inline void CommServer::removeClient(CommClient * client, char * error_msg) { Object::MapType err; err["message"] = Object(error_msg); Object::ListType eargs(1,Object(err)); Error e(Error::Instantiate()); e.SetArgs(eargs); if (client->online() && client->isOpen()) { client->send(e); } clients.erase(client); delete client; } void CommServer::removeClient(CommClient * client) { removeClient(client,"You caused exception. Connection closed"); } #define MAXLINE 4096 void CommServer::metaserverKeepalive() { char mesg[MAXLINE]; unsigned int packet_size=0; pack_uint32(SKEEP_ALIVE, mesg, &packet_size); sendto(metaFd,mesg,packet_size,0, (sockaddr *)&meta_sa, sizeof(meta_sa)); } void CommServer::metaserverReply() { char mesg[MAXLINE]; char *mesg_ptr; uint32_t handshake=0, command=0; struct sockaddr addr; socklen_t addrlen; unsigned int packet_size; if (recvfrom(metaFd, mesg, MAXLINE, 0, &addr, &addrlen) < 0) { log(WARNING, "WARNING: No reply from metaserver"); return; } mesg_ptr = unpack_uint32(&command, mesg); if(command == HANDSHAKE) { mesg_ptr = unpack_uint32(&handshake, mesg_ptr); debug(std::cout << "Server contacted successfully." << std::endl << std::flush;); packet_size = 0; mesg_ptr = pack_uint32(SERVERSHAKE, mesg, &packet_size); mesg_ptr = pack_uint32(handshake, mesg_ptr, &packet_size); sendto(metaFd,mesg,packet_size,0,(sockaddr*)&meta_sa,sizeof(meta_sa)); } } void CommServer::metaserverTerminate() { char mesg[MAXLINE]; unsigned int packet_size=0; pack_uint32(TERMINATE, mesg, &packet_size); sendto(metaFd,mesg,packet_size, 0, (sockaddr *)&meta_sa, sizeof(meta_sa)); }