File: addrset.go

package info (click to toggle)
docker.io 28.5.2%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 69,048 kB
  • sloc: sh: 5,867; makefile: 863; ansic: 184; python: 162; asm: 159
file content (201 lines) | stat: -rw-r--r-- 6,929 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// Package addrset implements a set of IP addresses.
package addrset

import (
	"errors"
	"fmt"
	"net"
	"net/netip"
	"strings"

	"github.com/docker/docker/libnetwork/bitmap"
	"github.com/docker/docker/libnetwork/internal/netiputil"
	"github.com/docker/docker/libnetwork/ipbits"
)

var (
	// ErrNotAvailable is returned when no more addresses are available to set
	ErrNotAvailable = errors.New("address not available")
	// ErrAllocated is returned when the specific address requested is already allocated
	ErrAllocated = errors.New("address already allocated")
)

const (
	// maxBitsPerBitmap is the max size for a single bitmap in the address set.
	//
	// [bitmap.Bitmap] is initialised with a uint64 num-bits. So, it can't contain
	// enough bits for a 64-bit range (it's one bit short, the last address in the
	// range can't be represented). If that's fixed, this max can be increased, but
	// addrsPerBitmap() will need updating to deal with the overflow.
	//
	// A max of 63-bits means a 64-bit address range (the norm for IPv6) is
	// represented by up-to two bitmaps.
	maxBitsPerBitmap = 63
	// minPrefixLen is the prefix length corresponding to maxBitsPerBitmap
	minPrefixLen = (net.IPv6len * 8) - maxBitsPerBitmap
)

// AddrSet is a set of IP addresses.
type AddrSet struct {
	pool    netip.Prefix
	bitmaps map[netip.Prefix]*bitmap.Bitmap
}

// New returns an AddrSet for the range of addresses in pool.
func New(pool netip.Prefix) *AddrSet {
	return &AddrSet{
		pool:    pool.Masked(),
		bitmaps: map[netip.Prefix]*bitmap.Bitmap{},
	}
}

// Add adds address addr to the set. If addr is already in the set, it returns a
// wrapped [ErrAllocated]. If addr is not in the set's address range, it returns
// an error.
func (as *AddrSet) Add(addr netip.Addr) error {
	if !as.pool.Contains(addr) {
		return fmt.Errorf("cannot add %s to '%s'", addr, as.pool)
	}
	bm, _, err := as.getBitmap(addr)
	if err != nil {
		return fmt.Errorf("finding bitmap for %s in '%s': %w", addr, as.pool, err)
	}
	bit := netiputil.HostID(addr, as.prefixLenPerBitmap())
	if err := bm.Set(bit); err != nil {
		return fmt.Errorf("setting bit %d for %s in pool '%s': %w", bit, addr, as.pool, mapErr(err))
	}
	return nil
}

// AddAny adds an arbitrary address to the set, and returns that address. Or, if
// no addresses are available, it returns a wrapped [ErrNotAvailable].
//
// If the address set's pool contains fewer than 1<<maxBitsPerBitmap addresses,
// AddAny will add any address from the entire set. If the pool is bigger than
// that, AddAny will only consider the first 1<<maxBitsPerBitmap addresses. If
// those are all allocated, it returns [ErrNotAvailable].
//
// When serial=true, the set is scanned starting from the address following
// the address most recently set by [AddrSet.AddAny] (or [AddrSet.AddAnyInRange]
// if the range is in the same 1<<maxBitsPerBitmap .
func (as *AddrSet) AddAny(serial bool) (netip.Addr, error) {
	// Only look at the first bitmap. It either contains the whole address range or
	// the first 1<<maxBitsPerBitmap addresses, which is a lot. (So, no need to
	// search other bitmaps, or work out if more bitmaps could be created).
	bm, _, err := as.getBitmap(as.pool.Addr())
	if err != nil {
		return netip.Addr{}, fmt.Errorf("no bitmap to add-any to '%s': %w", as.pool.Addr(), err)
	}
	ordinal, err := bm.SetAny(serial)
	if err != nil {
		return netip.Addr{}, fmt.Errorf("add-any to '%s': %w", as.pool.Addr(), mapErr(err))
	}
	return ipbits.Add(as.pool.Addr(), ordinal, 0), nil
}

// AddAnyInRange adds an arbitrary address from ipr to the set, and returns that
// address. Or, if no addresses are available, it returns a wrapped [ErrNotAvailable].
// If ipr is not fully contained within the set's range, it returns an error.
//
// When serial=true, the set is scanned starting from the address following
// the address most recently set by [AddrSet.AddAny] or [AddrSet.AddAnyInRange].
func (as *AddrSet) AddAnyInRange(ipr netip.Prefix, serial bool) (netip.Addr, error) {
	if !as.pool.Contains(ipr.Addr()) || ipr.Bits() < as.pool.Bits() {
		return netip.Addr{}, fmt.Errorf("add-any, range '%s' is not in subnet '%s'", ipr, as.pool)
	}
	iprMasked := ipr.Masked()
	bm, bmKey, err := as.getBitmap(iprMasked.Addr())
	if err != nil {
		return netip.Addr{}, fmt.Errorf("no bitmap to add-any in '%s' range '%s': %w", as.pool, ipr, err)
	}
	var ordinal uint64
	if ipr.Bits() <= bmKey.Bits() {
		ordinal, err = bm.SetAny(serial)
	} else {
		start, end := netiputil.SubnetRange(bmKey, iprMasked)
		ordinal, err = bm.SetAnyInRange(start, end, serial)
	}
	if err != nil {
		return netip.Addr{}, fmt.Errorf("add-any in '%s' range '%s': %w", as.pool, ipr, mapErr(err))
	}
	return ipbits.Add(bmKey.Addr(), ordinal, 0), nil
}

// Remove removes addr from the set or, if addr is not in the set's address range it
// returns an error. If addr is not in the set, it returns nil (removing an address
// that's not in the set is not an error).
func (as *AddrSet) Remove(addr netip.Addr) error {
	if !as.pool.Contains(addr) {
		return fmt.Errorf("%s cannot be removed from '%s'", addr, as.pool)
	}
	bm, bmKey, err := as.getBitmap(addr)
	if err != nil {
		return fmt.Errorf("remove '%s' from '%s': %w", addr, as.pool, err)
	}
	bit := netiputil.HostID(addr, as.prefixLenPerBitmap())
	if err := bm.Unset(bit); err != nil {
		return fmt.Errorf("unset bit %d for '%s' in '%s': %w", bit, addr, as.pool, err)
	}
	if bm.Bits()-bm.Unselected() == 0 {
		delete(as.bitmaps, bmKey)
	}
	return nil
}

// String returns a description of the address set.
func (as *AddrSet) String() string {
	if len(as.bitmaps) == 0 {
		return "empty address set"
	}
	if as.pool.Addr().BitLen()-as.pool.Bits() <= maxBitsPerBitmap {
		return as.bitmaps[as.pool].String()
	}
	bmStrings := make([]string, 0, len(as.bitmaps))
	for bmKey, bm := range as.bitmaps {
		bmStrings = append(bmStrings, fmt.Sprintf("range %s %s", bmKey, bm))
	}
	return strings.Join(bmStrings, " ")
}

func (as *AddrSet) getBitmap(addr netip.Addr) (*bitmap.Bitmap, netip.Prefix, error) {
	bits := as.pool.Addr().BitLen() - as.pool.Bits()
	if bits > maxBitsPerBitmap {
		bits = maxBitsPerBitmap
	}
	bmKey, err := addr.Prefix(as.pool.Addr().BitLen() - bits)
	if err != nil {
		return nil, netip.Prefix{}, err
	}
	bm, ok := as.bitmaps[bmKey]
	if !ok {
		bm = bitmap.New(as.addrsPerBitmap())
		as.bitmaps[bmKey] = bm
	}
	return bm, bmKey, nil
}

func (as *AddrSet) addrsPerBitmap() uint64 {
	bits := as.pool.Addr().BitLen() - as.pool.Bits()
	if bits > maxBitsPerBitmap {
		bits = maxBitsPerBitmap
	}
	return uint64(1) << bits
}

func (as *AddrSet) prefixLenPerBitmap() uint {
	bits := as.pool.Bits()
	if as.pool.Addr().Is6() && bits < minPrefixLen {
		return minPrefixLen
	}
	return uint(bits)
}

func mapErr(err error) error {
	if errors.Is(err, bitmap.ErrBitAllocated) {
		return ErrAllocated
	}
	if errors.Is(err, bitmap.ErrNoBitAvailable) {
		return ErrNotAvailable
	}
	return err
}