network-infra-utility 0.3.0 → 0.6.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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/GUIDE.md +212 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +70 -0
  6. data/Rakefile +1 -1
  7. data/bin/dns-query +836 -0
  8. data/bin/geo-doc +135 -0
  9. data/bin/geo-get +1 -1
  10. data/bin/geo-update +429 -0
  11. data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
  12. data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
  13. data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
  14. data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
  15. data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
  16. data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
  17. data/network-infra-utility.gemspec +4 -2
  18. data/network.rb +3 -1
  19. data/service/geodb/geodb.rb +278 -1
  20. data/service/ssh/README.md +955 -0
  21. data/service/ssh/bin/ssh-client +198 -0
  22. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
  23. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
  24. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
  25. data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
  26. data/service/ssh/ext/ssh_core/config/sys.config +0 -0
  27. data/service/ssh/ext/ssh_core/config/vm.args +0 -0
  28. data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
  29. data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
  30. data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
  31. data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
  32. data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
  33. data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
  34. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
  35. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
  36. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
  37. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
  38. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
  39. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
  40. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
  41. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
  42. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
  43. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
  44. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
  45. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
  46. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
  47. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
  48. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
  49. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
  50. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
  51. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
  52. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
  53. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
  54. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
  55. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
  56. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
  57. data/service/ssh/ext/ssh_core/rebar.config +24 -0
  58. data/service/ssh/ext/ssh_core/rebar.lock +1 -0
  59. data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
  60. data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
  61. data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
  62. data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
  63. data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
  64. data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
  65. data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
  66. data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
  67. data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +117 -0
  68. data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
  69. data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
  70. data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
  71. data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
  72. data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
  73. data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
  74. data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
  75. data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
  76. data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
  77. data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
  78. data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
  79. data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
  80. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
  81. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
  82. data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
  83. data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
  84. data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
  85. data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
  86. data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
  87. data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
  88. data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
  89. data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
  90. data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
  91. data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
  92. data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
  93. data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
  94. data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
  95. data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
  96. data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
  97. data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
  98. data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
  99. data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
  100. data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
  101. data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
  102. data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
  103. data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
  104. data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
  105. data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
  106. data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
  107. data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
  108. data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
  109. data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
  110. data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
  111. data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
  112. data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
  113. data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
  114. data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
  115. data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
  116. data/support/basic/as_num.rb +221 -0
  117. data/support/basic/mac_address.rb +281 -0
  118. data/version.rb +1 -1
  119. metadata +142 -1
@@ -0,0 +1,297 @@
1
+
2
+ # IP 模块功能说明
3
+
4
+ > 源码位置:`support/basic/ip.rb` / `ipv4_address.rb` / `ipv6_address.rb`
5
+ > 加载方式:`require 'network'` 自动加载,或单独 `require_relative 'support/basic/ip'`
6
+
7
+ ## 统一入口 `IP`
8
+
9
+ `IP` 模块提供 `v4` / `v6` / `range` / `cross` / `xross` 五个模块方法,内部按地址串是否含 `.` `:` 自动分派到 `IPv4` / `IPv6`。
10
+
11
+ ### `IP.v4(string)` — 解析 IPv4 或 CIDR
12
+
13
+ ```ruby
14
+ require 'network'
15
+
16
+ # 单地址 → 返回 IPv4 对象
17
+ ip = IP.v4('10.37.214.42')
18
+ ip.to_d # => "10.37.214.42" 十进制点分
19
+ ip.number # => 173185606 32位整数
20
+ ip.class_a? # => true
21
+ ip.private? # => true
22
+
23
+ # CIDR → 返回 [IPv4, IPv4Mask]
24
+ ip2, mask = IP.v4('10.37.214.42/17')
25
+ mask.to_d # => "255.255.128.0"
26
+ mask.mask_counter # => 17 前缀长度
27
+ ```
28
+
29
+ ### `IP.v6(string)` — 解析 IPv6 或 CIDR
30
+
31
+ ```ruby
32
+ # 单地址 → 返回 IPv6 对象
33
+ ip = IP.v6('2001:db8::1')
34
+ ip.to_s # => "2001:db8::1" 压缩形式
35
+ ip.to_s_full # => "2001:0db8:0000:...0001" 完整 8 段
36
+ ip.number # => 42540488161975842760550356425300241409 128位整数
37
+
38
+ # CIDR → 返回 [IPv6, IPv6Mask]
39
+ ip6, mask = IP.v6('2001:db8::/32')
40
+ mask.mask_counter # => 32
41
+ ```
42
+
43
+ ### `IP.range(addr)` — 展开地址区间
44
+
45
+ 支持三种输入,统一返回 `[start, end]`:
46
+
47
+ ```ruby
48
+ # CIDR → [网络地址, 广播地址]
49
+ start_ip, end_ip = IP.range('1.0.4.0/22')
50
+ start_ip.to_d # => "1.0.4.0"
51
+ end_ip.to_d # => "1.0.7.255"
52
+
53
+ # 区间串 → [起始, 结束]
54
+ s, e = IP.range('1.0.4.0-1.0.7.255')
55
+ s.to_d # => "1.0.4.0"
56
+ e.to_d # => "1.0.7.255"
57
+
58
+ # 单址 → [addr, addr]
59
+ s, e = IP.range('1.0.4.0')
60
+ [s.to_d, e.to_d] # => ["1.0.4.0", "1.0.4.0"]
61
+
62
+ # IPv6 同理
63
+ s6, e6 = IP.range('2001:db8::/64')
64
+ [s6.to_s, e6.to_s] # => ["2001:db8::", "2001:db8::ffff:ffff:ffff:ffff"]
65
+ ```
66
+
67
+ ### `IP.cross(range1, range2)` — 判断两区间是否相交
68
+
69
+ ```ruby
70
+ r1 = IP.range('10.0.0.0/24') # [10.0.0.0, 10.0.0.255]
71
+ r2 = IP.range('10.0.0.128/25') # [10.0.0.128, 10.0.0.255]
72
+ r3 = IP.range('10.0.1.0/24') # [10.0.1.0, 10.0.1.255]
73
+
74
+ IP.cross(r1, r2) # => true 相交
75
+ IP.cross(r1, r3) # => false 不相交
76
+ ```
77
+
78
+ ### `IP.xross(range1, range2, option)` — 求两区间合并端点
79
+
80
+ 返回排序去重后的端点序列,从 0 地址起算转换为 IP 对象:
81
+
82
+ ```ruby
83
+ r1 = IP.range('10.0.0.0/24')
84
+ r2 = IP.range('10.0.0.128/25')
85
+
86
+ IP.xross(r1, r2, :v4).map(&:to_d)
87
+ # => ["10.0.0.0", "10.0.0.128", "10.0.0.255"]
88
+
89
+ # IPv6 用 :v6
90
+ r1v6 = IP.range('2001:db8::/64')
91
+ r2v6 = IP.range('2001:db8::8000:0/113')
92
+ IP.xross(r1v6, r2v6, :v6).map(&:to_s)
93
+ ```
94
+
95
+ ---
96
+
97
+ ## IPv4 对象方法
98
+
99
+ ### 创建
100
+
101
+ ```ruby
102
+ ip = IPv4.new('10.37.214.42')
103
+ ip2, mask = IP.v4('192.168.1.0/24') # 推荐:通过 IP.v4 解析 CIDR
104
+
105
+ # IPv4 接受十六进制 / 二进制前缀
106
+ IPv4.new('0x0a.0x25.0xd6.0x2a').to_d # => "10.37.214.42"
107
+ IPv4.new('0b00001010.0.0.1').to_d # => "10.0.0.1"
108
+ ```
109
+
110
+ ### 输出格式
111
+
112
+ ```ruby
113
+ ip = IPv4.new('10.37.214.42')
114
+
115
+ ip.to_a # => [10, 37, 214, 42] 四段整数数组
116
+ ip.to_d # => "10.37.214.42" 十进制点分
117
+ ip.to_h # => "0a.25.d6.2a" 十六进制点分
118
+ ip.to_b # => "00001010.00100101.11010110.00101010" 二进制点分
119
+ ip.to_s # => "10.37.214.42" 同 to_d
120
+ ip.number # => 173185606 32位整数
121
+ ```
122
+
123
+ ### 算术运算
124
+
125
+ ```ruby
126
+ ip = IPv4.new('10.0.0.1')
127
+
128
+ (ip + 5).to_d # => "10.0.0.6" 加偏移量
129
+ (ip - 1).to_d # => "10.0.0.0" 减偏移量
130
+
131
+ # 按位与:可传 IPv4、IPv4Mask 或前缀长度
132
+ ip & IPv4.new('255.255.255.0') # 网络地址
133
+ ip & 24 # 同上,等价写法
134
+ ```
135
+
136
+ ### 地址分类
137
+
138
+ ```ruby
139
+ IPv4.new('10.0.0.1').class_a? # => true A 类 (10-126)
140
+ IPv4.new('128.1.0.0').class_b? # => true B 类 (128-191)
141
+ IPv4.new('192.0.1.0').class_c? # => true C 类 (192-223)
142
+ IPv4.new('224.0.0.1').class_d? # => true D 类 (224-239) 组播
143
+ IPv4.new('240.0.0.1').class_e? # => true E 类 (240-255) 保留
144
+
145
+ IPv4.new('10.0.0.1').private? # => true 私有地址 (10/172.16-31/192.168)
146
+ IPv4.new('8.8.8.8').private? # => false
147
+ IPv4.new('127.0.0.1').loopback? # => true 环回地址
148
+ IPv4.new('0.0.0.0').special? # => true 特殊地址
149
+ ```
150
+
151
+ ### 掩码与网络
152
+
153
+ ```ruby
154
+ ip, mask = IP.v4('10.37.214.42/17')
155
+
156
+ # 掩码属性
157
+ mask.to_d # => "255.255.128.0"
158
+ mask.mask? # => true 是否合法网络掩码
159
+ mask.mask_counter # => 17 前缀长度
160
+ mask.anti_mask # => 反掩码对象
161
+
162
+ # 网络地址
163
+ net = ip.network_with(mask)
164
+ net.to_d # => "10.37.128.0"
165
+
166
+ # 网段完整区间
167
+ start_ip, end_ip = ip.range_with(mask)
168
+ start_ip.to_d # => "10.37.128.0" 网络地址
169
+ end_ip.to_d # => "10.37.255.255" 广播地址
170
+
171
+ # 判断是否为该网段的网络地址
172
+ ip.network_with?(mask) # => false
173
+ net.network_with?(mask) # => true
174
+
175
+ # 最长公共前缀长度
176
+ IPv4.new('10.0.0.1').prefix_with(IPv4.new('10.0.1.1')) # => 23
177
+ ```
178
+
179
+ ### 子网划分 `delegation`
180
+
181
+ ```ruby
182
+ # 把 10.0.0.0/24 划分为 /26 子网 → 4 个子网
183
+ base = IPv4.new('10.0.0.0')
184
+ subnets = base.delegation(24, 26)
185
+ subnets.map { |ip, m| "#{ip.to_d}/#{m.mask_counter}" }
186
+ # => ["10.0.0.0/26", "10.0.0.64/26", "10.0.0.128/26", "10.0.0.192/26"]
187
+
188
+ # 也可传 IPv4/IPv4Mask 作为参数
189
+ base.delegation(IPv4Mask.number(24), IPv4Mask.number(27))
190
+ ```
191
+
192
+ ### 排序比较
193
+
194
+ ```ruby
195
+ addrs = [IPv4.new('10.0.0.3'), IPv4.new('10.0.0.1'), IPv4.new('10.0.0.2')]
196
+ addrs.sort.map(&:to_d) # => ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
197
+ addrs.min.to_d # => "10.0.0.1"
198
+ ```
199
+
200
+ ---
201
+
202
+ ## IPv6 对象方法
203
+
204
+ 接口与 IPv4 对称,区别在于 128 位、8 段十六进制。
205
+
206
+ ```ruby
207
+ ip6, mask6 = IP.v6('2001:db8::1/64')
208
+
209
+ # 输出
210
+ ip6.to_a # => [8193, 3512, 0, 0, 0, 0, 0, 1]
211
+ ip6.to_h # => "2001:0db8:0000:0000:0000:0000:0000:0001"
212
+ ip6.to_s # => "2001:db8::1" 压缩形式
213
+ ip6.to_s_full # => "2001:0db8:0000:...0001" 完整形式
214
+ ip6.number # => 42540488161975842760550356425300241409
215
+
216
+ # 算术 / 掩码 / 网络(同 IPv4)
217
+ ip6 & 64 # 网络地址
218
+ ip6.network_with(mask6).to_s # => "2001:db8::"
219
+ ip6.range_with(mask6).map(&:to_s) # => ["2001:db8::", "2001:db8::ffff:ffff:ffff:ffff"]
220
+
221
+ # 子网划分
222
+ base6 = IPv6.new('2001:db8::')
223
+ subnets6 = base6.delegation(64, 66)
224
+ subnets6.map { |ip, m| "#{ip.to_s}/#{m.mask_counter}" }
225
+ # => ["2001:db8::/66", "2001:db8:0:0:4000::/66", "2001:db8:0:0:8000::/66", "2001:db8:0:0:c000::/66"]
226
+ ```
227
+
228
+ ---
229
+
230
+ ## IPv4Mask / IPv6Mask 工厂
231
+
232
+ ```ruby
233
+ # 按前缀长度创建网络掩码
234
+ IPv4Mask.number(24).to_d # => "255.255.255.0"
235
+ IPv4Mask.number(17).to_d # => "255.255.128.0"
236
+ IPv6Mask.number(64).to_h # => "ffff:ffff:ffff:ffff:0000:0000:0000:0000"
237
+
238
+ # 按字符串创建(校验合法性)
239
+ IPv4Mask.string('255.255.0.0').mask_counter # => 16
240
+
241
+ # 反掩码
242
+ IPv4Mask.anti_number(24).to_d # => "0.0.0.255"
243
+ IPv4Mask.anti_string('0.0.0.255').anti_mask_counter # => 24
244
+ ```
245
+
246
+ ---
247
+
248
+ ## 典型业务场景
249
+
250
+ ### 1. 判断 IP 是否在某个网段内
251
+
252
+ ```ruby
253
+ ip = IP.v4('10.37.214.42')
254
+ net_start, net_end = IP.range('10.37.128.0/17')
255
+ ip.number.between?(net_start.number, net_end.number) # => true
256
+ ```
257
+
258
+ ### 2. CIDR 转区间 → 整数范围(geo-api 内部用法)
259
+
260
+ ```ruby
261
+ # geodb.rb 把 CIDR 转为 [start_num, end_num] 作为 JSON 键
262
+ range = IP.range('1.0.4.0/22').map(&:number) # => [16777984, 16778239]
263
+ ```
264
+
265
+ ### 3. 合并重叠网段
266
+
267
+ ```ruby
268
+ ranges = [
269
+ IP.range('10.0.0.0/24'),
270
+ IP.range('10.0.0.128/25'),
271
+ IP.range('10.0.1.0/24')
272
+ ]
273
+
274
+ # 去重合并
275
+ merged = [ranges.first]
276
+ ranges[1..].each do |r|
277
+ if IP.cross(merged.last, r)
278
+ merged[-1] = [merged.last.first, [merged.last.last, r.last].max]
279
+ else
280
+ merged << r
281
+ end
282
+ end
283
+
284
+ merged.map { |s, e| "#{s.to_d} - #{e.to_d}" }
285
+ # => ["10.0.0.0 - 10.0.1.255"]
286
+ ```
287
+
288
+ ### 4. 子网规划
289
+
290
+ ```ruby
291
+ # 从 172.16.0.0/16 中分配 /24 子网,取前 4 个
292
+ base = IPv4.new('172.16.0.0')
293
+ all_subnets = base.delegation(16, 24)
294
+ allocated = all_subnets.first(4)
295
+ allocated.map { |ip, m| ip.to_d + "/#{m.mask_counter}" }
296
+ # => ["172.16.0.0/24", "172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"]
297
+ ```
@@ -0,0 +1,296 @@
1
+
2
+ # MAC 模块功能说明
3
+
4
+ > 源码位置:`support/basic/mac_address.rb`
5
+ > 加载方式:`require 'network'` 自动加载,或单独 `require_relative 'support/basic/mac_address'`
6
+
7
+ ## MAC 地址背景
8
+
9
+ MAC 地址是数据链路层 48 位硬件地址,通常用 6 组或 3 组十六进制表示。
10
+
11
+ ### 支持的输入格式
12
+
13
+ | 分隔符 | 分组数 | 示例 | 说明 |
14
+ | ------ | ------ | ------------------- | ---------------- |
15
+ | `-` | 6 | `00-1A-2B-3C-4D-5E` | Windows 常用 |
16
+ | `:` | 6 | `00:1A:2B:3C:4D:5E` | Linux/Cisco 常用 |
17
+ | `.` | 3 | `001A.2B3C.4D5E` | 点分格式 |
18
+ | `-` | 3 | `001A-2B3C-4D5E` | 部分设备 3 段横杠 |
19
+ | 无 | — | `001A2B3C4D5E` | 无分隔符 |
20
+
21
+ ### 特殊位
22
+
23
+ | 位 | 位置 | 说明 |
24
+ | ------ | ----------- | ----------------------------- |
25
+ | I/G 位 | 首字节 bit0 | 0=单播(个体),1=组播 |
26
+ | U/L 位 | 首字节 bit1 | 0=全球分配(OUI),1=本地分配 |
27
+
28
+ ### EUI-64
29
+
30
+ MAC → EUI-64:在 OUI(前 3 字节)和 NIC(后 3 字节)之间插入 `FF:FE`。
31
+
32
+ ```
33
+ 00-1A-2B-3C-4D-5E → 00:1A:2B:FF:FE:3C:4D:5E
34
+ ```
35
+
36
+ IPv6 SLAAC 接口标识符在此基础上翻转 U/L 位(首字节 XOR `0x02`)。
37
+
38
+ ---
39
+
40
+ ## 统一入口 `MAC`
41
+
42
+ `MAC.address` 接受 String、Array 或 Integer,返回 `MacAddress` 对象:
43
+
44
+ ```ruby
45
+ require 'network'
46
+
47
+ MAC.address("00:1A:2B:3C:4D:5E") # 6 段冒号
48
+ MAC.address("001A.2B3C.4D5E") # 3 段点分
49
+ MAC.address("001A-2B3C-4D5E") # 3 段横杠
50
+ MAC.address("001A2B3C4D5E") # 无分隔符
51
+ MAC.address(0x001A2B3C4D5E) # Integer
52
+ MAC.address([0x00, 0x1A, 0x2B, 0x3C, 0x4D, 0x5E]) # Array
53
+ ```
54
+
55
+ 也可直接用 `MacAddress.new`,参数同上。
56
+
57
+ 不同格式的同一地址等价:
58
+
59
+ ```ruby
60
+ a = MAC.address("00:1A:2B:3C:4D:5E")
61
+ b = MAC.address("001A.2B3C.4D5E")
62
+ a == b # => true
63
+ a.number == b.number # => true
64
+ ```
65
+
66
+ ---
67
+
68
+ ## 属性
69
+
70
+ ```ruby
71
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
72
+
73
+ mac.numbers # => [0, 26, 43, 60, 77, 94] 6 字节整数数组
74
+ mac.number # => 112394521950 48 位整数
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 输出格式
80
+
81
+ ### `to_hex(separator, groups)` — 通用格式化
82
+
83
+ `separator` 控制分隔符,`groups` 控制分组数(1 / 2 / 3 / 6):
84
+
85
+ ```ruby
86
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
87
+
88
+ mac.to_hex("-", 6) # => "00-1a-2b-3c-4d-5e" 6 组(默认)
89
+ mac.to_hex(":", 6) # => "00:1a:2b:3c:4d:5e" Linux 格式
90
+ mac.to_hex(".", 3) # => "001a.2b3c.4d5e" Cisco 点分
91
+ mac.to_hex("", 1) # => "001a2b3c4d5e" 无分隔符
92
+ mac.to_hex("", 6) # => "001a2b3c4d5e" 无分隔符(6 组拼合)
93
+ ```
94
+
95
+ ### 其他格式方法
96
+
97
+ ```ruby
98
+ mac.to_s # => "00-1a-2b-3c-4d-5e" 默认 6 组 - 分隔
99
+ mac.to_upcase(":", 6) # => "00:1A:2B:3C:4D:5E" 大写
100
+ mac.to_downcase # => "00-1a-2b-3c-4d-5e" 小写
101
+ mac.inspect # => "00-1a-2b-3c-4d-5e" 同 to_s
102
+
103
+ mac.to_a # => [0, 26, 43, 60, 77, 94] 6 字节整数数组
104
+ ```
105
+
106
+ ---
107
+
108
+ ## 属性判定
109
+
110
+ ### 单播 / 组播(I/G 位)
111
+
112
+ ```ruby
113
+ MacAddress.new("00-1A-2B-3C-4D-5E").multicast? # => false 单播
114
+ MacAddress.new("01-00-5E-00-00-01").multicast? # => true 组播
115
+
116
+ # 别名
117
+ mac.group? # 等价 multicast?
118
+ ```
119
+
120
+ ### 全球 / 本地分配(U/L 位)
121
+
122
+ ```ruby
123
+ MacAddress.new("00-1A-2B-3C-4D-5E").locally_administered? # => false 全球分配
124
+ MacAddress.new("02-00-00-00-00-01").locally_administered? # => true 本地分配
125
+
126
+ MacAddress.new("00-1A-2B-3C-4D-5E").universally_administered? # => true
127
+ ```
128
+
129
+ ### 特殊地址
130
+
131
+ ```ruby
132
+ MacAddress.new("FF-FF-FF-FF-FF-FF").broadcast? # => true 全 1 广播
133
+ MacAddress.new("00-00-00-00-00-00").zero? # => true 全 0 空地址
134
+ ```
135
+
136
+ ### OUI / NIC 分段
137
+
138
+ ```ruby
139
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
140
+
141
+ mac.oui # => "00-1a-2b" 前 3 字节(组织唯一标识符)
142
+ mac.nic # => "3c-4d-5e" 后 3 字节(网卡标识)
143
+ ```
144
+
145
+ ---
146
+
147
+ ## EUI-64 / IPv6 SLAAC
148
+
149
+ ### `to_eui64` — 扩展为 8 字节
150
+
151
+ 在 OUI 和 NIC 之间插入 `FF:FE`,返回 8 字节整数数组:
152
+
153
+ ```ruby
154
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
155
+
156
+ mac.to_eui64
157
+ # => [0, 26, 43, 255, 254, 60, 77, 94]
158
+
159
+ mac.to_eui64_s # => "00:1a:2b:ff:fe:3c:4d:5e" 默认冒号分隔
160
+ mac.to_eui64_s("-") # => "00-1a-2b-ff-fe-3c-4d-5e"
161
+ ```
162
+
163
+ ### `to_interface_id` — IPv6 SLAAC 接口标识符
164
+
165
+ 在 EUI-64 基础上翻转 U/L 位(首字节 bit1 翻转),返回 8 字节整数数组:
166
+
167
+ ```ruby
168
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
169
+
170
+ mac.to_interface_id
171
+ # => [2, 26, 43, 255, 254, 60, 77, 94]
172
+
173
+ mac.to_interface_id_s # => "02:1a:2b:ff:fe:3c:4d:5e"
174
+ mac.to_interface_id_s("-") # => "02-1a-2b-ff-fe-3c-4d-5e"
175
+ ```
176
+
177
+ > `00` 翻转后变 `02`,因为 U/L 位(bit1)从 0 变 1,表示本地生成。
178
+
179
+ ---
180
+
181
+ ## 排序比较
182
+
183
+ `MacAddress` 包含 `Comparable`,按 48 位整数排序:
184
+
185
+ ```ruby
186
+ a = MacAddress.new("00-00-00-00-00-01")
187
+ b = MacAddress.new("00-00-00-00-00-02")
188
+ c = MacAddress.new("FF-FF-FF-FF-FF-FF")
189
+
190
+ a < b # => true
191
+ a == a # => true
192
+
193
+ [a, c, b].sort.map(&:number)
194
+ # => [1, 2, 281474976710655]
195
+ ```
196
+
197
+ ### 作为 Hash key
198
+
199
+ ```ruby
200
+ h = {
201
+ MacAddress.new("00-00-00-00-00-01") => "网关",
202
+ MacAddress.new("00-00-00-00-00-02") => "终端"
203
+ }
204
+ h[MacAddress.new("00:00:00:00:00:01")] # => "网关"(不同分隔符等价)
205
+
206
+ MacAddress.new("00:1A:2B:3C:4D:5E").hash == MacAddress.new("00-1A-2B-3C-4D-5E").hash
207
+ # => true
208
+ ```
209
+
210
+ ---
211
+
212
+ ## 有效性检查
213
+
214
+ `MacAddress.valid?` 不抛异常,返回布尔值:
215
+
216
+ ```ruby
217
+ MacAddress.valid?("00:1A:2B:3C:4D:5E") # => true
218
+ MacAddress.valid?("GG:1A:2B:3C:4D:5E") # => false
219
+ MacAddress.valid?("00-1A-2B-3C") # => false 段数错误
220
+ ```
221
+
222
+ ---
223
+
224
+ ## 兼容原接口
225
+
226
+ ### `writing(groups, splitter)` — 原版格式化方法
227
+
228
+ ```ruby
229
+ mac = MacAddress.new("00-1A-2B-3C-4D-5E")
230
+
231
+ mac.writing # => "001a-2b3c-4d5e" 默认 3 组 - 分隔
232
+ mac.writing(6, ":") # => "00:1a:2b:3c:4d:5e" 6 组冒号
233
+ mac.writing(10, "-") # => "00-1a-2b-3c-4d-5e" >=6 归为 6
234
+ ```
235
+
236
+ > 推荐使用 `to_hex` 代替 `writing`,接口更清晰。
237
+
238
+ ---
239
+
240
+ ## 典型业务场景
241
+
242
+ ### 1. 批量格式转换
243
+
244
+ ```ruby
245
+ # 设备导出的 MAC 格式不统一,统一转为冒号格式
246
+ macs = ["00-1A-2B-3C-4D-5E", "001A.2B3C.4D5E", "001A-2B3C-4D5E", "001A2B3C4D5E"]
247
+ macs.map { |m| MAC.address(m).to_hex(":", 6) }
248
+ # => ["00:1a:2b:3c:4d:5e", "00:1a:2b:3c:4d:5e", "00:1a:2b:3c:4d:5e", "00:1a:2b:3c:4d:5e"]
249
+ ```
250
+
251
+ ### 2. 识别组播地址
252
+
253
+ ```ruby
254
+ # 从抓包数据中筛出组播 MAC
255
+ mac_list = ["00-1A-2B-3C-4D-5E", "01-00-5E-00-00-01", "33-33-00-00-00-01"]
256
+
257
+ multicast = mac_list.select { |m| MAC.address(m).multicast? }
258
+ # => ["01-00-5E-00-00-01", "33-33-00-00-00-01"]
259
+ ```
260
+
261
+ ### 3. 提取 OUI 做厂商归类
262
+
263
+ ```ruby
264
+ # 按 OUI 前缀分组统计设备厂商
265
+ devices = [
266
+ "00-1A-2B-3C-4D-5E", # OUI: 00-1a-2b
267
+ "00-1A-2B-AA-BB-CC", # OUI: 00-1a-2b
268
+ "08-00-27-11-22-33", # OUI: 08-00-27
269
+ ]
270
+
271
+ devices.group_by { |m| MAC.address(m).oui }
272
+ # => {"00-1a-2b" => ["00-1A-2B-3C-4D-5E", "00-1A-2B-AA-BB-CC"],
273
+ # "08-00-27" => ["08-00-27-11-22-33"]}
274
+ ```
275
+
276
+ ### 4. 生成 IPv6 SLAAC 地址
277
+
278
+ ```ruby
279
+ # 已知 MAC 和 IPv6 前缀,生成 SLAAC 自动配置地址
280
+ mac = MAC.address("00-1A-2B-3C-4D-5E")
281
+ prefix = "2001:db8::"
282
+
283
+ interface_id = mac.to_interface_id_s(":")
284
+ slaac_address = "#{prefix}#{interface_id}"
285
+ # => "2001:db8::02:1a:2b:ff:fe:3c:4d:5e"
286
+ ```
287
+
288
+ ### 5. MAC 地址去重
289
+
290
+ ```ruby
291
+ # 不同格式记录的同一 MAC 需要去重
292
+ raw = ["00:1A:2B:3C:4D:5E", "00-1A-2B-3C-4D-5E", "001A.2B3C.4D5E", "001A-2B3C-4D5E", "08-00-27-11-22-33"]
293
+
294
+ unique = raw.map { |m| MAC.address(m) }.uniq.map(&:to_s)
295
+ # => ["00-1a-2b-3c-4d-5e", "08-00-27-11-22-33"]
296
+ ```