Skip to content

Commit

Permalink
IPBlock convenience functions (#76)
Browse files Browse the repository at this point in the history
* ipblock convenience functions

* more functions

* touching

* review comments

* fix touching method

* doc

* tests

* better implementation

* another test
  • Loading branch information
YairSlobodin1 authored Oct 8, 2024
1 parent 85a8048 commit 1e27bae
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 1 deletion.
7 changes: 7 additions & 0 deletions pkg/interval/intervalset.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ func (c *CanonicalSet) Min() int64 {
return c.intervalSet[0].Start()
}

func (c *CanonicalSet) Max() int64 {
if len(c.intervalSet) == 0 {
log.Panic("cannot take max from empty interval set")
}
return c.intervalSet[c.NumIntervals()-1].End()
}

// IsEmpty returns true if the CanonicalSet is empty
func (c *CanonicalSet) IsEmpty() bool {
return len(c.intervalSet) == 0
Expand Down
98 changes: 97 additions & 1 deletion pkg/netset/ipblock.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const (
// CidrAll represents the CIDR for all addresses "0.0.0.0/0"
CidrAll = "0.0.0.0/0"

FirstIPAddressString = "0.0.0.0"
LastIPAddressString = "255.255.255.255"

// internal const below
ipBase = 10
ipMask = 0xffffffff
Expand Down Expand Up @@ -144,7 +147,12 @@ func (b *IPBlock) ipCount() int {
return int(b.ipRange.CalculateSize())
}

// Split returns a set of IpBlock objects, each with a single range of ips
// IsSingleIPAddress returns true if this ipblock is a single IP address
func (b *IPBlock) IsSingleIPAddress() bool {
return b.ipRange.IsSingleNumber()
}

// Split returns a set of IPBlock objects, each with a single range of ips
func (b *IPBlock) Split() []*IPBlock {
intervals := b.ipRange.Intervals()
res := make([]*IPBlock, len(intervals))
Expand All @@ -156,6 +164,18 @@ func (b *IPBlock) Split() []*IPBlock {
return res
}

// SplitToCidrs returns a slice of IPBlocks, each representing a single CIDR
func (b *IPBlock) SplitToCidrs() []*IPBlock {
cidrs := make([]*IPBlock, 0)
for _, ipRange := range b.ipRange.Intervals() {
for _, cidrString := range intervalToCidrList(ipRange) {
ipblock, _ := IPBlockFromCidr(cidrString)
cidrs = append(cidrs, ipblock)
}
}
return cidrs
}

// int64ToIP4 returns a string of an ip address from an input integer ip value
func int64ToIP4(ipInt int64) string {
if ipInt < 0 || ipInt > math.MaxUint32 {
Expand Down Expand Up @@ -244,6 +264,16 @@ func IPBlockFromCidrOrAddress(s string) (*IPBlock, error) {
return IPBlockFromIPAddress(s)
}

// IPBlockFromIPRange returns a new IPBlock object that contains startIP-endIP
func IPBlockFromIPRange(startIP, endIP *IPBlock) (*IPBlock, error) {
if !startIP.IsSingleIPAddress() || !endIP.IsSingleIPAddress() {
return nil, fmt.Errorf("both startIP and endIP should be a single IP address")
}
return &IPBlock{
ipRange: interval.New(startIP.ipRange.Min(), endIP.ipRange.Min()).ToSet(),
}, nil
}

// IPBlockFromCidrList returns IPBlock object from multiple CIDRs given as list of strings
func IPBlockFromCidrList(cidrsList []string) (*IPBlock, error) {
res := NewIPBlock()
Expand Down Expand Up @@ -338,6 +368,51 @@ func (b *IPBlock) FirstIPAddress() string {
return int64ToIP4(b.ipRange.Min())
}

// FirstIPAddressObject returns the first IP Address for this IPBlock
func (b *IPBlock) FirstIPAddressObject() *IPBlock {
ipNum := b.ipRange.Min()
return &IPBlock{
ipRange: interval.New(ipNum, ipNum).ToSet(),
}
}

// LastIPAddress returns the last IP Address string for this IPBlock
func (b *IPBlock) LastIPAddress() string {
return int64ToIP4(b.ipRange.Max())
}

// LastIPAddressObject returns the last IP Address for this IPBlock
func (b *IPBlock) LastIPAddressObject() *IPBlock {
ipNum := b.ipRange.Max()
return &IPBlock{
ipRange: interval.New(ipNum, ipNum).ToSet(),
}
}

// NextIP returns the next ip address after this IPBlock
func (b *IPBlock) NextIP() (*IPBlock, error) {
if GetLastIPAddress().IsSubset(b) {
return nil, fmt.Errorf("%s is contained in ipblock", LastIPAddressString)
}
lastIP := b.LastIPAddressObject()
ipNum := lastIP.ipRange.Min() + 1
return &IPBlock{
ipRange: interval.New(ipNum, ipNum).ToSet(),
}, nil
}

// PreviousIP returns the previous ip address before this IPBlock
func (b *IPBlock) PreviousIP() (*IPBlock, error) {
if GetFirstIPAddress().IsSubset(b) {
return nil, fmt.Errorf("%s is contained in IPBlock", FirstIPAddressString)
}
firstIP := b.FirstIPAddressObject()
ipNum := firstIP.ipRange.Min() - 1
return &IPBlock{
ipRange: interval.New(ipNum, ipNum).ToSet(),
}, nil
}

func intervalToCidrList(ipRange interval.Interval) []string {
start := ipRange.Start()
end := ipRange.End()
Expand Down Expand Up @@ -396,6 +471,18 @@ func GetCidrAll() *IPBlock {
return res
}

// GetFirstIPAddress returns IPBlock object of 0.0.0.0
func GetFirstIPAddress() *IPBlock {
res, _ := IPBlockFromIPAddress(FirstIPAddressString)
return res
}

// GetLastIPAddress returns IPBlock object of 255.255.255.255
func GetLastIPAddress() *IPBlock {
res, _ := IPBlockFromIPAddress(LastIPAddressString)
return res
}

// PrefixLength returns the cidr's prefix length, assuming the ipBlock is exactly one cidr.
// Prefix length specifies the number of bits in the IP address that are to be used as the subnet mask.
func (b *IPBlock) PrefixLength() (int64, error) {
Expand All @@ -415,3 +502,12 @@ func (b *IPBlock) String() string {
}
return b.ToCidrListString()
}

// TouchingIPRanges returns true if this and other ipblocks objects are touching.
// assumption: both IPBlocks represent a single IP range
func (b *IPBlock) TouchingIPRanges(other *IPBlock) (bool, error) {
if b.ipRange.NumIntervals() != 1 || other.ipRange.NumIntervals() != 1 {
return false, fmt.Errorf("both ipblocks should be a single IP range")
}
return (!b.Overlap(other) && b.Union(other).ipRange.NumIntervals() == 1), nil
}
38 changes: 38 additions & 0 deletions pkg/netset/ipblock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,44 @@ func TestOps(t *testing.T) {

intersect2 := minus.Intersect(intersect)
require.True(t, intersect2.IsEmpty())

ipb3, err := ipb2.NextIP()
ipb4, _ := netset.IPBlockFromCidrOrAddress("1.2.3.5")
require.Nil(t, err)
require.Equal(t, ipb3, ipb4)

ipb5, err := ipb3.PreviousIP()
require.Nil(t, err)
require.Equal(t, ipb2, ipb5)

ipb6, err := ipb1.NextIP() // ipb6 = 1.2.4.0
ipb7, _ := netset.IPBlockFromCidrOrAddress("1.2.4.0")
require.Nil(t, err)
require.Equal(t, ipb6, ipb7)

require.False(t, ipb1.IsSingleIPAddress())
require.True(t, ipb2.IsSingleIPAddress())

ipb8, err := ipb7.PreviousIP() // ipb8 = 1.2.3.255
require.Nil(t, err)
require.Equal(t, ipb8, ipb1.LastIPAddressObject())

ipb9, err := netset.IPBlockFromIPRange(ipb4, ipb6)
require.Nil(t, err)
// ipb9 = 1.2.3.5-1.2.4.0. Equal to the union of:
// 1.2.3.5/32, 1.2.3.6/31, 1.2.3.8/29, 1.2.3.16/28,
// 1.2.3.32/27, 1.2.3.64/26, 1.2.3.128/25, 1.2.4.0/32
require.Len(t, ipb9.SplitToCidrs(), 8)

t1, err := ipb9.TouchingIPRanges(ipb2)
require.Nil(t, err)
require.True(t, t1)

t2, err := ipb9.TouchingIPRanges(ipb7)
require.Nil(t, err)
require.False(t, t2)

require.Equal(t, ipb7, ipb7.FirstIPAddressObject())
}

func TestConversions(t *testing.T) {
Expand Down

0 comments on commit 1e27bae

Please sign in to comment.