103

I do not wish to limit the rate of a specific service. My goals is to limit rate based solely on the incoming IP address. For example using a pseudo-rule:

john.domain.local (192.168.1.100) can only download from our httpd/ftp servers at "10KB/s" (instead of 1MB/s)

How could I rate limit using IPTables based on incoming IP addresses?

James
  • 1,117
  • 3
  • 9
  • 16

2 Answers2

168

IPTables isn't made for this kind of work, where lots and lots of packets need to be analyzed to make these decisions. IPTables is partly the answer though!

The real answer to this is the awesome and underused traffic control facilities in Linux. Note that mucking around with this without knowing what is going on may lead to you losing network connectivity to the machine! You have been warned!

Assuming eth0 is the outgoing device you will need to create a class-based traffic control queue which will by default output most traffic through the 'fast' queue and put a specific list of people into the 'slow' queue.

The beauty of this is you can create a situation whereby you allow lots of outbound traffic for the slow user unless an overriding class wants the bandwidth, but this example does not do this (will always provide 10kbps to the slow users). The queuing system will look something like this:

                         Inbound traffic
                              +
                              |
                              |
                              v
                     +------------------+
                     |   Class 1:1      |
                     |------------------|
                     |  Root (all flows)|
                     |       100mbit    |
                     +-----+-----+------+
                           |     |
                           |     |
                           |     |
                           |     |
                           |     |
          +----------+     |     |     +----------+
          |    1:11  +-----+     +-----+    1:12  |
          |----------|                 |----------|
          | Default  |                 | Slow     |
          |100mb-80kb|                 |   80kb   |
          +----------+                 +----------+

To do this, first you'll need to setup the queuing discipline in the kernel. The following will do this for you.. you must run this as one whole script

#!/bin/bash
tc qdisc add dev eth0 parent root handle 1: hfsc default 11
tc class add dev eth0 parent 1: classid 1:1 hfsc sc rate 100mbit ul rate 100mbit
tc class add dev eth0 parent 1:1 classid 1:11 hfsc sc rate 99920kbit ul rate 100000kbit
tc class add dev eth0 parent 1:1 classid 1:12 hfsc sc rate 80kbit ul rate 80kbit

tc qdisc add dev eth0 parent 1:11 handle 11:1 pfifo
tc qdisc add dev eth0 parent 1:12 handle 12:1 pfifo

The "default 11" is important as it tells the kernel what to do with traffic not classified.

Once this is done, you can then setup an iptables rule to classify packets that match a certain criteria. If you plan on putting lots and lots of people into this slow rule an ipset rule is more appropriate (which should be available on rhel6 I believe).

So, create an ipset database to do the matching against...

ipset create slowips hash:ip,port

Then create the iptables rule to do the match..

iptables -t mangle -I OUTPUT -m set --match-set slowips dst,src -j CLASSIFY --set-class 1:12

This instructs the kernel that if you match the destination IP with the source port from the set, classify it into the slow queue you setup with traffic control.

Now, finally whenever you want to slow an IP down you can use the ipset command to add the ip to the set such as this:

ipset add slowips 192.168.1.1,80
ipset add slowips 192.168.1.1,21
...

You can test it works using the command "tc -s class show dev eth0" and you will see stats in there indicating packets being redirected to the slow queue.

Note the only real downside to this is making it survive reboots. I don't think there are any init scripts available to create the ipsets from dumps on reboot (and they also must be created before iptables rules) and I'm certain there's no init scripts to resetup traffic control rules on reboot. If your not bothered, you can just recreate the whole thing from invoking a script in rc.local.

Glorfindel
  • 1,213
  • 3
  • 15
  • 22
Matthew Ife
  • 22,927
  • 2
  • 54
  • 71
  • 3
    Well, I can't thank you enough. This is very descriptive and very informative. I later realized that knowledge of TC would be required and I have since started looking into this. Thanks Again! – James Apr 28 '12 at 23:41
  • Oh and as for losing connection. I'm making sure I have the configuration down before moving from my VPS to the host machine. Also, I have VPN access to the private network on ETH0. I'll only be working on ETH1 so in theory I wont have the problem. But warning heard! – James Apr 28 '12 at 23:43
  • 2
    I cant tell you how many times I have read similar tutorials , this is the first one that made sense – RC1140 May 05 '12 at 20:16
  • 5
    As a sidenote, its typically more appropriate to do resource limitation like this in control groups (again, also possible and also underused and awesome) as you can define per-application limits on cpu, memory, IO and network in a centralized 'policy store'. But have yet to see such a question being posited to offer an answer. – Matthew Ife May 05 '12 at 20:44
  • 2
    If you don't like the `tc` syntax you could give [tcng](http://tcng.sourceforge.net/) a try which adds a bit more user friendly language that generates `tc` commands. I have used to like this in shell scripts: `echo '... multi line tcng configuration ...' | tcng | sh`. – Mattias Wadman May 06 '12 at 15:08
  • @MIfe http://meta.stackexchange.com/a/2729/148353 – MattJ May 06 '12 at 15:21
  • What's the relationship between tc and nftables? – CMCDragonkai Aug 03 '15 at 08:08
  • Last time I checked, theres no CLASSIFY extension in nftables but you can mark packets and use `tc filter` to classify the traffic based off of the firewall mark. – Matthew Ife Aug 03 '15 at 08:11
  • What do the last 2 lines (the ones with pfifo) do and why are they necessary? – leopoodle Oct 28 '18 at 05:09
5

It's as simple as taking a rate limiting rule and adding the -s switch. The -s switch matches incoming IPs. For example iptables -A INPUT -s 1.1.1.1 and then finishing up with your preferred method of rate limiting for that rule.

Wesley
  • 32,320
  • 9
  • 80
  • 116
  • Thank you for your prompt answer. Unfortunately my main problem is the second half it. I've looked into --limit and I haven't seen anything that allows me to limit based on KB/s - any direction you can point me? – James Apr 28 '12 at 21:28
  • 1
    @James I would have gotten back to you but I had to step out to a friend's house. Just got back and I see that MIfe has done quite a job. =) – Wesley Apr 29 '12 at 03:30