Added logic to find next available IP address

This commit is contained in:
Mike Conrad
2025-04-14 15:50:08 -04:00
parent 2964391f0a
commit d0e3d6777d
5 changed files with 159 additions and 4 deletions

42
configreader.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"fmt"
"io/fs"
"net"
"path/filepath"
"strings"
"gopkg.in/ini.v1"
)
func parseUsedIPsFromConfigs(dir string) ([]net.IP, error) {
var usedIPs []net.IP
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil || d.IsDir() || !strings.HasSuffix(d.Name(), ".conf") {
return nil
}
cfg, err := ini.Load(path)
if err != nil {
return fmt.Errorf("failed to parse config %s: %w", path, err)
}
// Check [Interface] section
if iface := cfg.Section("Interface"); iface != nil {
if addr := iface.Key("Address").String(); addr != "" {
ip := strings.Split(addr, "/")[0]
usedIPs = append(usedIPs, net.ParseIP(ip))
}
}
return nil
})
if err != nil {
return nil, err
}
return usedIPs, nil
}

2
go.mod
View File

@ -8,7 +8,9 @@ require (
)
require (
github.com/joho/godotenv v1.5.1 // indirect
github.com/pquerna/cachecontrol v0.2.0 // indirect
golang.org/x/crypto v0.37.0 // indirect
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

4
go.sum
View File

@ -4,6 +4,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
@ -18,5 +20,7 @@ golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-jose/go-jose.v2 v2.6.3 h1:nt80fvSDlhKWQgSWyHyy5CfmlQr+asih51R8PTWNKKs=
gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

100
ipaddresses.go Normal file
View File

@ -0,0 +1,100 @@
package main
import (
"fmt"
"log"
"net"
"os"
)
func getInterfaceIPAndMask(interfaceName string) (ip net.IP, mask net.IPMask, err error) {
iface, err := net.InterfaceByName(interfaceName)
if err != nil {
return nil, nil, fmt.Errorf("failed to get interface %s: %w", interfaceName, err)
}
addrs, err := iface.Addrs()
if err != nil {
return nil, nil, fmt.Errorf("failed to get addresses for %s: %w", interfaceName, err)
}
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok || ipNet.IP.IsLoopback() || ipNet.IP.To4() == nil {
continue
}
return ipNet.IP, ipNet.Mask, nil
}
return nil, nil, fmt.Errorf("no valid IPv4 address found for interface %s", interfaceName)
}
func getNextFreeIP() (net.IP, error) {
var wg string = os.Getenv("WIREGUARD_INTERFACE")
ip, mask, err := getInterfaceIPAndMask(wg)
if err != nil {
log.Fatal(err)
}
used, err := parseUsedIPsFromConfigs("./configs")
if err != nil {
fmt.Println("Error:", err)
return nil, fmt.Errorf("Unkown err: %s", err)
}
for _, ip := range used {
fmt.Println("Used IP:", ip)
}
// used := []net.IP{
// net.ParseIP("10.200.34.1"),
// net.ParseIP("10.200.34.2"),
// net.ParseIP("10.200.34.3"),
// net.ParseIP("10.200.34.4"),
// net.ParseIP("10.200.34.5"),
// net.ParseIP("10.200.34.6"),
// net.ParseIP("10.200.34.225"),
// net.ParseIP("10.200.34.226"),
// net.ParseIP("10.200.34.227"),
// }
network := ip.Mask(mask)
ipNet := &net.IPNet{IP: network, Mask: mask}
// Turn the used list into a map for fast lookup
usedMap := make(map[string]bool)
for _, u := range used {
usedMap[u.String()] = true
}
// Get network boundaries
start := ipToUint32(network) + 1
end := ipToUint32(lastIP(ipNet)) - 1 // Skip broadcast
for i := start; i <= end; i++ {
candidate := uint32ToIP(i)
if !usedMap[candidate.String()] {
return candidate, nil
}
}
return nil, fmt.Errorf("no free IPs in subnet %s", ipNet.String())
}
func ipToUint32(ip net.IP) uint32 {
ip = ip.To4()
return uint32(ip[0])<<24 | uint32(ip[1])<<16 | uint32(ip[2])<<8 | uint32(ip[3])
}
func uint32ToIP(n uint32) net.IP {
return net.IPv4(byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
}
func lastIP(ipNet *net.IPNet) net.IP {
ip := ipNet.IP.To4()
mask := ipNet.Mask
broadcast := make(net.IP, len(ip))
for i := range ip {
broadcast[i] = ip[i] | ^mask[i]
}
return broadcast
}

15
main.go
View File

@ -5,12 +5,12 @@ import (
"fmt"
"log"
"net/http"
"strings"
"os"
"strings"
"github.com/coreos/go-oidc"
"golang.org/x/oauth2"
"golang.org/x/oauth2/microsoft"
"github.com/coreos/go-oidc"
)
var (
@ -26,10 +26,16 @@ var (
func main() {
nextIP, err := getNextFreeIP()
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Next available IP:", nextIP)
}
ctx := context.Background()
issuer := fmt.Sprintf("https://login.microsoftonline.com/%s/v2.0", tenantID)
var err error
provider, err = oidc.NewProvider(ctx, issuer)
if err != nil {
log.Fatalf("failed to get provider: %v", err)
@ -50,6 +56,7 @@ func main() {
log.Println("Server started at http://localhost:5000")
log.Fatal(http.ListenAndServe(":5000", nil))
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
@ -77,7 +84,7 @@ func handleIndex(w http.ResponseWriter, r *http.Request) {
}
var claims struct {
Email string `json:"email"`
Email string `json:"email"`
PreferredUsername string `json:"preferred_username"`
Name string `json:"name"`
}