1 <?php
2 /**
3 * @file IPHitsLimit.php
4 * @version 1.0
5 * @author guoxiaosong <>
6 * @date 2012-11-14
7 * @brief Limit ip frequency.
8 */
9 class IPHitsLimit
10 {
11 const INDEX_DAY = 0;
12 const INDEX_HOUR = 1;
13 const INDEX_MINUTE = 2;
14 const INDEX_SECOND = 3;
15 // MemCahe link
16 private $_memCache;
17 // 1 day
18 private $_expiredTime = 86400;
19 private $_ipWhiteLists = array();
20 private $_ipBlackLists = array();
21
22 /**
23 * Constructor.
24 *
25 * @param boolean $init Initialize the shm or not.
26 */
27 function __construct()
28 {
29 $this->_memCache = new MemCache;
30 $this->_memCache->pconnect(KOPU_MEMCACHED_HOST, KOPU_MEMCACHED_PORT);
31
32 $this->init();
33 }
34
35 /**
36 * Initialize the shm.
37 *
38 */
39 function init()
40 {
41 $this->_expiredTime = KOPU_IPHITS_EXPIRED_TIME;
42 $this->_ipWhiteLists = Yii::app()->params->ipwhitelists;
43 $this->_ipBlackLists = Yii::app()->params->ipblacklists;
44 }
45
46 /**
47 * Check whether to limit $ip.
48 *
49 * @param string $ip The ip to check.
50 * @return int 0: no limit, 1: limit it.
51 */
52
53 public function check($ip)
54 {
55 $return = $this->_check($ip);
56 if ($return == 1) {
57 Yii::log($ip, CLogger::LEVEL_INFO, 'IPHitsLimit');
58 }
59 return $return;
60 }
61
62 private function _check($ip)
63 {
64 if (!($key = $this->_checkIP($ip)) || $this->_checkIPBlackList($ip)) {
65 return 1;
66 }
67
68 if ($this->_checkIPWhiteList($ip)) {
69 return 0;
70 }
71
72 $info = $this->_memCache->get($key);
73 $time = time();
74 if (empty($info)) {// new record
75 $info = array(
76 'time' => $time,
77 'count' => array(1, 1, 1, 1),
78 'ip' => $ip,
79 );
80 $this->_memCache->set($key, $info, MEMCACHE_COMPRESSED, $this->_expiredTime);
81 return 0;
82 } else {
83 $countTimeMap = $this->countTimeMap();
84 $info['count'] = $this->_addCount($info['count']);
85 if ($this->_campareTime($time, $info['time'], 'day')) {
86 // IP hits up to day limits.
87 if ($info['count'][self::INDEX_DAY] > $countTimeMap[self::INDEX_DAY]) {
88 $info['time'] = $time;
89 $this->_memCache->set($key, $info);
90 return 1;
91 }
92
93 if ($this->_campareTime($time, $info['time'], 'hour')) {
94 // Ip hits up to hour limits.
95 if ($info['count'][self::INDEX_HOUR] > $countTimeMap[self::INDEX_HOUR]) {
96 $info['time'] = $time;
97 $this->_memCache->set($key, $info);
98 return 1;
99 }
100
101 if ($this->_campareTime($time, $info['time'], 'minute')) {
102 // Ip hits up to minute limits
103 if ($info['count'][self::INDEX_MINUTE] > $countTimeMap[self::INDEX_MINUTE]) {
104 $info['time'] = $time;
105 $this->_memCache->set($key, $info);
106 return 1;
107 }
108
109 if ($this->_campareTime($time, $info['time'], 'second')) {
110 //Ip hits up to second limits.
111 if ($info['count'][self::INDEX_SECOND] > $countTimeMap[self::INDEX_SECOND]) {
112 $info['time'] = $time;
113 $this->_memCache->set($key, $info);
114 return 1;
115 }
116 } else {
117 $info['count'][self::INDEX_SECOND] = 1;
118 }
119 } else {
120 $info['count'][self::INDEX_MINUTE] = 1;
121 $info['count'][self::INDEX_SECOND] = 1;
122 }
123 } else {
124 // new hour need to reset day,minute,second count.
125 $info['count'][self::INDEX_HOUR] = 1;
126 $info['count'][self::INDEX_MINUTE] = 1;
127 $info['count'][self::INDEX_SECOND] = 1;
128 }
129 } else {
130 // new day need to reset all count.
131 $info['count'] = array(1, 1, 1, 1);
132 }
133 $info['time'] = $time;
134 $this->_memCache->set($key, $info);
135 return 0;
136 }
137
138 }
139
140 /**
141 * @return array
142 * key(seconds)
143 * value(limits)
144 */
145 function countTimeMap()
146 {
147 return array(
148 self::INDEX_DAY => KOPU_IPHITS_PER_DAY,
149 self::INDEX_HOUR => KOPU_IPHITS_PER_HOUR,
150 self::INDEX_MINUTE => KOPU_IPHITS_PER_MINUTE,
151 self::INDEX_SECOND => KOPU_IPHITS_PER_SECOND
152 );
153 }
154
155 /**
156 * Print ip info in shm.
157 *
158 * @param string $ip The ip.
159 */
160 function dump($ip)
161 {
162 $key = ip2long($ip);
163 if ($key == -1 || $key === FALSE) {// Invalid IP
164 return;
165 }
166 $info = $this->_memCache->get($key);
167 print_r($info);
168 return $info;
169 }
170
171 /**
172 * @return mixed
173 */
174 private function _addCount(array $count, $key = NULL)
175 {
176 if ($key == NULL || !isset($count[$key])) {
177 foreach ($count as &$v) {
178 $v++;
179 }
180 return $count;
181 } else {
182 $count[$key]++;
183 return $count;
184 }
185 }
186
187 private function _campareTime($t1, $t2, $type = 'day')
188 {
189 switch($type) {
190 case 'day' :
191 return (date('Y-m-d', $t1) == date('Y-m-d', $t2));
192 break;
193 case 'hour' :
194 return (date('Y-m-d H:00:00', $t1) == date('Y-m-d H:00:00', $t2));
195 break;
196 case 'minute' :
197 return (date('Y-m-d H:i:00', $t1) == date('Y-m-d H:i:00', $t2));
198 break;
199 case 'second' :
200 return $t1 == $t2;
201 break;
202 default :
203 return false;
204 break;
205 }
206 }
207
208 private function _checkIP($ip)
209 {
210 if (empty($ip)) {
211 return false;
212 }
213 $key = ip2long($ip);
214 if ($key == -1 || $key === FALSE) {// Invalid IP
215 return false;
216 }
217 return $key;
218 }
219
220 private function _checkIPWhiteList($ip)
221 {
222 return $this->isIpMatched($ip, $this->_ipWhiteLists);
223 }
224
225 private function _checkIPBlackList($ip)
226 {
227 return $this->isIpMatched($ip, $this->_ipBlackLists);
228 }
229
230 /**
231 * @param string $ip the IP address
232 * @return boolean whether the rule applies to the IP address
233 */
234 protected function isIpMatched($ip, $ips)
235 {
236 if(empty($ips))
237 return false;
238 foreach($ips as $rule)
239 {
240 if($rule==='*' || $rule===$ip || (($pos=strpos($rule,'*'))!==false && !strncmp($ip,$rule,$pos)))
241 return true;
242 }
243 return false;
244 }
245
246 }
247 ?>