Многие из вас пользуются VPN подключением к рабочей локальной сети из дома.
Благодаря этому, подключив VPN вы работаете с рабочей сетью «как будто находясь в ней».

Как выглядит типичная настройка в linux?
В /etc/resolv.conf прописывается (при помощи openresolv или NetworkManager)

search local.company.name
nameserver 10.0.20.186

Где local.company.name — домен компании, а 10.0.20.186 — ip адрес локального (в рабочей сети) DNS сервера.

Как выглядит работа?

root@t510 0 ~ % ping -c 1 cdash-master
PING cdash-master.local.company.name (10.0.20.237) 56(84) bytes of data.
64 bytes from 10.0.20.237: icmp_seq=1 ttl=63 time=214 ms
...

Что произошло?

  1. search добавил к cdash-master суффикс local.company.name
  2. DNS сервер в рабочей сети преобразовал cdash-master.local.company.name в 10.0.20.237

Всё прекрасно работает, пока у вас VPN подключение… одно.
Мне захотелось подключать одновременно два VPN и прописывать два DNS — один для рабочей сети, другой для локальной сети на hetzner, где крутятся мои виртуальные машины.

Казалось бы, что может быть проще? Однако путь к решению был долгим и извилистым.

Подробное описание результата

Рабочая сеть

Локальная подсеть — 10.0.20.0/24 (т.е. 10.0.20.* — из подсети)
Домен — *.local.company.name
DNS сервер — 10.0.20.186

ping -с 1 somename 

должен определить somename как somename.local.company.name, а somename.local.company.name превратить в IP адрес через DNS 10.0.20.186

Если VPN подключение выключено — имя не будет найдно

Личный сервер на hetzner

С ним всё немного хитрее

Локальная подсеть — 10.10.10.0/24 (т.е. 10.10.10.* — из подсети)
Домен — *.my.domain
Локальный DNS сервер — 10.10.10.253

Сервер my.domain имеет белый IP адрес и доступен извне
На сервере стоит nginx, который проксирует запросы вида subdomain.my.domain машинам из локальной подсети.

С одной стороны, если пользователь зашёл браузером на subdomain.my.domain, то DNS сервера регистратора вернут белый IP адрес my.domain, браузер отправит по данному IP адресу HTTP запрос, и дальше nginx разберётся куда его проксироват.

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

ssh subdomain

должны попадать напрямую на машину из локальной подсети.
Зачем?

Мне так удобно. Я один раз настроил рабочую машину, и прозрачно гуляю по рабочей сети и личной сети.
Снаружи по *.my.domain пользователи видят ровно один сервер (который по совместительству и VPN) и к локальным машинам доступа не имеют
Я же могу напрямую подключать по ssh как к машинам из рабочей сети, так и из личной

«Подожди», скажет мне юзер.
«Я просто пропишу /etc/resolv.conf»:

search local.company.name my.domain

«И всё будет работать.»

Не будет. Для каждой зоны — local.company.name и my.domain требуется прописать свой DNS сервер. Через resolv.conf, openresolv эта проблема не решается.

Решение

Мне пришлось несколько часов мучать гугль и знакомых админов, чтобы найти решение.
Мой пост призван сэкономить ваше время.

Итак, прежде всего нужно поставить bind. Рабочая машина у меня на Arch Linux:

pacman -S bind
systemctl enable named

Дальше настраиваем /etc/named.conf, добавляет туда

zone "local.company.name" IN {
    type forward;
    forwarders { 10.0.20.186; };
};

include "/usr/share/openvpn/named.conf.my.domain";

zone "." IN {
    type forward;
        forwarders { 8.8.8.8; 8.8.4.4; };
};

Настраиваем NetworkManager либо редактируем /etc/resolv.conf. Результатом в обоих случаях должен быть:
root@t510 0 /usr/share/openvpn # cat /etc/resolv.conf

# Generated by NetworkManager
search local.company.name my.domain
nameserver 127.0.0.1

В openvpn скрипт для my.domain добавляем

up "/usr/share/openvpn/update-dns-my.domain"
down "/usr/share/openvpn/update-dns-my.domain"

Добавляем скрипт /usr/share/openvpn/update-dns-my.domain который будет обновлять настройки bind и перезапускать его после установления VPN подключения

#!/bin/bash

set -eu

NAME=/usr/share/openvpn/named.conf.my.domain
TEMPLATE=${NAME}.template

function restart_bind ()
{
    /usr/bin/systemctl restart named
}

case $script_type in
    up)
    cat ${TEMPLATE} > ${NAME}
    restart_bind
    ;;
    down)
    cat /dev/null > ${NAME}
    restart_bind
    ;;
esac

Добавляем /usr/share/openvpn/named.conf.my.domain.template

zone "my.domain" IN {
     type forward;
     forwarders { 10.10.10.253; };
};

Как это работает?

При установлении VPN подключения к личному серверу:

  1. openvpn запускает скрипт /usr/share/openvpn/update-dns-my.domain
  2. скрипт в файл /usr/share/openvpn/named.conf.my.domain записывает содержимое /usr/share/openvpn/named.conf.my.domain.template
  3. скрипт перезапускает bind
  4. bind вычитывает настройки зоны my.domain из обновлённого файла
  5. зона my.domain теперь ресолвится DNS сервером из локальной сети (доступен через VPN)

При отключении VPN подключения

  1. openvpn запускает скрипт /usr/share/openvpn/update-dns-my.domain
  2. скрипт очищает содержимое /usr/share/openvpn/named.conf.my.domain
  3. скрипт перезапускает bind
  4. bind вычитывает настройки зоны my.domain из обновлённого файла (забывает про зону)
  5. зона my.domain теперь ресолвится общедоступными DNS

Как это выглядит на практике?

root@t510 0 /usr/share/openvpn # systemctl start named # не забываем включить bind
root@t510 0 /usr/share/openvpn # systemctl start openvpn@waltham # рабочая сеть 
root@t510 0 /usr/share/openvpn # systemctl start openvpn@hetzner # личная сеть

root@t510 0 /usr/share/openvpn # ping -c 1 cdash-master
PING cdash-master.local.company.name (10.0.20.237) 56(84) bytes of data.
64 bytes from 10.0.20.237: icmp_seq=1 ttl=63 time=214 ms
...

root@t510 0 /usr/share/openvpn # ping -c 1 db
PING db.my.domain (10.10.10.200) 56(84) bytes of data.
64 bytes from 10.10.10.200: icmp_seq=1 ttl=63 time=233 ms
...

root@t510 0 /usr/share/openvpn # systemctl stop openvpn@hetzner  

root@t510 0 /usr/share/openvpn # ping -c 1 db                  
PING db.my.domain (white-ip-address) 56(84) bytes of data.
64 bytes from my.domain (white-ip-address): icmp_seq=1 ttl=55 time=124 ms
...

root@t510 0 /usr/share/openvpn # systemctl start openvpn@hetzner 

root@t510 0 /usr/share/openvpn # ping -c 1 db                   
PING db.my.domain (10.10.10.200) 56(84) bytes of data.
64 bytes from 10.10.10.200: icmp_seq=1 ttl=63 time=118 ms
...

Забавно, что для получения такого простого поведения пришлось проделать столько работы.