keenetic 0.2.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/README.md +25 -51
- data/lib/keenetic/client.rb +82 -1
- 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/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 +13 -0
- metadata +14 -1
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/README.md
CHANGED
|
@@ -20,62 +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
|
|
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
|
-
# Static Routes
|
|
53
|
-
client.routes.all
|
|
54
|
-
client.routes.add(host: '1.2.3.4', interface: 'Wireguard0', comment: 'VPN host')
|
|
55
|
-
client.routes.add(network: '10.0.0.0/24', interface: 'Wireguard0', comment: 'VPN network')
|
|
56
|
-
client.routes.add_batch([
|
|
57
|
-
{ host: '1.2.3.4', interface: 'Wireguard0', comment: 'Host 1' },
|
|
58
|
-
{ network: '10.0.0.0/24', interface: 'Wireguard0', comment: 'Network 1' }
|
|
59
|
-
])
|
|
60
|
-
client.routes.delete(host: '1.2.3.4')
|
|
61
|
-
client.routes.delete(network: '10.0.0.0/24')
|
|
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
|
+
# 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
|
|
68
49
|
|
|
69
50
|
# Configuration
|
|
70
51
|
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
|
-
])
|
|
52
|
+
client.system_config.download # backup config
|
|
79
53
|
```
|
|
80
54
|
|
|
81
55
|
## Error Handling
|
|
@@ -89,18 +63,18 @@ rescue Keenetic::ConnectionError
|
|
|
89
63
|
# router unreachable
|
|
90
64
|
rescue Keenetic::TimeoutError
|
|
91
65
|
# request timed out
|
|
92
|
-
rescue Keenetic::NotFoundError
|
|
93
|
-
# resource not found
|
|
94
66
|
rescue Keenetic::ApiError => e
|
|
95
|
-
#
|
|
67
|
+
# other API errors
|
|
96
68
|
e.status_code
|
|
97
69
|
e.response_body
|
|
98
70
|
end
|
|
99
71
|
```
|
|
100
72
|
|
|
101
|
-
##
|
|
73
|
+
## Documentation
|
|
102
74
|
|
|
103
|
-
|
|
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
|
|
104
78
|
|
|
105
79
|
## Requirements
|
|
106
80
|
|
data/lib/keenetic/client.rb
CHANGED
|
@@ -117,6 +117,71 @@ 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
|
+
|
|
120
185
|
# Execute arbitrary RCI command(s).
|
|
121
186
|
#
|
|
122
187
|
# Provides raw access to the Keenetic RCI (Remote Command Interface).
|
|
@@ -211,6 +276,19 @@ module Keenetic
|
|
|
211
276
|
request(:post, '/rci/', body: commands)
|
|
212
277
|
end
|
|
213
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
|
+
|
|
214
292
|
# Check if client is authenticated.
|
|
215
293
|
# @return [Boolean]
|
|
216
294
|
def authenticated?
|
|
@@ -254,7 +332,10 @@ module Keenetic
|
|
|
254
332
|
url += "?#{URI.encode_www_form(options[:params])}"
|
|
255
333
|
end
|
|
256
334
|
|
|
257
|
-
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]
|
|
258
339
|
request_options[:body] = options[:body].to_json
|
|
259
340
|
request_options[:headers]['Content-Type'] = 'application/json'
|
|
260
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
|
|
@@ -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
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Dynamic DNS resource for managing KeenDNS and third-party DDNS.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === KeenDNS Status
|
|
8
|
+
# GET /rci/show/rc/ip/http/dyndns
|
|
9
|
+
#
|
|
10
|
+
# === Configure KeenDNS
|
|
11
|
+
# POST /rci/ip/http/dyndns
|
|
12
|
+
#
|
|
13
|
+
# === Third-Party DDNS
|
|
14
|
+
# GET /rci/show/dyndns
|
|
15
|
+
#
|
|
16
|
+
class Dyndns < Base
|
|
17
|
+
# Get KeenDNS status.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] KeenDNS configuration and status
|
|
20
|
+
# @example
|
|
21
|
+
# status = client.dyndns.keendns_status
|
|
22
|
+
#
|
|
23
|
+
def keendns_status
|
|
24
|
+
response = get('/rci/show/rc/ip/http/dyndns')
|
|
25
|
+
normalize_response(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Configure KeenDNS.
|
|
29
|
+
#
|
|
30
|
+
# @param params [Hash] KeenDNS configuration parameters
|
|
31
|
+
# @return [Hash, nil] API response
|
|
32
|
+
# @example
|
|
33
|
+
# client.dyndns.configure_keendns(enabled: true, domain: 'myrouter')
|
|
34
|
+
#
|
|
35
|
+
def configure_keendns(**params)
|
|
36
|
+
post('/rci/ip/http/dyndns', normalize_params(params))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get third-party DDNS providers status.
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash] Third-party DDNS configuration
|
|
42
|
+
# @example
|
|
43
|
+
# ddns = client.dyndns.third_party
|
|
44
|
+
#
|
|
45
|
+
def third_party
|
|
46
|
+
response = get('/rci/show/dyndns')
|
|
47
|
+
normalize_response(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Alias for third_party
|
|
51
|
+
alias providers third_party
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def normalize_response(response)
|
|
56
|
+
return {} unless response.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
deep_normalize_keys(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def normalize_params(params)
|
|
62
|
+
result = {}
|
|
63
|
+
params.each do |key, value|
|
|
64
|
+
api_key = key.to_s.tr('_', '-')
|
|
65
|
+
result[api_key] = value
|
|
66
|
+
end
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|