havox 0.11.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 +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
|