keenetic 0.2.0 → 1.1.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 +37 -51
- data/lib/keenetic/client.rb +109 -6
- data/lib/keenetic/resources/base.rb +4 -0
- data/lib/keenetic/resources/components.rb +88 -0
- data/lib/keenetic/resources/config.rb +37 -1
- data/lib/keenetic/resources/diagnostics.rb +102 -0
- data/lib/keenetic/resources/dns.rb +95 -0
- data/lib/keenetic/resources/dns_routes.rb +298 -0
- data/lib/keenetic/resources/dyndns.rb +71 -0
- data/lib/keenetic/resources/firewall.rb +103 -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/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 -1
- data/lib/keenetic.rb +14 -0
- metadata +17 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55da30f5dcac15dad86d5d93cd6dd35cd8d9f63ee2dfb63f73a8e8b32d66a4bb
|
|
4
|
+
data.tar.gz: 7be5b4c51ca2b764c7bc2cc68e1bb69402377d3c19106f0e288a15103847bbcd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 655fd847b64bec33b24534d23d8f7459cad8cede50af9e78b5289f615612e334b8bf53e74990569d52e3ee6276ac49e01e83894b35c4fd64c8a8418668fd17da
|
|
7
|
+
data.tar.gz: 14e3d21ee47872269d4a13bb0794e06d9926eb111e919788122cc76f6d2a6f7cb9af225c39cb9e397883c1194b3e32faa53e7fc58f8619f2bf2314c2052c0f13
|
data/README.md
CHANGED
|
@@ -20,62 +20,48 @@ 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
|
|
29
|
+
client.system.info # model, firmware
|
|
30
|
+
client.system.resources # CPU, memory, uptime
|
|
31
|
+
|
|
32
|
+
# Connected devices
|
|
33
|
+
client.devices.all # all registered devices
|
|
34
|
+
client.devices.active # currently connected
|
|
37
35
|
|
|
38
36
|
# Network
|
|
39
|
-
client.network.interfaces
|
|
40
|
-
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
#
|
|
50
|
-
client.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
client.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
client.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
client.
|
|
62
|
-
|
|
63
|
-
# Hotspot / Policies
|
|
64
|
-
client.hotspot.policies # all IP policies
|
|
65
|
-
client.hotspot.hosts # all registered hosts
|
|
66
|
-
client.hotspot.set_host_policy(mac: 'AA:BB:CC:DD:EE:FF', policy: 'Policy0')
|
|
67
|
-
client.hotspot.set_host_policy(mac: 'AA:BB:CC:DD:EE:FF', policy: nil) # remove policy
|
|
37
|
+
client.network.interfaces # all interfaces
|
|
38
|
+
client.wifi.access_points # Wi-Fi networks
|
|
39
|
+
client.internet.status # internet connectivity
|
|
40
|
+
|
|
41
|
+
# Static routes
|
|
42
|
+
client.routes.all # configured static routes
|
|
43
|
+
client.routes.add(network: '10.0.0.0/24', interface: 'Wireguard0', comment: 'VPN')
|
|
44
|
+
|
|
45
|
+
# DNS-based routes
|
|
46
|
+
client.dns_routes.domain_groups # FQDN domain groups
|
|
47
|
+
client.dns_routes.routes # DNS-based route mappings
|
|
48
|
+
client.dns_routes.create_domain_group(name: 'domain-list0', description: 'YouTube',
|
|
49
|
+
domains: ['youtube.com', 'googlevideo.com'])
|
|
50
|
+
client.dns_routes.add_route(group: 'domain-list0', interface: 'Wireguard0')
|
|
51
|
+
client.dns_routes.delete_route(index: 'abc123...')
|
|
52
|
+
|
|
53
|
+
# Port forwarding
|
|
54
|
+
client.nat.rules # NAT rules
|
|
55
|
+
client.nat.add_forward(index: 1, protocol: 'tcp', port: 8080,
|
|
56
|
+
to_host: '192.168.1.100', to_port: 80)
|
|
57
|
+
|
|
58
|
+
# VPN
|
|
59
|
+
client.vpn.status # VPN server status
|
|
60
|
+
client.vpn.clients # connected VPN clients
|
|
68
61
|
|
|
69
62
|
# Configuration
|
|
70
63
|
client.system_config.save # save to flash
|
|
71
|
-
client.system_config.download #
|
|
72
|
-
|
|
73
|
-
# Raw RCI Access (for custom commands)
|
|
74
|
-
client.rci({ 'show' => { 'system' => {} } })
|
|
75
|
-
client.rci([
|
|
76
|
-
{ 'show' => { 'system' => {} } },
|
|
77
|
-
{ 'show' => { 'version' => {} } }
|
|
78
|
-
])
|
|
64
|
+
client.system_config.download # backup config
|
|
79
65
|
```
|
|
80
66
|
|
|
81
67
|
## Error Handling
|
|
@@ -89,18 +75,18 @@ rescue Keenetic::ConnectionError
|
|
|
89
75
|
# router unreachable
|
|
90
76
|
rescue Keenetic::TimeoutError
|
|
91
77
|
# request timed out
|
|
92
|
-
rescue Keenetic::NotFoundError
|
|
93
|
-
# resource not found
|
|
94
78
|
rescue Keenetic::ApiError => e
|
|
95
|
-
#
|
|
79
|
+
# other API errors
|
|
96
80
|
e.status_code
|
|
97
81
|
e.response_body
|
|
98
82
|
end
|
|
99
83
|
```
|
|
100
84
|
|
|
101
|
-
##
|
|
85
|
+
## Documentation
|
|
102
86
|
|
|
103
|
-
|
|
87
|
+
- [Complete API Reference](docs/API_REFERENCE.md) - All features with detailed examples
|
|
88
|
+
- [API Coverage](API_PROGRESS.md) - Implementation status
|
|
89
|
+
- [Keenetic API Specification](KEENETIC_API.md) - Raw API documentation
|
|
104
90
|
|
|
105
91
|
## Requirements
|
|
106
92
|
|
data/lib/keenetic/client.rb
CHANGED
|
@@ -117,6 +117,76 @@ module Keenetic
|
|
|
117
117
|
@system_config ||= Resources::Config.new(self)
|
|
118
118
|
end
|
|
119
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
|
+
# @return [Resources::DnsRoutes] DNS-based routes and FQDN domain groups resource
|
|
186
|
+
def dns_routes
|
|
187
|
+
@dns_routes ||= Resources::DnsRoutes.new(self)
|
|
188
|
+
end
|
|
189
|
+
|
|
120
190
|
# Execute arbitrary RCI command(s).
|
|
121
191
|
#
|
|
122
192
|
# Provides raw access to the Keenetic RCI (Remote Command Interface).
|
|
@@ -211,6 +281,19 @@ module Keenetic
|
|
|
211
281
|
request(:post, '/rci/', body: commands)
|
|
212
282
|
end
|
|
213
283
|
|
|
284
|
+
# Make a POST request with raw body content (non-JSON).
|
|
285
|
+
#
|
|
286
|
+
# Used for file uploads like configuration restore.
|
|
287
|
+
#
|
|
288
|
+
# @param path [String] API path
|
|
289
|
+
# @param content [String] Raw content to send
|
|
290
|
+
# @param content_type [String] Content-Type header (default: text/plain)
|
|
291
|
+
# @return [String, nil] Response body
|
|
292
|
+
#
|
|
293
|
+
def post_raw(path, content, content_type: 'text/plain')
|
|
294
|
+
request(:post, path, raw_body: content, content_type: content_type)
|
|
295
|
+
end
|
|
296
|
+
|
|
214
297
|
# Check if client is authenticated.
|
|
215
298
|
# @return [Boolean]
|
|
216
299
|
def authenticated?
|
|
@@ -240,6 +323,25 @@ module Keenetic
|
|
|
240
323
|
def request(method, path, options = {})
|
|
241
324
|
authenticate! unless @authenticated || path == '/auth'
|
|
242
325
|
|
|
326
|
+
response = execute_request(method, path, options)
|
|
327
|
+
|
|
328
|
+
# Handle 401 by re-authenticating and retrying once
|
|
329
|
+
if response.code == 401 && path != '/auth'
|
|
330
|
+
config.logger.debug { "Keenetic: Got 401, re-authenticating..." }
|
|
331
|
+
@authenticated = false
|
|
332
|
+
@mutex.synchronize { perform_authentication }
|
|
333
|
+
|
|
334
|
+
response = execute_request(method, path, options)
|
|
335
|
+
|
|
336
|
+
if response.code == 401
|
|
337
|
+
raise AuthenticationError, "Request unauthorized after re-authentication (HTTP 401)"
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
handle_response(response)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def execute_request(method, path, options)
|
|
243
345
|
url = "#{config.base_url}#{path}"
|
|
244
346
|
|
|
245
347
|
request_options = {
|
|
@@ -254,16 +356,17 @@ module Keenetic
|
|
|
254
356
|
url += "?#{URI.encode_www_form(options[:params])}"
|
|
255
357
|
end
|
|
256
358
|
|
|
257
|
-
if options[:
|
|
359
|
+
if options[:raw_body]
|
|
360
|
+
request_options[:body] = options[:raw_body]
|
|
361
|
+
request_options[:headers]['Content-Type'] = options[:content_type] || 'text/plain'
|
|
362
|
+
elsif options[:body]
|
|
258
363
|
request_options[:body] = options[:body].to_json
|
|
259
364
|
request_options[:headers]['Content-Type'] = 'application/json'
|
|
260
365
|
end
|
|
261
366
|
|
|
262
367
|
config.logger.debug { "Keenetic: #{method.upcase} #{url}" }
|
|
263
368
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
handle_response(response)
|
|
369
|
+
Typhoeus::Request.new(url, request_options).run
|
|
267
370
|
end
|
|
268
371
|
|
|
269
372
|
def build_headers
|
|
@@ -297,7 +400,7 @@ module Keenetic
|
|
|
297
400
|
end
|
|
298
401
|
end
|
|
299
402
|
|
|
300
|
-
def handle_response(response)
|
|
403
|
+
def handle_response(response, allow_401: false)
|
|
301
404
|
parse_cookies(response)
|
|
302
405
|
|
|
303
406
|
if response.timed_out?
|
|
@@ -308,7 +411,7 @@ module Keenetic
|
|
|
308
411
|
raise ConnectionError, "Connection failed: #{response.return_message}"
|
|
309
412
|
end
|
|
310
413
|
|
|
311
|
-
unless response.success? || response.code == 401
|
|
414
|
+
unless response.success? || (allow_401 && response.code == 401)
|
|
312
415
|
if response.code == 404
|
|
313
416
|
raise NotFoundError, "Resource not found"
|
|
314
417
|
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
|
|
@@ -13,6 +13,11 @@ module Keenetic
|
|
|
13
13
|
# GET /ci/startup-config.txt
|
|
14
14
|
# Returns: Plain text configuration file
|
|
15
15
|
#
|
|
16
|
+
# === Upload Configuration
|
|
17
|
+
# POST /ci/startup-config.txt
|
|
18
|
+
# Body: Plain text configuration file
|
|
19
|
+
# Restores configuration from backup
|
|
20
|
+
#
|
|
16
21
|
class Config < Base
|
|
17
22
|
# Save current configuration to persistent storage.
|
|
18
23
|
#
|
|
@@ -42,12 +47,43 @@ module Keenetic
|
|
|
42
47
|
#
|
|
43
48
|
# @return [String] Configuration file content
|
|
44
49
|
# @example
|
|
45
|
-
# config_text = client.
|
|
50
|
+
# config_text = client.system_config.download
|
|
46
51
|
# File.write('router-backup.txt', config_text)
|
|
47
52
|
#
|
|
48
53
|
def download
|
|
49
54
|
get('/ci/startup-config.txt')
|
|
50
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
|
|
51
87
|
end
|
|
52
88
|
end
|
|
53
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
|