network-infra-utility 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +0 -12
- data/bin/geo-api +114 -0
- data/bin/geo-get +135 -0
- data/bin/geo-load +190 -0
- data/network-infra-utility.gemspec +9 -2
- data/network.rb +1 -1
- data/service/geodb/GeoAPI.md +227 -0
- data/service/geodb/api.rb +276 -0
- data/service/geodb/config.ru +15 -0
- data/service/geodb/geodb.rb +97 -0
- data/support/basic/ip.rb +88 -0
- data/support/basic/ipv4_address.rb +336 -0
- data/support/basic/ipv6_address.rb +244 -0
- data/version.rb +1 -1
- metadata +59 -4
data/support/basic/ip.rb
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# IP 统一入口模块。
|
|
5
|
+
#
|
|
6
|
+
# 提供 v4 / v6 / range / cross / xross 五个模块方法,
|
|
7
|
+
# 内部按地址串是否含 '.' ':' 自动分派到 IPv4 / IPv6。
|
|
8
|
+
require_relative 'ipv4_address'
|
|
9
|
+
require_relative 'ipv6_address'
|
|
10
|
+
|
|
11
|
+
module IP
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# 解析 IPv4 地址或 CIDR。
|
|
15
|
+
# string 不含 '/' → 返回 IPv4
|
|
16
|
+
# string 含 '/' → 返回 [IPv4, IPv4Mask]
|
|
17
|
+
def v4(string)
|
|
18
|
+
ip, msk = string.split('/')
|
|
19
|
+
return IPv4.new(ip) unless msk
|
|
20
|
+
|
|
21
|
+
mask = msk.include?('.') ? IPv4Mask.string(msk) : IPv4Mask.number(msk.to_i)
|
|
22
|
+
[IPv4.new(ip), mask]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# 解析 IPv6 地址或 CIDR。
|
|
26
|
+
# string 不含 '/' → 返回 IPv6
|
|
27
|
+
# string 含 '/' → 返回 [IPv6, IPv6Mask]
|
|
28
|
+
def v6(string)
|
|
29
|
+
ip, msk = string.split('/')
|
|
30
|
+
return IPv6.new(ip) unless msk
|
|
31
|
+
|
|
32
|
+
mask = msk.include?(':') ? IPv6Mask.string(msk) : IPv6Mask.number(msk.to_i)
|
|
33
|
+
[IPv6.new(ip), mask]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# 展开一个地址区间,返回 [start, end]。
|
|
37
|
+
# 支持三种输入:
|
|
38
|
+
# CIDR '1.0.4.0/22' → [网络地址, 广播地址]
|
|
39
|
+
# 区间 '1.0.4.0-1.0.7.255' → [起始, 结束]
|
|
40
|
+
# 单址 '1.0.4.0' → [addr, addr]
|
|
41
|
+
# 自动按 '.' ':' 区分 v4 / v6。
|
|
42
|
+
def range(addr)
|
|
43
|
+
return expand_dash_range(addr) if addr.include?('-')
|
|
44
|
+
|
|
45
|
+
if addr.include?('.')
|
|
46
|
+
gateway, netmask = v4(addr)
|
|
47
|
+
elsif addr.include?(':')
|
|
48
|
+
gateway, netmask = v6(addr)
|
|
49
|
+
else
|
|
50
|
+
raise ArgumentError, "Unrecognized address #{addr}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if netmask
|
|
54
|
+
network = gateway.network_with(netmask)
|
|
55
|
+
network.range_with(netmask)
|
|
56
|
+
else
|
|
57
|
+
[gateway, gateway]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# 两个闭区间是否相交。参数为 [start, end] 形式,元素需可比较。
|
|
62
|
+
def cross(range1, range2)
|
|
63
|
+
s1, e1 = range1
|
|
64
|
+
s2, e2 = range2
|
|
65
|
+
(s1 <= s2 && s2 <= e1) ||
|
|
66
|
+
(s1 <= e2 && e2 <= e1) ||
|
|
67
|
+
(s2 <= s1 && s1 <= e2) ||
|
|
68
|
+
(s2 <= e1 && e1 <= e2)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# 求两个区间的合并并集端点。
|
|
72
|
+
# 返回排序去重后的端点序列,转换为 IP 对象。
|
|
73
|
+
# option: :v4 或 :v6,决定从 0 地址起算。
|
|
74
|
+
def xross(range1, range2, option = :v4)
|
|
75
|
+
s1, e1 = range1
|
|
76
|
+
s2, e2 = range2
|
|
77
|
+
base_addr = option == :v6 ? '::' : '0.0.0.0'
|
|
78
|
+
[s1, e1, s2, e2].sort.uniq.map { |n| IP.send(option, base_addr) + n }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# 处理 'start-end' 形式的区间串。
|
|
82
|
+
# 递归解析两端各自可能是 CIDR 或单址,最终 flatten 成 [start, end]。
|
|
83
|
+
def expand_dash_range(addr)
|
|
84
|
+
sddr, eddr = addr.split('-')
|
|
85
|
+
[range(sddr).first, range(eddr).last]
|
|
86
|
+
end
|
|
87
|
+
module_function :expand_dash_range
|
|
88
|
+
end
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# IPv4 地址与掩码工具。
|
|
5
|
+
#
|
|
6
|
+
# 用法:
|
|
7
|
+
# ip, mask = IP.v4('10.37.214.42/17')
|
|
8
|
+
# net = ip.network_with(mask)
|
|
9
|
+
# p ip.to_d, mask.to_d, net.to_d
|
|
10
|
+
# p ip.mask?, mask.mask?, net.network_with?(mask)
|
|
11
|
+
# start_ip, end_ip = ip.range_with(mask)
|
|
12
|
+
# p start_ip.to_d, end_ip.to_d
|
|
13
|
+
#
|
|
14
|
+
# a = IP.v4('10.37.214.21/8')[0]
|
|
15
|
+
# b = IP.v4('145.217.33.14/16')[0]
|
|
16
|
+
# c = IP.v4('221.25.43.72/24')[0]
|
|
17
|
+
# d = IP.v4('238.13.189.51')
|
|
18
|
+
# e = IP.v4('246.211.74.63')
|
|
19
|
+
# p a.class_a?, b.class_b?, c.class_c?, d.class_d?, e.class_e?
|
|
20
|
+
# p a.private?, b.private?
|
|
21
|
+
|
|
22
|
+
require 'ipaddr'
|
|
23
|
+
|
|
24
|
+
# IPv4 地址。
|
|
25
|
+
#
|
|
26
|
+
# 持有四个 8 位段 @numbers 和 32 位整数 @number。
|
|
27
|
+
# 支持 CIDR 解析(通过 IP.v4)、掩码运算、范围展开、地址分类判断。
|
|
28
|
+
class IPv4
|
|
29
|
+
attr_reader :numbers, :number
|
|
30
|
+
|
|
31
|
+
# 解析地址串。支持十进制、0x 十六进制、0b 二进制三种前缀进制,
|
|
32
|
+
# 前缀作用于整串所有段。形如 '10.0.0.1' / '0x0a.0.0.1' / '0b00001010.0.0.1111'。
|
|
33
|
+
# 非法格式发 warn 并返回 self,使 IP.v4 仍拿到一个可判空的对象。
|
|
34
|
+
def generate(string)
|
|
35
|
+
unless /^(0[bdx])?([0-9a-fA-F]+\.){3}[0-9a-fA-F]+$/.match?(string)
|
|
36
|
+
warn "Abnormal format string #{string}!"
|
|
37
|
+
return self
|
|
38
|
+
end
|
|
39
|
+
@content, @mode = mode(string)
|
|
40
|
+
fours = format_parts(@content, @mode)
|
|
41
|
+
if valid?(fours)
|
|
42
|
+
@numbers = fours
|
|
43
|
+
@number = to_i
|
|
44
|
+
else
|
|
45
|
+
warn "Abnormal decimal string #{string}!"
|
|
46
|
+
end
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(string)
|
|
51
|
+
generate(string)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# ---- 进制解析 ----
|
|
55
|
+
|
|
56
|
+
# 拆出前缀与本体。'0b'/'0x'/'0d' 前缀原样保留为 mode,否则按十进制。
|
|
57
|
+
def mode(string)
|
|
58
|
+
if ['0b', '0x', '0d'].include?(string[0..1])
|
|
59
|
+
[string[2..], string[0..1]]
|
|
60
|
+
else
|
|
61
|
+
[string, '0d']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# 按 mode 把点分四段各自 eval 成整数。mode 前缀会被拼到每段前。
|
|
66
|
+
def format_parts(string, mode)
|
|
67
|
+
string.split('.').map { |part| eval("#{mode}#{part}") }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# 判断四段是否全落在 0..255 且共 4 段。
|
|
71
|
+
def valid?(nums)
|
|
72
|
+
nums.size == 4 && nums.all? { |n| (0..255).include?(n) }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# ---- 输出格式 ----
|
|
76
|
+
|
|
77
|
+
def to_a
|
|
78
|
+
@numbers
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def to_d
|
|
82
|
+
@numbers.map { |n| n.to_s(10) }.join('.')
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_h
|
|
86
|
+
@numbers.map { |n| '%02x' % n }.join('.')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_b
|
|
90
|
+
@numbers.map { |n| '%08b' % n }.join('.')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# 兼容原接口:to_s 即十进制点分。
|
|
94
|
+
def to_s
|
|
95
|
+
to_d
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# 32 位整数表示,内部缓存计算结果。
|
|
101
|
+
def to_i
|
|
102
|
+
@numbers.map { |n| '%08b' % n }.join.to_i(2)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# 从 32 位整数值构造新地址。
|
|
106
|
+
def rebuild_from_number(n)
|
|
107
|
+
bin = '%032b' % (n & 0xFFFFFFFF)
|
|
108
|
+
dotted = bin.unpack('a8a8a8a8').join('.')
|
|
109
|
+
IPv4.new('0b' + dotted)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
public
|
|
113
|
+
|
|
114
|
+
# ---- 算术运算(非破坏性,返回新对象)----
|
|
115
|
+
|
|
116
|
+
# 加 num 个地址,返回新 IPv4。num 可为 Integer 或 IPv4(取其 number)。num 为负则等价于减法。
|
|
117
|
+
def +(num)
|
|
118
|
+
delta = num.is_a?(IPv4) ? num.number : num
|
|
119
|
+
new_number = (@number + delta) & 0xFFFFFFFF
|
|
120
|
+
rebuild_from_number(new_number)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# 减 num 个地址,返回新 IPv4。
|
|
124
|
+
def -(num)
|
|
125
|
+
self.+(-num)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# 按位与。another 可为 IPv4(含 Mask 子类)或前缀长度 Integer。
|
|
129
|
+
def &(another)
|
|
130
|
+
target = case another
|
|
131
|
+
when IPv4 then another
|
|
132
|
+
when Integer then IPv4Mask.number(another)
|
|
133
|
+
else return self
|
|
134
|
+
end
|
|
135
|
+
new_addr = @numbers.each_with_index.map { |n, i| n & target.numbers[i] }
|
|
136
|
+
IPv4.new(new_addr.join('.'))
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# 子网划分。把当前网段按 sub_pref 前缀长度划分为若干子网,
|
|
140
|
+
# 返回 [[subnet_ip, mask], ...]。base_pref/sub_pref 可为前缀长度或 IPv4/IPv4Mask。
|
|
141
|
+
def delegation(base_pref, sub_pref)
|
|
142
|
+
base_len = base_pref.is_a?(IPv4) ? base_pref.mask_counter : base_pref
|
|
143
|
+
sub_len = sub_pref.is_a?(IPv4) ? sub_pref.mask_counter : sub_pref
|
|
144
|
+
return [] unless base_len && sub_len
|
|
145
|
+
return [] unless base_len < sub_len
|
|
146
|
+
return [] unless base_len <= 32 && sub_len <= 32
|
|
147
|
+
|
|
148
|
+
base = self & base_len
|
|
149
|
+
pref = IPv4Mask.number(sub_len)
|
|
150
|
+
(0..(2**(sub_len - base_len) - 1)).map do |i|
|
|
151
|
+
sub_ip = base + (i << (32 - sub_len))
|
|
152
|
+
[sub_ip, pref]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# ---- 地址分类 ----
|
|
157
|
+
|
|
158
|
+
def class_a?
|
|
159
|
+
(IPv4.new('10.0.0.0').number..IPv4.new('126.255.255.255').number).include?(@number)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def class_b?
|
|
163
|
+
(IPv4.new('128.1.0.0').number..IPv4.new('191.255.255.255').number).include?(@number)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def class_c?
|
|
167
|
+
(IPv4.new('192.0.1.0').number..IPv4.new('223.255.255.255').number).include?(@number)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def class_d?
|
|
171
|
+
(IPv4.new('224.0.0.0').number..IPv4.new('239.255.255.255').number).include?(@number)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def class_e?
|
|
175
|
+
(IPv4.new('240.0.0.0').number..IPv4.new('255.255.255.254').number).include?(@number)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def private?
|
|
179
|
+
n = @number
|
|
180
|
+
(IPv4.new('10.0.0.0').number..IPv4.new('10.255.255.255').number).include?(n) ||
|
|
181
|
+
(IPv4.new('172.16.0.0').number..IPv4.new('172.31.255.255').number).include?(n) ||
|
|
182
|
+
(IPv4.new('192.168.0.0').number..IPv4.new('192.168.255.255').number).include?(n)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def loopback?
|
|
186
|
+
(IPv4.new('127.0.0.0').number..IPv4.new('127.255.255.255').number).include?(@number)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# 特殊地址:环回、全 1 广播、全 0。
|
|
190
|
+
def special?
|
|
191
|
+
[IPv4.new('127.0.0.1').number, IPv4.new('255.255.255.255').number,
|
|
192
|
+
IPv4.new('0.0.0.0').number].include?(@number)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# 保留 is_another? 作为 special? 的兼容别名。
|
|
196
|
+
alias is_another? special?
|
|
197
|
+
|
|
198
|
+
# ---- 掩码判定 ----
|
|
199
|
+
|
|
200
|
+
# 是否为合法网络掩码(前 1 后 0,无 01 交错)。
|
|
201
|
+
def mask?
|
|
202
|
+
!to_b.gsub('.', '').include?('01')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# 是否为反掩码(前 0 后 1,无 10 交错)。
|
|
206
|
+
def anti_mask?
|
|
207
|
+
!to_b.gsub('.', '').include?('10')
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# 网络掩码的前缀长度。非掩码返回 nil。
|
|
211
|
+
def mask_counter
|
|
212
|
+
return nil unless mask?
|
|
213
|
+
counter, msk = 0, to_b.gsub('.', '')
|
|
214
|
+
while msk[-1] == '0'
|
|
215
|
+
counter += 1
|
|
216
|
+
msk = msk[0..-2]
|
|
217
|
+
end
|
|
218
|
+
32 - counter
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# 反掩码的尾部 1 个数。非反掩码返回 nil。
|
|
222
|
+
def anti_mask_counter
|
|
223
|
+
return nil unless anti_mask?
|
|
224
|
+
counter, msk = 0, to_b.gsub('.', '')
|
|
225
|
+
while msk[-1] == '1'
|
|
226
|
+
counter += 1
|
|
227
|
+
msk = msk[0..-2]
|
|
228
|
+
end
|
|
229
|
+
counter
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# 掩码对应的反掩码。非掩码返回 nil。
|
|
233
|
+
def anti_mask
|
|
234
|
+
return nil unless (counter = mask_counter)
|
|
235
|
+
IPv4Mask.anti_number(counter)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# 反掩码对应的掩码。非反掩码返回 nil。
|
|
239
|
+
def mask
|
|
240
|
+
return nil unless (counter = anti_mask_counter)
|
|
241
|
+
IPv4Mask.number(32 - counter)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# ---- 保留 is_ 前缀别名,兼容旧接口 ----
|
|
245
|
+
|
|
246
|
+
alias is_mask? mask?
|
|
247
|
+
alias is_anti_mask? anti_mask?
|
|
248
|
+
alias is_private? private?
|
|
249
|
+
alias is_class_a? class_a?
|
|
250
|
+
alias is_class_b? class_b?
|
|
251
|
+
alias is_class_c? class_c?
|
|
252
|
+
alias is_class_d? class_d?
|
|
253
|
+
alias is_class_e? class_e?
|
|
254
|
+
|
|
255
|
+
# ---- 网络/范围 ----
|
|
256
|
+
|
|
257
|
+
# 与掩码相与得到网络地址。
|
|
258
|
+
def network_with(mask)
|
|
259
|
+
new_addr = mask.numbers.each_with_index.map { |n, i| n & @numbers[i] }
|
|
260
|
+
IPv4.new(new_addr.join('.'))
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# 该网段的完整地址区间,含网络地址与广播地址。
|
|
264
|
+
# 前缀 /32 时返回 [self, self]。
|
|
265
|
+
def range_with(mask)
|
|
266
|
+
return [self, self] if mask.mask_counter.to_i == 32
|
|
267
|
+
net = network_with(mask)
|
|
268
|
+
prefix = mask.mask_counter.to_i
|
|
269
|
+
offset = 2**(32 - prefix) - 1
|
|
270
|
+
[net, net + offset]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# 判断自身是否可作 mask 所在网段的网络地址。
|
|
274
|
+
def network_with?(mask)
|
|
275
|
+
msk_counter, msk = 0, mask.to_b.gsub('.', '')
|
|
276
|
+
while msk[-1] == '0'
|
|
277
|
+
msk_counter += 1
|
|
278
|
+
msk = msk[0..-2]
|
|
279
|
+
end
|
|
280
|
+
nt_counter, nt = 0, to_b.gsub('.', '')
|
|
281
|
+
while nt[-1] == '0'
|
|
282
|
+
nt_counter += 1
|
|
283
|
+
nt = nt[0..-2]
|
|
284
|
+
end
|
|
285
|
+
nt_counter >= msk_counter
|
|
286
|
+
end
|
|
287
|
+
alias is_network_with? network_with?
|
|
288
|
+
|
|
289
|
+
# 两个地址的最长公共前缀长度。
|
|
290
|
+
def prefix_with(another_ip)
|
|
291
|
+
ip1 = to_b.gsub('.', '')
|
|
292
|
+
ip2 = another_ip.to_b.gsub('.', '')
|
|
293
|
+
count = 0
|
|
294
|
+
count += 1 while ip1[count] == ip2[count] && count < 32
|
|
295
|
+
count
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# ---- 排序比较 ----
|
|
299
|
+
|
|
300
|
+
include Comparable
|
|
301
|
+
def <=>(other)
|
|
302
|
+
@number <=> other.number
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# IPv4 掩码工厂,按前缀长度或字符串构造 IPv4(带掩码语义)。
|
|
307
|
+
class IPv4Mask
|
|
308
|
+
class << self
|
|
309
|
+
# 前缀长度 → 网络掩码。num 范围 0..32。
|
|
310
|
+
def number(num)
|
|
311
|
+
raise ArgumentError, "Invalid prefix length #{num}" unless (0..32).include?(num)
|
|
312
|
+
str = '0b' + ('1' * num + '0' * (32 - num)).unpack('a8a8a8a8').join('.')
|
|
313
|
+
IPv4.new(str)
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# 字符串 → 网络掩码,校验其为合法掩码。
|
|
317
|
+
def string(str)
|
|
318
|
+
temp = IPv4.new(str)
|
|
319
|
+
raise "Invalid net mask #{str}!" unless temp.mask?
|
|
320
|
+
temp
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# 反掩码前缀长度 (尾部 1 的个数) → 反掩码。
|
|
324
|
+
def anti_number(num)
|
|
325
|
+
str = '0b' + ('0' * num + '1' * (32 - num)).unpack('a8a8a8a8').join('.')
|
|
326
|
+
IPv4.new(str)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# 字符串 → 反掩码,校验其为合法反掩码。
|
|
330
|
+
def anti_string(str)
|
|
331
|
+
temp = IPv4.new(str)
|
|
332
|
+
raise "Invalid anti mask #{str}!" unless temp.anti_mask?
|
|
333
|
+
temp
|
|
334
|
+
end
|
|
335
|
+
end
|
|
336
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# IPv6 地址与掩码工具。
|
|
5
|
+
#
|
|
6
|
+
# 用法:
|
|
7
|
+
# ip, mask = IP.v6('2001:db8::1/64')
|
|
8
|
+
# net = ip.network_with(mask)
|
|
9
|
+
# start_ip, end_ip = ip.range_with(mask)
|
|
10
|
+
|
|
11
|
+
require 'ipaddr'
|
|
12
|
+
|
|
13
|
+
# IPv6 地址。
|
|
14
|
+
#
|
|
15
|
+
# 持有八个 16 位段 @numbers 和 128 位整数 @number。
|
|
16
|
+
# 解析借助标准库 IPAddr 展开压缩形式,再统一为点分十六进制段。
|
|
17
|
+
class IPv6
|
|
18
|
+
attr_reader :numbers, :number
|
|
19
|
+
|
|
20
|
+
# 解析地址串。借助 IPAddr 还原 '::' 压缩为完整 8 段十六进制;
|
|
21
|
+
# 非法地址抛异常(与 IPv4 的 warn 行为不同,沿用原作者设计)。
|
|
22
|
+
def generate(string)
|
|
23
|
+
@content = IPAddr.new(string).to_string
|
|
24
|
+
@mode = '0x'
|
|
25
|
+
eights = format_parts(@content, @mode)
|
|
26
|
+
@numbers = eights
|
|
27
|
+
@number = to_i
|
|
28
|
+
self
|
|
29
|
+
rescue IPAddr::InvalidAddressError => e
|
|
30
|
+
raise "Abnormal format string #{string}! #{e.message}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def initialize(string)
|
|
34
|
+
generate(string)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# ---- 进制解析 ----
|
|
38
|
+
|
|
39
|
+
def mode(string)
|
|
40
|
+
[IPAddr.new(string).to_string, '0x']
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def format_parts(string, mode = '0x')
|
|
44
|
+
string.split(':').map { |part| eval("#{mode}#{part}") }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def to_i
|
|
50
|
+
@numbers.map { |n| '%016b' % n }.join.to_i(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def rebuild_from_hex(hex_str)
|
|
54
|
+
IPv6.new(hex_str.unpack('a4a4a4a4a4a4a4a4').join(':'))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
public
|
|
58
|
+
|
|
59
|
+
# ---- 输出格式 ----
|
|
60
|
+
|
|
61
|
+
def to_a
|
|
62
|
+
@numbers
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_d
|
|
66
|
+
@numbers.map { |n| n.to_s(10) }.join(':')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def to_h
|
|
70
|
+
@numbers.map { |n| '%04x' % n }.join(':')
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_b
|
|
74
|
+
@numbers.map { |n| '%016b' % n }.join(':')
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# 压缩形式( '::' 形态),借助 IPAddr 还原。
|
|
78
|
+
def to_s
|
|
79
|
+
IPAddr.new(@content).to_s
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
alias to_s_short to_s
|
|
83
|
+
|
|
84
|
+
def to_s_full
|
|
85
|
+
to_h
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# ---- 算术运算(非破坏性)----
|
|
89
|
+
|
|
90
|
+
def +(num)
|
|
91
|
+
delta = num.is_a?(IPv6) ? num.number : num
|
|
92
|
+
new_number = (@number + delta)
|
|
93
|
+
new_number &= (1 << 128) - 1 if delta < 0 && new_number < 0
|
|
94
|
+
rebuild_from_hex('%032x' % new_number)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def -(num)
|
|
98
|
+
self.+(-num)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def &(another)
|
|
102
|
+
target = case another
|
|
103
|
+
when IPv6 then another
|
|
104
|
+
when Integer then IPv6Mask.number(another)
|
|
105
|
+
else return self
|
|
106
|
+
end
|
|
107
|
+
new_addr = @numbers.each_with_index.map { |n, i| n & target.numbers[i] }
|
|
108
|
+
IPv6.new(new_addr.map { |n| '%04x' % n }.join(':'))
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def delegation(base_pref, sub_pref)
|
|
112
|
+
base_len = base_pref.is_a?(IPv6) ? base_pref.mask_counter : base_pref
|
|
113
|
+
sub_len = sub_pref.is_a?(IPv6) ? sub_pref.mask_counter : sub_pref
|
|
114
|
+
return [] unless base_len && sub_len
|
|
115
|
+
return [] unless base_len < sub_len
|
|
116
|
+
return [] unless base_len <= 128 && sub_len <= 128
|
|
117
|
+
|
|
118
|
+
base = self & base_len
|
|
119
|
+
pref = IPv6Mask.number(sub_len)
|
|
120
|
+
(0..(2**(sub_len - base_len) - 1)).map do |i|
|
|
121
|
+
sub_ip = base + (i << (128 - sub_len))
|
|
122
|
+
[sub_ip, pref]
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# ---- 掩码判定 ----
|
|
127
|
+
|
|
128
|
+
def mask?
|
|
129
|
+
!to_b.gsub(':', '').include?('01')
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def anti_mask?
|
|
133
|
+
!to_b.gsub(':', '').include?('10')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mask_counter
|
|
137
|
+
return nil unless mask?
|
|
138
|
+
counter, msk = 0, to_b.gsub(':', '')
|
|
139
|
+
while msk[-1] == '0'
|
|
140
|
+
counter += 1
|
|
141
|
+
msk = msk[0..-2]
|
|
142
|
+
end
|
|
143
|
+
128 - counter
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def anti_mask_counter
|
|
147
|
+
return nil unless anti_mask?
|
|
148
|
+
counter, msk = 0, to_b.gsub(':', '')
|
|
149
|
+
while msk[-1] == '1'
|
|
150
|
+
counter += 1
|
|
151
|
+
msk = msk[0..-2]
|
|
152
|
+
end
|
|
153
|
+
counter
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def anti_mask
|
|
157
|
+
return nil unless (counter = mask_counter)
|
|
158
|
+
IPv6Mask.anti_number(counter)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def mask
|
|
162
|
+
return nil unless (counter = anti_mask_counter)
|
|
163
|
+
IPv6Mask.number(128 - counter)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
alias is_mask? mask?
|
|
167
|
+
alias is_anti_mask? anti_mask?
|
|
168
|
+
|
|
169
|
+
# ---- 网络/范围 ----
|
|
170
|
+
|
|
171
|
+
def network_with(mask)
|
|
172
|
+
new_addr = mask.numbers.each_with_index.map { |n, i| n & @numbers[i] }
|
|
173
|
+
IPv6.new(new_addr.map { |n| '%04x' % n }.join(':'))
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# 完整地址区间,含网络地址与全 1 广播地址。
|
|
177
|
+
def range_with(mask)
|
|
178
|
+
return [self, self] if mask.mask_counter.to_i == 128
|
|
179
|
+
net = network_with(mask)
|
|
180
|
+
prefix = mask.mask_counter.to_i
|
|
181
|
+
offset = 2**(128 - prefix) - 1
|
|
182
|
+
[net, net + offset]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def network_with?(mask)
|
|
186
|
+
msk_counter, msk = 0, mask.to_b.gsub(':', '')
|
|
187
|
+
while msk[-1] == '0'
|
|
188
|
+
msk_counter += 1
|
|
189
|
+
msk = msk[0..-2]
|
|
190
|
+
end
|
|
191
|
+
nt_counter, nt = 0, to_b.gsub(':', '')
|
|
192
|
+
while nt[-1] == '0'
|
|
193
|
+
nt_counter += 1
|
|
194
|
+
nt = nt[0..-2]
|
|
195
|
+
end
|
|
196
|
+
nt_counter >= msk_counter
|
|
197
|
+
end
|
|
198
|
+
alias is_network_with? network_with?
|
|
199
|
+
|
|
200
|
+
def prefix_with(another_ip)
|
|
201
|
+
ip1 = to_b.gsub(':', '')
|
|
202
|
+
ip2 = another_ip.to_b.gsub(':', '')
|
|
203
|
+
count = 0
|
|
204
|
+
count += 1 while ip1[count] == ip2[count] && count < 128
|
|
205
|
+
count
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# ---- 排序比较 ----
|
|
209
|
+
|
|
210
|
+
include Comparable
|
|
211
|
+
def <=>(other)
|
|
212
|
+
@number <=> other.number
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# IPv6 掩码工厂。
|
|
217
|
+
class IPv6Mask
|
|
218
|
+
class << self
|
|
219
|
+
def number(num)
|
|
220
|
+
raise ArgumentError, "Invalid prefix length #{num}" unless (0..128).include?(num)
|
|
221
|
+
hex = '%032x' % ('1' * num + '0' * (128 - num)).to_i(2)
|
|
222
|
+
str = hex.unpack('a4a4a4a4a4a4a4a4').join(':')
|
|
223
|
+
IPv6.new(str)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def string(str)
|
|
227
|
+
temp = IPv6.new(str)
|
|
228
|
+
raise "Invalid net mask #{str}!" unless temp.mask?
|
|
229
|
+
temp
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def anti_number(num)
|
|
233
|
+
hex = '%032x' % ('0' * num + '1' * (128 - num)).to_i(2)
|
|
234
|
+
str = hex.unpack('a4a4a4a4a4a4a4a4').join(':')
|
|
235
|
+
IPv6.new(str)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def anti_string(str)
|
|
239
|
+
temp = IPv6.new(str)
|
|
240
|
+
raise "Invalid anti mask #{str}!" unless temp.anti_mask?
|
|
241
|
+
temp
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
data/version.rb
CHANGED