
Пишем bash скрипт для подключения к OpenConnect VPN серверу
Обновлено 02.03.2025
Приветствую!
Сегодня напишем и опробуем простой, но продуманный bash скрипт, выполняющий клиентское подключение к OpenConnect VPN серверу, про запуск и настройку которого рассказывалось в одной из прошлых статей. Как говориться, “Нам нужно больше bash’а!”. Будет интересно (или больно) 😉
Присоединяйтесь к нашему каналу в телеграм: t.me/r4ven_me, уведомления о новых постах приходят туда в день публикации. А если у вас есть вопросы или просто желание пообщаться по тематике – заглядывайте в чат: t.me/r4ven_me_chat.
Предисловие
Еще раз напомню, что ранее мы с вами развернули свой VPN сервер на базе OpenConnect SSL based сервера (ocserv): Поднимаем OpenConnect SSL VPN сервер (ocserv) в docker для внутренних проектов.
А сегодня продемонстрирую работу скрипта клиентского подключения, который я написал с учетом одной полезной статьи на Habr, пресловутого ChatGPT и какой-то там матери.

Данный способ подключения предпочтителен на серверах без GUI, но на десктопе также прекрасно работает.
И так, начнем с описания логики работы.
Описание логики работы скрипта
Тут всё просто. Скрипт проверяет наличие исполняемого файла openconnect
, затем в фоновом режиме выполняет подключение к VPN серверу ocserv
и переходит в режим перманентного ожидания/проверки доступности внутреннего шлюза VPN. В случае 3-х неудачных попыток проверки доступности шлюза по пингу, скрипт завершает свою работу.
Но т.к. в нашем случае запуск/автозапуск скрипта будет выполнятся с помощью systemd
, то после “аварийного” завершения скрипта, systemd
перезапустить его через 5 секунд. Таким образом, подразумевается (но не гарантируется ) бесперебойная работа скрипта подключения.
Сам скрипт + systemd unit
Вот содержимое скрипта occlient.sh:
#!/usr/bin/env bash
## Enhanced error handling
set -Eeuo pipefail
## Var that defines working directory of script
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
## Custom vars
CERT_FILE="/path/to/exampleuser.p12"
CERT_PASS="examplepassword"
VPN_ADDRESS="vpn.example.com"
VPN_PORT="43443"
VPN_GATEWAY="10.10.10.1"
SSL_FLAG="1"
## System vars
OC_BIN="$(which openconnect)"
VPN_COMMAND="$OC_BIN -c $CERT_FILE $VPN_ADDRESS:$VPN_PORT"
CHECK_INTERVAL=10
TIMEOUT=30
RETRY_COUNT=3
## Function to print message
msg() {
echo -e "[$(date '+%F %T')] ${1-}" >&2
}
## Function to terminate VPN command process
terminate_vpn() {
msg "Terminating VPN connection"
pkill -SIGINT -f "${VPN_COMMAND}"
exit 1
}
## Function to check availability of VPN gateway
check_gateway() {
if ping -c 1 -W $TIMEOUT $VPN_GATEWAY &> /dev/null; then
# msg "Gateway $VPN_GATEWAY is reachable."
return 0
else
msg "Gateway $VPN_GATEWAY is not reachable."
return 1
fi
}
## Connecting to VPN
msg "Connecting to VPN..."
if [[ "$SSL_FLAG" == "1" ]]; then
"$OC_BIN" -c "$CERT_FILE" "${VPN_ADDRESS}":"${VPN_PORT}" <<< "$(echo ${CERT_PASS}$'\n')" &
else
"$OC_BIN" -c "$CERT_FILE" "${VPN_ADDRESS}":"${VPN_PORT}" <<< "$(echo ${CERT_PASS}$'\n'yes$'\n')" &
fi
# VPN_PID=$!
## Checking availability of gateway and exiting if there is no connection
while true; do
FAILED_COUNT=0
for (( i=0; i<RETRY_COUNT; i++ )); do
if check_gateway; then
break
else
FAILED_COUNT=$((FAILED_COUNT+1))
fi
if [[ $FAILED_COUNT -ge $RETRY_COUNT ]]; then
msg "Gateway $VPN_GATEWAY unreachable after $RETRY_COUNT attempts."
msg "Terminating VPN connection..."
terminate_vpn
fi
sleep $CHECK_INTERVAL
done
sleep $CHECK_INTERVAL
done
И systemd service unit occlient.service:
[Unit]
Description=OpenConnect VPN Client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/path/to/occlient.sh
KillSignal=SIGINT
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Все файлы, как обычно, доступны в моём репо на GitHub.
Далее будет разбор скрипта, но если вам это не интересно, то сразу переходите к пункту “Установка и тестовый запуск“.
Разбор скрипта
Блок №1
## Enhanced error handling
set -Eeuo pipefail
## Var that defines working directory of script
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd -P)
В первом блоке устанавливаются специальные флаги обработки ошибок при работе скрипта. Является рекомендацией. Также тут универсальным образом задана переменная, которая содержит в себе путь до директории, из которой запускается скрипт. Её наличие тоже опционально, если вдруг вам понадобиться расширить функционал.
Блок №2
## Custom vars
CERT_FILE="/path/to/exampleuser.p12"
CERT_PASS="examplepassword"
VPN_ADDRESS="vpn.example.com"
VPN_PORT="43443"
VPN_GATEWAY="10.10.10.1"
SSL_FLAG="1"
В этом блоке определяются пользовательские переменные. Это единственный блок, который обязательно необходимо модифицировать перед использованием. Как видно, тут определяются следующие значения:
CERT_FILE
– путь до файла пользовательского сертификатаp12
;CERT_PASS
– пароль от этого файла;VPN_ADDRESS
– адрес OpenConnect VPN сервера (ocserv);VPN_PORT
– порт подключения к серверу;VPN_GATEWAY
– адрес внутреннего VPN шлюза (необходим для проверки связи с сервером);SSL_FLAG
– переменная, определяющая использование валидного SSL. 1 – да, 0 – нет.
Прошу обратить внимание, что хранить чувствительную информацию, такую, как пароли, внутри скриптов считается не безопасным. В данной статье я лишь демонстрирую работу.
Как правильно использовать пароли внутри сценариев – это тема для отдельного поста. В будущем обязательно сделаю такой.
Блок №3
## System vars
OC_BIN="$(which openconnect)"
VPN_COMMAND="$OC_BIN -c $CERT_FILE $VPN_ADDRESS:$VPN_PORT"
CHECK_INTERVAL=10
TIMEOUT=30
RETRY_COUNT=3
Этот блок определяет служебные переменные. Изменять их не требуется.
У нас тут:
- Переменная
OC_BIN
, значение которой определяется результатом командыwhich openconnect
. Если в системе не установлена утилита openconnect, то при определении переменной команда завершится ошибкой, что в свою очередь завершит работу всего скрипта; - Переменная
VPN_COMMAND
– используется функциейterminate_vpn
(см. ниже) для идентификации процесса клиентского подключения, с целью его завершения; - Переменная
CHECK_INTERVAL
, определяющая период ожидания между попытками проверки связи с внутренним шлюзом VPN; - Переменная
TIMEOUT
– определяет время ожидания ответа при проверке связи с помощью утилитыping
в функцииcheck_gateway
(см. ниже); RETRY_COUNT
– количество проверок.
Блок №4
## Function to print message
msg() {
echo -e "[$(date '+%F %T')] ${1-}" >&2
}
Это функция вывода информационных сообщений, с префиксом в виде текущей метки времени и перенаправлением вывода в поток ошибок >&2
.
Блок №5
## Function to terminate VPN command process
terminate_vpn() {
msg "Terminating VPN connection"
pkill -SIGINT -f "${VPN_COMMAND}"
exit 1
}
Тут у нас функция, выполняющая завершение процесса клиентского подключения. Реализована с помощью команды pkill
, которой аргументом передается имя команды подключения (VPN_COMMAND
).
По теме управления процессами в Linux на сайте также есть отдельная статья: Командная строка Linux, процессы: команды jobs, fg, bg, ps, pgrep, kill, pkill, htop.
Блок №6
## Function to check availability of VPN gateway
check_gateway() {
if ping -c 1 -W $TIMEOUT $VPN_GATEWAY &> /dev/null; then
# msg "Gateway $VPN_GATEWAY is reachable."
return 0
else
msg "Gateway $VPN_GATEWAY is not reachable."
return 1
fi
}
Это функция проверки доступности внутреннего шлюза VPN сервера с помощью утилиты ping
. При успешной проверке функция возвращает 0, в ином случае 1. Логично)
Блок №7
## Connecting to VPN
msg "Connecting to VPN..."
if [[ "$SSL_FLAG" == "1" ]]; then
"$OC_BIN" -c "$CERT_FILE" "${VPN_ADDRESS}":"${VPN_PORT}" <<< "$(echo ${CERT_PASS}$'\n')" &
else
"$OC_BIN" -c "$CERT_FILE" "${VPN_ADDRESS}":"${VPN_PORT}" <<< "$(echo ${CERT_PASS}$'\n'yes$'\n')" &
fi
# VPN_PID=$!
В данном блоке выводится стартовое сообщение, затем выполняется проверка установленного флага SSL. В зависимости от его наличия, происходит соответствующее ветвление. В любом случае выполняется команда подключения к OpenConnect серверу, после чего, с помощью оператора контроля выполнения &
она отправляется в фоновый режим, передавая работу следующему коду скрипта.
Обратите внимание, что тут присутствует закомментированная переменная VPN_PID
, которая (если раскоментить) будет содержать в себе идентификатор (pid) последнего процесса, который был отправлен в фоновый режим, т.е. нашей команды подключения. Данный pid можно использовать вместо VPN_COMMAND
. Тут уже на ваше усмотрение.
Блок №8
## Checking availability of gateway and exiting if there is no connection
while true; do
FAILED_COUNT=0
for (( i=0; i<RETRY_COUNT; i++ )); do
if check_gateway; then
break
else
FAILED_COUNT=$((FAILED_COUNT+1))
fi
if [[ $FAILED_COUNT -ge $RETRY_COUNT ]]; then
msg "Gateway $VPN_GATEWAY unreachable after $RETRY_COUNT attempts."
msg "Terminating VPN connection..."
terminate_vpn
fi
sleep $CHECK_INTERVAL
done
sleep $CHECK_INTERVAL
done
Последний блок представляет собой бесконечный цикл, который выполняет проверку доступности внутреннего шлюза VPN и в случае успешной проверки, переходит в режим ожидания длительностью в CHECK_INTERVAL
, после чего вновь выполняет проверку.
В случае отсутствия связи, счетчик неудачных попыток (FAILED_COUNT
) будет увеличиваться на единицу. Если значение этого счетчика превысит значение RETRY_COUNT
, скрипт вызовет функцию остановки процесса подключения (terminate_vpn
) и завершит работу.
Установка и тестовый запуск
При подключении клиентом будет выступать Ubuntu 22, а сервером Debian 12.
Скачивание скрипта
Хочу сразу обратить внимание, что при использовании подключения с помощью утилиты
openconnect
необходимы права суперпользователя.
Для установки скрипта вы можете скопировать его вручную из этой статьи или скачать из моего GitHub, например, такой командой:
sudo curl -fLo /root/occlient.sh https://raw.githubusercontent.com/r4ven-me/docker/main/openconnect/openconnect-src/occlient.sh
Она скопирует файл в домашнюю директорию пользователя root.
После скачивания, необходимо выдать скрипту права на исполнение:
sudo chmod 700 /root/occlient.sh
Про права на файлы я подробно рассказывал в статье: Командная строка Linux, права на файлы: команды id, chmod, chown.
Теперь открываем скрипт на редактирование любым редактором. Я предпочитаю Vim/Neovim:
sudo vim /root/occlient.sh
На моём сайте также есть цикл статей про этот консольный редактор. Посмотреть список можно по соответствующему тегу: Vim/Neoim.
Заменяем все значения в блоке Custom vars на свои, сохраняем файл и выходим.
ВАЖНО!
Не забудьте верно указать SSL флаг в переменной SSL_FLAG
. Чтобы не возникло ошибок при запуске. Напомню, 1
– используется валидный SSL, 0
(или что угодно) – не используется.
В моём примере используется валидный SSL сертификат на сервере, поэтому переменная SSL_FLAG
установлена в 1.
Запуск скрипта
Если вы используете подключение с помощью утилиты
openconnect
на Linux сервере, и планируете туннелировать весь его трафик, то необходимо добавить специальные правила маршрутизации, без которых вы потеряете доступ до своего сервера, в т.ч. и по SSH:ip rule add table 128 from <public-ip> ip route add table 128 to <public_ip_subnet> dev <ineteface_name> ip route add table 128 default via <gateway>
Где:
- 128 – имя новой таблицы маршрутизации;
- <piblic-ip> – основной IP вашего сервера;
- <public_ip_subnet> – подсеть основного IP сервера;
- <interface_name> – имя физического интерфейса, к которому подключен основной IP;
- <gateway> – шлюз сети, через который ходит основной IP.
Пробуем запуститься:
sudo /root/occlient.sh
Если вы все шаги проделали верно, то увидите примерно такую картину:
Подключение успешно.
Открываем соседнюю вкладку и проверяем доступ в интернет:
ping -c3 8.8.8.8
ip route get 8.8.8.8
Трафик идёт через виртуальный интерфейс, всё корректно.
Проверить внешний IP в терминале можно такой командой:
curl ifconfig.me
Возвращаемся на вкладку со скриптом и нажимаем Ctrl+c для выхода.
Активация автозапуска с помощью systemd
Теперь настроим автозапуск/перезапуск нашего скрипта. Для этого скачиваем systemd unit:
sudo curl -fLo /etc/systemd/system/occlient.service https://raw.githubusercontent.com/r4ven-me/docker/main/openconnect/openconnect-src/occlient.service
Затем открываем его на редактирование:
sudo vim /etc/systemd/system/occlient.service
И меняем в нём путь до файла скрипта:
Сохраняем, выходим.
Перечитываем конфигурацию systemd и активируем автозапуск нашего сервиса при старте системы, а также запускаем его:
sudo systemctl daemon-reload
sudo systemctl enable --now occlient
sudo systemctl status occlient
Готово.
Для просмотра вывода юнита occlient.service в реальном времени выполните:
sudo journalctl -fu occlient
Послесловие
Вот мы с вами и настроили автоматическое подключение/переподключение к OpenConnect серверу с проверкой доступности.
Вновь отмечу, что данный способ подключения предпочтителен на серверах без GUI. Потому что при использовании клиентов на десктопе, лучше заюзать плагин NetworkManager (если, конечно, за сеть у вас отвечает он). Процедура подключения такого клиента описана в соответствующем разделе статьи по настройке ocserv: 5.1 Настройка OpenConnect клиента для Linux.
Спасибо, что читаете. С подключением 😉