83769ba715
This commits removes the locks used to guard data integrity for the database and replaces them with Transactions, turns out that SQL had a way to deal with this all along. This reduces the complexity we had with multiple locks that might stack or recurse (database, nofitifer, mapper). All notifications and state updates are now triggered _after_ a database change. Signed-off-by: Kristoffer Dalby <kristoffer@tailscale.com>
106 lines
2.4 KiB
Go
106 lines
2.4 KiB
Go
// 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.
|
|
|
|
package db
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/netip"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
|
"go4.org/netipx"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var ErrCouldNotAllocateIP = errors.New("could not find any suitable IP")
|
|
|
|
func (hsdb *HSDatabase) getAvailableIPs() (types.NodeAddresses, error) {
|
|
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) {
|
|
var ips types.NodeAddresses
|
|
var err error
|
|
for _, ipPrefix := range ipPrefixes {
|
|
var ip *netip.Addr
|
|
ip, err = getAvailableIP(rx, ipPrefix)
|
|
if err != nil {
|
|
return ips, err
|
|
}
|
|
ips = append(ips, *ip)
|
|
}
|
|
|
|
return ips, err
|
|
}
|
|
|
|
func getAvailableIP(rx *gorm.DB, ipPrefix netip.Prefix) (*netip.Addr, error) {
|
|
usedIps, err := getUsedIPs(rx)
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
func getUsedIPs(rx *gorm.DB) (*netipx.IPSet, error) {
|
|
// 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
|
|
rx.Model(&types.Node{}).Pluck("ip_addresses", &addressesSlices)
|
|
|
|
var ips netipx.IPSetBuilder
|
|
for _, slice := range addressesSlices {
|
|
var machineAddresses types.NodeAddresses
|
|
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
|
|
}
|