Mikrotik: Балансировка в КПСС и соблюдение скоростного режима

tjr0mrybd6odwtvv-1v0gtj0yyu.gifВ этой статье я хочу поделится своим решением балансировки с применением Классификатора по Сетевым Соединениям (Per Connection Classificator) и маркировкой трафика для QoS.

Предисловие


На просторах Хабра и интернета я встречал множество реализаций балансировки, включая и PPC, однако, в ряде этих реализаций, не во всех конечно, были моменты, совершенно дурацкого вида:

Статья MUM, хотя и очень не свежая
Bandwidth-based load-balancing
with failover. The easy way.
/ip firewall address-list
add address=1.1.1.0/24 list=Connected
add address=2.2.2.0/24 list=Connected
add address=192.168.22.0/24 list=Connected
add address=192.168.22.0/24 list=LAN
/ip firewall mangle
add chain=prerouting src-address-list=Connected
dst-address-list=Connected action=accept


Ну и кроме всего, связать это с QoS не является тривиальной задачей.

Введение в Policy Base Routing


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

Таблицы маршрутизации


Мы будем использовать рекурсивный просмотр маршрутов совместно со встроенным механизмом проверки доступности. Так-же, живость маршрута будем проверять по нескольким целям:

/ip route
# Это две цели для проверки маршрута, проверяем их через шлюз провайдера
add comment="ISP1: Check target1" distance=1 dst-address=77.88.8.8/32 \
    gateway=r.r.r.r scope=10
add comment="ISP1: Check target2" distance=1 dst-address=8.8.8.8/32 gateway=\
    r.r.r.r scope=10
# Это вполне "случайный" адресс, который не будет реально участвовать в \
#   маршрутизации, но он нужен для рекурсивной проверки маршрута,
# и этот адресс доступен через два "шлюза", живость которых мы проверяем
add check-gateway=ping comment="ISP1: Virtual route1" distance=2 dst-address=\
    127.1.0.1/32 gateway=77.88.8.8 scope=10
add check-gateway=ping comment="ISP1: Virtual route2" distance=2 dst-address=\
    127.2.0.1/32 gateway=8.8.8.8 scope=10
# В таблице маршрутизации первого провайдера задаем шлюз по умолчанию, этот \
#   шлюз, через рекурсию, указывает на реальный шлюз провайдера
add comment="ISP1: Default route" distance=3 gateway=127.1.0.1 routing-mark=\
    ISP1
add comment="ISP1: Default route" distance=3 gateway=127.2.0.1 routing-mark=\
    ISP1
# Маршрут в таблице ISP1 до сегмента сети провайдера, из которого вам выдали IP, \
# без него, весь трафик будет пытаться ходить только через шлюз. Чаще всего, вы \
# и не заметите этой проблемы ;)
add comment="ISP1: Neighbor net route" distance=1 dst-address=\
    n.n.n.n/n gateway=V-603 pref-src=a.a.a.a routing-mark=ISP1 \
    scope=10
# Тут описываем все тоже самое, но для таблицы маршрутизации main, эти \
#   маршруты обеспечат нам failover для соединений, которые должны были \
#   идти через второго провайдера
add check-gateway=ping comment="ISP1: Virtual backup route1" distance=2 \
    dst-address=127.1.1.1/32 gateway=77.88.8.8 scope=10
add check-gateway=ping comment="ISP1: Virtual backup route2" distance=2 \
    dst-address=127.2.1.1/32 gateway=8.8.8.8 scope=10
# И собственно сам дефолтовый маршрут в таблице main. Я явно указал \
#   distance у этого маршрута, для задания приоритета провайдеров при \
#   отказах маршрутов.
add comment="ISP1: Backup Default route" distance=201 gateway=127.1.1.1
add comment="ISP1: Backup Default route" distance=201 gateway=127.2.1.1

# Для второго провайдера делаем аналогично
add comment="ISP2: Check target1" distance=1 dst-address=77.88.8.1/32 \
    gateway=R.R.R.R scope=10
add comment="ISP2: Check target2" distance=1 dst-address=8.8.4.4/32 gateway=\
    R.R.R.R scope=10
#
add check-gateway=ping comment="ISP2: Virtual route1" distance=2 dst-address=\
    127.1.0.2/32 gateway=77.88.8.1 scope=10 
add check-gateway=ping comment="ISP2: Virtual route2" distance=2 dst-address=\
    127.2.0.2/32 gateway=8.8.4.4 scope=10
#
add comment="ISP2: Default route" distance=3 gateway=127.1.0.2 routing-mark=\
    ISP2
add comment="ISP2: Default route" distance=3 gateway=127.2.0.2 routing-mark=\
    ISP2
add comment="ISP2: Neighbor net route" distance=1 dst-address=\
    N.N.N.N/N gateway=ether2 pref-src=A.A.A.A routing-mark=ISP2 \
    scope=10
#
add check-gateway=ping comment="ISP2: Virtual backup route1" distance=2 \
    dst-address=127.1.1.2/32 gateway=77.88.8.1 scope=10

add check-gateway=ping comment="ISP2: Virtual backup route2" distance=2 \
    dst-address=127.2.1.2/32 gateway=8.8.4.4 scope=10
#
add comment="ISP2: Backup Default route" distance=202 gateway=127.2.1.2
add comment="ISP2: Backup Default route" distance=202 gateway=127.1.1.2
# По этим правилам маршрутизатор будет пытаться найти маршрут для пакета
/ip route rule
add routing-mark=ISP1 table=ISP1
add routing-mark=ISP2 table=ISP2
add comment=ISP1 src-address=a.a.a.a table=ISP1
add comment=ISP2 src-address=A.A.A.A table=ISP2


Для генерации маршрутов нам поможет такой скрипт:

DHCP route script

###############################################
:local ISPNum 1
:local rmark "ISP$ISPNum"
:global DHCPSpin
:local "backup-route-distance" 201
:local "check-target1" "77.88.8.8"
:local "check-target2" "77.88.8.8"
:local targets 1
############ use then static ip #######################
:local "server-address" "176.57.73.41"
:local "lease-address" "176.57.73.34"
:local "interface" "IV-210"
:local bound 1
###############################################
:local "route-num" 7
:local TTL 30
:local continue true;
:local i 0
:if ( $DHCPSpin !=0 || $DHCPSpin !=1 ) do={
 :set DHCPSpin 0
}
:if ( $targets = 2 ) do={
	:set "route-num" 11
}
:if ($bound=1) do={
	:local count [/ip route print count-only where comment~"^$rmark"]
	:if ($count = 0) do={
		:while ($continue && $i < $TTL) do={
			:if ( $DHCPSpin = 0 ) do={
				:set DHCPSpin 1
				/ip route add gateway=$"server-address" distance=1 dst-address=$"check-target1" scope=10 comment="$rmark: Check target1"
				:if ( $targets = 2 ) do={
					/ip route add gateway=$"server-address" distance=1 dst-address=$"check-target2" scope=10 comment="$rmark: Check target2"
				}
				:local "route-var" [/ip route get [find gateway=$"interface"]]
				/ip route add gateway=$"interface" distance=1 dst-address=($"route-var"->"dst-address") routing-mark=$rmark scope=10 pref-src=$"lease-address" comment="$rmark: Neighbor net route"
				/ip route add gateway=$"check-target1" distance=2 dst-address="127.1.0.$ISPNum" scope=10 check-gateway=ping comment="$rmark: Virtual route1"
				:if ( $targets = 2 ) do={
					/ip route add gateway=$"check-target2" distance=2 dst-address="127.2.0.$ISPNum" scope=10 check-gateway=ping comment="$rmark: Virtual route2"
				}
				/ip route add gateway=$"check-target1" distance=2 dst-address="127.1.1.$ISPNum" scope=10 check-gateway=ping comment="$rmark: Virtual backup route1"
				:if ( $targets = 2 ) do={
					/ip route add gateway=$"check-target2" distance=2 dst-address="127.2.1.$ISPNum" scope=10 check-gateway=ping comment="$rmark: Virtual backup route2"
				}
				/ip route add gateway="127.1.0.$ISPNum" distance=3 routing-mark=$rmark comment="$rmark: Default route"
				:if ( $targets = 2 ) do={
					/ip route add gateway="127.2.0.$ISPNum" distance=3 routing-mark=$rmark comment="$rmark: Default route"
				}
				/ip route add gateway="127.1.1.$ISPNum" distance=$"backup-route-distance" comment="$rmark: Backup Default route"
				:if ( $targets = 2 ) do={
					/ip route add gateway="127.2.1.$ISPNum" distance=$"backup-route-distance" comment="$rmark: Backup Default route"
				}
				/ip route rule add routing-mark=$rmark table=$rmark comment="$rmark: Route mark rule"
				/ip route rule add src-address=$"lease-address" table=$rmark comment="$rmark: Src addr rule"
				:set DHCPSpin 0
				:set continue false;
			} else={
				:set i ($i + 1)
				delay 1
			}
		}	
	} else={
		:if ($count = $"route-num") do={
			:set i 0
			:while ($continue && $i < $TTL) do={
				:if ( $DHCPSpin = 0 ) do={
					:set DHCPSpin 1
					:local test [/ip route find where comment="$rmark: Check target1"]
						:if ([/ip route get $test gateway] != $"server-address") do={
							/ip route set $test gateway=$"server-address"
						}
					:if ( $targets = 2 ) do={
						:local test [/ip route find where comment="$rmark: Check target2"]
							:if ([/ip route get $test gateway] != $"server-address") do={
								/ip route set $test gateway=$"server-address"
							}
					}
					:local test [/ip route rule find where comment=$rmark]
						:if ([/ip route rule get $test src-address] != $"lease-address") do={
							/ip route rule set $test src-address=$"lease-address"
						}
					:local test [/ip route find where comment="$rmark: Neighbor net route"]
						:local "route-var" [/ip route get [find where gateway=$"interface"]]
						:if ([/ip route get $test pref-src] != $"lease-address") do={
							/ip route set $test pref-src=$"lease-address"
						}
					:set DHCPSpin 0
					:set continue false;
				} else={
					:set i ($i + 1)
					delay 1
				}
			}	
		} else={
			:error "Multiple routes found"
		}
	}
} else={
	:set i 0
	:while ($continue && $i < $TTL) do={
		:if ( $DHCPSpin = 0 ) do={
			:set DHCPSpin 1
				/ip route remove [find comment~"^$rmark"]
				/ip route rule remove [find comment~"^$rmark"]
				:set DHCPSpin 0
				:set continue false;
		} else={
			:set i ($i + 1)
			delay 1
		}
	}
}


Данный скрипт может быть использован совместно с DHCP, если закоментить переменные в разделе «use then static ip»

Firewall, mangle


Задание только лишь таблицы маршрутизации будет недостаточно для работы балансировки, поэтому необходимо промаркировать соединения и пакеты в них метками наших провайдеров (метки ставятся в структурах данных ядра и не затрагивают реальные пакеты).

При настройке рекомендую группировать интерфейсы в списки. Даже если интерфейс один, помещайте его в персональный смысловой список. Назначая их в списки по назначению, вы упрощаете конфигурацию, и можете сделать её более переносимой.

Идея тут довольно простая: новое соединение пришедшее из интернета, сразу маркируется меткой провайдера, через которого пришло, а то что собирается уйти в сторону интернета балансируется с помощью PPC или направляется к «прибитому» (Nailed) провайдеру в соответствии с критерием.

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

Принципиальная (обобщенная) схема прохождения пакетов в таблице PreRoute:

PreRoute (P)

Кликабельно

c99vlatntrnu3o0mck0xghrr-xg.png

Функциональная схема, каждая колонка олицетворяет отдельную цепочку правил:

PreRoute (F)

Кликабельно

bvdom5p4bim6_otf0aipvzsmer4.png

Схема таблицы Forward принципиальная:

Forward (P)

Кликабельно

dxvbfrarzp8crex3cyh6dvzo-es.png

И собственно самый большой монстр, схема таблицы Forward функциональная:

Forward (F)

Кликабельно

y3oyjojax1r3iipxhqfnowuksty.png

Такие извращения нужны в частности из-за того, что нет условия вроде «ELSE», а еще, на соединении может быть только одна метка (которая есть целое число, для удобства, отображенная как понятное человеку название). Из-за наличия кучи QoS классов, да еще и маршрутов до разных провайдеров, пришлось реализовывать «вызовы функций с переменными в стеке» тем языком, который есть в пакетном фильтре RoS (который почти ничем не отличается от Linux и просто обернут немного в другую командную обертку). Если реализуют битовые операции с метками, то почти все извращения выше станут не нужны!

QoS


Если вы ищите универсальное решение: его нет. Каждый планирует политику исходя из потребностей своей сети. Я полагался на вот эту статью: Borderless Campus 1.0 Design Guide от Cisco, и тоже перенес её не 1 в 1.

Самый первый, и самый не сложный (хоть и большой), скрипт классификации трафика (бесполезен без остальных, нет своей точки входа для трафика):

Классификатор

###############################################
# by RouterOS 6.41
:if ( [/ip firewall layer7-protocol print count-only where name=rtp] = 0) do={
   /ip firewall layer7-protocol add name=rtp regexp="^\\x80"
}
:if ( [/ip firewall layer7-protocol print count-only where name="Multimedia Streaming"] = 0) do={
   /ip firewall layer7-protocol add name="Multimedia Streaming" regexp=.youtube.com|.googlevideo.com
}
:if ( [/ip firewall layer7-protocol print count-only where name="Viber"] = 0) do={
   /ip firewall layer7-protocol add name="Viber" regexp="^..([\\x03\\x04\\x0a\\x06\\x08\\x67\\x0b])\$|^..\\x90([\\xef\\x6f\\x64\\xe4])"
}
/ip firewall mangle

add action=mark-connection chain=ClassifyRule comment="Classify as DF" \
    new-connection-mark=DF passthrough=yes
add action=mark-connection chain=ClassifyRule comment="Classify as CS1" \
    new-connection-mark=CS1 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF32 Multimedia Streaming" dst-port=80,443 layer7-protocol=\
    "Multimedia Streaming" new-connection-mark=AF32 passthrough=yes protocol=\
    tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF32 Multimedia Streaming" dst-port=80,443 layer7-protocol=\
    "Multimedia Streaming" new-connection-mark=AF32 passthrough=yes protocol=\
    udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF32 Multimedia Streaming" layer7-protocol=\
    "Multimedia Streaming" new-connection-mark=AF32 passthrough=yes protocol=\
    tcp src-port=80,443
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF32 Multimedia Streaming" layer7-protocol=\
    "Multimedia Streaming" new-connection-mark=AF32 passthrough=yes protocol=\
    udp src-port=80,443
add action=mark-connection chain=ClassifyRule comment="Classify as EF NTP" \
    dst-port=123 new-connection-mark=EF passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as EF NTP" \
    new-connection-mark=EF passthrough=yes protocol=udp src-port=123
add action=mark-connection chain=ClassifyRule comment="Classify as EF IAX2" \
    dst-port=4569 new-connection-mark=EF passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as EF IAX2" \
    new-connection-mark=EF passthrough=yes protocol=udp src-port=4569
add action=jump chain=ClassifyRule comment="Classify as EF RTP voice" \
    dst-port=10000-20000 jump-target="RCM-(s)rtp" packet-size=50-210 \
    protocol=udp
add action=jump chain=ClassifyRule comment="Classify as EF RTP voice" \
    jump-target="RCM-(s)rtp" packet-size=50-210 protocol=udp src-port=\
    10000-20000
add action=mark-connection chain="RCM-(s)rtp" comment=\
    "Classify as EF RTP voice" layer7-protocol=rtp new-connection-mark=EF \
    passthrough=yes
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Viber" dst-port=1024-65536 new-connection-mark=\
    AF42 packet-size=12-210 layer7-protocol=Viber connection-mark=CS1 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 RTP Viber" new-connection-mark=AF42 packet-size=\
    12-210 layer7-protocol=Viber connection-mark=CS1 passthrough=yes protocol=udp src-port=1024-65536
	
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 RTP video" dst-port=10000-20000 new-connection-mark=\
    AF42 packet-size=211-65535 layer7-protocol=rtp connection-mark=CS1 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 RTP video" new-connection-mark=AF42 packet-size=\
    211-65535 layer7-protocol=rtp connection-mark=CS1 passthrough=yes protocol=udp src-port=10000-20000

add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Hangouts" dst-port=19302-19309 new-connection-mark=AF42 \
    passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Hangouts" new-connection-mark=AF42 passthrough=yes \
    protocol=udp src-port=19302-19309
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Viber" dst-port=5242,4244 new-connection-mark=AF42 \
    passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Viber" new-connection-mark=AF42 passthrough=yes \
    protocol=tcp src-port=5242,4244
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Viber" dst-port=7985,5243,9785,7985 \
    new-connection-mark=AF42 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 Viber" new-connection-mark=AF42 passthrough=yes \
    protocol=udp src-port=7985,5243,9785,7985
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 WhatsApp" dst-port=50318,59234,3478,45395 \
    new-connection-mark=AF42 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 WhatsApp" new-connection-mark=AF42 passthrough=yes \
    protocol=udp src-port=50318,59234,3478,45395
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 WhatsApp" dst-port=4244,5222-5223,5228,5242,50318,59234 \
    new-connection-mark=AF42 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF42 WhatsApp" new-connection-mark=AF42 passthrough=yes \
    protocol=tcp src-port=4244,5222-5223,5228,5242,50318,59234
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 SSH" \
    dst-port=22 new-connection-mark=CS2 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 SSH" \
    new-connection-mark=CS2 passthrough=yes protocol=tcp src-port=22
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 SNMP" \
    dst-port=161,162 new-connection-mark=CS2 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 SNMP" \
    new-connection-mark=CS2 passthrough=yes protocol=udp src-port=161,162
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 TELNET" dst-port=23,992 new-connection-mark=CS2 \
    passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 TELNET" new-connection-mark=CS2 passthrough=yes \
    protocol=tcp src-port=23,992
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 TFTP" \
    dst-port=69 new-connection-mark=CS2 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS2 TFTP" \
    new-connection-mark=CS2 passthrough=yes protocol=udp src-port=69
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 Syslog" dst-port=514 new-connection-mark=CS2 \
    passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 Syslog" new-connection-mark=CS2 passthrough=yes \
    protocol=tcp src-port=514
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 Syslog" dst-port=514 new-connection-mark=CS2 \
    passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS2 Syslog" new-connection-mark=CS2 passthrough=yes \
    protocol=udp src-port=514
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    dst-port=3002,9300,17988,17990 new-connection-mark=CS7 passthrough=yes \
    protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    new-connection-mark=CS7 passthrough=yes protocol=tcp src-port=\
    3002,9300,17988,17990
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    dst-port=623 new-connection-mark=CS7 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    new-connection-mark=CS7 passthrough=yes protocol=tcp src-port=623
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    dst-port=623 new-connection-mark=CS7 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS7 ILO" \
    new-connection-mark=CS7 passthrough=yes protocol=udp src-port=623
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 FTP" \
    dst-port=21 new-connection-mark=AF12 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as AF 12 FTP" \
    new-connection-mark=AF12 passthrough=yes protocol=tcp src-port=21
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 FTP" \
    connection-state=established,related connection-type=ftp \
    new-connection-mark=AF12 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    dst-port=1024-65535 new-connection-mark=AF12 passthrough=yes protocol=udp \
    src-port=137
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    dst-port=137 new-connection-mark=AF12 passthrough=yes protocol=udp \
    src-port=1024-65535
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    dst-port=135,137-139,445 new-connection-mark=AF12 passthrough=yes \
    protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    new-connection-mark=AF12 passthrough=yes protocol=udp src-port=\
    135,137-139,445
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    dst-port=135,139,445 new-connection-mark=AF12 passthrough=yes protocol=\
    tcp
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 SMB" \
    new-connection-mark=AF12 passthrough=yes protocol=tcp src-port=\
    135,139,445
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF12 EMAIL" dst-port=143,993,110,995,25,465,587 \
    new-connection-mark=AF12 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF12 EMAIL" new-connection-mark=AF12 passthrough=yes \
    protocol=tcp src-port=143,993,110,995,25,465,587
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 DCC" \
    dst-port=6277 new-connection-mark=AF12 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as AF12 DCC" \
    new-connection-mark=AF12 passthrough=yes protocol=udp src-port=6277
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF12 IPMI Virtual Media, Secure (Dell)" dst-port=3668,3669 \
    new-connection-mark=AF12 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as AF12 IPMI Virtual Media, Secure (Dell)" new-connection-mark=\
    AF12 passthrough=yes protocol=tcp src-port=3668,3669
add action=mark-connection chain=ClassifyRule comment="Classify as AF32 DAAP" \
    dst-port=3689 new-connection-mark=AF32 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as AF32 DAAP" \
    new-connection-mark=AF32 passthrough=yes protocol=udp src-port=3689
add action=mark-connection chain=ClassifyRule comment="Classify as AF32 DAAP" \
    dst-port=3689 new-connection-mark=AF32 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as AF32 DAAP" \
    new-connection-mark=AF32 passthrough=yes protocol=tcp src-port=3689
add action=mark-connection chain=ClassifyRule comment="Classify as CS6 OSPF" \
    new-connection-mark=CS6 passthrough=yes protocol=ospf
add action=mark-connection chain=ClassifyRule comment="Classify as CS6 RIP" \
    dst-port=520 new-connection-mark=CS6 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS6 RIP" \
    new-connection-mark=CS6 passthrough=yes protocol=udp src-port=520
add action=mark-connection chain=ClassifyRule comment="Classify as CS6 IKE" \
    dst-port=500 new-connection-mark=CS7 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS6 IKE" \
    new-connection-mark=CS7 passthrough=yes protocol=udp src-port=500
add action=mark-connection chain=ClassifyRule comment="Classify as  CS6 BGP" \
    dst-port=179 new-connection-mark=CS6 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as  CS6 BGP" \
    new-connection-mark=CS6 passthrough=yes protocol=tcp src-port=179
add action=mark-connection chain=ClassifyRule comment="Classify as CS4 RDP" \
    dst-port=3389-3400 new-connection-mark=CS4 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as CS4 RDP" \
    dst-port=3389-3400 new-connection-mark=CS4 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS4 RDP" \
    new-connection-mark=CS4 passthrough=yes protocol=tcp src-port=3389-3400
add action=mark-connection chain=ClassifyRule comment="Classify as CS4 RDP" \
    new-connection-mark=CS4 passthrough=yes protocol=udp src-port=3389-3400
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 VNC\\Aten\\AMI" dst-port=5900,5901,7578 \
    new-connection-mark=CS4 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 VNC\\Aten\\AMI" new-connection-mark=CS4 passthrough=yes \
    protocol=tcp src-port=5900,5901,7578
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 Citrix/ICA" dst-port=1604 new-connection-mark=CS4 \
    passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 Citrix/ICA" new-connection-mark=CS4 passthrough=yes \
    protocol=udp src-port=1604
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 Citrix/ICA" dst-port=1494,2598 new-connection-mark=CS4 \
    passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment=\
    "Classify as CS4 Citrix/ICA" new-connection-mark=CS4 passthrough=yes \
    protocol=tcp src-port=1494,2598
add action=mark-connection chain=ClassifyRule comment="Classify as CS3 SIP" \
    dst-port=5060 new-connection-mark=CS3 passthrough=yes protocol=udp
add action=mark-connection chain=ClassifyRule comment="Classify as CS3 SIP" \
    new-connection-mark=CS3 passthrough=yes protocol=udp src-port=5060
add action=mark-connection chain=ClassifyRule comment="Classify as CS3 SIP" \
    dst-port=5061 new-connection-mark=CS3 passthrough=yes protocol=tcp
add action=mark-connection chain=ClassifyRule comment="Classify as CS3 SIP" \
    new-connection-mark=CS3 passthrough=yes protocol=tcp src-port=5061


Этот классификатор назначает метку класса для различных видов трафика, основной набор я взял из Shorewall, если вам столько не надо, смело урезайте. Тут есть два простых правила, весь трафик сначала помечается как DF (Default), а затем, весь UDP помечается как CS1 (Scavenger, «мусорный» трафик, которым мы заполняем остатки полосы пропускания). И уже после, весь трафик переразмечается в другие классы.

Таким образом, torrent автоматически становится либо CS1, либо DF (если TCP), и куча другого трафика так-же может получить такие классы, если вы их сами не выделите.

Важно заметить, что Web трафик, соединения которого «длинные», потом перемаркируется в AF12 (для различных закачек и т.п.). Таким образом, серфинг проходит шустро (у DF приоритет 7), а когда начинается закачка, она автоматически попадает в AF12 (с приоритетом 8), тем самым, забить канал закачкой не удается.

Отдельно стоят всякие YouTube. Их трафик ловим с помощью Layer7 (не самый надежный вариант, но вполне рабочий).

Далее идет скрипт ответственный за маршрутизацию:

Маршрутизация
###############################################
:local ISPNum 6
#:local UserType [:toarray ("ViP","LoPri","Guest","Norm")]
:local UserType [:toarray ("Norm")]
:local DNSNat [:toarray ("local.domain")]
:local DNSNatIP [:toarray ("172.16.24.94")]
:local ClassifyLocal true
:local Balancing true
:local BalancingWeigh [:toarray (2,2,2,2,6,1)]
# by RouterOS 6.41

:if ( [/interface list print count-only where name=ISP] = 0) do={
	/interface list add name=ISP
}
:if ( [/interface list print count-only where name=TUN] = 0) do={
	/interface list add name=TUN
}
:if ( [/interface list print count-only where name=LAN-IF] = 0) do={
	/interface list add name=LAN-IF
}
:if ( [/interface list print count-only where name=GST-IF] = 0) do={
	/interface list add name=GST-IF
}
:if ( [/interface list print count-only where name=GRE] = 0) do={
	/interface list add name=GRE
}
:local isp ""
:local tun ""
:for i from=1 to=$ISPNum step=1 do={
	:if ( [/interface list print count-only where name=("ISP".$i)] = 0) do={
		/interface list add name=("ISP".$i)
	}
	:if ( [/interface list print count-only where name=("TUN".$i)] = 0) do={
		/interface list add name=("TUN".$i)
	}
	:if ( [/interface list print count-only where name=("ISP".$i."TUN")] = 0) do={
		/interface list add include=("ISP".$i.",TUN".$i) name=("ISP".$i."TUN")
	}
	:set isp ($isp."ISP".$i.",")
	:set tun ($tun."TUN".$i.",")
}
/interface list set ISP include=$isp
:foreach t in=$DNSNat do={ 
	:if ( [/ip firewall layer7-protocol print count-only where name=$t] = 0) do={
		/ip firewall layer7-protocol add name=$t regexp=(".".$t) comment=("DNS Nat ".$t)
	}
}
/ip firewall mangle
:local i 0
:foreach t in=$DNSNat do={ 
	add action=mark-packet chain=prerouting comment=("mark dns ".$t) dst-port=53 \
		in-interface-list=LAN-IF layer7-protocol=$t new-packet-mark=\
		("dns-".$t) passthrough=yes protocol=udp dst-address-type=local
	add action=mark-packet chain=prerouting comment=("mark dns ".$t) dst-port=53 \
		in-interface-list=LAN-IF layer7-protocol=$t new-packet-mark=\
		("dns-".$t) passthrough=yes protocol=tcp dst-address-type=local
	/ip firewall nat add action=dst-nat chain=dstnat comment=("DST-NAT dns ".$t) dst-port=53 \
		in-interface-list=LAN-IF packet-mark=("dns-".$t) protocol=udp \
		to-addresses=([:pick $DNSNatIP $i])
	/ip firewall nat add action=dst-nat chain=dstnat comment=("DST-NAT dns ".$t) dst-port=53 \
		in-interface-list=LAN-IF packet-mark=("dns-".$t) protocol=tcp \
		to-addresses=([:pick $DNSNatIP $i])
	/ip firewall nat add action=masquerade chain=srcnat comment=("mask dns ".$t) dst-port=53 \
		packet-mark=("dns-".$t) protocol=udp
	/ip firewall nat add action=masquerade chain=srcnat comment=("mask dns ".$t) dst-port=53 \
		packet-mark=("dns-".$t) protocol=tcp
	:set i ($i + 1)
}
add action=jump chain=prerouting comment="PreRoute: Catch New Connections" \
    connection-state=new jump-target=PR-New
add action=jump chain=prerouting comment="PreRoute: Catch packets with ConnMark from LAN" \
   connection-mark=!no-mark in-interface-list=!ISP jump-target=PR-RouteMark dst-address-list=!LAN-IP src-address-list=LAN-IP
:if ( $Balancing = true ) do={
	:local CommDen 0
	:foreach t in=$BalancingWeigh do={ 
		:set CommDen ($CommDen + $t)
	}
   :for i from=1 to=$ISPNum step=1 do={
	add action=mark-connection chain=PR-New comment=\
			("ISP".$i.": PreRoute: marking conn from ISP".$i) dst-address-list=!LAN-IP \
			in-interface-list=("ISP".$i) new-connection-mark=("ISP".$i) passthrough=yes \
			src-address-list=!LAN-IP
	}
	add action=jump chain=PR-New comment="PreRoute: prepare for balancing" \
		dst-address-list=!LAN-IP in-interface-list=!ISP \
		jump-target=PR-Nailed src-address-list=LAN-IP src-address-type=!local
	:for i from=1 to=$ISPNum step=1 do={
		add action=mark-connection chain=PR-Nailed comment=\
			("ISP".$i.": PreRoute: Nailed appropriate connection to ISP".$i) \
			new-connection-mark=("ISP".$i) passthrough=yes src-address-list=("ISP".$i."-Nailed")
	}
	add action=jump chain=PR-Nailed comment="PreRoute: Go to balancing" \
		connection-mark=no-mark jump-target=PR-Balancing
	:local w 0
	:for i from=1 to=$ISPNum step=1 do={
		:for j from=1 to=[:pick $BalancingWeigh ($i-1)] step=1 do={
			add action=mark-connection chain=PR-Balancing comment=\
				("ISP".$i.": PreRoute: PCC for ISP".$i) new-connection-mark=("ISP".$i) passthrough=yes \
				per-connection-classifier=("both-addresses:".$CommDen."/".$w)
			:set w ($w + 1)
		}
	}
	:for i from=1 to=$ISPNum step=1 do={
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for unclassifying ISP".$i." connection") \
			connection-mark=("ISP".$i) new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for unclassifying ISP".$i." connection") \
			connection-mark=("ISP".$i."-DF") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for EF ISP".$i." connection") \
			connection-mark=("ISP".$i."-EF") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for AF12 ISP".$i." connection") \
			connection-mark=("ISP".$i."-AF12") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i." PreRoute: set route-mark for AF32 ISP".$i." connection") \
			connection-mark=("ISP".$i."-AF32") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for AF42 ISP".$i." connection") \
			connection-mark=("ISP".$i."-AF42") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for CS1 ISP".$i." connection") connection-mark=\
			("ISP".$i."-CS1") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for CS2 ISP".$i." connection") connection-mark=\
			("ISP".$i."-CS2") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for CS3 ISP".$i." connection") connection-mark=\
			("ISP".$i."-CS3") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for CS4 ISP".$i." connection") connection-mark=\
			("ISP".$i."-CS4") new-routing-mark=("ISP".$i) passthrough=yes
		add action=mark-routing chain=PR-RouteMark comment=\
			("ISP".$i.": PreRoute: set route-mark for CS6 ISP".$i." connection") connection-mark=\
			("ISP".$i."-CS6") new-routing-mark=("ISP".$i) passthrough=yes
	}
} else={
	add action=mark-connection chain=PR-New comment=\
		"ISP1: PreRoute: marking conn from ISP1" dst-address-list=!LAN-IP \
		in-interface-list=ISP1 new-connection-mark=ISP1 passthrough=yes \
		src-address-list=!LAN-IP
	add action=mark-connection chain=PR-New comment=\
		"ISP1: PreRoute: marking conn from ISP1" dst-address-list=!LAN-IP \
		in-interface-list=!ISP new-connection-mark="ISP1" passthrough=yes \
		src-address-list=LAN-IP
}
add action=jump chain=forward comment="Forward: Catch New Connections" \
    connection-state=new jump-target=F-PreClassify
add action=jump chain=input comment="Input: Catch New Connections" \
    connection-state=new connection-mark=no-mark jump-target=I-PreClassify
add action=jump chain=output comment="Output: Catch New Connections" \
    connection-state=new connection-mark=no-mark jump-target=O-PreClassify
:for i from=1 to=$ISPNum step=1 do={
	add action=mark-connection chain=F-PreClassify comment=\
		("ISP".$i.": Forward: remark connection by ISP".$i." for effective route") \
		new-connection-mark=("ISP".$i) out-interface-list=("ISP".$i) passthrough=yes
	add action=mark-connection chain=F-PreClassify comment=\
		("Forward: associate TUN connection with ISP".$i) in-interface-list=("TUN".$i) \
		new-connection-mark=("ISP".$i) connection-mark=no-mark passthrough=yes
	add action=mark-connection chain=F-PreClassify comment=\
		("Forward: associate TUN connection with ISP".$i) new-connection-mark=("ISP".$i) \
		out-interface-list=("TUN".$i) connection-mark=no-mark passthrough=yes
	add action=jump chain=F-PreClassify comment=\
		("ISP".$i.": Forward: prepare for ISP".$i." classifying") connection-mark=("ISP".$i) \
		jump-target=("ISP".$i."-Classify")
	add action=jump chain=I-PreClassify comment=\
		("ISP".$i.": Input: prepare for ISP".$i." classifying") in-interface-list=("ISP".$i) \
		jump-target=("ISP".$i."-Classify")		
	add action=jump chain=O-PreClassify comment=\
		("ISP".$i.": Output: prepare for ISP".$i." classifying") out-interface-list=("ISP".$i) \
		jump-target=("ISP".$i."-Classify")
}
:if ( $ClassifyLocal = true ) do={
	add action=jump chain=F-PreClassify comment=\
		"Forward: Classifying LAN connection" connection-mark=no-mark \
		in-interface-list=LAN-IF jump-target=ClassifyRule
	add action=jump chain=F-PreClassify comment=\
		"Forward: Classifying LAN connection" connection-mark=no-mark \
		 jump-target=ClassifyRule out-interface-list=LAN-IF
}


Можем задать число провайдеров (у меня их 6-ть), указать домены, DNS резолвинг которых надо делать на особом DNS сервере, указать классы пользователей (первыми указываем все «особые» категории, в конце все «остальные»). Указываем на необходимость классификации трафика в локальной сети, а не только до интернета и задаем необходимость балансировки, с указанием относительного веса провайдеров.

Обязательно заполните список адресов LAN-IP. Он необходим для определения направления трафика и должен содержать адреса вашей внутренней сети.

Далее, скрипт для раскладывания трафика по очередям:

Маркировка для очередей
###############################################
:local ISPNum 6
#:local UserType [:toarray ("ViP","LoPri","Guest","Norm")]
:local UserType [:toarray ("Norm")]
:local SetDSCP true
:local InOutQueues false
# by RouterOS 6.41

:if ( [/interface list print count-only where name=ISP] = 0) do={
	/interface list add name=ISP
}
:if ( [/interface list print count-only where name=TUN] = 0) do={
	/interface list add name=TUN
}
:if ( [/interface list print count-only where name=WiFi] = 0) do={
	/interface list add name=WiFi
}
:local isp ""
:local tun ""
:for i from=1 to=$ISPNum step=1 do={
	:if ( [/interface list print count-only where name=("ISP".$i)] = 0) do={
		/interface list add name=("ISP".$i)
	}
	:if ( [/interface list print count-only where name=("TUN".$i)] = 0) do={
		/interface list add name=("TUN".$i)
	}
	:if ( [/interface list print count-only where name=("ISP".$i."TUN")] = 0) do={
		/interface list add include=("ISP".$i.",TUN".$i) name=("ISP".$i."TUN")
	}
	:set isp ($isp."ISP".$i.",")
	:set tun ($tun."TUN".$i.",")
}
/interface list set ISP include=$isp
/interface list set TUN include=$tun
/ip firewall mangle
:local PT no
:if ( $SetDSCP = true ) do={
	:set PT yes
}
:for i from=1 to=$ISPNum step=1 do={
	add action=jump chain=("ISP".$i."-Classify") comment=("Jump to classify ISP".$i) \
		jump-target=ClassifyRule
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-DF connmark for DF") connection-mark=DF \
		new-connection-mark=("ISP".$i."-DF") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-EF connmark for EF") connection-mark=EF \
		new-connection-mark=("ISP".$i."-EF") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-AF12 connmark for AF12") connection-mark=AF12 \
		new-connection-mark=("ISP".$i."-AF12") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-AF32 connmark for AF32") connection-mark=AF32 \
		new-connection-mark=("ISP".$i."-AF32") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-AF42 connmark for AF42") connection-mark=AF42 \
		new-connection-mark=("ISP".$i."-AF42") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS1 connmark for CS1") connection-mark=CS1 \
		new-connection-mark=("ISP".$i."-CS1") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS2 connmark for CS2") connection-mark=CS2 \
		new-connection-mark=("ISP".$i."-CS2") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS3 connmark for CS3") connection-mark=CS3 \
		new-connection-mark=("ISP".$i."-CS3") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS4 connmark for CS4") connection-mark=CS4 \
		new-connection-mark=("ISP".$i."-CS4") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS6 connmark for CS6") connection-mark=CS6 \
		new-connection-mark=("ISP".$i."-CS6") passthrough=yes
	add action=mark-connection chain=("ISP".$i."-Classify") comment=\
		("ISP".$i.": set ISP".$i."-CS7 connmark for CS7") connection-mark=CS7 \
		new-connection-mark=("ISP".$i."-CS7") passthrough=yes
}
add action=jump chain=forward comment="Forward: Download stream ISP" \
    in-interface-list=ISP jump-target=F-Download out-interface-list=!ISP
add action=jump chain=forward comment="Forward: download stream from TUN" \
    in-interface-list=TUN jump-target=F-Download out-interface-list=!ISP
add action=jump chain=forward comment="Forward: Upload stream from ISP" \
    in-interface-list=!ISP jump-target=F-Upload out-interface-list=ISP
add action=jump chain=forward comment="Forward: Upload stream from TUN" \
    in-interface-list=!ISP jump-target=F-Upload out-interface-list=TUN
:if ( $InOutQueues = true ) do={
	:for i from=1 to=$ISPNum step=1 do={
		add action=jump chain=input connection-mark=!no-mark in-interface-list=("ISP".$i."TUN") \
			jump-target=("ISP".$i."-".[:pick $UserType ([:len $UserType] -1 )]."-Download") packet-mark=no-mark comment=("Input: Download stream ISP".$i)
		add action=jump chain=output connection-mark=!no-mark out-interface-list=("ISP".$i."TUN") \
			jump-target=("ISP".$i."-".[:pick $UserType ([:len $UserType] -1 )]."-Upload") packet-mark=no-mark comment=("Output: Upload stream ISP".$i)
	}
}
:foreach d in=("Download","Upload") do={
	:foreach t in=$UserType do={
		:if ([:pick $UserType ([:len $UserType] -1 )] != $t) do={
			:if ($d = "Download") do={
				add action=jump chain=("F-".$d) comment=("Forward: ".$d." from ".$t) \
					dst-address-list=$t jump-target=("F-".$d.$t)
				} else={
					add action=jump chain=("F-".$d) comment=("Forward: ".$d." from ".$t) \
						src-address-list=$t jump-target=("F-".$d.$t)
			}
		} else={
			add action=jump chain=("F-".$d) comment=("Forward: ".$d." from ".$t) \
				jump-target=("F-".$d.$t)
		}
	}
	:foreach t in=$UserType do={ 
		:for i from=1 to=$ISPNum step=1 do={
			:if ($d = "Download") do={
				add action=jump chain=("F-".$d.$t) comment=\
					("ISP".$i.": Forward: ".$d." from ".$t." ISP".$i) in-interface-list=("ISP".$i."TUN") \
					jump-target=("ISP".$i."-".$t."-".$d)
			} else={
				add action=jump chain=("F-".$d.$t) comment=\
					("ISP".$i.": Forward: ".$d." from ".$t." ISP".$i) out-interface-list=("ISP".$i."TUN") \
					jump-target=("ISP".$i."-".$t."-".$d)

			}
			add action=jump chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Remark long DF connection") connection-bytes=524288-0 \
				connection-mark=("ISP".$i."-DF") jump-target=("ISP".$i."-Remark")
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q1 for ".$t." ISP".$i." EF") connection-mark=("ISP".$i."-EF") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q1") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set DF for ISP".$i) connection-mark=("ISP".$i."-EF") new-dscp=46 \
					passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q2 for ".$t." ISP".$i." CS7") connection-mark=("ISP".$i."-CS7") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q2") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS7 for ISP".$i) connection-mark=("ISP".$i."-CS7") new-dscp=\
					56 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q3 for ".$t." ISP".$i." CS6") connection-mark=("ISP".$i."-CS6") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q3") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS6 for ISP".$i) connection-mark=("ISP".$i."-CS6") new-dscp=\
					48 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q4 for ".$t." ISP".$i." CS4") connection-mark=("ISP".$i."-CS4") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q4") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS4 for ISP".$i) connection-mark=("ISP".$i."-CS4") new-dscp=\
					32 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q4 for ".$t." ISP".$i." AF42") connection-mark=\
				("ISP".$i."-AF42") new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q4") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set AF42 for ISP".$i) connection-mark=("ISP".$i."-AF42") \
					new-dscp=36 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q5 for ".$t." ISP".$i." AF32") connection-mark=\
				("ISP".$i."-AF32") new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q5") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set AF32 for ISP".$i) connection-mark=("ISP".$i."-AF32") \
					new-dscp=28 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q5 for ".$t." ISP".$i." CS3") connection-mark=("ISP".$i."-CS3") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q5") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS3 for ISP".$i) connection-mark=("ISP".$i."-CS3") new-dscp=\
					24 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q6 for ".$t." ISP".$i." CS2") connection-mark=("ISP".$i."-CS2") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q6") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS2 for ISP".$i) connection-mark=("ISP".$i."-CS2") new-dscp=\
					16 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q7 for ".$t." ISP".$i." DF") connection-mark=("ISP".$i."-DF") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q7") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set DF for ISP".$i) connection-mark=("ISP".$i."-DF") new-dscp=0 \
					passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q7 for ".$t." ISP".$i." Unclassify") connection-mark=("ISP".$i) \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q7") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set DF for ISP".$i) connection-mark=("ISP".$i) new-dscp=0 \
					passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q8 for ".$t." ISP".$i." AF12") connection-mark=\
				("ISP".$i."-AF12") new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q8") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set AF12 for ISP".$i) connection-mark=("ISP".$i."-AF12") \
					new-dscp=12 passthrough=no
			}
			add action=mark-packet chain=("ISP".$i."-".$t."-".$d) comment=\
				("ISP".$i.": Forward: PacketMark Q8 for ".$t." ISP".$i." CS1") connection-mark=("ISP".$i."-CS1") \
				new-packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q8") passthrough=$PT
			:if ( $SetDSCP = true ) do={
				add action=change-dscp chain=("ISP".$i."-".$t."-".$d) comment=\
					("ISP".$i.": Forward: DSCP Set CS1  for ISP".$i) connection-mark=("ISP".$i."-CS1") \
					new-dscp=8 passthrough=no
			}
		}
	}
}
:for i from=1 to=$ISPNum step=1 do={
	add action=mark-connection chain=("ISP".$i."-Remark") comment=\
		("ISP".$i.": Remark DF to AF12 Bulk") connection-bytes=524288-0 dst-port=80,443 \
		new-connection-mark=("ISP".$i."-AF12") passthrough=yes protocol=tcp
	add action=mark-connection chain=("ISP".$i."-Remark") comment=\
		("ISP".$i.": Remark DF to AF12 Bulk") connection-bytes=524288-0 \
		new-connection-mark=("ISP".$i."-AF12") passthrough=yes protocol=tcp src-port=\
		80,443
	add action=mark-connection chain=("ISP".$i."-Remark") comment=\
		("ISP".$i.": Remark to Scavenger") connection-bytes=524288-0 \
		new-connection-mark=("ISP".$i."-CS1") passthrough=yes protocol=tcp
}
add action=set-priority chain=postrouting new-priority=from-dscp-high-3-bits out-interface-list=WiFi passthrough=yes comment="Set Priority for WiFi"


Тут опций поменьше, из еще «неизвестных»: требовать установки DSCP у пакетов и постановки в очереди трафика сгенерированного самим маршрутизатором (если у вас есть туннели, то их трафик в таком случае будет учтен два раза, включайте только если знаете зачем вам оно надо, мне вот не надо).

Обратите внимание на списки интерфейсов TUN<номер>, в эти списки помещайте туннельные интерфейсы, которые ходят через соответствующего провайдера. Тогда туннельный трафик тоже пройдет QoS, но не внешний (по отношению к туннелю), а внутренний.

Список интерфейсов WiFi нужен для расстановки приоритетов WiFi трафика (технология WMM) с использованием меток DSCP.

В заключении, скрип генерирующий дерево очередей:

Дерево очередей
# by RouterOS 6.41
:local ISPNum 6
#:local UserType [:toarray ("ViP","Norm","LoPri","Guest")]
:local UserType [:toarray ("Norm")]
:local VoIPChanels 6
:local G711 84
:local Max [:toarray (10000,10000,10000,10000,30000,4800)]
:local Reserv 5
:local genOnly -1
:local R $Reserv
/queue tree
:foreach d in=("Download","Upload") do={
	:for i from=1 to=$ISPNum step=1 do={
		:if ( $genOnly = -1 || $genOnly = $i ) do={
			:if (([:pick $Max ($i-1)] * $Reserv / 100) > 500) do={
				:set R 500
			} else={
				:set R ([:pick $Max ($i-1)] * $Reserv / 100)
			}
			add max-limit=([:pick $Max ($i-1)]."k") name=("ISP".$i."-".[:pick $d 0]."R") parent=global
			:local prio (9-[:len $UserType])
			:foreach t in=$UserType do={
				add name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) parent=("ISP".$i."-".[:pick $d 0]."R") priority=$prio
				add limit-at=(($G711 * $VoIPChanels)."k") max-limit=([:pick $Max ($i-1)]."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q1") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q1") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=1
				add limit-at=(([:pick $Max ($i-1)] * 20 / 100)."k") max-limit=(([:pick $Max ($i-1)] - $R)."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q2") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q2") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=2 queue=pcq-download-default
				add limit-at=100k max-limit=1M name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q3") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q3") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=2 queue=default
				add limit-at=(([:pick $Max ($i-1)] * 30 / 100)."k") max-limit=([:pick $Max ($i-1)]."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q4") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q4") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=3 queue=pcq-download-default
				add limit-at=(([:pick $Max ($i-1)] * 15 / 100)."k") max-limit=([:pick $Max ($i-1)]."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q5") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q5") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=4 queue=pcq-download-default
				add limit-at=(([:pick $Max ($i-1)] * 10 / 100)."k") max-limit=([:pick $Max ($i-1)]."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q6") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q6") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=4 queue=pcq-download-default
				add limit-at=(([:pick $Max ($i-1)] * 15 / 100)."k") max-limit=(([:pick $Max ($i-1)] - $R)."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q7") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q7") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=4 queue=pcq-download-default
				:local LA ([:pick $Max ($i-1)] - ([:pick $Max ($i-1)] * 90 / 100 + $G711 * $VoIPChanels + 100))
				:if ( $LA < ([:pick $Max ($i-1)] /10) ) do={
					:set LA ([:pick $Max ($i-1)] /10)
				}
				add limit-at=($LA."k") max-limit=(([:pick $Max ($i-1)] - $R)."k") name=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q8") packet-mark=("ISP".$i."-".[:pick $d 0].[:pick $t 0]."Q8") parent=("ISP".$i."-".[:pick $d 0].[:pick $t 0]) priority=4 queue=pcq-download-default
			:local prio ($prio + 1)
			}
		}
	}
}


Обратите внимание, типы пользователей указанны в обратном порядке, точнее, в порядке приоритета. Задаем число VoIP каналов, и максимальную скорость провайдеров (подразумевая симметричную скорость). Параметр Reserv задает сколько зарезервировать под «колебания и вылеты» скорости за приделы заданных значений (для маленьких каналов в процентах, для больших в 500Кбит\с). genOnly задает номер провайдера, очереди которого надо перегенерить (-1 для всех, отсчет с 0).

Заключение


Немного о производительности: Классификация как таковая не требует много ресурсов, так-как затрагивает в основном только новые пакеты, которых обычно мало. Однако, перемаркировка DSCP меток пакета, установка обычных и маршрутных меток пакета производится с каждым пакетом. Использование костылинга вместо нормальных битовых операций совсем не улучшает производительность. Там где можно вытащить класс трафика или интерфейс, через который он пойдет, буквально одним правилом, приходится делать монстра из десятков правил! Как бы то ни было, RB951G способен прожевать около 52 Мбит\с в одну сторону с установкой DSCP. RB1100×4 не напрягается более чем на 8%, при трафике в 60 Мбит\с (локалку я у себя не классифицирую).

Важно, трафик который необходимо ставить в очереди или на маршрут, нельзя гонять через fasttrack (точнее можно, только результат вам не понравится).

© Habrahabr.ru