diff options
Diffstat (limited to 'net/netfilter/xt_hashlimit.c')
| -rw-r--r-- | net/netfilter/xt_hashlimit.c | 132 | 
1 files changed, 106 insertions, 26 deletions
diff --git a/net/netfilter/xt_hashlimit.c b/net/netfilter/xt_hashlimit.c index d95f9c963cd..26a668a84aa 100644 --- a/net/netfilter/xt_hashlimit.c +++ b/net/netfilter/xt_hashlimit.c @@ -171,8 +171,7 @@ dsthash_alloc_init(struct xt_hashlimit_htable *ht,  	if (ht->cfg.max && ht->count >= ht->cfg.max) {  		/* FIXME: do something. question is what.. */ -		if (net_ratelimit()) -			pr_err("max count of %u reached\n", ht->cfg.max); +		net_err_ratelimited("max count of %u reached\n", ht->cfg.max);  		ent = NULL;  	} else  		ent = kmem_cache_alloc(hashlimit_cachep, GFP_ATOMIC); @@ -388,9 +387,20 @@ static void htable_put(struct xt_hashlimit_htable *hinfo)  #define CREDITS_PER_JIFFY POW2_BELOW32(MAX_CPJ) +/* in byte mode, the lowest possible rate is one packet/second. + * credit_cap is used as a counter that tells us how many times we can + * refill the "credits available" counter when it becomes empty. + */ +#define MAX_CPJ_BYTES (0xFFFFFFFF / HZ) +#define CREDITS_PER_JIFFY_BYTES POW2_BELOW32(MAX_CPJ_BYTES) + +static u32 xt_hashlimit_len_to_chunks(u32 len) +{ +	return (len >> XT_HASHLIMIT_BYTE_SHIFT) + 1; +} +  /* Precision saver. */ -static inline u_int32_t -user2credits(u_int32_t user) +static u32 user2credits(u32 user)  {  	/* If multiplying would overflow... */  	if (user > 0xFFFFFFFF / (HZ*CREDITS_PER_JIFFY)) @@ -400,12 +410,53 @@ user2credits(u_int32_t user)  	return (user * HZ * CREDITS_PER_JIFFY) / XT_HASHLIMIT_SCALE;  } -static inline void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now) +static u32 user2credits_byte(u32 user)  { -	dh->rateinfo.credit += (now - dh->rateinfo.prev) * CREDITS_PER_JIFFY; -	if (dh->rateinfo.credit > dh->rateinfo.credit_cap) -		dh->rateinfo.credit = dh->rateinfo.credit_cap; +	u64 us = user; +	us *= HZ * CREDITS_PER_JIFFY_BYTES; +	return (u32) (us >> 32); +} + +static void rateinfo_recalc(struct dsthash_ent *dh, unsigned long now, u32 mode) +{ +	unsigned long delta = now - dh->rateinfo.prev; +	u32 cap; + +	if (delta == 0) +		return; +  	dh->rateinfo.prev = now; + +	if (mode & XT_HASHLIMIT_BYTES) { +		u32 tmp = dh->rateinfo.credit; +		dh->rateinfo.credit += CREDITS_PER_JIFFY_BYTES * delta; +		cap = CREDITS_PER_JIFFY_BYTES * HZ; +		if (tmp >= dh->rateinfo.credit) {/* overflow */ +			dh->rateinfo.credit = cap; +			return; +		} +	} else { +		dh->rateinfo.credit += delta * CREDITS_PER_JIFFY; +		cap = dh->rateinfo.credit_cap; +	} +	if (dh->rateinfo.credit > cap) +		dh->rateinfo.credit = cap; +} + +static void rateinfo_init(struct dsthash_ent *dh, +			  struct xt_hashlimit_htable *hinfo) +{ +	dh->rateinfo.prev = jiffies; +	if (hinfo->cfg.mode & XT_HASHLIMIT_BYTES) { +		dh->rateinfo.credit = CREDITS_PER_JIFFY_BYTES * HZ; +		dh->rateinfo.cost = user2credits_byte(hinfo->cfg.avg); +		dh->rateinfo.credit_cap = hinfo->cfg.burst; +	} else { +		dh->rateinfo.credit = user2credits(hinfo->cfg.avg * +						   hinfo->cfg.burst); +		dh->rateinfo.cost = user2credits(hinfo->cfg.avg); +		dh->rateinfo.credit_cap = dh->rateinfo.credit; +	}  }  static inline __be32 maskl(__be32 a, unsigned int l) @@ -511,6 +562,21 @@ hashlimit_init_dst(const struct xt_hashlimit_htable *hinfo,  	return 0;  } +static u32 hashlimit_byte_cost(unsigned int len, struct dsthash_ent *dh) +{ +	u64 tmp = xt_hashlimit_len_to_chunks(len); +	tmp = tmp * dh->rateinfo.cost; + +	if (unlikely(tmp > CREDITS_PER_JIFFY_BYTES * HZ)) +		tmp = CREDITS_PER_JIFFY_BYTES * HZ; + +	if (dh->rateinfo.credit < tmp && dh->rateinfo.credit_cap) { +		dh->rateinfo.credit_cap--; +		dh->rateinfo.credit = CREDITS_PER_JIFFY_BYTES * HZ; +	} +	return (u32) tmp; +} +  static bool  hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)  { @@ -519,6 +585,7 @@ hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)  	unsigned long now = jiffies;  	struct dsthash_ent *dh;  	struct dsthash_dst dst; +	u32 cost;  	if (hashlimit_init_dst(hinfo, &dst, skb, par->thoff) < 0)  		goto hotdrop; @@ -532,21 +599,21 @@ hashlimit_mt(const struct sk_buff *skb, struct xt_action_param *par)  			goto hotdrop;  		}  		dh->expires = jiffies + msecs_to_jiffies(hinfo->cfg.expire); -		dh->rateinfo.prev = jiffies; -		dh->rateinfo.credit = user2credits(hinfo->cfg.avg * -		                      hinfo->cfg.burst); -		dh->rateinfo.credit_cap = user2credits(hinfo->cfg.avg * -		                          hinfo->cfg.burst); -		dh->rateinfo.cost = user2credits(hinfo->cfg.avg); +		rateinfo_init(dh, hinfo);  	} else {  		/* update expiration timeout */  		dh->expires = now + msecs_to_jiffies(hinfo->cfg.expire); -		rateinfo_recalc(dh, now); +		rateinfo_recalc(dh, now, hinfo->cfg.mode);  	} -	if (dh->rateinfo.credit >= dh->rateinfo.cost) { +	if (info->cfg.mode & XT_HASHLIMIT_BYTES) +		cost = hashlimit_byte_cost(skb->len, dh); +	else +		cost = dh->rateinfo.cost; + +	if (dh->rateinfo.credit >= cost) {  		/* below the limit */ -		dh->rateinfo.credit -= dh->rateinfo.cost; +		dh->rateinfo.credit -= cost;  		spin_unlock(&dh->lock);  		rcu_read_unlock_bh();  		return !(info->cfg.mode & XT_HASHLIMIT_INVERT); @@ -568,14 +635,6 @@ static int hashlimit_mt_check(const struct xt_mtchk_param *par)  	struct xt_hashlimit_mtinfo1 *info = par->matchinfo;  	int ret; -	/* Check for overflow. */ -	if (info->cfg.burst == 0 || -	    user2credits(info->cfg.avg * info->cfg.burst) < -	    user2credits(info->cfg.avg)) { -		pr_info("overflow, try lower: %u/%u\n", -			info->cfg.avg, info->cfg.burst); -		return -ERANGE; -	}  	if (info->cfg.gc_interval == 0 || info->cfg.expire == 0)  		return -EINVAL;  	if (info->name[sizeof(info->name)-1] != '\0') @@ -588,6 +647,26 @@ static int hashlimit_mt_check(const struct xt_mtchk_param *par)  			return -EINVAL;  	} +	if (info->cfg.mode & ~XT_HASHLIMIT_ALL) { +		pr_info("Unknown mode mask %X, kernel too old?\n", +						info->cfg.mode); +		return -EINVAL; +	} + +	/* Check for overflow. */ +	if (info->cfg.mode & XT_HASHLIMIT_BYTES) { +		if (user2credits_byte(info->cfg.avg) == 0) { +			pr_info("overflow, rate too high: %u\n", info->cfg.avg); +			return -EINVAL; +		} +	} else if (info->cfg.burst == 0 || +		    user2credits(info->cfg.avg * info->cfg.burst) < +		    user2credits(info->cfg.avg)) { +			pr_info("overflow, try lower: %u/%u\n", +				info->cfg.avg, info->cfg.burst); +			return -ERANGE; +	} +  	mutex_lock(&hashlimit_mutex);  	info->hinfo = htable_find_get(net, info->name, par->family);  	if (info->hinfo == NULL) { @@ -680,10 +759,11 @@ static int dl_seq_real_show(struct dsthash_ent *ent, u_int8_t family,  				   struct seq_file *s)  {  	int res; +	const struct xt_hashlimit_htable *ht = s->private;  	spin_lock(&ent->lock);  	/* recalculate to show accurate numbers */ -	rateinfo_recalc(ent, jiffies); +	rateinfo_recalc(ent, jiffies, ht->cfg.mode);  	switch (family) {  	case NFPROTO_IPV4:  | 
