Overview | Index by: file name | procedure name | procedure call | annotation
interface-1.0.tm (annotations | original source)

#
#    Copyright (C) 2010 Alexandros Stergiakis <alsterg@gmail.com>
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

#//#
# The "interface" module defines commands for interface configuration. 
#
# @todo Better value checking within each handler: Mtu, Mac, ...
#//#

module require base 1.0
module provide interface 1.0

namespace eval ::module::interface {
    
namespace import ::helper::* ::module::api::* \
    ::module::base::dlist_procedures \
    ::module::base::dlist_avail_procs

namespace export dlist_interfaces \
                 get_interfaces \
                 get_mac \
                 get_state \
                 get_mtu \
                 iface_print_callback \
                 iface_rename_callback

proc description {} {
    return "Interface configuration"
}

proc version {} {
    
}

proc check {} {
    foreach feature [list \
                     CONFIG_NAMEIF \
                     CONFIG_IFCONFIG \
                     CONFIG_IFPLUGD \
                     CONFIG_FEATURE_IFCONFIG_STATUS \
                     CONFIG_FEATURE_IFCONFIG_SLIP \
                     CONFIG_FEATURE_IFCONFIG_HW \
                     CONFIG_FEATURE_IFCONFIG_BROADCAST_PLUS] {
        if {! [::helper::busybox_has $feature]} { ;# Can return error.
            error "Busybox doesn't have support for $feature."
        }
    }
}

proc reset {} {
    variable renames
    variable interface

    # All interfaces present on boot are 'up' by default, shutdown is explicitly listed in running-conf,
    # for those interfaces that need to be in 'down' state. Any interface however that appears after boot
    # will be on any state the kernel puts it, typically in 'down' state. That's why we use get_state,
    # so that we are able to sence the state of newly created interfaces as dictated by the kernel.    
    foreach iface [get_interfaces] {
        exec ifconfig $iface up 
    }
    
    # Restore original names.
    foreach iface [array names renames] {
        set mac [get_mac $iface]
        exec nameif -s $renames($iface) $mac
        unset renames($iface)
        unset interface($iface)
    }
    
    array set renames {}
    array set interface {}
    
    foreach iface [get_interfaces] {
        set interface($iface) [dict create]
    }
}

proc constructor {} {
    Global BASE_DIR
    
    log::Info "Loading \"interface\" module: [description]"
    
    check

    # Mapping of <current name> => <original name> of an interface.
    # An entry only present if interface has been renamed.
    variable renames
    array set renames {}
    
    # Mapping of <interface> => <dict> of interface properties.
    variable interface
    array set interface {}
    
    # List of callback procedures to execute while generating the
    # interface running configuration section. This flexibility is
    # not currently provided from the XML-based mechanism.
    # Order matters. They are executed in order of registration.
    # No need to reset this variable.
    variable print_callbacks [list]
    
    log::Debug "Interfaces found: [get_interfaces]"
    
    reset
    
    #        -f/-F           Treat link detection error as link down/link up
    #                        (otherwise exit on error)
    #        -a              Do not up interface automatically
    #        -M              Monitor creation/destruction of interface
    #                        (otherwise it must exist)
    #        -r PROG         Script to run
    #        -x ARG          Extra argument for script
    #        -I              Don't exit on nonzero exit code from script
    #        -p              Don't run script on daemon startup
    #        -q              Don't run script on daemon quit
    #        -l              Run script on startup even if no cable is detected
    #        -t SECS         Poll time in seconds
    #        -u SECS         Delay before running script after link up
    #        -d SECS         Delay after link down
    #        -m MODE         API mode (mii, priv, ethtool, wlan, auto)
    killall ifplugd
    variable interface_event_token [event bind INTERFACE LINKSTATE [namespace current]::event_INTERFACE_LINKSTATE]
    foreach iface [get_interfaces -loopback] {
        exec ifplugd -i $iface -f -a -I -p -q -t 1 -u 0 -d 0 -m auto -r [file join $BASE_DIR scripts ifplugd]
    }

    # Finally load Command Specs
    sysconf loadspecs "modules/interface/interface.specs"
}

proc destructor {} {
    variable interface_event_token
    
    # First unload Command Specs
    sysconf remove "interface"
    
    killall ifplugd
    
    reset
    
    event unbind $interface_event_token
}

################
# Exported
################

# Get the the mac addess of an interface.
#
# @param iface Interface name.
# @return Corresponding mac.
proc get_mac {iface} {
    Global SYS_DIR
    return [string trim [::fileutil::cat [file join $SYS_DIR class net $iface address]]]
}

# Get interface state.
#
# @param iface Interface name.
# @return "up", "down", or "unknown".
proc get_state {iface} {
    Global SYS_DIR
    return [string trim [::fileutil::cat [file join $SYS_DIR class net $iface operstate]]]
}

# Get interface MTU.
#
# @param iface Interface name.
# @return Corresponding mtu.
proc get_mtu {iface} {
    Global SYS_DIR
    return [string trim [::fileutil::cat [file join $SYS_DIR class net $iface mtu]]]
}

# Get the list of intefaces currently on the system
#
# @param flags -loopback : Don't include loopback interfaces in returned list.
# @return A list consisting of all the interface names.
proc get_interfaces {args} {
    Global SYS_DIR
    set ifs [glob -directory [file join $SYS_DIR class net] -tails -nocomplain -- *]
    foreach p $args {
        switch -exact -- $p {
            "-loopback" { lremove ifs "lo" }
            "+null" { lappend ifs "null" }
            default { error "unrecognized option $p "}
        }
    }
    return $ifs
}

# Alias of get_interfaces.
proc dlist_interfaces {sid args} {
    get_interfaces {*}$args
}

# Register callback procs to be called when generating interface running-conf section.
proc iface_print_callback {name} {
    variable print_callbacks
    lappend print_callbacks $name
}

# Register callback procs to be called when renaming an interface.
proc iface_rename_callback {name} {
    variable rename_callbacks
    lappend rename_callbacks $name
}

################
# Handlers
################

command ShInterface {cmdline argstart sid out no arguments args} {
    if {[dict exists $arguments INTERFACE]} {
        set iface [dict get $arguments INTERFACE]
        exec ifconfig $iface
    } else { 
        exec ifconfig -a
    }
}

command Interface {cmdline argstart sid out no arguments args} {
    set iface [dict get $arguments INTERFACE]
    sysconf confmode $sid set "interface"
    sysconf confmode $sid setstring "(interface-${iface})"
    sysconf confmode $sid store $iface
    return
}

proc print_Interface {} {
    variable print_callbacks
    variable interface
    
    set result "\n"
    foreach iface [lsort [array names interface]] {
        set properties $interface($iface)
        append result "interface $iface\n"
        
        if {[dict exists $properties description]} {
            set val [dict get $properties description]
            append result "  description {$val}\n"
        }

        foreach c $print_callbacks { append result [$c $iface] }
        
        if {[dict exists $properties mtu]} {
            set val [dict get $properties mtu]
            append result "  mtu $val\n"
        }
        
        if {[dict exists $properties mac]} {
            set val [dict get $properties mac]
            append result "  mac $val\n"
        }
        
        if {[dict exists $properties onlinkup]} {
            set val [dict get $properties onlinkup]
            append result "  on linkup $val\n"
        }

        if {[dict exists $properties onlinkdown]} {
            set val [dict get $properties onlinkdown]
            append result "  on linkdown $val\n"
        }
        
        if {[get_state $iface] eq "down"} {
            append result "  shutdown\n"
        }
        
        append result "  exit\n"
        append result "#\n"
    }
    return $result
}

command RenameInterface {cmdline argstart sid out no arguments args} {
    variable renames
    variable interface
    
    set iface [dict get $arguments INTERFACE]
    set properties $interface($iface)
    
    if {[get_state $iface] eq "up"} {
        error "interface must be in shutdown state to rename"
    }
    
    if {$no} {
        if {! [info exists renames($iface)]} {
            error "interface already in its original name"
        }
        set origname $renames($iface)
        set mac [get_mac $iface]
        exec nameif -s $origname $mac
        unset renames($iface)
        set interface($origname) $interface($iface)
        unset interface($iface)
        
        # Call rename callbacks, so that other MikroConf modules update their state as well.
        foreach cb $rename_callbacks {
            $cb $iface $origname ;# from to
        }
        return
    }
    
    set newname [dict get $arguments NAME]
    if {$newname in [get_interfaces]} {
        error "interface with this name already exists"
    }
    
    set mac [get_mac $iface]
    exec nameif -s $newname $mac
    
    if {[info exists renames($iface)]} { ;# renamed before
        set origname $renames($iface)
        unset renames($iface)
    } else { ;# first time renamed
        set origname $iface
    }
    set renames($newname) $origname
    set interface($newname) $interface($origname)
    unset interface($origname)
    
    # Call rename callbacks, so that other MikroConf modules update their state as well.
    foreach cb $rename_callbacks {
        $cb $iface $newname ;# from to
    }
    return
}

proc print_RenameInterface {} {
    variable renames
    
    set result {}
    foreach iface [array names renames] {
        append result "rename $renames($iface) $iface\n"
    }
    
    if {! [lempty $result]} {
        set result "#\n${result}#\n"
    }
    return $result
}

command Shutdown {cmdline argstart sid out no arguments args} {
    variable interface
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        exec ifconfig $iface up
    } else {
        exec ifconfig $iface down
    }
    return
}

command Description {cmdline argstart sid out no arguments args} {
    variable interface
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        dict unset interface($iface) description
    } else {
        dict set interface($iface) description [dict get $arguments DESCRIPTION]
    }
    return
}

# We let 'ifconfig' to check validity of provided value.
command Mtu {cmdline argstart sid out no arguments args} {
    variable interface
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        exec ifconfig $iface mtu [dict get interface($iface) mtu_orig]
        dict unset interface($iface) mtu mtu_orig
    } else {
        set old [get_mtu $iface]
        set new [dict get $arguments MTU]
        
        if {! [string is integer $new]} {
            ferror "illegal MTU" [getpos MTU]
        }
        exec ifconfig $iface mtu $new
        dict set interface($iface) mtu $new
        dict set interface($iface) mtu_orig $old
    }
    return
}

# We let 'ifconfig' to check validity of provided value.
command Mac {cmdline argstart sid out no arguments args} {
    variable interface
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        exec ifconfig $iface hw ether [dict get interface($iface) mac_orig]
        dict unset interface($iface) mac mac_orig
    } else {
        set old [get_mac $iface]
        set new [dict get $arguments MAC]
        exec ifconfig $iface hw ether $new
        dict set interface($iface) mac $new
        dict set interface($iface) mac_orig $old
    }
    return
}

command OnLinkdown {cmdline argstart sid out no arguments args} {
    variable interface
    
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        dict unset interface($iface) onlinkdown
    } else {
        dict set interface($iface) onlinkdown [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
    }
    return
}

command OnLinkup {cmdline argstart sid out no arguments args} {
    variable interface
    
    set iface [sysconf confmode $sid store]
    
    if {$no} {
        dict unset interface($iface) onlinkup
    } else {
        dict set interface($iface) onlinkup [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
    }
    return
}

proc event_INTERFACE_LINKSTATE {tag event details} {
    variable interface
    
    lassign $details iface state
    if {! [info exists interface($iface)]} { return }
    set properties $interface($iface)
    
    switch -exact -- $state {
        "up" {
            if {[dict exists $properties onlinkup]} {
                set name [dict get $properties onlinkup]
                if {[catch {
                        procedures::invoke {*}$name
                    } result options]} {
                    log::Error -stack $options "Event handler error \"$name\": $result"
                }
            }
        }
        "down" {
            if {[dict exists $properties onlinkdown]} {
                set name [dict get $properties onlinkdown]
                if {[catch {
                        procedures::invoke {*}$name
                    } result options]} {
                    log::Error -stack $options "Event handler error \"$name\": $result"
                }
            }
        }
        default {
            log::Notice "unrecognized interface state \"$state\""
        }
    }
}

} ;# End of Namespace

Overview | Index by: file name | procedure name | procedure call | annotation
File generated 2010-03-13 at 22:28.