diff --git a/pkg/interval/intervalset.go b/pkg/interval/intervalset.go index 7707d0a..d62e371 100644 --- a/pkg/interval/intervalset.go +++ b/pkg/interval/intervalset.go @@ -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 diff --git a/pkg/netset/ipblock.go b/pkg/netset/ipblock.go index 9536b91..1dcb173 100644 --- a/pkg/netset/ipblock.go +++ b/pkg/netset/ipblock.go @@ -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 @@ -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)) @@ -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 { @@ -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() @@ -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() @@ -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) { @@ -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 +} diff --git a/pkg/netset/ipblock_test.go b/pkg/netset/ipblock_test.go index ffbd05a..83af0ce 100644 --- a/pkg/netset/ipblock_test.go +++ b/pkg/netset/ipblock_test.go @@ -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) {