summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiuseppe Bilotta <giuseppe.bilotta@gmail.com>2006-10-27 14:18:23 +0000
committerGiuseppe Bilotta <giuseppe.bilotta@gmail.com>2006-10-27 14:18:23 +0000
commitd9f1c04e912d44c218a90b9b0debcb0a11b87631 (patch)
tree0f15c71db94fc2f2e135d1948a90da5c2c9882b5
parentd51861faa9607718870cea1acbbc0b2b2c2c7364 (diff)
Penalty-based flood protection
-rw-r--r--ChangeLog6
-rw-r--r--lib/rbot/ircbot.rb10
-rw-r--r--lib/rbot/ircsocket.rb74
-rw-r--r--lib/rbot/rfc2812.rb64
4 files changed, 79 insertions, 75 deletions
diff --git a/ChangeLog b/ChangeLog
index 203bd3c7..fae97f54 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2006-10-27 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
+
+ * Flood protection: first attempt at penalty-based flood protection.
+ This should make rbot much less prone to Excess Floods *and* still
+ serve normally without excessive delays.
+
2006-10-25 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
* HttpUtil: Strings returned by get_cached now have a cached? method
diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb
index 8249895b..4bf3e8d3 100644
--- a/lib/rbot/ircbot.rb
+++ b/lib/rbot/ircbot.rb
@@ -173,10 +173,6 @@ class IrcBot
:default => 4, :validate => Proc.new{|v| v >= 0},
:desc => "(flood prevention) max lines to burst to the server before throttling. Most ircd's allow bursts of up 5 lines",
:on_change => Proc.new {|bot, v| bot.socket.sendq_burst = v })
- BotConfig.register BotConfigStringValue.new('server.byterate',
- :default => "400/2", :validate => Proc.new{|v| v.match(/\d+\/\d/)},
- :desc => "(flood prevention) max bytes/seconds rate to send the server. Most ircd's have limits of 512 bytes/2 seconds",
- :on_change => Proc.new {|bot, v| bot.socket.byterate = v })
BotConfig.register BotConfigIntegerValue.new('server.ping_timeout',
:default => 30, :validate => Proc.new{|v| v >= 0},
:on_change => Proc.new {|bot, v| bot.start_server_pings},
@@ -654,12 +650,10 @@ class IrcBot
# relevant say() or notice() methods. This one should be used for IRCd
# extensions you want to use in modules.
def sendmsg(type, where, message, chan=nil, ring=0)
- # limit it according to the byterate, splitting the message
- # taking into consideration the actual message length
- # and all the extra stuff
+ # Split the message so that each line sent is not longher than 510 bytes
# TODO allow something to do for commands that produce too many messages
# TODO example: math 10**10000
- left = @socket.bytes_per - type.length - where.to_s.length - 4
+ left = 510 - type.length - where.to_s.length - 3
begin
if(left >= message.length)
sendq "#{type} #{where} :#{message}", chan, ring
diff --git a/lib/rbot/ircsocket.rb b/lib/rbot/ircsocket.rb
index ccc751f7..408287a8 100644
--- a/lib/rbot/ircsocket.rb
+++ b/lib/rbot/ircsocket.rb
@@ -168,6 +168,9 @@ module Irc
# wrapped TCPSocket for communication with the server.
# emulates a subset of TCPSocket functionality
class IrcSocket
+
+ MAX_IRC_SEND_PENALTY = 10
+
# total number of lines sent to the irc server
attr_reader :lines_sent
@@ -183,10 +186,6 @@ module Irc
# accumulator for the throttle
attr_reader :throttle_bytes
- # byterate components
- attr_reader :bytes_per
- attr_reader :seconds_per
-
# delay between lines sent
attr_reader :sendq_delay
@@ -197,7 +196,7 @@ module Irc
# port:: IRCd port
# host:: optional local host to bind to (ruby 1.7+ required)
# create a new IrcSocket
- def initialize(server, port, host, sendq_delay=2, sendq_burst=4, brt="400/2")
+ def initialize(server, port, host, sendq_delay=2, sendq_burst=4)
@timer = Timer::Timer.new
@timer.add(0.2) do
spool
@@ -222,23 +221,6 @@ module Irc
else
@sendq_burst = 4
end
- @bytes_per = 400
- @seconds_per = 2
- @throttle_bytes = 0
- @hit_limit = 0 # how many times did we reach the limit?
- setbyterate(brt)
- end
-
- def setbyterate(brt)
- if brt.match(/(\d+)\/(\d)/)
- @bytes_per = $1.to_i
- @seconds_per = $2.to_i
- debug "Byterate now #{byterate}"
- return true
- else
- debug "Couldn't set byterate #{brt}"
- return false
- end
end
def connected?
@@ -288,38 +270,6 @@ module Irc
end
end
- def byterate
- return "#{@bytes_per}/#{@seconds_per} (limit hit #{@hit_limit} times)"
- end
-
- def byterate=(newrate)
- @qmutex.synchronize do
- setbyterate(newrate)
- end
- end
-
- def run_throttle(more=0)
- now = Time.new
- # Each time we reach the limit, we reduce the bitrate. We reset the bitrate only if the throttle
- # manages to reset twice. This way we have better flood control, although the really perfect way
- # would be to calculate our penalty the way it's done serverside.
- if @throttle_bytes > 0
- if @throttle_bytes >= @bytes_per
- @hit_limit += 1
- @hit_limit = 3 if @hit_limit > 3
- end
- delta = ((now - @last_throttle)*(0.5**@hit_limit.ceil)*@bytes_per/@seconds_per).floor
- if delta > 0
- @throttle_bytes -= delta
- @throttle_bytes = 0 if @throttle_bytes < 0
- @last_throttle = now
- end
- else
- @hit_limit -= 0.5 if @hit_limit > 0
- end
- @throttle_bytes += more
- end
-
# used to send lines to the remote IRCd by skipping the queue
# message: IRC message to send
# it should only be used for stuff that *must not* be queued,
@@ -385,19 +335,10 @@ module Irc
return
end
debug "can send #{@sendq_burst - @burst} lines, there are #{@sendq.length} to send"
- (@sendq_burst - @burst).times do
- break if @sendq.empty?
+ while !@sendq.empty? and @burst < @sendq_burst and now > @last_send - MAX_IRC_SEND_PENALTY
mess = @sendq.next
- if @throttle_bytes == 0 or mess.length+@throttle_bytes < @bytes_per
- debug "flood protection: sending message of length #{mess.length}"
- debug "(byterate: #{byterate}, throttle bytes: #{@throttle_bytes})"
- puts_critical(@sendq.shift)
- else
- debug "flood protection: throttling message of length #{mess.length}"
- debug "(byterate: #{byterate}, throttle bytes: #{@throttle_bytes})"
- run_throttle
- break
- end
+ puts_critical(@sendq.shift)
+ @last_send += mess.irc_send_penalty
end
if @sendq.empty?
@timer.stop
@@ -452,7 +393,6 @@ module Irc
@last_send = Time.new
@lines_sent += 1
@burst += 1
- run_throttle(message.length + 1)
end
rescue => e
error "SEND failed: #{e.inspect}"
diff --git a/lib/rbot/rfc2812.rb b/lib/rbot/rfc2812.rb
index 26549539..9fa81cb5 100644
--- a/lib/rbot/rfc2812.rb
+++ b/lib/rbot/rfc2812.rb
@@ -1,3 +1,67 @@
+class ::String
+ # Calculate the penalty which will be assigned to this message
+ # by the IRCd
+ def irc_send_penalty
+ # According to eggrdop, the initial penalty is
+ penalty = 1 + self.length/100
+ # on everything but UnderNET where it's
+ # penalty = 2 + self.length/120
+
+ cmd, pars = self.split($;,2)
+ debug "cmd: #{cmd}, pars: #{pars.inspect}"
+ case cmd.to_sym
+ when :KICK
+ chan, nick, msg = pars.split
+ chan = chan.split(',')
+ nick = nick.split(',')
+ penalty += nick.length
+ penalty *= chan.length
+ when :MODE
+ chan, modes, argument = pars.split
+ extra = 0
+ if modes
+ extra = 1
+ if argument
+ extra += modes.split(/\+|-/).length
+ else
+ extra += 3 * modes.split(/\+|-/).length
+ end
+ end
+ if argument
+ extra += 2 * argument.split.length
+ end
+ penalty += extra * chan.split.length
+ when :TOPIC
+ penalty += 1
+ penalty += 2 unless pars.split.length < 2
+ when :PRIVMSG, :NOTICE
+ dests = pars.split($;,2).first
+ penalty += dests.split(',').length
+ when :WHO
+ # I'm too lazy to implement this one correctly
+ penalty += 5
+ when :AWAY, :JOIN, :VERSION, :TIME, :TRACE, :WHOIS, :DNS
+ penalty += 2
+ when :INVITE, :NICK
+ penalty += 3
+ when :ISON
+ penalty += 1
+ else # Unknown messages
+ penalty += 1
+ end
+ if penalty > 99
+ debug "Wow, more than 99 secs of penalty!"
+ penalty = 99
+ end
+ if penalty < 2
+ debug "Wow, less than 2 secs of penalty!"
+ penalty = 2
+ end
+ debug "penalty: #{penalty}"
+ return penalty
+ end
+end
+
module Irc
# RFC 2812 Internet Relay Chat: Client Protocol
#