Jupyter notebook к докладу https://slides.ooni.io/2018/cif/

Цветовое кодирование:

  • серый — единичные измерения поступающие от пробников RIPE Atlas
  • чёрный — портскан с адреса 178.176.30.221
  • синий — ts= из блоклиста РКН, совпадает со значением ts= в "дэльтах"
  • красный — примерное время блокировки на конкретном пробнике RIPE Atlas
In [1]:
PROBE_PLOT = True
experiment = 's5tg-03'
In [2]:
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [3]:
from pytz import reference
TZ = reference.LocalTimezone()
In [4]:
import pandas as pd
import requests
from scipy.optimize import minimize_scalar
In [5]:
rkn_ts = {
    #<ip ts="2018-09-20T03:30:00+03:00">45.56.118.171</ip>
    # no RIPE Atlas data
    #<ip ts="2018-09-20T17:01:00+03:00">66.175.214.174</ip>
    '66.175.214.174:1080': 1537452060,
    #<ip ts="2018-09-20T17:01:00+03:00">45.33.100.246</ip>
    '45.33.100.246:27435': 1537452060,
    #<ip ts="2018-09-20T21:28:00+03:00">104.200.21.102</ip>
    '104.200.21.102:15197': 1537468080,
    #<ip ts="2018-09-21T01:20:00+03:00">173.255.215.241</ip>
    '173.255.215.241:24914': 1537482000,
}
scan_ts = {
    '45.56.118.171:1080': 1537392745, # no RIPE Atlas data
    '66.175.214.174:1080': 1537445876,
    '45.33.100.246:27435': 1537445708,
    '104.200.21.102:15197': 1537464296,
    '173.255.215.241:24914': 1537473342,
}
In [6]:
d = pd.read_json('{}-full.jsonl'.format(experiment), lines=True)
d.head()
Out[6]:
cert cert_len dst from prb_id stored_timestamp timestamp
0 -----BEGIN CERTIFICATE-----\nMIICyDCCAbCgAwIBA... 1 45.33.100.246:27435 193.232.224.27 10059 1537443343 2018-09-20 11:33:18
1 -----BEGIN CERTIFICATE-----\nMIICyDCCAbCgAwIBA... 1 45.33.100.246:27435 193.232.224.27 10059 1537443492 2018-09-20 11:36:21
2 -----BEGIN CERTIFICATE-----\nMIICyDCCAbCgAwIBA... 1 45.33.100.246:27435 193.232.224.27 10059 1537443617 2018-09-20 11:39:13
3 -----BEGIN CERTIFICATE-----\nMIICyDCCAbCgAwIBA... 1 45.33.100.246:27435 193.232.224.27 10059 1537443796 2018-09-20 11:42:09
4 -----BEGIN CERTIFICATE-----\nMIICyDCCAbCgAwIBA... 1 45.33.100.246:27435 193.232.224.27 10059 1537444085 2018-09-20 11:45:14
In [7]:
if hasattr(d, 'dst'):
    dst = d.dst[0]
    print 'del dst', dst
    blue_line = rkn_ts[dst] * 1000000000
    scan_line = scan_ts[dst] * 1000000000
    assert d.dst.nunique() == 1
    del d['dst']
blue_dt = pd.to_datetime(blue_line, unit='ns')
scan_dt = pd.to_datetime(scan_line, unit='ns')
if not hasattr(d, 'good_cert'):
    known_certs = d.groupby('cert').cert.nunique()
    good_cert = max(dict(known_certs).items(), key=lambda _: _[1])[0]
    print 'add good_cert'
    d['good_cert'] = (d.cert == good_cert)
    print 'del cert'
    del d['cert']
if not hasattr(d, 'stored_utc'):
    print 'add stored_utc'
    d['stored_utc'] = pd.to_datetime(d.stored_timestamp, unit='s')
del dst 45.33.100.246:27435
add good_cert
del cert
add stored_utc
In [8]:
(d.stored_utc - d.timestamp).describe()
Out[8]:
count                     17912
mean     0 days 00:01:34.006364
std      0 days 00:01:01.034541
min             0 days 00:00:06
25%      0 days 00:01:00.750000
50%             0 days 00:01:29
75%             0 days 00:02:07
max             0 days 01:23:12
dtype: object

В некоторый момент с части пробников измерения были сняты, т.к. на данных пробах блокировка уже наступила и тратить RIPE Atlas кредиты на них не имело смысла. Пробы, с которых сигнал о блокировке ещё не был получен, продолжали генерировать измерения.

In [9]:
f = figure(figsize=(16,4))
axvline(blue_dt, color='blue')
axvline(scan_dt, color='black', ls='--')
xlim(d.timestamp.min(), d.timestamp.max())
f.axes[0].xaxis_date(TZ)
d.timestamp.hist(bins=100, color='grey')
title(u'Объём поступающих измерений')
xlabel(u'День, час, МСК')
show()
In [10]:
red_line = {} # prb_id -> timestamp

for prb_id in d.prb_id.unique():
    prb = d[d.prb_id == prb_id]
    yorig = prb.good_cert.astype('int')
    xorig = prb.timestamp.astype('int64')
    def separator_cost(x):
        cls = (xorig < x).astype('int')
        return (yorig != cls).sum()
    sol = minimize_scalar(separator_cost, bounds=(xorig.min(), xorig.max()), method='Bounded')
    assert sol.success == True
    red_line[prb_id] = sol.x
In [11]:
if PROBE_PLOT:
    for prb_id, red_dt in sorted(red_line.items(), key=lambda x: x[1]):
        prb = d[d.prb_id == prb_id]
        red_dt = pd.to_datetime(red_dt, unit='ns').floor('s')
                                 
        print 'prb_id: {}'.format(prb_id)
        f = figure(figsize=(16,3))
        xlim(d.timestamp.min(), d.timestamp.max())
        grid()
        f.axes[0].xaxis_date(TZ)
        xlabel(u'День, час, МСК')
        title('prb_id: {}, red_line: {} UTC'.format(prb_id, red_dt))
        scatter(list(prb.timestamp), prb.good_cert.astype('int'), color='grey')
        axvline(red_dt, color='red')
        axvline(blue_dt, color='blue')
        axvline(scan_dt, color='black', ls='--')
        show()
prb_id: 20749
prb_id: 14011
prb_id: 10059
prb_id: 35631
prb_id: 30741
prb_id: 21451
prb_id: 1227
prb_id: 17744
prb_id: 3780
prb_id: 27858
prb_id: 11098
prb_id: 11994
prb_id: 3696
prb_id: 401
prb_id: 3868
prb_id: 26190
prb_id: 27180
prb_id: 3815
prb_id: 11501
prb_id: 11429
prb_id: 3854
prb_id: 20873
prb_id: 241
prb_id: 3959
prb_id: 3834
prb_id: 12896
prb_id: 18906
prb_id: 10865
prb_id: 3768
prb_id: 12890
prb_id: 15523
prb_id: 26580
prb_id: 2274
prb_id: 14680
prb_id: 15121
prb_id: 2590
prb_id: 10822
prb_id: 21662
prb_id: 30097
prb_id: 34179
prb_id: 18691
prb_id: 3714
prb_id: 31479
prb_id: 35632
prb_id: 22777
prb_id: 32510
prb_id: 11473
prb_id: 11437
prb_id: 11902
prb_id: 14230
prb_id: 35271
prb_id: 22643
prb_id: 30988
prb_id: 4440
prb_id: 28137
prb_id: 19878
prb_id: 1341
prb_id: 26656
prb_id: 11487
prb_id: 31198
prb_id: 19803
prb_id: 28161
prb_id: 3892
prb_id: 3613
prb_id: 652
prb_id: 25354
prb_id: 13321