Mercurial > nand-ecoscentric
view packages/devs/eth/synth/ecosynth/current/host/ethernet.tcl @ 3403:8c8767bb9a62 default tip
devs/nand/micron_mt29: Remove defunct references (in comments) to mt29f2g08_oob_ecc.
| author | Ross Younger <wry@ecoscentric.com> |
|---|---|
| date | Sun, 18 Jan 2015 17:59:01 +1300 |
| parents | 74dbf4c3f2e1 |
| children |
line wrap: on
line source
# {{{ Banner # ============================================================================ # # ethernet.tcl # # Ethernet support for the eCos synthetic target I/O auxiliary # # ============================================================================ # ####ECOSHOSTGPLCOPYRIGHTBEGIN#### # ------------------------------------------- # This file is part of the eCos host tools. # Copyright (C) 2002 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., 51 Franklin Street, # Fifth Floor, Boston, MA 02110-1301, USA. # ------------------------------------------- # ####ECOSHOSTGPLCOPYRIGHTEND#### # ============================================================================ # #####DESCRIPTIONBEGIN#### # # Author(s): bartv # Contact(s): bartv # Date: 2002/08/07 # Version: 0.01 # Description: # Implementation of the ethernet device. This script should only ever # be run from inside the ecosynth auxiliary. # # ####DESCRIPTIONEND#### # ============================================================================ # }}} # Overview. # # Linux provides a number of different ways of performing low-level # ethernet I/O from user space, including accessing an otherwise # unused ethernet card via a PF_PACKET socket, and the tap facility. # The necessary functionality is not readily accessible from Tcl, # and performing this low-level I/O generally requires special # privileges. Therefore the actual I/O happens in a C program # rawether, installed suid root, # # The synthetic ethernet package supports up to four ethernet devices, # eth0 to eth3. The target definition file maps these onto the # underlying I/O facility. Instantiation requires spawning a rawether # process with appropriate arguments, and then waiting for a message # from that process indicating whether or not the instantiation # succeeded. That message includes the MAC address. A file event # handler is installed to handle data detected by raw ether. # # eCos can send a number of requests: transmit a packet, start the # interface (possibly in promiscuous mode), stop the interface, # or get the various parameters such as the MAC address. All those # requests can just be passed on to the rawether process. Incoming # ethernet packets are slightly more complicated: rawether will # immediately pass these up to this Tcl script, which will buffer # the packets until they are requested by eCos; in addition an # interrupt will be raised. namespace eval ethernet { # The protocol between eCos and this script. variable SYNTH_ETH_TX 0x01 variable SYNTH_ETH_RX 0x02 variable SYNTH_ETH_START 0x03 variable SYNTH_ETH_STOP 0x04 variable SYNTH_ETH_GETPARAMS 0x05 variable SYNTH_ETH_MULTIALL 0x06 # This array holds all the interesting data for all the # interfaces, indexed by the instance id. It is also useful # to keep track of the instance id's associated with ethernet # devices. array set data [list] set ids [list] # One-off initialization, for example loading images. If this fails # then all attempts at instantiation will fail as well. variable init_ok 1 variable install_dir $synth::device_install_dir variable rawether_executable [file join $ethernet::install_dir "rawether"] if { ![file exists $rawether_executable] } { synth::report_error "Ethernet device, rawether executable has not been installed in $ethernet::install_dir.\n" set init_ok 0 } elseif { ![file executable $rawether_executable] } { synth::report_error "Ethernet device, installed program $rawether_executable is not executable.\n" set init_ok 0 } if { $synth::flag_gui } { foreach _image [list "netrecord.xbm"] { variable image_[file rootname $_image] if { ! [synth::load_image "ethernet::image_[file rootname $_image]" [file join $ethernet::install_dir $_image]] } { set init_ok 0 } } unset _image } # Maximum number of packets that should be buffered per interface. # This can be changed in the target definition variable max_buffered_packets 16 if { [synth::tdf_has_option "ethernet" "max_buffer"] } { set ethernet::max_buffered_packets [synth::tdf_get_option "ethernet" "max_buffer"] if { ![string is integer -strict $ethernet::max_buffered_packets] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ Entry max_buffer should be a simple integer, not $ethernet::max_buffered_packets\n" set init_ok 0 } } # Define hooks for tx and rx packets synth::hook_define "ethernet_tx" synth::hook_define "ethernet_rx" # Get a list of known ethernet devices proc devices_get_list { } { set result [list] foreach id $ids { lappend result $::ethernet::data($id,name) } return $result } # ---------------------------------------------------------------------------- proc instantiate { id name data } { if { ! $ethernet::init_ok } { synth::report_warning "Cannot instantiate ethernet device $name, initialization failed.\n" return "" } # id is a small number that uniquely identifies this device. It will # be used as an array index. # name is something like eth0 or eth1 # There should be no device-specific data # The hard work is done by an auxiliary process which needs to be # spawned off. It requires some additional information to map the # eCos device name on to a suitable Linux network device such # as tap0. That information has to come from the config file. if { ![synth::tdf_has_option "ethernet" $name] } { synth::report_error "Cannot instantiate ethernet device $name\n \ No entry in target definition file $synth::target_definition\n" return "" } set use [synth::tdf_get_option "ethernet" $name] # Do some validation here, before the rawether process is started. # Typical entries would look like # eth0 real eth1 # eth1 ethertap [[tap-device] [MAC] [persistent]] set junk "" set optional "" set mac "" if { [regexp -- {^\s*real\s*[a-zA-z0-9_]+$} $use] } { # Real ethernet. } elseif { [regexp -- {^\s*ethertap\s*(.*)$} $use junk optional ] } { if { "" != $optional } { if { ! [regexp -- {^tap[0-9]+\s*(.*)$} $optional junk mac ] } { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ Should be \"ethertap \[<tap-device> \[<MAC address>\]\] [persistent]\"\n" return "" } if { "" != $mac } { if { ! [regexp -- {^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*} $mac ] } { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ MAC address should be of the form xx:xx:xx:xx:xx:xx, all hexadecimal digits.\n" return "" } } } } else { synth::report_error "Cannot instantiate ethernet device $name\n \ Invalid entry \"$use\" in target definition file $synth::target_definition\n \ Should be \"real <Linux ethernet device>\" or \"ethertap \[<tap-device> \[<MAC address>\]\]\"\n" return "" } # Now spawn the rawether process. Its stdin and stdout are # pipes connected to ecosynth. Its stderr is redirected to # the current tty to avoid confusion between incoming ethernet # packets and diagnostics. if { [catch { set rawether [open "|$ethernet::rawether_executable $use 2>/dev/tty" w+] } message ] } { synth::report_error "Failed to spawn rawether process for device $name\n $message" return "" } # No translation on this pipe please. fconfigure $rawether -translation binary -encoding binary -buffering none # Now wait for the rawether device to initialize. It should send back a single # byte, '0' for failure or '1' for success. Failure is followed by a text # message which should be reported. Success is followed by a six-byte MAC # address. set reply [read $rawether 1] if { "" == $reply } { synth::report_error "rawether process for device $name exited unexpectedly.\n" catch { close $rawether } return "" } if { "1" != $reply } { set message [read $rawether 1024] synth::report_error "rawether process was unable to initialize eCos device $name ($use)\n $message" catch { close $rawether } return "" } set reply [read $rawether 7] if { [string length $reply] != 7 } { synth::report_error "rawether process for eCos device $name ($use) failed to provide the initialization response.\n" catch { close $rawether } return "" } set mac [string range $reply 0 5] set multi [string index $reply 6] # Finally allocate an interrupt vector set vector [synth::interrupt_allocate $name] if { -1 == $vector } { # No more interrupts left. An error will have been reported already. catch { close $rawether } return "" } # The device is up and running. Fill in the array entries lappend ethernet::ids $id set ethernet::data($id,alive) 1 set ethernet::data($id,name) $name set ethernet::data($id,rawether) $rawether set ethernet::data($id,packets) [list] set ethernet::data($id,packet_count) 0 set ethernet::data($id,up) 0 set ethernet::data($id,interrupt_vector) $vector set ethernet::data($id,MAC) $mac set ethernet::data($id,multi) $multi # Set up the event handler to handle incoming packets. There should # not be any until the interface is brought up fileevent $rawether readable [list ethernet::handle_packet $name $id $rawether] # Finally return the request handler. The eCos device driver will # automatically get back an ack. return ethernet::handle_request } # ---------------------------------------------------------------------------- # eCos has sent a request to a device instance. Most of these requests should # just be forwarded to rawether. Some care has to be taken to preserve # packet boundaries and avoid confusion. It is also necessary to worry # about the rawether process exiting unexpectedly, which may cause # puts operations to raise an error (subject to buffering). # # Note: it might actually be more efficient to always send a header plus # 1514 bytes of data, reducing the number of system calls at the cost of # some extra data copying, but with at least two process switches per # ethernet transfer efficiency is not going to be particularly good # anyway. proc send_rawether { id packet } { if { $ethernet::data($id,alive) } { set chan $ethernet::data($id,rawether) if { [catch { puts -nonewline $chan $packet } ] } { set ethernet::data($id,alive) 0 # No further action is needed here, instead the read handler # will detect EOF and report abnormal termination. } } } proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } { if { $reqcode == $ethernet::SYNTH_ETH_TX } { # Transmit a single packet. To preserve packet boundaries # this involves a four-byte header containing opcode and # size, followed by the data itself. set header [binary format "ccs" $reqcode 0 [string length $reqdata]] ethernet::send_rawether $id $header ethernet::send_rawether $id $reqdata if { $ethernet::logging_enabled } { ethernet::log_packet $ethernet::data($id,name) "tx" $reqdata } synth::hook_call "ethernet_tx" $ethernet::data($id,name) $reqdata } elseif { $reqcode == $ethernet::SYNTH_ETH_RX } { # Return a single packet to eCos, plus a count of the number # of remaining packets. All packets are buffered here, not # in rawether. if { $ethernet::data($id,packet_count) == 0 } { synth::send_reply 0 0 "" } else { incr ethernet::data($id,packet_count) -1 set packet [lindex $ethernet::data($id,packets) 0] set ethernet::data($id,packets) [lrange $ethernet::data($id,packets) 1 end] synth::send_reply $ethernet::data($id,packet_count) [string length $packet] $packet if { $ethernet::logging_enabled } { ethernet::log_packet $ethernet::data($id,name) "rx" $packet } synth::hook_call "ethernet_rx" $ethernet::data($id,name) $packet } } elseif { $reqcode == $ethernet::SYNTH_ETH_START } { # Start the interface in either normal or promiscuous # mode, depending on arg1. No reply is expected. Also # mark the interface as up so that any packets transmitted # by rawether will not be discarded set ethernet::data($id,up) 1 set header [binary format "ccs" $reqcode $arg1 0] ethernet::send_rawether $id $header } elseif { $reqcode == $ethernet::SYNTH_ETH_STOP } { # Stop the interface. All pending packets should be # discarded and no new packets should be accepted. # No reply is expected so just pass this on to rawether set ethernet::data($id,up) 0 set ethernet::data($id,packets) [list] set ethernet::data($id,packet_count) 0 set header [binary format "ccs" $reqcode 0 0] ethernet::send_rawether $id $header } elseif { $reqcode == $ethernet::SYNTH_ETH_GETPARAMS } { # Retrieve the interrupt number, the MAC address, # and the multicast flag for this interface. eCos should be # expecting back 6 bytes of data for the MAC, plus an # extra byte for the multi flag, and the interrupt # number as the return code. This is all known locally. set reply "$ethernet::data($id,MAC)$ethernet::data($id,multi)" synth::send_reply $ethernet::data($id,interrupt_vector) 7 $reply } elseif { $reqcode == $ethernet::SYNTH_ETH_MULTIALL } { set header [binary format "ccs" $reqcode $arg1 0] ethernet::send_rawether $id $header } else { synth::report_error "Received unexpected request $reqcode for ethernet device" } } # ---------------------------------------------------------------------------- # Incoming data. # # The rawether process continually reads packets from the low-level device # and tries to forward them on to this script, where they will be received # by an event handler. The packet consists of a four-byte header containing # the size, followed by the ethernet data itself. This ensures that # packet boundaries are preserved. Incoming packets are buffered inside # the auxiliary until eCos sends an RX request, and an interrupt is # generated. # # If eCos stops accepting data or if it cannot process the ethernet packets # quickly enough then the auxiliary could end up buffering an unbounded # amount of data. That is a bad idea, so there is an upper bound on the # number of buffered packets. Any excess packets get dropped. # # Error conditions or EOF indicate that rawether has terminated. This # should not happen during normal operation. rawether should only exit # because of an ecos_exit hook when the channel gets closed, and the # event handler gets removed first. # # Incoming packets are logged when they are received by eCos, not when # they are received from the rawether device. That gives a somewhat more # accurate view of what is happening inside eCos - a packet stuck in # a fifo has little impact. proc _handle_packet_error { msg id } { append msg " No further I/O will happen on this interface.\n" synth::report_warning $msg set ethernet::data($id,alive) 0 fileevent $ethernet::data($id,rawether) readable "" catch { close $ethernet::data($id,rawether) } } proc handle_packet { name id chan } { set header [read $chan 4] if { 4 != [string length $header] } { ethernet::_handle_packet_error "rawether process for $name has terminated unexpectedly.\n" $id return } binary scan $header "ccs" code arg1 len if { $ethernet::SYNTH_ETH_RX != $code } { set msg "protocol mismatch from rawether process for $name\n" append msg " Function code $code not recognised.\n" ethernet::_handle_packet_error $msg $id return } if { ($len < 14) || ($len > 1514) } { set msg "protocol mismatch from rawether process for $name\n" append msg " Invalid transfer length $len\n" ethernet::_handle_packet_error $msg $id return } set data [read $chan $len] if { $len != [string length $data] } { set msg "protocol mismatch from rawether process for $name\n" append msg " Expected $len byte ethernet packet, received [string length $data] bytes\n" ethernet::_handle_packet_error $msg $id return } # The data has been received correctly. Should it be buffered? if { !$ethernet::data($id,up) } { return } if { $ethernet::data($id,packet_count) >= $ethernet::max_buffered_packets } { return } # Store the packet, and inform eCos there is work to be done lappend ethernet::data($id,packets) $data incr ethernet::data($id,packet_count) synth::interrupt_raise $ethernet::data($id,interrupt_vector) } # ---------------------------------------------------------------------------- # When eCos has exited, the rawether processes can and should be # shut down immediately. proc ecos_exited { arg_list } { foreach id $ethernet::ids { if { $ethernet::data($id,alive) } { set ethernet::data($id,alive) 0 fileevent $ethernet::data($id,rawether) readable "" catch { close $ethernet::data($id,rawether) } } } } synth::hook_add "ecos_exit" ethernet::ecos_exited # ---------------------------------------------------------------------------- # Read in various data files for use by the filters # # Other possible sources of information include arp, ypcat, and # dns. Those are avoided for now because they involve running # additional processes that might hang for a while. Also arp # would only give useful information for very recently accessed # machines, NIS might not be running, and dns could involve an # expensive lookup while the system is running . array set services [list] array set hosts [list] array set protocols [list] proc read_services { } { catch { set fd [open "/etc/services" "r"] while { -1 != [gets $fd line] } { set junk "" set name "" set number "" set protocol "" if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)/((?:tcp)|(?:udp)).*$} $line junk name number protocol] } { set ethernet::services($number,$protocol) $name } } close $fd } } proc read_protocols { } { catch { set fd [open "/etc/protocols" "r"] while { -1 != [gets $fd line] } { set junk "" set name "" set number "" if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)\s.*} $line junk name number] } { set ethernet::protocols($number) $name } } close $fd } } proc read_hosts { } { catch { set fd [open "/etc/hosts" "r"] while { -1 != [gets $fd line] } { set junk "" set name "" set number "" # Deliberately ignore parts of the name after the first . if { [regexp -- {^([0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3})\s*([a-zA-Z0-9]+)(\.|\s|$)} $line junk number name] } { # The number should be naturalized if it is going to match reliably scan $line "%d.%d.%d.%d" a b c d set index [expr (($a & 0x0FF) << 24) | (($b & 0x0FF) << 16) | (($c & 0x0FF) << 8) | ($d & 0x0FF)] set ethernet::hosts($index) $name } } close $fd } } # ---------------------------------------------------------------------------- # Filtering support. This is only really used when running in GUI mode. # However all the relevant options are still extracted and validated, # to avoid warnings about unrecognised options. variable logging_enabled 0 variable max_show 64 # Construct a string for the data, either all of it or up to max_show bytes. # This is just hex in chunks of four bytes. proc format_hex_data { data } { set result "" set len [string length $data] if { $len > $ethernet::max_show } { set len $ethernet::max_show } binary scan $data "H[expr 2 * $len]" hex for { set i 0 } { $i < $len } { incr i 4 } { append result "[string range $hex [expr $i * 2] [expr ($i * 2) + 7]] " } set result [string trimright $result] return $result } # Given an IPv4 network address, turn it into a.b.c.d and the # host name as well (if known). The argument should be a 32-bit # integer. proc inet_ipv4_ntoa { number } { set result [format "%d.%d.%d.%d" [expr ($number >> 24) & 0x0FF] [expr ($number >> 16) & 0x0FF] \ [expr ($number >> 8) & 0x0FF] [expr $number & 0x0FF]] if { [info exists ethernet::hosts($number) ] } { append result "($ethernet::hosts($number))" } return $result } # Given an ipv4 address encapsulated in an IPv6 address, do the necessary # conversion. We have something like 123:4567, we want a.b.c.d plus # a host address. proc inet_ipv4_in_ipv6_ntoa { top bottom } { if { "" == $top } { set top 0 } if { "" == $bottom } { set bottom 0 } set top "0x$top" set bottom "0x$bottom" set ipv4 [expr ($top << 16) | $bottom] return inet_ipv4_ntoa $ipv4 } # Ditto for IPv6. The argument should be a 32-digit hexadecimal string. # For now there is no simple way of mapping these onto host names, # unless the address is an IPv4-mapped or compatible one, or one of # special cases such as loopback. proc inet_ipv6_ntoa { number } { # We have something like 12345678abcdef. Start by inserting the appropriate # colons. set result [format "%s:%s:%s:%s:%s:%s:%s:%s" [string range $number 0 3] [string range $number 4 7] \ [string range $number 8 11] [string range $number 12 15] [string range $number 16 19] \ [string range $number 20 23] [string range $number 24 27] [string range $number 28 31]] # Now eliminate unwanted 0's at the start of each range. regsub {^0+} $result {} result regsub -all {:0+} $result {:} result # If we have ended up with sequences of colons, abbreviate # them into pairs. regsub -all {::+} $result {::} result # There are a couple of special addresses if { "::1" == $result } { return "::1(loopback)" } elseif { "::" == $result } { return "::(IN6ADDR_ANY)" } # Look for IPv4-mapped addresses. set junk "" set ipv4_1 "" set ipv4_2 "" if { [regexp -nocase -- {::ffff:([0-9a-f]{0,3}):([0-9a-f]{0,3})$} $result junk ipv4_1 ipv4_2] } { set result [inet_ipv4_in_ipv6_nto $ipv4_1 $ipv4_2] return "::FFFF:$result" } elseif { [regexp -nocase -- {::([0-9a-f]{0,3}):([0-9a-f]{0,3})$} $result junk ipv4_1 ipv4_2] } { set result [inet_ipv4_in_ipv6_nto $ipv4_1 $ipv4_2] return "::$result" } else { # Could still be aggregatable global unicast, link-local, site-local or multicast. # But not decoded further for now. return $result } } proc log_packet { device direction packet } { if { [string length $packet] < 14 } { return } binary scan $packet {H2H2H2H2H2H2 H2H2H2H2H2H2 S} dest5 dest4 dest3 dest2 dest1 dest0 src5 src4 src3 src2 src1 src0 eth_protocol set packet [string range $packet 14 end] set ether_msg "$device $direction: [string length $packet] bytes, " append ether_msg [format ">%s:%s:%s:%s:%s:%s <%s:%s:%s:%s:%s:%s" $dest5 $dest4 $dest3 $dest2 $dest1 $dest0 $src5 $src4 $src3 $src2 $src1 $src0] set eth_protocol [expr $eth_protocol & 0x0FFFF] if { $eth_protocol <= 1536 } { append ether_msg " 802.3 " if { [string length $packet] < 8 } { return } binary scan $packet {a6 S} junk eth_protocol set packet [string range $packet 8 end] } append ether_msg [format " %04x" $eth_protocol] if { $eth_protocol == 0x0800 } { append ether_msg "(ip)" } elseif { $eth_protocol == 0x00806 } { append ether_msg "(arp)" } elseif { $eth_protocol == 0x08035 } { append ether_msg "(rarp)" } append ether_msg " [ethernet::format_hex_data $packet]\n" synth::output $ether_msg "eth_ether" if { 0x0806 == $eth_protocol } { # An ARP request. This should always be 28 bytes. if { [string length $packet] < 28 } { return } binary scan $packet {SSccS H2H2H2H2H2H2 I H2H2H2H2H2H2 I} hard_type prot_type hard_size prot_size op \ sender5 sender4 sender3 sender2 sender1 sender0 sender_ip \ target5 target4 target3 target2 target1 target0 target_ip set hard_type [expr $hard_type & 0x0FFFF] set prot_type [expr $prot_type & 0x0FFFF] set hard_size [expr $hard_size & 0x0FF] set prot_size [expr $prot_size & 0x0FF] set op [expr $op & 0x0FFFF] set sender_ip [expr $sender_ip & 0x0FFFFFFFF] set target_ip [expr $target_ip & 0x0FFFFFFFF] set arp_msg "$device $direction: ARP " if { $op == 1 } { append arp_msg "request " } elseif { $op == 2 } { append arp_msg "reply " } else { append_arp_msg "<unknown opcode> " } if { $hard_type != 1 } { append arp_msg "(unexpected hard_type field $hard_type, should be 1) " } if { $prot_type != 0x0800 } { append arp_msg "(unexpected prot_type field $prot_type, should be 0x0800) " } if { $hard_size != 6 } { append arp_msg "(unexpected hard_size field $hard_size, should be 6) " } if { $prot_size != 4 } { append arp_msg "(unexpected prot_size field $prot_size, should be 4) " } append arp_msg [format ", sender %s:%s:%s:%s:%s:%s " $sender5 $sender4 $sender3 $sender2 $sender1 $sender0] append arp_msg [ethernet::inet_ipv4_ntoa $sender_ip] append arp_msg [format ", target %s:%s:%s:%s:%s:%s " $target5 $target4 $target3 $target2 $target1 $target0] append arp_msg [ethernet::inet_ipv4_ntoa $target_ip] append arp_msg "\n" synth::output $arp_msg "eth_arp" return } if { 0x0800 != $eth_protocol } { return } # We have an IP packet. Is this IPv4 or IPv6? The first byte contains # the version and the overall length of the IP header in 32-bit words if { [string length $packet] < 20 } { return } binary scan $packet {c} tmp set ip_version [expr ($tmp >> 4) & 0x0F] set ip_hdrsize [expr $tmp & 0x0F] if { 4 == $ip_version } { binary scan $packet {ccSSSccSII} tmp tos len id frag ttl ip_protocol checksum source_ip dest_ip set ipv4_msg "$device $direction: IPv4" if { 0 != $tos } { append ipv4_msg [format " tos %02x," [expr $tos & 0x0FF]] } append ipv4_msg [format " len %d, id %d," [expr $len & 0x0FFFF] [expr $id & 0x0FFFF]] if { 0 != $frag } { append ipv4_msg [format " frag %u" [expr 8 * ($frag & 0x01FFF)]] if { 0 != ($frag & 0x04000) } { append ipv4_msg " DF" } if { 0 != ($frag & 0x02000) } { append ipv4_msg " MF" } append ipv4_msg "," } append ipv4_msg [format " ttl %d," $ttl] set ip_protocol [expr $ip_protocol & 0x0FF] if { [info exists ethernet::protocols($ip_protocol)] } { append ipv4_msg " $ethernet::protocols($ip_protocol)," } else { append ipv4_msg [format " protocol %d" $ip_protocol] } set source_name [ethernet::inet_ipv4_ntoa $source_ip] set dest_name [ethernet::inet_ipv4_ntoa $dest_ip] append ipv4_msg " >${dest_name}, <${source_name}\n" synth::output $ipv4_msg "eth_ipv4" # If this packet is a fragment other than the first, do not try to decode # subsequent packets. The header information will not be present. if { 0 != ($frag & 0x01FFF)} { return } set packet [string range $packet [expr 4 * $ip_hdrsize] end] } elseif { 6 == $ip_version } { if { [string length $packet] < 40 } { return } binary scan $packet {ISccH16H16} flow payload_length next_header hop_limit source_ip dest_ip set ipv6_msg "$device $direction: IPv6" set prio [expr ($flow & 0x0F000000) >> 24] set flow [expr $flow & 0x00FFFFFF] if { 0 != $flow } { append ipv6_msg [format " flow %04x prio %x," $flow $prio] } append ipv6_msg " payload [expr $payload bytes & 0x0FFFF]," append ipv6_msg " hop limit [expr $hop_limit & 0x0FF]," set next_header [expr $next_header & 0x0FF] if { [info exists ethernet::protocols($next_header)] } { append ipv6_msg " $ethernet::protocols($next_header)," } else { append ipv6_msg [format " protocol %d," $next_header] } set source_name [ethernet::inet_ipv6_ntoa $source_ip] set dest_name [ethernet::inet_ipv6_ntoa $dest_ip] append ipv6_msg " >${dest_name}, <${source_name}\n" synth::output $ipv6_msg "eth_ipv6" set packet [string range $packet 40 end] } else { synth::output "$device $direction: unknown IP version $ip_version\n" "eth_ipv4" return } # Now for some known protocols, icmp, tcp, udp and icmpv6 # Possible ipv6-frag should be handled here as well. The # fragment header should be followed by another header such # as tcp or udp. if { 1 == $ip_protocol } { # ipv4 ICMP if { [string length $packet] < 4 } { return } binary scan $packet {ccS} code type checksum set icmpv4_msg "$device $direction: ICMPv4 " set error 0 set data 0 switch -- $code { 0 { append icmpv4_msg "ping reply" if { [string length $packet] >= 8 } { # The id and seq are in the sender's format, not network format. # We have to assume either little or bigendian, so go for the former binary scan $packet {iss} junk id seq append icmpv4_msg [format " id %u, seq %u" [expr $id & 0x0FFFF] [expr $seq & 0x0FFFF]] set data 1 set packet [string range $packet 8 end] } } 3 { append icmpv4_msg "unreachable/" switch -- $type { 0 { append icmpv4_msg "network" } 1 { append icmpv4_msg "host" } 2 { append icmpv4_msg "protocol" } 3 { append icmpv4_msg "port" } 4 { append icmpv4_msg "frag needed but don't frag set" } 5 { append icmpv4_msg "source route failed" } 6 { append icmpv4_msg "destination network unknown" } 7 { append icmpv4_msg "destination host unknown" } 8 { append icmpv4_msg "source host isolated" } 9 { append icmpv4_msg "destination network prohibited" } 10 { append icmpv4_msg "destination host prohibited" } 11 { append icmpv4_msg "network for TOS" } 12 { append icmpv4_msg "host for TOS" } 13 { append icmpv4_msg "communication prohibited" } 14 { append icmpv4_msg "host precedence violation" } 15 { append icmpv4_msg "precedence cutoff" } default { append icmpv4_msg "unknown" } } set error 1 } 4 { append icmpv4_msg "source quench" set error 1 } 5 { append icmpv4_msg "redirect/" switch -- $type { 0 { append icmpv4_msg "network" } 1 { append icmpv4_msg "host" } 2 { append icmpv4_msg "tos & network" } 3 { append icmpv4_msg "tos & host" } default { append icmpv4_msg "unknown" } } set error 1 } 8 { append icmpv4_msg "ping request" if { [string length $packet] >= 8 } { binary scan $packet {iss} junk id seq append icmpv4_msg [format " id %u, seq %u" [expr $id & 0x0FFFF] [expr $seq & 0x0FFFF]] set data 1 set packet [string range $packet 8 end] } } 9 { append icmpv4_msg "router advertisement" } 10 { append icmpv4_msg "router solicitation" } 11 { append icmpv4_msg "time exceeded/" switch -- $type { 0 { append icmpv4_msg "transit" } 1 { append icmpv4_msg "reassembly" } default { append icmpv4_msg "unknown" } } set error 1 } 12 { append icmpv4_msg "parameter problem/" switch -- $type { 0 { append icmpv4_msg "IP header bad" } 1 { append icmpv4_msg "required option missing" } default { append icmpv4_msg "unknown" } } set error 1 } 13 { append icmpv4_msg "timestamp request" } 14 { append icmpv4_msg "timestamp reply" } 15 { append icmpv4_msg "information request" } 16 { append icmpv4_msg "information reply" } 17 { append icmpv4_msg "address mask request" } 18 { append icmpv4_msg "address mask reply" } default { append icmpv4_msg "unknown" } } if { $error && ([string length $packet] >= 36) } { # The ICMP message contains an IP header and hopefully the TCP or UDP ports as well # Only deal with the simple cases. binary scan $packet {iiccSiccSIISS} icmp_junk1 icmp_junk2 ip_lenver ip_junk1 ip_junk2 ip_junk3 ip_junk4 ip_protocol ip_junk5 \ ip_source ip_dest ip_source_port ip_dest_port if { (5 == ($ip_lenver & 0x0F)) && ((6 == $ip_protocol) || (17 == $ip_protocol)) } { if { 6 == $ip_protocol } { append icmpv4_msg ", tcp" } else { append icmpv4_msg ", udp" } append icmpv4_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv4_ntoa $ip_source]:$ip_source_port" } } append icmpv4_msg "\n" synth::output $icmpv4_msg "eth_icmpv4" # Only some of the requests contain additional data that should be displayed if { !$data } { return } } elseif { 58 == $ip_protocol } { # ipv6 ICMP if { [string length $packet] < 4 } { return } binary scan $packet {ccS} code type checksum set icmpv6_msg "$device $direction: ICMPv6 " set error 0 set data 0 switch -- $code { 1 { append icmpv6_msg "unreachable/" switch -- $type { 0 { append icmpv6_msg "no route" } 1 { append icmpv6_msg "prohibited" } 2 { append icmpv6_msg "not a neighbour" } 3 { append icmpv6_msg "any other reason" } 4 { append icmpv6_msg "UDP port unreachable" } default { append icmpv6_msg "unknown" } } set error 1 } 2 { append icmpv6_msg "packet too big" set error 1 } 3 { append icmpv6_msg "time exceeded/" switch -- $type { 0 { append icmpv6_msg "hop limit" } 1 { append icmpv6_msg "fragment reassembly" } default { append icmpv6_msg "unknown" } } set error 1 } 4 { append icmpv6_msg "parameter problem" switch -- $type { 0 { append icmpv6_msg "erroneous header" } 1 { append icmpv6_msg "unrecognized next header" } 2 { append icmpv6_msg "unrecognized option" } default { append icmpv6_msg "unknown" } } set error 1 } 128 { append icmpv6_msg "ping request" # FIXME: is this the same format as for icmpv4? } 129 { append icmpv6_msg "ping reply" # FIXME: is this the same format as for icmpv4? } 130 { append icmpv6_msg "group membership query" } 131 { append icmpv6_msg "group membership report" } 132 { append icmpv6_msg "group membership reduction" } 133 { append icmpv6_msg "router solicitation" } 134 { append icmpv6_msg "router advertisement" } 135 { append icmpv6_msg "neighbour solicitation" } 136 { append icmpv6_msg "neighbour advertisement" } 137 { append icmpv6_msg "redirect" } } if { $error && ([string length $packet] >= 44) } { # The ICMP message contains an IPv6 header and hopefully the TCP or UDP ports as well binary scan $packet {isccH16H16SS} icmp_junk1 icmp_junk2 ip_protocol icmp_junk3 ip_source ip_dest ip_source_port ip_dest_port if { 6 == $ip_protocol } { append icmpv6_msg ", tcp" } elseif { 17 == $ip_protocol } { append icmpv6_msg ", udp" } append icmpv6_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv6_ntoa $ip_source]:$ip_source_port" } append icmpv6_msg "\n" synth::output $icmpv6_msg "eth_icmpv6" if { !$data } { return } } elseif { 6 == $ip_protocol } { # TCP if { [string length $packet] < 20 } { return } binary scan $packet {SSIIccSSS} source_port dest_port seq ack hdrsize flags winsize checksum urg set source_port [expr $source_port & 0x0FFFF] set dest_port [expr $dest_port & 0x0FFFF] set hdrsize [expr ($hdrsize >> 4) & 0x0F] set winsize [expr $winsize & 0x0FFFF] set urg [expr $urg & 0x0FFFF] set tcp_msg "$device $direction tcp: " append tcp_msg " >${dest_name}:${dest_port}" if { [info exists ethernet::services($dest_port,udp)] } { append tcp_msg "($ethernet::services($dest_port,udp))" } append tcp_msg "<${source_name}:$source_port" if { [info exists ethernet::services($source_port,udp)] } { append tcp_msg "($ethernet::services($source_port,udp))" } append tcp_msg ", " if { $flags & 0x08 } { append tcp_msg "PSH " } if { $flags & 0x04 } { append tcp_msg "RST " } if { $flags & 0x02 } { append tcp_msg "SYN " } if { $flags & 0x01 } { append tcp_msg "FIN " } append tcp_msg [format "seq %u" $seq] if { 0 != ($flags & 0x010) } { append tcp_msg [format ", ACK %u" $ack] } append tcp_msg ", win $winsize" if { 0 != ($flags & 0x020) } { append tcp_msg ", URG $urg" } append tcp_msg "\n" synth::output $tcp_msg "eth_tcp" set packet [string range $packet [expr 4 * $hdrsize] end] } elseif { 17 == $ip_protocol } { # UDP if { [string length $packet] < 8 } { return } set udp_msg "$device $direction: udp " binary scan $packet {SSSS} source_port dest_port len checksum set source_port [expr $source_port & 0x0FFFF] set dest_port [expr $dest_port & 0x0FFFF] append udp_msg [format "%d bytes, " [expr $len & 0x0FFFF]] append udp_msg " >${dest_name}:$dest_port" if { [info exists ethernet::services($dest_port,udp)] } { append udp_msg "($ethernet::services($dest_port,udp))" } append udp_msg "<${source_name}:$source_port" if { [info exists ethernet::services($source_port,udp)] } { append udp_msg "($ethernet::services($source_port,udp))" } append udp_msg "\n" synth::output $udp_msg "eth_udp" set packet [string range $packet 8 end] } else { # Unknown protocol, so no way of knowing where the data starts. return } # At this point we may have a payload. This should be # dumped in both hex and ascii. The code tries to preserve # alignment. if { [string length $packet] == 0 } { return } set hexdata_msg "$device $direction: data [format_hex_data $packet]\n" set asciidata_msg "$device $direction: data " set len [string length $packet] if { $len > $ethernet::max_show } { set len $ethernet::max_show } for { set i 0 } { $i < $len } { incr i } { set char [string index $packet $i] if { "\r" == $char } { append asciidata_msg "\\r" } elseif { "\n" == $char } { append asciidata_msg "\\n" } elseif { "\t" == $char } { append asciidata_msg "\\t" } elseif { [string is print -strict $char] } { append asciidata_msg " $char" } else { append asciidata_msg "??" } if { 3 == ($i % 4) } { append asciidata_msg " " } } append asciidata_msg "\n" synth::output $hexdata_msg "eth_hexdata" synth::output $asciidata_msg "eth_asciidata" return } # A utility for handling the ethernet record button on the toolbar proc logging_button_toggle { } { if { $ethernet::logging_enabled } { set ethernet::logging_enabled 0 .toolbar.ethernet_logging configure -relief flat } else { set ethernet::logging_enabled 1 .toolbar.ethernet_logging configure -relief sunken } } # A dummy procedure for initialization. All of this could execute at # the toplevel, but there are lots of locals. proc filters_initialize { } { ethernet::read_services ethernet::read_protocols ethernet::read_hosts # Add a button on the toolbar for enabling/disabling logging. # Also add an entry to the help menu if { $synth::flag_gui } { button .toolbar.ethernet_logging -image $ethernet::image_netrecord -borderwidth 2 -relief flat -command ethernet::logging_button_toggle pack .toolbar.ethernet_logging -side left -padx 2 synth::register_balloon_help .toolbar.ethernet_logging "Record ethernet traffic" if { [synth::tdf_has_option "ethernet" "logging"] } { set ethernet::logging_enabled [synth::tdf_get_option "ethernet" "logging"] } else { # Default to logging ethernet traffic. This may not be the right thing to do # because users may see too much output by default, but it is easy enough # to disable. set ethernet::logging_enabled 1 } if { $ethernet::logging_enabled } { .toolbar.ethernet_logging configure -relief sunken } set ethernet_help [file join $synth::device_src_dir "doc" "devs-eth-synth-ecosynth.html"] if { ![file readable $ethernet_help] } { synth::report_warning "Failed to locate synthetic ethernet documentation $ethernet_help\n \ Help->Ethernet target menu option disabled.\n" set ethernet_help "" } if { "" == $ethernet_help } { .menubar.help add command -label "Ethernet" -state disabled } else { .menubar.help add command -label "Ethernet" -command [list synth::handle_help "file://$ethernet_help"] } } if { [synth::tdf_has_option "ethernet" "max_show"] } { set ethernet::max_show [synth::tdf_get_option "ethernet" "max_show"] if { ! [string is integer -strict $ethernet::max_show] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ Entry max_show should be a simple integer, not $ethernet::max_show\n" set ethernet::init_ok 0 } } # Filters. First, perform some validation. set known_filters [list "ether" "arp" "ipv4" "ipv6" "icmpv4" "icmpv6" "udp" "tcp" "hexdata" "asciidata"] set tdf_filters [synth::tdf_get_options "ethernet" "filter"] array set filter_options [list] foreach filter $tdf_filters { if { 0 == [llength $filter] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ Option \"filter\" requires the name of a known filters.\n" set ethernet::init_ok 0 continue } set name [lindex $filter 0] if { [info exists filter_options($name)] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ \"filter $name\" should be defined only once.\n" set ethernet::init_ok 0 continue } if { -1 == [lsearch -exact $known_filters $name] } { synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \ Unknown filter \"$name\".\n \ Known filters are $known_filters\n" set ethernet::init_ok 0 continue } set filter_options($name) [lrange $filter 1 end] } # We now know about all the filter entries in the target definition file. # Time to create the filters themselves, provided we are running in GUI mode. if { $synth::flag_gui } { foreach filter $known_filters { if { ! [info exists filter_options($filter)] } { synth::filter_add "eth_$filter" -text "ethernet $filter" } else { array set parsed_options [list] set message "" if { ![synth::filter_parse_options $filter_options($filter) parsed_options message] } { synth::report_error \ "Invalid entry in target definition file $synth::target_definition\n \ Ethernet filter $filter\n $message" set ethernet::init_ok 0 } else { set parsed_options("-text") "ethernet $filter" synth::filter_add_parsed "eth_$filter" parsed_options } } } } } ethernet::filters_initialize } return ethernet::instantiate
