После прочтения статьи уважаемого ValdikSS решил изучить DPI своего провайдера и, при благоприятном развитии событий, найти пути его обхода. Ниже — результаты моих изысканий.
Итак, провайдер — ТТК Ульяновск (бывш. DARS Telecom), PPPoE-подключение с выделением внешнего IP на время сессии. Блокировка осуществляется заворачиванием заблокированных подсетей/хостов на свой DPI. DNS не подменяется.
Причем некоторые сайты просто и незатейливо заблокированы по IP (спасибо хоть RST присылают). Например, bt-анонсеры Рутрекера:
Для тех же сайтов, где нужно показать страничку блокировки работает пассивный DPI. Рассмотрим на примере Рутрекера:
Посмотрим на данный вывод подробнее. С момента 605860 до 646913 клиент осуществляет тройное рукопожатие с «настоящим» Рутрекером. В 647281 клиент посылает HTTP-запрос Рутрекеру длинной 77 байт, но вместо обычного ACK получает от «поддельного» Рутрекера FPA с HTTP-перенаправлением длинной 246 байт. Причем, заметьте, с корректным ACK 78, но левым, в рамках данного TCP-соединения, номером последовательности SEQ. Далее клиент закрывает соединение и переходит куда сказали. ACK и HTTP-ответ от Рутрекера так и не приходят.
В принципе, понятно зачем провайдеру потребовался этот странный TCP с флагами FPA: FIN для начала завершения соединения со стороны клиента, PUSH для проталкивания этого сегмента в TCP-очереди клиента, а корректный ACK чтоб всё выглядело красиво и этот «поддельный» TCP-сегмент не был отброшен TCP\IP-стеком клиента. Но этот FPA его же и погубил.
Итак, основная стратегия — отбрасывать TCP-сегменты с установленными флагами FPA, как-бы приходящие с адресов заблокированных ресурсов.
В качестве роутера в моей домашней сети трудится FreeBSD на старом Атоме, пакетным фильтром работает pf. Основной проблемой стало то, что pf — это statefull-фаервол, т.е. после первого же нашего разрешенного исходящего SYN в адрес Рутрекера в таблицу состояний фаервола заносится запись (state) и все последующие запросы и, что нам особенно важно, ответы в пределах данного TCP-соединения ни через какие правила фаервола более не проходят, они все разрешены.
Подобное поведение сильно увеличивает производительность брандмауэра и сокращает количество правил, но в нашем случае препятствует эффективной фильтрации. Поэтому все запросы/ответы к заблокированным ресурсам будем производить в stateless-режиме, т.е. без сохранения состояния. В pf правила получились такие:
Директива no state в разрешающих правилах запрещает сохранение состояния, первое правило в блоке UNBLOCK разрешает исходящие на заблокированный ресурс, второе правило блокирует «поддельные» входящие с установленными флагами FPA, третье — разрешает все остальные входящие. Директива quick во всех правилах прекращает просмотр фаерволом всех последующих правил фильтрации при совпадении с данным правилом. Результат (тройное рукопожатие пропущено):
221343 — HTTP-запрос к Рутрекеру
237686 — ответ от провайдерского DPI с флагами FPA (дамп с внешнего интерфейса роутера, поэтому он тут виден; клиент его не получает)
563977 — клиент ответа так и не дождался, повторяет запрос
604853 — настоящий Рутрекер присылает ACK
605043 — настоящий Рутрекер присылает HTTP-ответ
P.S. То же самое можно проделать с помощью любого современного фаервола, pf здесь только потому, что я его использую. Так же внимательный читатель мог заметить, что IP-спуфинг, осуществляемый DPI провайдера, элементарно детектируется по IP TTL (58 против 61). Но pf не умеет фильтровать все поля IP-пакетов, только основные. При использовании других фаерволов несовпадение IP TTL можно использовать как дополнительный признак «поддельного» пакета, наряду с TCP FPA.
That's All Folks!
Итак, провайдер — ТТК Ульяновск (бывш. DARS Telecom), PPPoE-подключение с выделением внешнего IP на время сессии. Блокировка осуществляется заворачиванием заблокированных подсетей/хостов на свой DPI. DNS не подменяется.
% traceroute ya.ru
traceroute to ya.ru (87.250.250.242), 64 hops max, 40 byte packets
1 bras.ulttk.ru (79.132.125.1) 0.899 ms 0.787 ms 0.817 ms
2 ulk06.ulk26.transtelecom.net (217.150.41.98) 1.552 ms 1.536 ms 1.536 ms
3 * * *
4 Yandex-gw.transtelecom.net (188.43.3.213) 21.828 ms 15.955 ms 17.003 ms
% traceroute rutracker.org
traceroute to rutracker.org (195.82.146.214), 64 hops max, 40 byte packets
1 bras.ulttk.ru (79.132.125.1) 1.778 ms 0.843 ms 0.751 ms
2 ulk06.ulk26.transtelecom.net (217.150.41.98) 1.656 ms 1.698 ms 1.439 ms
3 * * *
4 188.43.0.18 (188.43.0.18) 16.589 ms
BlackList-gw.transtelecom.net (188.43.30.130) 16.435 ms
BL-gw.transtelecom.net (188.43.31.170) 16.377 ms
5 Filter-gw.transtelecom.net (188.43.30.33) 17.006 ms 16.859 ms 17.080 ms
% traceroute kinozal.tv
traceroute: Warning: kinozal.tv has multiple addresses; using 104.24.107.53
traceroute to kinozal.tv (104.24.107.53), 64 hops max, 40 byte packets
1 bras.ulttk.ru (79.132.125.1) 0.810 ms 0.809 ms 0.739 ms
2 ulk06.ulk26.transtelecom.net (217.150.41.98) 1.653 ms 1.802 ms 1.736 ms
3 * * *
4 Filter-gw.transtelecom.net (188.43.30.34) 16.451 ms
BL-gw.transtelecom.net (188.43.31.170) 16.405 ms 16.418 ms
5 Filter-gw.transtelecom.net (188.43.30.33) 99.751 ms 78.541 ms 17.021 ms
6 Cloudflare-msk-gw.transtelecom.net (188.43.3.65) 212.754 ms 107.803 ms 117.062 ms
7 104.24.107.53 (104.24.107.53) 28.015 ms 17.216 ms 17.357 ms
Причем некоторые сайты просто и незатейливо заблокированы по IP (спасибо хоть RST присылают). Например, bt-анонсеры Рутрекера:
02:48:58.530078 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
ip109.176.ulttk.ru.22099 > bt.rutracker.org.http: Flags [S], cksum 0xd538 (correct), seq 3425095266, win 65535, options [mss 1452,nop,wscale 6,sackOK,TS val 1991139339 ecr 0], length 0
02:48:58.546482 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 40)
bt.rutracker.org.http > ip109.176.ulttk.ru.22099: Flags [R.], cksum 0x13b9 (correct), seq 0, ack 3425095267, win 0, length 0
Для тех же сайтов, где нужно показать страничку блокировки работает пассивный DPI. Рассмотрим на примере Рутрекера:
02:27:28.605860 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
ip109.176.ulttk.ru.27338 > rutracker.org.http: Flags [S], cksum 0xeafc (correct), seq 3703194126, win 65535, options [mss 1452,nop,wscale 6,sackOK,TS val 1989849414 ecr 0], length 0
02:27:28.646812 IP (tos 0x0, ttl 58, id 0, offset 0, flags [DF], proto TCP (6), length 48)
rutracker.org.http > ip109.176.ulttk.ru.27338: Flags [S.], cksum 0x81d2 (correct), seq 2683499593, ack 3703194127, win 14600, options [mss 1400,nop,wscale 8], length 0
02:27:28.646913 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
ip109.176.ulttk.ru.27338 > rutracker.org.http: Flags [.], cksum 0xe266 (correct), seq 1, ack 1, win 1028, length 0
02:27:28.647281 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 117)
ip109.176.ulttk.ru.27338 > rutracker.org.http: Flags [P.], cksum 0xb1d5 (correct), seq 1:78, ack 1, win 1028, length 77: HTTP, length: 77
GET / HTTP/1.1
Host: rutracker.org
User-Agent: curl/7.56.0
Accept: */*
02:27:28.663665 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 286)
rutracker.org.http > ip109.176.ulttk.ru.27338: Flags [FP.], cksum 0xf88c (correct), seq 1:247, ack 78, win 0, length 246: HTTP, length: 246
HTTP/1.1 301 Moved Permanently
02:27:28.663724 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
ip109.176.ulttk.ru.27338 > rutracker.org.http: Flags [.], cksum 0xe126 (correct), seq 78, ack 248, win 1024, length 0
02:27:28.664047 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 40)
ip109.176.ulttk.ru.27338 > rutracker.org.http: Flags [F.], cksum 0xe121 (correct), seq 78, ack 248, win 1028, length 0
02:27:28.695429 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
ip109.176.ulttk.ru.42348 > dib-filtr-gw.transtelecom.net.http: Flags [S], cksum 0x0556 (correct), seq 2835326510, win 65535, options [mss 1452,nop,wscale 6,sackOK,TS val 1989849504 ecr 0], length 0
Посмотрим на данный вывод подробнее. С момента 605860 до 646913 клиент осуществляет тройное рукопожатие с «настоящим» Рутрекером. В 647281 клиент посылает HTTP-запрос Рутрекеру длинной 77 байт, но вместо обычного ACK получает от «поддельного» Рутрекера FPA с HTTP-перенаправлением длинной 246 байт. Причем, заметьте, с корректным ACK 78, но левым, в рамках данного TCP-соединения, номером последовательности SEQ. Далее клиент закрывает соединение и переходит куда сказали. ACK и HTTP-ответ от Рутрекера так и не приходят.
В принципе, понятно зачем провайдеру потребовался этот странный TCP с флагами FPA: FIN для начала завершения соединения со стороны клиента, PUSH для проталкивания этого сегмента в TCP-очереди клиента, а корректный ACK чтоб всё выглядело красиво и этот «поддельный» TCP-сегмент не был отброшен TCP\IP-стеком клиента. Но этот FPA его же и погубил.
Итак, основная стратегия — отбрасывать TCP-сегменты с установленными флагами FPA, как-бы приходящие с адресов заблокированных ресурсов.
В качестве роутера в моей домашней сети трудится FreeBSD на старом Атоме, пакетным фильтром работает pf. Основной проблемой стало то, что pf — это statefull-фаервол, т.е. после первого же нашего разрешенного исходящего SYN в адрес Рутрекера в таблицу состояний фаервола заносится запись (state) и все последующие запросы и, что нам особенно важно, ответы в пределах данного TCP-соединения ни через какие правила фаервола более не проходят, они все разрешены.
Подобное поведение сильно увеличивает производительность брандмауэра и сокращает количество правил, но в нашем случае препятствует эффективной фильтрации. Поэтому все запросы/ответы к заблокированным ресурсам будем производить в stateless-режиме, т.е. без сохранения состояния. В pf правила получились такие:
WAN="ng0"
LAN="re0"
Blocked="{ заблок.IP1, заблок.IP2, заблок.IP3, и т.д. }"
...
# -UNBLOCK-
# for LAN hosts
pass in quick on $LAN proto tcp from $LAN:network to $Blocked no state
block drop out quick on $LAN proto tcp from $Blocked to $LAN:network flags FPA/FPA
pass out quick on $LAN proto tcp from $Blocked to $LAN:network no state
# -UNBLOCK-
# for me (router itself, for testing)
pass out quick on $WAN proto tcp from ($WAN) to $Blocked no state
block drop in quick on $WAN proto tcp from $Blocked to ($WAN) flags FPA/FPA
pass in quick on $WAN proto tcp from $Blocked to ($WAN) no state
...
Директива no state в разрешающих правилах запрещает сохранение состояния, первое правило в блоке UNBLOCK разрешает исходящие на заблокированный ресурс, второе правило блокирует «поддельные» входящие с установленными флагами FPA, третье — разрешает все остальные входящие. Директива quick во всех правилах прекращает просмотр фаерволом всех последующих правил фильтрации при совпадении с данным правилом. Результат (тройное рукопожатие пропущено):
01:48:10.221343 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 117)
ip109.176.ulttk.ru.42714 > rutracker.org.http: Flags [P.], cksum 0xccb6 (correct), seq 1:78, ack 1, win 1028, length 77: HTTP, length: 77
GET / HTTP/1.1
Host: rutracker.org
User-Agent: curl/7.56.0
Accept: */*
01:48:10.237686 IP (tos 0x0, ttl 61, id 0, offset 0, flags [DF], proto TCP (6), length 286)
rutracker.org.http > ip109.176.ulttk.ru.42714: Flags [FP.], cksum 0x136e (correct), seq 1:247, ack 78, win 0, length 246: HTTP, length: 246
HTTP/1.1 301 Moved Permanently
01:48:10.563977 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 117)
ip109.176.ulttk.ru.42714 > rutracker.org.http: Flags [P.], cksum 0xccb6 (correct), seq 1:78, ack 1, win 1028, length 77: HTTP, length: 77
GET / HTTP/1.1
Host: rutracker.org
User-Agent: curl/7.56.0
Accept: */*
01:48:10.604853 IP (tos 0x0, ttl 58, id 42669, offset 0, flags [DF], proto TCP (6), length 40)
rutracker.org.http > ip109.176.ulttk.ru.42714: Flags [.], cksum 0x00c5 (correct), seq 1, ack 78, win 58, length 0
01:48:10.605043 IP (tos 0x0, ttl 58, id 42670, offset 0, flags [DF], proto TCP (6), length 422)
rutracker.org.http > ip109.176.ulttk.ru.42714: Flags [P.], cksum 0x9bc5 (correct), seq 1:383, ack 78, win 58, length 382: HTTP, length: 382
HTTP/1.1 301 Moved Permanently
Server: nginx
Date: Thu, 26 Oct 2017 21:48:10 GMT
Content-Type: text/html
Content-Length: 178
Location: http://rutracker.org/forum/index.php
Connection: keep-alive
221343 — HTTP-запрос к Рутрекеру
237686 — ответ от провайдерского DPI с флагами FPA (дамп с внешнего интерфейса роутера, поэтому он тут виден; клиент его не получает)
563977 — клиент ответа так и не дождался, повторяет запрос
604853 — настоящий Рутрекер присылает ACK
605043 — настоящий Рутрекер присылает HTTP-ответ
P.S. То же самое можно проделать с помощью любого современного фаервола, pf здесь только потому, что я его использую. Так же внимательный читатель мог заметить, что IP-спуфинг, осуществляемый DPI провайдера, элементарно детектируется по IP TTL (58 против 61). Но pf не умеет фильтровать все поля IP-пакетов, только основные. При использовании других фаерволов несовпадение IP TTL можно использовать как дополнительный признак «поддельного» пакета, наряду с TCP FPA.
That's All Folks!