Skip to content
128 changes: 87 additions & 41 deletions xt_ratelimit.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,33 @@ struct ratelimit_net {
struct proc_dir_entry *ipt_ratelimit;
};

typedef union {
struct {
u32 tc;
u32 te;
} flds;
long long_v;
}
tce_t;

/* CAR accounting */
struct ratelimit_car {
unsigned long last; /* last refill (jiffies) */
u32 tc; /* committed token bucket counter */
u32 te; /* exceeded token bucket counter */
atomic64_t last; /* last refill (jiffies) */

/* committed token bucket counter */
/* exceeded token bucket counter */
atomic64_t tce;

u32 cbs; /* committed burst size (bytes) */
u32 ebs; /* extended burst size (bytes) */
u32 cir; /* committed information rate (bits/s) */
u32 cir; /* committed information rate (bits/s) / (HZ * 8) */
};

struct ratelimit_stat {
u64 green_bytes;
u64 red_bytes;
u32 green_pkt;
u32 red_pkt;
unsigned long first; /* first time seen */
atomic64_t green_bytes;
atomic64_t red_bytes;
atomic_t green_pkt;
atomic_t red_pkt;

#ifdef RATE_ESTIMATOR
#define RATEST_SECONDS 4 /* length of rate estimator time slot */
Expand Down Expand Up @@ -184,6 +194,8 @@ static int ratelimit_seq_ent_show(struct ratelimit_match *mt,
{
struct ratelimit_ent *ent = mt->ent;
int i;
unsigned long last;
tce_t tce;

/* to print entities only once, we print only entities
* where match is first element */
Expand All @@ -198,23 +210,28 @@ static int ratelimit_seq_ent_show(struct ratelimit_match *mt,
&ent->matches[i].addr);
}
seq_printf(s, " cir %u cbs %u ebs %u;",
ent->car.cir, ent->car.cbs, ent->car.ebs);
ent->car.cir * (HZ * BITS_PER_BYTE), ent->car.cbs, ent->car.ebs);

tce.long_v = atomic64_read(&ent->car.tce);
seq_printf(s, " tc %u te %u last", tce.flds.tc, tce.flds.te);

seq_printf(s, " tc %u te %u last", ent->car.tc, ent->car.te);
if (ent->car.last)
seq_printf(s, " %ld;", jiffies - ent->car.last);
last = atomic64_read(&ent->car.last);
if (last)
seq_printf(s, " %ld;", jiffies - last);
else
seq_printf(s, " never;");

seq_printf(s, " conf %u/%llu",
ent->stat.green_pkt, ent->stat.green_bytes);
(u32)atomic_read(&ent->stat.green_pkt),
(u64)atomic64_read(&ent->stat.green_bytes));

#ifdef RATE_ESTIMATOR
seq_printf(s, " %lu bps", calc_rate_est(&ent->stat));
#endif

seq_printf(s, ", rej %u/%llu",
ent->stat.red_pkt, ent->stat.red_bytes);
(u32)atomic_read(&ent->stat.red_pkt),
(u64)atomic64_read(&ent->stat.red_bytes));

seq_printf(s, "\n");

Expand Down Expand Up @@ -442,7 +459,7 @@ static int parse_rule(struct xt_ratelimit_htable *ht, char *str, size_t size)
val = simple_strtoul(v, NULL, 10);
switch (i) {
case 0:
ent->car.cir = val;
ent->car.cir = val / (HZ * BITS_PER_BYTE);
/* autoconfigure optimal parameters */
val = val / 8 + (val / 8 / 2);
/* FALLTHROUGH */
Expand Down Expand Up @@ -841,37 +858,66 @@ ratelimit_mt(const struct sk_buff *skb, struct xt_action_param *par)
if (ent) {
struct ratelimit_car *car = &ent->car;
const unsigned int len = skb->len; /* L3 */
const unsigned long delta_ms = (now - car->last) * (MSEC_PER_SEC / HZ);

spin_lock(&ent->lock_bh);
car->tc += len;
if (delta_ms) {
const u32 tok = delta_ms * (car->cir / (BITS_PER_BYTE * MSEC_PER_SEC));

car->tc -= min(tok, car->tc);
if (!ent->stat.first)
ent->stat.first = now;
car->last = now;
unsigned long time_passed, old, last;
u32 tc, te, tok;
tce_t tce, new_tce;
long old_tce;

/*
* calculate how much time has passed since the last update
* and then update last update time
*/
while (true) { /* cas loop */
last = atomic64_read(&car->last);
time_passed = now - last;
old = atomic64_cmpxchg(&car->last, last, now);
if (old == last) {
break; /* success */
}
}
if (car->tc > car->cbs) { /* extended burst */
car->te += car->tc - car->cbs;
if (car->te > car->ebs) {
car->te = 0;
match = true; /* match is drop */

/*
* process packet's bytes
*/
while (true) { /* cas loop */
tce.long_v = atomic64_read(&car->tce);
tc = tce.flds.tc;
te = tce.flds.te;
match = false;

tc += len;

if (time_passed) {
tok = time_passed * car->cir;
tc -= min(tok, tc);
}

if (tc > car->cbs) { /* extended burst */
te += tc - car->cbs;
if (te > car->ebs) {
te = 0;
tc -= len;
match = true; /* drop */
}
}

/* new tce */
new_tce.flds.tc = tc;
new_tce.flds.te = te;
old_tce = atomic64_cmpxchg(&car->tce, tce.long_v, new_tce.long_v);
if (old_tce == tce.long_v) {
break; /* success */
}
}
if (match) {
ent->stat.red_bytes += len;
ent->stat.red_pkt++;
car->tc -= len;

if (likely(match)) {
atomic64_add(len, &ent->stat.red_bytes);
atomic_inc(&ent->stat.red_pkt);
} else {
ent->stat.green_bytes += len;
ent->stat.green_pkt++;
#ifdef RATE_ESTIMATOR
rate_estimator(&ent->stat, now / RATEST_JIFFIES, len);
#endif
atomic64_add(len, &ent->stat.green_bytes);
atomic_inc(&ent->stat.green_pkt);
}
spin_unlock(&ent->lock_bh);
} else {
if (ht->other == OT_MATCH)
match = true; /* match is drop */
Expand Down