wireless-rb 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/CHANGELOG.md +49 -0
- data/README-zh.md +37 -2
- data/README.md +34 -3
- data/docs/architecture-zh.md +4 -4
- data/lib/wireless/ap_session.rb +113 -52
- data/lib/wireless/collector.rb +49 -36
- data/lib/wireless/configuration.rb +0 -2
- data/lib/wireless/coverage.rb +15 -5
- data/lib/wireless/fields.rb +25 -20
- data/lib/wireless/h3c/{authentication_parser.rb → authentication.rb} +4 -4
- data/lib/wireless/h3c/command_output.rb +44 -3
- data/lib/wireless/h3c/configuration.rb +21 -5
- data/lib/wireless/h3c/document.rb +32 -0
- data/lib/wireless/h3c/parsers/display_current_configuration.rb +143 -69
- data/lib/wireless/h3c/parsers/display_dot1x_connection.rb +4 -2
- data/lib/wireless/h3c/parsers/display_lldp_neighbor_information_verbose.rb +34 -10
- data/lib/wireless/h3c/parsers/display_mac_authentication_connection.rb +4 -2
- data/lib/wireless/h3c/parsers/display_wlan_ap_all_verbose.rb +37 -11
- data/lib/wireless/h3c/parsers/display_wlan_ap_model_all.rb +5 -5
- data/lib/wireless/h3c/parsers/display_wlan_bss_all_verbose.rb +6 -6
- data/lib/wireless/h3c/parsers/display_wlan_client_security_block_mac.rb +2 -2
- data/lib/wireless/h3c/parsers/display_wlan_client_verbose.rb +14 -8
- data/lib/wireless/h3c/parsers/display_wlan_statistics_client_ip_conflict.rb +2 -2
- data/lib/wireless/h3c/profile.rb +6 -3
- data/lib/wireless/h3c/record_parser.rb +138 -0
- data/lib/wireless/huawei/parser.rb +86 -0
- data/lib/wireless/huawei/parsers/display_ap_all.rb +14 -0
- data/lib/wireless/huawei/parsers/display_station_all.rb +14 -0
- data/lib/wireless/huawei/profile.rb +22 -0
- data/lib/wireless/observation.rb +2 -0
- data/lib/wireless/parser_registry.rb +2 -1
- data/lib/wireless/projection.rb +4 -0
- data/lib/wireless/runtime_parser.rb +16 -11
- data/lib/wireless/snapshot.rb +2 -1
- data/lib/wireless/terminal.rb +32 -0
- data/lib/wireless/version.rb +1 -1
- data/lib/wireless.rb +0 -2
- metadata +9 -20
- data/lib/wireless/h3c/session_parser.rb +0 -58
- data/lib/wireless/h3c/verbose_parser.rb +0 -82
- data/lib/wireless/h3c_runtime.rb +0 -18
- data/lib/wireless/huawei_runtime.rb +0 -65
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../command_output"
|
|
4
4
|
require_relative "../configuration"
|
|
5
|
-
|
|
5
|
+
require_relative "../document"
|
|
6
|
+
require "ctx_parser/configuration"
|
|
6
7
|
|
|
7
8
|
module Wireless
|
|
8
9
|
module H3c
|
|
9
10
|
module Parsers
|
|
10
11
|
# 用缩进保留型号和 Radio 归属;相关命令不受支持时降低完整性。
|
|
11
|
-
class DisplayCurrentConfiguration < CtxParser::
|
|
12
|
+
class DisplayCurrentConfiguration < CtxParser::Configuration
|
|
12
13
|
include CommandOutput
|
|
13
14
|
|
|
14
15
|
private
|
|
15
16
|
|
|
17
|
+
# 配置由 Document 标记受损行,仍保留无关事实和原始来源。
|
|
18
|
+
def terminal_issue(_text) = nil
|
|
19
|
+
|
|
16
20
|
# 声明当前命令解析器所需的换行归一化方式。
|
|
17
21
|
def output_newlines = :preserve
|
|
18
22
|
|
|
@@ -20,7 +24,9 @@ module Wireless
|
|
|
20
24
|
def parse_text(text, complete:)
|
|
21
25
|
@configuration = H3c::Configuration.new
|
|
22
26
|
@issues = []
|
|
23
|
-
|
|
27
|
+
@setting_assignments = {}.compare_by_identity
|
|
28
|
+
read_configuration(text, comments: nil, boundaries: /\A(?:#|return)\z/, document_class: Document)
|
|
29
|
+
@issues.concat(document.issues)
|
|
24
30
|
recognized = cursor.lines.any? { |line| line.text == "return" }
|
|
25
31
|
blocks.each { |block| recognized = parse_block(block) || recognized }
|
|
26
32
|
@issues << { code: :missing_completeness_evidence } unless complete
|
|
@@ -43,14 +49,12 @@ module Wireless
|
|
|
43
49
|
line = block.text
|
|
44
50
|
selected = owner ? nested_scope(line, owner) : root_scope(line)
|
|
45
51
|
if selected
|
|
46
|
-
|
|
47
|
-
block.children.each { |child| parse_block(child, selected) }
|
|
48
|
-
selected.finish
|
|
49
|
-
end
|
|
52
|
+
parse_scope(block, selected)
|
|
50
53
|
true
|
|
51
54
|
elsif !owner && (match = /\Asysname (\S+)\z/.match(line))
|
|
52
|
-
@configuration.data[:global_settings]
|
|
53
|
-
|
|
55
|
+
settings = @configuration.data[:global_settings]
|
|
56
|
+
assign_setting(settings, :hostname, match[1], block)
|
|
57
|
+
capture(block, settings[:hostname], [ :global_settings, :hostname ]) if settings.key?(:hostname)
|
|
54
58
|
true
|
|
55
59
|
elsif owner
|
|
56
60
|
apply_block(block, owner)
|
|
@@ -68,9 +72,12 @@ module Wireless
|
|
|
68
72
|
if (match = /\Awlan (ap-group|service-template|ap) (\S+)(?: (.+))?\z/i.match(line))
|
|
69
73
|
kind = { "ap-group" => :ap_groups, "service-template" => :service_templates, "ap" => :access_points }.fetch(match[1].downcase)
|
|
70
74
|
name, suffix = match[2], match[3]
|
|
75
|
+
if kind == :ap_groups && suffix && !/\Atype \S+\z/i.match?(suffix)
|
|
76
|
+
raise ArgumentError, "Invalid AP group type"
|
|
77
|
+
end
|
|
71
78
|
raise ArgumentError, "Invalid service template" if kind == :service_templates && suffix
|
|
72
|
-
raise ArgumentError, "Missing AP model" if kind == :access_points &&
|
|
73
|
-
@configuration.entity(kind, name, model: suffix&.
|
|
79
|
+
raise ArgumentError, "Missing AP model" if kind == :access_points && !/\Amodel \S+\z/i.match?(suffix.to_s)
|
|
80
|
+
@configuration.entity(kind, name, model: suffix&.sub(/\Amodel /i, ""), type: suffix&.sub(/\Atype /i, ""))
|
|
74
81
|
elsif (match = /\Awlan (global-configuration|virtual-ap-group(?: \S+)?|ap-license-group|vip-client-group)\z/i.match(line))
|
|
75
82
|
key = match[1].tr("-", "_").split.first.to_sym
|
|
76
83
|
@configuration.global(key, identity: line.split[2])
|
|
@@ -89,7 +96,7 @@ module Wireless
|
|
|
89
96
|
|
|
90
97
|
# 标记已识别叶子语句,不将其嵌套未知语法提升到父对象。
|
|
91
98
|
def apply_block(block, owner)
|
|
92
|
-
unless apply(owner, block
|
|
99
|
+
unless apply(owner, block, @configuration.data)
|
|
93
100
|
unrecognized_block(block)
|
|
94
101
|
return false
|
|
95
102
|
end
|
|
@@ -108,134 +115,201 @@ module Wireless
|
|
|
108
115
|
|
|
109
116
|
# 逐来源行记录不支持语法,并保留完整来源块。
|
|
110
117
|
def unrecognized_block(block)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
pending = [ block ]
|
|
119
|
+
until pending.empty?
|
|
120
|
+
current = pending.pop
|
|
121
|
+
cursor.mark(current.index, state: :unsupported)
|
|
122
|
+
@issues << { code: :unrecognized_wireless_configuration, line: current.line }
|
|
123
|
+
pending.concat(current.children.reverse)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# 无关配置可忽略,未知父级内的无线声明不能被当作不存在。
|
|
128
|
+
def ignore(block)
|
|
129
|
+
pending = [ block ]
|
|
130
|
+
until pending.empty?
|
|
131
|
+
current = pending.pop
|
|
132
|
+
if /\A(?:undo )?wlan\b/i.match?(current.text)
|
|
133
|
+
unrecognized_block(current)
|
|
134
|
+
else
|
|
135
|
+
cursor.mark(current.index, state: :ignored)
|
|
136
|
+
pending.concat(current.children.reverse)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
nil
|
|
114
140
|
end
|
|
115
141
|
|
|
116
142
|
# 报告未在预期作用域消费的相关子语句。
|
|
117
143
|
def unparsed_child(block) = unrecognized_block(block)
|
|
118
144
|
|
|
145
|
+
# 每个声明块独立检查单值;重复进入的顺序配置仍保留各自来源快照。
|
|
146
|
+
def parse_scope(block, owner)
|
|
147
|
+
previous_assignments = @setting_assignments
|
|
148
|
+
@setting_assignments = {}.compare_by_identity
|
|
149
|
+
scope(block, owner.path) do
|
|
150
|
+
block.children.each { |child| parse_block(child, owner) }
|
|
151
|
+
if owner.kind == :service_templates && @setting_assignments.values.any? { |fields| fields.values.any? { |entry| entry[:conflicted] } }
|
|
152
|
+
owner.record[:authentication_type] = nil
|
|
153
|
+
else
|
|
154
|
+
owner.finish
|
|
155
|
+
end
|
|
156
|
+
owner.record
|
|
157
|
+
end
|
|
158
|
+
ensure
|
|
159
|
+
@setting_assignments = previous_assignments
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# 等价重复不改变完整性;同块冲突删除事实,并使原始来源和后续声明失效。
|
|
163
|
+
def assign_setting(record, key, value, block)
|
|
164
|
+
assign_settings(record, key, { key => value }, block)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# 一条命令的关联字段共享身份,冲突时整体排除,不能留下残缺派生标志。
|
|
168
|
+
def assign_settings(record, identity, values, block)
|
|
169
|
+
fields = (@setting_assignments[record] ||= {})
|
|
170
|
+
previous = fields[identity]
|
|
171
|
+
if previous && (previous[:conflicted] || previous[:value] != values)
|
|
172
|
+
(previous[:value].keys | values.keys).each { |key| record.delete(key) }
|
|
173
|
+
cursor.mark(previous[:block].index, state: :invalid)
|
|
174
|
+
cursor.mark(block.index, state: :invalid)
|
|
175
|
+
@issues << { code: :conflicting_configuration_value, line: block.line, first_line: previous[:block].line }
|
|
176
|
+
previous[:conflicted] = true
|
|
177
|
+
else
|
|
178
|
+
fields[identity] ||= { value: values, block: block }
|
|
179
|
+
record.merge!(values)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# 未声明的可选 VLAN 保持未知,已声明值须在设备 VLAN 范围内。
|
|
184
|
+
def configured_vlan(raw)
|
|
185
|
+
vlan = Fields.integer(raw, max: 4094)
|
|
186
|
+
raise ArgumentError, "Invalid configured VLAN" if vlan && vlan.zero?
|
|
187
|
+
vlan
|
|
188
|
+
end
|
|
189
|
+
|
|
119
190
|
# 将已识别的无线设置应用到当前作用域记录。
|
|
120
|
-
def apply(scope,
|
|
191
|
+
def apply(scope, block, config)
|
|
192
|
+
line = block.text
|
|
121
193
|
owner, kind, record = scope.record, scope.kind, scope.settings
|
|
122
194
|
words = line.split
|
|
123
195
|
enabled = !line.start_with?("undo ")
|
|
124
196
|
case line
|
|
125
197
|
when /\A(?:undo )?region-code-lock enable\z/
|
|
126
198
|
return false unless kind == :global_configuration
|
|
127
|
-
record
|
|
199
|
+
assign_setting(record, :region_code_lock, enabled, block)
|
|
128
200
|
when /\Anas-id (\S+)\z/
|
|
129
201
|
return false unless kind == :global_configuration
|
|
130
|
-
record
|
|
202
|
+
assign_setting(record, :nas_id, Regexp.last_match(1), block)
|
|
131
203
|
when /\A(?:undo )?rrop anti-bmc network (ipv4-and-ipv6-simple) enable\z/
|
|
132
204
|
return false unless kind == :ap_groups
|
|
133
|
-
(record[:rrop_anti_bmc] ||= {}
|
|
205
|
+
assign_setting(record[:rrop_anti_bmc] ||= {}, :network, { mode: Regexp.last_match(1), enabled: enabled }, block)
|
|
134
206
|
when /\Arrop anti-bmc default-action (deny|permit)\z/
|
|
135
207
|
return false unless kind == :ap_groups
|
|
136
|
-
(record[:rrop_anti_bmc] ||= {}
|
|
208
|
+
assign_setting(record[:rrop_anti_bmc] ||= {}, :default_action, Regexp.last_match(1), block)
|
|
137
209
|
when /\Aserial-id (\S+)\z/
|
|
138
210
|
return false unless %i[access_points ap_groups].include?(kind)
|
|
139
|
-
kind == :ap_groups ? owner[:serial_ids] << words.last : record
|
|
211
|
+
kind == :ap_groups ? owner[:serial_ids] << words.last : assign_setting(record, :serial_id, words.last, block)
|
|
140
212
|
when /\Aap (\S+)\z/
|
|
141
213
|
return false unless kind == :ap_groups
|
|
142
214
|
owner[:ap_names] << words.last
|
|
143
215
|
when /\Avlan (\d+)\z/
|
|
144
|
-
vlan =
|
|
145
|
-
kind == :ap_groups ? owner[:vlan_ids] << vlan : owner
|
|
216
|
+
vlan = configured_vlan(words.last)
|
|
217
|
+
kind == :ap_groups ? owner[:vlan_ids] << vlan : assign_setting(owner, :vlan_id, vlan, block)
|
|
146
218
|
when /\Assid (.+)\z/
|
|
147
219
|
return false unless kind == :service_templates
|
|
148
|
-
owner
|
|
220
|
+
assign_setting(owner, :ssid, Regexp.last_match(1), block)
|
|
149
221
|
when /\A(?:undo )?service-template enable\z/
|
|
150
222
|
return false unless kind == :service_templates
|
|
151
|
-
owner
|
|
223
|
+
assign_setting(owner, :enabled, enabled, block)
|
|
152
224
|
when /\Aservice-template (\S+)(?: vlan (\d+))?\z/
|
|
153
225
|
return false unless kind == :radio
|
|
154
226
|
name, vlan = Regexp.last_match.captures
|
|
155
|
-
|
|
156
|
-
previous = record[:service_templates].find { |entry| entry[:name] == name }
|
|
157
|
-
raise ArgumentError, "Conflicting template binding" if previous && previous != binding
|
|
158
|
-
record[:service_templates] |= [ binding ]
|
|
227
|
+
@configuration.bind_template(record, name, configured_vlan(vlan))
|
|
159
228
|
when /\Apreshared-key\s+.+\z/i
|
|
160
|
-
record
|
|
229
|
+
assign_setting(record, :has_preshared_key, true, block)
|
|
161
230
|
when /\Aakm mode (.+)\z/
|
|
162
|
-
record
|
|
231
|
+
assign_setting(record, :akm_modes, Regexp.last_match(1).downcase.gsub("802.1x", "dot1x").split, block)
|
|
163
232
|
when /\A(cipher-suite|security-ie) (.+)\z/
|
|
164
233
|
key = words.first == "cipher-suite" ? :cipher_suites : :security_ies
|
|
165
|
-
record
|
|
234
|
+
assign_setting(record, key, words.drop(1).map(&:downcase), block)
|
|
166
235
|
when /\Aclient-security authentication-mode (.+)\z/
|
|
167
|
-
|
|
236
|
+
mode = Regexp.last_match(1).downcase
|
|
237
|
+
unless %w[dot1x mac mac-and-dot1x dot1x-then-mac mac-then-dot1x oui-then-dot1x].include?(mode)
|
|
238
|
+
raise ArgumentError, "Invalid client authentication mode"
|
|
239
|
+
end
|
|
240
|
+
assign_setting(record, :client_security_authentication_mode, mode, block)
|
|
168
241
|
when /\Aclient-security (ignore-authentication|ignore-authorization)\z/
|
|
169
|
-
record
|
|
242
|
+
assign_setting(record, words.last.tr("-", "_").to_sym, true, block)
|
|
170
243
|
when /\A(dot1x|mac-authentication) domain (\S+)\z/
|
|
171
|
-
record
|
|
244
|
+
assign_setting(record, "#{words.first.tr('-', '_')}_domain".to_sym, words.last, block)
|
|
172
245
|
when /\Aportal (enable method|domain|bas-ip|apply web-server|apply mac-trigger-server) (\S+)\z/
|
|
173
246
|
key, value = Regexp.last_match.captures
|
|
174
247
|
# 仅保留 Portal 是否存在;URL 与凭据不属于无线清单契约。
|
|
175
|
-
(record[:portal] ||= {}
|
|
248
|
+
assign_setting(record[:portal] ||= {}, key.tr(" -", "__").to_sym, value.include?("://") ? true : value, block)
|
|
176
249
|
when /\A(?:undo )?(dot1x|mac-authentication)\z/
|
|
177
|
-
record
|
|
250
|
+
assign_setting(record, "#{Regexp.last_match(1).tr('-', '_')}_enabled".to_sym, enabled, block)
|
|
178
251
|
when /\Adot1x authentication-method (chap|eap|pap)\z/
|
|
179
|
-
record
|
|
252
|
+
assign_setting(record, :dot1x_authentication_method, Regexp.last_match(1), block)
|
|
180
253
|
when /\Amac-authentication user-name-format fixed(?: account \S+)?(?: password (simple|cipher) \S+)?\z/
|
|
181
254
|
# 固定账号的明文和密文均不进入普通事实或捕获快照。
|
|
182
255
|
storage = Regexp.last_match(1)
|
|
183
|
-
record
|
|
184
|
-
|
|
256
|
+
assign_settings(record, :mac_username_setting,
|
|
257
|
+
{ mac_authentication_user_name_format: "fixed", mac_authentication_has_password: !storage.nil? }, block)
|
|
185
258
|
when /\Amac-authentication user-name-format (mac-address(?: (?:with-hyphen|without-hyphen))?(?: (?:lowercase|uppercase))?)\z/
|
|
186
|
-
record
|
|
187
|
-
record.delete(:mac_authentication_has_password)
|
|
259
|
+
assign_settings(record, :mac_username_setting, { mac_authentication_user_name_format: Regexp.last_match(1) }, block)
|
|
188
260
|
when /\Adot1x timer (supp-timeout|tx-period) (\d+)\z/
|
|
189
|
-
(record[:dot1x_timers] ||= {}
|
|
261
|
+
assign_setting(record[:dot1x_timers] ||= {}, words[2].tr("-", "_").to_sym, Fields.integer(words.last), block)
|
|
190
262
|
when /\Awlan (?:client-security )?static-(black|white)list mac-address (\S+)(?: (.+))?\z/
|
|
191
263
|
kind, mac, reason = Regexp.last_match.captures
|
|
192
|
-
config[kind == "black" ? :blacklist : :whitelist] << { mac_address: Fields.
|
|
264
|
+
config[kind == "black" ? :blacklist : :whitelist] << { mac_address: Fields.mac_address(mac), reason: reason }
|
|
193
265
|
when /\A(?:undo )?(?:wlan )?(auto-ap|auto-persistent|band-navigation|client inspect|radio-load-balance band-navigation) enable\z/
|
|
194
|
-
record
|
|
266
|
+
assign_setting(record, Regexp.last_match(1).tr(" -", "__").to_sym, enabled, block)
|
|
195
267
|
when /\A(?:undo )?(beacon ssid-hide|bss transition-management enable)\z/
|
|
196
|
-
record
|
|
268
|
+
assign_setting(record, line.include?("ssid-hide") ? :ssid_hidden : :btm_enabled, enabled, block)
|
|
197
269
|
when /\A(client keep-alive|client-rate-limit|firmware-upgrade|radio) (enable|disable)\z/
|
|
198
270
|
key = { "client keep-alive" => :client_keep_alive, "client-rate-limit" => :client_rate_limit_enabled,
|
|
199
271
|
"firmware-upgrade" => :firmware_upgrade, "radio" => :enabled }.fetch(Regexp.last_match(1))
|
|
200
|
-
record
|
|
272
|
+
assign_setting(record, key, words.last == "enable", block)
|
|
201
273
|
when /\A(client keep-alive interval|client max-count|client cache aging-time|bss transition-management disassociation forced timer|statistics-interval|priority|dot1x retry|wlan tcp mss) (\d+)\z/
|
|
202
274
|
key = { "client keep-alive interval" => :client_keep_alive_interval, "client max-count" => :client_max_count,
|
|
203
275
|
"client cache aging-time" => :client_cache_aging_time, "bss transition-management disassociation forced timer" => :btm_disassociation_timer,
|
|
204
276
|
"statistics-interval" => :statistics_interval, "priority" => :priority, "dot1x retry" => :dot1x_retry, "wlan tcp mss" => :tcp_mss }.fetch(Regexp.last_match(1))
|
|
205
|
-
record
|
|
277
|
+
assign_setting(record, key, Fields.integer(words.last), block)
|
|
206
278
|
when /\A(?:description|map-configuration) (.+)\z/
|
|
207
|
-
record
|
|
208
|
-
when /\Abackup-ac ip (\S+)\z/ then record
|
|
209
|
-
when /\Achannel band-width (.+)\z/ then record
|
|
210
|
-
when /\Achannel auto unlock\z/
|
|
279
|
+
assign_setting(record, words.first.tr("-", "_").to_sym, words.drop(1).join(" "), block)
|
|
280
|
+
when /\Abackup-ac ip (\S+)\z/ then assign_setting(record, :backup_ac_ip, Fields.ip_address(words.last), block)
|
|
281
|
+
when /\Achannel band-width (.+)\z/ then assign_setting(record, :channel_bandwidth, Regexp.last_match(1), block)
|
|
282
|
+
when /\Achannel auto unlock\z/
|
|
283
|
+
assign_settings(record, :channel_setting, { channel: "auto", channel_number: nil, channel_unlocked: true }, block)
|
|
211
284
|
when /\Achannel (.+)\z/
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
285
|
+
channel = Regexp.last_match(1)
|
|
286
|
+
channel_number = Fields.channel(channel)
|
|
287
|
+
assign_settings(record, :channel_setting, { channel: channel, channel_number: channel_number }, block)
|
|
288
|
+
when /\Amax-power (\d+(?:\.\d+)?)\z/ then assign_setting(record, :max_power, Fields.number(words.last), block)
|
|
215
289
|
when /\Aclient-rate-limit (inbound|outbound) mode (\S+) cir (\d+)\z/
|
|
216
|
-
(record[:client_rate_limit] ||= {}
|
|
290
|
+
assign_setting(record[:client_rate_limit] ||= {}, words[1].to_sym, { mode: words[3], cir_bps: Fields.integer(words.last) * 1000 }, block)
|
|
217
291
|
when /\Arate (mandatory|supported|disabled|multicast) (.+)\z/
|
|
218
|
-
(record[:rates] ||= {}
|
|
292
|
+
assign_setting(record[:rates] ||= {}, words[1].to_sym, words[1] == "multicast" ? words.drop(2).join(" ") : words.drop(2).map { |word| Fields.number(word) }, block)
|
|
219
293
|
when /\Aoption client (reject|reconnect) enable rssi (-?\d+(?:\.\d+)?)\z/
|
|
220
|
-
(record[:client_options] ||= {}
|
|
294
|
+
assign_setting(record[:client_options] ||= {}, words[2].to_sym, Fields.number(words.last), block)
|
|
221
295
|
when /\Asacp anti-sticky enable(?: rssi (-?\d+(?:\.\d+)?))? interval (\d+)\z/
|
|
222
296
|
signal, interval = Regexp.last_match.captures
|
|
223
|
-
record
|
|
297
|
+
assign_setting(record, :anti_sticky, { enabled: true, rssi: Fields.number(signal), interval: Fields.integer(interval) }, block)
|
|
224
298
|
when /\Apacket-filter name (\S+) (inbound|outbound)\z/
|
|
225
|
-
(record[:packet_filters] ||= {}
|
|
299
|
+
assign_setting(record[:packet_filters] ||= {}, words.last.to_sym, words[2], block)
|
|
226
300
|
when /\A(?:client forwarding-location|wlan image-load filepath|wlan fast-forwarding mode|led-mode) (\S+)\z/
|
|
227
301
|
key = { "client" => :forwarding_location, "wlan image-load" => :image_load_filepath,
|
|
228
302
|
"wlan fast-forwarding" => :fast_forwarding_mode, "led-mode" => :led_mode }
|
|
229
|
-
record
|
|
303
|
+
assign_setting(record, key.fetch(words.first == "wlan" ? words.take(2).join(" ") : words.first), words.last, block)
|
|
230
304
|
when /\A(local|member) ip (\S+)\z/
|
|
231
|
-
words.first == "local" ? record
|
|
232
|
-
when /\Aclient-mac (\S+)\z/ then (record[:mac_addresses] ||= []) << Fields.
|
|
233
|
-
when "wlan tunnel-preempt enable" then record
|
|
234
|
-
when "broadcast-probe reply disable" then record
|
|
235
|
-
when "radar-detect disable" then record
|
|
236
|
-
when "calibrate-channel self-decisive enable all" then record
|
|
237
|
-
when "calibrate-power self-decisive enable all" then record
|
|
238
|
-
when "ap-license-synchronization enable" then record
|
|
305
|
+
words.first == "local" ? assign_setting(record, :local_ip, Fields.ip_address(words.last), block) : (record[:member_ips] ||= []) << Fields.ip_address(words.last)
|
|
306
|
+
when /\Aclient-mac (\S+)\z/ then (record[:mac_addresses] ||= []) << Fields.mac_address(words.last)
|
|
307
|
+
when "wlan tunnel-preempt enable" then assign_setting(record, :tunnel_preempt, true, block)
|
|
308
|
+
when "broadcast-probe reply disable" then assign_setting(record, :broadcast_probe_reply, false, block)
|
|
309
|
+
when "radar-detect disable" then assign_setting(record, :radar_detect_disabled, true, block)
|
|
310
|
+
when "calibrate-channel self-decisive enable all" then assign_setting(record, :calibrate_channel, true, block)
|
|
311
|
+
when "calibrate-power self-decisive enable all" then assign_setting(record, :calibrate_power, true, block)
|
|
312
|
+
when "ap-license-synchronization enable" then assign_setting(record, :license_synchronization, true, block)
|
|
239
313
|
else return false
|
|
240
314
|
end
|
|
241
315
|
true
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../record_parser"
|
|
4
|
+
require_relative "../authentication"
|
|
4
5
|
|
|
5
6
|
module Wireless
|
|
6
7
|
module H3c
|
|
7
8
|
module Parsers
|
|
8
9
|
# 解析 display dot1x connection 报告的会话。
|
|
9
|
-
class DisplayDot1xConnection <
|
|
10
|
+
class DisplayDot1xConnection < RecordParser
|
|
11
|
+
include Authentication
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -23,8 +23,25 @@ module Wireless
|
|
|
23
23
|
current = nil
|
|
24
24
|
local = nil
|
|
25
25
|
description_indent = nil
|
|
26
|
+
description = description_line = nil
|
|
27
|
+
store_description = lambda do
|
|
28
|
+
if description
|
|
29
|
+
assign_value(current[:details] ||= {}, :system_description, description, issues, description_line)
|
|
30
|
+
description = description_indent = nil
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
continuation_lines = {}
|
|
26
34
|
flush = lambda do
|
|
27
35
|
if current && current.values_at(:local_interface, :remote_chassis_id, :remote_interface).all? { |value| value && !value.strip.empty? }
|
|
36
|
+
if /mac address/i.match?(current.dig(:details, :chassis_type).to_s)
|
|
37
|
+
begin
|
|
38
|
+
current[:remote_chassis_id] = Fields.mac_address(current[:remote_chassis_id])
|
|
39
|
+
rescue ArgumentError
|
|
40
|
+
issues << { code: :invalid_chassis_id }
|
|
41
|
+
current = nil
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
end
|
|
28
45
|
records << current
|
|
29
46
|
elsif current
|
|
30
47
|
issues << { code: :incomplete_neighbor }
|
|
@@ -35,10 +52,11 @@ module Wireless
|
|
|
35
52
|
row = line.strip
|
|
36
53
|
next if row.empty? || /\A[-=]+\z/.match?(row)
|
|
37
54
|
if description_indent && line[/\A\s*/].size > description_indent
|
|
38
|
-
|
|
55
|
+
description << "\n" << row
|
|
56
|
+
continuation_lines[index + 1] = true
|
|
39
57
|
next
|
|
40
58
|
end
|
|
41
|
-
|
|
59
|
+
store_description.call
|
|
42
60
|
if (match = /\ALLDP neighbor-information of port \d+\[(.+?)\]\s*:?\z/i.match(row))
|
|
43
61
|
flush.call
|
|
44
62
|
local = match[1]
|
|
@@ -52,27 +70,33 @@ module Wireless
|
|
|
52
70
|
label = label.strip.downcase
|
|
53
71
|
current ||= { local_interface: local }
|
|
54
72
|
case label
|
|
55
|
-
when "chassis id" then current
|
|
56
|
-
when "port id" then current
|
|
57
|
-
when "system name" then current
|
|
73
|
+
when "chassis id" then assign_value(current, :remote_chassis_id, raw, issues, index + 1)
|
|
74
|
+
when "port id" then assign_value(current, :remote_interface, raw, issues, index + 1)
|
|
75
|
+
when "system name" then assign_value(current, :remote_name, Fields.value(raw), issues, index + 1)
|
|
58
76
|
when "management address"
|
|
59
77
|
begin
|
|
60
|
-
current
|
|
78
|
+
assign_value(current, :management_address, Fields.ip_address(Fields.value(raw)), issues, index + 1)
|
|
61
79
|
rescue IPAddr::InvalidAddressError, ArgumentError
|
|
62
80
|
issues << { code: :invalid_management_address, line: index + 1 }
|
|
63
81
|
end
|
|
64
|
-
when "port vlan id(pvid)" then (current[:details] ||= {}
|
|
82
|
+
when "port vlan id(pvid)" then assign_value(current[:details] ||= {}, :port_vlan_id, raw, issues, index + 1)
|
|
65
83
|
when *DETAILS
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
if label == "system description"
|
|
85
|
+
description = raw.dup
|
|
86
|
+
description_line = index + 1
|
|
87
|
+
description_indent = line[/\A\s*/].size
|
|
88
|
+
else
|
|
89
|
+
assign_value(current[:details] ||= {}, label.tr(" ", "_").to_sym, raw, issues, index + 1)
|
|
90
|
+
end
|
|
68
91
|
else issues << { code: :unrecognized_field, line: index + 1 }
|
|
69
92
|
end
|
|
70
93
|
else
|
|
71
94
|
issues << { code: :unrecognized_line, line: index + 1 }
|
|
72
95
|
end
|
|
73
96
|
end
|
|
97
|
+
store_description.call
|
|
74
98
|
flush.call
|
|
75
|
-
total =
|
|
99
|
+
total = reported_total(text, noun: "(?:LLDP )?neighbors", issues: issues, continuation_lines: continuation_lines)
|
|
76
100
|
total ||= 0 if /\ANo (?:LLDP )?neighbors\s*$/i.match?(text.strip)
|
|
77
101
|
Fields.result(records, issues: issues, complete: complete, total: total,
|
|
78
102
|
identity: %i[local_interface remote_chassis_id remote_interface])
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../record_parser"
|
|
4
|
+
require_relative "../authentication"
|
|
4
5
|
|
|
5
6
|
module Wireless
|
|
6
7
|
module H3c
|
|
7
8
|
module Parsers
|
|
8
9
|
# 解析 display mac-authentication connection 报告的会话。
|
|
9
|
-
class DisplayMacAuthenticationConnection <
|
|
10
|
+
class DisplayMacAuthenticationConnection < RecordParser
|
|
11
|
+
include Authentication
|
|
10
12
|
end
|
|
11
13
|
end
|
|
12
14
|
end
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../record_parser"
|
|
4
4
|
|
|
5
5
|
module Wireless
|
|
6
6
|
module H3c
|
|
7
7
|
module Parsers
|
|
8
8
|
# 单条回显同时包含 AP 清单及其嵌套 Radio 清单。
|
|
9
|
-
class DisplayWlanApAllVerbose <
|
|
9
|
+
class DisplayWlanApAllVerbose < RecordParser
|
|
10
10
|
AP_FIELDS = { "ap id" => :ap_id, "ap group name" => :group_name, "state" => :operational_status,
|
|
11
11
|
"model" => :model, "mac address" => :mac_address, "ip address" => :management_ip,
|
|
12
12
|
"serial id" => :serial, "s/w version" => :os_version, "client count" => :client_count,
|
|
13
|
-
"number of clients" => :client_count, "online clients" => :client_count, "online time" => :uptime_seconds }.freeze
|
|
13
|
+
"number of clients" => :client_count, "online clients" => :client_count, "online time" => :online_seconds, "system uptime" => :uptime_seconds }.freeze
|
|
14
14
|
RADIO_FIELDS = { "admin state" => :admin_status, "state" => :admin_status, "radio type" => :protocol,
|
|
15
15
|
"type" => :protocol, "channel" => :channel, "channel usage(%)" => :utilization,
|
|
16
16
|
"channel usage" => :utilization, "noise floor" => :noise_dbm, "channel bandwidth" => :channel_width,
|
|
17
|
-
"operating bandwidth" => :channel_width }.freeze
|
|
17
|
+
"operating bandwidth" => :channel_width, "channel band-width" => :channel_width }.freeze
|
|
18
|
+
RADIO_SECTIONS = { "operational vht-mcs set" => :vht_mcs, "operational ht mcs set" => :ht_mcs,
|
|
19
|
+
"operational he-mcs set" => :he_mcs, "operational rate" => :operational_rates }.freeze
|
|
20
|
+
SECTION_FIELDS = %w[mandatory supported multicast disabled].freeze
|
|
18
21
|
AP_DETAILS = [ "backup type", "ready-for-switchover", "system uptime", "region code", "region code lock",
|
|
19
22
|
"udp control port number", "udp data port number", "h/w version", "boot version", "usb state", "power level",
|
|
20
23
|
"power info", "poe status", "description", "priority", "echo interval", "echo count", "keepalive interval",
|
|
@@ -25,7 +28,7 @@ module Wireless
|
|
|
25
28
|
"connection count", "backup ipv4", "backup ipv6", "ctrl-tunnel encryption", "ctrl-tunnel encryption state",
|
|
26
29
|
"data-tunnel encryption", "data-tunnel encryption state", "led mode", "remote configuration", "energysaving level",
|
|
27
30
|
"ap type", "map file", "forwarding mode", "ble mode", "capwap data-tunnel status" ].freeze
|
|
28
|
-
RADIO_DETAILS = [ "basic bssid", "bssid", "antenna type", "client dot11ac-only", "client dot11n-only",
|
|
31
|
+
RADIO_DETAILS = [ "basic bssid", "base bssid", "bssid", "antenna type", "client dot11ac-only", "client dot11n-only",
|
|
29
32
|
"secondary channel offset", "secondary channel mode", "short gi for 20mhz", "short gi for 40mhz",
|
|
30
33
|
"short gi for 80mhz", "short gi for 160mhz", "mimo", "green-energy-management", "a-msdu", "a-mpdu", "ldpc", "stbc",
|
|
31
34
|
"max power", "operational rate", "distance", "ani", "fragmentation threshold", "beacon interval", "protection threshold",
|
|
@@ -40,16 +43,35 @@ module Wireless
|
|
|
40
43
|
# 解释当前命令记录,保留不完整行诊断。
|
|
41
44
|
def parse_text(text, complete:)
|
|
42
45
|
access_points, radios, issues = [], [], []
|
|
46
|
+
aps_with_radio_evidence = {}.compare_by_identity
|
|
43
47
|
ap = radio = nil
|
|
44
|
-
|
|
48
|
+
section = nil
|
|
49
|
+
section_indent = nil
|
|
50
|
+
read_fields(text, issues).each do |label, raw, line, indent|
|
|
45
51
|
if label == "ap name"
|
|
46
52
|
ap = { name: raw }
|
|
47
53
|
access_points << ap
|
|
48
54
|
radio = nil
|
|
55
|
+
section = nil
|
|
49
56
|
elsif (match = /\Aradio (\d+)\z/.match(label))
|
|
50
57
|
radio = { ap_name: ap&.fetch(:name), radio_id: match[1] }
|
|
51
58
|
radios << radio
|
|
59
|
+
aps_with_radio_evidence[ap] = true if ap
|
|
60
|
+
section = nil
|
|
52
61
|
else
|
|
62
|
+
if radio && RADIO_SECTIONS.key?(label)
|
|
63
|
+
section = RADIO_SECTIONS.fetch(label)
|
|
64
|
+
section_indent = indent
|
|
65
|
+
# 空标题本身不属于测量;其子标签在相应速率或 MCS 集合内拥有身份。
|
|
66
|
+
assign_value(radio[:details] ||= {}, label.tr(" -", "__").to_sym, raw, issues, line)
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
section = nil if section && indent <= section_indent
|
|
70
|
+
if radio && section && SECTION_FIELDS.include?(label)
|
|
71
|
+
details = radio[:details] ||= {}
|
|
72
|
+
assign_value(details[section] ||= {}, label.to_sym, raw, issues, line)
|
|
73
|
+
next
|
|
74
|
+
end
|
|
53
75
|
owner = radio && RADIO_FIELDS.key?(label) ? radio : ap
|
|
54
76
|
mapping = owner.equal?(radio) && radio ? RADIO_FIELDS : AP_FIELDS
|
|
55
77
|
details = AP_DETAILS.include?(label) ? ap : RADIO_DETAILS.include?(label) ? radio : nil
|
|
@@ -65,13 +87,17 @@ module Wireless
|
|
|
65
87
|
issues << { code: :invalid_identity } if missing
|
|
66
88
|
missing
|
|
67
89
|
end
|
|
68
|
-
total =
|
|
90
|
+
total = record_count(text, "APs", issues)
|
|
69
91
|
issues << { code: :count_mismatch, expected: total, parsed: valid_aps.size } if total && total != valid_aps.size
|
|
70
|
-
# AP
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
# AP 总数只证明 AP 范围;未报告 Radio 的 AP 不能证明其 Radio 清单为空。
|
|
93
|
+
radio_issues = issues.dup
|
|
94
|
+
valid_aps.each do |record|
|
|
95
|
+
radio_issues << { code: :missing_radio_evidence, ap_name: record[:name] } unless aps_with_radio_evidence.key?(record)
|
|
96
|
+
end
|
|
97
|
+
radio_total = total == 0 && valid_aps.empty? ? 0 : nil
|
|
98
|
+
CtxParser::Results.new(by_dimension: {
|
|
73
99
|
access_points: Fields.result(valid_aps, issues: issues.dup, complete: complete, total: total, identity: [ :name ]),
|
|
74
|
-
radios: Fields.result(radios, issues:
|
|
100
|
+
radios: Fields.result(radios, issues: radio_issues, complete: complete, total: radio_total, identity: %i[ap_name radio_id])
|
|
75
101
|
})
|
|
76
102
|
end
|
|
77
103
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../record_parser"
|
|
4
4
|
|
|
5
5
|
module Wireless
|
|
6
6
|
module H3c
|
|
7
7
|
module Parsers
|
|
8
8
|
# 型号能力与发现的 AP 资产及 Radio 分开保存。
|
|
9
|
-
class DisplayWlanApModelAll <
|
|
9
|
+
class DisplayWlanApModelAll < RecordParser
|
|
10
10
|
MODEL_FIELDS = { "alias" => :alias, "vendor name" => :vendor_name, "vendor id" => :vendor_id,
|
|
11
11
|
"license weight" => :license_weight, "license type" => :license_type, "radio count" => :radio_count, "type" => :type }.freeze
|
|
12
12
|
RADIO_FIELDS = { "mode" => :modes, "default mode" => :default_mode, "bss count" => :bss_count }.freeze
|
|
@@ -22,8 +22,8 @@ module Wireless
|
|
|
22
22
|
lines = text.lines
|
|
23
23
|
model = section = nil
|
|
24
24
|
mapping = MODEL_FIELDS
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
read_fields(text, issues).each do |label, raw, line|
|
|
26
|
+
parse_field(issues, line) do
|
|
27
27
|
if label == "ap model"
|
|
28
28
|
raise ArgumentError, "Missing AP model" unless raw
|
|
29
29
|
model = { model: raw, radios: [], versions: [] }
|
|
@@ -48,7 +48,7 @@ module Wireless
|
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
records.each { |record| validate_model(record, issues) }
|
|
51
|
-
Fields.result(records, issues: issues, complete: complete, total:
|
|
51
|
+
Fields.result(records, issues: issues, complete: complete, total: record_count(text, "AP models", issues), identity: [ :model ])
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# 根据声明字段类型归一化型号能力值。
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../
|
|
3
|
+
require_relative "../record_parser"
|
|
4
4
|
|
|
5
5
|
module Wireless
|
|
6
6
|
module H3c
|
|
7
7
|
module Parsers
|
|
8
8
|
# 同时接受以 BSSID 或 AP 名称开头的 BSS 块。
|
|
9
|
-
class DisplayWlanBssAllVerbose <
|
|
9
|
+
class DisplayWlanBssAllVerbose < RecordParser
|
|
10
10
|
MAPPING = COMMON_FIELDS.merge("service template" => :profile, "service template name" => :profile,
|
|
11
11
|
"state" => :operational_status, "client count" => :client_count, "number of clients" => :client_count,
|
|
12
12
|
"online clients" => :client_count).freeze
|
|
@@ -17,7 +17,7 @@ module Wireless
|
|
|
17
17
|
private
|
|
18
18
|
|
|
19
19
|
# 返回当前命令格式中用于开始新记录的字段。
|
|
20
|
-
def
|
|
20
|
+
def record_start_label(fields)
|
|
21
21
|
first = fields.find { |label,| label == "bssid" || label == "ap name" }
|
|
22
22
|
"ap name" if first&.first == "ap name"
|
|
23
23
|
end
|
|
@@ -27,10 +27,10 @@ module Wireless
|
|
|
27
27
|
super && record[:ap_name] && !record[:ap_name].strip.empty? && !record[:radio_id].nil?
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# 保留 BSS
|
|
31
|
-
def
|
|
30
|
+
# 保留 BSS 安全字段,其余字段委派给共享记录解析器。
|
|
31
|
+
def assign_record_field(record, label, raw, issues, line)
|
|
32
32
|
if SECURITY.include?(label)
|
|
33
|
-
(record[:security] ||= {}
|
|
33
|
+
assign_value(record[:security] ||= {}, label.tr(" -", "__").to_sym, raw, issues, line)
|
|
34
34
|
else
|
|
35
35
|
super
|
|
36
36
|
end
|