Commit ba5c67eb authored by Mike Perry's avatar Mike Perry
Browse files

Randomize HTTP request order and pipeline depth.

This is an experimental defense against Website Traffic Fingerprinting:
http://lorre.uni.lu/~andriy/papers/acmccs-wpes11-fingerprinting.pdf

See:
https://blog.torproject.org/blog/experimental-defense-website-traffic-fingerprinting

This patch is different from the approach described in that post, as well as
the 10.x ESR patch, as the pipelining code has changed significantly between
the time of writing of that post and Firefox 17.

The main control nob for this patch is now the about:config pref
"network.http.pipelining.max-optimistic-requests". The value of that pref
represents the minimum number of pipelined requests we will attempt to batch
together.

The total outstanding pipeline size is randomized between that value and
"network.http.pipelining.maxrequests" on a per-host basis.

Care must be taken when evaluating this defense, as pipeline behavior is
extremely sensitive to browser performance. In fact, a debug build alone is
enough to significantly impair request availability to the pipeline (due
slower document parsing and rendering). For this reason, we provide two
separate debug log defines. For most evaluation circumstances, you want to
define only WTF_TEST in an optimized build to only log request order,
combination behavior, and cases where the pipeline is forcibly disabled.

This patch may also have some minor impact on SPDY request order, but the SPDY
implementation has not been altered directly. It has several stream queues
that may also benefit from reordering and batching, as well as a more compact
request representation that will allow more requests to be packed inside Tor
cells. If you have interest in evaluating SPDY in a study of Website Traffic
Fingerprinting, please contact me.
parent f3f6cf76
Loading
Loading
Loading
Loading
+253 −106
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@
#include "mozilla/VisualEventTracer.h"
#include <algorithm>

#include <stdlib.h>

using namespace mozilla;
using namespace mozilla::net;

@@ -45,15 +47,26 @@ InsertTransactionSorted(nsTArray<nsHttpTransaction*> &pendingQ, nsHttpTransactio
    // insert into queue with smallest valued number first.  search in reverse
    // order under the assumption that many of the existing transactions will
    // have the same priority (usually 0).
    uint32_t len = pendingQ.Length();

    for (int32_t i=pendingQ.Length()-1; i>=0; --i) {
        nsHttpTransaction *t = pendingQ[i];
        if (trans->Priority() >= t->Priority()) {
            pendingQ.InsertElementAt(i+1, trans);
    if (pendingQ.IsEmpty()) {
        pendingQ.InsertElementAt(0, trans);
        return;
    }
    }

    pendingQ.InsertElementAt(0, trans);

    // FIXME: Refactor into standalone helper (for nsHttpPipeline)
    // Or at least simplify this function if this shuffle ends up
    // being an improvement.
    uint32_t i = 0;
    for (i=0; i < len; ++i) {
        uint32_t ridx = rand() % len;

        nsHttpTransaction *tmp = pendingQ[i];
        pendingQ[i] = pendingQ[ridx];
        pendingQ[ridx] = tmp;
    }
}

//-----------------------------------------------------------------------------
@@ -981,6 +994,10 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid
    nsHttpTransaction *trans;
    nsresult rv;
    bool dispatchedSuccessfully = false;
    int dispatchCount = 0;
#ifdef WTF_DEBUG
    uint32_t total = ent->mPendingQ.Length();
#endif

    // if !considerAll iterate the pending list until one is dispatched successfully.
    // Keep iterating afterwards only until a transaction fails to dispatch.
@@ -988,18 +1005,21 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid
    for (uint32_t i = 0; i < ent->mPendingQ.Length(); ) {
        trans = ent->mPendingQ[i];

        // When this transaction has already established a half-open
        // When this entry has already established a half-open
        // connection, we want to prevent any duplicate half-open
        // connections from being established and bound to this
        // transaction. Allow only use of an idle persistent connection
        // (if found) for transactions referred by a half-open connection.
        // transaction.
        bool alreadyHalfOpen = false;
        if (ent->SupportsPipelining()) {
            alreadyHalfOpen = (ent->UnconnectedHalfOpens() > 0);
        } else {
            for (int32_t j = 0; j < ((int32_t) ent->mHalfOpens.Length()); ++j) {
                if (ent->mHalfOpens[j]->Transaction() == trans) {
                    alreadyHalfOpen = true;
                    break;
                }
            }
        }

        rv = TryDispatchTransaction(ent, alreadyHalfOpen, trans);
        if (NS_SUCCEEDED(rv) || (rv != NS_ERROR_NOT_AVAILABLE)) {
@@ -1011,6 +1031,7 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid

            if (ent->mPendingQ.RemoveElement(trans)) {
                dispatchedSuccessfully = true;
                dispatchCount++;
                NS_RELEASE(trans);
                continue; // dont ++i as we just made the array shorter
            }
@@ -1018,11 +1039,22 @@ nsHttpConnectionMgr::ProcessPendingQForEntry(nsConnectionEntry *ent, bool consid
            LOG(("  transaction not found in pending queue\n"));
        }

        if (dispatchedSuccessfully && !considerAll)
            break;
        // We want to keep walking the dispatch table to ensure requests
        // get combined properly.
        //if (dispatchedSuccessfully && !considerAll)
        //    break;

        ++i;
    }

#ifdef WTF_DEBUG
    if (dispatchedSuccessfully) {
        fprintf(stderr, "WTF-queue: Dispatched %d/%d pending transactions for %s\n",
                dispatchCount, total, ent->mConnInfo->Host());
        return true;
    }
#endif

    return dispatchedSuccessfully;
}

@@ -1323,6 +1355,10 @@ nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
    if (AtActiveConnectionLimit(ent, trans->Caps()))
        return NS_ERROR_NOT_AVAILABLE;

#ifdef WTF_DEBUG
        fprintf(stderr, "WTF: MakeNewConnection() is creating a transport (pipelines %d) for host %s\n",
                ent->SupportsPipelining(), ent->mConnInfo->Host());
#endif
    nsresult rv = CreateTransport(ent, trans, trans->Caps(), false);
    if (NS_FAILED(rv)) {
        /* hard failure */
@@ -1339,7 +1375,7 @@ nsHttpConnectionMgr::MakeNewConnection(nsConnectionEntry *ent,
}

bool
nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
nsHttpConnectionMgr::AddToBestPipeline(nsConnectionEntry *ent,
                                           nsHttpTransaction *trans,
                                           nsHttpTransaction::Classifier classification,
                                           uint16_t depthLimit)
@@ -1376,48 +1412,117 @@ nsHttpConnectionMgr::AddToShortestPipeline(nsConnectionEntry *ent,
    if (maxdepth < 2)
        return false;

    nsAHttpTransaction *activeTrans;
    // Find out how many requests of this class we have
    uint32_t sameClass = 0;
    uint32_t allClasses = ent->mPendingQ.Length();
    for (uint32_t i = 0; i < allClasses; ++i) {
        if (trans != ent->mPendingQ[i] &&
            classification == ent->mPendingQ[i]->Classification()) {
            sameClass++;
        }
    }

    nsAHttpTransaction *activeTrans;
    nsHttpPipeline *pipeline;
    nsHttpConnection *bestConn = nullptr;
    uint32_t activeCount = ent->mActiveConns.Length();
    uint32_t bestConnLength = 0;
    uint32_t connLength;
    uint32_t pipelineDepth;
    uint32_t requestLen;
    uint32_t totalDepth = 0;

    // Now, try to find the best pipeline
    nsTArray<nsHttpConnection *> validConns;
    nsTArray<nsHttpConnection *> betterConns;
    nsTArray<nsHttpConnection *> bestConns;
    uint32_t numPipelines = 0;

    for (uint32_t i = 0; i < activeCount; ++i) {
        nsHttpConnection *conn = ent->mActiveConns[i];
        if (!conn->SupportsPipelining())
            continue;

        if (conn->Classification() != classification)
        if (!conn->SupportsPipelining())
            continue;

        activeTrans = conn->Transaction();

        if (!activeTrans ||
            activeTrans->IsDone() ||
            NS_FAILED(activeTrans->Status()))
            continue;

        connLength = activeTrans->PipelineDepth();
        pipeline = activeTrans->QueryPipeline();
        if (!pipeline)
            continue;

        numPipelines++;

        if (maxdepth <= connLength)
        pipelineDepth = activeTrans->PipelineDepth();
        requestLen = pipeline->RequestDepth();

        totalDepth += pipelineDepth;

        // If we're within striking distance of our pipeline
        // packaging goal, give a little slack on the depth
        // limit to allow us to try to get there. Don't give
        // too much slack, though, or we'll tend to have
        // request packages of the same size when we have
        // many content elements appear at once.
        if (maxdepth +
              PR_MIN(mMaxOptimisticPipelinedRequests,
                     requestLen + allClasses)
              <= pipelineDepth)
            continue;

        if (!bestConn || (connLength < bestConnLength)) {
            bestConn = conn;
            bestConnLength = connLength;
        }
    }
        validConns.AppendElement(conn);

        // Prefer a pipeline that either has at least two requests
        // queued already, or for which we can add multiple requests
        if (requestLen + allClasses < mMaxOptimisticPipelinedRequests)
            continue;

    if (!bestConn)
        betterConns.AppendElement(conn);

        // Prefer a pipeline with the same classification if 
        // our current classes will put it over the line
        if (conn->Classification() != classification)
            continue;
        if (requestLen + sameClass < mMaxOptimisticPipelinedRequests)
            continue;

        bestConns.AppendElement(conn);
    }

    const char *type;
    if (bestConns.Length()) {
        type = "best";
        bestConn = bestConns[rand()%bestConns.Length()];
    } else if (betterConns.Length()) {
        type = "better";
        bestConn = betterConns[rand()%betterConns.Length()];
    } else if (validConns.Length() && totalDepth == 0) {
        // We only use valid conns if it's a last resort
        // (No other requests are pending or in flight)
        type = "valid";
        bestConn = validConns[rand()%validConns.Length()];
    } else {
        return false;
    }

    activeTrans = bestConn->Transaction();
    nsresult rv = activeTrans->AddTransaction(trans);
    if (NS_FAILED(rv))
        return false;

    LOG(("   scheduling trans %p on pipeline at position %d\n",
         trans, trans->PipelinePosition()));
    LOG(("   scheduling trans %p on pipeline at position %d, type %s\n",
         trans, trans->PipelinePosition(), type));

#ifdef WTF_DEBUG
    pipeline = activeTrans->QueryPipeline();
    fprintf(stderr,
            "WTF-depth: Added trans to %s of %d/%d/%d/%d pipelines. Request len %d/%d/%d for %s\n",
            type, bestConns.Length(), betterConns.Length(), validConns.Length(),
            numPipelines, pipeline->RequestDepth(), activeTrans->PipelineDepth(),
            maxdepth, ent->mConnInfo->Host());
#endif

    if ((ent->PipelineState() == PS_YELLOW) && (trans->PipelinePosition() > 1))
        ent->SetYellowConnection(bestConn);
@@ -1491,26 +1596,12 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
    nsHttpTransaction::Classifier classification = trans->Classification();
    uint32_t caps = trans->Caps();

    bool allowNewPipelines = true;

    // no keep-alive means no pipelines either
    if (!(caps & NS_HTTP_ALLOW_KEEPALIVE))
        caps = caps & ~NS_HTTP_ALLOW_PIPELINING;

    // 0 - If this should use spdy then dispatch it post haste.
    // 1 - If there is connection pressure then see if we can pipeline this on
    //     a connection of a matching type instead of using a new conn
    // 2 - If there is an idle connection, use it!
    // 3 - if class == reval or script and there is an open conn of that type
    //     then pipeline onto shortest pipeline of that class if limits allow
    // 4 - If we aren't up against our connection limit,
    //     then open a new one
    // 5 - Try a pipeline if we haven't already - this will be unusual because
    //     it implies a low connection pressure situation where
    //     MakeNewConnection() failed.. that is possible, but unlikely, due to
    //     global limits
    // 6 - no connection is available - queue it

    bool attemptedOptimisticPipeline = !(caps & NS_HTTP_ALLOW_PIPELINING);

    // step 0
    // look for existing spdy connection - that's always best because it is
    // essentially pipelining without head of line blocking
@@ -1549,17 +1640,14 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
        trans->DispatchedAsBlocking();
    }

    // step 1
    // If connection pressure, then we want to favor pipelining of any kind
    if (IsUnderPressure(ent, classification) && !attemptedOptimisticPipeline) {
        attemptedOptimisticPipeline = true;
        if (AddToShortestPipeline(ent, trans,
                                  classification,
                                  mMaxOptimisticPipelinedRequests)) {
    // step 1: Try a pipeline
    if (caps & NS_HTTP_ALLOW_PIPELINING &&
        AddToBestPipeline(ent, trans, classification,
                          mMaxPipelinedRequests)) {
        return NS_OK;
    }
    }

    // XXX: Kill this block? It's new.. but it may be needed for SPDY
    // Subject most transactions at high parallelism to rate pacing.
    // It will only be actually submitted to the
    // token bucket once, and if possible it is granted admission synchronously.
@@ -1577,9 +1665,20 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
        }
    }

    // step 2
    // consider an idle persistent connection
    if (caps & NS_HTTP_ALLOW_KEEPALIVE) {
    // Step 2: Decide if we should forbid new pipeline creation.
    //
    // FIXME: We repurposed mMaxOptimisticPipelinedRequests here to mean:
    // "Don't make a new pipeline until you have this many requests pending and 
    // no potential connections to put them on". It might be nice to give this
    // its own pref..
    if (HasPipelines(ent) &&
            ent->mPendingQ.Length() < mMaxOptimisticPipelinedRequests &&
            trans->Classification() != nsAHttpTransaction::CLASS_SOLO &&
            caps & NS_HTTP_ALLOW_PIPELINING)
        allowNewPipelines = false;

    // step 3: consider an idle persistent connection
    if (allowNewPipelines && (caps & NS_HTTP_ALLOW_KEEPALIVE)) {
        nsRefPtr<nsHttpConnection> conn;
        while (!conn && (ent->mIdleConns.Length() > 0)) {
            conn = ent->mIdleConns[0];
@@ -1613,21 +1712,8 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
        }
    }

    // step 3
    // consider pipelining scripts and revalidations
    if (!attemptedOptimisticPipeline &&
        (classification == nsHttpTransaction::CLASS_REVALIDATION ||
         classification == nsHttpTransaction::CLASS_SCRIPT)) {
        attemptedOptimisticPipeline = true;
        if (AddToShortestPipeline(ent, trans,
                                  classification,
                                  mMaxOptimisticPipelinedRequests)) {
            return NS_OK;
        }
    }

    // step 4
    if (!onlyReusedConnection) {
    // step 4: Maybe make a connection? 
    if (!onlyReusedConnection && allowNewPipelines) {
        nsresult rv = MakeNewConnection(ent, trans);
        if (NS_SUCCEEDED(rv)) {
            // this function returns NOT_AVAILABLE for asynchronous connects
@@ -1641,16 +1727,16 @@ nsHttpConnectionMgr::TryDispatchTransaction(nsConnectionEntry *ent,
        }
    }

    // step 5
    if (caps & NS_HTTP_ALLOW_PIPELINING) {
        if (AddToShortestPipeline(ent, trans,
                                  classification,
                                  mMaxPipelinedRequests)) {
            return NS_OK;
        }
    }

    // step 6
    // XXX: We dequeue and queue the same url here sometimes..
#ifdef WTF_DEBUG
    nsHttpRequestHead *head = trans->RequestHead();
    fprintf(stderr, "WTF: Queuing url %s%s\n",
            ent->mConnInfo->Host(),
            head ? head->RequestURI().BeginReading() : "<unknown?>");
#endif
     
    // step 5: Queue it
    return NS_ERROR_NOT_AVAILABLE;                /* queue it */
}

@@ -1742,10 +1828,28 @@ nsHttpConnectionMgr::DispatchAbstractTransaction(nsConnectionEntry *ent,
        if (!NS_SUCCEEDED(rv))
            return rv;
        transaction = pipeline;
#ifdef WTF_DEBUG
        if (HasPipelines(ent) &&
                ent->mPendingQ.Length()+1 < mMaxOptimisticPipelinedRequests) {
            fprintf(stderr, "WTF-new-bug: New pipeline created from %d idle conns for host %s with %d/%d pending\n",
                    ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(),
                    mMaxOptimisticPipelinedRequests);
        } else {
            fprintf(stderr, "WTF-new: New pipeline created from %d idle conns for host %s with %d/%d pending\n",
                    ent->mIdleConns.Length(), ent->mConnInfo->Host(), ent->mPendingQ.Length(),
                    mMaxOptimisticPipelinedRequests);
        }
#endif
    }
    else {
        LOG(("   not using pipeline datastructure due to class solo.\n"));
        transaction = aTrans;
#ifdef WTF_TEST
        nsHttpRequestHead *head = transaction->RequestHead();
        fprintf(stderr, "WTF-order: Pipeline forbidden for url %s%s\n",
                ent->mConnInfo->Host(),
                head ? head->RequestURI().BeginReading() : "<unknown?>");
#endif
    }

    nsRefPtr<nsConnectionHandle> handle = new nsConnectionHandle(conn);
@@ -1859,28 +1963,19 @@ nsHttpConnectionMgr::ProcessNewTransaction(nsHttpTransaction *trans)
        MOZ_ASSERT(((int32_t)ent->mActiveConns.IndexOf(conn)) != -1,
                   "Sticky Connection Not In Active List");
        trans->SetConnection(nullptr);
#ifdef WTF_TEST
        fprintf(stderr, "WTF-bad: Sticky connection status on 1 transaction to host %s\n",
                ent->mConnInfo->Host());
#endif
        rv = DispatchTransaction(ent, trans, conn);
    }
    else
        rv = TryDispatchTransaction(ent, false, trans);

    if (NS_SUCCEEDED(rv)) {
        LOG(("  ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
        return rv;
    }

    if (rv == NS_ERROR_NOT_AVAILABLE) {
        LOG(("  adding transaction to pending queue "
             "[trans=%p pending-count=%u]\n",
             trans, ent->mPendingQ.Length()+1));
        // put this transaction on the pending queue...
    } else {
        // XXX: maybe check the queue first and directly call TryDispatch?
        InsertTransactionSorted(ent->mPendingQ, trans);
        NS_ADDREF(trans);
        ProcessPendingQForEntry(ent, true);
        return NS_OK;
    }

    LOG(("  ProcessNewTransaction Hard Error trans=%p rv=%x\n", trans, rv));
    return rv;
}


@@ -2504,16 +2599,52 @@ nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param)
    if (preferredEntry)
        ent = preferredEntry;

    if (mNumHalfOpenConns < gHttpHandler->ParallelSpeculativeConnectLimit() &&
    if (ent->SupportsPipelining()) {
        /* Only speculative connect if we're not pipelining and have no other pending
         * unconnected half-opens.. */
        if (ent->UnconnectedHalfOpens() == 0 && ent->mIdleConns.Length() == 0
                && !RestrictConnections(ent) && !HasPipelines(ent)
                && !AtActiveConnectionLimit(ent, trans->Caps())) {
#ifdef WTF_DEBUG
            fprintf(stderr, "WTF: Creating speculative connection because we have no pipelines\n");
#endif
            CreateTransport(ent, trans, trans->Caps(), true);
        }
    } else if (mNumHalfOpenConns < gHttpHandler->ParallelSpeculativeConnectLimit() &&
        !ent->mIdleConns.Length() && !RestrictConnections(ent) &&
        !AtActiveConnectionLimit(ent, trans->Caps())) {
#ifdef WTF_DEBUG
            fprintf(stderr, "WTF: Creating speculative connection because we can't pipeline\n");
#endif
        CreateTransport(ent, trans, trans->Caps(), true);
    }
    else {
    } else {
        LOG(("  Transport not created due to existing connection count\n"));
    }
}

bool
nsHttpConnectionMgr::HasPipelines(nsConnectionEntry *ent)
{
    uint32_t activeCount = ent->mActiveConns.Length();

    if (!ent->SupportsPipelining()) {
        return false;
    }

    for (uint32_t i = 0; i < activeCount; ++i) {
        nsHttpConnection *conn = ent->mActiveConns[i];
        if (!conn->SupportsPipelining())
            continue;

        nsAHttpTransaction *activeTrans = conn->Transaction();

        if (activeTrans && !activeTrans->IsDone() &&
            !NS_FAILED(activeTrans->Status()))
            return true;
    }
    return false;
}
 
bool
nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
{
@@ -2870,6 +3001,10 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
        nsRefPtr<nsHttpTransaction> temp = dont_AddRef(mEnt->mPendingQ[index]);
        mEnt->mPendingQ.RemoveElementAt(index);
        gHttpHandler->ConnMgr()->AddActiveConn(conn, mEnt);
#ifdef WTF_DEBUG
        fprintf(stderr, "WTF: Speculative half-opened connection is now ready for %s (pipelines %d)\n",
                mEnt->mConnInfo->Host(), mEnt->SupportsPipelining());
#endif
        rv = gHttpHandler->ConnMgr()->DispatchTransaction(mEnt, temp, conn);
    }
    else {
@@ -3063,10 +3198,16 @@ nsConnectionEntry::nsConnectionEntry(nsHttpConnectionInfo *ci)
    , mPreferIPv6(false)
{
    NS_ADDREF(mConnInfo);

    // Randomize the pipeline depth (3..12)
    mGreenDepth = gHttpHandler->GetMaxOptimisticPipelinedRequests()
                  + rand() % (gHttpHandler->GetMaxPipelinedRequests()
                              - gHttpHandler->GetMaxOptimisticPipelinedRequests());

    if (gHttpHandler->GetPipelineAggressive()) {
        mGreenDepth = kPipelineUnlimited;
        mPipelineState = PS_GREEN;
    }

    mInitialGreenDepth = mGreenDepth;
    memset(mPipeliningClassPenalty, 0, sizeof(int16_t) * nsAHttpTransaction::CLASS_MAX);
}
@@ -3104,8 +3245,9 @@ nsConnectionEntry::OnPipelineFeedbackInfo(
        LOG(("Transaction completed at pipeline depth of %d. Host = %s\n",
             depth, mConnInfo->Host()));

        if (depth >= 3)
            mGreenDepth = kPipelineUnlimited;
        // Don't set this. We want to keep our initial random value..
        //if (depth >= 3)
        //    mGreenDepth = kPipelineUnlimited;
    }

    nsAHttpTransaction::Classifier classification;
@@ -3133,6 +3275,11 @@ nsConnectionEntry::OnPipelineFeedbackInfo(
                 mPipelineState, mConnInfo->Host()));
            mPipelineState = PS_RED;
            mPipeliningPenalty = 0;
#ifdef WTF_TEST
            fprintf(stderr, "WTF-bad: Red pipeline status disabled host %s\n",
                    mConnInfo->Host());
#endif

        }

        if (mLastCreditTime.IsNull())
+14 −1
Original line number Diff line number Diff line
@@ -24,6 +24,18 @@
#include "nsIObserver.h"
#include "nsITimer.h"
#include "nsIX509Cert3.h"
#include "nsIRandomGenerator.h"
 
// We need our own optional debug define because pipelining behavior
// is significantly altered by rendering speed (which is abysmal on
// debug builds)
#ifdef DEBUG
# define WTF_DEBUG
#endif

#ifdef WTF_DEBUG
# define WTF_TEST
#endif

class nsHttpPipeline;

@@ -515,6 +527,7 @@ private:
    nsresult BuildPipeline(nsConnectionEntry *,
                           nsAHttpTransaction *,
                           nsHttpPipeline **);
    bool     HasPipelines(nsConnectionEntry *);
    bool     RestrictConnections(nsConnectionEntry *);
    nsresult ProcessNewTransaction(nsHttpTransaction *);
    nsresult EnsureSocketThreadTarget();
@@ -531,7 +544,7 @@ private:

    nsresult MakeNewConnection(nsConnectionEntry *ent,
                               nsHttpTransaction *trans);
    bool     AddToShortestPipeline(nsConnectionEntry *ent,
    bool     AddToBestPipeline(nsConnectionEntry *ent,
                                   nsHttpTransaction *trans,
                                   nsHttpTransaction::Classifier classification,
                                   uint16_t depthLimit);
+2 −0
Original line number Diff line number Diff line
@@ -240,6 +240,8 @@ public:
                                     nsCString& hostLine);

    bool GetPipelineAggressive()     { return mPipelineAggressive; }
    uint32_t GetMaxPipelinedRequests()     { return mMaxPipelinedRequests; }
    uint32_t GetMaxOptimisticPipelinedRequests()     { return mMaxOptimisticPipelinedRequests; }
    void GetMaxPipelineObjectSize(int64_t *outVal)
    {
        *outVal = mMaxPipelineObjectSize;
+60 −2
Original line number Diff line number Diff line
@@ -90,6 +90,32 @@ nsHttpPipeline::~nsHttpPipeline()
        free(mPushBackBuf);
}

// Generate a shuffled request ordering sequence 
void
nsHttpPipeline::ShuffleTransOrder(uint32_t count)
{
   if (count < 2)
       return;

   uint32_t pos = mRequestQ[0]->PipelinePosition();
   uint32_t i = 0;

   for (i=0; i < count; ++i) {
       uint32_t ridx = rand() % count;

       nsAHttpTransaction *tmp = mRequestQ[i];
       mRequestQ[i] = mRequestQ[ridx];
       mRequestQ[ridx] = tmp;
   }

   for (i=0; i < count; ++i) {
       mRequestQ[i]->SetPipelinePosition(pos);
       pos++;
   }

   LOG(("nsHttpPipeline::ShuffleTransOrder: Shuffled %d transactions.\n", count));
}

nsresult
nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
{
@@ -115,6 +141,8 @@ nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
    // the pipeline object.
    trans->SetConnection(this);

    ShuffleTransOrder(mRequestQ.Length());

    if (mConnection && !mClosed && mRequestQ.Length() == 1)
        mConnection->ResumeSend();

@@ -759,8 +787,11 @@ nsHttpPipeline::CancelPipeline(nsresult originalReason)
    if (respLen > 1)
        mResponseQ.TruncateLength(1);

    /* Don't flag timed out connections as unreusable.. Tor is just slow :( */
    if (originalReason != NS_ERROR_NET_TIMEOUT) {
        DontReuse();
        Classify(nsAHttpTransaction::CLASS_SOLO);
    }

    return total;
}
@@ -841,8 +872,19 @@ nsHttpPipeline::FillSendBuf()

    uint32_t n;
    uint64_t avail;
    uint64_t totalSent = 0;
    uint64_t reqsSent = 0;
    uint64_t alreadyPending = 0;

    mSendBufIn->Available(&alreadyPending);

    nsAHttpTransaction *trans;
    nsITransport *transport = Transport();
#ifdef WTF_TEST
    uint64_t totalAvailable = Available();
    nsRefPtr<nsHttpConnectionInfo> ci;
    GetConnectionInfo(getter_AddRefs(ci));
#endif

    while ((trans = Request(0)) != nullptr) {
        avail = trans->Available();
@@ -863,6 +905,7 @@ nsHttpPipeline::FillSendBuf()
            }

            mSendingToProgress += n;
            totalSent += n;
            if (!mSuppressSendEvents && transport) {
                // Simulate a SENDING_TO event
                trans->OnTransportStatus(transport,
@@ -873,6 +916,14 @@ nsHttpPipeline::FillSendBuf()

        avail = trans->Available();
        if (avail == 0) {
#ifdef WTF_TEST
            nsHttpRequestHead *head = trans->RequestHead();
            fprintf(stderr, "WTF-order: Pipelined req %d/%d (%dB). Url: %s%s\n",
                    trans->PipelinePosition(), PipelineDepth(), n,
                    ci->Host(), head ? head->RequestURI().BeginReading() : "<unknown?>");
#endif
            reqsSent++;

            // move transaction from request queue to response queue
            mRequestQ.RemoveElementAt(0);
            mResponseQ.AppendElement(trans);
@@ -892,5 +943,12 @@ nsHttpPipeline::FillSendBuf()
        else
            mRequestIsPartial = true;
    }

#ifdef WTF_TEST
    if (totalSent)
      fprintf(stderr, "WTF-combine: Sent %ld/%ld bytes of %ld combined pipelined requests for host %s\n",
              alreadyPending+totalSent, totalAvailable, reqsSent, ci->Host());
#endif

    return NS_OK;
}
+4 −0
Original line number Diff line number Diff line
@@ -27,12 +27,16 @@ public:
    nsHttpPipeline();
    virtual ~nsHttpPipeline();

    uint32_t RequestDepth() { return mRequestQ.Length(); }

private:
    nsresult FillSendBuf();

    static NS_METHOD ReadFromPipe(nsIInputStream *, void *, const char *,
                                  uint32_t, uint32_t, uint32_t *);

    void ShuffleTransOrder(uint32_t);

    // convenience functions
    nsAHttpTransaction *Request(int32_t i)
    {