/* net/netserver.h This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include #include #include #ifndef _WIN32 #include #include #include #include #include #endif #include #include #include "sys/sys.h" #include "core/application.h" #include "core/gameserver.h" #include "core/netclient.h" #include "core/netserver.h" #include "core/cvar.h" #include "core/func.h" #include "core/core.h" #include "core/range.h" #include "core/stats.h" #include "core/zone.h" #ifdef _WIN32 typedef int socklen_t; #endif namespace core { NetServer::NetServer(std::string const host, unsigned int const port) { con_print << "^BInitializing network server..." << std::endl; con_debug << " protocol version " << PROTOCOLVERSION << std::endl; // initialize variables netserver_fd = -1; netserver_error = true; // initialize socket netserver_fd = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (netserver_fd == -1) { con_error << "Network can't create socket!" << std::endl; //perror("socket"); return; } // Get the local adress to bind to netserver_addr.sin_family = AF_INET; netserver_addr.sin_port = htons(port); if (host.size()) { netserver_addr.sin_addr.s_addr = inet_addr(host.c_str()); if (netserver_addr.sin_addr.s_addr == INADDR_NONE) { con_error << "Network invalid address " << host << "!" << std::endl; return; } } else { netserver_addr.sin_addr.s_addr = htonl(INADDR_ANY); } memset(netserver_addr.sin_zero, '\0', sizeof(netserver_addr.sin_zero)); // bind the local address to the socket ( note the typecast) if (::bind(netserver_fd, (struct sockaddr *) &netserver_addr, sizeof(struct sockaddr)) == -1) { con_error << "Network can't bind to local address!" << std::endl; //perror("bind"); return; } con_print << " listening on " << inet_ntoa(netserver_addr.sin_addr) << ":" << ntohs(netserver_addr.sin_port) << std::endl; // add the listening socket to the file descriptor set FD_ZERO(&serverset); #ifdef _WIN32 FD_SET((unsigned int) netserver_fd, &serverset); #else FD_SET(netserver_fd, &serverset); #endif netserver_error = false; } NetServer::~NetServer() { con_print << "^BShutting down network server..." << std::endl; std::string netmsg("disconnect\n"); // delete all clients Clients:: iterator it; for (it = clients.begin(); it != clients.end(); ++it) { // notify the game server if ((*it)->state() == NetClient::Connected) server()->player_disconnect((*it)->player()); (*it)->send_raw(netmsg); (*it)->transmit(); delete(*it); } clients.clear(); if (valid()) { #ifdef _WIN32 closesocket(fd()); #else close(fd()); #endif } } void NetServer::abort() { netserver_error = true; } // find the client corresponding to a player NetClient *NetServer::find_client(Player const *player) { for (Clients::iterator it = clients.begin(); it != clients.end(); ++it) { if ((*it)->player() == player) { return (*it); } } return 0; } // remove disconnected clients void NetServer::reap() { for (Clients:: iterator it = clients.begin(); it != clients.end();) { NetClient *client = *it; if (client->client_timeout + NETTIMEOUT < application()->time()) { // client timed out, send a disconnect send_disconnect((*it)); // print a message std::string message("^B"); message.append(client->player()->name()); message.append(" ^Btimed out."); if (client->state() == NetClient::Connected) { server()->broadcast(message, client->player()); } else { con_print << message << std::endl; } } if (client->error()) { // notify other clients for (Clients::iterator cit = clients.begin(); cit != clients.end(); ++cit) { if ((*cit) != (*it)) { send_player_disconnect_info((*cit), (*it)->player()); } } if (client->state() == NetClient::Connected) { // notify the game server server()->player_disconnect((*it)->player()); } // remove the client delete client; clients.erase(it++); } else { ++it; } } } void NetServer::receive() { if (error()) return; timeval timeout; timeout.tv_sec = 0; // 1,000,000 microseconds = 1 second, default is 2500 timeout.tv_usec = (long) Cvar::net_selecttimeout->value(); if (timeout.tv_usec < 0) timeout.tv_usec = 0; else if (timeout.tv_usec > 1000000) { timeout.tv_sec = timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; } fd_set readset = serverset; int nb = select(fd() + 1, &readset, NULL, NULL, &timeout); if (nb == -1) { #ifndef _WIN32 // ncurses needs SIGWINCH catched if (errno == EINTR) { return; } #endif con_error << "Network error on select()" << std::endl; this->abort(); return; } if (nb && FD_ISSET(fd(), &readset)) { // receive incoming data struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); memset(recbuf, '\0', sizeof(recbuf)); ssize_t bytes_received = ::recvfrom(fd(), recbuf, FRAMESIZE - 1, 0, (struct sockaddr *) & client_addr, &client_addr_len); if (bytes_received == -1) { con_error << "Network error on recvfrom()!" << std::endl; this->abort(); return; } else { //con_debug << "Incoming data '" << recbuf << "'"<< bytes_received << " bytes" << std::endl; } Stats::network_bytes_received += bytes_received; // originator std::string client_host(inet_ntoa(client_addr.sin_addr)); unsigned int client_port = ntohs(client_addr.sin_port); // get messages from clients bool msg_received = false; for (Clients::iterator it = clients.begin(); it != clients.end() && !msg_received; ++it) { NetClient *client = *it; if ((client->host() == client_host) && (client->port() == (int) client_port)) { // receive data client->receive(recbuf); // process parsed messages while (client->has_messages()) { std::string message; client->retreive(message); parse_incoming_message(client, message); } msg_received = true; } } if (!msg_received) { // new connection // FIXME maxclients NetClient *client = client_connect(client_host , client_port); if (client) { // receive data client->receive(recbuf); // process parsed messages while (client->has_messages()) { std::string message; client->retreive(message); parse_incoming_message(client, message); } } } } // remove dead connections reap(); } NetClient * NetServer::client_connect(std::string const host, int const port) { //con_print << "Client " << host << ":" << port << " connected\n"; NetClient *client = new NetClient(host, port, fd()); if (client->error()) { con_warn << client->host() << ":" << client->port() << " connection failed!\n"; delete(client); return 0; } clients.push_back(client); client->player()->set_dirty(false); client->player()->reputation().set_dirty(false); return client; } void NetServer::client_initialize(NetClient *client) { // send info types send_infotypes(client); client->transmit(); // send zones for (Zone::Registry::iterator it = Zone::registry().begin(); it != Zone::registry().end(); ++it) { send_zone_update(client, (*it).second); } // send player list for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); ++it) { if (((*it)->id() != client->player()->id())) { send_player_update(client, (*it)); } } // send connect completed std::string connect("connect\n"); client->send_raw(connect); client->transmit(); // set client state to pending client->client_state = NetClient::Pending; // send welcome message std::string welcome("^B"); welcome.append(Cvar::sv_name->str()); send_message(client, Message::Info, welcome); welcome.assign("^B"); welcome.append(Cvar::sv_description->str()); send_message(client, Message::Info, welcome); client->transmit(); } // send updates to one client void NetServer::client_frame(NetClient *client, unsigned long timestamp) { if (client->state() != NetClient::Connected) return; Zone *zone = client->player()->zone(); if (zone) { if (client->player()->zonechange()) { // send zone info send_zone_update(client, zone); // send entities in zone if (client->player()->zone()) { for (Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); ++it) { Entity *entity = (*it); if (!entity->died()) { send_entity_create(client, entity); } } } // the actual zone change is send by the "pif" message } else { // send a server frame marker send_frame_marker(client, server()->timer().timestamp()); // client location for server-side distance checks math::Vector3f location; if (client->player()->control()) { location.assign(client->player()->control()->location()); } else if (client->player()->view()) { location.assign(client->player()->view()->location()); } // send updates for entities in the zone for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); ++it) { Entity *entity = (*it).second; if (entity->zone() == zone) { if (entity->died()) { if (!entity->entity_created) { send_entity_delete(client, entity); } } else if (entity->entity_created) { if ((entity->type() != Entity::Projectile) || (math::distance(location, entity->location()) < range::fxdistance)) { send_entity_create(client, entity); } } else if (entity->oldzone()) { // this entity has entered the zone send_entity_create(client, entity); } else if (entity->dirty()) { // FIXME only within visual range send_entity_update(client, entity); } } else if (entity->oldzone() == zone) { // the entity has left the zone send_entity_delete(client, entity); } } } } // send reputation updates if (client->player()->reputation().dirty()) { send_player_reputation(client); } // send inventory update for control // FIXME this should be done for all player assets if (client->player()->control() && client->player()->control()->inventory() && client->player()->control()->inventory()->dirty()) { //con_debug << "SERVER Sending inventory for entity " << client->player()->control()->id() << " server timestamp " << game()->timestamp() << std::endl; send_inventory_update(client, client->player()->control(), client->player()->control()->inventory()->timestamp()); } // send inventory updates for view if (client->player()->view() && client->player()->view()->inventory() && client->player()->view()->inventory()->dirty()) { //con_debug << "SERVER Sending inventory for entity " << client->player()->view()->id() << " server timestamp " << game()->timestamp() << std::endl; send_inventory_update(client, client->player()->view(), client->player()->view()->inventory()->timestamp()); } // send updates for other players for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); ++it) { if (((*it)->id() != client->player()->id()) && ((*it)->dirty())) { send_player_update(client, (*it)); } } } // transmit pending packets to all clients void NetServer::transmit() { // send updates to each client for (Clients::iterator it = clients.begin(); it != clients.end(); ++it) { NetClient *client = *it; client->transmit(); } } // run a network server frame, send updates to clients void NetServer::frame(unsigned long timestamp) { /* FIXME Only entities within visual range should send updates (1024 game units?) Fix reliable messages It would be nice if players with the highest ping got their updates first */ // send updates to each client for (Clients::iterator it = clients.begin(); it != clients.end(); ++it) { NetClient *client = *it; client->transmit(); // send game state changes if (client->state() == NetClient::Connected) client_frame(client, timestamp); if (client->player()->dirty() || client->player()->zonechange()) { send_player_update(client); } client->transmit(); } // clear dirty state for (Clients::iterator it = clients.begin(); it != clients.end(); ++it) { NetClient *client = *it; if (client->player()->dirty() || client->player()->zonechange()) { client->player()->set_dirty(false); client->player()->set_zonechange(false); client->player()->reputation().set_dirty(false); } } } // send outgoing messages to clients /** * The following messages can be send to a client * * frame * ent * die * inf * inv * ping * rep * sup * * msg * supported message channels are "info" "public" "rcon" and "snd" * "snd" is a special channel to transmit sound events * box * send a messagebox * zone */ // send a text message on a specified channel to a single client void NetServer::send_message(NetClient *client, const Message::Channel channel, const std::string & message) { if (!message.size()) return; std::string msg_channel; switch (channel) { case core::Message::Info: // Info message msg_channel.assign("info"); break; case core::Message::Local: // Chat message in the local zone msg_channel.assign("local"); break; case core::Message::Public: // Public chat message msg_channel.assign("public"); break; case core::Message::Private: // Private chat message msg_channel.assign("private"); break; case core::Message::RCon: // RCon message msg_channel.assign("rcon"); break; case core::Message::Sound: // Sound event msg_channel.assign("snd"); break; default: con_warn << "message on unknown channel " << channel << "!" << std::endl; return; break; } std::string str_message(message); aux::strip_quotes(str_message); std::string msg("msg "); msg.append(msg_channel); msg += ' '; msg.append(str_message); msg += '\n'; client->send_raw(msg); } // send a messagebox to a single client void NetServer::send_messagebox(NetClient *client, const std::string & text, const std::string &label1, const std::string command1, const std::string &label2, const std::string command2) { std::string str_text(text); aux::strip_quotes(str_text); std::string str_label1(label1); aux::strip_quotes(str_label1); std::string str_command1(command1); aux::strip_quotes(str_command1); std::string str_label2(label2); aux::strip_quotes(str_label2); std::string str_command2(command2); aux::strip_quotes(str_command2); std::ostringstream msg(""); msg << "box "; msg << "\"" << str_text << '\"' << ' '; msg << "\"" << str_label1 << '\"' << ' '; msg << "\"" << str_command1 << '\"' << ' '; msg << "\"" << str_label2 << '\"' << ' '; msg << "\"" << str_command2 << '\"' << '\n'; client->send_raw(msg.str()); } // disconnect a client void NetServer::send_disconnect(NetClient *client) { client->send_raw("disconnect\n"); client->transmit(); client->abort(); } // send a "frame" message to a client void NetServer::send_frame_marker(NetClient *client, unsigned long timestamp) { std::ostringstream msg(""); msg << "frame " << timestamp << "\n"; if (client->state() == NetClient::Connected) { client->send_raw(msg.str()); } } // send a "ent" create entity message to all clients void NetServer::send_entity_create(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg; msg << "ent " << entity->id() << " " << entity->type() << " "; entity->serialize_server_create(msg); msg << '\n'; client->send_raw(msg.str()); // send entity menus for (Entity::Menus::iterator it = entity->menus().begin(); it != entity->menus().end(); ++it) { msg.clear(); msg.str(""); msg << "menu " << entity->id() << " "; Descriptions::serialize((*it), msg); msg << '\n'; client->send_raw(msg.str()); // con_debug << "sending menu " << entity->label() << " " << (*it)->label() << std::endl; } } } // send a "die" delete entity message to a client void NetServer::send_entity_delete(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg(""); msg << "die " << entity->id() << '\n'; client->send_raw(msg.str()); } } // send a "sup" server update entity message to a client void NetServer::send_entity_update(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg; msg << "sup " << entity->id() << " " << entity->type() << " "; entity->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } } // send a "zone" update zone message to a client void NetServer::send_zone_update(NetClient *client, Zone *zone) { std::ostringstream msg; msg << "zone " << zone->id() << " "; zone->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a "pif" update player information to a single player void NetServer::send_player_update(NetClient *client) { std::ostringstream msg; msg << "pif 0 "; client->player()->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a "rep" update player reputation to a single player void NetServer::send_player_reputation(NetClient *client) { std::ostringstream msg; msg << "rep "; client->player()->reputation().serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a short "pif" update player information to a single player void NetServer::send_player_update(NetClient *client, Player *player) { std::ostringstream msg; msg << "pif " << player->id() << ' '; player->serialize_short_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a "pid" player disconnect information void NetServer::send_player_disconnect_info(NetClient *client, Player *player) { std::ostringstream msg; msg << "pid " << player->id() << '\n'; client->send_raw(msg.str()); } // send a "inf" info record void NetServer::send_info_update(NetClient *client, Info *info) { if (!info || !info->type()) return; std::ostringstream msg; msg << "inf " << info->id() << " \"" << info->type()->label() << "\" \"" << info->label() << "\" "; info->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send "inf" info types void NetServer::send_infotypes(NetClient *client) { std::ostringstream msg; msg << "inf 0 " << InfoType::registry().size(); for (InfoType::Registry::const_iterator it = InfoType::registry().begin(); it != InfoType::registry().end(); ++it) { msg << " \"" << (*it)->label() << "\""; } msg << '\n'; client->send_raw(msg.str()); } // send a "inv" inventory update void NetServer::send_inventory_update(NetClient *client, Entity *entity, const unsigned long timestamp) { if (!entity || !entity->inventory()) return; // send message header std::ostringstream msg; msg << "inv " << entity->id() << " " << game()->timestamp() << " "; // send full update marker bool send_full_update = false; if (entity->inventory()->timestamp_erase() >= timestamp) { send_full_update = true; msg << "1 "; } else { send_full_update = false; msg << "0 "; } // send items size_t nbitems = 0; std::ostringstream itemstr; for (Inventory::Items::const_iterator it = entity->inventory()->items().begin(); it != entity->inventory()->items().end(); ++it) { const Item *item = (*it); if (send_full_update || (item->timestamp() >= timestamp)) { itemstr << item->id() << " " << item->info()->id() << " "; item->serialize_server_update(itemstr); nbitems++; } } msg << nbitems; if (nbitems) { msg << " " << itemstr.str(); } msg << '\n'; client->send_raw(msg.str()); } // parse incoming client messages /** * The following incoming protocol messages are parsed; * * disconnect * cmd * cup * inf * pif * ping * msg * info * req * inv */ void NetServer::parse_incoming_message(NetClient *client, const std::string & message) { if (!message.size()) return; std::stringstream msgstream(message); std::string command; msgstream >> command; if (command.compare("disconnect") == 0 ) { // disconnect client->abort(); return; } else if (command.compare("connect") == 0) { // connection request // connect is the first command expected from the client if (client->state() != NetClient::Connecting) return; unsigned int protover; if (msgstream >> protover) { if (protover != PROTOCOLVERSION) { // send protocol version mismatch notification std::stringstream netmsgstream(""); netmsgstream << "^WProtocol version mismatch: "; netmsgstream << "client " << protover << " server " << PROTOCOLVERSION << "!\n"; con_print << client->host() << ":" << client->port() << " " << netmsgstream.str() << std::endl; send_message(client, Message::Info, netmsgstream.str()); send_messagebox(client, netmsgstream.str(), "", "", "", ""); send_disconnect(client); } else { client_initialize(client); } } else { std::string message("Unknown client protocol version!"); con_print << client->host() << ":" << client->port() << " " << message << std::endl; send_message(client, Message::Info, message); send_disconnect(client); } // player guid std:: string guid; if (msgstream >> guid) { client->player()->guid().assign(guid); } con_print << client->host() << ":" << client->port() << " connected with UID " << client->player()->guid().str() << std::endl; return; } else if (command.compare("pif") == 0) { // pif - update player information // client connection is completed on the first pif std::string oldname(client->player()->name()); client->player()->receive_client_update(msgstream); if (client->state() == NetClient::Pending) { client->client_state = NetClient::Connected; server()->player_connect(client->player()); con_print << client->host() << ":" << client->port() << " id " << client->player()->id() << " joins as ^B" << client->player()->name() << std::endl; } else if ((client->state() == NetClient::Connected) && (client->player()->name() != oldname)) { std::string netmsg("^B"); netmsg.append(oldname); netmsg.append(" ^Brenamed to "); netmsg.append(client->player()->name()); server()->broadcast(netmsg); } } else if (command.compare("ping") == 0) { unsigned long timestamp; if (msgstream >> timestamp) { client->player()->set_ping(server()->timer().timestamp() - timestamp); } return; } if (client->state() != NetClient::Connected) return; if (command.compare("cmd") == 0) { if (message.size() > command.size() + 1) { std::string cmdline(message.substr(command.size() + 1)); server()->exec(client->player(), cmdline); } return; } else if (command.compare("cup") == 0) { // cup - client update entity //con_debug << message << "\n"; unsigned int id; if (msgstream >> id) { Entity *entity = Entity::find(id); if (!entity) { con_warn << client->host() << ":" << client->port() << " update for unknown entity " << id << "\n"; return; } if (entity->type() != Entity::Controlable) { con_warn << client->host() << ":" << client->port() << " update for non-controlable entity " << id << "\n"; return; } EntityControlable *entitycontrolable = (EntityControlable *)entity; if (entitycontrolable->owner() != client->player()) { con_warn << client->host() << ":" << client->port() << " update for non-owned entity " << id << "\n"; return; } entitycontrolable->set_dirty(true); entitycontrolable->receive_client_update(msgstream); } return; } else if (command.compare("req") == 0) { // request entity unsigned int id; if (!(msgstream >> id)) { con_warn << "^B" << client->player()->name() << "^W invalid entity request" << std::endl; return; } Entity *entity = Entity::find(id); if (entity) { send_entity_create(client, entity); } else { con_warn << "^B" << client->player()->name() << "^W entity request for unkown entity " << id << std::endl; } return; } else if (command.compare("inf") == 0) { // request information record unsigned int id; if (!(msgstream >> id)) { con_warn << "^B" << client->player()->name() << "^W invalid info request" << std::endl; return; } Info *info = 0; if (id) { info = Info::find(id); if (info) { //con_debug << "Sending info for " << info->id() << " " << info->type()->label() << ":" << info->label() << std::endl; send_info_update(client, info); client->transmit(); } } return; } else if (command.compare("inv") == 0) { // request information record unsigned int id; unsigned long client_timestamp; if (!(msgstream >> id >> client_timestamp)) { con_warn << "^B" << client->player()->name() << "^W invalid inventory request" << std::endl; return; } Entity *entity = Entity::find (id); if (!entity) { con_warn << "^B" << client->player()->name() << "^W invalid request for non-existing entity " << id << std::endl; return; } if (!entity->inventory()) { con_warn << "^B" << client->player()->name() << "^W invalid request for entity " << id << " without inventory" << std::endl; return; } //con_debug << "SERVER Sending inventory for entity " << id << " client timestamp " << client_timestamp << std::endl; send_inventory_update(client, entity, client_timestamp); client->transmit(); } else if (command.compare("rcon") == 0) { if ((message.size() > command.size() + 1) && Cvar::sv_password->str().size()) { if ((Cvar::sv_password->str().compare(client->player()->rconpassword()) == 0)) { con_print << "^B" << client->player()->name() << "^F rcon: " << message.substr(command.size() + 1) << std::endl; core::CommandBuffer::exec(); core::console()->set_rcon(true); core::cmd() << message.substr(command.size() + 1) << "\n"; core::CommandBuffer::exec(); while (console()->rconbuf().size()) { send_message(client, Message::RCon, (*console()->rconbuf().begin())); core::console()->rconbuf().pop_front(); } // disable rcon buffering console()->set_rcon(false); } else { send_message(client, Message::Info, "rcon access denied"); con_warn << "^B" << client->player()->name() << "^W rcon access denied" << std::endl; } } return; } else if (command.compare("msg") == 0 ) { std::string channel; msgstream >> channel; if (!channel.size()) return; const size_t subpos = command.size() + channel.size() + 2; if (message.size() <= subpos) return; if (channel.compare("public") == 0) { server()->shout(client->player(), message.substr(subpos)); } else if (channel.compare("local") == 0) { server()->say(client->player(), message.substr(subpos)); } else if (channel.compare("private") == 0) { server()->say(client->player(), message.substr(subpos)); } return; } } }