mirror of
https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake.git
synced 2025-10-14 14:11:23 -04:00
more functional rate limiting
This commit is contained in:
parent
9c4fc24719
commit
75ac969fc7
1 changed files with 55 additions and 13 deletions
|
@ -65,6 +65,13 @@ Params =
|
|||
return false if 'false' == val || '0' == val
|
||||
return null
|
||||
|
||||
# Get an object value and parse it as a byte count. Example byte counts are
|
||||
# "100" and "1.3m". Returns default_val if param is not a key. Return null on
|
||||
# a parsing error.
|
||||
getByteCount: (query, param, defaultValue) ->
|
||||
spec = query[param]
|
||||
return defaultValue if undefined == spec
|
||||
parseByteCount spec
|
||||
|
||||
# Parse a cookie data string (usually document.cookie). The return type is an
|
||||
# object mapping cookies names to values. Returns null on error.
|
||||
|
@ -97,6 +104,24 @@ Params =
|
|||
return null
|
||||
{ host: host, port: port }
|
||||
|
||||
# Parse a count of bytes. A suffix of "k", "m", or "g" (or uppercase)
|
||||
# does what you would think. Returns null on error.
|
||||
parseByteCount: (spec) ->
|
||||
UNITS = {
|
||||
k: 1024, m: 1024 * 1024, g: 1024 * 1024 * 1024
|
||||
K: 1024, M: 1024 * 1024, G: 1024 * 1024 * 1024
|
||||
}
|
||||
matches = spec.match /^(\d+(?:\.\d*)?)(\w*)$/
|
||||
return null if null == matches
|
||||
count = Number matches[1]
|
||||
return null if isNaN count
|
||||
if '' == matches[2]
|
||||
units = 1
|
||||
else
|
||||
units = UNITS[matches[2]]
|
||||
return null if null == units
|
||||
count * Number(units)
|
||||
|
||||
safe_repr = (s) -> SAFE_LOGGING ? '[scrubbed]' : JSON.stringify(s)
|
||||
|
||||
# HEADLESS is true if we are running not in a browser with a DOM.
|
||||
|
@ -105,6 +130,10 @@ if window && window.location
|
|||
query = Query.parse(window.location.search.substr(1))
|
||||
DEBUG = Params.getBool(query, 'debug', false)
|
||||
HEADLESS = 'undefined' == typeof(document)
|
||||
|
||||
# Bytes per second. Set to undefined to disable limit.
|
||||
DEFAULT_RATE_LIMIT = DEFAULT_RATE_LIMIT || undefined
|
||||
MIN_RATE_LIMIT = 10 * 1024
|
||||
RATE_LIMIT_HISTORY = 5.0
|
||||
|
||||
DEFAULT_PORTS =
|
||||
|
@ -165,14 +194,14 @@ makeWebsocket = (addr) ->
|
|||
|
||||
class BucketRateLimit
|
||||
amount: 0.0
|
||||
last_update: new Date()
|
||||
lastUpdate: new Date()
|
||||
|
||||
constructor: (@capacity, @time) ->
|
||||
|
||||
age: ->
|
||||
now = new Date()
|
||||
delta = (now - @last_update) / 1000.0
|
||||
@last_update = now
|
||||
delta = (now - @lastUpdate) / 1000.0
|
||||
@lastUpdate = now
|
||||
@amount -= delta * @capacity / @time
|
||||
@amount = 0.0 if @amount < 0.0
|
||||
|
||||
|
@ -186,10 +215,18 @@ class BucketRateLimit
|
|||
age()
|
||||
(@amount - @capacity) / (@capacity / @time)
|
||||
|
||||
is_limited: ->
|
||||
isLimited: ->
|
||||
@age()
|
||||
@amount > @capacity
|
||||
|
||||
# A rate limiter that never limits.
|
||||
class DummyRateLimit
|
||||
constructor: (@capacity, @time) ->
|
||||
update: (n) -> true
|
||||
when: -> 0.0
|
||||
isLimited: -> false
|
||||
|
||||
|
||||
# TODO: Different ICE servers.
|
||||
config = {
|
||||
iceServers: [
|
||||
|
@ -223,7 +260,7 @@ class Snowflake
|
|||
proxyPairs: []
|
||||
proxyPair: null
|
||||
|
||||
rateLimit: null # TODO
|
||||
rateLimit: null
|
||||
badge: null
|
||||
$badge: null
|
||||
state: MODE.INIT
|
||||
|
@ -237,8 +274,15 @@ class Snowflake
|
|||
@badge = new Badge()
|
||||
@$badgem = @badge.elem
|
||||
@$badge.setAttribute('id', 'snowflake-badge') if (@$badge)
|
||||
rateLimitBytes = 0
|
||||
@rateLimit = new BucketRateLimit(rateLimitBytes * RATE_LIMIT_HISTORY, RATE_LIMIT_HISTORY);
|
||||
|
||||
rateLimitBytes = undefined
|
||||
if 'off' != query['ratelimit']
|
||||
rateLimitBytes = Params.getByteCount(query, 'ratelimit', DEFAULT_RATE_LIMIT)
|
||||
if undefined == rateLimitBytes
|
||||
@rateLimit = new DummyRateLimit()
|
||||
else
|
||||
@rateLimit = new BucketRateLimit(rateLimitBytes * RATE_LIMIT_HISTORY,
|
||||
RATE_LIMIT_HISTORY)
|
||||
|
||||
# TODO: User-supplied for now, but should fetch from facilitator later.
|
||||
setRelayAddr: (relayAddr) ->
|
||||
|
@ -438,21 +482,19 @@ class ProxyPair
|
|||
# WebRTC --> websocket
|
||||
if @relayIsReady() && @relay.bufferedAmount < @MAX_BUFFER && @c2rSchedule.length > 0
|
||||
chunk = @c2rSchedule.shift()
|
||||
# @rate_limit.update chunk.length
|
||||
@rateLimit.update chunk.length
|
||||
@relay.send chunk
|
||||
busy = true
|
||||
# websocket --> WebRTC
|
||||
if @webrtcIsReady() && @client.bufferedAmount < @MAX_BUFFER && @r2cSchedule.length > 0
|
||||
chunk = @r2cSchedule.shift()
|
||||
# this.rate_limit.update(chunk.length)
|
||||
@rateLimit.update chunk.length
|
||||
@client.send chunk
|
||||
busy = true
|
||||
checkChunks() while busy # && !@rate_limit.is_limited()
|
||||
checkChunks() while busy && !@rateLimit.isLimited()
|
||||
|
||||
# TODO: a more real rate limit
|
||||
if @r2cSchedule.length > 0 || @c2rSchedule.length > 0 || (@relayIsReady() && @relay.bufferedAmount > 0) || (@webrtcIsReady() && @client.bufferedAmount > 0)
|
||||
@flush_timeout_id = setTimeout @flush, 1000
|
||||
# @flush_timeout_id = setTimeout @flush, @rate_limit.when() * 1000
|
||||
@flush_timeout_id = setTimeout @flush, @rateLimit.when() * 1000
|
||||
|
||||
#
|
||||
## -- DOM & Input Functionality -- ##
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue