Вопрос или проблема
Я хотел бы добавить вторичное резервное интернет-подключение к своему серверу на Linux. Я планирую использовать для этой цели USB LTE модем.
Поскольку это сотовое соединение будет с ограничением по трафику, я хочу сократить количество данных, которые могут потребляться, до абсолютного минимума, необходимого для работы.
У меня есть собственное серверное приложение, в которое я могу вносить любые изменения. Оно выполняет несколько задач, для которых критически важна непрерывная связь, и другие задачи, где время простоя не имеет большого значения.
Я представляю это примерно так:
- Серверу нужно сделать внешний HTTP API запрос. Первая попытка осуществляется по умолчанию системы (например, eth0, основное интернет-подключение).
- Если запрос не удался или время ожидания истекло, повторить запрос через интерфейс LTE.
Только трафик, который мой серверный процесс явно хочет отправить через LTE, должен отправляться через LTE. Никакой другой трафик из любой части системы не должен идти через LTE.
- В частности, я буду использовать опцию сокета
localAddress
в Node.js, чтобы указать, что запрос должен быть сделан через LTE. - Как мне гарантировать, что другой трафик не будет маршрутизироваться через интерфейс LTE (даже если eth0 не работает)?
- Что насчет разрешения DNS?
В итоге я реализовал это, настроив альтернативную таблицу маршрутов и правило политики маршрутизации для исходного адреса резервного интерфейса.
Мой USB LTE модем представлен как устройство NDIS, поэтому он просто отображается как eth1
с IP-адресом 192.168.0.190 и выполняет NAT-маршрутизацию внутри. Я настроил eth1
с постоянным IP-адресом и вручную настроил маршруты.
-
Стандартная конфигурация использует DHCP, поэтому отключите интерфейс и убедитесь, что все автоматически добавленные маршруты удалены.
-
Добавьте статическую конфигурацию IP для интерфейса и подключите его.
-
Добавьте записи в альтернативную таблицу маршрутов (я выбрал
1
) для подсети и шлюза по умолчанию.# ip route add 192.168.0.0/24 dev eth1 src 192.168.0.190 table 1 # ip route add default via 192.168.0.1 table 1
-
Установите правила политики маршрутизации так, чтобы приложения, которые явно используют 192.168.0.190 в качестве своего исходного адреса, использовали таблицу маршрутов 1 вместо стандартной.
# ip rule add from 192.168.0.190/32 table 1 # ip rule add to 192.168.0.190/32 table 1
На этом этапе вы должны иметь возможность протестировать ваше соединение.
$ curl https://wtfismyip.com/text
1.2.3.4 # Внешний IP основного провайдера
$ curl --interface 192.168.0.190 https://wtfismyip.com/text
5.6.7.8 # Внешний IP резервного LTE соединения
Если все в порядке, сделайте конфигурацию постоянной. Я добавил следующее в /etc/network/interfaces
:
iface eth1 inet static
address 192.168.0.190
netmask 255.255.255.0
post-up ip route add 192.168.0.0/24 dev eth1 src 192.168.0.190 table 1
post-up ip route add default via 192.168.0.1 table 1
post-up ip rule add from 192.168.0.190/32 table 1
post-up ip rule add to 192.168.0.190/32 table 1
Теперь только приложения, которые явно связываются с 192.168.0.190 при установлении исходящих подключений, будут маршрутизироваться через резервное соединение. Весь остальной трафик будет маршрутизироваться через eth0
(или что-либо, что настроено в main
[таблице маршрутов по умолчанию]).
Возможно, у вас есть что-то, что перечисляет все доступные IP-адреса и пытается отправить трафик с них, что может привести к неожиданному трафику через резервное соединение, но это маловероятно. Я не наблюдал такого трафика.
Обратите внимание, что это не решает проблему разрешения DNS. В ситуации, когда основное соединение отключено, вы можете повезти и получить результат поиска из кэша, но на это не стоит полагаться. Я бы не стал настраивать системный резольвер для отправки запросов через интерфейс LTE. Вместо этого ваше приложение может вручную обрабатывать разрешение DNS при осуществлении резервных запросов.
В Node.js выполнение HTTP-запросов (или любого TCP-соединения) из определенного исходного адреса просто. Просто укажите опцию localAddress
option, например:
https.get('https://wtfismyip.com/text', { localAddress: '192.168.0.190' }, …);
Решить вопрос с DNS немного сложнее. Также доступна опция lookup
, которая позволяет переопределить процесс разрешения DNS по умолчанию. Вы можете использовать пользовательский dns.Resolver
для выполнения разрешения. К сожалению, в Node.js не было способа указать исходный адрес для запросов DNS, поэтому я добавил его. С этим вы можете объединить все элементы воедино:
const resolver = new dns.Resolver();
resolver.setServers(['8.8.8.8']);
resolver.setLocalAddress('192.168.0.190'); // требуется node > v15.0.0
https.get('https://wtfismyip.com/text', {
localAddress: '192.168.0.190',
lookup: function(hostname, opts, cb) {
resolver.resolve(hostname, function(err, records) {
if (err) cb(err);
else if (!records[0]) cb(new Error('Missing DNS record'));
else cb(null, records[0], 4);
});
}
}, function(res) { … });
.
Ответ или решение
Когда вы решаете задачу обеспечения резервного интернет-соединения для вашего Linux-сервера, используя USB LTE модем, есть несколько ключевых аспектов, которые следует учитывать. Важной задачей является минимизация использования передаваемых данных через дорогостоящий и ограниченный по трафику LTE канал, а также обеспечение того, чтобы только определенные критичные процессы имели доступ к этому соединению.
Теория (Theory):
Основной концепт, который необходимо реализовать – это использование альтернативной таблицы маршрутизации, которая будет использоваться только тогда, когда ваше приложение явно указано для отправки данных через USB LTE модем. Большинство систем работают с одной таблицей маршрутизации, которая определяет путь для всего интернет-трафика. Однако, в Linux есть возможность создания более одной таблицы маршрутизации, что позволяет нам назначать различные маршруты для различных интерфейсов.
Использование этой функциональности помогает избежать ситуации, когда весь трафик сервера начинает использовать LTE-соединение в случае отказа основного соединения. Ваше приложение должно избирательно управлять маршрутами, отправляя трафик через LTE только тогда, когда это необходимо. В вашем случае это достигается посредством конфигурирования отдельной таблицы маршрутизации и политик маршрутизации с использованием IP адреса через localAddress
в Node.js.
Пример (Example):
-
Настройка статического IP для интерфейса LTE: Во-первых, необходимо установить статический IP для интерфейса вашего USB модема, представленного как
eth1
. Это позволит избежать автоматического получения маршруты через DHCP, который может конфликтовать с вашими настройками.# Удаление автоматически добавленных маршрутов ip route flush dev eth1 # Установка статического IP ip addr add 192.168.0.190/24 dev eth1
-
Настройка альтернативной таблицы маршрутизации: Создание новой таблицы маршрутизации, которая будет использоваться для данных, передаваемых через LTE.
# Добавление маршрутов в таблицу 1 ip route add 192.168.0.0/24 dev eth1 src 192.168.0.190 table 1 ip route add default via 192.168.0.1 table 1
-
Правила политики маршрутизации: Установка правил, определяющих, какой источник IP должен использовать альтернативную таблицу маршрутизации.
ip rule add from 192.168.0.190/32 table 1 ip rule add to 192.168.0.190/32 table 1
Применение (Application):
После настройки маршрутизации и политик, вам необходимо адаптировать ваше приложение для использования нового интерфейса при необходимости. В Node.js это можно сделать посредством указания localAddress
при отправке HTTP-запросов.
Пример кода на Node.js:
const https = require('https');
const dns = require('dns');
const resolver = new dns.Resolver();
resolver.setServers(['8.8.8.8']);
resolver.setLocalAddress('192.168.0.190'); // Требуется Node.js > v15.0.0
https.get('https://example.com', {
localAddress: '192.168.0.190',
lookup: function(hostname, opts, cb) {
resolver.resolve(hostname, function(err, records) {
if (err) cb(err);
else if (!records[0]) cb(new Error('Missing DNS record'));
else cb(null, records[0], 4);
});
}
}, function(res) {
console.log('Response status code:', res.statusCode);
});
Особенности DNS: В случае отказа вашего основного соединения элегантным решением является обработка DNS-запросов самостоятельно в вашем приложении через кастомное разрешение имен, как показано выше. Это гарантирует, что DNS-запросы также будут отправляться через LTE интерфейс, сохраняя все части трафика через соответствующий интерфейс.
Заключительно, интеграция резервного интернет-соединения с ограниченным использованием трафика требует тщательного управления сетевыми маршрутами и конфигурации приложений. Важно создать решение, которое сможет динамически переключаться на резервное соединение только для тех задач, которые действительно в этом нуждаются, избегая излишнего стресса на ваш LTE канал.