sqreen 1.15.0 → 1.15.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|