ovsimager 0.0.2
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 +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +48 -0
- data/Rakefile +7 -0
- data/bin/ovsimager +11 -0
- data/lib/ovsimager.rb +2 -0
- data/lib/ovsimager/dotwriter.rb +86 -0
- data/lib/ovsimager/ipnetns.rb +97 -0
- data/lib/ovsimager/linuxbridge.rb +33 -0
- data/lib/ovsimager/ovsimager.rb +145 -0
- data/lib/ovsimager/ovsvs.rb +72 -0
- data/lib/ovsimager/tcpdump.rb +64 -0
- data/lib/ovsimager/utils.rb +19 -0
- data/lib/ovsimager/version.rb +3 -0
- data/ovsimager.gemspec +24 -0
- data/sample-interfaces.png +0 -0
- data/sample-ping-trace.png +0 -0
- data/spec/ovsimager_spec.rb +11 -0
- data/spec/spec_helper.rb +2 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5a4813849f38c8999ec90b9f68e1607e681681e9
|
4
|
+
data.tar.gz: c08b1ec71b6b0a1b5e04c459bdc4f7238823edab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a270e213b0422701c25006ee62397d623ecfd5c19a483459936c2da2f38e7c62abce02afc83634c25f618fbb76e196c3474dd3b83e24868452381e54aa0de42
|
7
|
+
data.tar.gz: adcdff1ffb9073c446e171c64592e41247eb51e21978bd7a4591bb38b3ac77fe78092891f6fbbb09ce44461a6e55f7579a486f8ea996f0d0509686555bd062a4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Tomoki Sekiyama
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# OVSImager
|
2
|
+
|
3
|
+
OVSImager draws a graph that describes relationship among Open vSwitch
|
4
|
+
bridges, Linux bridges, and namespaces for routing. It can also mark
|
5
|
+
the ports where ping packets went through using tcpdump, which is a
|
6
|
+
useful feature for trouble-shooting in SDN environments.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
$ gem install ovsimager
|
11
|
+
# sudo yum install graphviz
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Draw a graph of Open vSwitch'es
|
16
|
+
|
17
|
+
$ ovsimager
|
18
|
+
( => interfaces.png will be generated. )
|
19
|
+
|
20
|
+

|
21
|
+
|
22
|
+
### Trace ping packets
|
23
|
+
|
24
|
+
Execute ping packet with size = 400 byte, and trace them:
|
25
|
+
|
26
|
+
$ ping -s 400 192.0.2.1
|
27
|
+
$ sudo ovsimager -d
|
28
|
+
( => interfaces.png will be generated. )
|
29
|
+
|
30
|
+

|
31
|
+
|
32
|
+
Or, ovsimager also can send ping:
|
33
|
+
|
34
|
+
$ sudo ovsimager -d -f 10.0.0.1 -t 192.0.2.1
|
35
|
+
|
36
|
+
Colors:
|
37
|
+
|
38
|
+
- Yellow => both 'ping' and 'pong' are went through
|
39
|
+
- Pink => only 'ping' is went through
|
40
|
+
- Red => only 'pong' is went through
|
41
|
+
|
42
|
+
## Contributing
|
43
|
+
|
44
|
+
1. Fork it ( https://github.com/NeoCat/ovsimager/fork )
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
48
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/ovsimager
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
|
4
|
+
$LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
|
5
|
+
|
6
|
+
require 'ovsimager'
|
7
|
+
|
8
|
+
ovsimager = OVSImager::OVSImager.new
|
9
|
+
ovsimager.parse_options
|
10
|
+
ovsimager.execute_dump
|
11
|
+
ovsimager.show_all
|
data/lib/ovsimager.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module OVSImager
|
2
|
+
class DotWriter
|
3
|
+
def initialize(fname)
|
4
|
+
@fname = fname
|
5
|
+
@dot = File.open(fname, 'w')
|
6
|
+
@dot.puts 'graph interfaces {'
|
7
|
+
@dot.puts ' compound=true'
|
8
|
+
@dot.puts ' node [shape=rect]'
|
9
|
+
@dot_peers = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def escape(name)
|
13
|
+
name.to_s.gsub('-', '_')
|
14
|
+
end
|
15
|
+
|
16
|
+
def mark2color(mark)
|
17
|
+
{'<' => 'red', '>' => 'pink', '*' => 'yellow'}[mark]
|
18
|
+
end
|
19
|
+
|
20
|
+
def finish(pngname)
|
21
|
+
@dot.puts @dot_peers.join "\n"
|
22
|
+
@dot.puts '}'
|
23
|
+
@dot.close
|
24
|
+
@dot = nil
|
25
|
+
system("dot -Tpng \"#{@fname}\" -o \"#{pngname}\"")
|
26
|
+
end
|
27
|
+
|
28
|
+
# For OVSVS & LinuxBridge
|
29
|
+
def br_begin(name, br_type)
|
30
|
+
@dot.puts " subgraph cluster_br__#{escape(name)} {"
|
31
|
+
@dot.puts " label = \"#{br_type}Bridge #{name}\""
|
32
|
+
end
|
33
|
+
|
34
|
+
def br_iface(name, mark, dump, inet, tag, peer, remote=nil)
|
35
|
+
fill = mark ? "fillcolor=#{mark2color(mark)},style=filled," : ''
|
36
|
+
label = "#{name}<BR/><FONT POINT-SIZE=\"10\">#{inet.join(',')}"
|
37
|
+
if tag or remote
|
38
|
+
label += "<BR/>#{tag}#{remote && remote.gsub('>','>')}"
|
39
|
+
end
|
40
|
+
if dump
|
41
|
+
label += " </FONT><FONT COLOR=\"blue\">"
|
42
|
+
if dump[0] && dump[2] && dump[0] == dump[3] && dump[1] == dump[2]
|
43
|
+
label += "<BR/>[#{dump[0]} <-> #{dump[1]}]"
|
44
|
+
else
|
45
|
+
label += "<BR/>[#{dump[0]} --> #{dump[1]}]" if dump[0]
|
46
|
+
label += "<BR/>[#{dump[3]} <-- #{dump[2]}]" if dump[2]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
label += " </FONT>"
|
50
|
+
@dot.puts " #{escape(name)} [#{fill}label=<#{label}>]"
|
51
|
+
if peer && name <= peer
|
52
|
+
@dot_peers << " #{escape(name)} -- #{escape(peer)}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def br_end
|
57
|
+
@dot.puts " }"
|
58
|
+
end
|
59
|
+
|
60
|
+
# For IPNetNS
|
61
|
+
def ns_begin(name)
|
62
|
+
@dot.puts " subgraph cluster_ns__#{escape(name)} {"
|
63
|
+
@dot.puts " label = \"Namespace\\n#{name}\""
|
64
|
+
@dot.puts " style = \"filled\""
|
65
|
+
@dot.puts " fillcolor = \"#eeeeee\""
|
66
|
+
@dot.puts " ns__#{escape(name)} " +
|
67
|
+
"[label=\"\",style=invis,width=0,height=0,margin=0]"
|
68
|
+
@nsname = name
|
69
|
+
end
|
70
|
+
|
71
|
+
def ns_br_iface(name)
|
72
|
+
@dot_peers << " #{escape(name)} -- ns__#{escape(@nsname)} " +
|
73
|
+
"[style=dashed,lhead=cluster_ns__#{escape(@nsname)}]"
|
74
|
+
end
|
75
|
+
|
76
|
+
def ns_iface(name, mark, dump, inet, tag, peer)
|
77
|
+
br_iface(name, mark, dump, inet, tag, peer)
|
78
|
+
# @dot.puts " #{escape(name)} -- #{escape(@last)} [style=invis]" if @last
|
79
|
+
# @last = name
|
80
|
+
end
|
81
|
+
|
82
|
+
def ns_end
|
83
|
+
@dot.puts ' }'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require_relative 'utils'
|
2
|
+
|
3
|
+
module OVSImager
|
4
|
+
class IPNetNS
|
5
|
+
def initialize()
|
6
|
+
@ns = exec_ip('netns').split(/\n/)
|
7
|
+
@ifaces = {:root => parse_address(exec_ip('a'), :root)}
|
8
|
+
links = parse_link(exec_ip('-d link'), :root)
|
9
|
+
merge_link_type(@ifaces[:root], links)
|
10
|
+
@ns.each {|ns|
|
11
|
+
out = exec_ip("netns exec #{ns} ip a", true)
|
12
|
+
@ifaces[ns] = parse_address(out, ns)
|
13
|
+
out = exec_ip("netns exec #{ns} ip -d link", true)
|
14
|
+
links = parse_link(out, ns)
|
15
|
+
merge_link_type(@ifaces[ns], links)
|
16
|
+
}
|
17
|
+
find_veth_pair
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_hash()
|
21
|
+
@ifaces
|
22
|
+
end
|
23
|
+
|
24
|
+
def ifaces_hash()
|
25
|
+
@ifaces.inject({}) {|h, (ns, v)| v.each {|i| h[i[:name]] = i}; h}
|
26
|
+
end
|
27
|
+
|
28
|
+
def ifaces_ary()
|
29
|
+
@ifaces.inject([]) {|a, (ns, v)| v.each {|i| a[i[:id].to_i] = i}; a}
|
30
|
+
end
|
31
|
+
|
32
|
+
def exec_ip(args, root=false)
|
33
|
+
Utils.execute("ip #{args}", root)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
def parse(out, args)
|
38
|
+
out.split(/\n(?=[^ \s])/).map do |iface|
|
39
|
+
if iface.match(/^(\d+):\s+(\S+?):+/)
|
40
|
+
params = {:id => $1, :name => $2}
|
41
|
+
yield params, iface, args
|
42
|
+
else
|
43
|
+
STDERR.puts "IPNetNS: parse error: #{iface}"
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_address(out, ns)
|
50
|
+
parse(out, ns) do |params, iface, ns|
|
51
|
+
params[:ns] = ns
|
52
|
+
params[:mac] = $1 if iface.match(/link\/\w+ (\S+)/)
|
53
|
+
[:inet, :inet6].each do |key|
|
54
|
+
params[key] = iface.scan(/#{key.to_s} (\S+)/)
|
55
|
+
end
|
56
|
+
[:mtu, :state].each do |key|
|
57
|
+
params[key] = $1 if iface.match(/#{key.to_s} (\S+)/)
|
58
|
+
end
|
59
|
+
params
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_link(out, ns)
|
64
|
+
parse(out, ns) do |params, iface, ns|
|
65
|
+
params[:ns] = ns
|
66
|
+
params[:mac] = $1 if iface.match(/link\/\w+ (\S+)/)
|
67
|
+
params[:type] = (iface.split(/\n/)[2] || '').strip
|
68
|
+
params
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def merge_link_type(ifaces, links)
|
73
|
+
link_types = links.inject({}) {|h, link| h[link[:id]] = link[:type]; h}
|
74
|
+
ifaces.each {|iface| iface[:type] = link_types[iface[:id]]}
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_veth_pair()
|
78
|
+
ifaces = ifaces_ary
|
79
|
+
ifaces.each do |iface|
|
80
|
+
next unless iface
|
81
|
+
if iface[:type] == 'veth' && !iface[:peer]
|
82
|
+
if iface[:ns] == :root
|
83
|
+
out = Utils::execute("ethtool -S #{iface[:name]}")
|
84
|
+
else
|
85
|
+
out = exec_ip("netns exec #{iface[:ns]} ethtool -S #{iface[:name]}", root=true)
|
86
|
+
end
|
87
|
+
if out.match /peer_ifindex: (\d+)/
|
88
|
+
iface[:peer] = ifaces[$1.to_i][:name]
|
89
|
+
ifaces[$1.to_i][:peer] = iface[:name]
|
90
|
+
else
|
91
|
+
STDERR.puts("Failed to lookup veth peer of '#{iface[:name]}'")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'utils'
|
2
|
+
|
3
|
+
module OVSImager
|
4
|
+
class LinuxBridge
|
5
|
+
def initialize()
|
6
|
+
brctl_out = exec_brtcl
|
7
|
+
@br = parse brctl_out
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash()
|
11
|
+
return @br
|
12
|
+
end
|
13
|
+
|
14
|
+
def exec_brtcl()
|
15
|
+
Utils.execute('brctl show')
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def parse(str)
|
20
|
+
params = {}
|
21
|
+
str.split(/\n(?=\S)/)[1..-1].map do |br|
|
22
|
+
data = br.split
|
23
|
+
params[data[0]] = {
|
24
|
+
:name => data[0],
|
25
|
+
:id => data[1],
|
26
|
+
:stp => data[2],
|
27
|
+
:interfaces => [data[0]] + data[3..-1],
|
28
|
+
}
|
29
|
+
end
|
30
|
+
params
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require_relative 'ovsvs'
|
3
|
+
require_relative 'linuxbridge'
|
4
|
+
require_relative 'ipnetns'
|
5
|
+
require_relative 'tcpdump'
|
6
|
+
require_relative 'dotwriter'
|
7
|
+
|
8
|
+
module OVSImager
|
9
|
+
class OVSImager
|
10
|
+
DOT_FILENAME = 'interfaces.dot'
|
11
|
+
PNG_FILENAME = 'interfaces.png'
|
12
|
+
|
13
|
+
def initialize(dump_mode=false, ping_from=nil, ping_to=nil)
|
14
|
+
@netns = IPNetNS.new
|
15
|
+
@ifaces = @netns.ifaces_hash
|
16
|
+
@linbr = LinuxBridge.new
|
17
|
+
@ovsvs = OVSVS.new
|
18
|
+
|
19
|
+
@dotwriter = DotWriter.new(DOT_FILENAME)
|
20
|
+
|
21
|
+
@dump_mode = dump_mode
|
22
|
+
@mark = {}
|
23
|
+
@dump_result = {}
|
24
|
+
@ping_from = ping_from
|
25
|
+
@ping_to = ping_to
|
26
|
+
@done = {'lo' => true}
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_options
|
30
|
+
OptionParser.new do |opts|
|
31
|
+
opts.banner = "Usage: #$0 [options]"
|
32
|
+
opts.on("-d", "--dump",
|
33
|
+
"enable dump mode (trace ping -s 400 packets)") do
|
34
|
+
@dump_mode = true
|
35
|
+
end
|
36
|
+
opts.on("-f ADDRESS", "--from ADDRESS",
|
37
|
+
"send ping from specified address") do |v|
|
38
|
+
@ping_from = v
|
39
|
+
end
|
40
|
+
opts.on("-t ADDRESS", "--to ADDRESS",
|
41
|
+
"send ping to specified address") do |v|
|
42
|
+
@ping_to = v
|
43
|
+
end
|
44
|
+
end.parse!
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute_dump
|
48
|
+
return unless @dump_mode
|
49
|
+
tcpdump = TcpDump.new(@ping_from && @ping_to, @ping_from, @ping_to)
|
50
|
+
@dump_result = tcpdump.test(@ifaces)
|
51
|
+
@dump_result.each do |(iface, result)|
|
52
|
+
if result[0] && result[2]
|
53
|
+
@mark[iface] = '*'
|
54
|
+
elsif result[0]
|
55
|
+
@mark[iface] = '>'
|
56
|
+
elsif result[2]
|
57
|
+
@mark[iface] = '<'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def show_all
|
63
|
+
show_ovsvs
|
64
|
+
puts '-' * 80
|
65
|
+
show_linbr
|
66
|
+
puts '-' * 80
|
67
|
+
show_netns
|
68
|
+
@dotwriter.finish(PNG_FILENAME) if @dotwriter
|
69
|
+
@dotwriter = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def show_iface_common(name, inet, patch, tag='', ns='')
|
74
|
+
puts " [#{@mark[name]||' '}] #{name}#{tag}#{patch}\t" +
|
75
|
+
"#{inet.join(',')}\t#{ns}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def show_iface(iface)
|
79
|
+
patch = iface[:peer] ? " <-> #{iface[:peer]}" : ''
|
80
|
+
show_iface_common(iface[:name], iface[:inet], patch)
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_ovsvs
|
84
|
+
@ovsvs.to_hash[:bridges].each do |br|
|
85
|
+
puts "OVS Bridge #{br[:name]}:"
|
86
|
+
@dotwriter.br_begin(br[:name], 'OVS ')
|
87
|
+
|
88
|
+
br[:ports].each do |port|
|
89
|
+
name = port[:name]
|
90
|
+
iface = @ifaces[name] || {}
|
91
|
+
inet = iface[:inet] || []
|
92
|
+
tag = port[:tag] ? ' (tag=' + port[:tag] + ')' : ''
|
93
|
+
peer = port[:peer] || iface[:peer]
|
94
|
+
remote = port[:remote_ip] ?
|
95
|
+
" #{port[:local_ip] || ''} => #{port[:remote_ip]}" : ''
|
96
|
+
patch = peer ? ' <-> ' + peer : remote
|
97
|
+
ns = iface[:ns] == :root ? '' : iface[:ns]
|
98
|
+
|
99
|
+
show_iface_common(name, inet, patch, tag, ns)
|
100
|
+
@dotwriter.br_iface(name, @mark[name], @dump_result[name],
|
101
|
+
inet, tag, peer, remote)
|
102
|
+
@done[name] = true
|
103
|
+
end
|
104
|
+
@dotwriter.br_end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def show_linbr
|
109
|
+
@linbr.to_hash.each do |name, br|
|
110
|
+
puts "Bridge #{name}"
|
111
|
+
@dotwriter.br_begin(name, '')
|
112
|
+
br[:interfaces].each do |ifname|
|
113
|
+
iface = @ifaces[ifname]
|
114
|
+
@dotwriter.br_iface(ifname, @mark[ifname], @dump_result[ifname],
|
115
|
+
iface[:inet], iface[:tag], iface[:peer])
|
116
|
+
show_iface iface
|
117
|
+
@done[ifname] = true
|
118
|
+
end
|
119
|
+
@dotwriter.br_end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def show_netns
|
124
|
+
@netns.to_hash.each do |name, ifaces|
|
125
|
+
puts "Namespace #{name}"
|
126
|
+
@dotwriter.ns_begin(name)
|
127
|
+
ifaces.each do |iface|
|
128
|
+
ifname = iface[:name]
|
129
|
+
if ifname != 'lo' and !iface[:inet].empty?
|
130
|
+
if @done[ifname]
|
131
|
+
@dotwriter.ns_br_iface(ifname)
|
132
|
+
else
|
133
|
+
@dotwriter.ns_iface(ifname, @mark[ifname], @dump_result[ifname],
|
134
|
+
iface[:inet], iface[:tag], iface[:peer])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
show_iface iface unless @done[iface[:name]]
|
138
|
+
@done[iface[:name]] = true
|
139
|
+
end
|
140
|
+
@dotwriter.ns_end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative 'utils'
|
2
|
+
|
3
|
+
module OVSImager
|
4
|
+
class OVSVS
|
5
|
+
def initialize()
|
6
|
+
vsctl_out = exec_vstcl
|
7
|
+
@vs = parse(vsctl_out, ['bridge', 'port', 'interface'])
|
8
|
+
@vs[:bridges] ||= []
|
9
|
+
|
10
|
+
# Mark the peer port.
|
11
|
+
@vs[:bridges].each do |br|
|
12
|
+
br[:ports].each do |port|
|
13
|
+
iface = port[:interfaces][0]
|
14
|
+
if iface[:type] == 'patch' && iface[:options].match(/peer="?(\S+?)"?[,\}]/)
|
15
|
+
port[:peer] = $1
|
16
|
+
end
|
17
|
+
if iface[:type] == 'gre' || iface[:type] == 'vxlan'
|
18
|
+
if iface[:options].match(/remote_ip="?(\S+?)"?[,\}]/)
|
19
|
+
port[:remote_ip] = $1
|
20
|
+
end
|
21
|
+
if iface[:options].match(/local_ip="?(\S+?)"?[,\}]/)
|
22
|
+
port[:local_ip] = $1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Move the port that has the same name with the interface to first.
|
29
|
+
@vs[:bridges].each do |vs|
|
30
|
+
vs[:ports].sort!{|a, b|
|
31
|
+
vs[:name] == a[:name] ? -1 : vs[:name] == b[:name] ? 1 :
|
32
|
+
a[:ns] == b[:ns] ? a[:name] <=> b[:name] : a[:ns] <=> b[:ns]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash()
|
38
|
+
return @vs
|
39
|
+
end
|
40
|
+
|
41
|
+
def exec_vstcl()
|
42
|
+
begin
|
43
|
+
Utils.execute('ovs-vsctl show', root=true)
|
44
|
+
rescue
|
45
|
+
''
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def parse(str, types)
|
51
|
+
name, attrs = str.split(/\n/, 2)
|
52
|
+
return {} unless name
|
53
|
+
name.gsub!(/^\"|\"$/, '')
|
54
|
+
params = {:name => name}
|
55
|
+
return params unless attrs
|
56
|
+
|
57
|
+
indent = attrs.match(/^(\s*)/)[1]
|
58
|
+
attrs.gsub!(/^#{indent}([^ ]*):\s+"?(.*?)"?\s*(?:$|\n)/) do |m|
|
59
|
+
params[$1.to_sym] = $2
|
60
|
+
''
|
61
|
+
end
|
62
|
+
return params if types.empty?
|
63
|
+
|
64
|
+
params[(types[0]+'s').downcase.to_sym] =
|
65
|
+
attrs.split(/\s+#{types[0]} /i)[1..-1].map do |cstr|
|
66
|
+
parse(cstr, types[1..-1])
|
67
|
+
end
|
68
|
+
|
69
|
+
return params
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module OVSImager
|
4
|
+
class TcpDump
|
5
|
+
SIZE = 400
|
6
|
+
def initialize(ping=false, from=nil, to=nil)
|
7
|
+
throw 'must be root.' if Process::UID.eid != 0
|
8
|
+
@ping = ping
|
9
|
+
@from = from
|
10
|
+
@to = to
|
11
|
+
end
|
12
|
+
|
13
|
+
def test(ifaces)
|
14
|
+
result = {}
|
15
|
+
ping = nil
|
16
|
+
if @ping
|
17
|
+
puts "Sending ping from #{@from} to #{@to} ..."
|
18
|
+
ping = IO.popen("ping -s #{SIZE} -c 15 -I #{@from} #{@to} >/dev/null", "r")
|
19
|
+
end
|
20
|
+
|
21
|
+
threads = ifaces.map do |(iface, iref)|
|
22
|
+
Thread.new do
|
23
|
+
Thread.current[:iface] = iface
|
24
|
+
ns = iref[:ns]
|
25
|
+
nscmd = ns == :root ? '' : "ip netns exec #{ns} "
|
26
|
+
dump = IO.popen("exec #{nscmd}tcpdump -v -l -n -i #{iface} \\( icmp or udp port 4789 \\) and greater #{SIZE} 2>&1", "r")
|
27
|
+
puts dump.gets
|
28
|
+
time_end = Time.now + 5
|
29
|
+
req_from = req_to = rep_from = rep_to = nil
|
30
|
+
while (waitmax = time_end - Time.now) > 0 do
|
31
|
+
rs, ws, = IO.select([dump], [], [], waitmax)
|
32
|
+
break unless rs
|
33
|
+
if r = rs[0]
|
34
|
+
msg = r.gets
|
35
|
+
break unless msg
|
36
|
+
# puts msg
|
37
|
+
if msg.match(/length #{SIZE+8}/) &&
|
38
|
+
msg.match(/([\da-f\.:]+) > ([\da-f\.:]+): ICMP echo (request|reply)/)
|
39
|
+
if $3 == 'request'
|
40
|
+
req_from = $1
|
41
|
+
req_to = $2
|
42
|
+
else
|
43
|
+
rep_from = $1
|
44
|
+
rep_to = $2
|
45
|
+
end
|
46
|
+
break if req_from && req_to && rep_from && rep_to
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
puts "Killing tcpdump(#{dump.pid}) on interface #{iface}."
|
51
|
+
Process.kill('TERM', dump.pid)
|
52
|
+
dump.close
|
53
|
+
result[iface] = [req_from, req_to, rep_from, rep_to]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
threads.each {|th| th.join(10)}
|
57
|
+
if @ping
|
58
|
+
Process.kill('TERM', ping.pid)
|
59
|
+
ping.close
|
60
|
+
end
|
61
|
+
return result
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module OVSImager
|
4
|
+
class Utils
|
5
|
+
def self.get_root_helper(root=true)
|
6
|
+
return '' if not root or Process::UID.eid == 0
|
7
|
+
root ? 'sudo ' : ''
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.execute(cmd, root=false)
|
11
|
+
root_helper = self.get_root_helper(root)
|
12
|
+
out = `#{root_helper}#{cmd}`
|
13
|
+
if $? != 0
|
14
|
+
raise "command execution failure: #{$?}"
|
15
|
+
end
|
16
|
+
return out
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/ovsimager.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ovsimager/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ovsimager"
|
8
|
+
spec.version = OVSImager::VERSION
|
9
|
+
spec.authors = ["NeoCat"]
|
10
|
+
spec.email = ["neocat@neocat.jp"]
|
11
|
+
spec.summary = %q{Draw graph of Open vSwitch virtual bridges.}
|
12
|
+
spec.description = %q{OVSImager draws a graph that describes relationship among Open vSwitch bridges, Linux bridges, and namespaces for routing. It can also mark the ports where ping packets went through using tcpdump, which is a useful feature for trouble-shooting in SDN environments. }
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
end
|
Binary file
|
Binary file
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ovsimager
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- NeoCat
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: 'OVSImager draws a graph that describes relationship among Open vSwitch
|
56
|
+
bridges, Linux bridges, and namespaces for routing. It can also mark the ports where
|
57
|
+
ping packets went through using tcpdump, which is a useful feature for trouble-shooting
|
58
|
+
in SDN environments. '
|
59
|
+
email:
|
60
|
+
- neocat@neocat.jp
|
61
|
+
executables:
|
62
|
+
- ovsimager
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".rspec"
|
68
|
+
- ".travis.yml"
|
69
|
+
- Gemfile
|
70
|
+
- LICENSE.txt
|
71
|
+
- README.md
|
72
|
+
- Rakefile
|
73
|
+
- bin/ovsimager
|
74
|
+
- lib/ovsimager.rb
|
75
|
+
- lib/ovsimager/dotwriter.rb
|
76
|
+
- lib/ovsimager/ipnetns.rb
|
77
|
+
- lib/ovsimager/linuxbridge.rb
|
78
|
+
- lib/ovsimager/ovsimager.rb
|
79
|
+
- lib/ovsimager/ovsvs.rb
|
80
|
+
- lib/ovsimager/tcpdump.rb
|
81
|
+
- lib/ovsimager/utils.rb
|
82
|
+
- lib/ovsimager/version.rb
|
83
|
+
- ovsimager.gemspec
|
84
|
+
- sample-interfaces.png
|
85
|
+
- sample-ping-trace.png
|
86
|
+
- spec/ovsimager_spec.rb
|
87
|
+
- spec/spec_helper.rb
|
88
|
+
homepage: ''
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.2.3
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: Draw graph of Open vSwitch virtual bridges.
|
112
|
+
test_files:
|
113
|
+
- spec/ovsimager_spec.rb
|
114
|
+
- spec/spec_helper.rb
|