changed net module to net/netip and added file ops

This commit is contained in:
Adora Laura Kalb 2023-03-13 17:50:16 +01:00
parent 8475bad2d7
commit 928b08d0ce
4 changed files with 223 additions and 40 deletions

View file

@ -6,7 +6,7 @@ package cmd
import (
"fmt"
"net"
"net/netip"
"os"
"github.com/spf13/cobra"
@ -29,30 +29,42 @@ var ipaddCmd = &cobra.Command{
hostname = args[1]
}
ip := net.ParseIP(ipaddress)
ip, parseerr := netip.ParseAddr(ipaddress)
// Exit if parsed value is no valid IP
if ip == nil {
fmt.Printf("[ERROR] not a valid IP: %v\n", ipaddress)
if parseerr != nil {
fmt.Println("[ERROR]", parseerr)
os.Exit(1)
}
// Exit if parsed value is an IPv6 Address
// TODO: Implement IPv6 support
if ip.To4() == nil {
if !ip.Is4() {
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
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 == "" {
fmt.Printf("Adding IP %v\n", ipaddress)
} else {
fmt.Printf("Adding IP %v with hostname %v\n", ipaddress, hostname)
if !subnetexists {
fmt.Printf("[ERROR] Found no suitable subnet for IP %v\n", ipaddress)
fmt.Printf("[ERROR] Maybe you need to add it first?\n")
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)
}
},
}

View file

@ -84,7 +84,4 @@ func initConfig() {
println("[ERROR] Can't read config file!", err)
}
}
fmt.Println(viper.AllKeys())
}

View file

@ -5,11 +5,17 @@ Copyright © 2023 Laura Kalb <dev@lauka.net>
package cmd
import (
"net"
"bufio"
"fmt"
"net/netip"
"os"
"strings"
"github.com/spf13/viper"
)
type Subnet struct {
Subnet net.IPNet
Subnet netip.Prefix
Name string
Vlan string
Addresses []Address
@ -20,23 +26,178 @@ type Address struct {
FQDN string
}
// GetSubnet 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).
// 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).
//
// Returns the best subnet as Subnet object and true if a suitable
// 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
}
}
// 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
// file.
//
// Returns nil on success or the error that happened.
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
}
// 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
}

View file

@ -6,7 +6,7 @@ package cmd
import (
"fmt"
"net"
"net/netip"
"os"
"github.com/spf13/cobra"
@ -14,29 +14,37 @@ import (
// addCmd represents the add command
var subnetaddCmd = &cobra.Command{
Use: "add subnet [vlan]",
Use: "add subnet subnet-name [vlan]",
Short: "Add a new subnet",
Long: `Add a new subnet`,
Args: cobra.RangeArgs(1, 2),
Args: cobra.RangeArgs(2, 3),
Aliases: []string{"a"},
Run: func(cmd *cobra.Command, args []string) {
var subnet string
var vlanid string
var netname string
if len(args) == 1 {
if len(args) == 2 {
subnet = args[0]
netname = args[1]
vlanid = "-"
}
if len(args) == 2 {
if len(args) == 3 {
subnet = args[0]
vlanid = args[1]
netname = args[1]
vlanid = args[2]
}
// 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
if err != nil {
@ -46,23 +54,28 @@ var subnetaddCmd = &cobra.Command{
// Exit if parsed value is an IPv6 Address
// TODO: Implement IPv6 support
if ipnet.IP.To4() == nil {
if !ipnet.Addr().Is4() {
fmt.Printf("[ERROR] IPv6 is not yet supported!\n")
os.Exit(1)
}
// Ask for Subnet Name
// TODO: Check if net name only contains letters, numbers and hyphens
fmt.Printf("Subnet name: ")
fmt.Scan(&netname)
subnetobject := Subnet{}
subnetobject.Subnet = ipnet
subnetobject.Name = netname
subnetobject.Vlan = vlanid
if vlanid == "-" {
fmt.Printf("Adding Subnet %v.\n", subnet)
} else {
fmt.Printf("Adding Subnet %v with VLAN Tag %v.\n", subnet, vlanid)
writeerr := WriteSubnet(subnetobject)
if writeerr != nil {
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)
}
},
}