Fragmentacja pakietu i zmiana wartości MTU

Spis treści

MTU (Maximum Transmission Unit) to maksymalna długość pakietu jaki może zostać przesłany przez sieć. Może ona wynosić do 64KiB ale większość punktów sieciowych, takich jak routery, wymusza o wiele mniejsze rozmiary pakietów. Domyślna wartość MTU dla protokołu Ethernet to 1500 bajtów, oczywiście bez nagłówka warstwy fizycznej, który ma dodatkowe 14 bajtów. Czasami te standardowe ustawienia mogą powodować problemy w przypadku pewnych konfiguracji sieci i gdy ich doświadczamy, przydałoby się zmienić rozmiar MTU przesyłanych pakietów.

Fragmentacja pakietów

Maksymalny rozmiar segmentu TCP (MSS) określa jak dużo danych określony host jest w stanie zaakceptować w pojedynczym datagramie IP. Generalnie rzecz biorąc, host A porównuje wartość MTU (-40 bajtów na nagłówki) swojego interfejsu wyjściowego z rozmiarem bufora odbiorczego i wybiera te wartość, która jest niższa. Następnie tak określony MSS jest transmitowany w pakiecie SYN podczas fazy three-way handshake. Host B porównuje otrzymany MSS z wartością jaką ma w MTU na swoim interfejsie wyjściowym (-40 bajtów na nagłówki) i wybiera niższą wartość oraz ustawia ją jako MSS dla pakietów przesyłanych do hosta A. I podobnie w drugą stronę, czyli host B porównuje MTU na swoim interfejsie wyjściowym (-40 bajtów na nagłówki) z rozmiarem bufora odbiorczego i wybiera najniższą wartość, po czym transmituje ją w pakiecie SYN-ACK na drugą stronę. Host A odbiera ją i porównuje z wartością MTU swojego interfejsu wyjściowego (-40 bajtów na nagłówki). Wybiera niższą z nich i ustawia jako MSS dla pakietów przesyłanych do hosta B.

Teoretycznie cały proces wygląda w porządku ale tylko w przypadku połączenia tych dwóch maszyn bezpośrednio. Jeśli na drodze znajdzie się jakiś router, wtedy może wystąpić fragmentacja pakietu IP, bo router nie jest świadom tej powyższej negocjacji i ustalonych wartości. Jeśli obie ze stron wynegocjują sobie wartość MTU, która jest większa niż ta ustawiona na pośredniczącym routerze, mamy pofragmentowane pakiety. A to z tego powodu, że router nie jest w stanie przetworzyć danych o segmencie większym niż ma to zdefiniowane w swoich opcjach. Dlatego też taki pakiet musi zostać podzielony na kilka mniejszych. Po dotarciu do odbiorcy, te fragmenty są składane w całość w oparciu o numer identyfikacyjny pakietu, flagę More fragments oraz offset danych przed przesłaniem go do wyższej warstwy. Wszystkie te informacje są podane w nagłówku IP:

mtu-fragmentacja-pakietow

Fragmentacja pakietów pociąga za sobą kilka nieprzyjemnych następstw. Przede wszystkim, zwiększone zapotrzebowanie na procesor i pamięć operacyjną, bo przecie trzeba te pakiety podzielić przed wysłaniem, a potem je jeszcze złożyć na drugim końcu połączenia. Nie jest to znowu jakiś wielki koszt. Głównym problemem jest forwardowanie pakietów, np. przez routery, które powinny przez ten proces przechodzić jak najszybciej, a nie dodatkowo zawracać sobie głowę fragmentacją. Kolejny problem widoczny jest przy retransmisji. Jeśli taki pofragmentowany kawałek ulegnie zgubieniu, trzeba będzie przesłać wszystkie części jeszcze raz. Weźmy na przykład zasoby NFS. Jeśli ten system plików w naszym przypadku ma rozmiar bloku 8192 bajtów, to po uwzględnieniu wszystkich nagłówków (NFS, UDP, IP), taki datagram będzie ważył jakieś 8500 bajtów. Zatem trzeba będzie ten pakiet podzielić na 6 kawałków (5x1500+1100). Jeśli któryś z nich zginie, np. z powodu zbyt dużego tłoku na kablach, całe 8500 bajtów będzie trzeba zretransmitować, przy czym znów je będzie trzeba podzielić przed wysłaniem. Do tego dochodzi jeszcze problem z bezpieczeństwem, gdzie przy pomocy fragmentacji można przepisywać pola nagłówka IP.

Wyłączenie fragmentacji

Mając na uwadze wszystko powyższe, zaleca się ustawienie bitu Don't fragment w nagłówku IP, który nie pozwoli na fragmentację pakietów. Możemy to zrobić za pomocą pliku /etc/sysctl.conf przez dopisanie do niego tej poniższej linijki:

net.ipv4.ip_no_pmtu_disc = 0

Path MTU Discovery decyduje o tym, czy połączenie TCP ma korzystać ze stałej, maksymalnej wartości MTU, czy też ma próbować ją sobie samodzielnie ustalić. W tym celu stos IP ustawia w każdym wychodzącym pakiecie bit Don't fragment . Gdy nadejdzie odpowiedni komunikat statusu ICMP, wartość MTU jest zmieniana zgodnie z wiadomością.

Działanie tego mechanizmu może zaobserwować posługując się narzędziem tracepath :

# tracepath -n wp.pl
 1?: [LOCALHOST]                                         pmtu 1500
 1:  192.168.1.1                                           1.669ms
 1:  192.168.1.1                                           1.600ms
 2:  192.168.22.51                                        20.157ms asymm  3
 3:  192.168.22.51                                        10.155ms
 4:  213.199.236.17                                       31.509ms
 5:  88.199.219.118                                       16.038ms asymm  8
 6:  153.19.0.154                                         24.546ms asymm  8
 7:  153.19.102.6                                         20.238ms asymm  8
 8:  212.77.96.69                                         22.116ms
 9:  212.77.100.101                                       18.338ms reached
     Resume: pmtu 1500 hops 9 back 9

Badanie MTU może się nie sprawdzać w przypadku źle skonfigurowanych firewalli, które odrzucają wszelkie pakiety ICMP lub też w przypadku źle skonfigurowanych interfejsów, np. gdy oba końce połączenia point-to-point nie zgadzają się na MTU.

Zmiana wartości MTU

Aktualną wartość MTU możemy odczytać w statystykach każdego interfejsu sieciowego. W poniższym przykładzie została ona odczytana za pomocą narzędzia ip dostępnego w pakiecie iproute2 ale można także posłużyć się narzędziem ifconfig :

# ip link show dev bond0
7: bond0:  mtu 1500 qdisc noqueue state UP mode DEFAULT group default
    link/ether 3c:4a:92:00:4c:5b brd ff:ff:ff:ff:ff:ff

Jeśli mamy problem z nawiązaniem połączenia z internetem, musimy pokombinować z wartością MTU, która jest widoczna wyżej. Choć zwykle domyślna wartość powinna działać bez zarzutu. Odpalmy zatem jakiś sniffer sieciowy (np. wireshark) i wydajmy w terminalu to poniższe polecenie:

# ping -c 1 -s 1472 onet.pl
PING onet.pl (213.180.141.140) 1472(1500) bytes of data.
1480 bytes from sg1.any.onet.pl (213.180.141.140): icmp_seq=1 ttl=57 time=21.7 ms

--- onet.pl ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 21.729/21.729/21.729/0.000 ms

Jeśli w snifferze widzimy, że pakiety ulegają fragmentacji, tak jak to widoczne jest na poniższym obrazku:

mtu-fragmentacja-pakietow-wireshark

Oznacza to, że MTU o wartości 1500 jest nieodpowiedni. Co prawda, wydaliśmy polecenie z parametrem -s 1472 ale do tego dochodzi jeszcze nagłówek UDP ( 8 bajtów), no i oczywiście nagłówki IP (kolejne 20 bajtów) i nagłówek ethernetowy ( 14 bajtów). Łącznie daje nam to 1514 bajtów, z tym, że w MTU nie wliczany jest nagłówek warstwy fizycznej, dlatego MTU wynosi 1500 bajtów.

W przypadku gdy już dobraliśmy optymalną wartość MTU, trzeba ją jakoś ustawić w systemie. W przypadku korzystania z initu systemd, trzeba stworzyć plik /etc/systemd/network/05-bond0.link , np. o poniższej treści:

[Match]
Driver=bonding

[Link]
Description=MTU for bond interface
MTUBytes=1440
BitsPerSecond=100M

Oczywiście dopasowań może być wiele, ja wykorzystuje do tego sterowniki kart sieciowych/wirtualnych interfejsów. W przypadku gdy chcemy ustalić jaki sterownik jest od naszej karty, wtedy korzystamy z ethtool :

# ethtool -i eth1
driver: r8169
...

I to ten sterownik trzeba podać w pliku .link . Po zresetowaniu maszyny, MTU powinno zostać prawidłowo ustawione.

# ip link show bond0
7: bond0:  mtu 1440 qdisc noqueue state UP mode DEFAULT group default
    link/ether 3c:4a:92:00:4c:5b brd ff:ff:ff:ff:ff:ff

W przypadku korzystania z pliku /etc/network/interfaces , wystarczy dodać poniższą linijkę do bloku konfiguracyjnego interfejsu:

auto bond0
iface bond0 inet dhcp
    ...
    mtu 1440
    ...
Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.