# copyright (C) 1997-2001 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

set rcsId {$Id: arp.tcl,v 1.32 2001/01/27 15:08:19 jfontain Exp $}


package provide arp [lindex {$Revision: 1.32 $} 1]
package require network 1

namespace eval arp {

    array set data {
        updates 0
        0,label IP 0,type dictionary 0,message {IP address} 0,anchor left
        1,label hardware 1,type dictionary 1,message {hardware address}
        2,label decoded 2,type dictionary 2,message {decoded hardware address} 2,anchor left
        3,label type 3,type ascii 3,message {hardware type}
        4,label flags 4,type ascii 4,message {entry flags (C: complete, M: permanent, P: published)}
        5,label interface 5,type ascii 5,message {networking interface}
        sort {0 increasing}
        switches {-n 0 --numeric 0 -r 1 --remote 1}
    }
    set file [open arp.htm]
    set data(helpText) [read $file]                                                           ;# initialize HTML help data from file
    close $file

    proc initialize {optionsName} {
        upvar $optionsName options
        variable lookup
        variable remote
        variable data
        variable arpFile
        variable etherName

        set lookup [expr {![info exists options(-n)]&&![info exists options(--numeric)]}]            ;# host or network names lookup
        if {![catch {set locator $options(--remote)}]||![catch {set locator $options(-r)}]} {                   ;# remote monitoring
            set data(pollTimes) {20 10 30 60 120 300 600}                                ;# poll less often when remotely monitoring
            foreach {remote(protocol) remote(user) remote(host)} [network::parseRemoteLocator $locator] {}
            network::checkRemoteOutputEmptiness $remote(protocol) $remote(user) $remote(host)
            set data(identifier) arp($remote(host))
            set file [open "| /usr/bin/$remote(protocol) -n -l $remote(user) $remote(host) cat /proc/net/arp"]
            fileevent $file readable {set ::arp::remote(busy) 0}
            vwait ::arp::remote(busy)                                                                       ;# allow GUI interaction
            # detect errors early (but avoid write on pipe with no readers errors by reading whole data)
            if {[catch {read $file} message]||[catch {close $file} message]} {
                error "on remote host $remote(host) as user $remote(user): $message"
            }
            set file [open "| /usr/bin/$remote(protocol) -n -l $remote(user) $remote(host) cat /etc/ethers"]
            fileevent $file readable {set ::arp::remote(busy) 0}
            vwait ::arp::remote(busy)                                                                       ;# allow GUI interaction
            if {![catch {close $file}]} {                                     ;# check file readability because opening always works
                set ethernetFile [open "| /usr/bin/$remote(protocol) -n -l $remote(user) $remote(host) cat /etc/ethers"]
            }
        } else {
            set data(pollTimes) {10 5 20 30 60 120 300 600}
            catch {set ethernetFile [open /etc/ethers]}
            set arpFile [open /proc/net/arp]                                          ;# keep local file open for better performance
        }
        if {[info exist ethernetFile]} {                                         ;# save ethernet address to name mapping if present
            while {[gets $ethernetFile line]>=0} {
                if {[string match #* $line]} continue                                                               ;# skip comments
                if {[scan $line {%x:%x:%x:%x:%x:%x %s} byte(0) byte(1) byte(2) byte(3) byte(4) byte(5) name]==7} {
                    set address [format {%02X:%02X:%02X:%02X:%02X:%02X} $byte(0) $byte(1) $byte(2) $byte(3) $byte(4) $byte(5)]
                    set etherName($address) $name
                }
            }
            close $ethernetFile
        }
    }

    set file [open vendor.txt]
    while {[gets $file line]>=0} {                              ;# save ethernet address code (first 3 bytes) to vendor name mapping
        if {[scan $line {%2x%2x%2x %s} byte(0) byte(1) byte(2) word]==4} {                   ;# only keep the vendor name first word
            set code [format {%02X:%02X:%02X} $byte(0) $byte(1) $byte(2)]
            set vendor($code) $word
        }
    }
    close $file

    # initialize hardware type mapping (from the /usr/src/linux/include/linux/if_arp.h file)
    array set hardwareType {
        0 NETROM 1 ETHER 2 EETHER 3 AX25 4 PRONET 5 CHAOS 6 IEEE802 7 ARCNET 8 APPLETLK 15 DLCI 19 ATM 23 METRICOM
        256 SLIP 257 CSLIP 258 SLIP6 259 CSLIP6 260 RSRVD 264 ADAPT 270 ROSE 271 X25 272 HWX25
        512 PPP 513 HDLC 516 LAPB 517 DDCMP
        768 TUNNEL 769 TUNNEL6 770 FRAD 771 SKIP 772 LOOPBACK 773 LOCALTLK 774 FDDI 775 BIF 776 SIT 777 IPDDP 778 IPGRE 779 PIMREG
        780 HIPPI 781 ASH 782 ECONET 783 IRDA 784 FCPP 785 FCAL 786 FCPL 787 FCFABRIC
        800 IEEE802_TR
        65535 VOID
    }

    set nextIndex 0

    proc update {} {
        variable remote
        variable arpFile
        variable index
        variable nextIndex
        variable data
        variable hardwareType
        variable etherName
        variable vendor
        variable lookup

        if {[info exists remote]} {
            if {![info exists arpFile]} {                                  ;# start data gathering process in a non blocking fashion
                if {$remote(busy)} return                                           ;# core invocation while waiting for remote data
                set remote(busy) 1
                set file [open "| /usr/bin/$remote(protocol) -n -l $remote(user) $remote(host) cat /proc/net/arp"]
                fileevent $file readable "set ::arp::arpFile $file; ::arp::update"   ;# do not hang GUI, allow other modules updates
                return                                                                                       ;# wait for remote data
            }                                                                                 ;# else continue below to process data
        } else {
            seek $arpFile 0                                                                         ;# rewind before retrieving data
        }
        gets $arpFile                                                                                            ;# skip header line
        set lines {}
        while {[gets $arpFile line]>=0} {
            lappend lines $line
        }
        if {[info exists remote]} {                                                 ;# closing is necessary since seek does not work
            read $arpFile                                    ;# avoid write on pipe with no readers errors by reading remaining data
            if {[catch {close $arpFile} message]} {                                      ;# communication error can be detected here
                flashMessage "arp error: $message"
                set lines {}                                                                   ;# consider data corrupted as a whole
            }
            unset arpFile
            set remote(busy) 0
        }
        foreach line $lines {
            scan $line {%u.%u.%u.%u 0x%x 0x%x %s %*s %s} ip(0) ip(1) ip(2) ip(3) type flags address interface
            if {[catch {set index($address)} row]} {
                # new entry, use unique hardware address as index instead of IP address
                # (in which case duplicate addresses could not be displayed)
                set row [set index($address) $nextIndex]
                incr nextIndex
                set data($row,1) $address
                set data($row,2) {}
                if {$type==1} {                                                                                          ;# ethernet
                    if {[info exists etherName($address)]} {                                                      ;# try name lookup
                        set data($row,2) $etherName($address)
                    } else {
                        regexp {^(..:..:..):(..:..:..)$} $address dummy code number
                        if {[info exists vendor($code)]} {                                                      ;# try vendor lookup
                            set data($row,2) $vendor($code):$number
                        }
                    }
                }
            }
            set data($row,0) $ip(0).$ip(1).$ip(2).$ip(3)
            if {$lookup} {
                catch {set data($row,0) [network::hostfromaddress $data($row,0)]}                                 ;# try name lookup
            }
            if {[catch {set data($row,3) $hardwareType($type)}]} {
                set data($row,3) ?                                                                               ;# unknown hardware
            }
            set data($row,4) {}                                         ;# flags from the /usr/src/linux/include/linux/if_arp.h file
            if {$flags&2} {append data($row,4) C}                                                                 ;# completed entry
            if {$flags&4} {append data($row,4) M}                                                                 ;# permanent entry
            if {$flags&8} {append data($row,4) P}                                                                 ;# published entry
            set data($row,5) $interface
            set current($address) {}
        }
        foreach {address row} [array get index] {                                                     ;# cleanup disappeared entries
            if {[info exists current($address)]} continue
            unset index($address) data($row,0) data($row,1) data($row,2) data($row,3)
        }
        incr data(updates)
    }

}
