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,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
|