Настройка OpenVPN Ubuntu 18.04

Материал из First-Leon
Перейти к: навигация, поиск

Предварительная настройка

Устанавливаем сервер и дополнительные утилиты:

apt-get install openvpn dos2unix easy-rsa
mkdir /etc/openvpn/new
mkdir /etc/openvpn/new/key
cp -r /usr/share/easy-rsa/ /etc/openvpn/new/
cp /etc/openvpn/new/easy-rsa/openssl-1.0.0.cnf /etc/openvpn/new/easy-rsa/openssl.cnf
mkdir /etc/openvpn/new/ccd

Отключаем интерактивный ввод данных для генерируемых ключей (удаляем ключ --interact):

sed -i "s/--interact/ /g" /etc/openvpn/new/easy-rsa/build-ca
sed -i "s/--interact/ /g" /etc/openvpn/new/easy-rsa/build-key-server
sed -i "s/--interact/ /g" /etc/openvpn/new/easy-rsa/build-key

Укажем параметры генерации ключей:

mcedit /etc/openvpn/new/easy-rsa/vars
# 2048 бит. Чтоб совсем спокойно спалось
export KEY_SIZE=2048
# 20 лет валидности сертификата, для рекордного аптайма )
export CA_EXPIRE=7300
export KEY_EXPIRE=7300
# Страна
export KEY_COUNTRY="RU"
# Регион
export KEY_PROVINCE="ALT"
# Населенный пункт
export KEY_CITY="BARNAUL"
export KEY_ORG="Organization"
export KEY_EMAIL="почта@админа.ru"
export KEY_OU=IT

Создаем скрипт автоматической генерации ключей и конфигов

mcedit /etc/openvpn/keygen.sh
#!/bin/bash

# $1 название сети
# $2 порт
# $3 подсеть (например: 10.0.0)
# $4 tap интерфейс
# $5 первый dns адрес vpn сервера
# $6 второй dns адрес vpn сервера

# Проверяем сеть на существование
if [ -e /etc/openvpn/$1 ]; then
    echo "Сеть существует!!!"
    exit 0
else
    echo "Сеть не существует. Всё Ok. Продолжаем."
fi

net=${3%%.} #Удаляем точку в конце. Для не внимательных ) 

# Генерируем ключи
cp -r /etc/openvpn/new /etc/openvpn/$1/
cd /etc/openvpn/$1
for i in {002..254};
do
    echo  "ifconfig-push $net."`echo $i | sed 's/^[0^t]*//'`" 255.255.255.0" > ccd/$1-$i
done
cd easy-rsa
. ./vars
./clean-all
bash build-dh
bash build-ca
bash build-key-server $1
#Массово генерируем клиентские сертификаты:
for i in {002..254};
do
    ./build-key $1-$i
done
openvpn --genkey --secret ta.key
cp ta.key ../
cp keys/ca.crt ../
cp keys/ca.key ../
cp keys/dh*.pem ../
cp keys/$1.key ../
cp keys/$1.crt ../
cd ../../

mv $1/easy-rsa/keys/$1-*.key $1/key
mv $1/easy-rsa/keys/$1-*.crt $1/key

# Создаем конфиг linux сервера
echo "port $2"                                          >  $1/$1-server.conf
echo "proto udp"                                        >> $1/$1-server.conf
echo "dev $4"                                           >> $1/$1-server.conf
echo "ca /etc/openvpn/$1/ca.crt"                        >> $1/$1-server.conf
echo "cert /etc/openvpn/$1/$1.crt"                      >> $1/$1-server.conf
echo "key /etc/openvpn/$1/$1.key"                       >> $1/$1-server.conf
echo "dh /etc/openvpn/$1/dh2048.pem"                    >> $1/$1-server.conf
echo "server $net.0 255.255.255.0"                      >> $1/$1-server.conf
echo "client-config-dir /etc/openvpn/$1/ccd"            >> $1/$1-server.conf
echo "tls-auth /etc/openvpn/$1/ta.key 0"                >> $1/$1-server.conf
echo "comp-lzo"                                         >> $1/$1-server.conf
echo "max-clients 253"                                  >> $1/$1-server.conf
echo "status /var/log/openvpn/$1-status.log"            >> $1/$1-server.conf
echo "log /var/log/openvpn/$1-vpn.log"                  >> $1/$1-server.conf
echo "log-append /var/log/openvpn/$1-vpn.log"           >> $1/$1-server.conf
echo "script-security 2"                                >> $1/$1-server.conf
# Уровень отладочной информации verb                    >> $1/$1-server.conf
echo "verb 2"                                           >> $1/$1-server.conf
echo "multihome"                                        >> $1/$1-server.conf
# Разрешаем обмен пакетами между клиентами              >> $1/$1-server.conf
echo "client-to-client"                                 >> $1/$1-server.conf
echo "keepalive 10 60"                                  >> $1/$1-server.conf

# Создаем конфиг windows сервера
echo "port $2"                                                                    >  $1/$1-server.ovpn
echo "proto udp"                                                                  >> $1/$1-server.ovpn
echo "dev $4"                                                                     >> $1/$1-server.ovpn
echo "ca \"$1\\\\ca.crt\""                                                        >> $1/$1-server.ovpn
echo "cert \"$1\\\\$1.crt\""                                                      >> $1/$1-server.ovpn
echo "key \"$1\\\\$1.key\""                                                       >> $1/$1-server.ovpn
echo "dh \"$1\\\\dh2048.pem\""                                                    >> $1/$1-server.ovpn
echo "server $net.0 255.255.255.0"                                                >> $1/$1-server.ovpn
echo "client-config-dir \"$1\\\\ccd\""                                            >> $1/$1-server.ovpn
echo "tls-auth \"C:\\\\Program\ Files\\\\OpenVPN\\\\config\\\\$1\\\\ta.key\" 0"   >> $1/$1-server.ovpn
echo "comp-lzo"                                                                   >> $1/$1-server.ovpn
echo "max-clients 253"                                                            >> $1/$1-server.ovpn
echo "status \"C:\\\\Program\ Files\\\\OpenVPN\\\\log\\\\$1-status.log\""         >> $1/$1-server.ovpn
echo "log \"C:\\\\Program\ Files\\\\OpenVPN\\\\log\\\\$1-vpn.log\""               >> $1/$1-server.ovpn
echo "script-security 2"                                                          >> $1/$1-server.ovpn
# Уровень отладочной информации verb                                              >> $1/$1-server.ovpn
echo "verb 2"                                                                     >> $1/$1-server.ovpn
# Разрешаем обмен пакетами между клиентами                                        >> $1/$1-server.ovpn
echo "client-to-client"                                                           >> $1/$1-server.ovpn
echo "keepalive 10 60 "                                                           >> $1/$1-server.ovpn

# Конвертируем переносы строк в Win стиль
unix2dos $1/$1-server.ovpn

# Настраиваем логирование
touch /var/log/openvpn/$1-vpn.log
touch /var/log/openvpn/$1-status.log
chown root:adm /var/log/openvpn/$1-*
chmod 640 /var/log/openvpn/$1-*

# Создаем конфиг linux клиента
echo "client"                                                                     >> $1/$1-client.conf
echo "dev tap"                                                                    >> $1/$1-client.conf
echo "proto udp"                                                                  >> $1/$1-client.conf
echo "remote $5 $2"                                                               >> $1/$1-client.conf
echo "remote $6 $2"                                                               >> $1/$1-client.conf
echo "resolv-retry infinite"                                                      >> $1/$1-client.conf
echo "nobind"                                                                     >> $1/$1-client.conf
echo "persist-key"                                                                >> $1/$1-client.conf
echo "persist-tun"                                                                >> $1/$1-client.conf
echo "ca $1/ca.crt"                                                               >> $1/$1-client.conf
echo "cert $1/$1-000.crt"                                                         >> $1/$1-client.conf
echo "key $1/$1-000.key"                                                          >> $1/$1-client.conf
# Проверяем сертификат, предъявленный сервером.                                   >> $1/$1-client.conf
echo "ns-cert-type server"                                                        >> $1/$1-client.conf
echo "tls-auth $1/ta.key 1"                                                       >> $1/$1-client.conf
echo "comp-lzo"                                                                   >> $1/$1-client.conf
# отправку ping-подобных сообщений для того, чтобы каждая сторона знала что другая перестала отвечать
# Пинг каждые 5 секунд, если в течение 20 секунд нет ответа, то считается что удаленных хост не доступен
echo "keepalive 4 16"                                                             >> $1/$1-client.conf
echo "verb 3"                                                                     >> $1/$1-client.conf
echo "route-method exe"                                                           >> $1/$1-client.conf
echo "route-delay 5"                                                              >> $1/$1-client.conf
echo "#up /etc/openvpn/update-systemd-resolved"                                    >> $1/$1-client.conf
echo "#down /etc/openvpn/update-systemd-resolved"                                  >> $1/$1-client.conf

# Создаем конфиг windows клиента
unix2dos -n $1/$1-client.conf $1/$1-client.ovpn
echo "# Игнорировать остальные DNS сервера, если используем DNS сервер через VPN" >> $1/$1-client.ovpn
echo "block-outside-dns"                                                          >> $1/$1-client.ovpn

# Создаем конфиги для каждого клиента
mkdir $1/config
for i in {002..254};
do
    cat $1/$1-client.conf > $1/config/$1-client-$i.conf

    echo "<ca>" >> $1/config/$1-client-$i.conf
    cat $1/ca.crt >> $1/config/$1-client-$i.conf
    echo "</ca>" >> $1/config/$1-client-$i.conf

    echo "<cert>" >> $1/config/$1-client-$i.conf
    cat $1/key/$1-$i.crt >> $1/config/$1-client-$i.conf
    echo "</cert>" >> $1/config/$1-client-$i.conf
 
    echo "<key>" >> $1/config/$1-client-$i.conf
    cat $1/key/$1-$i.key >> $1/config/$1-client-$i.conf
    echo "</key>" >> $1/config/$1-client-$i.conf

    echo "<tls-auth>" >> $1/config/$1-client-$i.conf
    cat $1/ta.key >> $1/config/$1-client-$i.conf
    echo "</tls-auth>" >> $1/config/$1-client-$i.conf
done

Права на запуск скрипта:

chmod +x /etc/openvpn/keygen.sh

Создаем каталог логов:

mkdir /var/log/openvpn

Генерация ключей и конфигурационных файлов

cd /etc/openvpn
./keygen.sh vpnnetwork 60000 10.20.30 tap0 vpn01.first-leon.ru vpn02.first-leon.ru

Список параметров:

# $1 название сети
# $2 порт
# $3 подсеть (например: 10.20.30)
# $4 tap интерфейс
# $5 первый dns адрес vpn сервера
# $6 второй dns адрес vpn сервера

В результате выполнения скрипта, в каталоге "название сети" будут расположены следующие файлы и директории:

ccd
config                      # конфиги клиентов содержащие ключи
key                         # ключи клиентов
ca.crt
dh512.pem
ta.key
название_сети-client.conf   # Линуксовый клиентский конфиг
название_сети-client.ovpn   # Виндовый клиентский конфиг
название_сети-server.conf   # Линуксовый серверный конфиг
название_сети-server.ovpn   # Виндовый серверный конфиг

Запуск сервера в Linux

Если запускаем сервер на другой машине, чем та, на которой создавали конфиги и ключи, то копируем в /etc/openvpn каталог "название сети".

OpenVPN устроен так: сколько конфигурационных файлов в директории для конфигов - столько он и запускает клиентов/серверов.

Стало быть для запуска сервера нужно:

cp /etc/openvpn/"название сети"/"название сети"-server.conf /etc/openvpn/
systemctl reenable openvpn.service
systemctl restart openvpn.service

Запуск сервера в Windows

Для Win сервера меняется путь расположения каталога с ключами:

C:\Program Files\OpenVPN\config\"название сети"

Конфигурационный файл будет лежать тут:

C:\Program Files\OpenVPN\config\название_сети-server.ovpn

Настройка клиента в Linux

Для запуска клиента, копируем конфигурационный файл:

/etc/openvpn/название_сети/название_сети-client-XXX.conf

Рестартуем сервис:

systemctl restart openvpn.service
systemctl restart openvpn

Настройка клиента в Windows

Обратить внимание на разрядность системы! Устанавливайте клиента соответствующей разрядности.

В моем случае необходимости в графической оболочке управления сервисом не было, потому ее и не ставил:

Ovpn01.png

Ovpn02.png

Копируем конфигурационный файл:

C:\Program Files\OpenVPN\config\название_сети-client-XXX.ovpn

Рестартуем службу в оснастке управления службами и при необходимости настраиваем автозапуск:

Ovpn04.png

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

Image07.png

В случае возникновения проблем, смотрим логи

Image08.png

В данном примере, в логе сказано: "Все хорошо, соединение установлено!"

Image09.png

Разное

Добавление tap адаптера в Windows

При установке openvpn в систему автоматически добавляется tap адаптер. Он и будет использоваться при запуске первого сервера/клиента.

Для каждого дополнительного сервера/клиента необходимо вручную добавить дополнительные адаптеры путем запуска скрипта:

C:\Program Files\TAP-Windows\bin\addtap.bat

Проблемы при добавлении маршрутов в Windows

Попробуйте увеличить значение

route-delay 5

На практике не более 1% машин требуют значения более 5-ти (в большинстве случаев хватает 2)

Добавление маршрута по умолчанию через vpn соединение

Добавить в конфиг клиента: В Linux:

redirect-gateway

В Windows:

redirect-gateway def1
#Или
redirect-gateway local def1

Отзыв сертификата

В случае, если вам нужно запретить подключение к серверу для какого либо клиента, необходимо отозвать сертификат этого клиент. Для этого необходимо выполнить следующее:

cd /etc/openvpn/название_сети/easy-rsa/2.0/
. ./var
./revoke-full название_сети-xxx

Где xxx - номер клиента.

После этих операция, в каталоге /etc/openvpn/название_сети/easy-rsa/2.0/keys появится файл crl.pem содержащий изъятые сертификаты.

Необходимо указать серверу принимать во внимание этот файл. Для этого в конфиге сервера /etc/openvpn/"название сети"/"название сети"-server.conf добавить параметр:

crl-verify /etc/openvpn/"название_сети"/easy-rsa/2.0/keys/crl.pem

Обновление DNS серверов в Ubuntu 18.04

При использовании DNS сервера через VPN, в Ubuntu 18.04 необходимо добавить скрипт обновления DNS через systemd:
#!/usr/bin/env bash
#
# OpenVPN helper to add DHCP information into systemd-resolved via DBus.
# Copyright (C) 2016, Jonathan Wright <jon@than.io>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# This script will parse DHCP options set via OpenVPN (dhcp-option) to update
# systemd-resolved directly via DBus, instead of updating /etc/resolv.conf. To
# install, set as the 'up' and 'down' script in your OpenVPN configuration file
# or via the command-line arguments, alongside setting the 'down-pre' option to
# run the 'down' script before the device is closed. For example:
#   up /etc/openvpn/scripts/update-systemd-resolved
#   down /etc/openvpn/scripts/update-systemd-resolved
#   down-pre 

# Define what needs to be called via DBus
DBUS_DEST="org.freedesktop.resolve1"
DBUS_NODE="/org/freedesktop/resolve1" 
SCRIPT_NAME="${BASH_SOURCE[0]##*/}"
log() {
  logger -s -t "$SCRIPT_NAME" "$@"
}
for level in emerg err warning info debug; do
  printf -v functext -- '%s() { log -p user.%s -- "$@" ; }' "$level" "$level"
  eval "$functext"
done

usage() {
  err "${1:?${1}. }. Usage: ${SCRIPT_NAME} up|down device_name."
}

busctl_call() {
  # Preserve busctl's exit status
  busctl call "$DBUS_DEST" "$DBUS_NODE" "${DBUS_DEST}.Manager" "$@" || {
    local -i status=$?
    emerg "'busctl' exited with status $status"
    return $status
  }
}

get_link_info() {
  dev="$1"
  shift

  link=
  link="$(ip link show dev "$dev")" || return $?

  echo "$dev" "${link%%:*}"
}

dhcp_settings() {
  for foreign_option in "${!foreign_option_@}"; do
    foreign_option_value="${!foreign_option}" 

    "$foreign_option_value" == *dhcp-option*  \
      && echo "${foreign_option_value#dhcp-option }"
  done
}

up() {
  local link="$1"
  shift
  local if_index="$1"
  shift

  info "Link '$link' coming up"

  # Preset values for processing -- will be altered in the various process_*
  # functions.
  local -a dns_servers=() dns_domain=() dns_search=() dns_routed=()
  local -i dns_server_count=0 dns_domain_count=0 dns_search_count=0 dns_routed_count=0
  local dns_sec=""

  while read -r setting; do
    setting_type="${setting%% *}"
    setting_value="${setting#* }"

    process_setting_function="${setting_type,,}"
    process_setting_function="process_${process_setting_function//-/_}"

    if declare -f "$process_setting_function" &>/dev/null; then
      "$process_setting_function" "$setting_value" || return $?
    else
      warning "Not a recognized DHCP setting: '${setting}'"
    fi
  done < <(dhcp_settings)

  if [[ "${#dns_servers[*]}" -gt 0 ]]; then
    busctl_params=("$if_index" "$dns_server_count" "${dns_servers[@]}")
    info "SetLinkDNS(${busctl_params[*]})"
    busctl_call SetLinkDNS 'ia(iay)' "${busctl_params[@]}" || return $?
  fi

  if [[ "${#dns_domain[*]}" -gt 0 \
     || "${#dns_search[*]}" -gt 0 \
     || "${#dns_routed[*]}" -gt 0 ]]; then
    dns_count=$((dns_domain_count+dns_search_count+dns_routed_count))
    busctl_params=("$if_index" "$dns_count")
    if [[ "${#dns_domain[*]}" -gt 0 ]]; then
      busctl_params+=("${dns_domain[@]}")
    fi
    if [[ "${#dns_search[*]}" -gt 0 ]]; then
      busctl_params+=("${dns_search[@]}")
    fi
    if [[ "${#dns_routed[*]}" -gt 0 ]]; then
      busctl_params+=("${dns_routed[@]}")
    fi
    info "SetLinkDomains(${busctl_params[*]})"
    busctl_call SetLinkDomains 'ia(sb)' "${busctl_params[@]}" || return $?
  fi

  if [[ -n "${dns_sec}" ]]; then
    if [[ "${dns_sec}" == "default" ]]; then
      # We need to provide an empty string to use the default settings
      info "SetLinkDNSSEC($if_index )"
      busctl_call SetLinkDNSSEC 'is' "$if_index" "" || return $?
    else
      info "SetLinkDNSSEC($if_index ${dns_sec})"
      busctl_call SetLinkDNSSEC 'is' "$if_index" "${dns_sec}" || return $?
    fi
  fi
}

down() {
  local link="$1"
  shift
  local if_index="$1"
  shift

  info "Link '$link' going down"
  if [[ "$(whoami 2>/dev/null)" != "root" ]]; then
    # Cleanly handle the privilege dropped case by not calling RevertLink
    info "Privileges dropped in the client: Cannot call RevertLink."
  else
    busctl_call RevertLink i "$if_index"
  fi
}

process_dns() {
  address="$1"
  shift

  if looks_like_ipv6 "$address"; then
    process_dns_ipv6 "$address" || return $?
  elif looks_like_ipv4 "$address"; then
    process_dns_ipv4 "$address" || return $?
  else
    err "Not a valid IPv6 or IPv4 address: '$address'"
    return 1
  fi
}

looks_like_ipv4() {
  -n "$1"  && {
    local dots="${1//[^.]}"
    (( ${#dots} == 3 ))
  }
}

looks_like_ipv6() {
  -n "$1"  && {
    local colons="${1//[^:]}"
    (( ${#colons} >= 2 ))
  }
}

process_dns_ipv4() {
  local address="$1"
  shift

  info "Adding IPv4 DNS Server ${address}"
  (( dns_server_count += 1 ))
  dns_servers+=(2 4 ${address//./ })
}

# Enforces RFC 5952:
#   1. Don't shorten a single 0 field to '::'
#   2. Only longest run of zeros should be compressed
#   3. If there are multiple longest runs, the leftmost should be compressed
#   4. Address must be maximally compressed, so no all-zero runs next to '::'
#
# ...
#
# Thank goodness we don't have to handle port numbers, though :)
parse_ipv6() {
  local raw_address="$1"

  log_invalid_ipv6() {
    local message="'$raw_address' is not a valid IPv6 address"
    emerg "${message}: $*"
  }

  trap -- 'unset -f log_invalid_ipv6' RETURN

  if "$raw_address" == *::*::* ; then
    log_invalid_ipv6 "address cannot contain more than one '::'"
    return 1
  elif "$raw_address" =~ :0+::  || "$raw_address" =~ ::0+: ; then
    log_invalid_ipv6 "address contains a 0-group adjacent to '::' and is not maximally shortened"
    return 1
  fi

  local -i length=8
  local -a raw_segments=()

  IFS=$':' read -r -a raw_segments <<<"$raw_address"

  local -i raw_length="${#raw_segments[@]}"

  if (( raw_length > length )); then
    log_invalid_ipv6 "expected ${length} segments, got ${raw_length}"
    return 1
  fi

  # Store zero-runs keyed to their sizes, storing all non-zero segments prefixed
  # with a token marking them as such.
  local nonzero_prefix=$'!'
  local -i zero_run_i=0 compressed_i=0
  local -a tokenized_segments=()
  local decimal_segment= next_decimal_segment=

  for (( i = 0 ; i < raw_length ; i++ )); do
    raw_segment="${raw_segments[i]}"
    printf -v decimal_segment -- '%d' "0x${raw_segment:-0}"
    # We're in the compressed group.  The length of this run should be
    # enough to bring the total number of segments to 8.
    if -z "$raw_segment" ; then
      (( compressed_i = zero_run_i ))
      # `+ 1' because the length of the current segment is counted in
      # `raw_length'.
      (( tokenized_segments[zero_run_i] = ((length - raw_length) + 1) ))
      # If we have an address like `::1', skip processing the next group to
      # avoid double-counting the zero-run, and increment the number of
      # 0-groups to add since the second empty group is counted in
      # `raw_length'.
      if [[ -z "${raw_segments[i + 1]}" ]]; then
        (( i++ ))
        (( tokenized_segments[zero_run_i]++ ))
      fi
      (( zero_run_i++ ))
    elif (( decimal_segment == 0 )); then
      (( tokenized_segments[zero_run_i]++ ))
      # The run is over if the next segment is not 0, so increment the
      # tracking index.
      printf -v next_decimal_segment -- '%d' "0x${raw_segments[i + 1]}"
      (( next_decimal_segment != 0 )) && (( zero_run_i++ ))
    else
      # Prefix the raw segment with `nonzero_prefix' to mark this as a
      # non-zero field.
      tokenized_segments[zero_run_i]="${nonzero_prefix}${decimal_segment}"
      (( zero_run_i++ ))
    fi
  done

  if "$raw_address" == *::* ; then
    if (( ${#tokenized_segments[*]} == length )); then
      log_invalid_ipv6 "single '0' fields should not be compressed"
      return 1
    else
      local -i largest_run_i=0 largest_run=0
      for (( i = 0 ; i < ${#tokenized_segments[@]}; i ++ )); do
        # Skip groups that aren't zero-runs
        [[ "${tokenized_segments[i]:0:1}" == "$nonzero_prefix" ]] && continue 
        if (( tokenized_segments[i] > largest_run )); then
          (( largest_run_i = i ))
          largest_run="${tokenized_segments[i]}"
        fi
      done
      local -i compressed_run="${tokenized_segments[compressed_i]}"
      if (( largest_run > compressed_run )); then
        log_invalid_ipv6 "the compressed run of all-zero fields is smaller than the largest such run"
        return 1
      elif (( largest_run == compressed_run )) && (( largest_run_i < compressed_i )); then
        log_invalid_ipv6 "only the leftmost largest run of all-zero fields should be compressed"
        return 1
      fi
    fi
  fi
  for segment in "${tokenized_segments[@]}"; do
    if [[ "${segment:0:1}" == "$nonzero_prefix" ]]; then
      printf -- '%04x\n' "${segment#${nonzero_prefix}}"
    else
      for (( n = 0 ; n < segment ; n++ )); do
        echo 0000
      done
    fi
  done
}

process_dns_ipv6() {
  local address="$1"
  shift

  info "Adding IPv6 DNS Server ${address}"
  local -a segments=()
  segments=($(parse_ipv6 "$address")) || return $?
  # Add AF_INET6 and byte count
  dns_servers+=(10 16)
  for segment in "${segments[@]}"; do
    dns_servers+=("$((16#${segment:0:2}))" "$((16#${segment:2:2}))")
  done
  (( dns_server_count += 1 ))
}

process_domain() {
  local domain="$1"
  shift
  info "Setting DNS Domain ${domain}"
  (( dns_domain_count = 1 ))
  dns_domain=("${domain}" false)
}

process_adapter_domain_suffix() {
  # This enables support for ADAPTER_DOMAIN_SUFFIX which is a Microsoft standard
  # which works in the same way as DOMAIN to set the primary search domain on
  # this specific link.
  process_domain "$@"
}

process_domain_search() {
  local domain="$1"
  shift
  info "Adding DNS Search Domain ${domain}"
  (( dns_search_count += 1 ))
  dns_search+=("${domain}" false)
}

process_domain_route() {
  local domain="$1"
  shift
  info "Adding DNS Routed Domain ${domain}"
  (( dns_routed_count += 1 ))
  dns_routed+=("${domain}" true)
}

process_dnssec() {
  local option="$1" setting=""
  shift
  case "${option,,}" in
    yes|true)
      setting="yes" ;;
    no|false)
      setting="no" ;;
    default)
      setting="default" ;;
    allow-downgrade)
      setting="allow-downgrade" ;;
    *)
      local message="'$option' is not a valid DNSSEC option"
      emerg "${message}"
      return 1 ;;
  esac
  info "Setting DNSSEC to ${setting}"
  dns_sec="${setting}"
}

main() {
  local script_type="$1"
  shift
  local dev="$1"
  shift
  if -z "$script_type" ; then
    usage 'No script type specified'
    return 1
  elif -z "$dev" ; then
    usage 'No device name specified'
    return 1
  elif ! declare -f "${script_type}" &>/dev/null; then
    usage "Invalid script type: '${script_type}'"
    return 1
  else
    if ! read -r link if_index _ < <(get_link_info "$dev"); then
      usage "Invalid device name: '$dev'"
      return 1
    fi
    "$script_type" "$link" "$if_index" "$@"
  fi
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]] || "$AUTOMATED_TESTING" == 1 ; then
  set -o nounset
  main "${script_type:-}" "${dev:-}" "$@"
fi

Вопросы, предложения, обсуждения

Тут