Ve zdrojovém balíku dhcp se nachází kód pro DHCP klienta (dhclient) a DHCP server (dhcpd). Ve společné části kódu je chyba v plánovači akcí, která na 64bitových systémech způsobuje, že dhclient za určitých podmínek zbytečně selže.

DHCP server posílá v nabídce konfigurace (DHCPOFFER) čas, po který bude přidělená adresa platná. Tento čas může doplnit o další dvojici časových intervalů, které určují načasování dalších fází DHCP, kdy se nejdříve klient snaží kontaktovat stejný DHCP server (renewal-time) a později libovolný DHCP server (rebinding-time).

Z RFC 2131 a RFC 2132 vyplývá, že časy musejí být v následujícím vztahu, aby vůbec bylo možné projít všemi fázemi:

renewal-time < rebinding-time < lease-time

Navíc RFC 2131 připouští speciální hodnotu lease-time nastavenou na 0xffffffff, která znamená nekonečnou platnost konfigurace. Striktně vzato by pak renewal-time a rebinding-time nekonečnou hodnotu mít neměly. Na druhou stranu, výchozí hodnoty obou z nich jsou odvozené od lease-time přenásobením po řadě koeficienty 0.5 a 0.875. Z matematického pohledu je tedy jejich výchozí hodnotou nekonečno a nerovnost neplatí.

Připojení na konferenci GUADEC

Návštěvníci konference měli k dispozici bezdrátové připojení, kde DHCP server posílal všechny tři hodnoty nastavené na 0xffffffff. To je sice striktně z hlediska RFC špatně, nicméně zmatek pochází právě ze samotného RFC. V tomto případě by bylo dobré, kdyby klient ze situace nějakým způsobem vybruslil a ponechal jen varování v logu. Nejlogičtější mi přijde přijmout fakt, že lease-time nastavená na 0xffffffff znamená nekonečnou hodnotu bez potřeby obnovy a renewal-time i rebinding-time prostě ignorovat.

Program dhclient správně nakonfiguruje adresu i DNS, ale plánování akcí skončí chybou. NetworkManager se o chybě dozví prostřednictvím návratové hodnoty a spojení ukončí. Podle informací od Dana Williamse se chyba týká pouze 64bitových architektur.

Kromě dumpu komunikace sítě to bylo dobře vidět na souboru výpůjček. Povšimněte si hlavně posledních tří řádků s hodnotami renew, rebind a expire:

lease {
  interface "wlan0";
  fixed-address 172.20.9.194;
  option subnet-mask 255.255.0.0;
  option routers 172.20.0.1;
  option dhcp-lease-time 4294967295;
  option dhcp-message-type 5;
  option domain-name-servers 8.8.8.8,213.60.205.173;
  option dhcp-server-identifier 172.20.0.3;
  option dhcp-renewal-time 4294967295;
  option dhcp-rebinding-time 4294967295;
  renew 6 2148/08/31 04:35:25;
  rebind 6 2148/08/31 04:35:25;
  expire 6 2148/08/31 04:35:25;
}

Hodnoty odpovídají aktuálnímu času zvýšenému o 0xffffffff. To si můžete ověřit například v interaktivním režimu Pythonu:

>>> import datetime
>>> datetime.datetime.now() + datetime.timedelta(0, 0xffffffff)
datetime.datetime(2148, 9, 12, 22, 25, 27, 539214)

Pád DHCP serveru s lease-time 0xffffffff

Protože jsem se o chybě dozvěděl ten den, kdy byla nahlášená na NetworkManager, přirozeně jsem ji chtěl reprodukovat. Nastavil jsem tedy DHCP server ze stejného zdrojového balíku, aby posílal lease-time 0xffffffff:

default-lease-time infinite;
max-lease-time infinite;

A protože server používá stejný plánovač jako klient, tak skončil s chybou a nemohl jsem ho použít k reprodukování chyby v klientu.

Konfigurace renewal-time a rebinding-time

Napsal jsem si primitivní patch, aby mi DHCP server nepadal. Ale ani tak se mi nedařilo problém reprodukovat. Následující výpis výpůjčky se liší od výpisu z konference v řádkcích renew a rebind, kde jsou zřetelně vidět nižší hodnoty roků než v řádku expire:

lease {
  interface "eth1";
  fixed-address 192.168.25.12;
  option subnet-mask 255.255.255.0;
  option routers 192.168.25.1;
  option dhcp-lease-time 4294967295;
  option dhcp-message-type 5;
  option domain-name-servers 8.8.8.8;
  option dhcp-server-identifier 192.168.25.1;
  option domain-name "example.org";
  renew 2 2080/08/13 00:57:12;
  rebind 2 2131/08/28 09:22:41;
  expire 0 2148/09/01 04:11:19;
}

Bylo tedy zřejmé, že je potřeba, aby server posílal volbu renewal-time. Nastavil jsem tedy obě další volby:

default-lease-time infinite;
max-lease-time infinite;
option dhcp-renewal-time 0xffffffff;
option dhcp-rebinding-time 0xffffffff;

Ačkoliv jsem vše pečlivě nastavil, DHCP server klientu dvě nové volby vůbec neposílal, aniž by se v logu objevilo hlášení chyby. Jiří Popelka, kterému také vděčím za četné připomínky k tomuto článku, mě upozornil, že server tiše kontroluje již dříve uvedený vztah mezi těmito hodnotami, ačkoli z manuálové stránky dhcp-options(5) vyplývá vztah s neostrými nerovnostmi:

renewal-time <= rebinding-time <= lease-time

Ukázalo se, že chyba je v manuálové stránce a oprava se má dostat do ISC DHCP verze 4.2.5. Poslední test tedy probíhal s mírně upravenou konfigurací:

default-lease-time infinite;
max-lease-time infinite;
option dhcp-renewal-time 0xfffffffe;
option dhcp-rebinding-time 0xfffffffd;

Reprodukováno, opraveno

První patche se objevily už 25. července večer a v testovacím repozitáři se objevil update 27. července. Takže když se mi konečně povedlo 30. července chybu reprodukovat, měl jsem možnost hned otestovat opravu a přidat karmu, aby se update dříve dostal do stabilního repozitáře, což se stalo 1. srpna. Byla použita oprava od Dana Williamse, kterou začlenil Jiří Popelka.

Fedora v tuto chvíli udržuje skoro čtyři desítky patchů zdrojového balíku dhcp. U devatenácti z nich je ve spec souboru označeno, že byly nahlášeny upstreamu a dostaly přidělené číslo chyby. Nicméně ISC bohužel nemá nic jako bugzillu, kde by bylo možné hlášení chyb dále sledovat.

Závěrem…

Je pravděpodobné, že se problém s připojením týkal někdy někde i vás. Možná jste to svedli na špatný signál wifi, nebo jste možná jen neměli čas to podrobně zkoumat. Sám mám pár tipů na sítě, kam jsem se záhadně nepřipojil. Jsem docela zvědavý, jestli se po této opravě připojím, nebo ne.

Vzhledem k problémům s upstreamováním oprav může být tato informace užitečná i pro uživatele dalších distribucí, které takovýto patch ještě nezačlenily. Standardy týkající se DHCP s sebou nesou i další problémy, z nichž některé můžou mít na svědomí zbytečně pomalé připojování k sítím, zvláště k těm, které nedoručují spolehlivě multicast, což je možná většina wifi sítí.

Kdokoli z vás cítí podobnou touhu hrát si se sítěmi jako já, nebojte se hlásit chyby, posílat patche a nebo se účastnit prostým dokumentováním aktuální situace. Pokud narazíte na nějaké potíže, klidně mi napište nebo mě přidejte do Cc u vašeho bugreportu.