AdGuardHome/internal/dhcpd/iprange.go
Stanislav Chzhen bea39934bd Pull request 1771: AG-20352-dhcpd-lease-netip-addr
Merge in DNS/adguard-home from AG-20352-dhcpd-lease-netip-addr to master

Squashed commit of the following:

commit 4acd094e2d6ed972bac99cdb671670f6d8e61721
Merge: 51f61c19 df61741f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 23 16:44:17 2023 +0300

    Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr

commit 51f61c193fdd31ee675be5598fc361228e407eb3
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 22 18:25:30 2023 +0300

    dhcpd: fix typo

commit 2e64ad55475957925d2a3010c649e0adc5f18c4f
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 22 17:20:31 2023 +0300

    dhcpd: add todo

commit 668d4f62fd2c5b2e168025bf0b6bb36d7b617c80
Merge: 0020006e 306c1983
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 22 15:09:38 2023 +0300

    Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr

commit 0020006e89f336dc58db1a2ca3ce90d2e7a5ca16
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 22 15:08:58 2023 +0300

    all: imp code

commit 9a77f79869cdbde6de760734c0d8cf504e0464ef
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 20 18:13:35 2023 +0300

    dhcpd: add todo

commit 638c4ce2af72235bf065a6492d58f3f1b1e7644a
Merge: c82b18f1 48431f8b
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 20 13:57:19 2023 +0300

    Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr

commit c82b18f1408d9e353aec517f4283e2f3eb260890
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Mon Mar 20 13:56:31 2023 +0300

    all: imp code

commit 27e518120024103c292ac1cf134c6801fffc967e
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Mar 17 12:14:02 2023 +0300

    dhcpd: imp tests

commit 8e919b0ceb0b20d1935587e717c7965cd8a33ad9
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Fri Mar 17 11:02:50 2023 +0300

    dhcpd: add tests

commit 78ddefa73a255509af1c788147d6b1c332bb66ba
Merge: c68e85c4 9f7a582d
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 16 14:15:24 2023 +0300

    Merge branch 'master' into AG-20352-dhcpd-lease-netip-addr

commit c68e85c40947b6c83516424a2bd7af89b99447b4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Thu Mar 16 14:14:43 2023 +0300

    all: add tests

commit f338086309a68c4b71036fa14f757e39358702d4
Author: Stanislav Chzhen <s.chzhen@adguard.com>
Date:   Wed Mar 15 12:44:06 2023 +0300

    all: dhcpd lease netip addr
2023-03-23 16:52:01 +03:00

120 lines
2.9 KiB
Go

package dhcpd
import (
"fmt"
"math"
"math/big"
"net"
"github.com/AdguardTeam/golibs/errors"
)
// ipRange is an inclusive range of IP addresses. A nil range is a range that
// doesn't contain any IP addresses.
//
// It is safe for concurrent use.
//
// TODO(a.garipov): Perhaps create an optimized version with uint32 for IPv4
// ranges? Or use one of uint128 packages?
//
// TODO(e.burkov): Use netip.Addr.
type ipRange struct {
start *big.Int
end *big.Int
}
// maxRangeLen is the maximum IP range length. The bitsets used in servers only
// accept uints, which can have the size of 32 bit.
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 net.IP) (r *ipRange, err error) {
defer func() { err = errors.Annotate(err, "invalid ip range: %w") }()
// Make sure that both are 16 bytes long to simplify handling in
// methods.
start, end = start.To16(), end.To16()
startInt := (&big.Int{}).SetBytes(start)
endInt := (&big.Int{}).SetBytes(end)
diff := (&big.Int{}).Sub(endInt, startInt)
if diff.Sign() <= 0 {
return nil, fmt.Errorf("start is greater than or equal to end")
} else if !diff.IsUint64() || diff.Uint64() > maxRangeLen {
return nil, fmt.Errorf("range is too large")
}
r = &ipRange{
start: startInt,
end: endInt,
}
return r, nil
}
// contains returns true if r contains ip.
func (r *ipRange) contains(ip net.IP) (ok bool) {
if r == nil {
return false
}
ipInt := (&big.Int{}).SetBytes(ip.To16())
return r.containsInt(ipInt)
}
// containsInt returns true if r contains ipInt. For internal use only.
func (r *ipRange) containsInt(ipInt *big.Int) (ok bool) {
return ipInt.Cmp(r.start) >= 0 && ipInt.Cmp(r.end) <= 0
}
// ipPredicate is a function that is called on every IP address in
// (*ipRange).find. ip is given in the 16-byte form.
type ipPredicate func(ip net.IP) (ok bool)
// find finds the first IP address in r for which p returns true. ip is in the
// 16-byte form.
func (r *ipRange) find(p ipPredicate) (ip net.IP) {
if r == nil {
return nil
}
ip = make(net.IP, net.IPv6len)
_1 := big.NewInt(1)
for i := (&big.Int{}).Set(r.start); i.Cmp(r.end) <= 0; i.Add(i, _1) {
i.FillBytes(ip)
if p(ip) {
return ip
}
}
return nil
}
// 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 net.IP) (offset uint64, ok bool) {
if r == nil {
return 0, false
}
ip = ip.To16()
ipInt := (&big.Int{}).SetBytes(ip)
if !r.containsInt(ipInt) {
return 0, false
}
offsetInt := (&big.Int{}).Sub(ipInt, r.start)
// Assume that the range was checked against maxRangeLen during
// construction.
return offsetInt.Uint64(), true
}
// String implements the fmt.Stringer interface for *ipRange.
func (r *ipRange) String() (s string) {
return fmt.Sprintf("%s-%s", r.start, r.end)
}