123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152 |
- Filename: 111-local-traffic-priority.txt
- Title: Prioritizing local traffic over relayed traffic
- Version: $Revision$
- Last-Modified: $Date$
- Author: Roger Dingledine
- Created: 14-Mar-2007
- Status: Finished
- Overview:
- We describe some ways to let Tor users operate as a relay and enforce
- rate limiting for relayed traffic without impacting their locally
- initiated traffic.
- Motivation:
- Right now we encourage people who use Tor as a client to configure it
- as a relay too ("just click the button in Vidalia"). Most of these users
- are on asymmetric links, meaning they have a lot more download capacity
- than upload capacity. But if they enable rate limiting too, suddenly
- they're limited to the same download capacity as upload capacity. And
- they have to enable rate limiting, or their upstream pipe gets filled
- up, starts dropping packets, and now their net connection doesn't work
- even for non-Tor stuff. So they end up turning off the relaying part
- so they can use Tor (and other applications) again.
- So far this hasn't mattered that much: most of our fast relays are
- being operated only in relay mode, so the rate limiting makes sense
- for them. But if we want to be able to attract many more relays in
- the future, we need to let ordinary users act as relays too.
- Further, as we begin to deploy the blocking-resistance design and we
- rely on ordinary users to click the "Tor for Freedom" button, this
- limitation will become a serious stumbling block to getting volunteers
- to act as bridges.
- The problem:
- Tor implements its rate limiting on the 'read' side by only reading
- a certain number of bytes from the network in each second. If it has
- emptied its token bucket, it doesn't read any more from the network;
- eventually TCP notices and stalls until we resume reading. But if we
- want to have two classes of service, we can't know what class a given
- incoming cell will be until we look at it, at which point we've already
- read it.
- Some options:
- Option 1: read when our token bucket is full enough, and if it turns
- out that what we read was local traffic, then add the tokens back into
- the token bucket. This will work when local traffic load alternates
- with relayed traffic load; but it's a poor option in general, because
- when we're receiving both local and relayed traffic, there are plenty
- of cases where we'll end up with an empty token bucket, and then we're
- back where we were before.
- More generally, notice that our problem is easy when a given TCP
- connection either has entirely local circuits or entirely relayed
- circuits. In fact, even if they are both present, if one class is
- entirely idle (none of its circuits have sent or received in the past
- N seconds), we can ignore that class until it wakes up again. So it
- only gets complex when a single connection contains active circuits
- of both classes.
- Next, notice that local traffic uses only the entry guards, whereas
- relayed traffic likely doesn't. So if we're a bridge handling just
- a few users, the expected number of overlapping connections would be
- almost zero, and even if we're a full relay the number of overlapping
- connections will be quite small.
- Option 2: build separate TCP connections for local traffic and for
- relayed traffic. In practice this will actually only require a few
- extra TCP connections: we would only need redundant TCP connections
- to at most the number of entry guards in use.
- However, this approach has some drawbacks. First, if the remote side
- wants to extend a circuit to you, how does it know which TCP connection
- to send it on? We would need some extra scheme to label some connections
- "client-only" during construction. Perhaps we could do this by seeing
- whether any circuit was made via CREATE_FAST; but this still opens
- up a race condition where the other side sends a create request
- immediately. The only ways I can imagine to avoid the race entirely
- are to specify our preference in the VERSIONS cell, or to add some
- sort of "nope, not this connection, why don't you try another rather
- than failing" response to create cells, or to forbid create cells on
- connections that you didn't initiate and on which you haven't seen
- any circuit creation requests yet -- this last one would lead to a bit
- more connection bloat but doesn't seem so bad. And we already accept
- this race for the case where directory authorities establish new TCP
- connections periodically to check reachability, and then hope to hang
- up on them soon after. (In any case this issue is moot for bridges,
- since each destination will be one-way with respect to extend requests:
- either receiving extend requests from bridge users or sending extend
- requests to the Tor server, never both.)
- The second problem with option 2 is that using two TCP connections
- reveals that there are two classes of traffic (and probably quickly
- reveals which is which, based on throughput). Now, it's unclear whether
- this information is already available to the other relay -- he would
- easily be able to tell that some circuits are fast and some are rate
- limited, after all -- but it would be nice to not add even more ways to
- leak that information. Also, it's less clear that an external observer
- already has this information if the circuits are all bundled together,
- and for this case it's worth trying to protect it.
- Option 3: tell the other side about our rate limiting rules. When we
- establish the TCP connection, specify the different policy classes we
- have configured. Each time we extend a circuit, specify which policy
- class that circuit should be part of. Then hope the other side obeys
- our wishes. (If he doesn't, hang up on him.) Besides the design and
- coordination hassles involved in this approach, there's a big problem:
- our rate limiting classes apply to all our connections, not just
- pairwise connections. How does one server we're connected to know how
- much of our bucket has already been spent by another? I could imagine
- a complex and inefficient "ok, now you can send me those two more cells
- that you've got queued" protocol. I'm not sure how else we could do it.
- (Gosh. How could UDP designs possibly be compatible with rate limiting
- with multiple bucket sizes?)
- Option 4: put both classes of circuits over a single connection, and
- keep track of the last time we read or wrote a high-priority cell. If
- it's been less than N seconds, give the whole connection high priority,
- else give the whole connection low priority.
- Option 5: put both classes of circuits over a single connection, and
- play a complex juggling game by periodically telling the remote side
- what rate limits to set for that connection, so you end up giving
- priority to the right connections but still stick to roughly your
- intended bandwidthrate and relaybandwidthrate.
- Option 6: ?
- Prognosis:
- Nick really didn't like option 2 because of the partitioning questions.
- I've put option 4 into place as of Tor 0.2.0.3-alpha.
- In terms of implementation, it will be easy: just add a time_t to
- or_connection_t that specifies client_used (used by the initiator
- of the connection to rate limit it differently depending on how
- recently the time_t was reset). We currently update client_used
- in three places:
- - command_process_relay_cell() when we receive a relay cell for
- an origin circuit.
- - relay_send_command_from_edge() when we send a relay cell for
- an origin circuit.
- - circuit_deliver_create_cell() when send a create cell.
- We could probably remove the third case and it would still work,
- but hey.
|