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.
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,41 @@
1
+ module Havox
2
+ module OpenFlow10
3
+ module OVS
4
+ module Actions
5
+ extend Havox::FieldParser
6
+
7
+ def self.treat(actions_array, opts = {})
8
+ of_actions = []
9
+ actions_array.each do |obj|
10
+ of_actions <<
11
+ case obj[:action]
12
+ when 'Output' then basic_action(:output, obj[:arg_a])
13
+ when 'Enqueue' then output_or_enqueue(obj, opts[:output])
14
+ when 'SetField' then basic_action_from_set_field(obj)
15
+ else raise_unknown_action(obj)
16
+ end
17
+ end
18
+ of_actions
19
+ end
20
+
21
+ private
22
+
23
+ def self.basic_action_from_set_field(obj)
24
+ if obj[:arg_a].eql?('vlan')
25
+ obj[:arg_b].eql?('<none>') ? basic_action(:strip_vlan) : basic_action(:mod_vlan_vid, obj[:arg_b])
26
+ else
27
+ raise_unknown_action(obj)
28
+ end
29
+ end
30
+
31
+ def self.output_or_enqueue(obj, change_to_output)
32
+ if change_to_output
33
+ basic_action(:output, obj[:arg_a])
34
+ else
35
+ basic_action(:enqueue, obj[:arg_a], obj[:arg_b])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,35 @@
1
+ # This dictionary structure translates Merlin match fields to OpenVSwitch
2
+ # readable fields. More details can be found at:
3
+ # http://www.pica8.com/document/v2.3/pdf/ovs-commands-reference.pdf
4
+
5
+ module Havox
6
+ module OpenFlow10
7
+ module OVS
8
+ module Matches
9
+ extend Havox::FieldParser
10
+
11
+ FIELDS = {
12
+ 'ethSrc' => :dl_src,
13
+ 'ethDst' => :dl_dst,
14
+ 'ethTyp' => :dl_type,
15
+ 'ipSrc' => :nw_src,
16
+ 'ipDst' => :nw_dst,
17
+ 'ipProto' => :nw_proto,
18
+ 'nwProto' => :nw_proto,
19
+ 'port' => :in_port,
20
+ 'switch' => :dp_id,
21
+ 'tcpSrcPort' => :tp_src,
22
+ 'tcpDstPort' => :tp_dst,
23
+ 'vlanId' => :dl_vlan,
24
+ 'vlanPcp' => :dl_vlan_pcp
25
+ }
26
+
27
+ def self.treat(hash)
28
+ hash[:nw_src] = parsed_ipv4(hash[:nw_src]) unless hash[:nw_src].nil?
29
+ hash[:nw_dst] = parsed_ipv4(hash[:nw_dst]) unless hash[:nw_dst].nil?
30
+ hash
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ module Havox
2
+ module OpenFlow10
3
+ module RouteFlow
4
+ module Actions
5
+ extend Havox::FieldParser
6
+
7
+ def self.treat(actions_array, opts = {})
8
+ of_actions = []
9
+ actions_array.each do |obj|
10
+ of_actions <<
11
+ case obj[:action]
12
+ when 'Output' then basic_action(:output, obj[:arg_a].to_i)
13
+ when 'Enqueue' then output_or_enqueue(obj, opts[:output])
14
+ when 'SetField' then basic_action_from_set_field(obj)
15
+ else raise_unknown_action(obj)
16
+ end
17
+ end
18
+ of_actions
19
+ end
20
+
21
+ private
22
+
23
+ def self.basic_action_from_set_field(obj)
24
+ if obj[:arg_a].eql?('vlan')
25
+ if obj[:arg_b].eql?('<none>')
26
+ basic_action(:strip_vlan) # Inferred (vandervecken).
27
+ else
28
+ basic_action(:set_vlan_id, obj[:arg_b].to_i) # Inferred (vandervecken).
29
+ end
30
+ else
31
+ raise_unknown_action(obj)
32
+ end
33
+ end
34
+
35
+ def self.output_or_enqueue(obj, change_to_output)
36
+ if change_to_output
37
+ basic_action(:output, obj[:arg_a].to_i)
38
+ else
39
+ basic_action(:enqueue, obj[:arg_a].to_i, obj[:arg_b].to_i) # Inferred.
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,44 @@
1
+ # This dictionary structure translates Merlin match fields to RouteFlow
2
+ # readable fields. Since the current version of RouteFlow does not support
3
+ # all returned fields yet, some of them were inferred based on the others
4
+ # (those are marked as 'inferred'). As RouteFlow gets updated, the inferred
5
+ # names can be updated as well. RouteFlow's main repo can be accessed at:
6
+ # https://github.com/routeflow/RouteFlow.
7
+
8
+ module Havox
9
+ module OpenFlow10
10
+ module RouteFlow
11
+ module Matches
12
+ extend Havox::FieldParser
13
+
14
+ FIELDS = {
15
+ 'ethSrc' => :ethernet_src, # Inferred.
16
+ 'ethDst' => :ethernet,
17
+ 'ethTyp' => :ethertype,
18
+ 'ipSrc' => :ipv4_src, # Inferred.
19
+ 'ipDst' => :ipv4,
20
+ 'ipProto' => :nw_proto,
21
+ 'nwProto' => :nw_proto,
22
+ 'port' => :in_port, # Inferred.
23
+ 'switch' => :dp_id,
24
+ 'tcpSrcPort' => :tp_src,
25
+ 'tcpDstPort' => :tp_dst,
26
+ 'vlanId' => :vlan_id, # Inferred (vandervecken).
27
+ 'vlanPcp' => :vlan_pcp # Inferred.
28
+ }
29
+
30
+ def self.treat(hash)
31
+ hash[:ethertype] = hash[:ethertype].to_i unless hash[:ethertype].nil?
32
+ hash[:ipv4_src] = parsed_ipv4(hash[:ipv4_src]) unless hash[:ipv4_src].nil?
33
+ hash[:ipv4] = parsed_ipv4(hash[:ipv4]) unless hash[:ipv4].nil?
34
+ hash[:nw_proto] = hash[:nw_proto].to_i unless hash[:nw_proto].nil?
35
+ hash[:in_port] = hash[:in_port].to_i unless hash[:in_port].nil?
36
+ hash[:tp_src] = hash[:tp_src].to_i unless hash[:tp_src].nil?
37
+ hash[:tp_dst] = hash[:tp_dst].to_i unless hash[:tp_dst].nil?
38
+ hash[:vlan_id] = hash[:vlan_id].to_i unless hash[:vlan_id].nil?
39
+ hash
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ module Havox
2
+ module OpenFlow10
3
+ module Trema
4
+ module Actions
5
+ extend Havox::FieldParser
6
+
7
+ def self.treat(actions_array, opts = {})
8
+ of_actions = []
9
+ actions_array.each do |obj|
10
+ of_actions <<
11
+ case obj[:action]
12
+ when 'Output' then basic_action(:output, obj[:arg_a].to_i)
13
+ when 'Enqueue' then output_or_enqueue(obj, opts[:output])
14
+ when 'SetField' then basic_action_from_set_field(obj)
15
+ else raise_unknown_action(obj)
16
+ end
17
+ end
18
+ of_actions
19
+ end
20
+
21
+ private
22
+
23
+ def self.basic_action_from_set_field(obj)
24
+ if obj[:arg_a].eql?('vlan')
25
+ if obj[:arg_b].eql?('<none>')
26
+ basic_action(:strip_vlan)
27
+ else
28
+ basic_action(:set_vlan_vid, obj[:arg_b].to_i)
29
+ end
30
+ else
31
+ raise_unknown_action(obj)
32
+ end
33
+ end
34
+
35
+ def self.output_or_enqueue(obj, change_to_output)
36
+ if change_to_output
37
+ basic_action(:output, obj[:arg_a].to_i)
38
+ else
39
+ basic_action(:enqueue, obj[:arg_a].to_i, obj[:arg_b].to_i)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ # This dictionary module translates Merlin match fields to Trema readable
2
+ # fields, based on the official OpenFlow 1.0 nomenclatures, as described in the
3
+ # OpenFlow specifications at:
4
+ # http://archive.openflow.org/documents/openflow-spec-v1.0.0.pdf
5
+
6
+ module Havox
7
+ module OpenFlow10
8
+ module Trema
9
+ module Matches
10
+ extend Havox::FieldParser
11
+
12
+ FIELDS = {
13
+ 'ethSrc' => :source_mac_address,
14
+ 'ethDst' => :destination_mac_address,
15
+ 'ethTyp' => :ether_type, # https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml
16
+ 'ipSrc' => :source_ip_address,
17
+ 'ipDst' => :destination_ip_address,
18
+ 'ipProto' => :ip_protocol, # http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
19
+ 'nwProto' => :ip_protocol,
20
+ 'port' => :in_port,
21
+ 'switch' => :dp_id,
22
+ 'tcpSrcPort' => :transport_source_port,
23
+ 'tcpDstPort' => :transport_destination_port,
24
+ 'vlanId' => :vlan_vid,
25
+ 'vlanPcp' => :vlan_priority
26
+ }
27
+
28
+ def self.treat(hash)
29
+ hash[:ether_type] = hash[:ether_type].to_i unless hash[:ether_type].nil?
30
+ hash[:source_ip_address] = parsed_ipv4(hash[:source_ip_address]) unless hash[:source_ip_address].nil?
31
+ hash[:destination_ip_address] = parsed_ipv4(hash[:destination_ip_address]) unless hash[:destination_ip_address].nil?
32
+ hash[:ip_protocol] = hash[:ip_protocol].to_i unless hash[:ip_protocol].nil?
33
+ hash[:in_port] = hash[:in_port].to_i unless hash[:in_port].nil?
34
+ hash[:transport_source_port] = hash[:transport_source_port].to_i unless hash[:transport_source_port].nil?
35
+ hash[:transport_destination_port] = hash[:transport_destination_port].to_i unless hash[:transport_destination_port].nil?
36
+ hash[:vlan_vid] = hash[:vlan_vid].to_i unless hash[:vlan_vid].nil?
37
+ hash[:vlan_priority] = hash[:vlan_priority].to_i unless hash[:vlan_priority].nil?
38
+ hash
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ module Havox
2
+ module RouteFlow
3
+ class << self
4
+ ENTRY_REGEX = /[A-Z>\*\s]{3}.*(via|is).*,.*$/
5
+
6
+ private
7
+
8
+ def config
9
+ Havox.configuration
10
+ end
11
+
12
+ def cmd
13
+ Havox::Command
14
+ end
15
+
16
+ def ssh_connection
17
+ Net::SSH.start(config.rf_host, config.rf_user, password: config.rf_password) do |ssh|
18
+ yield(ssh)
19
+ end
20
+ end
21
+
22
+ def parse(output)
23
+ result = output.each_line.map { |l| l.match(ENTRY_REGEX) }.compact
24
+ result.map(&:to_s)
25
+ end
26
+ end
27
+
28
+ def self.run(command)
29
+ output = nil
30
+ ssh_connection { |ssh| output = ssh.exec!(command) }
31
+ output
32
+ end
33
+
34
+ def self.fetch(vm_name, protocol = nil)
35
+ result = run(cmd.show_ip_route(vm_name, protocol))
36
+ result = parse(result)
37
+ result = Havox::RouteFiller.new(result).filled_routes
38
+ result
39
+ end
40
+
41
+ def self.ribs(vm_names, opts = {})
42
+ routes = []
43
+ vm_names.each do |vm_name|
44
+ raw_routes = fetch(vm_name)
45
+ routes += raw_routes.map { |rr| Havox::Route.new(rr, vm_name, opts) }
46
+ end
47
+ routes
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Havox
2
+ VERSION = '0.11.1'
3
+ end
@@ -0,0 +1,9 @@
1
+ srcs := { h1; h2; h3; h4; h5; h6 };
2
+ exits_a := { h20 };
3
+ exits_b := { h60 };
4
+
5
+ foreach (s, d): cross(srcs, exits_a)
6
+ ipProto = 17 and ethTyp = 2048 and tcpDstPort = 80 -> .* s2 at min(100 Mbps);
7
+
8
+ foreach (s, d): cross(srcs, exits_b)
9
+ ipProto = 17 and ethTyp = 2048 and tcpDstPort = 25 -> .* s6 at min(100 Mbps);
@@ -0,0 +1,5 @@
1
+ fire1 := { h1; h2 };
2
+ fire2 := { h1; h2 };
3
+
4
+ foreach (s, d) : cross({h1}, {h2})
5
+ * -> ( .* fire1 .* fire2 .* ) | ( .* fire2 .* fire1 .* ) at min(1 Bps);
@@ -0,0 +1,2 @@
1
+ foreach (s,d) : cross({h1}, {h2})
2
+ * -> s1 at max(50 Mbps);
@@ -0,0 +1,2 @@
1
+ foreach (s,d) : cross({h1}, {h2})
2
+ * -> s1 at min(50 Mbps);
@@ -0,0 +1,8 @@
1
+ foreach (s, d): cross({ h1; h3; h4 }, { h2 })
2
+ tcpDstPort = 80 -> .* s6 at min(100 Mbps);
3
+
4
+ foreach (s, d): cross({ h1; h3; h4 }, { h2 })
5
+ ipProto = udp -> .* s6 at min(100 Mbps);
6
+
7
+ foreach (s, d): cross({ h1; h3; h4 }, { h2 })
8
+ ipDst = 172.50.0.0 -> .* s6 at min(100 Mbps);
@@ -0,0 +1,2 @@
1
+ foreach (s, d): cross({ h1; h2; h3; h4 }, { h1; h2; h3; h4 })
2
+ tcpDstPort = 80 -> .* s6 .* at min(1 Mbps);
@@ -0,0 +1,8 @@
1
+ srcs := { h1; h2 };
2
+ dsts := { h4 };
3
+
4
+ foreach (s, d): cross(srcs, dsts)
5
+ ethTyp = 2048 and ipProto = 6 and tcpDstPort = 80 -> .* s2 s3 .* at min(100 Mbps);
6
+
7
+ foreach (s, d): cross(dsts, srcs)
8
+ ethTyp = 2048 and ipProto = 6 and tcpSrcPort = 80 -> .* s3 s2 .* at min(100 Mbps);
@@ -0,0 +1,15 @@
1
+ srcs := { h1; h2 };
2
+ dsts := { h4 };
3
+ all := { h1; h2; h3; h4 };
4
+
5
+ foreach (s, d): cross(srcs, dsts)
6
+ ethTyp = 2048 and ipProto = 6 and tcpDstPort = 80 -> .* s2 s3 .* at min(100 Mbps);
7
+
8
+ foreach (s, d): cross(dsts, srcs)
9
+ ethTyp = 2048 and ipProto = 6 and tcpSrcPort = 80 -> .* s3 s2 .* at min(100 Mbps);
10
+
11
+ foreach (s, d): cross(all, all)
12
+ ethTyp = 2048 and ipProto = 1 -> .* at min(100 Mbps);
13
+
14
+ foreach (s, d): cross(all, all)
15
+ ethTyp = 2054 -> .* at min(100 Mbps);
@@ -0,0 +1,7 @@
1
+ foreach (s, d): cross({ h1; h3; h4 }, { h1; h2; h3; h4 })
2
+ ethTyp = 2048 and ipProto = 6 and tcpDstPort = 80 -> .* s5 at min(100 Mbps);
3
+
4
+ (*
5
+ foreach (s, d): cross({ h1; h2; h3; h4 }, { h1; h2; h3; h4 })
6
+ ethTyp = 2048 and ipProto = 6 -> .* s8 at min(100 Mbps);
7
+ *)
@@ -0,0 +1,2 @@
1
+ foreach (s, d): cross({ h1; h2; h3; h4 }, { h1; h2; h3; h4 })
2
+ ethTyp = 2048 and ipProto = 6 and tcpDstPort = 80 -> .* at min(100 Mbps);
@@ -0,0 +1,63 @@
1
+ digraph g1 {
2
+ h1 [type = host, mac = "00:00:00:00:00:01", ip = "10.0.0.1"];
3
+ h2 [type = host, mac = "00:00:00:00:00:02", ip = "10.0.0.2"];
4
+ h3 [type = host, mac = "00:00:00:00:00:03", ip = "10.0.0.3"];
5
+ h4 [type = host, mac = "00:00:00:00:00:04", ip = "10.0.0.4"];
6
+ h5 [type = host, mac = "00:00:00:00:00:05", ip = "10.0.0.5"];
7
+ h6 [type = host, mac = "00:00:00:00:00:06", ip = "10.0.0.6"];
8
+ h20 [type = host, mac = "00:00:00:00:00:20", ip = "10.0.0.20"];
9
+ h60 [type = host, mac = "00:00:00:00:00:60", ip = "10.0.0.60"];
10
+
11
+ s1 [type = switch, ip = "11.0.0.1", id = 1];
12
+ s2 [type = switch, ip = "11.0.0.2", id = 2];
13
+ s3 [type = switch, ip = "11.0.0.3", id = 3];
14
+ s4 [type = switch, ip = "11.0.0.4", id = 4];
15
+ s5 [type = switch, ip = "11.0.0.5", id = 5];
16
+ s6 [type = switch, ip = "11.0.0.6", id = 6];
17
+
18
+ s1 -> h1 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
19
+ h1 -> s1 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
20
+
21
+ s2 -> h2 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
22
+ h2 -> s2 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
23
+
24
+ s3 -> h3 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
25
+ h3 -> s3 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
26
+
27
+ s4 -> h4 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
28
+ h4 -> s4 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
29
+
30
+ s5 -> h5 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
31
+ h5 -> s5 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
32
+
33
+ s6 -> h6 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
34
+ h6 -> s6 [src_port = 1, dst_port = 1, cost = 1, capacity = "1Gbps"];
35
+
36
+ s2 -> h20 [src_port = 5, dst_port = 1, cost = 1, capacity = "1Gbps"];
37
+ h20 -> s2 [src_port = 5, dst_port = 1, cost = 1, capacity = "1Gbps"];
38
+
39
+ s6 -> h60 [src_port = 4, dst_port = 1, cost = 1, capacity = "1Gbps"];
40
+ h60 -> s6 [src_port = 4, dst_port = 1, cost = 1, capacity = "1Gbps"];
41
+
42
+ s1 -> s2 [src_port = 2, dst_port = 2, cost = 1, capacity = "1Gbps"];
43
+ s1 -> s3 [src_port = 3, dst_port = 2, cost = 1, capacity = "1Gbps"];
44
+
45
+ s2 -> s1 [src_port = 2, dst_port = 2, cost = 1, capacity = "1Gbps"];
46
+ s2 -> s3 [src_port = 3, dst_port = 3, cost = 1, capacity = "1Gbps"];
47
+ s2 -> s4 [src_port = 4, dst_port = 2, cost = 1, capacity = "1Gbps"];
48
+
49
+ s3 -> s1 [src_port = 2, dst_port = 3, cost = 1, capacity = "1Gbps"];
50
+ s3 -> s2 [src_port = 3, dst_port = 3, cost = 1, capacity = "1Gbps"];
51
+ s3 -> s4 [src_port = 4, dst_port = 3, cost = 1, capacity = "1Gbps"];
52
+ s3 -> s5 [src_port = 5, dst_port = 2, cost = 1, capacity = "1Gbps"];
53
+
54
+ s4 -> s2 [src_port = 2, dst_port = 4, cost = 1, capacity = "1Gbps"];
55
+ s4 -> s3 [src_port = 3, dst_port = 4, cost = 1, capacity = "1Gbps"];
56
+ s4 -> s6 [src_port = 4, dst_port = 2, cost = 1, capacity = "1Gbps"];
57
+
58
+ s5 -> s3 [src_port = 2, dst_port = 5, cost = 1, capacity = "1Gbps"];
59
+ s5 -> s6 [src_port = 3, dst_port = 3, cost = 1, capacity = "1Gbps"];
60
+
61
+ s6 -> s4 [src_port = 2, dst_port = 4, cost = 1, capacity = "1Gbps"];
62
+ s6 -> s5 [src_port = 3, dst_port = 3, cost = 1, capacity = "1Gbps"];
63
+ }