package main import ( "database/sql" "fmt" "log" "net" "net/http" "os" //"os/exec" "os/signal" "strconv" "strings" "sync" "time" "encoding/json" "io" "io/ioutil" _ "github.com/mattn/go-sqlite3" "github.com/google/gopacket" "github.com/google/gopacket/pcap" ) /* export CGO_LDFLAGS="-L/usr/lib/x86_64-linux-gnu -lpcap -static" export CGO_CFLAGS="-I/usr/include" export GODEBUG=netdns=go go build -tags netgo -a -ldflags '-extldflags "-static"' -o sniffer sniffer.go */ type Config struct { APIUrls []string `json:"api_urls"` } var ( snapshot = int32(1600) promisc = true timeout = pcap.BlockForever db *sql.DB ) // Struktur für die Graph-Diagramm-Daten type graphDiagram struct { Protocol string `json:"protocol"` Connections []string `json:"connections"` } type APIRoute struct { Path string `json:"path"` Description string `json:"description"` } var mutex sync.Mutex func main() { sig := make(chan os.Signal, 1) signal.Notify(sig, os.Interrupt) var err error db, err = sql.Open("sqlite3", "file:flows.db?cache=shared&mode=rwc&_busy_timeout=5000") if err != nil { log.Fatal(err) } defer db.Close() _, _ = db.Exec("PRAGMA journal_mode=WAL;") _, _ = db.Exec("PRAGMA synchronous=NORMAL;") _, _ = db.Exec("PRAGMA temp_store=MEMORY;") _, _ = db.Exec("PRAGMA journal_size_limit=67108864;") _, _ = db.Exec("PRAGMA cache_size = -10000;") _, _ = db.Exec("PRAGMA mmap_size = 268435456;") _, _ = db.Exec("PRAGMA locking_mode = NORMAL;") _, _ = db.Exec("PRAGMA page_size = 4096;") db.SetMaxOpenConns(1) createTables() go func() { for { updateEndpoints() time.Sleep(1 * time.Minute) } }() go startHTTPServer() ifaces, err := net.Interfaces() if err != nil { log.Fatal("Error fetching network interfaces:", err) } for _, iface := range ifaces { if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 { continue } go startSniffer(iface.Name, "tcp") go startSniffer(iface.Name, "udp") } <-sig } func createTables() { queries := []string{ `CREATE TABLE IF NOT EXISTS protocols ( id INTEGER PRIMARY KEY, name TEXT UNIQUE );`, `CREATE TABLE IF NOT EXISTS endpoints ( id INTEGER PRIMARY KEY, dnsname TEXT, ip TEXT UNIQUE );`, `CREATE TABLE IF NOT EXISTS ports ( id INTEGER PRIMARY KEY, port TEXT UNIQUE );`, `CREATE TABLE IF NOT EXISTS flows ( id INTEGER PRIMARY KEY, protocol_id INTEGER, src_endpoint_id INTEGER, src_port_id INTEGER, dst_endpoint_id INTEGER, dst_port_id INTEGER, UNIQUE(protocol_id, src_endpoint_id, src_port_id, dst_endpoint_id, dst_port_id) );`, } for _, q := range queries { if _, err := db.Exec(q); err != nil { log.Fatal("DB init error: ", err) } } } func startHTTPServer() { // Die Handlers richtig definieren, sodass keine URLs überschrieben werden http.HandleFunc("/", handleAPIHelp) // Diese Route kommt als letztes, um Konflikte zu vermeiden http.HandleFunc("/api/show/", handleRequest) // Route für die Startseite http.HandleFunc("/api/text/", handleText) // Route für "text" http.HandleFunc("/api/form/", handleAPIConfig) // Route für das Webformular http.HandleFunc("/api/show/multi/", handleRequestMulti) // Route für "show/multi" http.HandleFunc("/api/text/multi/", handleTextMulti) // Route für "text/multi" log.Fatal(http.ListenAndServe(":8080", nil)) } func handleAPIHelp(w http.ResponseWriter, r *http.Request) { routes := []struct { Path string `json:"path"` Method string `json:"method"` Description string `json:"description"` }{ {Path: "/", Method: "GET", Description: "Zeigt diese Hilfeübersicht als JSON."}, {Path: "/api/show/", Method: "GET", Description: "Zeigt die Startseite mit einer visuellen Map an."}, {Path: "/api/show/multi/", Method: "GET", Description: "Zeigt die Startseite mit einer visuellen Map für mehrere Server an."}, {Path: "/api/text/", Method: "GET", Description: "Zeigt den Graph-Text-Code eines einzelnen Servers."}, {Path: "/api/text/multi/", Method: "GET", Description: "Fragt mehrere Server ab und kombiniert deren Graph-Text-Code."}, {Path: "/api/form/", Method: "POST", Description: "Ein Webformular zum Eintragen von API-Servern."}, } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(routes); err != nil { http.Error(w, "Fehler beim Erstellen der Hilfeantwort", http.StatusInternalServerError) } } func handleAPIConfig(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { form := `
Gespeichert! Weiterleitung...
`)) log.Println("Formular erfolgreich verarbeitet und Daten gespeichert.") } } // Funktion, um das Graph-Diagramm als JSON zu generieren func handleText(w http.ResponseWriter, r *http.Request) { mutex.Lock() defer mutex.Unlock() // Holen der Protokolle aus der Tabelle protocols, err := getProtocols() if err != nil { log.Fatal("Error fetching protocols: ", err) } // Erstelle eine Slice von Diagramm-Objekten var graphDiagrams []map[string]interface{} // Für jedes Protokoll das Graph-Diagramm erstellen und in JSON-Format speichern for _, protocol := range protocols { fullGraph := generateGraph(protocol) lines := strings.Split(fullGraph, "\n") // Erstelle eine Map für das aktuelle Diagramm diagram := map[string]interface{}{ "protocol": protocol, "connections": []string{}, } // Alle Zeilen außer der ersten (graph LR) in die Verbindungen einfügen for _, line := range lines[1:] { if line != "" { diagram["connections"] = append(diagram["connections"].([]string), line) } } // Füge das Diagramm zu der Liste hinzu graphDiagrams = append(graphDiagrams, diagram) } // Antwort als JSON zurückgeben w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // Erstelle die JSON-Antwort response, err := json.Marshal(graphDiagrams) if err != nil { http.Error(w, "Fehler beim Erstellen der JSON-Antwort", http.StatusInternalServerError) return } // Schreibe die JSON-Daten in die Antwort w.Write(response) } // Funktion zum Laden der Konfiguration aus einer JSON-Datei func loadConfig(filename string) (*Config, error) { // Die Konfigurationsdatei lesen data, err := ioutil.ReadFile(filename) if err != nil { return nil, fmt.Errorf("Fehler beim Laden der Datei: %v", err) } // Die Konfiguration parsen var config Config if err := json.Unmarshal(data, &config); err != nil { return nil, fmt.Errorf("Fehler beim Parsen der Konfiguration: %v", err) } return &config, nil } // Funktion, die den Inhalt von http://127.0.0.1:8080/text abfragt func getTextDataFromAPI(apiURL string) (interface{}, error) { // Wir bauen die URL für /text url := fmt.Sprintf("%s/api/text/", apiURL) // Erstelle eine HTTP-Anfrage client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get(url) if err != nil { return nil, fmt.Errorf("Fehler beim Abrufen von %s: %v", url, err) } defer resp.Body.Close() // Überprüfen, ob der Statuscode 200 OK ist if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Fehler: Statuscode %d von %s", resp.StatusCode, url) } // Den Body der Antwort lesen body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("Fehler beim Lesen des Antwortkörpers von %s: %v", url, err) } // Versuchen, die Antwort als Array zu parsen var dataArray []interface{} if err := json.Unmarshal(body, &dataArray); err == nil { // Wenn es ein Array ist, geben wir das Array zurück return dataArray, nil } // Falls es kein Array war, versuchen wir es als Objekt (Map) var dataMap map[string]interface{} if err := json.Unmarshal(body, &dataMap); err != nil { return nil, fmt.Errorf("Fehler beim Parsen der JSON-Antwort von %s: %v", url, err) } // Wenn es ein Objekt war, geben wir das Objekt zurück return dataMap, nil } // Handler für /text/multi func handleTextMulti(w http.ResponseWriter, r *http.Request) { // Konfiguration laden config, err := loadConfig("api_config.json") if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Liste für alle abgerufenen Daten var allData []interface{} // Über alle API-URLs iterieren for _, apiURL := range config.APIUrls { // Daten von der /text-Route der API abfragen textData, err := getTextDataFromAPI(apiURL) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } // Die abgerufenen Daten zu allData hinzufügen switch data := textData.(type) { case []interface{}: // Falls es ein Array ist, fügen wir es direkt hinzu allData = append(allData, data...) case map[string]interface{}: // Falls es ein Objekt ist, fügen wir das Objekt als ein Element hinzu allData = append(allData, data) default: http.Error(w, "Unbekannter Datentyp bei der Antwort von API", http.StatusInternalServerError) return } } // Setze den Content-Type auf "application/json" w.Header().Set("Content-Type", "application/json") // Die gesammelten Daten als JSON zurückgeben result, err := json.Marshal(allData) if err != nil { http.Error(w, fmt.Sprintf("Fehler beim Erstellen der Antwort: %v", err), http.StatusInternalServerError) return } // Gebe die gesammelten Daten als JSON zurück w.Write(result) } func handleRequest(w http.ResponseWriter, r *http.Request) { resp, err := http.Get("http://localhost:8080/api/text/") if err != nil { http.Error(w, "Fehler beim Laden des JSON", 500) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data []graphDiagram if err := json.Unmarshal(body, &data); err != nil { http.Error(w, "JSON-Parsing-Fehler", 500) return } // Nodes und Edges aufbauen nodeSet := map[string]bool{} var edges []string for idx, ds := range data { for _, conn := range ds.Connections { conn = strings.TrimSpace(conn) parts := strings.Split(conn, "-->") if len(parts) != 2 { continue } from := strings.TrimSpace(parts[0]) to := strings.TrimSpace(parts[1]) nodeSet[from] = true nodeSet[to] = true // Edge für vis.js edges = append(edges, fmt.Sprintf(`{ from: %q, to: %q, arrows: "to", label: "ds%d" }`, from, to, idx)) } } // Nodes generieren var nodes []string for id := range nodeSet { nodes = append(nodes, fmt.Sprintf(`{ id: %q, label: %q }`, id, id)) } // HTML+JS-Ausgabe fmt.Fprint(w, ` `) } func handleRequestMulti(w http.ResponseWriter, r *http.Request) { resp, err := http.Get("http://localhost:8080/api/text/multi/") if err != nil { http.Error(w, "Fehler beim Laden des JSON", 500) return } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var data []graphDiagram if err := json.Unmarshal(body, &data); err != nil { http.Error(w, "JSON-Parsing-Fehler", 500) return } // Nodes und Edges aufbauen nodeSet := map[string]bool{} var edges []string for idx, ds := range data { for _, conn := range ds.Connections { conn = strings.TrimSpace(conn) parts := strings.Split(conn, "-->") if len(parts) != 2 { continue } from := strings.TrimSpace(parts[0]) to := strings.TrimSpace(parts[1]) nodeSet[from] = true nodeSet[to] = true // Edge für vis.js edges = append(edges, fmt.Sprintf(`{ from: %q, to: %q, arrows: "to", label: "ds%d" }`, from, to, idx)) } } // Nodes generieren var nodes []string for id := range nodeSet { nodes = append(nodes, fmt.Sprintf(`{ id: %q, label: %q }`, id, id)) } // HTML+JS-Ausgabe fmt.Fprint(w, ` `) } func generateGraph(protocol string) string { var sb strings.Builder sb.WriteString("graph LR\n") // Die Abfrage für ein bestimmtes Protokoll query := fmt.Sprintf(` SELECT p.name, COALESCE(se.dnsname, se.ip) || ':' || pr.port AS src, COALESCE(de.dnsname, de.ip) || ':' || pd.port AS dst FROM flows f JOIN protocols p ON f.protocol_id = p.id JOIN endpoints se ON f.src_endpoint_id = se.id JOIN endpoints de ON f.dst_endpoint_id = de.id JOIN ports pr ON f.src_port_id = pr.id JOIN ports pd ON f.dst_port_id = pd.id WHERE p.name = '%s'; `, protocol) rows, err := db.Query(query) if err != nil { log.Fatal("Error fetching flows: ", err) } defer rows.Close() seen := make(map[string]bool) // Durchlaufen der Abfrageergebnisse und Hinzufügen der Verbindungen zum Graph-Graph for rows.Next() { var protocol, src, dst string if err := rows.Scan(&protocol, &src, &dst); err != nil { log.Fatal("Error scanning flow: ", err) } srcLabel := normalizePortLabel(src) dstLabel := normalizePortLabel(dst) // Nodes für Quelle und Ziel erstellen srcNode := fmt.Sprintf("%s:%s", srcLabel, protocol) dstNode := fmt.Sprintf("%s:%s", dstLabel, protocol) key := fmt.Sprintf("%s-->%s", srcNode, dstNode) // Verbindungen hinzufügen, wenn sie noch nicht existieren if !seen[key] { sb.WriteString(fmt.Sprintf(" %s --> %s\n", srcNode, dstNode)) seen[key] = true } } if err := rows.Err(); err != nil { log.Fatal("Error iterating over rows: ", err) } return sb.String() } func getProtocols() ([]string, error) { query := `SELECT name FROM protocols;` rows, err := db.Query(query) if err != nil { return nil, err } defer rows.Close() var protocols []string for rows.Next() { var protocol string if err := rows.Scan(&protocol); err != nil { return nil, err } protocols = append(protocols, protocol) } if err := rows.Err(); err != nil { return nil, err } return protocols, nil } func startSniffer(ifaceName string, protocol string) { var filter string switch protocol { case "tcp": filter = "tcp" case "udp": filter = "udp" default: return } handle, err := pcap.OpenLive(ifaceName, snapshot, promisc, timeout) if err != nil { log.Printf("Error opening interface %s: %v\n", ifaceName, err) return } defer handle.Close() if err := handle.SetBPFFilter(filter); err != nil { log.Printf("Error setting filter on %s: %v\n", ifaceName, err) return } packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) for packet := range packetSource.Packets() { processPacket(packet, protocol) } } func processPacket(packet gopacket.Packet, protocol string) { netLayer := packet.NetworkLayer() transLayer := packet.TransportLayer() if netLayer == nil || transLayer == nil { return } srcIP, dstIP := netLayer.NetworkFlow().Endpoints() srcPort, dstPort := transLayer.TransportFlow().Endpoints() mutex.Lock() defer mutex.Unlock() protocolID := getOrCreateProtocol(protocol) srcID := getOrCreateEndpoint(srcIP.String()) dstID := getOrCreateEndpoint(dstIP.String()) srcPortID := getOrCreatePort(srcPort.String()) dstPortID := getOrCreatePort(dstPort.String()) _, err := db.Exec(`INSERT OR IGNORE INTO flows (protocol_id, src_endpoint_id, src_port_id, dst_endpoint_id, dst_port_id) VALUES (?, ?, ?, ?, ?)`, protocolID, srcID, srcPortID, dstID, dstPortID) if err != nil { log.Fatal("Error inserting flow: ", err) } } func getOrCreateProtocol(name string) int64 { var id int64 err := db.QueryRow(`SELECT id FROM protocols WHERE name = ?`, name).Scan(&id) if err == sql.ErrNoRows { res, err := db.Exec(`INSERT INTO protocols (name) VALUES (?)`, name) if err != nil { log.Fatal("Error inserting protocol: ", err) } id, _ = res.LastInsertId() } else if err != nil { log.Fatal("Error selecting protocol: ", err) } return id } func getOrCreateEndpoint(ip string) int64 { var id int64 err := db.QueryRow(`SELECT id FROM endpoints WHERE ip = ?`, ip).Scan(&id) if err == sql.ErrNoRows { res, err := db.Exec(`INSERT INTO endpoints (ip) VALUES (?)`, ip) if err != nil { log.Fatal("Error inserting endpoint: ", err) } id, _ = res.LastInsertId() } else if err != nil { log.Fatal("Error selecting endpoint: ", err) } return id } func getOrCreatePort(port string) int64 { var id int64 err := db.QueryRow(`SELECT id FROM ports WHERE port = ?`, port).Scan(&id) if err == sql.ErrNoRows { res, err := db.Exec(`INSERT INTO ports (port) VALUES (?)`, port) if err != nil { log.Fatal("Error inserting port: ", err) } id, _ = res.LastInsertId() } else if err != nil { log.Fatal("Error selecting port: ", err) } return id } func normalizePortLabel(addr string) string { lastColon := strings.LastIndex(addr, ":") if lastColon == -1 { return addr } ip := addr[:lastColon] portStr := addr[lastColon+1:] if port, err := strconv.Atoi(portStr); err == nil && port > 30000 { return fmt.Sprintf("%s:dynamic", ip) } return addr } func updateEndpoints() { type entry struct { ID int64 IP string } var pending []entry rows, err := db.Query("SELECT id, ip FROM endpoints WHERE dnsname IS NULL") if err != nil { log.Fatal("Error fetching endpoints: ", err) } defer rows.Close() for rows.Next() { var e entry if err := rows.Scan(&e.ID, &e.IP); err != nil { log.Printf("Skipping invalid row: %v\n", err) continue } pending = append(pending, e) } if err := rows.Err(); err != nil { log.Fatal("Error iterating rows: ", err) } for _, e := range pending { dnsName, err := resolveDNS(e.IP) if err != nil { //log.Printf("Error resolving DNS for IP %s: %v\n", e.IP, err) continue } if dnsName == "" { //log.Printf("No DNS name found for IP %s\n", e.IP) continue } _, err = db.Exec(`UPDATE endpoints SET dnsname = ? WHERE id = ?`, dnsName, e.ID) if err != nil { log.Printf("DB update failed for IP %s: %v\n", e.IP, err) continue } //log.Printf("Updated endpoint: %s => %s\n", e.IP, dnsName) } } /* // DNS-Auflösung über `nslookup` (nutzt lokale DNS-Konfig) func resolveDNS(ip string) (string, error) { out, err := exec.Command("nslookup", ip).CombinedOutput() if err != nil { return "", fmt.Errorf("nslookup failed: %v", err) } lines := strings.Split(string(out), "\n") for _, line := range lines { line = strings.TrimSpace(line) if strings.HasPrefix(line, "name =") || strings.Contains(line, "name =") { parts := strings.Split(line, "name =") if len(parts) > 1 { return strings.TrimSpace(parts[1]), nil } } } return "", nil } */ // DNS-Auflösung mit Go func resolveDNS(ip string) (string, error) { names, err := net.LookupAddr(ip) if err != nil || len(names) == 0 { //return "", fmt.Errorf("LookupAddr failed: %v", err) return "", nil } // net.LookupAddr gibt oft mehrere Namen zurück, wir nehmen den ersten und säubern ihn return strings.TrimSuffix(names[0], "."), nil }