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.