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