onsdag 30 augusti 2017

Hooka in i Netfilter

Detta är en fristående fortsättning på mitt förra inlägg där jag visade hur man skriver en enkel kärnmodul som kan laddas in i en aktiv Linuxkärna. Om du inte redan har koll på hur man kompilerar en modul som kan laddas in i din dators kärna rekommenderar jag att du börjar med mitt förra inlägg innan du tar dig an den här.

Efter du läst den här texten kommer du:

  • Ha bättre förståelse för hur nätverk fungerar i en Linuxmaskin
  • Kunna skriva moduler som hookar in i valfri del av routingprocessen och i och med det få kontroll och fri tillgång till alla datapaket och dess innehåll
Låter detta intressant? Vad bra, då kör vi!

Vi börjar med en kort överblick över Netfilter, som är det ramverk i Linuxkärnan vi kommer använda oss av idag.

IP-paketets fantastiska resa genom Linuxkärnan

Det finns två olika sätt för ett nytt IP-paket att dyka upp i kärnan. Antingen har den skapats på din dator, eller så kommer den in utifrån. Hur paketet hanteras av kärnan skiljer sig något åt mellan de två fallen, så jag kommer gå igenom båda. Jag kommer i tur och ordning presentera de hooks, eller 'krokar', som Netfilter erbjuder moduler att hooka in i. Om ni är vana användare av iptables så kommer ni känna igen er.

Ett paket kommer lastat

Paket som kommer in utifrån tas först emot av ett interface. Detta kan vara av typen Ethernet, Wi-Fi eller valfritt länkprotokoll. Modulen vi skriver lever på IP-lagret, så vi behöver faktiskt inte bry oss om alls hur paketet tog sig till oss (eller vilken väg den tar härifrån).

Det här initiala tillståndet kallas i Linuxvärlden för PREROUTING, det som händer innan kärnan har kollat upp hur paketet ska hanteras. Efter det här steget är det dags för paketet att routas. Detta innebär i stora drag att kärnan tar reda på om

  1. paketet är menat åt den lokala maskinen
  2. eller
  3. paketet är menat för en annan maskin och måste skickas vidare åt rätt håll.
För de flesta hemdatorer är alternativ 1 det klart vanligaste. Talar vi istället om en router så är det nästan uteslutande alternativ 2 som sker.
Nu banar vägen åt olika håll. Vi börjar med att undersöka alternativ 1.

Alternativ 1) Välkommen in

Detta steget kallas kort och gott för INPUT. Paketet packas upp och skickas vidare upp i nätverksstacken tills den förhoppningsvis slutligen når ett väntande program.

Alternativ 2) Du ska ditåt

Ska paketet till en annan host hamnar den istället i FORWARD. Innan den kommer hit har kärnan tagit reda på vilken väg paketet behöver skickas för att komma dit den ska. Detta betyder att kärnan vid det här laget har full koll på:
  • vilket interface paketet ska skickas ut genom.
  • om den behöver adresseras till någon next-hop-router.
Var inte orolig om du inte har koll på vad en 'next-hop' är, den kunskapen är inte viktig för det här blogginlägget.

I övrigt händer det inte så mycket spännande i FORWARD. Det är mest en transportsträcka för att komma fram till den slutgiltiga stationen som är..

POSTROUTING, där paketet skickas nedåt i nätverksstacken och till slut skickas iväg ut på ett interface för att fortsätta sin resa.

Ett nytt paket föds

Paket som skapas lokalt går igenom en liknande routningprocess som den efter PREROUTING, och skickas sedan till OUTPUT. Här passerar all lokalt genererad trafik, och härifrån finns bara en väg att gå, och det är till samma POSTROUTING som jag beskrev ovan.

Hooks i Netfilter

Nu har ni fått en väldigt kortfattad rundtur i Netfilter. Vi kommer nu använda oss av dessa hookar för att själva få total kontroll över hur paket skickas genom vår maskin.

Talk is cheap. Show me the code.

Precis som förra gången börjar vi med att skapa oss en projektmapp.
~$ mkdir myhook
~$ cd myhook
~/myhook$

Ni som läst mitt förra inlägg kommer känna igen sig i följande kodsnutt för myhook.c

Det enda nya här är KERN_INFO. Den säger att de meddelanden vi skriver ut endast är informativa. Vill vi uppmärksamma att något gått fel kan vi istället använda oss av KERN_WARN eller KERN_ERR. Samtliga nivåer finns listade här för den som är nyfiken.

Vårt projekt behöver även en make-fil. Dagens Makefile ser ut så här:

Nästan en exakt kopia av den vi använde senast. Testa gärna att bygga med make och städa upp med make clean för att se att allting fungerar. Återigen, om något känns oklart ber jag dig att läsa mitt förra inlägg innan du fortsätter med den här.

Än så länge har jag inte visat er något nytt, men det ska vi ändra på. Kolla här!

Lite mer kod än vi jobbat med hittills men fortfarande inga konstigheter.

Det första vi ser är my_hook_function som är den funktion som kommer köras för varje paket som färdas ut genom din dator. Parametern skb, som är en förkortning för 'socket buffer', innehåller all information kärnan har om paketet. För den nyfikne finns hela innehållet deklarerat här.

Just nu skriver vår hook bara ut ett spydigt meddelande och returnerar NF_DROP, vilket kommer leda till att kärnan slänger paketet utan att skicka ut den på något interface. Om vi istället vill släppa ett paket vidare returnerar vi NF_ACCEPT.

printk_ratelimit() är en väldigt händig funktion som ser till att vi inte fyller våra loggar för snabbt. Den kommer returnera 1 så länge vi inte försöker skriva fler än tio meddelanden till loggen under en femsekundersperiod.

Vi måste på något sätt berätta för Netfilter att vi finns och hur den kan använda oss. Detta gör vi genom att fylla i en struct av typen nf_hook_ops. Man ger den namnet på sin hook-funktion, vilket protokoll man är intresserad av, vilken kedja man vill hooka in och var i ordningen man vill ligga i den kedjan. Som ni kan se i koden ovan fyller vi in att vår hook-funktion är my_hook_funktion, att vi vill hooka in i kedjan för IPV4 i POST_ROUTING och att vi vill vara den första hooken som körs där.

I vår init-funktion registrerar vi vår hook i Netfilter, och i vår exit-funktion avregistrerar vi oss. Kollar ni i andra moduler i Linux källkod så kommer ni se att de flesta följer samma mall, mer eller mindre. Man registrerar sina tjänster i init-funktionen, och avregistrerar dem i exit.

Testa gärna att bygga modulen med make och ladda in den med insmod. Du bör snabbt märka att ditt internet slutar fungera. Ingen fara! Ladda bara ur din modul med rmmod så bör allt bli som vanligt igen.

Var det allt?

Visst är vår modul häftig, men så länge du inte bara är ute efter att spara på din internettrafik så är den är inte så användbar. Jag tänkte avsluta med att visa er en modul som gör något lite mer spännande. Den kommer undersöka varje paket och kolla om det är en HTTP-request till en viss domän, och i så fall blockera.

Koden utgår från att man har vissa kunskaper om IP, TCP och HTTP, men har ni inte det så borde ni ändå kunna hänga med. Koden ser ut som följer:

Hooken kollar att det är ett TCP-paket på väg mot port 80. Är det det så använder den kärnans färdigbyggda funktion strnstr och letar efter vår utvalda domän i paketets data. Hittar vi den så droppar vi paketet direkt. Effekten blir att personer som använder sig av din dator för att nå internet kommer kunna surfa som vanligt, så länge de inte försöker nå vår utvalda sida.

Nu har vi skrivit en fantastisk modul som FRA säkert gärna skulle lägga sina vantar på. Det är bara en liten sak som stör mig. Om vi vill ändra vilken sida vi blockerar så måste vi ändra i koden och kompilera om modulen, vilket känns som lite för mycket arbete. Lösningen på detta är väldigt enkel och kräver bara en extra rad kod:

Rad 11 gör att vi numera kan välja vilken sida vi vill blockera varje gång vi laddar in vår modul. Exempel:
~$ sudo insmod myhook.ko target=klockren.nu

Hur fantastisk vår modul än är så har den fortfarande vissa begränsningar. Den kan bara blockera en sida åt gången, och den kan inte hantera HTTPS-trafik, vilket utgör den större delen av vårt vardagliga internetsurfande idag. I nästa blogginlägg kommer jag visa hur vi kan göra vår modul mer flexibel genom att blanda in iptables och då kunna blockera ett godtyckligt antal domäner med hjälp av egna matchningar.

"Problemet" (vissa menar att det är hela poängen) med HTTPS är knivigare. Det finns sätt, och jag kommer gå igenom i alla fall en metod jag känner till för att filtrera HTTPS-trafik i framtida blogginlägg.

Vad kul att du hängde med hela vägen! Nu är det bara att fortsätta undersöka och experimentera fritt med din nya kärnmodul. Har du svårt att veta var du ska börja så kan starta med att kolla in hur Linuxkärnan jobbar med IP, TCP och UDP.

söndag 20 augusti 2017

Att skriva en kärnmodul

När jag för cirka två år sedan började använda Linux dagligen var kärnan och kärnprogrammering något magiskt, främmande och läskigt. Två år senare är den fortfarande till stor del främmande, men inte läskig, och jag är numera nästan helt säker på att inget magiskt pågår under huven på min dator.

Min väg in i kärnan startade med Understanding Linux Network Internals av Christian Benvenuti, och det är också nätverksstacken jag jobbar med och har bäst koll på. Jag vet inte huruvida den är en bra introduktionsbok eller inte, men eftersom mitt arbete kretsar kring routrar och annat nätverksrelaterat så kändes den passande. Den har i skrivande stund 13 år på nacken, men den är fortfarande relevant som en introduktion till hur ett nätverkspacket färdas genom ett Linuxsystem.

Linux nätverksstack är ett fascinerande område som jag kommer att skriva mer om i senare inlägg, men just den här blogposten tänkte jag ägna åt att visa er hur enkelt det är att börja skriva kärnkod!

Vad är egentligen en kärna?

...och vad är skillnaden mellan en kärna och ett operativsystem? Om vi jämför med webbutveckling så är operativsystemet datorns 'front-end' och kärnan dess 'back-end'. Kärnan tillhandahåller de mest grundläggande tjänster som krävs för att kunna använda hårdvaran på ett effektivt sätt. Bland dessa tjänster kan man bland annat hitta processhantering, minneshantering, läsning och skrivning till disk och sändning och mottagning av nätverksdata. Ett operativsystem tillhandahåller istället tjänster riktade till användaren av systemet, till exempel grafiska gränssnitt, kommandotolk, ordbehandlingsprogram med mera.
Operativsystem v.s. Kärna

Typ av kärna

För att göra det enkelt för sig så kan man koka ner operativsystemskärnor till två olika varianter. Antingen har man en monolitisk kärna, eller en så kallad microkernel.

En monolitisk kärna är ett enda program där allt som behövs finns med från början. Där finns minneshantering, input/output-hantering, nätverkshantering och så vidare. En microkernel å andra sidan tillhandahåller bara en väldigt liten 'kärna' som i stort sätt endast innehåller funktionalitet för att synkronisera och skicka information mellan andra delar av kärnan som länkas in oberoende av varandra. För er som känner till fenomenet microservices fungerar en microkernel enligt samma princip.
Monolitisk vs Microkernel


Linux är en monolitisk kärna. Har man väl byggt den så finns där allt man behöver. Där finns hela nätverksstacken, processchemaläggning,  hantering av virtuellt minne, filsystem och en massa annat. Men utöver detta erbjuder Linux även så kallade LKMs, eller 'Loadable Kernel Modules'. Dessa är, något förenklat, kod som dynamiskt, alltså samtidigt som kärnar kör, kan laddas in och laddas ur. När den laddas in blir kärnmodulens kod en del av den aktiva kärnan. Från modulens synvinkel är det i stort sätt ingen skillnad mellan att laddas in som modul och att vara inbyggd i kärnan från början.
En modul laddas in i en aktiv kärna

Det där med kärnmodul låter roligt. Hur skriver man en?

Tack för frågan! Det ska bli mig ett nöje att besvara den. För att kunna ta till dig texten är det bra om du har koll på grundläggande C-programmering och någon form av vana vid kommandotolken i ett Linux-system. Jag använder personligen Ubuntu som operativsystem, men det borde inte vara några problem att hänga med så länge man kör valfri Linux-distribution.

För att kunna bygga kärnmoduler behöver du ha din kärnas header-filer, gcc och GNU make installerat. På debian-baserade system (Till exempel Ubuntu) görs detta enklast med kommandot
sudo apt-get update && sudo apt-get install build-essential linux-headers-`uname -r`
Vi börjar med att skapa en mapp för vårt projekt.
~$ mkdir module
~$ cd module
~/module$ 

Så långt allt väl. Nu är det väl dags att skriva lite kod? Jajemän! Öppna din favorit-editor så sätter vi igång.

Så här ser koden i mymodule.c ut:


Inte så farligt va? Man behöver bara inkludera filen <linux/module.h> och definiera två funktioner, init_module och cleanup_module. Den första funktionen körs när modulen laddas in, den andra när modulen tas bort. Vi returnerar 0 i vår init-funktion för att signalera att allt gått bra.

Nu vill vi bygga modulen. Linux använder sig av GNU make, så det gör vi med. Oroa dig inte om du saknar erfarenhet av GNU make, Linux-kärnan har färdiga make-filer för oss att använda. För att bygga en modul som ska kunna laddas in i kärnan din dator använder sig av just nu, använder vi oss av make-filen som ligger i mappen /lib/modules/`uname -r`/build/. På något sätt behöver vi få denna make-fil att hitta mymodule.c och bygga en färdig modul åt oss. För att lyckas med detta behöver den lite hjälp. För att ge kbuild, som Linuxkärnans byggsystem heter, den information den behöver skapar vi en egen enkel make-fil, Makefile, i vår projektkatalog. Just nu behöver den bara innehålla följande:


Nu vet kärnans make-fil om att den ska bygga en modul som heter 'mymodule'. 'm'-et säger att modulen ska byggas separat och inte byggas in i en komplett kärna.

Nu bygger vi! Kör följande kommando i projektkatalogen.
~/module$ make -C /lib/modules/`uname -r`/build M=`pwd` modules
 -C säger åt make att använda sig av make-filen i den efterföljande katalogen. modules innebär att make-filen ska bygga alla moduler den hittar i katalogen M som vi sätter till katalogen vi står i just nu.

Om inget gått allvarligt fel så ska vi nu ha en färdig modul, mymodule.ko i vår projektmapp. Vi laddar in den i vår kärna!
~/module$ sudo insmod mymodule.ko
Grattis, du är nu kärnprogrammerare! Du har skrivit kod som i detta nu existerar i ditt operativsystems kärna. Den kanske inte gör så spännande saker, men det kommer vi ändra på i kommande inlägg i denna bloggserie.

Vi kan på flera sätt verifiera att vår kärna verkligen laddats in och nu anses vara en legitim del av den aktiva kärnan. För det första kan vi se att vår inladdnings-funktion kördes genom att leta efter texten "I'm alive!" i syslogen och/eller kärnans egen log dmesg. Kommandot lsmod listar alla moduler som just nu är inladdade i den aktiva kärnan. mymodule borde finnas med även där. Om du är väldigt skeptiskt lagd och av någon anledning fortfarande inte är helt övertygad så kan du som ett sista bevis se att vi har fått en helt egen mapp i /sys/module/mymodule.

Nu när jag visat hur vi laddar in vår kärnmodul ska jag också visa hur man tar bort den.
~/module$ sudo rmmod mymodule
Lägg märke till att vi när vi laddade in modulen var tvungna att skriva hela filnamnet, men när vi laddar ur modulen så räcker det med mymodule. Du kan verifiera att modulen tagits bort ur kärnan på samma sätt som vi verifierade att den hade laddats in.

Nu kanske du oroar dig över meddelanden om att du numera kör en "Tainted kernel". Detta beror på att vi inte specificerat vilken licens vår modul kör under. Detta ska vi rätta till nu! Vi ska samtidigt ändra så att våra två funktioner får lite roligare och bättre namn. Här är den slutgiltiga koden för den här blogposten:



Som du ser om du följt alla steg och byggt en modul så har vi fått en massa skräp i vår projektmapp. Vi kan köra följande kommando för att rensa bort alla automatiskt genererade filer:
~/module$ make -C /lib/modules/`uname -r`/build M=`pwd` clean
Sådär! Nu är det rent och snyggt i vår projektmapp igen. För att slippa komma ihåg alla långa kommandon för att bygga och städa uppdaterar vi vår make-fil lite grann:



Nu är byggning och städning av vårt projekt lika enkelt som att skriva make respektive make clean.

I nästa post kommer jag visa hur man 'hookar in' i linux nätverksstack och manipulerar paket som färdas igenom den.

söndag 13 augusti 2017

Hej världen

På den här bloggen har jag tänkt lägga upp texter om programmering och om hur det kan vara att jobba som utvecklare. Jag har än så länge bara ett års erfarenhet som professionell utvecklare, men något matnyttigt som är värt att skriva om har jag nog hunnit lära mig.

Som "grön" programmerare kan strömmen av all ny information kännas överväldigande. Allt är nytt och ofta upplever man att saker man lär sig ena veckan är borta nästa. Genom att bearbeta nyförvärvade kunskaper i text på denna hemsida hoppas jag få ordning och reda på all information och samtidigt känna att det jag lär mig stannar kvar längre. Om någon annan än jag kan dra någon nytta, eller nöje, från dessa texter blir ingen gladare än jag!

Under det senaste halvåret har jag haft det stora nöjet att få sätta mig in i Linuxkärnan och då framförallt om hur Linux nätverkssubsystem fungerar. Så min första serie av inlägg kommer handla om detta. Mitt mål med denna serie är att visa att kärnprogrammering inte alls är något läskigt, farligt eller onödigt komplicerat.

Jag kommer försöka vara så detaljerad och pedagogisk som möjligt, men det är ändå bra om den som läser har grundläggande kunskaper om C-programmering och hur det är att befinna sig i en Linux-miljö.

Då kör vi!