iptables limit 模組限速不準確原因分析
iptables -I test -m limit --limit xxx/yyy
iptables使用者空間:libxt_limit.c
int parse_rate(const char *rate, uint32_t *val)
該函式中:*val = XT_LIMIT_SCALE * mult/ r;XT_LIMIT_SCALE:10000(時間精度)
mult:yyy是s則mult:1,yyy是m則mult:60,yyy是h則mult是:60x60,yyy是d則mult是:24x60x60
r為xxx所以上式中的*val為傳輸一個包需要的時間。iptables顯示的數字實際應該是:10000/(*val),即表示每秒允許的資料包數量。
*val=10000*1/2600=3
10000/3=3333
以iptables -I test -m limit --limit 1400/s為例:
*val=10000*1/1400=7
10000/7=1428
iptables核心空間:xt_limit.c
staticstructxt_matchlimit_mt_reg__read_mostly={ .name="limit", .revision=0, .family=NFPROTO_UNSPEC, .match=limit_mt, .checkentry=limit_mt_check, . .matchsize=sizeof(structxt_rateinfo), #ifdef CONFIG_COMPAT .compatsize=sizeof(structcompat_xt_rateinfo), .compat_from_user=limit_mt_compat_from_user, .compat_to_user=limit_mt_compat_to_user, #endif .me=THIS_MODULE, }; staticint__initlimit_mt_init(void) { returnxt_register_match } staticvoid__exitlimit_mt_exit(void) { xt_unregister_match(&limit_mt_reg); } module_init(limit_mt_init); module_exit(limit_mt_exit); |
Limt模組核心實現採用令牌桶演算法。我們需要關注的是limit_mt和limit_mt_check這兩個函式。
Limit_mt_check檢查使用者空間傳入的引數是否合法,並初始化令牌桶的最大值和初值,以及一個數據包需要消耗的令牌數量。
/* Precision saver. */ staticu_int32_t user2credits(u_int32_tuser) { /* If multiplying would overflow... */ if(user>0xFFFFFFFF/(HZ*CREDITS_PER_JIFFY)) /* Divide first. */ return(user/XT_LIMIT_SCALE)*HZ*CREDITS_PER_JIFFY; return(user*HZ*CREDITS_PER_JIFFY)/XT_LIMIT_SCALE; } staticintlimit_mt_check(conststructxt_mtchk_param*par) { structxt_rateinfo*r=par->matchinfo; structxt_limit_priv*priv; /* Check for overflow. */ if(r->burst==0 ||user2credits(r->avg*r->burst)<user2credits(r->avg)){ pr_info("Overflow, try lower: %u/%u\n", r->avg,r->burst); return-ERANGE; } priv=kmalloc(sizeof(*priv),GFP_KERNEL); if(priv==NULL) return-ENOMEM; /* For SMP, we only want to use one set of state. */ r->master=priv; if(r->cost==0){ /* User avg in seconds * XT_LIMIT_SCALE: convert to jiffies * 128. */ priv->prev=jiffies; priv->credit=user2credits(r->avg*r->burst);/* Credits full. */ r->credit_cap=user2credits(r->avg*r->burst);/* Credits full. */ r->cost=user2credits(r->avg); } return0; } user2credits()函式計算一個數據包對應的令牌數量。 User2creadits(r->avg),引數r-avg為使用者空間計算的一個包的平均時間。 所以 user2creadits(r->avg)一個數據包對應的令牌數量。 user2creadtis(r->avg*r->burst)引數r->burst為突發資料包個數。r->avg*r->burst為r->burst個數據包的平均時間,user2creadtis(r->avg*r->burst)既是r->burst個數據包對應的令牌數量。 r->credit_cap是令牌桶的最大值。r->cost是一個數據包需要消耗的令牌數量。 priv->prev記錄上一個資料包到來時的時間,使用jiffies表示。 Priv->credit為令牌數量的實時值。 初始令牌數量的實時值=最大值。 Limt_mt函式通過令牌桶演算法監測當前的資料流是否滿足限制的要求,滿足則返回true,否則返回false。 |
staticbool
limit_mt(conststructsk_buff*skb,structxt_action_param*par)
{
conststructxt_rateinfo*r=par->matchinfo;
structxt_limit_priv*priv=r->master;
unsignedlongnow=jiffies;
spin_lock_bh(&limit_lock);
priv->credit+=(now-xchg(&priv->prev,now))*CREDITS_PER_JIFFY;
if(priv->credit>r->credit_cap)
priv->credit=r->credit_cap;
if(priv->credit>=r->cost){
/* We're not limited. */
priv->credit-=r->cost;
spin_unlock_bh(&limit_lock);
returntrue;
}
spin_unlock_bh(&limit_lock);
returnfalse;
}
priv->credit += (now -xchg(&priv->prev, now)) *CREDITS_PER_JIFFY ;
計算從上一個資料包到當前資料包這段時間內產生的令牌數量並加到priv->credit上。CREDITS_PER_JIFFY為每個jiffy產生的令牌數量。
if(priv->credit > r->credit_cap)
priv->credit= r->credit_cap;
當前令牌數量大於最大令牌數量時,更新當前令牌數量為最大令牌數。
if(priv->credit >= r->cost) {
/*We're not limited. */
priv->credit-= r->cost;
spin_unlock_bh(&limit_lock);
returntrue;
}
If判斷當前有令牌可用的情況下,從噹噹令牌數量減去當前資料包消耗的令牌數量。
CREDITS_PER_JIFFY如下:
HZ:100 為什麼是100
MAX_CPJ:497 不懂代表什麼
CREDITS_PER_JIFFY:256
#define MAX_CPJ(0xFFFFFFFF / (HZ*60*60*24))
/* Repeatedshift and or gives us all 1s, final shift and add 1 gives
* us the power of 2 below the theoretical max,so GCC simply does a
* shift. */
#define_POW2_BELOW2(x) ((x)|((x)>>1))
#define_POW2_BELOW4(x) (_POW2_BELOW2(x)|_POW2_BELOW2((x)>>2))
#define_POW2_BELOW8(x) (_POW2_BELOW4(x)|_POW2_BELOW4((x)>>4))
#define_POW2_BELOW16(x) (_POW2_BELOW8(x)|_POW2_BELOW8((x)>>8))
#define_POW2_BELOW32(x) (_POW2_BELOW16(x)|_POW2_BELOW16((x)>>16))
#definePOW2_BELOW32(x) ((_POW2_BELOW32(x)>>1) + 1)
#defineCREDITS_PER_JIFFY POW2_BELOW32(MAX_CPJ)
綜上:
核心部分的演算法是根據使用者空間傳遞的每個包需要的時間,以及峰值包數量,生成令牌桶演算法的最大令牌數量和每個包需要消耗的令牌數量。
然後每當一個數據包到來就去更新當前令牌數量,然後檢測令牌是否夠用,如果當前令牌夠用,就從當前令牌數中減去此刻這個資料包消耗的令牌數,並返回true,如果當前沒有令牌可用即返回false。
整個過程是:使用者輸入資料->每個包平均時間->每個包消耗的令牌數以及令牌桶令牌總數量
使用者空間*val= XT_LIMIT_SCALE * mult / r計算每個資料包消耗的時間這一步採用“/”運算,那麼這裡不可避免的產生誤差。
所以該演算法的實現過程本身存在的誤差,導致使用者空間輸入的資料並不是真正生效的資料。