#
# Copyright 2005-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/ipv4.tcl
# NAME
#   ipv4.tcl -- file for handeling IPv4
#****

#****f* ipv4.tcl/findFreeIPv4Net
# NAME
#   findFreeIPv4Net -- find free IPv4 network
# SYNOPSIS
#   set ipnet [findFreeIPv4Net $mask]
# FUNCTION
#   Finds a free IPv4 network. Network is concidered to be free
#   if there are no simulated nodes attached to it. 
# INPUTS
#   * mask -- this parameter is left unused for now
# RESULT
#   * ipnet -- returns the free IPv4 network address in the form 10.a.b 
#****
 
proc findFreeIPv4Net { mask } {
    global g_prefs node_list

    set ipnets {}
    foreach node $node_list {
	foreach ifc [ifcList $node] {
	    set ipnet [lrange [split [getIfcIPv4addr $node $ifc] .] 0 2]
	    if {[lsearch $ipnets $ipnet] == -1} {
		lappend ipnets $ipnet
	    }
	}
    }
    # include mobility newlinks in search
    foreach newlink [.c find withtag "newlink"] {
        set ipnet [lrange [split [lindex [.c gettags $newlink] 4] .] 0 2]
	lappend ipnets $ipnet
    }
    if {![info exists g_prefs(gui_ipv4_addr)]} { setDefaultAddrs ipv4 }
    set abc [split $g_prefs(gui_ipv4_addr) .]
    set a [lindex $abc 0]
    set b [lindex $abc 1]
    set c [lindex $abc 2]

    for { set i $b } { $i <= 255 } { incr i } {
	for { set j $c } { $j <= 255 } { incr j } {
	    if {[lsearch $ipnets "$a $i $j"] == -1} {
		set ipnet "$a.$i.$j"
		return $ipnet
	    }
	}
    }
}

#****f* ipv4.tcl/autoIPv4addr 
# NAME
#   autoIPv4addr -- automaticaly assign an IPv4 address
# SYNOPSIS
#   autoIPv4addr $node $iface 
# FUNCTION
#   automaticaly assignes an IPv4 address to the interface $iface of 
#   of the node $node  
# INPUTS
#   * node -- the node containing the interface to which a new 
#     IPv4 address should be assigned
#   * iface -- the interface to witch a new, automatilacy generated, IPv4  
#     address will be assigned
#****

proc autoIPv4addr { node iface } {
    set peer_ip4addrs {}
    set netmaskbits 24 ;# default

    if { [[typemodel $node].layer] != "NETWORK" } {
	#
	# Shouldn't get called at all for link-layer nodes
	#
	puts "autoIPv4 called for a [[typemodel $node].layer] layer node"
	return
    }
    setIfcIPv4addr $node $iface ""

    set peer_node [logicalPeerByIfc $node $iface]
    # find addresses of NETWORK layer peer nodes
    if { [[typemodel $peer_node].layer] == "LINK" || [nodeType $peer_node] == "OVS" } {
	foreach l2node [listLANnodes $peer_node {}] {
	    foreach ifc [ifcList $l2node] {
		set peer [logicalPeerByIfc $l2node $ifc]
		set peer_if [ifcByLogicalPeer $peer $l2node]
		set peer_ip4addr [getIfcIPv4addr $peer $peer_if]
		if { $peer_ip4addr != "" } {
		    lappend peer_ip4addrs [lindex [split $peer_ip4addr /] 0]
		    set netmaskbits [lindex [split $peer_ip4addr /] 1]
		}
	    }
	}
    # point-to-point link with another NETWORK layer peer
    } else {
	set peer_if [ifcByLogicalPeer $peer_node $node]
	set peer_ip4addr [getIfcIPv4addr $peer_node $peer_if]
	set peer_ip4addrs [lindex [split $peer_ip4addr /] 0]
	if { $peer_ip4addr != "" } {
	    set netmaskbits [lindex [split $peer_ip4addr /] 1]
	}
    }
    # first node connected to wlan should use wlan prefix
    if { [nodeType $peer_node] == "wlan" && 
    	 [llength $peer_ip4addrs] == 0 } {
	# use the special "wireless" pseudo-interface
	set peer_ip4addr [getIfcIPv4addr $peer_node wireless]
	set peer_ip4addrs [lindex [split $peer_ip4addr /] 0]
	set netmaskbits [lindex [split $peer_ip4addr /] 1]
    }
    set nodetype [nodeType $node]
    if { $nodetype == "router" } { set nodetype [getNodeModel $node] }
    switch -exact -- $nodetype {
	router {
	    set targetbyte 1
	}
	host {
	    set targetbyte 10
	}
	PC -
	pc {
	    set targetbyte 20
	}
	default {
	    set targetbyte 1
	}
    }
    # peer has an IPv4 address, allocate a new address on the same network
    if { $peer_ip4addrs != "" } {
	set ipnums [split [lindex $peer_ip4addrs 0] .]
	set net "[lindex $ipnums 0].[lindex $ipnums 1].[lindex $ipnums 2]"
	set ipaddr $net.$targetbyte
	while { [lsearch $peer_ip4addrs $ipaddr] >= 0 } {
	    incr targetbyte
	    set ipaddr $net.$targetbyte
	}
	setIfcIPv4addr $node $iface "$ipaddr/$netmaskbits"
    } else {
	set ipnet [findFreeIPv4Net 24]
	setIfcIPv4addr $node $iface "$ipnet.$targetbyte/$netmaskbits"
    }
}


#****f* ipv4.tcl/autoIPv4defaultroute 
# NAME
#   autoIPvdefaultroute -- automaticaly assign a default route 
# SYNOPSIS
#   autoIPv4defaultroute $node $iface 
# FUNCTION
#   searches the interface of the node for a router, if a router is found
#   then it is a new default gateway. 
# INPUTS
#   * node  -- default gateway is provided for this node 
#   * iface -- the interface on witch we search for a new default gateway
#****

proc autoIPv4defaultroute { node iface } {
    if { [[typemodel $node].layer] != "NETWORK" } {
	#
	# Shouldn't get called at all for link-layer nodes
	#
	puts "autoIPv4defaultroute called for [[typemodel $node].layer] node"
	return
    }

    set peer_node [logicalPeerByIfc $node $iface]

    if { [[typemodel $peer_node].layer] == "LINK" } {
	foreach l2node [listLANnodes $peer_node {}] {
	    foreach ifc [ifcList $l2node] {
		set peer [logicalPeerByIfc $l2node $ifc]
		if { [nodeType $peer] != "router" && 
		     [nodeType $peer] != "ine" } {
		    continue
		}
		set peer_if [ifcByLogicalPeer $peer $l2node]
		set peer_ip4addr [getIfcIPv4addr $peer $peer_if]
		if { $peer_ip4addr != "" } {
		    set gw [lindex [split $peer_ip4addr /] 0]
		    setStatIPv4routes $node [list "0.0.0.0/0 $gw"]
		    return
		}
	    }
	}
    } else {
	if { [nodeType $peer_node] != "router" && 
	     [nodeType $peer_node] != "ine" } {
	    return
	}
	set peer_if [ifcByLogicalPeer $peer_node $node]
	set peer_ip4addr [getIfcIPv4addr $peer_node $peer_if]
	if { $peer_ip4addr != "" } {
	    set gw [lindex [split $peer_ip4addr /] 0]
	    setStatIPv4routes $node [list "0.0.0.0/0 $gw"]
	    return
	}
    }
}


#****f* ipv4.tcl/checkIPv4Addr 
# NAME
#   checkIPv4Addr -- check the IPv4 address 
# SYNOPSIS
#   set valid [checkIPv4Addr $str]
# FUNCTION
#   Checks if the provided string is a valid IPv4 address. 
# INPUTS
#   * str -- string to be evaluated. Valid IPv4 address is writen in form
#     a.b.c.d 
# RESULT
#   * valid -- function returns 0 if the input string is not in the form
#     of a valid IP address, 1 otherwise
#****

proc checkIPv4Addr { str } {
    set n 0
    if { $str == "" } {
	return 1
    }
    while { $n < 4 } {
	if { $n < 3 } {
	    set i [string first . $str]
	} else {
	    set i [string length $str]
	}
	if { $i < 1 } {
	    return 0
	}
	set part [string range $str 0 [expr $i - 1]]
	if { [string length [string trim $part]] != $i } {
	    return 0
	}
	if { ![string is integer $part] } {
	    return 0
	}
	if { $part < 0 || $part > 255 } {
	    return 0
	}
	set str [string range $str [expr $i + 1] end]
	incr n
    }
    return 1
}


#****f* ipv4.tcl/checkIPv4Net 
# NAME
#   checkIPv4Net -- check the IPv4 network 
# SYNOPSIS
#   set valid [checkIPv4Net $str]
# FUNCTION
#   Checks if the provided string is a valid IPv4 network. 
# INPUTS
#   * str -- string to be evaluated. Valid string is in form a.b.c.d/m 
# RESULT
#   * valid -- function returns 0 if the input string is not in the form
#     of a valid IP address, 1 otherwise
#****

proc checkIPv4Net { str } {
    if { $str == "" } {
	return 1
    }
    if { ![checkIPv4Addr [lindex [split $str /] 0]]} {
	return 0
    }
    set net [string trim [lindex [split $str /] 1]]
    if { [string length $net] == 0 } {
	return 0
    }
    return [checkIntRange $net 0 32]
}

#
# Boeing
# ***** ipv4.tcl/ipv4ToString
# NAME
#  ipv4ToString -- convert 32-bit integer to dotted decimal notation
# ****

proc ipv4ToString { ip } {
	return "[expr ($ip >> 24) & 0xFF].[expr ($ip >> 16) & 0xFF].[expr ($ip >> 8) & 0xFF].[expr $ip & 0xFF]"
}

#
# Boeing
# ***** ipv4.tcl/stringToIPv4
# NAME
#  stringToIPv4 -- convert dotted decimal string to 32-bit integer 
proc stringToIPv4 { ip } {
    set parts [split $ip .]
    set a [lindex $parts 0]; set b [lindex $parts 1];
    set c [lindex $parts 2]; set d [lindex $parts 3];
    return [expr {(($a << 24) + ($b << 16) + ($c << 8) + $d ) & 0xFFFFFFFF}]
}


proc ipv4ToNet { ip prefixlen } {
    set ipn [stringToIPv4 $ip]
    set ones [string repeat 1 $prefixlen]
    set zeroes [string repeat 0 [expr {32 - $prefixlen}]]
    binary scan [binary format B32 $ones$zeroes] H8 mask
    set netn [expr {$ipn & "0x$mask"}]
    return [ipv4ToString $netn]
}

proc getDefaultIPv4Addrs { } {
    global g_prefs
    return [list "10.0.0.0" "192.168.0.0" "172.16.0.0"]
}

proc isMulticast { str } {
    set i [string first . $str]
    if { $i < 1 } { return false }
    set part [string range $str 0 [expr {$i - 1}]]
    if { ![string is integer $part] } { return false }
    if { $part < 224 || $part > 239 } { return false }
    return true
}

proc ipv4List { node wantmask } {
    set r ""
    foreach ifc [ifcList $node] {
	foreach ip [getIfcIPv4addr $node $ifc] {
	    if { $wantmask } {
		lappend r $ip
	    } else {
		lappend r [lindex [split $ip /] 0]
	    }
	}
    }
    return $r
}


# this can be called with node_list to get a list of all subnets
proc ipv4SubnetList { nodes } {
    set r ""
    foreach node $nodes {
	foreach ipnet [ipv4List $node 1] {
	    set ip [lindex [split $ipnet /] 0]
	    set pl [lindex [split $ipnet /] 1]
	    set net "[ipv4ToNet $ip $pl]/$pl"
	    if { [lsearch $r $net] < 0 } {
		lappend r $net
	    }
	}
    }
    return $r
}