# # CORE API # Copyright 2005-2013 the Boeing Company. # See the LICENSE file included in this distribution. # # author: Jeff Ahrenholz # # version of the API document that is used set CORE_API_VERSION 1.23 set DEFAULT_API_PORT 4038 set g_api_exec_num 100; # starting execution number # set scale for X/Y coordinate translation set XSCALE 1.0 set YSCALE 1.0 set XOFFSET 0 set YOFFSET 0 # current session; 0 is a new session set g_current_session 0 set g_session_dialog_hint 1 # this is an array of lists, with one array entry for each widget or callback, # and the entry is a list of execution numbers (for matching replies with # requests) array set g_execRequests { shell "" observer "" } # for a simulator, uncomment this line or cut/paste into debugger: # set XSCALE 4.0; set YSCALE 4.0; set XOFFSET 1800; set YOFFSET 300 array set nodetypes { 0 def 1 phys 2 xen 3 tbd 4 lanswitch 5 hub \ 6 wlan 7 rj45 8 tunnel 9 ktunnel 10 emane } array set regtypes { wl 1 mob 2 util 3 exec 4 gui 5 emul 6 } array set regntypes { 1 wl 2 mob 3 util 4 exec 5 gui 6 emul 7 relay 10 session } array set regtxttypes { wl "Wireless Module" mob "Mobility Module" \ util "Utility Module" exec "Execution Server" \ gui "Graphical User Interface" emul "Emulation Server" \ relay "Relay" } set DEFAULT_GUI_REG "gui core_2d_gui" array set eventtypes { definition_state 1 configuration_state 2 \ instantiation_state 3 runtime_state 4 \ datacollect_state 5 shutdown_state 6 \ event_start 7 event_stop 8 event_pause 9 \ event_restart 10 file_open 11 file_save 12 \ event_scheduled 31 } set CORE_STATES \ "NONE DEFINITION CONFIGURATION INSTANTIATION RUNTIME DATACOLLECT SHUTDOWN" set EXCEPTION_LEVELS \ "NONE FATAL ERROR WARNING NOTICE" # Event handler invoked for each message received by peer proc receiveMessage { channel } { global curcanvas showAPI set prmsg $showAPI set type 0 set flags 0 set len 0 set seq 0 #puts "API receive data." # disable the fileevent here, then reinstall the handler at the end fileevent $channel readable "" # channel closed if { [eof $channel] } { resetChannel channel 1 return } # # read first four bytes of message header set more_data 1 while { $more_data == 1 } { if { [catch { set bytes [read $channel 4] } e] } { # in tcl8.6 this occurs during shutdown #puts "channel closed: $e" break; } if { [fblocked $channel] == 1} { # 4 bytes not available yet break; } elseif { [eof $channel] } { resetChannel channel 1 break; } elseif { [string bytelength $bytes] == 0 } { # zero bytes read - parseMessageHeader would fail break; } # parse type/flags/length if { [parseMessageHeader $bytes type flags len] < 0 } { # Message header error break; } # read message data of specified length set bytes [read $channel $len] #if { $prmsg== 1} { # puts "read $len bytes (type=$type, flags=$flags, len=$len)..." #} # handle each message type switch -exact -- "$type" { 1 { parseNodeMessage $bytes $len $flags } 2 { parseLinkMessage $bytes $len $flags } 3 { parseExecMessage $bytes $len $flags $channel } 4 { parseRegMessage $bytes $len $flags $channel } 5 { parseConfMessage $bytes $len $flags $channel } 6 { parseFileMessage $bytes $len $flags $channel } 8 { parseEventMessage $bytes $len $flags $channel } 9 { parseSessionMessage $bytes $len $flags $channel } 10 { parseExceptionMessage $bytes $len $flags $channel; #7 { parseIfaceMessage $bytes $len $flags $channel } # } default { puts "Unknown Message = $type" } } # end switch } # end while # update the canvas catch { # this messes up widgets #raiseAll .c .c config -cursor left_ptr ;# otherwise we have hourglass/pirate update } if {$channel != -1 } { resetChannel channel 0 } } # # Open an API socket to the specified server:port, prompt user for retry # if specified; set the readable file event and parameters; # returns the channel name or -1 on error. # proc openAPIChannel { server port retry } { # use default values (localhost:4038) when none specified if { $server == "" || $server == 0 } { set server "localhost" } if { $port == 0 } { global DEFAULT_API_PORT set port $DEFAULT_API_PORT } # loop when retry is true set s -1 while { $s < 0 } { # TODO: fix this to remove lengthy timeout periods... # (need to convert all channel I/O to use async channel) # vwait doesn't work here, blocks on socket call #puts "Connecting to $server:$port..."; # verbose set svcstart [getServiceStartString] set e "This feature requires a connection to the CORE daemon.\n" set e "$e\nFailed to connect to $server:$port!\n" set e "$e\nHave you started the CORE daemon with" set e "$e '$svcstart'?" if { [catch {set s [socket $server $port]} ex] } { puts "\n$e\n (Error: $ex)" set s -1 if { ! $retry } { return $s; }; # error, don't retry } if { $s > 0 } { puts "connected." }; # verbose if { $retry } {; # prompt user with retry dialog if { $s < 0 } { set choice [tk_dialog .connect "Error" $e \ error 0 Retry "Start daemon..." Cancel] if { $choice == 2 } { return $s } ;# cancel if { $choice == 1 } { set sudocmd "gksudo" set cmd "core-daemon -d" if { [catch {exec $sudocmd $cmd & } e] } { puts "Error running '$sudocmd $cmd'!" } after 300 ;# allow time for daemon to start } # fall through for retry... } } }; # end while # now we have a valid socket, set up encoding and receive event fconfigure $s -blocking 0 -encoding binary -translation { binary binary } \ -buffering full -buffersize 4096 fileevent $s readable [list receiveMessage $s] return $s } # # Reinstall the receiveMessage event handler # proc resetChannel { channel_ptr close } { upvar 1 $channel_ptr channel if {$close == 1} { close $channel pluginChannelClosed $channel set $channel -1 } if { [catch { fileevent $channel readable \ [list receiveMessage $channel] } ] } { # may print error here } } # # Catch errors when flushing sockets # proc flushChannel { channel_ptr msg } { upvar 1 $channel_ptr channel if { [catch { flush $channel } err] } { puts "*** $msg: $err" set channel -1 return -1 } return 0 } # # CORE message header # proc parseMessageHeader { bytes type flags len } { # variables are passed by reference upvar 1 $type mytype upvar 1 $flags myflags upvar 1 $len mylen # # read the four-byte message header # if { [binary scan $bytes ccS mytype myflags mylen] != 3 } { puts "*** warning: message header error" return -1 } else { set mytype [expr {$mytype & 0xFF}]; # convert signed to unsigned set myflags [expr {$myflags & 0xFF}] if { $mylen == 0 } { puts "*** warning: zero length message header!" # empty the channel #set bytes [read $channel] return -1 } } return 0 } # # CORE API Node message TLVs # proc parseNodeMessage { data len flags } { global node_list curcanvas c router eid showAPI nodetypes CORE_DATA_DIR global XSCALE YSCALE XOFFSET YOFFSET deployCfgAPI_lock #puts "Parsing node message of length=$len, flags=$flags" set prmsg $showAPI set current 0 array set typenames { 1 num 2 type 3 name 4 ipv4_addr 5 mac_addr \ 6 ipv6_addr 7 model 8 emulsrv 10 session \ 32 xpos 33 ypos 34 canv \ 35 emuid 36 netid 37 services \ 48 lat 49 long 50 alt \ 66 icon 80 opaque } array set typesizes { num 4 type 4 name -1 ipv4_addr 4 ipv6_addr 16 \ mac_addr 8 model -1 emulsrv -1 session -1 \ xpos 2 ypos 2 canv 2 emuid 4 \ netid 4 services -1 lat 4 long 4 alt 4 \ icon -1 opaque -1 } array set vals { num 0 type 0 name "" ipv4_addr -1 ipv6_addr -1 \ mac_addr -1 model "" emulsrv "" session "" \ xpos 0 ypos 0 canv "" \ emuid -1 netid -1 services "" \ lat 0 long 0 alt 0 \ icon "" opaque "" } if { $prmsg==1 } { puts -nonewline "NODE(flags=$flags," } # # TLV parsing # while { $current < $len } { # TLV header if { [binary scan $data @${current}cc type length] != 2 } { puts "TLV header error" break } set length [expr {$length & 0xFF}]; # convert signed to unsigned if { $length == 0 } {; # prevent endless looping if { $type == 0 } { puts -nonewline "(extra padding)"; break } else { puts "Found zero-length TLV for type=$type, dropping."; break } } set pad [pad_32bit $length] # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" incr current 2 if {![info exists typenames($type)] } { ;# unknown TLV type if { $prmsg } { puts -nonewline "unknown=$type," } incr current $length continue } set typename $typenames($type) set size $typesizes($typename) # 32-bit and 64-bit vals pre-padded if { $size == 4 || $size == 8 } { incr current $pad } # read TLV data depending on size switch -exact -- "$size" { 2 { binary scan $data @${current}S vals($typename) } 4 { binary scan $data @${current}I vals($typename) } 8 { binary scan $data @${current}W vals($typename) } 16 { binary scan $data @${current}c16 vals($typename) } -1 { binary scan $data @${current}a${length} vals($typename) } } if { $size == -1 } { incr current $pad } ;# string vals post-padded if { $type == 6 } { incr current $pad } ;# 128-bit vals post-padded incr current $length # special handling of data here switch -exact -- "$typename" { ipv4_addr { array set vals [list $typename \ [ipv4ToString $vals($typename)] ] } mac_addr { array set vals [list $typename \ [macToString $vals($typename)] ] } ipv6_addr { array set vals [list $typename \ [ipv6ToString $vals($typename)] ] } xpos { array set vals [list $typename \ [expr { ($vals($typename) * $XSCALE) - $XOFFSET }] ] } ypos { array set vals [list $typename \ [expr { ($vals($typename) * $YSCALE) - $YOFFSET }] ] } } if { $prmsg } { puts -nonewline "$typename=$vals($typename)," } } if { $prmsg } { puts ") "} # # Execution # # TODO: enforce message parameters here if { ![info exists nodetypes($vals(type))] } { puts "NODE: invalid node type ($vals(type)), dropping"; return } set node "n$vals(num)" set node_id "$eid\_$node" if { [lsearch $node_list $node] == -1 } {; # check for node existance set exists false } else { set exists true } if { $vals(name) == "" } {; # make sure there is a node name set name $node if { $exists } { set name [getNodeName $node] } array set vals [list name $name] } if { $exists } { if { $flags == 1 } { puts "Node add msg but node ($node) already exists, dropping." return } } elseif { $flags != 1 } { puts -nonewline "Node modify/delete message but node ($node) does " puts "not exist dropping." return } if { $vals(icon) != "" } { set icon $vals(icon) if { [file pathtype $icon] == "relative" } { set icon "$CORE_DATA_DIR/icons/normal/$icon" } if { ![file exists $icon ] } { puts "Node icon '$vals(icon)' does not exist." array set vals [list icon ""] } else { array set vals [list icon $icon] } } global $node set wlans_needing_update { } if { $vals(emuid) != -1 } { # For Linux (FreeBSD populates ngnodeidmap in l3node.instantiate/ # buildInterface when the netgraph ID is known) # populate ngnodeidmap for later use with wireless; it is treated as # a hex value string (without the leading "0x") global ngnodeidmap foreach wlan [findWlanNodes $node] { if { ![info exists ngnodeidmap($eid\_$wlan)] } { set netid [string range $wlan 1 end] set emulation_type [lindex [getEmulPlugin $node] 1] # TODO: verify that this incr 1000 is for OpenVZ if { $emulation_type == "openvz" } { incr netid 1000 } set ngnodeidmap($eid\_$wlan) [format "%x" $netid] } if { ![info exists ngnodeidmap($eid\_$wlan-$node)] } { set ngnodeidmap($eid\_$wlan-$node) [format "%x" $vals(emuid)] lappend wlans_needing_update $wlan } } ;# end foreach wlan } # local flags: informational message that node was added or deleted if {[expr {$flags & 0x8}]} { if { ![info exists c] } { return } if {[expr {$flags & 0x1}] } { ;# add flag nodeHighlights $c $node on green after 3000 "nodeHighlights .c $node off green" } elseif {[expr {$flags & 0x2}] } { ;# delete flag nodeHighlights $c $node on black after 3000 "nodeHighlights .c $node off black" } # note: we may want to save other data passed in this message here # rather than just returning... return } # now we have all the information about this node switch -exact -- "$flags" { 0 { apiNodeModify $node vals } 1 { apiNodeCreate $node vals } 2 { apiNodeDelete $node } default { puts "NODE: unsupported flags ($flags)"; return } } } # # modify a node # proc apiNodeModify { node vals_ref } { global c eid zoom curcanvas upvar $vals_ref vals if { ![info exists c] } { return } ;# batch mode set draw 0 if { $vals(icon) != "" } { setCustomImage $node $vals(icon) set draw 1 } # move the node and its links if {$vals(xpos) != 0 && $vals(ypos) != 0} { moveNodeAbs $c $node [expr {$zoom * $vals(xpos)}] \ [expr {$zoom * $vals(ypos)}] } if { $vals(name) != "" } { setNodeName $node $vals(name) set draw 1 } if { $vals(services) != "" } { set services [split $vals(services) |] setNodeServices $node $services } # TODO: handle other optional on-screen data # lat, long, alt, heading, platform type, platform id if { $draw && [getNodeCanvas $node] == $curcanvas } { .c delete withtag "node && $node" .c delete withtag "nodelabel && $node" drawNode .c $node } } # # add a node # proc apiNodeCreate { node vals_ref } { global $node nodetypes node_list canvas_list curcanvas eid upvar $vals_ref vals # create GUI object set nodetype $nodetypes($vals(type)) set nodename $vals(name) if { $nodetype == "emane" } { set nodetype "wlan" } ;# special case - EMANE if { $nodetype == "def" || $nodetype == "xen" } { set nodetype "router" } newNode [list $nodetype $node] ;# use node number supplied from API message setNodeName $node $nodename if { $vals(canv) == "" } { setNodeCanvas $node $curcanvas } else { set canv $vals(canv) if { ![string is integer $canv] || $canv < 0 || $canv > 100} { puts "warning: invalid canvas '$canv' in Node message!" return } set canv "c$canv" if { [lsearch $canvas_list $canv] < 0 && $canv == "c0" } { # special case -- support old imn files with Canvas0 global $canv lappend canvas_list $canv set $canv {} setCanvasName $canv "Canvas0" set curcanvas $canv switchCanvas none } else { while { [lsearch $canvas_list $canv] < 0 } { set canvnew [newCanvas ""] switchCanvas none ;# redraw canvas tabs } } setNodeCanvas $node $canv } setNodeCoords $node "$vals(xpos) $vals(ypos)" lassign [getDefaultLabelOffsets [nodeType $node]] dx dy setNodeLabelCoords $node "[expr $vals(xpos) + $dx] [expr $vals(ypos) + $dy]" setNodeLocation $node $vals(emulsrv) if { $vals(icon) != "" } { setCustomImage $node $vals(icon) } drawNode .c $node set model $vals(model) if { $model != "" && $vals(type) < 4} { # set model only for (0 def 1 phys 2 xen 3 tbd) 4 lanswitch setNodeModel $node $model if { [lsearch -exact [getNodeTypeNames] $model] == -1 } { puts "warning: unknown node type '$model' in Node message!" } } if { $vals(services) != "" } { set services [split $vals(services) |] setNodeServices $node $services } if { $vals(type) == 7 } { ;# RJ45 node - used later to control linking netconfInsertSection $node [list model $vals(model)] } elseif { $vals(type) == 10 } { ;# EMANE node set section [list mobmodel coreapi ""] netconfInsertSection $node $section #set sock [lindex [getEmulPlugin $node] 2] #sendConfRequestMessage $sock $node "all" 0x1 -1 "" } elseif { $vals(type) == 6 } { ;# WLAN node if { $vals(opaque) != "" } { # treat opaque as a list to accomodate other data set i [lsearch $vals(opaque) "range=*"] if { $i != -1 } { set range [lindex $vals(opaque) $i] setNodeRange $node [lindex [split $range =] 1] } } } } # # delete a node # proc apiNodeDelete { node } { removeGUINode $node } # # CORE API Link message TLVs # proc parseLinkMessage { data len flags } { global router def_router_model eid global link_list node_list ngnodeidmap ngnodeidrmap showAPI execMode set prmsg $showAPI set current 0 set c .c #puts "Parsing link message of length=$len, flags=$flags" array set typenames { 1 node1num 2 node2num 3 delay 4 bw 5 per \ 6 dup 7 jitter 8 mer 9 burst 10 session \ 16 mburst 32 ltype 33 guiattr 34 uni \ 35 emuid1 36 netid 37 key \ 48 if1num 49 if1ipv4 50 if1ipv4mask 51 if1mac \ 52 if1ipv6 53 if1ipv6mask \ 54 if2num 55 if2ipv4 56 if2ipv4mask 57 if2mac \ 64 if2ipv6 65 if2ipv6mask } array set typesizes { node1num 4 node2num 4 delay 8 bw 8 per -1 \ dup -1 jitter 8 mer 2 burst 2 session -1 \ mburst 2 ltype 4 guiattr -1 uni 2 \ emuid1 4 netid 4 key 4 \ if1num 2 if1ipv4 4 if1ipv4mask 2 if1mac 8 \ if1ipv6 16 if1ipv6mask 2 \ if2num 2 if2ipv4 4 if2ipv4mask 2 if2mac 8 \ if2ipv6 16 if2ipv6mask 2 } array set vals { node1num -1 node2num -1 delay 0 bw 0 per "" \ dup "" jitter 0 mer 0 burst 0 session "" \ mburst 0 ltype 0 guiattr "" uni 0 \ emuid1 -1 netid -1 key -1 \ if1num -1 if1ipv4 -1 if1ipv4mask 24 if1mac -1 \ if1ipv6 -1 if1ipv6mask 64 \ if2num -1 if2ipv4 -1 if2ipv4mask 24 if2mac -1 \ if2ipv6 -1 if2ipv6mask 64 } set emuid1 -1 if { $prmsg==1 } { puts -nonewline "LINK(flags=$flags," } # # TLV parsing # while { $current < $len } { # TLV header if { [binary scan $data @${current}cc type length] != 2 } { puts "TLV header error" break } set length [expr {$length & 0xFF}]; # convert signed to unsigned if { $length == 0 } {; # prevent endless looping if { $type == 0 } { puts -nonewline "(extra padding)"; break } else { puts "Found zero-length TLV for type=$type, dropping."; break } } set pad [pad_32bit $length] # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" incr current 2 if {![info exists typenames($type)] } { ;# unknown TLV type if { $prmsg } { puts -nonewline "unknown=$type," } incr current $length continue } set typename $typenames($type) set size $typesizes($typename) # 32-bit and 64-bit vals pre-padded if { $size == 4 || $size == 8} { incr current $pad } # read TLV data depending on size switch -exact -- "$size" { 2 { binary scan $data @${current}S vals($typename) } 4 { binary scan $data @${current}I vals($typename) } 8 { binary scan $data @${current}W vals($typename) } 16 { binary scan $data @${current}c16 vals($typename) } -1 { binary scan $data @${current}a${length} vals($typename) } } incr current $length # special handling of data here switch -exact -- "$typename" { delay - jitter { if { $vals($typename) > 2000000 } { array set vals [list $typename 2000000] } } bw { if { $vals($typename) > 1000000000 } { array set vals [list $typename 0] } } per { if { $vals($typename) > 100 } { array set vals [list $typename 100] } } dup { if { $vals($typename) > 50 } { array set vals [list $typename 50] } } emuid1 { if { $emuid1 == -1 } { set emuid $vals($typename) } else { ;# this sets emuid2 if we already have emuid1 array set vals [list emuid2 $vals($typename) ] array set vals [list emuid1 $emuid1 ] } } if1ipv4 - if2ipv4 { array set vals [list $typename \ [ipv4ToString $vals($typename)] ] } if1mac - if2mac { array set vals [list $typename \ [macToString $vals($typename)] ] } if1ipv6 - if2ipv6 { array set vals [list $typename \ [ipv6ToString $vals($typename)] ] } } if { $prmsg } { puts -nonewline "$typename=$vals($typename)," } if { $size == 16 } { incr current $pad } ;# 128-bit vals post-padded if { $size == -1 } { incr current $pad } ;# string vals post-padded } if { $prmsg == 1 } { puts ") " } # perform some sanity checking of the link message if { $vals(node1num) == $vals(node2num) || \ $vals(node1num) < 0 || $vals(node2num) < 0 } { puts -nonewline "link message error - node1=$vals(node1num), " puts "node2=$vals(node2num)" return } # convert node number to node and check for node existance set node1 "n$vals(node1num)" set node2 "n$vals(node2num)" if { [lsearch $node_list $node1] == -1 || \ [lsearch $node_list $node2] == -1 } { puts "Node ($node1/$node2) in link message not found, dropping" return } # set IPv4 and IPv6 address if specified, otherwise may be automatic set prefix1 [chooseIfName $node1 $node2] set prefix2 [chooseIfName $node2 $node1] foreach i "1 2" { # set interface name/number if { $vals(if${i}num) == -1 } { set ifname [newIfc [set prefix${i}] [set node${i}]] set prefixlen [string length [set prefix${i}]] set if${i}num [string range $ifname $prefixlen end] array set vals [list if${i}num [set if${i}num]] } set ifname [set prefix${i}]$vals(if${i}num) array set vals [list if${i}name $ifname] # record IPv4/IPv6 addresses for newGUILink foreach j "4 6" { if { $vals(if${i}ipv${j}) != -1 } { setIfcIPv${j}addr [set node${i}] $ifname \ $vals(if${i}ipv${j})/$vals(if${i}ipv${j}mask) } } if { $vals(if${i}mac) != -1 } { setIfcMacaddr [set node${i}] $ifname $vals(if${i}mac) } } # adopt network address for WLAN (WLAN must be node 1) if { [nodeType $node1] == "wlan" } { set v4addr $vals(if2ipv4) if { $v4addr != -1 } { set v4net [ipv4ToNet $v4addr $vals(if2ipv4mask)] setIfcIPv4addr $node1 wireless "$v4net/$vals(if2ipv4mask)" } set v6addr $vals(if2ipv6) if { $v6addr != -1 } { set v6net [ipv6ToNet $v6addr $vals(if2ipv6mask)] setIfcIPv6addr $node1 wireless "${v6net}::0/$vals(if2ipv6mask)" } } if { $execMode == "batch" } { return ;# no GUI to update in batch mode } # treat 100% loss as link delete if { $flags == 0 && $vals(per) == 100 } { apiLinkDelete $node1 $node2 vals return } # now we have all the information about this node switch -exact -- "$flags" { 0 { apiLinkAddModify $node1 $node2 vals 0 } 1 { apiLinkAddModify $node1 $node2 vals 1 } 2 { apiLinkDelete $node1 $node2 vals } default { puts "LINK: unsupported flags ($flags)"; return } } } # # add or modify a link # if add flag is set, check if two nodes are part of same wlan, and do wlan # linkage, or add a wired link; otherwise modify wired/wireless link with # supplied parameters proc apiLinkAddModify { node1 node2 vals_ref add } { global eid defLinkWidth set c .c upvar $vals_ref vals if {$vals(key) > -1} { if { [nodeType $node1] == "tunnel" } { netconfInsertSection $node1 [list "tunnel-key" $vals(key)] } if { [nodeType $node2] == "tunnel" } { netconfInsertSection $node2 [list "tunnel-key" $vals(key)] } } # look for a wired link in the link list set wired_link [linkByPeers $node1 $node2] if { $wired_link != "" && $add == 0 } { ;# wired link exists, modify it #puts "modify wired link" if { $vals(uni) == 1 } { ;# unidirectional link effects message set peers [linkPeers $wired_link] if { $node1 == [lindex $peers 0] } { ;# downstream n1 <-- n2 set bw [list $vals(bw) [getLinkBandwidth $wired_link up]] set delay [list $vals(delay) [getLinkDelay $wired_link up]] set per [list $vals(per) [getLinkBER $wired_link up]] set dup [list $vals(dup) [getLinkBER $wired_link up]] set jitter [list $vals(jitter) [getLinkJitter $wired_link up]] } else { ;# upstream n1 --> n2 set bw [list [getLinkBandwidth $wired_link] $vals(bw)] set delay [list [getLinkDelay $wired_link] $vals(delay)] set per [list [getLinkBER $wired_link] $vals(per)] set dup [list [getLinkBER $wired_link] $vals(dup)] set jitter [list $vals(jitter) [getLinkJitter $wired_link]] } setLinkBandwidth $wired_link $bw setLinkDelay $wired_link $delay setLinkBER $wired_link $per setLinkDup $wired_link $dup setLinkJitter $wired_link $jitter } else { setLinkBandwidth $wired_link $vals(bw) setLinkDelay $wired_link $vals(delay) setLinkBER $wired_link $vals(per) setLinkDup $wired_link $vals(dup) setLinkJitter $wired_link $vals(jitter) } updateLinkLabel $wired_link updateLinkGuiAttr $wired_link $vals(guiattr) return # if add flag is set and a wired link already exists, assume wlan linkage # special case: rj45 model=1 means link via wireless } elseif {[nodeType $node1] == "rj45" || [nodeType $node2] == "rj45"} { if { [nodeType $node1] == "rj45" } { set rj45node $node1; set othernode $node2; } else { set rj45node $node2; set othernode $node1; } if { [netconfFetchSection $rj45node model] == 1 } { set wlan [findWlanNodes $othernode] if {$wlan != ""} {newGUILink $wlan $rj45node};# link rj4node to wlan } } # no wired link; determine if both nodes belong to the same wlan, and # link them; otherwise add a wired link if add flag is set set wlan $vals(netid) if { $wlan < 0 } { # WLAN not specified with netid, search for common WLAN set wlans1 [findWlanNodes $node1] set wlans2 [findWlanNodes $node2] foreach w $wlans1 { if { [lsearch -exact $wlans2 $w] < 0 } { continue } set wlan $w break } } if { $wlan < 0 } { ;# no common wlan if {$add == 1} { ;# add flag was set - add a wired link global g_newLink_ifhints set g_newLink_ifhints [list $vals(if1name) $vals(if2name)] newGUILink $node1 $node2 if { [getNodeCanvas $node1] != [getNodeCanvas $node2] } { set wired_link [linkByPeersMirror $node1 $node2] } else { set wired_link [linkByPeers $node1 $node2] } setLinkBandwidth $wired_link $vals(bw) setLinkDelay $wired_link $vals(delay) setLinkBER $wired_link $vals(per) setLinkDup $wired_link $vals(dup) setLinkJitter $wired_link $vals(jitter) updateLinkLabel $wired_link updateLinkGuiAttr $wired_link $vals(guiattr) # adopt link effects for WLAN (WLAN must be node 1) if { [nodeType $node1] == "wlan" } { setLinkBandwidth $node1 $vals(bw) setLinkDelay $node1 $vals(delay) setLinkBER $node1 $vals(per) } return } else { ;# modify link, but no wired link or common wlan! puts -nonewline "link modify message received, but no wired link" puts " or wlan for nodes $node1-$node2, dropping" return } } set wlan "n$wlan" drawWlanLink $node1 $node2 $wlan } # # delete a link # proc apiLinkDelete { node1 node2 vals_ref } { global eid upvar $vals_ref vals set c .c # look for a wired link in the link list set wired_link [linkByPeers $node1 $node2] if { $wired_link != "" } { removeGUILink $wired_link non-atomic return } set wlan $vals(netid) if { $wlan < 0 } { # WLAN not specified with netid, search for common WLAN set wlans1 [findWlanNodes $node1] set wlans2 [findWlanNodes $node2] foreach w $wlans1 { if { [lsearch -exact $wlans2 $w] < 0 } { continue } set wlan $w break } } if { $wlan < 0 } { puts "apiLinkDelete: no common WLAN!" return } set wlan "n$wlan" # look for wireless link on the canvas, remove GUI object $c delete -withtags "wlanlink && $node2 && $node1 && $wlan" $c delete -withtags "linklabel && $node2 && $node1 && $wlan" } # # CORE API Execute message TLVs # proc parseExecMessage { data len flags channel } { global node_list curcanvas c router eid showAPI global XSCALE YSCALE XOFFSET YOFFSET set prmsg $showAPI set current 0 # set default values set nodenum 0 set execnum 0 set exectime 0 set execcmd "" set execres "" set execstatus 0 set session "" if { $prmsg==1 } { puts -nonewline "EXEC(flags=$flags," } # parse each TLV while { $current < $len } { # TLV header set typelength [parseTLVHeader $data current] set type [lindex $typelength 0] set length [lindex $typelength 1] if { $length == 0 || $length == "" } { break } set pad [pad_32bit $length] # verbose debugging #puts "exec tlv type=$type length=$length pad=$pad current=$current" if { [expr {$current + $length + $pad}] > $len } { puts "error with EXEC message length (len=$len, TLV length=$length)" break } # TLV data switch -exact -- "$type" { 1 { incr current $pad binary scan $data @${current}I nodenum if { $prmsg==1 } { puts -nonewline "node=$nodenum/" } } 2 { incr current $pad binary scan $data @${current}I execnum if { $prmsg == 1} { puts -nonewline "exec=$execnum," } } 3 { incr current $pad binary scan $data @${current}I exectime if { $prmsg == 1} { puts -nonewline "time=$exectime," } } 4 { binary scan $data @${current}a${length} execcmd if { $prmsg == 1} { puts -nonewline "cmd=$execcmd," } incr current $pad } 5 { binary scan $data @${current}a${length} execres if { $prmsg == 1} { puts -nonewline "res=($length bytes)," } incr current $pad } 6 { incr current $pad binary scan $data @${current}I execstatus if { $prmsg == 1} { puts -nonewline "status=$execstatus," } } 10 { binary scan $data @${current}a${length} session if { $prmsg == 1} { puts -nonewline "session=$session," } incr current $pad } default { if { $prmsg == 1} { puts -nonewline "unknown=" } if { $prmsg == 1} { puts -nonewline "$type," } } } # end switch # advance current pointer incr current $length } if { $prmsg == 1 } { puts ") "} set node "n$nodenum" set node_id "$eid\_$node" # check for node existance if { [lsearch $node_list $node] == -1 } { puts "Execute message but node ($node) does not exist, dropping." return } global $node # Callback support - match execnum from response with original request, and # invoke type-specific callback global g_execRequests foreach type [array names g_execRequests] { set idx [lsearch $g_execRequests($type) $execnum] if { $idx > -1 } { set g_execRequests($type) \ [lreplace $g_execRequests($type) $idx $idx] exec_${type}_callback $node $execnum $execcmd $execres $execstatus return } } } # spawn interactive terminal proc exec_shell_callback { node execnum execcmd execres execstatus } { #puts "opening terminal for $node by running '$execres'" set title "CORE: [getNodeName $node] (console)" set term [get_term_prog false] set xi [string first "xterm -e" $execres] # shell callback already has xterm command, launch it using user-defined # term program (e.g. remote nodes 'ssh -X -f a.b.c.d xterm -e ...' if { $xi > -1 } { set execres [string replace $execres $xi [expr $xi+7] $term] if { [catch {exec sh -c "$execres" & } ] } { puts "Warning: failed to open terminal for $node" } return # no xterm command; execute shell callback in a terminal (e.g. local nodes) } elseif { \ [catch {eval exec $term "$execres" & } ] } { puts "Warning: failed to open terminal for $node: ($term $execres)" } } # # CORE API Register message TLVs # parse register message into plugin capabilities # proc parseRegMessage { data len flags channel } { global regntypes showAPI set prmsg $showAPI set current 0 set str 0 set session "" set fnhint "" set plugin_cap_list {} ;# plugin capabilities list if { $prmsg==1 } { puts -nonewline "REG(flags=$flags," } # parse each TLV while { $current < $len } { # TLV header if { [binary scan $data @${current}cc type length] != 2 } { puts "TLV header error" break } set length [expr {$length & 0xFF}]; # convert signed to unsigned if { $length == 0 } { # prevent endless looping if { $type == 0 } { puts -nonewline "(extra padding)" break } else { puts "Found zero-length TLV for type=$type, dropping." break } } set pad [pad_32bit $length] # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" incr current 2 # TLV data if { [info exists regntypes($type)] } { set plugin_type $regntypes($type) binary scan $data @${current}a${length} str if { $prmsg == 1} { puts -nonewline "$plugin_type=$str," } if { $type == 10 } { ;# session number set session $str } else { lappend plugin_cap_list "$plugin_type=$str" if { $plugin_type == "exec" } { set fnhint $str } } } else { if { $prmsg == 1} { puts -nonewline "unknown($type)," } } incr current $pad # end switch # advance current pointer incr current $length } if { $prmsg == 1 } { puts ") "} # reg message with session number indicates the sid of a session that # was just started from XML or Python script (via reg exec=scriptfile.py) if { $session != "" } { # The channel passed to here is soon after discarded for # sessions that are started from XML or Python scripts. This causes # an exception in the GUI when responding back to daemon if the # response is sent after the channel has been destroyed. Setting # the channel to -1 basically disables the GUI response to the daemon, # but it turns out the daemon does not need the response anyway. set channel -1 # assume session string only contains one session number connectShutdownSession connect $channel $session $fnhint return } set plugin [pluginByChannel $channel] if { [setPluginCapList $plugin $plugin_cap_list] < 0 } { return } # callback to refresh any open dialogs this message may refresh pluginsConfigRefreshCallback } proc parseConfMessage { data len flags channel } { global showAPI node_list MACHINE_TYPES set prmsg $showAPI set current 0 set str 0 set nodenum -1 set obj "" set tflags 0 set types {} set values {} set captions {} set bitmap {} set possible_values {} set groups {} set opaque {} set session "" set netid -1 if { $prmsg==1 } { puts -nonewline "CONF(flags=$flags," } # parse each TLV while { $current < $len } { set typelength [parseTLVHeader $data current] set type [lindex $typelength 0] set length [lindex $typelength 1] set pad [pad_32bit $length] if { $length == 0 || $length == "" } { # allow some zero-length string TLVs if { $type < 5 || $type > 9 } { break } } # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" # TLV data switch -exact -- "$type" { 1 { incr current $pad binary scan $data @${current}I nodenum if { $prmsg == 1} { puts -nonewline "node=$nodenum/" } } 2 { binary scan $data @${current}a${length} obj if { $prmsg == 1} { puts -nonewline "obj=$obj," } incr current $pad } 3 { binary scan $data @${current}S tflags if { $prmsg == 1} { puts -nonewline "cflags=$tflags," } } 4 { set type 0 set types {} if { $prmsg == 1} { puts -nonewline "types=" } # number of 16-bit values set types_len $length # get each 16-bit type value, add to list while {$types_len > 0} { binary scan $data @${current}S type if {$type > 0 && $type < 12} { lappend types $type if { $prmsg == 1} { puts -nonewline "$type/" } } incr current 2 incr types_len -2 } if { $prmsg == 1} { puts -nonewline "," } incr current -$length; # length incremented below incr current $pad } 5 { set values {} binary scan $data @${current}a${length} vals if { $prmsg == 1} { puts -nonewline "vals=$vals," } set values [split $vals |] incr current $pad } 6 { set captions {} binary scan $data @${current}a${length} capt if { $prmsg == 1} { puts -nonewline "capt=$capt," } set captions [split $capt |] incr current $pad } 7 { set bitmap {} binary scan $data @${current}a${length} bitmap if { $prmsg == 1} { puts -nonewline "bitmap," } incr current $pad } 8 { set possible_values {} binary scan $data @${current}a${length} pvals if { $prmsg == 1} { puts -nonewline "pvals=$pvals," } set possible_values [split $pvals |] incr current $pad } 9 { set groups {} binary scan $data @${current}a${length} groupsstr if { $prmsg == 1} { puts -nonewline "groups=$groupsstr," } set groups [split $groupsstr |] incr current $pad } 10 { binary scan $data @${current}a${length} session if { $prmsg == 1} { puts -nonewline "session=$session," } incr current $pad } 35 { incr current $pad binary scan $data @${current}I netid if { $prmsg == 1} { puts -nonewline "netid=$netid/" } } 80 { set opaque {} binary scan $data @${current}a${length} opaquestr if { $prmsg == 1} { puts -nonewline "opaque=$opaquestr," } set opaque [split $opaquestr |] incr current $pad } default { if { $prmsg == 1} { puts -nonewline "unknown=" } if { $prmsg == 1} { puts -nonewline "$type," } } } # end switch # advance current pointer incr current $length } if { $prmsg == 1 } { puts ") "} set objs_ok [concat "services session metadata emane" $MACHINE_TYPES] if { $nodenum > -1 } { set node "n$nodenum" } else { set node "" } # check for node existance if { [lsearch $node_list $node] == -1 } { if { [lsearch $objs_ok $obj] < 0 } { set msg "Configure message for $obj but node ($node) does" set msg "$msg not exist, dropping." puts $msg return } } else { global $node } # for handling node services # this could be improved, instead of checking for the hard-coded object # "services" and opaque data for service customization if { $obj == "services" } { if { $tflags & 0x2 } { ;# update flag if { $opaque != "" } { set services [lindex [split $opaque ":"] 1] set services [split $services ","] customizeServiceValues n$nodenum $values $services } # TODO: save services config with the node } elseif { $tflags & 0x1 } { ;# request flag # TODO: something else } else { popupServicesConfig $channel n$nodenum $types $values $captions \ $possible_values $groups $session } return # metadata received upon XML file load } elseif { $obj == "metadata" } { parseMetaData $values return # session options received upon XML file load } elseif { $obj == "session" && $tflags & 0x2 } { setSessionOptions $types $values return } # handle node machine-type profile if { [lsearch $MACHINE_TYPES $obj] != -1 } { if { $tflags == 0 } { popupNodeProfileConfig $channel n$nodenum $obj $types $values \ $captions $bitmap $possible_values $groups $session \ $opaque } else { puts -nonewline "warning: received Configure message for profile " puts "with unexpected flags!" } return } # update the configuration for a node without displaying dialog box if { $tflags & 0x2 } { if { $obj == "emane" && $node == "" } { set node [lindex [findWlanNodes ""] 0] } if { $node == "" } { puts "ignoring Configure message for $obj with no node" return } # this is similar to popupCapabilityConfigApply setCustomConfig $node $obj $types $values 0 if { $obj != "emane" && [nodeType $node] == "wlan"} { set section [list mobmodel coreapi $obj] netconfInsertSection $node $section } # configuration request - unhandled } elseif { $tflags & 0x1 } { # configuration response data from our request (from GUI plugin configure) } else { popupCapabilityConfig $channel n$nodenum $obj $types $values \ $captions $bitmap $possible_values $groups } } # process metadata received from Conf Message when loading XML proc parseMetaData { values } { global canvas_list annotation_list execMode g_comments foreach value $values { # data looks like this: "annotation a1={iconcoords {514.0 132.0...}}" lassign [splitKeyValue $value] key object_config lassign $key class object # metadata with no object name e.g. comments="Comment text" if { "$class" == "comments" } { set g_comments $object_config continue } elseif { "$class" == "global_options" } { foreach opt $object_config { lassign [split $opt =] key value setGlobalOption $key $value } continue } # metadata having class and object name if {"$class" == "" || $object == ""} { puts "warning: invalid metadata value '$value'" } if { "$class" == "canvas" } { if { [lsearch $canvas_list $object] < 0 } { lappend canvas_list $object } } elseif { "$class" == "annotation" } { if { [lsearch $annotation_list $object] < 0 } { lappend annotation_list $object } } else { puts "metadata parsing error: unknown object class $class" } global $object set $object $object_config } if { $execMode == "batch" } { return } switchCanvas none redrawAll } proc parseFileMessage { data len flags channel } { global showAPI node_list set prmsg $showAPI array set tlvnames { 1 num 2 name 3 mode 4 fno 5 type 6 sname \ 10 session 16 data 17 cdata } array set tlvsizes { num 4 name -1 mode -3 fno 2 type -1 sname -1 \ session -1 data -1 cdata -1 } array set defvals { num -1 name "" mode -1 fno -1 type "" sname "" \ session "" data "" cdata "" } if { $prmsg==1 } { puts -nonewline "FILE(flags=$flags," } array set vals [parseMessage $data $len $flags [array get tlvnames] \ [array get tlvsizes] [array get defvals]] if { $prmsg } { puts ") "} # hook scripts received in File Message if { [string range $vals(type) 0 4] == "hook:" } { global g_hook_scripts set state [string range $vals(type) 5 end] lappend g_hook_scripts [list $vals(name) $state $vals(data)] return } # required fields foreach t "num name data" { if { $vals($t) == $defvals($t) } { puts "Received File Message without $t, dropping."; return; } } # check for node existance set node "n$vals(num)" if { [lsearch $node_list $node] == -1 } { puts "File message but node ($node) does not exist, dropping." return } else { global $node } # service customization received in File Message if { [string range $vals(type) 0 7] == "service:" } { customizeServiceFile $node $vals(name) $vals(type) $vals(data) true } } proc parseEventMessage { data len flags channel } { global showAPI eventtypes g_traffic_start_opt execMode node_list set prmsg $showAPI set current 0 set nodenum -1 set eventtype -1 set eventname "" set eventdata "" set eventtime "" set session "" if { $prmsg==1 } { puts -nonewline "EVENT(flags=$flags," } # parse each TLV while { $current < $len } { set typelength [parseTLVHeader $data current] set type [lindex $typelength 0] set length [lindex $typelength 1] if { $length == 0 || $length == "" } { break } set pad [pad_32bit $length] # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" # TLV data switch -exact -- "$type" { 1 { incr current $pad binary scan $data @${current}I nodenum if { $prmsg == 1} { puts -nonewline "node=$nodenum," } } 2 { incr current $pad binary scan $data @${current}I eventtype if { $prmsg == 1} { set typestr "" foreach t [array names eventtypes] { if { $eventtypes($t) == $eventtype } { set typestr "-$t" break } } puts -nonewline "type=$eventtype$typestr," } } 3 { binary scan $data @${current}a${length} eventname if { $prmsg == 1} { puts -nonewline "name=$eventname," } incr current $pad } 4 { binary scan $data @${current}a${length} eventdata if { $prmsg == 1} { puts -nonewline "data=$eventdata," } incr current $pad } 5 { binary scan $data @${current}a${length} eventtime if { $prmsg == 1} { puts -nonewline "time=$eventtime," } incr current $pad } 10 { binary scan $data @${current}a${length} session if { $prmsg == 1} { puts -nonewline "session=$session," } incr current $pad } default { if { $prmsg == 1} { puts -nonewline "unknown=" } if { $prmsg == 1} { puts -nonewline "$type," } } } # end switch # advance current pointer incr current $length } if { $prmsg == 1 } { puts ") "} # TODO: take other actions here based on Event Message if { $eventtype == 4 } { ;# entered the runtime state if { $g_traffic_start_opt == 1 } { startTrafficScripts } if { $execMode == "batch" } { global g_current_session g_abort_session if {$g_abort_session} { puts "Current session ($g_current_session) aborted. Disconnecting." shutdownSession } else { puts "Session running. Session id is $g_current_session. Disconnecting." } exit.real } } elseif { $eventtype == 6 } { ;# shutdown state set name [lindex [getEmulPlugin "*"] 0] if { [getAssignedRemoteServers] == "" } { # start a new session if not distributed # otherwise we need to allow time for node delete messages # from other servers pluginConnect $name disconnect 1 pluginConnect $name connect 1 } } elseif { $eventtype >= 7 || $eventtype <= 10 } { if { [string range $eventname 0 8] == "mobility:" } { set node "n$nodenum" if {[lsearch $node_list $node] == -1} { puts "Event message with unknown node %nodenum." return } handleMobilityScriptEvent $node $eventtype $eventdata $eventtime } } } proc parseSessionMessage { data len flags channel } { global showAPI g_current_session g_session_dialog_hint execMode set prmsg $showAPI set current 0 set sessionids {} set sessionnames {} set sessionfiles {} set nodecounts {} set sessiondates {} set thumbs {} set sessionopaque {} if { $prmsg==1 } { puts -nonewline "SESSION(flags=$flags," } # parse each TLV while { $current < $len } { set typelength [parseTLVHeader $data current] set type [lindex $typelength 0] set length [lindex $typelength 1] if { $length == 0 || $length == "" } { puts "warning: zero-length TLV, discarding remainder of message!" break } set pad [pad_32bit $length] # verbose debugging #puts "tlv type=$type length=$length pad=$pad current=$current" # TLV data switch -exact -- "$type" { 1 { set sessionids {} binary scan $data @${current}a${length} sids if { $prmsg == 1} { puts -nonewline "sids=$sids," } set sessionids [split $sids |] incr current $pad } 2 { set sessionnames {} binary scan $data @${current}a${length} snames if { $prmsg == 1} { puts -nonewline "names=$snames," } set sessionnames [split $snames |] incr current $pad } 3 { set sessionfiles {} binary scan $data @${current}a${length} sfiles if { $prmsg == 1} { puts -nonewline "files=$sfiles," } set sessionfiles [split $sfiles |] incr current $pad } 4 { set nodecounts {} binary scan $data @${current}a${length} ncs if { $prmsg == 1} { puts -nonewline "ncs=$ncs," } set nodecounts [split $ncs |] incr current $pad } 5 { set sessiondates {} binary scan $data @${current}a${length} sdates if { $prmsg == 1} { puts -nonewline "dates=$sdates," } set sessiondates [split $sdates |] incr current $pad } 6 { set thumbs {} binary scan $data @${current}a${length} th if { $prmsg == 1} { puts -nonewline "thumbs=$th," } set thumbs [split $th |] incr current $pad } 10 { set sessionopaque {} binary scan $data @${current}a${length} sessionopaque if { $prmsg == 1} { puts -nonewline "$sessionopaque," } incr current $pad } default { if { $prmsg == 1} { puts -nonewline "unknown=" } if { $prmsg == 1} { puts -nonewline "$type," } } } # end switch # advance current pointer incr current $length } if { $prmsg == 1 } { puts ") "} if {$g_current_session == 0} { # set the current session to the channel port number set current_session [lindex [fconfigure $channel -sockname] 2] } else { set current_session $g_current_session } if {[lsearch $sessionids $current_session] == -1} { puts -nonewline "*** warning: current session ($g_current_session) " puts "not found in session list: $sessionids" } set orig_session_choice $g_current_session set g_current_session $current_session setGuiTitle "" if {$execMode == "closebatch"} { # we're going to close some session, so this is expected global g_session_choice if {[lsearch $sessionids $g_session_choice] == -1} { puts -nonewline "*** warning: current session ($g_session_choice) " puts "not found in session list: $sessionids" } else { set flags 0x2 ;# delete flag set sid $g_session_choice set name "" set f "" set nodecount "" set thumb "" set user "" sendSessionMessage $channel $flags $sid $name $f $nodecount $thumb $user puts "Session shutdown message sent." } exit.real } if {$orig_session_choice == 0 && [llength $sessionids] == 1} { # we just started up and only the current session exists set g_session_dialog_hint 0 return } if {$execMode == "batch"} { puts "Another session is active." exit.real } if { $g_session_dialog_hint } { popupSessionConfig $channel $sessionids $sessionnames $sessionfiles \ $nodecounts $sessiondates $thumbs $sessionopaque } set g_session_dialog_hint 0 } # parse message TLVs given the possible TLV names and sizes # default values are supplied in defaultvals, parsed values are returned proc parseMessage { data len flags tlvnamesl tlvsizesl defaultvalsl } { global showAPI set prmsg $showAPI array set tlvnames $tlvnamesl array set tlvsizes $tlvsizesl array set vals $defaultvalsl ;# this array is returned set current 0 while { $current < $len } { set typelength [parseTLVHeader $data current] set type [lindex $typelength 0] set length [lindex $typelength 1] if { $length == 0 || $length == "" } { break } set pad [pad_32bit $length] if {![info exists tlvnames($type)] } { ;# unknown TLV type if { $prmsg } { puts -nonewline "unknown=$type," } incr current $length continue } set tlvname $tlvnames($type) set size $tlvsizes($tlvname) # 32-bit and 64-bit vals pre-padded if { $size == 4 || $size == 8 } { incr current $pad } # read TLV data depending on size switch -exact -- "$size" { 2 { binary scan $data @${current}S vals($tlvname) } 4 { binary scan $data @${current}I vals($tlvname) } 8 { binary scan $data @${current}W vals($tlvname) } 16 { binary scan $data @${current}c16 vals($tlvname) } -1 { binary scan $data @${current}a${length} vals($tlvname) } } if { $size == -1 } { incr current $pad } ;# string vals post-padded if { $type == 6 } { incr current $pad } ;# 128-bit vals post-padded incr current $length if { $prmsg } { puts -nonewline "$tlvname=$vals($tlvname)," } } return [array get vals] } proc parseExceptionMessage { data len flags channel } { global showAPI set prmsg $showAPI array set typenames { 1 num 2 sess 3 level 4 src 5 date 6 txt 10 opaque } array set typesizes { num 4 sess -1 level 2 src -1 date -1 txt -1 \ opaque -1 } array set defvals { num -1 sess "" level -1 src "" date "" txt "" opaque ""} if { $prmsg==1 } { puts -nonewline "EXCEPTION(flags=$flags," } array set vals [parseMessage $data $len $flags [array get typenames] \ [array get typesizes] [array get defvals]] if { $prmsg == 1 } { puts ") "} if { $vals(level) == $defvals(level) } { puts "Exception Message received without an exception level."; return; } receiveException [array get vals] } proc sendNodePosMessage { channel node nodeid x y wlanid force } { global showAPI set prmsg $showAPI if { $channel == -1 } { set channel [lindex [getEmulPlugin $node] 2] if { $channel == -1 } { return } } set node_num [string range $node 1 end] set x [format "%u" [expr int($x)]] set y [format "%u" [expr int($y)]] set len [expr 8+4+4] ;# node number, x, y if {$nodeid > -1} { incr len 8 } if {$wlanid > -1} { incr len 8 } if {$force == 1 } { set crit 0x4 } else { set crit 0x0 } #puts "sending [expr $len+4] bytes: $nodeid $x $y $wlanid" if { $prmsg == 1 } { puts -nonewline ">NODE(flags=$crit,$node,x=$x,y=$y" } set msg [binary format ccSc2sIc2Sc2S \ 1 $crit $len \ {1 4} 0 $node_num \ {0x20 2} $x \ {0x21 2} $y ] set msg2 "" set msg3 "" if { $nodeid > -1 } { if { $prmsg == 1 } { puts -nonewline ",emuid=$nodeid" } set msg2 [binary format c2sI {0x23 4} 0 $nodeid] } if { $wlanid > -1 } { if { $prmsg == 1 } { puts -nonewline ",netid=$wlanid" } set msg3 [binary format c2sI {0x24 4} 0 $wlanid] } if { $prmsg == 1 } { puts ")" } puts -nonewline $channel $msg$msg2$msg3 flushChannel channel "Error sending node position" } # build a new node proc sendNodeAddMessage { channel node } { global showAPI CORE_DATA_DIR set prmsg $showAPI set len [expr {8+8+4+4}]; # node number, type, x, y set ipv4 0 set ipv6 0 set macstr "" set wireless 0 # type, name set type [getNodeTypeAPI $node] set model [getNodeModel $node] set model_len [string length $model] set model_pad_len [pad_32bit $model_len] set model_pad [binary format x$model_pad_len] set name [getNodeName $node] set name_len [string length $name] set name_pad_len [pad_32bit $name_len] set name_pad [binary format x$name_pad_len] incr len [expr { 2+$name_len+$name_pad_len}] if {$model_len > 0} { incr len [expr {2+$model_len+$model_pad_len }] } set node_num [string range $node 1 end] # fixup node type for EMANE-enabled WLAN nodes set opaque "" if { [isEmane $node] } { set type 0xA } # emulation server (node location) set emusrv [getNodeLocation $node] set emusrv_len [string length $emusrv] set emusrv_pad_len [pad_32bit $emusrv_len] set emusrv_pad [binary format x$emusrv_pad_len] if { $emusrv_len > 0 } { incr len [expr {2+$emusrv_len+$emusrv_pad_len } ] } # canvas set canv [getNodeCanvas $node] if { $canv != "c1" } { set canv [string range $canv 1 end] ;# convert "c2" to "2" incr len 4 } else { set canv "" } # services set svc [getNodeServices $node false] set svc [join $svc "|"] set svc_len [string length $svc] set svc_pad_len [pad_32bit $svc_len] set svc_pad [binary format x$svc_pad_len] if { $svc_len > 0 } { incr len [expr {2+$svc_len+$svc_pad_len } ] } # icon set icon [getCustomImage $node] if { [file dirname $icon] == "$CORE_DATA_DIR/icons/normal" } { set icon [file tail $icon] ;# don't include standard icon path } set icon_len [string length $icon] set icon_pad_len [pad_32bit $icon_len] set icon_pad [binary format x$icon_pad_len] if { $icon_len > 0 } { incr len [expr {2+$icon_len+$icon_pad_len} ] } # opaque data set opaque_len [string length $opaque] set opaque_pad_len [pad_32bit $opaque_len] set opaque_pad [binary format x$opaque_pad_len] if { $opaque_len > 0 } { incr len [expr {2+$opaque_len+$opaque_pad_len} ] } # length must be calculated before this if { $prmsg == 1 } { puts -nonewline ">NODE(flags=add/str,$node,type=$type,$name," } set msg [binary format c2Sc2sIc2sIcc \ {0x1 0x11} $len \ {0x1 4} 0 $node_num \ {0x2 4} 0 $type \ 0x3 $name_len ] puts -nonewline $channel $msg$name$name_pad # IPv4 address if { $ipv4 > 0 } { if { $prmsg == 1 } { puts -nonewline "$ipv4str," } set msg [binary format c2sI {0x4 4} 0 $ipv4] puts -nonewline $channel $msg } # MAC address if { $macstr != "" } { if { $prmsg == 1 } { puts -nonewline "$macstr," } set mac [join [split $macstr ":"] ""] puts -nonewline $channel [binary format c2x2W {0x5 8} 0x$mac] } # IPv6 address if { $ipv6 != 0 } { if { $prmsg == 1 } { puts -nonewline "$ipv6str," } set msg [binary format c2 {0x6 16} ] puts -nonewline $channel $msg foreach ipv6w [split $ipv6 ":"] { set msg [binary format S 0x$ipv6w] puts -nonewline $channel $msg } puts -nonewline $channel [binary format x2]; # 2 bytes padding } # model type if { $model_len > 0 } { set mh [binary format cc 0x7 $model_len] puts -nonewline $channel $mh$model$model_pad if { $prmsg == 1 } { puts -nonewline "m=$model," } } # emulation server if { $emusrv_len > 0 } { puts -nonewline $channel [binary format cc 0x8 $emusrv_len] puts -nonewline $channel $emusrv$emusrv_pad if { $prmsg == 1 } { puts -nonewline "srv=$emusrv," } } # X,Y coordinates set coords [getNodeCoords $node] set x [format "%u" [expr int([lindex $coords 0])]] set y [format "%u" [expr int([lindex $coords 1])]] set msg [binary format c2Sc2S {0x20 2} $x {0x21 2} $y] puts -nonewline $channel $msg # canvas if { $canv != "" } { if { $prmsg == 1 } { puts -nonewline "canvas=$canv," } set msg [binary format c2S {0x22 2} $canv] puts -nonewline $channel $msg } if { $prmsg == 1 } { puts -nonewline "x=$x,y=$y" } # services if { $svc_len > 0 } { puts -nonewline $channel [binary format cc 0x25 $svc_len] puts -nonewline $channel $svc$svc_pad if { $prmsg == 1 } { puts -nonewline ",svc=$svc" } } # icon if { $icon_len > 0 } { puts -nonewline $channel [binary format cc 0x42 $icon_len] puts -nonewline $channel $icon$icon_pad if { $prmsg == 1 } { puts -nonewline ",icon=$icon" } } # opaque data if { $opaque_len > 0 } { puts -nonewline $channel [binary format cc 0x50 $opaque_len] puts -nonewline $channel $opaque$opaque_pad if { $prmsg == 1 } { puts -nonewline ",opaque=$opaque" } } if { $prmsg == 1 } { puts ")" } flushChannel channel "Error sending node add" } # delete a node proc sendNodeDelMessage { channel node } { global showAPI set prmsg $showAPI set len 8; # node number set node_num [string range $node 1 end] if { $prmsg == 1 } { puts ">NODE(flags=del/str,$node_num)" } set msg [binary format c2Sc2sI \ {0x1 0x12} $len \ {0x1 4} 0 $node_num ] puts -nonewline $channel $msg flushChannel channel "Error sending node delete" } # send a message to build, modify, or delete a link # type should indicate add/delete/link/unlink proc sendLinkMessage { channel link type {sendboth true} } { global showAPI set prmsg $showAPI set node1 [lindex [linkPeers $link] 0] set node2 [lindex [linkPeers $link] 1] set if1 [ifcByPeer $node1 $node2]; set if2 [ifcByPeer $node2 $node1] if { [nodeType $node1] == "pseudo" } { return } ;# never seems to occur if { [nodeType $node2] == "pseudo" } { set mirror2 [getLinkMirror $node2] set node2 [getNodeName $node2] if { [string range $node1 1 end] > [string range $node2 1 end] } { return ;# only send one link message (for two pseudo-links) } set if2 [ifcByPeer $node2 $mirror2] } set node1_num [string range $node1 1 end] set node2_num [string range $node2 1 end] # flag for sending unidirectional link messages set uni 0 if { $sendboth && [isLinkUni $link] } { set uni 1 } # set flags and link message type from supplied type parameter set flags 0 set ltype 1 ;# add/delete a link (not wireless link/unlink) set netid -1 if { $type == "add" || $type == "link" } { set flags 1 } elseif { $type == "delete" || $type == "unlink" } { set flags 2 } if { $type == "link" || $type == "unlink" } { set ltype 0 ;# a wireless link/unlink event set tmp [getLinkOpaque $link net] if { $tmp != "" } { set netid [string range $tmp 1 end] } } set key "" if { [nodeType $node1] == "tunnel" } { set key [netconfFetchSection $node1 "tunnel-key"] if { $key == "" } { set key 1 } } if {[nodeType $node2] == "tunnel" } { set key [netconfFetchSection $node2 "tunnel-key"] if { $key == "" } { set key 1 } } if { $prmsg == 1 } { puts -nonewline ">LINK(flags=$flags,$node1_num-$node2_num," } # len = node1num, node2num, type set len [expr {8+8+8}] set delay [getLinkDelay $link] if { $delay == "" } { set delay 0 } set jitter [getLinkJitter $link] if { $jitter == "" } { set jitter 0 } set bw [getLinkBandwidth $link] if { $bw == "" } { set bw 0 } set per [getLinkBER $link]; # PER and BER if { $per == "" } { set per 0 } set per_len 0 set per_msg [buildStringTLV 0x5 $per per_len] set dup [getLinkDup $link] if { $dup == "" } { set dup 0 } set dup_len 0 set dup_msg [buildStringTLV 0x6 $dup dup_len] if { $type != "delete" } { incr len [expr {12+12+$per_len+$dup_len+12}] ;# delay,bw,per,dup,jitter if {$prmsg==1 } { puts -nonewline "$delay,$bw,$per,$dup,$jitter," } } # TODO: mer, burst, mburst if { $prmsg == 1 } { puts -nonewline "type=$ltype," } if { $uni } { incr len 4 if { $prmsg == 1 } { puts -nonewline "uni=$uni," } } if { $netid > -1 } { incr len 8 if { $prmsg == 1 } { puts -nonewline "netid=$netid," } } if { $key != "" } { incr len 8 if { $prmsg == 1 } { puts -nonewline "key=$key," } } set if1num [ifcNameToNum $if1]; set if2num [ifcNameToNum $if2] set if1ipv4 0; set if2ipv4 0; set if1ipv6 ""; set if2ipv6 ""; set if1ipv4mask 0; set if2ipv4mask 0; set if1ipv6mask ""; set if2ipv6mask ""; set if1mac ""; set if2mac ""; if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \ [nodeType $node1] == "tunnel") } { incr len 4 if { $prmsg == 1 } { puts -nonewline "if1n=$if1num," } if { $type != "delete" } { getIfcAddrs $node1 $if1 if1ipv4 if1ipv6 if1mac if1ipv4mask \ if1ipv6mask len } } if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \ [nodeType $node2] == "tunnel") } { incr len 4 if { $prmsg == 1 } { puts -nonewline "if2n=$if2num," } if { $type != "delete" } { getIfcAddrs $node2 $if2 if2ipv4 if2ipv6 if2mac if2ipv4mask \ if2ipv6mask len } } # start building the binary message on channel # length must be calculated before this set msg [binary format ccSc2sIc2sI \ {0x2} $flags $len \ {0x1 4} 0 $node1_num \ {0x2 4} 0 $node2_num ] puts -nonewline $channel $msg if { $type != "delete" } { puts -nonewline $channel [binary format c2sW {0x3 8} 0 $delay] puts -nonewline $channel [binary format c2sW {0x4 8} 0 $bw] puts -nonewline $channel $per_msg puts -nonewline $channel $dup_msg puts -nonewline $channel [binary format c2sW {0x7 8} 0 $jitter] } # TODO: mer, burst, mburst # link type puts -nonewline $channel [binary format c2sI {0x20 4} 0 $ltype] # unidirectional flag if { $uni } { puts -nonewline $channel [binary format c2S {0x22 2} $uni] } # network ID if { $netid > -1 } { puts -nonewline $channel [binary format c2sI {0x24 4} 0 $netid] } if { $key != "" } { puts -nonewline $channel [binary format c2sI {0x25 4} 0 $key] } # interface 1 info if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \ [nodeType $node1] == "tunnel") } { puts -nonewline $channel [ binary format c2S {0x30 2} $if1num ] } if { $if1ipv4 > 0 } { puts -nonewline $channel [binary format c2sIc2S \ {0x31 4} 0 $if1ipv4 {0x32 2} $if1ipv4mask ] } if { $if1mac != "" } { set if1mac [join [split $if1mac ":"] ""] puts -nonewline $channel [binary format c2x2W {0x33 8} 0x$if1mac] } if {$if1ipv6 != ""} { puts -nonewline $channel [binary format c2 {0x34 16}] foreach ipv6w [split $if1ipv6 ":"] { puts -nonewline $channel \ [binary format S 0x$ipv6w] } puts -nonewline $channel [binary format x2c2S {0x35 2} $if1ipv6mask] } # interface 2 info if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \ [nodeType $node2] == "tunnel") } { puts -nonewline $channel [ binary format c2S {0x36 2} $if2num ] } if { $if2ipv4 > 0 } { puts -nonewline $channel [binary format c2sIc2S \ {0x37 4} 0 $if2ipv4 {0x38 2} $if2ipv4mask ] } if { $if2mac != "" } { set if2mac [join [split $if2mac ":"] ""] puts -nonewline $channel [binary format c2x2W {0x39 8} 0x$if2mac] } if {$if2ipv6 != ""} { puts -nonewline $channel [binary format c2 {0x40 16}] foreach ipv6w [split $if2ipv6 ":"] { puts -nonewline $channel \ [binary format S 0x$ipv6w] } puts -nonewline $channel [binary format x2c2S {0x41 2} $if2ipv6mask] } if { $prmsg==1 } { puts ")" } flushChannel channel "Error sending link message" ########################################################## # send a second Link Message for unidirectional link effects if { $uni < 1 } { return } # first calculate length and possibly print the message set flags 0 if { $prmsg == 1 } { puts -nonewline ">LINK(flags=$flags,$node2_num-$node1_num," } set len [expr {8+8+8}] ;# len = node2num, node1num (swapped), type set delay [getLinkDelay $link up] if { $delay == "" } { set delay 0 } set jitter [getLinkJitter $link up] if { $jitter == "" } { set jitter 0 } set bw [getLinkBandwidth $link up] if { $bw == "" } { set bw 0 } set per [getLinkBER $link up]; # PER and BER if { $per == "" } { set per 0 } set per_len 0 set per_msg [buildStringTLV 0x5 $per per_len] set dup [getLinkDup $link up] if { $dup == "" } { set dup 0 } set dup_len 0 set dup_msg [buildStringTLV 0x6 $dup dup_len] incr len [expr {12+12+$per_len+$dup_len+12}] ;# delay,bw,per,dup,jitter if {$prmsg==1 } { puts -nonewline "$delay,$bw,$per,$dup,$jitter," } if { $prmsg == 1 } { puts -nonewline "type=$ltype," } incr len 4 ;# unidirectional flag if { $prmsg == 1 } { puts -nonewline "uni=$uni," } # note that if1num / if2num are reversed here due to reversed node nums if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \ [nodeType $node2] == "tunnel") } { incr len 4 if { $prmsg == 1 } { puts -nonewline "if1n=$if2num," } } if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \ [nodeType $node1] == "tunnel") } { incr len 4 if { $prmsg == 1 } { puts -nonewline "if2n=$if1num," } } # build and send the link message set msg [binary format ccSc2sIc2sI \ {0x2} $flags $len \ {0x1 4} 0 $node2_num \ {0x2 4} 0 $node1_num ] puts -nonewline $channel $msg puts -nonewline $channel [binary format c2sW {0x3 8} 0 $delay] puts -nonewline $channel [binary format c2sW {0x4 8} 0 $bw] puts -nonewline $channel $per_msg puts -nonewline $channel $dup_msg puts -nonewline $channel [binary format c2sW {0x7 8} 0 $jitter] puts -nonewline $channel [binary format c2sI {0x20 4} 0 $ltype] puts -nonewline $channel [binary format c2S {0x22 2} $uni] if { $if2num >= 0 && ([[typemodel $node2].layer] == "NETWORK" || \ [nodeType $node2] == "tunnel") } { puts -nonewline $channel [ binary format c2S {0x30 2} $if2num ] } if { $if1num >= 0 && ([[typemodel $node1].layer] == "NETWORK" || \ [nodeType $node1] == "tunnel") } { puts -nonewline $channel [ binary format c2S {0x36 2} $if1num ] } if { $prmsg==1 } { puts ")" } flushChannel channel "Error sending link message" } # helper to get IPv4, IPv6, MAC address and increment length # also prints TLV-style addresses if showAPI is true proc getIfcAddrs { node ifc ipv4p ipv6p macp ipv4maskp ipv6maskp lenp } { global showAPI upvar $ipv4p ipv4 upvar $ipv6p ipv6 upvar $macp mac upvar $ipv4maskp ipv4mask upvar $ipv6maskp ipv6mask upvar $lenp len if { $ifc == "" || $node == "" } { return } # IPv4 address set ipv4str [getIfcIPv4addr $node $ifc] if {$ipv4str != ""} { set ipv4 [lindex [split $ipv4str /] 0] if { [info exists ipv4mask ] } { set ipv4mask [lindex [split $ipv4str / ] 1] incr len 12; # 8 addr + 4 mask if { $showAPI == 1 } { puts -nonewline "$ipv4str," } } else { incr len 8; # 8 addr if { $showAPI == 1 } { puts -nonewline "$ipv4," } } set ipv4 [stringToIPv4 $ipv4]; # convert to integer } # IPv6 address set ipv6str [getIfcIPv6addr $node $ifc] if {$ipv6str != ""} { set ipv6 [lindex [split $ipv6str /] 0] if { [info exists ipv6mask ] } { set ipv6mask [lindex [split $ipv6str / ] 1] incr len 24; # 20 addr + 4 mask if { $showAPI == 1 } { puts -nonewline "$ipv6str," } } else { incr len 20; # 20 addr if { $showAPI == 1 } { puts -nonewline "$ipv6," } } set ipv6 [expandIPv6 $ipv6]; # convert to long string } # MAC address (from conf if there, otherwise generated) if { [info exists mac] } { set mac [lindex [getIfcMacaddr $node $ifc] 0] if {$mac == ""} { set mac [getNextMac] } if { $showAPI == 1 } { puts -nonewline "$mac," } incr len 12; } } # # Register Message: (registration types) # This is a simple Register Message, types is an array of # tuples. proc sendRegMessage { channel flags types_list } { global showAPI regtypes set prmsg $showAPI if { $channel == -1 || $channel == "" } { set plugin [lindex [getEmulPlugin "*"] 0] set channel [pluginConnect $plugin connect true] if { $channel == -1 } { return } } set len 0 array set types $types_list # array names output is unreliable, sort it set type_list [lsort -dict [array names types]] foreach type $type_list { if { ![info exists regtypes($type)] } { puts "sendRegMessage: unknown registration type '$type'" return -1 } set str_$type $types($type) set str_${type}_len [string length [set str_$type]] set str_${type}_pad_len [pad_32bit [set str_${type}_len]] set str_${type}_pad [binary format x[set str_${type}_pad_len]] incr len [expr { 2 + [set str_${type}_len] + [set str_${type}_pad_len]}] } if { $prmsg == 1 } { puts ">REG($type_list)" } # message header set msg1 [binary format ccS 4 $flags $len] puts -nonewline $channel $msg1 foreach type $type_list { set type_num $regtypes($type) set tlvh [binary format cc $type_num [set str_${type}_len]] puts -nonewline $channel $tlvh[set str_${type}][set str_${type}_pad] } flushChannel channel "Error: API channel was closed" } # # Configuration Message: (object, type flags, node) # This is a simple Configuration Message containing flags proc sendConfRequestMessage { channel node model flags netid opaque } { global showAPI set prmsg $showAPI if { $channel == -1 || $channel == "" } { set pname [lindex [getEmulPlugin $node] 0] set channel [pluginConnect $pname connect true] if { $channel == -1 } { return } } set model_len [string length $model] set model_pad_len [pad_32bit $model_len] set model_pad [binary format x$model_pad_len ] set len [expr {4+2+$model_len+$model_pad_len}] # optional network ID to provide Netgraph mapping if { $netid != -1 } { incr len 8 } # convert from node name to number if { [string is alpha [string range $node 0 0]] } { set node [string range $node 1 end] } if { $node > 0 } { incr len 8 } # add a session number when configuring services set session "" set session_len 0 set session_pad_len 0 set session_pad "" if { $node <= 0 && $model == "services" } { global g_current_session set session [format "0x%x" $g_current_session] set session_len [string length $session] set session_pad_len [pad_32bit $session_len] set session_pad [binary format x$session_pad_len] incr len [expr {2 + $session_len + $session_pad_len}] } # opaque data - used when custom configuring services set opaque_len 0 set msgop [buildStringTLV 0x50 $opaque opaque_len] if { $opaque_len > 0 } { incr len $opaque_len } if { $prmsg == 1 } { puts -nonewline ">CONF(flags=0," if { $node > 0 } { puts -nonewline "node=$node," } puts -nonewline "obj=$model,cflags=$flags" if { $session != "" } { puts -nonewline ",session=$session" } if { $netid > -1 } { puts -nonewline ",netid=$netid" } if { $opaque_len > 0 } { puts -nonewline ",opaque=$opaque" } puts ") request" } # header, node node number, node model header set msg1 [binary format c2S {5 0} $len ] set msg1b "" if { $node > 0 } { set msg1b [binary format c2sI {1 4} 0 $node] } set msg1c [binary format cc 2 $model_len] # request flag set msg2 [binary format c2S {3 2} $flags ] # session number set msg3 "" if { $session != "" } { set msg3 [binary format cc 0x0A $session_len] set msg3 $msg3$session$session_pad } # network ID set msg4 "" if { $netid != -1 } { set msg4 [binary format c2sI {0x23 4} 0 0x$netid ] } #catch {puts -nonewline $channel $msg1$model$model_pad$msg2$msg3$msg4$msg5} puts -nonewline $channel $msg1$msg1b$msg1c$model$model_pad$msg2$msg3$msg4 if { $opaque_len > 0 } { puts -nonewline $channel $msgop } flushChannel channel "Error: API channel was closed" } # # Configuration Message: (object, type flags, node, types, values) # This message is more complicated to build because of the list of # data types and values. proc sendConfReplyMessage { channel node model types values opaque } { global showAPI set prmsg $showAPI # convert from node name to number if { [string is alpha [string range $node 0 0]] } { set node [string range $node 1 end] } # add a session number when configuring services set session "" set session_len 0 set session_pad_len 0 set session_pad "" if { $node <= 0 && $model == "services" && $opaque == "" } { global g_current_session set session [format "0x%x" $g_current_session] set session_len [string length $session] set session_pad_len [pad_32bit $session_len] set session_pad [binary format x$session_pad_len] incr len [expr {$session_len + $session_pad_len}] } if { $prmsg == 1 } { puts -nonewline ">CONF(flags=0," if {$node > -1 } { puts -nonewline "node=$node," } puts -nonewline "obj=$model,cflags=0" if {$session != "" } { puts -nonewline "session=$session," } if {$opaque != "" } { puts -nonewline "opaque=$opaque," } puts "types=<$types>,values=<$values>) reply" } # types (16-bit values) and values set n 0 set type_len [expr {[llength $types] * 2} ] set type_data [binary format cc 4 $type_len] set value_data "" foreach type $types { set t [binary format S $type] set type_data $type_data$t set val [lindex $values $n] if { $val == "" } { #puts "warning: empty value $n (type=$type)" if { $type != 10 } { set val 0 } } incr n lappend value_data $val }; # end foreach set value_len 0 set value_data [join $value_data |] set msgval [buildStringTLV 0x5 $value_data value_len] set type_pad_len [pad_32bit $type_len] set type_pad [binary format x$type_pad_len ] set model_len [string length $model] set model_pad_len [pad_32bit $model_len] set model_pad [binary format x$model_pad_len ] # opaque data - used when custom configuring services set opaque_len 0 set msgop [buildStringTLV 0x50 $opaque opaque_len] # 4 bytes header, model TLV set len [expr 4+2+$model_len+$model_pad_len] if { $node > -1 } { incr len 8 } # session number set msg3 "" if { $session != "" } { incr len [expr {2 + $session_len + $session_pad_len }] set msg3 [binary format cc 0x0A $session_len] set msg3 $msg3$session$session_pad } if { $opaque_len > 0 } { incr len $opaque_len } # types TLV, values TLV incr len [expr {2 + $type_len + $type_pad_len + $value_len}] # header, node node number, node model header set msgh [binary format c2S {5 0} $len ] set msgwl "" if { $node > -1 } { set msgwl [binary format c2sI {1 4} 0 $node] } set model_hdr [binary format cc 2 $model_len] # no flags set type_hdr [binary format c2S {3 2} 0 ] set msg $msgh$msgwl$model_hdr$model$model_pad$type_hdr$type_data$type_pad set msg $msg$msgval$msg3 puts -nonewline $channel $msg if { $opaque_len > 0 } { puts -nonewline $channel $msgop } flushChannel channel "Error sending conf reply" } # Event Message proc sendEventMessage { channel type nodenum name data flags } { global showAPI eventtypes set prmsg $showAPI set len [expr 8] ;# event type if {$nodenum > -1} { incr len 8 } set name_len [string length $name] set name_pad_len [pad_32bit $name_len] if { $name_len > 0 } { incr len [expr {2 + $name_len + $name_pad_len}] } set data_len [string length $data] set data_pad_len [pad_32bit $data_len] if { $data_len > 0 } { incr len [expr {2 + $data_len + $data_pad_len}] } if { $prmsg == 1 } { puts -nonewline ">EVENT(flags=$flags," } set msg [binary format ccS 8 $flags $len ] ;# message header set msg2 "" if { $nodenum > -1 } { if { $prmsg == 1 } { puts -nonewline "node=$nodenum," } set msg2 [binary format c2sI {0x01 4} 0 $nodenum] } if { $prmsg == 1} { set typestr "" foreach t [array names eventtypes] { if { $eventtypes($t) == $type } { set typestr "-$t"; break } } puts -nonewline "type=$type$typestr," } set msg3 [binary format c2sI {0x02 4} 0 $type] set msg4 "" set msg5 "" if { $name_len > 0 } { if { $prmsg == 1 } { puts -nonewline "name=$name," } set msg4 [binary format cc 0x03 $name_len ] set name_pad [binary format x$name_pad_len ] set msg5 $name$name_pad } set msg6 "" set msg7 "" if { $data_len > 0 } { if { $prmsg == 1 } { puts -nonewline "data=$data" } set msg6 [binary format cc 0x04 $data_len ] set data_pad [binary format x$data_pad_len ] set msg7 $data$data_pad } if { $prmsg == 1 } { puts ")" } puts -nonewline $channel $msg$msg2$msg3$msg4$msg5$msg6$msg7 flushChannel channel "Error sending Event type=$type" } # deploy working configuration using CORE API # Deploys a current working configuration. It creates all the # nodes and link as defined in configuration file. proc deployCfgAPI { sock } { global eid global node_list link_list annotation_list canvas_list global mac_byte4 mac_byte5 global execMode global ngnodemap global mac_addr_start global deployCfgAPI_lock global eventtypes global g_comments if { ![info exists deployCfgAPI_lock] } { set deployCfgAPI_lock 0 } if { $deployCfgAPI_lock } { puts "***error: deployCfgAPI called while deploying config" return } set deployCfgAPI_lock 1 ;# lock set mac_byte4 0 set mac_byte5 0 if { [info exists mac_addr_start] } { set mac_byte5 $mac_addr_start } set t_start [clock seconds] global systype set systype [lindex [checkOS] 0] statgraph on [expr (2*[llength $node_list]) + [llength $link_list]] sendSessionProperties $sock # this tells the CORE services that we are starting to send # configuration data # clear any existing config sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0 # inform CORE services about emulation servers, hook scripts, canvas info, # and services sendEventMessage $sock $eventtypes(configuration_state) -1 "" "" 0 sendEmulationServerInfo $sock 0 sendSessionOptions $sock sendHooks $sock sendCanvasInfo $sock sendNodeTypeInfo $sock 0 # send any custom service info before the node messages sendNodeCustomServices $sock # send Node add messages for all emulation nodes foreach node $node_list { set node_id "$eid\_$node" set type [nodeType $node] set name [getNodeName $node] if { $type == "pseudo" } { continue } statgraph inc 1 statline "Creating node $name" if { [[typemodel $node].layer] == "NETWORK" } { nodeHighlights .c $node on red } # inform the CORE daemon of the node sendNodeAddMessage $sock $node pluginCapsInitialize $node "mobmodel" writeNodeCoords $node [getNodeCoords $node] } # send Link add messages for all network links for { set pending_links $link_list } { $pending_links != "" } {} { set link [lindex $pending_links 0] set i [lsearch -exact $pending_links $link] set pending_links [lreplace $pending_links $i $i] statgraph inc 1 set lnode1 [lindex [linkPeers $link] 0] set lnode2 [lindex [linkPeers $link] 1] if { [nodeType $lnode2] == "router" && \ [getNodeModel $lnode2] == "remote" } { continue; # remote routers are ctrl. by GUI; TODO: move to daemon } sendLinkMessage $sock $link add } # GUI-specific meta-data send via Configure Messages if { [llength $annotation_list] > 0 } { sendMetaData $sock $annotation_list "annotation" } sendMetaData $sock $canvas_list "canvas" ;# assume >= 1 canvas # global GUI options - send as meta-data set obj "metadata" set values [getGlobalOptionList] sendConfReplyMessage $sock -1 $obj "10" "{global_options=$values}" "" if { [info exists g_comments] && $g_comments != "" } { sendConfReplyMessage $sock -1 $obj "10" "{comments=$g_comments}" "" } # status bar graph statgraph off 0 statline "Network topology instantiated in [expr [clock seconds] - $t_start] seconds ([llength $node_list] nodes and [llength $link_list] links)." # TODO: turn on tcpdump if enabled; customPostConfigCommands; # addons 4 deployCfgHook # draw lines between wlan nodes # initialization does not work earlier than this foreach node $node_list { # WLAN handling: draw lines between wireless nodes if { [nodeType $node] == "wlan" && $execMode == "interactive" } { wlanRunMobilityScript $node } } sendTrafficScripts $sock # tell the CORE services that we are ready to instantiate sendEventMessage $sock $eventtypes(instantiation_state) -1 "" "" 0 set deployCfgAPI_lock 0 ;# unlock statline "Network topology instantiated in [expr [clock seconds] - $t_start] seconds ([llength $node_list] nodes and [llength $link_list] links)." } # # emulation shutdown procedure when using the CORE API proc shutdownSession {} { global link_list node_list eid eventtypes execMode set nodecount [getNodeCount] if { $nodecount == 0 } { # This allows switching to edit mode without extra API messages, # such as when file new is selected while running an existing session. return } # prepare the channel set plugin [lindex [getEmulPlugin "*"] 0] set sock [pluginConnect $plugin connect true] sendEventMessage $sock $eventtypes(datacollect_state) -1 "" "" 0 # shut down all links foreach link $link_list { set lnode2 [lindex [linkPeers $link] 1] if { [nodeType $lnode2] == "router" && \ [getNodeModel $lnode2] == "remote" } { continue; # remote routers are ctrl. by GUI; TODO: move to daemon } sendLinkMessage $sock $link delete false } # shut down all nodes foreach node $node_list { set type [nodeType $node] if { [[typemodel $node].layer] == "NETWORK" && $execMode != "batch" } { nodeHighlights .c $node on red } sendNodeDelMessage $sock $node pluginCapsDeinitialize $node "mobmodel" deleteNodeCoords $node } sendNodeTypeInfo $sock 1 sendEmulationServerInfo $sock 1 } # inform the CORE services about the canvas information to support # conversion between X,Y and lat/long coordinates proc sendCanvasInfo { sock } { global curcanvas if { ![info exists curcanvas] } { return } ;# batch mode set obj "location" set scale [getCanvasScale $curcanvas] set refpt [getCanvasRefPoint $curcanvas] set refx [lindex $refpt 0] set refy [lindex $refpt 1] set latitude [lindex $refpt 2] set longitude [lindex $refpt 3] set altitude [lindex $refpt 4] set types [list 2 2 10 10 10 10] set values [list $refx $refy $latitude $longitude $altitude $scale] sendConfReplyMessage $sock -1 $obj $types $values "" } # inform the CORE services about the default services for a node type, which # are used when node-specific services have not been configured for a node proc sendNodeTypeInfo { sock reset } { global node_list set obj "services" if { $reset == 1} { sendConfRequestMessage $sock -1 "all" 0x3 -1 "" return } # build a list of node types in use set typesinuse "" foreach node $node_list { set type [nodeType $node] if { $type != "router" } { continue } set model [getNodeModel $node] if { [lsearch $typesinuse $model] < 0 } { lappend typesinuse $model } } foreach type $typesinuse { # build a list of type + enabled services, all strings set values [getNodeTypeServices $type] set values [linsert $values 0 $type] set types [string repeat "10 " [llength $values]] sendConfReplyMessage $sock -1 $obj $types $values "" # send any custom profiles for a node type; node type passed in opaque set machine_type [getNodeTypeMachineType $type] set values [getNodeTypeProfile $type] if { $values != "" } { set types [string repeat "10 " [llength $values]] sendConfReplyMessage $sock -1 $machine_type $types $values \ "$machine_type:$type" } } } # inform the CORE services about any services that have been customized for # a particular node proc sendNodeCustomServices { sock } { global node_list foreach node $node_list { set cfgs [getCustomConfig $node] set cfgfiles "" foreach cfg $cfgs { set ids [split [getConfig $cfg "custom-config-id"] :] if { [lindex $ids 0] != "service" } { continue } if { [llength $ids] == 3 } { # customized service config file -- build a list lappend cfgfiles $cfg continue } set s [lindex $ids 1] set values [getConfig $cfg "config"] set t [string repeat "10 " [llength $values]] sendConfReplyMessage $sock $node services $t $values "service:$s" } # send customized service config files after the service info foreach cfg $cfgfiles { set idstr [getConfig $cfg "custom-config-id"] set ids [split $idstr :] if { [lindex $ids 0] != "service" } { continue } set s [lindex $ids 1] set filename [lindex $ids 2] set data [join [getConfig $cfg "config"] "\n"] sendFileMessage $sock $node "service:$s" $filename "" $data \ [string length $data] } } } # publish hooks to the CORE services proc sendHooks { sock } { global g_hook_scripts if { ![info exists g_hook_scripts] } { return } foreach hook $g_hook_scripts { set name [lindex $hook 0] set state [lindex $hook 1] set data [lindex $hook 2] # TODO: modify sendFileMessage to make node number optional sendFileMessage $sock n0 "hook:$state" $name "" $data \ [string length $data] } } # inform the CORE services about the emulation servers that will be used proc sendEmulationServerInfo { sock reset } { global exec_servers set node -1 ;# not used set obj "broker" set servernames [getAssignedRemoteServers] if { $servernames == "" } { return } ;# not using emulation servers if { $reset == 1} { sendConfRequestMessage $sock $node $obj 0x3 -1 "" return } set servers "" foreach servername $servernames { set host [lindex $exec_servers($servername) 0] set port [lindex $exec_servers($servername) 1] lappend servers "$servername:$host:$port" } set serversstring [join $servers ,] set types [list 10] set values [list $serversstring] sendConfReplyMessage $sock $node $obj $types $values "" } # returns the length of node_list minus any pseudo-nodes (inter-canvas nodes) proc getNodeCount {} { global node_list set nodecount 0 foreach node $node_list { if { [nodeType $node] != "pseudo" } { incr nodecount } } return $nodecount } # send basic properties of a session proc sendSessionProperties { sock } { global currentFile CORE_DATA_DIR CORE_USER set sessionname [file tail $currentFile] set nodecount [getNodeCount] if { $sessionname == "" } { set sessionname "untitled" } set tf "/tmp/thumb.jpg" if { ![writeCanvasThumbnail .c $tf] } { set src "$CORE_DATA_DIR/icons/normal/thumb-unknown.gif" set tf "/tmp/thumb.gif" if [catch { file copy $src $tf } e] { puts -nonewline "warning: failed to copy $src to $tf\n($e)" set tf "" } } set user $CORE_USER sendSessionMessage $sock 0 0 $sessionname $currentFile $nodecount $tf $user } # send session options from global array in Config Message proc sendSessionOptions { sock } { if { $sock == -1 } { set sock [lindex [getEmulPlugin "*"] 2] } set values [getSessionOptionsList] set types [string repeat "10 " [llength $values]] sendConfReplyMessage $sock -1 "session" $types $values "" } # send annotations as key=value metadata in Config Message proc sendAnnotations { sock } { global annotation_list if { $sock == -1 } { set sock [lindex [getEmulPlugin "*"] 2] } set values "" foreach a $annotation_list { global $a set val [set $a] lappend values "annotation $a=$val" } set types [string repeat "10 " [llength $values]] sendConfReplyMessage $sock -1 "metadata" $types $values "" } # send items as key=value metadata in Config Message proc sendMetaData { sock items itemtype } { if { $sock == -1 } { set sock [lindex [getEmulPlugin "*"] 2] } set values "" foreach i $items { global $i set val [set $i] lappend values "$itemtype $i=$val" } set types [string repeat "10 " [llength $values]] sendConfReplyMessage $sock -1 "metadata" $types $values "" } # send an Event message for the definition state (this clears any existing # state), then send all node and link definitions to the CORE services proc sendNodeLinkDefinitions { sock } { global node_list link_list annotation_list canvas_list eventtypes global g_comments #sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0 foreach node $node_list { sendNodeAddMessage $sock $node pluginCapsInitialize $node "mobmodel" } foreach link $link_list { sendLinkMessage $sock $link add } # GUI-specific meta-data send via Configure Messages sendMetaData $sock $annotation_list "annotation" sendMetaData $sock $canvas_list "canvas" set obj "metadata" set values [getGlobalOptionList] sendConfReplyMessage $sock -1 $obj "10" "{global_options=$values}" "" if { [info exists g_comments] && $g_comments != "" } { sendConfReplyMessage $sock -1 $obj "10" "{comments=$g_comments}" "" } } proc getNodeTypeAPI { node } { set type [nodeType $node] if { $type == "router" } { set model [getNodeModel $node] set type [getNodeTypeMachineType $model] } switch -exact -- "$type" { router { return 0x0 } netns { return 0x0 } jail { return 0x0 } physical { return 0x1 } xen { return 0x2 } tbd { return 0x3 } lanswitch { return 0x4 } hub { return 0x5 } wlan { return 0x6 } rj45 { return 0x7 } tunnel { return 0x8 } ktunnel { return 0x9 } emane { return 0xA } default { return 0x0 } } } # send an Execute message proc sendExecMessage { channel node cmd exec_num flags } { global showAPI g_api_exec_num set prmsg $showAPI set node_num [string range $node 1 end] set cmd_len [string length $cmd] if { $cmd_len > 255 } { puts "sendExecMessage error: cmd too long!"; return} set cmd_pad_len [pad_32bit $cmd_len] set cmd_pad [binary format x$cmd_pad_len] if { $exec_num == 0 } { incr g_api_exec_num set exec_num $g_api_exec_num } # node num + exec num + command string set len [expr {8 + 8 + 2 + $cmd_len + $cmd_pad_len}] if { $prmsg == 1 } {puts ">EXEC(flags=$flags,$node,n=$exec_num,cmd='$cmd')" } set msg [binary format ccSc2sIc2sIcc \ 3 $flags $len \ {1 4} 0 $node_num \ {2 4} 0 $exec_num \ 4 $cmd_len \ ] puts -nonewline $channel $msg$cmd$cmd_pad flushChannel channel "Error sending file message" } # if source file (sf) is specified, then send a message that the file source # file should be copied to the given file name (f); otherwise, include the file # data in this message proc sendFileMessage { channel node type f sf data data_len } { global showAPI set prmsg $showAPI set node_num [string range $node 1 end] set f_len [string length $f] set f_pad_len [pad_32bit $f_len] set f_pad [binary format x$f_pad_len] set type_len [string length $type] set type_pad_len [pad_32bit $type_len] set type_pad [binary format x$type_pad_len] if { $sf != "" } { set sf_len [string length $sf] set sf_pad_len [pad_32bit $sf_len] set sf_pad [binary format x$sf_pad_len] set data_len 0 set data_pad_len 0 } else { set sf_len 0 set sf_pad_len 0 set data_pad_len [pad_32bit $data_len] set data_pad [binary format x$data_pad_len] } # TODO: gzip compression w/tlv type 0x11 # node number TLV + file name TLV + ( file src name / data TLV) set len [expr {8 + 2 + 2 + $f_len + $f_pad_len + $sf_len + $sf_pad_len \ + $data_len + $data_pad_len}] # 16-bit data length if { $data_len > 255 } { incr len 2 if { $data_len > 65536 } { puts -nonewline "*** error: File Message data length too large " puts "($data_len > 65536)" return } } if { $type_len > 0 } { incr len [expr {2 + $type_len + $type_pad_len}] } set flags 1; # add flag if { $prmsg == 1 } { puts -nonewline ">FILE(flags=$flags,$node,f=$f," if { $type != "" } { puts -nonewline "type=$type," } if { $sf != "" } { puts "src=$sf)" } else { puts "data=($data_len))" } } set msg [binary format ccSc2sIcc \ 6 $flags $len \ {1 4} 0 $node_num \ 2 $f_len \ ] set msg2 "" if { $type_len > 0 } { set msg2 [binary format cc 0x5 $type_len] set msg2 $msg2$type$type_pad } if { $sf != "" } { ;# source file name TLV set msg3 [binary format cc 0x6 $sf_len] puts -nonewline $channel $msg$f$f_pad$msg2$msg3$sf$sf_pad } else { ;# file data TLV if { $data_len > 255 } { set msg3 [binary format ccS 0x10 0 $data_len] } else { set msg3 [binary format cc 0x10 $data_len] } puts -nonewline $channel $msg$f$f_pad$msg2$msg3$data$data_pad } flushChannel channel "Error sending file message" } # Session Message proc sendSessionMessage { channel flags num name sfile nodecount tf user } { global showAPI set prmsg $showAPI if { $channel == -1 } { set pname [lindex [getEmulPlugin "*"] 0] set channel [pluginConnect $pname connect true] if { $channel == -1 } { return } } set num_len [string length $num] set num_pad_len [pad_32bit $num_len] set len [expr {2 + $num_len + $num_pad_len}] if { $num_len <= 0 } { puts "error: sendSessionMessage requires at least one session number" return } set name_len [string length $name] set name_pad_len [pad_32bit $name_len] if { $name_len > 0 } { incr len [expr { 2 + $name_len + $name_pad_len }] } set sfile_len [string length $sfile] set sfile_pad_len [pad_32bit $sfile_len] if { $sfile_len > 0 } { incr len [expr { 2 + $sfile_len + $sfile_pad_len }] } set nc_len [string length $nodecount] set nc_pad_len [pad_32bit $nc_len] if { $nc_len > 0 } { incr len [expr { 2 + $nc_len + $nc_pad_len }] } set tf_len [string length $tf] set tf_pad_len [pad_32bit $tf_len] if { $tf_len > 0 } { incr len [expr { 2 + $tf_len + $tf_pad_len }] } set user_len [string length $user] set user_pad_len [pad_32bit $user_len] if { $user_len > 0 } { incr len [expr { 2 + $user_len + $user_pad_len }] } if { $prmsg == 1 } { puts -nonewline ">SESSION(flags=$flags" } set msgh [binary format ccS 0x09 $flags $len ] ;# message header if { $prmsg == 1 } { puts -nonewline ",sids=$num" } set num_hdr [binary format cc 0x01 $num_len] set num_pad [binary format x$num_pad_len ] set msg1 "$num_hdr$num$num_pad" set msg2 "" if { $name_len > 0 } { if { $prmsg == 1 } { puts -nonewline ",name=$name" } # TODO: name_len > 255 set name_hdr [binary format cc 0x02 $name_len] set name_pad [binary format x$name_pad_len] set msg2 "$name_hdr$name$name_pad" } set msg3 "" if { $sfile_len > 0 } { if { $prmsg == 1 } { puts -nonewline ",file=$sfile" } # TODO: sfile_len > 255 set sfile_hdr [binary format cc 0x03 $sfile_len] set sfile_pad [binary format x$sfile_pad_len] set msg3 "$sfile_hdr$sfile$sfile_pad" } set msg4 "" if { $nc_len > 0 } { if { $prmsg == 1 } { puts -nonewline ",nc=$nodecount" } set nc_hdr [binary format cc 0x04 $nc_len] set nc_pad [binary format x$nc_pad_len] set msg4 "$nc_hdr$nodecount$nc_pad" } set msg5 "" if { $tf_len > 0 } { if { $prmsg == 1 } { puts -nonewline ",thumb=$tf" } set tf_hdr [binary format cc 0x06 $tf_len] set tf_pad [binary format x$tf_pad_len] set msg5 "$tf_hdr$tf$tf_pad" } set msg6 "" if { $user_len > 0 } { if { $prmsg == 1 } { puts -nonewline ",user=$user" } set user_hdr [binary format cc 0x07 $user_len] set user_pad [binary format x$user_pad_len] set msg6 "$user_hdr$user$user_pad" } if { $prmsg == 1 } { puts ")" } puts -nonewline $channel $msgh$msg1$msg2$msg3$msg4$msg5$msg6 flushChannel channel "Error sending Session num=$num" } # return a new execution number and record it in the execution request list # for the given callback (e.g. widget) type proc newExecCallbackRequest { type } { global g_api_exec_num g_execRequests incr g_api_exec_num set exec_num $g_api_exec_num lappend g_execRequests($type) $exec_num return $exec_num } # ask daemon to load or save an XML file based on the current session proc xmlFileLoadSave { cmd name } { global oper_mode eventtypes set plugin [lindex [getEmulPlugin "*"] 0] set sock [pluginConnect $plugin connect true] if { $sock == -1 || $sock == "" } { return } # inform daemon about nodes and links when saving in edit mode if { $cmd == "save" && $oper_mode != "exec" } { sendSessionProperties $sock # this tells the CORE services that we are starting to send # configuration data # clear any existing config sendEventMessage $sock $eventtypes(definition_state) -1 "" "" 0 sendEventMessage $sock $eventtypes(configuration_state) -1 "" "" 0 sendEmulationServerInfo $sock 0 sendSessionOptions $sock sendHooks $sock sendCanvasInfo $sock sendNodeTypeInfo $sock 0 # send any custom service info before the node messages sendNodeCustomServices $sock sendNodeLinkDefinitions $sock } elseif { $cmd == "open" } { # reset config objects sendNodeTypeInfo $sock 1 } sendEventMessage $sock $eventtypes(file_$cmd) -1 $name "" 0 } ############################################################################ # # Helper functions below here # # helper function to get interface number from name proc ifcNameToNum { ifc } { # eth0, eth1, etc. if {[string range $ifc 0 2] == "eth"} { set ifnum [string range $ifc 3 end] # l0, l1, etc. } else { set ifnum [string range $ifc 1 end] } if { $ifnum == "" } { return -1 } if {![string is integer $ifnum]} { return -1 } return $ifnum } # # parse the type and length from a TLV header proc parseTLVHeader { data current_ref } { global showAPI set prmsg $showAPI upvar $current_ref current if { [binary scan $data @${current}cc type length] != 2 } { if { $prmsg == 1 } { puts "TLV header error" } return "" } set length [expr {$length & 0xFF}]; # convert signed to unsigned if { $length == 0 } { if { $type == 0 } { # prevent endless looping if { $prmsg == 1 } { puts -nonewline "(extra padding)" } return "" } else { # support for length > 255 incr current 2 if { [binary scan $data @${current}S length] != 1 } { puts "error reading TLV length (type=$type)" return "" } set length [expr {$length & 0xFFFF}] if { $length == 0 } { # zero-length string, not length > 255 incr current -2 } } } incr current 2 return [list $type $length] } # return the binary string, and length by reference proc buildStringTLV { type data len_ref } { upvar $len_ref len set data_len [string length $data] if { $data_len > 65536 } { puts "warning: buildStringTLV data truncated" set data_len 65536 set data [string range 0 65535] } set data_pad_len [pad_32bit $data_len] set data_pad [binary format x$data_pad_len] if { $data_len == 0 } { set len 0 return "" } if { $data_len > 255 } { set hdr [binary format ccS $type 0 $data_len] set hdr_len 4 } else { set hdr [binary format cc $type $data_len] set hdr_len 2 } set len [expr {$hdr_len + $data_len + $data_pad_len}] return $hdr$data$data_pad } # calculate padding to 32-bit word boundary # 32-bit and 64-bit values are pre-padded, strings and 128-bit values are # post-padded to word boundary, depending on type proc pad_32bit { len } { # total length = 2 + len + pad if { $len < 256 } { set hdrsiz 2 } else { set hdrsiz 4 } # calculate padding to fill 32-bit boundary return [expr { -($hdrsiz + $len) % 4 }] } proc macToString { mac_num } { set mac_bytes "" # convert 64-bit integer into 12-digit hex string set mac_num 0x[format "%.12lx" $mac_num] while { $mac_num > 0 } { # append 8-bit hex number to list set uchar [format "%02x" [expr $mac_num & 0xFF]] lappend mac_bytes $uchar # shift off 8-bits set mac_num [expr $mac_num >> 8] } # make sure we have six hex digits set num_zeroes [expr 6 - [llength $mac_bytes]] while { $num_zeroes > 0 } { lappend mac_bytes 00 incr num_zeroes -1 } # this is lreverse in tcl8.5 and later set r {} set i [llength $mac_bytes] while { $i > 0 } { lappend r [lindex $mac_bytes [incr i -1]] } return [join $r :] } proc hexdump { data } { # read data as hex binary scan $data H* hex # split into pairs of hex digits regsub -all -- {..} $hex {& } hex return $hex }