Contents

Ubuntu as a Gateway

Быстрый переход:

| Ubuntu 20.04 | chrony | netplan | dns | isc-dhcpd | ipset | iptables | htb.init | build | libvirt | bridge

Обозначения:

  • $ timedatactl - запуск от пользователя
  • root> timedatactl - запуск от суперпользователя
  • some text - вывод программы
  • # - комментарий
  • ... - пропуск не нужной информации

Сервер времени chrony

Установка часового пояса

1
2
3
4
5
$ sudo timedatectl status
$ sudo timedatectl list-timezones
$ sudo timedatectl set-timezone Europe/Moscow
$ ls -l /etc/localtime
lrwxrwxrwx 1 root root 35 апр  6 19:00 /etc/localtime -> ../usr/share/zoneinfo/Europe/Moscow

Установка и настройка chrony

Установим:

1
$ sudo apt install chrony

Проверим, что запущен и работает:

1
2
3
$ sudo systemctl status chrony
$ sudo systemctl is-enabled chrony
$ sudo systemctl enable --now chrony

Разбор параметров:

1
$ chronyc tracking
  • Reference ID - идентификатор и имя, с которым компьютер в настоящее время синхронизирован.
  • Stratum - количество переходов к компьютеру с установленными основными часами.
  • Ref time - это время по Гринвичу, в которое было выполнено последнее измерение из эталонного источника.
  • System time - задержка системных часов от синхронизированного сервера.
  • Last offset - расчетное смещение последнего обновления часов.
  • RMS offset - долгосрочное среднее арифметическое значения смещения.
  • Frequency - это частота, на которой часы системы будут работать неправильно, если хронограф не проведет - коррекцию. Она выражена в ppm - ч/м (частей на миллион).
  • Residual freq - остаточная частота указывает на разницу между измерениями от опорного источника и используемой в настоящее время частотой.
  • Skew - расчетная погрешность, связанная с погрешностью частоты.
  • Root delay - суммарная задержка сетевого пути к опорному серверу, с которым синхронизируется компьютер.
  • Leap status - это статус, который может иметь одно из следующих значений - нормальное, добавить второй, удалить второй или не синхронизироваться.

Получим информацию об источниках:

1
2
3
$ chronyc sources
# с комментариями :)
$ chronyc sources -v

Выберем ближайшие сервера времени к нашему серверу и добавим их в конфиг:

1
$ sudo vi /etc/chrony/chrony.conf

По-умолчанию chrony работает как клиент, чтобы работал как сервер времени добавим в конфигурационный файл разрешение с какими сетями можно работать:

1
2
allow 192.168.8.0/24
allow 172.16.8.0/24

Если добавить просто allow, то chrony будет отвечать всем. Подробнее об опциях здесь

Перезапустим демона:

1
$ sudo systemctl restart chronyd

Посмотрим логи chrony:

1
$ journalctl --unit=chrony

Настройка сетевых интерфейсов

Есть 2 интерфейса, enp6s0 и enp7s0. В enp7s0 будет подключен интернет, а в enp6s0 локальные сети.

1
2
3
4
5
6
7
8
9
$ ip -c a | grep enp -A 4
2: enp6s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:e0:4c:2a:3d:70 brd ff:ff:ff:ff:ff:ff
3: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 50:3e:aa:1f:6e:fc brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.54/24 brd 192.168.3.255 scope global dynamic enp7s0
       valid_lft 76623sec preferred_lft 76623sec
    inet6 fe80::523e:aaff:fe1f:6efc/64 scope link
       valid_lft forever preferred_lft forever

Настроим интерфейсы с помощью netplan. Все конфигурационные файлы Netplan хранятся в директории /etc/netplan, имя файла может быть любым, расширение должно быть .yaml. Если файлов конфигурации несколько, то они обрабатываются в алфавитном порядке.

У меня получился такой конфиг:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ sudo cat /etc/netplan/manual.yaml
network:
  ethernets:
    enp6s0:
      dhcp4: false
      addresses:
        - 192.168.8.1/24
        - 172.16.8.1/24
    enp7s0:
      dhcp4: true
      nameservers:
        addresses:
          - 127.0.0.1
        search:
          - zavod.lan
  version: 2
  renderer: networkd

Так же можно прописать статические маршруты в .yaml-файлах и настройки wi-fi. Куча примеров на офиц. сайте

Перед применением конфигурации, проверим её:

1
$ sudo netplan generate

Если команда generate не выдала ничего, значит конфиг написан без ошибок. Я удалил все автоматически сгенерированные файлы, оставил только свой. Применить изменения можно либо командой netplan apply, либо netplan try, которая вернёт прежние настройки, если новые вы не подтвердите за 120 сек.

Теперь, подключив сетевой кабель, проверим адреса на сетевой карте:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ ip -c a | grep enp -A 6
2: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:e0:4c:2a:3d:70 brd ff:ff:ff:ff:ff:ff
    inet 192.168.8.1/24 brd 192.168.8.255 scope global enp6s0
       valid_lft forever preferred_lft forever
    inet 172.16.8.1/24 brd 172.16.8.255 scope global enp6s0
       valid_lft forever preferred_lft forever
    inet6 fe80::2e0:4cff:fe2a:3d70/64 scope link
       valid_lft forever preferred_lft forever
3: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 50:3e:aa:1f:6e:fc brd ff:ff:ff:ff:ff:ff
    inet 192.168.3.54/24 brd 192.168.3.255 scope global dynamic enp7s0
       valid_lft 86518sec preferred_lft 86518sec
    inet6 fe80::523e:aaff:fe1f:6efc/64 scope link
       valid_lft forever preferred_lft forever

$ ip route
default via 192.168.3.1 dev enp7s0 proto dhcp src 192.168.3.54 metric 100
172.16.8.0/24 dev enp6s0 proto kernel scope link src 172.16.8.1
192.168.3.0/24 dev enp7s0 proto kernel scope link src 192.168.3.54
192.168.3.1 dev enp7s0 proto dhcp scope link src 192.168.3.54 metric 100
192.168.8.0/24 dev enp6s0 proto kernel scope link src 192.168.8.1

Настройка dns (bind)

Устанавливаем необходимое:

1
$ sudo apt install bind9 bind9utils bind9-doc bind9-host

Версия с доп. информацией:

1
$ named -V

В Ubuntu 20.04 пакет bind использует корневые сервера из /usr/share/dns/root.hints, вместо db.root

Добавим в автозагрузку и запустим:

1
2
$ sudo systemctl enable bind9
$ sudo systemctl start bind9

Bind работает от пользователя bind:bind, который создался автоматически во время установки. В основном используется udp/53, но так же и tcp/53 используется для больших ответов сервера и для передачи зоны.

Посмотрим, запустился ли:

1
$ sudo lsof -i -P -n | grep bind
  • -P параметр указывает показывать номера портов
  • -n параметр указывает показывать ip-адреса

Для управления используется rndc, подключается к TCP-порту 953:

1
2
3
$ sudo rndc status
...
server is up and running

Для описания своих зон нужно использовать файл /etc/bind/named.conf.local, а для настройки bind - /etc/bind/named.conf.options.

DNS-resolver

Посмотрим какой используется resolver в системе:

1
2
3
$ sudo systemd-resolve --status
# or
$ sudo resolvectl status

Чтобы установить наш DNS-сервер как resolver по-умолчанию, необходимо добавить следующую строку в файл /etc/systemd/resolved.conf в секцию [Resolve]:

1
2
3
4
$ cat /etc/systemd/resolved.conf
...
DNS=127.0.0.1
...

Ну и нужно перезапустить:

1
$ sudo systemctl restart systemd-resolved

Наблюдаем в секции Global DNS Servers: 127.0.0.1:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ sudo systemd-resolve --status | grep -i "DNS Servers" -B 8
Global
       LLMNR setting: no
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no
         DNS Servers: 127.0.0.1
--
Link 3 (enp7s0)
      Current Scopes: DNS
DefaultRoute setting: yes
       LLMNR setting: yes
MulticastDNS setting: no
  DNSOverTLS setting: no
      DNSSEC setting: no
    DNSSEC supported: no
         DNS Servers: 127.0.0.1

Ещё посмотрим на файл:

1
2
3
4
$ cat /etc/resolv.conf
nameserver 127.0.0.53
search lan
options edns0 trust-ad

Исправим, чтобы указывало на наш dns-server:

1
2
3
$ sudo apt install resolvconf
$ sudo systemctl enable named-resolvconf.service
$ sudo systemctl start named-resolvconf.service

Проверим, что теперь указывает на наш dns-server:

1
2
3
4
$ cat /etc/resolv.conf
nameserver 127.0.0.1
search lan
options edns0 trust-ad

Посмотреть логи можно 2мя способами:

1
2
$ sudo journalctl -e --unit=named
$ sudo journalctl -xe | grep named

Настройка dns-зон

Вот так выглядят конфиг-файлы. Конфиг-файл настроек bind:

/etc/bind/named.conf.options
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ sudo cat /etc/bind/named.conf.options
acl "lan" {
    192.168.8.0/24;
    172.16.8.0/24;
    localhost;
};

options {
  directory "/var/cache/bind";

  // If there is a firewall between you and nameservers you want
  // to talk to, you may need to fix the firewall to allow multiple
  // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

  // If your ISP provided one or more IP addresses for stable
  // nameservers, you probably want to use them as forwarders.
  // Uncomment the following block, and insert the addresses replacing
  // the all-0's placeholder.

  forwarders {
    1.1.1.1;
    8.8.8.8;
    8.8.4.4;
  };

  //========================================================================
  // If BIND logs error messages about the root key being expired,
  // you will need to update your keys.  See https://www.isc.org/bind-keys
  //========================================================================
  dnssec-validation auto;
  auth-nxdomain no;

  listen-on { lan; };
  listen-on-v6 { none; };

  recursion yes;
  allow-recursion { lan; };

  allow-query { lan; };

  version "NOT CURRENTLY AVAILABLE";
  querylog yes;
};

Файл для описания зон. Тут умышленно выдаём неправильный адрес для имени vk.com:

ВНИМАНИЕ! Стандартный путь хранения dns зон /var/lib/bind/

/etc/bind/named.conf.local
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cat /etc/bind/named.conf.local
//
// Do any local configuration here
//

// Consider adding the 1918 zones here, if they are not used in your
// organization
//include "/etc/bind/zones.rfc1918";

zone "vk.com" { type master; notify no; file "/etc/bind/db.127"; };

zone "zavod.lan" {
  type master;
  file "/etc/bind/dynamic/zavod.lan";
  notify no;
};

zone "8.168.192.in-addr.arpa" {
  type master;
  file "/etc/bind/dynamic/8.168.192.in-addr.arpa";
  notify no;
};

zone "8.16.172.in-addr.arpa" {
  type master;
  file "/etc/bind/dynamic/8.16.172.in-addr.arpa";
  notify no;
};

Сами файлы зон. @ означает имя из строки zone "zavod.lan" в файле /usr/bind/named.conf.local.

/etc/bind/dynamic/zavod.lan
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ sudo cat /etc/bind/dynamic/zavod.lan
$TTL    604800
@       IN      SOA     puma.zavod.lan. root.localhost. (
                             13         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name Server Information
; @ = name of zone from named.conf.local

@        IN      NS ns1.zavod.lan.
@        IN      NS ns2.zavod.lan.

; IP address of Name Server

ns1     IN      A 192.168.8.1
ns2     IN      A 172.16.8.1

; Mail Exchanger

; zavod.lan.    IN     MX   10   mail.zavod.lan.

; A – Record HostName To Ip Address

puma  IN  A 192.168.8.1
router  IN  A 192.168.8.1
www     IN      A 192.168.8.88
wind  IN  A 172.16.8.88

; CNAME record

ftp     IN      CNAME www.zavod.lan.
/etc/bind/dynamic/8.168.192.in-addr.arpa
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ sudo cat /etc/bind/dynamic/8.168.192.in-addr.arpa
;
; BIND reverse data file for local loopback interface
;
$TTL    604800
@       IN      SOA     puma.zavod.lan. root.localhost. (
                              5         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name Server Information
@       IN      NS  ns1.zavod.lan.
@       IN      NS  ns2.zavod.lan.

; Reverse lookup for Name Server

1 IN      PTR    ns1.zavod.lan.

;PTR Record IP address to HostName

88  IN  PTR    www.zavod.lan.
1 IN      PTR    puma.zavod.lan.
/etc/bind/dynamic/8.16.172.in-addr.arpa
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ sudo cat cat /etc/bind/dynamic/8.16.172.in-addr.arpa
;
; BIND reverse data file for local loopback interface
;
$TTL    604800
@       IN      SOA     puma.zavod.lan. root.localhost. (
                              5         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name Server Information
@       IN      NS  ns1.zavod.lan.
@       IN      NS  ns2.zavod.lan.

; Reverse lookup for Name Server

1 IN      PTR    ns2.zavod.lan.

;PTR Record IP address to HostName

88  IN  PTR    wind.zavod.lan.
1 IN      PTR    puma.zavod.lan.

Вынесем логирование в отдельные файлы

Логи будут писаться в директорию /var/log/named, поэтому её необходимо создать и дать права пользователю bind писать в неё:

1
2
$ sudo mkdir /var/log/named
$ sudo chown bind:bind /var/log/named

severity — указывает уровень логирования. Варианты: critical, error, warning, notice, info, debug, dynamic.

Теперь остаётся добавить в конфиг named следующие директивы:

/etc/bind/named.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cat <<- EOF | sudo tee --append /etc/bind/named.conf

logging {
channel "misc" {
  // file "/var/lib/bind/misc.log" versions 4 size 4m;
  file "/var/log/named/misc.log" versions 4 size 4m;
  print-time YES;
  print-severity YES;
  print-category YES;
  severity info;
  };
channel "query" {
  // file "/var/lib/bind/query.log" versions 4 size 4m;
  file "/var/log/named/query.log" versions 4 size 4m;
  print-time YES;
  print-severity NO;
  print-category NO;
  severity info;
  };
category default {
  "misc";
  };
category queries {
  "query";
  };
};
EOF

Проверим настройки и перезапустим:

1
2
$ sudo named-checkconf /etc/bind/named.conf
$ sudo systemctl restart named

Готово, логи теперь здесь /var/log/named/ и в /var/log/syslog будет чище.

Настроим управление через утилиту rndc, а так же дадим возможность isc-dhcp-server обновлять dns-зоны.

Для этого нужно сгенерировать rndc.key, а так же добавить его в конфиги bind и isc-dhcp-server.

Сгенерируем rndc.key:

1
2
rndc-confgen -a
wrote key file "/etc/bind/rndc.key

Добавим rndc.key в /etc/bind/named.conf и проверим конфиг:

1
2
3
$ echo "include \"/etc/bind/rndc.key\";" | sudo tee --append /etc/bind/named.conf
include "/etc/bind/rndc.key";
$ sudo named-checkconf

Добавим возможность управления только с localhost:

1
2
3
4
5
6
7
8
cat <<- EOF | tee --append /etc/bind/named.conf

controls {
  inet 127.0.0.1 port 953
  allow { 127.0.0.1; } keys { "rndc-key"; };
};
EOF
$ sudo named-checkconf

Так же в файлах зон named.conf.local нужно указать строку allow-update { key rndc-key; };, благодая ей будет возможно обновление файла через rndc.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ cat /etc/bind/named.conf.local | grep -Ei "zone|allow-update"
// Consider adding the 1918 zones here, if they are not used in your
//include "/etc/bind/zones.rfc1918";
zone "vk.com" { type master; notify no; file "/etc/bind/db.127"; };
zone "zavod.lan" {
  allow-update { key rndc-key; };
zone "8.168.192.in-addr.arpa" {
  allow-update { key rndc-key; };
zone "8.16.172.in-addr.arpa" {
  allow-update { key rndc-key; };

Проверим, нет ли ошибок в конфигах и зонах:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ sudo named-checkconf /etc/bind/named.conf
$ named-checkzone zavod.lan /etc/bind/dynamic/zavod.lan
zone zavod.lan/IN: loaded serial 13
OK
$ named-checkzone 8.16.172.in-addr.arpa /etc/bind/dynamic/8.16.172.in-addr.arpa
zone 8.16.172.in-addr.arpa/IN: loaded serial 5
OK
$ named-checkzone 8.168.192.in-addr.arpa /etc/bind/dynamic/8.168.192.in-addr.arpa
zone 8.168.192.in-addr.arpa/IN: loaded serial 5
OK

Если ошибок нет, то нужно перезапустить демон named:

1
$ sudo systemctl restart named

Теперь настроим связь между named и isc-dhcp-server, чтобы при выдаче адреса, этот адрес записывался в зоны dns.

Стандартный путь для хранения зон это /var/lib/bind. Если хранить их в другом месте, то нужно добавить эту папку в apparmor и дать права на запись.

Так как у меня зоны лежат в /etc/bind/dynamic, придётся в apparmor разрешить named право на запись.

Убедимся, что named присутствует:

1
2
3
$ sudo apparmor_status | grep named
   /usr/sbin/named
   /usr/sbin/named (4764)

Добавим после /etc/bind/** r в файле /etc/apparmor.d/usr.sbin.named строку:

1
  /etc/bind/dynamic/** rw,

Сохраним файл и рестартнём apparmor:

1
$ sudo systemctl restart apparmor

Установим bind:bind на директорию /etc/bind/dynamic:

1
$ sudo chown -R bind:bind /etc/bind/dynamic

Теперь нужно подправить конфиг isc-dhcp-server, как его готовить описано ниже.

После настройки isc-dhcp-server следует вернуться сюда и применить эти настройки. Нужно добавить в файл /etc/dhcp/dhcpd.conf следующее:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ddns-updates on;
ddns-update-style interim;
update-static-leases on;

# Содержимое файла ключа /etc/bind/rndc.key
# Лучше его подключить с помощью директивы include /etc/bind/rndc.key
key "rndc-key" {
  algorithm hmac-sha256;
  secret "6p6yC7XOWuSp3I66vfmfoy1LjoFIUKypcjp935AUILI=";
};

# Зоны которые должен обновлять сервер:
zone zavod.lan. {
  primary 127.0.0.1;
  key rndc-key;
}

zone 8.168.192.in-addr.arpa. {
  primary 127.0.0.1;
  key rndc-key;
}

zone 8.16.172.in-addr.arpa. {
  primary 127.0.0.1;
  key rndc-key;
}

Проверим что всё верно и перезапустим демон:

1
2
$ sudo dhcpd -t
$ sudo systemctl restart isc-dhcp-server

Теперь, когда в локально сети появится новый компьютер, isc-dhcp-server выдаст ему свободный ip-адрес и обновит dns-зоны. При этом в каталоге рядом с зонами будут добавлены соответствующие файлы с расширением .jnl.

Работа с rndc

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

1
2
3
4
5
6
7
8
# убедимся, что всё синхонизировано, т.е. перенесём все временные данные из файла .jnl в файл зоны:
$ sudo rndc sync

# приостановим динамическое обновление
$ sudo rndc freeze zavod.lan

# либо приостановим обновление всех зон
$ sudo rndc freeze

После ручной правки зоны нужно сначала перечитать файлы зон:

1
$ sudo rndc reload

А затем восстановить работу динамического обновления:

1
2
3
4
$ sudo rndc thaw zavod.lan

# или для всех зон:
$ sudo rndc thaw

Может сложиться такая ситуация: На компьютере в локальной сети поменяли сетевую карту (изменился MAC-адрес), а запись с этим именем компьютера осталась в файле зон. Тогда нужно описанным выше способом удалить старую запись, а новая будет добавлена автоматически. Сообщения такого вида в логах подскажут о такой ситуации:

1
Apr 15 14:01:15 puma dhcpd[6253]: Forward map from ubuntu.zavod.lan to 172.16.8.94 FAILED: Has an address record but no DHCID, not mine.

Сменим ip-address у компьютера в сети:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ $ ip -c link show enp2s0
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 00:e0:4c:52:79:8f brd ff:ff:ff:ff:ff:ff
$ sudo ip link set enp2s0 address bc:47:3a:ba:ae:76
$ sudo ip -c a
2: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether bc:47:3a:ba:ae:76 brd ff:ff:ff:ff:ff:ff
    inet 192.168.8.88/24 brd 192.168.8.255 scope global dynamic noprefixroute enp2s0
       valid_lft 164sec preferred_lft 164sec
    inet6 fe80::51dd:19a6:7e53:e9a9/64 scope link noprefixroute
       valid_lft forever preferred_lft forever

По истечении valid_lft 164sec isc-dhcp-server почему-то новый ip-address не удалось получить. Получим вручную:

1
2
user@Q3:~$ sudo dhclient -r
user@Q3:~$ sudo dhclient enp2s0

Настройка isc-dhcp-server

Как и всегда, примеры конфигурации можно посмотреть здесь:

1
2
$ ls /usr/share/doc/
$ cat /usr/share/doc/isc-dhcp-server/examples/dhcpd.conf.example

Установим dhcp-server и укажем интерфейс для работы:

1
2
$ sudo apt install isc-dhcp-server
$ echo "INTERFACESv4=\"enp6s0\"" | sudo tee --append /etc/default/isc-dhcp-server

У меня получился вот такой конфиг:

/etc/dhcp/dhcpd.conf
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
$ sudo cat /etc/dhcp/dhcpd.conf
# option definitions common to all supported networks...
option domain-name "puma.local1";
option domain-name-servers 1.1.1.1, 8.8.8.8, 8.8.4.4;

# default-lease-time 600;
# max-lease-time 7200;

# The ddns-updates-style parameter controls whether or not the server will
# attempt to do a DNS update when a lease is confirmed. We default to the
# behavior of the version 2 packages ('none', since DHCP v2 didn't
# have support for DDNS.)
ddns-update-style none;

# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
authoritative;

# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
log-facility local7;

# This is a very basic subnet declaration.
#subnet 10.254.239.0 netmask 255.255.255.224 {
#  range 10.254.239.10 10.254.239.20;
#  option routers rtr-239-0-1.example.org, rtr-239-0-2.example.org;
#}

# This declaration allows BOOTP clients to get dynamic addresses,
# which we don't really recommend.
#subnet 10.254.239.32 netmask 255.255.255.224 {
#  range dynamic-bootp 10.254.239.40 10.254.239.60;
#  option broadcast-address 10.254.239.31;
#  option routers rtr-239-32-1.example.org;
#}

# Чтобы сделать несколько разных подсетей нужно использовть shared-network sharedname:
shared-network mynetwork {
  # Описание сети, указывающее какая из подсетей будет
  # обслуживаться. Указывается сетевой адрес и маска сети
  subnet 192.168.8.0 netmask 255.255.255.0 {
    # диапазон адресов для клиентов
    # 192.168.8.33-192.168.0.254
    range 192.168.8.33 192.168.8.254;
    # адрес DNS сервера, который будут использовать клиенты
    option domain-name-servers ns1.puma.local1;
    # устанавка домена по-умолчанию (dns-suffix)
    option domain-name "local1";
    # маска подсети для клиетов
    option subnet-mask 255.255.255.0;
    # маршрутизатор по умолчанию
    option routers 192.168.8.1; # or router.puma.local1
    # определяем широковещательный адрес
    option broadcast-address 192.168.8.255;
    # сказать клиентам, чтобы отдали адрес через 21600 секунд (6 часов)
    # после получения адреса
    default-lease-time 600;
    # забрать адрес самому через 28800 секунд (8 часов)
    max-lease-time 7200;
    # адрес ntp-сервера
    option ntp-servers ntp.local1;
    server-identifier puma.local1;
    # 3 опции для samba, если нужно обеспечить поддержку WINS:
    # option netbios-name-servers 192.168.0.1;
    # option netbios-dd-server 192.168.0.1;
    # option netbios-node-type 8;
    # Описания хостов
    group test { # Чтобы не было: WARNING: Host declarations are global.
      host q3 {
        hardware ethernet 00:e0:5c:52:79:8f;
        fixed-address 192.168.8.55;
      #  filename "vmunix.passacaglia";
      #  server-name "toccata.example.com";
      }
    }
  }

  subnet 172.16.8.0 netmask 255.255.255.0 {
    # диапазон адресов для клиентов
    # 192.168.8.33-192.168.0.254
    range 172.16.8.33 172.16.8.254;
    # адрес DNS сервера, который будут использовать клиенты
    option domain-name-servers ns1.puma.local1;
    # устанавка домена по-умолчанию (dns-suffix)
    option domain-name "local2";
    # маска подсети для клиетов
    option subnet-mask 255.255.255.0;
    # маршрутизатор по умолчанию
    option routers 172.16.8.1; # or router.puma.local2
    # определяем широковещательный адрес
    option broadcast-address 172.16.8.255;
    # сказать клиентам, чтобы отдали адрес через 21600 секунд (6 часов)
    # после получения адреса
    default-lease-time 100;
    # default-lease-time 600;
    # забрать адрес самому через 28800 секунд (8 часов)
    max-lease-time 200;
    # max-lease-time 7200;
    # адрес ntp-сервера
    option ntp-servers ntp.local2;
    server-identifier puma.local2;
    # 3 опции для samba, если нужно обеспечить поддержку WINS:
    # option netbios-name-servers 192.168.0.1;
    # option netbios-dd-server 192.168.0.1;
    # option netbios-node-type 8;
    # Описания хостов
    group test { # Чтобы не было: WARNING: Host declarations are global.
      host q32 {
        hardware ethernet 00:e0:4c:52:79:8f;
        fixed-address 172.16.8.88;
        # filename "vmunix.passacaglia";
        # server-name "toccata.example.com";
      }
    }
  }
}

По-умолчанию логи будут писаться в /var/log/syslog, можно вывести их в отдельный файл. Для этого нужно в конфиге включить опцию log-facility local7;, а так же добавить в конец файла /etc/rsyslog.d/50-default.conf:

1
2
local7.* /var/log/dhcpd.log
& stop

И перезапустить:

1
$ sudo systemctl restart rsyslog

Теперь логи будут добавляться в /var/log/dhcpd.log, но они так же будут и туда, куда раньше: /var/log/syslog. Для отключения этого нужно изменить строку в файле /etc/rsyslog.d/50-default.conf c (1) на (2):

1
2
(1) *.*;auth,authpriv.none          -/var/log/syslog
(2) *.*;auth,authpriv.none;local7.none    -/var/log/syslog

Ну и перезапустить демон:

1
$ sudo systemctl restart rsyslog

Тестируем конфиг и смотрим где лежит lease-файл:

1
$ dhcpd -t

Запускаем службу:

1
$ sudo systemctl start isc-dhcp-server

Смотрим status службы, если не она запустилась, ответы здесь:

  • /var/log/syslog
  • /var/log/dhcpd.log

Итак, dhcpd работает, можно посмотреть какие адреса выданы:

1
$ dhcp-lease-list

Чтобы пересоздать lease-file, нужно удалить старый и перезапустить демон isc-dhcpd. Проверим lease-файл на ошибки:

1
$ dhcpd -T

Нужно не забыть отключить isc-dhcp-server6, если он не используется!

После перезагрузки я вызвал journalctl -b и обнаружил ошибку: Can't create PID file /run/dhcp-server/dhcpd.pid: No such file or directory., а так же ошибки о неудачном запуске isc-dhcp-server6. Проблема решилась отключением dhcpd для ipv6:
sudo systemctl disable isc-dhcp-server6

Настройка ipset

ipset нужен для создания списков, которые потом можно использовать в правилах iptables. Для начала установим:

1
$ sudo apt install ipset

Возможные типы списков:

  • net - сети, например 192.168.8.0/24
  • ip - только ip, например 192.168.8.55
  • mac - MAC адреса, например 11:22:33:44:55:66
  • port - порты, удобно при создании списков ip,port
  • iface - сетевые интерфейсы, удобно при создании списков ip,iface

Примеры создания списков, где mylist - имя списка:

1
2
3
4
5
6
7
$ sudo ipset -N mylist nethash
$ sudo ipset create mylist nethash
$ sudo ipset create mylist hash:net
$ sudo ipset create mylist hash:ip
$ sudo ipset create mylist hash:ip,port
$ sudo ipset create mylist hash:ip,iface
$ sudo ipset create mylist hash:mac

Удалить список:

1
$ sudo ipset destroy mylist

Добавить данные в спискок, учтите, данные должены соответствовать тому, какой список был создан.

1
2
3
4
5
6
$ sudo ipset add mylist 192.168.5.5/24
$ sudo ipset add mylist 192.168.5.5
$ sudo ipset add mylist 192.168.5.5,80
$ sudo ipset add mylist 192.168.5.5,udp:1812
$ sudo ipset add mylist 192.168.5.5,eth0
$ sudo ipset add mylist 11:22:33:44:55:66

Удалить элемент из списка:

1
$ sudo ipset del mylist 192.168.5.5

Переименовать список:

1
$ sudo ipset rename OLDNAME NEWNAME

Посмотреть содержимое все или конкретного списка:

1
2
3
$ sudo ipset -L
$ sudo ipset --list
$ sudo ipset -L mylist

Использование списков в iptables:

1
2
3
$ sudo iptables -I INPUT -m set --match-set mylist src -j DROP
# или так
$ sudo iptables -I INPUT -m set ! --match-set mylist src -j ACCEPT

Чтобы после перезагрузки настройки ipset не превратились в тыкву:

1
2
$ sudo ipset save -file /etc/ipset.conf
$ sudo ipset restore -file /etc/ipset.conf

Опишем systemd сервис, который будет подгружать настроки ipset после загрузки системы и инициализации сети:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
cat <<- EOF | sudo tee /etc/systemd/system/ipset.service
[Unit]
Description=Service to start ipset rules
Before=network.target
Before=netfilter-persistent.service
Before=ufw.service
ConditionFileNotEmpty=/etc/ipset.conf

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/sbin/ipset restore -file /etc/ipset.conf
ExecStop=/sbin/ipset flush
ExecStopPost=/sbin/ipset destroy

[Install]
WantedBy=multi-user.target

RequiredBy=netfilter-persistent.service
RequiredBy=ufw.service
EOF
1
2
3
4
$ sudo systemctl daemon-reload
$ sudo systemctl list-unit-files | grep ipset
$ sudo systemctl is-enabled ipset.service
$ sudo systemctl enable ipset.service

Настройка iptables

Схема прохожения и подробное описание работы здесь.

  • enp6s0 - внутренний интерфейс
  • enp7s0 - внешний интерфейс

Чтоб посмотреть правила (по-умолчанию таблица filter):

1
$ sudo iptables -nvL

Для конкретной таблицы:

1
2
3
4
5
$ sudo iptables -nvL -t raw
$ sudo iptables -nvL -t mangle
$ sudo iptables -nvL -t nat
$ sudo iptables -nvL -t filter
$ sudo iptables -nvL -t security

Чтобы обнулить настройки iptables:

1
2
3
$ sudo iptables --flush
$ sudo iptables --table nat --flush
$ sudo iptables --delete-chain

Включим ip forwarding:

1
2
3
4
5
$ sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
# либо
$ sudo sysctl --write net.ipv4.ip_forward=1
# чтобы после перезагрузки работало:
$ echo "net.ipv4.ip_forward = 1" | sudo tee --append /etc/sysctl.conf

Ключ -A добавляет правило в конец цепочки, ключ -I в начало

1
2
3
sudo iptables -A FORWARD -i eth0 -o eth1 -s 192.168.8.0/24 -m conntrack --ctstate NEW -j ACCEPT
sudo iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
sudo iptables -A POSTROUTING -t nat -j MASQUERADE

Сохраним правила:

1
2
3
4
$ sudo apt-get install iptables-persistent
$ sudo netfilter-persistent save
# или сохранить по пути:
$ sudo netfilter-persistent save > /etc/iptables/rules.v4

Здесь всякие бинарники для управления iptables: ls /usr/sbin/iptables-*

Восстановим правила:

1
$ sudo iptables-restore /etc/iptables/rules.v4

Чтобы вывести список всех активных правил iptables по спецификации, выполните команду iptables с параметром -S:

1
$ sudo iptables -S

На этом базовая настрока iptables окончена.

Скрипт iptables.sh с комментариями.

iptables.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#!/bin/bash

# external
extif=enp7s0
extip="192.168.3.54"

# internal
intif=enp6s0
intip1="192.168.8.1"
intip2="172.16.8.1"
intlan1="192.168.8.0/24"
intlan2="172.16.8.0/24"

# lan obj
nginx="192.168.8.55:80"
slow="192.168.8.55"

echo "   -> Очистим все правила"
iptables --flush
iptables --table nat --flush
iptables --table mangle --flush
iptables --delete-chain 		# удаляем все таблицы пользователя

echo "   -> Установим политику по-умолчанию"
iptables --policy INPUT DROP
iptables --policy FORWARD ACCEPT
iptables --policy OUTPUT ACCEPT

echo "   -> Разрешим loopback интерфейс"
iptables -A INPUT -i lo -j ACCEPT
# iptables -A OUTPUT -o lo -j ACCEPT

# --- PREROUTING

echo ""
echo "   --- PREROUTING"
# echo "   -> Маркируем соединение компа из локальной сети"
# iptables -t mangle -A PREROUTING -s "$slow" -j CONNMARK --set-xmark 12
#
# echo "   -> Маркируем пакеты в соединении компа из локальной сети"
# iptables -t mangle -A PREROUTING -m mark --mark 12 -j MARK --set-mark 87

# echo "   -> Маркируем трафик компа из локальной сети"
# iptables -t mangle -A PREROUTING -s "$slow" -j MARK --set-mark 87

echo "   -> Прокинем трафик с внешнего ip на внутренний (dnat)"
iptables -t nat -A PREROUTING -d "$extip" -p tcp -m tcp --dport 80 -j DNAT --to-destination "$nginx"

# echo "   -> Завернём весь icmp-трафик на себя"
# iptables -t nat -A PREROUTING -d 0/0 -p icmp -j DNAT --to-destination "$intip1"

# echo "   -> Завернём весь трафик по 53 порту из сети на себя"
# iptables -t nat -A PREROUTING -d 0/0 -s "$intlan1" -p udp -m udp --dport 53 -j DNAT --to-destination "$intip1"
# iptables -t nat -A PREROUTING -d 0/0 -s "$intlan1" -p tcp -m tcp --dport 53 -j DNAT --to-destination "$intip1"
#
# iptables -t nat -A PREROUTING -d 0/0 -s "$intlan2" -p udp -m udp --dport 53 -j DNAT --to-destination "$intip1"
# iptables -t nat -A PREROUTING -d 0/0 -s "$intlan2" -p tcp -m tcp --dport 53 -j DNAT --to-destination "$intip1"

# --- INPUT

echo ""
echo "   --- INPUT"
echo "   -> Правило без действия, только для счётчика"
iptables -A INPUT -s 192.168.8.55/32 -p tcp --dport 22

echo "   -> Разрешаем established, related"
iptables -A INPUT -i "$extif" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

echo "   -> Запрещаем invalid трафик"
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP

echo "   -> Разрешаем входящий SSH"
iptables -A INPUT -i "$extif" -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

# Разрешаем icmp-traffic идущий из сети
# iptables -A INPUT -s "$intlan1" -p icmp -j ACCEPT
# iptables -A INPUT -s "$intlan2" -p icmp -j ACCEPT

# Разрешаем доступ к указанным портам только из локальной сети
# iptables -A INPUT -s "$intlan1" -p tcp -m multiport --dport 22,53 -m conntrack --ctstate NEW -j ACCEPT
# iptables -A INPUT -s "$intlan2" -p tcp -m multiport --dport 22,53 -m conntrack --ctstate NEW -j ACCEPT

# iptables -A INPUT -s "$intlan1" -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT
# iptables -A INPUT -s "$intlan2" -p udp --dport 53 -m conntrack --ctstate NEW -j ACCEPT

echo "   -> Разрешаем любой траффик из локальной сети куда угодно"
iptables -A INPUT -s "$intlan1" -p all -j ACCEPT
iptables -A INPUT -s "$intlan2" -p all -j ACCEPT

# Запрещаем остальные входящие соединения (если политика по-умолчанию не DROP) на внешний интерфес
# iptables -A INPUT -j DROP

# --- FORWARD

echo ""
echo "   --- FORWARD"
# echo "   -> Маркируем соединение компа (на скачивание) $slow из локальной сети"
# iptables -t mangle -A FORWARD -d "$slow" -j CONNMARK --set-mark 12
#
# echo "   -> Маркируем пакеты в соединении 12"
# iptables -t mangle -A FORWARD -m connmark --mark 12 -j MARK --set-mark 87
#
# echo "   -> Маркируем соединение компа (на выгруз) $slow из локальной сети"
# iptables -t mangle -A FORWARD -s "$slow" -j CONNMARK --set-mark 13
#
# echo "   -> Маркируем пакеты в соединении 13"
# iptables -t mangle -A FORWARD -m connmark --mark 13 -j MARK --set-mark 88


echo "   -> Маркируем соединения из списка garant15peak30 (на скачивание) из локальной сети на ресурс vk.com (выполнится dns-запрос и в правила попадут ip адреса vk)"
iptables -t mangle -A FORWARD -m set --match-set garant15peak30 dst -s vk.com -j CONNMARK --set-mark 12
echo "   -> Маркируем пакеты в соединении 12"
iptables -t mangle -A FORWARD -m connmark --mark 12 -j MARK --set-mark 87

echo "   -> Маркируем соединения из списка garant15peak30 (на выгруз) из локальной сети на ресурс vk.com (выполнится dns-запрос и в правила попадут ip адреса vk)"
iptables -t mangle -A FORWARD -m set --match-set garant15peak30 src -d vk.com -j CONNMARK --set-mark 13
echo "   -> Маркируем пакеты в соединении 13"
iptables -t mangle -A FORWARD -m connmark --mark 13 -j MARK --set-mark 88


echo "   -> Маркируем исходящие соединения ip-телефонов Yealink SIP-T21 (default 46)"
iptables -t mangle -A FORWARD -m dscp --dscp 46 -j CONNMARK --set-mark 15
echo "   -> Маркируем пакеты в соединении 15"
iptables -t mangle -A FORWARD -m connmark --mark 15 -j MARK --set-mark 90

echo "   -> Маркируем входящие соединения ip-телефонов Yealink SIP-T21 (default 46)"
iptables -t mangle -A FORWARD -m dscp --dscp 46 -j CONNMARK --set-mark 16
echo "   -> Маркируем пакеты в соединении 16"
iptables -t mangle -A FORWARD -m connmark --mark 16 -j MARK --set-mark 91
echo "   -> Для теста можно установить dscp на клиенте, например:"
echo "   -> $ sudo iptables -t mangle -A OUTPUT -j DSCP --set-dscp 46"


echo "   -> Разрешаем уже установленные и связанные транзитные соединения  WAN -> LAN"
iptables -A FORWARD -i "$extif" -o "$intif" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

echo "   -> Запрещаем invalid трафик"
iptables -A FORWARD -m conntrack --ctstate INVALID -j DROP

# echo "   -> Разрешим весь dnat-трафик"
# iptables -A FORWARD -i "$extif" -o "$intif" -m conntrack --ctstate DNAT -j ACCEPT

echo "   -> Разрешим dnat-трафик на $nginx:80 внутрь сети"
iptables -A FORWARD -i "$extif" -o "$intif" -p tcp -m tcp --dport 80 -m conntrack --ctstate NEW -j ACCEPT

echo "   -> Запрещаем остальные транзитные соединения WAN -> LAN"
iptables -A FORWARD -i "$extif" -o "$intif" -j DROP

echo "   -> Разрешаем LAN -> WAN для локальных сетей"
iptables -A FORWARD -i "$intif" -o "$extif" -s "$intlan1","$intlan2" -p all -j ACCEPT

echo "   -> Разрешаем трафик из $intlan1 в $intlan2"
iptables -A FORWARD -s "$intlan1" -d "$intlan2" -p all -j ACCEPT
echo "   -> Разрешаем трафик из $intlan2 в $intlan1"
iptables -A FORWARD -s "$intlan2" -d "$intlan1" -p all -j ACCEPT

echo "   -> Запрещаем весь проходящий трафик (можно просто политику DROP поставить)"
iptables -A FORWARD -j DROP


# --- OUTPUT

echo ""
echo "   --- OUTPUT"
echo "   -> Запрещаем invalid трафик"
iptables -A OUTPUT -m conntrack --ctstate INVALID -j DROP

# --- POSTROUTING

echo ""
echo "   --- POSTROUTING"
echo "   -> Прокинем трафик из локальной сети наружу (snat)"
# iptables -A POSTROUTING -t nat -o "$extif" -m conntrack --ctstate NEW -j MASQUERADE
iptables -t nat -A POSTROUTING -s "$intlan1" -d 0/0 -o "$extif" -m conntrack --ctstate NEW -j SNAT --to-source "$extip"
iptables -t nat -A POSTROUTING -s "$intlan2" -d 0/0 -o "$extif" -m conntrack --ctstate NEW -j SNAT --to-source "$extip"

Шейпинг трафика

Описание(htb.init):

При помощи шейпинга мы можем управлять только исходящим из интерфейса трафиком. Тут возникает проблема с nat. Если нам нужно шейпировать трафик и натить на том же роутере, то в правилах нужно использовать MARK, чтобы маркировать пакеты, а затем использовать маркированные пакеты при шейпинге.

Общий алгоритм:

  • Создаём корневую дисциплину для каждого интерфейса и указываем класс, куда будет попадать не классифицированный трафик
  • Создаём корневой класс и устанавливаем ширину канала
  • Создаём дочерний класс для абонента
  • Создаём дисциплину шейпирования класса абонента
  • Создаём фильтры, для определения трафика абонента

Например, есть 2 интерфейса:

  • $intif(enp6s0) - внутренний интерфейс
  • $extif(enp7s0) - внешний интерфейс

Для компьютера внутри сети скорость скачивания нужно резать на $intif, а скорость отдачи на $extif.

Описание параметров:

  • rate - гарантированная полоса пропускания
  • ceil - максимальная полоса которую может получить данный класс

rate не может быть больше ceil Параметры rate и ceil для корневого класса должны совпадать. Таким образом мы определяем общую полосу пропускания. Сумма rate’ов классов-потомков, не должна превышать rate родительского класса. Идентификаторы классов в пределах интерфейса должны быть уникальны.

Каждый конфигурационный файл описывает очередь, это заложено в самом названии файла, которое имеет формат:

1
$HTB_PATH/<ifname>-<clsid>(:<clsid>).<description>
  • clsid - задается цифровыми значениями от 0x2 до 0xFFFF (записывается без приставки 0x)

Сам интерфейс описывается файлом только с именем ifname. Например eth0 и имеет идентификатор класса clsid=0.

  • eth0-2 - основной (корневой) класс с clsid=2.

  • eth0-2:3 - класс очереди clsid=3, унаследует ограничения от родительского clsid=2.

  • eth0-2:3:4 - класс очередь clsid=4, унаследует ограничения от родительского clsid=3 и 2, т.е. накладываются ее более жесткие ограничения.

  • DEFAULT=30 - указывает номер класса, куда попадает трафик не попавший ни под одно правило. Класс ‘по умолчанию’ задается без правила RULE (но это не значит, что туда будет автоматом попадать все, наоборот, в эту очередь ничего не пойдет). default 0

  • R2Q=100 - точность шейпера. default 10

Точность 10 хороша для скорости 5-500kbps и должга быть увеличена, для больших скоростей.

  • DCACHE=no - Не знаешь не трогай. default “no”

  • RATE=5Mbit - выделенная (гарантированная) пропускная способность очереди, задается в Kbit, Mbit или bps (bytes per second)

  • CEIL=6MBit - максимальная пропускная способность очереди. default CEIL=RATE

  • BURST=<bytes> - default computed

  • CBURST=<bytes> - default computed

  • PRIO=<number> - приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0

  • LEAF=none - правило распределения внутри самой очереди. default “none”

  • LEAF=sfq - равномерное распределение между участниками очереди.

Возможные варианты LEAF=none|sfq|pfifo|bfifo optional, default “none”

  • MTU=<bytes> - default “1600”

  • RULE= - правила, определяющий трафик, который должен проходить через данную очередь. В одном файле могут присутствовать сразу несколько правил.

1
RULE=[[saddr[/prefix]][:port[/mask]],][daddr[/prefix]][:port[/mask]]

Если трафик попадает по условиям в очередь -2:10 (например, где правило задано по маске), то дальше он уже не будет проверять условия в -2:20 (где допустим будет описано правило с конкретно этим ip), -2:30 и т.д.

  • MARK=101 : трафик имеющий метку. Метим в таблице mangle, либо в PRERIUTING, либо в FORWARD выше исключающих этот трафик правил.
1
2
$ sudo iptables -t mangle -A PREROUTING -s 192.168.8.55 -j MARK --set-mark 101
$ sudo iptables -t mangle -A PREROUTING -s 192.168.8.55 -j RETURN
  • TIME - временные параметры
1
2
3
TIME=[<dow><dow>.../]<from>-<till>;<rate>[/<burst>][,<ceil>[/<cburst>]]
TIME=60123/18:00-06:00;256Kbit/10Kb,384Kbit
TIME=18:00-06:00;256Kbit
  • REALM=[srealm,][drealm] - именное обозначение направлений
1
REALM=russia,internet

Пример 1

  1. Скачаем htb.init, положить сюда /usr/sbin/htb.init и дать права:
1
$ sudo chmod u+x /sbin/htb.init
  1. Создадим директорию для конфигов:
1
$ sudo mkdir -p /etc/sysconfig/htb

При запуске, кэш будет храниться в этом файле: /var/cache/htb.init

  1. Создадим конфиги для входящего трафика $intif(enp6s0):
  • Общий для $intif(enp6s0):
1
2
3
4
# cat << EOF > enp6s0
DEFAULT=10 # указывает номер класса, куда попадает трафик не попавший ни под одно правило, в моём случае в enp6s0-2:10.default
R2Q=100    # точность ограничений
EOF
  • Корневой класс для исходящей ширины канала:
1
2
3
4
5
# cat << EOF > enp6s0-2.root
RATE=100Mbit    # гарантированная пропускная способность
CEIL=1000Mbit   # максимальная пропускная способность
# BURST=15k     # первые 15k без ограничений
EOF
  • Дочерний класс для ограничения скорости пакетов, маркированных 87
1
2
3
4
5
6
7
# cat << EOF > enp6s0-2:30.MARK87
RATE=512Kbit    # гарантированная пропускная способность
CEIL=512Kbit    # максимальная пропускная способность
LEAF=sfq        # равномерное распределение между участниками очереди.
MARK=87         # применять правило для пакетов, маркированных меткой 87
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
EOF
  • Класс для ограничения скорости по-умолчанию:
1
2
3
4
5
6
# cat << EOF > enp6s0-2:10.default
RATE=10Mbit    # гарантированная пропускная способность
CEIL=10Mbit    # максимальная пропускная способность
LEAF=sfq       # равномерное распределение между участниками очереди.
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
EOF

Перезапускаем скрипт:

1
$ sudo htb.init restart

Маркированные пакеты будут замедлены до 512Kbit, не маркированные до 10Mbit, убедимся (запуск с клиента):

1
$ sudo iperf3 -c speedtest.hostkey.ru -p 5200 -R -P 1
  1. Теперь займёмся исходящим трафиком $extif(enp7s0). Все классы создаются аналогично:
1
2
3
4
# cat << EOF > enp7s0
DEFAULT=10 # указывает номер класса, куда попадает трафик не попавший ни под одно правило, в моём случае в enp6s0-2:10.default
R2Q=100    # точность ограничений
EOF
1
2
3
4
5
# cat << EOF > enp7s0-2.root
RATE=10Mbit    # гарантированная пропускная способность
CEIL=10Mbit   # максимальная пропускная способность
# BURST=15k     # первые 15k без ограничений
EOF
1
2
3
4
5
6
7
# cat << EOF > enp7s0-2:30.MARK88
RATE=4Mbit    # гарантированная пропускная способность
CEIL=4Mbit    # максимальная пропускная способность
LEAF=sfq        # равномерное распределение между участниками очереди.
MARK=88        # применять правило для пакетов, маркированных меткой 87
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
EOF
1
2
3
4
5
6
# cat << EOF > enp7s0-2:10.default
RATE=10Mbit    # гарантированная пропускная способность
CEIL=10Mbit    # максимальная пропускная способность
LEAF=sfq       # равномерное распределение между участниками очереди.
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
EOF
  1. Перезапустим правила управления трафиком:
1
$ sudo /sbin/htb.init restart

Проверяем с клиента:

1
$ iperf3 -c speedtest.studiofunk.de -p 5200

Пример 2.

Выделить пропускную полосу 512Кбит/с для локального клиента 192.168.8.55. На сервере-шлюзе 2 интерфейса:

  • $intif(enp6s0) - внутренний интерфейс
  • $extif(enp7s0) - внешний интерфейс

Создаём файлы с соответствующим содержимым:

1
2
3
4
# cat << EOF > enp6s0
DEFAULT=30
R2Q=100
EOF
1
2
3
4
# cat << EOF > enp6s0-2.root
# Скорость корневого класса 100Мбит, так как других классов тут не будет
RATE=100Mbit
EOF
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# cat << EOF > enp6s0-2:10.local
# Правило для локального трафика
RATE=1Kbit
CEIL=100Mbit
LEAF=sfq
# Если из локальной сети обращаются по внутреннему адресу к шлюзу, не ограничивать
RULE=192.168.0.0/24,192.168.0.0/24
# Если из локальной сети обращаются по внешнему адресу к шлюзу, не ограничивать
RULE=82.24.110.14/32,192.168.0.0/24
PRIO=10
EOF
1
2
3
4
5
6
7
8
# cat << EOF > enp6s0-2:20.voip
# Гарантированная скорость 512Кbit, для трафика проходящего к 192.168.0.200
RATE=512Kbit
CEIL=2Mbit
LEAF=sfq
RULE=*,192.168.0.200
PRIO=1
EOF
1
2
3
4
5
6
7
# cat << EOF > enp6s0-2:30.all
# Правило по умолчанию. Оставшийся трафик будет интернет трафиком.
RATE=1Kbit
CEIL=1536Kbit
LEAF=sfq
PRIO=10
EOF

Применим:

1
2
3
4
sudo htb.init compile # тест. можно посмотреть какие правила будут сформированы и ошибки, если есть.
sudo htb.init start # запуск
sudo htb.init stop # остановка
sudo htb.init restart # перезапуск после внесения изменений в файлы конфигурации

Измерим скорость на удалённом сервере. Если клиент в сети отдаёт:

1
2
$ sudo iperf3 -c speedtest.hostkey.ru -p 5200 -P 5
$ sudo iperf3 -c speedtest.hostkey.ru -p 5200 -P 1

Если клиент в сети принимает:

1
$ sudo iperf3 -R -c speedtest.hostkey.ru -p 5200 -P 1

Поднять свой сервер:

1
sudo iperf3 -s -p 5001

Перед проверкой нужно убедиться что порт в цепочке INPUT открыт.

Текущие конфиги:

  • enp6s0 - внутренний интерфейс
  • enp7s0 - внешний интерфейс

enp6s0:

1
2
3
# $ cat enp6s0
DEFAULT=10 # указывает номер класса, куда попадает трафик не попавший ни под одно правило, в моём случае в enp6s0-2:10.default
R2Q=100    # точность ограничений
1
2
3
4
# $ cat enp6s0-2.root
RATE=100Mbit    # гарантированная пропускная способность
CEIL=1000Mbit   # максимальная пропускная способность
# BURST=15k     # первые 15k без ограничений
1
2
3
4
5
# $ cat enp6s0-2:10.default
RATE=10Mbit  # гарантированная пропускная способность
CEIL=10Mbit  # максимальная пропускная способность
LEAF=sfq     # равномерное распределение между участниками очереди.
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
1
2
3
4
5
6
# $ cat enp6s0-2:30.MARK87
RATE=512Kbit  # гарантированная пропускная способность
CEIL=512Kbit  # максимальная пропускная способность
LEAF=sfq      # равномерное распределение между участниками очереди.
MARK=87       # применять правило для пакетов, маркированных меткой 87
# PRIO=20       # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0
1
2
3
4
5
6
$ cat enp6s0-2:40.MARK90
RATE=2Mbit  # гарантированная пропускная способность
CEIL=2Mbit  # максимальная пропускная способность
LEAF=sfq      # равномерное распределение между участниками очереди.
MARK=90       # применять правило для пакетов, маркированных меткой 90
# PRIO=10       # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0

enp7s0:

1
2
3
# $ cat enp7s0
DEFAULT=10 # указывает номер класса, куда попадает трафик не попавший ни под одно правило, в моём случае в enp6s0-2:10.default
R2Q=100    # точность ограничений
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# $ cat enp7s0-2.root
RATE=10Mbit    # гарантированная пропускная способность
CEIL=10Mbit   # максимальная пропускная способность
# BURST=15k     # первые 15k без ограничений
```bash
# $ cat enp7s0-2:10.default
RATE=10Mbit  # гарантированная пропускная способность
CEIL=10Mbit  # максимальная пропускная способность
LEAF=sfq     # равномерное распределение между участниками очереди.
# PRIO=30      # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0, при отправке пакетов, классы с низким значением поля приоритета оправляют первыми
1
2
3
4
5
6
# $ cat enp7s0-2:30.MARK88
RATE=64Kbit   # гарантированная пропускная способность
CEIL=64Kbit   # максимальная пропускная способность
LEAF=sfq      # равномерное распределение между участниками очереди.
MARK=88       # применять правило для пакетов, маркированных меткой 87
# PRIO=20       # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0
1
2
3
4
5
6
# $ cat enp7s0-2:40.MARK91
RATE=2Mbit   # гарантированная пропускная способность
CEIL=2Mbit   # максимальная пропускная способность
LEAF=sfq      # равномерное распределение между участниками очереди.
MARK=91       # применять правило для пакетов, маркированных меткой 91
# PRIO=10       # приоритет трафика очереди к другим очередям в классе. Чем меньше число, тем выше приоритет.default 0

htb.init создаёт файл конфигурации и хранит его в /var/cache/htb.init

Чтобы не было ругани на maxdepth изменим скрипт:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ diff /usr/sbin/htb.init htb.init-v0.8.5
471,472c471,472
< 			find $HTB_PATH -maxdepth 1 \( -type f -or -type l \) \
< 			-name "$dev-*" -not -name '*~' \
---
> 			find $HTB_PATH \( -type f -or -type l \) \
> 			-name "$dev-*" -not -name '*~' -maxdepth 1 \
486,487c486,487
< 			[ `find $HTB_PATH -maxdepth 1 \( -type f -or -type l \) \
< 			  -name "$dev*" -newer $HTB_CACHE| \
---
> 			[ `find $HTB_PATH \( -type f -or -type l \) \
> 			  -name "$dev*" -maxdepth 1 -newer $HTB_CACHE| \
496c496
< 		[ `find $HTB_PATH -maxdepth 1 -type f -name "$1*" \
---
> 		[ `find $HTB_PATH -type f -name "$1*" -maxdepth 1 \

Файл systemd для запуска демона:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
systemctl cat htb.init.service
# /etc/systemd/system/htb.init.service
[Unit]
Description=Service to start htb.init rules
After=network.target

[Service]
user=root
group=root
Type=oneshot
RemainAfterExit=yes
ExecStart=htb.init start
ExecStop=htb.init stop


[Install]
WantedBy=default.target
RequiredBy=netfilter-persistent.service
RequiredBy=ufw.service

Сам файл htb.init:

htb.init:
   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
#!/bin/bash
#
#    htb.init v0.8.5
#    Copyright (C) 2002-2004  Lubomir Bulej <pallas@kadan.cz>
#
#    chkconfig:   2345 11 89
#    description: script to set up HTB traffic control
#
#    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 2 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, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    To get the latest version, check on Freshmeat for actual location:
#
#		http://freshmeat.net/projects/htb.init
#
#
# VERSION HISTORY
# ---------------
# v0.8.5- Nathan Shafer <nicodemus at users.sourceforge.net>
#	  - allow symlins to class files
#	- Seth J. Blank <antifreeze at users.sourceforge.net>
#	  - replace hardcoded ip/tc location with variables
#	- Mark Davis <mark.davis at gmx.de>
#	  - allow setting of PRIO_{MARK,RULE,REALM} in class file
# v0.8.4- Lubomir Bulej <pallas at kadan.cz>
#	  - fixed small bug in RULE parser to correctly parse
#	    rules with identical source and destination fields
#	  - removed the experimental INJECT keyword
#	  - ignore *~ backup files when looking for classes
#	- Mike Boyer <boyer at administrative.com>
#	  - fix to allow arguments to be passed to "restart" command
#	- <face at pos.sk>
#	  - fix to preserve class priority after timecheck
# v0.8.3- Lubomir Bulej <pallas at kadan.cz>
#	  - use LC_COLLATE="C" when sorting class files
#	- Paulo Sedrez
#	  - fix time2abs to allow hours with leading zero in TIME rules
# v0.8.2- Lubomir Bulej <pallas at kadan.cz>
#	  - thanks to Hasso Tepper for reporting the following problems
#	  - allow dots in interface names for use with VLAN interfaces
#	  - fixed a thinko resulting from "cosmetic overdosage" :)
# v0.8.1- Lubomir Bulej <pallas at kadan.cz>
#	  - added function alternatives for sed/find with less features. To
#	    enable them, you need to set HTB_BASIC to nonempty string.
#	  - added posibility to refer to RATE/CEIL of parent class when
#	    setting RATE/CEIL for child class. Look for "prate" or "pceil"
#	    in the documentation.
#	  - fixed broken "timecheck" invocation
# v0.8	- Lubomir Bulej <pallas at kadan.cz>
#	  - simplified and converted CBQ.init 0.7 into HTB.init
#	  - changed configuration file naming conventions
#	  - lots of HTB specific changes
#
#
# INTRODUCTION
# ------------
#
# This script is a clone of CBQ.init and is meant to simplify setup of HTB
# based traffic control. HTB setup itself is pretty simple compared to CBQ,
# so the purpose of this script is to allow the administrator of large HTB
# configurations to manage individual classes using simple, human readable
# files.
#
# The "H" in HTB stands for "hierarchical", so while many people did not use
# (or know about) the possibility to build hierarchical structures using
# CBQ.init, it should be obvious thing to expect from HTB.init :-)
#
# In HTB.init this is done differently, compared to CBQ.init: the usage of
# PARENT keyword was dropped and instead, class file naming convetion was
# introduced. This convention allows the child class to determine ID of its
# parent class from the filename and also (if not abused :) enforces file
# ordering so that the parent classes are created before their children.
#
# HTB.init uses simple caching mechanism to speed up "start" invocation if the
# configuration is unchanged. When invoked for the first time, it compiles the
# configuration files into simple shell script containing the sequence of "tc"
# commands required to setup the traffic control. This cache-script is stored
# in /var/cache/htb.init by default and is invalidated either by presence of
# younger class config file, or by invoking HTB.init with "start invalidate".
#
# If you want to HTB.init to setup the traffic control directly without the
# cache, invoke it with "start nocache" parameters. Caching is also disabled
# if you have logging enabled (ie. HTB_DEBUG is not empty).
#
# If you only want HTB.init to translate your configuration to "tc" commands,
# invoke it using the "compile" command. Bear in mind that "compile" does not
# check if the "tc" commands were successful - this is done (in certain places)
# only when invoked with "start nocache" command. When you are testing your
# configuration, you should use it to check whether it is completely valid.
#
# In case you are getting strange sed/find errors, try to uncomment line with
# HTB_BASIC setting, or set the variable to nonempty string. This will enable
# function alternatives which require less advanced sed/find functionality. As
# a result, the script will run slower but will probably run. Also the caching
# will not work as expected and you will have to invalidate the cache manually
# by invoking HTB.init with "start invalidate".
#
#
# CONFIGURATION
# -------------
#
# Every traffic class is described by a single file in placed in $HTB_PATH
# directory, /etc/sysconfig/htb by default. The naming convention is different
# compared to CBQ.init. First notable change is missing 'htb-' prefix. This
# was replaced by interface name to improve human readability and to separate
# qdisc-only configuration.
#
# Global qdisc options are placed in $HTB_PATH/<ifname>, where <ifname> is
# (surprisingly) name of the interface, made of characters and numbers. This
# file must be present if you want to setup HTB on that interface. If you
# don't have any options to put into it, leave it empty, but present.
#
# Class options belong to files with names matching this expression:
# $HTB_PATH/<ifname>-<clsid>(:<clsid>)*<description>
#
# <clsid> is class ID which is hexadecimal number in range 0x2-0xFFFF, without
# the "0x" prefix. If a colon-delimited list of class IDs is specified, the
# last <clsid> in the list represents ID of the class in the config file.
#
# <clsid> preceding the last <clsid> is class ID of the parent class. To keep
# ordering so that parent classes are always created before their children, it
# is recommended to include full <clsid> path from root class to the leaf one.
#
# <description> is (almost) arbitrary string where you can put symbolic
# class names for better readability.
#
# Examples of valid names:
#
#	eth0-2		root class with ID 2, on device eth0
#	eth0-2:3	child class with ID 3 and parent 2, on device eth0
#	eth0-2:3:4	child class with ID 4 and parent 3, on device eth0
#	eth1-2.root	root class with ID 2, on device eth1
#
#
# The configuration files may contain the following parameters. For detailed
# description of HTB parameters see http://luxik.cdi.cz/~devik/qos/htb.
#
### HTB qdisc parameters
#
# The following parameters apply to HTB root queuening discipline only and
# are expected to be put into $HTB_PATH/<ifname> files. These files must
# exist (even empty) if you want to configure HTB on given interface.
#
# DEFAULT=<clsid>				optional, default 0
# DEFAULT=30
#
#	<dclsid> is ID of the default class where UNCLASSIFIED traffic goes.
#	Unlike HTB qdisc, HTB.init uses 0 as default class ID, which is
#	internal FIFO queue that will pass packets along at FULL speed!
#
#	If you want to avoid surprises, always define default class and
#	allocate minimal portion of bandwidth to it.
#
# R2Q=<number>					optional, default 10
# R2Q=100
#
#	This allows you to set coefficient for computing DRR (Deficit
#	Round Robin) quanta. The default value of 10 is good for rates
#	from 5-500kbps and should be increased for higher rates.
#
# DCACHE=yes|no					optional, default "no"
#
#	This parameters turns on "dequeue cache" which results in degraded
#	fairness but allows HTB to be used on very fast network devices.
#	This is turned off by default.
#
### HTB class parameters
#
# The following are parameters for HTB classes and are expected
# to be put into $HTB_PATH/<ifname>-<clsid>(:<clsid>)*.* files.
#
# RATE=<speed>|prate|pceil			mandatory
# RATE=5Mbit
#
#	Bandwidth allocated to the class. Traffic going through the class is
#	shaped to conform to specified rate. You can use Kbit, Mbit or bps,
#	Kbps and Mbps as suffices. If you don't specify any unit, bits/sec
#	are used. Also note that "bps" means "bytes per second", not bits.
#
#	The "prate" or "pceil" values will resolve to RATE or CEIL of parent
#	class. This feature is meant to help humans to keep configuration
#	files consistent.
#
# CEIL=<speed>|prate|pceil			optional, default $RATE
# CEIL=6MBit
#
#	The maximum bandwidth that can be used by the class. The difference
#	between CEIL and RATE amounts to bandwidth the class can borrow, if
#	there is unused bandwidth left.
#
#	By default, CEIL is equal to RATE so the class cannot borrow bandwidth
#	from its parent. If you want the class to borrow unused bandwidth, you
#	must specify the maximal amount it can use, if available.
#
#	When several classes compete for the unused bandwidth, each of the
#	classes is given share proportional to their RATE.
#
# BURST=<bytes>					optional, default computed
# BURST=10Kb
#
# CBURST=<bytes>				optional, default computed
# CBURST=2Kb
#
#	BURST and CBURST parameters control the amount of data that can
#	be sent from one class at maximum (hardware) speed before trying
#	to service other class.
#
#	If CBURST is small (one packet size) it shapes bursts not to
#	exceed CEIL rate the same way PEAK works for TBF.
#
# PRIO=<number>					optional, default 0
# PRIO=5
#
#	Priority of class traffic. The higher the number, the lesser the
#	priority. Also, classes with higher priority are offered excess
#	bandwidth first.
#
# LEAF=none|sfq|pfifo|bfifo			optional, default "none"
#
#	Tells the script to attach specified leaf queueing discipline to HTB
#	class. By default, no leaf qdisc is used.
#
#	If you want to ensure (approximately) fair sharing of bandwidth among
#	several hosts in the same class, you should specify LEAF=sfq to attach
#	SFQ as leaf queueing discipline to the class.
#
# MTU=<bytes>  					optional, default "1600"
#
#	Maximum packet size HTB creates rate maps for. The default should
#	be sufficient for most cases, it certainly is for Ethernet.
#
### SFQ qdisc parameters
#
# The SFQ queueing discipline is a cheap way to fairly share class bandwidth
# among several hosts. The fairness is approximate because it is stochastic,
# but is not CPU intensive and will do the job in most cases. If you desire
# real fairness, you should probably use WRR (weighted round robin) or WFQ
# queueing disciplines. Note that SFQ does not do any traffic shaping - the
# shaping is done by the HTB class the SFQ is attached to.
#
# QUANTUM=<bytes>				optional, qdisc default
#
#	Amount of data in bytes a stream is allowed to dequeue before next
#	queue gets a turn. Defaults to one MTU-sized packet. Do not set
#	this parameter below the MTU!
#
# PERTURB=<seconds>				optional, default "10"
#
#	Period of hash function perturbation. If unset, hash reconfiguration
#	will never take place which is what you probably don't want. The
#	default value of 10 seconds is probably a good value.
#
### PFIFO/BFIFO qdisc parameters
#
# Those are simple FIFO queueing disciplines. They only have one parameter
# which determines their length in bytes or packets.
#
# LIMIT=<packets>|<bytes>			optional, qdisc default
# LIMIT=1000
#
#	Number of packets/bytes the queue can hold. The unit depends on
#	the type of queue used.
#
### Filtering parameters
#
# RULE=[[saddr[/prefix]][:port[/mask]],][daddr[/prefix]][:port[/mask]]
#
#	These parameters make up "u32" filter rules that select traffic for
#	each of the classes. You can use multiple RULE fields per config.
#
#	The optional port mask should only be used by advanced users who
#	understand how the u32 filter works.
#
# Some examples:
#
#	RULE=10.1.1.0/24:80
#		selects traffic going to port 80 in network 10.1.1.0
#
#	RULE=10.2.2.5
#		selects traffic going to any port on single host 10.2.2.5
#
#	RULE=10.2.2.5:20/0xfffe
#		selects traffic going to ports 20 and 21 on host 10.2.2.5
#
#	RULE=:25,10.2.2.128/26:5000
#		selects traffic going from anywhere on port 50 to
#		port 5000 in network 10.2.2.128
#
#	RULE=10.5.5.5:80,
#		selects traffic going from port 80 of single host 10.5.5.5
#
#
#
# REALM=[srealm,][drealm]
#
#	These parameters make up "route" filter rules that classify traffic
#	according to packet source/destination realms. For information about
#	realms, see Alexey Kuznetsov's IP Command Reference. This script
#	does not define any realms, it justs builds "tc filter" commands
#	for you if you need to classify traffic this way.
#
#	Realm is either a decimal number or a string referencing entry in
#	/etc/iproute2/rt_realms (usually).
#
# Some examples:
#
#	REALM=russia,internet
#		selects traffic going from realm "russia" to realm "internet"
#
#	REALM=freenet,
#		selects traffic going from realm "freenet"
#
#	REALM=10
#		selects traffic going to realm 10
#
#
#
# MARK=<mark>
#
#	These parameters make up "fw" filter rules that select traffic for
#	each of the classes accoring to firewall "mark". Mark is a decimal
#	number packets are tagged with if firewall rules say so. You can
#	use multiple MARK fields per config.
#
#
# Note:	Rules for different filter types can be combined. Attention must be
#	paid to the priority of filter rules, which can be set below through
#	the PRIO_{RULE,MARK,REALM} variables.
#
### Time ranging parameters
#
# TIME=[<dow><dow>.../]<from>-<till>;<rate>[/<burst>][,<ceil>[/<cburst>]]
# TIME=60123/18:00-06:00;256Kbit/10Kb,384Kbit
# TIME=18:00-06:00;256Kbit
#
#	This parameter allows you to change class bandwidth during the day or
#	week. You can use multiple TIME rules. If there are several rules with
#	overlapping time periods, the last match is taken. The <rate>, <burst>,
#	<ceil> and <cburst> fields correspond to parameters RATE, BURST, CEIL
#	and CBURST.
#
#	<dow> is single digit in range 0-6 and represents day of week as
#	returned by date(1). To specify several days, just concatenate the
#	digits together.
#
#
#
# TRIVIAL EXAMPLE
# ---------------
#
# Consider the following example:
# (taken from Linux Advanced Routing & Traffic Control HOWTO)
#
# You have a Linux server with total of 5Mbit available bandwidth. On this
# machine, you want to limit webserver traffic to 5Mbit, SMTP traffic to 3Mbit
# and everything else (unclassified traffic) to 1Kbit. In case there is unused
# bandwidth, you want to share it between SMTP and unclassified traffic.
#
# The "total bandwidth" implies one top-level class with maximum bandwidth
# of 5Mbit. Under the top-level class, there are three child classes.
#
# First, the class for webserver traffic is allowed to use 5Mbit of bandwidth.
#
# Second, the class for SMTP traffic is allowed to use 3Mbit of bandwidth and
# if there is unused bandwidth left, it can use it but must not exceed 5Mbit
# in total.
#
# And finally third, the class for unclassified traffic is allowed to use
# 1Kbit of bandwidth and borrow unused bandwith, but must not exceed 5Mbit.
#
# If there is demand in all classes, each of them gets share of bandwidth
# proportional to its default rate. If there unused is bandwidth left, they
# (again) get share proportional to their default rate.
#
# Configuration files for this scenario:
# ---------------------------------------------------------------------------
# eth0		eth0-2.root	eth0-2:10.www	eth0-2:20.smtp	eth0-2:30.dfl
# ----		-----------	-------------	--------------	-------------
# DEFAULT=30	RATE=5Mbit	RATE=5Mbit	RATE=3Mbit	RATE=1Kbit
#		BURST=15k	BURST=15k	CEIL=5Mbit	CEIL=5Mbit
#				LEAF=sfq	BURST=15k	BURST=15k
#				RULE=*:80,	LEAF=sfq	LEAF=sfq
#						RULE=*:25
# ---------------------------------------------------------------------------
#
# Remember that you can only control traffic going out of your linux machine.
# If you have a host connected to network and want to control its traffic on
# the gateway in both directions (with respect to the host), you need to setup
# traffic control for that host on both (or all) gateway interfaces.
#
# Enjoy.
#
#############################################################################

export LC_ALL=C

### Command locations
TC=/sbin/tc
IP=/sbin/ip
MP=/sbin/modprobe

### Default filter priorities (must be different)
PRIO_RULE_DEFAULT=${PRIO_RULE:-100}
PRIO_MARK_DEFAULT=${PRIO_MARK:-200}
PRIO_REALM_DEFAULT=${PRIO_REALM:-300}

### Default HTB_PATH & HTB_CACHE settings
HTB_PATH=${HTB_PATH:-/etc/sysconfig/htb}
HTB_CACHE=${HTB_CACHE:-/var/cache/htb.init}

### Uncomment for sed/find with less features (useful for busybox)
#HTB_BASIC="yes"

### Uncomment to enable logfile for debugging
#HTB_DEBUG="/var/run/htb-$1"

### Modules to probe for. Uncomment the last HTB_PROBE
### line if you have QoS support compiled into kernel
HTB_PROBE="sch_htb sch_sfq cls_fw cls_u32 cls_route"
#HTB_PROBE=""

### Config keywords
HTB_QDISC="DEFAULT\|DCACHE\|R2Q"
HTB_CLASS="RATE\|CEIL\|BURST\|CBURST\|PRIO\|LEAF\|MTU"
HTB_CLASS="$HTB_CLASS\|PRIO_RULE\|PRIO_MARK\|PRIO_REALM"
HTB_CLASS="$HTB_CLASS\|LIMIT\|QUANTUM\|PERTURB"


#############################################################################
############################# SUPPORT FUNCTIONS #############################
#############################################################################

if [ -z "$HTB_BASIC" ]; then
	### List of network devices
	all_device_list () {
		ip link show \
		| sed -n "/^[0-9]/ { s/[[:space:]]//g; \
		s/^[0-9]\+:\([^@-]\+\)\(@.\+\)\?:<.*/\1/; p; }"
	} # all_device_list


	### Load & filter file $HTB_PATH/$1
	htb_filter_file () {
		sed -n "s/#.*//; s/[^a-zA-Z0-9.,;:=/*-_]\+//g; \
		/^[a-zA-Z0-9]\+=[a-zA-Z0-9.,:;/*-_]\+$/ p" $HTB_PATH/$1
	} # htb_filter_file


	### Parse class ID chain from file name
	htb_clsid_chain () {
		echo "${1#*-}" \
		| sed -n "/^[0-9a-fA-F]/ { s/^\([0-9a-fA-F:]\+\).*/\1/; \
		s/::/:/g; s/:$//; p; }"
	} # htb_clsid_chain


	### List of classes in $HTB_PATH
	htb_class_list () {
		for dev in `htb_device_list`; do
			find $HTB_PATH \( -type f -or -type l \) \
			-name "$dev-*" -not -name '*~' -maxdepth 1 \
			-printf "%f\n"| sort
		done
	} # htb_class_list

	### Gather $1 rules from $CFILE
	htb_cfile_rules () {
		echo "$CFILE"| sed -n "/^$1=/ { s/.*=//; p; }"
	} # htb_cfile_rules


	### Validate cache against config files
	htb_valid_cache () {
		for dev in `htb_device_list`; do
			[ `find $HTB_PATH \( -type f -or -type l \) \
			  -name "$dev*" -maxdepth 1 -newer $HTB_CACHE| \
			  wc -l` -gt 0 ] && VALID=0
			[ $VALID -ne 1 ] && break
		done
	} # htb_valid_cache


	### Find class config for device $1, which is newer than cache
	htb_cache_older () {
		[ `find $HTB_PATH -type f -name "$1*" -maxdepth 1 \
		   -newer $HTB_CACHE| wc -l` -gt 0 ] && return 0
		return 1
	} # htb_cache_older


	### Get current RATE and CEIL
	htb_class_state () {
		tc class show dev $1 \
		| sed -n "s/[[:space:]]\+/ /g; /^class htb 1:$2 / \
		{ s/.*rate \(.\+\) burst.*/\1/; p; q; }"
	} # htb_class_state

else ### Less feature-hungry versions of above functions

	all_device_list () {
		ip link show \
		| grep "^[0-9]" \
		| sed "s/[[:space:]]//g; \
		s/^[0-9]\+:\([^@-]\+\)\(@.\+\)\?:<.*/\1/"
	} # all_device_list

	htb_filter_file () {
		sed 's/#.*//; s/[^a-zA-Z0-9.,;:=/*-_]\+//g' $HTB_PATH/$1 \
		| grep '^[a-zA-Z0-9]\+=[a-zA-Z0-9.,;:/*-_]\+$'
	} # htb_filter_file

	htb_clsid_chain () {
		echo "${1#*-}" \
		| grep '^[a-fA-F0-9]' \
		| sed 's/^\([a-fA-F0-9:]\+\).*/\1/; s/::/:/g; s/:$//'
	} # htb_clsid_chain

	htb_class_list () {
		PFX=`echo "$HTB_PATH"| sed 's/\//\\\\\//g'`
		for dev in `htb_device_list`; do
			find $HTB_PATH -type f -name "$dev-*" \
			| grep "^$HTB_PATH/$dev-[^/]\+[^~]$" \
			| sed "s/$PFX\///" \
			| sort
		done
	} # htb_class_list

	htb_cfile_rules () {
		echo "$CFILE"| grep "^$1="| cut -d"=" -f2
	} # htb_cfile_rules

	htb_cache_older () {
		### cache is always up-to-date
		return 1
	} # htb_cache_older

	htb_class_state () {
		tc class show dev $1 \
		| sed 's/[[:space:]]\+/ /g' \
		| grep "^class htb 1:$2 " \
		| sed 's/.*rate \(.\+\) burst.*/\1/'
	} # htb_class_state
fi # HTB_BASIC


### List of HTB devices
htb_device_list () {
	for dev in `all_device_list`; do
		[ -f $HTB_PATH/$dev ] && echo $dev
	done
} # htb_device_list


### Remove root class from device $1
htb_device_off () {
	tc qdisc del dev $1 root 2> /dev/null
} # htb_device_off


### Remove HTB from all devices
htb_off () {
	for dev in `htb_device_list`; do
		htb_device_off $dev
	done
} # htb_off


### Prefixed message
htb_message () {
	echo -e "**HTB: $@"
} # htb_message

### Failure message
htb_failure () {
	htb_message "$@"
	exit 1
} # htb_failure

### Failure w/htb_off
htb_fail_off () {
	htb_message "$@"
	htb_off
	exit 1
} # htb_fail_off


### Convert time to absolute value
htb_time2abs () {
	local min=${1##*:}; min=${min##0}
	local hrs=${1%%:*}; hrs=${hrs##0}
	echo $[hrs*60 + min]
} # htb_time2abs


### Display traffic control setup
htb_show () {
	for dev in `all_device_list`; do
		[ `tc qdisc show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: queueing disciplines\n"
		tc $1 qdisc show dev $dev; echo

		[ `tc class show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: traffic classes\n"
		tc $1 class show dev $dev; echo

		[ `tc filter show dev $dev| wc -l` -eq 0 ] && continue
		echo -e "### $dev: filtering rules\n"
		tc $1 filter show dev $dev; echo
	done
} # htb_show



### Derive DEVICE, CLASS and PARENT from $1
### Check validity of CLASS and PARENT class IDs
### Load class configuration from $HTP_PATH/$1
### Configure class parameters from CFILE
htb_load_class () {
	DEVICE=${1%%-*}
	CLSIDS=`htb_clsid_chain $1`
	CLASS=${CLSIDS##*:}; [ -z "$CLASS" ] &&
		htb_fail_off "$1 has invalid class ID!"

	[ $[0x$CLASS] -lt 2 -o $[0x$CLASS] -gt 65535 ] &&
		htb_fail_off "class ID of $1 must be in range 0x2-0xFFFF!"

	CLSIDS=${CLSIDS%$CLASS}; CLSIDS=${CLSIDS%:}
	PARENT=${CLSIDS##*:}; [ -n "$PARENT" ] &&
		[ $[0x$PARENT] -lt 2 -o $[0x$PARENT] -gt 65535 ] &&
			htb_fail_off "parent ID of $1 must be in range 0x2-0xFFFF!"

	CFILE=`htb_filter_file $1`


	### Set defaults & load class
	MTU=""; LEAF=none; PERTURB=10
	RATE=""; BURST=""; CEIL=""; CBURST=""
	PRIO=""; LIMIT=""; QUANTUM=""

	PRIO_RULE=$PRIO_RULE_DEFAULT
	PRIO_MARK=$PRIO_MARK_DEFAULT
	PRIO_REALM=$PRIO_REALM_DEFAULT

	eval `echo "$CFILE"| grep "^\($HTB_CLASS\)="`
	RNAME=""; CNAME=""

	### Resolve RATE if needed
	[ "$RATE" = "prate" ] && RNAME=RATE_$PARENT
	[ "$RATE" = "pceil" ] && RNAME=CEIL_$PARENT
	[ -n "$RNAME" ] && RATE=${!RNAME}

	### RATE is required
	[ -z "$RATE" ] &&
		htb_fail_off "missing or unresolvable RATE in $1!"

	### Resolve CEIL if needed
	[ "$CEIL" = "prate" ] && CNAME=RATE_$PARENT
	[ "$CEIL" = "pceil" ] && CNAME=CEIL_$PARENT
	[ -n "$CNAME" ] && CEIL=${!CNAME}

	### Store CEIL & RATE for children
	eval RATE_$CLASS=$RATE
	eval CEIL_$CLASS=${CEIL:-$RATE}
} # htb_load_class


#############################################################################
#################################### INIT ###################################
#############################################################################

### Check iproute2 tools
[ -x $TC -a -x $IP ] ||
	htb_failure "iproute2 utilities not installed or executable!"

### Check $HTB_PATH directory
[ -d $HTB_PATH -a -r $HTB_PATH -a -x $HTB_PATH ] ||
	htb_failure "$HTB_PATH does not exist or is not readable!"

### ip/tc wrappers
if [ "$1" = "compile" ]; then
	### no module probing
	HTB_PROBE=""

	ip () {
		$IP "$@"
	} # ip

	### echo-only version of "tc" command
	tc () {
		echo "$TC $@"
	} # tc

elif [ -n "$HTB_DEBUG" ]; then
	echo -e "# `date`" > $HTB_DEBUG

	### Logging version of "ip" command
	ip () {
		echo -e "\n# ip $@" >> $HTB_DEBUG
		$IP "$@" 2>&1 | tee -a $HTB_DEBUG
	} # ip

	### Logging version of "tc" command
	tc () {
		echo -e "\n# tc $@" >> $HTB_DEBUG
		$TC "$@" 2>&1 | tee -a $HTB_DEBUG
	} # tc
else
	# default wrappers

	ip () {
		$IP "$@"
	} # ip

	tc () {
		$TC "$@"
	} # tc
fi # ip/tc wrappers


case "$1" in

#############################################################################
############################### START/COMPILE ###############################
#############################################################################

start|compile)

### Probe QoS modules (start only)
for module in $HTB_PROBE; do
	$MP $module || htb_failure "failed to load module $module"
done

### If we are in compile/nocache/logging mode, don't bother with cache
if [ "$1" != "compile" -a "$2" != "nocache" -a -z "$HTB_DEBUG" ]; then
	VALID=1

	### validate the cache
	[ "$2" = "invalidate" -o ! -f $HTB_CACHE ] && VALID=0
	[ $VALID -eq 1 ] && for dev in `htb_device_list`; do
		htb_cache_older $dev && VALID=0
		[ $VALID -ne 1 ] && break
	done

	### compile the config if the cache is invalid
	if [ $VALID -ne 1 ]; then
		$0 compile > $HTB_CACHE ||
			htb_fail_off "failed to compile HTB configuration!"
	fi

	### run the cached commands
	exec /bin/sh $HTB_CACHE 2> /dev/null
fi


### Setup root qdisc on all configured devices
DEVICES=`htb_device_list`
[ -z "$DEVICES" ] && htb_failure "no configured devices found!"

for dev in $DEVICES; do
	### Retrieve root qdisc options
	DEFAULT=""; DCACHE=""; R2Q=""
	eval `htb_filter_file $dev| grep "^\($HTB_QDISC\)="`
	[ "$DCACHE" = "yes" ] && DCACHE="dcache" || DCACHE=""

	### Remove old root qdisc from device
	htb_device_off $dev

	### Setup root qdisc for the device
	tc qdisc add dev $dev root handle 1 htb \
	default ${DEFAULT:-0} ${R2Q:+r2q $R2Q} $DCACHE ||
		htb_fail_off "failed to set root qdisc on $dev!"

	[ "$1" = "compile" ] && echo
done # dev


### Setup traffic classes (if configured)
for classfile in `htb_class_list`; do
	htb_load_class $classfile

	### Create the class
	tc class add dev $DEVICE parent 1:$PARENT classid 1:$CLASS \
	htb rate $RATE ${CEIL:+ceil $CEIL} ${BURST:+burst $BURST} \
	${PRIO:+prio $PRIO} ${CBURST:+cburst $CBURST} ${MTU:+mtu $MTU} ||
		htb_fail_off "failed to add class $CLASS with parent $PARENT on $DEVICE!"

	### Create leaf qdisc if set
	if [ "$LEAF" != "none" ]; then
		if [ "$LEAF" = "sfq" ]; then
			LEAFPARM="${PERTURB:+perturb $PERTURB} ${QUANTUM:+quantum $QUANTUM}"
		elif [ "$LEAF" = "pfifo" -o "$LEAF" = "bfifo" ]; then
			LEAFPARM="${LIMIT:+limit $LIMIT}"
		else
			htb_fail_off "unknown leaf qdisc ($LEAF) in $classfile!"
		fi

		tc qdisc add dev $DEVICE \
		parent 1:$CLASS handle $CLASS $LEAF $LEAFPARM ||
			htb_fail_off "failed to add leaf qdisc to class $CLASS on $DEVICE!"
	fi


	### Create fw filter for MARK fields
	for mark in `htb_cfile_rules MARK`; do
		### Attach fw filter to root class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_MARK handle $mark fw classid 1:$CLASS
	done ### mark

	### Create route filter for REALM fields
	for realm in `htb_cfile_rules REALM`; do
		### Split realm into source & destination realms
		SREALM=${realm%%,*}; DREALM=${realm##*,}
		[ "$SREALM" = "$DREALM" ] && SREALM=""

		### Convert asterisks to empty strings
		SREALM=${SREALM#\*}; DREALM=${DREALM#\*}

		### Attach route filter to the root class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_REALM route ${SREALM:+from $SREALM} \
		${DREALM:+to $DREALM} classid 1:$CLASS
	done ### realm

	### Create u32 filter for RULE fields
	for rule in `htb_cfile_rules RULE`; do
		### Split rule into source & destination
		SRC=${rule%%,*}; DST=${rule##*,}
		[ "$SRC" = "$rule" ] && SRC=""


		### Split destination into address, port & mask fields
		DADDR=${DST%%:*}; DTEMP=${DST##*:}
		[ "$DADDR" = "$DST" ] && DTEMP=""

		DPORT=${DTEMP%%/*}; DMASK=${DTEMP##*/}
		[ "$DPORT" = "$DTEMP" ] && DMASK="0xffff"


		### Split up source (if specified)
		SADDR=""; SPORT=""
		if [ -n "$SRC" ]; then
			SADDR=${SRC%%:*}; STEMP=${SRC##*:}
			[ "$SADDR" = "$SRC" ] && STEMP=""

			SPORT=${STEMP%%/*}; SMASK=${STEMP##*/}
			[ "$SPORT" = "$STEMP" ] && SMASK="0xffff"
		fi


		### Convert asterisks to empty strings
		SADDR=${SADDR#\*}; DADDR=${DADDR#\*}

		### Compose u32 filter rules
		u32_s="${SPORT:+match ip sport $SPORT $SMASK}"
		u32_s="${SADDR:+match ip src $SADDR} $u32_s"
		u32_d="${DPORT:+match ip dport $DPORT $DMASK}"
		u32_d="${DADDR:+match ip dst $DADDR} $u32_d"

		### Uncomment the following if you want to see parsed rules
		#echo "$rule: $u32_s $u32_d"

		### Attach u32 filter to the appropriate class
		tc filter add dev $DEVICE parent 1:0 protocol ip \
		prio $PRIO_RULE u32 $u32_s $u32_d classid 1:$CLASS
	done ### rule

	[ "$1" = "compile" ] && echo
done ### classfile
;;


#############################################################################
################################# TIME CHECK ################################
#############################################################################

timecheck)

### Get time + weekday
TIME_TMP=`date +%w/%k:%M`
TIME_DOW=${TIME_TMP%%/*}
TIME_NOW=${TIME_TMP##*/}
TIME_ABS=`htb_time2abs $TIME_NOW`

### Check all classes (if configured)
for classfile in `htb_class_list`; do
	### Load class and gather all TIME rules
	htb_load_class $classfile
	TIMESET=`htb_cfile_rules TIME`
	[ -z "$TIMESET" ] && continue

	MATCH=0; CHANGE=0
	for timerule in $TIMESET; do
		### Split TIME rule to pieces
		TIMESPEC=${timerule%%;*}; PARAMS=${timerule##*;}
		WEEKDAYS=${TIMESPEC%%/*}; INTERVAL=${TIMESPEC##*/}
		BEG_TIME=${INTERVAL%%-*}; END_TIME=${INTERVAL##*-}

		### Check the day-of-week (if present)
		[ "$WEEKDAYS" != "$INTERVAL" -a \
		  -n "${WEEKDAYS##*$TIME_DOW*}" ] && continue

		### Compute interval boundaries
		BEG_ABS=`htb_time2abs $BEG_TIME`
		END_ABS=`htb_time2abs $END_TIME`

		### Midnight wrap fixup
		if [ $BEG_ABS -gt $END_ABS ]; then
			[ $TIME_ABS -le $END_ABS ] &&
				TIME_ABS=$[TIME_ABS + 24*60]

			END_ABS=$[END_ABS + 24*60]
		fi

		### If time period matches, remember params and set MATCH flag
		if [ $TIME_ABS -ge $BEG_ABS -a $TIME_ABS -lt $END_ABS ]; then
			RATESPEC=${PARAMS%%,*}; CEILSPEC=${PARAMS##*,}
			[ "$RATESPEC" = "$CEILSPEC" ] && CEILSPEC=""

			NEW_RATE=${RATESPEC%%/*}; NEW_BURST=${RATESPEC##*/}
			[ "$NEW_RATE" = "$NEW_BURST" ] && NEW_BURST=""

			NEW_CEIL=${CEILSPEC%%/*}; NEW_CBURST=${CEILSPEC##*/}
			[ "$NEW_CEIL" = "$NEW_CBURST" ] && NEW_CBURST=""

			MATCH=1
		fi
	done ### timerule


	### Get current RATE and CEIL of a class
	read RATE_NOW JUNK CEIL_NOW <<-EOT
	`htb_class_state $DEVICE $CLASS`
	EOT

	[ -z "$RATE_NOW" -o -z "$CEIL_NOW" ] && continue


	### Fill empty values if matched
	if [ $MATCH -ne 0 ]; then
		NEW_RATE=${NEW_RATE:-$RATE_NOW}
		NEW_CEIL=${NEW_CEIL:-$CEIL_NOW}

		NEW_BURST=${NEW_BURST:-$BURST}
		NEW_CBURST=${NEW_CBURST:-$CBURST}

	### Force configured values if not matched
	else
		NEW_RATE=$RATE; NEW_CEIL=$CEIL
		NEW_BURST=$BURST; NEW_CBURST=$CBURST
	fi



	### Check for RATE and CEIL changes
	[ "$RATE_NOW" != "$NEW_RATE" ] && CHANGE=1
	[ "$CEIL_NOW" != "$NEW_CEIL" ] && CHANGE=1

	### If there are no changes, go for next class
	[ $CHANGE -eq 0 ] && continue


	### Replace HTB class
	tc class change dev $DEVICE classid 1:$CLASS htb \
	prio $PRIO rate $NEW_RATE ${NEW_CEIL:+ceil $NEW_CEIL} \
	${NEW_BURST:+burst $NEW_BURST} ${NEW_CBURST:+cburst $NEW_CBURST}

	htb_message "$TIME_NOW: change on $DEVICE:$CLASS ($RATE_NOW/$CEIL_NOW -> $NEW_RATE/$NEW_CEIL)"
done ### class file
;;


#############################################################################
################################## THE REST #################################
#############################################################################

stop)
	htb_off
	;;

list)
	htb_show
	;;

stats)
	htb_show -s
	;;

restart)
	shift
	$0 stop
	$0 start "$@"
	;;

*)
	echo "Usage: `basename $0` {start|compile|stop|restart|timecheck|list|stats}"
esac

Бонус

Собрать и установить netatop для atop:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
wget https://www.atoptool.nl/download/netatop-3.1.tar.gz
tar -tf netatop-3.1.tar.gz
tar -zxvf netatop-3.1.tar.gz
cd netatop-3.1
cat README
sudo apt install zlib1g-dev
sudo apt install checkinstall
sudo apt-get install linux-headers-`uname -r`
make
checkinstall --install=no
sudo dpkg -i netatop_3.1-1_amd64.deb
sudo systemctl enable netatop.service
sudo systemctl start netatop.service

Включить передачу графики по ssh:

При установке на сервер приедет графическая оболочка. У меня приехала gnome. sudo dpkg -l gnome*

Установим необходимое и перезагрузимся:

1
2
$ sudo apt list xorg openbox
$ sudo reboot

В конфиге sshd_config подправить значение X11Forwarding yes.

Подключаемся по ssh:

1
$ ssh -X puma

Запускаем графическое приложение:

1
$ virt-manager

Установить виртуализацию libvrt:

Проверим, есть ли поддержка:

1
2
3
$ egrep -c '(vmx|svm)' /proc/cpuinfo
$ sudo apt install cpu-checker
$ kvm-ok

Устанавливаем:

1
$ sudo apt install -y qemu qemu-kvm libvirt-daemon libvirt-clients bridge-utils virt-manager

Проверим, работает ли демон виртуализации:

1
$ sudo systemctl status libvirtd

Проверим загружены ли модули kvm:

1
$ lsmod | grep -i kvm

Посмотреть список доступных образов:

1
$ osinfo-query os

Теперь можно установить виртуальную машину c помощью:

  • virsh
  • virt-install
  • virt-manager (графический интерфес, можно подключиться к удалённому хосту с виртуалками)
1
2
$ sudo virt-install --name=deepin-vm --os-variant=Debian10 --vcpu=2 --ram=2048 --graphics spice --location=/home/Downloads/deepin-20Beta-desktop-amd64.iso --network bridge:vibr0
$ sudo virt-install --name=ubu-virt --os-variant=ubuntu20.04 --vcpu=2 --ram=2048 --graphics spice --cdrom=iso/ubuntu-20.04.2-live-server-amd64.iso --network bridge:br0

Настройка моста.

Установим необходимое:

1
$ sudo apt install bridge-utils

Вот такой конфиг получился:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ cat <<- EOF | tee /etc/netplan/manual.yaml
network:
  ethernets:
    enp6s0:
      dhcp4: false
    # addresses:
    #   - 192.168.8.1/24
    #   - 172.16.8.1/24
    enp7s0:
      dhcp4: true
      nameservers:
        addresses:
          - 127.0.0.1
        search:
          - zavod.lan
  bridges:
    br0:
      interfaces:
        - enp6s0
      addresses:
        - 192.168.8.1/24
        - 172.16.8.1/24
      parameters:
        stp: true

  version: 2
  renderer: networkd
EOF

Проверим, что всё без ошибок:

1
$ sudo netplan generate

Применить, с возможность откатить изменения не получится:

1
2
3
4
$ sudo netplan try
br0: reverting custom parameters for bridges and bonds is not supported

Please carefully review the configuration and use 'netplan apply' directly.

После объединения интерфейсов в bridge, нужно поменять настройки сервисов (например, нужно указать новый интерфейс br0 для isc-dhcp-server). Так же необходимо изменить настройки сетевого экрана, в частности тоже имя интерфейса.

Применяем настройки и поднимаем интерфейс:

1
$ sudo netplan --debug apply

Посмотреть сетевые обекты можно так:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# bridge control
brctl show

# network control
networkctl
networkctl status br0

# ip list
ip a | grep " br0:" -A 3
# show host routes
ip route

# show arp table (IP to MAC)
arp -n

Создадим свою сеть для виртуалок:

1
2
3
4
5
6
7
8
# https://libvirt.org/formatnetwork.html#examplesBridge
cat <<- EOF | tee host-bridge.xml
<network>
  <name>host-bridge</name>
  <forward mode="bridge"/>
  <bridge name="br0"/>
</network>
EOF
1
2
3
4
5
6
7
# create libvirt network using existing host bridge
$ sudo virsh net-define host-bridge.xml
$ sudo virsh net-start host-bridge
$ sudo virsh net-autostart host-bridge

# state should be active, autostart, and persistent
$ sudo virsh net-list --all

Теперь, при создании виртуалки нужно указать сеть host-bridge.

При перезапуске демона libvirtd, он будет создавать свои правила файервола. Все настройки libvirtd находятся по стандартному пути: /etc/libvirt, если удалить default.xml из /etc/libvirt/qemu/networks/autostart/, то настройки применяться будут, т.к. в данному случае мы удаляем sysmlink.

Посмотреть все сети:

1
$ virsh net-list --all

Удалить сеть default:

1
$ virsh net-destroy default

Изменить сеть default:

1
$ sudo virsh net-edit default