CentOS anycast dns-resolvers
Исторически сложилось, что ISP сообщает клиенту два IP-адреса в качестве настроек DNS. Многие ОС и протоколы, например IPCP из PPP, и не поддерживают больше, а реальность такова, что у крупных операторов два сервера не способны обслуживать все клиентские запросы. А если география предоставления услуг охватывает страны или континенты, то два сервера вообще не вариант.
Для преодоления этой ситуации можно использовать достаточно простой подход, который используется для корневых DNS-серверов - anycast DNS. Этот подход можно использовать и для других протоколов работающих по принципу запрос-ответ без сохранения состояния, например, для NTP.
Этот конкретный пример будет для CentOS6 (unbound, quagga) и Juniper MX, но есть рабочие серверы на FreeBSD.
# 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
# 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"
!
! 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
!
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
#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
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
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 всё просто, но опять же есть небольшой трюк с 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;
}
}
}
Для преодоления этой ситуации можно использовать достаточно простой подход, который используется для корневых DNS-серверов - anycast DNS. Этот подход можно использовать и для других протоколов работающих по принципу запрос-ответ без сохранения состояния, например, для NTP.
Этот конкретный пример будет для CentOS6 (unbound, quagga) и Juniper MX, но есть рабочие серверы на FreeBSD.
Начнём с CentOS:
Сетевые интерфейсы.
eth0
"смотрит" в сторону MX'a. Экономим ipv4 - bgp будем поднимать на "серых" адресах 10.1.0.48/29.
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;
}
}
}
Комментарии