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.
@@ -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