Overview | Index by: file name |
procedure name |
procedure call |
annotation
ipconf-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 "ipconf" module is responsible for basic IP Configuration.
#//#
module require base 1.0
module require interface 1.0
module provide ipconf 1.0
package require dns 1.3.2 ;# Tcllib
namespace eval ::module::ipconf {
namespace import ::helper::* ::module::api::* \
::module::interface::dlist_interfaces \
::module::interface::iface_print_callback \
::module::interface::iface_rename_callback \
::module::base::dlist_procedures
proc description {} {
return "IP Configuration"
}
proc version {} {
}
proc check {} {
foreach feature [list \
CONFIG_APP_UDHCPC \
CONFIG_FEATURE_UDHCPC_ARPING \
CONFIG_FEATURE_UDHCP_RFC3397 \
\
CONFIG_BB_SYSCTL \
CONFIG_HOSTNAME \
CONFIG_ROUTE \
CONFIG_IFCONFIG \
CONFIG_ARP \
CONFIG_NETSTAT] {
if {! [::helper::busybox_has $feature]} { ;# Can return error.
error "Busybox doesn't have support for $feature."
}
}
}
proc reset {} {
variable HOSTS_FILE
variable RESOLV_FILE
variable HOSTNAME_FILE
fileutil::writeFile $HOSTS_FILE "127.0.0.1 localhost"
fileutil::writeFile $RESOLV_FILE ""
# Local hostname
fileutil::writeFile $HOSTNAME_FILE "Router"
exec hostname Router
# List of hostnames configured to statically map to IP addresses, in order of configuration.
variable IpHost_order {}
# Array that maps configured Hostnames to static IP addresses.
variable ip_host_map
array set ip_host_map {}
# List of name servers configured, in the order of configuration.
variable nameservers {}
# The local domain name.
variable domainname {}
# Restore default value.
Global -rw DNS_LOOKUPS
variable dnslookups_def
set DNS_LOOKUPS $dnslookups_def
# List of {prefix netmask} keys of configured static routes, in order of configuration.
variable ip_routes [list]
# Array that maps {prefix netmask} keys to route properties.
variable ip_routes_map
array set ip_host_map {}
# Array that maps interface names to their ip configuration.
variable ip_address_map
array set ip_address_map {}
# DHCP Client configuration per interface (key)
variable dhcp_client_map
array set dhcp_client_map {}
killall udhcpc
}
proc constructor {} {
log::Info "Loading \"ipconf\" module: [description]"
Global ETC_DIR
variable HOSTS_FILE [file join $ETC_DIR hosts]
variable RESOLV_FILE [file join $ETC_DIR resolv.conf]
variable HOSTNAME_FILE [file join $ETC_DIR hostname]
check
# Flag to indicate that DNS will be used to resolve IPs to hostnames.
# Store in this variable the default value.
Global DNS_LOOKUPS
variable dnslookups_def $DNS_LOOKUPS
reset
# Finally load Command Specs
sysconf loadspecs "modules/ipconf/ipconf.specs"
iface_print_callback [namespace current]::print_ifaceConf
iface_print_callback [namespace current]::print_dhcpConf
iface_rename_callback [namespace current]::rename_ifaceConf
iface_rename_callback [namespace current]::rename_dhcpConf
variable dhcp_events_token [event bind DHCPCLIENT EVENT [namespace current]::event_DHCPCLIENT_EVENT]
}
proc destructor {} {
variable dhcp_events_token
# First unload Command Specs
sysconf remove "ipconf"
reset
event bind $dhcp_events_token
}
################
# Handlers
################
command ShArp {cmdline argstart sid out no arguments args} {
Global DNS_LOOKUPS
exec arp -a [? !$DNS_LOOKUPS "-n"]
}
command ShIpRoute {cmdline argstart sid out no arguments args} {
Global DNS_LOOKUPS
exec route [? !$DNS_LOOKUPS "-n"]
}
command ShNetstat {cmdline argstart sid out no arguments args} {
Global DNS_LOOKUPS
if {[dict exists $arguments TYPE]} {
set type [string map {tcp -t udp -u raw -u unix -x listening -l} [dict get $arguments TYPE]]
} else {
set type "-a" ;# all.
}
exec netstat [? !$DNS_LOOKUPS "-n"] $type
}
# @limit Local DNS caching.
command ShHosts {cmdline argstart sid out no arguments args} {
variable ip_host_map
variable IpHost_order
variable domainname
variable nameservers
puts -nonewline $out "Default domain is "
if {[lempty $domainname]} {
puts "not set"
} else {
puts $domainname
}
if {[lempty $nameservers]} {
puts "Name/address lookup uses local mapping\n"
} else {
puts "Name/address lookup uses DNS"
puts "Name servers are $nameservers\n"
}
puts [format {%-40s %-8s %-6s %-s} "Host" "Flags" "Type" "Addresses"]
foreach hostname $IpHost_order {
puts [format {%-40s %-8s %-6s %-s} $hostname "Static" "IP" $ip_host_map($hostname)]
}
return
}
command Hostname {cmdline argstart sid out no arguments args} {
variable HOSTNAME_FILE
# We let the 'hostname' tool to verify correctness,
# which in case of busybox's 'hostname' it relies,
# in turn, on the 'sethostname' syscall.
set hostname [dict get $arguments HOSTNAME]
if {! [ishostname $hostname]} {
ferror "illegal hostname" [getpos HOSTNAME]
}
exec hostname [dict get $arguments HOSTNAME]
fileutil::writeFile $HOSTNAME_FILE [dict get $arguments HOSTNAME]
return
}
proc print_Hostname {} {
# 'info hostname' doesn't get updated after hostname changes (Tcl.8.5).
return "hostname [dict get [uname] node-name]\n"
}
# @limit Support for IPv6.
command IpHost {cmdline argstart sid out no arguments args} {
variable HOSTS_FILE
variable ip_host_map
variable IpHost_order
if {$no} {
if {! [file exists $HOSTS_FILE]} { return }
set hostname [dict get $arguments HOSTNAME]
if {! [ishostname $hostname]} {
ferror "illegal hostname" [getpos HOSTNAME]
}
if {$hostname ni $IpHost_order} {
ferror "hostname does not exist" [getpos HOSTNAME]
}
removeline $HOSTS_FILE "$hostname *"
unset ip_host_map($hostname)
lremove IpHost_order $hostname
} else {
foreach {var val} $arguments {
switch -exact -- $var {
"HOSTNAME" {
set hostname $val
if {! [ishostname $hostname]} {
ferror "illegal hostname" [getpos HOSTNAME]
}
if {$hostname in $IpHost_order} {
# Note: IPs are not appended to existing ones.
lremove IpHost_order $hostname
unset ip_host_map($hostname)
removeline $HOSTS_FILE "$hostname *"
}
}
"IPADDRESS" {
if {! [isip $val]} {
ferror "illegal ip address" [getpos IPADDRESS]
}
lappend ipaddrs $val
::fileutil::appendToFile $HOSTS_FILE "$hostname $val\n"
}
}
}
set ip_host_map($hostname) $ipaddrs
lappend IpHost_order $hostname
}
return
}
proc print_IpHost {} {
variable IpHost_order
variable ip_host_map
set result {}
foreach hostname $IpHost_order {
append result "ip host $hostname $ip_host_map($hostname)\n"
}
return $result
}
################################
# DNS Related
################################
command IpNameserver {cmdline argstart sid out no arguments args} {
variable RESOLV_FILE
variable nameservers
set ip [dict get $arguments IPADDRESS]
if {! [isip $ip]} {
ferror "not a valid IP address" [getpos IPADDRESS]
}
if {$no} {
if {$ip ni $nameservers} {
ferror "name server is not registered" [getpos IPADDRESS]
}
removeline $RESOLV_FILE "nameserver $ip"
lremove nameservers $ip
return
}
if {[llength $nameservers] >= 3} {
error "a maximum of three name servers can be specified"
}
::fileutil::appendToFile $RESOLV_FILE "nameserver $ip\n"
lappend nameservers $ip
return
}
proc print_IpNameserver {} {
variable nameservers
set result {}
if {! [lempty $nameservers]} {
foreach ip $nameservers {
append result "ip name-server $ip" "\n"
}
}
return $result
}
command IpDomainname {cmdline argstart sid out no arguments args} {
variable RESOLV_FILE
variable domainname
set name [dict get $arguments DOMAIN]
if {! [isdomainname $name]} {
ferror "not a valid domain name" [getpos DOMAIN]
}
if {$no} {
removeline $RESOLV_FILE "domain *"
set domainname {}
return
}
if {! [lempty $domainname]} {
removeline $RESOLV_FILE "domain *"
}
::fileutil::appendToFile $RESOLV_FILE "domain $name\n"
set domainname $name
return
}
proc print_IpDomainname {} {
variable domainname
if {! [lempty $domainname]} {
return "ip domain-name $domainname\n"
}
return
}
command IpDomainlookup {cmdline argstart sid out no arguments args} {
Global -rw DNS_LOOKUPS
if {$no} {
set DNS_LOOKUPS 0
} else {
set DNS_LOOKUPS 1
}
return
}
proc print_IpDomainlookup {} {
Global DNS_LOOKUPS
if {! $DNS_LOOKUPS} {
return "no ip domain-lookup\n"
}
return
}
command IpRoute {cmdline argstart sid out no arguments args} {
variable ip_routes
variable ip_routes_map
set prefix [dict get $arguments PREFIX]
if {! [isip $prefix]} {
ferror "not a valid prefix address" [getpos PREFIX]
}
set netmask [dict get $arguments NETMASK]
if {! [isip $netmask]} {
ferror "not a valid netmask" [getpos NETMASK]
}
set key [list $prefix $netmask]
set pros [dict create]
if {$no} {
exec route del -net $prefix netmask $netmask
lremove ip_routes $key
unset ip_routes_map($key)
return
}
if {[dict exists $arguments DISTANCE]} {
set distance [dict get $arguments DISTANCE]
if {! [string is integer $distance] || ! (0 <= $distance <= 255)} {
ferror "distance must be an integer in the range 0..255" [getpos DISTANCE]
}
dict set pros distance $distance
} else {
if {[dict exists $arguments INTERFACE]} {
set distance 0 ;# Default distance for directly connected interfaces.
} else {
set distance 1 ;# Default distance for static routes.
}
}
if {[dict exists $arguments INTERFACE]} {
set iface [dict get $arguments INTERFACE]
if {$iface eq "null"} {
exec route add -net $prefix netmask $netmask metric $distance reject
dict set pros next "null"
} else {
exec route add -net $prefix netmask $netmask metric $distance dev $iface
dict set pros next "$iface"
}
} else {
set nexthop [dict get $arguments IPADDRESS]
if {! [isip $nexthop]} {
ferror "not a valid IP address" [getpos IPADDRESS]
}
exec route add -net $prefix netmask $netmask metric $distance gw $nexthop
dict set pros next "$nexthop"
}
lappend ip_routes $key
set ip_routes_map($key) $pros
}
proc print_IpRoute {} {
variable ip_routes
variable ip_routes_map
set result {}
foreach e $ip_routes {
lassign $e prefix netmask
set pros $ip_routes_map($e)
set nexthop [dict get $pros next]
if {[dict exists $pros distance]} {
set distance [dict get $pros distance]
append result "ip route $prefix $netmask $next $distance\n"
} else {
append result "ip route $prefix $netmask $next\n"
}
}
return $result
}
command IpAddress {cmdline argstart sid out no arguments args} {
variable ip_address_map
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
# kill first udhcpc if ip was assigned via dhcp
dict set dhcp_client_map($iface) "enable" 0
udhcpc_reset $iface
exec ifconfig $iface 0.0.0.0
unset ip_address_map($iface)
return
}
if {[dict exists $arguments dhcp]} {
# Remove static address first
catch { unset ip_address_map($iface) }
dict set dhcp_client_map($iface) "enable" 1
udhcpc_reset $iface
} else { ;# Static address
# Disable dhcp first
dict set dhcp_client_map($iface) "enable" 0
udhcpc_reset $iface
set address [dict get $arguments IPADDRESS]
if {! [isip $address]} {
ferror "not a valid IP address" [getpos IPADDRESS]
}
set netmask [dict get $arguments NETMASK]
if {! [isip $netmask]} {
ferror "not a valid netmask" [getpos NETMASK]
}
exec ifconfig $iface $address netmask $netmask
set ip_address_map($iface) [list $address $netmask]
}
return
}
proc print_ifaceConf {iface} {
variable ip_address_map
if {$iface in [array names ip_address_map]} {
lassign $ip_address_map($iface) address netmask
return " ip address $address $netmask\n"
}
return
}
proc rename_ifaceConf {from to} {
variable ip_address_map
set ip_address_map($to) $ip_address_map($from)
unset ip_address_map($from)
}
######################
# DHCP Client
######################
proc udhcpc_reset {iface} {
Global BASE_DIR
variable dhcp_client_map
if {! [info exists dhcp_client_map($iface)]} {
return
}
set properties $dhcp_client_map($iface)
if {[dict exists $properties "pid"]} {
kill KILL [dict get $properties "pid"]
dict unset dhcp_client_map($iface) "pid"
}
if {! [dict exists $properties "enable"] || ! [dict get $properties "enable"]} { return }
set params "-i $iface -s [file join $BASE_DIR scripts udhcpc] -S -R -a "
if {[dict exists $properties "client-id"]} {
append params "-c [dict get $properties client-id] "
}
if {[dict exists $properties "class-id"]} {
append params "-V [dict get $properties class-id] "
}
if {[dict exists $properties "hostname"]} {
append params "-h [dict get $properties hostname] "
}
if {[dict exists $properties "request-ip"]} {
append params "-r [dict get $properties request-ip] "
}
if {[dict exists $properties "request"]} {
foreach option [dict get $properties "request"] {
append params "-O [dict get $properties request] "
}
}
dict set dhcp_client_map($iface) "pid" [exec udhcpc {*}$params > /dev/null 2> /dev/null &]
}
command IpDhcpClientClientId {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) "client-id"
} else {
dict set dhcp_client_map($iface) "client-id" [dict get $arguments CLIENTID]
}
udhcpc_reset $iface
}
command IpDhcpClientClassId {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) "class-id"
} else {
dict set dhcp_client_map($iface) "class-id" [dict get $arguments CLASSID]
}
udhcpc_reset $iface
}
command IpDhcpClientHostname {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) "hostname"
} else {
set hostname [dict get $arguments HOSTNAME]
if {! [ishostname $hostname]} {
ferror "illegal hostname" [getpos HOSTNAME]
}
dict set dhcp_client_map($iface) "hostname" $hostname
}
udhcpc_reset $iface
}
command IpDhcpClientRequestIp {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) "request-ip"
} else {
set ipaddr [dict get $arguments IPADDRESS]
if {! [isip $ipaddr]} {
ferror "illegal ip address" [getpos IPADDRESS]
}
dict set dhcp_client_map($iface) "request-ip" $ipaddr
}
udhcpc_reset $iface
}
command IpDhcpClientRequest {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
set option [dict get $arguments OPTION]
if {$no} {
if {! [dict exists $dhcp_client_map($iface) "request"] || $option ni [dict get $dhcp_client_map($iface) "request"]} {
ferror "dhcp option not requested" [getpos OPTION]
}
dict update dhcp_client_map($iface) "request" newval { lremove newval $option }
if {[lempty [dict get $dhcp_client_map($iface) "request"]]} {
dict unset dhcp_client_map($iface) "request"
}
} else {
dict lappend dhcp_client_map($iface) "request" $option
}
udhcpc_reset $iface
}
command OnDhcpBound {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) on_dhcp_bound
} else {
dict set dhcp_client_map($iface) on_dhcp_bound [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
}
return
}
command OnDhcpRenew {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) on_dhcp_renew
} else {
dict set dhcp_client_map($iface) on_dhcp_renew [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
}
return
}
command OnDhcpDeconfig {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) on_dhcp_deconfig
} else {
dict set dhcp_client_map($iface) on_dhcp_deconfig [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
}
return
}
command OnDhcpLeasefail {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) on_dhcp_leasefail
} else {
dict set dhcp_client_map($iface) on_dhcp_leasefail [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
}
return
}
command OnDhcpNak {cmdline argstart sid out no arguments args} {
variable dhcp_client_map
set iface [sysconf confmode $sid store]
if {$no} {
dict unset dhcp_client_map($iface) on_dhcp_nak
} else {
dict set dhcp_client_map($iface) on_dhcp_nak [concat [dict get $arguments PROCEDURE] [getall ARGUMENT]]
}
return
}
proc event_DHCPCLIENT_EVENT {tag event details} {
variable dhcp_client_map
lassign $details iface state
if {! [info exists dhcp_client_map($iface)]} { return }
set properties $dhcp_client_map($iface)
switch -exact -- $state {
"bound" {
if {[dict exists $properties on_dhcp_bound]} {
set name [dict get $properties on_dhcp_bound]
if {[catch {
procedures::invoke {*}$name
} result options]} {
log::Error -stack $options "Event while evaluating event handler \"$name\": $result"
}
}
}
"renew" {
if {[dict exists $properties on_dhcp_renew]} {
set name [dict get $properties on_dhcp_renew]
if {[catch {
procedures::invoke {*}$name
} result options]} {
log::Error -stack $options "Event while evaluating event handler \"$name\": $result"
}
}
}
"deconfig" {
if {[dict exists $properties on_dhcp_deconfig]} {
set name [dict get $properties on_dhcp_deconfig]
if {[catch {
procedures::invoke {*}$name
} result options]} {
log::Error -stack $options "Event while evaluating event handler \"$name\": $result"
}
}
}
"leasefail" {
if {[dict exists $properties on_dhcp_leasefail]} {
set name [dict get $properties on_dhcp_leasefail]
if {[catch {
procedures::invoke {*}$name
} result options]} {
log::Error -stack $options "Event while evaluating event handler \"$name\": $result"
}
}
}
"nak" {
if {[dict exists $properties on_dhcp_nak]} {
set name [dict get $properties on_dhcp_nak]
if {[catch {
procedures::invoke {*}$name
} result options]} {
log::Error -stack $options "Event while evaluating event handler \"$name\": $result"
}
}
}
default {
log::Notice "unrecognized dhcp event \"$state\""
}
}
}
proc print_dhcpConf {iface} {
variable dhcp_client_map
if {! [info exists dhcp_client_map($iface)]} {
return
}
set result {}
set properties $dhcp_client_map($iface)
if {[dict exists $properties "client-id"]} {
append result " ip dhcp client client-id [dict get $properties client-id]\n"
}
if {[dict exists $properties "class-id"]} {
append result " ip dhcp client class-id [dict get $properties class-id]\n"
}
if {[dict exists $properties "hostname"]} {
append result " ip dhcp client hostname [dict get $properties hostname]\n"
}
if {[dict exists $properties "request-ip"]} {
append result " ip dhcp client request-ip [dict get $properties request-ip]\n"
}
if {[dict exists $properties "request"]} {
foreach option [dict get $properties "request"] {
append result " ip dhcp client request [dict get $properties request]\n"
}
}
if {[dict exists $properties on_dhcp_bound]} {
append result " on dhcp bound [dict get $properties on_dhcp_bound]\n"
}
if {[dict exists $properties on_dhcp_renew]} {
append result " on dhcp bound [dict get $properties on_dhcp_renew]\n"
}
if {[dict exists $properties on_dhcp_deconfig]} {
append result " on dhcp bound [dict get $properties on_dhcp_deconfig]\n"
}
if {[dict exists $properties on_dhcp_leasefail]} {
append result " on dhcp bound [dict get $properties on_dhcp_leasefail]\n"
}
if {[dict exists $properties on_dhcp_nak]} {
append result " on dhcp bound [dict get $properties on_dhcp_nak]\n"
}
if {[dict exists $properties "enable"] && [dict get $properties "enable"]} {
append result " ip address dhcp\n"
}
return $result
}
proc rename_dhcpConf {from to} {
variable dhcp_client_map
set dhcp_client_map($to) $dhcp_client_map($from)
unset dhcp_client_map($from)
}
} ;# End of Namespace
Overview | Index by: file name |
procedure name |
procedure call |
annotation
File generated 2010-03-13 at 22:28.