AdGuardHome/internal/dhcpsvc/iprange.go
Ainar Garipov 25918e56fa Pull request 2100: v0.107.42-rc
Squashed commit of the following:

commit 284190f748345c7556e60b67f051ec5f6f080948
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Wed Dec 6 19:36:00 2023 +0300

    all: sync with master; upd chlog
2023-12-07 17:23:00 +03:00

98 lines
2.7 KiB
Go

package dhcpsvc
import (
"encoding/binary"
"fmt"
"math"
"math/big"
"net/netip"
"github.com/AdguardTeam/golibs/errors"
)
// ipRange is an inclusive range of IP addresses. A zero range doesn't contain
// any IP addresses.
//
// It is safe for concurrent use.
type ipRange struct {
start netip.Addr
end netip.Addr
}
// maxRangeLen is the maximum IP range length. The bitsets used in servers only
// accept uints, which can have the size of 32 bit.
//
// TODO(a.garipov, e.burkov): Reconsider the value for IPv6.
const maxRangeLen = math.MaxUint32
// newIPRange creates a new IP address range. start must be less than end. The
// resulting range must not be greater than maxRangeLen.
func newIPRange(start, end netip.Addr) (r ipRange, err error) {
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
switch false {
case start.Is4() == end.Is4():
return ipRange{}, fmt.Errorf("%s and %s must be within the same address family", start, end)
case start.Less(end):
return ipRange{}, fmt.Errorf("start %s is greater than or equal to end %s", start, end)
default:
diff := (&big.Int{}).Sub(
(&big.Int{}).SetBytes(end.AsSlice()),
(&big.Int{}).SetBytes(start.AsSlice()),
)
if !diff.IsUint64() || diff.Uint64() > maxRangeLen {
return ipRange{}, fmt.Errorf("range length must be within %d", uint32(maxRangeLen))
}
}
return ipRange{
start: start,
end: end,
}, nil
}
// contains returns true if r contains ip.
func (r ipRange) contains(ip netip.Addr) (ok bool) {
// Assume that the end was checked to be within the same address family as
// the start during construction.
return r.start.Is4() == ip.Is4() && !ip.Less(r.start) && !r.end.Less(ip)
}
// ipPredicate is a function that is called on every IP address in
// [ipRange.find].
type ipPredicate func(ip netip.Addr) (ok bool)
// find finds the first IP address in r for which p returns true. It returns an
// empty [netip.Addr] if there are no addresses that satisfy p.
//
// TODO(e.burkov): Use.
func (r ipRange) find(p ipPredicate) (ip netip.Addr) {
for ip = r.start; !r.end.Less(ip); ip = ip.Next() {
if p(ip) {
return ip
}
}
return netip.Addr{}
}
// offset returns the offset of ip from the beginning of r. It returns 0 and
// false if ip is not in r.
func (r ipRange) offset(ip netip.Addr) (offset uint64, ok bool) {
if !r.contains(ip) {
return 0, false
}
startData, ipData := r.start.As16(), ip.As16()
be := binary.BigEndian
// Assume that the range length was checked against maxRangeLen during
// construction.
return be.Uint64(ipData[8:]) - be.Uint64(startData[8:]), true
}
// String implements the fmt.Stringer interface for *ipRange.
func (r ipRange) String() (s string) {
return fmt.Sprintf("%s-%s", r.start, r.end)
}