554 lines
17 KiB
Tcl
554 lines
17 KiB
Tcl
# shows the Two-node Tool
|
|
proc popupTwoNodeDialog { } {
|
|
global twonodePID lastTwoNodeHop g_twoNodeSelect g_twoNodeSelectCallback
|
|
|
|
markerOptions off
|
|
set wi .twonodetool
|
|
catch {destroy $wi}
|
|
toplevel $wi
|
|
|
|
wm transient $wi .
|
|
wm resizable $wi 200 300
|
|
wm title $wi "Two-node Tool"
|
|
|
|
set twonodePID 0
|
|
set lastTwoNodeHop ""
|
|
set g_twoNodeSelect ""
|
|
set g_twoNodeSelectCallback selectTwoNode_twonodetool
|
|
|
|
global twoNode1 twoNode2
|
|
frame $wi.e
|
|
labelframe $wi.e.nodes -text "Nodes" -padx 4 -pady 4
|
|
label $wi.e.nodes.srcl -text "source node"
|
|
label $wi.e.nodes.dstl -text "destination node"
|
|
radiobutton $wi.e.nodes.src -text " (none) " -variable g_twoNodeSelect \
|
|
-value "$wi.e.nodes.src" -indicatoron off -activebackground green \
|
|
-selectcolor green -padx 4 -pady 4
|
|
radiobutton $wi.e.nodes.dst -text " (none) " -variable g_twoNodeSelect \
|
|
-value "$wi.e.nodes.dst" -indicatoron off -activebackground red \
|
|
-selectcolor red -padx 4 -pady 4
|
|
label $wi.e.nodes.help -text "click to select nodes"
|
|
pack $wi.e.nodes.srcl $wi.e.nodes.src $wi.e.nodes.dstl $wi.e.nodes.dst \
|
|
-side left
|
|
pack $wi.e.nodes.help -side left -padx 4 -pady 4
|
|
pack $wi.e.nodes $wi.e -side top -fill x
|
|
|
|
|
|
labelframe $wi.cmd -text "Command line"
|
|
global twoNodeTool
|
|
set twoNodeTool "traceroute"
|
|
radiobutton $wi.cmd.trace -text "traceroute" -variable twoNodeTool \
|
|
-value traceroute -command selectTwoNode_twonodetool
|
|
radiobutton $wi.cmd.ping -text "ping" -variable twoNodeTool \
|
|
-value ping -command selectTwoNode_twonodetool
|
|
entry $wi.cmd.cmd -bg white -width 50
|
|
pack $wi.cmd.trace $wi.cmd.ping $wi.cmd.cmd -side left -padx 4 -pady 4
|
|
pack $wi.cmd -side top -fill x
|
|
|
|
# results text box
|
|
labelframe $wi.results -text "Command results"
|
|
text $wi.results.text -bg white -width 80 -height 10 \
|
|
-yscrollcommand "$wi.results.scroll set"
|
|
scrollbar $wi.results.scroll -command "$wi.results.text yview"
|
|
pack $wi.results.text -side left -fill both -expand true -padx 4 -pady 4
|
|
pack $wi.results.scroll -side left -fill y -expand true -padx 4 -pady 4
|
|
pack $wi.results -side top -expand true -fill both
|
|
|
|
# buttons on the bottom
|
|
frame $wi.butt -borderwidth 6
|
|
button $wi.butt.run -text "Run" -command "runTwoNodeCommand $wi"
|
|
button $wi.butt.cancel -text "Clear" -command "clearTwoNodeDialog $wi 0"
|
|
button $wi.butt.close -text "Close" -command "clearTwoNodeDialog $wi 1"
|
|
pack $wi.butt.run $wi.butt.cancel $wi.butt.close -side left
|
|
pack $wi.butt -side bottom
|
|
}
|
|
|
|
#
|
|
# reset the Two Node Tool window
|
|
proc clearTwoNodeDialog { wi done} {
|
|
global eid activetool twonodePID lastTwoNodeHop systype
|
|
|
|
$wi.results.text delete 1.0 end
|
|
clearLinkHighlights
|
|
set lastTwoNodeHop ""
|
|
set node [string trim [$wi.e.nodes.src cget -text]]
|
|
|
|
if { $twonodePID > 0 } {
|
|
set os [lindex $systype 0]
|
|
set emul [getEmulPlugin $node]
|
|
set emulation_type [lindex $emul 1]
|
|
catch {
|
|
exec kill -9 $twonodePID 2> /dev/null
|
|
}
|
|
set twonodePID 0
|
|
}
|
|
|
|
if {$done} { ;# close Two Node window
|
|
set activetool select
|
|
.c delete withtag "twonode"
|
|
destroy $wi
|
|
}
|
|
}
|
|
|
|
#
|
|
# called from editor.tcl:button1 when user clicks on a node
|
|
# g_twoNodeSelect is the global variable of the radio button, whose value is
|
|
# set to the name of the button control to modify
|
|
proc selectTwoNode { node } {
|
|
global activetool g_twoNodeSelect g_twoNodeSelectCallback
|
|
|
|
if { ![winfo exists $g_twoNodeSelect] } { return }; # user has closed window
|
|
|
|
set radius 30
|
|
set color red
|
|
catch {
|
|
# works for radiobutton, but not ttk::checkbutton
|
|
set color [$g_twoNodeSelect cget -selectcolor]
|
|
}
|
|
set deltags "twonode && twonode$g_twoNodeSelect"
|
|
set tags "twonode $node twonode$g_twoNodeSelect"
|
|
$g_twoNodeSelect configure -text " $node "
|
|
drawNodeCircle $node $radius $color $tags $deltags
|
|
|
|
eval $g_twoNodeSelectCallback
|
|
set g_twoNodeSelect ""
|
|
set activetool select; # allow moving nodes now
|
|
}
|
|
|
|
# draw a circle around a node
|
|
proc drawNodeCircle { node radius color tags deltags } {
|
|
set c .c
|
|
if { $deltags != "" } { $c delete withtag $deltags }
|
|
if { $node == "" } { return }
|
|
|
|
set coords [getNodeCoords $node]
|
|
set x [lindex $coords 0]
|
|
set y [lindex $coords 1]
|
|
set x1 [expr {$x-$radius}]; set x2 [expr {$x+$radius}];
|
|
set y1 [expr {$y-$radius}]; set y2 [expr {$y+$radius}];
|
|
|
|
$c create oval $x1 $y1 $x2 $y2 -width 5 -outline $color -tags $tags
|
|
}
|
|
|
|
#
|
|
# generate a command line string for the two-node tool
|
|
# called when tool or either node is selected
|
|
proc selectTwoNode_twonodetool { } {
|
|
global eid twoNodeTool
|
|
set wi .twonodetool
|
|
|
|
if { ![winfo exists $wi] } { return }; # user has closed window
|
|
|
|
# get the tool and its options
|
|
set tool $twoNodeTool
|
|
set opts ""
|
|
if {$twoNodeTool == "traceroute"} {
|
|
set opts "-n -t 0"
|
|
} elseif {$twoNodeTool == "ping"} {
|
|
set opts "-R -n"
|
|
}
|
|
|
|
# get source node and destination ip address, if possible
|
|
set src [string trim [$wi.e.nodes.src cget -text]]
|
|
set dst "(none)"
|
|
set node2 [string trim [$wi.e.nodes.dst cget -text]]
|
|
if {$src != "(none)" && $node2 != "(none)"} {
|
|
set dst [getDestinationAddress $src $node2]
|
|
} else {
|
|
$wi.cmd.cmd delete 0 end
|
|
return
|
|
}
|
|
|
|
# erase existing command (edits are lost) and insert a new one
|
|
global systype
|
|
set os [lindex $systype 0]
|
|
set emul [getEmulPlugin $src]
|
|
set emulation_type [lindex $emul 1]
|
|
if { $os == "Linux" } {
|
|
if { $emulation_type == "openvz" } {
|
|
set node_id [string range $src 1 end]
|
|
incr node_id 1000
|
|
set cmd "/usr/sbin/vzctl exec $node_id $tool $opts $dst"
|
|
$wi.cmd.cmd delete 0 end
|
|
$wi.cmd.cmd insert 0 $cmd
|
|
return
|
|
}
|
|
}
|
|
set cmd "$tool $opts $dst"
|
|
set sock [lindex $emul 2]
|
|
set flags 0x44;# set TTY, critical flags
|
|
set exec_num [newExecCallbackRequest twonode]
|
|
sendExecMessage $sock $src $cmd $exec_num $flags
|
|
}
|
|
|
|
#
|
|
# return an IP address for node2; if node1 is directly connected, return the
|
|
# address on that subnet, otherwise pick the first interface address
|
|
proc getDestinationAddress { node1 node2 } {
|
|
set ifc ""
|
|
if {$node1 != "" && $node2 != ""} {
|
|
set ifc [ifcByPeer $node2 $node1]; # node2 directly connected to node1?
|
|
}
|
|
if {$ifc == ""} {;# node not directly connected, pick first interface
|
|
set ifc [lindex [ifcList $node2] 0]
|
|
}
|
|
if { $node2 == "" } { return "" }
|
|
return "[lindex [split [getIfcIPv4addr $node2 $ifc] /] 0]"
|
|
}
|
|
|
|
#
|
|
# callback when exec response is received
|
|
proc exec_twonode_callback { node execnum execcmd execres execstatus } {
|
|
set wi .twonodetool
|
|
if { ![winfo exists $wi] } { return }
|
|
|
|
set i [string first "&&" $execres]
|
|
if { $i >= 0 } {
|
|
incr i 3
|
|
set execres [string range $execres $i end]
|
|
}
|
|
$wi.cmd.cmd delete 0 end
|
|
$wi.cmd.cmd insert 0 $execres
|
|
}
|
|
|
|
#
|
|
# run the command from the Two Node Tool window
|
|
proc runTwoNodeCommand { wi } {
|
|
global twoNodeTool node_list
|
|
set c .c
|
|
|
|
if { ![winfo exists $wi] } { return }; # user has closed window
|
|
|
|
clearTwoNodeDialog $wi 0; # clean up any previous processes
|
|
|
|
set node [string trim [$wi.e.nodes.src cget -text]]
|
|
if { [lsearch $node_list $node] < 0 } { return }
|
|
set cmd [$wi.cmd.cmd get]
|
|
set tool $twoNodeTool
|
|
|
|
# de-select
|
|
$c dtag node selected
|
|
$c delete -withtags selectmark
|
|
|
|
$wi.results.text delete 1.0 end
|
|
$wi.results.text insert end "$cmd\n"
|
|
$wi.results.text see end
|
|
|
|
after 100 doTwoNode $tool $node \"$cmd\"
|
|
}
|
|
|
|
#
|
|
# dipatch command remotely or to fileevent handler
|
|
proc doTwoNode { tool node cmd } {
|
|
global eid twonodePID exec_servers lastTwoNodeHop
|
|
set wi .twonodetool
|
|
|
|
set lastTwoNodeHop $node
|
|
|
|
if { $cmd == "" } { return }
|
|
|
|
# local execution - uses file event handler
|
|
set fileId [open "|$cmd" r]
|
|
fconfigure $fileId -buffering line
|
|
set twonodePID [pid $fileId]
|
|
fileevent $fileId readable [list readTwoNodeStream $node $fileId $tool]
|
|
}
|
|
|
|
#
|
|
# event handler for Two Node command pipe
|
|
proc readTwoNodeStream { node fileId tool } {
|
|
fileevent $fileId readable ""; # turn handler off
|
|
set wi .twonodetool
|
|
|
|
if {![winfo exists $wi]} {
|
|
catch { close $fileId }
|
|
return; # the window has been closed
|
|
}
|
|
if { ![eof $fileId] } {
|
|
gets $fileId line
|
|
$wi.results.text insert end "$line\n"
|
|
$wi.results.text see end
|
|
drawTwoNodeLine $node $line $tool
|
|
update
|
|
# reinstall event handler
|
|
fileevent $fileId readable [list readTwoNodeStream $node $fileId $tool]
|
|
} else {
|
|
#set p [pid $fileId]
|
|
$wi.results.text insert end "done.\n"
|
|
$wi.results.text see end
|
|
catch { close $fileId }
|
|
update
|
|
}
|
|
}
|
|
|
|
#
|
|
# parse a line of trace/ping output and highlight the next hop
|
|
proc drawTwoNodeLine { node line type } {
|
|
global lastTwoNodeHop
|
|
|
|
# parse the nexthop from raw input
|
|
set nexthop ""
|
|
if {$type == "traceroute"} {
|
|
set tmp [string range $line 2 17]
|
|
set nexthop [regexp -inline {[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+} $tmp]
|
|
} elseif {$type == "ping"} {
|
|
set first [string range $line 0 0]
|
|
if {$first == "R"} { ;# RR: (reroute header)
|
|
clearLinkHighlights ;# new route will be displayed, color old one?
|
|
set tmp [string range $line 4 end]
|
|
} elseif {$first == " " } { ;# tab character
|
|
set tmp [string range $line 1 end]
|
|
} else { ;# not reroute info
|
|
set lastTwoNodeHop $node ;# need beginning node
|
|
return
|
|
}
|
|
set nexthop [regexp -inline {[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+} $tmp]
|
|
}
|
|
if { $nexthop == "" } { return }; # garbage
|
|
|
|
# search for hops matching this nexthop address
|
|
set hops [findNextHops $lastTwoNodeHop $nexthop ""]
|
|
if {[llength $hops] == 0} {
|
|
puts "Couldn't highlight next hop: $nexthop";
|
|
return
|
|
}
|
|
|
|
# highlight the path
|
|
set a $lastTwoNodeHop
|
|
foreach b $hops {
|
|
highlightLink $a $b
|
|
set a $b
|
|
}
|
|
set lastTwoNodeHop $b
|
|
}
|
|
|
|
#
|
|
# search for a peer node having the nexthop address
|
|
# lastnode parameter prevents infinite recursion
|
|
proc findNextHops { node nexthop lastnode } {
|
|
if { $node == "" } { return "" }; # initial lastTwoNodeHop value
|
|
|
|
foreach ifc [ifcList $node] {
|
|
set peer [peerByIfc $node $ifc]
|
|
if { $peer == "" } { continue }
|
|
if { $lastnode != "" && $peer == $lastnode } { continue };# no recursion
|
|
set peertype [nodeType $peer]
|
|
switch $peertype {
|
|
lanswitch -
|
|
hub -
|
|
wlan {
|
|
set hops [findNextHops $peer $nexthop $node]
|
|
if { [llength $hops] > 0 } {
|
|
# don't include wlan in list of hops
|
|
if {$peertype == "wlan"} { return $hops }
|
|
# include peer in list of hops
|
|
return [linsert $hops 0 $peer]
|
|
}
|
|
}
|
|
default {
|
|
if { [nodeHasAddr $peer $nexthop] } { return $peer }
|
|
}
|
|
};# end switch
|
|
}
|
|
return ""
|
|
}
|
|
|
|
#
|
|
# returns 1 if node has address, 0 otherwise
|
|
# (getIfcByPeer and getIfcIPv4addr are not enough, since traceroute can report
|
|
# any of the peer's addresses)
|
|
proc nodeHasAddr { node addr } {
|
|
foreach ifc [ifcList $node] {
|
|
set nodeaddr [lindex [split [getIfcIPv4addr $node $ifc] /] 0]
|
|
if { $nodeaddr == $addr } {
|
|
return 1
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
#
|
|
# Highlight the link between two nodes
|
|
proc highlightLink { node1 node2 } {
|
|
global link_list
|
|
set hlink ""
|
|
|
|
set wlanlinks [.c find withtag "wlanlink && $node1 && $node2"]
|
|
if { $wlanlinks != "" } {
|
|
set hlink [lindex $wlanlinks 0]
|
|
} else {
|
|
set links [.c find withtag "link && $node1 && $node2"]
|
|
if { $links != "" } { set hlink [lindex $links 0] }
|
|
}
|
|
if { $hlink == "" } { return }
|
|
# don't mess with link width! labels will redraw vertically
|
|
.c itemconfigure "$hlink" -fill "#E4CF30" -width 5
|
|
update
|
|
}
|
|
|
|
#
|
|
# Remove highlighting from all links
|
|
proc clearLinkHighlights { } {
|
|
global link_list defLinkColor
|
|
foreach link $link_list {
|
|
set limages [.c find withtag "link && $link"]
|
|
set limage1 [lindex $limages 0]
|
|
set tags [.c gettags $limage1]
|
|
set lnode1 [lindex $tags 2]
|
|
set lnode2 [lindex $tags 3]
|
|
set width [getLinkWidth $link]
|
|
set fill [getLinkColor $link]
|
|
.c itemconfigure "link && $link" -fill $fill -width $width
|
|
.c itemconfigure "linklabel && $link" -fill black
|
|
if { $lnode1 != "" && $link != "" } {
|
|
.c itemconfigure "interface && $lnode1 && $link" -fill black
|
|
}
|
|
if { $lnode2 != "" && $link != "" } {
|
|
.c itemconfigure "interface && $lnode2 && $link" -fill black
|
|
}
|
|
}
|
|
foreach wlanlink [.c find withtag wlanlink] {
|
|
set tags [.c gettags $wlanlink]
|
|
set wlan [lindex $tags 3]
|
|
set color [getWlanColor $wlan]
|
|
.c itemconfigure "$wlanlink" -fill $color -width 2
|
|
}
|
|
update
|
|
}
|
|
|
|
#
|
|
# Boeing: shows the Two-node Tool
|
|
proc popupRunDialog { } {
|
|
global node_list activetool systype
|
|
|
|
set activetool select
|
|
markerOptions off
|
|
set wi .runtool
|
|
catch {destroy $wi}
|
|
toplevel $wi
|
|
|
|
wm transient $wi .
|
|
wm resizable $wi 200 300
|
|
wm title $wi "Run Tool"
|
|
|
|
global runnodelist
|
|
set runnodelist $node_list
|
|
|
|
labelframe $wi.n -text "Run on these nodes"
|
|
frame $wi.n.nodes
|
|
listbox $wi.n.nodes.nodes -width 15 -height 11 -selectmode extended \
|
|
-exportselection 0 \
|
|
-listvariable runnodelist -yscrollcommand "$wi.n.nodes.scroll set"
|
|
scrollbar $wi.n.nodes.scroll -command "$wi.n.nodes.nodes yview"
|
|
frame $wi.n.nodesel -height 5
|
|
button $wi.n.nodesel.all -text "all" \
|
|
-command "$wi.n.nodes.nodes selection set 0 end"
|
|
button $wi.n.nodesel.none -text "none" \
|
|
-command "$wi.n.nodes.nodes selection clear 0 end"
|
|
pack $wi.n.nodes.nodes $wi.n.nodes.scroll -side left -fill y -padx 4 -pady 4
|
|
pack $wi.n.nodesel.all $wi.n.nodesel.none -side left -padx 4 -pady 4
|
|
pack $wi.n.nodes -side top -expand true -fill both
|
|
pack $wi.n.nodesel -side bottom -fill x
|
|
pack $wi.n -side right -fill both
|
|
|
|
$wi.n.nodes.nodes selection set 0 end ;# select all
|
|
|
|
labelframe $wi.cmd -text "Command line"
|
|
entry $wi.cmd.cmd -bg white -width 50
|
|
pack $wi.cmd.cmd -side left -padx 4 -pady 4
|
|
pack $wi.cmd -side top -fill x
|
|
|
|
set cmd "ps ax"
|
|
set os [lindex $systype 0]
|
|
# TODO: use CORE API Execute message for all cases
|
|
if { $os == "Linux" } {
|
|
set emulation_type [lindex [getEmulPlugin "*"] 1]
|
|
if { $emulation_type == "openvz" } {
|
|
set cmd "/usr/sbin/vzctl exec NODE $cmd"
|
|
}
|
|
}
|
|
$wi.cmd.cmd insert 0 $cmd
|
|
|
|
# results text box
|
|
labelframe $wi.results -text "Command results"
|
|
text $wi.results.text -bg white -width 80 -height 10 \
|
|
-yscrollcommand "$wi.results.scroll set"
|
|
scrollbar $wi.results.scroll -command "$wi.results.text yview"
|
|
pack $wi.results.text -side left -fill both -expand true -padx 4 -pady 4
|
|
pack $wi.results.scroll -side left -fill y -padx 4 -pady 4
|
|
pack $wi.results -side top -expand true -fill both
|
|
|
|
# buttons on the bottom
|
|
frame $wi.butt -borderwidth 6
|
|
button $wi.butt.run -text "Run" -command "runToolCommand $wi \"\""
|
|
button $wi.butt.close -text "Close" -command "destroy $wi"
|
|
pack $wi.butt.run $wi.butt.close -side left
|
|
pack $wi.butt -side bottom
|
|
}
|
|
|
|
#
|
|
# run the command from the Run Tool window
|
|
proc runToolCommand { wi node } {
|
|
global node_list eid systype
|
|
set c .c
|
|
|
|
if { ![winfo exists $wi] } { return }; # user has closed window
|
|
|
|
# start running commands
|
|
if { $node == "" } {
|
|
$wi.results.text delete 1.0 end
|
|
set selected [$wi.n.nodes.nodes curselection]
|
|
if { [llength $selected] == 0 } {
|
|
$wi.results.text insert end "No nodes are selected. Highlight one or more nodes on the right and try again."
|
|
return
|
|
}
|
|
set node [lindex $node_list [lindex $selected 0]]
|
|
after 100 runToolCommand $wi $node; # callback for starting node
|
|
return
|
|
}
|
|
|
|
set next ""
|
|
set getnext 0
|
|
foreach i [$wi.n.nodes.nodes curselection] { ;# find the next node
|
|
set n [lindex $node_list $i]
|
|
if {$n == $node } {
|
|
set getnext 1
|
|
} elseif { $getnext == 1 } {
|
|
# only run commands on router/pc/host nodes
|
|
if {[lsearch {router pc host} [nodeType $n]] < 0} { continue }
|
|
set next $n
|
|
break
|
|
}
|
|
}
|
|
|
|
# build the command (replace NODE with node name)
|
|
set cmd [$wi.cmd.cmd get]
|
|
set i [string first "NODE" $cmd]
|
|
set os [lindex $systype 0]
|
|
set node_id $node
|
|
$wi.results.text insert end "> $cmd\n"
|
|
$wi.results.text see end
|
|
update
|
|
|
|
# run the command via Execute API message
|
|
set exec_num [newExecCallbackRequest runtool]
|
|
set plugin [getEmulPlugin $node]
|
|
set emulation_sock [lindex $plugin 2]
|
|
sendExecMessage $emulation_sock $node $cmd $exec_num 0x30
|
|
|
|
if { $next != "" } {
|
|
runToolCommand $wi $next; # callback for next node in list
|
|
}
|
|
}
|
|
|
|
# callback after receiving exec message response
|
|
proc exec_runtool_callback { node execnum cmd result status } {
|
|
set wi .runtool
|
|
|
|
if { ![winfo exists $wi] } { return }; # user has closed window
|
|
|
|
$wi.results.text insert end "> $node > $cmd:\n"
|
|
$wi.results.text insert end "$result\n"
|
|
$wi.results.text see end
|
|
}
|
|
|