phoenix-firestorm/indra/newview/llappcorehttp.cpp

568 lines
17 KiB
C++

/**
* @file llappcorehttp.cpp
* @brief
*
* $LicenseInfo:firstyear=2012&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2012-2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llappcorehttp.h"
#include "llappviewer.h"
#include "llviewercontrol.h"
#include <openssl/x509_vfy.h>
#include <openssl/ssl.h>
#include "llsecapi.h"
#include <curl/curl.h>
#include "llcorehttputil.h"
// Here is where we begin to get our connection usage under control.
// This establishes llcorehttp policy classes that, among other
// things, limit the maximum number of connections to outside
// services. Each of the entries below maps to a policy class and
// has a limit, sometimes configurable, of how many connections can
// be open at a time.
const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0);
const long LLAppCoreHttp::PIPELINING_DEPTH(5L);
// Default and dynamic values for classes
static const struct
{
U32 mDefault;
U32 mMin;
U32 mMax;
U32 mRate;
bool mPipelined;
std::string mKey;
const char * mUsage;
} init_data[LLAppCoreHttp::AP_COUNT] =
{
{ // AP_DEFAULT
8, 8, 8, 0, false,
"",
"other"
},
{ // AP_TEXTURE
8, 1, 12, 0, true,
"TextureFetchConcurrency",
"texture fetch"
},
{ // AP_MESH1
32, 1, 128, 0, false,
"MeshMaxConcurrentRequests",
"mesh fetch"
},
{ // AP_MESH2
8, 1, 32, 0, true,
"Mesh2MaxConcurrentRequests",
"mesh2 fetch"
},
{ // AP_LARGE_MESH
2, 1, 8, 0, false,
"",
"large mesh fetch"
},
{ // AP_UPLOADS
2, 1, 8, 0, false,
"",
"asset upload"
},
{ // AP_LONG_POLL
32, 32, 32, 0, false,
"",
"long poll"
},
{ // AP_INVENTORY
4, 1, 4, 0, false,
"",
"inventory"
},
{ // AP_MATERIALS
2, 1, 8, 0, false,
"RenderMaterials",
"material manager requests"
},
{ // AP_AGENT
2, 1, 32, 0, false,
"Agent",
"Agent requests"
}
};
static void setting_changed();
LLAppCoreHttp::HttpClass::HttpClass()
: mPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID),
mConnLimit(0U),
mPipelined(false)
{}
LLAppCoreHttp::LLAppCoreHttp()
: mRequest(NULL),
mStopHandle(LLCORE_HTTP_HANDLE_INVALID),
mStopRequested(0.0),
mStopped(false),
mPipelined(true)
{}
LLAppCoreHttp::~LLAppCoreHttp()
{
delete mRequest;
mRequest = NULL;
}
void LLAppCoreHttp::init()
{
LLCoreHttpUtil::setPropertyMethods(
boost::bind(&LLControlGroup::getBOOL, boost::ref(gSavedSettings), _1),
boost::bind(&LLControlGroup::declareBOOL, boost::ref(gSavedSettings), _1, _2, _3, LLControlVariable::PERSIST_NONDFT));
LLCore::LLHttp::initialize();
LLCore::HttpStatus status = LLCore::HttpRequest::createService();
if (! status)
{
LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " << status.toString()
<< LL_ENDL;
}
// Point to our certs or SSH/https: will fail on connect
status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE,
LLCore::HttpRequest::GLOBAL_POLICY_ID,
gDirUtilp->getCAFile(), NULL);
if (! status)
{
LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " << status.toString()
<< LL_ENDL;
}
// Establish HTTP Proxy, if desired.
status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY,
LLCore::HttpRequest::GLOBAL_POLICY_ID,
1, NULL);
if (! status)
{
LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString()
<< LL_ENDL;
}
// Set up SSL Verification call back.
status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_SSL_VERIFY_CALLBACK,
LLCore::HttpRequest::GLOBAL_POLICY_ID,
sslVerify, NULL);
if (!status)
{
LL_WARNS("Init") << "Failed to set SSL Verification. Reason: " << status.toString() << LL_ENDL;
}
// Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy):
// 0 - None
// 1 - Basic start, stop simple transitions
// 2 - libcurl CURLOPT_VERBOSE mode with brief lines
// 3 - with partial data content
static const std::string http_trace("QAModeHttpTrace");
if (gSavedSettings.controlExists(http_trace))
{
long trace_level(0L);
trace_level = long(gSavedSettings.getU32(http_trace));
status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE,
LLCore::HttpRequest::GLOBAL_POLICY_ID,
trace_level, NULL);
}
// Setup default policy and constrain if directed to
mHttpClasses[AP_DEFAULT].mPolicy = LLCore::HttpRequest::DEFAULT_POLICY_ID;
// Setup additional policies based on table and some special rules
llassert(LL_ARRAY_SIZE(init_data) == AP_COUNT);
for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
{
const EAppPolicy app_policy(static_cast<EAppPolicy>(i));
if (AP_DEFAULT == app_policy)
{
// Pre-created
continue;
}
mHttpClasses[app_policy].mPolicy = LLCore::HttpRequest::createPolicyClass();
// We have run out of available HTTP policies. Adjust HTTP_POLICY_CLASS_LIMIT in _httpinternal.h
llassert(mHttpClasses[app_policy].mPolicy != LLCore::HttpRequest::INVALID_POLICY_ID);
if (! mHttpClasses[app_policy].mPolicy)
{
// Use default policy (but don't accidentally modify default)
LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage
<< ". Using default policy."
<< LL_ENDL;
mHttpClasses[app_policy].mPolicy = mHttpClasses[AP_DEFAULT].mPolicy;
continue;
}
}
// Need a request object to handle dynamic options before setting them
mRequest = new LLCore::HttpRequest;
// Apply initial settings
refreshSettings(true);
// Kick the thread
status = LLCore::HttpRequest::startThread();
if (! status)
{
LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " << status.toString()
<< LL_ENDL;
}
// Signal for global pipelining preference from settings
static const std::string http_pipelining("HttpPipelining");
if (gSavedSettings.controlExists(http_pipelining))
{
LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl(http_pipelining);
if (cntrl_ptr.isNull())
{
LL_WARNS("Init") << "Unable to set signal on global setting '" << http_pipelining
<< "'" << LL_ENDL;
}
else
{
mPipelinedSignal = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed));
}
}
// Register signals for settings and state changes
for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
{
const EAppPolicy app_policy(static_cast<EAppPolicy>(i));
if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey))
{
LLPointer<LLControlVariable> cntrl_ptr = gSavedSettings.getControl(init_data[i].mKey);
if (cntrl_ptr.isNull())
{
LL_WARNS("Init") << "Unable to set signal on global setting '" << init_data[i].mKey
<< "'" << LL_ENDL;
}
else
{
mHttpClasses[app_policy].mSettingsSignal = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed));
}
}
}
}
void setting_changed()
{
LLAppViewer::instance()->getAppCoreHttp().refreshSettings(false);
}
namespace
{
// The NoOpDeletor is used when wrapping LLAppCoreHttp in a smart pointer below for
// passage into the LLCore::Http libararies. When the smart pointer is destroyed,
// no action will be taken since we do not in this case want the entire LLAppCoreHttp object
// to be destroyed at the end of the call.
//
// *NOTE$: Yes! It is "Deletor"
// http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb
// "delete" derives from Latin "deletus"
void NoOpDeletor(LLCore::HttpHandler *)
{ /*NoOp*/ }
}
void LLAppCoreHttp::requestStop()
{
llassert_always(mRequest);
mStopHandle = mRequest->requestStopThread(LLCore::HttpHandler::ptr_t(this, NoOpDeletor));
if (LLCORE_HTTP_HANDLE_INVALID != mStopHandle)
{
mStopRequested = LLTimer::getTotalSeconds();
}
}
void LLAppCoreHttp::cleanup()
{
if (LLCORE_HTTP_HANDLE_INVALID == mStopHandle)
{
// Should have been started already...
requestStop();
}
if (LLCORE_HTTP_HANDLE_INVALID == mStopHandle)
{
LL_WARNS("Cleanup") << "Attempting to cleanup HTTP services without thread shutdown"
<< LL_ENDL;
}
else
{
while (! mStopped && LLTimer::getTotalSeconds() < (mStopRequested + MAX_THREAD_WAIT_TIME))
{
mRequest->update(200000);
ms_sleep(50);
}
if (! mStopped)
{
LL_WARNS("Cleanup") << "Attempting to cleanup HTTP services with thread shutdown incomplete"
<< LL_ENDL;
}
}
for (int i(0); i < LL_ARRAY_SIZE(mHttpClasses); ++i)
{
mHttpClasses[i].mSettingsSignal.disconnect();
}
mPipelinedSignal.disconnect();
delete mRequest;
mRequest = NULL;
LLCore::HttpStatus status = LLCore::HttpRequest::destroyService();
if (! status)
{
LL_WARNS("Cleanup") << "Failed to shutdown HTTP services, continuing. Reason: "
<< status.toString()
<< LL_ENDL;
}
}
void LLAppCoreHttp::refreshSettings(bool initial)
{
LLCore::HttpStatus status;
// Global pipelining setting
bool pipeline_changed(false);
static const std::string http_pipelining("HttpPipelining");
if (gSavedSettings.controlExists(http_pipelining))
{
// Default to true (in ctor) if absent.
bool pipelined(gSavedSettings.getBOOL(http_pipelining));
if (pipelined != mPipelined)
{
mPipelined = pipelined;
pipeline_changed = true;
}
LL_INFOS("Init") << "HTTP Pipelining " << (mPipelined ? "enabled" : "disabled") << "!" << LL_ENDL;
}
for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i)
{
const EAppPolicy app_policy(static_cast<EAppPolicy>(i));
if (initial)
{
// Init-time only settings, can use the static setters here
if (init_data[i].mRate)
{
// Set any desired throttle
status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_THROTTLE_RATE,
mHttpClasses[app_policy].mPolicy,
init_data[i].mRate,
NULL);
if (! status)
{
LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
<< " throttle rate. Reason: " << status.toString()
<< LL_ENDL;
}
}
}
// Init- or run-time settings. Must use the queued request API.
// Pipelining changes
if (initial || pipeline_changed)
{
const bool to_pipeline(mPipelined && init_data[i].mPipelined);
if (to_pipeline != mHttpClasses[app_policy].mPipelined)
{
// Pipeline election changing, set dynamic option via request
LLCore::HttpHandle handle;
const long new_depth(to_pipeline ? PIPELINING_DEPTH : 0);
handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_PIPELINING_DEPTH,
mHttpClasses[app_policy].mPolicy,
new_depth,
LLCore::HttpHandler::ptr_t());
if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
status = mRequest->getStatus();
LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
<< " pipelining. Reason: " << status.toString()
<< LL_ENDL;
}
else
{
LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage
<< " pipelining. New value: " << new_depth
<< LL_ENDL;
mHttpClasses[app_policy].mPipelined = to_pipeline;
}
}
}
// Get target connection concurrency value
U32 setting(init_data[i].mDefault);
if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey))
{
U32 new_setting(gSavedSettings.getU32(init_data[i].mKey));
if (new_setting)
{
// Treat zero settings as an ask for default
setting = llclamp(new_setting, init_data[i].mMin, init_data[i].mMax);
}
}
if (initial || setting != mHttpClasses[app_policy].mConnLimit || pipeline_changed)
{
// Set it and report. Strategies depend on pipelining:
//
// No Pipelining. Llcorehttp manages connections itself based
// on the PO_CONNECTION_LIMIT setting. Set both limits to the
// same value for logical consistency. In the future, may
// hand over connection management to libcurl after the
// connection cache has been better vetted.
//
// Pipelining. Libcurl is allowed to manage connections to a
// great degree. Steady state will connection limit based on
// the per-host setting. Transitions (region crossings, new
// avatars, etc.) can request additional outbound connections
// to other servers via 2X total connection limit.
//
LLCore::HttpHandle handle;
handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT,
mHttpClasses[app_policy].mPolicy,
(mHttpClasses[app_policy].mPipelined ? 2 * setting : setting),
LLCore::HttpHandler::ptr_t());
if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
status = mRequest->getStatus();
LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
<< " concurrency. Reason: " << status.toString()
<< LL_ENDL;
}
else
{
handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_PER_HOST_CONNECTION_LIMIT,
mHttpClasses[app_policy].mPolicy,
setting,
LLCore::HttpHandler::ptr_t());
if (LLCORE_HTTP_HANDLE_INVALID == handle)
{
status = mRequest->getStatus();
LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage
<< " per-host concurrency. Reason: " << status.toString()
<< LL_ENDL;
}
else
{
LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage
<< " concurrency. New value: " << setting
<< LL_ENDL;
mHttpClasses[app_policy].mConnLimit = setting;
if (initial && setting != init_data[i].mDefault)
{
LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage
<< " concurrency. New value: " << setting
<< LL_ENDL;
}
}
}
}
}
}
LLCore::HttpStatus LLAppCoreHttp::sslVerify(const std::string &url,
const LLCore::HttpHandler::ptr_t &handler, void *appdata)
{
X509_STORE_CTX *ctx = static_cast<X509_STORE_CTX *>(appdata);
LLCore::HttpStatus result;
LLPointer<LLCertificateStore> store = gSecAPIHandler->getCertificateStore("");
LLPointer<LLCertificateChain> chain = gSecAPIHandler->getCertificateChain(ctx);
LLSD validation_params = LLSD::emptyMap();
LLURI uri(url);
validation_params[CERT_HOSTNAME] = uri.hostName();
// *TODO: In the case of an exception while validating the cert, we need a way
// to pass the offending(?) cert back out. *Rider*
try
{
// don't validate hostname. Let libcurl do it instead. That way, it'll handle redirects
store->validate(VALIDATION_POLICY_SSL & (~VALIDATION_POLICY_HOSTNAME), chain, validation_params);
}
catch (LLCertValidationTrustException &cert_exception)
{
// this exception is is handled differently than the general cert
// exceptions, as we allow the user to actually add the certificate
// for trust.
// therefore we pass back a different error code
// NOTE: We're currently 'wired' to pass around CURL error codes. This is
// somewhat clumsy, as we may run into errors that do not map directly to curl
// error codes. Should be refactored with login refactoring, perhaps.
result = LLCore::HttpStatus(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_SSL_CACERT);
result.setMessage(cert_exception.what());
LLPointer<LLCertificate> cert = cert_exception.getCert();
cert->ref(); // adding an extra ref here
result.setErrorData(cert.get());
// We should probably have a more generic way of passing information
// back to the error handlers.
}
catch (LLCertException &cert_exception)
{
result = LLCore::HttpStatus(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_SSL_PEER_CERTIFICATE);
result.setMessage(cert_exception.what());
LLPointer<LLCertificate> cert = cert_exception.getCert();
cert->ref(); // adding an extra ref here
result.setErrorData(cert.get());
}
catch (...)
{
// any other odd error, we just handle as a connect error.
result = LLCore::HttpStatus(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_SSL_CONNECT_ERROR);
}
return result;
}
void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *)
{
mStopped = true;
}