CentOS anycast dns-resolvers

Исторически сложилось, что ISP сообщает клиенту два IP-адреса в качестве настроек DNS. Многие ОС и протоколы, например IPCP из PPP, и не поддерживают больше, а реальность такова, что у крупных операторов два сервера не способны обслуживать все клиентские запросы. А если география предоставления услуг охватывает страны или континенты, то два сервера вообще не вариант.

Для преодоления этой ситуации можно использовать достаточно простой подход, который используется для корневых DNS-серверов - anycast DNS. Этот подход можно использовать и для других протоколов работающих по принципу запрос-ответ без сохранения состояния, например, для NTP.

Этот конкретный пример будет для CentOS6 (unbound, quagga) и Juniper MX, но есть рабочие серверы на FreeBSD.

 

Начнём с CentOS:

Сетевые интерфейсы.

eth0 

"смотрит" в сторону MX'a. Экономим ipv4 - bgp будем поднимать на "серых" адресах 10.1.0.48/29.

# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=6A:02:85:D0:E2:01
BOOTPROTO=none
ONBOOT="yes"
TYPE=Ethernet
IPADDR=10.1.0.50
NETMASK=255.255.255.248
GATEWAY=10.1.0.49
DEFROUTE=yes
ONBOOT=yes
IPV6INIT=yes
IPV6ADDR=1234:5800:0:100::a/125
IPV6_DEFAULTGW=1234:5800:0:100::9
IPV6_DEFROUTE=yes
NAME="eth0"
USERCTL=no


loopbacks:

Вот тут и будут наши реальные адреса: два, на которые мы принимаем запросы от клиентов (1.0.128.10  и 1.0.128.14) и один (1.0.128.8), с которого мы будем отправлять запросы в "мир".

# cat /etc/sysconfig/network-scripts/ifcfg-lo
DEVICE=lo
IPADDR=127.0.0.1
NETMASK=255.0.0.0
NETWORK=127.0.0.0
BROADCAST=127.255.255.255
ONBOOT=yes
IPV6INIT=yes
IPV6ADDR=1234:5800::1/128
NAME=loopback


# cat /etc/sysconfig/network-scripts/ifcfg-lo:0
DEVICE=lo:0
ONPARENT=yes
IPADDR=1.0.128.8
NETMASK=255.255.255.255
ONBOOT=yes
NAME="DNS requests source"



# cat /etc/sysconfig/network-scripts/ifcfg-lo:1
DEVICE=lo:1
ONPARENT=yes
IPADDR=1.0.128.10
NETMASK=255.255.255.255
ONBOOT=yes
NAME="resolver#1"


# cat /etc/sysconfig/network-scripts/ifcfg-lo:2
DEVICE=lo:2
ONPARENT=yes
IPADDR=1.0.128.14
NETMASK=255.255.255.255
ONBOOT=yes
NAME="resolver#2"
 

BGP

Используем BGP, так как он легко фильтруется, поддерживает неравновесное распределение нагрузки и т.д.. Анонсируем нашему маршрутизатору все три ipv4-адреса. С ipv6 есть небольшая хитрость - next-hop global.


Quagga:

# cat /etc/quagga/bgpd.conf
!
! Zebra configuration saved from vty
!   2014/05/12 08:55:50
!
hostname ns-resolver00
password zebra
enable password quagga
log file /var/log/quagga/zebra.log
!
router bgp 65001
 bgp router-id 1.0.128.8
 bgp log-neighbor-changes
 bgp graceful-restart
 network 1.0.128.8/32
 network 1.0.128.10/32
 network 1.0.128.14/32
 neighbor 10.1.0.49 remote-as 65004
 neighbor 10.1.0.49 description "router4"
 neighbor 10.1.0.49 route-map RM-DNS_RESOLVER out
 neighbor 1234:5800:0:100::9 remote-as 65004

 neighbor 1234:5800:0:100::9 description "router6"
 no neighbor 1234:5800:0:100::9 activate
!
 address-family ipv6
 network 1234:5800::1/128
 neighbor 1234:5800:0:100::9 activate
 neighbor 1234:5800:0:100::9 route-map RM-DNS_RESOLVER6 out
 exit-address-family
!
access-list 99 permit 1.0.128.10
access-list 99 permit 1.0.128.14
access-list 99 permit 1.0.128.8
!
ipv6 prefix-list PL-DNS_RESOLVER6 seq 5 permit 1234:5800::1/128
!
route-map RM-DNS_RESOLVER permit 10
 match ip address 99
!
route-map RM-DNS_RESOLVER6 permit 10
 match ipv6 address prefix-list PL-DNS_RESOLVER6
 set ipv6 next-hop global 1234:5800:0:100::a
!

Unbound

Явно указываем, на какие адреса принимаем запросы, а с какого отправляем в "мир". Аналогичные опции есть у всех современных DNS-рекурсоров.

server:
 # specify the interfaces to answer queries from by ip-address.
        interface: 1.0.128.10
        interface: 1.0.128.14
        interface: 1234:5800::1

        
        interface-automatic: no

 # specify the interfaces to send outgoing queries to authoritative
 # server from by ip-address. If none, the default (all) interface
 # is used. Specify every interface on a 'outgoing-interface:' line.
        outgoing-interface: 1.0.128.8
        outgoing-interface: 1234:5800:0:100::a


Cron

На случай отказа поместим периодическую проверку работоспособности рекурсора в cron:

#minute hour    mday    month   wday    command
PATH=/usr/bin:/bin:/usr/sbin:/sbin
#
@reboot                                 echo '0' > /var/tmp/dns-check
*/1     *       *       *       *       service unbound status >/dev/null || service unbound restart
*/2     *       *       *       *       /var/adm/check_dns.sh
 

# cat /var/adm/check_dns.sh
#!/bin/sh

export PATH=/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin

MAIL_RECIPIENT="NOC@bigisp.domain"
DATE=$(date "+%Y-%m-%d %H:%m")
FFILE="/var/tmp/dns-check"

STATUS=$(head -n1 "${FFILE}")


drill
bigisp.domain > /dev/null
ST=$?
if [ "${ST}" != "0" ]; then
    if [ "${STATUS}" -ge 5 ]; then
        echo "Immediate actions for resolver are required!!!" | mail -s "Alert from resolver [server stopped]" ${MAIL_RECIPIENT}
    elif [ "${STATUS}" = "5" ]; then
        echo "Shutdown resolver at ${DATE}" | mail -s "Alert from resolver [Critical Error]" ${MAIL_RECIPIENT}
        vtysh -E -c "conf t
router bgp 65001
no network 1.0.128.10/32
no network 1.0.128.14/32
no network 1234:5800::1/128
end
clear ip bgp 10.1.0.49 out"
    else
        echo "Restart unbound at ${DATE} because 'drill
bigisp.domain @resolver' returned: ${ST}" | mail -s "Alert from resolver [unbound restarted]" ${MAIL_RECIPIENT}
        service unbound restart
    fi
    STATUS=$(( ${STATUS}+1 ))
    echo "${STATUS}" > "${FFILE}"
else
    echo "0" > "${FFILE}"
fi



iptables:

Используем iptables для защиты сервера. Небольшой трюк с NOTRACK чтобы не переполнить таблицу состояний, в остальном всё стандартно. Используем SNAT чтобы программы, например, wget или yum, которые не умеют привязываться к адресу loopback'а имели доступ в Internet.

cat /etc/sysconfig/iptables
# Generated by iptables-save v1.4.7 on Thu Sep 18 20:17:49 2014
*raw
:PREROUTING ACCEPT [288965:24355290]
:OUTPUT ACCEPT [290702:22209038]
-A PREROUTING -p udp -m udp --sport 53 -j NOTRACK
-A PREROUTING -p udp -m udp --dport 53 -j NOTRACK
-A OUTPUT -p udp -m udp --sport 53 -j NOTRACK
-A OUTPUT -p udp -m udp --dport 53 -j NOTRACK
COMMIT
# Completed on Thu Sep 18 20:17:49 2014
# Generated by iptables-save v1.4.7 on Thu Sep 18 20:17:49 2014
*nat
:PREROUTING ACCEPT [889:58520]
:POSTROUTING ACCEPT [46:2760]
:OUTPUT ACCEPT [46:2760]
-A POSTROUTING -s 10.1.0.48/29 ! -d 10.1.0.48/29 -o eth0 -j SNAT --to-source
1.0.128.8
COMMIT
# Completed on Thu Sep 18 20:17:49 2014
# Generated by iptables-save v1.4.7 on Thu Sep 18 20:17:49 2014
*mangle
:PREROUTING ACCEPT [288971:24355694]
:INPUT ACCEPT [288971:24355694]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [290707:22209502]
:POSTROUTING ACCEPT [290707:22209502]
COMMIT
# Completed on Thu Sep 18 20:17:49 2014
# Generated by iptables-save v1.4.7 on Thu Sep 18 20:17:49 2014
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [263101:20083880]
-A INPUT -p udp -m udp --sport 53 -j ACCEPT
-A INPUT -p udp -m udp --dport 53 -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -m state --state INVALID -j DROP
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -s 10.1.0.49/32 -p tcp -m state --state NEW -m tcp -m multiport --dports 22,179 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
-A OUTPUT -p udp -m udp --dport 53 -j ACCEPT
COMMIT
# Completed on Thu Sep 18 20:17:49 2014

JunOS:

Сетевой интерфейс:

DNS-cерверы включены в virtual chassis из нескольких коммутаторов juniper ex4200, поэтому у нас тут LACP-транк.

interfaces {

 ae3 {
        description "ae48 ex4200";
        vlan-tagging;
        mtu 9192;
        encapsulation flexible-ethernet-services;
        aggregated-ether-options {
            minimum-links 1;
            lacp {
                active;
                periodic fast;
            }
        }

        unit 53 {
            description SERVERS-DNS-RESOLVERs;
            vlan-id 53;
            family inet {
                no-redirects;
                policer {
                    arp POL-ARP;
                }
                address 10.1.0.49/29;
            }
            family inet6 {
                address 1234:5800:0:100::9/125;
            }
        }

    }

}

BGP


С BGP всё просто, но опять же есть небольшой трюк с ipv6.

protocols {

    bgp {

        group dns4 {
            type external;
            passive;
            import rm-dns-in;
            family inet {
                unicast {
                }
            }
            export default-only;
            peer-as 65001;
            multipath;

            neighbor 10.1.0.50 {
                description resolver0;
            }
            neighbor 192.168.250.102 {
                description resolver1;
            }
            neighbor 10.1.0.52 {
                description resolver2;
            }
            neighbor 10.1.0.53 {
                description resolver3;
            }
        }

        group dns6 {
            type external;
            passive;
            import rm-dns6-in;
            family inet6 {
                unicast;
            }
            export default6-only;
            peer-as 65001;

            neighbor 1234:5800:0:100::a {
                description resolver0;
            }
            neighbor 1234:5800:0:100::b {
                description resolver1;
            }
            neighbor 1234:5800:0:100::c {
                description resolver2;
            }
            neighbor 1234:5800:0:100::d {
                description resolver3;
            }
        }

    }

}

policy-options {

    policy-statement rm-dns-in {
        term dns {
            from {
                route-filter 1.0.128.8/29 upto /32;
            }
            then accept;
        }
        term deny {
            then reject;
        }
    }



    policy-statement rm-dns6-in {
        term dns {
            from {
                family inet6;
                route-filter 1234:5800::0/128 exact;
                route-filter 1234:5800::1/128 exact;
                route-filter fec0:000:0000:ffff::1/128 exact;
            }
            then {
                local-preference 400;
                accept;
            }
        }
        term deny {
            then reject;
        }
    }

}
 

Комментарии

Популярные сообщения из этого блога

streaming multicast IPTV to HLS (nginx+RTMP)

using pg_partman to help partitioning postgresql for zabbix