понедельник, 12 августа 2019 г.

Империя нанесет ответный удар или а как нужно было

Отважный и лысый


Сегодня весь день диджиталрезистанс носится с вот этой статьей, радостно потирая лапки.


Как я и сказал, телеграмщики ведут прошедшую войну и даже собственный опыт ничему не способен их научить.

Послушайте, господа, это уже даже не смешно. Вы, повторяю, прежде, чем что-то выдумывать, поконсультируйтесь со специалистами, у которых проксирование - хлеб.

Так называемый FakeTLS неспособен выдержать даже самых элементарных проверок. По факту, вы рисуете на лбу большую красную мишень.

Мало того, что сам Телеграм браво начинает коннект на известные пулы адресов с HTTP POST (и никто вообще не чешется как-то изменить протокол, чтобы хотя бы не палиться со страшной силой и не сливать клиентов с потрохами фактом использование ТГ).

Так еще и MTProto proxy при элементарном openssl connect не показывают вообще никакого TLS сертификата. Даже самоподписного.

Простейший пробинг ufdbguard сразу же, незамедлительно, выпасает фейковый HTTPS и терминирует его. Сервер, ну-ка покажи нам легитимный, подписанный рутами, серт. Что, нету? TCP RST!

Простите, вы кого, собственно, пытаетесь обмануть? Самих себя? Или недоумков с DPI L2, которые вот так, просто, без всяких проверок, считают все, идущее по 443 порту, HTTPS?

Клиент предъявляет серт, вы говорите? Прекрасно, клиент, ну-ка, покажи-ка легитимный серт, подписанный рутами! Что, нету? TCP RST!

Это если забыть о том, что прокси телеграма долбятся в dns.google.com по DoH как ненормальные, сразу же выдавая себя с потрохами даже без SSL Bump - просто по SNI. Правда думаете, что кто-то еще в таком же темпе обращается туда же с DoH?

Прикидываться гуглом или легитимным сайтом? Вы точно в этом уверены? Помимо того, что это признак DNS спуфинга и мало какая система безопасности молча это сглотнет и пропустит, так, увидев в SNI google.com (и вообще все, что угодно) - достаточно быстренько зарезолвить показываемый домен, сравнить его с целевым IP, при несовпадении просто терминировать сессию.


Вы правда надеетесь, что eSNI спасет и сохранит дурней, которые с фанфарами вот таким глупым образом прикидываются HTTPS? Во-первых, eSNI не стандарт, а пока что драфт. Который мало кто поддерживает и не факт, что будет поддерживать. Во-вторых, он - буду откровенен - имеет fallback на 1.2. Достаточно на мидлбоксе сказать NO_TLSv1_3:

Мы же не хотим потерять клиентов, которые суть новая нефть и наши денежки? Значит, fallback на 1.2, проверка SNI - вы уже наши.

А как нужно было?


А нужно было вот как.

Чтобы изобразить легитимный TLS - нужно быть этим самым легитимным TLS.

По факту, вам нужно огромное множество точек входа, которые будут вашими прокси. Причем будут честным, легитимным TLS, выдерживающим какие угодно проверки на каких угодно проксях.

По факту, вам нужен модуль mod_mtproto к апачу или nginx (а не вот эта вот лабуда, написанная на скриптятине или эрланге - это ж надо было додуматься, компиляторы эрланга буквально на каждом углу валяются, не так ли, умники?), который будет стоять на большинстве легитимных веб-серверов интернета, повсюду. И который будет принимать трафик телеграма внутри легитимного туннеля TLS. После чего роутить его непосредственно на сервера телеграм или куда вам там взбрендится.

Как сообщить клиентам о существовании таких точек входа? Вы просто указываете большим списком в проксях телеграма такие ресурсы, как www.disney.com, www.google.com, и так далее - большие, известные, безобидные сервера с котиками, с которыми вы договорились о форвардинге трафика Телеграма (вы точно уверены, что владельцам сайтов вы нужны, с вашим трафиком, которого немало, который надо забесплатно хэндлить и куда-то там отправлять? Вот то-то и оно, ключевая проблема именно в этом), этот список большой, а Телеграм умеет keep alive by round-robin (а он, кстати, этого посейчас не умеет).

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

Соответственно, этих точек входа должно быть, как редкоземельных элементов - везде и нигде. Куда ни ткнул - попал в прокси телеграм.

Вот тогда только вы будете почти незаметны на прокси, и вас нельзя будет выпалить и терминировать.


PS. Где умный человек прячет дерево? В лесу. © Г.К.Честертон

вторник, 18 июня 2019 г.

Oracle Developer Studio: создание дистрибутивов приложений

Динамическая линковка штука хорошая, кто бы спорил. Правда, часто требует, чтобы рантайм уже присутствовал в целевой системе и, иногда, это приводит к проблемам вида DLL Hell.


Есть несколько очевидных примеров подобного ада - CentOS и ее похождения с glibc, смена ABI в libstdc++ и тому подобная веселуха.

Обычно решается статической линковкой (если она вообще возможна; есть целый ряд случаев, когда нет - например, когда стандартная библиотека экспонирует зависимость libc, которая статически линковаться не может в принципе).

Sun (а впоследствие, Oracle) решила проблему просто. В системе нет статических .a библиотек (в самом Oracle есть; при установке он линкуется в целевой системе). То есть все системные рантаймы до единого динамические.

Есть, однако, проблемка. Когда вы собираете приложение для дистрибуции с Oracle Developer Studio, и используете libgcc_s и libstdc++, они в целевой системе могут и отсутствовать. Они еще и лежат по умолчанию (при установке Dev Studio) в хитром месте (/opt/developerstudio12.6/lib/compilers/CC-gcc/lib; это когда мы используем стандартные библиотеки GCC. Если мы используем штатные библиотеки студии, там будут другие пути и несколько иная процедура).



Что делать? Тащить на целевую систему Dev Studio?

Это так себе идея.

На помощь в этом случае приходит RUNPATH.

Вообще говоря, штука не самоочевидная (а в GCC она еще и разным образом реализована на уровне версий компилятора).

В руководствах Оракла она, конечно, описана. Но описана так себе - на практике некоторые вещи не стыкуются и, вообще говоря, не самоочевидны (особенно libatomic, SPARC и тому подобное).

Как сделать правильно распространение библиотек C/C++ рантайма от Developer Studio с собственным приложением?

Как сказано в руководства, сначала нужно выбрать место, в котором будут лежать рантаймы вашего приложения (libgcc_s и libstdc++ в нашем случае).

Для простоты положим их в целевую директорию нашего приложения/библиотеки:
 # 32 bit  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/libstdc++.so.6 /usr/local/lib/dest_dir  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/libgcc_s.so.1 /usr/local/lib/dest_dir  
   
 # 64 bit (amd64)  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/amd64/libstdc++.so.6 /usr/local/lib/dest_dir/64  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/amd64/libgcc_s.so.1 /usr/local/lib/dest_dir/64  
   
 # SPARC 64 bit  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/sparcv9/libstdc++.so.6 /usr/local/lib/dest_dir/64  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/sparcv9/libgcc_s.so.1 /usr/local/lib/dest_dir/64  
 cp /opt/developerstudio12.6/lib/compilers/atomic/sparcv9/libstatomic.so.1 /usr/local/lib/dest_dir/64  
   
 # SPARC 32 bit  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/libstdc++.so.6 /usr/local/lib/dest_dir  
 cp /opt/developerstudio12.6/lib/compilers/CC-gcc/lib/libgcc_s.so.1 /usr/local/lib/dest_dir  
 cp /opt/developerstudio12.6/lib/compilers/atomic/libstatomic.so.1 /usr/local/lib/dest_dir

Затем, при конфигурировании (мы используем autotools с Developer Studio), мы указываем пути-источники -L и RUNPATH опциями -R. Обратите внимание, что на Солярис надо указать -lCrunG3 для линковки системного рантайма (костыль, да), а, в случае необходимости atomic, следует помнить, что на x86/64 библиотеку libatomic подключать, как правило, не требуется, а вот на SPARC - обязательно, и делается это не совсем так, как описано в документации. Проще сделать так, как показано в примере ниже (и добавить -latomic):

 # amd64  
 ./configure 'CXXFLAGS=-m64 -xlinkopt=2 -R/usr/local/lib/dest_dir/64 -L/opt/developerstudio12.6/lib/compilers/CC-gcc/lib/amd64 -lstdc++ -lgcc_s -lCrunG3' --libdir=/usr/local/lib/dest_dir/64  
   
 # SPARC 64 bit  
 ./configure 'CXXFLAGS=-m64 -xlinkopt=2 -R/usr/local/lib/dest_dir/64 -L/opt/developerstudio12.6/lib/compilers/CC-gcc/lib/sparcv9 -R/usr/local/lib/dest_dir/64 /opt/developerstudio12.6/lib/compilers/atomic/sparcv9/libstatomic.so.1 -lstdc++ -lgcc_s -lCrunG3 -latomic' --libdir=/usr/local/lib/dest_dir/64  

Пример показывает сборку библиотеки, зависящей от libgcc_s и libstdc++.

Обратите внимание, libatomic задается одной опцией -R/usr/local/lib/dest_dir/64  /opt/developerstudio12.6/lib/compilers/atomic/sparcv9/libstatomic.so.1, так проще и это работает (документация невнятно описывает этот момент).

Затем, как обычно, make && make install-strip.

После этого целевую директорию можно архивировать в пакет вместе с вашей библиотекой и ее рантаймами. Предварительно можно убедиться при помощи ldd, что пути рантаймов в целевом приложении указывают на копии в целевой директории.


Вот теперь все. В целевой системе нет необходимости устанавливать Developer Studio для выполнения вашего приложения.

суббота, 27 апреля 2019 г.

C++: Считывание переменных окружения внутри программы

Простая вещь.

Очень извращенный C-style пример считывания числового значения из переменной среды, через макрос:
 #include <string>  
 #include <iostream>  
   
 #include <stdlib.h>     // For getenv  
   
 unsigned int get_environment(const char* p_name, unsigned int p_default) {  
      if (getenv(p_name))  
           return atoi(getenv(p_name));     /* return environment value */  
      else return p_default;  
 }  
   
 #define ENV_VALUE get_environment("TEST", 64)  
   
 int main(int argc, char* argv[])  
 {  
   
 std::cout << ENV_VALUE <<std::endl;  
   
 }

Один тонкий момент: переменную надо экспортировать, а то не увидим ее:


Это не совсем C++, да и парсинг, по-хорошему, нужно делать.

Будем считывать строкой:

 #include <string>  
 #include <iostream>  
   
 #include <cstdlib>     /* For std::getenv */  
   
 std::string get_environment(const char* p_name) {  
      if (std::getenv(p_name))  
           return std::getenv(p_name);  
      else return "N/A";  
 }  
   
 int main(int argc, char* argv[])  
 {  
   
 std::cout << get_environment("TEST") <<std::endl;  
   
 } 

Так как getenv() это С-функция, проверять результат на NULL надо всегда. Иначе дампом плюнет.

Примерно вот так в libmtmalloc сделана конфигурация параметров библиотеки через переменные окружения.

среда, 17 апреля 2019 г.

"А король-то голый!" или Как заблокировать Телеграм


ДИСКЛЕЙМЕР Я не подаю ценных идей цензорам. Все, описанное в данной статье, было своевременно сообщено технической поддержке Телеграм год назад, которая благополучно проигнорировала все обращения. Ввиду этого, а также в силу того, что протоколы не изменились ни на йоту, считаю себя вправе опубликовать результаты исследований и технические детали.

ДИСКЛЕЙМЕР 2 Меня глубоко возмущает подход разнообразных "девелуперов" противоцензурных решений, которые, разрабатывая свои решения, не почесались провести тестирование на контролируемом бордере на предмет реальной их устойчивости. При этом имеют нахальство заявлять, что они устойчивы к цензуре и так далее и тому подобное. Как показывает практика, подобные заявления не соответствуют действительности (мягко говоря).

Собственно Телеграм

Фундаментально слабым местом протокола Телеграм является bootstrap клиента. Первоначальный вход начинается с HTTP POST к серверу по характерному IP-адресу к API. То есть, клиент сообщает серверу о своем присутствии онлайн:


Вызов API происходит на четыре подсети:

 149.154.164.0/22  
 149.154.172.0/22  
 91.108.4.0/22  
 91.108.56.0/24  
 2001:67c:4e8::/48  
 2001:b28:f23d::/48  

Собственно говоря, на этом все. Достаточно заблокировать данные сети на бордере, и вход с аутентификацией становится невозможным. Никто другой эти подсети не использует, так что мы можем с чистой совестью их побанить.

На второй фазе, после входа, происходит переход по тем же адресам под TLS.


Давайте посмотрим, что у нас на  149.154.167.50:443 --

 # openssl s_client -connect 149.154.167.50:443  
 CONNECTED(00000003)  
 1:error:140790E5:SSL routines:ssl23_write:ssl handshake failure:s23_lib.c:177:  
 ---  
 no peer certificate available  
 ---  
 No client certificate CA names sent  
 ---  
 SSL handshake has read 0 bytes and written 307 bytes  
 ---  
 New, (NONE), Cipher is (NONE)  
 Secure Renegotiation IS NOT supported  
 Compression: NONE  
 Expansion: NONE  
 No ALPN negotiated  
 SSL-Session:  
   Protocol : TLSv1.2  
   Cipher  : 0000  
   Session-ID:   
   Session-ID-ctx:   
   Master-Key:   
   Key-Arg  : None  
   PSK identity: None  
   PSK identity hint: None  
   SRP username: None  
   Start Time: 1555498957  
   Timeout  : 300 (sec)  
   Verify return code: 0 (ok)  
 ---  
   

Мы видим, что, хотя соединение у нас по TLSv1.2, легитимный сертификат CA не предъявляется (более того, не предъявляется вообще никакого сертификата).

На 2 фазе, таким образом, нам достаточно не разрешить метод CONNECT по сетям Телеграм на порту 443.

Исходя из вышеизложенного, напишем универсальное решение для транспарентного прокси L7 (для простоты ограничимся IPv4 - не все страны и не все провайдеры поддерживают IPv6 на магистралях):

 # Block Telegram  
 acl Telegram url_regex 149\.154\.1(6[0-9]|7[0-5])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\/api$  
 acl Telegram url_regex 91\.108\.([4-7]|5[6|7])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])\/api$  
 http_access deny Telegram  
 deny_info TCP_RESET Telegram  
   
 acl Telegram_api_terminate ssl::server_name_regex 149\.154\.1(6[0-9]|7[0-5])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])  
 acl Telegram_api_terminate ssl::server_name_regex 91\.108\.([4-7]|5[6|7])\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])  
   
 # SSL bump rules  
 acl DiscoverSNIHost at_step SslBump1  
 ssl_bump peek DiscoverSNIHost  
 ssl_bump terminate Telegram_api_terminate  
 ssl_bump splice all  
   

Как видите, мы, не выполняя MiTM и не устанавливая сертификата прокси клиенту, в первом ACL просто терминируем bootstrap на уровне HTTP, посылая клиенту TCP RST (можно и просто запретить метод POST на эти сети), во втором ACL мы терминируем CONNECT по SNI (по факту, здесь SNI не нужен - мы видим IP, по которому без всякого SNI и происходит соединение).

Применим эти правила:


Клиент не может сообщить о себе серверу. Сервер, не зная о его присутствии, не может PUSH бросить - он не знает, куда и кому его  бросать.

Как видите, все трескучие заявления околоайтишных "экспертов", что "мы-де замаскируемся под HTTPS" - не более, чем слова. Недостаточно встать на 443 порт, чтобы замаскироваться под HTTPS. Проверяется это в одно действие (если оставить в стороне проверку HTTP headers истинного HTTPS) тривиальной проверкой хэндшейка. И, даже если не копать глубоко, bootstrap слишком примитивен и перешибается легким пинком.

UPDATE Если Телеграм начинает прятаться за легитимными CDN (что происходило в прошлом году), то его bootstrap все равно имеет характерный вид HTTP POST http://A.B.C.D/api . В этом случае мы просто расширяем регулярное выражение до вида ^http://0.0.0.0/api$ (по данным исследования, больше подобных поведенческих сигнатур ни у кого нет) и, либо идентифицируем IP и помещаем их в бан-лист динамически на срок от 1 часа до суток, либо просто пресекаем HTTP POST и последующий CONNECT с отправкой в сессию TCP RST. Честно говоря, сигнатура бутстрапа настолько характерная, что я немного удивлен тем, что она еще не добавлена в Cisco NBAR2.

Я хочу добавить, что подавляющее большинство массовых мессенджеров, выполняющих бутстрап и аутентификацию по HTTPS на центральный сервер, также банятся в одно действие по SNI. По моему глубокому убеждению, eSNI в обозримом будущем не имеет реальных шансов стать массовым (по причине необходимости кэширования веб-трафика, вне зависимости от того, HTTP он или HTTPS - плата за трафик есть плата за трафик; а также по причине необходимости аудита соединений на предмет защиты от малвари и фишинга), а TLS 1.3 требует предъявления SNI в обязательном порядке.

По этой причине, все мессенджеры, не имеющие встроенной соксификации - в опасности.

MTProto proxy

Существует мнение, что MTProto proxy решает все проблемы с цензурой.

На первый взгляд кажется, что действительно решает. Если ковыряться на L2.

Все, однако, не настолько радужно.

Во-первых, клиент Телеграм постоянно выполняет вот такие обращения к dns.google.com для определения IP прокси по DoH в огромном количестве:

Обратите внимание вот на что.

Мне на данный момент неизвестно ни одно другое приложение, которое в таком количестве обращается к dns.google.com. IP-адреса которого можно в короткое время отресолвить и просто заблокировать. Но проще их заблокировать по SNI (совместимость важнее не только производительности, но и безопасности, как известно. Гугл клиентов терять не собирается и eSNI не использует. И, скорее всего, использовать и не будет. По очевидным причинам).

Кроме того, это очень стрёмная идея - давать проксям имена со словами "proxy" , "socks" и тому подобным. Так как MiTM HTTPS - гораздо проще сделать, чем вам кажется - вы сразу рисуете мишень на лбу. Нет, не у себя. У клиента, который использует прокси. Из СОРМ вытаскивается клиентский IP в данный момент времени - и все, можно посылать автозак.

Попробуем сделать обычный ресолв:
 # dig free.fckrknbot.com  
   
 ; <<>> DiG 9.10.8-P1 <<>> free.fckrknbot.com  
 ;; global options: +cmd  
 ;; Got answer:  
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35037  
 ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1  
   
 ;; OPT PSEUDOSECTION:  
 ; EDNS: version: 0, flags:; udp: 1472  
 ;; QUESTION SECTION:  
 ;free.fckrknbot.com.      IN   A  
   
 ;; ANSWER SECTION:  
 free.fckrknbot.com.   1432  IN   CNAME  fckrkn.socks5.ovh.  
 fckrkn.socks5.ovh.   1435  IN   A    127.0.0.1  
   
 ;; Query time: 0 msec  
 ;; SERVER: 127.0.0.1#53(127.0.0.1)  
 ;; WHEN: Wed Apr 17 17:27:28 +06 2019  
 ;; MSG SIZE rcvd: 94  
   
 root @ khorne / # dig fckrkn.socks5.ovh  
   
 ; <<>> DiG 9.10.8-P1 <<>> fckrkn.socks5.ovh  
 ;; global options: +cmd  
 ;; Got answer:  
 ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 11761  
 ;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1  
   
 ;; OPT PSEUDOSECTION:  
 ; EDNS: version: 0, flags:; udp: 1472  
 ;; QUESTION SECTION:  
 ;fckrkn.socks5.ovh.       IN   A  
   
 ;; ANSWER SECTION:  
 fckrkn.socks5.ovh.   1426  IN   A    127.0.0.1  
   
 ;; Query time: 0 msec  
 ;; SERVER: 127.0.0.1#53(127.0.0.1)  
 ;; WHEN: Wed Apr 17 17:27:37 +06 2019  
 ;; MSG SIZE rcvd: 62     

Диджиталрезистанс кажется сам себе предельно умным и иногда ресолвится в localhost. Очень смешно.

Что ж, пойдем по другому пути.

Ну, во-первых, если по дороге стоит транспарентный прокси L7, то на всех нестандартных портах не разрешен метод CONNECT (это базовые настройки безопасности) и соединиться с прокси у клиента шансов нет вообще. То есть MTProto proxy на non-HTTPS портах сразу же отваливаются сами по себе.

Во-вторых, давайте посмотрим, а какой, собственно, TLS handshake у этих самых MTProto proxy. Выдернем истинный IP:


Опачки, а это вообще не TLS. И, вообще говоря, вполне достаточно включить в Сквиде on_unsupported_protocol (который по дефолту делает terminate; кстати говоря, вы давно сквида видели? :) А он вполне себе развивается) - и все, что nonHTTPS на 443 порту, сразу отправится в бан.

Давайте, однако, поступим умно и не будем охотиться за каждым из MTProto proxy с айпишником наперевес - это контрпродуктивно. И мы не будем напрягаться и лезть в DoH (хотя можно, если очень захотеть. DNS hijacking штука в принципе не особо сложная).

Пришибем их всех - бог отличит своих.

Возьмем вот такой софт. В котором есть TCP Probing (написанный на Си и работающий очень быстро).

И включим вот такую настройку (мы уже знаем, что, ни Телеграм, ни его прокси, прикинуться настоящим TLS не способны и предъявить легитимный сертификат, подписанный легитимным CA, не могут):



Запускаем, проверяем:


Бабах. Вы убиты. Все и сразу.


Но есть и хорошие новости

Николай сделал одну умную вещь. Так как Телеграм соксифицирован, вам никто не мешает локально установить Тор (бриджированный, с obfs4) и локально отфорвардить туда Телеграм:



Это то, до чего никак не додумается Signal (несмотря на неоднократные подсказки их саппорту. Не совсем по теме, но bootstrap Signal выполняется по HTTPS, однако палится по SNI и в Иране подобным методом почти вглухую забанен. Сигнал об этом знает, но его девелуперы ничего с этим поделать не могут - они не умеют в локальный SOCKS5).

Да, Тор медленный и печальный, да, бриджи иногда отваливаются (вы же, конечно, знаете, что Тор умеет бриджи из directory вытягивать автоматом, если вообще хоть до одного может достучаться?).

Но. Обфусцированный Тор действительно сложно обнаружить и пресечь на контролируемом бордере от уровня L2 до уровня L7. 

Ну и OBFS4 реально хорошо прикидывается легитимным HTTP(S) (а не так, как это делает Телеграм и его прокси. Недостаточно просто стоять на 80/443 порту, чтобы за веб-трафик сойти, да).

Finally

Насколько я понимаю, протокол уже легко не изменить (да и не будет его никто менять, пока не поотваливаются несколько миллионов пользователей одной северной страны; а потом будет слишком поздно их затаскивать обратно из Тук-Тук). Так что придется играть теми картами, которые раздали.

Соксификация, в принципе, решает проблему. Надо лишь заблаговременно озаботиться подбором работающих obfs4 бриджей и настройкой Тор на устройствах (потому что потом bridges.torproject.org могут тоже побанить до кучи).

Я хочу выразить отдельное Фе девелуперам censor-proof решений, которые не почесались ни на берегу, ни даже сейчас, проделать тесты на цензуроустойчивость своих - гм! - решений (это относится не только к мессенджерам, но и к коммерческим и около VPN - они тоже элементарно отправляются в бан, либо по сигнатуре, либо по бутстрапу с аутентификацией).

"ТщательнЕй надо, товарищи! ТщательнЕй!" ©

Ну и, пользуясь случаем, хочу заявить публичный FEATURE REQUEST (так как техсаппорт Телеграм не подает признаков жизни от слова "вообще"):

Господа, а вам не кажется, что в режиме включенных прокси было бы неслабо иметь keep alive - вы все равно постоянно стукаетесь в каждый прокси - и, по мере того, как MTProto и другие прокси дохнут, переключаться на работоспособные (из заданного списка) по round robin?

Так же, как это, например, реализовано в Unbound по отношению к вышестоящим форвардинговым пирам?

Насколько мне помнится, такой функционал вроде бы даже заявлялся - но вот беда, в официальном клиенте нигде и никак не работает. Если этого нет - нехилтон бы это сделать. Хотя бы это. В преддверии серьезных работ по блокировке.


Всем добра и чая.

четверг, 11 апреля 2019 г.

C++: thread pool + GC

Многие задавали вопрос, к чему было сделано вот это изменение.

Ну, логично - зачем счетчик спящих воркеров у тред пула? 😅

Представьте себе такую задачу.

Ваш тред пул обмолачивает задания с переменной нагрузкой - задачи для выполнения то есть, то нет. И время от времени - а лучше всего в периоды затишья - вам надо освободить память, которую использовали процессы, выполнявшиеся в пуле. Особенно когда пул работает 24x7x365.

Окей, давайте инкрементировать атомарный счетчик спящих воркеров при засыпании на conditional variable (под локом, и для того, чтобы не использовать строгий инкремент, воспользуемся fetch_add  - нет смысла использовать тяжелый CAS, мы все равно под мьютексом):
     {  
       std::unique_lock<std::mutex> lock(m_conditional_mutex);  
       if (std::exchange(m_ready, false)) continue;  // If post() occurs here, don't sleep  
       #if defined SLEEP_CNT  
       m_sleep_cnt.fetch_add(1, std::memory_order_relaxed);  
       #endif  
       m_conditional_lock.wait(lock, [this] { return std::exchange(m_ready, false); });  
       #if defined SLEEP_CNT  
       m_sleep_cnt.fetch_sub(1, std::memory_order_relaxed);  
       #endif  
     }  

Однако, как мы воспользуемся этим счетчиком в приложении?

Мы хотим в отдельном треде, который крутится сам по себе, при засыпании всего пула, выполнить вызов GC один раз в начале периода затишья (нет смысла его просто периодически выполнять, верно? Как вот здесь в README описывается).

 #if defined LTALLOC_GC  
 #include <atomic>  
 #include <condition_variable>  
 #include <mutex>  
 #endif  
   
 ...  
   
   
      #if defined LTALLOC_GC  
      std::atomic<bool> v_ronce { false };  
      std::thread([&] { for (;;) {  
           std::condition_variable v_cv;  
           std::mutex v_cv_mtx;  
           std::unique_lock<std::mutex> lock(v_cv_mtx);  
           if (v_cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(LTALLOC_GC_TIMEOUT)) == std::cv_status::timeout)  
                if (tp::m_sleep_cnt.load(std::memory_order_relaxed) == v_threads && v_ronce.exchange(false, std::memory_order_acquire))  
                     ltsqueeze(0);/* ltsqueeze run once when all pool workers are sleeping */  
      } }).detach();  
      #endif  
   
      while (condition) { /* Thread pool producer loop */  
           pool.post([]() { processdata(v_par); });  
           #if defined LTALLOC_GC  
           if (!v_ronce.load(std::memory_order_relaxed))  
                std::atomic_store_explicit(&v_ronce, true, std::memory_order_release);  
           #endif  
      }

Нам никак не обойтись без еще одной флаговой переменной, управляющей однократным выполнением вызова GC. Ну и, да, реализуем периодические засыпания треда GC при помощи conditional variable с таймаутом (и для предотвращения spurious wakeups), а не std::this_thread::sleep_for (мне не нравятся такие реализации).

That's all, folks! 😀