Skip to content

Commit a40f82f

Browse files
peanballramonskie
authored andcommitted
feat: add IPv6 to IP and CIDRBlock types
Uses net/netip.Addr and .Prefix (CIDR) to ensure the same computation. Tests, similar to IPv4 tests were added.
1 parent 54fd284 commit a40f82f

File tree

5 files changed

+129
-50
lines changed

5 files changed

+129
-50
lines changed

bosh/cidr_block.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,25 @@ func (c CIDRBlock) GetLastIP() IP {
4242
uint32Four += mask
4343
binary.BigEndian.PutUint32(four[:], uint32Four)
4444
return IP{netip.AddrFrom4(four)}
45+
} else if a.Is6() || a.Is4In6() {
46+
six := a.As16()
47+
hi := binary.BigEndian.Uint64(six[:8])
48+
lo := binary.BigEndian.Uint64(six[8:])
49+
masklen := c.cidr.Addr().BitLen() - c.cidr.Bits()
50+
51+
var maskhi uint64
52+
var masklo uint64
53+
if masklen > 64 {
54+
maskhi = uint64(math.Pow(2, float64(masklen-64))) - 1
55+
masklo = uint64(math.Pow(2, float64(64))) - 1
56+
} else {
57+
masklo = uint64(math.Pow(2, float64(masklen))) - 1
58+
}
59+
60+
binary.BigEndian.PutUint64(six[:], hi+maskhi)
61+
binary.BigEndian.PutUint64(six[8:], lo+masklo)
62+
return IP{netip.AddrFrom16(six)}
4563
}
64+
4665
panic("not implemented")
4766
}

bosh/cidr_block_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,64 @@ var _ = Describe("CIDRBlock", func() {
7171
})
7272
})
7373
})
74+
Context("v6", func() {
75+
BeforeEach(func() {
76+
var err error
77+
cidrBlock, err = bosh.ParseCIDRBlock("2001:db8:cf::/80")
78+
Expect(err).NotTo(HaveOccurred())
79+
})
80+
81+
Describe("GetFirstIP", func() {
82+
It("returns the first ip of the cidr block", func() {
83+
ip := cidrBlock.GetFirstIP()
84+
Expect(ip.String()).To(Equal("2001:db8:cf::"))
85+
})
86+
})
87+
88+
Describe("GetNthIP", func() {
89+
It("returns the nth ip of the cidr block", func() {
90+
ip := cidrBlock.GetNthIP(6)
91+
Expect(ip.String()).To(Equal("2001:db8:cf::6"))
92+
})
93+
})
94+
95+
Describe("GetLastIP", func() {
96+
It("returns the first ip of the cidr block", func() {
97+
ip := cidrBlock.GetLastIP()
98+
Expect(ip.String()).To(Equal("2001:db8:cf::ffff:ffff:ffff"))
99+
})
100+
})
101+
102+
Describe("ParseCIDRBlock", func() {
103+
Context("failure cases", func() {
104+
Context("when input string is not a valid CIDR block", func() {
105+
It("returns an error", func() {
106+
_, err := bosh.ParseCIDRBlock("whatever")
107+
Expect(err).To(MatchError(ContainSubstring("no '/'")))
108+
})
109+
})
110+
111+
Context("when input string contains an invalid ip", func() {
112+
It("returns an error", func() {
113+
_, err := bosh.ParseCIDRBlock("not-an-ip/96")
114+
Expect(err).To(MatchError(ContainSubstring("unable to parse IP")))
115+
})
116+
})
117+
118+
Context("when input string contains mask bits which are not an integer", func() {
119+
It("returns an error", func() {
120+
_, err := bosh.ParseCIDRBlock("2001:db8:cf::/not-mask-bits")
121+
Expect(err).To(MatchError(ContainSubstring(`bad bits after slash: "not-mask-bits"`)))
122+
})
123+
})
124+
125+
Context("when input string contains mask bits which are out of range", func() {
126+
It("returns an error", func() {
127+
_, err := bosh.ParseCIDRBlock("2001:db8:cf::/243")
128+
Expect(err).To(MatchError(ContainSubstring("prefix length out of range")))
129+
})
130+
})
131+
})
132+
})
133+
})
74134
})

bosh/ip_test.go

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,89 +9,89 @@ import (
99

1010
var _ = Describe("IP", func() {
1111
Context("v4", func() {
12-
Describe("ParseIP", func() {
13-
It("returns an IP object that represents IP from string", func() {
14-
ip, err := bosh.ParseIP("10.0.16.255")
15-
Expect(err).NotTo(HaveOccurred())
16-
Expect(ip.String()).To(Equal("10.0.16.255"))
17-
})
18-
19-
Context("failure cases", func() {
20-
It("returns an error if it cannot parse ip", func() {
21-
_, err := bosh.ParseIP("not valid")
22-
Expect(err).To(MatchError(ContainSubstring("unable to parse IP")))
12+
Describe("ParseIP", func() {
13+
It("returns an IP object that represents IP from string", func() {
14+
ip, err := bosh.ParseIP("10.0.16.255")
15+
Expect(err).NotTo(HaveOccurred())
16+
Expect(ip.String()).To(Equal("10.0.16.255"))
2317
})
2418

25-
It("returns an error if ip parts are not digits", func() {
26-
_, err := bosh.ParseIP("x.x.x.x")
27-
Expect(err).To(MatchError(ContainSubstring("unexpected character")))
28-
})
19+
Context("failure cases", func() {
20+
It("returns an error if it cannot parse ip", func() {
21+
_, err := bosh.ParseIP("not valid")
22+
Expect(err).To(MatchError(ContainSubstring("unable to parse IP")))
23+
})
2924

30-
It("returns an error if ip parts are out of the allowed range", func() {
31-
_, err := bosh.ParseIP("999.999.999.999")
32-
Expect(err).To(MatchError(ContainSubstring("IPv4 field has value >255")))
33-
})
25+
It("returns an error if ip parts are not digits", func() {
26+
_, err := bosh.ParseIP("x.x.x.x")
27+
Expect(err).To(MatchError(ContainSubstring("unexpected character")))
28+
})
29+
30+
It("returns an error if ip parts are out of the allowed range", func() {
31+
_, err := bosh.ParseIP("999.999.999.999")
32+
Expect(err).To(MatchError(ContainSubstring("IPv4 field has value >255")))
33+
})
3434

35-
It("returns an error if ip has too many parts", func() {
36-
_, err := bosh.ParseIP("1.1.1.1.1.1.1")
37-
Expect(err).To(MatchError(ContainSubstring("IPv4 address too long")))
35+
It("returns an error if ip has too many parts", func() {
36+
_, err := bosh.ParseIP("1.1.1.1.1.1.1")
37+
Expect(err).To(MatchError(ContainSubstring("IPv4 address too long")))
38+
})
3839
})
3940
})
40-
})
4141

42-
Describe("Add", func() {
43-
It("returns an IP object that represents IP offsetted by 1", func() {
44-
ip, err := bosh.ParseIP("10.0.16.1")
45-
ip = ip.Add(1)
46-
Expect(err).NotTo(HaveOccurred())
47-
Expect(ip.String()).To(Equal("10.0.16.2"))
42+
Describe("Add", func() {
43+
It("returns an IP object that represents IP offsetted by 1", func() {
44+
ip, err := bosh.ParseIP("10.0.16.1")
45+
ip = ip.Add(1)
46+
Expect(err).NotTo(HaveOccurred())
47+
Expect(ip.String()).To(Equal("10.0.16.2"))
48+
})
4849
})
49-
})
5050

51-
Describe("Subtract", func() {
52-
It("returns an IP object that represents IP offsetted by -1", func() {
53-
ip, err := bosh.ParseIP("10.0.16.2")
54-
ip = ip.Subtract(1)
55-
Expect(err).NotTo(HaveOccurred())
56-
Expect(ip.String()).To(Equal("10.0.16.1"))
51+
Describe("Subtract", func() {
52+
It("returns an IP object that represents IP offsetted by -1", func() {
53+
ip, err := bosh.ParseIP("10.0.16.2")
54+
ip = ip.Subtract(1)
55+
Expect(err).NotTo(HaveOccurred())
56+
Expect(ip.String()).To(Equal("10.0.16.1"))
57+
})
5758
})
58-
})
5959

60-
Describe("String", func() {
61-
It("returns a string representation of IP object", func() {
62-
ip, err := bosh.ParseIP("10.0.16.1")
63-
Expect(err).NotTo(HaveOccurred())
64-
Expect(ip.String()).To(Equal("10.0.16.1"))
60+
Describe("String", func() {
61+
It("returns a string representation of IP object", func() {
62+
ip, err := bosh.ParseIP("10.0.16.1")
63+
Expect(err).NotTo(HaveOccurred())
64+
Expect(ip.String()).To(Equal("10.0.16.1"))
65+
})
6566
})
6667
})
67-
})
6868
Context("v6", func() {
6969
Describe("ParseIP", func() {
7070
It("returns an IP object that represents IP from string", func() {
71-
ip, err := bosh.ParseIP("2001:db8:cf:0:0:ffff:1337")
71+
ip, err := bosh.ParseIP("2001:db8:cf:0:0:0:ffff:1337")
7272
Expect(err).NotTo(HaveOccurred())
7373
Expect(ip.String()).To(Equal("2001:db8:cf::ffff:1337"))
7474
})
7575

7676
Context("failure cases", func() {
7777
It("returns an error if it cannot parse ip", func() {
7878
_, err := bosh.ParseIP("2001:db8:cf::not valid")
79-
Expect(err).To(MatchError(ContainSubstring("unable to parse IP")))
79+
Expect(err).To(MatchError(ContainSubstring("each colon-separated field must have at least one digit")))
8080
})
8181

8282
It("returns an error if ip parts are not digits", func() {
8383
_, err := bosh.ParseIP("2001:db8:cf:x:x:x:x")
84-
Expect(err).To(MatchError(ContainSubstring("unexpected character")))
84+
Expect(err).To(MatchError(ContainSubstring("each colon-separated field must have at least one digit")))
8585
})
8686

8787
It("returns an error if ip parts are out of the allowed range", func() {
8888
_, err := bosh.ParseIP("2001:db8:cf:G::")
89-
Expect(err).To(MatchError(ContainSubstring("IPv6 field has value >F")))
89+
Expect(err).To(MatchError(ContainSubstring("each colon-separated field must have at least one digit")))
9090
})
9191

9292
It("returns an error if ip has too many parts", func() {
9393
_, err := bosh.ParseIP("2001:db8:cf:0:0:0:ffff:ffff:ffff")
94-
Expect(err).To(MatchError(ContainSubstring("IPv6 address too long")))
94+
Expect(err).To(MatchError(ContainSubstring("trailing garbage after address")))
9595
})
9696
})
9797
})

cloudconfig/aws/ops_generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ iso_az_subnet_id_mapping:
180180
"us-east-1a": "****",
181181
}
182182
_, err := opsGenerator.GenerateVars(incomingState)
183-
Expect(err).To(MatchError(`"****" cannot parse CIDR block`))
183+
Expect(err).To(MatchError(`netip.ParsePrefix("****"): no '/'`))
184184
})
185185
})
186186

cloudconfig/cloudstack/ops_generator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ static_subnet3: 10.0.47.55-10.0.47.254
108108
"subnet": "****",
109109
}
110110
_, err := opsGenerator.GenerateVars(incomingState)
111-
Expect(err).To(MatchError(`"****" cannot parse CIDR block`))
111+
Expect(err).To(MatchError(`netip.ParsePrefix("****"): no '/'`))
112112
})
113113
})
114114

0 commit comments

Comments
 (0)