среда, 14 октября 2015 г.

Solaris: DNSCrypt High Availability

Хотя возможность использования DNSCrypt в отказоустойчивой конфигурации (в сочетании с кэширующим DNS), в общем, известна, большинство пользователей, не умея писать скрипты, с нетерпением ждут окошечной гуёвой версии отказоустойчивого DNSCrypt.

Между тем, все достаточно просто.

Честно говоря, мне было лениво писать для Solaris настоящий отказоустойчивый SMF, с non-default instances, поэтому я поступил немного проще.

Так как dnscrypt-proxy демонстрирует завидную стабильность как сервис, то я просто тупо и цинично написал SMF из трех файлов, позволяющий запустить перед Unbound любое произвольное число пиров dnscrypt, на разных прослушивающих портах.

Предварительно соберем dnscrypt-proxy из исходников в 64 битах (напоминаю, что пререквизитами являются libsodium и libevent):

 После сборки libsodium в /usr/local/lib и ПЕРЕД сборкой dnscrypt необходимо (Solaris):  
   
 # 32 bit  
 crle -u -l /usr/local/lib  
 crle -u -l /opt/csw/lib  
 crle -c /var/ld/ld.config -l /lib:/usr/lib:/usr/local/lib:/opt/csw/lib  
   
 # 64 bit  
 crle -64 -u -l /usr/local/lib  
 crle -64 -u -l /opt/csw/lib/64  
 crle -64 -c /var/ld/64/ld.config -l /lib/64:/usr/lib/64:/usr/local/lib:/opt/csw/lib/64:/usr/sfw/lib/64  
   
 и лишь затем делать ./configure && gmake && gmake install-strip  
   
 Также может потребоваться установка libevent (если ее еще нет в системе).  
   
 # 32 bit GCC  
 ./configure 'CFLAGS=-O3 -m32'  
   
 # 64 bit GCC  
 ./configure 'CFLAGS=-O3 -m64'  
   
 gmake && gmake install-strip  


Затем напишем три файла.



  • /etc/dnscrypt.conf
 # Base dnscrypt installation directory  
 BASE_DIR="/usr/local"  
   
 # Local address  
 LOCAL_ADDRESS="127.0.0.1"  
   
 # Active requests  
 # Default 250  
 ACTIVE_REQUESTS=4096  
   
 # Specify resolvers names from list.  
 # HA option. List element: <resolver_name>:<local_peer_port>  
 RESOLVERS="4armed:5551 cloudns-can:5552 cloudns-syd:5553 soltysiak:5554 dnsmachine.net-de:5555"  
   
 # Change full path to resolvers CSV if it in non-standard location or name  
 RESOLVERS_LIST="$BASE_DIR/share/dnscrypt-proxy/dnscrypt-resolvers.csv"  
   
 # TCP only flag. Leave empty if not set.  
 # Set to 1 for turn on (if UDP blocking).  
 # Beware, tcp only is SLOWER.  
 USE_TCP_ONLY=""  
   
 # A value below or equal to 512 will disable this mechanism,   
 # unless a client sends a packet with an OPT section providing a payload size.  
 EDNS_PAYLOAD_SIZE="4096"  
   
 # Ephemeral public keys for every query (1.5.x and above)  
 # Default is -E (enabled) or --ephemeral-keys  
 # Leave blank to disable  
 EPHEMERAL_KEYS="--ephemeral-keys"  
   
 # Log level.  
 # 0 - disable  
 LOG_LEVEL="0"  
 #####  

В переменной RESOLVERS задаются ресолверы из списка dnscrypt-resolvers.csv, в виде пары "имя:порт", где "порт" - значение локально прослушиваемого порта, куда будут смотреть настройки форвардинга локального кэширующего DNS. Сколько ресолверов зададите - такая степень отказоустойчивости и будет.
  • /var/svc/manifest/network/dnscrypt-ha.xml
 <?xml version="1.0"?>  
 <!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">  
 <!--  Manifest-file for dnscrypt-ha, put this file in  
     /var/svc/manifest/network/dnscrypt-ha.xml   
     and run #svccfg import /var/svc/manifest/network/dnscrypt-ha.xml  
     Fixed by Yuri Voinov (C) 2007,2015  
 -->  
 <service_bundle type='manifest' name='dnscrypt-ha'>  
   
 <service  
     name='network/dnscrypt-ha'  
     type='service'  
     version='1'>  
   
     <create_default_instance enabled='false' />  
   
     <single_instance />  
   
     <dependency name='fs-local'  
         grouping='require_all'  
         restart_on='none'  
         type='service'>  
         <service_fmri  
             value='svc:/system/filesystem/local' />  
     </dependency>  
   
     <dependency name='net-loopback'  
         grouping='require_all'  
         restart_on='none'  
         type='service'>  
         <service_fmri value='svc:/network/loopback' />  
     </dependency>  
   
     <dependency name='net-physical'  
         grouping='require_all'  
         restart_on='none'  
         type='service'>  
         <service_fmri value='svc:/network/physical' />  
     </dependency>  
   
     <dependency name='utmp'  
         grouping='require_all'  
         restart_on='none'  
         type='service'>  
         <service_fmri value='svc:/system/utmp' />  
     </dependency>  
   
     <dependency name='dnscrypt-ha-config'  
         grouping='require_all'  
         restart_on='refresh'  
         type='path'>  
         <service_fmri value='file://localhost/etc/dnscrypt-ha.conf' />  
     </dependency>  
   
     <exec_method  
         type='method'  
         name='start'  
         exec='/lib/svc/method/init.dnscrypt-ha %m'  
         timeout_seconds='60'/>  
   
     <exec_method  
         type='method'  
         name='stop'  
         exec=':kill'  
         timeout_seconds='60' />  
   
     <exec_method  
         type='method'  
         name='restart'  
         exec='/lib/svc/method/init.dnscrypt-ha %m'  
         timeout_seconds='60' />  
   
     <property_group name='startd'  
         type='framework'>  
         <!-- sub-process core dumps shouldn't restart session -->  
         <propval name='ignore_error'  
           type='astring' value='core,signal' />  
     </property_group>  
   
     <property_group name='general' type='framework'>  
         <!-- to start stop squid -->  
         <propval name='action_authorization' type='astring'  
             value='solaris.smf.manage' />  
     </property_group>  
   
     <stability value='Unstable' />  
   
     <template>  
         <common_name>  
             <loctext xml:lang='C'>  
             dnscrypt HA service  
             </loctext>  
         </common_name>  
     </template>  
   
 </service>  
   
 </service_bundle>  

  • /lib/svc/method/init.dnscrypt-ha

И затем сам управляющий метод:

 #!/sbin/sh  
   
 #  
 # Control Method for dnscrypt-ha (/lib/svc/method/init.dnscrypt-ha)  
 # Written by Yuri Voinov (C) 2007,2015  
 #  
 # ident "@(#)dnscrypt-ha.sh  2.4  11/10/15 YV"  
 #  
   
 . /lib/svc/share/smf_include.sh  
 . /lib/svc/share/net_include.sh  
   
 #############  
 # Variables #  
 #############  
   
 # Config file, by default,  
 # finds in /etc. Lines, commented with #  
 # in config file, will be skipped  
 config_file="/etc/dnscrypt-ha.conf"  
   
 pid_base="/tmp"  
   
 #  
 # OS Commands location variables  
 #  
 CAT=`which cat`  
 CUT=`which cut`  
 ECHO=`which echo`  
 KILL=`which kill`  
 RM=`which rm`  
 UNAME=`which uname`  
   
 # OS release  
 OS_VER=`$UNAME -r|$CUT -f2 -d"."`  
 OS_NAME=`$UNAME -s|$CUT -f1 -d" "`  
   
 ###############  
 # Subroutines #  
 ###############  
   
 check_os ()  
 {  
  # Check OS version  
  if [ ! "$OS_NAME" = "SunOS" -a ! "$OS_VER" -lt "10" ]; then  
  $ECHO "ERROR: Unsupported OS $OS_NAME $OS_VER"  
  $ECHO "Exiting..."  
  exit 1  
  fi  
 }  
   
 startproc()  
 {  
 # Start dnscrypt daemons  
  program=$1  
  LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH  
  export LD_LIBRARY_PATH  
  for R in $RESOLVERS; do  
  port=`$ECHO $R|$CUT -f2 -d":"`  
  resolver=`$ECHO $R|$CUT -f1 -d":"`  
  pid_file=$pid_base/dnscrypt_ha_$port.pid  
  $DNSCRYPT_PATH/$program --daemonize --pidfile=$pid_file --local-address=$LOCAL_ADDRESS:$port --max-active-requests=$ACTIVE_REQUESTS $USE_TCP $RLIST --resolver-name=$resolver $EDNS $EPHEMERAL_KEYS --loglevel=$LOG_LEVEL  
  done  
 }  
   
 stopproc()  
 {  
 # Stop dnscrypt daemons  
  for R in $RESOLVERS; do  
  port=`echo $R|cut -f2 -d":"`  
  pid_file=$pid_base/dnscrypt_ha_$port.pid  
  $KILL -9 `$CAT $pid_file` && $RM $pid_file  
  done  
 }  
   
 ##############  
 # Main block #  
 ##############  
   
 # Check OS version  
 check_os  
   
 ##########################################################  
 # Check config file exists  
 if [ ! -f "$config_file" -o ! -s "$config_file" ]; then  
  $ECHO "ERROR: Config file not found or is empty."  
  $ECHO "Exiting..."  
  exit 1  
 else  
  # Source config file  
  . $config_file  
 fi  
 #### Check config contents  
 if [ -z "$LOCAL_ADDRESS" ]; then  
  # If local address is not specified, then set it to 127.0.0.1  
  LOCAL_ADDRESS="127.0.0.1"  
 fi  
 if [ ! -z "$USE_TCP_ONLY" ]; then  
  # If TCP flag specified, set option  
  USE_TCP="--tcp-only"  
 fi  
 if [ ! -z "$RESOLVERS_LIST" ]; then  
  # If non-standard location or list name is given, set option  
  RLIST="--resolvers-list=$RESOLVERS_LIST"  
 fi  
 if [ ! -z "$EDNS_PAYLOAD_SIZE" ]; then  
  # If EDNS payload size specified, set option  
  EDNS="--edns-payload-size=$EDNS_PAYLOAD_SIZE"  
 fi  
 # Set other variables  
 # dnscrypt files paths  
 DNSCRYPT_PATH="$BASE_DIR""/sbin"  
 # dnscrypt files  
 DNSCRYPT_BIN_FILE="dnscrypt-proxy"  
 # Check daemon installed  
 if [ ! -f "$DNSCRYPT_PATH/$DNSCRYPT_BIN_FILE" -a ! -x "$DNSCRYPT_PATH/$DNSCRYPT_BIN_FILE" ]; then  
  $ECHO "ERROR: DNSCrypt not found!"  
  $ECHO "Exiting..."  
  exit 1  
 fi  
 ##########################################################  
   
 case "$1" in  
 "restart")  
  stopproc $DNSCRYPT_BIN_FILE  
  startproc $DNSCRYPT_BIN_FILE  
  ;;  
 "start")  
  startproc $DNSCRYPT_BIN_FILE  
  ;;  
 "stop")  
  stopproc $DNSCRYPT_BIN_FILE  
  ;;  
 *)  
  $ECHO "Usage: $0 { start | stop | restart }"  
  exit 1  
 esac  
   
 exit 0  
 ####  

Осталось немного. Загрузить сервисный манифест командой:

 svccfg import /var/svc/manifest/network/dnscrypt-ha.xml  

и запустить сервис:

 svcadm enable dnscrypt-ha  

Убедимся, что пиры запущены:



Осталось лишь добавить записи о них в unbound.conf:

 forward-zone:  
  name: "."  
  forward-addr: 127.0.0.1@5551  
  forward-addr: 127.0.0.1@5552  
  forward-addr: 127.0.0.1@5553  
  forward-addr: 127.0.0.1@5554
  forward-addr: 127.0.0.1@5555

и перезапустить Unbound.

Для совсем ленивых есть полностью готовое решение с ленивчиками установки и удаления сервиса. :)

Замечание

DNSLeakTest будет вам показывать при тестировании сервера dnscrypt, через которые вы ходите, последовательно - так как Unbound обходит пиры форвардеров по round-robin:


так что не паникуйте, так и должно быть.