keenetic 0.1.0 → 1.0.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/LICENSE.txt +21 -0
- data/README.md +28 -26
- data/lib/keenetic/client.rb +131 -1
- data/lib/keenetic/resources/base.rb +4 -0
- data/lib/keenetic/resources/components.rb +88 -0
- data/lib/keenetic/resources/config.rb +89 -0
- data/lib/keenetic/resources/diagnostics.rb +102 -0
- data/lib/keenetic/resources/dns.rb +95 -0
- data/lib/keenetic/resources/dyndns.rb +71 -0
- data/lib/keenetic/resources/firewall.rb +103 -0
- data/lib/keenetic/resources/hotspot.rb +282 -0
- data/lib/keenetic/resources/ipv6.rb +74 -0
- data/lib/keenetic/resources/mesh.rb +84 -0
- data/lib/keenetic/resources/nat.rb +202 -0
- data/lib/keenetic/resources/qos.rb +89 -0
- data/lib/keenetic/resources/routes.rb +432 -0
- data/lib/keenetic/resources/schedule.rb +87 -0
- data/lib/keenetic/resources/system.rb +134 -0
- data/lib/keenetic/resources/usb.rb +135 -0
- data/lib/keenetic/resources/users.rb +92 -0
- data/lib/keenetic/resources/vpn.rb +153 -0
- data/lib/keenetic/version.rb +1 -2
- data/lib/keenetic.rb +33 -0
- metadata +20 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc71a113a4147e3ffd1035596420469ca8c7d9e9639623fd14172b2137d902f6
|
|
4
|
+
data.tar.gz: 7fcb0cbd7b8a1219715288354b58bfa7a8f15ed7900334be7fd4014a1de9e788
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e03cfc9b96ac18afa8ec3526d735970e5381983325920aaf0f771c1e31337f4c0800158a320081c696ad973b75eab29846b961ea5ea76d2c12b6209516a894e4
|
|
7
|
+
data.tar.gz: fba387ac8b75676298f75715b22c1c348d3ca749fe44234b2c0dbd8d01ff6b9554f78ddf19fcea2b4dcfbee17ccadc253f91a5c777c7c91427d89140fab5335e
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anton
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
CHANGED
|
@@ -20,34 +20,36 @@ Keenetic.configure do |config|
|
|
|
20
20
|
end
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Quick Start
|
|
24
24
|
|
|
25
25
|
```ruby
|
|
26
26
|
client = Keenetic.client
|
|
27
27
|
|
|
28
|
-
# Connected devices
|
|
29
|
-
client.devices.all
|
|
30
|
-
client.devices.active
|
|
31
|
-
client.devices.find(mac: 'AA:BB:CC:DD:EE:FF')
|
|
32
|
-
client.devices.update(mac: 'AA:BB:CC:DD:EE:FF', name: 'My Phone')
|
|
33
|
-
|
|
34
28
|
# System info
|
|
35
|
-
client.system.info
|
|
36
|
-
client.system.resources
|
|
37
|
-
|
|
38
|
-
# Network
|
|
39
|
-
client.network.interfaces
|
|
29
|
+
client.system.info # model, firmware
|
|
30
|
+
client.system.resources # CPU, memory, uptime
|
|
40
31
|
|
|
41
|
-
#
|
|
42
|
-
client.
|
|
43
|
-
client.
|
|
44
|
-
|
|
45
|
-
# Internet
|
|
46
|
-
client.internet.status
|
|
47
|
-
client.internet.speed
|
|
32
|
+
# Connected devices
|
|
33
|
+
client.devices.all # all registered devices
|
|
34
|
+
client.devices.active # currently connected
|
|
48
35
|
|
|
49
|
-
#
|
|
50
|
-
client.
|
|
36
|
+
# Network
|
|
37
|
+
client.network.interfaces # all interfaces
|
|
38
|
+
client.wifi.access_points # Wi-Fi networks
|
|
39
|
+
client.internet.status # internet connectivity
|
|
40
|
+
|
|
41
|
+
# Port forwarding
|
|
42
|
+
client.nat.rules # NAT rules
|
|
43
|
+
client.nat.add_forward(index: 1, protocol: 'tcp', port: 8080,
|
|
44
|
+
to_host: '192.168.1.100', to_port: 80)
|
|
45
|
+
|
|
46
|
+
# VPN
|
|
47
|
+
client.vpn.status # VPN server status
|
|
48
|
+
client.vpn.clients # connected VPN clients
|
|
49
|
+
|
|
50
|
+
# Configuration
|
|
51
|
+
client.system_config.save # save to flash
|
|
52
|
+
client.system_config.download # backup config
|
|
51
53
|
```
|
|
52
54
|
|
|
53
55
|
## Error Handling
|
|
@@ -61,18 +63,18 @@ rescue Keenetic::ConnectionError
|
|
|
61
63
|
# router unreachable
|
|
62
64
|
rescue Keenetic::TimeoutError
|
|
63
65
|
# request timed out
|
|
64
|
-
rescue Keenetic::NotFoundError
|
|
65
|
-
# resource not found
|
|
66
66
|
rescue Keenetic::ApiError => e
|
|
67
|
-
#
|
|
67
|
+
# other API errors
|
|
68
68
|
e.status_code
|
|
69
69
|
e.response_body
|
|
70
70
|
end
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
##
|
|
73
|
+
## Documentation
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
- [Complete API Reference](docs/API_REFERENCE.md) - All features with detailed examples
|
|
76
|
+
- [API Coverage](API_PROGRESS.md) - Implementation status
|
|
77
|
+
- [Keenetic API Specification](KEENETIC_API.md) - Raw API documentation
|
|
76
78
|
|
|
77
79
|
## Requirements
|
|
78
80
|
|
data/lib/keenetic/client.rb
CHANGED
|
@@ -102,6 +102,120 @@ module Keenetic
|
|
|
102
102
|
@logs ||= Resources::Logs.new(self)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
# @return [Resources::Routes] Static routes resource
|
|
106
|
+
def routes
|
|
107
|
+
@routes ||= Resources::Routes.new(self)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [Resources::Hotspot] Hotspot hosts and policies resource
|
|
111
|
+
def hotspot
|
|
112
|
+
@hotspot ||= Resources::Hotspot.new(self)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @return [Resources::Config] Configuration management resource
|
|
116
|
+
def system_config
|
|
117
|
+
@system_config ||= Resources::Config.new(self)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @return [Resources::Nat] NAT and port forwarding resource
|
|
121
|
+
def nat
|
|
122
|
+
@nat ||= Resources::Nat.new(self)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# @return [Resources::Vpn] VPN server resource
|
|
126
|
+
def vpn
|
|
127
|
+
@vpn ||= Resources::Vpn.new(self)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @return [Resources::Diagnostics] Network diagnostics resource
|
|
131
|
+
def diagnostics
|
|
132
|
+
@diagnostics ||= Resources::Diagnostics.new(self)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @return [Resources::Firewall] Firewall resource
|
|
136
|
+
def firewall
|
|
137
|
+
@firewall ||= Resources::Firewall.new(self)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @return [Resources::Mesh] Mesh Wi-Fi system resource
|
|
141
|
+
def mesh
|
|
142
|
+
@mesh ||= Resources::Mesh.new(self)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# @return [Resources::Usb] USB devices and storage resource
|
|
146
|
+
def usb
|
|
147
|
+
@usb ||= Resources::Usb.new(self)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# @return [Resources::Dns] DNS settings and cache resource
|
|
151
|
+
def dns
|
|
152
|
+
@dns ||= Resources::Dns.new(self)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# @return [Resources::Dyndns] Dynamic DNS resource
|
|
156
|
+
def dyndns
|
|
157
|
+
@dyndns ||= Resources::Dyndns.new(self)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# @return [Resources::Schedule] Access schedules resource
|
|
161
|
+
def schedule
|
|
162
|
+
@schedule ||= Resources::Schedule.new(self)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# @return [Resources::Users] User accounts resource
|
|
166
|
+
def users
|
|
167
|
+
@users ||= Resources::Users.new(self)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# @return [Resources::Components] System components resource
|
|
171
|
+
def components
|
|
172
|
+
@components ||= Resources::Components.new(self)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# @return [Resources::Qos] QoS and traffic control resource
|
|
176
|
+
def qos
|
|
177
|
+
@qos ||= Resources::Qos.new(self)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# @return [Resources::Ipv6] IPv6 network resource
|
|
181
|
+
def ipv6
|
|
182
|
+
@ipv6 ||= Resources::Ipv6.new(self)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Execute arbitrary RCI command(s).
|
|
186
|
+
#
|
|
187
|
+
# Provides raw access to the Keenetic RCI (Remote Command Interface).
|
|
188
|
+
# Use this for custom commands not covered by the gem's resources.
|
|
189
|
+
#
|
|
190
|
+
# == Keenetic API
|
|
191
|
+
# POST http://<host>/rci/
|
|
192
|
+
# Content-Type: application/json
|
|
193
|
+
# Body: Array or Hash of RCI commands
|
|
194
|
+
#
|
|
195
|
+
# @param body [Hash, Array<Hash>] RCI command(s) to execute
|
|
196
|
+
# @return [Hash, Array, nil] Parsed JSON response
|
|
197
|
+
#
|
|
198
|
+
# @example Execute single command
|
|
199
|
+
# client.rci({ 'show' => { 'system' => {} } })
|
|
200
|
+
# # => { "show" => { "system" => { ... } } }
|
|
201
|
+
#
|
|
202
|
+
# @example Execute batch commands
|
|
203
|
+
# client.rci([
|
|
204
|
+
# { 'show' => { 'system' => {} } },
|
|
205
|
+
# { 'show' => { 'version' => {} } }
|
|
206
|
+
# ])
|
|
207
|
+
# # => [{ "show" => { "system" => { ... } } }, { "show" => { "version" => { ... } } }]
|
|
208
|
+
#
|
|
209
|
+
# @example Execute write command
|
|
210
|
+
# client.rci([
|
|
211
|
+
# { 'ip' => { 'hotspot' => { 'host' => { 'mac' => 'aa:bb:cc:dd:ee:ff', 'permit' => true } } } }
|
|
212
|
+
# ])
|
|
213
|
+
#
|
|
214
|
+
def rci(body)
|
|
215
|
+
commands = body.is_a?(Array) ? body : [body]
|
|
216
|
+
batch(commands)
|
|
217
|
+
end
|
|
218
|
+
|
|
105
219
|
# Make a GET request to the router API.
|
|
106
220
|
#
|
|
107
221
|
# == Keenetic API
|
|
@@ -162,6 +276,19 @@ module Keenetic
|
|
|
162
276
|
request(:post, '/rci/', body: commands)
|
|
163
277
|
end
|
|
164
278
|
|
|
279
|
+
# Make a POST request with raw body content (non-JSON).
|
|
280
|
+
#
|
|
281
|
+
# Used for file uploads like configuration restore.
|
|
282
|
+
#
|
|
283
|
+
# @param path [String] API path
|
|
284
|
+
# @param content [String] Raw content to send
|
|
285
|
+
# @param content_type [String] Content-Type header (default: text/plain)
|
|
286
|
+
# @return [String, nil] Response body
|
|
287
|
+
#
|
|
288
|
+
def post_raw(path, content, content_type: 'text/plain')
|
|
289
|
+
request(:post, path, raw_body: content, content_type: content_type)
|
|
290
|
+
end
|
|
291
|
+
|
|
165
292
|
# Check if client is authenticated.
|
|
166
293
|
# @return [Boolean]
|
|
167
294
|
def authenticated?
|
|
@@ -205,7 +332,10 @@ module Keenetic
|
|
|
205
332
|
url += "?#{URI.encode_www_form(options[:params])}"
|
|
206
333
|
end
|
|
207
334
|
|
|
208
|
-
if options[:
|
|
335
|
+
if options[:raw_body]
|
|
336
|
+
request_options[:body] = options[:raw_body]
|
|
337
|
+
request_options[:headers]['Content-Type'] = options[:content_type] || 'text/plain'
|
|
338
|
+
elsif options[:body]
|
|
209
339
|
request_options[:body] = options[:body].to_json
|
|
210
340
|
request_options[:headers]['Content-Type'] = 'application/json'
|
|
211
341
|
end
|
|
@@ -17,6 +17,10 @@ module Keenetic
|
|
|
17
17
|
client.post(path, body)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def post_raw(path, content, content_type: 'text/plain')
|
|
21
|
+
client.post_raw(path, content, content_type: content_type)
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
# Convert kebab-case keys to snake_case symbols
|
|
21
25
|
# @param hash [Hash] Hash with string keys
|
|
22
26
|
# @return [Hash] Hash with symbolized snake_case keys
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Components resource for managing installable router components.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Installed Components
|
|
8
|
+
# GET /rci/show/components
|
|
9
|
+
#
|
|
10
|
+
# === Available Components
|
|
11
|
+
# GET /rci/show/components/available
|
|
12
|
+
#
|
|
13
|
+
# === Install Component
|
|
14
|
+
# POST /rci/components/install
|
|
15
|
+
#
|
|
16
|
+
# === Remove Component
|
|
17
|
+
# POST /rci/components/remove
|
|
18
|
+
#
|
|
19
|
+
class Components < Base
|
|
20
|
+
# Get installed components.
|
|
21
|
+
#
|
|
22
|
+
# @return [Array<Hash>] List of installed components
|
|
23
|
+
# @example
|
|
24
|
+
# installed = client.components.installed
|
|
25
|
+
#
|
|
26
|
+
def installed
|
|
27
|
+
response = get('/rci/show/components')
|
|
28
|
+
normalize_components(response)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get available components for installation.
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<Hash>] List of available components
|
|
34
|
+
# @example
|
|
35
|
+
# available = client.components.available
|
|
36
|
+
#
|
|
37
|
+
def available
|
|
38
|
+
response = get('/rci/show/components/available')
|
|
39
|
+
normalize_components(response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Install a component.
|
|
43
|
+
#
|
|
44
|
+
# @param name [String] Component name
|
|
45
|
+
# @return [Hash, nil] API response
|
|
46
|
+
# @example
|
|
47
|
+
# client.components.install(name: 'transmission')
|
|
48
|
+
#
|
|
49
|
+
def install(name:)
|
|
50
|
+
post('/rci/components/install', { 'name' => name })
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Remove a component.
|
|
54
|
+
#
|
|
55
|
+
# @param name [String] Component name
|
|
56
|
+
# @return [Hash, nil] API response
|
|
57
|
+
# @example
|
|
58
|
+
# client.components.remove(name: 'transmission')
|
|
59
|
+
#
|
|
60
|
+
def remove(name:)
|
|
61
|
+
post('/rci/components/remove', { 'name' => name })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def normalize_components(response)
|
|
67
|
+
components_data = case response
|
|
68
|
+
when Array
|
|
69
|
+
response
|
|
70
|
+
when Hash
|
|
71
|
+
response['component'] || response['components'] || []
|
|
72
|
+
else
|
|
73
|
+
[]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return [] unless components_data.is_a?(Array)
|
|
77
|
+
|
|
78
|
+
components_data.map { |component| normalize_component(component) }.compact
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def normalize_component(data)
|
|
82
|
+
return nil unless data.is_a?(Hash)
|
|
83
|
+
|
|
84
|
+
deep_normalize_keys(data)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Manages router configuration operations.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Save Configuration
|
|
8
|
+
# POST /rci/ (batch format)
|
|
9
|
+
# Body: [{"system": {"configuration": {"save": {}}}}]
|
|
10
|
+
# Saves current configuration to persistent storage
|
|
11
|
+
#
|
|
12
|
+
# === Download Configuration
|
|
13
|
+
# GET /ci/startup-config.txt
|
|
14
|
+
# Returns: Plain text configuration file
|
|
15
|
+
#
|
|
16
|
+
# === Upload Configuration
|
|
17
|
+
# POST /ci/startup-config.txt
|
|
18
|
+
# Body: Plain text configuration file
|
|
19
|
+
# Restores configuration from backup
|
|
20
|
+
#
|
|
21
|
+
class Config < Base
|
|
22
|
+
# Save current configuration to persistent storage.
|
|
23
|
+
#
|
|
24
|
+
# == Keenetic API Request
|
|
25
|
+
# POST /rci/ (batch format)
|
|
26
|
+
# Body: [{"system": {"configuration": {"save": {}}}}]
|
|
27
|
+
#
|
|
28
|
+
# Configuration changes are typically auto-saved, but this method
|
|
29
|
+
# forces an immediate save to flash storage.
|
|
30
|
+
#
|
|
31
|
+
# @return [Array<Hash>] API response
|
|
32
|
+
# @example
|
|
33
|
+
# client.config.save
|
|
34
|
+
# # => [{ "system" => { "configuration" => { "save" => {} } } }]
|
|
35
|
+
#
|
|
36
|
+
def save
|
|
37
|
+
client.batch([{ 'system' => { 'configuration' => { 'save' => {} } } }])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Download the startup configuration file.
|
|
41
|
+
#
|
|
42
|
+
# == Keenetic API Request
|
|
43
|
+
# GET /ci/startup-config.txt
|
|
44
|
+
#
|
|
45
|
+
# Returns the full router configuration as a text file.
|
|
46
|
+
# This is the same format used for backup/restore operations.
|
|
47
|
+
#
|
|
48
|
+
# @return [String] Configuration file content
|
|
49
|
+
# @example
|
|
50
|
+
# config_text = client.system_config.download
|
|
51
|
+
# File.write('router-backup.txt', config_text)
|
|
52
|
+
#
|
|
53
|
+
def download
|
|
54
|
+
get('/ci/startup-config.txt')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Upload and restore a configuration file.
|
|
58
|
+
#
|
|
59
|
+
# == Keenetic API Request
|
|
60
|
+
# POST /ci/startup-config.txt
|
|
61
|
+
# Content-Type: text/plain
|
|
62
|
+
# Body: Configuration file content
|
|
63
|
+
#
|
|
64
|
+
# Uploads a configuration file to the router. The configuration
|
|
65
|
+
# will be applied after a reboot.
|
|
66
|
+
#
|
|
67
|
+
# == Warning
|
|
68
|
+
# This is a potentially destructive operation. Uploading an
|
|
69
|
+
# invalid or incompatible configuration may make the router
|
|
70
|
+
# inaccessible. Always ensure you have physical access to the
|
|
71
|
+
# router before restoring configuration.
|
|
72
|
+
#
|
|
73
|
+
# @param content [String] Configuration file content
|
|
74
|
+
# @return [String, nil] Response from the router
|
|
75
|
+
# @example Upload from string
|
|
76
|
+
# client.system_config.upload(config_text)
|
|
77
|
+
#
|
|
78
|
+
# @example Upload from file
|
|
79
|
+
# config_text = File.read('router-backup.txt')
|
|
80
|
+
# client.system_config.upload(config_text)
|
|
81
|
+
#
|
|
82
|
+
def upload(content)
|
|
83
|
+
raise ArgumentError, 'Configuration content cannot be empty' if content.nil? || content.strip.empty?
|
|
84
|
+
|
|
85
|
+
post_raw('/ci/startup-config.txt', content)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Diagnostics resource for network troubleshooting tools.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Ping
|
|
8
|
+
# POST /rci/tools/ping
|
|
9
|
+
# Body: { host, count }
|
|
10
|
+
#
|
|
11
|
+
# === Traceroute
|
|
12
|
+
# POST /rci/tools/traceroute
|
|
13
|
+
# Body: { host }
|
|
14
|
+
#
|
|
15
|
+
# === DNS Lookup
|
|
16
|
+
# POST /rci/tools/nslookup
|
|
17
|
+
# Body: { host }
|
|
18
|
+
#
|
|
19
|
+
class Diagnostics < Base
|
|
20
|
+
# Ping a host from the router.
|
|
21
|
+
#
|
|
22
|
+
# == Keenetic API Request
|
|
23
|
+
# POST /rci/tools/ping
|
|
24
|
+
# Body: { "host": "8.8.8.8", "count": 4 }
|
|
25
|
+
#
|
|
26
|
+
# @param host [String] Hostname or IP address to ping
|
|
27
|
+
# @param count [Integer] Number of ping packets (default: 4)
|
|
28
|
+
# @return [Hash] Ping results
|
|
29
|
+
#
|
|
30
|
+
# @example Ping Google DNS
|
|
31
|
+
# result = client.diagnostics.ping('8.8.8.8')
|
|
32
|
+
# # => { host: "8.8.8.8", transmitted: 4, received: 4, loss: 0, ... }
|
|
33
|
+
#
|
|
34
|
+
# @example Ping with custom count
|
|
35
|
+
# result = client.diagnostics.ping('google.com', count: 10)
|
|
36
|
+
#
|
|
37
|
+
def ping(host, count: 4)
|
|
38
|
+
response = post('/rci/tools/ping', { 'host' => host, 'count' => count })
|
|
39
|
+
normalize_ping_result(response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Trace route to a host from the router.
|
|
43
|
+
#
|
|
44
|
+
# == Keenetic API Request
|
|
45
|
+
# POST /rci/tools/traceroute
|
|
46
|
+
# Body: { "host": "google.com" }
|
|
47
|
+
#
|
|
48
|
+
# @param host [String] Hostname or IP address to trace
|
|
49
|
+
# @return [Hash] Traceroute results with hops
|
|
50
|
+
#
|
|
51
|
+
# @example Trace route to Google
|
|
52
|
+
# result = client.diagnostics.traceroute('google.com')
|
|
53
|
+
# # => { host: "google.com", hops: [{ hop: 1, ip: "192.168.1.1", time: 1 }, ...] }
|
|
54
|
+
#
|
|
55
|
+
def traceroute(host)
|
|
56
|
+
response = post('/rci/tools/traceroute', { 'host' => host })
|
|
57
|
+
normalize_traceroute_result(response)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Perform DNS lookup from the router.
|
|
61
|
+
#
|
|
62
|
+
# == Keenetic API Request
|
|
63
|
+
# POST /rci/tools/nslookup
|
|
64
|
+
# Body: { "host": "google.com" }
|
|
65
|
+
#
|
|
66
|
+
# @param host [String] Hostname to resolve
|
|
67
|
+
# @return [Hash] DNS lookup results
|
|
68
|
+
#
|
|
69
|
+
# @example Lookup Google
|
|
70
|
+
# result = client.diagnostics.nslookup('google.com')
|
|
71
|
+
# # => { host: "google.com", addresses: ["142.250.185.46", ...], ... }
|
|
72
|
+
#
|
|
73
|
+
def nslookup(host)
|
|
74
|
+
response = post('/rci/tools/nslookup', { 'host' => host })
|
|
75
|
+
normalize_nslookup_result(response)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Alias for nslookup
|
|
79
|
+
alias dns_lookup nslookup
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def normalize_ping_result(response)
|
|
84
|
+
return {} unless response.is_a?(Hash)
|
|
85
|
+
|
|
86
|
+
deep_normalize_keys(response)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def normalize_traceroute_result(response)
|
|
90
|
+
return {} unless response.is_a?(Hash)
|
|
91
|
+
|
|
92
|
+
deep_normalize_keys(response)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def normalize_nslookup_result(response)
|
|
96
|
+
return {} unless response.is_a?(Hash)
|
|
97
|
+
|
|
98
|
+
deep_normalize_keys(response)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# DNS resource for managing DNS settings and cache.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading DNS Servers
|
|
8
|
+
# GET /rci/show/ip/name-server
|
|
9
|
+
# Returns: Configured DNS servers
|
|
10
|
+
#
|
|
11
|
+
# === Reading DNS Cache
|
|
12
|
+
# GET /rci/show/dns/cache
|
|
13
|
+
# Returns: DNS cache entries
|
|
14
|
+
#
|
|
15
|
+
# === Reading DNS Proxy Settings
|
|
16
|
+
# GET /rci/show/dns/proxy
|
|
17
|
+
# Returns: DNS proxy configuration
|
|
18
|
+
#
|
|
19
|
+
# === Clear DNS Cache
|
|
20
|
+
# POST /rci/dns/cache/clear
|
|
21
|
+
# Body: {}
|
|
22
|
+
#
|
|
23
|
+
class Dns < Base
|
|
24
|
+
# Get configured DNS servers.
|
|
25
|
+
#
|
|
26
|
+
# == Keenetic API Request
|
|
27
|
+
# GET /rci/show/ip/name-server
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash] DNS servers configuration
|
|
30
|
+
# @example
|
|
31
|
+
# servers = client.dns.servers
|
|
32
|
+
#
|
|
33
|
+
def servers
|
|
34
|
+
response = get('/rci/show/ip/name-server')
|
|
35
|
+
normalize_response(response)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Alias for servers
|
|
39
|
+
alias name_servers servers
|
|
40
|
+
|
|
41
|
+
# Get DNS cache entries.
|
|
42
|
+
#
|
|
43
|
+
# == Keenetic API Request
|
|
44
|
+
# GET /rci/show/dns/cache
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash] DNS cache data
|
|
47
|
+
# @example
|
|
48
|
+
# cache = client.dns.cache
|
|
49
|
+
#
|
|
50
|
+
def cache
|
|
51
|
+
response = get('/rci/show/dns/cache')
|
|
52
|
+
normalize_response(response)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Get DNS proxy settings.
|
|
56
|
+
#
|
|
57
|
+
# == Keenetic API Request
|
|
58
|
+
# GET /rci/show/dns/proxy
|
|
59
|
+
#
|
|
60
|
+
# @return [Hash] DNS proxy configuration
|
|
61
|
+
# @example
|
|
62
|
+
# proxy = client.dns.proxy
|
|
63
|
+
#
|
|
64
|
+
def proxy
|
|
65
|
+
response = get('/rci/show/dns/proxy')
|
|
66
|
+
normalize_response(response)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Alias for proxy
|
|
70
|
+
alias proxy_settings proxy
|
|
71
|
+
|
|
72
|
+
# Clear DNS cache.
|
|
73
|
+
#
|
|
74
|
+
# == Keenetic API Request
|
|
75
|
+
# POST /rci/dns/cache/clear
|
|
76
|
+
# Body: {}
|
|
77
|
+
#
|
|
78
|
+
# @return [Hash, nil] API response
|
|
79
|
+
# @example
|
|
80
|
+
# client.dns.clear_cache
|
|
81
|
+
#
|
|
82
|
+
def clear_cache
|
|
83
|
+
post('/rci/dns/cache/clear', {})
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def normalize_response(response)
|
|
89
|
+
return {} unless response.is_a?(Hash)
|
|
90
|
+
|
|
91
|
+
deep_normalize_keys(response)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|