Hálózat feltérképezése Go-ban: netmapper

A werkstatt sorozat harmadik része – egy hálózati scanner ami goroutine-okkal 254 hostot párhuzamosan vizsgál.

Hálózat feltérképezése Go-ban: netmapper

Rendszergazdaként időnként tudni akarom mi fut a hálózaton. Melyik IP él, milyen portok nyitottak, hol fut SSH, hol webszerver. Az nmap erre tökéletes, de néha egy egyszerűbb, saját eszköz is elég – és közben megtanulod a Go egyik legerősebb képességét: a párhuzamosítást.

A netmapper két fázisban dolgozik. Először végigmegy a /24-es subneten és megkeresi az élő hostokat. Utána minden élő hoston végigscanneli a megadott portokat. Mindkét fázis párhuzamos – 254 goroutine fut egyszerre a host discovery-ben.

Miért érdekes ez Go-ban?

Bash-ben is írtam hálózati scripteket. Egy for ciklus, nc -z minden portra, és vársz. Egy /24-es subnet scannelése szekvenciálisan percekig tart, mert minden timeout-ot ki kell várni.

Go-ban a goroutine-ok ezt másodpercekre csökkentik. Egy go kulcsszó a függvényhívás előtt, és az már párhuzamosan fut. Nem kell thread pool, nem kell async/await, nem kell callback. A Go runtime osztja be a goroutine-okat az operációs rendszer száljaira.

A két fázis

Az első fázisban 254 goroutine indul el egyszerre, mindegyik egy IP címet vizsgál:

for i := 1; i <= 254; i++ {
    wg.Add(1)
    go func(n int) {
        defer wg.Done()
        ip := fmt.Sprintf("%s.%d", subnet, n)
        if isAlive(ip, timeout, ports) {
            aliveIPs <- ip
        }
    }(i)
}

Az isAlive megpróbál TCP kapcsolatot nyitni a közismert portokon. Ha bármelyikre válaszol a host, él. Az eredményt egy channel-be küldi – ez a Go módja az adatátadásnak goroutine-ok között.

A WaitGroup pedig a szinkronizáció: megvárjuk amíg mind a 254 goroutine végzett, utána zárjuk a channel-t.

A második fázisban az élő hostok portjait scanneljük – szintén párhuzamosan, de itt egy Mutex is kell:

mu.Lock()
host.Ports = append(host.Ports, p)
mu.Unlock()

A mutex azért kell, mert több goroutine egyszerre próbálna írni ugyanabba a slice-ba. Mutex nélkül a párhuzamos írások összekeveredhetnek – ez a “race condition”, és Go-ban a -race flag-gel ki is tudod mutatni.

Három párhuzamossági primitív

A netmapper három Go párhuzamossági eszközt használ, és mindhármat érdemes megérteni:

A goroutine a könnyűsúlyú szál. A go func() elindít egyet, a Go runtime kezeli. Ezer goroutine simán elfér a memóriában – egy operációs rendszer szál ennek a sokszorosát igényelné.

A channel a goroutine-ok közötti kommunikáció. Az aliveIPs <- ip beküld egy értéket, a for ip := range aliveIPs pedig sorban kiveszi őket. A channel típusos és thread-safe – nem kell lockolni.

A Mutex a klasszikus zárolás. Amikor nem channel-en, hanem közös memórián dolgoznak a goroutine-ok, a mutex biztosítja hogy egyszerre csak egy írhat.

Konfigurálható portok

Az eredeti verzióban a portlista be volt drótozva a kódba. A production ready verzióban a -ports flag-gel tetszőlegesen megadható:

netmapper -subnet 10.0.0 -ports 22,80,443,8080

A parsePorts függvény kezeli a validációt – hibás port szám, tartományon kívüli érték, duplikátumok. Ez apróságnak tűnik, de a CLI eszközöknél az input validáció az ami megkülönbözteti a scriptet a tooltól.

Tesztelés hálózat nélkül

A hálózati kódot nehéz teszteni – nem akarsz valódi hostokat scannelni a tesztben. A megoldás: a Go net csomagjával indítasz egy ideiglenes TCP listener-t a localhost-on, és arra scannelsz:

ln, _ := net.Listen("tcp", "127.0.0.1:0")
defer ln.Close()

host := scanHost("127.0.0.1", 500*time.Millisecond, []int{port})

A :0 port jelenti hogy a rendszer ad egy szabad portot. Így a teszt determinisztikus – tudod hogy pontosan egy port nyitott, és azt kell megtalálnia.

Mit tanultam?

A netmapper három Go koncepciót tanított meg amik az envcheck-ben és a stackctl-ben nem jöttek elő:

Goroutine-ok – hogyan indíts párhuzamos feladatokat és hogyan várd meg őket WaitGroup-pal. Channel-ek – hogyan kommunikáljanak a goroutine-ok egymással. Mutex – hogyan védd meg a közös adatstruktúrákat párhuzamos írástól.

Ez a három eszköz együtt a Go párhuzamossági modellje. Nem olyan bonyolult mint amilyennek hangzik – a lényeg hogy a goroutine-ok olcsók, a channel-ek biztonságosak, és a mutex ott kell ahol közös memóriába írnak.

Hogyan próbáld ki?

git clone https://github.com/brtkcs/werkstatt-tools.git
cd werkstatt-tools/netmapper
go run main.go -subnet 192.168.1

Vagy a saját hálózatodon:

go build -o netmapper
./netmapper -subnet 10.0.0 -timeout 1000 -ports 22,80,443

A teljes forráskód a werkstatt-tools repóban.

Ez a werkstatt sorozat harmadik része. Az envcheck fájlokat olvasott, a stackctl HTTP-t szolgált ki. A netmapper-rel a Go párhuzamosítási modelljébe merülünk – goroutine-ok, channel-ek, mutex.