Работа с GRE туннелями или история одного велосипеда
Почему периодически айтишники делают велосипеды? Ответ простой - потому что на своём велосипеде ездить удобнее. Ну кто хотя бы раз не написал свой модуль логирования для какого-нибудь языка?
Постановка задачи
На объекте есть какие-то подсети, в которых живут инженерные системы. И есть ряд хостов в датацентрах, с которых необходимо получить доступ до этих самых инженерных объектов. В качестве сетевого оборудования на объекте часто либо Cisco, либо Microtic, либо некая линуксовая машина (но на этот случай есть другие, более приятные и удобные для нас инструменты). Исторически так сложилось, что штатным для нас методом обеспечения связности являются GRE туннели) с поднятием маршрутизации в нужные подсети через конечные точки туннеля с последующим закрытием доступа файрволом по белым адресам с обеих сторон. Вопросы шифрования туннелей пока не поднимаем, это возможно будет темой для отдельной статьи.
Для начала предположим, что у нас есть как минимум два варианта:
- Linux сервер <--> активное сетевое оборудование
- Linux сервер <--> Linux server
Реализация средствами сетевого оборудования будет зависеть от типа этого оборудования. В случае, если у нас два линуксовых сервера, то обе стороны настраиваются симметрично за исключением небольших правок в последнем октете адреса.
Пусть у нас для туннелей используются подсети /30, т.к. для туннеля нам необходимо всего два адреса. В нашем примере это будет подсеть 10.99.99.0/24, соответственно, адреса конечных точек - 10.99.99.2/24 для Linux сервера и 10.99.99.3/24 для маршрутизатора. У линукс сервера будет белый статический адрес 123.1.1.1/32, у маршрутизатора - 234.1.1.1/32. За маршрутизатором - серая подсеть 192.168.99.0/24, в которую нужно будет получать доступ с нашего linux сервера. Чтобы не было путаницы, будем добавлять в имена туннелей название удалённого объекта. Например, это "ЖК Чебурашка" - будем называть туннель "gre-che". В качестве наглядного примера схему поместил в начале данного поста.
Шпаргалка для Mikrotik
В качестве примера оставлю небольшую шпаргалку для настройки противоположной точки туннеля на mikrotik:
/interface gre
add name="gre-che" mtu=1476 local-address=234.1.1.1 remote-address=123.1.1.1
/ip address
interface=gre-che add address=10.99.99.2/30
Предположим, что со стороны linux сервера у нас уже есть маршрутизация в другую подсеть с той же адресацией и нам нужна подмена. На стороне Mikrotik netmapping делается следующим образом:
/ip firewall
nat add chain=dstnat action=netmap to-addresses=192.168.99.0/24 dst-address=172.16.99.0/24 in-interface=gre-che
При этом неизменным будет оставаться последний октет адреса и при обращении со стороны linux сервера к адресу 172.16.99.155 mikrotik перенаправит его на адрес 192.168.99.155.
Причины создания велосипеда
Теперь переходим к самому интересному. Зачем собственно нужен велосипед, если можно спокойно настроить роутинг, файрвол, бриджи и маршрутизацию средствами операционной системы? Причин несколько:
- для создания нового туннеля (а у нас их много) требуется прописать много всего в разных местах;
- cпособ настройки отличается для разных систем и для разных версий одной системы - где-то может быть установлен red hat based дистрибутив, где-то debian based, где-то alpine, а где-то вообще OpenWRT, где-то есть нетплан, где-то его нет;
- нужен какой-то контроль за состоянием, даже если мы считаем, что операционная система должна поднять маршрут до узла, какие-то ручные действия в форсмажорной ситуации могут убить маршрут или бридж и система его не поднимет - одним словом, нужно быть уверенным в том, что при любых воздействиях поднимется всё, что нужно для коммуникации между узлами
- нужна удобная точка входа для добавления метрик на случай тех самых форсмажорных ситуаций
Последний пункт тоже выходит за рамки этой статьи, просто добавлю, что в случае, если у нас куда-то пропал туннель или мост или ещё что-то, можно в этих же точках добавить отправку курлом метрики в инфлюкс с именем вида "gre-che-routefail", где che берётся из параметров к скрипту.
Сам велосипед
#!/bin/sh
# Script for upping gre tunnel with routed networks
# by Evgeniy Shumilov <evgeniy.shumilov@gmail.com>
help() {
echo
echo "Usage: network_address remote_white_ip iface_postfix routednet1 [routednet2 ... routednetN] [info]"
echo "Example (apply): $0 10.99.103.0 95.128.142.211 platon 192.168.131.0/24 192.168.136.0/24"
echo "Example (only info): $0 10.99.103.0 95.128.142.211 platon 192.168.131.0/24 192.168.136.0/24"
echo
}
[ -z "$4" ] && help
iface=gre$3
mtu=1476
diface=`ip r s | grep '^default ' | sed 's/^.* dev //;/secondary/d;s/ .*//'`
local=`ip a s "$diface" | sed '/[ ]*inet /!d;s/^.*inet //;/secondary/d;s/\/.*//'`
remote="$2"
grenet="$1"/30
routednets=`echo "$*" | sed 's/^.* '"$3"' //;s/ info$//'`
greip1=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+1"/"$5 }'`
greip2=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+2 }'`
if [ -n "$(echo "$*"| grep ' info$')" ]; then
echo "GRE ext addresses: $local(loc) <--> $remote(rem)"
echo "GRE int addresses: $greip1(loc) <--> $greip2(rem)"
echo "GRE network: $grenet"
echo "GRE iface name: $iface"
echo "GRE iface mtu: $mtu"
echo "Routed networks: $routednets via $greip2"
exit 0
fi
[ -z "$(ip t | grep -F "$iface:")" ] && echo "Trying to up gre tunnel..." && ip t a "$iface" mode gre local "$local" remote "$remote" ttl 255 && ip link set "$iface" mtu "$mtu"
sleep 0.5
ip l s "$iface" up
[ -z "$(ip a s dev "$iface" | grep -F "inet $greip1")" ] && ip a a "$greip1" dev "$iface"
#[ -z "$(ip r | grep -F "$grenet")" ] && echo ip r a "$grenet" dev "$iface" proto kernel scope link src "$greip2"
for routednet in $routednets; do
[ -z "$(ip r | grep -F "$routednet ")" ] && ip r a "$routednet" via "$greip2"
done
Для поднятия туннелей необходимо вызвать скрипт со следующими параметрами:
/opt/bin/gretun 10.99.99.0 234.1.1.1 che 172.16.99.0/24
Где 10.99.99.0 - адрес нашей подсети /30, 234.1.1.1 - белый адрес удалённого маршрутизатора/сервера, che - это постфикс, который добавляется к имени интерфейса, а 172.16.99.0/24 - это маршрутизируемая подсеть.
При запуске скрипт получит белый адрес текущего сервера, используя адрес интерфейса из маршрута по умолчанию, затем поднимет туннель со всеми необходимыми параметрами и добавит нужные маршруты, если их нет. При повторном запуске скрипт проверит наличие интерфейса и маршрутов и если чего-то не будет хватать - восстановит, если всё будет в порядке - не будет предпринимать никаких действий.
Если нужно посмотреть данные о туннеле, который должен быть поднят с текущими параметрами, достаточно к строке запуска добавить последним параметром "info":
someuser@somehost:~$ sudo/opt/bin/gretun 10.99.99.0 234.1.1.1 platon 172.16.99.0/24 info
GRE ext addresses: 123.1.1.1(loc) <--> 234.1.1.1(rem)
GRE int addresses: 10.99.99.2/30(loc) <--> 10.99.99.1(rem)
GRE network: 10.99.99.0/30
GRE iface name: greche
GRE iface mtu: 1476
Routed networks: 172.16.99.0/24 via 10.99.99.2
Также при запуске можно передать в параметрах несколько подсетей (в том числе и с параметром info для просмотра):
someuser@somehost:~$ sudo/opt/bin/gretun 10.99.99.0 234.1.1.1 che 172.16.97.0/24 172.16.98.0/24 172.16.99.0/24
Обычно я сначала запускаю скрипт руками с параметром info, проверяю вывод, далее запускаю без параметра info, проверяю фактическую связность, если нужно - корректрирую параметры и далее просто ставлю скрипт в крон.
Для того, чтобы посмотреть, какие туннели у меня построены с текущего хоста, достаточно сделать sudo crontab -l | grep gretun. Для нашего примера мы получаем следующую запись:
* * * * * /opt/bin/gretun 10.99.99.0 234.1.1.1 che 172.16.99.0/24 2&1 > /dev/null
Если у нас два linux сервера, то для противоположной стороны необходимо запускать аналогичный скрипт, предварительно внеся в него измненения. Вот эту часть:
greip1=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+1"/"$5 }'`
greip2=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+2 }'`
нужно изменить следующим образом:
greip1=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+2"/"$5 }'`
greip2=`echo "$grenet" | tr '/' '.' | awk -F '.' '{ print $1"."$2"."$3"."$4+1 }'`
Можно иметь два скрипта с различными именами, например, gretunlow и gretunhigh в зависимости от того, меньшее или большее значение должен принимать октет на текущем сервере.
Какие проблемы мы решили
Ранее был разброс и шатания, в отношении именования туннелей, стандартизации адресации, при выделении подсетей. Теперь достаточно дёрнуть греп кронлиста по постфиксу организации или gretun и получить стройный список, в котором будет видно сразу всё, что нужно и ничего лишнего - внешние адреса по постфиксу организации, адреса сетей туннелей, все маршрутизируемые подсети. Ну и скорость настройки новых туннелей увеличилась в десятки раз.
Теги: linux, networking, shell, админское