#
# Copyright 2004-2008 University of Zagreb, Croatia.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# This work was supported in part by Croatian Ministry of Science
# and Technology through the research contract #IP-2003-143.
#


#****h* imunes/nodecfg.tcl
# NAME
#  nodecfg.tcl -- file used for manipultaion with nodes in IMUNES
# FUNCTION
#  This module is used to define all the actions used for configuring 
#  nodes in IMUNES. The definition of nodes is presented in NOTES
#  section.
#
# NOTES
#  The IMUNES configuration file contains declarations of IMUNES objects.
#  Each object declaration contains exactly the following three fields:
#
#     object_class object_id class_specific_config_string
#
#  Currently only two object classes are supported: node and link. In the
#  future we plan to implement a canvas object, which should allow placing
#  other objects into multiple visual maps.
#
#  "node" objects are further divided by their type, which can be one of
#  the following:
#  * router
#  * host
#  * pc
#  * lanswitch
#  * hub
#  * rj45
#  * pseudo
#
#  The following node types are to be implemented in the future:
#  * text
#  * image
#
#
# Routines for manipulation of per-node network configuration files
# IMUNES keeps per-node network configuration in an IOS / Zebra / Quagga
# style format.
#
# Network configuration is embedded in each node's config section via the
# "network-config" statement. The following functions can be used to
# manipulate the per-node network config:
#
# netconfFetchSection { node_id sectionhead }
#	Returns a section of a config file starting with the $sectionhead
#	line, and ending with the first occurence of the "!" sign.
#
# netconfClearSection { node_id sectionhead }
#	Removes the appropriate section from the config.
#
# netconfInsertSection { node_id section }
#	Inserts a section in the config file. Sections beginning with the
#	"interface" keyword are inserted at the head of the config, and
#	all other sequences are simply appended to the config tail.
#
# getIfcOperState { node_id ifc }
#	Returns "up" or "down".
#
# setIfcOperState { node_id ifc state }
#	Sets the new interface state. Implicit default is "up".
#
#Boeing:
# getIfcDumpState { node_id ifc }
#	Returns "tcpdump on" or "tcpdump off".
#
#Boeing:
# setIfcDumpState { node_id ifc state }
#	Sets the tcpdump state for the interface
#
# getIfcQDisc { node_id ifc }
# getIfcQDisc { node_id ifc }
#	Returns "FIFO", "WFQ" or "DRR".
#
# setIfcQDisc { node_id ifc qdisc }
#	Sets the new queuing discipline. Implicit default is FIFO.
#
# getIfcQDrop { node_id ifc }
#	Returns "drop-tail" or "drop-head".
#
# setIfcQDrop { node_id ifc qdrop }
#	Sets the new queuing discipline. Implicit default is "drop-tail".
#
# getIfcQLen { node_id ifc }
#	Returns the queue length limit in packets.
#
# setIfcQLen { node_id ifc len }
#	Sets the new queue length limit.
#
# getIfcMTU { node_id ifc }
#	Returns the configured MTU, or an empty string if default MTU is used.
#
# setIfcMTU { node_id ifc mtu }
#	Sets the new MTU. Zero MTU value denotes the default MTU.
#
# getIfcIPv4addr { node_id ifc }
#	Returns a list of all IPv4 addresses assigned to an interface.
#
# setIfcIPv4addr { node_id ifc addr }
#	Sets a new IPv4 address(es) on an interface. The correctness of the
#	IP address format is not checked / enforced.
#
# getIfcIPv6addr { node_id ifc }
#	Returns a list of all IPv6 addresses assigned to an interface.
#
# setIfcIPv6addr { node_id ifc addr }
#	Sets a new IPv6 address(es) on an interface. The correctness of the
#	IP address format is not checked / enforced.
#
# getStatIPv4routes { node_id }
#	Returns a list of all static IPv4 routes as a list of
#	{destination gateway {metric}} pairs.
#
# setStatIPv4routes { node_id route_list }
#	Replace all current static route entries with a new one, in form of
#	a list, as described above.
#
# getStatIPv6routes { node_id }
#	Returns a list of all static IPv6 routes as a list of
#	{destination gateway {metric}} pairs.
#
# setStatIPv6routes { node_id route_list }
#	Replace all current static route entries with a new one, in form of
#	a list, as described above.
#
# getNodeName { node_id }
#	Returns node's logical name.
#
# setNodeName { node_id name }
#	Sets a new node's logical name.
#
# nodeType { node_id }
#	Returns node's type.
#
# getNodeModel { node_id }
#	Returns node's optional model identifier.
#
# setNodeModel { node_id model }
#	Sets the node's optional model identifier.
#
# getNodeCanvas { node_id }
#	Returns node's canvas affinity.
#
# setNodeCanvas { node_id canvas_id }
#	Sets the node's canvas affinity.
#
# getNodeCoords { node_id }
#	Return icon coords.
#
# setNodeCoords { node_id coords }
#	Sets the coordinates.
#
# getNodeLabelCoords { node_id }
#	Return node label coordinates.
#
# setNodeLabelCoords { node_id coords }
#	Sets the label coordinates.
#
# getNodeCPUConf { node_id }
#	Returns node's CPU scheduling parameters { minp maxp weight }.
#
# setNodeCPUConf { node_id param_list }
#	Sets the node's CPU scheduling parameters.
#
# ifcList { node_id }
#	Returns a list of all interfaces present in a node.
#
# peerByIfc { node_id ifc }
#	Returns id of the node on the other side of the interface
#
# logicalPeerByIfc { node_id ifc }
#	Returns id of the logical node on the other side of the interface.
#
# ifcByPeer { local_node_id peer_node_id }
#	Returns the name of the interface connected to the specified peer 
#       if the peer is on the same canvas, otherwise returns an empty string.
#
# ifcByLogicalPeer { local_node_id peer_node_id }
#	Returns the name of the interface connected to the specified peer.
#	Returns the right interface even if the peer node is on the other
#	canvas.
#
# hasIPv4Addr { node_id }
# hasIPv6Addr { node_id }
#	Returns true if at least one interface has an IPv{4|6} address
#	configured, otherwise returns false.
#
# removeNode { node_id }
#	Removes the specified node as well as all the links that bind 
#       that node to any other node.
#
# newIfc { ifc_type node_id }
#	Returns the first available name for a new interface of the 
#       specified type.
#
# All of the above functions are independent to any Tk objects. This means
# they can be used for implementing tasks external to GUI, so inside the
# GUI any updating of related Tk objects (such as text labels etc.) will
# have to be implemented by additional Tk code.
#
# Additionally, an alternative configuration can be specified in 
# "custom-config" section.
#
# getCustomConfig { node_id }
#
# setCustomConfig { node_id cfg }
#
# getCustomEnabled { node_id }
#
# setCustomEnabled { node_id state }
#****

#****f* nodecfg.tcl/typemodel
# NAME
#   typemodel -- find node's type and routing model 
# SYNOPSIS
#   set typemod [typemodel $node_id]
# FUNCTION
#   For input node this procedure returns the node's  
#   type and routing model (if exists) 
# INPUTS
#   * node_id -- node id
# RESULT
#   * typemod -- returns node's type and routing model in form type.model
#****

proc typemodel { node } {
    return [nodeType $node]
}

#****f* nodecfg.tcl/getNodeLocation
# NAME
#   getNodeLocation -- get location of the node
# SYNOPSIS
#   set location [getNodeLocation $node_id]
# FUNCTION
#   For input node this procedure returns the name of the CORE box 
#   controlling the node.
# INPUTS
#   * node_id -- node id
# RESULT
#   * location -- returns the location of the node
#****

proc getNodeLocation { node } {
    global $node

    set loc_tmp [lindex [lsearch -inline [set $node] "location *"] 1]
    return $loc_tmp
}



#****f* nodecfg.tcl/setNodeLocation
# NAME
#   setNodeLocation -- set location of the node
# SYNOPSIS
#   setNodeLocation $node_id $location
# FUNCTION
#   For input node this procedure sets the name of the CORE box 
#   controlling the node.
# INPUTS
#   * node_id -- node id
#   * location -- the name of the CORE box controlling the node
#****

proc setNodeLocation { node location } {
    global $node

    set i [lsearch [set $node] "location *"]
    if { $i >= 0 } {
        set $node [lreplace [set $node] $i $i]
    }

    if { $location == "" } { return }
    
    lappend $node [list location $location]

    return
}

# returns true if any connected peer has the specified location
proc nodePeerHasLocation { node location } {
    foreach ifc [ifcList $node] {
	set peer [peerByIfc $node $ifc]
	if { [getNodeLocation $peer] == $location } {
	    return 1
	}
    }
    return 0
}


#****f* nodecfg.tcl/setConfig
# NAME
#   setConfig -- add an element to the *-config
#		structure
# SYNOPSIS
#   setConfig $strlist $str
# FUNCTION
#   Procedure returns requested element that belongs
#	to *-config structure. 
# INPUTS
#    * strlist -- *-config structure
#    * cfg -- current *-config that will be extended
#	with new elements
#    * str -- new element
# RESULT
#    * strlist -- new *-config sructure
#****

proc setConfig { strlist cfg str } {

    set i [lsearch $strlist "$str *"]

    if { $i < 0 } {
	if { $cfg != {} } {
	    set newcfg [list $str $cfg]
	    lappend strlist $newcfg
	}
    } else {
	set oldval [lindex [lsearch -inline $strlist "$str *"] 1]
	if { $oldval != $cfg } {
	    set strlist [lreplace $strlist $i $i [list $str $cfg]]
	}
    }

    return $strlist
}


#****f* nodecfg.tcl/getConfig
# NAME
#   getConfig -- get an element of the *-config
# SYNOPSIS
#   getConfig $strlist $str
# FUNCTION
#	Procedure returns requested element that belongs
#		to *-config structure. 
# INPUTS
#   * strlist -- *-config structure
#	* str -- an element of the *-config structure
#****

proc getConfig { strlist str } {

    return [lindex [lsearch -inline $strlist "$str *"] 1]
}


#****f* nodecfg.tcl/getCustomEnabled
# NAME
#   getCustomEnabled -- get custom configuration enabled state 
# SYNOPSIS
#   set enabled [getCustomEnabled $node_id]
# FUNCTION
#   For input node this procedure returns true if custom configuration
#   is enabled for the specified node. 
# INPUTS
#   * node_id -- node id
# RESULT
#   * enabled -- returns true if custom configuration is enabled 
#****

proc getCustomEnabled { node } {
    global $node

    if { [lindex [lsearch -inline [set $node] "custom-enabled *"] 1] == true } {
	return true
    } else {
	return false
    }
}

#****f* nodecfg.tcl/setCustomEnabled
# NAME
#   setCustomEnabled -- set custom configuration enabled state 
# SYNOPSIS
#   setCustomEnabled $node_id $enabled
# FUNCTION
#   For input node this procedure enables or disables custom configuration.
# INPUTS
#   * node_id -- node id
#   * enabled -- true if enabling custom configuration, false if disabling 
#****

proc setCustomEnabled { node enabled } {
    global $node

    set i [lsearch [set $node] "custom-enabled *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i]
    }
    if { $enabled == true } {
	lappend $node [list custom-enabled $enabled]
    }
}

#****f* nodecfg.tcl/getCustomCmd
# NAME
#   getCustomCmd -- get custom configuration command 
# SYNOPSIS
#   set command [getCustomCmd $node_id]
# FUNCTION
#   For input node this procedure returns custom command.
# INPUTS
#   * node_id -- node id
# RESULT
#   * command -- custom configuration command 
#****

proc getCustomCmd { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "custom-command *"] 1]
}

#****f* nodecfg.tcl/setCustomCmd
# NAME
#   setCustomEnabled -- set custom configuration command 
# SYNOPSIS
#   setCustomCmd $node_id $command
# FUNCTION
#   For input node this procedure sets custom command.
# INPUTS
#   * node_id -- node id
#   * command -- custom configuration command 
#****

proc setCustomCmd { node cmd } {
    global $node

    set i [lsearch [set $node] "custom-command *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i]
    }
	
    lappend $node [list custom-command $cmd]
}

#****f* nodecfg.tcl/getCustomConfig
# NAME
#   getCustomConfig -- get custom configuration section 
# SYNOPSIS
#   set cfg [getCustomConfig $node_id]
# FUNCTION
#   For input node this procedure returns custom configuration section.
# INPUTS
#   * node_id -- node id
# RESULT
#   * cfg -- returns custom configuration section 
#****

proc getCustomConfig { node } {
    global $node
    set customCfgList {}
	
    set customcmd ""
    set customcfg ""
    set customcmd [lsearch -inline [set $node] "custom-command *"]
    set customcmdval [lindex $customcmd 1]
    set customcfg [lsearch -inline [set $node] "custom-config *"]
    set customcfgval [lindex $customcfg 1]
    if { $customcmd != "" } {
	set customid [list custom-config-id generic]
	set customcmd [list custom-command $customcmdval]
	set customcfg [list config $customcfgval]
	set customCfgList [list [list $customid $customcmd $customcfg]]
    } else {
	set values [lsearch -all -inline [set $node] "custom-config *"]
	foreach val $values {
	    lappend customCfgList [lindex $val 1]
    	}
    }

    return $customCfgList
}


proc getCustomConfigByID { node id } {
    set customCfgList [getCustomConfig $node]
    foreach element $customCfgList {
	if { $id == [getConfig $element "custom-config-id"] } {
	    return [getConfig $element "config"]
	}
    }
    return ""
}

#****f* nodecfg.tcl/setCustomConfig
# NAME
#   setCustomConfig -- set custom configuration command 
# SYNOPSIS
#   setCustomConfig $node_id $cfg
# FUNCTION
#   For input node this procedure sets custom configuration section.
# INPUTS
#   * node_id -- node id
#   * id -- custom-config id
#   * cmd -- custom command
#   * cfg -- custom configuration section 
#   * delete -- if delete is set to 1, setCustomConfig is invoked
#	to delete custom-config with custom-config-id $id
#   * 
#****

proc setCustomConfig { node id cmd cfg delete } {
    global viewcustomid
    global $node

    # removes first occurrence of custom-command and custom-config
    set i [lsearch [set $node] "custom-command *"]
    if { $i != "-1" } {
	# remove custom-command
	set $node [lreplace [set $node] $i $i]
	# remove custom-config
	set j [lsearch [set $node] "custom-config *"]
	set $node [lreplace [set $node] $j $j]
    }
	
    # removes existing custom-config if custom-config-id matches
    set cnt 0
    set indices [lsearch -all [set $node] "custom-config *"]
    foreach i $indices {
	set tmp [lindex [set $node] $i]
	set customCfg [lindex $tmp 1]
	set cid [lindex [lsearch -inline $customCfg "custom-config-id *"] 1]

	if { $cid == $id } {
	    set $node [lreplace [set $node] $i $i]
	}
    }
	
    # adds the new config specified in the dialog box
    if { $delete == 0 } {
	if { $cfg != {} && $cmd != {} && $id != {} } {
	    set newid [list custom-config-id $id]
	    set viewcustomid [lindex $newid 1]
	    set newcmd [list custom-command $cmd]
	    set newcfg [list config $cfg]
	    # Boeing: insert the new custom config so it's the first (active) 
	    # custom config in the list, or just add it to the end
	    set first [lindex $indices 0]
	    if { $first < 0 } {
	        set first end
	    }
	    set $node [linsert [set $node] $first \
	    		[ list custom-config [list $newid $newcmd $newcfg] ]]
	    #lappend $node [ list custom-config [list $newid $newcmd $newcfg] ]
	}
    }
}

#****f* nodecfg.tcl/netconfFetchSection
# NAME
#   netconfFetchSection -- fetch the network configuration section 
# SYNOPSIS
#   set section [netconfFetchSection $node_id $sectionhead]
# FUNCTION
#   Returns a section of a network part of a configuration file starting with the $sectionhead
#   line, and ending with the first occurrence of the "!" sign.
# INPUTS
#   * node_id     -- node id
#   * sectionhead -- represents the first line of the section in network-config part of
#     the configuration file
# RESULT
#   * section -- returns a part of the configuration file between sectionhead and "!"
#****

proc netconfFetchSection { node sectionhead } {
    global $node

    set cfgmode global
    set section {}
    # Boeing: read custom config if enabled
    if { [lindex [lsearch -inline [set $node] "custom-enabled *"] 1] == true } {
        # this will match the first custom-config encountered
        set netconf [lindex [lsearch -inline [set $node] "custom-config *"] 1]
	set tmp [lindex [lsearch -inline $netconf "config *"] 1]
	if {$tmp != "" } { set netconf $tmp }
    } else {
        set netconf [lindex [lsearch -inline [set $node] "network-config *"] 1]
    }
    # if 'nocustom' keyword is passed in sectionhead, don't use custom-config
    if { [lsearch $sectionhead "nocustom"] > -1 } {
        # remove "nocustom" from sectionhead
        set sectionhead [lsearch -all -inline -not $sectionhead "nocustom"]
        # do not read custom config
        set netconf [lindex [lsearch -inline [set $node] "network-config *"] 1]
    }
    # end Boeing
    foreach line $netconf {
        if { $cfgmode == "section" } {
            if { "$line" == "!" } {
                return $section
            }
            lappend section "$line"
            continue
        }
        if { "$line" == "$sectionhead" } {
            set cfgmode section
        # Boeing: search the first part of $line for $sectionhead
        } elseif { "router bgp" == "$sectionhead" } { 
            if { [string first $sectionhead $line 0] == 0 } {
                set cfgmode section
                lappend section "$line"
            }
        }
    }
}

#****f* nodecfg.tcl/netconfClearSection
# NAME
#   netconfClearSection -- clear the section from a network-config part
# SYNOPSIS
#   netconfClearSection $node_id $sectionhead
# FUNCTION
#   Removes the appropriate section from the network part of the configuration.
# INPUTS
#   * node_id     -- node id
#   * sectionhead -- represents the first line of the section that is to be removed from network-config 
#     part of the configuration.
#****

proc netconfClearSection { node sectionhead } {
    global $node

    set i [lsearch [set $node] "network-config *"]
    set netconf [lindex [lindex [set $node] $i] 1]
    set lnum_beg -1
    set lnum_end 0
    foreach line $netconf {
	if { $lnum_beg == -1 && "$line" == "$sectionhead" } {
	    set lnum_beg $lnum_end
	}
	if { $lnum_beg > -1 && "$line" == "!" } {
	    set netconf [lreplace $netconf $lnum_beg $lnum_end]
	    set $node [lreplace [set $node] $i $i \
		[list network-config $netconf]]
	    return
	}
	incr lnum_end
    }
}

#****f* nodecfg.tcl/netconfInsertSection
# NAME
#   netconfInsertSection -- Insert the section to a network-config part of configuration
# SYNOPSIS
#   netconfInsertSection $node_id $section
# FUNCTION
#   Inserts a section in the configuration. Sections beginning with the
#   "interface" keyword are inserted at the head of the configuration, and
#   all other sequences are simply appended to the configuration tail.
# INPUTS
#   * node_id -- the node id of the node whose config section is inserted
#   * section -- represents the section that is being inserted. If there
#     was a section in network config with the same section head, it is lost.
#****

proc netconfInsertSection { node section } {
    global $node

    set sectionhead [lindex $section 0]
    netconfClearSection $node $sectionhead
    set i [lsearch [set $node] "network-config *"]
    set netconf [lindex [lindex [set $node] $i] 1]
    set lnum_beg end
    if { "[lindex $sectionhead 0]" == "interface" } {
	set lnum [lsearch $netconf "hostname *"]
	if { $lnum >= 0 } {
	    set lnum_beg [expr $lnum + 2]
	}
    } elseif { "[lindex $sectionhead 0]" == "hostname" } {
	set lnum_beg 0
    }
    if { "[lindex $section end]" != "!" } {
	lappend section "!"
    }
    foreach line $section {
	set netconf [linsert $netconf $lnum_beg $line]
	if { $lnum_beg != "end" } {
	    incr lnum_beg
	}
    }
    set $node [lreplace [set $node] $i $i [list network-config $netconf]]
}


#Boeing: proc to find out whether tcpdump should be on for an interface
proc getIfcDumpState { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "tcpdump" } {
	    return "tcpdump on"
	}
    }
    return "tcpdump off"
}

#Boeing: proc to set tcpdump for an interface
proc setIfcDumpState { node ifc state } {
    set ifcfg [list "interface $ifc"]
    if { $state == "tcpdump on" } {
	lappend ifcfg " tcpdump"
    }
    foreach line [netconfFetchSection $node "interface $ifc"] {
        if { [lindex $line 0] != "tcpdump" && \
	    [lrange $line 0 1] != "no tcpdump" } {
            lappend ifcfg $line
        }
    }
    netconfInsertSection $node $ifcfg
}


#****f* nodecfg.tcl/getIfcOperState
# NAME
#   getIfcOperState -- get interface operating state
# SYNOPSIS
#   set state [getIfcOperState $node_id $ifc]
# FUNCTION
#   Returns the operating state of the specified interface. It can be "up" or "down".
# INPUTS
#   * node_id -- node id
#   * ifc     -- The interface that is up or down
# RESULT
#   * state -- the operating state of the interface, can either "up" or "down".
#****

proc getIfcOperState { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "shutdown" } {
	    return "down"
	}
    }
    return "up"
}

#****f* nodecfg.tcl/setIfcOperState
# NAME
#   setIfcOperState -- set interface operating state
# SYNOPSIS
#   setIfcOperState $node_id $ifc
# FUNCTION
#   Sets the operating state of the specified interface. It can be set to "up" or "down".
# INPUTS
#   * node_id  -- node id
#   * ifc      -- interface
#   * state    -- new operating state of the interface, can be either "up" or "down"
#****

proc setIfcOperState { node ifc state } {
    set ifcfg [list "interface $ifc"]
    if { $state == "down" } {
	lappend ifcfg " shutdown"
    }
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lindex $line 0] != "shutdown" && \
	    [lrange $line 0 1] != "no shutdown" } {
	    lappend ifcfg $line
	}
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getIfcQDisc
# NAME
#   getIfcQDisc -- get interface queuing discipline
# SYNOPSIS
#   set qdisc [getIfcQDisc $node_id $ifc]
# FUNCTION
#   Returns one of the supported queuing discipline ("FIFO", "WFQ" or "DRR") that is activ
#   for the specified interface.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's queuing discipline is checked.
#   * ifc     -- The interface name.
# RESULT
#   * qdisc -- returns queuing discipline of the interface, can be "FIFO", "WFQ" or "DRR".
#****

proc getIfcQDisc { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "fair-queue" } {
	    return WFQ
	}
	if { [lindex $line 0] == "drr-queue" } {
	    return DRR
	}
    }
    return FIFO
}

#****f* nodecfg.tcl/setIfcQDisc
# NAME
#   setIfcQDisc -- set interface queueing discipline
# SYNOPSIS
#   setIfcQDisc $node_id $ifc $qdisc
# FUNCTION
#	Sets the new queuing discipline for the interface. Implicit default is FIFO.
# INPUTS
#   * node_id  -- represents the node id of the node whose interface's queuing discipline is set.
#   * ifc      -- interface name.
#   * qdisc    -- queuing discipline of the interface, can be "FIFO", "WFQ" or "DRR".
#****

proc setIfcQDisc { node ifc qdisc } {
    set ifcfg [list "interface $ifc"]
    if { $qdisc == "WFQ" } {
	lappend ifcfg " fair-queue"
    }
    if { $qdisc == "DRR" } {
	lappend ifcfg " drr-queue"
    }
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lindex $line 0] != "fair-queue" && \
	    [lindex $line 0] != "drr-queue" } {
	    lappend ifcfg $line
	}
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getIfcQDrop
# NAME
#   getIfcQDrop -- get interface queue dropping policy
# SYNOPSIS
#   set qdrop [getIfcQDrop $node_id $ifc]
# FUNCTION
#   Returns one of the supported queue dropping policies ("drop-tail" or "drop-head") that is active
#   for the specified interface.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's queue dropping policy is checked.
#   * ifc     -- The interface name.
# RESULT
#   * qdrop -- returns queue dropping policy of the interface, can be "drop-tail" or "drop-head".
#****

proc getIfcQDrop { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "drop-head" } {
	    return drop-head
	}
    }
    return drop-tail
}

#****f* nodecfg.tcl/setIfcQDrop
# NAME
#   setIfcQDrop -- set interface queue dropping policy

# SYNOPSIS
#   setIfcQDrop $node_id $ifc $qdrop
# FUNCTION
#   Sets the new queuing discipline. Implicit default is "drop-tail".
# INPUTS
#   * node_id -- represents the node id of the node whose interface's queue droping policie is set.
#   * ifc     -- interface name.
#   * qdrop   -- new queue dropping policy of the interface, can be "drop-tail" or "drop-head".
#****

proc setIfcQDrop { node ifc qdrop } {
    set ifcfg [list "interface $ifc"]
    if { $qdrop == "drop-head" } {
	lappend ifcfg " drop-head"

    }
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lindex $line 0] != "drop-head" && \
	    [lindex $line 0] != "drop-tail" } {
	    lappend ifcfg $line
	}
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getIfcQLen
# NAME
#   getIfcQLen -- get interface queue length
# SYNOPSIS
#   set qlen [getIfcQLen $node_id $ifc]
# FUNCTION
#   Returns the queue length limit in number of packets.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's queue length is checked.
#   * ifc     -- interface name.
# RESULT
#   * qlen -- queue length limit represented in number of packets.
#****

proc getIfcQLen { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "queue-len" } {
	    return [lindex $line 1]
	}
    }
    return 50
}

#****f* nodecfg.tcl/setIfcQLen
# NAME
#   setIfcQLen -- set interface queue length
# SYNOPSIS
#   setIfcQLen $node_id $ifc $len
# FUNCTION
#   Sets the queue length limit.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's queue length is set.
#   * ifc     -- interface name.
#   * qlen    -- queue length limit represented in number of packets.
#****

proc setIfcQLen { node ifc len } {
    set ifcfg [list "interface $ifc"]
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lindex $line 0] != "queue-len" } {
	    lappend ifcfg $line
	}
    }
    if { $len > 5 && $len != 50 } {
	lappend ifcfg " queue-len $len"
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getIfcMTU
# NAME
#   getIfcMTU -- get interface MTU size.
# SYNOPSIS
#   set mtu [getIfcMTU $node_id $ifc]
# FUNCTION
#   Returns the configured MTU, or a default MTU.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's MTU is checked.
#   * ifc     -- interface name.
# RESULT
#   * mtu -- maximum transmission unit of the packet, represented in bytes.
#****

proc getIfcMTU { node ifc } {
    foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	if { [lindex $line 0] == "mtu" } {
	    return [lindex $line 1]
	}
    }
    # Return defaults
    switch -exact [string range $ifc 0 2] {
	eth { return 1500 }
	ser { return 2044 }
    }
}

#****f* nodecfg.tcl/setIfcMTU
# NAME
#   setIfcMTU -- set interface MTU size.
# SYNOPSIS
#   setIfcMTU $node_id $ifc $mtu
# FUNCTION
#   Sets the new MTU. Zero MTU value denotes the default MTU.
# INPUTS
#   * node_id -- represents the node id of the node whose interface's MTU is set.
#   * ifc     -- interface name.
#   * mtu     -- maximum transmission unit of a packet, represented in bytes.
#****

proc setIfcMTU { node ifc mtu } {
    set ifcfg [list "interface $ifc"]
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lindex $line 0] != "mtu" } {
	    lappend ifcfg $line
	}
    }
    switch -exact [string range $ifc 0 2] {
	eth { set limit 1500 }
	ser { set limit 2044 }
    }
    if { $mtu >= 256 && $mtu < $limit } {
	lappend ifcfg " mtu $mtu"
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getIfcIPv4addr
# NAME
#   getIfcIPv4addr -- get interface IPv4 address.
# SYNOPSIS
#   set addr [getIfcIPv4addr $node_id $ifc]
# FUNCTION
#   Returns the list of IPv4 addresses assigned to the specified interface.
# INPUTS
#   * node_id -- node id
#   * ifc  -- interface name.
# RESULT
#   * addr -- A list of all the IPv4 addresses assigned to the specified interface.
#****

proc getIfcIPv4addr { node ifc } {
    set addrlist {}
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] == "ip address" } {
	    lappend addrlist [lindex $line 2]
	}
    }
    # XXX remove this extra check if special OpenVZ case is removed
    # this forces a search of network-config when no IP address has been found
    if { [llength $addrlist] == 0 } {
	foreach line [netconfFetchSection $node "nocustom interface $ifc"] {
	    if { [lrange $line 0 1] == "ip address" } {
		lappend addrlist [lindex $line 2]
	    }
	}
    }
    return $addrlist
}

#****f* nodecfg.tcl/setIfcIPv4addr
# NAME
#   setIfcIPv4addr -- set interface IPv4 address.
# SYNOPSIS
#   setIfcIPv4addr $node_id $ifc $addr
# FUNCTION
#   Sets a new IPv4 address(es) on an interface. The correctness of the
#   IP address format is not checked / enforced.
# INPUTS
#   * node_id -- the node id of the node whose interface's IPv4 address is set.
#   * ifc     -- interface name.
#   * addr    -- new IPv4 address.
#****

proc setIfcIPv4addr { node ifc addr } {
    set ifcfg [list "interface $ifc"]
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] != "ip address" } {
	    lappend ifcfg $line
	}
    }
    if { $addr != "" } {
	lappend ifcfg " ip address $addr"
    }
    netconfInsertSection $node $ifcfg
    return
}

#****f* nodecfg.tcl/getIfcIPv6addr
# NAME
#   getIfcIPv6addr -- get interface IPv6 address.
# SYNOPSIS
#   set addr [getIfcIPv6addr $node_id $ifc]
# FUNCTION
#   Returns the list of IPv6 addresses assigned to the specified interface.
# INPUTS
#   * node_id -- the node id of the node whose interface's IPv6 addresses are returned.
#   * ifc     -- interface name.
# RESULT
#   * addr -- A list of all the IPv6 addresses assigned to the specified interface.
#****

proc getIfcIPv6addr { node ifc } {
    set addrlist {}
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] == "ipv6 address" } {
	    lappend addrlist [lindex $line 2]
	}
    }
    return $addrlist
}

#****f* nodecfg.tcl/setIfcIPv6addr
# NAME
#   setIfcIPv6addr -- set interface IPv6 address.
# SYNOPSIS
#   setIfcIPv6addr $node_id $ifc $addr
# FUNCTION
#   Sets a new IPv6 address(es) on an interface. The correctness of the
#   IP address format is not checked / enforced.
# INPUTS
#   * node_id -- the node id of the node whose interface's IPv4 address is set.
#   * ifc     -- interface name.
#   * addr    -- new IPv6 address.
#****

proc setIfcIPv6addr { node ifc addr } {
    set ifcfg [list "interface $ifc"]
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] != "ipv6 address" } {
	    lappend ifcfg $line
	}
    }
    if { $addr != "" } {
	lappend ifcfg " ipv6 address $addr"
    }
    netconfInsertSection $node $ifcfg
}

proc getIfcMacaddr { node ifc } {
    set addrlist {}
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] == "mac address" } {
 	    lappend addrlist [lindex $line 2]
 	}
    }
    return $addrlist
}

proc setIfcMacaddr { node ifc macaddr} {
    set ifcfg [list "interface $ifc"]
    foreach line [netconfFetchSection $node "interface $ifc"] {
	if { [lrange $line 0 1] != "mac address" } {
	    lappend ifcfg $line
	}
    }
    if { $macaddr != "" } {
	lappend ifcfg " mac address $macaddr"
    }
    netconfInsertSection $node $ifcfg
}

#****f* nodecfg.tcl/getStatIPv4routes
# NAME
#   getStatIPv4routes -- get static IPv4 routes.
# SYNOPSIS
#   set routes [getStatIPv4routes $node_id]
# FUNCTION
#   Returns a list of all static IPv4 routes as a list of
#   {destination gateway {metric}} pairs.
# INPUTS
#   * node_id -- node id
# RESULT
#   * routes -- the list of all static routes defined for the specified node
#****

proc getStatIPv4routes { node } {
    global $node

    set routes {}
    set netconf [lindex [lsearch -inline [set $node] "network-config *"] 1]
    foreach entry [lsearch -all -inline $netconf "ip route *"] {
	lappend routes [lrange $entry 2 end]
    }
    return $routes
}

#****f* nodecfg.tcl/setStatIPv4routes
# NAME
#   setStatIPv4routes -- set static IPv4 routes.
# SYNOPSIS
#   setStatIPv4routes $node_id $routes
# FUNCTION
#   Replace all current static route entries with a new one, in form of
#   a list of {destination gateway {metric}} pairs.
# INPUTS
#   * node_id -- the node id of the node whose static routes are set.
#   * routes  -- the list of all static routes defined for the specified node
#****

proc setStatIPv4routes { node routes } {
    netconfClearSection $node "ip route [lindex [getStatIPv4routes $node] 0]"
    set section {}
    foreach route $routes {
	lappend section "ip route $route"
    }
    netconfInsertSection $node $section
}

#****f* nodecfg.tcl/getStatIPv6routes
# NAME
#   getStatIPv6routes -- get static IPv6 routes.
# SYNOPSIS
#   set routes [getStatIPv6routes $node_id]
# FUNCTION
#   Returns a list of all static IPv6 routes as a list of
#   {destination gateway {metric}} pairs.
# INPUTS
#   * node_id -- node id
# RESULT
#   * routes -- the list of all static routes defined for the specified node
#****

proc getStatIPv6routes { node } {
    global $node

    set routes {}
    set netconf [lindex [lsearch -inline [set $node] "network-config *"] 1]
    foreach entry [lsearch -all -inline $netconf "ipv6 route *"] {
	lappend routes [lrange $entry 2 end]
    }
    return $routes
}

#****f* nodecfg.tcl/setStatIPv6routes
# NAME
#   setStatIPv4routes -- set static IPv6 routes.
# SYNOPSIS
#   setStatIPv6routes $node_id $routes
# FUNCTION
#   Replace all current static route entries with a new one, in form of
#   a list of {destination gateway {metric}} pairs.
# INPUTS
#   * node_id -- node id
#   * routes  -- the list of all static routes defined for the specified node
#****

proc setStatIPv6routes { node routes } {
    netconfClearSection $node "ipv6 route [lindex [getStatIPv6routes $node] 0]"
    set section {}
    foreach route $routes {
	lappend section "ipv6 route $route"

    }
    netconfInsertSection $node $section
}

#****f* nodecfg.tcl/getNodeName
# NAME
#   getNodeName -- get node name.
# SYNOPSIS
#   set name [getNodeName $node_id]
# FUNCTION
#   Returns node's logical name.
# INPUTS
#   * node_id -- node id
# RESULT
#   * name -- logical name of the node
#****

proc getNodeName { node } {
    global $node

    set netconf [lindex [lsearch -inline [set $node] "network-config *"] 1]
    return [lrange [lsearch -inline $netconf "hostname *"] 1 end]
}

#****f* nodecfg.tcl/setNodeName
# NAME
#   setNodeName -- set node name.
# SYNOPSIS
#   setNodeName $node_id $name
# FUNCTION
#   Sets node's logical name.
# INPUTS
#   * node_id -- node id
#   * name    -- logical name of the node
#****

proc setNodeName { node name } {
    netconfClearSection $node "hostname [getNodeName $node]"
    netconfInsertSection $node [list "hostname $name"]
}

#****f* nodecfg.tcl/getNodeType
# NAME
#   getNodeType -- get node type.
# SYNOPSIS
#   set type [getNodeType $node_id]
# FUNCTION
#   Returns node's type.
# INPUTS
#   * node_id -- node id
# RESULT
#   * type -- type of the node
#****

proc nodeType { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "type *"] 1]
}

#****f* nodecfg.tcl/getNodeModel
# NAME
#   getNodeModel -- get node routing model.
# SYNOPSIS
#   set model [getNodeModel $node_id]
# FUNCTION
#
# INPUTS
#   * node_id -- node id
# RESULT
#   * model -- routing model of the specified node
#****

proc getNodeModel { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "model *"] 1]
}

#****f* nodecfg.tcl/setNodeModel
# NAME
#   setNodeModel -- set node routing model.
# SYNOPSIS
#   setNodeModel $node_id $model
# FUNCTION
#
# INPUTS
#   * node_id -- node id
#   * model   -- routing model of the specified node
#****

proc setNodeModel { node model } {
    global $node

    set i [lsearch [set $node] "model *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "model $model"]
    } else {
	set $node [linsert [set $node] 1 "model $model"]
    }
}

#****f* nodecfg.tcl/getNodeCoords
# NAME
#   getNodeCoords -- get node icon coordinates.
# SYNOPSIS
#   set coords [getNodeCoords $node_id]
# FUNCTION
#   Returns node's icon coordinates.
# INPUTS
#   * node_id -- node id
# RESULT
#   * coords -- coordinates of the node's icon a list in form of {Xcoord Ycoord}
#****

proc getNodeCoords { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "iconcoords *"] 1]
}

#****f* nodecfg.tcl/setNodeCoords
# NAME
#   setNodeCoords -- set node's icon coordinates.
# SYNOPSIS

#   setNodeCoords $node_id $coords
# FUNCTION
#   Sets node's icon coordinates.
# INPUTS
#   * node_id -- node id
#   * coords  -- coordinates of the node's icon in form of Xcoord Ycoord
#****

proc setNodeCoords { node coords } {
    global $node

    set i [lsearch [set $node] "iconcoords *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "iconcoords {$coords}"]
    } else {
	set $node [linsert [set $node] end "iconcoords {$coords}"]
    }
    writeNodeCoords $node $coords
}

# this saves each node's current X,Y position to a /tmp/pycore.nnnnn/nX.xy
proc writeNodeCoords { node coords } {
    global oper_mode g_current_session

    if { [info exists oper_mode] && $oper_mode != "exec" } { return }
    if { $g_current_session == 0 } { return }
    if { [nodeType $node] != "router" } { return }

    set fn "/tmp/pycore.$g_current_session/$node.xy"
    catch { 
	set f [open $fn w]
	puts $f $coords
	close $f
    }
}

# cleanup the /tmp/pycore.nnnnn/nX.xy file
proc deleteNodeCoords { node } {
    global g_current_session

    if { $g_current_session == 0 } { return }
    if { [nodeType $node] != "router" } { return }

    set fn "/tmp/pycore.$g_current_session/$node.xy"
    if { [file exists $fn] } { file delete $fn }
}

#****f* nodecfg.tcl/getNodeLabelCoords
# NAME
#   getNodeLabelCoords -- get node's label coordinates.
# SYNOPSIS
#   set coords [getNodeLabelCoords $node_id]
# FUNCTION
#   Returns node's label coordinates.
# INPUTS
#   * node_id -- node id
# RESULT
#   * coords -- coordinates of the node's label a list in form of {Xcoord Ycoord}
#****

proc getNodeLabelCoords { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "labelcoords *"] 1]
}

#****f* nodecfg.tcl/setNodeLabelCoords
# NAME
#   setNodeLabelCoords -- set node's label coordinates.
# SYNOPSIS
#   setNodeLabelCoords $node_id $coords
# FUNCTION
#   Sets node's label coordinates.
# INPUTS
#   * node_id -- node id
#   * coords  -- coordinates of the node's label in form of Xcoord Ycoord
#****

proc setNodeLabelCoords { node coords } {
    global $node

    set i [lsearch [set $node] "labelcoords *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "labelcoords {$coords}"]
    } else {
	set $node [linsert [set $node] end "labelcoords {$coords}"]
    }
}

# return [dx, dy] offset for a node label, which may depend on its type
proc getDefaultLabelOffsets { nodetype } {
    set dx 0
    set dy 32
    if { [lsearch {hub lanswitch} $nodetype] >= 0 } {
	set dy 24
    }
    return [list $dx $dy]
}

#****f* nodecfg.tcl/ifcList
# NAME
#   ifcList -- get list of all interfaces
# SYNOPSIS
#   set ifcs [ifcList $node_id]
# FUNCTION
#   Returns a list of all interfaces present in a node.
# INPUTS
#   * node_id -- node id
# RESULT
#   * ifcs -- list of all node's interfaces.
#****

proc ifcList { node } {
    global $node
    if { ![info exists $node] } { return "" }
    set interfaces ""
    foreach entry [lsearch -all -inline [set $node] "interface-peer *"] {
	lappend interfaces [lindex [lindex $entry 1] 0]
    }
    return $interfaces
}

#****f* nodecfg.tcl/peerByIfc
# NAME
#   peerByIfc -- get node's peer by interface.
# SYNOPSIS
#   set peer [peerByIfc $node_id $ifc]
# FUNCTION
#   Returns id of the node on the other side of the interface. If the node 
#   on the other side of the interface is situated on the other canvas or connected
#   via split link, this function returns a pseudo node.
# INPUTS
#   * node_id -- node id
#   * ifc     -- interface name
# RESULT
#   * peer -- node id of the node on the other side of the interface
#****

proc peerByIfc { node ifc } {
    global $node

    set entry [lsearch -inline [set $node] "interface-peer {$ifc *}"]
    return [lindex [lindex $entry 1] 1]
}

#****f* nodecfg.tcl/logicalPeerByIfc
# NAME
#   logicalPeerByIfc -- get node's peer by interface.
# SYNOPSIS
#   set peer [logicalPeerByIfc $node_id $ifc]
# FUNCTION
#   Returns id of the node on the other side of the interface. If the node on the other
#   side of the interface is connected via normal link (not split) this function acts the same
#   as the function peerByIfc, but if the nodes are connected via split links or situated on different
#   canvases this function returns the logical peer node.
# INPUTS
#   * node_id -- node id
#   * ifc     -- interface name
# RESULT
#   * peer -- node id of the node on the other side of the interface
#****

proc logicalPeerByIfc { node ifc } {
    global $node

    set peer [peerByIfc $node $ifc]
    if { $peer == "" } { return "" }; # Boeing
    if { [nodeType $peer] != "pseudo" } {
	return $peer

    } else {
	set mirror_node [getNodeMirror $peer]
	set mirror_ifc [ifcList $mirror_node]
	return [peerByIfc $mirror_node $mirror_ifc]
    }
}

#****f* nodecfg.tcl/ifcByPeer
# NAME
#   ifcByPeer -- get node interface by peer.
# SYNOPSIS
#   set ifc [peerByIfc $node_id $peer_id]
# FUNCTION
#   Returns the name of the interface connected to the specified peer.
#   If the peer node is on different canvas or connected via split link
#   to the specified node this function returns an empty string.
# INPUTS
#   * node_id -- node id
#   * peer_id -- id of the peer node
# RESULT
#   * ifc -- interface name
#****

proc ifcByPeer { node peer } {
    global $node

    set entry [lsearch -inline [set $node] "interface-peer {* $peer}"]
    return [lindex [lindex $entry 1] 0]
}

#****f* nodecfg.tcl/ifcByLogicalPeer
# NAME
#   ifcByPeer -- get node interface by peer.
# SYNOPSIS
#   set ifc [peerByIfc $node_id $peer_id]
# FUNCTION
#   Returns the name of the interface connected to the specified peer.
#   Returns the right interface even if the peer node is on the other
#   canvas or connected via split link.
# INPUTS
#   * node_id -- node id
#   * peer_id -- id of the peer node
# RESULT
#   * ifc -- interface name
#****

proc ifcByLogicalPeer { node peer } {
    global $node

    set ifc [ifcByPeer $node $peer]
    if { $ifc == "" } {
	#
	# Must search through pseudo peers
	#
	foreach ifc [ifcList $node] {
	    set t_peer [peerByIfc $node $ifc]
	    if { [nodeType $t_peer] == "pseudo" } {
		set mirror [getNodeMirror $t_peer]
		if { [peerByIfc $mirror [ifcList $mirror]] == $peer } {
		    return $ifc
		}
	    }
	}
	return ""
    } else {
	return $ifc    
    }
}

#****f* nodecfg.tcl/hasIPv4Addr
# NAME
#   hasIPv4Addr -- has IPv4 address.
# SYNOPSIS
#   set check [hasIPv4Addr $node_id]
# FUNCTION
#   Returns true if at least one interface has an IPv4 address
#   configured, otherwise returns false.
# INPUTS
#   * node_id -- node id
# RESULT
#   * check -- true if at least one interface has IPv4 address, otherwise false.
#****

proc hasIPv4Addr { node } {
    foreach ifc [ifcList $node] {
	if { [getIfcIPv4addr $node $ifc] != "" } {
	    return true
	}
    }
    return false
}

#****f* nodecfg.tcl/hasIPv6Addr
# NAME
#   hasIPv4Addr -- has IPv6 address.
# SYNOPSIS
#   set check [hasIPv6Addr $node_id]
# FUNCTION
#   Retruns true if at least one interface has an IPv6 address
#   configured, otherwise returns false.
# INPUTS
#   * node_id -- node id
# RESULT
#   * check -- true if at least one interface has IPv6 address, otherwise false.
#****

proc hasIPv6Addr { node } {
    foreach ifc [ifcList $node] {
	if { [getIfcIPv6addr $node $ifc] != "" } {
	    return true
	}
    }
    return false
}

#****f* nodecfg.tcl/removeNode
# NAME
#   removeNode -- removes the node
# SYNOPSIS
#   removeNode $node_id
# FUNCTION
#   Removes the specified node as well as all the links binding that node to 
#   the other nodes.
# INPUTS
#   * node_id -- node id
#****

proc removeNode { node } {
    global node_list $node

    foreach ifc [ifcList $node] {
	set peer [peerByIfc $node $ifc]
	set link [linkByPeers $node $peer]
	removeLink $link
    }
    set i [lsearch -exact $node_list $node]
    set node_list [lreplace $node_list $i $i]
}

#****f* nodecfg.tcl/getNodeCanvas
# NAME
#   getNodeCanvas -- get node canvas id
# SYNOPSIS
#   set canvas [getNodeCanvas $node_id]
# FUNCTION
#   Returns node's canvas affinity.
# INPUTS
#   * node_id -- node id
# RESULT
#   * canvas -- canvas id
#****

proc getNodeCanvas { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "canvas *"] 1]
}

#****f* nodecfg.tcl/setNodeCanvas
# NAME
#   setNodeCanvas -- set node canvas
# SYNOPSIS
#   setNodeCanvas $node_id $canvas
# FUNCTION
#   Sets node's canvas affinity.
# INPUTS
#   * node_id -- node id
#   * canvas -- canvas id
#****

proc setNodeCanvas { node canvas } {
    global $node

    set i [lsearch [set $node] "canvas *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "canvas $canvas"]
    } else {
	set $node [linsert [set $node] end "canvas $canvas"]
    }
}

proc getNodeHidden { node } {
    global $node

    set h [lindex [lsearch -inline [set $node] "hidden *"] 1]
    if { $h == "" } { return 0 }
    return $h
}

proc setNodeHidden { node value } {
    global $node

    set i [lsearch [set $node] "hidden *"]
    if { $value == 0 } {
	if { $i >= 0 } {
	    set $node [lreplace [set $node] $i $i]
	}
	return
    }
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "hidden $value"]
    } else {
	set $node [linsert [set $node] end "hidden $value"]
    }
}

#****f* nodecfg.tcl/newIfc
# NAME
#   newIfc -- new interface
# SYNOPSIS
#   set ifc [newIfc $type $node_id]
# FUNCTION
#   Returns the first available name for a new interface of the specified type.
# INPUTS
#   * node_id -- node id
#   * type    -- type
# RESULT
#   * ifc -- the first available name for a interface of the specified type
#****

proc newIfc { type node } {
    set interfaces [ifcList $node]
    set firstinterface 0
    for { set id $firstinterface } { [lsearch -exact $interfaces $type$id] >= 0 } {incr id} {}
    return $type$id
}

#****f* nodecfg.tcl/newNode
# NAME
#   newNode -- new node
# SYNOPSIS
#   set node_id [newNode $type]
# FUNCTION
#   Returns the node id of a new node of the specified type.
# INPUTS
#   * type -- node type
# RESULT
#   * node_id -- node id of a new node of the specified type
#****

proc newNode { type } {
    global node_list def_router_model
    global viewid
    catch {unset viewid}

    # overload the passed in parameter - allow specifing new node num
    if { [llength $type] > 1 } {
    	set node [lindex $type 1]
	set type [lindex $type 0]
    } else {
    set node [newObjectId node]
    }
	
    global $node
    set $node {}
    lappend $node "type $type"
    if { $type == "router" } {
	lappend $node "model $def_router_model"
	set nconfig [list \
		"hostname $node" \
		! ]
    # tunnel
    } elseif {$type == "rj45" || $type == "tunnel" || $type == "ktunnel" } {
	set nconfig [list \
		"hostname UNASSIGNED" \
		! ]
    # set wlan default parameters upon creation
    } elseif { $type == "wlan" } {
	global DEFAULT_WLAN_MODEL DEFAULT_WLAN_MODEL_TYPES 
	global DEFAULT_WLAN_MODEL_VALS
	set nconfig [list \
		"hostname $type[string range $node 1 end]" \
		! \
		"mobmodel" \
		"coreapi" \
		"$DEFAULT_WLAN_MODEL" \
		! ]
	lappend $node "network-config [list $nconfig]"
	setCustomConfig $node $DEFAULT_WLAN_MODEL $DEFAULT_WLAN_MODEL_TYPES \
			$DEFAULT_WLAN_MODEL_VALS 0
    } else {
		set nconfig [list \
		"hostname $node" \
		! ]
    }

    # wlan has already changed node global above
    if { $type != "wlan" } {
    lappend $node "network-config [list $nconfig]"
    }
    lappend node_list $node
    return $node
}

#****f* nodecfg.tcl/getNodeMirror
# NAME
#   getNodeMirror -- get node mirror
# SYNOPSIS
#   set mirror_node_id [getNodeMirror $node_id]
# FUNCTION
#   Returns the node id of a mirror pseudo node of the node. Mirror node is the
#   corresponding pseudo node. The pair of pseudo nodes, node and his mirror node, are 
#   introduced to form a split in a link. This split can be used for avoiding crossed 
#   links or for displaying a link between the nodes on a different canvas.
# INPUTS
#   * node_id -- node id
# RESULT
#   * mirror_node_id -- node id of a mirror node
#****

proc getNodeMirror { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "mirror *"] 1]
}

#****f* nodecfg.tcl/setNodeMirror
# NAME
#   setNodeMirror -- set node mirror
# SYNOPSIS
#   setNodeMirror $node_id $mirror_node_id
# FUNCTION
#   Sets the node id of a mirror pseudo node of the specified node. Mirror node is the
#   corresponding pseudo node. The pair of pseudo nodes, node and his mirror node, are 
#   introduced to form a split in a link. This split can be used for avoiding crossed 
#   links or for displaying a link between the nodes on a different canvas.
# INPUTS
#   * node_id -- node id
#   * mirror_node_id -- node id of a mirror node
#****

proc setNodeMirror { node value } {
    global $node

    set i [lsearch [set $node] "mirror *"]
    if { $value == "" } {
	set $node [lreplace [set $node] $i $i]
    } else {
	set $node [linsert [set $node] end "mirror $value"]
    }
}


#****f* nodecfg.tcl/setType
# NAME
#   setType -- set node's type.
# SYNOPSIS
#   setType $node_id $type
# FUNCTION
#   Sets node's type.
# INPUTS
#   * node_id -- node id
#   * type  -- type of node
#****

proc setType { node type } {
    global $node

    set i [lsearch [set $node] "type *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "type $type"]
    } else {
	set $node [linsert [set $node] 1 "type $type"]
    }
}

# begin Boeing: node commands specific to wlan
proc getNodeRange { node } {
    global $node

    return [lindex [lsearch -inline [set $node] "range *"] 1]
}

proc setNodeRange { node value } {
    global $node
    set i [lsearch [set $node] "range *"]
    if { $value == "" } {
	if { $i > 0 } {
	    set $node [lreplace [set $node] $i $i]
	}
	return
    }
    if { $i > 0 } {
	set $node [lreplace [set $node] $i $i "range $value"]
    } else {
        set $node [linsert [set $node] end "range $value"]
    }
    return
}
# end Boeing

# Boeing - custom post config commands
proc getCustomPostConfigCommands { node } {
    global $node
    return [lindex [lsearch -inline [set $node] "custom-post-config-commands *"] 1]
}

#Boeing custom pre config commands
proc getCustomPreConfigCommands { node } {
    global $node
    return [lindex [lsearch -inline [set $node] "custom-pre-config-commands *"] 1]
}

#Boeing custom post config commands
proc setCustomPostConfigCommands { node cfg } {
    global $node

    set i [lsearch [set $node] "custom-post-config-commands *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i]
    }
    if { $cfg != {} } {
	lappend $node [list custom-post-config-commands $cfg]
    }
    return
}

# get the services saved for this node; if want_defaults is true and no services
# have been configured, return the default services for this node type
proc getNodeServices { node want_defaults } {
    global $node

    set i [lsearch [set $node] "services *"]
    set s [lindex [lindex [set $node] $i] 1] 
    if { $want_defaults && $i < 0 } {
	set s [getNodeTypeServices [getNodeModel $node]]
    }
    return $s
}

# save the list of services configured for this node
proc setNodeServices { node services } {
    global $node

    set i [lsearch [set $node] "services *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i "services {$services}"]
    } else {
	set $node [linsert [set $node] end "services {$services}"]
    }
}

# Boeing - custom image
proc getCustomImage { node } {
	global $node
	return [lindex [lsearch -inline [set $node] "custom-image *"] 1]
}

# Boeing - custom image
proc setCustomImage { node image } {
    global $node

    set i [lsearch [set $node] "custom-image *"]
    if { $i >= 0 } {
	set $node [lreplace [set $node] $i $i]
    }
    if { $image != "" } {
	lappend $node [list custom-image $image]
    }
    return
}

# if cmd=save save all node positions, otherwise reset them with cmd=reset
proc resetAllNodeCoords { cmd } {
    global node_list g_saved_node_coords zoom

    # save the node coordinates to a global array
    if { $cmd == "save" } {
	array unset g_saved_node_coords
    	foreach node $node_list {
	    set coords [getNodeCoords $node]
	    if { $coords == "" } { continue }
	    array set g_saved_node_coords [list $node $coords]
	}
    # restore the node coordinates from the global array
    } elseif { $cmd == "reset" } {
	if { ![array exists g_saved_node_coords] } { return }
    	foreach node $node_list {
	    if { ![info exists g_saved_node_coords($node)] } { continue }
	    set coords $g_saved_node_coords($node)
	    if { [llength $coords] != 2 } { continue }
	    set x [expr {$zoom * [lindex $coords 0]}]
	    set y [expr {$zoom * [lindex $coords 1]}]
	    moveNodeAbs .c $node $x $y
	}
    }

}

