Transmisja w sieci - pomiar, sterowanie i obciążenie łącza.
Artykuł ten skierowany jest do wszystkich, którzy szukają rozwiązań związanych ze sterowaniem prędkością transmisji w sieci oraz z pomiarem ilości przesłanych danych (transmisją) oraz obciążeniem łącza w określonym czasie (trafikiem).
Jest bardzo wiele rozwiązań tych zagadnień. Większość jednak tych zaliczanych do bardzo dobrych opartych jest o SNMP. Co jednak kiedy w sieci mamy dostęp tylko do części urządzeń albo – ograniczę mój przykład - tylko do routera. Urządzenia w sieci LAN nie dla nas dostępne, albo nie możemy ich konfigurować. Każdy doświadczony admin wie, że w firmach zawsze coś się trafi, jakieś przedpotopowe „COŚ” do którego nie ma dokumentacji albo narzucone z centrali CISCO do którego nie ma uprawnień i żeby nie wiem jak się wytężał i choćby nie wiem ile zjadł kotletów to i tak nie dostanie „bo nie”.
Powstaje zatem luka na rozwiązanie proste, może i „nieprofesjonalne” ale za to możliwe do skonfigurowania przez nawet początkującego informatyka i nie wymagające bezkresu wiedzy. Tak – do skonfigurowania „na początek”, „na szybko”.
Poniższy artykuł jest właśnie propozycją wypełnienia tejże.
UWAGA. Opiszę TYLKO przypadek routera !
Jeżeli szukasz narzędzi pomiarów i kształtowania ruchu na danej maszynie to raczej zainteresuj się protokołem SNMP oraz narzędziami typu MRTG i wieloma, wieloma innymi gotowymi rozwiązaniami.
W tym artykule będę się posługiwał wytrychami myślowymi i słownictwem zbliżonym do tego jakiego używają szukający. Mądre słowa są dla doświadczonych, proste dla początkujących.
Schemat:

W celu uproszczenia opisu schemat sieci jest banalny ale jeżeli nie umiesz go skalować to potrzebna ci jest wiedza o wiele bardziej podstawowa.
1) Podział łącza
W zarządzanej sieci jedną z najważniejszych spraw jest tzw. „podział łącza”.
Uwaga ! Przedstawiony przeze mnie podział łącza oparty o HTB pozwala zarządzać tylko strumieniem danych wypływających z sieci. Jeżeli chcesz kształtować ruch wpływający do sieci musisz zainteresować się IMQ czego ja opisywał w tym artykule nie będę. Tak naprawdę napisze to co można przeczytać tutaj http://lukasz.bromirski.net/docs/translations/lartc-pl.html ale może w bardziej skondensowany sposób.
Co to znaczy ruch wychodzący/wypływający z sieci (w odróżnieniu od ruchu wchodzącego/wpływającego). Chodzi o to, że pakiety wysyłane przez komputery w sieci wewnętrznej są kierowane do odpowiednich klas. Te klasy są zdefiniowane dla każdego komputer w sieci wewnętrznej oddzielnie, zatem musi być ich tyle ile komputerów. Spróbuj sobie to wyobrazić. Kabel sieciowy to taka wiązka (widok przekroju poprzecznego na rysunku poniżej) a strumień danych z określonego komputera płynie określonym włóknem w tej wiązce. Dla jednego komputera to włókno może być o większej średnicy, dla innego o mniejszej przez co jeden może przesyłać dane szybciej, drugi wolniej. Ważne jest to, żeby pamiętać, że w jeżeli skonfigurujemy nasz podział tak, że dany komputer korzysta tylko z jednego włókna to jego ruch będzie musiał się zmieścić w tym włóknie. Do automatycznego podziału tegoż służy polecenie, które podam na końcu. Ruch, któremu nie nadano żadnych parametrów przepływa pozostałą przepustowością łącza.

A więc przykładowy kod:
tc qdisc del root dev eth0
tc qdisc add dev eth0 root handle 1:0 htb default 10
tc class add dev eth0 parent 1:0 classid 1:1 htb rate 1000kbit ceil 1000kbit
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 1000kbit ceil 1000kbittc class add dev eth0 parent 1:1 classid 1:10 htb rate 1000kbit ceil 1000kbit
tc class add dev eth0 parent 1:1 classid 1:11 htb rate 128kbit ceil 128kbit
tc class add dev eth0 parent 1:1 classid 1:12 htb rate 512kbit ceil 1000kbittc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 10 fw classid 1:10
tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 12 fw classid 1:11
tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 13 fw classid 1:12tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
tc qdisc add dev eth0 parent 1:11 handle 11: sfq perturb 10
tc qdisc add dev eth0 parent 1:12 handle 12: sfq perturb 10
tc qdisc add dev eth0 parent 1:13 handle 13: sfq perturb 10
Wyjaśnienie:
1)
tc qdisc add dev eth0 root handle 1:0 htb default 10
To default właśnie znaczy, że wszystkie pakiety które nie są skierowane idą włóknem w tym przypadku o szerokości równej całemu łączu (1:10)
2)
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 1000kbit ceil 1000kbit
Tu są zdefiniowane 3 klasy dla 3 komputerów. Pierwsza mówi tyle, że „przydziel jeden mega a w chwili mniejszego ruchu na sieci jeden mega” – co w przypadku określania klasy wysycającej całe łącze jest dość oczywiste :P. Druga mówi „przydziel 128 kilo i nawet jeżeli będzie chwila mniejszego ruchu na sieci to zostań przy 128 kilo – nie przyznawaj więcej nawet jeżeli możesz”. Trzecia - „przydziel 512 kilo, a jak będzie wolne łącze to zezwól na transmisję nawet do 1 mega”.
3)
tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 10 fw classid 1:10
To jest bardzo ważne: „handle 10” wynika z konfiguracji IPTABLES. Ta wartość jest dowolnie przez Ciebie zdefiniowana. Kolejne wartości powoduję skierowanie strumienia danych oznaczonych tą wartością do odpowiedniej kolejki.
4)
tc qdisc add dev eth0 parent 1:10 handle 10: sfq perturb 10
A to właśnie wspomniane wcześniej automatyczne dzielenie przepustowości określonego włókna między strumienie wysyłane przez komputer jeżeli wszystkie jego transmisje są skierowane do jednej kolejki. Jeżeli skonfigurujesz tak podział łącza, że każda usługa (najczęściej charakteryzowana przez właściwy dla niej port) jest zaznaczona przez IPTABLES oddzielenie i kierowana jest do oddzielnej kolejki to OK. Proces takiego powiedzmy „load-balancingu” wewnątrz włókna nie będzie się odbywał.
Jeszcze raz !
Kolejki są oddzielne dla każdego z komputerów, jeżeli wyślesz ruch z kilku komputerów do jednej kolejki to SFQ podzieli przepustowość tej kolejki między komputery w stosunku 1/ilość komputerów wysyłających dane w danym momencie.
2) Znakowanie PAKIETÓW
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.2 -j MARK –set-mark 10
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.2 -j RETURN
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.3 -j MARK –set-mark 12
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.3 -j RETURN
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.4 -j MARK –set-mark 13
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.4 -j RETURN
Gdzieś przed routowaniem (linia poniżej) wpisz to co powyżej. Oczywiście dla większych sieci (jeżeli używasz skryptu do ustawiania IPTABLES) warto zamknąć te linie w pętlę.
iptables -t nat -A PREROUTING …. -j DNAT –to 192.168.1.2
Gdzieś przez globalnym FORWARD należy dodać poniższe linie (również ew. generowane z pętli)
iptables -X licznik10
iptables -N licznik10
iptables -A licznik10 -j RETURN
iptables -A FORWARD -i eth1 -s 192.168.1.2 -j licznik10iptables -X licznik11
iptables -N licznik11
iptables -A licznik11 -j RETURN
iptables -A FORWARD -i eth1 -s 192.168.1.3 -j licznik11iptables -X licznik12
iptables -N licznik12
iptables -A licznik12 -j RETURN
iptables -A FORWARD -i eth1 -s 192.168.1.4 -j licznik12
Jak to działa. UWAGA BARDZO WAŻNE !!!
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.2 -j MARK –set-mark 10
iptables -t mangle -A PREROUTING -p tcp -s 192.168.1.2 -j RETURN
Te dwie linijki opisują pakiet kierowany z komputera 192.168.1.2 wartością „10”. Tak po prostu – nie ma tu żadnej magii. Opisane w punkcie 1.3) ustawienia „czytają” tę wartość i na jej podstawie podejmują decyzję w które włókno wpuścić pakiet. „tc filter add dev eth0 parent 1:0 protocol ip prio 1 handle 10 fw classid 1:10” a dokładnie „handle 10” i „–set-mark 10” to właśnie kluczowa dla konfiguracji para. Oczywiście Ty możesz zmienić zasady oznaczania pakietów. Np. poprzez port. (ja mam tak że osobno oznaczam i kolejkuję 3 porty dla każdego urządzenia w sieci. Mam ich 50 więc łącznie mam 150 definicji kolejek (!))
Na pewno ciekawi cię co to jest to:
iptables -X licznik10
iptables -N licznik10
iptables -A licznik10 -j RETURN
iptables -A FORWARD -i eth1 -s 192.168.1.2 -j licznik10
Otóż to jest „miękkie” przejście do następnego tematu:
3) Zliczanie ruchu.
Tu zastosowałem bardzo prostą metodę. Otóż wśród dziesiątek wyrafinowanych narzędzi do mierzenia statystyk ruchu w sieci najprostszym jest samo IPTABLES. Tak. IPTABLES umie zliczać ruch tylko, że prezentacja tych wartości jest dość utrudniona i tak naprawdę to raczej to jest problemem a nie samo liczenie.
Powyższe 4 linijki kodu z IPTABLES, mówią tyle, że w czasie wykonywania FORWARD przy spełnieniu odpowiednich warunków (IP, adapter – chodzi o to by liczyć ruch raz, a nie dwa razy (np. w tę i z powrotem)) wskocz na chwilę do łańcucha licznikXX i wyskocz z powrotem do FORWARDU. Po co ? A no po to, żeby IPTABELS zapisało w licznikXX przepływające dane spełniające określone kryteria.
Sprawdź sam:
# iptables -nvxL licznik10
Chain licznik10 (1 references)
pkts bytes target prot opt in out source destination
179389 15116466 RETURN 0 — * * 0.0.0.0/0 0.0.0.0/0
Pamiętaj, żeby w IPTABLES włączać i czyścić: iptables -t mangle -F
4)Prezentacja
Właściwie same „niskopoziomowe” działania mamy skończone. Teraz spróbujmy to jakoś zaprezentować.
Ja używam 2 plików i bazy danych.
1) Bash:
#!/bin/bash # # Autor: Marek Wysmułek # marek.wysmulek@altvision.pl # # # ta część sprawdza czy upłynęło 60 sekund od ostatniego restartu. Chodzi o to, by nie zacząć liczyć w interwale którszym niż minuta. # s=`date -d “`echo “select data from statystyki order by id DESC limit 1″ mysql -s -u mysql -D minuty`” +%s` while [ $(($s+60)) -gt `date “+%s”` ] do sleep 1 done # # To jest pętla główna # # To jest pętla nieskończona po to, by można było skrypt zawiesić w pamięci. # while [ 1 ]; do r=0; # Pobieram aktualną datę w sekundach s=`date “+%s”`; # Rozpoczynam pętlę, dla kazdego z komputerów for a in `seq 0 2`; do # Pobieram wartość transferu t=`iptables –line-numbers -nxvL licznik$a | grep ^1 | awk ‘{print $3}’`; if [ “$i” ]; then # Tu wyliczam trafik dzieląc różnicę bieżącego transferu i poprzednio odczytanego transferu przez ilość sekund razy 8 m=$((8*($t-${k[$a]})/($s-$i))); else # Po restarcie IPTABLES czyści wartości transferu więc jeżeli pierwszy raz wykonuję pętlę, # to żeby uniknąć dzielenie przez zero zakładam trafik „0” m=0; fi; # zapisuję do bazy wartości dla każdego komputera oddzielnie echo “insert into statystyki(numer,trafik,data) values(’$(($a+1))’,'$m’,DATE_FORMAT(now(),’%Y-%m-%d %H:%i:00′));” | mysql -u mysql -D minuty; r=$(($r+$m)); k[$a]=$t; done; # zapisuję do bazy wartości sumaryczne echo “insert into suma(trafik,data) values(’$r’,DATE_FORMAT(now(),’%Y-%m-%d %H:%i:00′));” | mysql -u mysq -D minuty; i=$s; # czekam minutę sleep 60; done;
Nazwij ten plik jak chcesz np. statystyki, nadaj chmod +x statystyki i odpal ./statystyki &.
2) Baza danych
Oczywiście musisz mieć wcześniej stworzoną bazę danych. Ja korzystam z MySQLa np.:
#CREATE DATABASE minuty;
#CREATE TABLE `statystyki` (
`id` int(11) NOT NULL auto_increment,
`numer` int(11) NOT NULL,
`trafik` int(11) NOT NULL,
`data` datetime NOT NULL,
PRIMARY KEY (`id`));# CREATE TABLE `suma` (
`id` int(11) NOT NULL auto_increment,
`trafik` int(11) NOT NULL,
`data` datetime NOT NULL,
PRIMARY KEY (`id`));
3)PHP
Na serwerze tworzę odpowiedni plik np. minuty.php
<!– Autor : Marek Wysmułek Marek.wysmulek@altvision.pl –> <html> <head> <!–Odświerzam stroną co minutę automatycznie –> <meta HTTP-EQUIV=”Refresh” CONTENT=”60″> <meta http-equiv=”Content-Type” content=”text/html; charset=iso-8859-2″> </head> <body> <? // // Korzystam ze znakomitej i bardzo prostej w konfiguracji klasy “phplot” http://sourceforge.net/projects/phplot/ // leciutko przeze mnie przerobionej tak, aby ilość punktów na osi nie musiał być jednakowy z ilością wartości // // W liniii 79: var $wartosci_x = array(); // W linii 2645: $ylab = $this->FormatLabel(’y', number_format(round($y_tmp), 0, ‘, ‘, ‘ ‘)); /* W linii 2711 do 2717 if (count($this->wartosci_x)==0) $xlab = $this->FormatLabel(’x', $x_tmp); else { $xlab = $this->FormatLabel(’x', array_pop($this->wartosci_x)); } */ include(’phplot/phplot.php’); // // oraz swojej wlasnej uproszczonej do wymiany kodu miedzy MySQl?em a PostgreSQL?em // include(’klasaDB.php'); $baza = new BazaDanych; $baza->typ="mysql"; $baza->host="localhost"; $baza->dbname="minuty"; $baza->user="mysql"; $baza->password=""; $baza->polaczenie(); if (!$baza->db) echo "Blad 01". $baza->blad(); $data=getdate(); // // Tu definiuję okres za jaki odczytuję dane z bazy danych. Z racji przesunięć i różnic czasu w zegarach systemowych i innych dziwnych wartości // empirycznie stwierdziłem, że okres rzeba powiększyć o kwant okresu (w tym przypadku jedna minutę) // $okres=61; $teraz=date('Y-m-d H:i', mktime($data['hours'], $data['minutes']-$okres, 0, $data['mon'], $data['mday'], $data['year'])); echo "<b>Godzina ".date('Y-m-d H:i')."</b><br /><br />"; // // UWAGA "0" to jest wykres ogólny! // for($i=0;$i<=3;$i++) { if($i==0) $sql = "select trafik, data from suma where data >= '$teraz' order by data"; else $sql = "select trafik, data from statystyki where data >= '$teraz' and numer=$i order by data"; $result = $baza->zapytanie($sql); if (!$result) { echo "Blad 02: $sql: " . $baza->blad(); } $dane = array(); $wartosci_x = array(); while($identyfikator=$baza->wiersz($result)) { array_push($dane,array($identyfikator[1],$identyfikator[0]); $godzina = date('i', strtotime($identyfikator[1])); array_push($wartosci_x, $godzina); }; // Ustaw takie wymiary jakie Ci odpowiadają. $graph = new PHPlot(1000,150); $graph->SetDataType("text-data"); //Must be called before SetDataValues $graph->SetDataValues($dane); $graph->wartosci_x = array_reverse($wartosci_x); $ilosc_x = count($wartosci_x)-1; $graph->SetNumXticks($ilosc_x); $graph->SetXTickLabelPos("plotdown"); $graph->SetXDataLabelPos("none"); $graph->SetXLabelAngle(90); $graph->SetPlotType("lines"); $graph->SetLineWidth(3); $graph->SetYTickLabelPos("both"); $graph->SetMarginsPixels(100,100,0,30); $graph->file_format="png"; $graph->is_inline=true; // Usta taki rodzaj grafiki jaki jest obsługiwany przez Twojego Apache?a $graph->output_file="minuty".$i.".png"; if($i==0) { echo "Trafik sumaryczny "; $graph->SetDataColors("green"); } else echo "Box $i "; $graph->DrawGraph(); echo "<img style='vertical-align: middle' src='minuty$i.png' /><br /><br />n"; } $baza->zakoncz(); ?> </body> </html>
(pobierz plik klasaDB.php)
i dostaję coś takiego:

5) Na koniec
Oczywiście musisz pamiętać, żeby wszystkie skrypty się włączały (np. poprzez krypt IPTABLES) i o wszystkich innych szczegółach, które są zbyt trywialne, żeby o nich pisać.
Zapraszam od dyskusji wszystkich, którzy chcieliby ew. poprawić zamieszczone materiały, napisać wydajniejsze skrypty.
Proszę również wszystkich „Newbies” o komentarze, czy to im działa i czy zrozumieli to co napisałem.
Chciałbym, aby ten artykuł był pomocny wielu tym, którzy – tak jak ja – potrzebowali „czegoś prostego na szybko”.
Te moje rozwiązania to tylko początek ! Nie traktuj ich jak docelowe, raczej jak przystanek do poznania głębiej poruszonego zagadnienia.
Pozdrawiam Marek Wysmułek.
AltVision