mirror of
https://codeberg.org/lauralani/ipam.git
synced 2024-11-27 22:00:01 +01:00
changed net module to net/netip and added file ops
This commit is contained in:
parent
8475bad2d7
commit
928b08d0ce
4 changed files with 223 additions and 40 deletions
|
@ -6,7 +6,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -29,30 +29,42 @@ var ipaddCmd = &cobra.Command{
|
||||||
hostname = args[1]
|
hostname = args[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
ip := net.ParseIP(ipaddress)
|
ip, parseerr := netip.ParseAddr(ipaddress)
|
||||||
|
|
||||||
// Exit if parsed value is no valid IP
|
// Exit if parsed value is no valid IP
|
||||||
if ip == nil {
|
if parseerr != nil {
|
||||||
fmt.Printf("[ERROR] not a valid IP: %v\n", ipaddress)
|
fmt.Println("[ERROR]", parseerr)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if parsed value is an IPv6 Address
|
// Exit if parsed value is an IPv6 Address
|
||||||
// TODO: Implement IPv6 support
|
// TODO: Implement IPv6 support
|
||||||
if ip.To4() == nil {
|
if !ip.Is4() {
|
||||||
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
|
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check if there is already a subnet that can contain this IP, err if not
|
subnet, subnetexists := SearchBestSubnet(ip)
|
||||||
|
|
||||||
if hostname == "" {
|
if !subnetexists {
|
||||||
fmt.Printf("Adding IP %v\n", ipaddress)
|
fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", ipaddress)
|
||||||
} else {
|
fmt.Printf("[ERROR] Maybe you need to add it first?\n")
|
||||||
fmt.Printf("Adding IP %v with hostname %v\n", ipaddress, hostname)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save to file
|
subnet.Addresses = append(subnet.Addresses, Address{ip.String(), hostname})
|
||||||
|
|
||||||
|
writeerr := WriteSubnet(subnet)
|
||||||
|
if writeerr != nil {
|
||||||
|
fmt.Println("[ERROR]", writeerr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostname == "" {
|
||||||
|
fmt.Printf("added ip:\nip: %v\n", ipaddress)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("added ip:\nip: %v\nhostname: %v\n", ipaddress, hostname)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,4 @@ func initConfig() {
|
||||||
println("[ERROR] Can't read config file!", err)
|
println("[ERROR] Can't read config file!", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(viper.AllKeys())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
175
cmd/storage.go
175
cmd/storage.go
|
@ -5,11 +5,17 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Subnet struct {
|
type Subnet struct {
|
||||||
Subnet net.IPNet
|
Subnet netip.Prefix
|
||||||
Name string
|
Name string
|
||||||
Vlan string
|
Vlan string
|
||||||
Addresses []Address
|
Addresses []Address
|
||||||
|
@ -20,15 +26,76 @@ type Address struct {
|
||||||
FQDN string
|
FQDN string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSubnet tries to load the most fitting IP subnet file on disk.
|
// SearchBestSubnet tries to load the most fitting IP subnet file
|
||||||
// It takes an IP object and tries to get the best subnet (meaning
|
// on disk. It takes an IP object and tries to get the best subnet
|
||||||
// the subnet with the smallest subnet size).
|
// (meaning the subnet with the smallest subnet size).
|
||||||
//
|
//
|
||||||
// Returns the best subnet as Subnet object and true if a suitable
|
// Returns the best subnet as Subnet object and true if a suitable
|
||||||
// subnet was found, otherwise an empty Subnet object and false.
|
// subnet was found, otherwise an empty Subnet object and false.
|
||||||
func GetSubnet(ip net.IP) (Subnet, bool) {
|
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
|
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")
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteSubnet takes a given Subnet object and tries to write it to
|
// WriteSubnet takes a given Subnet object and tries to write it to
|
||||||
|
@ -36,7 +103,101 @@ func GetSubnet(ip net.IP) (Subnet, bool) {
|
||||||
//
|
//
|
||||||
// Returns nil on success or the error that happened.
|
// Returns nil on success or the error that happened.
|
||||||
func WriteSubnet(subnet Subnet) error {
|
func WriteSubnet(subnet Subnet) error {
|
||||||
//if subnet.Subnet
|
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 {
|
||||||
|
for _, element := range subnet.Addresses {
|
||||||
|
_, err := file.WriteString(element.IP + ":" + element.FQDN + "\n")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("[ERROR]", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(), ":")
|
||||||
|
a := Address{s[0], s[1]}
|
||||||
|
subnet.Addresses = append(subnet.Addresses, a)
|
||||||
|
}
|
||||||
|
counter = counter + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanerr := scanner.Err(); scanerr != nil {
|
||||||
|
return Subnet{}, openerr
|
||||||
|
}
|
||||||
|
|
||||||
|
return subnet, nil
|
||||||
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -14,29 +14,37 @@ import (
|
||||||
|
|
||||||
// addCmd represents the add command
|
// addCmd represents the add command
|
||||||
var subnetaddCmd = &cobra.Command{
|
var subnetaddCmd = &cobra.Command{
|
||||||
Use: "add subnet [vlan]",
|
Use: "add subnet subnet-name [vlan]",
|
||||||
Short: "Add a new subnet",
|
Short: "Add a new subnet",
|
||||||
Long: `Add a new subnet`,
|
Long: `Add a new subnet`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(2, 3),
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var subnet string
|
var subnet string
|
||||||
var vlanid string
|
var vlanid string
|
||||||
var netname string
|
var netname string
|
||||||
|
|
||||||
if len(args) == 1 {
|
if len(args) == 2 {
|
||||||
subnet = args[0]
|
subnet = args[0]
|
||||||
|
netname = args[1]
|
||||||
vlanid = "-"
|
vlanid = "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 2 {
|
if len(args) == 3 {
|
||||||
subnet = args[0]
|
subnet = args[0]
|
||||||
vlanid = args[1]
|
netname = args[1]
|
||||||
|
vlanid = args[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse subnet into ParseCIDR to test if it's a valid subnet
|
// Parse subnet into ParseCIDR to test if it's a valid subnet
|
||||||
_, ipnet, err := net.ParseCIDR(subnet)
|
// _, ipnet, err := net.ParseCIDR(subnet)
|
||||||
|
ipnet, err := netip.ParsePrefix(subnet)
|
||||||
|
|
||||||
|
// Exit if subnet already exists, no need to add it then
|
||||||
|
if SubnetExists(ipnet) {
|
||||||
|
fmt.Printf("[ERROR] Subnet already exists: %v\n", subnet)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Exit if parsed value is no valid IP
|
// Exit if parsed value is no valid IP
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -46,23 +54,28 @@ var subnetaddCmd = &cobra.Command{
|
||||||
|
|
||||||
// Exit if parsed value is an IPv6 Address
|
// Exit if parsed value is an IPv6 Address
|
||||||
// TODO: Implement IPv6 support
|
// TODO: Implement IPv6 support
|
||||||
if ipnet.IP.To4() == nil {
|
if !ipnet.Addr().Is4() {
|
||||||
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
|
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for Subnet Name
|
subnetobject := Subnet{}
|
||||||
// TODO: Check if net name only contains letters, numbers and hyphens
|
subnetobject.Subnet = ipnet
|
||||||
fmt.Printf("Subnet name: ")
|
subnetobject.Name = netname
|
||||||
fmt.Scan(&netname)
|
subnetobject.Vlan = vlanid
|
||||||
|
|
||||||
if vlanid == "-" {
|
writeerr := WriteSubnet(subnetobject)
|
||||||
fmt.Printf("Adding Subnet %v.\n", subnet)
|
|
||||||
} else {
|
if writeerr != nil {
|
||||||
fmt.Printf("Adding Subnet %v with VLAN Tag %v.\n", subnet, vlanid)
|
fmt.Println("[ERROR]", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Save subnet to file
|
if vlanid == "-" {
|
||||||
|
fmt.Printf("added subnet:\nnet: %v\nname: %v\n", subnet, netname)
|
||||||
|
} else {
|
||||||
|
fmt.Printf("added subnet:\nnet: %v\nname: %v\nvlan: %v\n", subnet, netname, vlanid)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue