Работа с GRE туннелями или история одного велосипеда

Зайчатки разума

Записная книжка айтишника

Работа с GRE туннелями или история одного велосипеда

2022-06-01 13:48:24 — Evgeniy Shumilov

  Почему периодически айтишники делают велосипеды? Ответ простой - потому что на своём велосипеде ездить удобнее. Ну кто хотя бы раз не написал свой модуль логирования для какого-нибудь языка?

Постановка задачи

  На объекте есть какие-то подсети, в которых живут инженерные системы. И есть ряд хостов в датацентрах, с которых необходимо получить доступ до этих самых инженерных объектов. В качестве сетевого оборудования на объекте часто либо 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, админское

comments powered by Disqus