havox 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +674 -0
- data/README.md +82 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.rb +4 -0
- data/examples/example_3x3.dot +81 -0
- data/examples/example_3x3.hvx +5 -0
- data/exe/havox +107 -0
- data/havox.gemspec +33 -0
- data/lib/havox.rb +51 -0
- data/lib/havox/app/api.rb +57 -0
- data/lib/havox/app/helpers/methods.rb +43 -0
- data/lib/havox/classes/edge.rb +11 -0
- data/lib/havox/classes/modified_policy.rb +44 -0
- data/lib/havox/classes/node.rb +19 -0
- data/lib/havox/classes/policy.rb +68 -0
- data/lib/havox/classes/rib.rb +30 -0
- data/lib/havox/classes/route.rb +82 -0
- data/lib/havox/classes/route_filler.rb +43 -0
- data/lib/havox/classes/rule.rb +74 -0
- data/lib/havox/classes/rule_expander.rb +47 -0
- data/lib/havox/classes/rule_sanitizer.rb +42 -0
- data/lib/havox/classes/topology.rb +75 -0
- data/lib/havox/classes/translator.rb +37 -0
- data/lib/havox/configuration.rb +20 -0
- data/lib/havox/dsl/directive.rb +70 -0
- data/lib/havox/dsl/directive_proxy.rb +36 -0
- data/lib/havox/dsl/examples/routeflow.hvx +12 -0
- data/lib/havox/dsl/network.rb +64 -0
- data/lib/havox/exceptions.rb +21 -0
- data/lib/havox/modules/command.rb +31 -0
- data/lib/havox/modules/field_parser.rb +21 -0
- data/lib/havox/modules/merlin.rb +85 -0
- data/lib/havox/modules/openflow10/ovs/actions.rb +41 -0
- data/lib/havox/modules/openflow10/ovs/matches.rb +35 -0
- data/lib/havox/modules/openflow10/routeflow/actions.rb +45 -0
- data/lib/havox/modules/openflow10/routeflow/matches.rb +44 -0
- data/lib/havox/modules/openflow10/trema/actions.rb +45 -0
- data/lib/havox/modules/openflow10/trema/matches.rb +43 -0
- data/lib/havox/modules/routeflow.rb +50 -0
- data/lib/havox/version.rb +3 -0
- data/lib/merlin/policies/common.mln +9 -0
- data/lib/merlin/policies/defense.mln +5 -0
- data/lib/merlin/policies/max.mln +2 -0
- data/lib/merlin/policies/min.mln +2 -0
- data/lib/merlin/policies/routeflow.mln +8 -0
- data/lib/merlin/policies/test.mln +2 -0
- data/lib/merlin/policies/tetrahedron.mln +8 -0
- data/lib/merlin/policies/tetrahedron.sample.mln +15 -0
- data/lib/merlin/policies/web_balanced.mln +7 -0
- data/lib/merlin/policies/web_unbalanced.mln +2 -0
- data/lib/merlin/topologies/common.dot +63 -0
- data/lib/merlin/topologies/defense.dot +20 -0
- data/lib/merlin/topologies/max.dot +17 -0
- data/lib/merlin/topologies/min.dot +11 -0
- data/lib/merlin/topologies/routeflow.dot +37 -0
- data/lib/merlin/topologies/tetrahedron.dot +39 -0
- data/lib/trema/controllers/main_controller.rb +113 -0
- data/lib/trema/topologies/common_topology.rb +36 -0
- data/lib/trema/topologies/routeflow_topology.rb +21 -0
- data/lib/trema/topologies/tetrahedron_topology.rb +22 -0
- metadata +253 -0
@@ -0,0 +1,43 @@
|
|
1
|
+
helpers do
|
2
|
+
def handle_file(params)
|
3
|
+
filepath = "./#{params[:filename]}"
|
4
|
+
File.open(filepath, 'w') { |f| f.write(params[:tempfile].read) }
|
5
|
+
filepath
|
6
|
+
end
|
7
|
+
|
8
|
+
def run_network(dot_filepath, hvx_filepath, opts = {})
|
9
|
+
mln_filename = "./#{File.basename(hvx_filepath, '.hvx')}.mln"
|
10
|
+
eval File.read(hvx_filepath)
|
11
|
+
mln_blocks = Havox::Network.transcompile(opts)
|
12
|
+
print_blocks(mln_blocks, opts)
|
13
|
+
return nil if mln_blocks.empty?
|
14
|
+
File.open(mln_filename, 'w') { |f| f.write(mln_blocks.join("\n")) }
|
15
|
+
mln_filename
|
16
|
+
end
|
17
|
+
|
18
|
+
def print_blocks(merlin_blocks, opts)
|
19
|
+
print_opts(opts)
|
20
|
+
if merlin_blocks.empty?
|
21
|
+
puts 'No Merlin code was generated'.bold.red
|
22
|
+
else
|
23
|
+
puts 'Merlin code generated:'.bold
|
24
|
+
puts merlin_blocks.join("\n").green
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def print_policy(policy, opts)
|
29
|
+
print_opts(opts)
|
30
|
+
puts "Generated #{policy.rules.size} rule(s)".bold
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_opts(opts)
|
34
|
+
puts 'Havox will:'.bold
|
35
|
+
puts "- generate Merlin code with QoS '#{opts[:qos]}'".blue unless opts[:qos].nil?
|
36
|
+
puts "- generate Merlin code with preference for exit switches: #{opts[:preferred].join(', ')}".blue unless opts[:preferred].nil?
|
37
|
+
puts "- generate Merlin code with arbitrary exit switches: #{opts[:arbitrary].join(', ')}".blue unless opts[:arbitrary].nil?
|
38
|
+
puts '- force attribute redefinition for rules with an attribute defined twice'.blue if opts[:force]
|
39
|
+
puts '- automatically append policies for ARP and ICMP protocols'.blue if opts[:basic]
|
40
|
+
puts '- expand generated rules from VLAN-based to their full predicates'.blue if opts[:expand]
|
41
|
+
puts '- switch all occurrences of Enqueue action to Output action'.blue if opts[:output]
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Havox
|
2
|
+
class ModifiedPolicy
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(topology_file_path, policy_file_path)
|
6
|
+
@topology_file_path = topology_file_path
|
7
|
+
@original_policy_file_path = policy_file_path
|
8
|
+
@hosts = parsed_hosts
|
9
|
+
@path = nil
|
10
|
+
append_basic_policies
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def arp_policy
|
16
|
+
Havox::DSL::Directive.new(nil, nil, ethernet_type: 2054).
|
17
|
+
to_block(@hosts, @hosts, 'min(100 Mbps)')
|
18
|
+
end
|
19
|
+
|
20
|
+
def icmp_policy
|
21
|
+
Havox::DSL::Directive.new(nil, nil, ethernet_type: 2048, ip_protocol: 1).
|
22
|
+
to_block(@hosts, @hosts, 'min(100 Mbps)')
|
23
|
+
end
|
24
|
+
|
25
|
+
def policies
|
26
|
+
[icmp_policy, arp_policy]
|
27
|
+
end
|
28
|
+
|
29
|
+
def append_basic_policies
|
30
|
+
policy_file_string = File.read(@original_policy_file_path)
|
31
|
+
basename = File.basename(@original_policy_file_path, '.mln')
|
32
|
+
Tempfile.open([basename, '.mln']) do |tmp|
|
33
|
+
tmp.puts "#{policy_file_string}\n"
|
34
|
+
tmp.puts policies.join("\n")
|
35
|
+
@path = tmp.path
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parsed_hosts
|
40
|
+
topology = Havox::Topology.new(@topology_file_path)
|
41
|
+
topology.host_names
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Havox
|
2
|
+
class Node
|
3
|
+
attr_reader :name, :type, :attributes
|
4
|
+
|
5
|
+
def initialize(name, attributes)
|
6
|
+
@name = name
|
7
|
+
@type = attributes[:type]&.to_sym
|
8
|
+
@attributes = attributes
|
9
|
+
end
|
10
|
+
|
11
|
+
def host?
|
12
|
+
@type.eql?(:host)
|
13
|
+
end
|
14
|
+
|
15
|
+
def switch?
|
16
|
+
@type.eql?(:switch)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Havox
|
2
|
+
class Policy
|
3
|
+
attr_reader :rules
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@opts = opts
|
7
|
+
@rules = nil
|
8
|
+
generate_rules
|
9
|
+
check_ip_netmasks if Havox::Network.reachable.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_json
|
13
|
+
@rules.map(&:to_h).to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
MERLIN_IP_SRC = 'ipSrc'
|
19
|
+
MERLIN_IP_DST = 'ipDst'
|
20
|
+
MERLIN_ETHERTYPE = 'ethTyp'
|
21
|
+
ETHERTYPE_IP = 2048
|
22
|
+
|
23
|
+
def generate_rules
|
24
|
+
@rules = Havox::Merlin.compile!(
|
25
|
+
@opts[:merlin_topology],
|
26
|
+
@opts[:merlin_policy],
|
27
|
+
@opts
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_ip_netmasks
|
32
|
+
@rules.each do |r|
|
33
|
+
r.matches[src_ip] = netmasked_or_nil(r.matches[src_ip]) if r.matches.key?(src_ip)
|
34
|
+
r.matches[dst_ip] = netmasked_or_nil(r.matches[dst_ip]) if r.matches.key?(dst_ip)
|
35
|
+
delete_ip_match(r.matches, src_ip) if has_key_but_nil?(r.matches, src_ip)
|
36
|
+
delete_ip_match(r.matches, dst_ip) if has_key_but_nil?(r.matches, dst_ip)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def src_ip
|
41
|
+
Havox::Translator.instance.fields_to(@opts[:syntax])[MERLIN_IP_SRC]
|
42
|
+
end
|
43
|
+
|
44
|
+
def dst_ip
|
45
|
+
Havox::Translator.instance.fields_to(@opts[:syntax])[MERLIN_IP_DST]
|
46
|
+
end
|
47
|
+
|
48
|
+
def ethertype
|
49
|
+
Havox::Translator.instance.fields_to(@opts[:syntax])[MERLIN_ETHERTYPE]
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete_ip_match(matches, target_ip_key)
|
53
|
+
matches.delete(target_ip_key)
|
54
|
+
matches[ethertype] = ETHERTYPE_IP unless matches.key?(ethertype)
|
55
|
+
end
|
56
|
+
|
57
|
+
def netmasked_or_nil(ip)
|
58
|
+
Havox::Network.reachable.each do |network|
|
59
|
+
return network if IPAddr.new(network).include?(ip)
|
60
|
+
end
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def has_key_but_nil?(matches, target_key)
|
65
|
+
matches.key?(target_key) && matches[target_key].nil?
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Havox
|
2
|
+
class RIB
|
3
|
+
attr_reader :routes
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@opts = opts
|
7
|
+
@routes = Havox::RouteFlow.ribs(vm_names, @opts)
|
8
|
+
end
|
9
|
+
|
10
|
+
def routes_to(ip, protocol = :bgp)
|
11
|
+
@routes.select do |r|
|
12
|
+
r.protocol.eql?(protocol) && IPAddr.new(r.network).include?(ip)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def network_list(protocol = :bgp)
|
17
|
+
@routes.select { |r| r.protocol.eql?(protocol) }.map(&:network).uniq
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def vm_names
|
23
|
+
case @opts[:vm_names]
|
24
|
+
when Array then @opts[:vm_names]
|
25
|
+
when String then @opts[:vm_names].split(',').map(&:strip)
|
26
|
+
else Havox.configuration.rf_lxc_names
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Havox
|
2
|
+
class Route
|
3
|
+
attr_reader :network, :via, :interface, :protocol, :best, :fib, :raw,
|
4
|
+
:timestamp, :router, :recursive_via
|
5
|
+
|
6
|
+
IP_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}/
|
7
|
+
TYPE_CHAR_REGEX = /^[\w\s]/
|
8
|
+
|
9
|
+
ROUTE_REGEX = %r(^
|
10
|
+
(?<flags>[A-Z>\*\s]{3})\s*
|
11
|
+
(?<network>#{IP_REGEX}\/[0-9]{1,2})?\s
|
12
|
+
(\[\d*\/\d*\])?\s?
|
13
|
+
(via\s(?<via>([0-9]{1,3}\.){3}[0-9]{1,3})|is\sdirectly\sconnected)
|
14
|
+
(,\s(?<interface>\w+)|\s\(recursive\svia\s(?<recursive_via>#{IP_REGEX})\))
|
15
|
+
(,\s(?<timestamp>\d\d:\d\d:\d\d))?
|
16
|
+
)x
|
17
|
+
|
18
|
+
TYPE_HASH = {
|
19
|
+
'O' => :ospf, 'B' => :bgp, 'C' => :connected, 'S' => :static,
|
20
|
+
'R' => :rip, 'I' => :isis, 'A' => :babel, 'K' => :kernel
|
21
|
+
}
|
22
|
+
|
23
|
+
def initialize(raw, router, opts = {})
|
24
|
+
@router = router
|
25
|
+
@opts = opts
|
26
|
+
@raw = raw
|
27
|
+
parse_raw_entry
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
connection = @via.nil? ? 'direct' : "via #{@via}"
|
32
|
+
"#{@protocol.upcase}: to #{@network} #{connection} in " \
|
33
|
+
"#{@interface || @recursive_via}#{', BEST' if @best}" \
|
34
|
+
"#{', FIB route' if @fib}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def inspect
|
38
|
+
"Route #{object_id.to_s(16)}, router #{@router}, #{to_s}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_h
|
42
|
+
{ router: @router, protocol: @protocol, network: @network, via: @via,
|
43
|
+
recursive_via: @recursive_via, interface: @interface,
|
44
|
+
timestamp: @timestamp, best: @best, fib: @fib }
|
45
|
+
end
|
46
|
+
|
47
|
+
def direct?
|
48
|
+
@via.nil?
|
49
|
+
end
|
50
|
+
|
51
|
+
def bgp?
|
52
|
+
@protocol.eql?(:bgp)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ospf?
|
56
|
+
@protocol.eql?(:ospf)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def parse_raw_entry
|
62
|
+
parsed_entry = @raw.match(ROUTE_REGEX)
|
63
|
+
evaluate_protocol(parsed_entry[:flags])
|
64
|
+
evaluate_route_attributes(parsed_entry)
|
65
|
+
end
|
66
|
+
|
67
|
+
def evaluate_protocol(flags_str)
|
68
|
+
type_char = flags_str.scan(TYPE_CHAR_REGEX).first
|
69
|
+
@protocol = TYPE_HASH[type_char] || :unknown
|
70
|
+
end
|
71
|
+
|
72
|
+
def evaluate_route_attributes(parsed_entry)
|
73
|
+
@network = parsed_entry[:network]&.to_s
|
74
|
+
@via = parsed_entry[:via]&.to_s
|
75
|
+
@interface = parsed_entry[:interface]&.to_s
|
76
|
+
@timestamp = parsed_entry[:timestamp]&.to_s
|
77
|
+
@best = parsed_entry[:flags].include?('>')
|
78
|
+
@fib = parsed_entry[:flags].include?('*')
|
79
|
+
@recursive_via = parsed_entry[:recursive_via]&.to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Havox
|
2
|
+
class RouteFiller
|
3
|
+
attr_reader :filled_routes
|
4
|
+
|
5
|
+
ROUTE_REGEX = %r(^
|
6
|
+
(?<protocol_char>[A-Z\s]{1})[>\s]{1}[\*\s]{1}\s
|
7
|
+
(?<network>([0-9]{1,3}\.){3}[0-9]{1,3}\/[0-9]{1,2})?.+$
|
8
|
+
)x
|
9
|
+
|
10
|
+
def initialize(raw_routes)
|
11
|
+
@raw_routes = raw_routes
|
12
|
+
@filled_routes = []
|
13
|
+
fill_routes
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def fill_routes
|
19
|
+
pivot = nil # Sets null as initial value for the pivot, since the first route is always OK.
|
20
|
+
@raw_routes.each do |raw_route|
|
21
|
+
match = raw_route.match(ROUTE_REGEX) # Tries to match the current route to the regex.
|
22
|
+
if match[:network].nil? # If the network match is null, the current route must be treated:
|
23
|
+
@filled_routes << filled_route(raw_route, pivot) # -- Treats the current route and adds it to the returning array.
|
24
|
+
else # Otherwise, the current route is OK:
|
25
|
+
pivot = raw_route # -- Sets the current route as the new pivot.
|
26
|
+
@filled_routes << raw_route # -- Adds the current route to the returning array.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def filled_route(raw_route, pivot)
|
32
|
+
match = pivot.match(ROUTE_REGEX) # Extracts protocol char and network from the pivot.
|
33
|
+
network_str = "#{match[:protocol_char]} * #{match[:network]} [110/10] " # Builds the substitution string containing the char and the network.
|
34
|
+
treated_route = raw_route.gsub(/\s{2}\*\s+(?=via|is)/, network_str) # Fits the substitution string to the correct place.
|
35
|
+
raise_error(raw_route) if treated_route.eql?(raw_route) # Raises an error if the treated route is equal to its initial state.
|
36
|
+
treated_route
|
37
|
+
end
|
38
|
+
|
39
|
+
def raise_error(raw_route)
|
40
|
+
raise Havox::OperationError, "Failed to fill route '#{raw_route}'"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Havox
|
2
|
+
class Rule
|
3
|
+
attr_reader :matches, :actions, :dp_id, :raw
|
4
|
+
|
5
|
+
def initialize(raw, opts = {})
|
6
|
+
@opts = opts
|
7
|
+
@syntax = @opts[:syntax] || :trema
|
8
|
+
@matches = parsed_matches(raw)
|
9
|
+
@actions = parsed_actions(raw)
|
10
|
+
@dp_id = @matches[:dp_id].to_i
|
11
|
+
@matches.delete(:dp_id)
|
12
|
+
@raw = raw.strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s # Stringifies the rule.
|
16
|
+
sep = ' AND '
|
17
|
+
matches_str = @matches.map { |k, v| "#{k.to_s} = #{v.to_s}" }.join(sep)
|
18
|
+
actions_str = @actions.map do |o|
|
19
|
+
%Q(#{o[:action]}(#{o[:arg_a]}#{", #{o[:arg_b]}" unless o[:arg_b].nil?}))
|
20
|
+
end
|
21
|
+
"#{matches_str} --> #{actions_str.join(sep)}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"Rule #{object_id.to_s(16)}, dp_id = #{@dp_id}: #{to_s}" # Prints the rule when debugging or array listing.
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
{ dp_id: @dp_id, matches: @matches, actions: @actions }
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parsed_matches(raw_rule)
|
35
|
+
ok_matches = {}
|
36
|
+
raw_matches = raw_rule.split('->').first.split('and') # Parses each match field in the 1st part.
|
37
|
+
raw_matches = raw_matches.map { |str| str.tr('()*', '').strip } # Removes unwanted characters.
|
38
|
+
raw_matches = raw_matches.reject(&:empty?) # Rejects resulting empty match fields.
|
39
|
+
raw_matches.each do |raw_match| # Parses and adds each match based on the dictionary.
|
40
|
+
stmt = raw_match.split(/\s?=\s?/) # Splits the statement by '='.
|
41
|
+
field = translate.fields_to(@syntax)[stmt.first] # Gets the right field by the raw field name.
|
42
|
+
ok_matches[field] = stmt.last unless already_set?(ok_matches, stmt)
|
43
|
+
end
|
44
|
+
translate.matches_to(@syntax, ok_matches)
|
45
|
+
end
|
46
|
+
|
47
|
+
def parsed_actions(raw_rule)
|
48
|
+
ok_actions = []
|
49
|
+
raw_actions = raw_rule.split('->').last.strip # Parses the actions in the 2nd part.
|
50
|
+
raw_actions = raw_actions.split(/(?<=\))\s+(?=\w)/) # Splits the string into single raw actions.
|
51
|
+
raw_actions.each do |raw_action|
|
52
|
+
regex = /(?<action>\w+)\((?<arg_a>[\w<>]+)[,\s]*(?<arg_b>[\w<>]*)\)/ # Matches raw actions in the format 'Action(x, y)'.
|
53
|
+
ok_actions << hashed(raw_action.match(regex)) # Adds the structured action to the returning array.
|
54
|
+
end
|
55
|
+
translate.actions_to(@syntax, ok_actions, @opts)
|
56
|
+
end
|
57
|
+
|
58
|
+
def hashed(match_data)
|
59
|
+
Hash[match_data.names.map(&:to_sym).zip(match_data.captures)] # Converts the match data to a hash object.
|
60
|
+
end
|
61
|
+
|
62
|
+
def translate
|
63
|
+
Havox::Translator.instance
|
64
|
+
end
|
65
|
+
|
66
|
+
def already_set?(matches_hash, stmt)
|
67
|
+
field = translate.fields_to(@syntax)[stmt.first]
|
68
|
+
unless matches_hash[field].nil? || matches_hash[field].eql?(stmt.last)
|
69
|
+
return !@opts[:force]
|
70
|
+
end
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Havox
|
2
|
+
class RuleExpander
|
3
|
+
attr_reader :expanded_rules
|
4
|
+
|
5
|
+
SET_VLAN_REGEX = /SetField\(vlan,\s?(<none>|\d+)\)/
|
6
|
+
SET_VLAN_ID_REGEX = /SetField\(vlan,\s?(?<vlan_id>\d+)\)/
|
7
|
+
VLAN_MATCH_REGEX = /vlanId\s?=\s?(?<vlan_id>\d+)/
|
8
|
+
PREDICATE_REGEX = /\(switch\s?=\s?\d+\s?and(?<pred>.+)\)/
|
9
|
+
|
10
|
+
def initialize(raw_rules)
|
11
|
+
@raw_rules = raw_rules
|
12
|
+
@vlan_predicates = {}
|
13
|
+
@expanded_rules = []
|
14
|
+
scan_vlan_predicates
|
15
|
+
expand
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def expand
|
21
|
+
@raw_rules.each do |raw_rule|
|
22
|
+
rule_str = raw_rule.gsub(SET_VLAN_REGEX, '') # Removes any 'SetField(vlan, x)' pattern, x being a number or '<none>'.
|
23
|
+
@expanded_rules << sub_vlan_predicates(rule_str) # Substitutes 'vlanId = x' pattern for the full predicate.
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def sub_vlan_predicates(raw_rule)
|
28
|
+
match_data = raw_rule.match(VLAN_MATCH_REGEX) # Matches 'vlanId = x' pattern.
|
29
|
+
unless match_data.nil?
|
30
|
+
vlan_id = match_data[:vlan_id]
|
31
|
+
return raw_rule.gsub(VLAN_MATCH_REGEX, @vlan_predicates[vlan_id]) # Returns the substitution of the pattern for the full predicate.
|
32
|
+
end
|
33
|
+
raw_rule # Returns the raw rule unmodified if there are no match pattern.
|
34
|
+
end
|
35
|
+
|
36
|
+
def scan_vlan_predicates
|
37
|
+
@raw_rules.each do |raw_rule|
|
38
|
+
match_data = raw_rule.match(SET_VLAN_ID_REGEX) # Matches 'SetField(vlan, x)' pattern, x being a number only.
|
39
|
+
unless match_data.nil?
|
40
|
+
vlan_id = match_data[:vlan_id]
|
41
|
+
predicate = raw_rule.split('->').first.match(PREDICATE_REGEX)[:pred] # Takes the predicate of the rule without the 'switch = x' substring.
|
42
|
+
@vlan_predicates[vlan_id] = predicate # Associates the predicate to its VLAN id.
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|