#include "TcpConnectionSSL.h" #include "../Base64.h" #include "../Framework/Logger.h" #include "../Framework/Framework.h" extern Logger* debug; extern Framework* fw; TcpConnectionSSL::TcpConnectionSSL(asio::io_service& io_service, int type, asio::ssl::context& context, Database* db) : socket_(io_service, context) { this->type = type; this->db = db; server = NULL; client = NULL; self_created_packet = false; encoded_data = ""; packetCounter = 0; state = NORMAL; send_data = NULL; send_length = 0; special_data = NULL; special_data_length = 0; ping_timer = NULL; memcheck_timer = NULL; } socketSSL::lowest_layer_type& TcpConnectionSSL::socket() { return socket_.lowest_layer(); } void TcpConnectionSSL::start() { //before reading stuff we have to do a handshake socket_.async_handshake(asio::ssl::stream_base::server, boost::bind(&TcpConnectionSSL::handle_handshake, shared_from_this(), asio::placeholders::error)); } ////////////////// // Handle stuff // ////////////////// void TcpConnectionSSL::handle_handshake(const boost::system::error_code& error) { if (!error) { remoteIp = getRemoteIp(); remotePort = getRemotePort(); if(fw->addConnection(type)) { socket_.async_read_some(asio::buffer(received_data, max_length), boost::bind(&TcpConnectionSSL::handle_read, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred)); debug->notification(1, type, "--[%s:%i] connected", remoteIp.c_str(), remotePort); // check if incoming connection is local or public if(!fw->isIpLocal(remoteIp)) // and set the emulator ip accordingly emuIp = fw->portsCfg().emulator_ip; else emuIp = getLocalIp(); } else debug->notification(1, type, "Maximum number of allowed connections for this type reached, ignoring [%s:%i]", remoteIp.c_str(), remotePort); } else { // getting the IP/Port here seems to work in Windows but apparently the connection is already destroyed here in Linux so just print the error debug->warning(1, type, "handle_handshake() - disconnected, error message: %s - error code: %i", error.message().c_str(), error.value()); } } void TcpConnectionSSL::handle_read(const system::error_code& error, size_t bytes_transferred) { if (!error) { state = NORMAL; //is it safe to always set this to NORMAL? received_length = bytes_transferred; StoreIncomingData(); while(incomingQueue.size() > 0) { Packet* receivedPacket = incomingQueue.front(); //the next unread packet is the front of the queue ProcessPacket(receivedPacket); if(!self_created_packet) { delete receivedPacket; incomingQueue.pop_front(); } else self_created_packet = false; } if(outgoingQueue.size() < 1 && state != SKIP && state != DISCONNECT) { debug->warning(1, type, "handle_read() - We dont have a packet in the Queue to process! Setting state to DISCONNECT"); state = DISCONNECT; } switch(state) { case NORMAL: { if(send_data) debug->warning(1, type, "We are overwriting some packet content here!!! -> %s", send_data); send_data = PacketToData(outgoingQueue.front()); async_write(socket_, asio::buffer(send_data, send_length), boost::bind(&TcpConnectionSSL::handle_write, shared_from_this(), asio::placeholders::error)); break; } case SKIP: { socket_.async_read_some(asio::buffer(received_data, max_length), boost::bind(&TcpConnectionSSL::handle_read, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred)); break; } case DISCONNECT: handle_stop(); default: break; } } else //client gets disconnected { debug->warning(1, type, "handle_read() - [%s:%i] error message: %s - error code: %i", remoteIp.c_str(), remotePort, error.message().c_str(), error.value()); handle_stop(); } } void TcpConnectionSSL::handle_write(const system::error_code& error) { if (!error) { //packet was sent here string packetData = outgoingQueue.front()->toString(); debug->notification(2, type, "->[%s:%i] %s 0x%08x {%s}", remoteIp.c_str(), remotePort, outgoingQueue.front()->GetType(), outgoingQueue.front()->GetType2(), packetData.c_str()); send_data = NULL; delete outgoingQueue.front(); outgoingQueue.pop_front(); if(outgoingQueue.size() > 0) state = QUEUE; else state = NORMAL; switch(state) { case NORMAL: { socket_.async_read_some(asio::buffer(received_data, max_length), boost::bind(&TcpConnectionSSL::handle_read, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred)); break; } case QUEUE: { if(send_data) debug->warning(1, type, "We are overwriting some packet content here!! -> %s", send_data); send_data = PacketToData(outgoingQueue.front()); async_write(socket_, asio::buffer(send_data, send_length), boost::bind(&TcpConnectionSSL::handle_write, shared_from_this(), asio::placeholders::error)); break; } default: break; } } else { debug->warning(1, type, "handle_write() - [%s:%i] error message: %s - error code: %i", remoteIp.c_str(), remotePort, error.message().c_str(), error.value()); handle_stop(); } } void TcpConnectionSSL::handle_stop() { debug->notification(1, type, "--[%s:%i] disconnected", remoteIp.c_str(), remotePort); system::error_code ec; if(ping_timer) { ping_timer->cancel(ec); delete ping_timer; } if(memcheck_timer) { memcheck_timer->cancel(ec); delete memcheck_timer; } fw->subtractConnection(type); if(server) delete server; if(client) delete client; } void TcpConnectionSSL::handle_one_write(const system::error_code& error) { if(!error) { string packetData = specialQueue.front()->toString(); debug->notification(2, type, "->[%s:%i] %s 0x%08x {%s}", remoteIp.c_str(), remotePort, specialQueue.front()->GetType(), specialQueue.front()->GetType2(), packetData.c_str()); special_data = NULL; delete specialQueue.front(); specialQueue.pop_front(); if(!specialQueue.empty()) { debug->notification(4, type, "handle_one_write() - one-way packet sent, there are still packets in the special queue though, calling handle again..."); special_data = PacketToData(specialQueue.front(), true); async_write(socket_, asio::buffer(special_data, special_data_length), boost::bind(&TcpConnectionSSL::handle_one_write, shared_from_this(), asio::placeholders::error)); } else debug->notification(5, type, "handle_one_write() - one-way packet sent, getting out of scope..."); } else debug->warning(1, type, "handle_one_write() - [%s:%i] - error message: %s - error code: %i", remoteIp.c_str(), remotePort, error.message().c_str(), error.value()); } void TcpConnectionSSL::handle_ping(const system::error_code& error) { if(!error) { if(state != DISCONNECT) { // the ping packet doesn't actually do anything and was just implemented in an attempt to replicate the original traffic Packet* sendPacket = new Packet("fsys", 0x80000000, false); sendPacket->SetVar("TXN", "Ping"); specialQueue.push_back(sendPacket); if(specialQueue.size() > 1) //if there is already a packet in the queue we just add this packet to the queue, sooner or later it should be processed debug->notification(4, type, "handle_ping() - there is more than one packet in the queue, %i", specialQueue.size()); else { if(special_data) debug->warning(1, type, "We are overwriting some packet content here!!! -> %s", special_data); special_data = PacketToData(specialQueue.front(), true); async_write(socket_, asio::buffer(special_data, special_data_length), boost::bind(&TcpConnectionSSL::handle_one_write, shared_from_this(), asio::placeholders::error)); } } } else { if(type > UNKNOWN || type < DEBUG) //this happens when the connection is already closed type = DEBUG; debug->warning(5, type, "handle_ping() - error: %s - error code: %i", error.message().c_str(), error.value()); } } void TcpConnectionSSL::handle_memcheck(const system::error_code& error) { if(!error) { if(state != DISCONNECT) { // the memcheck packet doesn't actually do anything and was just implemented in an attempt to replicate the original traffic string salt = fw->randomString(9, SEED_NUMBERS); Packet* sendPacket = new Packet("fsys", 0x80000000, false); sendPacket->SetVar("TXN", "MemCheck"); sendPacket->SetVar("memcheck.[]", "0"); sendPacket->SetVar("type", "0"); sendPacket->SetVar("salt", salt); specialQueue.push_back(sendPacket); if(specialQueue.size() > 1) //if there is already a packet in the queue we just add this packet to the queue, sooner or later it should be processed debug->notification(4, type, "handle_memcheck() - there is more than one packet in the queue, %i", specialQueue.size()); else { if(special_data) debug->warning(1, type, "We are overwriting some packet content here!!! -> %s", special_data); special_data = PacketToData(specialQueue.front(), true); async_write(socket_, asio::buffer(special_data, special_data_length), boost::bind(&TcpConnectionSSL::handle_one_write, shared_from_this(), asio::placeholders::error)); } } } else { if(type > UNKNOWN || type < DEBUG) //this happens when the connection is already closed type = DEBUG; debug->warning(4, type, "handle_memcheck() - error: %s - error code: %i", error.message().c_str(), error.value()); } } ////////////////// // Packet stuff // ////////////////// void TcpConnectionSSL::StoreIncomingData() { if(incomingQueue.empty()) { int current_length = 0; do{ char type[HEADER_LENGTH+1]; memcpy((void*)type, received_data+current_length, HEADER_LENGTH); unsigned int type2 = decode(received_data+current_length+4, 4); int length = decode(received_data+current_length+8, 4); unsigned char* data = new unsigned char[length-12]; memcpy(data, received_data+current_length+12, length-12); //Create the package and add it to the end of the queue incomingQueue.push_back(new Packet(type, type2, length, (char*)data)); string packetData = incomingQueue.back()->toString(); debug->notification(2, this->type, "<-[%s:%i] %s 0x%08x {%s}", remoteIp.c_str(), remotePort, incomingQueue.back()->GetType(), incomingQueue.back()->GetType2(), packetData.c_str()); current_length += length; delete[] data; if(current_length != received_length) debug->warning(4, this->type, "StoreIncomingData() - we have multiple packets incoming at once"); }while(current_length != received_length); } if(incomingQueue.empty()) debug->warning(2, type, "StoreIncomingData() - incoming Queue has no content!"); } char* TcpConnectionSSL::PacketToData(Packet* packet, bool is_special) { string packetData = packet->GetData(); if(packetData.size() > 8096 && !packet->isEncoded()) // packet exceeds max size, base64 encode it and send it in chunks (can only happen in ssl connection?) { Packet* sendPacket = NULL; bool first = true; packetData.pop_back(); // delete last newline (safe to do since it should be always there in a self-created packet) size_t pos = 0, decoded_size = packetData.size(); packetData = base64_encode(reinterpret_cast(packetData.c_str()), decoded_size); int encoded_size = packetData.size(); while((pos = packetData.find('=', packetData.size()-9)) != string::npos) // bring string into proper format again packetData.replace(pos, 1, "%3d"); // split up the data in multiple packets std::list tempList; for(int i = 0, new_size = packetData.size(); new_size > 0; i++, new_size -= 8096) { sendPacket = new Packet(packet->GetType(), 0xb0000000, true, first); sendPacket->SetVar("decodedSize", decoded_size); sendPacket->SetVar("size", encoded_size); string buffer; if (new_size < 8096) buffer = packetData.substr(i*8096, new_size); else buffer = packetData.substr(i*8096, 8096); sendPacket->SetVar("data", buffer); sendPacket->isEncoded(true); tempList.push_back(sendPacket); if(first) first = false; } // this is only for logging and not actually important uint32_t packet_type = (packet->GetType2() & 0xff000000) | (packetCounter+1); debug->notification(4, this->type, "=>[%s:%i] %s 0x%08x {%s}", remoteIp.c_str(), remotePort, packet->GetType(), packet_type, packet->toString().c_str()); // get rid of the original uncoded packet delete outgoingQueue.front(); outgoingQueue.pop_front(); packetData.clear(); // we want this series of packets at the beginning of outgoingQueue but in order to do that we have to reverse their order while(!tempList.empty()) { outgoingQueue.push_front(tempList.back()); tempList.pop_back(); } return PacketToData(outgoingQueue.front()); // call the packet processing function again with the first of the split packets } uint8_t *type1 = (uint8_t*)packet->GetType(); uint32_t type2 = (uint32_t)packet->GetType2(); uint8_t *fmt = (uint8_t*)packetData.c_str(); static uint8_t *buff = NULL; int slen, len; len = strlen((char*)fmt); slen = 12 + len; if(slen > 0) buff = (uint8_t*)realloc(buff, slen); memcpy(buff, type1, 4); if(packet->GetValCount() && (type2 & 0x80000000) == 0x80000000) { // its a regular packet, do we want to count it? if((type2 & 0x00ffffff) == 1) packetCounter = 0; // is the packet the first one we are recieving? if(packet->GetValFirst()) packetCounter++; // packet counter (not needed for linked packets so we stop counting after first one is sent) type2 = (type2 & 0xff000000) | packetCounter; } packet->SetType2((unsigned int)type2); //update the packet so it is shown correctly in the log (the data that we send here is not affected by this) encode(buff + 4, type2, 4); encode(buff + 8, slen, 4); memcpy(buff + 12, fmt, len); // EA uses the final NULL delimiter so we set the last char from the data to NULL if(buff[len+12-1] == '\n') // every self-created packet normally has a newline at the end buff[len+12-1] = '\0'; if(is_special) special_data_length = slen; else send_length = slen; return (char*)buff; } unsigned int TcpConnectionSSL::decode(unsigned char* data, int bytes) { int num, i; for(num = i = 0; i < bytes; i++) { //num |= (data[i] << (i << 3)); // little num |= (data[i] << ((bytes - 1 - i) << 3)); // big } return(num); } unsigned int TcpConnectionSSL::encode(uint8_t *data, uint32_t num, int bytes) { for(int i = 0; i < bytes; i++) { //data[i] = num >> (i << 3); // little data[i] = num >> ((bytes - 1 - i) << 3); // big } return(bytes); } ////////////////// // Socket stuff // ////////////////// string TcpConnectionSSL::getLocalIp() { return socket().local_endpoint().address().to_string(); } int TcpConnectionSSL::getLocalPort() { return socket().local_endpoint().port(); } string TcpConnectionSSL::getRemoteIp() { return socket().remote_endpoint().address().to_string(); } int TcpConnectionSSL::getRemotePort() { return socket().remote_endpoint().port(); } ///////////////////////////// // Packet processing stuff // ///////////////////////////// void TcpConnectionSSL::ProcessPacket(Packet* receivedPacket) { string txn = receivedPacket->GetVar("TXN"); if(!txn.empty()) { bool unknown = false; char* packet_type = receivedPacket->GetType(); do{ //fsys// if(strcmp(packet_type, "fsys") == 0) { Packet* sendPacket; if(txn.compare("Hello") == 0) { sendPacket = new Packet(packet_type, 0x80000000); sendPacket->SetVar("domainPartition.domain", "eagames"); //sendPacket->SetVar("messengerIp", "messaging.ea.com"); //sendPacket->SetVar("messengerPort", "13505"); sendPacket->SetVar("messengerIp", emuIp); sendPacket->SetVar("messengerPort", fw->portsCfg().emulator_port); sendPacket->SetVar("domainPartition.subDomain", "BFBC2"); sendPacket->SetVar("TXN", txn); sendPacket->SetVar("activityTimeoutSecs", "0"); // we could let idle clients disconnect here automatically? sendPacket->SetVar("curTime", fw->getTime()); sendPacket->SetVar("theaterIp", emuIp); if(this->type == GAME_SERVER_SSL) { //sendPacket->SetVar("theaterIp", "bfbc2-pc-server.theater.ea.com"); //sendPacket->SetVar("theaterPort", 19026); sendPacket->SetVar("theaterPort", fw->portsCfg().theater_server_port); server = new GameServer(this->type, this->db); } else if(this->type == GAME_CLIENT_SSL) { //sendPacket->SetVar("theaterIp", "bfbc2-pc.theater.ea.com"); //sendPacket->SetVar("theaterPort", 18395); sendPacket->SetVar("theaterPort", fw->portsCfg().theater_client_port); client = new GameClient(this->type, this->db, this->emuIp, false); // last parameter has no meaning here } outgoingQueue.push_back(sendPacket); string salt = fw->randomString(9, SEED_NUMBERS); sendPacket = new Packet("fsys", 0x80000000, false); sendPacket->SetVar("TXN", "MemCheck"); sendPacket->SetVar("memcheck.[]", "0"); sendPacket->SetVar("type", "0"); sendPacket->SetVar("salt", salt); outgoingQueue.push_back(sendPacket); } else if(txn.compare("MemCheck") == 0) { if(!ping_timer && !memcheck_timer) // activate both ping and memcheck timers when we receive this { ping_timer = new asio::deadline_timer(socket_.get_io_service());//, posix_time::seconds(150)); // only create the object here, it'll be updated below anyway memcheck_timer = new asio::deadline_timer(socket_.get_io_service(), posix_time::seconds(500)); memcheck_timer->async_wait(boost::bind(&TcpConnectionSSL::handle_memcheck, shared_from_this(), asio::placeholders::error)); } else // update memcheck timer (was approximately 300 seconds in captured traffics) { memcheck_timer->expires_from_now(posix_time::seconds(300)); memcheck_timer->async_wait(boost::bind(&TcpConnectionSSL::handle_memcheck, shared_from_this(), asio::placeholders::error)); } state = SKIP; } else if(txn.compare("Ping") == 0) { // should we do this when its done below for practically every packet we receive anyway? //ping_timer->expires_from_now(posix_time::seconds(150)); //ping->async_wait(boost::bind(&TcpConnectionSSL::handle_ping, this, asio::placeholders::error)); state = SKIP; } else if(txn.compare("GetPingSites") == 0) { //if everything goes well we should never get here (on the server side) sendPacket = new Packet(packet_type, 0x80000000); sendPacket->SetVar("TXN", txn); sendPacket->SetVar("pingSite.[]", "4"); sendPacket->SetVar("pingSite.0.addr", emuIp); sendPacket->SetVar("pingSite.0.type", "0"); sendPacket->SetVar("pingSite.0.name", "gva"); sendPacket->SetVar("pingSite.1.addr", emuIp); sendPacket->SetVar("pingSite.1.type", "0"); sendPacket->SetVar("pingSite.1.name", "nrt"); sendPacket->SetVar("pingSite.2.addr", emuIp); sendPacket->SetVar("pingSite.2.type", "0"); sendPacket->SetVar("pingSite.2.name", "iad"); sendPacket->SetVar("pingSite.3.addr", emuIp); sendPacket->SetVar("pingSite.3.type", "0"); sendPacket->SetVar("pingSite.3.name", "sjc"); sendPacket->SetVar("minPingSitesToPing", "0"); outgoingQueue.push_back(sendPacket); } else if(txn.compare("Goodbye") == 0) { state = DISCONNECT; } else unknown = true; } //acct// else if(strcmp(packet_type, "acct") == 0) { if(server) unknown = server->acct(&outgoingQueue, receivedPacket, txn); else // must be client unknown = client->acct(&outgoingQueue, receivedPacket, txn, remoteIp.c_str()); } //asso// else if(strcmp(packet_type, "asso") == 0) { if(server) unknown = server->asso(&outgoingQueue, receivedPacket, txn); else // must be client unknown = client->asso(&outgoingQueue, receivedPacket, txn); } //xmsg// Client only? else if(strcmp(packet_type, "xmsg") == 0) { if(client) unknown = client->xmsg(&outgoingQueue, receivedPacket, txn); else debug->warning(2, type, "xmsg - unexpected msg for server"); } //pres else if(strcmp(packet_type, "pres") == 0) { if(server) unknown = server->pres(&outgoingQueue, receivedPacket, txn); else // must be client unknown = client->pres(&outgoingQueue, receivedPacket, txn); } //rank// else if(strcmp(packet_type, "rank") == 0) { if(server) state = server->rank(&outgoingQueue, receivedPacket, txn); else // must be client state = client->rank(&outgoingQueue, receivedPacket, txn); } //recp// Client only? else if(strcmp(packet_type, "recp") == 0) { if(client) unknown = client->recp(&outgoingQueue, receivedPacket, txn); else debug->warning(2, type, "recp - unexpected packet for server"); } //pnow// Client only? else if(strcmp(packet_type, "pnow") == 0) { if(client) unknown = client->pnow(&outgoingQueue, receivedPacket, txn); else debug->warning(2, type, "pnow - unexpected packet for server"); } //fltr// Server only? else if(strcmp(packet_type, "fltr") == 0) { if(server) unknown = server->fltr(&outgoingQueue, receivedPacket, txn); else debug->warning(2, type, "fltr - unexpected packet for client"); } else unknown = true; }while(state == QUEUE); if(ping_timer && incomingQueue.size() <= 1) // all packets are processed, update ping timer (would it be better to just remove the queue check?) { int duration = ping_timer->expires_from_now().total_seconds(); if(duration < 0 || duration > 150) duration = 150; else if(duration >= 0 && duration+10 < 150) // reset the timer duration = 150 + (duration % 10); // ping intervals seem to be approximately 150 seconds (why did I think it was a good idea to do it like this?) ping_timer->expires_from_now(posix_time::seconds(duration)); ping_timer->async_wait(boost::bind(&TcpConnectionSSL::handle_ping, shared_from_this(), asio::placeholders::error)); } if(unknown) { debug->warning(1, type, "Unknown packet! Setting state to DISCONNECT"); state = DISCONNECT; // should we really be that restrictive? } } else // no TXN was included in this packet so this one has to be part of a large base64 encoded packet? { string buffer = receivedPacket->GetVar("size"); string data = receivedPacket->GetVar("data"); if(!buffer.empty() && !data.empty()) { // is it safe to assume that the encoded packets are ordered and that there won't be different encoded packets exchanged at the same time? size_t size = lexical_cast(buffer); encoded_data.append(receivedPacket->GetVar("data")); if(encoded_data.size() >= size) // all data should be received, decode it { if(!self_created_packet) self_created_packet = true; size_t pos = encoded_data.size()-9; while((pos = encoded_data.find("%3d", pos)) != string::npos) // see if there are any '=' in string and convert it back { encoded_data.replace(pos, 3, 1, '='); pos = encoded_data.size()-6; } string decoded_data = base64_decode(encoded_data); Packet* packet = new Packet(receivedPacket->GetType(), receivedPacket->GetType2(), decoded_data.size(), decoded_data.c_str()); delete receivedPacket; // first we have to pop the last packet we got (which is the last of the series of encoded packets) incomingQueue.pop_front(); //we dont want this to be added at the back because there could be another packet that we received between the gathering and therefore messing up the packet_count incomingQueue.push_front(packet); debug->notification(4, this->type, "<=[%s:%i] %s 0x%08x {%s}", remoteIp.c_str(), remotePort, packet->GetType(), packet->GetType2(), packet->toString().c_str()); encoded_data.clear(); // reset storage for data } else state = SKIP; } else debug->warning(2, type, "Unknown packet here!"); } }