2023-03-12 21:27:02 +01:00
|
|
|
/*
|
|
|
|
Copyright © 2023 Laura Kalb <dev@lauka.net>
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
2023-03-13 17:50:16 +01:00
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"net/netip"
|
|
|
|
"os"
|
2023-03-14 10:38:08 +01:00
|
|
|
"sort"
|
2023-03-13 17:50:16 +01:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/spf13/viper"
|
2023-03-12 21:27:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Subnet struct {
|
2023-03-13 17:50:16 +01:00
|
|
|
Subnet netip.Prefix
|
2023-03-12 21:27:02 +01:00
|
|
|
Name string
|
|
|
|
Vlan string
|
|
|
|
Addresses []Address
|
|
|
|
}
|
|
|
|
|
|
|
|
type Address struct {
|
2023-03-14 10:38:08 +01:00
|
|
|
IP netip.Addr
|
2023-03-12 21:27:02 +01:00
|
|
|
FQDN string
|
|
|
|
}
|
|
|
|
|
2023-03-13 17:50:16 +01:00
|
|
|
// SearchBestSubnet tries to load the most fitting IP subnet file
|
|
|
|
// on disk. It takes an IP object and tries to get the best subnet
|
|
|
|
// (meaning the subnet with the smallest subnet size).
|
2023-03-12 21:27:02 +01:00
|
|
|
//
|
|
|
|
// Returns the best subnet as Subnet object and true if a suitable
|
|
|
|
// subnet was found, otherwise an empty Subnet object and false.
|
2023-03-13 17:50:16 +01:00
|
|
|
func SearchBestSubnet(ip netip.Addr) (Subnet, bool) {
|
|
|
|
subnets := ListSubnets()
|
|
|
|
var smallestprefix int = 0
|
|
|
|
bestmatch, _ := netip.ParsePrefix("0.0.0.0/32")
|
|
|
|
var isipv4 bool = ip.Is4()
|
|
|
|
var subnet Subnet
|
|
|
|
|
|
|
|
for _, net := range subnets {
|
|
|
|
prefix, _ := netip.ParsePrefix(net)
|
|
|
|
if prefix.Addr().Is4() == isipv4 {
|
|
|
|
if prefix.Contains(ip) {
|
|
|
|
if prefix.Bits() > smallestprefix {
|
|
|
|
bestmatch = prefix
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !bestmatch.Addr().IsUnspecified() {
|
|
|
|
var geterr error
|
|
|
|
subnet, geterr = GetSubnet(bestmatch)
|
|
|
|
if geterr != nil {
|
|
|
|
fmt.Println("[ERROR]", geterr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
return subnet, true
|
|
|
|
} else {
|
|
|
|
return Subnet{}, false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SubnetExists will return true if the given subnet already exists
|
|
|
|
// on file, false otherwise.
|
|
|
|
func SubnetExists(net netip.Prefix) bool {
|
|
|
|
subnets := ListSubnets()
|
|
|
|
|
|
|
|
for _, b := range subnets {
|
|
|
|
if b == net.String() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListSubnets returns a list of strings containing the current
|
|
|
|
// subnets configured.
|
|
|
|
func ListSubnets() []string {
|
|
|
|
subnets := make([]string, 0)
|
|
|
|
var datadir string = viper.GetString("DataPath")
|
2023-03-12 21:27:02 +01:00
|
|
|
|
2023-03-13 17:50:16 +01:00
|
|
|
subnetfiles, readerr := os.ReadDir(datadir)
|
|
|
|
|
|
|
|
if len(subnetfiles) == 0 {
|
|
|
|
return subnets
|
|
|
|
}
|
|
|
|
|
|
|
|
if readerr != nil {
|
|
|
|
fmt.Println("[ERROR]", readerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
for _, element := range subnetfiles {
|
|
|
|
subnets = append(subnets, strings.Replace(element.Name(), "_", "/", 1))
|
|
|
|
}
|
|
|
|
|
|
|
|
return subnets
|
2023-03-12 21:27:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// WriteSubnet takes a given Subnet object and tries to write it to
|
|
|
|
// file.
|
|
|
|
//
|
|
|
|
// Returns nil on success or the error that happened.
|
|
|
|
func WriteSubnet(subnet Subnet) error {
|
2023-03-13 17:50:16 +01:00
|
|
|
var datadir string = viper.GetString("DataPath")
|
|
|
|
|
|
|
|
_, direrr := os.Stat(datadir)
|
|
|
|
if direrr != nil {
|
|
|
|
mkerr := os.MkdirAll(datadir, 0755)
|
|
|
|
if mkerr != nil {
|
|
|
|
println("[ERROR] Can't create ipam config directory!", mkerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
filename := datadir + strings.Replace(subnet.Subnet.String(), "/", "_", 1)
|
|
|
|
|
|
|
|
file, fileerr := os.Create(filename)
|
|
|
|
if fileerr != nil {
|
|
|
|
fmt.Println("[ERROR]", fileerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
_, suberr := file.WriteString(subnet.Subnet.String() + "\n")
|
|
|
|
if suberr != nil {
|
|
|
|
fmt.Println("[ERROR]", suberr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, nameerr := file.WriteString(subnet.Name + "\n")
|
|
|
|
if nameerr != nil {
|
|
|
|
fmt.Println("[ERROR]", nameerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
_, vlanerr := file.WriteString(subnet.Vlan + "\n")
|
|
|
|
if vlanerr != nil {
|
|
|
|
fmt.Println("[ERROR]", vlanerr)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(subnet.Addresses) != 0 {
|
2023-03-14 10:38:08 +01:00
|
|
|
subnetsorted := SortAddresses(subnet.Addresses)
|
|
|
|
for _, element := range subnetsorted {
|
|
|
|
_, err := file.WriteString(element.IP.String() + ":" + element.FQDN + "\n")
|
2023-03-13 17:50:16 +01:00
|
|
|
if err != nil {
|
|
|
|
fmt.Println("[ERROR]", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-12 21:27:02 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-13 17:50:16 +01:00
|
|
|
|
|
|
|
// GetSubnet reads the corresponding file for the given
|
|
|
|
// netip.Prefix net and constructs a Subnet object.
|
|
|
|
//
|
|
|
|
// Returns the Subnet object and nil if the file read was
|
|
|
|
// successful, an empty Subnet object and the error otherwise.
|
|
|
|
func GetSubnet(net netip.Prefix) (Subnet, error) {
|
|
|
|
var datadir string = viper.GetString("DataPath")
|
|
|
|
filename := datadir + strings.Replace(net.String(), "/", "_", 1)
|
|
|
|
var subnet Subnet = Subnet{}
|
|
|
|
|
|
|
|
// open file
|
|
|
|
file, openerr := os.Open(filename)
|
|
|
|
if openerr != nil {
|
|
|
|
return Subnet{}, openerr
|
|
|
|
}
|
|
|
|
// remember to close the file at the end of the program
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// read the file line by line using scanner
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
|
|
|
|
|
|
var counter int = 0
|
|
|
|
for scanner.Scan() {
|
|
|
|
switch counter {
|
|
|
|
case 0:
|
|
|
|
subnet.Subnet, _ = netip.ParsePrefix(scanner.Text())
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
subnet.Name = scanner.Text()
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
subnet.Vlan = scanner.Text()
|
|
|
|
|
|
|
|
default:
|
|
|
|
s := strings.Split(scanner.Text(), ":")
|
2023-03-14 10:38:08 +01:00
|
|
|
ip, _ := netip.ParseAddr(s[0])
|
|
|
|
a := Address{ip, s[1]}
|
2023-03-13 17:50:16 +01:00
|
|
|
subnet.Addresses = append(subnet.Addresses, a)
|
|
|
|
}
|
|
|
|
counter = counter + 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if scanerr := scanner.Err(); scanerr != nil {
|
|
|
|
return Subnet{}, openerr
|
|
|
|
}
|
|
|
|
|
|
|
|
return subnet, nil
|
|
|
|
}
|
2023-03-14 10:38:08 +01:00
|
|
|
|
|
|
|
// SortAddresses sorts the given list of IP addresses
|
|
|
|
// using netip.Addr.Less() and returns the sorted slice.
|
|
|
|
func SortAddresses(list []Address) []Address {
|
|
|
|
|
|
|
|
if len(list) <= 1 {
|
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(list, func(i, j int) bool {
|
|
|
|
return list[i].IP.Less(list[j].IP)
|
|
|
|
})
|
|
|
|
return list
|
|
|
|
}
|