sqreen 1.15.0 → 1.15.1
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/lib/sqreen/actions.rb +112 -43
- data/lib/sqreen/callback_tree.rb +16 -3
- data/lib/sqreen/callbacks.rb +7 -0
- data/lib/sqreen/instrumentation.rb +2 -0
- data/lib/sqreen/js/mini_racer_adapter.rb +26 -4
- data/lib/sqreen/rule_attributes.rb +1 -0
- data/lib/sqreen/rule_callback.rb +4 -0
- data/lib/sqreen/rules_callbacks/blacklist_ips.rb +1 -1
- data/lib/sqreen/rules_callbacks/run_block_user_actions.rb +1 -1
- data/lib/sqreen/rules_callbacks/run_req_start_actions.rb +8 -2
- data/lib/sqreen/runner.rb +7 -6
- data/lib/sqreen/trie.rb +274 -0
- data/lib/sqreen/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 87e7db04c8d69640cf2da93b2dc235f5b1a95c8f2847db137c3047eb9f87ac1a
|
4
|
+
data.tar.gz: 690b3963a003fced7eb7e699576a488b006dc63589e372c67b0f808d9b2aafd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e70942a29b5a702ebd3e023d72ad49a58234510c798e0fbf88dfc99388ddbbb24bc0c25a41f3103df92884d1b9804355e2d724868372f9dc3197bcad134a9fd4
|
7
|
+
data.tar.gz: 5719e61c1bae72c9dbf4257ccce892cf8135b7a887e69dce1320c5955ddae9b575a992ab42a83e28887daaa09187705686579a6cabc53c555d5931f90a812b11
|
data/lib/sqreen/actions.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
# Please refer to our terms for more information: https://www.sqreen.io/terms.html
|
3
3
|
|
4
4
|
require 'ipaddr'
|
5
|
+
require 'sqreen/trie'
|
5
6
|
require 'sqreen/log'
|
6
7
|
require 'sqreen/exception'
|
7
8
|
require 'sqreen/sdk'
|
@@ -24,21 +25,17 @@ module Sqreen
|
|
24
25
|
class Repository
|
25
26
|
include Singleton
|
26
27
|
|
27
|
-
def
|
28
|
-
|
29
|
-
@actions.default_proc = proc { |h, k| h[k] = [] }
|
28
|
+
def add(params, action)
|
29
|
+
action.class.index(params || {}, action)
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
def [](action_class)
|
37
|
-
@actions[action_class]
|
32
|
+
def get(action_class, key)
|
33
|
+
action_class = Base.get_type_class(action_class) unless action_class.class == Class
|
34
|
+
action_class.actions_matching key
|
38
35
|
end
|
39
36
|
|
40
37
|
def clear
|
41
|
-
|
38
|
+
Base.known_subclasses.each(&:clear)
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
@@ -68,6 +65,8 @@ module Sqreen
|
|
68
65
|
end
|
69
66
|
|
70
67
|
# Base class for actions
|
68
|
+
# subclasses must also implement some methods in their singleton classes
|
69
|
+
# (actions_matching, index and clear)
|
71
70
|
class Base
|
72
71
|
attr_reader :id, :expiry, :send_response
|
73
72
|
|
@@ -124,10 +123,27 @@ module Sqreen
|
|
124
123
|
@@subclasses[name]
|
125
124
|
end
|
126
125
|
|
126
|
+
def known_subclasses
|
127
|
+
@@subclasses.values
|
128
|
+
end
|
129
|
+
|
127
130
|
def known_types
|
128
131
|
@@subclasses.keys
|
129
132
|
end
|
130
133
|
|
134
|
+
# all actions matching, possibly already expired
|
135
|
+
def actions_matching(_key)
|
136
|
+
raise 'implement in singletons of subclasses'
|
137
|
+
end
|
138
|
+
|
139
|
+
def index(_params, _action)
|
140
|
+
raise 'implement in singletons of subclasses'
|
141
|
+
end
|
142
|
+
|
143
|
+
def clear
|
144
|
+
raise 'implement in singletons of subclasses'
|
145
|
+
end
|
146
|
+
|
131
147
|
def inherited(subclass)
|
132
148
|
class << subclass
|
133
149
|
public :new
|
@@ -143,43 +159,74 @@ module Sqreen
|
|
143
159
|
end
|
144
160
|
end
|
145
161
|
|
146
|
-
module
|
147
|
-
|
162
|
+
module IpRangesIndex
|
163
|
+
def add_prefix(prefix_str, data)
|
164
|
+
@trie_v4 ||= Sqreen::Trie.new
|
165
|
+
@trie_v6 ||= Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
|
166
|
+
prefix = Sqreen::Prefix.from_str(prefix_str, data)
|
167
|
+
trie = prefix.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
|
168
|
+
trie.insert prefix
|
169
|
+
end
|
170
|
+
|
171
|
+
def matching_actions(client_ip)
|
172
|
+
parsed_ip = IPAddr.new(client_ip)
|
173
|
+
trie = parsed_ip.family == Socket::AF_INET6 ? @trie_v6 : @trie_v4
|
174
|
+
found = trie.search_matching(parsed_ip.to_i, parsed_ip.family)
|
175
|
+
return [] unless found.size > 0
|
176
|
+
|
177
|
+
Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
|
178
|
+
found.map(&:data)
|
179
|
+
end
|
180
|
+
|
181
|
+
def clear
|
182
|
+
@trie_v4 = Sqreen::Trie.new
|
183
|
+
@trie_v6 = Sqreen::Trie.new(nil, nil, Socket::AF_INET6)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
module IpRangeIndexedActionClass
|
188
|
+
include IpRangesIndex
|
189
|
+
|
190
|
+
def actions_matching(client_ip)
|
191
|
+
matching_actions client_ip
|
192
|
+
end
|
193
|
+
|
194
|
+
def index(params, action)
|
195
|
+
ranges = parse_ip_ranges params
|
196
|
+
|
197
|
+
ranges.each do |r|
|
198
|
+
add_prefix r, action
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
148
203
|
|
204
|
+
# returns array of prefixes in string form
|
149
205
|
def parse_ip_ranges(params)
|
150
206
|
ranges = params['ip_cidr']
|
151
207
|
unless ranges && ranges.is_a?(Array) && !ranges.empty?
|
152
208
|
raise 'no non-empty ip_cidr array present'
|
153
209
|
end
|
154
210
|
|
155
|
-
|
156
|
-
end
|
157
|
-
|
158
|
-
def matches_ip?(client_ip)
|
159
|
-
parsed_ip = IPAddr.new client_ip
|
160
|
-
found = ranges.find { |r| r.include? parsed_ip }
|
161
|
-
return false unless found
|
162
|
-
|
163
|
-
Sqreen.log.debug("Client ip #{client_ip} matches #{found.inspect}")
|
164
|
-
true
|
211
|
+
ranges
|
165
212
|
end
|
166
213
|
end
|
167
214
|
|
168
215
|
# Block a list of IP address ranges. Standard "raise" behavior.
|
169
216
|
class BlockIp < Base
|
170
|
-
|
217
|
+
extend IpRangeIndexedActionClass
|
218
|
+
|
171
219
|
self.type_name = 'block_ip'
|
172
220
|
|
173
221
|
def initialize(id, opts, params = {})
|
222
|
+
# no need to store the ranges for this action, the index filter the class
|
174
223
|
super(id, opts)
|
175
|
-
parse_ip_ranges params
|
176
224
|
end
|
177
225
|
|
178
226
|
def do_run(client_ip)
|
179
|
-
return nil unless matches_ip? client_ip
|
180
227
|
e = Sqreen::AttackBlocked.new("Blocked client's IP #{client_ip} " \
|
181
|
-
"(action: #{id}
|
182
|
-
{ :status => :raise, :exception => e }
|
228
|
+
"(action: #{id}). No action is required")
|
229
|
+
{ :status => :raise, :exception => e, :skip_rem_cbs => true }
|
183
230
|
end
|
184
231
|
|
185
232
|
def event_properties(client_ip)
|
@@ -190,7 +237,8 @@ module Sqreen
|
|
190
237
|
# Block a list of IP address ranges by forcefully redirecting the user
|
191
238
|
# to a specific URL.
|
192
239
|
class RedirectIp < Base
|
193
|
-
|
240
|
+
extend IpRangeIndexedActionClass
|
241
|
+
|
194
242
|
self.type_name = 'redirect_ip'
|
195
243
|
|
196
244
|
attr_reader :redirect_url
|
@@ -199,16 +247,15 @@ module Sqreen
|
|
199
247
|
super(id, opts)
|
200
248
|
@redirect_url = params['url']
|
201
249
|
raise "no url provided for action #{id}" unless @redirect_url
|
202
|
-
parse_ip_ranges params
|
203
250
|
end
|
204
251
|
|
205
252
|
def do_run(client_ip)
|
206
|
-
return nil unless matches_ip? client_ip
|
207
253
|
Sqreen.log.info "Will request redirect for client with IP #{client_ip} " \
|
208
|
-
"(action: #{id}
|
254
|
+
"(action: #{id})."
|
209
255
|
{
|
210
256
|
:status => :skip,
|
211
257
|
:new_return_value => [303, { 'Location' => @redirect_url }, ['']],
|
258
|
+
:skip_rem_cbs => true,
|
212
259
|
}
|
213
260
|
end
|
214
261
|
|
@@ -222,15 +269,45 @@ module Sqreen
|
|
222
269
|
class BlockUser < Base
|
223
270
|
self.type_name = 'block_user'
|
224
271
|
|
272
|
+
class << self
|
273
|
+
def actions_matching(identity_params)
|
274
|
+
key = stringify_keys(identity_params)
|
275
|
+
actions = @idx[key]
|
276
|
+
actions || []
|
277
|
+
end
|
278
|
+
|
279
|
+
def index(params, action)
|
280
|
+
@idx ||= {}
|
281
|
+
users = params['users']
|
282
|
+
raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
|
283
|
+
raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
|
284
|
+
|
285
|
+
users.each do |u|
|
286
|
+
@idx[u] ||= []
|
287
|
+
@idx[u] << action
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def clear
|
292
|
+
@idx = {}
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
def stringify_keys(hash)
|
298
|
+
Hash[
|
299
|
+
hash.map { |k, v| [k.to_s, v] }
|
300
|
+
]
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
# BlockUser proper definition continues
|
305
|
+
|
225
306
|
def initialize(id, opts, params = {})
|
226
307
|
super(id, opts)
|
227
|
-
@users = params['users']
|
228
|
-
raise ::Sqreen::Exception, 'nil "users" param for block_user action' if @users.nil?
|
229
|
-
raise ::Sqreen::Exception, '"users" param must be an array' unless @users.is_a? Array
|
230
308
|
end
|
231
309
|
|
232
310
|
def do_run(identity_params)
|
233
|
-
return unless @users.include? stringify_keys(identity_params)
|
234
311
|
Sqreen.log.info(
|
235
312
|
"Will raise due to user being blocked by action #{id}. " \
|
236
313
|
"Blocked user identity: #{identity_params}"
|
@@ -250,14 +327,6 @@ module Sqreen
|
|
250
327
|
def event_properties(identity_params)
|
251
328
|
{ 'user' => identity_params }
|
252
329
|
end
|
253
|
-
|
254
|
-
private
|
255
|
-
|
256
|
-
def stringify_keys(hash)
|
257
|
-
Hash[
|
258
|
-
hash.map { |k, v| [k.to_s, v] }
|
259
|
-
]
|
260
|
-
end
|
261
330
|
end
|
262
331
|
end
|
263
332
|
end
|
data/lib/sqreen/callback_tree.rb
CHANGED
@@ -25,15 +25,15 @@ module Sqreen
|
|
25
25
|
|
26
26
|
if cb.pre?
|
27
27
|
methods[0] ||= []
|
28
|
-
methods[0]
|
28
|
+
add_to_array(methods[0], cb)
|
29
29
|
end
|
30
30
|
if cb.post?
|
31
31
|
methods[1] ||= []
|
32
|
-
methods[1]
|
32
|
+
add_to_array(methods[1], cb)
|
33
33
|
end
|
34
34
|
if cb.failing?
|
35
35
|
methods[2] ||= []
|
36
|
-
methods[2]
|
36
|
+
add_to_array(methods[2], cb)
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -88,5 +88,18 @@ module Sqreen
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def add_to_array(arr, cb)
|
95
|
+
# find last element with prio equal or smaller; insert after
|
96
|
+
pos = arr.size
|
97
|
+
arr.reverse_each do |arr_cb|
|
98
|
+
break if arr_cb.priority <= cb.priority
|
99
|
+
pos -= 1
|
100
|
+
end
|
101
|
+
|
102
|
+
arr.insert(pos, cb)
|
103
|
+
end
|
91
104
|
end
|
92
105
|
end
|
data/lib/sqreen/callbacks.rb
CHANGED
@@ -36,6 +36,8 @@ module Sqreen
|
|
36
36
|
#
|
37
37
|
# CB can also declare that they are whitelisted and should not be run at the moment.
|
38
38
|
|
39
|
+
DEFAULT_PRIORITY = 100
|
40
|
+
|
39
41
|
attr_reader :klass, :method
|
40
42
|
attr_reader :overtimeable
|
41
43
|
|
@@ -55,6 +57,11 @@ module Sqreen
|
|
55
57
|
false
|
56
58
|
end
|
57
59
|
|
60
|
+
# the lower, the closer to the beginning of the list
|
61
|
+
def priority
|
62
|
+
DEFAULT_PRIORITY
|
63
|
+
end
|
64
|
+
|
58
65
|
def pre?
|
59
66
|
@has_pre
|
60
67
|
end
|
@@ -95,6 +95,7 @@ module Sqreen
|
|
95
95
|
res[:rule_name] = rule
|
96
96
|
end
|
97
97
|
returns << res
|
98
|
+
break if res.is_a?(Hash) && res[:skip_rem_cbs]
|
98
99
|
rescue StandardError => e
|
99
100
|
Sqreen.log.warn { "we catch an exception: #{e.inspect}" }
|
100
101
|
Sqreen.log.debug e.backtrace
|
@@ -710,6 +711,7 @@ module Sqreen
|
|
710
711
|
add_callback(rcb)
|
711
712
|
end
|
712
713
|
|
714
|
+
# add hardcoded callbacks, observing priority
|
713
715
|
hardcoded_callbacks(framework).each { |cb| add_callback(cb) }
|
714
716
|
|
715
717
|
Sqreen.instrumentation_ready = true
|
@@ -85,9 +85,9 @@ module Sqreen
|
|
85
85
|
ctx.add_code(@code_id, @code) unless ctx.has_code?(@code_id)
|
86
86
|
|
87
87
|
begin
|
88
|
-
json_args =
|
88
|
+
json_args = convert_arguments(arguments)
|
89
89
|
ctx.eval_unsafe(
|
90
|
-
|
90
|
+
"sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
|
91
91
|
rescue @module::ScriptTerminatedError
|
92
92
|
nil
|
93
93
|
end
|
@@ -100,9 +100,31 @@ module Sqreen
|
|
100
100
|
|
101
101
|
private
|
102
102
|
|
103
|
-
def
|
104
|
-
|
103
|
+
def convert_arguments(args)
|
104
|
+
JSON.generate(args)
|
105
|
+
rescue JSON::GeneratorError, Encoding::UndefinedConversionError
|
106
|
+
fixed_args = fixup_bad_encoding(args)
|
107
|
+
JSON.generate(fixed_args)
|
108
|
+
end
|
109
|
+
|
110
|
+
def fixup_bad_encoding(arg, max_depth = 100)
|
111
|
+
return nil if max_depth <= 0
|
112
|
+
|
113
|
+
if arg.is_a?(Array)
|
114
|
+
return arg.map { |it| fixup_bad_encoding(it, max_depth - 1) }
|
115
|
+
end
|
116
|
+
|
117
|
+
if arg.is_a?(Hash)
|
118
|
+
return Hash[
|
119
|
+
arg.map do |k, v|
|
120
|
+
[fixup_bad_encoding(k, max_depth - 1),
|
121
|
+
fixup_bad_encoding(v, max_depth - 1)]
|
122
|
+
end
|
123
|
+
]
|
124
|
+
end
|
125
|
+
|
105
126
|
return arg unless arg.is_a?(String)
|
127
|
+
|
106
128
|
unless arg.valid_encoding?
|
107
129
|
return arg.dup.force_encoding(Encoding::ISO_8859_1)
|
108
130
|
end
|
data/lib/sqreen/rule_callback.rb
CHANGED
@@ -14,7 +14,7 @@ module Sqreen
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def post(_retval, _inst, args, _budget = nil)
|
17
|
-
actions = actions_repo[
|
17
|
+
actions = actions_repo.get('block_user', args[@auth_keys_idx])
|
18
18
|
|
19
19
|
actions.each do |action|
|
20
20
|
res = action.run args[@auth_keys_idx]
|
@@ -9,6 +9,8 @@ module Sqreen
|
|
9
9
|
module Rules
|
10
10
|
# Runs actions concerned with whether the request ought to be served
|
11
11
|
class RunReqStartActions < FrameworkCB
|
12
|
+
PRIORITY = 95
|
13
|
+
|
12
14
|
def initialize(framework)
|
13
15
|
if defined?(Sqreen::Frameworks::SinatraFramework) &&
|
14
16
|
framework.is_a?(Sqreen::Frameworks::SinatraFramework)
|
@@ -30,13 +32,17 @@ module Sqreen
|
|
30
32
|
framework && !framework.whitelisted_match.nil?
|
31
33
|
end
|
32
34
|
|
35
|
+
def priority
|
36
|
+
PRIORITY
|
37
|
+
end
|
38
|
+
|
33
39
|
def pre(_inst, _args, _budget = nil, &_block)
|
34
40
|
return unless framework
|
35
41
|
ip = framework.client_ip
|
36
42
|
return unless ip
|
37
43
|
|
38
|
-
actions = actions_repo
|
39
|
-
actions_repo
|
44
|
+
actions = actions_repo.get(Sqreen::Actions::BlockIp, ip) +
|
45
|
+
actions_repo.get(Sqreen::Actions::RedirectIp, ip)
|
40
46
|
|
41
47
|
actions.each do |act|
|
42
48
|
res = run_client_ip_action(act, ip)
|
data/lib/sqreen/runner.rb
CHANGED
@@ -445,9 +445,14 @@ module Sqreen
|
|
445
445
|
def load_actions(hashes)
|
446
446
|
unsupported = Set.new
|
447
447
|
|
448
|
+
repos = Sqreen::Actions::Repository.instance
|
449
|
+
repos.clear
|
450
|
+
|
448
451
|
actions = hashes.map do |h|
|
449
452
|
begin
|
450
|
-
Sqreen::Actions.deserialize_action(h)
|
453
|
+
act = Sqreen::Actions.deserialize_action(h)
|
454
|
+
repos.add h['parameters'], act
|
455
|
+
act
|
451
456
|
rescue Sqreen::Actions::UnknownActionType => e
|
452
457
|
Sqreen.log.warn("Unsupported action type: #{e.action_type}")
|
453
458
|
unsupported << e.action_type
|
@@ -458,11 +463,7 @@ module Sqreen
|
|
458
463
|
end
|
459
464
|
|
460
465
|
actions = actions.reject(&:nil?)
|
461
|
-
Sqreen.log.debug("
|
462
|
-
|
463
|
-
repos = Sqreen::Actions::Repository.instance
|
464
|
-
repos.clear
|
465
|
-
actions.each { |action| repos << action }
|
466
|
+
Sqreen.log.debug("Added #{actions.size} valid actions")
|
466
467
|
|
467
468
|
unsupported
|
468
469
|
end
|
data/lib/sqreen/trie.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module Sqreen
|
4
|
+
Trie = Struct.new(:head, :num_active_nodes, :family) do
|
5
|
+
attr_reader :max_num_bits
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
self.family ||= Socket::AF_INET
|
10
|
+
self.num_active_nodes = 0
|
11
|
+
@max_num_bits = family == Socket::AF_INET ? 32 : 128
|
12
|
+
end
|
13
|
+
|
14
|
+
def insert(arg_prefix)
|
15
|
+
raise 'family mismatch' if arg_prefix.family != family
|
16
|
+
|
17
|
+
if head.nil?
|
18
|
+
node = Node.new(
|
19
|
+
arg_prefix.bitlen, # bit
|
20
|
+
arg_prefix,
|
21
|
+
nil, nil, nil, #l , r, parent
|
22
|
+
)
|
23
|
+
self.head = node
|
24
|
+
self.num_active_nodes += 1
|
25
|
+
return node
|
26
|
+
end
|
27
|
+
|
28
|
+
arg_addr = arg_prefix.address
|
29
|
+
arg_bitlen = arg_prefix.bitlen
|
30
|
+
xcur = self.head
|
31
|
+
|
32
|
+
# descend until we find the end of the tree or go past the
|
33
|
+
# bitlen of the prefix we're adding
|
34
|
+
while xcur.bit < arg_bitlen || xcur.empty?
|
35
|
+
if bit_set?(arg_addr, xcur.bit)
|
36
|
+
break if xcur.r.nil?
|
37
|
+
xcur = xcur.r
|
38
|
+
else
|
39
|
+
break if xcur.l.nil?
|
40
|
+
xcur = xcur.l
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
trie_addr = xcur.prefix.address
|
45
|
+
|
46
|
+
# find first bit that differs between addr and trie_addr
|
47
|
+
cmp_bit_end = [arg_bitlen, xcur.bit].min # after last to be compared
|
48
|
+
|
49
|
+
differ_bit = (0...cmp_bit_end).find do |i|
|
50
|
+
bit_set?(arg_addr, i) ^ bit_set?(trie_addr, i)
|
51
|
+
end
|
52
|
+
differ_bit = cmp_bit_end if differ_bit.nil?
|
53
|
+
|
54
|
+
# go up till we find the parent before the bits differ
|
55
|
+
xparent = xcur.parent
|
56
|
+
while !xparent.nil? && xparent.bit >= differ_bit
|
57
|
+
xcur = xparent
|
58
|
+
xparent = xcur.parent
|
59
|
+
end
|
60
|
+
|
61
|
+
# case 1: replace current node's prefix
|
62
|
+
if differ_bit == arg_bitlen && xcur.bit == arg_bitlen
|
63
|
+
xcur.prefix = arg_prefix
|
64
|
+
return xcur
|
65
|
+
end
|
66
|
+
|
67
|
+
xnew = Node.new(arg_prefix.bitlen, arg_prefix, nil, nil, nil)
|
68
|
+
self.num_active_nodes += 1
|
69
|
+
|
70
|
+
# case 2: append below found node
|
71
|
+
if xcur.bit == differ_bit
|
72
|
+
xnew.parent = xcur
|
73
|
+
if bit_set?(arg_addr, xcur.bit)
|
74
|
+
xcur.r = xnew
|
75
|
+
else
|
76
|
+
xcur.l = xnew
|
77
|
+
end
|
78
|
+
|
79
|
+
return xnew
|
80
|
+
end
|
81
|
+
|
82
|
+
# case 3: take place of found node
|
83
|
+
if arg_bitlen == differ_bit
|
84
|
+
if bit_set?(trie_addr, arg_bitlen)
|
85
|
+
xnew.r = xcur
|
86
|
+
else
|
87
|
+
xnew.l = xcur
|
88
|
+
end
|
89
|
+
|
90
|
+
xnew.parent = xcur.parent
|
91
|
+
|
92
|
+
if xcur.parent.nil?
|
93
|
+
self.head = xnew
|
94
|
+
elsif xcur.parent.r.equal?(xcur)
|
95
|
+
xcur.parent.r = xnew
|
96
|
+
else
|
97
|
+
xcur.parent.l = xnew
|
98
|
+
end
|
99
|
+
|
100
|
+
xcur.parent = xnew
|
101
|
+
|
102
|
+
return xnew
|
103
|
+
end
|
104
|
+
|
105
|
+
# case 4: need to add intermediate node to be parent of the
|
106
|
+
# both xnew and xcur
|
107
|
+
|
108
|
+
# last arg is the parent of the new node
|
109
|
+
xglue = Node.new(differ_bit, nil, nil, nil, xcur.parent)
|
110
|
+
self.num_active_nodes += 1
|
111
|
+
|
112
|
+
if bit_set?(arg_addr, differ_bit)
|
113
|
+
xglue.r = xnew
|
114
|
+
xglue.l = xcur
|
115
|
+
else
|
116
|
+
xglue.r = xcur
|
117
|
+
xglue.l = xnew
|
118
|
+
end
|
119
|
+
|
120
|
+
xnew.parent = xglue
|
121
|
+
|
122
|
+
if xcur.parent.nil?
|
123
|
+
self.head = xglue
|
124
|
+
elsif xcur.equal?(xcur.parent.r)
|
125
|
+
xcur.parent.r = xglue
|
126
|
+
else
|
127
|
+
xcur.parent.l = xglue
|
128
|
+
end
|
129
|
+
|
130
|
+
xcur.parent = xglue
|
131
|
+
|
132
|
+
xnew
|
133
|
+
end
|
134
|
+
|
135
|
+
# generate pdf with `dot -Tpdf <input> > foo.pdf`
|
136
|
+
def to_dot(header = true)
|
137
|
+
res = ''
|
138
|
+
res = "graph trie {\n" if header
|
139
|
+
|
140
|
+
next_name = 'a'
|
141
|
+
obj_label_map = Hash.new do |h, k|
|
142
|
+
h[k] = next_name
|
143
|
+
next_name = next_name.next
|
144
|
+
h[k]
|
145
|
+
end
|
146
|
+
|
147
|
+
head.walk(max_num_bits, true) do |node|
|
148
|
+
node_name = obj_label_map[node.object_id]
|
149
|
+
|
150
|
+
if node.empty?
|
151
|
+
label = "<glue at bit #{node.bit}>"
|
152
|
+
else
|
153
|
+
ip_addr = IPAddr.new(node.prefix.address, node.prefix.family)
|
154
|
+
label = ip_addr.to_s + '/' + node.prefix.bitlen.to_s
|
155
|
+
end
|
156
|
+
res += "#{node_name} [label=\"#{label}\"]\n"
|
157
|
+
res += "#{node_name} -- #{obj_label_map[node.l.object_id]} [label=\"l\"]\n" if node.l
|
158
|
+
res += "#{node_name} -- #{obj_label_map[node.r.object_id]} [label=\"r\"]\n" if node.r
|
159
|
+
end
|
160
|
+
res += "}\n" if header
|
161
|
+
res
|
162
|
+
end
|
163
|
+
|
164
|
+
def search_best(addr, family)
|
165
|
+
nodes = search_common(addr, family)
|
166
|
+
for i in (nodes.size - 1).downto(0)
|
167
|
+
xcur = nodes[i]
|
168
|
+
return node_to_ip_addr(xcur) if xcur.prefix.matches?(addr, family)
|
169
|
+
end
|
170
|
+
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def search_matching(addr, family)
|
175
|
+
nodes = search_common(addr, family)
|
176
|
+
nodes.select { |xcur| xcur.prefix.matches?(addr, family) }
|
177
|
+
.map { |xcur| node_to_ip_addr(xcur) }
|
178
|
+
.reverse
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
def node_to_ip_addr(node)
|
184
|
+
ret = IPAddr.new(node.prefix.address, node.prefix.family)
|
185
|
+
ret.singleton_class.send(:define_method, :data) { node.prefix.data }
|
186
|
+
ret
|
187
|
+
end
|
188
|
+
|
189
|
+
def bit_set?(addr, i)
|
190
|
+
addr[max_num_bits - i - 1] == 1
|
191
|
+
end
|
192
|
+
|
193
|
+
def search_common(addr, family)
|
194
|
+
raise 'family mismatch' unless family == self.family
|
195
|
+
|
196
|
+
xstack = []
|
197
|
+
|
198
|
+
xcur = head
|
199
|
+
while !xcur.nil?
|
200
|
+
unless xcur.empty?
|
201
|
+
xstack << xcur
|
202
|
+
end
|
203
|
+
|
204
|
+
xcur = bit_set?(addr, xcur.bit) ? xcur.r : xcur.l
|
205
|
+
end
|
206
|
+
|
207
|
+
xstack
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
Prefix = Struct.new(:family, :bitlen, :address, :data) do # addr is integer
|
212
|
+
def initialize(*args)
|
213
|
+
super
|
214
|
+
raise ArgumentError, 'no family given' unless family
|
215
|
+
raise ArgumentError, 'no bitlen given' unless bitlen
|
216
|
+
raise ArgumentError, 'no address given' unless address
|
217
|
+
end
|
218
|
+
|
219
|
+
def matches?(addr, family)
|
220
|
+
raise 'family mismatch' unless family == self.family
|
221
|
+
shift_amount = (family == Socket::AF_INET ? 32 : 128) - self.bitlen
|
222
|
+
(addr ^ self.address) >> shift_amount == 0
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def Prefix.from_str(str, data = nil)
|
227
|
+
ip_addr = IPAddr.new(str)
|
228
|
+
if str =~ /\/(\d+)$/
|
229
|
+
bitlen = $~[1].to_i
|
230
|
+
else
|
231
|
+
bitlen = ip_addr.family == Socket::AF_INET6 ? 128 : 32
|
232
|
+
end
|
233
|
+
Prefix.new(ip_addr.family, bitlen, ip_addr.to_i, data)
|
234
|
+
end
|
235
|
+
|
236
|
+
# bit starts at 0 (most significant)
|
237
|
+
Node = Struct.new(:bit, :prefix, :l, :r, :parent) do
|
238
|
+
def initialize(*args)
|
239
|
+
super
|
240
|
+
raise ArgumentError, 'no bit given' if bit.nil?
|
241
|
+
end
|
242
|
+
|
243
|
+
def empty?
|
244
|
+
prefix.nil?
|
245
|
+
end
|
246
|
+
|
247
|
+
# cover the whole tree
|
248
|
+
def walk(max_bits, empty_nodes = false)
|
249
|
+
xstack = Array.new(max_bits + 1)
|
250
|
+
sidx = 0 # stack index
|
251
|
+
xhead = self
|
252
|
+
xcur = xhead
|
253
|
+
while !xcur.nil?
|
254
|
+
yield xcur unless xcur.empty? && !empty_nodes
|
255
|
+
|
256
|
+
if xcur.l
|
257
|
+
if xcur.r
|
258
|
+
xstack[sidx] = xcur.r
|
259
|
+
sidx += 1
|
260
|
+
end
|
261
|
+
xcur = xcur.l
|
262
|
+
elsif xcur.r
|
263
|
+
xcur = xcur.r
|
264
|
+
elsif sidx.nonzero?
|
265
|
+
sidx -= 1
|
266
|
+
xcur = xstack[sidx]
|
267
|
+
else
|
268
|
+
xcur = nil
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
data/lib/sqreen/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqreen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.15.
|
4
|
+
version: 1.15.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sqreen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-10-
|
11
|
+
date: 2018-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sq_mini_racer
|
@@ -117,6 +117,7 @@ files:
|
|
117
117
|
- lib/sqreen/session.rb
|
118
118
|
- lib/sqreen/shared_storage.rb
|
119
119
|
- lib/sqreen/shared_storage23.rb
|
120
|
+
- lib/sqreen/trie.rb
|
120
121
|
- lib/sqreen/version.rb
|
121
122
|
homepage: https://www.sqreen.io/
|
122
123
|
licenses: []
|