2023-05-11 01:09:18 -06:00
|
|
|
// Codehere is mostly taken from github.com/tailscale/tailscale
|
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2023-05-21 10:37:59 -06:00
|
|
|
package db
|
2023-05-11 01:09:18 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/netip"
|
|
|
|
|
2023-05-21 10:37:59 -06:00
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
2023-05-11 01:09:18 -06:00
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
|
|
"go4.org/netipx"
|
2024-02-08 09:28:19 -07:00
|
|
|
"gorm.io/gorm"
|
2023-05-11 01:09:18 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
var ErrCouldNotAllocateIP = errors.New("could not find any suitable IP")
|
|
|
|
|
2023-09-24 05:42:05 -06:00
|
|
|
func (hsdb *HSDatabase) getAvailableIPs() (types.NodeAddresses, error) {
|
2024-02-08 09:28:19 -07:00
|
|
|
return Read(hsdb.DB, func(rx *gorm.DB) (types.NodeAddresses, error) {
|
|
|
|
return getAvailableIPs(rx, hsdb.ipPrefixes)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAvailableIPs(rx *gorm.DB, ipPrefixes []netip.Prefix) (types.NodeAddresses, error) {
|
2023-09-24 05:42:05 -06:00
|
|
|
var ips types.NodeAddresses
|
2023-05-11 01:09:18 -06:00
|
|
|
var err error
|
2024-02-08 09:28:19 -07:00
|
|
|
for _, ipPrefix := range ipPrefixes {
|
2023-05-11 01:09:18 -06:00
|
|
|
var ip *netip.Addr
|
2024-02-08 09:28:19 -07:00
|
|
|
ip, err = getAvailableIP(rx, ipPrefix)
|
2023-05-11 01:09:18 -06:00
|
|
|
if err != nil {
|
|
|
|
return ips, err
|
|
|
|
}
|
|
|
|
ips = append(ips, *ip)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ips, err
|
|
|
|
}
|
|
|
|
|
2024-02-08 09:28:19 -07:00
|
|
|
func getAvailableIP(rx *gorm.DB, ipPrefix netip.Prefix) (*netip.Addr, error) {
|
|
|
|
usedIps, err := getUsedIPs(rx)
|
2023-05-11 01:09:18 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
ipPrefixNetworkAddress, ipPrefixBroadcastAddress := util.GetIPPrefixEndpoints(ipPrefix)
|
|
|
|
|
|
|
|
// Get the first IP in our prefix
|
|
|
|
ip := ipPrefixNetworkAddress.Next()
|
|
|
|
|
|
|
|
for {
|
|
|
|
if !ipPrefix.Contains(ip) {
|
|
|
|
return nil, ErrCouldNotAllocateIP
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case ip.Compare(ipPrefixBroadcastAddress) == 0:
|
|
|
|
fallthrough
|
|
|
|
case usedIps.Contains(ip):
|
|
|
|
fallthrough
|
|
|
|
case ip == netip.Addr{} || ip.IsLoopback():
|
|
|
|
ip = ip.Next()
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
default:
|
|
|
|
return &ip, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-08 09:28:19 -07:00
|
|
|
func getUsedIPs(rx *gorm.DB) (*netipx.IPSet, error) {
|
2023-05-11 01:09:18 -06:00
|
|
|
// FIXME: This really deserves a better data model,
|
|
|
|
// but this was quick to get running and it should be enough
|
|
|
|
// to begin experimenting with a dual stack tailnet.
|
|
|
|
var addressesSlices []string
|
2024-02-08 09:28:19 -07:00
|
|
|
rx.Model(&types.Node{}).Pluck("ip_addresses", &addressesSlices)
|
2023-05-11 01:09:18 -06:00
|
|
|
|
|
|
|
var ips netipx.IPSetBuilder
|
|
|
|
for _, slice := range addressesSlices {
|
2023-09-24 05:42:05 -06:00
|
|
|
var machineAddresses types.NodeAddresses
|
2023-05-11 01:09:18 -06:00
|
|
|
err := machineAddresses.Scan(slice)
|
|
|
|
if err != nil {
|
|
|
|
return &netipx.IPSet{}, fmt.Errorf(
|
|
|
|
"failed to read ip from database: %w",
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, ip := range machineAddresses {
|
|
|
|
ips.Add(ip)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ipSet, err := ips.IPSet()
|
|
|
|
if err != nil {
|
|
|
|
return &netipx.IPSet{}, fmt.Errorf(
|
|
|
|
"failed to build IP Set: %w",
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return ipSet, nil
|
|
|
|
}
|