Блокировка сайтов при помощи DNS

Материал из RSU WiKi
Перейти к: навигация, поиск
Пример страницы, выдаваемой пользователям при попытке посещения запрещенного сайта

Содержание

Введение

Данная статья описывает настройку DNS-сервера ISC Bind для ограничения доступа к нежелательным ресурсам машин в локальной сети на дистрибутиве SLES 10 SP2. Для работы нам потребуется named, Nginx (или apache2), несколько свободных ip-адресов и acl-файлы прокси-сервера squid с блокируемыми доменами. В примерах рассмотрим блокирование баннерной рекламы и сайтов популярных соц. сетей Vkontakte и Odnoklassniki.

DNS блокировка может пригодиться в случаях больших нагрузок на сеть, если планируется отказаться от прокси-сервера squid или перейти на использование технологии transparent-proxy.

Настройка ISC Bind

Для начала настроим named. Предположим, что у нас уже есть списки блокировки необходимых доменов в формате dstdomain для squid (их можно взять в статье Списки заблокированных сайтов):

social.list

.odnoklassniki.ru
.vkadre.ru
.vkontakte.ru
.vk.com

banners.list

.ads.sup.com
.adv.aport.ru
.adv.computerra.ru
.ads.sixapart.com
.a.gismeteo.ru
.a.ucoz.net
.ad.adriver.ru
.adserver.yahoo.com
.bin-layer.de
.popuptraf.ru
.doubleclick.net
.rotabanner.auto.ru

Сконвертируем данные файлы в формат, пригодный для named при помощи следующего скрипта:

squid2named.sh

#!/bin/bash
 
echo "# Autogenerated blocking zones for bind. Data from squid domain blacklists." > blocks.conf
echo "#" `date` >> blocks.conf
echo "" >> blocks.conf
 
for j in `ls *.list`
 do
  NAME=`echo $j | awk -F "." '{ print $1; }' `
  for i in `cat $j | tr -d "\r" | sed 's/^.//'`
   do
    echo zone "\"$i\"" \{ type master\; file \"master/block-$NAME\"\; allow-query \{ any\; \}\; \}\; >> blocks.conf
   done
done

Получившийся файл blocks.conf кладем в /etc/named.d/, и прописываем его в /etc/sysconfig/named, меняя строку:

NAMED_CONF_INCLUDE_FILES=""

на

NAMED_CONF_INCLUDE_FILES="blocks.conf"

Не забываем запустить SuSEconfig!

Приступаем к созданию файлов-зон для named. Поскольку нам необходимы разные сообщения при блокировках, зон будет две: block-banners и block-social. Если различные сообщения не требуются, можно в принципе обойтись и одной.

Пример зоны-заглушки:

block-banners

$TTL 24h                                                                                                                
                                                                                                                        
@       IN SOA ns.example.com. hostmaster.example.com. (                                                                  
                  2003052802  86400  300  604800  3600 )                                                                
                                                                                                                        
@       IN      NS   ns.example.com.                                                                                     
@       IN      A    192.0.2.200 ; домен
*       IN      A    192.0.2.200 ; все поддомены

Кладем оба получившихся файла зон в каталог /var/lib/named/master. Наступает время заняться apache2.

Сетевая подсистема

Сконфигурируем web-сервер и сетевую подсистему на несколько виртуальных ip-based хостов, на которых у нас будут выводиться сообщения для пользователей.

Идем в /etc/sysconfig/network, находим интересующий нас интерфейс и прописываем необходимое количество alias'ов (в нашем примере 2 шт):

ETHERDEVICE='eth0'                                                                                                    
IPADDR='192.0.2.1'                                                                                               
NETMASK='255.255.255.0'                                                                                             
NETWORK='192.0.2.0'                                                                                              
BROADCAST='192.0.2.255'                                                                                            
VLAN='YES'                                                                                                            
STARTMODE='onboot'                                                                                                    
                                                                                                                      
IPADDR_banhit='192.0.2.200'
NETMASK='255.255.255.0'
LABEL_banhit='banhit'
                                                                                                                      
IPADDR_sochit='192.0.2.201'
NETMASK='255.255.255.0'
LABEL_banhit='sochit'

Выполняем команду:

service network restart

и проверяем, поднялись ли интерфейсы:

ifconfig

Настройка web-сервера

Вариант I: Apache 2

В apache2 настраиваем ip-based virual hosts. В директории /etc/apache2/vhosts.d копируем файл-пример vhost.template в файлы 192.0.2.200.conf и 192.0.2.201.conf. Настроенный файл:

<VirtualHost 192.0.2.200:80>
    ServerAdmin webmaster@example.com
    ServerName hit-banner.example.com

    DocumentRoot /srv/www/vhosts/192.0.2.200

    ErrorLog /var/log/apache2/hit-banner.example.com-error_log
    CustomLog /var/log/apache2/hit-banner.example.com-access_log combined

    HostnameLookups Off
    UseCanonicalName Off
    ServerSignature Off

    <Directory "/srv/www/vhosts/192.0.2.200">
	Options Indexes FollowSymLinks   
	AllowOverride All
	Order allow,deny
	Allow from all
    </Directory>
</VirtualHost>

Создаем в /srv/www/ поддиректории vhosts/192.0.2.200/ и vhosts/192.0.2.201/ и раскладываем по директориям файлы с описанием блокировок [1] не забывая переопределить ошибку 404 на /index.html:

.htaccess

ErrorDocument 404 /index.html

Вариант II: Nginx

В Nginx настраиваем ip-based virual hosts. В директории /etc/nginx/vhosts.d создаем файлы 192.0.2.200.conf и 192.0.2.201.conf. Настроенный файл:

server {
        listen       192.0.2.200:80;
        server_name  hit-banner.example.com;

        location / {
            root   /srv/www/vhosts/192.0.2.200/;
            index  index.html;
        }

        error_page  404              /index.html;

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /srv/www/htdocs/;
        }

        location ~ /\.ht {
            deny  all;
        }
    }

Создаем в /srv/www/ поддиректории vhosts/192.0.2.200/ и vhosts/192.0.2.201/ и раскладываем по директориям файлы с описанием блокировок [1]. Ошибка 404 уже переопределена на /index.html в конфигурационном файле виртуального хоста Nginx.

Перезапускаем named и apache2/nginx:

service apache2 restart
service nginx restart
service named reload

Для проверки необходимо очистить на клиентской машине кэш DNS (How do I Flush DNS?) и пропинговать интересующий хост:

ping vk.com

Веб-интерфейс для работы со списками

/srv/www/htdocs/modbanlists/index.php

<?php
    function writeToFile($name, $content)
    {
        $file = fopen($name, "w");
        if (!$file)
            return "-4";
        fputs($file, $_POST["content"]);
        fclose($file);
        return "0";
    }
    function makeBackupFile($name, $content = "")
    {
        if (empty($content))
        {
            $content = readFromFile($name);
            switch ($content)
            {
                case "-1":
                    return "-1";
                case "-2":
                    return "-2";
            }
        }
        $date = date("Y-m-d", time());
        $number = getBackupsAmount($date);
        $backupName = "$name.$date.$number";
        $fileBackup = fopen($backupName, "w");
        if (!$fileBackup)
            return "-3";
        fputs($fileBackup, $content);
        fclose($fileBackup);
        return "0";
    }
    function readFromFile($name)
    {
        if (!file_exists($name))
            $content = "-1";
        $file = fopen($name, "r");
        if (!$file)
            $content = "-2";
        $content = "";
        while ($buffer = fgets($file))
        {
            $content .= $buffer;
        }
        fclose($file);
        return $content;
    }
    function getBackupsAmount($date)
    {
        if (empty($date))
            return "EMPTY_DATE_ERROR";
        $pattern = ".list.$date";
        $dir = opendir(".");
        $amount = 0;
        while ($file = readdir($dir))
            if (substr_count($file, $pattern) > 0)
                $amount++;
        closedir($dir);
        return $amount;
    }
    function getFiles()
    {
        $dir = opendir(".");
        $files = array();
        while ($file = readdir($dir))
            if (preg_match("/\.list$/i", $file))
                array_push($files, $file);
        closedir($dir);
        return $files;
    }
    $message = "Выбираем список";
    $mode = "list";
    if (!empty($_POST["name"]))
    {
        $mode = "open";
        if (isset($_POST["previousName"]) && $_POST["name"] == $_POST["previousName"])
            $mode = "edit";
    }
    switch ($mode)
    {
        case "list":
            break;
        case "open":
            if (!empty($_POST["name"]))
            {
                $name = $_POST["name"];
                $content = readFromFile($name);
                switch ($content)
                {
                    case "-1":
                        die("File \"$name\" doesn`t exists");
                    case "-2":
                        die("Can`t open file \"$name\" for reading");
                    default: $message = "Content has been read from file \"$name\"";
                }
            }
            else
                $message = "File name hasn`t been given";
            break;
        case "edit":
            if (!empty($_POST["name"]))
            {
                $name = $_POST["name"];
                $result = makeBackupFile($name);
                switch ($result)
                {
                    case "-1":
                        die("File \"$name\" doesn`t exists");
                    case "-2":
                        die("Can`t open file \"$name\" for reading");
                    case "-3":
                        die("Can`t open backup file for writing");
                }
                $message = "Backup done";
                if (empty($_POST["content"]))
                    die("New content is empty");
                $content = $_POST["content"];
                $result = writeToFile($name, $content);
                switch ($result)
                {
                    case "-4":
                        die("Can`t open file \"$name\" for writing");
                }
                $message .= ". File saved";
            }
            else
                $message = "File name hasn`t been given";
            break;
    }
    $files = getFiles();
    $options = "";
    foreach ($files as $file)
        if ($file == $name)
            $options .= "<option selected=\"selected\" value=\"$file\">$file</option>";
        else
            $options .= "<option value=\"$file\">$file</option>";
    if ($mode == "edit")
        $name = "";
?>
<html>
    <head>
        <title>ЦУП: Банлисты</title>
        <style type="text/css">
	    body {
		background: #5A643C url(page_bg.png) 0 0 repeat;
		padding: 0px; margin: 0px;
	    }
	    div#page {
		background: url(page_body_bg.png) 0 0 repeat-x;
		width: 100%;
		padding-top: 5pt;
	    }
            div
            {
                width: 350px;
            }
            #name,
            #content
            {
                width: 100%;
                border: 1px outset #999999;
            }
            #content
            {
                height: 300px;
            }
            #submit
            {
                text-align: right;
            }
            #submitButton
            {
                width: 125px;
            }
            #background
            {
                padding: 10px;
                background: #cccc66;
            }
            .Field
            {
                /*width: 400px;*/
                margin: 0px auto;
            }
            .Border01,
            .Border02
            {
                height: 1px;
                overflow: hidden;
            }
            .Text,
            .Border02
            {
                background-color: #b4b476;
                border: 1px solid #b4b476;
            }
            .Border01
            {
                margin: 0px 2px;
                background-color: #b4b476;
            }
            .Border02
            {
                border-width: 0px 1px;
                margin: 0px 1px;
            }
            .Text
            {
                padding: 0px 1px;
            }
        </style>
    </head>
    <body>
	<div id="page">
        <div class="Field">
            <div class="Border01"></div>
            <div class="Border02"></div>
            <div class="Text">
                <h1>ЦУП: Банлисты</h1>
                <h3><?php print $message; ?></h3>
                <form action="." method="post">
                    <div><input type="hidden" name="previousName" value="<?php print $name; ?>"></div>
                    <?php if ($mode != "open") { ?><div><select id="name" name="name"><?php print  $options; ?></select></div><?php } else {?><div><input type="hidden" name="name" value="<?php print  $name; ?>"></div><?php } echo "\n"; ?>
                    <?php if (!empty($name)) { ?><div><textarea id="content" name="content"><?php print  $content; ?></textarea></div><?php } echo "\n"; ?>
                    <div id="submit"><input id="submitButton" type="submit" value="Жать сюда" /></div>
                </form>
            </div>
            <div class="Border02"></div>
            <div class="Border01"></div>
        </div>
        <!--<div id="background">
        <h1>Ban lists administration</h1>
        <h3><?php print  $message; ?></h3>
        <form action="." method="post">
            <div><input type="hidden" name="previousName" value="<?php print  $name; ?>"></div>
            <div><select id="name" name="name"><?php print  $options; ?></select></div>
            <?php if (!empty($name)) { ?><div><textarea id="content" name="content"><?php print  $content; ?></textarea></div><?php } echo "\n"; ?>
            <div id="submit"><input id="submitButton" type="submit" value="Submit" /></div>
        </form>
        </div>-->
	<?php if ($mode != "open") { ?>
	<div>&nbsp;</div>
        <div class="Field">
            <div class="Border01"></div>
            <div class="Border02"></div>
            <div class="Text">
                <h3>Коротко о главном</h3>
		<dl style="font-size: 8pt; font-family: Arial, Tahoma;">
		 <dt><b>anonymizer.list</b></dt>
		 <dd><i>анонимные прокси и морды шифрованного веб-серфинга, домены</i></dd>
		 <dt><b>banner.list</b></dt>
		 <dd><i>баннерные сети, домены</i></dd>
		 <dt><b>games.list</b></dt>
		 <dd><i>он-лайн гамье, домены</i></dd>
		 <dt><b>porno.list</b></dt>
		 <dd><i>порносайты, домены</i></dd>
		 <dt><b>social.list</b></dt>
		 <dd><i>соц.сети, домены</i></dd>
		 <dt><b>malicious.list</b></dt>
		 <dd><i>вирня и трояны, домены</i></dd>
		 <dt><b>jihad.list</b></dt>
		 <dd><i>список ФСБ, домены</i></dd>
		</dl>
		<p><b>ВАЖНО</b>: сервер подхватывает изменения, сортирует списки алфавитно и удаляет дубликаты самостоятельно по мере необходимости, но не чаще чем раз в час.</p>
            </div>
            <div class="Border02"></div>
            <div class="Border01"></div>
        </div>
	<?php } ?>
	</div>
    </body>
</html>

Cron

В случае, если наполнение списков происходит автоматизированно или через веб-интерфейс, изменения в файлах мониторятся спец. скриптами в cron:

/etc/cron.hourly/named_check_lists

#!/bin/bash
 
# Скрипт для проверки наличия изменений и сортировки списков
# с блокированными доменами для прокси сервера Squid
 
qfile="/var/tmp/named.q"
 
cd /srv/www/htdocs/modbanlists/
 
# Checking if any list was edited & modifying qfile
 
for i in `ls *.list`
do
 file="/srv/www/htdocs/modbanlists/$i"
 fileatime="/srv/www/htdocs/modbanlists/$i.time"
 logfile="/var/log/named-watchdog.log"
 a=""
 b=""
 
 [ ! -f $fileatime ] && stat $file | grep Modify > $fileatime || : [ -f $fileatime ] && a="$(cat $fileatime)" || exit 1
 b="$(stat $file | grep Modify)"
 if [ "$a" != "$b" ]; then
    echo "Sorting $i & removing duplines..."
    #sort $i | uniq > $i.s && mv $i.s $i
    cat $i | tr "[:upper:]" "[:lower:]" | tr -d "\r" | sort | uniq > $i.s && mv $i.s $i
    echo "Modifying time for $i..."
    # update time
    stat $file | grep Modify > $fileatime
    chown wwwrun.www $i
    chmod a+rw $i
    date  > $qfile
 fi
done
 
# Данный скрипт не производит перезагрузку прокси сервера самостоятельно,
# он должен использоваться в связке со скриптом squid-autorelaoder,
# который отслеживает изменения файла $qfile


named_autoreloader

#!/bin/bash
 
# В скрипт, который должен передавать этому флаг перезагрузки SQUID
# надо вставить вот эти две строки:
#
# qfile="/var/tmp/squid_chekmodify.txt"
# date  > $qfile
 
 qfile="/var/tmp/named.q"
 qfileatime="$qfile.time"
 
# Checking qfile & reloading squid if needed
 
 file=$qfile
 fileatime=$qfileatime
 logfile="/var/log/named-watchdog.log"
 
 [ ! -f $fileatime ] && stat $file | grep Modify > $fileatime || : [ -f $fileatime ] && a="$(cat $fileatime)" || exit 1
 b="$(stat $file | grep Modify)"
 if [ "$a" != "$b" ]; then
    echo "Restarting server...."
#    rcsquid reload
    /opt/squid2named/do-web.sh
    # update time
    stat $file | grep Modify > $fileatime
 
    echo "----" >> $logfile
    date >> $logfile
    echo "Named reloaded by autoreloader watchdog:" >> $logfile
 fi


/opt/squid2named/do-web.sh

#!/bin/bash
 
BC="/opt/squid2named/blocks.conf"
 
echo "Creating config from sources..."
 
echo "# Autogenerated blocking zones for bind. Data from squid domain blacklists." > $BC
echo "#" `date` >> $BC
echo "" >> $BC
 
cd /srv/www/htdocs/modbanlists/
for j in `ls *.list`
 do
  cat $j | tr "[:upper:]" "[:lower:]" | sort | uniq > $j.s && mv $j.s $j
  NAME=`echo $j | awk -F "." '{ print $1; }' `
  for i in `cat $j | tr -d "\r" | sed 's/^.//'`
   do
    echo zone "\"$i\"" \{ type master\; file \"master/block-$NAME\"\; allow-transfer \{ uni-secondary\; \}\; allow-query \{ university\; \}\; \}\;
 >> $BC
   done
done
 
echo "Copying config & reloading named..."
cp $BC /etc/named.d/blocks.conf
rcnamed reload

Appendixes

A: Преобразование списков EasyList

Для наполнения локальных списков можно воспользоваться списками EasyList. Рассмотрим процесс преобразования в формат dstdomain на примере списка EasyPrivacy:

wget --no-check-certificate https://easylist-downloads.adblockplus.org/easyprivacy.txt
grep "||" easyprivacy.txt | grep -v "/" | grep -v "*" | awk -F "[||,^]" '{ print "."$3; }' | \
grep -v '^\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}\.[0-9]\{1,3\}$' | \
sort > easypriv2dstdomain.txt

Получившийся файл содержит список доменов в формате dstdomain, пригодном для подключения к прокси-серверу или системе DNS-фильтрации.

При желании процесс можно поместить в cron и вести обновление списков автоматически.

Примечания

  1. 1,0 1,1 foboss.livejournal.com: Обратная связь с пользователями — великая вещь!

См. также

Ссылки

Личные инструменты
Пространства имён

Варианты
Действия
Навигация
Инструменты