/** * NMT_SERVERSHAKE - last part of the 3-way server handshake * Response: None * @param in * @param out */ void MetaServer::processSERVERSHAKE(const MetaServerPacket& in, MetaServerPacket& out) { unsigned int shake = in.getIntData(4); std::string ip = in.getAddressStr(); m_Logger.debug("processSERVERSHAKE(%u)", shake); /** * If a handshake exists, we can know the following: * 1) the client is valid * 2) they have a non-expired handshake * * What we do then is to: * 1) register a server session * 2) de-register the handshake ( maybe we just let it expire ? ) */ if( msdo.handshakeExists(shake) ) { std::stringstream ss; ss << in.getPort(); msdo.addServerSession(ip); msdo.addServerAttribute(ip,"port", ss.str() ); // clear stream first ss.str(""); ss << in.getAddressInt(); msdo.addServerAttribute(ip,"ip_int", ss.str() ); } }
void MetaServer::processCLIENTFILTER(const MetaServerPacket& in, MetaServerPacket& out) { unsigned int name_length = in.getIntData(4); unsigned int value_length = in.getIntData(8); std::string msg = in.getPacketMessage(12); std::string name = msg.substr(0,name_length); std::string value = msg.substr(name_length); std::string ip = in.getAddressStr(); m_Logger.debug("processCLIENTFILTER(%s,%s)", name.c_str(), value.c_str() ); msdo.addClientFilter(ip,name,value); out.setPacketType(NMT_NULL); }
/** * @param in incoming metaserver packet * * NMT_SERVERKEEPALIVE indicates a keep alive for a server, also serves as a "registration" * Response Packet Type: NMT_HANDSHAKE * - pack packet type * - pack random number */ void MetaServer::processSERVERKEEPALIVE(const MetaServerPacket& in, MetaServerPacket& out) { uint32_t i = msdo.addHandshake(); if ( i > 0 ) { m_Logger.debug("processSERVERKEEPALIVE(%u)", i); out.setPacketType(NMT_HANDSHAKE); out.addPacketData(i); out.setAddress( in.getAddress() ); } }
void MetaServer::processCLIENTSHAKE(const MetaServerPacket& in, MetaServerPacket& out) { unsigned int shake = in.getIntData(4); std::string ip = in.getAddressStr(); m_Logger.debug("processCLIENTSHAKE(%u)", shake ); if( msdo.handshakeExists(shake) ) { std::stringstream ss; ss << in.getPort(); msdo.addClientSession(ip); msdo.addClientAttribute(ip,"port", ss.str() ); } }
void MetaServer::processTERMINATE(const MetaServerPacket& in, MetaServerPacket& out) { /** * For backwards compat, we make a regular "TERM" packet end a server session * and a TERM packet with any additional data sent signifies a client session. */ if ( in.getSize() > (sizeof(uint32_t)) ) { m_Logger.debug("processTERMINATE-client(%s)", in.getAddressStr().c_str() ); msdo.removeClientSession(in.getAddressStr()); } else { m_Logger.debug("processTERMINATE-server(%s)", in.getAddressStr().c_str() ); msdo.removeServerSession(in.getAddressStr()); } }
/* * I would like to thank this particular integration test * for showing me that the setUp is called for every test, * not once per testsuite as I had originally thought. */ void setUp() { mp1 = new MetaServerPacket(); mp1->setPacketType(NMT_SERVERKEEPALIVE); mp1->setSequence(1); mp1->setTimeOffset(1); mp2 = new MetaServerPacket(); mp2->setPacketType(NMT_SERVERSHAKE); mp2->addPacketData(22); mp2->setSequence(2); mp2->setTimeOffset(2); p = new PacketReader(); file_name = "/tmp/packet_test.bin"; pl = new PacketLogger(file_name); }
int main(int argc, char** argv) { /** * Argument Wrangling * */ boost::program_options::options_description desc( "PacketPlayer" ); boost::program_options::variables_map vm; boost::asio::io_service io_service; //boost::array<char, MAX_PACKET_BYTES> recvBuffer; boost::asio::ip::udp::endpoint sender_endpoint; //size_t bytes_recvd; PacketReader* p; /** * Note: options inside the configuration file that are NOT listed here * become ignored and are not accessible. */ desc.add_options() ( "help,h", "Display help message" ) ( "server", boost::program_options::value<std::string>()->default_value("localhost"), "MetaServer host. \nDefault:localhost" ) ( "port", boost::program_options::value<int>()->default_value(8453), "MetaServer port. \nDefault:8453" ) ( "playonly", boost::program_options::value<std::string>(), "Play output info only.\nDefault: false" ) ( "file", boost::program_options::value<std::string>(), "Binary Packet logfile.\nDefault: none" ) ; try { boost::program_options::store( boost::program_options::parse_command_line(argc, argv, desc), vm ); boost::program_options::notify(vm); /** * Special case for help */ if ( vm.count("help") ) { std::cout << desc << std::endl; return 0; } std::cout << "Server : " << vm["server"].as<std::string>() << std::endl; std::cout << "Port : " << vm["port"].as<int>() << std::endl; std::cout << "---------------" << std::endl; for (boost::program_options::variables_map::iterator it=vm.begin(); it!=vm.end(); ++it ) { if ( it->second.value().type() == typeid(int) ) { std::cout << it->first.c_str() << "=" << it->second.as<int>() << std::endl; } else if (it->second.value().type() == typeid(std::string) ) { std::cout << it->first.c_str() << "=" << it->second.as<std::string>().c_str() << std::endl; } else if (it->second.value().type() == typeid(attribute_list) ) { std::cout << it->first.c_str() << "=Attribute List" << std::endl; } } std::cout << "-------------------------" << std::endl; /** * because boost query is too stupid to take port as an int */ std::stringstream port_str; port_str << vm["port"].as<int>(); /** * Wrangle the file etc options */ std::string file = ""; bool playOnly = false; if( vm.count("playonly") ) { std::string s = vm["playonly"].as<std::string>(); if ( boost::iequals(s,"true") ) { playOnly = true; } } if( vm.count("file") ) { file = vm["file"].as<std::string>(); } if ( file.empty() ) { std::cout << "--file option not specified, but is required" << std::endl; return 13; } /** * Initialize reader */ p = new PacketReader(); int pkts = p->parseBinaryFile(file); if( playOnly ) { while(p->hasPacket()) { MetaServerPacket msp = p->pop(); unsigned int i = msp.getPacketType(); std::string s = msp.getOutBound() ? ">>>>" : "<<<<"; std::cout << "Packet ["; std::cout << std::setfill('0') << std::setw(10) << msp.getSequence(); std::cout << "] " << s << " : " << NMT_PRETTY[i] << std::endl; } } else { std::cout << "TODO: play packets to listed metaserver" << std::endl; } std::cout << "Packet File : " << file << std::endl; std::cout << "Packet Count: " << pkts << std::endl; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } std::cout << "All Done!" << std::endl; return 0; }
int main(int argc, char** argv) { /** * Argument Wrangling * */ boost::program_options::options_description desc( "TestClient" ); boost::program_options::variables_map vm; boost::asio::io_service io_service; std::array<char, MAX_PACKET_BYTES> recvBuffer; boost::asio::ip::udp::endpoint sender_endpoint; size_t bytes_recvd; /** * Note: options inside the configuration file that are NOT listed here * become ignored and are not accessible. */ desc.add_options() ( "help,h", "Display help message" ) ( "server", boost::program_options::value<std::string>()->default_value("localhost"), "MetaServer host. \nDefault:localhost" ) ( "port", boost::program_options::value<int>()->default_value(8453), "MetaServer port. \nDefault:8453" ) ( "attribute", boost::program_options::value<attribute_list>(), "Set client attribute.\nDefault: none" ) ( "filter", boost::program_options::value<attribute_list>(), "Set client filters.\nDefault: none" ) ( "keepalives", boost::program_options::value<int>()->default_value(3), "Number of Keepalives. \nDefault:3" ) ; try { boost::program_options::store( boost::program_options::parse_command_line(argc, argv, desc), vm ); boost::program_options::notify(vm); /** * Special case for help */ if ( vm.count("help") ) { std::cout << desc << std::endl; return 0; } std::cout << "Server : " << vm["server"].as<std::string>() << std::endl; std::cout << "Port : " << vm["port"].as<int>() << std::endl; std::cout << "Keepalives : " << vm["keepalives"].as<int>() << std::endl; std::cout << "---------------" << std::endl; for (boost::program_options::variables_map::iterator it=vm.begin(); it!=vm.end(); ++it ) { if ( it->second.value().type() == typeid(int) ) { std::cout << it->first.c_str() << "=" << it->second.as<int>() << std::endl; } else if (it->second.value().type() == typeid(std::string) ) { std::cout << it->first.c_str() << "=" << it->second.as<std::string>().c_str() << std::endl; } else if (it->second.value().type() == typeid(attribute_list) ) { std::cout << it->first.c_str() << "=Attribute List" << std::endl; } } std::cout << "-------------------------" << std::endl; /** * because boost query is too stupid to take port as an int */ std::stringstream port_str; port_str << vm["port"].as<int>(); boost::asio::ip::udp::socket s(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)); boost::asio::ip::udp::resolver resolver(io_service); boost::asio::ip::udp::resolver::query query(boost::asio::ip::udp::v4(), vm["server"].as<std::string>(), port_str.str() ); boost::asio::ip::udp::resolver::iterator iterator = resolver.resolve(query); /** * Step 1 : keepalive x3 w/ sleep */ /** * 1.1 - send keepalive */ for ( int i=0 ; i < vm["keepalives"].as<int>() ; ++i ) { MetaServerPacket keep; keep.setPacketType(NMT_CLIENTKEEPALIVE); std::cout << "Sending keepalive ... "; s.send_to(boost::asio::buffer(keep.getBuffer(), keep.getSize()), *iterator ); /** * 1.2 - receive handshake */ bytes_recvd = s.receive_from( boost::asio::buffer(recvBuffer), sender_endpoint ); MetaServerPacket shake( recvBuffer, bytes_recvd ); shake.setAddress(sender_endpoint.address().to_string()); shake.setPort(sender_endpoint.port()); std::cout << "Got handshake ... "; unsigned int shake_key = shake.getIntData(4); /** * 1.3 - send clientshake */ MetaServerPacket clientshake; clientshake.setPacketType(NMT_CLIENTSHAKE); clientshake.addPacketData(shake_key); clientshake.setAddress( shake.getAddress() ); s.send_to(boost::asio::buffer(clientshake.getBuffer(), clientshake.getSize()), *iterator ); std::cout << "Sending registration." << std::endl; //std::cout << "Sleeping between keepalives : 2s" << std::endl; //sleep(2); } /** * Step 2 : register attributes if any */ if ( vm.count("attribute") ) { std::cout << "Registering Client Attributes: " << std::endl; attribute_list v = vm["attribute"].as<attribute_list>(); while(!v.empty()) { std::string ele = v.back(); size_t pos = ele.find_first_of("="); if( pos != std::string::npos ) { std::string n = ele.substr(0,pos); std::string v = ele.substr(pos+1); std::cout << " register: " << n << std::endl; std::cout << " value: " << v << std::endl; MetaServerPacket a; a.setPacketType(NMT_CLIENTATTR); a.addPacketData(n.length()); a.addPacketData(v.length()); a.addPacketData(n); a.addPacketData(v); s.send_to(boost::asio::buffer(a.getBuffer(), a.getSize()), *iterator ); } else { std::cout << " Attribute Ignored : " << ele << std::endl; } v.pop_back(); } } /** * Step 3 : register filters */ if ( vm.count("filter") ) { std::cout << "Registering Client Filters: " << std::endl; attribute_list v = vm["filter"].as<attribute_list>(); while(!v.empty()) { std::string ele = v.back(); size_t pos = ele.find_first_of("="); if( pos != std::string::npos ) { std::string n = ele.substr(0,pos); std::string v = ele.substr(pos+1); std::cout << " register: " << n << std::endl; std::cout << " value: " << v << std::endl; MetaServerPacket a; a.setPacketType(NMT_CLIENTFILTER); a.addPacketData(n.length()); a.addPacketData(v.length()); a.addPacketData(n); a.addPacketData(v); s.send_to(boost::asio::buffer(a.getBuffer(), a.getSize()), *iterator ); } else { std::cout << " Filter Ignored : " << ele << std::endl; } v.pop_back(); } } /** * Step 4 : send listreq */ unsigned int total=1; unsigned int from=0; unsigned int packed=0; unsigned int count=0; while(1) { if ( from > total || total == 0 ) break; std::cout << "List Request: " << std::endl; MetaServerPacket req; req.setPacketType(NMT_LISTREQ); req.addPacketData(from); req.setAddress( sender_endpoint.address().to_string() ); req.setPort( sender_endpoint.port() ); s.send_to(boost::asio::buffer(req.getBuffer(), req.getSize()), *iterator ); bytes_recvd = s.receive_from( boost::asio::buffer(recvBuffer), sender_endpoint ); MetaServerPacket resp( recvBuffer, bytes_recvd ); resp.setAddress(sender_endpoint.address().to_string()); resp.setPort(sender_endpoint.port()); if ( resp.getPacketType() != NMT_LISTRESP || resp.getPacketType() == NMT_PROTO_ERANGE ) break; std::cout << "Received server list packet"; total = resp.getIntData(sizeof(uint32_t)*1); // 4 packed = resp.getIntData(sizeof(uint32_t)*2); // 8 std::cout << " Received " << packed << " / " << total << " servers." << std::endl; for ( count = 1 ; count <= packed; count++ ) { unsigned int offset = (sizeof(uint32_t)*2) + (sizeof(uint32_t)*count); //std::cout << " " << count << " / " << offset << " == "; uint32_t ip = resp.getIntData(offset); //std::cout << ip << std::endl; std::cout << "Server: " << resp.IpNetToAscii(ip) << std::endl; } from += packed; } /** * Step 4: send terminate */ std::cout << "Sending Terminate: " << std::endl; MetaServerPacket term; term.setPacketType(NMT_TERMINATE); term.addPacketData(0); // increase size of term packet to indicate client term.setAddress( sender_endpoint.address().to_string() ); term.setPort( sender_endpoint.port() ); s.send_to(boost::asio::buffer(term.getBuffer(), term.getSize()), *iterator ); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } std::cout << "All Done!" << std::endl; return 0; }
/** * NMT_LISTREQ: * 4 bytes type * 4 bytes start index * * Response: * * NMT_LISTRESP * * 4 bytes type * 4 bytes total servers in list * 4 bytes servers in this packet ( triggering client to have another REQ with total-servers offset ) * 4 bytes per server in this packet * */ void MetaServer::processLISTREQ( const MetaServerPacket& in, MetaServerPacket& out) { uint32_t server_index = in.getIntData(4); uint32_t total = msdo.getServerSessionCount(); uint32_t packed_max = total; uint32_t packed = 0; std::list<uint32_t> resp_list; /* * If we are unable to pack the entire list into 1 packet */ if ( (total*sizeof(uint32_t) - (server_index*sizeof(uint32_t)) ) > (MAX_UDP_OUT_BYTES-4-4-4) ) { /* * We want it to round ... just like the price is right, the goal is not to go over */ packed_max = (MAX_UDP_OUT_BYTES-4-4-4) / sizeof(uint32_t); } /* * We hide the craziness of what goes on here inside the single method. * The goal, is to get the list of servers constrained by packed_max */ std::list<std::string> sess_list = msdo.getServerSessionList(server_index,packed_max); std::list<std::string>::iterator list_itr; for ( list_itr = sess_list.begin(); list_itr != sess_list.end() ; list_itr++ ) { /* * Defensive to make sure we're not going to exceed our max */ if ( packed >= packed_max ) break; /* * Defensive to make sure that the item in the list is * actually a valid data item we can send ( orthogonal processes * such as expiry could invalidate at any time ). * * Thus we can iterate over as much of the server list as we need to * and dead items won't count, only those added to the response packet. */ if ( msdo.serverSessionExists( *list_itr )) { /* * Note: see if there is a way to do this without atoi */ resp_list.push_back( atoi( msdo.getServerSession(*list_itr)["ip_int"].c_str() ) ); ++packed; } } if ( packed != resp_list.size() ) { m_Logger.warn("Packed (%u) vs Response(%u) MISMATCH!", packed, resp_list.size() ); } out.setAddress( in.getAddress() ); out.setPacketType(NMT_LISTRESP); /** * If the list is empty, just send a 0,0 to indicate completion. * NOTE: I think this logic is a bug in the protocol, as the * original MS code looks as if this was just not working correctly. */ if ( resp_list.size() > 0 ) { out.addPacketData( msdo.getServerSessionCount() ); out.addPacketData( (uint32_t)resp_list.size() ); while ( ! resp_list.empty() ) { m_Logger.debug("processLISTRESP(%d) - Adding", resp_list.front() ); out.addPacketData(resp_list.front()); resp_list.pop_front(); } } else { /* * For the record, I think this is a stupid protocol construct */ m_Logger.debug("processLISTRESP(0,0) - Empty"); out.addPacketData( 0 ); out.addPacketData( 0 ); } }
/** * Convenience method that evaluates what type of packet and call appropriate handle method * @param msp incoming metaserver packet * @param rsp outgoing metaserver packet to be filled */ void MetaServer::processMetaserverPacket(MetaServerPacket& msp, MetaServerPacket& rsp) { /* * Packet Sequence: store this so that we can replay the packets in the * same order after the fact * Time Offset: time in milliseconds relative to the "start time". The start time * is defined as the first packet to be processed. I chose this because * it will be possible to replay the packets in the correct order, at * exactly the same rate, relative to the start of the first packet */ if ( m_PacketSequence == 0 ) m_startTime = boost::posix_time::microsec_clock::local_time(); ++m_PacketSequence; msp.setSequence(m_PacketSequence); msp.setTimeOffset( getDeltaMillis() ); switch(msp.getPacketType()) { case NMT_SERVERKEEPALIVE: processSERVERKEEPALIVE(msp,rsp); break; case NMT_SERVERSHAKE: processSERVERSHAKE(msp,rsp); break; case NMT_TERMINATE: processTERMINATE(msp,rsp); break; case NMT_CLIENTKEEPALIVE: processCLIENTKEEPALIVE(msp,rsp); break; case NMT_CLIENTSHAKE: processCLIENTSHAKE(msp,rsp); break; case NMT_LISTREQ: processLISTREQ(msp,rsp); break; case NMT_SERVERATTR: processSERVERATTR(msp,rsp); break; case NMT_CLIENTATTR: processCLIENTATTR(msp,rsp); break; case NMT_CLIENTFILTER: processCLIENTFILTER(msp,rsp); break; default: --m_PacketSequence; m_Logger.debug("Packet Type [%u] not supported.", msp.getPacketType()); break; } /* * Flag response packets sequence and offset tagging */ ++m_PacketSequence; rsp.setSequence(m_PacketSequence); rsp.setTimeOffset( getDeltaMillis() ); /* * Packet Logging */ if ( m_logPackets ) { // always log the incoming packets, even if they are bad ( as a bad incoming packet could be the cause // of an issue ) m_PacketLogger->LogPacket(msp); // we don't want to log if: // 1) sequence is 0 : this means the sequence is not set ... this means something has gone astray elsewhere // 2) packet type is NULL : these responses are never sent to the client if ( rsp.getSequence() != 0 && rsp.getPacketType() != NMT_NULL ) m_PacketLogger->LogPacket(rsp); } }