Brijesh's Git Server — network-scan @ 822d6b35dfdd5e88f2265b6f75d63224ef140964

my own tool to scan for devices in local network, beyond just arp -a

feat: Import OUI data from IEEE to sqlite

IEEE has OUI text file publicly available.
This commit adds logic to scan data from the text file downloaded from IEEE and insert it in a sqlite database.
Brijesh Wawdhane brijesh@wawdhane.com
Fri, 20 Sep 2024 00:08:27 +0530
commit

822d6b35dfdd5e88f2265b6f75d63224ef140964

A .gitignore

@@ -0,0 +1,2 @@

+*.db +*.txt
A database/database.go

@@ -0,0 +1,79 @@

+package database + +import ( + "database/sql" + "fmt" + "os" + "scan-network/utils" + + _ "github.com/mattn/go-sqlite3" +) + +type Database struct { + db *sql.DB +} + +func CreateDbIfNotExists(dbPath string) error { + _, err := os.Stat(dbPath) + if os.IsNotExist(err) { + utils.PrintInColor("Database file does not exist, creating it...", 136) + file, err := os.Create(dbPath) + if err != nil { + return fmt.Errorf("error creating database file: %w", err) + } + file.Close() + } + return nil +} + +func New(dbPath string) (*Database, error) { + if err := CreateDbIfNotExists(dbPath); err != nil { + return nil, err + } + + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + return nil, fmt.Errorf("error opening database: %w", err) + } + return &Database{db: db}, nil +} + +func (d *Database) Close() error { + return d.db.Close() +} + +func (d *Database) CreateOuiTable() error { + _, err := d.db.Exec(` + CREATE TABLE IF NOT EXISTS oui ( + id TEXT PRIMARY KEY, + assignment TEXT UNIQUE, + organisation TEXT + ) + `) + if err != nil { + return fmt.Errorf("error creating table: %w", err) + } + return nil +} + +func (d *Database) InsertOUI(assignment, organisation string) error { + id := utils.GenerateBUID() + _, err := d.db.Exec("INSERT OR REPLACE INTO oui (id, assignment, organisation) VALUES (?, ?, ?)", + id, assignment, organisation) + if err != nil { + return fmt.Errorf("error inserting OUI: %w", err) + } + return nil +} + +func (d *Database) FindOrganisationByAssignment(assignment string) (string, error) { + var organisation string + err := d.db.QueryRow("SELECT organisation FROM oui WHERE assignment = ?", assignment).Scan(&organisation) + if err != nil { + if err == sql.ErrNoRows { + return "", fmt.Errorf("no organisation found for assignment: %s", assignment) + } + return "", fmt.Errorf("error querying database: %w", err) + } + return organisation, nil +}
A definitions/definitions.go

@@ -0,0 +1,6 @@

+package definitions + +type OUI struct { + organisation string + assignment string +}
A go.mod

@@ -0,0 +1,8 @@

+module network-scan + +go 1.23.0 + +require ( + github.com/google/uuid v1.6.0 + github.com/mattn/go-sqlite3 v1.14.23 +)
A main.go

@@ -0,0 +1,49 @@

+package main + +import ( + "log" + "os" + + "scan-network/database" + "scan-network/oui" + "scan-network/utils" +) + +const dbPath string = "./oui.db" + +func init() { + err := database.CreateDbIfNotExists(dbPath) + if err != nil { + log.Fatalf("Error creating database file: %v", err) + } + + db, err := database.New(dbPath) + if err != nil { + log.Fatalf("Error opening database: %v", err) + } + defer db.Close() + + err = db.CreateOuiTable() + if err != nil { + log.Fatalf("Error creating table: %v", err) + } +} + +func main() { + db, err := database.New(dbPath) + if err != nil { + log.Fatalf("Error opening database: %v", err) + } else { + utils.PrintInColor("Database opened successfully", 28) + } + defer db.Close() + + file, err := os.Open("standards-oui.ieee.org.txt") + if err != nil { + log.Fatalf("Error opening file: %v", err) + } + defer file.Close() + + oui.ScanDataFromTextFile("/Users/brijesh/projects/ongoing/scan-network/standards-oui.ieee.org.txt", db) + utils.PrintInColor("OUI data imported successfully", 28) +}
A oui/oui-import.go

@@ -0,0 +1,46 @@

+package oui + +import ( + "bufio" + "fmt" + "os" + "regexp" + "scan-network/database" +) + +func ScanDataFromTextFile(filePath string, db *database.Database) { + file, err := os.Open(filePath) + if err != nil { + fmt.Println("Error opening file:", err) + return + } + defer file.Close() + + scanner := bufio.NewScanner(file) + regexBase16 := regexp.MustCompile(`([0-9A-Fa-f]{6})\s+\(base 16\)\s+(.*)`) + + var organisation string + index := 1 + for scanner.Scan() { + line := scanner.Text() + + if matches := regexBase16.FindStringSubmatch(line); matches != nil { + macPrefix := matches[1] + organisation = matches[2] + + err := db.InsertOUI(macPrefix, organisation) + if err != nil { + fmt.Printf("%d) Error inserting this record => Organisation: %s Assignment Base16: %s\n", index, organisation, macPrefix) + fmt.Println("Error inserting OUI:", err) + } else { + fmt.Printf("Inserted %d records successfully\n", index) + } + + index++ + } + } + + if err := scanner.Err(); err != nil { + fmt.Println("Error reading file:", err) + } +}
A utils/buid.go

@@ -0,0 +1,37 @@

+package utils + +import ( + "crypto/rand" + "math/big" + "time" +) + +const ( + base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" + buidLength = 24 + randomCharsLength = 17 +) + +func GenerateBUID() string { + // Pre-allocate byte slice for result + result := make([]byte, buidLength) + + // Convert the current Unix timestamp to base32 + now := time.Now().UTC().Unix() + pos := buidLength - randomCharsLength + + for now > 0 { + pos-- + result[pos] = base32Alphabet[now&31] + now /= 32 + } + + // Generate the random part directly into the result slice + for i := buidLength - randomCharsLength; i < buidLength; i++ { + n, _ := rand.Int(rand.Reader, big.NewInt(32)) + result[i] = base32Alphabet[n.Int64()] + // result[i] = base32Alphabet[rand.Intn(32)] + } + + return string(result[pos:]) +}
A utils/printInColor.go

@@ -0,0 +1,7 @@

+package utils + +import "fmt" + +func PrintInColor(text string, color int) { + fmt.Printf("\x1b[38;5;%dm%s\x1b[0m\n", color, text) +}