├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── bgpranking_redis
├── __init__.py
├── api.py
├── constraints.default.py
└── tools.py
├── doc
├── Makefile
└── source
│ ├── code
│ └── API.rst
│ ├── conf.py
│ └── index.rst
├── example
├── api_web
│ ├── client
│ │ ├── MANIFEST.in
│ │ ├── README.md
│ │ ├── bgpranking_web
│ │ │ ├── __init__.py
│ │ │ └── api.py
│ │ └── setup.py
│ └── server
│ │ └── webservice.py
├── export
│ ├── asn_ranks
│ │ ├── agg_consummer.py
│ │ ├── consumer.py
│ │ ├── generate_aggs.py
│ │ ├── init_redis.py
│ │ ├── launch.sh
│ │ ├── launch_local_redis.sh
│ │ └── local_redis.conf
│ └── day_ips
│ │ ├── consumer.py
│ │ ├── dates.sh
│ │ ├── dump.py
│ │ ├── init_redis.py
│ │ ├── launch.sh
│ │ ├── launch_local_redis.sh
│ │ └── local_redis.conf
├── ip_zmq
│ └── client.py
├── logging
│ ├── logs
│ │ └── .keepdir
│ ├── mail.conf
│ └── start_logging.sh
├── twitter_bot
│ ├── microblog
│ │ ├── __init__.py
│ │ ├── api_wrapper.py
│ │ └── python_stream_api.py
│ └── start_bot.py
└── website
│ ├── __init__.py
│ ├── compil.sh
│ ├── config
│ └── web_bgp-ranking.ini
│ ├── data
│ ├── csv
│ │ └── .is_csv_dir
│ ├── csv_agg
│ │ └── .keepdir
│ └── js
│ │ └── .keepdir
│ ├── js
│ ├── world_benelux.js
│ ├── world_luxembourg.js
│ └── worldmap_script.js
│ ├── logs
│ └── .keepdir
│ ├── master.py
│ ├── master_controler.py
│ ├── start_website.sh
│ ├── templates
│ ├── __init__.py
│ ├── asn_details.tmpl
│ ├── comparator.tmpl
│ ├── index_asn.tmpl
│ ├── ip_lookup.tmpl
│ ├── map.tmpl
│ ├── master.tmpl
│ ├── trend.tmpl
│ └── trend_benelux.tmpl
│ └── thirdparty
│ ├── dygraph
│ └── .keepdir
│ ├── jvectormap
│ └── .keepdir
│ └── update_thirdparty.sh
└── setup.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.csv
3 | *.js
4 | build/
5 | example/csv/csv/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, 2014 Raphaël Vinot
2 | Copyright (c) 2013, 2014 CIRCL - Computer Incident Response Center Luxembourg
3 | (c/o smile, security made in Lëtzebuerg, Groupement
4 | d'Intérêt Economique)
5 |
6 | All rights reserved.
7 |
8 | Redistribution and use in source and binary forms, with or without modification,
9 | are permitted provided that the following conditions are met:
10 |
11 | 1. Redistributions of source code must retain the above copyright notice,
12 | this list of conditions and the following disclaimer.
13 | 2. Redistributions in binary form must reproduce the above copyright notice,
14 | this list of conditions and the following disclaimer in the documentation
15 | and/or other materials provided with the distribution.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
25 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
26 | OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Python API to connect to the redis database of a BGP Ranking instance.
2 |
3 | The documentation is available here:
4 | http://circl.github.com/bgpranking-redis-api/code/API.html
5 |
6 | Important
7 | =========
8 |
9 | Please note that you can query the public instance of BGP Ranking via
10 | the REST API available in this repository under `example/api_web/client/`
11 |
--------------------------------------------------------------------------------
/bgpranking_redis/__init__.py:
--------------------------------------------------------------------------------
1 | import helper_global
2 | helper_global.__prepare()
3 |
4 | from api import *
5 |
--------------------------------------------------------------------------------
/bgpranking_redis/api.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | Entry point of the API
6 | """
7 |
8 | import redis
9 | import IPy
10 | from dateutil import parser
11 | from dateutil import tz
12 | import datetime
13 |
14 | from . import constraints as c
15 |
16 | HAS_PTR = False
17 |
18 | try:
19 | from ipasn_redis import IPASN
20 | HAS_IPASN = True
21 | except ImportError:
22 | HAS_IPASN = False
23 |
24 | try:
25 | from asnhistory import ASNHistory
26 | HAS_ASN_HIST = True
27 | except ImportError:
28 | HAS_ASN_HIST = False
29 |
30 | try:
31 | from pubsublogger import publisher
32 | publisher.channel = 'API_Redis'
33 | HAS_LOGGING = True
34 | except ImportError:
35 | HAS_LOGGING = False
36 |
37 |
38 | class BGPRanking(object):
39 |
40 | def __init__(self):
41 | self.global_db = redis.Redis(port=c.redis_port1, db=c.redis_db_global, host=c.redis_hostname1)
42 | self.history_db = redis.Redis(port=c.redis_port2, db=c.redis_db_history, host=c.redis_hostname2)
43 | self.config_db = redis.Redis(port=c.redis_port2, db=c.redis_db_config, host=c.redis_hostname2)
44 | self.history_db_cache = redis.Redis(port=c.redis_cached_port, db=c.redis_cached_db_history, host=c.redis_hostname1)
45 |
46 | if c.ptr_host and c.ptr_port:
47 | # PTR record lookup configured
48 | self.ptr_redis = redis.Redis(host=c.ptr_host, port=c.ptr_port)
49 | self.has_ptr = True
50 | else:
51 | self.has_ptr = False
52 | if HAS_IPASN and c.ipasn_host and c.ipasn_port:
53 | # IP ASN history lookup configured
54 | self.ipasn = IPASN(host=c.ipasn_host, port=c.ipasn_port)
55 | self.has_ipasn = True
56 | else:
57 | self.has_ipasn = False
58 | if HAS_ASN_HIST and c.asn_host and c.asn_port:
59 | # ASN Description history configured
60 | self.asnhistory = ASNHistory(host=c.asn_host, port=c.asn_port)
61 | self.has_asnhistory = True
62 | else:
63 | self.has_asnhistory = False
64 |
65 | def get_ptr_record(self, ip):
66 | # Get PTR Record when looking for an IP
67 | if self.has_ptr:
68 | return self.ptr_redis.get(ip)
69 |
70 | def get_asn_descriptions(self, asn):
71 | if not self.has_asnhistory:
72 | publisher.debug('ASN History not enabled.')
73 | return [datetime.date.today(), 'ASN History not enabled.']
74 | desc_history = self.asnhistory.get_all_descriptions(asn)
75 | return [(date.astimezone(tz.tzutc()).date(), descr) for date, descr in desc_history]
76 |
77 | def get_ip_info(self, ip, days_limit=None):
78 | """
79 | Return informations related to an IP address.
80 |
81 | :param ip: The IP address
82 | :param days_limit: The number of days we want to check in the past
83 | (default: around 2 years)
84 | :rtype: Dictionary
85 |
86 | .. note:: Format of the output:
87 |
88 | .. code-block:: python
89 |
90 | {
91 | 'ip': ip,
92 | 'days_limit' : days_limit,
93 | 'ptrrecord' : 'ptr.record.com',
94 | 'history':
95 | [
96 | {
97 | 'asn': asn,
98 | 'interval': [first, last],
99 | 'block': block,
100 | 'timestamp': timestamp,
101 | 'descriptions':
102 | [
103 | [date, descr],
104 | ...
105 | ]
106 | },
107 | ...
108 | ]
109 | }
110 | """
111 | if days_limit is None:
112 | days_limit = 750
113 | to_return = {'ip': ip, 'days_limit': days_limit, 'history': []}
114 | if self.has_ptr:
115 | to_return['ptrrecord'] = self.get_ptr_record(ip)
116 | if not self.has_ipasn:
117 | publisher.debug('IPASN not enabled.')
118 | to_return['error'] = 'IPASN not enabled.'
119 | return to_return
120 | if not ip:
121 | to_return['error'] = 'No IP provided.'
122 | return to_return
123 | for first, last, asn, block in self.ipasn.aggregate_history(ip, days_limit):
124 | first_date = parser.parse(first).replace(tzinfo=tz.tzutc()).date()
125 | last_date = parser.parse(last).replace(tzinfo=tz.tzutc()).date()
126 | if self.has_asnhistory:
127 | desc_history = self.asnhistory.get_all_descriptions(asn)
128 | valid_descriptions = []
129 | for date, descr in desc_history:
130 | date = date.astimezone(tz.tzutc()).date()
131 | test_date = date - datetime.timedelta(days=1)
132 | if last_date < test_date:
133 | # Too new
134 | continue
135 | elif last_date >= test_date and first_date <= test_date:
136 | # Changes within the interval
137 | valid_descriptions.append([date.isoformat(), descr])
138 | elif first_date > test_date:
139 | # get the most recent change befrore the interval
140 | valid_descriptions.append([date.isoformat(), descr])
141 | break
142 | else:
143 | publisher.debug('ASN History not enabled.')
144 | valid_descriptions = [datetime.date.today().isoformat(), 'ASN History not enabled.']
145 | if len(valid_descriptions) == 0:
146 | if len(desc_history) != 0:
147 | # fallback, use the oldest description.
148 | date = desc_history[-1][0].astimezone(tz.tzutc()).date()
149 | descr = desc_history[-1][1]
150 | valid_descriptions.append([date.isoformat(), descr])
151 | else:
152 | # No history found for this ASN
153 | if last_date > datetime.date(2013, 1, 1):
154 | # ASN has been seen recently, should not happen
155 | # as the asn history module is running since early 2013
156 | publisher.error('Unable to find the ASN description of {}. IP address: {}. ASN History might be down.'.format(asn, ip))
157 | valid_descriptions.append(['0000-00-00', 'No ASN description has been found.'])
158 | entry = {}
159 | entry['asn'] = asn
160 | entry['interval'] = [first_date.isoformat(), last_date.isoformat()]
161 | entry['block'] = block
162 | entry['timestamp'] = self.get_first_seen(asn, block)
163 | entry['descriptions'] = valid_descriptions
164 | to_return['history'].append(entry)
165 | return to_return
166 |
167 | def asn_exists(self, asn):
168 | return self.global_db.exists(asn)
169 |
170 | def get_default_date(self, delta_days=1):
171 | return self.get_default_date_raw(delta_days).isoformat()
172 |
173 | def get_default_date_raw(self, delta_days=1):
174 | """
175 | Get the default date displayed on the website.
176 | """
177 | delta = datetime.timedelta(days=delta_days)
178 | timestamp = self.history_db.get('latest_ranking')
179 | if timestamp:
180 | default_date_raw = parser.parse(timestamp.split()[0]).date() - delta
181 | else:
182 | default_date_raw = datetime.date.today() - delta
183 | return default_date_raw
184 |
185 | def get_last_seen_sources(self, asn, dates_sources):
186 | """
187 | Return a dictionary conteining the last date a particular ASN
188 | has been seen by a source.
189 | """
190 | if not self.asn_exists(asn):
191 | return {}
192 | string = '{asn}|{date}|{source}|rankv{ip_version}'
193 | s_dates = dates_sources.keys()
194 | s_dates.sort(reverse=True)
195 | p = self.history_db.pipeline()
196 | for date in s_dates:
197 | sources = dates_sources[date]
198 | if len(sources) > 0:
199 | [p.exists(string.format(asn=asn, date=date, source=source, ip_version=c.ip_version)) for source in sources]
200 | asns_found = p.execute()
201 | i = 0
202 | to_return = {}
203 | for date in s_dates:
204 | sources = dates_sources[date]
205 | if len(sources) > 0:
206 | for source in sources:
207 | if to_return.get(source) is None and asns_found[i]:
208 | to_return[source] = date
209 | i += 1
210 | return to_return
211 |
212 | def get_all_ranks_single_asn(self, asn, dates_sources, with_details_sources=False):
213 | """
214 | Get all the ranks on a timeframe for a single ASN.
215 |
216 | :param asn: Autonomous System Number
217 | :param dates_sources: Dictionaries of the dates and sources
218 |
219 | .. note:: Format of the dictionary:
220 |
221 | .. code-block:: python
222 |
223 | {
224 | YYYY-MM-DD: [source1, source2, ...],
225 | YYYY-MM-DD: [source1, source2, ...],
226 | ...
227 | }
228 |
229 | :param with_details_sources: Returns the details by sources if True
230 |
231 | :rype: Dictionary
232 |
233 | .. note:: Format of the output:
234 |
235 | .. code-block:: python
236 |
237 | {
238 | date1:
239 | {
240 | 'details':
241 | [
242 | (source1, rank),
243 | (source2, rank),
244 | ...
245 | ],
246 | 'total': sum_of_ranks,
247 | 'description': description
248 | }
249 | ...
250 | }
251 |
252 | The details key is only present if
253 | ``with_details_sources`` is True.
254 | """
255 | to_return = {}
256 | if with_details_sources is None:
257 | with_details_sources = False
258 | if not self.asn_exists(asn):
259 | return to_return
260 | string = '{asn}|{date}|{source}|rankv{ip_version}'
261 | p = self.history_db.pipeline()
262 | for date, sources in dates_sources.iteritems():
263 | if len(sources) > 0:
264 | p.mget([string.format(asn=asn, date=date, source=source, ip_version=c.ip_version) for source in sources])
265 | ranks = p.execute()
266 | i = 0
267 | for date, sources in dates_sources.iteritems():
268 | impacts = self.get_all_weights(date)
269 | if len(sources) > 0:
270 | to_return[date] = {}
271 | zipped_sources_rank = zip(sources, ranks[i])
272 | if with_details_sources:
273 | to_return[date]['details'] = list(zipped_sources_rank)
274 | to_return[date]['description'] = self.asn_desc_via_history(asn)
275 | to_return[date]['total'] = 0
276 | for s, r in zipped_sources_rank:
277 | if r is not None:
278 | to_return[date]['total'] += float(r) * float(impacts[s])
279 | i += 1
280 | return to_return
281 |
282 | def existing_asns_timeframe(self, dates_sources):
283 | """
284 | Get all the ASNs seen in the lists on a timeframe.
285 | """
286 | asns_keys = []
287 | for date, sources in dates_sources.iteritems():
288 | asns_keys.extend(['{date}|{source}|asns'.format(date=date, source=source) for source in sources])
289 | p = self.global_db.pipeline(False)
290 | [p.smembers(k) for k in asns_keys]
291 | asns_list = p.execute()
292 | return set.union(*asns_list)
293 |
294 | def get_all_ranks_all_asns(self, dates_sources, with_details_sources=False):
295 | """
296 | Get all ranks for all the ASNs on a timeframe.
297 |
298 | :param dates_sources: Dictionaries of the dates and sources
299 |
300 | .. note:: Format of the dictionary:
301 |
302 | .. code-block:: python
303 |
304 | {
305 | YYYY-MM-DD: [source1, source2, ...],
306 | YYYY-MM-DD: [source1, source2, ...],
307 | ...
308 | }
309 |
310 | :param with_details_sources: Also returns the ranks by sources
311 |
312 | :rype: Dictionary
313 |
314 | .. note:: Format of the dictionary:
315 |
316 | .. code-block:: python
317 |
318 | {
319 | YYYY-MM-DD:
320 | {
321 | asn1 :
322 | {
323 | 'details':
324 | [
325 | (source1, rank),
326 | (source2, rank),
327 | ...
328 | ],
329 | 'total': sum_of_ranks,
330 | 'description': description
331 | }
332 | ...
333 | }
334 | ...
335 | }
336 |
337 | The details key is only present if
338 | ``with_details_sources`` is True.
339 | """
340 | asns = self.existing_asns_timeframe(dates_sources)
341 | to_return = {}
342 | for asn in asns:
343 | details = self.get_all_ranks_single_asn(asn, dates_sources, with_details_sources)
344 | for date, entries in details.iteritems():
345 | if to_return.get(date) is None:
346 | to_return[date] = {}
347 | to_return[date][asn] = entries
348 | return to_return
349 |
350 | def get_all_blocks(self, asn):
351 | """
352 | Expose the list of blocks announced by an AS.
353 |
354 | :param asn: Autonomous System Number
355 | :rtype: Set of ip blocks
356 | """
357 | return self.global_db.smembers(asn)
358 |
359 | def get_all_blocks_description(self, asn):
360 | """
361 | Get the most recent description of all the blocks announced by an AS
362 |
363 | :param asn: Autonomous System Number
364 | :rtype: List
365 |
366 | .. note:: Format of the list:
367 |
368 | .. code-block:: python
369 |
370 | [asn, [(block, description), ...]]
371 | """
372 | blocks = self.get_all_blocks(asn)
373 | if len(blocks) > 0:
374 | block_keys = ['{asn}|{block}'.format(asn=asn, block=block) for block in blocks]
375 | p = self.global_db.pipeline(False)
376 | [p.hgetall(block_key) for block_key in block_keys]
377 | ts_descr = p.execute()
378 | i = 0
379 | block_descr = []
380 | for block_key in block_keys:
381 | if ts_descr[i] is not None:
382 | asn, block = block_key.split('|')
383 | most_recent_ts = sorted(ts_descr[i].keys())[0]
384 | descr = ts_descr[i][most_recent_ts]
385 | block_descr.append((block, descr))
386 | i += 1
387 | return asn, block_descr
388 | else:
389 | return asn, []
390 |
391 | def get_first_seen(self, asn, block):
392 | """
393 | Get the oldest timestamp where the block has been announced by the AS
394 | """
395 | timestamps = self.global_db.hkeys(asn + '|' + block)
396 | if timestamps is not None and len(timestamps) > 0:
397 | timestamps.sort()
398 | return timestamps[0]
399 | return None
400 |
401 | def get_block_descriptions(self, asn, block):
402 | """
403 | Return all the descriptions of a block, in a listed sorted by
404 | timestamp (new => old)
405 |
406 | :param asn: Autonomous System Number
407 | :param block: IP Block you are looking for
408 |
409 | :rtype: List
410 |
411 | .. note:: Format of the list:
412 |
413 | .. code-block:: python
414 |
415 | [asn, block, [(ts, descr), ...]]
416 | """
417 | ts_descr = self.global_db.hgetall('{}|{}'.format(asn, block))
418 | timestamps = sorted(ts_descr.keys(), reverse=True)
419 | descriptions = [(t, ts_descr[t])for t in timestamps]
420 | return asn, block, descriptions
421 |
422 | def get_all_blocks_descriptions(self, asn):
423 | """
424 | Return all tuples timestamp-description of all the blocs announced
425 | by an AS over time.
426 |
427 | :param asn: Autonomous System Number
428 | :rtype: List
429 |
430 | .. note:: Format of the list:
431 |
432 | .. code-block:: python
433 |
434 | [ asn,
435 | {
436 | block:
437 | [
438 | (timestamp, description),
439 | ...
440 | ],
441 | ...
442 | }
443 | ]
444 | """
445 | blocks_descriptions = {}
446 | for block in self.get_all_blocks(asn):
447 | asn, block, descriptions = self.get_block_descriptions(asn, block)
448 | blocks_descriptions[block] = descriptions
449 | return asn, blocks_descriptions
450 |
451 | def asn_desc_via_history(self, asn):
452 | if self.has_asnhistory:
453 | asn_descr = self.asnhistory.get_last_description(asn)
454 | if asn_descr is None:
455 | # The ASN has no descripion in the database
456 | # publisher.error(\
457 | # 'Unable to find the ASN description of {}. ASN History might be down.'.\
458 | # format(asn))
459 | asn_descr = 'No ASN description has been found.'
460 | else:
461 | publisher.debug('ASN History not enabled.')
462 | asn_descr = 'ASN History not enabled.'
463 | return asn_descr
464 |
465 | def get_asn_descs(self, asn, date=None, sources=None):
466 | """
467 | Get all what is available in the database about an ASN for
468 | one day
469 |
470 | :param asn: Autonomous System Number
471 | :param date: Date of the information (default: last ranked day)
472 | :param sources: List of sources (default: the available sources
473 | at ``date``)
474 | :rtype: Dictionary
475 |
476 | .. note:: Format of the dictionary:
477 |
478 | .. code-block:: python
479 |
480 | {
481 | 'date': date,
482 | 'sources': [source1, source2, ...],
483 | 'asn': asn,
484 | 'asn_description': asn_description,
485 | asn:
486 | {
487 | 'clean_blocks':
488 | {
489 | block: [[ts, descr], ...],
490 | ....
491 | },
492 | 'old_blocks':
493 | {
494 | block: [[ts, descr], ...],
495 | ....
496 | },
497 | block:
498 | {
499 | 'description': description,
500 | 'timestamp': ts,
501 | 'old_descr': [(timestamp, description), ...]
502 | 'nb_of_ips': nb,
503 | 'sources': [source1, source2, ...]
504 | 'rank': subnet_rank
505 | },
506 | ...
507 | }
508 | }
509 |
510 |
511 | """
512 | if not self.asn_exists(asn):
513 | # The ASN does not exists in the database
514 | return None
515 |
516 | if date is None:
517 | date = self.get_default_date()
518 | day_sources = self.daily_sources([date])
519 | if sources is None:
520 | sources = list(day_sources)
521 | else:
522 | if not isinstance(sources, list):
523 | sources = [sources]
524 | sources = list(day_sources.intersection(set(sources)))
525 | asn_descr = self.asn_desc_via_history(asn)
526 | to_return = {'date': date, 'sources': sources, 'asn': asn, 'asn_description': asn_descr, asn: {}}
527 |
528 | key_clean_set = '{asn}|{date}|clean_set'.format(asn=asn, date=date)
529 | for ip_block in self.get_all_blocks(asn):
530 | # Get descriptions
531 | asn, block, ts_descr = self.get_block_descriptions(asn, ip_block)
532 | # Find out if the block has been seen these day
533 | p = self.global_db.pipeline(False)
534 | [p.scard('{}|{}|{}|{}'.format(asn, ip_block, date, source)) for source in sources]
535 | ips_by_sources = p.execute()
536 | nb_of_ips = sum(ips_by_sources)
537 |
538 | if nb_of_ips == 0:
539 | if ip_block in self.history_db.smembers(key_clean_set):
540 | if to_return[asn].get('clean_blocks') is None:
541 | to_return[asn]['clean_blocks'] = {}
542 | to_return[asn]['clean_blocks'][ip_block] = ts_descr
543 | else:
544 | if to_return[asn].get('old_blocks') is None:
545 | to_return[asn]['old_blocks'] = {}
546 | to_return[asn]['old_blocks'][ip_block] = ts_descr
547 | else:
548 | impacts = self.get_all_weights(date)
549 | # Compute the local ranking: the ranking if this subnet is
550 | # the only one for this AS
551 | i = 0
552 | sum_rank = 0
553 | sources_exists = []
554 | for source in sources:
555 | sum_rank += float(ips_by_sources[i]) * float(impacts[source])
556 | if ips_by_sources[i] != 0:
557 | sources_exists.append(source)
558 | i += 1
559 | local_rank = sum_rank / IPy.IP(ip_block).len()
560 | to_return[asn][ip_block] = {'description': ts_descr[0][1], 'timestamp': ts_descr[0][0],
561 | 'old_descr': ts_descr[1:], 'nb_of_ips': nb_of_ips,
562 | 'sources': sources_exists, 'rank': local_rank}
563 | return to_return
564 |
565 | def get_all_weights(self, date=None):
566 | """
567 | Get the weights for all the sources.
568 |
569 | :param date: Date of the information (default: last ranked day)
570 |
571 | :rtype: Dictionary
572 |
573 | .. note:: Format of the dictionary:
574 |
575 | .. code-block:: python
576 |
577 | {
578 | source: date,
579 | ...
580 | }
581 | """
582 | if date is None:
583 | date = self.get_default_date()
584 | sources = self.daily_sources([date])
585 | to_return = {}
586 | if len(sources) > 0:
587 | impacts = self.config_db.mget(sources)
588 | to_return = dict(zip(sources, impacts))
589 | return to_return
590 |
591 | def daily_sources(self, dates):
592 | """
593 | Get the sources parsed during a list of dates
594 |
595 | :param dates: List of dates
596 |
597 | :rtype: Set of sources for each date
598 |
599 | """
600 | if len(dates) == 1:
601 | return self.global_db.smembers('{date}|sources'.format(date=dates[0]))
602 | else:
603 | p = self.global_db.pipeline(False)
604 | [p.smembers('{date}|sources'.format(date=date)) for date in dates]
605 | return p.execute()
606 |
607 | def get_ips_descs(self, asn, block, date=None, sources=None):
608 | """
609 | Get all what is available in the database about an subnet for one day
610 |
611 | :param asn: Autonomous System Number
612 | :param asn_timestamp: First the subnet has been seen in the db
613 | (for this ASN)
614 | :param date: Date of the information (default: last ranked day)
615 | :param sources: List of sources (default: the available sources
616 | at ``date``)
617 |
618 | :rtype: Dictionary
619 |
620 | .. note:: Format of the dictionary:
621 |
622 | .. code-block:: python
623 |
624 | {
625 | 'date': date,
626 | 'sources': [source1, source2, ...],
627 | 'asn': asn,
628 | 'asn_description': asn_description,
629 | 'block': block
630 | block:
631 | {
632 | ip:
633 | {
634 | 'sources': [source1, source2, ...],
635 | 'ptrrecord': 'ptr.record.com'
636 | }
637 | ...
638 | }
639 | }
640 |
641 | """
642 |
643 | if date is None:
644 | date = self.get_default_date()
645 | day_sources = self.daily_sources([date])
646 | if sources is None:
647 | sources = list(day_sources)
648 | else:
649 | if not isinstance(sources, list):
650 | sources = [sources]
651 | sources = list(day_sources.intersection(set(sources)))
652 | asn_descr = self.asn_desc_via_history(asn)
653 | to_return = {'date': date, 'sources': sources, 'asn': asn, 'asn_description': asn_descr, 'block': block, block: {}}
654 | asn_block_key = '{asn}|{b}|{date}|'.format(asn=asn, b=block, date=date)
655 | pipeline = self.global_db.pipeline(False)
656 | [pipeline.smembers('{asn_b}{source}'.format(asn_b=asn_block_key, source=source)) for source in sources]
657 | ips_by_source = pipeline.execute()
658 | i = 0
659 | for source in sources:
660 | ips = ips_by_source[i]
661 | for ip_details in ips:
662 | ip, timestamp = ip_details.split('|')
663 | if to_return[block].get(ip) is None:
664 | to_return[block][ip] = {'sources': [], 'ptrrecord': None}
665 | to_return[block][ip]['sources'].append(source)
666 | i += 1
667 | if self.has_ptr:
668 | for ip in to_return[block]:
669 | to_return[block][ip]['ptrrecord'] = self.get_ptr_record(ip)
670 | return to_return
671 |
672 | def get_stats(self, dates_sources):
673 | """
674 | Return amount of asn and subnets found by source on a list of days.
675 |
676 | :param dates_sources: Dictionaries of the dates and sources
677 |
678 | :rtype: Dictionary
679 |
680 | .. note:: Format of the Dictionary
681 |
682 | .. code-block:: python
683 |
684 | {
685 | date:
686 | {
687 | 'sources':
688 | {
689 | source : [nb_asns, nb_subnets],
690 | ...
691 | },
692 | 'total_asn': total_asn
693 | 'total_subnets': total_subnets
694 | }
695 | ...
696 | }
697 |
698 | """
699 | to_return = {}
700 | p = self.global_db.pipeline(False)
701 | for date, sources in dates_sources.iteritems():
702 | to_return[date] = {}
703 | for source in sources:
704 | current_key = '{date}|{source}|asns'.format(date=date, source=source)
705 | p.scard(current_key)
706 | p.scard(current_key + '_details')
707 | cards = p.execute()
708 | i = 0
709 | for date, sources in dates_sources.iteritems():
710 | to_return[date] = {'sources': {}}
711 | total_asn = 0
712 | total_subnets = 0
713 | for source in sources:
714 | nb_asns = cards[i]
715 | nb_subnets = cards[i + 1]
716 | total_asn += nb_asns
717 | total_subnets += nb_subnets
718 | to_return[date]['sources'].update({source: (nb_asns, nb_subnets)})
719 | i = i + 2
720 | to_return[date]['total_asn'] = total_asn
721 | to_return[date]['total_subnets'] = total_subnets
722 | return to_return
723 |
724 | # Need cached database
725 | def cache_get_dates(self):
726 | """
727 | **From the temporary database**
728 |
729 | Get a list of dates. The ranking are available in the cache database.
730 | """
731 | return sorted(self.history_db_cache.smembers('all_dates'))
732 |
733 | def cache_get_stats(self):
734 | """
735 | Return amount of asn and subnets found by source from the cache.
736 | """
737 | dates = self.cache_get_dates()
738 | dates_sources = dict(zip(dates, self.daily_sources(dates)))
739 | return self.get_stats(dates_sources)
740 |
741 | def cache_get_daily_rank(self, asn, source='global', date=None):
742 | """
743 | **From the temporary database**
744 |
745 | Get a single rank.
746 |
747 | :param asn: Autonomous System Number
748 | :param source: Source to use. global is the aggregated view for
749 | all the sources
750 | :param date: Date of the information (default: last ranked day)
751 |
752 | :rtype: List
753 |
754 | .. note:: Format of the list
755 |
756 | .. code-block:: python
757 |
758 | [asn, asn_description, date, source, rank]
759 | """
760 | if source is None:
761 | source = 'global'
762 | if date is None:
763 | date = self.get_default_date()
764 | histo_key = '{date}|{source}|rankv{ip_version}'.format(date=date, source=source, ip_version=c.ip_version)
765 | asn_descr = self.asn_desc_via_history(asn)
766 | return asn, asn_descr, date, source, self.history_db_cache.zscore(histo_key, asn)
767 |
768 | def cache_get_position(self, asn, source='global', date=None):
769 | """
770 | **From the temporary database**
771 |
772 | Get the position of the ASN in the zrank.
773 |
774 | :param asn: Autonomous System Number
775 | :param source: Source to use. global is the aggregated view for
776 | all the sources
777 | :param date: Date of the information (default: last ranked day)
778 |
779 | :rtype: Integer, position in the list and size of the list.
780 |
781 | .. note:: if None, the zrank does not exists (source or date invalid)
782 | """
783 | if source is None:
784 | source = 'global'
785 | if date is None:
786 | date = self.get_default_date()
787 | histo_key = '{date}|{source}|rankv{ip_version}'.format(date=date, source=source, ip_version=c.ip_version)
788 | return self.history_db_cache.zrevrank(histo_key, asn), self.history_db_cache.zcard(histo_key)
789 |
790 | def cache_get_top_asns(self, source='global', date=None, limit=100, with_sources=True):
791 | """
792 | **From the temporary database**
793 |
794 | Get worse ASNs.
795 |
796 | :param source: Source used to rank the ASNs. global is the
797 | aggregated view for all the sources
798 | :param date: Date of the information (default: last ranked day)
799 | :param limit: Number of ASNs to get
800 | :param with_sources: Get the list of sources where each ASN has
801 | been found.
802 |
803 | :rtype: Dictionary
804 |
805 | .. note:: Format of the Dictionary
806 |
807 | .. code-block:: python
808 |
809 | {
810 | 'source': source,
811 | 'date': date,
812 | 'size_list': size,
813 | 'top_list':
814 | [
815 | (
816 | (asn, asn_description, rank),
817 | set([source1, source2, ...])
818 | ),
819 | ...
820 | ]
821 | }
822 |
823 | The set of sources is only presetn if ``with_sources``
824 | is True
825 |
826 | """
827 | if source is None:
828 | source = 'global'
829 | if date is None:
830 | date = self.get_default_date()
831 | if limit is None:
832 | limit = 100
833 | if with_sources is None:
834 | with_sources = True
835 | histo_key = '{date}|{histo_key}|rankv{ip_version}'.format(date=date, histo_key=source, ip_version=c.ip_version)
836 | to_return = {'source': source, 'date': date, 'size_list': self.history_db_cache.zcard(histo_key), 'top_list': []}
837 | ranks = self.history_db_cache.zrevrange(histo_key, 0, limit, True)
838 | if ranks is None:
839 | return to_return
840 | temp_rank = []
841 | for asn, rank in ranks:
842 | if asn == '-1':
843 | continue
844 | asn_descr = self.asn_desc_via_history(asn)
845 | temp_rank.append((asn, asn_descr, rank))
846 | if not with_sources:
847 | to_return['top_list'] = temp_rank
848 | else:
849 | p = self.history_db_cache.pipeline(False)
850 | [p.smembers('{}|{}'.format(date, rank[0])) for rank in temp_rank]
851 | to_return['top_list'] = list(zip(temp_rank, p.execute()))
852 | return to_return
853 |
--------------------------------------------------------------------------------
/bgpranking_redis/constraints.default.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | ip_version = 4
5 | # How any days in the past do we want
6 | default_timeframe = 3
7 |
8 | # Redis Config
9 | redis_hostname1 = '127.0.0.1'
10 | redis_port1 = 6379
11 | redis_db_global = 5
12 | redis_db_history = 6
13 | redis_db_config = 7
14 | redis_hostname2 = '127.0.0.1'
15 | redis_port2 = 6479
16 | # Redis Cached
17 | redis_cached_port = 6382
18 | redis_cached_db_history = 6
19 | # PTR records DB
20 | ptr_host = '127.0.0.1'
21 | ptr_port = 8323
22 | # ASN history
23 | asn_host = '127.0.0.1'
24 | asn_port = 6389
25 | # IP ASN
26 | ipasn_host = '127.0.0.1'
27 | ipasn_port = 16379
28 |
29 | # Archive config
30 | last_year_archived = 2015
31 | archive_host = '127.0.0.1'
32 | archive_port = 6399
33 |
--------------------------------------------------------------------------------
/bgpranking_redis/tools.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import glob
6 | from csv import DictReader, DictWriter
7 | from socket import socket, AF_INET, SOCK_STREAM
8 | import json
9 | import operator
10 |
11 | from bgpranking_redis import BGPRanking
12 |
13 |
14 | def aggregate_csvs(output_csv_dir, output_agg_dir, with_world=True, **kwargs):
15 | """
16 | Aggregate lists of ASNs in a single CSV file.
17 |
18 | kwargs has to be like:
19 | list_name_1 = [list of asns], list_name_2 = [list of asns]
20 |
21 | The output CSV file will have this format:
22 | date, world, list_name_1, list_name_2, ....
23 | """
24 | csv_files = glob.glob(os.path.join(output_csv_dir, '*'))
25 | result = {}
26 |
27 | for csv_file in csv_files:
28 | asn = os.path.basename(csv_file)
29 | with open(csv_file, 'r') as f:
30 | reader = DictReader(f)
31 | for entry in reader:
32 | # Removing 1 because we aggregate data
33 | rank = float(entry['rank']) - 1
34 | if result.get(entry['day']) is None:
35 | result[entry['day']] = {}
36 | if with_world:
37 | if result[entry['day']].get('world') is None:
38 | result[entry['day']]['world'] = 0
39 | result[entry['day']]['world'] += rank
40 | for key, arg in kwargs.iteritems():
41 | if asn in arg:
42 | if result[entry['day']].get(key) is None:
43 | result[entry['day']][key] = 0
44 | result[entry['day']][key] += rank
45 | break
46 | if with_world:
47 | fieldnames = ['world']
48 | else:
49 | fieldnames = []
50 | fieldnames += kwargs.keys()
51 | filename = os.path.join(output_agg_dir, '_'.join(fieldnames))
52 | with open(filename, 'w') as f:
53 | w = DictWriter(f, fieldnames=['date'] + fieldnames)
54 | w.writeheader()
55 | for date, entries in result.iteritems():
56 | entries.update({'date': date})
57 | w.writerow(entries)
58 |
59 |
60 | def get_asns_country_code(asns):
61 | text_lines = ["begin", "verbose"]
62 | [text_lines.append("AS" + asn) for asn in asns]
63 | text_lines.append("end")
64 | text = '\n'.join(text_lines) + '\n'
65 |
66 | s = socket(AF_INET, SOCK_STREAM)
67 | s.connect(("whois.cymru.com", 43))
68 | s.send(text)
69 | response = ''
70 | data = s.recv(2048)
71 | while data:
72 | response += data
73 | data = s.recv(2048)
74 | s.close()
75 | to_return = {}
76 | splitted = response.split('\n')
77 | for r in splitted[1:-1]:
78 | s = r.split("|")
79 | asn = s[0].strip()
80 | cc = s[1].strip()
81 | to_return[asn] = cc
82 | return to_return
83 |
84 |
85 | def generate_dumps_for_worldmap(output_dir_js=None, output_dir_csv=None):
86 | bgpranking = BGPRanking()
87 | ranks = bgpranking.cache_get_top_asns(limit=-1, with_sources=False)
88 | if ranks.get('top_list') is not None:
89 | info = get_asns_country_code([asn for asn, description, rank in ranks.get('top_list')])
90 | to_dump = {}
91 | for asn, d, rank in ranks.get('top_list'):
92 | cc = info[asn]
93 | if to_dump.get(cc) is None:
94 | to_dump[cc] = 0
95 | to_dump[cc] += rank
96 | if output_dir_js is not None:
97 | f = open(os.path.join(output_dir_js, 'worldmap.js'), "w")
98 | f.write("var ranks =\n" + json.dumps(to_dump))
99 | f.close()
100 | if output_dir_csv is not None:
101 | for_csv = sorted(to_dump.iteritems(), key=operator.itemgetter(1), reverse=True)
102 | with open(os.path.join(output_dir_csv, 'worldmap.csv'), "w") as f:
103 | w = DictWriter(f, fieldnames=['country', 'rank'])
104 | w.writeheader()
105 | for country, rank in for_csv:
106 | w.writerow({'country': country, 'rank': rank})
107 |
--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BGPRankingPythonAPI.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BGPRankingPythonAPI.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/BGPRankingPythonAPI"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BGPRankingPythonAPI"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/doc/source/code/API.rst:
--------------------------------------------------------------------------------
1 | ***
2 | API
3 | ***
4 |
5 | .. automodule:: bgpranking.api
6 | :members:
7 |
8 | .. automodule:: bgpranking.helper_global
9 | :members:
10 |
--------------------------------------------------------------------------------
/doc/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # BGP Ranking Python API documentation build configuration file, created by
4 | # sphinx-quickstart on Mon Nov 26 16:03:33 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 | sys.path.insert(0, os.path.abspath('../../'))
21 |
22 | # -- General configuration -----------------------------------------------------
23 |
24 | # If your documentation needs a minimal Sphinx version, state it here.
25 | #needs_sphinx = '1.0'
26 |
27 | # Add any Sphinx extension module names here, as strings. They can be extensions
28 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
29 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
30 |
31 | # Add any paths that contain templates here, relative to this directory.
32 | templates_path = ['_templates']
33 |
34 | # The suffix of source filenames.
35 | source_suffix = '.rst'
36 |
37 | # The encoding of source files.
38 | #source_encoding = 'utf-8-sig'
39 |
40 | # The master toctree document.
41 | master_doc = 'index'
42 |
43 | # General information about the project.
44 | project = u'BGP Ranking Python API'
45 | copyright = u'2012, CIRCL'
46 |
47 | # The version info for the project you're documenting, acts as replacement for
48 | # |version| and |release|, also used in various other places throughout the
49 | # built documents.
50 | #
51 | # The short X.Y version.
52 | version = '1.0'
53 | # The full version, including alpha/beta/rc tags.
54 | release = '1.0'
55 |
56 | # The language for content autogenerated by Sphinx. Refer to documentation
57 | # for a list of supported languages.
58 | #language = None
59 |
60 | # There are two options for replacing |today|: either, you set today to some
61 | # non-false value, then it is used:
62 | #today = ''
63 | # Else, today_fmt is used as the format for a strftime call.
64 | #today_fmt = '%B %d, %Y'
65 |
66 | # List of patterns, relative to source directory, that match files and
67 | # directories to ignore when looking for source files.
68 | exclude_patterns = []
69 |
70 | # The reST default role (used for this markup: `text`) to use for all documents.
71 | #default_role = None
72 |
73 | # If true, '()' will be appended to :func: etc. cross-reference text.
74 | #add_function_parentheses = True
75 |
76 | # If true, the current module name will be prepended to all description
77 | # unit titles (such as .. function::).
78 | #add_module_names = True
79 |
80 | # If true, sectionauthor and moduleauthor directives will be shown in the
81 | # output. They are ignored by default.
82 | #show_authors = False
83 |
84 | # The name of the Pygments (syntax highlighting) style to use.
85 | pygments_style = 'sphinx'
86 |
87 | # A list of ignored prefixes for module index sorting.
88 | #modindex_common_prefix = []
89 |
90 |
91 | # -- Options for HTML output ---------------------------------------------------
92 |
93 | # The theme to use for HTML and HTML Help pages. See the documentation for
94 | # a list of builtin themes.
95 | html_theme = 'default'
96 |
97 | # Theme options are theme-specific and customize the look and feel of a theme
98 | # further. For a list of options available for each theme, see the
99 | # documentation.
100 | #html_theme_options = {}
101 |
102 | # Add any paths that contain custom themes here, relative to this directory.
103 | #html_theme_path = []
104 |
105 | # The name for this set of Sphinx documents. If None, it defaults to
106 | # " v documentation".
107 | #html_title = None
108 |
109 | # A shorter title for the navigation bar. Default is the same as html_title.
110 | #html_short_title = None
111 |
112 | # The name of an image file (relative to this directory) to place at the top
113 | # of the sidebar.
114 | #html_logo = None
115 |
116 | # The name of an image file (within the static path) to use as favicon of the
117 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
118 | # pixels large.
119 | #html_favicon = None
120 |
121 | # Add any paths that contain custom static files (such as style sheets) here,
122 | # relative to this directory. They are copied after the builtin static files,
123 | # so a file named "default.css" will overwrite the builtin "default.css".
124 | html_static_path = ['_static']
125 |
126 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
127 | # using the given strftime format.
128 | #html_last_updated_fmt = '%b %d, %Y'
129 |
130 | # If true, SmartyPants will be used to convert quotes and dashes to
131 | # typographically correct entities.
132 | #html_use_smartypants = True
133 |
134 | # Custom sidebar templates, maps document names to template names.
135 | #html_sidebars = {}
136 |
137 | # Additional templates that should be rendered to pages, maps page names to
138 | # template names.
139 | #html_additional_pages = {}
140 |
141 | # If false, no module index is generated.
142 | #html_domain_indices = True
143 |
144 | # If false, no index is generated.
145 | #html_use_index = True
146 |
147 | # If true, the index is split into individual pages for each letter.
148 | #html_split_index = False
149 |
150 | # If true, links to the reST sources are added to the pages.
151 | #html_show_sourcelink = True
152 |
153 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
154 | #html_show_sphinx = True
155 |
156 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
157 | #html_show_copyright = True
158 |
159 | # If true, an OpenSearch description file will be output, and all pages will
160 | # contain a tag referring to it. The value of this option must be the
161 | # base URL from which the finished HTML is served.
162 | #html_use_opensearch = ''
163 |
164 | # This is the file name suffix for HTML files (e.g. ".xhtml").
165 | #html_file_suffix = None
166 |
167 | # Output file base name for HTML help builder.
168 | htmlhelp_basename = 'BGPRankingPythonAPIdoc'
169 |
170 |
171 | # -- Options for LaTeX output --------------------------------------------------
172 |
173 | latex_elements = {
174 | # The paper size ('letterpaper' or 'a4paper').
175 | #'papersize': 'letterpaper',
176 |
177 | # The font size ('10pt', '11pt' or '12pt').
178 | #'pointsize': '10pt',
179 |
180 | # Additional stuff for the LaTeX preamble.
181 | #'preamble': '',
182 | }
183 |
184 | # Grouping the document tree into LaTeX files. List of tuples
185 | # (source start file, target name, title, author, documentclass [howto/manual]).
186 | latex_documents = [
187 | ('index', 'BGPRankingPythonAPI.tex', u'BGP Ranking Python API Documentation',
188 | u'CIRCL', 'manual'),
189 | ]
190 |
191 | # The name of an image file (relative to this directory) to place at the top of
192 | # the title page.
193 | #latex_logo = None
194 |
195 | # For "manual" documents, if this is true, then toplevel headings are parts,
196 | # not chapters.
197 | #latex_use_parts = False
198 |
199 | # If true, show page references after internal links.
200 | #latex_show_pagerefs = False
201 |
202 | # If true, show URL addresses after external links.
203 | #latex_show_urls = False
204 |
205 | # Documents to append as an appendix to all manuals.
206 | #latex_appendices = []
207 |
208 | # If false, no module index is generated.
209 | #latex_domain_indices = True
210 |
211 |
212 | # -- Options for manual page output --------------------------------------------
213 |
214 | # One entry per manual page. List of tuples
215 | # (source start file, name, description, authors, manual section).
216 | man_pages = [
217 | ('index', 'bgprankingpythonapi', u'BGP Ranking Python API Documentation',
218 | [u'CIRCL'], 1)
219 | ]
220 |
221 | # If true, show URL addresses after external links.
222 | #man_show_urls = False
223 |
224 |
225 | # -- Options for Texinfo output ------------------------------------------------
226 |
227 | # Grouping the document tree into Texinfo files. List of tuples
228 | # (source start file, target name, title, author,
229 | # dir menu entry, description, category)
230 | texinfo_documents = [
231 | ('index', 'BGPRankingPythonAPI', u'BGP Ranking Python API Documentation',
232 | u'CIRCL', 'BGPRankingPythonAPI', 'One line description of project.',
233 | 'Miscellaneous'),
234 | ]
235 |
236 | # Documents to append as an appendix to all manuals.
237 | #texinfo_appendices = []
238 |
239 | # If false, no module index is generated.
240 | #texinfo_domain_indices = True
241 |
242 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
243 | #texinfo_show_urls = 'footnote'
244 |
--------------------------------------------------------------------------------
/doc/source/index.rst:
--------------------------------------------------------------------------------
1 | .. BGP Ranking Python API documentation master file, created by
2 | sphinx-quickstart on Mon Nov 26 16:03:33 2012.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to BGP Ranking Python API's documentation!
7 | ==================================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | Code Documentation
15 | ==================
16 |
17 | .. toctree::
18 | :maxdepth: 1
19 |
20 | code/API
21 |
22 |
23 | Indices and tables
24 | ==================
25 |
26 | * :ref:`genindex`
27 | * :ref:`modindex`
28 | * :ref:`search`
29 |
30 |
--------------------------------------------------------------------------------
/example/api_web/client/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include README.md
2 |
--------------------------------------------------------------------------------
/example/api_web/client/README.md:
--------------------------------------------------------------------------------
1 | Python library to access the BGP Ranking REST API.
2 |
--------------------------------------------------------------------------------
/example/api_web/client/bgpranking_web/__init__.py:
--------------------------------------------------------------------------------
1 | from api import *
2 |
--------------------------------------------------------------------------------
/example/api_web/client/bgpranking_web/api.py:
--------------------------------------------------------------------------------
1 | #!/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | This is a python implementation to simplify the use of the JSON API.
6 | It allows an access to all the functions of the Redis API, via the network.
7 |
8 | The parameters are as consistent as possible with the Redis API.
9 |
10 | """
11 |
12 | try:
13 | import simplejson as json
14 | except:
15 | import json
16 |
17 | import requests
18 |
19 | url = 'http://bgpranking.circl.lu/json'
20 |
21 |
22 | def __prepare_request(query):
23 | headers = {'content-type': 'application/json'}
24 | r = requests.post(url, data=json.dumps(query), headers=headers)
25 | return r.json()
26 |
27 |
28 | def ip_lookup(ip, days_limit=None):
29 | """See :class:`bgpranking.api.get_ip_info`"""
30 | query = {'method': 'ip_lookup'}
31 | query.update({'ip': ip, 'days_limit': days_limit})
32 | return __prepare_request(query)
33 |
34 |
35 | def all_ranks_single_asn(asn, last_day=None, timeframe=None,
36 | dates_sources=None, with_details_sources=None):
37 | """See :class:`bgpranking.api.get_all_ranks_single_asn`"""
38 | query = {'method': 'all_ranks_single_asn'}
39 | query.update({'asn': asn, 'last_day': last_day,
40 | 'timeframe': timeframe, 'dates_sources': dates_sources,
41 | 'with_details_sources': with_details_sources})
42 | return __prepare_request(query)
43 |
44 |
45 | def all_ranks_all_asns(last_day=None, timeframe=None,
46 | dates_sources=None, with_details_sources=None):
47 | """See :class:`bgpranking.api.get_all_ranks_all_asns`"""
48 | query = {'method': 'all_ranks_all_asns'}
49 | query.update({'last_day': last_day, 'timeframe': timeframe,
50 | 'dates_sources': dates_sources,
51 | 'with_details_sources': with_details_sources})
52 | return __prepare_request(query)
53 |
54 |
55 | def asn_description(asn, date=None, sources=None):
56 | """See :class:`bgpranking.api.get_asn_descs`"""
57 | query = {'method': 'asn_description'}
58 | if sources is not None and isinstance(sources, list):
59 | sources = [sources]
60 | query.update({'asn': asn, 'date': date, 'sources': sources})
61 | return __prepare_request(query)
62 |
63 |
64 | def ips_description(asn, asn_timestamp, date=None, sources=None):
65 | """See :class:`bgpranking.api.get_ips_descs`"""
66 | query = {'method': 'ips_description'}
67 | if sources is not None and isinstance(sources, list):
68 | sources = [sources]
69 | query.update({'asn': asn, 'asn_timestamp': asn_timestamp,
70 | 'date': date, 'sources': sources})
71 | return __prepare_request(query)
72 |
73 |
74 | def stats(last_day=None, timeframe=None, dates_sources=None):
75 | """See :class:`bgpranking.api.get_stats`"""
76 | query = {'method': 'stats'}
77 | query.update({'last_day': last_day, 'timeframe': timeframe,
78 | 'dates_sources': dates_sources})
79 | return __prepare_request(query)
80 |
81 |
82 | def block_descriptions(asn, block):
83 | """See :class:`bgpranking.api.get_block_descriptions`"""
84 | query = {'method': 'block_descriptions'}
85 | query.update({'asn': asn, 'block': block})
86 | return __prepare_request(query)
87 |
88 |
89 | def cached_dates():
90 | """See :class:`bgpranking.api.cache_get_dates`"""
91 | query = {'method': 'cached_dates'}
92 | return __prepare_request(query)
93 |
94 |
95 | def cached_daily_rank(asn, date=None, sources=None):
96 | """See :class:`bgpranking.api.cache_get_daily_rank`"""
97 | query = {'method': 'cached_daily_rank'}
98 | if sources is not None and isinstance(sources, list):
99 | sources = [sources]
100 | query.update({'asn': asn, 'date': date, 'sources': sources})
101 | return __prepare_request(query)
102 |
103 |
104 | def cached_top_asns(date=None, source=None, limit=None, with_sources=None):
105 | """See :class:`bgpranking.api.cache_get_top_asns`"""
106 | query = {'method': 'cached_top_asns'}
107 | query.update({'date': date, 'source': source, 'limit': limit,
108 | 'with_sources': with_sources})
109 | return __prepare_request(query)
110 |
111 |
112 | def cached_position(asn, date=None):
113 | """See :class:`bgpranking.api.cache_get_position`"""
114 | query = {'method': 'cached_position'}
115 | query.update({'asn': asn, 'date': date})
116 | return __prepare_request(query)
117 |
--------------------------------------------------------------------------------
/example/api_web/client/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | from setuptools import setup
5 |
6 | setup(
7 | name='bgpranking-web',
8 | version='1.1.1',
9 | description='Library to access the BGP Ranking REST API.',
10 | url='https://github.com/CIRCL/bgpranking-redis-api',
11 | author='Raphaël Vinot',
12 | author_email='raphael.vinot@circl.lu',
13 | maintainer='Raphaël Vinot',
14 | packages=['bgpranking_web'],
15 | long_description=open('README.md').read(),
16 | classifiers=[
17 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
18 | 'Development Status :: 5 - Production/Stable',
19 | 'Environment :: Console',
20 | 'Intended Audience :: Science/Research',
21 | 'Intended Audience :: Telecommunications Industry',
22 | 'Programming Language :: Python',
23 | 'Topic :: Security',
24 | 'Topic :: Internet',
25 | 'Topic :: System :: Networking',
26 | ],
27 |
28 | install_requires=['requests']
29 | )
30 |
--------------------------------------------------------------------------------
/example/api_web/server/webservice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | The JSON API allows to do queries on the whole database of BGP Ranking
6 | via the Web interface.
7 |
8 | By default, the path where the webservice is listening is
9 | http://domain/json
10 |
11 | You need to send a well-formed JSON object with a entry 'method' and as
12 | value the name of the function you want to call (listed bellow).
13 |
14 | The other entries of the JSON Object will depend on the function. To keep
15 | it simple, the name of each entry is the parameter name from the Redis API.
16 |
17 | The default parameters are as consistent as possible with the one of the
18 | Redis API (:class:`bgpranking.api`)
19 |
20 | """
21 |
22 |
23 | from flask import Flask, json, request
24 | import StringIO
25 | import csv
26 |
27 | import bgpranking_redis as bgpranking
28 | bgpranking.asnhistory.redis_host = '127.0.0.1'
29 | bgpranking.asnhistory.redis_port = 6389
30 | bgpranking.ipasn.hostname = '127.0.0.1'
31 | bgpranking.ipasn.port = 6390
32 | bgpranking.ptr_host = '127.0.0.1'
33 | bgpranking.ptr_port = 8323
34 |
35 | logging = True
36 | try:
37 | if logging:
38 | from pubsublogger import publisher
39 | publisher.channel = 'API_Web'
40 | except:
41 | logging = False
42 |
43 |
44 | app = Flask(__name__)
45 | app.debug = True
46 |
47 | authorized_methods = ['all_ranks_single_asn', 'all_ranks_all_asns',
48 | 'block_descriptions', 'asn_description', 'ips_description',
49 | 'stats', 'cached_dates', 'cached_daily_rank', 'cached_top_asns',
50 | 'ip_lookup', 'cached_position']
51 |
52 |
53 | class SetEncoder(json.JSONEncoder):
54 | def default(self, obj):
55 | if type(obj) == type(set()):
56 | return list(obj)
57 | return json.JSONEncoder.default(self, obj)
58 |
59 |
60 | def __default_dates_sources(req):
61 | dates_sources = req.get('dates_sources')
62 | if dates_sources is None:
63 | dates_sources = bgpranking.prepare_sources_by_dates(req.get('last_day'), req.get('timeframe'))
64 | return dates_sources
65 |
66 |
67 | def __csv2string(data):
68 | si = StringIO.StringIO()
69 | cw = csv.writer(si)
70 | cw.writerow(data)
71 | return si.getvalue().strip('\r\n')
72 |
73 |
74 | def __query_logging(ip, user_agent, method, date=None, source=None,
75 | asn=None, asn_details=None, compared_asns=None,
76 | ip_lookup=None, level=None):
77 | if level == 'warning':
78 | publisher.info(__csv2string([ip, user_agent, method, date,
79 | source, asn, asn_details, compared_asns, ip_lookup]))
80 | elif level == 'error':
81 | publisher.info(__csv2string([ip, user_agent, method, date,
82 | source, asn, asn_details, compared_asns, ip_lookup]))
83 | else:
84 | publisher.info(__csv2string([ip, user_agent, method, date,
85 | source, asn, asn_details, compared_asns, ip_lookup]))
86 |
87 |
88 | @app.route('/json', methods=['POST'])
89 | def __entry_point():
90 | """
91 | Function called when an query is made on /json. Expects a JSON
92 | object with at least a 'method' entry.
93 | """
94 | ip = request.remote_addr
95 | ua = request.headers.get('User-Agent', 'Empty User-Agent')
96 | method = request.json.get('method')
97 | if method is None:
98 | __query_logging(ip, ua, method, level='warning')
99 | return json.dumps({'error': 'No method provided.'})
100 | if method not in authorized_methods:
101 | # unauthorized query
102 | __query_logging(ip, ua, method, level='warning')
103 | return json.dumps({'error': 'Unauthorized method.'})
104 | fct = globals().get(method)
105 | if fct is None:
106 | # unknown method, the method is authorized, but does not exists...
107 | __query_logging(ip, ua, method, level='error')
108 | return json.dumps({'error': 'Unknown method.'})
109 | try:
110 | result = fct(request.json)
111 | __query_logging(ip, ua, method)
112 | return result
113 | except:
114 | __query_logging(ip, ua, method, level='error')
115 | return json.dumps({'error': 'Something went wrong.'})
116 |
117 |
118 | def ip_lookup(request):
119 | """See :class:`bgpranking.api.get_ip_info`"""
120 | ip = request.get('ip')
121 | if ip is None:
122 | return json.dumps({})
123 | return json.dumps(bgpranking.get_ip_info(ip, request.get('days_limit')))
124 |
125 |
126 | def all_ranks_single_asn(request):
127 | """See :class:`bgpranking.api.get_all_ranks_single_asn`"""
128 | asn = request.get('asn')
129 | if asn is None:
130 | return json.dumps({})
131 | dates_sources = __default_dates_sources(request)
132 | return json.dumps(bgpranking.get_all_ranks_single_asn(asn,
133 | dates_sources, request.get('with_details_sources')))
134 |
135 |
136 | def all_ranks_all_asns(request):
137 | """See :class:`bgpranking.api.get_all_ranks_all_asns`"""
138 | dates_sources = __default_dates_sources(request)
139 | with_details_sources = request.get('with_details_sources')
140 | return json.dumps(bgpranking.get_all_ranks_all_asns(dates_sources,
141 | with_details_sources))
142 |
143 |
144 | def block_descriptions(request):
145 | """See :class:`bgpranking.api.get_block_descriptions`"""
146 | asn = request.get('asn')
147 | block = request.get('block')
148 | if asn is None or block is None:
149 | return json.dumps([])
150 | return json.dumps(bgpranking.get_block_descriptions(asn, block))
151 |
152 |
153 | def asn_description(request):
154 | """See :class:`bgpranking.api.get_asn_descs`"""
155 | asn = request.get('asn')
156 | if asn is None:
157 | return json.dumps({})
158 | return json.dumps(bgpranking.get_asn_descs(asn,
159 | request.get('date'), request.get('sources')))
160 |
161 |
162 | def ips_description(request):
163 | """See :class:`bgpranking.api.get_ips_descs`"""
164 | asn = request.get('asn')
165 | block = request.get('block')
166 | if asn is None or block is None:
167 | return json.dumps({})
168 | return json.dumps(bgpranking.get_ips_descs(asn, block,
169 | request.get('date'), request.get('sources')))
170 |
171 |
172 | def stats(request):
173 | """See :class:`bgpranking.api.get_stats`"""
174 | return json.dumps(bgpranking.get_stats(
175 | __default_dates_sources(request)))
176 |
177 |
178 | # need cached data
179 | def cached_dates(request):
180 | """See :class:`bgpranking.api.cache_get_dates`"""
181 | return json.dumps(bgpranking.cache_get_dates())
182 |
183 |
184 | def cached_daily_rank(request):
185 | """See :class:`bgpranking.api.cache_get_daily_rank`"""
186 | asn = request.get('asn')
187 | if asn is None:
188 | return json.dumps({})
189 | cached_dates = bgpranking.cache_get_dates()
190 | date = request.get('date')
191 | if date is None or date in cached_dates:
192 | return json.dumps(bgpranking.cache_get_daily_rank(asn,
193 | request.get('sources'), date))
194 | return json.dumps({})
195 |
196 |
197 | def cached_top_asns(request):
198 | """See :class:`bgpranking.api.cache_get_top_asns`"""
199 | cached_dates = bgpranking.cache_get_dates()
200 | date = request.get('date')
201 | if date is None or date in cached_dates:
202 | return json.dumps(bgpranking.cache_get_top_asns(
203 | request.get('source'), date, request.get('limit'),
204 | request.get('with_sources')),
205 | cls=SetEncoder)
206 | return json.dumps({})
207 |
208 |
209 | def cached_position(request):
210 | """See :class:`bgpranking.api.cache_get_position`"""
211 | asn = request.get('asn')
212 | if asn is None:
213 | return json.dumps({})
214 | cached_dates = bgpranking.cache_get_dates()
215 | date = request.get('date')
216 | if date is None or date in cached_dates:
217 | return json.dumps(bgpranking.cache_get_position(asn, date=date),
218 | cls=SetEncoder)
219 | return json.dumps({})
220 |
221 | if __name__ == '__main__':
222 | app.run()
223 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/agg_consummer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | from csv import DictReader
5 | import redis
6 | import os
7 | import sys
8 |
9 | if __name__ == '__main__':
10 | r = redis.Redis(unix_socket_path='./redis_export.sock')
11 | countries = r.smembers('countries')
12 | if len(countries) == 0:
13 | print 'No country codes availables, breaking.'
14 | sys.exit()
15 | cached_asns = {}
16 | for cc in countries:
17 | cached_asns[cc] = r.smembers(cc)
18 | while True:
19 | csv_file = r.spop('known_asns')
20 | if csv_file is None:
21 | break
22 | asn = os.path.basename(csv_file)
23 | with open(csv_file, 'r') as f:
24 | reader = DictReader(f)
25 | p = r.pipeline(False)
26 | for entry in reader:
27 | p.sadd('days', entry['day'])
28 | # Removing 1 because we aggregate data
29 | rank = float(entry['rank']) - 1
30 | p.hincrbyfloat(entry['day'], 'world', rank)
31 | for cc in countries:
32 | if asn in cached_asns[cc]:
33 | p.hincrbyfloat(entry['day'], cc, rank)
34 | break
35 | p.execute()
36 |
37 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/consumer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import redis
5 | import bgpranking
6 | import os
7 | from csv import writer
8 |
9 | csv_dir = os.path.join('..', '..', 'website', 'data', 'csv')
10 |
11 | if __name__ == '__main__':
12 |
13 | r = redis.Redis(unix_socket_path='./redis_export.sock')
14 | interval = int(r.get('interval_size'))
15 | dates_sources = bgpranking.prepare_sources_by_dates(None, interval)
16 | while True:
17 | asn = r.spop('asns')
18 | if asn is None:
19 | break
20 | filename = os.path.join(csv_dir, asn)
21 | with open(filename, 'w') as csvfile:
22 | w = writer(csvfile)
23 | w.writerow(['day', 'rank'])
24 | ranks = bgpranking.get_all_ranks_single_asn(asn, dates_sources)
25 | for date, entry in ranks.iteritems():
26 | w.writerow([date, 1 + entry['total']])
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/generate_aggs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import sys
5 | import json
6 | import os
7 | import urllib2
8 | from bgpranking import tools
9 | from csv import DictWriter
10 | import datetime
11 |
12 | import argparse
13 | import glob
14 | import redis
15 |
16 |
17 | csv_dir = os.path.join('..', '..', 'website', 'data', 'csv')
18 | agg_csv_dir = os.path.join('..', '..','website', 'data', 'csv_agg')
19 | js_dir = os.path.join('..', '..', 'website', 'data', 'js')
20 |
21 | ripe_url = 'https://stat.ripe.net/data/country-resource-list/data.json?resource={cc}&time={day}'
22 |
23 | def get_announces(ripe_url):
24 | try:
25 | handle = urllib2.urlopen(ripe_url, timeout=10)
26 | except:
27 | return None
28 | json_dump = handle.read()
29 | data = json.loads(json_dump)
30 | asns = data['data']['resources']['asn']
31 | return asns
32 |
33 | if __name__ == '__main__':
34 | parser = argparse.ArgumentParser(
35 | description='Bunch of function to create aggregations')
36 | parser.add_argument('-pka', '--push_known_asns', action='store_true',
37 | default=False)
38 | parser.add_argument('-cc', '--country_codes', nargs='+', type=str)
39 | parser.add_argument('-dcc', '--dump_country_codes', nargs='+', type=str)
40 | parser.add_argument('-map', '--make_map', action='store_true', default=False)
41 | args = parser.parse_args()
42 | r = redis.Redis(unix_socket_path='./redis_export.sock')
43 |
44 | if args.push_known_asns:
45 | csv_files = glob.glob(os.path.join(csv_dir, '*'))
46 | p = r.pipeline(False)
47 | [p.sadd('known_asns', csv_file) for csv_file in csv_files]
48 | p.execute()
49 | print 'Number of asns:', len(csv_files)
50 | elif args.country_codes is not None and len(args.country_codes) > 0:
51 | p = r.pipeline(False)
52 | for cc in args.country_codes:
53 | date = datetime.date.today()
54 | counter = 0
55 | while True:
56 | if counter >= 10:
57 | sys.exit(1)
58 | cur_date = date - datetime.timedelta(days=counter)
59 | url = ripe_url.format(cc=cc, day=cur_date.isoformat())
60 | asns = get_announces(url)
61 | if asns is None or len(asns) < 5:
62 | #print 'Unable to download the list of ASNs. Abording.'
63 | counter += 1
64 | continue
65 | p.sadd(cc, *asns)
66 | break
67 | p.sadd('countries', *args.country_codes)
68 | p.execute()
69 | elif args.dump_country_codes is not None and len(args.dump_country_codes) > 0:
70 | filename = os.path.join(agg_csv_dir, '_'.join(args.dump_country_codes))
71 | with open(filename, 'w') as f:
72 | w = DictWriter(f, fieldnames= ['date'] + args.dump_country_codes)
73 | w.writeheader()
74 | for date in r.smembers('days'):
75 | entries = {'date': date}
76 | for cc in args.dump_country_codes:
77 | entries.update({cc: r.hget(date, cc)})
78 | w.writerow(entries)
79 | elif args.make_map:
80 | tools.generate_dumps_for_worldmap(js_dir, agg_csv_dir)
81 | sys.exit(0)
82 |
83 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/init_redis.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import bgpranking
5 | import redis
6 |
7 | interval_size = 800
8 |
9 | if __name__ == '__main__':
10 |
11 | dates_sources = bgpranking.prepare_sources_by_dates(None, interval_size)
12 | asns = bgpranking.existing_asns_timeframe(dates_sources)
13 |
14 | r = redis.Redis(unix_socket_path='./redis_export.sock')
15 | r.set('interval_size', interval_size)
16 | r.sadd('asns', *asns)
17 |
18 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | while true; do
4 | echo ----- New Run -----
5 | date
6 | bash ./launch_local_redis.sh
7 | echo -n 'Preparing Redis database... '
8 | python ./init_redis.py
9 | echo 'done.'
10 |
11 | echo -n 'Exporting ranks to CSV... '
12 | for i in {1..10}; do
13 | python ./consumer.py &
14 | done
15 | python ./consumer.py
16 | echo 'done.'
17 | date
18 |
19 | sleep 10
20 | redis-cli -s ./redis_export.sock flushall
21 |
22 | # ------------------------------------------------------
23 |
24 | echo -n 'Preparing database...'
25 | python ./generate_aggs.py --push_known_asns
26 |
27 | echo -n 'Generating worldmap...'
28 | python ./generate_aggs.py --make_map
29 | echo 'done.'
30 |
31 | echo -n 'Fetching ASNs by country codes... '
32 | python ./generate_aggs.py --country_codes be ch de lu fr nl
33 |
34 | if [ $? -eq 0 ]; then
35 | echo 'done.'
36 | echo -n 'Preparing aggregations...'
37 | for i in {1..10}; do
38 | python ./agg_consummer.py &
39 | done
40 | python ./agg_consummer.py
41 | echo 'done.'
42 | date
43 | sleep 10
44 |
45 | # ------------------------------------------------------
46 |
47 | echo -n 'Dumping aggregations...'
48 | python ./generate_aggs.py --dump_country_codes world lu
49 | python ./generate_aggs.py --dump_country_codes be ch de lu fr nl
50 | echo 'done.'
51 | else
52 | echo ' unable to do so.'
53 | fi
54 |
55 |
56 | redis-cli -s ./redis_export.sock shutdown
57 | echo ----- End of Run. -----
58 | sleep 10000
59 | done
60 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/launch_local_redis.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | HOME="/home/raphael/"
4 |
5 | REDIS_SERVER="${HOME}/gits/redis/src/redis-server"
6 |
7 | ${REDIS_SERVER} ./local_redis.conf
8 |
--------------------------------------------------------------------------------
/example/export/asn_ranks/local_redis.conf:
--------------------------------------------------------------------------------
1 | # Redis configuration file example
2 |
3 | # Note on units: when memory size is needed, it is possible to specify
4 | # it in the usual form of 1k 5GB 4M and so forth:
5 | #
6 | # 1k => 1000 bytes
7 | # 1kb => 1024 bytes
8 | # 1m => 1000000 bytes
9 | # 1mb => 1024*1024 bytes
10 | # 1g => 1000000000 bytes
11 | # 1gb => 1024*1024*1024 bytes
12 | #
13 | # units are case insensitive so 1GB 1Gb 1gB are all the same.
14 |
15 | # By default Redis does not run as a daemon. Use 'yes' if you need it.
16 | # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
17 | daemonize yes
18 |
19 | # When running daemonized, Redis writes a pid file in /var/run/redis.pid by
20 | # default. You can specify a custom pid file location here.
21 | pidfile ./redis_export.pid
22 |
23 | # Accept connections on the specified port, default is 6379.
24 | # If port 0 is specified Redis will not listen on a TCP socket.
25 | port 0
26 |
27 | # If you want you can bind a single interface, if the bind option is not
28 | # specified all the interfaces will listen for incoming connections.
29 | #
30 | # bind 127.0.0.1
31 |
32 | # Specify the path for the unix socket that will be used to listen for
33 | # incoming connections. There is no default, so Redis will not listen
34 | # on a unix socket when not specified.
35 | #
36 | unixsocket ./redis_export.sock
37 | unixsocketperm 755
38 |
39 | # Close the connection after a client is idle for N seconds (0 to disable)
40 | timeout 0
41 |
42 | # TCP keepalive.
43 | #
44 | # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
45 | # of communication. This is useful for two reasons:
46 | #
47 | # 1) Detect dead peers.
48 | # 2) Take the connection alive from the point of view of network
49 | # equipment in the middle.
50 | #
51 | # On Linux, the specified value (in seconds) is the period used to send ACKs.
52 | # Note that to close the connection the double of the time is needed.
53 | # On other kernels the period depends on the kernel configuration.
54 | #
55 | # A reasonable value for this option is 60 seconds.
56 | tcp-keepalive 0
57 |
58 | # Specify the server verbosity level.
59 | # This can be one of:
60 | # debug (a lot of information, useful for development/testing)
61 | # verbose (many rarely useful info, but not a mess like the debug level)
62 | # notice (moderately verbose, what you want in production probably)
63 | # warning (only very important / critical messages are logged)
64 | loglevel notice
65 |
66 | # Specify the log file name. Also 'stdout' can be used to force
67 | # Redis to log on the standard output. Note that if you use standard
68 | # output for logging but daemonize, logs will be sent to /dev/null
69 | logfile stdout
70 |
71 | # To enable logging to the system logger, just set 'syslog-enabled' to yes,
72 | # and optionally update the other syslog parameters to suit your needs.
73 | # syslog-enabled no
74 |
75 | # Specify the syslog identity.
76 | # syslog-ident redis
77 |
78 | # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
79 | # syslog-facility local0
80 |
81 | # Set the number of databases. The default database is DB 0, you can select
82 | # a different one on a per-connection basis using SELECT where
83 | # dbid is a number between 0 and 'databases'-1
84 | databases 2
85 |
86 | ################################ SNAPSHOTTING #################################
87 | #
88 | # Save the DB on disk:
89 | #
90 | # save
91 | #
92 | # Will save the DB if both the given number of seconds and the given
93 | # number of write operations against the DB occurred.
94 | #
95 | # In the example below the behaviour will be to save:
96 | # after 900 sec (15 min) if at least 1 key changed
97 | # after 300 sec (5 min) if at least 10 keys changed
98 | # after 60 sec if at least 10000 keys changed
99 | #
100 | # Note: you can disable saving at all commenting all the "save" lines.
101 | #
102 | # It is also possible to remove all the previously configured save
103 | # points by adding a save directive with a single empty string argument
104 | # like in the following example:
105 | #
106 | # save ""
107 |
108 | #save 900 1
109 | #save 300 10
110 | #save 60 10000
111 |
112 | # By default Redis will stop accepting writes if RDB snapshots are enabled
113 | # (at least one save point) and the latest background save failed.
114 | # This will make the user aware (in an hard way) that data is not persisting
115 | # on disk properly, otherwise chances are that no one will notice and some
116 | # distater will happen.
117 | #
118 | # If the background saving process will start working again Redis will
119 | # automatically allow writes again.
120 | #
121 | # However if you have setup your proper monitoring of the Redis server
122 | # and persistence, you may want to disable this feature so that Redis will
123 | # continue to work as usually even if there are problems with disk,
124 | # permissions, and so forth.
125 | stop-writes-on-bgsave-error yes
126 |
127 | # Compress string objects using LZF when dump .rdb databases?
128 | # For default that's set to 'yes' as it's almost always a win.
129 | # If you want to save some CPU in the saving child set it to 'no' but
130 | # the dataset will likely be bigger if you have compressible values or keys.
131 | rdbcompression yes
132 |
133 | # Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
134 | # This makes the format more resistant to corruption but there is a performance
135 | # hit to pay (around 10%) when saving and loading RDB files, so you can disable it
136 | # for maximum performances.
137 | #
138 | # RDB files created with checksum disabled have a checksum of zero that will
139 | # tell the loading code to skip the check.
140 | rdbchecksum yes
141 |
142 | # The filename where to dump the DB
143 | dbfilename dump_export.rdb
144 |
145 | # The working directory.
146 | #
147 | # The DB will be written inside this directory, with the filename specified
148 | # above using the 'dbfilename' configuration directive.
149 | #
150 | # The Append Only File will also be created inside this directory.
151 | #
152 | # Note that you must specify a directory here, not a file name.
153 | dir ./
154 |
155 | ################################# REPLICATION #################################
156 |
157 | # Master-Slave replication. Use slaveof to make a Redis instance a copy of
158 | # another Redis server. Note that the configuration is local to the slave
159 | # so for example it is possible to configure the slave to save the DB with a
160 | # different interval, or to listen to another port, and so on.
161 | #
162 | # slaveof
163 |
164 | # If the master is password protected (using the "requirepass" configuration
165 | # directive below) it is possible to tell the slave to authenticate before
166 | # starting the replication synchronization process, otherwise the master will
167 | # refuse the slave request.
168 | #
169 | # masterauth
170 |
171 | # When a slave loses its connection with the master, or when the replication
172 | # is still in progress, the slave can act in two different ways:
173 | #
174 | # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
175 | # still reply to client requests, possibly with out of date data, or the
176 | # data set may just be empty if this is the first synchronization.
177 | #
178 | # 2) if slave-serve-stale-data is set to 'no' the slave will reply with
179 | # an error "SYNC with master in progress" to all the kind of commands
180 | # but to INFO and SLAVEOF.
181 | #
182 | slave-serve-stale-data yes
183 |
184 | # You can configure a slave instance to accept writes or not. Writing against
185 | # a slave instance may be useful to store some ephemeral data (because data
186 | # written on a slave will be easily deleted after resync with the master) but
187 | # may also cause problems if clients are writing to it because of a
188 | # misconfiguration.
189 | #
190 | # Since Redis 2.6 by default slaves are read-only.
191 | #
192 | # Note: read only slaves are not designed to be exposed to untrusted clients
193 | # on the internet. It's just a protection layer against misuse of the instance.
194 | # Still a read only slave exports by default all the administrative commands
195 | # such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
196 | # security of read only slaves using 'rename-command' to shadow all the
197 | # administrative / dangerous commands.
198 | slave-read-only yes
199 |
200 | # Slaves send PINGs to server in a predefined interval. It's possible to change
201 | # this interval with the repl_ping_slave_period option. The default value is 10
202 | # seconds.
203 | #
204 | # repl-ping-slave-period 10
205 |
206 | # The following option sets a timeout for both Bulk transfer I/O timeout and
207 | # master data or ping response timeout. The default value is 60 seconds.
208 | #
209 | # It is important to make sure that this value is greater than the value
210 | # specified for repl-ping-slave-period otherwise a timeout will be detected
211 | # every time there is low traffic between the master and the slave.
212 | #
213 | # repl-timeout 60
214 |
215 | # Disable TCP_NODELAY on the slave socket after SYNC?
216 | #
217 | # If you select "yes" Redis will use a smaller number of TCP packets and
218 | # less bandwidth to send data to slaves. But this can add a delay for
219 | # the data to appear on the slave side, up to 40 milliseconds with
220 | # Linux kernels using a default configuration.
221 | #
222 | # If you select "no" the delay for data to appear on the slave side will
223 | # be reduced but more bandwidth will be used for replication.
224 | #
225 | # By default we optimize for low latency, but in very high traffic conditions
226 | # or when the master and slaves are many hops away, turning this to "yes" may
227 | # be a good idea.
228 | repl-disable-tcp-nodelay no
229 |
230 | # The slave priority is an integer number published by Redis in the INFO output.
231 | # It is used by Redis Sentinel in order to select a slave to promote into a
232 | # master if the master is no longer working correctly.
233 | #
234 | # A slave with a low priority number is considered better for promotion, so
235 | # for instance if there are three slaves with priority 10, 100, 25 Sentinel will
236 | # pick the one wtih priority 10, that is the lowest.
237 | #
238 | # However a special priority of 0 marks the slave as not able to perform the
239 | # role of master, so a slave with priority of 0 will never be selected by
240 | # Redis Sentinel for promotion.
241 | #
242 | # By default the priority is 100.
243 | slave-priority 100
244 |
245 | ################################## SECURITY ###################################
246 |
247 | # Require clients to issue AUTH before processing any other
248 | # commands. This might be useful in environments in which you do not trust
249 | # others with access to the host running redis-server.
250 | #
251 | # This should stay commented out for backward compatibility and because most
252 | # people do not need auth (e.g. they run their own servers).
253 | #
254 | # Warning: since Redis is pretty fast an outside user can try up to
255 | # 150k passwords per second against a good box. This means that you should
256 | # use a very strong password otherwise it will be very easy to break.
257 | #
258 | # requirepass foobared
259 |
260 | # Command renaming.
261 | #
262 | # It is possible to change the name of dangerous commands in a shared
263 | # environment. For instance the CONFIG command may be renamed into something
264 | # hard to guess so that it will still be available for internal-use tools
265 | # but not available for general clients.
266 | #
267 | # Example:
268 | #
269 | # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
270 | #
271 | # It is also possible to completely kill a command by renaming it into
272 | # an empty string:
273 | #
274 | # rename-command CONFIG ""
275 | #
276 | # Please note that changing the name of commands that are logged into the
277 | # AOF file or transmitted to slaves may cause problems.
278 |
279 | ################################### LIMITS ####################################
280 |
281 | # Set the max number of connected clients at the same time. By default
282 | # this limit is set to 10000 clients, however if the Redis server is not
283 | # able to configure the process file limit to allow for the specified limit
284 | # the max number of allowed clients is set to the current file limit
285 | # minus 32 (as Redis reserves a few file descriptors for internal uses).
286 | #
287 | # Once the limit is reached Redis will close all the new connections sending
288 | # an error 'max number of clients reached'.
289 | #
290 | # maxclients 10000
291 |
292 | # Don't use more memory than the specified amount of bytes.
293 | # When the memory limit is reached Redis will try to remove keys
294 | # accordingly to the eviction policy selected (see maxmemmory-policy).
295 | #
296 | # If Redis can't remove keys according to the policy, or if the policy is
297 | # set to 'noeviction', Redis will start to reply with errors to commands
298 | # that would use more memory, like SET, LPUSH, and so on, and will continue
299 | # to reply to read-only commands like GET.
300 | #
301 | # This option is usually useful when using Redis as an LRU cache, or to set
302 | # an hard memory limit for an instance (using the 'noeviction' policy).
303 | #
304 | # WARNING: If you have slaves attached to an instance with maxmemory on,
305 | # the size of the output buffers needed to feed the slaves are subtracted
306 | # from the used memory count, so that network problems / resyncs will
307 | # not trigger a loop where keys are evicted, and in turn the output
308 | # buffer of slaves is full with DELs of keys evicted triggering the deletion
309 | # of more keys, and so forth until the database is completely emptied.
310 | #
311 | # In short... if you have slaves attached it is suggested that you set a lower
312 | # limit for maxmemory so that there is some free RAM on the system for slave
313 | # output buffers (but this is not needed if the policy is 'noeviction').
314 | #
315 | # maxmemory
316 |
317 | # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
318 | # is reached. You can select among five behaviors:
319 | #
320 | # volatile-lru -> remove the key with an expire set using an LRU algorithm
321 | # allkeys-lru -> remove any key accordingly to the LRU algorithm
322 | # volatile-random -> remove a random key with an expire set
323 | # allkeys-random -> remove a random key, any key
324 | # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
325 | # noeviction -> don't expire at all, just return an error on write operations
326 | #
327 | # Note: with any of the above policies, Redis will return an error on write
328 | # operations, when there are not suitable keys for eviction.
329 | #
330 | # At the date of writing this commands are: set setnx setex append
331 | # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
332 | # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
333 | # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
334 | # getset mset msetnx exec sort
335 | #
336 | # The default is:
337 | #
338 | # maxmemory-policy volatile-lru
339 |
340 | # LRU and minimal TTL algorithms are not precise algorithms but approximated
341 | # algorithms (in order to save memory), so you can select as well the sample
342 | # size to check. For instance for default Redis will check three keys and
343 | # pick the one that was used less recently, you can change the sample size
344 | # using the following configuration directive.
345 | #
346 | # maxmemory-samples 3
347 |
348 | ############################## APPEND ONLY MODE ###############################
349 |
350 | # By default Redis asynchronously dumps the dataset on disk. This mode is
351 | # good enough in many applications, but an issue with the Redis process or
352 | # a power outage may result into a few minutes of writes lost (depending on
353 | # the configured save points).
354 | #
355 | # The Append Only File is an alternative persistence mode that provides
356 | # much better durability. For instance using the default data fsync policy
357 | # (see later in the config file) Redis can lose just one second of writes in a
358 | # dramatic event like a server power outage, or a single write if something
359 | # wrong with the Redis process itself happens, but the operating system is
360 | # still running correctly.
361 | #
362 | # AOF and RDB persistence can be enabled at the same time without problems.
363 | # If the AOF is enabled on startup Redis will load the AOF, that is the file
364 | # with the better durability guarantees.
365 | #
366 | # Please check http://redis.io/topics/persistence for more information.
367 |
368 | appendonly no
369 |
370 | # The name of the append only file (default: "appendonly.aof")
371 | # appendfilename appendonly.aof
372 |
373 | # The fsync() call tells the Operating System to actually write data on disk
374 | # instead to wait for more data in the output buffer. Some OS will really flush
375 | # data on disk, some other OS will just try to do it ASAP.
376 | #
377 | # Redis supports three different modes:
378 | #
379 | # no: don't fsync, just let the OS flush the data when it wants. Faster.
380 | # always: fsync after every write to the append only log . Slow, Safest.
381 | # everysec: fsync only one time every second. Compromise.
382 | #
383 | # The default is "everysec", as that's usually the right compromise between
384 | # speed and data safety. It's up to you to understand if you can relax this to
385 | # "no" that will let the operating system flush the output buffer when
386 | # it wants, for better performances (but if you can live with the idea of
387 | # some data loss consider the default persistence mode that's snapshotting),
388 | # or on the contrary, use "always" that's very slow but a bit safer than
389 | # everysec.
390 | #
391 | # More details please check the following article:
392 | # http://antirez.com/post/redis-persistence-demystified.html
393 | #
394 | # If unsure, use "everysec".
395 |
396 | # appendfsync always
397 | appendfsync everysec
398 | # appendfsync no
399 |
400 | # When the AOF fsync policy is set to always or everysec, and a background
401 | # saving process (a background save or AOF log background rewriting) is
402 | # performing a lot of I/O against the disk, in some Linux configurations
403 | # Redis may block too long on the fsync() call. Note that there is no fix for
404 | # this currently, as even performing fsync in a different thread will block
405 | # our synchronous write(2) call.
406 | #
407 | # In order to mitigate this problem it's possible to use the following option
408 | # that will prevent fsync() from being called in the main process while a
409 | # BGSAVE or BGREWRITEAOF is in progress.
410 | #
411 | # This means that while another child is saving, the durability of Redis is
412 | # the same as "appendfsync none". In practical terms, this means that it is
413 | # possible to lose up to 30 seconds of log in the worst scenario (with the
414 | # default Linux settings).
415 | #
416 | # If you have latency problems turn this to "yes". Otherwise leave it as
417 | # "no" that is the safest pick from the point of view of durability.
418 | no-appendfsync-on-rewrite no
419 |
420 | # Automatic rewrite of the append only file.
421 | # Redis is able to automatically rewrite the log file implicitly calling
422 | # BGREWRITEAOF when the AOF log size grows by the specified percentage.
423 | #
424 | # This is how it works: Redis remembers the size of the AOF file after the
425 | # latest rewrite (if no rewrite has happened since the restart, the size of
426 | # the AOF at startup is used).
427 | #
428 | # This base size is compared to the current size. If the current size is
429 | # bigger than the specified percentage, the rewrite is triggered. Also
430 | # you need to specify a minimal size for the AOF file to be rewritten, this
431 | # is useful to avoid rewriting the AOF file even if the percentage increase
432 | # is reached but it is still pretty small.
433 | #
434 | # Specify a percentage of zero in order to disable the automatic AOF
435 | # rewrite feature.
436 |
437 | auto-aof-rewrite-percentage 100
438 | auto-aof-rewrite-min-size 64mb
439 |
440 | ################################ LUA SCRIPTING ###############################
441 |
442 | # Max execution time of a Lua script in milliseconds.
443 | #
444 | # If the maximum execution time is reached Redis will log that a script is
445 | # still in execution after the maximum allowed time and will start to
446 | # reply to queries with an error.
447 | #
448 | # When a long running script exceed the maximum execution time only the
449 | # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
450 | # used to stop a script that did not yet called write commands. The second
451 | # is the only way to shut down the server in the case a write commands was
452 | # already issue by the script but the user don't want to wait for the natural
453 | # termination of the script.
454 | #
455 | # Set it to 0 or a negative value for unlimited execution without warnings.
456 | lua-time-limit 5000
457 |
458 | ################################## SLOW LOG ###################################
459 |
460 | # The Redis Slow Log is a system to log queries that exceeded a specified
461 | # execution time. The execution time does not include the I/O operations
462 | # like talking with the client, sending the reply and so forth,
463 | # but just the time needed to actually execute the command (this is the only
464 | # stage of command execution where the thread is blocked and can not serve
465 | # other requests in the meantime).
466 | #
467 | # You can configure the slow log with two parameters: one tells Redis
468 | # what is the execution time, in microseconds, to exceed in order for the
469 | # command to get logged, and the other parameter is the length of the
470 | # slow log. When a new command is logged the oldest one is removed from the
471 | # queue of logged commands.
472 |
473 | # The following time is expressed in microseconds, so 1000000 is equivalent
474 | # to one second. Note that a negative number disables the slow log, while
475 | # a value of zero forces the logging of every command.
476 | slowlog-log-slower-than 10000
477 |
478 | # There is no limit to this length. Just be aware that it will consume memory.
479 | # You can reclaim memory used by the slow log with SLOWLOG RESET.
480 | slowlog-max-len 128
481 |
482 | ############################### ADVANCED CONFIG ###############################
483 |
484 | # Hashes are encoded using a memory efficient data structure when they have a
485 | # small number of entries, and the biggest entry does not exceed a given
486 | # threshold. These thresholds can be configured using the following directives.
487 | hash-max-ziplist-entries 512
488 | hash-max-ziplist-value 64
489 |
490 | # Similarly to hashes, small lists are also encoded in a special way in order
491 | # to save a lot of space. The special representation is only used when
492 | # you are under the following limits:
493 | list-max-ziplist-entries 512
494 | list-max-ziplist-value 64
495 |
496 | # Sets have a special encoding in just one case: when a set is composed
497 | # of just strings that happens to be integers in radix 10 in the range
498 | # of 64 bit signed integers.
499 | # The following configuration setting sets the limit in the size of the
500 | # set in order to use this special memory saving encoding.
501 | set-max-intset-entries 512
502 |
503 | # Similarly to hashes and lists, sorted sets are also specially encoded in
504 | # order to save a lot of space. This encoding is only used when the length and
505 | # elements of a sorted set are below the following limits:
506 | zset-max-ziplist-entries 128
507 | zset-max-ziplist-value 64
508 |
509 | # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
510 | # order to help rehashing the main Redis hash table (the one mapping top-level
511 | # keys to values). The hash table implementation Redis uses (see dict.c)
512 | # performs a lazy rehashing: the more operation you run into an hash table
513 | # that is rehashing, the more rehashing "steps" are performed, so if the
514 | # server is idle the rehashing is never complete and some more memory is used
515 | # by the hash table.
516 | #
517 | # The default is to use this millisecond 10 times every second in order to
518 | # active rehashing the main dictionaries, freeing memory when possible.
519 | #
520 | # If unsure:
521 | # use "activerehashing no" if you have hard latency requirements and it is
522 | # not a good thing in your environment that Redis can reply form time to time
523 | # to queries with 2 milliseconds delay.
524 | #
525 | # use "activerehashing yes" if you don't have such hard requirements but
526 | # want to free memory asap when possible.
527 | activerehashing yes
528 |
529 | # The client output buffer limits can be used to force disconnection of clients
530 | # that are not reading data from the server fast enough for some reason (a
531 | # common reason is that a Pub/Sub client can't consume messages as fast as the
532 | # publisher can produce them).
533 | #
534 | # The limit can be set differently for the three different classes of clients:
535 | #
536 | # normal -> normal clients
537 | # slave -> slave clients and MONITOR clients
538 | # pubsub -> clients subcribed to at least one pubsub channel or pattern
539 | #
540 | # The syntax of every client-output-buffer-limit directive is the following:
541 | #
542 | # client-output-buffer-limit
543 | #
544 | # A client is immediately disconnected once the hard limit is reached, or if
545 | # the soft limit is reached and remains reached for the specified number of
546 | # seconds (continuously).
547 | # So for instance if the hard limit is 32 megabytes and the soft limit is
548 | # 16 megabytes / 10 seconds, the client will get disconnected immediately
549 | # if the size of the output buffers reach 32 megabytes, but will also get
550 | # disconnected if the client reaches 16 megabytes and continuously overcomes
551 | # the limit for 10 seconds.
552 | #
553 | # By default normal clients are not limited because they don't receive data
554 | # without asking (in a push way), but just after a request, so only
555 | # asynchronous clients may create a scenario where data is requested faster
556 | # than it can read.
557 | #
558 | # Instead there is a default limit for pubsub and slave clients, since
559 | # subscribers and slaves receive data in a push fashion.
560 | #
561 | # Both the hard or the soft limit can be disabled by setting them to zero.
562 | client-output-buffer-limit normal 0 0 0
563 | client-output-buffer-limit slave 256mb 64mb 60
564 | client-output-buffer-limit pubsub 32mb 8mb 60
565 |
566 | # Redis calls an internal function to perform many background tasks, like
567 | # closing connections of clients in timeot, purging expired keys that are
568 | # never requested, and so forth.
569 | #
570 | # Not all tasks are perforemd with the same frequency, but Redis checks for
571 | # tasks to perform accordingly to the specified "hz" value.
572 | #
573 | # By default "hz" is set to 10. Raising the value will use more CPU when
574 | # Redis is idle, but at the same time will make Redis more responsive when
575 | # there are many keys expiring at the same time, and timeouts may be
576 | # handled with more precision.
577 | #
578 | # The range is between 1 and 500, however a value over 100 is usually not
579 | # a good idea. Most users should use the default of 10 and raise this up to
580 | # 100 only in environments where very low latency is required.
581 | hz 10
582 |
583 | ################################## INCLUDES ###################################
584 |
585 | # Include one or more other config files here. This is useful if you
586 | # have a standard template that goes to all Redis server but also need
587 | # to customize a few per-server settings. Include files can include
588 | # other files, so use this wisely.
589 | #
590 | # include /path/to/local.conf
591 | # include /path/to/other.conf
592 |
--------------------------------------------------------------------------------
/example/export/day_ips/consumer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import redis
5 | import bgpranking
6 | import argparse
7 |
8 |
9 | if __name__ == '__main__':
10 | parser = argparse.ArgumentParser(description='Prepare the information to dump.')
11 | parser.add_argument('-f', '--full', action="store_true", default=False,
12 | help='Do a full dump (asn, block, ip, sources).')
13 | args = parser.parse_args()
14 |
15 |
16 | r = redis.Redis(unix_socket_path='./redis_export.sock')
17 | date = r.get('date')
18 | weights = bgpranking.get_all_weights(date)
19 | while True:
20 | asn_b = r.spop('asn_b')
21 | if asn_b is None:
22 | break
23 | asn, block = asn_b.split("_")
24 | ip_descs = bgpranking.get_ips_descs(asn, block, date)
25 | if len(ip_descs.get(block)) != 0:
26 | p = r.pipeline(False)
27 | for ip, sources in ip_descs.get(block).iteritems():
28 | p.zincrby('ips', ip, sum([float(weights[s]) for s in sources]))
29 | if args.full:
30 | p.hmset(ip, {'asn': asn, 'block':block,
31 | 'sources': '|'.join(sources)})
32 | p.execute()
33 |
34 |
35 |
--------------------------------------------------------------------------------
/example/export/day_ips/dates.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | START_DATE="2011-06-01"
4 | END_DATE="2013-08-01"
5 |
6 |
7 | echo $START_DATE > dates.txt
8 |
9 | DATE=$START_DATE
10 | while [ "$DATE" != "$END_DATE" ] ; do
11 | DATE=$( date +%Y-%m-%d -d "$DATE -d 1day" )
12 | echo $DATE >> dates.txt
13 | done
14 |
--------------------------------------------------------------------------------
/example/export/day_ips/dump.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import redis
5 | import csv
6 | import argparse
7 |
8 | if __name__ == '__main__':
9 | parser = argparse.ArgumentParser(description='Dump on the disk the daily informations.')
10 | parser.add_argument('-f', '--full', action="store_true", default=False,
11 | help='Do a full dump (asn, block, ip, sources).')
12 | args = parser.parse_args()
13 |
14 | r = redis.Redis(unix_socket_path='./redis_export.sock')
15 | date = r.get('date')
16 | with open('ips_' + date, 'w') as f:
17 | ips = list(r.zrange('ips', 0, -1))
18 | for i in range(len(ips)):
19 | ips[i] = "%3s.%3s.%3s.%3s" % tuple(ips[i].split("."))
20 | ips.sort()
21 | for i in range(len(ips)):
22 | ips[i] = ips[i].replace(" ", "")
23 | p = r.pipeline(False)
24 | [p.zscore('ips', ip) for ip in ips]
25 | weights = p.execute()
26 | [f.write(','.join(map(str, iw)) + '\n') for iw in zip(ips, weights)]
27 | if args.full:
28 | with open('full_' + date, 'wb') as f:
29 | w = csv.writer(f)
30 | ips = r.zrange('ips', 0, -1)
31 | p = r.pipeline(False)
32 | [p.hgetall(ip) for ip in ips]
33 | all_entries = p.execute()
34 | i = 0
35 | for entries in all_entries:
36 | w.writerow([entries['asn'], entries['block'], ips[i],
37 | entries['sources']])
38 | i += 1
39 | r.shutdown()
40 |
--------------------------------------------------------------------------------
/example/export/day_ips/init_redis.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | import bgpranking
5 | import argparse
6 |
7 | import redis
8 |
9 | if __name__ == '__main__':
10 | parser = argparse.ArgumentParser(description='Generate a list of all IPS for a day.')
11 | parser.add_argument('-d', '--date', default=None, help='Date of the dump (YYYY-MM-DD)')
12 |
13 | args = parser.parse_args()
14 | date = args.date
15 |
16 | if date is None:
17 | date = bgpranking.get_default_date()
18 |
19 | dates_sources = bgpranking.prepare_sources_by_dates(date, 1)
20 |
21 | asns = bgpranking.existing_asns_timeframe(dates_sources)
22 |
23 | r = redis.Redis(unix_socket_path='./redis_export.sock')
24 | r.set('date', date)
25 | for asn in asns:
26 | blocks = bgpranking.get_all_blocks(asn)
27 | p = r.pipeline(False)
28 | for b in blocks:
29 | p.sadd('asn_b', "{asn}_{b}".format(asn=asn, b=b))
30 | p.execute()
31 |
32 |
--------------------------------------------------------------------------------
/example/export/day_ips/launch.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | while read date ; do
4 |
5 | echo $date
6 | bash ./launch_local_redis.sh
7 | python ./init_redis.py -d $date
8 |
9 | date
10 | for i in {1..10}; do
11 | python ./consumer.py &
12 | done
13 |
14 | python ./consumer.py
15 | date
16 | sleep 10
17 |
18 | python ./dump.py
19 | date
20 | done < dates.txt
21 |
--------------------------------------------------------------------------------
/example/export/day_ips/launch_local_redis.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | HOME="/home/raphael/"
4 |
5 | REDIS_SERVER="${HOME}/gits/redis/src/redis-server"
6 |
7 | ${REDIS_SERVER} ./local_redis.conf
8 |
--------------------------------------------------------------------------------
/example/export/day_ips/local_redis.conf:
--------------------------------------------------------------------------------
1 | # Redis configuration file example
2 |
3 | # Note on units: when memory size is needed, it is possible to specify
4 | # it in the usual form of 1k 5GB 4M and so forth:
5 | #
6 | # 1k => 1000 bytes
7 | # 1kb => 1024 bytes
8 | # 1m => 1000000 bytes
9 | # 1mb => 1024*1024 bytes
10 | # 1g => 1000000000 bytes
11 | # 1gb => 1024*1024*1024 bytes
12 | #
13 | # units are case insensitive so 1GB 1Gb 1gB are all the same.
14 |
15 | # By default Redis does not run as a daemon. Use 'yes' if you need it.
16 | # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
17 | daemonize yes
18 |
19 | # When running daemonized, Redis writes a pid file in /var/run/redis.pid by
20 | # default. You can specify a custom pid file location here.
21 | pidfile ./redis_export.pid
22 |
23 | # Accept connections on the specified port, default is 6379.
24 | # If port 0 is specified Redis will not listen on a TCP socket.
25 | port 0
26 |
27 | # If you want you can bind a single interface, if the bind option is not
28 | # specified all the interfaces will listen for incoming connections.
29 | #
30 | # bind 127.0.0.1
31 |
32 | # Specify the path for the unix socket that will be used to listen for
33 | # incoming connections. There is no default, so Redis will not listen
34 | # on a unix socket when not specified.
35 | #
36 | unixsocket ./redis_export.sock
37 | unixsocketperm 755
38 |
39 | # Close the connection after a client is idle for N seconds (0 to disable)
40 | timeout 0
41 |
42 | # TCP keepalive.
43 | #
44 | # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
45 | # of communication. This is useful for two reasons:
46 | #
47 | # 1) Detect dead peers.
48 | # 2) Take the connection alive from the point of view of network
49 | # equipment in the middle.
50 | #
51 | # On Linux, the specified value (in seconds) is the period used to send ACKs.
52 | # Note that to close the connection the double of the time is needed.
53 | # On other kernels the period depends on the kernel configuration.
54 | #
55 | # A reasonable value for this option is 60 seconds.
56 | tcp-keepalive 0
57 |
58 | # Specify the server verbosity level.
59 | # This can be one of:
60 | # debug (a lot of information, useful for development/testing)
61 | # verbose (many rarely useful info, but not a mess like the debug level)
62 | # notice (moderately verbose, what you want in production probably)
63 | # warning (only very important / critical messages are logged)
64 | loglevel notice
65 |
66 | # Specify the log file name. Also 'stdout' can be used to force
67 | # Redis to log on the standard output. Note that if you use standard
68 | # output for logging but daemonize, logs will be sent to /dev/null
69 | logfile stdout
70 |
71 | # To enable logging to the system logger, just set 'syslog-enabled' to yes,
72 | # and optionally update the other syslog parameters to suit your needs.
73 | # syslog-enabled no
74 |
75 | # Specify the syslog identity.
76 | # syslog-ident redis
77 |
78 | # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
79 | # syslog-facility local0
80 |
81 | # Set the number of databases. The default database is DB 0, you can select
82 | # a different one on a per-connection basis using SELECT where
83 | # dbid is a number between 0 and 'databases'-1
84 | databases 2
85 |
86 | ################################ SNAPSHOTTING #################################
87 | #
88 | # Save the DB on disk:
89 | #
90 | # save
91 | #
92 | # Will save the DB if both the given number of seconds and the given
93 | # number of write operations against the DB occurred.
94 | #
95 | # In the example below the behaviour will be to save:
96 | # after 900 sec (15 min) if at least 1 key changed
97 | # after 300 sec (5 min) if at least 10 keys changed
98 | # after 60 sec if at least 10000 keys changed
99 | #
100 | # Note: you can disable saving at all commenting all the "save" lines.
101 | #
102 | # It is also possible to remove all the previously configured save
103 | # points by adding a save directive with a single empty string argument
104 | # like in the following example:
105 | #
106 | # save ""
107 |
108 | #save 900 1
109 | #save 300 10
110 | #save 60 10000
111 |
112 | # By default Redis will stop accepting writes if RDB snapshots are enabled
113 | # (at least one save point) and the latest background save failed.
114 | # This will make the user aware (in an hard way) that data is not persisting
115 | # on disk properly, otherwise chances are that no one will notice and some
116 | # distater will happen.
117 | #
118 | # If the background saving process will start working again Redis will
119 | # automatically allow writes again.
120 | #
121 | # However if you have setup your proper monitoring of the Redis server
122 | # and persistence, you may want to disable this feature so that Redis will
123 | # continue to work as usually even if there are problems with disk,
124 | # permissions, and so forth.
125 | stop-writes-on-bgsave-error yes
126 |
127 | # Compress string objects using LZF when dump .rdb databases?
128 | # For default that's set to 'yes' as it's almost always a win.
129 | # If you want to save some CPU in the saving child set it to 'no' but
130 | # the dataset will likely be bigger if you have compressible values or keys.
131 | rdbcompression yes
132 |
133 | # Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
134 | # This makes the format more resistant to corruption but there is a performance
135 | # hit to pay (around 10%) when saving and loading RDB files, so you can disable it
136 | # for maximum performances.
137 | #
138 | # RDB files created with checksum disabled have a checksum of zero that will
139 | # tell the loading code to skip the check.
140 | rdbchecksum yes
141 |
142 | # The filename where to dump the DB
143 | dbfilename dump_export.rdb
144 |
145 | # The working directory.
146 | #
147 | # The DB will be written inside this directory, with the filename specified
148 | # above using the 'dbfilename' configuration directive.
149 | #
150 | # The Append Only File will also be created inside this directory.
151 | #
152 | # Note that you must specify a directory here, not a file name.
153 | dir ./
154 |
155 | ################################# REPLICATION #################################
156 |
157 | # Master-Slave replication. Use slaveof to make a Redis instance a copy of
158 | # another Redis server. Note that the configuration is local to the slave
159 | # so for example it is possible to configure the slave to save the DB with a
160 | # different interval, or to listen to another port, and so on.
161 | #
162 | # slaveof
163 |
164 | # If the master is password protected (using the "requirepass" configuration
165 | # directive below) it is possible to tell the slave to authenticate before
166 | # starting the replication synchronization process, otherwise the master will
167 | # refuse the slave request.
168 | #
169 | # masterauth
170 |
171 | # When a slave loses its connection with the master, or when the replication
172 | # is still in progress, the slave can act in two different ways:
173 | #
174 | # 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
175 | # still reply to client requests, possibly with out of date data, or the
176 | # data set may just be empty if this is the first synchronization.
177 | #
178 | # 2) if slave-serve-stale-data is set to 'no' the slave will reply with
179 | # an error "SYNC with master in progress" to all the kind of commands
180 | # but to INFO and SLAVEOF.
181 | #
182 | slave-serve-stale-data yes
183 |
184 | # You can configure a slave instance to accept writes or not. Writing against
185 | # a slave instance may be useful to store some ephemeral data (because data
186 | # written on a slave will be easily deleted after resync with the master) but
187 | # may also cause problems if clients are writing to it because of a
188 | # misconfiguration.
189 | #
190 | # Since Redis 2.6 by default slaves are read-only.
191 | #
192 | # Note: read only slaves are not designed to be exposed to untrusted clients
193 | # on the internet. It's just a protection layer against misuse of the instance.
194 | # Still a read only slave exports by default all the administrative commands
195 | # such as CONFIG, DEBUG, and so forth. To a limited extend you can improve
196 | # security of read only slaves using 'rename-command' to shadow all the
197 | # administrative / dangerous commands.
198 | slave-read-only yes
199 |
200 | # Slaves send PINGs to server in a predefined interval. It's possible to change
201 | # this interval with the repl_ping_slave_period option. The default value is 10
202 | # seconds.
203 | #
204 | # repl-ping-slave-period 10
205 |
206 | # The following option sets a timeout for both Bulk transfer I/O timeout and
207 | # master data or ping response timeout. The default value is 60 seconds.
208 | #
209 | # It is important to make sure that this value is greater than the value
210 | # specified for repl-ping-slave-period otherwise a timeout will be detected
211 | # every time there is low traffic between the master and the slave.
212 | #
213 | # repl-timeout 60
214 |
215 | # Disable TCP_NODELAY on the slave socket after SYNC?
216 | #
217 | # If you select "yes" Redis will use a smaller number of TCP packets and
218 | # less bandwidth to send data to slaves. But this can add a delay for
219 | # the data to appear on the slave side, up to 40 milliseconds with
220 | # Linux kernels using a default configuration.
221 | #
222 | # If you select "no" the delay for data to appear on the slave side will
223 | # be reduced but more bandwidth will be used for replication.
224 | #
225 | # By default we optimize for low latency, but in very high traffic conditions
226 | # or when the master and slaves are many hops away, turning this to "yes" may
227 | # be a good idea.
228 | repl-disable-tcp-nodelay no
229 |
230 | # The slave priority is an integer number published by Redis in the INFO output.
231 | # It is used by Redis Sentinel in order to select a slave to promote into a
232 | # master if the master is no longer working correctly.
233 | #
234 | # A slave with a low priority number is considered better for promotion, so
235 | # for instance if there are three slaves with priority 10, 100, 25 Sentinel will
236 | # pick the one wtih priority 10, that is the lowest.
237 | #
238 | # However a special priority of 0 marks the slave as not able to perform the
239 | # role of master, so a slave with priority of 0 will never be selected by
240 | # Redis Sentinel for promotion.
241 | #
242 | # By default the priority is 100.
243 | slave-priority 100
244 |
245 | ################################## SECURITY ###################################
246 |
247 | # Require clients to issue AUTH before processing any other
248 | # commands. This might be useful in environments in which you do not trust
249 | # others with access to the host running redis-server.
250 | #
251 | # This should stay commented out for backward compatibility and because most
252 | # people do not need auth (e.g. they run their own servers).
253 | #
254 | # Warning: since Redis is pretty fast an outside user can try up to
255 | # 150k passwords per second against a good box. This means that you should
256 | # use a very strong password otherwise it will be very easy to break.
257 | #
258 | # requirepass foobared
259 |
260 | # Command renaming.
261 | #
262 | # It is possible to change the name of dangerous commands in a shared
263 | # environment. For instance the CONFIG command may be renamed into something
264 | # hard to guess so that it will still be available for internal-use tools
265 | # but not available for general clients.
266 | #
267 | # Example:
268 | #
269 | # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
270 | #
271 | # It is also possible to completely kill a command by renaming it into
272 | # an empty string:
273 | #
274 | # rename-command CONFIG ""
275 | #
276 | # Please note that changing the name of commands that are logged into the
277 | # AOF file or transmitted to slaves may cause problems.
278 |
279 | ################################### LIMITS ####################################
280 |
281 | # Set the max number of connected clients at the same time. By default
282 | # this limit is set to 10000 clients, however if the Redis server is not
283 | # able to configure the process file limit to allow for the specified limit
284 | # the max number of allowed clients is set to the current file limit
285 | # minus 32 (as Redis reserves a few file descriptors for internal uses).
286 | #
287 | # Once the limit is reached Redis will close all the new connections sending
288 | # an error 'max number of clients reached'.
289 | #
290 | # maxclients 10000
291 |
292 | # Don't use more memory than the specified amount of bytes.
293 | # When the memory limit is reached Redis will try to remove keys
294 | # accordingly to the eviction policy selected (see maxmemmory-policy).
295 | #
296 | # If Redis can't remove keys according to the policy, or if the policy is
297 | # set to 'noeviction', Redis will start to reply with errors to commands
298 | # that would use more memory, like SET, LPUSH, and so on, and will continue
299 | # to reply to read-only commands like GET.
300 | #
301 | # This option is usually useful when using Redis as an LRU cache, or to set
302 | # an hard memory limit for an instance (using the 'noeviction' policy).
303 | #
304 | # WARNING: If you have slaves attached to an instance with maxmemory on,
305 | # the size of the output buffers needed to feed the slaves are subtracted
306 | # from the used memory count, so that network problems / resyncs will
307 | # not trigger a loop where keys are evicted, and in turn the output
308 | # buffer of slaves is full with DELs of keys evicted triggering the deletion
309 | # of more keys, and so forth until the database is completely emptied.
310 | #
311 | # In short... if you have slaves attached it is suggested that you set a lower
312 | # limit for maxmemory so that there is some free RAM on the system for slave
313 | # output buffers (but this is not needed if the policy is 'noeviction').
314 | #
315 | # maxmemory
316 |
317 | # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
318 | # is reached. You can select among five behaviors:
319 | #
320 | # volatile-lru -> remove the key with an expire set using an LRU algorithm
321 | # allkeys-lru -> remove any key accordingly to the LRU algorithm
322 | # volatile-random -> remove a random key with an expire set
323 | # allkeys-random -> remove a random key, any key
324 | # volatile-ttl -> remove the key with the nearest expire time (minor TTL)
325 | # noeviction -> don't expire at all, just return an error on write operations
326 | #
327 | # Note: with any of the above policies, Redis will return an error on write
328 | # operations, when there are not suitable keys for eviction.
329 | #
330 | # At the date of writing this commands are: set setnx setex append
331 | # incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
332 | # sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
333 | # zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
334 | # getset mset msetnx exec sort
335 | #
336 | # The default is:
337 | #
338 | # maxmemory-policy volatile-lru
339 |
340 | # LRU and minimal TTL algorithms are not precise algorithms but approximated
341 | # algorithms (in order to save memory), so you can select as well the sample
342 | # size to check. For instance for default Redis will check three keys and
343 | # pick the one that was used less recently, you can change the sample size
344 | # using the following configuration directive.
345 | #
346 | # maxmemory-samples 3
347 |
348 | ############################## APPEND ONLY MODE ###############################
349 |
350 | # By default Redis asynchronously dumps the dataset on disk. This mode is
351 | # good enough in many applications, but an issue with the Redis process or
352 | # a power outage may result into a few minutes of writes lost (depending on
353 | # the configured save points).
354 | #
355 | # The Append Only File is an alternative persistence mode that provides
356 | # much better durability. For instance using the default data fsync policy
357 | # (see later in the config file) Redis can lose just one second of writes in a
358 | # dramatic event like a server power outage, or a single write if something
359 | # wrong with the Redis process itself happens, but the operating system is
360 | # still running correctly.
361 | #
362 | # AOF and RDB persistence can be enabled at the same time without problems.
363 | # If the AOF is enabled on startup Redis will load the AOF, that is the file
364 | # with the better durability guarantees.
365 | #
366 | # Please check http://redis.io/topics/persistence for more information.
367 |
368 | appendonly no
369 |
370 | # The name of the append only file (default: "appendonly.aof")
371 | # appendfilename appendonly.aof
372 |
373 | # The fsync() call tells the Operating System to actually write data on disk
374 | # instead to wait for more data in the output buffer. Some OS will really flush
375 | # data on disk, some other OS will just try to do it ASAP.
376 | #
377 | # Redis supports three different modes:
378 | #
379 | # no: don't fsync, just let the OS flush the data when it wants. Faster.
380 | # always: fsync after every write to the append only log . Slow, Safest.
381 | # everysec: fsync only one time every second. Compromise.
382 | #
383 | # The default is "everysec", as that's usually the right compromise between
384 | # speed and data safety. It's up to you to understand if you can relax this to
385 | # "no" that will let the operating system flush the output buffer when
386 | # it wants, for better performances (but if you can live with the idea of
387 | # some data loss consider the default persistence mode that's snapshotting),
388 | # or on the contrary, use "always" that's very slow but a bit safer than
389 | # everysec.
390 | #
391 | # More details please check the following article:
392 | # http://antirez.com/post/redis-persistence-demystified.html
393 | #
394 | # If unsure, use "everysec".
395 |
396 | # appendfsync always
397 | appendfsync everysec
398 | # appendfsync no
399 |
400 | # When the AOF fsync policy is set to always or everysec, and a background
401 | # saving process (a background save or AOF log background rewriting) is
402 | # performing a lot of I/O against the disk, in some Linux configurations
403 | # Redis may block too long on the fsync() call. Note that there is no fix for
404 | # this currently, as even performing fsync in a different thread will block
405 | # our synchronous write(2) call.
406 | #
407 | # In order to mitigate this problem it's possible to use the following option
408 | # that will prevent fsync() from being called in the main process while a
409 | # BGSAVE or BGREWRITEAOF is in progress.
410 | #
411 | # This means that while another child is saving, the durability of Redis is
412 | # the same as "appendfsync none". In practical terms, this means that it is
413 | # possible to lose up to 30 seconds of log in the worst scenario (with the
414 | # default Linux settings).
415 | #
416 | # If you have latency problems turn this to "yes". Otherwise leave it as
417 | # "no" that is the safest pick from the point of view of durability.
418 | no-appendfsync-on-rewrite no
419 |
420 | # Automatic rewrite of the append only file.
421 | # Redis is able to automatically rewrite the log file implicitly calling
422 | # BGREWRITEAOF when the AOF log size grows by the specified percentage.
423 | #
424 | # This is how it works: Redis remembers the size of the AOF file after the
425 | # latest rewrite (if no rewrite has happened since the restart, the size of
426 | # the AOF at startup is used).
427 | #
428 | # This base size is compared to the current size. If the current size is
429 | # bigger than the specified percentage, the rewrite is triggered. Also
430 | # you need to specify a minimal size for the AOF file to be rewritten, this
431 | # is useful to avoid rewriting the AOF file even if the percentage increase
432 | # is reached but it is still pretty small.
433 | #
434 | # Specify a percentage of zero in order to disable the automatic AOF
435 | # rewrite feature.
436 |
437 | auto-aof-rewrite-percentage 100
438 | auto-aof-rewrite-min-size 64mb
439 |
440 | ################################ LUA SCRIPTING ###############################
441 |
442 | # Max execution time of a Lua script in milliseconds.
443 | #
444 | # If the maximum execution time is reached Redis will log that a script is
445 | # still in execution after the maximum allowed time and will start to
446 | # reply to queries with an error.
447 | #
448 | # When a long running script exceed the maximum execution time only the
449 | # SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
450 | # used to stop a script that did not yet called write commands. The second
451 | # is the only way to shut down the server in the case a write commands was
452 | # already issue by the script but the user don't want to wait for the natural
453 | # termination of the script.
454 | #
455 | # Set it to 0 or a negative value for unlimited execution without warnings.
456 | lua-time-limit 5000
457 |
458 | ################################## SLOW LOG ###################################
459 |
460 | # The Redis Slow Log is a system to log queries that exceeded a specified
461 | # execution time. The execution time does not include the I/O operations
462 | # like talking with the client, sending the reply and so forth,
463 | # but just the time needed to actually execute the command (this is the only
464 | # stage of command execution where the thread is blocked and can not serve
465 | # other requests in the meantime).
466 | #
467 | # You can configure the slow log with two parameters: one tells Redis
468 | # what is the execution time, in microseconds, to exceed in order for the
469 | # command to get logged, and the other parameter is the length of the
470 | # slow log. When a new command is logged the oldest one is removed from the
471 | # queue of logged commands.
472 |
473 | # The following time is expressed in microseconds, so 1000000 is equivalent
474 | # to one second. Note that a negative number disables the slow log, while
475 | # a value of zero forces the logging of every command.
476 | slowlog-log-slower-than 10000
477 |
478 | # There is no limit to this length. Just be aware that it will consume memory.
479 | # You can reclaim memory used by the slow log with SLOWLOG RESET.
480 | slowlog-max-len 128
481 |
482 | ############################### ADVANCED CONFIG ###############################
483 |
484 | # Hashes are encoded using a memory efficient data structure when they have a
485 | # small number of entries, and the biggest entry does not exceed a given
486 | # threshold. These thresholds can be configured using the following directives.
487 | hash-max-ziplist-entries 512
488 | hash-max-ziplist-value 64
489 |
490 | # Similarly to hashes, small lists are also encoded in a special way in order
491 | # to save a lot of space. The special representation is only used when
492 | # you are under the following limits:
493 | list-max-ziplist-entries 512
494 | list-max-ziplist-value 64
495 |
496 | # Sets have a special encoding in just one case: when a set is composed
497 | # of just strings that happens to be integers in radix 10 in the range
498 | # of 64 bit signed integers.
499 | # The following configuration setting sets the limit in the size of the
500 | # set in order to use this special memory saving encoding.
501 | set-max-intset-entries 512
502 |
503 | # Similarly to hashes and lists, sorted sets are also specially encoded in
504 | # order to save a lot of space. This encoding is only used when the length and
505 | # elements of a sorted set are below the following limits:
506 | zset-max-ziplist-entries 128
507 | zset-max-ziplist-value 64
508 |
509 | # Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
510 | # order to help rehashing the main Redis hash table (the one mapping top-level
511 | # keys to values). The hash table implementation Redis uses (see dict.c)
512 | # performs a lazy rehashing: the more operation you run into an hash table
513 | # that is rehashing, the more rehashing "steps" are performed, so if the
514 | # server is idle the rehashing is never complete and some more memory is used
515 | # by the hash table.
516 | #
517 | # The default is to use this millisecond 10 times every second in order to
518 | # active rehashing the main dictionaries, freeing memory when possible.
519 | #
520 | # If unsure:
521 | # use "activerehashing no" if you have hard latency requirements and it is
522 | # not a good thing in your environment that Redis can reply form time to time
523 | # to queries with 2 milliseconds delay.
524 | #
525 | # use "activerehashing yes" if you don't have such hard requirements but
526 | # want to free memory asap when possible.
527 | activerehashing yes
528 |
529 | # The client output buffer limits can be used to force disconnection of clients
530 | # that are not reading data from the server fast enough for some reason (a
531 | # common reason is that a Pub/Sub client can't consume messages as fast as the
532 | # publisher can produce them).
533 | #
534 | # The limit can be set differently for the three different classes of clients:
535 | #
536 | # normal -> normal clients
537 | # slave -> slave clients and MONITOR clients
538 | # pubsub -> clients subcribed to at least one pubsub channel or pattern
539 | #
540 | # The syntax of every client-output-buffer-limit directive is the following:
541 | #
542 | # client-output-buffer-limit
543 | #
544 | # A client is immediately disconnected once the hard limit is reached, or if
545 | # the soft limit is reached and remains reached for the specified number of
546 | # seconds (continuously).
547 | # So for instance if the hard limit is 32 megabytes and the soft limit is
548 | # 16 megabytes / 10 seconds, the client will get disconnected immediately
549 | # if the size of the output buffers reach 32 megabytes, but will also get
550 | # disconnected if the client reaches 16 megabytes and continuously overcomes
551 | # the limit for 10 seconds.
552 | #
553 | # By default normal clients are not limited because they don't receive data
554 | # without asking (in a push way), but just after a request, so only
555 | # asynchronous clients may create a scenario where data is requested faster
556 | # than it can read.
557 | #
558 | # Instead there is a default limit for pubsub and slave clients, since
559 | # subscribers and slaves receive data in a push fashion.
560 | #
561 | # Both the hard or the soft limit can be disabled by setting them to zero.
562 | client-output-buffer-limit normal 0 0 0
563 | client-output-buffer-limit slave 256mb 64mb 60
564 | client-output-buffer-limit pubsub 32mb 8mb 60
565 |
566 | # Redis calls an internal function to perform many background tasks, like
567 | # closing connections of clients in timeot, purging expired keys that are
568 | # never requested, and so forth.
569 | #
570 | # Not all tasks are perforemd with the same frequency, but Redis checks for
571 | # tasks to perform accordingly to the specified "hz" value.
572 | #
573 | # By default "hz" is set to 10. Raising the value will use more CPU when
574 | # Redis is idle, but at the same time will make Redis more responsive when
575 | # there are many keys expiring at the same time, and timeouts may be
576 | # handled with more precision.
577 | #
578 | # The range is between 1 and 500, however a value over 100 is usually not
579 | # a good idea. Most users should use the default of 10 and raise this up to
580 | # 100 only in environments where very low latency is required.
581 | hz 10
582 |
583 | ################################## INCLUDES ###################################
584 |
585 | # Include one or more other config files here. This is useful if you
586 | # have a standard template that goes to all Redis server but also need
587 | # to customize a few per-server settings. Include files can include
588 | # other files, so use this wisely.
589 | #
590 | # include /path/to/local.conf
591 | # include /path/to/other.conf
592 |
--------------------------------------------------------------------------------
/example/ip_zmq/client.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import zmq
4 |
5 | ip = '127.0.0.1'
6 | port = '5556'
7 |
8 | zmq_socket = None
9 |
10 |
11 | def __prepare():
12 | global zmq_socket
13 | context = zmq.Context()
14 | zmq_socket = context.socket(zmq.SUB)
15 | zmq_socket.connect('tcp://{}:{}'.format(ip, port))
16 | zmq_socket.setsockopt(zmq.SUBSCRIBE, '')
17 |
18 | def run():
19 | if zmq_socket is None:
20 | __prepare()
21 | while True:
22 | msg = zmq_socket.recv()
23 | yield msg
24 |
25 | if __name__ == '__main__':
26 | for a in run():
27 | print a
28 |
29 |
--------------------------------------------------------------------------------
/example/logging/logs/.keepdir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/logging/logs/.keepdir
--------------------------------------------------------------------------------
/example/logging/mail.conf:
--------------------------------------------------------------------------------
1 | [mail]
2 | dest_mail = foo@bar.com,bar@foo.de
3 | smtp_server = smtp.example.com
4 | smtp_port = 25
5 | src_server = service.foo.fr
6 |
--------------------------------------------------------------------------------
/example/logging/start_logging.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | log_subscriber --mail ./mail.conf --channel Website --log_path ./logs/ &
4 | log_subscriber --mail ./mail.conf --channel API_Twitter --log_path ./logs/ &
5 | log_subscriber --mail ./mail.conf --channel API_Redis --log_path ./logs/ &
6 | log_subscriber --mail ./mail.conf --channel API_Web --log_path ./logs/ &
7 |
--------------------------------------------------------------------------------
/example/twitter_bot/microblog/__init__.py:
--------------------------------------------------------------------------------
1 | import api_wrapper
2 | api_wrapper.__prepare()
3 |
4 | from api_wrapper import *
5 |
6 |
--------------------------------------------------------------------------------
/example/twitter_bot/microblog/api_wrapper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | """
6 | Micro-blog
7 | ~~~~~~~~~~
8 |
9 | Microblog client for twitter
10 |
11 | """
12 |
13 | import twitter
14 | import datetime
15 | import dateutil
16 |
17 | from micro_blog_keys import twitter_consumer_key,twitter_consumer_secret,\
18 | twitter_access_key,twitter_access_secret
19 | import bgpranking
20 |
21 | api = None
22 | username = "bgpranking"
23 |
24 | def __prepare():
25 | global api
26 | api = twitter.Api(consumer_key=twitter_consumer_key,
27 | consumer_secret=twitter_consumer_secret,
28 | access_token_key=twitter_access_key,
29 | access_token_secret=twitter_access_secret)
30 |
31 | def prepare_string():
32 | to_return = 'Top Ranking {date}\n'.format(
33 | date=datetime.date.today().isoformat())
34 | top = bgpranking.cache_get_top_asns(limit=5, with_sources=False)
35 | for asn, descr, rank in top['top_list']:
36 | rank = round(1+rank, 4)
37 | to_return += '{asn}: {rank}\n'.format(asn=asn, rank=rank)
38 | to_return += 'http://bgpranking.circl.lu'
39 | return to_return
40 |
41 | def post_new_top_ranking():
42 | posted = False
43 | today = datetime.date.today()
44 | status = api.GetUserTimeline("bgpranking", count=100)
45 | for s in status:
46 | t = s.text
47 | if t is not None and t.startswith('Top Ranking'):
48 | most_recent_post = dateutil.parser.parse(
49 | s.created_at).replace(tzinfo=None).date()
50 | if most_recent_post < today:
51 | posted = True
52 | to_post = prepare_string()
53 | api.PostUpdate(to_post)
54 | break
55 | return posted
56 |
--------------------------------------------------------------------------------
/example/twitter_bot/microblog/python_stream_api.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 |
5 | """
6 | Micro-blog
7 | ~~~~~~~~~~
8 |
9 | Microblog client for twitter
10 |
11 | """
12 |
13 | import tweepy
14 | import IPy
15 |
16 | from micro_blog_keys import *
17 | import bgpranking
18 |
19 | api = None
20 | auth = None
21 | base_asn_address = 'http://bgpranking.circl.lu/asn_details?asn='
22 |
23 | def __prepare():
24 | global api
25 | global auth
26 | auth = tweepy.OAuthHandler(twitter_consumer_key,
27 | twitter_consumer_secret)
28 | auth.set_access_token(twitter_access_key, twitter_access_secret)
29 | api = tweepy.API(auth)
30 |
31 | class CustomStreamListener(tweepy.StreamListener):
32 | def on_status(self, status):
33 | if status.text.startswith('@bgpranking AS'):
34 | asn = status.text.strip('@bgpranking AS')
35 | try:
36 | int(asn)
37 | except:
38 | print status.text
39 | return
40 | msg_id = status.id
41 | sender = status.user.screen_name
42 | dates_sources = bgpranking.prepare_sources_by_dates(timeframe=5)
43 | ranks = bgpranking.get_all_ranks_single_asn(asn, dates_sources)
44 | to_post = '@{user}: {asn}\n'.format(user=sender, asn=asn)
45 | dates = sorted(ranks.keys(), reverse=True)
46 | for date in dates:
47 | info = ranks[date]
48 | to_post += '{date}: {rank}\n'.format(date=date,rank=round(1+info['total'], 4))
49 | to_post += base_asn_address + asn
50 | api.update_status(to_post, msg_id)
51 | elif status.text.startswith('@bgpranking IP'):
52 | ip = status.text.strip('@bgpranking IP')
53 | try:
54 | IPy.IP(ip)
55 | except:
56 | print status.text
57 | return
58 | msg_id = status.id
59 | sender = status.user.screen_name
60 | info = bgpranking.get_ip_info(ip, 10)
61 | to_post_short = '@{user}: {ip}: http://bgpranking.circl.lu/ip_lookup?ip={ip}'.format(user=sender, ip=ip)
62 | if len(info['history']) > 0:
63 | latest_data = info['history'][0]
64 | template = '\n{asn} - {block}: {base_asn_url}{asn};ip_details={block}\n{descr}'
65 | descr = latest_data['descriptions'][0][1]
66 | if len(descr) > 40:
67 | descr = 'Too Long for Twitter.'
68 | to_post = to_post_short + template.format(asn=latest_data['asn'],
69 | base_asn_url=base_asn_address,
70 | block=latest_data['block'], descr=descr)
71 | try:
72 | api.update_status(to_post, msg_id)
73 | except:
74 | api.update_status(to_post_short, msg_id)
75 |
76 | else:
77 | print status.text
78 |
79 | def on_error(self, status_code):
80 | print >> sys.stderr, 'Encountered error with status code:', status_code
81 | return True # Don't kill the stream
82 |
83 | def on_timeout(self):
84 | print >> sys.stderr, 'Timeout...'
85 | return True # Don't kill the stream
86 |
87 |
88 | def stream_mentions():
89 | options = {'secure':True}
90 | listener = CustomStreamListener()
91 | s = tweepy.streaming.Stream(auth, listener, **options)
92 | s.userstream()
93 |
94 | if __name__ == '__main__':
95 | __prepare()
96 | stream_mentions()
97 |
--------------------------------------------------------------------------------
/example/twitter_bot/start_bot.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | """
5 | :file:`bin/services/microblog.py` - Microblogging client
6 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7 |
8 | Start the microblogging client which posts on twitter and identica
9 | """
10 |
11 | import time
12 | from pubsublogger import publisher
13 | import microblog
14 |
15 | dev_mode = True
16 |
17 | if __name__ == '__main__':
18 |
19 | sleep_timer = 3600
20 |
21 | publisher.channel = 'API_Twitter'
22 |
23 | while 1:
24 | try:
25 | if microblog.post_new_top_ranking():
26 | publisher.info('New Ranking posted on twitter and identica.')
27 | print 'New Ranking posted on twitter and identica.'
28 | except Exception as e:
29 | publisher.error("Something bad occurs: " + e)
30 | print "Something bad occurs: " + str(e)
31 | time.sleep(sleep_timer)
32 |
--------------------------------------------------------------------------------
/example/website/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/example/website/compil.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | cheetah compile -R
4 |
--------------------------------------------------------------------------------
/example/website/config/web_bgp-ranking.ini:
--------------------------------------------------------------------------------
1 | [global]
2 | log.error_file = "error.log"
3 | log.access_file = "access.log"
4 | server.environment = "production"
5 | server.socket_host = '127.0.0.1'
6 | server.socket_port = 8085
7 | engine.autoreload_on = True
8 | engine.autoreload_frequency = 5
9 | tools.proxy.on = True
10 |
11 | [/]
12 | tools.staticdir.root = os.getcwd()
13 |
14 | [/csv]
15 | tools.staticdir.on = True
16 | tools.staticdir.dir = "data/csv/"
17 |
18 | [/csv_agg]
19 | tools.staticdir.on = True
20 | tools.staticdir.dir = "data/csv_agg/"
21 |
22 | [/js_data]
23 | tools.staticdir.on = True
24 | tools.staticdir.dir = "data/js/"
25 |
26 | [/js]
27 | tools.staticdir.on = True
28 | tools.staticdir.dir = "js/"
29 |
30 | [/dygraph]
31 | tools.staticdir.on = True
32 | tools.staticdir.dir = "thirdparty/dygraph/"
33 |
34 | [/jvectormap]
35 | tools.staticdir.on = True
36 | tools.staticdir.dir = "thirdparty/jvectormap/"
37 |
38 |
--------------------------------------------------------------------------------
/example/website/data/csv/.is_csv_dir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/website/data/csv/.is_csv_dir
--------------------------------------------------------------------------------
/example/website/data/csv_agg/.keepdir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/website/data/csv_agg/.keepdir
--------------------------------------------------------------------------------
/example/website/data/js/.keepdir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/website/data/js/.keepdir
--------------------------------------------------------------------------------
/example/website/js/world_benelux.js:
--------------------------------------------------------------------------------
1 | g = new Dygraph(
2 | document.getElementById("graphdiv1"),
3 | "csv_agg/be_ch_de_lu_fr_nl",
4 | {
5 | axes: {
6 | y: {
7 | valueFormatter: function(y) {
8 | return y.toPrecision(8);
9 | },
10 | axisLabelFormatter: function(y) {
11 | return y.toPrecision(5);
12 | },
13 | }
14 | },
15 | legend: 'always',
16 | rollPeriod: 7,
17 | showRoller: true,
18 | logscale : true,
19 | yAxisLabelWidth: 50,
20 | xAxisLabelWidth: 60
21 | }
22 | );
23 |
24 |
--------------------------------------------------------------------------------
/example/website/js/world_luxembourg.js:
--------------------------------------------------------------------------------
1 | g = new Dygraph(
2 | document.getElementById("graphdiv1"),
3 | "csv_agg/world_lu",
4 | {
5 | labels: ['date', 'World', 'Luxembourg'],
6 | 'Luxembourg': { axis: {} },
7 | axes: {
8 | y:{
9 | },
10 | y2: {
11 | valueFormatter: function(y) {
12 | return y.toPrecision(8);
13 | },
14 | axisLabelFormatter: function(y) {
15 | return y.toPrecision(5);
16 | },
17 | }
18 | },
19 | legend: 'always',
20 | fillGraph: true,
21 | rollPeriod: 7,
22 | showRoller: true,
23 | logscale : true,
24 | ylabel: 'World Trend',
25 | y2label: 'Luxembourg Trend',
26 | yAxisLabelWidth: 50,
27 | xAxisLabelWidth: 60
28 | }
29 | );
30 |
31 |
--------------------------------------------------------------------------------
/example/website/js/worldmap_script.js:
--------------------------------------------------------------------------------
1 | $(function(){
2 | $('#world-map').vectorMap(
3 | {
4 | map: 'world_mill_en',
5 | series: {
6 | regions: [{
7 | values: ranks,
8 | scale: ['#F0FFFF', '#00FF00', '#FFFF00', '#FF0000', '#000000'],
9 | normalizeFunction: 'polynomial'
10 | }]
11 | },
12 | onRegionLabelShow: function(e, el, code){
13 | el.html(el.html()+' ('+ranks[code]+')');
14 | }
15 | }
16 | );
17 | });
18 |
19 |
--------------------------------------------------------------------------------
/example/website/logs/.keepdir:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/website/logs/.keepdir
--------------------------------------------------------------------------------
/example/website/master.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | View class of the website
4 | ~~~~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | The website respects the MVC design pattern and this class is the view.
7 |
8 | """
9 |
10 | import os
11 | import cherrypy
12 | from Cheetah.Template import Template
13 | import cgi
14 | import csv
15 | import StringIO
16 | from csv import DictReader
17 | import urllib2
18 | import json
19 | from cherrypy import _cperror
20 |
21 | import master_controler
22 | from pubsublogger import publisher
23 |
24 | def merge_csvs(asns):
25 | url = 'http://{host}:{port}/csv/'.format(
26 | host = cherrypy.config.get('server.socket_host'),
27 | port = cherrypy.config.get('server.socket_port'))
28 | asns = json.loads(asns)
29 | if asns[0] == 0:
30 | return json.dumps('')
31 | temp_dict = {}
32 | no_entries = []
33 | for asn in asns:
34 | try:
35 | f = urllib2.urlopen(url + asn)
36 | for line in DictReader(f):
37 | date = line['day']
38 | rank = line['rank']
39 | if temp_dict.get(date) is None:
40 | temp_dict[date] = {}
41 | temp_dict[date][asn] = rank
42 | except:
43 | no_entries += asn
44 | to_return = 'date,' + ','.join(asns) + '\n'
45 | for date, entries in temp_dict.iteritems():
46 | to_return += date
47 | for asn in asns:
48 | rank = entries.get(asn)
49 | if rank is None:
50 | rank = 0
51 | to_return += ',' + str(rank)
52 | to_return += '\n'
53 | return json.dumps(to_return)
54 |
55 |
56 | class Master(object):
57 |
58 | def __init__(self):
59 | self.dir_templates = 'templates'
60 | publisher.channel = 'Website'
61 |
62 | def __none_if_empty(self, to_check = None):
63 | """
64 | Ensure the empty paramaters are None before doing anything
65 | """
66 | if to_check is None or len(to_check) == 0:
67 | return None
68 | return cgi.escape(to_check, True)
69 |
70 | def __init_template(self, template_name, source = None, date = None):
71 | """
72 | Initialize the basic components of the template
73 | """
74 | template = Template(file = os.path.join(self.dir_templates,
75 | template_name + '.tmpl'))
76 | source = self.__none_if_empty(source)
77 | date = self.__none_if_empty(date)
78 | template.css_file = 'http://www.circl.lu/css/styles.css'
79 | template.logo = 'http://www.circl.lu/pics/logo.png'
80 | template.banner = 'http://www.circl.lu/pics/topbanner.jpg'
81 | template.sources = master_controler.get_sources(date)
82 | template.dates = master_controler.get_dates()
83 | template.source = source
84 | template.date = date
85 | return template
86 |
87 | def __csv2string(self, data):
88 | si = StringIO.StringIO();
89 | cw = csv.writer(si);
90 | cw.writerow(data);
91 | return si.getvalue().strip('\r\n');
92 |
93 | def __query_logging(self, ip, user_agent, webpage, date=None, source=None,
94 | asn=None, asn_details=None, compared_asns=None, ip_lookup=None):
95 | publisher.info(self.__csv2string([ip, user_agent, webpage, date,
96 | source, asn, asn_details, compared_asns, ip_lookup]))
97 |
98 | @cherrypy.expose
99 | def default(self):
100 | """
101 | Load the index
102 | """
103 | return str(self.index())
104 |
105 | @cherrypy.expose
106 | def index(self, source = None, date = None):
107 | """
108 | Generate the view of the global ranking
109 | """
110 | source = self.__none_if_empty(source)
111 | date = self.__none_if_empty(date)
112 | self.__query_logging(cherrypy.request.remote.ip,
113 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
114 | webpage='index', date=date, source=source)
115 | histo, list_size = master_controler.prepare_index(source, date)
116 | template = self.__init_template('index_asn', source, date)
117 | template.list_size = list_size
118 | template.histories = histo
119 | return str(template)
120 |
121 | @cherrypy.expose
122 | def asn_details(self, source = None, asn = None, ip_details = None, date = None):
123 | """
124 | Generate the view of an ASN
125 | """
126 | asn = self.__none_if_empty(asn)
127 | source = self.__none_if_empty(source)
128 | date = self.__none_if_empty(date)
129 | if asn is None:
130 | return self.index(source, date)
131 | self.__query_logging(cherrypy.request.remote.ip,
132 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
133 | webpage='asn_details', date=date, source=source, asn=asn,
134 | asn_details = ip_details)
135 | ip_details = self.__none_if_empty(ip_details)
136 | template = self.__init_template('asn_details', source, date)
137 | asn = asn.lstrip('AS')
138 | if asn.isdigit():
139 | template.asn = asn
140 | asn_description, position, as_infos = master_controler.get_as_infos(asn,
141 | date, source)
142 | if as_infos is not None and len(as_infos) > 0:
143 | template.asn_description = asn_description
144 | template.asn_descs = as_infos
145 | template.current_sources = master_controler.get_last_seen_sources(asn)
146 | template.desc_history = master_controler.get_asn_descriptions(asn)
147 | template.position = position[0]
148 | template.size = position[1]
149 | if len(template.current_sources.keys()) > 0:
150 | template.sources = template.current_sources.keys()
151 | if ip_details is not None:
152 | template.ip_details = ip_details
153 | template.ip_descs = master_controler.get_ip_info(asn,
154 | ip_details, date, source)
155 | else:
156 | template.error = "Invalid query: " + asn
157 | return str(template)
158 |
159 | @cherrypy.expose
160 | def comparator(self, asns = None):
161 | """
162 | Generate the view comparing a set of ASNs
163 | """
164 | asns = self.__none_if_empty(asns)
165 | self.__query_logging(cherrypy.request.remote.ip,
166 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
167 | webpage='comparator', compared_asns=asns)
168 | template = self.__init_template('comparator')
169 | template.asns = asns
170 | if asns is not None:
171 | asns_list, details_list = master_controler.get_comparator_metatada(asns)
172 | template.asns_json = json.dumps(asns_list)
173 | template.asns_details = details_list
174 | else:
175 | template.asns_json = json.dumps([0])
176 | template.asns_details = None
177 | return str(template)
178 |
179 | @cherrypy.expose
180 | def trend(self):
181 | """
182 | Print the trend World vs Luxembourg
183 | """
184 | return self.trend_benelux()
185 | #self.__query_logging(cherrypy.request.remote.ip,
186 | # cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
187 | # webpage='trend')
188 | #return str(self.__init_template('trend'))
189 |
190 | @cherrypy.expose
191 | def trend_benelux(self):
192 | """
193 | Print the trend of the benelux countries
194 | """
195 | self.__query_logging(cherrypy.request.remote.ip,
196 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
197 | webpage='trend_benelux')
198 | return str(self.__init_template('trend_benelux'))
199 |
200 | @cherrypy.expose
201 | def map(self):
202 | """
203 | Print the worldmap
204 | """
205 | self.__query_logging(cherrypy.request.remote.ip,
206 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
207 | webpage='map')
208 | return str(self.__init_template('map'))
209 |
210 | @cherrypy.expose
211 | def ip_lookup(self, ip = None):
212 | ip = self.__none_if_empty(ip)
213 | self.__query_logging(cherrypy.request.remote.ip,
214 | cherrypy.request.headers.get('User-Agent', 'Empty User-Agent'),
215 | webpage='ip_lookup', ip_lookup=ip)
216 | template = self.__init_template('ip_lookup')
217 | template.ip = ip
218 | result = master_controler.get_ip_lookup(ip)
219 | if result is not None:
220 | template.history = result[0]
221 | template.ptrrecord = result[1]
222 | else:
223 | template.history = None
224 | template.ptrrecord = None
225 | return str(template)
226 |
227 | def error_page_404(status, message, traceback, version):
228 | """
229 | Display an error if the page does not exists
230 | """
231 | return "Error %s - This page does not exist." % status
232 |
233 | def handle_error():
234 | cherrypy.response.status = 500
235 | cherrypy.response.body = ["Sorry, an error occured"]
236 | publisher.error('Request: '+ str(cherrypy.request.params) + '\n' +_cperror.format_exc())
237 |
238 | if __name__ == "__main__":
239 | website = Master()
240 |
241 | cherrypy.config.update({'error_page.404': error_page_404})
242 | cherrypy.config.update({'request.error_response': handle_error})
243 | cherrypy.quickstart(website, config = 'config/web_bgp-ranking.ini')
244 |
--------------------------------------------------------------------------------
/example/website/master_controler.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | Controler class of the website
4 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 |
6 | The website respects the MVC design pattern and this class is the controler.
7 | It gets the datas from the `report` class.
8 |
9 | """
10 |
11 | import bgpranking
12 |
13 | def prepare_index(source, date, limit=100):
14 | response = bgpranking.cache_get_top_asns(source, date, limit)
15 | if len(response['top_list']) != 0:
16 | return [(rank[0], rank[1], 1 + rank[2], ', '.join(sources))
17 | for rank, sources in response['top_list']], \
18 | response['size_list']
19 |
20 | def get_sources(date):
21 | if date is None:
22 | date = bgpranking.get_default_date()
23 | return bgpranking.daily_sources([date])
24 |
25 | def get_dates():
26 | return bgpranking.cache_get_dates()
27 |
28 | def get_asn_descriptions(asn):
29 | return bgpranking.get_asn_descriptions(asn)
30 |
31 | def get_last_seen_sources(asn):
32 | dates_sources = bgpranking.prepare_sources_by_dates(timeframe = 365)
33 | return bgpranking.get_last_seen_sources(asn, dates_sources)
34 |
35 | def get_comparator_metatada(asns_string):
36 | splitted_asns = asns_string.split()
37 | if type(splitted_asns) == type(str):
38 | splitted_asns = [splitted_asns]
39 | details_list = []
40 | for asn in splitted_asns:
41 | details_list.append(bgpranking.get_all_blocks_descriptions(asn))
42 | return splitted_asns, details_list
43 |
44 | def get_as_infos(asn, date = None, sources = None):
45 | response = bgpranking.get_asn_descs(asn, date, sources)
46 | if response is None or response.get(asn) is None:
47 | return None, None, None
48 | to_return = []
49 | if type(sources) == type(list()):
50 | position = None
51 | else:
52 | position = bgpranking.cache_get_position(asn, sources, date)
53 | for key, entry in response[asn].iteritems():
54 | if key == 'clean_blocks':
55 | to_return += [[descr[0][1], block, 0, '', 0]
56 | for block, descr in entry.iteritems()]
57 | elif key == 'old_blocks':
58 | pass
59 | else:
60 | to_return.append([entry['description'], key, entry['nb_of_ips'],
61 | ', '.join(entry['sources']), 1 + entry['rank']])
62 | return response['asn_description'], position, sorted(to_return,
63 | key=lambda element: (element[4], element[2]), reverse = True)
64 |
65 | def get_ip_info(asn, block, date = None, sources = None):
66 | response = bgpranking.get_ips_descs(asn, block, date, sources)
67 | if response.get(block) is None:
68 | return None
69 | to_return = [(ip, details['ptrrecord'], ', '.join(details['sources']))
70 | for ip, details in response[block].iteritems()]
71 | return sorted(to_return, key=lambda element: len(element[2]),
72 | reverse=True)
73 |
74 | def get_ip_lookup(ip):
75 | response = bgpranking.get_ip_info(ip)
76 | if len(response.get('history')) == 0:
77 | return None
78 | return response.get('history'), response.get('ptrrecord')
79 |
--------------------------------------------------------------------------------
/example/website/start_website.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | python master.py
4 |
--------------------------------------------------------------------------------
/example/website/templates/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CIRCL/bgpranking-redis-api/7b80c44e291169b8ce849cdb51beb618b96f2386/example/website/templates/__init__.py
--------------------------------------------------------------------------------
/example/website/templates/asn_details.tmpl:
--------------------------------------------------------------------------------
1 | #import cgi
2 | #from templates.master import master
3 | #extends master
4 |
5 | #attr title = 'BGP Ranking - ASN Details'
6 | #attr ip_details = None
7 | #attr javascript = None
8 | #attr asn_descs = []
9 | #attr current_sources = []
10 | #attr asn_description = None
11 | #attr desc_history = []
12 |
13 | #block menu
14 |
40 |
41 | #if $asn is not None
42 | #if len($current_sources) > 0
43 | This AS ($asn) is known in the following lists (last time seen)
44 | #if $position is not None
45 | at position $position/$size in the current top list:
46 | #else
47 | :
48 | #end if
49 |
50 | #for $source, $date in $current_sources.iteritems()
51 |
52 | $source: $date
53 |
54 | #end for
55 |
56 | #end if
57 | #if len($desc_history) > 0
58 | Over time, this AS ($asn) had following descriptions:
59 |
60 | #for $date, $description in $desc_history
61 |
62 | $date: $description
63 |
64 | #end for
65 |
66 | #end if
67 | #end if
68 | #end block menu
69 |
70 | #block main_content
71 | #if $asn_description is not None
72 |
73 |
74 |
114 |
115 | The data are available to download.
117 |
118 |
Relational graphs between the Benelux countries, France, Germany and Switzerland
12 | with the number of infected systems vs the number of active hosts.