From 6c391e8c0a11dc3a8b6e83277151d909b15236c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Moska=C5=82a?= Date: Wed, 24 Jan 2024 17:29:52 +0100 Subject: [PATCH] Initial commit --- .gitignore | 5 ++ go.mod | 8 +++ go.sum | 6 ++ index.html | 118 ++++++++++++++++++++++++++++++ minipam.go | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 343 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 index.html create mode 100644 minipam.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5772ca3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.* +!.gitignore +minipam +*.yaml +*.json \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f98507d --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module minipam + +go 1.21 + +require ( + github.com/gorilla/mux v1.8.1 + gopkg.in/yaml.v3 v3.0.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3e12cf5 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/index.html b/index.html new file mode 100644 index 0000000..103edd2 --- /dev/null +++ b/index.html @@ -0,0 +1,118 @@ + + + + MinIPAM + + + + + Subnets:
+
+
+

Subnet

+

Space usage

+
+ + \ No newline at end of file diff --git a/minipam.go b/minipam.go new file mode 100644 index 0000000..3ccfc19 --- /dev/null +++ b/minipam.go @@ -0,0 +1,206 @@ +package main + +import ( + "crypto/tls" + _ "embed" + "encoding/json" + "github.com/gorilla/mux" + "gopkg.in/yaml.v3" + "log" + "net" + "net/http" + "net/netip" + "os" + "os/exec" + "time" +) + +type ConfigT struct { + BindAddress string `yaml:"bind_address"` + ScanSubnets []string `yaml:"scan_subnets"` + DelayBetweenScans time.Duration `yaml:"delay_between_scans"` + ExcludeSpecialAddresses bool `yaml:"exclude_special_addresses"` + PersistenceLocation string `yaml:"persistence_location"` + UseTLS bool `yaml:"use_tls"` + TLSKeyFile string `yaml:"tls_key_file"` + TLSCertFile string `yaml:"tls_cert_file"` +} + +type HostT struct { + FirstSeen time.Time `json:"first_seen"` + LastSeen time.Time `json:"last_seen"` + Online bool `json:"online"` + RevDNS string `json:"rev_dns"` +} + +type SubnetT struct { + Hosts map[string]HostT `json:"hosts"` + TotalAddresses int `json:"total_addresses"` + UsedAddresses int `json:"used_addresses"` + HostList []string `json:"host_list"` +} + +type PersistenceT struct { + Subnets map[string]SubnetT +} + +var conf ConfigT +var p PersistenceT + +//go:embed index.html +var indexhtml []byte + +func main() { + + if len(os.Args) < 2 { + log.Fatalf("Usage: %s ", os.Args[0]) + } + + f, err := os.Open(os.Args[1]) + if err != nil { + log.Fatalf("Failed to open %s: %s", os.Args[1], err) + } + err = yaml.NewDecoder(f).Decode(&conf) + if err != nil { + log.Fatalf("Failed to load config file: %s", err) + } + _ = f.Close() + + f, err = os.Open(conf.PersistenceLocation) + if err != nil { + log.Printf("Failed to open persistence file: %s. Will try to create new one later.", err) + } else { + err = json.NewDecoder(f).Decode(&p) + _ = f.Close() + if err != nil { + log.Fatalf("Failed to decode persistence file: %s", err) + } + } + if p.Subnets == nil { + p.Subnets = make(map[string]SubnetT) + } + go scanner() + r := mux.NewRouter() + r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { + _, _ = writer.Write(indexhtml) + }).Methods("GET") + + r.HandleFunc("/data", func(writer http.ResponseWriter, request *http.Request) { + _ = json.NewEncoder(writer).Encode(&p) + }).Methods("GET") + server := &http.Server{ + Addr: conf.BindAddress, + Handler: r, + TLSConfig: &tls.Config{ + MinVersion: tls.VersionTLS13, + }, + } + if conf.UseTLS { + err = server.ListenAndServeTLS(conf.TLSCertFile, conf.TLSKeyFile) + } else { + err = server.ListenAndServe() + } + if err != nil { + log.Fatalf("%s", err) + } + +} + +func ping(addr string) bool { + cmd := exec.Command("ping", "-n", "-W", "0.2", "-c", "1", addr) + cmd.Stdin = nil + cmd.Stdout = nil + cmd.Stderr = nil + err := cmd.Run() + if err == nil { + return true + } + return false +} + +func scanner() { + for { + + for _, v := range conf.ScanSubnets { + + persistenceSubnet, ok := p.Subnets[v] + if !ok { + persistenceSubnet = SubnetT{} + persistenceSubnet.Hosts = make(map[string]HostT) + } + log.Printf("Scanning subnet %s", v) + prefix, err := netip.ParsePrefix(v) + if err != nil { + log.Printf("Error: %s", err) + continue + } + prefix = prefix.Masked() + addr := prefix.Addr() + + if conf.ExcludeSpecialAddresses { + addr = addr.Next() + } + persistenceSubnet.TotalAddresses = 0 + persistenceSubnet.UsedAddresses = 0 + persistenceSubnet.HostList = make([]string, 0) + for { + if !prefix.Contains(addr) { + break + } + //skip broadcast address + if conf.ExcludeSpecialAddresses && !prefix.Contains(addr.Next()) { + break + } + persistenceSubnet.TotalAddresses++ + //fmt.Println(addr.String()) + persistenceSubnet.HostList = append(persistenceSubnet.HostList, addr.String()) + pingstate := ping(addr.String()) + host, ok := persistenceSubnet.Hosts[addr.String()] + if pingstate { + persistenceSubnet.UsedAddresses++ + //log.Printf("%s is up", addr.String()) + rdnsString := "" + rdns, err := net.LookupAddr(addr.String()) + if err == nil { + if len(rdns) > 0 { + rdnsString = rdns[0] + } + } + if !ok { + persistenceSubnet.Hosts[addr.String()] = HostT{ + FirstSeen: time.Now(), + LastSeen: time.Now(), + Online: true, + RevDNS: rdnsString, + } + } else { + host.LastSeen = time.Now() + host.Online = true + host.RevDNS = rdnsString + persistenceSubnet.Hosts[addr.String()] = host + } + + } else if ok { + host.Online = false + persistenceSubnet.Hosts[addr.String()] = host + persistenceSubnet.UsedAddresses++ + } + + addr = addr.Next() + } + p.Subnets[v] = persistenceSubnet + } + log.Printf("Scan finished") + f, err := os.Create(conf.PersistenceLocation) + if err != nil { + log.Printf("Failed to save persistence: %s", err) + } else { + err := json.NewEncoder(f).Encode(&p) + _ = f.Close() + if err != nil { + log.Printf("Failed to encode persistence: %s", err) + } + } + time.Sleep(conf.DelayBetweenScans) + } +}