Фильтрация запрещенного контента из реестра РКН

Хочу в 100500 раз выразить благодарность OpenSource разработчикам.

Ввиду того что стоит старое железо НЕ поддерживающее инструкции SSE3 , SSE4 , было предусмотрено установить именно nfqfilter для временной блокировки с последующей сборкой новой железки , с поддержкой данных инструкций. Собрали временную железяку , достаточно не плохую для UPLINK в 700 мб/c. Данная инструкция написана для себя дабы не потерять и не забыть. Поехали!!

ВНИМАНИЕ! Данный проект больше не поддерживается. Рекомендуется использовать extFilter https://github.com/max197616/extfilter

Программа компилируется и работает под CentOS 7. Для сборки программы необходимы следующие программы и библиотеки:

 libnetfilter_queue
 libnfnetlink
 Poco >= 1.6
 nDPI = 1.7-stable (ставится автоматически с github с наложением необходимых патчей)
 git
 patch

Ставится софтина на CentOS 7 , но так как мне не хочется плодить зоопарк в сети провайдера , я собрал это все на Debian 8. Почему 8 ? Так исторически сложилось =) Идем далее! Скачиваем nDPI 1.7 — ВНИМАНИЕ! Именно 1.7 как рекомендует разработчик софта!

cd ./nDPI-1.7
./autogen.sh
make
make install

Устанавливаем libpoco 1.6:

apt-get install make g++ openssl libssl-dev unixodbc-dev libmysqlclient-dev
cd ./poco-1.6.1
./configure
make
make install

Ставим сам nfqfilter:

apt install libnetfilter-queue-dev libnfnetlink-dev
git clone https://github.com/max197616/nfqfilter.git
cd nfqfilter
./autogen.sh
./configure
make
make install

Скопируем nfqfilter.service и добавим в автозагрузку системы:

cd /opt
cp nfqfilter/etc/systemd/nfqfilter.service /lib/systemd/system/nfqfilter.service
chmod 644 /lib/systemd/system/nfqfilter.service 
systemctl daemon-reload
systemctl enable nfqfilter

Далее редактируем конфиг nano /usr/local/etc/nfqfilter.ini . Пример конфига:

; номер очереди
queue = 0
; файл с доменами для блокировки
domainlist = /opt/zapret/db/domains
; файл с url для блокировки
urllist = /opt/zapret/db/urls
; файл с ssl доменами для блокировки
ssllist = /opt/zapret/db/ssl_host
; файл с ip:port для блокировки
hostlist = /opt/zapret/db/hosts

; куда редиректить в случае наличия в списках
redirect_url = http://zapret.pit.su/
; HTTP код ответа. default: 302 Moved Temporarily
http_code = 302 Moved Temporarily
; Что добавлять в redirect_url, line - строка из файла url, url - запрещенный url, none - ничего
;url_additional_info=url

; дополнительные порты для протоколов (nDPI)
protocols = /opt/zapret/db/protos
; периодичность вывода статистики в лог, секунд
statistic_interval = 300
; если установлено в true, то сам nfqfilter отправляет tcp rst клиенту и серверу (тогда mark_value не используется) в случае фильтрации https и ip:port
send_rst = true
; каким значением метить пакеты для iptables в случае наличия в списках ssl и hosts
mark_value = 17
; количество обрабатываемых пакетов (default: 1024)
; max_pending_packets = 1024
; сохранять пакеты ошибками в /tmp
; save_bad_packets = false
; блокировать ssl client hello по первому пакету, если в ssl пакете нет server_name. Обязательно наличие файла sslips с ip адресами.
; block_undetected_ssl = false
; Путь к файлу с ip адресами для блокировки ssl по ip, если в ssl пакете нет server_name (block_undetected_ssl = true)
sslips = /opt/zapret/db/ssl_ips
; перевод host: в строчные символы
; lower_host = false
;
; match_url_exactly = false
; Если в url встречаются escape последовательности символов 0-9, a-z, A-Z, то переводить их в символы
; url_decode = false
; Количество потоков обработки пакетов
; num_threads = 2

[logging]
loggers.root.level = information
;loggers.root.level = debug
loggers.root.channel = fileChannel
channels.fileChannel.class = FileChannel
channels.fileChannel.path = /var/log/nfqfilter.log
channels.fileChannel.rotation = 1 M
channels.fileChannel.archive = timestamp
channels.fileChannel.formatter.class = PatternFormatter
channels.fileChannel.formatter.pattern = %Y-%m-%d %H:%M:%S.%i [%P] %p %s - %t
channels.fileChannel.formatter.times = local

Далее в iptables создаём:

iptables -t mangle -N ZAPRET_GOV
iptables -t mangle -A ZAPRET_GOV -m connbytes --connbytes-mode bytes --connbytes-dir both --connbytes 100000 -j RETURN
iptables -t mangle -A ZAPRET_GOV -p tcp --dport 80 -j NFQUEUE --queue-num 0
iptables -t mangle -A ZAPRET_GOV -p tcp --dport 443 -j NFQUEUE --queue-num 0
iptables -t mangle -A PREROUTING -j ZAPRET_GOV

В FORWARD:

ipset create zapret_gov_ssl_ip hash:net maxelem 4000000 hashsize 250000 -exist
iptables -A FORWARD -m mark --mark 10 -p tcp -j REJECT --reject-with tcp-rst
iptables -A FORWARD -m set --match-set zapret_gov_ssl_ip dst -p tcp -j REJECT --reject-with tcp-rst

С настройкой NFQ вроде бы все! Для того что бы сформировать для нее списки блокировки ресурсов я использую zapret + extfilter_maker , так как nfqfilter_config затыкается на непонятном этапе. Суть та же самая что у одной что у другой софтины , файлы одинаковые создаются.

Следующий момент по установке zapret и сборке файлов в кучу. Для этого я поместил все в одну папку /opt/zapret и своял скрипт который будет по cron за меня все делать в положенное время. Приступим!

cd /opt
git clone https://github.com/max197616/zapret.git

Доставим недостающие библиотеки в систему для работы zapret.pl

apt-get install apache2-dev
cpan install XML::LibXML::Reader File::Basename File::Spec Config::Simple File::Basename Getopt::Long Log::Log4perl Net::IP  Net::SMTP Email::MIME PerlIO::gzip MIME::Base64 File::Path File::Copy SOAP::Lite Devel::Size Log::Dispatch::FileRotate;

Ставим MySQL после чего заходим и создаем пользователя отдельного и базу данных следующими командами !

apt-get install mysql-server 
mysql -uroot -p
mysql> create database zapret;
mysql> GRANT ALL PRIVILEGES ON zapret.* TO zapret@localhost IDENTIFIED BY 'supermegapass' WITH GRANT OPTION;
mysql> quit
mysql -uzapret -p zapret < /opt/zapret/zapret.sql

База подготовлена , осталось запустить zapret.pl. Ах да , нужно еще собрать поддержку ГОСТ 2012 для openssl 1.1.0l (я выбрал последний из этой ревизии).

# Debian 8
# OpenSSL 1.1.0l 2 Nov 2017
# curl 7.58.0 (x86_64-pc-linux-gnu)

# Компилим GOST-engine
sudo apt install cmake libssl-dev
git clone --branch=openssl_1_1_0 https://github.com/gost-engine/engine.git gost-engine/engine
cd gost-engine/engine
cmake .
make

# узнаем нужную директорию, копируем туда
openssl version -e
cp bin/gost.so /usr/lib/x86_64-linux-gnu/engines-1.1

# конфиг
cp /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf.original
nano /etc/ssl/openssl.cnf

# в начало файла
openssl_conf = openssl_def

# в конец
[openssl_def]
engines = engine_section

[engine_section]
gost = gost_section

[gost_section]
engine_id = gost
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so
default_algorithms = ALL
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet

# включаем новый конфиг
export OPENSSL_CONF=/etc/ssl/openssl.cnf
# проверяем версию
openssl version
# проверяем подключилась ли библиотека 
openssl engine

На этом все. Конвертируем полученный на Windows PKCS#12 в PEM:

openssl pkcs12 -in p12.pfx -out cert.pem -nodes -clcerts

Кладем его в /opt/zapret/key (ну это лично мое пожелание). Вообще в zapret должен лежать файл в корне исполняемого файла , но так как ключ надо обновлять каждый год я вот решил сделать так , перед этим поправив строчку ниже.

# Заменил строчку my $openssl_bin_path=
my $openssl_bin_path="/usr/bin/"

# строчку 
`$openssl_bin_path/openssl smime -sign -in $req_file -out $sig_file -binary -signer $dir/cert.pem -outform DER`;
# заменил на 
`$openssl_bin_path/openssl smime -sign -in $req_file -out $sig_file -binary -signer $dir/key/cert.pem -outform DER`;

Ах да! Сам конфиг то zapret’a!

[DB]
host = 'localhost'
user = 'zapret'
password = 'supermegapass'
name = 'zapret'

[API]
url = 'http://vigruzki.rkn.gov.ru/services/OperatorRequest/?wsdl'
form_request = 1

[PATH]
req_file = "req.xml"
sig_file = "req.xml.sig"
template_file = "req_template.xml"
our_blacklist = /opt/zapret/dump/our_blacklist
# Путь к каталогу, где будет архив полученных файлов. Архив будет создаваться в виде:
# yyyy
#   yyyy-mm
#     yyyy-mm-dd
#       arch-yyyy-mm-dd-hh_mm_ss.zip
archive = /opt/zapret/dump

[NS]
resolve = 1
resolvers = x.x.x.x, 8.8.8.8
# резолвить ли хосты в ipv6 адреса
ipv6_support = yes
# сохранять ли уже отрезолвленные записи
keep_resolved = true

[MAIL]
to = 'test1@test.ru' , 'test2@test.ru'
from = 'zapret@test.ru'
server = 'smtp.test.ru'
port = 25
auth = 1
login = 'zapret@test.ru'
password = 'parolotpochte'
excludes = 1
new = 1
new_ips = 1
removed = 1
removed_ips = 1
# Mail if there is only subnet without domains/urls in content block:
alone = 1

Запускаем zapret.pl что бы он отработал , он выгрузит архив и занесет в базу данных соответствующие записи.

git clone https://github.com/max197616/extfilter.git
mv extfilter/scripts/extfilter-maker/ extfilter_make/ & rm -rf extfilter/
cpan install Net::CIDR::Lite;

И создаем папку с файлами:

mkdir db
touch db/domains db/urls db/ssl_host db/hosts db/protos db/ssl_ips

Следующим этапом выгружаем из базы в файл , предварительно перенеся его в /opt/zapret, запуском extfilter_maker.pl , предварительно отредактировав конфиг:

[DB]
# адрес сервера mysql
host = 'localhost'
# имя пользователя
user = 'zapret'
# пароль
password = 'supermegapass'
# имя БД
name = 'zapret'

[APP]
# блокируемые домены
domains = db/domains
# блокируемые urlы
urls = db/urls
# блокируемые ssl хосты
ssls = db/ssl_host
# блокируемые ip:port
hosts = db/hosts
# дополнительные порты для ndpi
protocols = db/protos
# ip адреса для блокировки ssl, если нет имени хоста в сертификате
ssls_ips = db/ssl_ips
# из списка доменов заносим так же в список ssl
domains_ssl = true

# выгружать в ssls_ips только ip адреса, указанные в реестре.
#only_original_ssl_ip = false

# Замена специальных символов. Если в extfilter используется нормализация url, то включать не надо.
#make_sp_chars = false

# Выгружать ip для полной блокировки в режиме моста
#ips_to_hosts = false
# Выгружать сети для полной блокировки в режиме моста
#nets_to_hosts = false

Выложу правленое все на google disk, чтоб не потерять и не забыть. Следом запускаем сам nfqfilter и вуаля , плов готов!
Сам скрипт автоматизации не замысловатый =)

#!/bin/sh

log_zap="/var/log/zapret_update.log"
log_ext="/var/log/extfilter_maker.log"
logfile="/var/log/rkn.log"
string_desc="# "
string_date=$(date)
#конкатенация строк
string=$string_desc$string_date
echo $string > $logfile
echo "========Запускаем скрипт ZAPRET========" >> $logfile
sudo ./zapret.pl
tail -n 10 $log_zap >> $logfile
echo "========Запуск формирования списков========" >> $logfile
sudo ./extfilter_maker.pl
echo "fz139.ttk.ru" >> db/domains
tail -n 10 $log_ext >> $logfile
/etc/init.d/nfqfilter stop
echo "База данных РКН успешно обновлена" >> $logfile

P.S. Я это дело еще все мониторю для эффективности знаменитым monit. Конфиг для примера:

user@nat:~$ cat /etc/monit/conf.d/nfqfilter.conf
check process nfqfilter with pidfile /var/run/nfqfilter.pid
    start program = "/etc/init.d/nfqfilter start"
    stop program = "/etc/init.d/nfqfilter stop"

Рекомендуемый файл конфигурации /etc/monit/monitrc:
cat /dev/null > /etc/monit/monitrc
nano /etc/monit/monitrc

set daemon 1
  with start delay 1

set logfile /var/log/monit.log
set idfile /var/lib/monit/id
set statefile /var/lib/monit/state

set httpd port 2812 and
  use address localhost
  allow localhost

include /etc/monit/conf.d/*

/etc/init.d/monit restart

Управление процессом:

monit reload — Перезагрузить конфигурацию
monit summary — Вывести информацию о состоянии

Пример файла /etc/init.d/nfqfilter :

user@nat:~$ cat  /etc/init.d/nfqfilter
#!/bin/sh
### BEGIN INIT INFO
# Provides:          nfqfilter
# Required-Start:    $network $syslog $named
# Required-Stop:     $network $syslog $named
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: NFQFilter для фильтрации трафика по требованию РКН
### END INIT INFO

if [ -z "$1" ] ; then
    echo "Usage: $0 [start|stop|restart]"
    exit 0
fi

APP="nfqfilter"
BINFILE="/usr/local/bin/$APP"
PIDFILE="/var/run/$APP.pid"
LOGFILE="/var/log/$APP.log"
CFGFILE="/usr/local/etc/$APP.ini"

if [ "$1" = "start" ]; then
    if [ -f $PIDFILE ]; then
        kill $(cat $PIDFILE)
        sleep 1
    fi
    ulimit -n 65536
    $BINFILE --daemon --pidfile=$PIDFILE -c $CFGFILE
elif [ "$1" = "stop" ]; then
    if [ -f $PIDFILE ]; then
        kill $(cat $PIDFILE)
    fi
elif [ "$1" = "restart" ]; then
    /bin/kill -HUP $(cat $PIDFILE)
else
    exec $0
fi
exit 0

Плюс вывел на комплексный экран сам лог файл через Zabbix-Agent, скриншоты внизу: