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,42 @@
|
|
1
|
+
module Havox
|
2
|
+
class RuleSanitizer
|
3
|
+
attr_reader :sanitized_rules
|
4
|
+
|
5
|
+
SET_VLAN_ID_REGEX = /SetField\(vlan,\s?(?<vlan_id>\d+)\)/
|
6
|
+
VLAN_MATCH_REGEX = /vlanId\s?=\s?(?<vlan_id>\d+)/
|
7
|
+
SELF_VLAN_REGEX = /\s?and\svlanId\s?=\s?65535/
|
8
|
+
|
9
|
+
def initialize(raw_rules)
|
10
|
+
@raw_rules = raw_rules
|
11
|
+
@setted_vlan_ids = []
|
12
|
+
@sanitized_rules = []
|
13
|
+
scan_setted_vlan_ids
|
14
|
+
sanitize
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Main method which iterates over all the rules and cleans them.
|
20
|
+
def sanitize
|
21
|
+
@raw_rules.each do |raw_rule|
|
22
|
+
mod_raw_rule = raw_rule.gsub(SELF_VLAN_REGEX, '')
|
23
|
+
@sanitized_rules << mod_raw_rule if valid_vlan?(mod_raw_rule)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Determines all VLAN IDs defined by the SetField action.
|
28
|
+
def scan_setted_vlan_ids
|
29
|
+
@raw_rules.each do |raw_rule|
|
30
|
+
match_data = raw_rule.match(SET_VLAN_ID_REGEX)
|
31
|
+
@setted_vlan_ids << match_data[:vlan_id] unless match_data.nil?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# The rule is valid if it does not have matches like 'vlanId = x' or, in
|
36
|
+
# case it has, if 'x' is set by the SetField action.
|
37
|
+
def valid_vlan?(raw_rule)
|
38
|
+
match_data = raw_rule.match(VLAN_MATCH_REGEX)
|
39
|
+
match_data.nil? || @setted_vlan_ids.include?(match_data[:vlan_id])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Havox
|
2
|
+
class Topology
|
3
|
+
attr_reader :nodes, :edges
|
4
|
+
|
5
|
+
NODE_REGEX = /^\s*(?<name>\w+)\s?\[(?<attributes>.*)\];$/i
|
6
|
+
EDGE_REGEX = /^\s*(?<from>\w+)\s?->\s?(?<to>\w+)\s?\[(?<attributes>.*)\];$/i
|
7
|
+
|
8
|
+
def initialize(file_path)
|
9
|
+
@file_path = file_path
|
10
|
+
@nodes = []
|
11
|
+
@edges = []
|
12
|
+
parse_dot_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def host_names
|
16
|
+
@nodes.select(&:host?).map(&:name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def switch_ips
|
20
|
+
switches = @nodes.select(&:switch?)
|
21
|
+
switch_ip_hash = {}
|
22
|
+
switches.each { |s| switch_ip_hash[s.name] = s.attributes[:ip] }
|
23
|
+
switch_ip_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
def switch_hosts
|
27
|
+
exit_edges = @edges.select { |e| e.from.switch? && e.to.host? }
|
28
|
+
hash = {}
|
29
|
+
exit_edges.each do |e|
|
30
|
+
if hash[e.from.name].nil?
|
31
|
+
hash[e.from.name] = [e.to.name]
|
32
|
+
else
|
33
|
+
hash[e.from.name] << e.to.name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def parse_dot_file
|
42
|
+
File.read(@file_path).each_line do |line|
|
43
|
+
parse_node(line)
|
44
|
+
parse_edge(line)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_node(line)
|
49
|
+
match = line.match(NODE_REGEX)
|
50
|
+
unless match.nil?
|
51
|
+
attributes = hashed_attributes(match[:attributes].to_s)
|
52
|
+
@nodes << Havox::Node.new(match[:name].to_s, attributes)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_edge(line)
|
57
|
+
match = line.match(EDGE_REGEX)
|
58
|
+
unless match.nil?
|
59
|
+
attributes = hashed_attributes(match[:attributes].to_s)
|
60
|
+
node_from = @nodes.find { |n| n.name.eql?(match[:from].to_s) }
|
61
|
+
node_to = @nodes.find { |n| n.name.eql?(match[:to].to_s) }
|
62
|
+
@edges << Havox::Edge.new(node_from, node_to, attributes)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def hashed_attributes(raw_attrs)
|
67
|
+
hash = {}
|
68
|
+
raw_attrs.gsub('"', '').split(',').each do |str|
|
69
|
+
field, value = str.strip.split(/\s*=\s*/)
|
70
|
+
hash[field.to_sym] = value
|
71
|
+
end
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Havox
|
2
|
+
class Translator
|
3
|
+
# OPTIMIZE: Refactor the translation schema to be scalable.
|
4
|
+
# Current way of selecting the syntax via multiple match and action files
|
5
|
+
# is not DRY. Refactor the translation schema to be scalable, maybe using
|
6
|
+
# YAML or some other better structure.
|
7
|
+
|
8
|
+
include Singleton
|
9
|
+
|
10
|
+
def fields_to(syntax)
|
11
|
+
translation_module(syntax)::Matches::FIELDS
|
12
|
+
end
|
13
|
+
|
14
|
+
def matches_to(syntax, matches_array)
|
15
|
+
translation_module(syntax)::Matches.treat(matches_array)
|
16
|
+
end
|
17
|
+
|
18
|
+
def actions_to(syntax, actions_array, opts = {})
|
19
|
+
translation_module(syntax)::Actions.treat(actions_array, opts)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def translation_module(syntax)
|
25
|
+
case syntax
|
26
|
+
when :ovs then Havox::OpenFlow10::OVS
|
27
|
+
when :routeflow then Havox::OpenFlow10::RouteFlow
|
28
|
+
when :trema then Havox::OpenFlow10::Trema
|
29
|
+
else raise_unknown_translator(syntax)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def raise_unknown_translator(syntax)
|
34
|
+
raise Havox::UnknownTranslator, "Unknown translator '#{syntax}'"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Havox
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :rf_host, :rf_user, :rf_password, :rf_lxc_names, :merlin_host,
|
4
|
+
:merlin_user, :merlin_password, :merlin_path, :gurobi_path,
|
5
|
+
:protocol_daemons
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@rf_host = '192.168.56.101'
|
9
|
+
@rf_user = 'routeflow'
|
10
|
+
@rf_password = 'routeflow'
|
11
|
+
@rf_lxc_names = %w(rfvmA rfvmB rfvmC rfvmD)
|
12
|
+
@merlin_host = '192.168.56.102'
|
13
|
+
@merlin_user = 'frenetic'
|
14
|
+
@merlin_password = 'frenetic'
|
15
|
+
@merlin_path = '/home/frenetic/merlin'
|
16
|
+
@gurobi_path = '/opt/gurobi701/linux64'
|
17
|
+
@protocol_daemons = [:bgpd]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Havox
|
2
|
+
module DSL
|
3
|
+
class Directive
|
4
|
+
attr_reader :switch, :attributes
|
5
|
+
|
6
|
+
MERLIN_DIC = {
|
7
|
+
destination_ip: 'ipDst',
|
8
|
+
destination_mac: 'ethDst',
|
9
|
+
destination_port: 'tcpDstPort',
|
10
|
+
ethernet_type: 'ethTyp',
|
11
|
+
in_port: 'port',
|
12
|
+
ip_protocol: 'ipProto',
|
13
|
+
source_ip: 'ipSrc',
|
14
|
+
source_mac: 'ethSrc',
|
15
|
+
source_port: 'tcpSrcPort',
|
16
|
+
vlan_id: 'vlanId',
|
17
|
+
vlan_priority: 'vlanPcp'
|
18
|
+
}
|
19
|
+
|
20
|
+
def initialize(type, switch, attributes = {})
|
21
|
+
@type = type
|
22
|
+
@switch = switch
|
23
|
+
@attributes = attributes
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(name, *args, &block)
|
27
|
+
@attributes[name] = args.first
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_block(src_hosts, dst_hosts, qos = nil)
|
31
|
+
"#{foreach_code(src_hosts, dst_hosts)}\n #{to_statement(qos)}\n"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def to_statement(qos)
|
37
|
+
fields = @attributes.map { |k, v| "#{MERLIN_DIC[k]} = #{treated(v, k)}" }
|
38
|
+
predicate = fields.join(' and ')
|
39
|
+
qos_str = qos.nil? ? '' : " at #{qos}"
|
40
|
+
"#{predicate} -> #{regex_path}#{qos_str};"
|
41
|
+
end
|
42
|
+
|
43
|
+
def format_hosts(host_names)
|
44
|
+
"{ #{host_names.join('; ')} }"
|
45
|
+
end
|
46
|
+
|
47
|
+
def foreach_code(src_hosts, dst_hosts)
|
48
|
+
src_hosts_str = format_hosts(src_hosts)
|
49
|
+
dst_hosts_str = format_hosts(dst_hosts)
|
50
|
+
"foreach (s, d): cross(#{src_hosts_str}, #{dst_hosts_str})"
|
51
|
+
end
|
52
|
+
|
53
|
+
def treated(value, field)
|
54
|
+
case field
|
55
|
+
when :source_ip then netmask_removed(value)
|
56
|
+
when :destination_ip then netmask_removed(value)
|
57
|
+
else value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def netmask_removed(ip)
|
62
|
+
IPAddr.new(ip).to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
def regex_path
|
66
|
+
".* #{@switch.to_s}".strip
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Havox
|
2
|
+
module DSL
|
3
|
+
class DirectiveProxy
|
4
|
+
def exit(switch, &block)
|
5
|
+
eval_directive(:exit, switch, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
# def drop(&block)
|
9
|
+
# eval_directive(:drop, nil, &block)
|
10
|
+
# end
|
11
|
+
|
12
|
+
def topology(file_path)
|
13
|
+
raise_invalid_topology(file_path) unless File.exists?(file_path)
|
14
|
+
topo = Havox::Topology.new(file_path)
|
15
|
+
Havox::Network.topology = topo
|
16
|
+
end
|
17
|
+
|
18
|
+
def associate(router, switch)
|
19
|
+
Havox::Network.devices[router.to_s] = switch.to_s
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def eval_directive(type, switch, &block)
|
25
|
+
directive = Havox::DSL::Directive.new(type, switch)
|
26
|
+
directive.instance_eval(&block)
|
27
|
+
Havox::Network.directives << directive
|
28
|
+
end
|
29
|
+
|
30
|
+
def raise_invalid_topology(file_path)
|
31
|
+
raise Havox::Network::InvalidTopology,
|
32
|
+
"invalid topology file '#{file_path}'"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Havox::Network.define do
|
2
|
+
topology 'lib/merlin/topologies/routeflow.dot'
|
3
|
+
|
4
|
+
exit(:s5) { destination_port 80 }
|
5
|
+
exit(:s6) { destination_ip '172.50.0.0/16' }
|
6
|
+
exit(:s7) { source_ip '172.70.0.0/16' }
|
7
|
+
|
8
|
+
exit :s8 do
|
9
|
+
destination_port 20
|
10
|
+
destination_ip '172.50.0.0/16'
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Havox
|
2
|
+
module Network
|
3
|
+
@directives = []
|
4
|
+
@devices = {}
|
5
|
+
@rib = nil
|
6
|
+
@topology = nil
|
7
|
+
|
8
|
+
def self.directives; @directives end
|
9
|
+
def self.rib; @rib end
|
10
|
+
def self.devices; @devices end
|
11
|
+
def self.topology; @topology end
|
12
|
+
def self.topology=(topo); @topology = topo end
|
13
|
+
|
14
|
+
def self.define(&block)
|
15
|
+
reset!
|
16
|
+
directive_proxy = Havox::DSL::DirectiveProxy.new
|
17
|
+
directive_proxy.instance_eval(&block)
|
18
|
+
@rib = Havox::RIB.new
|
19
|
+
infer_associations_by_ospf
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.transcompile(opts = {})
|
23
|
+
@directives.map do |d|
|
24
|
+
src_hosts = @topology.host_names - @topology.switch_hosts[d.switch.to_s]
|
25
|
+
dst_hosts = @topology.switch_hosts[d.switch.to_s]
|
26
|
+
d.to_block(src_hosts, dst_hosts, opts[:qos])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reachable(protocol = :bgp)
|
31
|
+
@rib.nil? ? [] : @rib.network_list(protocol)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.reset!
|
35
|
+
@directives = []
|
36
|
+
@devices = {}
|
37
|
+
@rib = nil
|
38
|
+
@topology = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
class << self
|
42
|
+
private
|
43
|
+
|
44
|
+
def infer_associations_by_ospf
|
45
|
+
direct_ospf_routes = @rib.routes.select { |r| r.ospf? && r.direct? }
|
46
|
+
grouped_routes = direct_ospf_routes.group_by(&:network)
|
47
|
+
@topology.switch_ips.each do |switch_name, switch_ip|
|
48
|
+
associate_routers(grouped_routes, switch_name, switch_ip)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def associate_routers(grouped_routes, switch_name, switch_ip)
|
53
|
+
grouped_routes.each do |network_str, routes|
|
54
|
+
network = IPAddr.new(network_str)
|
55
|
+
router_name = routes.last.router
|
56
|
+
if network.include?(switch_ip) && @devices[router_name].nil?
|
57
|
+
@devices[router_name] = switch_name
|
58
|
+
break
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Havox
|
2
|
+
module Merlin
|
3
|
+
class ParsingError < StandardError; end
|
4
|
+
end
|
5
|
+
|
6
|
+
module RouteFlow
|
7
|
+
class UnknownProtocol < StandardError; end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Trema
|
11
|
+
class UnpredictedAction < StandardError; end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Network
|
15
|
+
class InvalidTopology < StandardError; end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnknownTranslator < StandardError; end
|
19
|
+
class UnknownAction < StandardError; end
|
20
|
+
class OperationError < StandardError; end
|
21
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Havox
|
2
|
+
module Command
|
3
|
+
class << self
|
4
|
+
def show_ip_route(vm_name, protocol)
|
5
|
+
vtysh_run(vm_name, "show ip route #{protocol}")
|
6
|
+
end
|
7
|
+
|
8
|
+
def vtysh_run(vm_name, command)
|
9
|
+
rf_command(vm_name, "/usr/bin/vtysh -c '#{command}'")
|
10
|
+
end
|
11
|
+
|
12
|
+
def compile(topology_file, policy_file)
|
13
|
+
merlin_command("-topo #{topology_file} #{policy_file} -verbose")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def rf_command(vm_name, command)
|
19
|
+
"sudo lxc-attach -n #{vm_name} -- #{command}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def merlin_command(args)
|
23
|
+
env_vars = "GUROBI_HOME=\"#{Havox.configuration.gurobi_path}\" " \
|
24
|
+
'PATH="${PATH}:${GUROBI_HOME}/bin" ' \
|
25
|
+
'LD_LIBRARY_PATH="${GUROBI_HOME}/lib" ' \
|
26
|
+
'GRB_LICENSE_FILE="${GUROBI_HOME}/gurobi.lic"'
|
27
|
+
"#{env_vars} #{Havox.configuration.merlin_path}/Merlin.native #{args}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Havox
|
2
|
+
module FieldParser
|
3
|
+
def parsed_ipv4(ip_integer)
|
4
|
+
ip_integer = ip_integer.to_i
|
5
|
+
value = ip_integer.positive? ? ip_integer : (2**32 - ip_integer.abs) # Handles two's complement integers.
|
6
|
+
bits = value.to_s(2).rjust(32, '0') # Transforms the string number into a 32-bit sequence.
|
7
|
+
octets = bits.scan(/\d{8}/).map { |octet_bits| octet_bits.to_i(2) } # Splits the sequence into decimal octets.
|
8
|
+
octets.join('.') # Returns the joined octets.
|
9
|
+
end
|
10
|
+
|
11
|
+
def basic_action(action, arg_a = nil, arg_b = nil)
|
12
|
+
{ action: action, arg_a: arg_a, arg_b: arg_b }
|
13
|
+
end
|
14
|
+
|
15
|
+
def raise_unknown_action(obj)
|
16
|
+
raise Havox::UnknownAction,
|
17
|
+
"Unable to translate action #{obj[:action]} with arguments A:" \
|
18
|
+
" #{obj[:arg_a]} and B: #{obj[:arg_b]}, respectively"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Havox
|
2
|
+
module Merlin
|
3
|
+
class << self
|
4
|
+
private
|
5
|
+
|
6
|
+
ERROR_REGEX = /Uncaught\sexception:\s*(.+)Raised/
|
7
|
+
BASENAME_REGEX = /^.+\/(?<name>[^\/]+)$/
|
8
|
+
SEPARATOR_REGEX = /On\sswitch\s\d+/
|
9
|
+
RULES_BLOCK_REGEX = /(?<=OpenFlow\srules\s)\(\d+\):#{SEPARATOR_REGEX}(.+)(?=Queue\sConfigurations)/
|
10
|
+
|
11
|
+
def config
|
12
|
+
Havox.configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
def cmd
|
16
|
+
Havox::Command
|
17
|
+
end
|
18
|
+
|
19
|
+
def ssh_connection
|
20
|
+
Net::SSH.start(config.merlin_host, config.merlin_user, password: config.merlin_password) do |ssh|
|
21
|
+
yield(ssh)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse(result)
|
26
|
+
result = result.tr("\n", '').tr("\t", ' ') # Removes line break and tab characters.
|
27
|
+
check_for_errors(result) # Raises an error if Merlin has errored.
|
28
|
+
result = result.scan(RULES_BLOCK_REGEX).flatten.first # Matches OpenFlow rules block.
|
29
|
+
return [] if result.nil? # Returns an empty array if no rules were created.
|
30
|
+
result = result.split(SEPARATOR_REGEX) # Splits the block into separated rules.
|
31
|
+
result.map(&:to_s) # Converts NetSSH special string to string.
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_for_errors(result)
|
35
|
+
error_msg = result.scan(ERROR_REGEX).flatten.first
|
36
|
+
raise Havox::Merlin::ParsingError, error_msg unless error_msg.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
def basename(path)
|
40
|
+
path.match(BASENAME_REGEX)[:name]
|
41
|
+
end
|
42
|
+
|
43
|
+
def check_options(opts)
|
44
|
+
opts[:dst] ||= "#{config.merlin_path}/examples/" # Sets the upload path in Merlin VM.
|
45
|
+
opts[:force] ||= false # Forces Havox to ignore field conflicts.
|
46
|
+
opts[:basic] ||= false # Instructs to append basic policies.
|
47
|
+
opts[:expand] ||= false # Expands raw rules from VLAN-based to full predicates.
|
48
|
+
opts[:output] ||= false # Switches Enqueue action for Output action.
|
49
|
+
opts[:syntax] ||= :trema # Sets the output syntax for generated rules.
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.run(command)
|
54
|
+
output = nil
|
55
|
+
ssh_connection { |ssh| output = ssh.exec!(command) }
|
56
|
+
output
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.upload!(file, dst = nil)
|
60
|
+
dst ||= "#{config.merlin_path}/examples/"
|
61
|
+
ssh_connection { |ssh| ssh.scp.upload!(file, dst) }
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.compile(topology_file, policy_file, opts = {})
|
66
|
+
rules = []
|
67
|
+
result = run(cmd.compile(topology_file, policy_file)) # Runs Merlin in the remote VM and retrieves its output.
|
68
|
+
result = parse(result) # Parses the output into raw rules.
|
69
|
+
result = Havox::RuleSanitizer.new(result).sanitized_rules # Removes unwanted rule snippets.
|
70
|
+
result = Havox::RuleExpander.new(result).expanded_rules if opts[:expand] # Expands each raw rule in the parsed result.
|
71
|
+
result.each { |raw_rule| rules << Havox::Rule.new(raw_rule, opts) } # Creates Rule instances for each raw rule.
|
72
|
+
rules
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.compile!(topology_file, policy_file, opts = {})
|
76
|
+
check_options(opts)
|
77
|
+
policy_file = Havox::ModifiedPolicy.new(topology_file, policy_file).path if opts[:basic]
|
78
|
+
if upload!(topology_file, opts[:dst]) && upload!(policy_file, opts[:dst])
|
79
|
+
topology_file = "#{opts[:dst]}#{basename(topology_file)}"
|
80
|
+
policy_file = "#{opts[:dst]}#{basename(policy_file)}"
|
81
|
+
compile(topology_file, policy_file, opts)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|