File: ipv4net.rb

package info (click to toggle)
ruby-netaddr 2.0.6-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 296 kB
  • sloc: ruby: 2,022; makefile: 7
file content (262 lines) | stat: -rw-r--r-- 8,128 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
module NetAddr
	
	#IPv4Net represents an IPv4 network. 
	class IPv4Net
		
		#arguments:
		#* ip - an IPv4 object
		#* m32 - a Mask32 object. will default to a /32 if nil
		def initialize(ip,m32)
			if (!ip.kind_of?(IPv4))
				raise ArgumentError, "Expected an IPv4 object for 'ip' but got a #{ip.class}."
			elsif (m32 != nil && !m32.kind_of?(Mask32))
				raise ArgumentError, "Expected a Mask32 object for 'm32' but got a #{m32.class}."
			end
			
			if (m32 == nil)
				m32 = Mask32.new(32)
			end
			@m32 = m32
			@base = IPv4.new(ip.addr & m32.mask)
		end
		
		# parse will create an IPv4Net from its string representation. Will default to a /32 netmask if not specified.
		# Throws ValidationError on error.
		def IPv4Net.parse(net)
			net = net.strip
			m32 = nil
			if (net.include?("/")) # cidr format
				addr,mask = net.split("/")
				m32 = Mask32.parse(mask)
			elsif (net.include?(" ") ) # extended format
				addr,mask = net.split(' ')
				m32 = Mask32.parse(mask)
			else
				addr = net
			end
			ip = IPv4.parse(addr)
			return IPv4Net.new(ip,m32)
		end

		# extended returns the IPv4Net in extended format (eg. x.x.x.x y.y.y.y)
		def extended()
			return @base.to_s + " " + Util.int_to_IPv4(@m32.mask)
		end
		
		# cmp compares equality with another IPv4Net. Return:
		# * 1 if this IPv4Net is numerically greater
		# * 0 if the two are equal
		# * -1 if this IPv4Net is numerically less
		#
		# The comparison is initially performed on using the cmp() method of the network address, however, in cases where the network
		# addresses are identical then the netmasks will be compared with the cmp() method of the netmask. 
		def cmp(other)
			if (!other.kind_of?(IPv4Net))
				raise ArgumentError, "Expected an IPv4Net object for 'other' but got a #{other.class}."
			end
			cmp = self.network.cmp(other.network)
			if (cmp != 0)
				return cmp
			end
			return self.netmask.cmp(other.netmask)
		end
		
		#contains returns true if the IPv4Net contains the IPv4
		def contains(ip)
			if (!ip.kind_of?(IPv4))
				raise ArgumentError, "Expected an IPv4 object for 'ip' but got a #{ip.class}."
			end
			if (@base.addr == ip.addr & @m32.mask)
				return true
			end
			return false
		end
		
		# fill returns a copy of the given Array, stripped of any networks which are not subnets of this IPv4Net
		# and with any missing gaps filled in.
		def fill(list)
			list = Util.filter_IPv4Net(list)
			return Util.fill(self,list)
		end
		
		# netmask returns the Mask32 object representing the netmask for this network
		def netmask()
			@m32
		end
			
		# network returns the IPv4 object representing the network address
		def network()
			@base
		end
		
		#len returns the number of IP addresses in this network. It will return 0 for /0 networks.
		def len()
			return self.netmask.len
		end
		
		# next returns the next largest consecutive IP network or nil if the end of the address space is reached.
		def next()
			net = self.nth_next_sib(1)
			if (!net)
				return nil
			end
			return net.grow
		end
		
		# next_sib returns the network immediately following this one or nil if the end of the address space is reached.
		def next_sib()
			self.nth_next_sib(1)
		end
		
		# nth returns the IPv4 at the given index.
		# The size of the network may be determined with the len() method.
		# If the range is exceeded then return nil.
		def nth(index)
			if (!index.kind_of?(Integer))
				raise ArgumentError, "Expected an Integer for 'index' but got a #{index.class}."
			elsif (index >= self.len)
				return nil
			end
			return IPv4.new(self.network.addr + index)
		end
		
		# nth_subnet returns the subnet IPv4Net at the given index.
		# The number of subnets may be determined with the subnet_count() method.
		# If the range is exceeded  or an invalid prefix_len is provided then return nil.
		def nth_subnet(prefix_len,index)
			count = self.subnet_count(prefix_len)
			if (count == 0 || index >= count)
				return nil
			end
			sub0 = IPv4Net.new(self.network, Mask32.new(prefix_len))
			return sub0.nth_next_sib(index)
		end
		
		# prev returns the previous largest consecutive IP network or nil if this is 0.0.0.0.
		def prev()
			net = self.grow
			return net.prev_sib
		end
		
		# prev_sib returns the network immediately preceding this one or nil if this network is 0.0.0.0.
		def prev_sib()
			if (self.network.addr == 0)
				return nil
			end
			
			shift = 32 - self.netmask.prefix_len
			addr = ((self.network.addr>>shift) - 1) << shift
			return IPv4Net.new(IPv4.new(addr), self.netmask)
		end
		
		# rel determines the relationship to another IPv4Net. Returns:
		# * 1 if this IPv4Net is the supernet of other
		# * 0 if the two are equal
		# * -1 if this IPv4Net is a subnet of other
		# * nil if the networks are unrelated
		def rel(other)
			if (!other.kind_of?(IPv4Net))
				raise ArgumentError, "Expected an IPv4Net object for 'other' but got a #{other.class}."
			end
			
			# when networks are equal then we can look exlusively at the netmask
			if (self.network.addr == other.network.addr)
				return self.netmask.cmp(other.netmask)
			end
			
			# when networks are not equal we can use hostmask to test if they are
			# related and which is the supernet vs the subnet
			hostmask = self.netmask.mask ^ NetAddr::F32
			otherHostmask = other.netmask.mask ^ NetAddr::F32
			if (self.network.addr|hostmask == other.network.addr|hostmask)
				return 1
			elsif (self.network.addr|otherHostmask == other.network.addr|otherHostmask)
				return -1
			end
			return nil
		end
		
		# resize returns a copy of the network with an adjusted netmask.
		# Throws ValidationError on invalid prefix_len.
		def resize(prefix_len)
			m32 = Mask32.new(prefix_len)
			return IPv4Net.new(self.network,m32)
		end
		
		# subnet_count returns the number a subnets of a given prefix length that this IPv4Net contains.
		# It will return 0 for invalid requests (ie. bad prefix or prefix is shorter than that of this network).
		# It will also return 0 if the result exceeds the capacity of a 32-bit integer (ie. if you want the # of /32 a /0 will hold)
		def subnet_count(prefix_len)
			if (prefix_len <= self.netmask.prefix_len || prefix_len > 32 || prefix_len - self.netmask.prefix_len >= 32)
				return 0
			end
			return 1 << (prefix_len - self.netmask.prefix_len)
		end
		
		# summ creates a summary address from this IPv4Net and another.
		# It returns nil if the two networks are incapable of being summarized.
		def summ(other)
			if (!other.kind_of?(IPv4Net))
				raise ArgumentError, "Expected an IPv4Net object for 'other' but got a #{other.class}."
			end
			
			# netmasks must be identical
			if (self.netmask.prefix_len != other.netmask.prefix_len)
				return nil
			end
			
			# merge-able networks will be identical if you right shift them by the number of bits in the hostmask + 1
			shift = 32 - self.netmask.prefix_len + 1
			addr = self.network.addr >> shift
			otherAddr = other.network.addr >> shift
			if (addr != otherAddr)
				return nil
			end
			return self.resize(self.netmask.prefix_len - 1)
		end
		
		# to_s returns the IPv4Net as a String
		def to_s()
			return @base.to_s + @m32.to_s
		end
		
		# version returns "4" for IPv4
		def version()
			return 4
		end
		
		
		protected

		# grow decreases the prefix length as much as possible without crossing a bit boundary.
		def grow()
			addr = self.network.addr
			mask = self.netmask.mask
			prefix_len = self.netmask.prefix_len
			self.netmask.prefix_len.downto(0) do
				mask = (mask << 1) & NetAddr::F32
				if addr|mask != mask || prefix_len == 0 # // bit boundary crossed when there are '1' bits in the host portion
					break
				end
				prefix_len -= 1
			end
			return IPv4Net.new(IPv4.new(addr),Mask32.new(prefix_len))
		end
		
		# nth_next_sib returns the nth next sibling network or nil if address space exceeded.
		def nth_next_sib(nth)
			if (nth < 0)
				return nil
			end
			
			shift = 32 - self.netmask.prefix_len
			addr = ((self.network.addr>>shift) + nth) << shift
			if addr > NetAddr::F32
				return nil
			end
			return IPv4Net.new(IPv4.new(addr), self.netmask)
		end
		
	end # end class IPv4Net
	
end # end module