diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index 41dae01a7..b4dc33db0 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -55,6 +55,7 @@ set(llmessage_SOURCE_FILES llmessagethrottle.cpp llmime.cpp llnamevalue.cpp + llnetcanary.cpp llnullcipher.cpp llpacketack.cpp llpacketbuffer.cpp @@ -147,6 +148,7 @@ set(llmessage_HEADER_FILES llmime.h llmsgvariabletype.h llnamevalue.h + llnetcanary.h llnullcipher.h llpacketack.h llpacketbuffer.h diff --git a/indra/llmessage/llnetcanary.cpp b/indra/llmessage/llnetcanary.cpp new file mode 100644 index 000000000..59ede8029 --- /dev/null +++ b/indra/llmessage/llnetcanary.cpp @@ -0,0 +1,140 @@ +// +#include "llnetcanary.h" +#include "llerror.h" +#ifdef _MSC_VER +#include +static WSADATA trapWSAData; +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern int errno; //error number +#define SOCKADDR_IN struct sockaddr_in +#define SOCKADDR struct sockaddr +#define SOCKET int +#define INVALID_SOCKET -1 +#define SOCKET_ERROR -1 +#define SD_BOTH 2 +#define closesocket close +#endif +SOCKADDR_IN trapLclAddr; +LLNetCanary::LLNetCanary(int requested_port) +{ + mGood = TRUE; + int nRet; + int hSocket; + int snd_size = 400000; + int rec_size = 400000; + int buff_size = 4; +#ifdef _MSC_VER + if(WSAStartup(0x0202, &trapWSAData)) + { + S32 err = WSAGetLastError(); + WSACleanup(); + llwarns << "WSAStartup() failure: " << err << llendl; + mGood = FALSE; + return; + } +#endif + // Get a datagram socket + hSocket = (int)socket(AF_INET, SOCK_DGRAM, 0); + if (hSocket == INVALID_SOCKET) + { +#ifdef _MSC_VER + S32 err = WSAGetLastError(); + WSACleanup(); +#else + S32 err = errno; +#endif + mGood = FALSE; + llwarns << "socket() failure: " << err << llendl; + return; + } + + // Name the socket (assign the local port number to receive on) + trapLclAddr.sin_family = AF_INET; + trapLclAddr.sin_addr.s_addr = htonl(INADDR_ANY); + trapLclAddr.sin_port = htons(requested_port); + //llinfos << "bind() port: " << requested_port << llendl; + nRet = bind(hSocket, (struct sockaddr*) &trapLclAddr, sizeof(trapLclAddr)); + if (nRet == SOCKET_ERROR) + { +#ifdef _MSC_VER + S32 err = WSAGetLastError(); + WSACleanup(); +#else + S32 err = errno; +#endif + mGood = FALSE; + llwarns << "bind() failed: " << err << llendl; + return; + } + + // Find out what address we got + SOCKADDR_IN socket_address; + S32 socket_address_size = sizeof(socket_address); + getsockname(hSocket, (SOCKADDR*) &socket_address, (socklen_t *)&socket_address_size); + mPort = ntohs(socket_address.sin_port); + //llinfos << "got port " << mPort << llendl; + + // Set socket to be non-blocking +#ifdef _MSC_VER + unsigned long argp = 1; + nRet = ioctlsocket (hSocket, FIONBIO, &argp); +#else + nRet = fcntl(hSocket, F_SETFL, O_NONBLOCK); +#endif + if (nRet == SOCKET_ERROR) + { +#ifdef _MSC_VER + S32 err = WSAGetLastError(); + WSACleanup(); +#else + S32 err = errno; +#endif + mGood = FALSE; + llwarns << "Failed to set socket non-blocking, Err: " << err << llendl; + return; + } + + // set a large receive buffer + nRet = setsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, buff_size); + if (nRet) + { + llinfos << "Can't set receive buffer size!" << llendl; + } + // set a large send buffer + nRet = setsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, buff_size); + if (nRet) + { + llinfos << "Can't set send buffer size!" << llendl; + } + //getsockopt(hSocket, SOL_SOCKET, SO_RCVBUF, (char *)&rec_size, &buff_size); + //getsockopt(hSocket, SOL_SOCKET, SO_SNDBUF, (char *)&snd_size, &buff_size); + //LL_DEBUGS("AppInit") << "startNet - receive buffer size : " << rec_size << LL_ENDL; + //LL_DEBUGS("AppInit") << "startNet - send buffer size : " << snd_size << LL_ENDL; + + mSocket = hSocket; +} +LLNetCanary::~LLNetCanary() +{ + if(mGood) + { + if(mSocket) + { + shutdown(mSocket, SD_BOTH); + closesocket(mSocket); + } +#ifdef _MSC_VER + WSACleanup(); +#endif + } +} +// diff --git a/indra/llmessage/llnetcanary.h b/indra/llmessage/llnetcanary.h new file mode 100644 index 000000000..a5a5d6b4e --- /dev/null +++ b/indra/llmessage/llnetcanary.h @@ -0,0 +1,25 @@ +// +#ifndef LL_LLNETCANARY_H +#define LL_LLNETCANARY_H +#include "stdtypes.h" +#include +class LLNetCanary +{ +public: + LLNetCanary(int requested_port); + ~LLNetCanary(); + BOOL mGood; + S32 mSocket; + U16 mPort; + U8 mBuffer[8192]; + typedef struct + { + F64 time; // LLTimer::getElapsedSeconds(); + //U8 message[4]; + U32 message; + U32 points; + std::string name; + } entry; +}; +#endif +// diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index a66a1013f..ed70e5408 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -550,6 +550,76 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count, bool faked_message, U8 fak S32 receive_size = 0; do { + // + // Expire old canary entries + F64 now = LLTimer::getElapsedSeconds(); + std::map::iterator canary_entry_iter; + std::map::iterator canary_entry_end = mCanaryEntries.end(); + for( canary_entry_iter = mCanaryEntries.begin(); + canary_entry_iter != mCanaryEntries.end(); ) + { + if( ((*canary_entry_iter).second.time + 30.0f) < now) + { + llinfos << "Expiring ban on " << (*canary_entry_iter).second.name << " message, " << (*canary_entry_iter).second.points << " points" << llendl; + mCanaryEntries.erase(canary_entry_iter++); + } + else + { + canary_entry_iter++; + } + } + if(!faked_message && mSpoofProtectionLevel > 1) + { + // Canaries receive + std::vector::iterator canary_iter = mCanaries.begin(); + std::vector::iterator canary_end = mCanaries.end(); + for( ; canary_iter != canary_end; ++canary_iter) + { + U8* canary_buffer = (*canary_iter)->mBuffer; + S32 len = receive_packet((*canary_iter)->mSocket, (char*)canary_buffer); + if(len) + { + //llinfos << "canary received " << len << " bytes on port " << (*canary_iter)->mPort << llendl; + zeroCodeExpand(&canary_buffer, &len); + if(len < 7) continue; // too short to be an slmsg + LLNetCanary::entry entry; + entry.message = 0; + if(canary_buffer[6] != 0xFF) // High XX + entry.message = canary_buffer[6]; + else if((len >= 8) && (canary_buffer[7] != 0xFF)) // Medium FFXX + entry.message = (255 << 8) | canary_buffer[7]; + else if((len >= 10) && (canary_buffer[7] == 0xFF)) // Low FFFFXXXX + { + U16 message_id_U16 = 0; + memcpy(&message_id_U16, &canary_buffer[8], 2); + message_id_U16 = ntohs(message_id_U16); + entry.message = 0xFFFF0000 | message_id_U16; + } + else continue; // not an slmsg + + if(mCanaryEntries.find(entry.message) == mCanaryEntries.end()) + { + // brand new entry + LLMessageTemplate* temp = get_ptr_in_map(mMessageNumbers, entry.message); + entry.name = temp ? temp->mName : "Invalid"; + entry.points = 1; + entry.time = now; + mCanaryEntries[entry.message] = entry; + if(mSpoofProtectionLevel == 2) + llinfos << "Temporarily banning a " << entry.name << " message" << llendl; + } + else + { + // strike two, three... + mCanaryEntries[entry.message].points++; + mCanaryEntries[entry.message].time = now; + if((mSpoofProtectionLevel > 2) && (mCanaryEntries[entry.message].points == 2)) + llinfos << "Temporarily banning a " << mCanaryEntries[entry.message].name << " message" << llendl; + } + } + } + } + // clearReceiveState(); BOOL recv_reliable = FALSE; @@ -625,6 +695,61 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count, bool faked_message, U8 fak // process the message as normal mIncomingCompressedSize = zeroCodeExpand(&buffer, &receive_size); mCurrentRecvPacketID = ntohl(*((U32*)(&buffer[1]))); + // + BOOL spoofed_packet = FALSE; + if(mSpoofProtectionLevel > 0) + { + S32 len = receive_size; + U32 message = 0; + if(buffer[6] != 0xFF) // High XX + message = buffer[6]; + else if((len >= 8) && (buffer[7] != 0xFF)) // Medium FFXX + message = (255 << 8) | buffer[7]; + else if((len >= 10) && (buffer[7] == 0xFF)) // Low FFFFXXXX + { + U16 message_id_U16 = 0; + memcpy(&message_id_U16, &buffer[8], 2); + message_id_U16 = ntohs(message_id_U16); + message = 0xFFFF0000 | message_id_U16; + } + if(!mCurrentRecvPacketID) + { + LL_WARNS("Messaging") << "CODE DONKEY_A" << llendl; + if(mSpoofDroppedCallback) + { + LLNetCanary::entry entry; + LLMessageTemplate* temp = get_ptr_in_map(mMessageNumbers, message); + entry.name = temp ? temp->mName : "Invalid"; + entry.message = message; + entry.time = now; + entry.points = 1; + mSpoofDroppedCallback(entry); + } + spoofed_packet = TRUE; + valid_packet = FALSE; + } + else if((mSpoofProtectionLevel > 1) && (receive_size >= 7)) + { + if(mCanaryEntries.find(message) != mCanaryEntries.end()) + { + if( + (mSpoofProtectionLevel == 2) || + (mCanaryEntries[message].points > 1) + ) + { + LL_WARNS("Messaging") << "Dropped probably spoofed " << mCanaryEntries[message].name << " packet, " << mCanaryEntries[message].points << " points" << llendl; + if(mSpoofDroppedCallback) + { + mSpoofDroppedCallback(mCanaryEntries[message]); + } + spoofed_packet = TRUE; + valid_packet = FALSE; + break; + } + } + } + } // mSpoofProtectionLevel + // host = getSender(); const bool resetPacketId = true; @@ -707,6 +832,9 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count, bool faked_message, U8 fak // But we don't want to acknowledge UseCircuitCode until the circuit is // available, which is why the acknowledgement test is done above. JC bool trusted = cdp && cdp->getTrusted(); + // + if(!spoofed_packet) + // valid_packet = mTemplateMessageReader->validateMessage( buffer, receive_size, @@ -1862,7 +1990,11 @@ void open_circuit(LLMessageSystem *msgsystem, void** /*user_data*/) msgsystem->getIPPortFast(_PREHASH_CircuitInfo, _PREHASH_Port, port); // By default, OpenCircuit's are untrusted - msgsystem->enableCircuit(LLHost(ip, port), FALSE); + // +//#ifndef LL_RELEASE_FOR_DOWNLOAD + llwarns << "OpenCircuit " << LLHost(ip, port) << llendl; +//#endif + // msgsystem->enableCircuit(LLHost(ip, port), FALSE); } void close_circuit(LLMessageSystem *msgsystem, void** /*user_data*/) @@ -2718,6 +2850,9 @@ void end_messaging_system(bool print_summary) LLTransferTargetVFile::updateQueue(true); // shutdown LLTransferTargetVFile if (gMessageSystem) { + // + gMessageSystem->stopSpoofProtection(); + // gMessageSystem->stopLogging(); if (print_summary) @@ -4080,4 +4215,155 @@ const LLHost& LLMessageSystem::getSender() const LLHTTPRegistration > gHTTPRegistrationTrustedMessageWildcard("/trusted-message/"); +// +void LLMessageSystem::startSpoofProtection(U32 level) +{ + if(!mPort) + { + llwarns << "listen port is 0!!!" << llendl; + } + mSpoofProtectionLevel = level; + mCanaries.clear(); + mCanaryEntries.clear(); + // Make canaries + std::string canary_info(""); + + if(mSpoofProtectionLevel > 2) // 3 or greater + { + // FULL CANARY POWER + int rPort = 768 + ll_rand(32); + if(mPort < rPort) rPort = mPort - 16; + if(rPort < 1) rPort = 1; + while(rPort < 65536) + { + if(rPort != mPort) + { + LLNetCanary* canary = new LLNetCanary(rPort); + if(canary->mGood) + { + mCanaries.push_back(canary); + canary_info.append(llformat(" %d", canary->mPort)); + } + else + delete canary; + } + int dist = llabs(mPort - rPort); + if(dist > 4096) + rPort += ll_rand(2048) + 2048; + else if(dist > 2048) + rPort += ll_rand(1024) + 1024; + else if(dist > 1024) + rPort += ll_rand(512) + 512; + else if(dist > 512) + rPort += ll_rand(256) + 256; + else if(dist > 128) + rPort += ll_rand(64) + 64; + else if(dist > 16) + rPort += ll_rand(8) + 8; + else + rPort += 4; + } + } + else if(mSpoofProtectionLevel == 2) + { + // Minimal canaries + for(int o = -32; o <= 32; o += 8) + { + int rPort = mPort + o; + if(rPort != mPort) + { + LLNetCanary* canary = new LLNetCanary(rPort); + if(canary->mGood) + { + mCanaries.push_back(canary); + canary_info.append(llformat(" %d", canary->mPort)); + } + else + delete canary; + } + } + } + + if(mCanaries.size()) + { + llinfos << "level " << mSpoofProtectionLevel << ", " << mCanaries.size() << " canaries: " << canary_info << llendl; + } + else + { + llinfos << "level " << mSpoofProtectionLevel << ", no canaries" << llendl; + } +} + +void LLMessageSystem::stopSpoofProtection() +{ + llinfos << "cleaning up" << llendl; + // Shut down canaries + std::vector::iterator canary_iter = mCanaries.begin(); + std::vector::iterator canary_end = mCanaries.end(); + for( ; canary_iter != canary_end; ++canary_iter) + { + LLNetCanary* canary = (*canary_iter); + delete canary; + } + mCanaries.clear(); + // Empty canary entries + mCanaryEntries.clear(); +} + +void LLMessageSystem::setSpoofDroppedCallback(void (*callback)(LLNetCanary::entry)) +{ + mSpoofDroppedCallback = callback; +} + +// Copypasta from LLTemplateMessageReader +BOOL LLMessageSystem::decodeTemplate( const U8* buffer, S32 buffer_size, LLMessageTemplate** msg_template ) +{ + const U8* header = buffer + LL_PACKET_ID_SIZE; + if (buffer_size <= 0) return FALSE; + U32 num = 0; + if (header[0] != 255) + { + // high frequency message + num = header[0]; + } + else if ((buffer_size >= ((S32) LL_MINIMUM_VALID_PACKET_SIZE + 1)) && (header[1] != 255)) + { + // medium frequency message + num = (255 << 8) | header[1]; + } + else if ((buffer_size >= ((S32) LL_MINIMUM_VALID_PACKET_SIZE + 3)) && (header[1] == 255)) + { + // low frequency message + U16 message_id_U16 = 0; + // I think this check busts the message system. + // it appears that if there is a NULL in the message #, it won't copy it.... + // what was the goal? + //if(header[2]) + memcpy(&message_id_U16, &header[2], 2); + + // dependant on endian-ness: + // U32 temp = (255 << 24) | (255 << 16) | header[2]; + + // independant of endian-ness: + message_id_U16 = ntohs(message_id_U16); + num = 0xFFFF0000 | message_id_U16; + } + else // bogus packet received (too short) + { + return(FALSE); + } + + LLMessageTemplate* temp = get_ptr_in_map(mMessageNumbers,num); + if (temp) + { + *msg_template = temp; + } + else + { + return(FALSE); + } + + return(TRUE); +} +// +#include "llnetcanary.h" +// const U32 MESSAGE_MAX_STRINGS_LENGTH = 64; const U32 MESSAGE_NUMBER_OF_HASH_BUCKETS = 8192; @@ -215,6 +218,12 @@ class LLMessageSystem : public LLMessageSenderInterface private: U8 mSendBuffer[MAX_BUFFER_SIZE]; S32 mSendSize; + // + U32 mSpoofProtectionLevel; + std::vector mCanaries; + std::map mCanaryEntries; + void (*mSpoofDroppedCallback)(LLNetCanary::entry); + // bool mBlockUntrustedInterface; LLHost mUntrustedInterface; @@ -236,7 +245,6 @@ class LLMessageSystem : public LLMessageSenderInterface // message_template_name_map_t mMessageTemplates; message_template_number_map_t mMessageNumbers; - // //public: // @@ -614,6 +622,11 @@ public: // Change this message to be UDP black listed. void banUdpMessage(const std::string& name); + // + void startSpoofProtection(U32 level); + void stopSpoofProtection(); + void setSpoofDroppedCallback(void (*callback)(LLNetCanary::entry)); + // private: // A list of the circuits that need to be sent DenyTrustedCircuit messages. @@ -756,7 +769,13 @@ private: LLUUID mSessionID; void addTemplate(LLMessageTemplate *templatep); +// +public: +// BOOL decodeTemplate( const U8* buffer, S32 buffer_size, LLMessageTemplate** msg_template ); +// +private: +// void logMsgFromInvalidCircuit( const LLHost& sender, BOOL recv_reliable ); void logTrustedMsgFromUntrustedCircuit( const LLHost& sender ); diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 1baa295d3..e2dcd6ad4 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -784,6 +784,28 @@ S32 Value 5 + + SpoofProtectionAlerts + + Comment + Enables IP Spoofing protection alerts + Persist + 1 + Type + Boolean + Value + 0 + + SpoofProtectionLevel + + Comment + IP Spoofing protection level + Persist + 1 + Type + U32 + Value + 0 Socks5ProxyEnabled diff --git a/indra/newview/hbprefsinert.cpp b/indra/newview/hbprefsinert.cpp index dad35a489..9dbd3032a 100644 --- a/indra/newview/hbprefsinert.cpp +++ b/indra/newview/hbprefsinert.cpp @@ -74,6 +74,7 @@ private: U32 mLinksForChattingObjects; U32 mTimeFormat; U32 mDateFormat; + U32 mSpoofProtectionAtOpen; }; @@ -180,6 +181,7 @@ void LLPrefsInertImpl::refresh() { combo->setCurrentByIndex(mDateFormat); } + mSpoofProtectionAtOpen = gSavedSettings.getU32("SpoofProtectionLevel"); } void LLPrefsInertImpl::cancel() @@ -199,7 +201,8 @@ void LLPrefsInertImpl::cancel() gSavedSettings.setBOOL("RevokePermsOnStandUp", mRevokePermsOnStandUp); gSavedSettings.setBOOL("WindEnabled", mEnableLLWind); gSavedSettings.setBOOL("BroadcastViewerEffects", mBroadcastViewerEffects); - + gSavedSettings.setU32("SpoofProtectionLevel", mSpoofProtectionAtOpen); + gLLWindEnabled = mEnableLLWind; if(mInitialEnableClouds != gSavedSettings.getBOOL("CloudsEnabled")) @@ -261,7 +264,16 @@ void LLPrefsInertImpl::apply() gSavedSettings.setString("ShortTimeFormat", short_time); gSavedSettings.setString("LongTimeFormat", long_time); gSavedSettings.setString("TimestampFormat", timestamp); - + if(gMessageSystem) + { + U32 new_spoof_protection = gSavedSettings.getU32("SpoofProtectionLevel"); + if(new_spoof_protection != mSpoofProtectionAtOpen) + { + mSpoofProtectionAtOpen = new_spoof_protection; + gMessageSystem->stopSpoofProtection(); + gMessageSystem->startSpoofProtection(new_spoof_protection); + } + } refreshValues(); } diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index f7b3e8d95..9370e406b 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -926,6 +926,14 @@ bool idle_startup() LLAppViewer::instance()->earlyExit("LoginFailedNoNetwork", LLSD().insert("DIAGNOSTIC", diagnostic)); } + // + if(gMessageSystem) + { + gMessageSystem->startSpoofProtection(gSavedSettings.getU32("SpoofProtectionLevel")); + gMessageSystem->setSpoofDroppedCallback(spoof_dropped_callback); + } + // + #if LL_WINDOWS // On the windows dev builds, unpackaged, the message.xml file will // be located in indra/build-vc**/newview//app_settings. diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1c42d0294..4f2245f3c 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -5915,3 +5915,15 @@ void LLOfferInfo::forceResponse(InventoryOfferResponse response) LLNotifications::instance().forceResponse(params, response); } +// lol +void spoof_dropped_callback(LLNetCanary::entry entry) +{ + if(gSavedSettings.getBOOL("SpoofProtectionAlerts")) + { + LLSD args; + args["[MESSAGE]"] = llformat("A suspicious %s packet was dropped based on your IP Spoofing Protection settings.", entry.name.c_str()); + LLNotifications::instance().add("SystemMessageTip",args); + } +} +// + diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h index 2d0cd471d..d33fdf039 100644 --- a/indra/newview/llviewermessage.h +++ b/indra/newview/llviewermessage.h @@ -197,6 +197,10 @@ void process_decline_callingcard(LLMessageSystem* msg, void**); // Message system exception prototypes void invalid_message_callback(LLMessageSystem*, void*, EMessageException); +// +void spoof_dropped_callback(LLNetCanary::entry entry); +// + void process_initiate_download(LLMessageSystem* msg, void**); void start_new_inventory_observer(); diff --git a/indra/newview/skins/default/xui/en-us/panel_preferences_inert.xml b/indra/newview/skins/default/xui/en-us/panel_preferences_inert.xml index 5da6413c6..135b7d892 100644 --- a/indra/newview/skins/default/xui/en-us/panel_preferences_inert.xml +++ b/indra/newview/skins/default/xui/en-us/panel_preferences_inert.xml @@ -96,6 +96,25 @@ mouse_opaque="true" name="enable_clouds" radio_style="false" width="400" /> + + + IP Spoofing Protection (0 being off, 3 being highest) + + + +