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
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# NAT resource for managing port forwarding rules.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading NAT Rules
|
|
8
|
+
# GET /rci/show/ip/nat
|
|
9
|
+
# Returns: Array of port forwarding rules
|
|
10
|
+
#
|
|
11
|
+
# === Adding Port Forward
|
|
12
|
+
# POST /rci/ip/nat
|
|
13
|
+
# Body: { index, description, protocol, interface, port, to-host, to-port, enabled }
|
|
14
|
+
#
|
|
15
|
+
# === Deleting Port Forward
|
|
16
|
+
# POST /rci/ip/nat
|
|
17
|
+
# Body: { index, no: true }
|
|
18
|
+
#
|
|
19
|
+
# === Reading UPnP Mappings
|
|
20
|
+
# GET /rci/show/upnp/redirect
|
|
21
|
+
# Returns: Array of automatic UPnP port mappings
|
|
22
|
+
#
|
|
23
|
+
class Nat < Base
|
|
24
|
+
# Get all NAT/port forwarding rules.
|
|
25
|
+
#
|
|
26
|
+
# == Keenetic API Request
|
|
27
|
+
# GET /rci/show/ip/nat
|
|
28
|
+
#
|
|
29
|
+
# == Response Fields
|
|
30
|
+
# - index: Rule index/priority
|
|
31
|
+
# - description: Rule description
|
|
32
|
+
# - protocol: "tcp", "udp", or "any"
|
|
33
|
+
# - interface: WAN interface
|
|
34
|
+
# - port: External port
|
|
35
|
+
# - end_port: End of port range (optional)
|
|
36
|
+
# - to_host: Internal host IP
|
|
37
|
+
# - to_port: Internal port
|
|
38
|
+
# - enabled: Rule is active
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Hash>] List of normalized NAT rules
|
|
41
|
+
# @example
|
|
42
|
+
# rules = client.nat.rules
|
|
43
|
+
# # => [{ index: 1, description: "Web Server", protocol: "tcp", port: 8080, ... }]
|
|
44
|
+
#
|
|
45
|
+
def rules
|
|
46
|
+
response = get('/rci/show/ip/nat')
|
|
47
|
+
normalize_rules(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Find a NAT rule by index.
|
|
51
|
+
#
|
|
52
|
+
# @param index [Integer] Rule index
|
|
53
|
+
# @return [Hash, nil] Rule data or nil if not found
|
|
54
|
+
# @example
|
|
55
|
+
# rule = client.nat.find_rule(1)
|
|
56
|
+
# # => { index: 1, description: "Web Server", ... }
|
|
57
|
+
#
|
|
58
|
+
def find_rule(index)
|
|
59
|
+
rules.find { |r| r[:index] == index }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Add a port forwarding rule.
|
|
63
|
+
#
|
|
64
|
+
# == Keenetic API Request
|
|
65
|
+
# POST /rci/ip/nat
|
|
66
|
+
# Body: { index, description, protocol, interface, port, to-host, to-port, enabled }
|
|
67
|
+
#
|
|
68
|
+
# @param index [Integer] Rule index/priority
|
|
69
|
+
# @param protocol [String] Protocol: "tcp", "udp", or "any"
|
|
70
|
+
# @param port [Integer] External port
|
|
71
|
+
# @param to_host [String] Internal host IP address
|
|
72
|
+
# @param to_port [Integer] Internal port
|
|
73
|
+
# @param interface [String] WAN interface name (default: "ISP")
|
|
74
|
+
# @param description [String, nil] Optional rule description
|
|
75
|
+
# @param end_port [Integer, nil] End of port range (optional)
|
|
76
|
+
# @param enabled [Boolean] Whether rule is active (default: true)
|
|
77
|
+
# @return [Hash, Array, nil] API response
|
|
78
|
+
#
|
|
79
|
+
# @example Add simple port forward
|
|
80
|
+
# client.nat.add_forward(
|
|
81
|
+
# index: 1,
|
|
82
|
+
# protocol: 'tcp',
|
|
83
|
+
# port: 8080,
|
|
84
|
+
# to_host: '192.168.1.100',
|
|
85
|
+
# to_port: 80
|
|
86
|
+
# )
|
|
87
|
+
#
|
|
88
|
+
# @example Add port range forward
|
|
89
|
+
# client.nat.add_forward(
|
|
90
|
+
# index: 2,
|
|
91
|
+
# protocol: 'udp',
|
|
92
|
+
# port: 27015,
|
|
93
|
+
# end_port: 27030,
|
|
94
|
+
# to_host: '192.168.1.50',
|
|
95
|
+
# to_port: 27015,
|
|
96
|
+
# description: 'Game Server'
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
def add_forward(index:, protocol:, port:, to_host:, to_port:, interface: 'ISP',
|
|
100
|
+
description: nil, end_port: nil, enabled: true)
|
|
101
|
+
params = {
|
|
102
|
+
'index' => index,
|
|
103
|
+
'protocol' => protocol,
|
|
104
|
+
'interface' => interface,
|
|
105
|
+
'port' => port,
|
|
106
|
+
'to-host' => to_host,
|
|
107
|
+
'to-port' => to_port,
|
|
108
|
+
'enabled' => enabled
|
|
109
|
+
}
|
|
110
|
+
params['description'] = description if description
|
|
111
|
+
params['end-port'] = end_port if end_port
|
|
112
|
+
|
|
113
|
+
post('/rci/ip/nat', params)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Delete a port forwarding rule.
|
|
117
|
+
#
|
|
118
|
+
# == Keenetic API Request
|
|
119
|
+
# POST /rci/ip/nat
|
|
120
|
+
# Body: { index, no: true }
|
|
121
|
+
#
|
|
122
|
+
# @param index [Integer] Rule index to delete
|
|
123
|
+
# @return [Hash, Array, nil] API response
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# client.nat.delete_forward(index: 1)
|
|
127
|
+
#
|
|
128
|
+
def delete_forward(index:)
|
|
129
|
+
post('/rci/ip/nat', { 'index' => index, 'no' => true })
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get all UPnP port mappings.
|
|
133
|
+
#
|
|
134
|
+
# UPnP mappings are automatically created by devices on the network.
|
|
135
|
+
# These are read-only and managed by the devices themselves.
|
|
136
|
+
#
|
|
137
|
+
# == Keenetic API Request
|
|
138
|
+
# GET /rci/show/upnp/redirect
|
|
139
|
+
#
|
|
140
|
+
# == Response Fields
|
|
141
|
+
# - protocol: "tcp" or "udp"
|
|
142
|
+
# - interface: WAN interface
|
|
143
|
+
# - port: External port
|
|
144
|
+
# - to_host: Internal host IP
|
|
145
|
+
# - to_port: Internal port
|
|
146
|
+
# - description: Mapping description (from device)
|
|
147
|
+
#
|
|
148
|
+
# @return [Array<Hash>] List of normalized UPnP mappings
|
|
149
|
+
# @example
|
|
150
|
+
# mappings = client.nat.upnp_mappings
|
|
151
|
+
# # => [{ protocol: "tcp", port: 51234, to_host: "192.168.1.50", ... }]
|
|
152
|
+
#
|
|
153
|
+
def upnp_mappings
|
|
154
|
+
response = get('/rci/show/upnp/redirect')
|
|
155
|
+
normalize_upnp_mappings(response)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def normalize_upnp_mappings(response)
|
|
161
|
+
return [] unless response.is_a?(Array)
|
|
162
|
+
|
|
163
|
+
response.map { |mapping| normalize_upnp_mapping(mapping) }.compact
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def normalize_upnp_mapping(data)
|
|
167
|
+
return nil unless data.is_a?(Hash)
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
protocol: data['protocol'],
|
|
171
|
+
interface: data['interface'],
|
|
172
|
+
port: data['port'],
|
|
173
|
+
to_host: data['to-host'],
|
|
174
|
+
to_port: data['to-port'],
|
|
175
|
+
description: data['description']
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def normalize_rules(response)
|
|
180
|
+
return [] unless response.is_a?(Array)
|
|
181
|
+
|
|
182
|
+
response.map { |rule| normalize_rule(rule) }.compact
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def normalize_rule(data)
|
|
186
|
+
return nil unless data.is_a?(Hash)
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
index: data['index'],
|
|
190
|
+
description: data['description'],
|
|
191
|
+
protocol: data['protocol'],
|
|
192
|
+
interface: data['interface'],
|
|
193
|
+
port: data['port'],
|
|
194
|
+
end_port: data['end-port'],
|
|
195
|
+
to_host: data['to-host'],
|
|
196
|
+
to_port: data['to-port'],
|
|
197
|
+
enabled: normalize_boolean(data['enabled'])
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# QoS resource for Quality of Service and traffic control.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Traffic Shaper Status
|
|
8
|
+
# GET /rci/show/ip/traffic-control
|
|
9
|
+
#
|
|
10
|
+
# === IntelliQoS Settings
|
|
11
|
+
# GET /rci/show/ip/qos
|
|
12
|
+
#
|
|
13
|
+
# === Traffic Statistics by Host
|
|
14
|
+
# GET /rci/show/ip/hotspot/summary
|
|
15
|
+
#
|
|
16
|
+
class Qos < Base
|
|
17
|
+
# Get traffic shaper status.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] Traffic shaper configuration and status
|
|
20
|
+
# @example
|
|
21
|
+
# shaper = client.qos.traffic_shaper
|
|
22
|
+
#
|
|
23
|
+
def traffic_shaper
|
|
24
|
+
response = get('/rci/show/ip/traffic-control')
|
|
25
|
+
normalize_response(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Alias for traffic_shaper
|
|
29
|
+
alias shaper traffic_shaper
|
|
30
|
+
|
|
31
|
+
# Get IntelliQoS settings.
|
|
32
|
+
#
|
|
33
|
+
# @return [Hash] IntelliQoS configuration
|
|
34
|
+
# @example
|
|
35
|
+
# qos = client.qos.intelliqos
|
|
36
|
+
#
|
|
37
|
+
def intelliqos
|
|
38
|
+
response = get('/rci/show/ip/qos')
|
|
39
|
+
normalize_response(response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Alias for intelliqos
|
|
43
|
+
alias settings intelliqos
|
|
44
|
+
|
|
45
|
+
# Get traffic statistics by host.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Hash>] Traffic statistics per host
|
|
48
|
+
# @example
|
|
49
|
+
# stats = client.qos.traffic_stats
|
|
50
|
+
#
|
|
51
|
+
def traffic_stats
|
|
52
|
+
response = get('/rci/show/ip/hotspot/summary')
|
|
53
|
+
normalize_stats(response)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Alias for traffic_stats
|
|
57
|
+
alias host_stats traffic_stats
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def normalize_response(response)
|
|
62
|
+
return {} unless response.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
deep_normalize_keys(response)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def normalize_stats(response)
|
|
68
|
+
stats_data = case response
|
|
69
|
+
when Array
|
|
70
|
+
response
|
|
71
|
+
when Hash
|
|
72
|
+
response['host'] || response['hosts'] || response['stat'] || []
|
|
73
|
+
else
|
|
74
|
+
[]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return [] unless stats_data.is_a?(Array)
|
|
78
|
+
|
|
79
|
+
stats_data.map { |stat| normalize_stat(stat) }.compact
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def normalize_stat(data)
|
|
83
|
+
return nil unless data.is_a?(Hash)
|
|
84
|
+
|
|
85
|
+
deep_normalize_keys(data)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|