havox 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +82 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/config.rb +4 -0
  12. data/examples/example_3x3.dot +81 -0
  13. data/examples/example_3x3.hvx +5 -0
  14. data/exe/havox +107 -0
  15. data/havox.gemspec +33 -0
  16. data/lib/havox.rb +51 -0
  17. data/lib/havox/app/api.rb +57 -0
  18. data/lib/havox/app/helpers/methods.rb +43 -0
  19. data/lib/havox/classes/edge.rb +11 -0
  20. data/lib/havox/classes/modified_policy.rb +44 -0
  21. data/lib/havox/classes/node.rb +19 -0
  22. data/lib/havox/classes/policy.rb +68 -0
  23. data/lib/havox/classes/rib.rb +30 -0
  24. data/lib/havox/classes/route.rb +82 -0
  25. data/lib/havox/classes/route_filler.rb +43 -0
  26. data/lib/havox/classes/rule.rb +74 -0
  27. data/lib/havox/classes/rule_expander.rb +47 -0
  28. data/lib/havox/classes/rule_sanitizer.rb +42 -0
  29. data/lib/havox/classes/topology.rb +75 -0
  30. data/lib/havox/classes/translator.rb +37 -0
  31. data/lib/havox/configuration.rb +20 -0
  32. data/lib/havox/dsl/directive.rb +70 -0
  33. data/lib/havox/dsl/directive_proxy.rb +36 -0
  34. data/lib/havox/dsl/examples/routeflow.hvx +12 -0
  35. data/lib/havox/dsl/network.rb +64 -0
  36. data/lib/havox/exceptions.rb +21 -0
  37. data/lib/havox/modules/command.rb +31 -0
  38. data/lib/havox/modules/field_parser.rb +21 -0
  39. data/lib/havox/modules/merlin.rb +85 -0
  40. data/lib/havox/modules/openflow10/ovs/actions.rb +41 -0
  41. data/lib/havox/modules/openflow10/ovs/matches.rb +35 -0
  42. data/lib/havox/modules/openflow10/routeflow/actions.rb +45 -0
  43. data/lib/havox/modules/openflow10/routeflow/matches.rb +44 -0
  44. data/lib/havox/modules/openflow10/trema/actions.rb +45 -0
  45. data/lib/havox/modules/openflow10/trema/matches.rb +43 -0
  46. data/lib/havox/modules/routeflow.rb +50 -0
  47. data/lib/havox/version.rb +3 -0
  48. data/lib/merlin/policies/common.mln +9 -0
  49. data/lib/merlin/policies/defense.mln +5 -0
  50. data/lib/merlin/policies/max.mln +2 -0
  51. data/lib/merlin/policies/min.mln +2 -0
  52. data/lib/merlin/policies/routeflow.mln +8 -0
  53. data/lib/merlin/policies/test.mln +2 -0
  54. data/lib/merlin/policies/tetrahedron.mln +8 -0
  55. data/lib/merlin/policies/tetrahedron.sample.mln +15 -0
  56. data/lib/merlin/policies/web_balanced.mln +7 -0
  57. data/lib/merlin/policies/web_unbalanced.mln +2 -0
  58. data/lib/merlin/topologies/common.dot +63 -0
  59. data/lib/merlin/topologies/defense.dot +20 -0
  60. data/lib/merlin/topologies/max.dot +17 -0
  61. data/lib/merlin/topologies/min.dot +11 -0
  62. data/lib/merlin/topologies/routeflow.dot +37 -0
  63. data/lib/merlin/topologies/tetrahedron.dot +39 -0
  64. data/lib/trema/controllers/main_controller.rb +113 -0
  65. data/lib/trema/topologies/common_topology.rb +36 -0
  66. data/lib/trema/topologies/routeflow_topology.rb +21 -0
  67. data/lib/trema/topologies/tetrahedron_topology.rb +22 -0
  68. 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