Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Random IP Assignment #983

Closed
wants to merge 10 commits into from
Closed
4 changes: 4 additions & 0 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ ip_prefixes:
- fd7a:115c:a1e0::/48
- 100.64.0.0/10

# Whether to randomize the ip address assignemnt or to
blobcode marked this conversation as resolved.
Show resolved Hide resolved
# assign sequentially.
randomize_address: false

# DERP is a relay system that Tailscale uses when a direct
# connection cannot be established.
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
Expand Down
6 changes: 5 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Config struct {
EphemeralNodeInactivityTimeout time.Duration
NodeUpdateCheckInterval time.Duration
IPPrefixes []netip.Prefix
RandomizeIP bool
PrivateKeyPath string
NoisePrivateKeyPath string
BaseDomain string
Expand Down Expand Up @@ -165,6 +166,8 @@ func LoadConfig(path string, isFile bool) error {
viper.SetDefault("derp.server.enabled", false)
viper.SetDefault("derp.server.stun.enabled", true)

viper.SetDefault("randomize_ip", false)
blobcode marked this conversation as resolved.
Show resolved Hide resolved

viper.SetDefault("unix_socket", "/var/run/headscale.sock")
viper.SetDefault("unix_socket_permission", "0o770")

Expand Down Expand Up @@ -540,7 +543,8 @@ func GetHeadscaleConfig() (*Config, error) {
GRPCAllowInsecure: viper.GetBool("grpc_allow_insecure"),
DisableUpdateCheck: viper.GetBool("disable_check_updates"),

IPPrefixes: prefixes,
IPPrefixes: prefixes,
RandomizeIP: viper.GetBool("randomize_ip"),
PrivateKeyPath: AbsolutePathFromConfigPath(
viper.GetString("private_key_path"),
),
Expand Down
10 changes: 9 additions & 1 deletion machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,15 @@ func (h *Headscale) RegisterMachine(machine Machine,
h.ipAllocationMutex.Lock()
defer h.ipAllocationMutex.Unlock()

ips, err := h.getAvailableIPs()
var ips MachineAddresses
var err error

if h.cfg.RandomizeIP {
ips, err = h.getRandomAvailableIPs()
} else {
ips, err = h.getAvailableIPs()
}

if err != nil {
log.Error().
Caller().
Expand Down
28 changes: 28 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"encoding/json"
"fmt"
"io/fs"
mrand "math/rand"
"net"
"net/netip"
"os"
Expand All @@ -20,6 +21,7 @@ import (
"regexp"
"strconv"
"strings"
"time"

"github.com/rs/zerolog/log"
"github.com/spf13/viper"
Expand Down Expand Up @@ -155,6 +157,22 @@ func (h *Headscale) getAvailableIPs() (MachineAddresses, error) {
return ips, err
}

func (h *Headscale) getRandomAvailableIPs() (MachineAddresses, error) {
var ips MachineAddresses
var err error
ipPrefixes := h.cfg.IPPrefixes
for _, ipPrefix := range ipPrefixes {
var ip *netip.Addr
ip, err = h.getAvailableIP(ipPrefix)
if err != nil {
return shuffleIPs(ips), err
}
ips = append(ips, *ip)
}

return shuffleIPs(ips), err

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error is being handled in the loop, it will always be nil and it should be explicit as such.

return shuffleIPs(ips), nil

}

func GetIPPrefixEndpoints(na netip.Prefix) (netip.Addr, netip.Addr) {
var network, broadcast netip.Addr
ipRange := netipx.RangeOfPrefix(na)
Expand Down Expand Up @@ -230,6 +248,16 @@ func (h *Headscale) getUsedIPs() (*netipx.IPSet, error) {
return ipSet, nil
}

func shuffleIPs(ips MachineAddresses) MachineAddresses {
// Seeding needed to ensure random order between runs.
mrand.Seed(time.Now().UnixNano())
mrand.Shuffle(len(ips), func(i, j int) {
ips[i], ips[j] = ips[j], ips[i]
})

return ips
}

func tailNodesToString(nodes []*tailcfg.Node) string {
temp := make([]string, len(nodes))

Expand Down
11 changes: 11 additions & 0 deletions utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ func (s *Suite) TestGetAvailableIp(c *check.C) {
c.Assert(ips[0].String(), check.Equals, expected.String())
}

func (s *Suite) TestGetRandomAvailableIp(c *check.C) {
ips, err := app.getRandomAvailableIPs()

c.Assert(err, check.IsNil)

expected := netip.MustParseAddr("10.27.0.1")

c.Assert(len(ips), check.Equals, 1)
c.Assert(ips[0].String(), check.Equals, expected.String())
}

func (s *Suite) TestGetUsedIps(c *check.C) {
ips, err := app.getAvailableIPs()
c.Assert(err, check.IsNil)
Expand Down