3 # ============================================================================
7 # Ethernet support for the eCos synthetic target I/O auxiliary
9 # ============================================================================
10 # ####COPYRIGHTBEGIN####
12 # ----------------------------------------------------------------------------
13 # Copyright (C) 2002 Bart Veer
15 # This file is part of the eCos host tools.
17 # This program is free software; you can redistribute it and/or modify it
18 # under the terms of the GNU General Public License as published by the Free
19 # Software Foundation; either version 2 of the License, or (at your option)
22 # This program is distributed in the hope that it will be useful, but WITHOUT
23 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
27 # You should have received a copy of the GNU General Public License along with
28 # this program; if not, write to the Free Software Foundation, Inc.,
29 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
30 # ----------------------------------------------------------------------------
32 # ####COPYRIGHTEND####
33 # ============================================================================
34 # #####DESCRIPTIONBEGIN####
41 # Implementation of the ethernet device. This script should only ever
42 # be run from inside the ecosynth auxiliary.
44 # ####DESCRIPTIONEND####
45 # ============================================================================
51 # Linux provides a number of different ways of performing low-level
52 # ethernet I/O from user space, including accessing an otherwise
53 # unused ethernet card via a PF_PACKET socket, and the tap facility.
54 # The necessary functionality is not readily accessible from Tcl,
55 # and performing this low-level I/O generally requires special
56 # privileges. Therefore the actual I/O happens in a C program
57 # rawether, installed suid root,
59 # The synthetic ethernet package supports up to four ethernet devices,
60 # eth0 to eth3. The target definition file maps these onto the
61 # underlying I/O facility. Instantiation requires spawning a rawether
62 # process with appropriate arguments, and then waiting for a message
63 # from that process indicating whether or not the instantiation
64 # succeeded. That message includes the MAC address. A file event
65 # handler is installed to handle data detected by raw ether.
67 # eCos can send a number of requests: transmit a packet, start the
68 # interface (possibly in promiscuous mode), stop the interface,
69 # or get the various parameters such as the MAC address. All those
70 # requests can just be passed on to the rawether process. Incoming
71 # ethernet packets are slightly more complicated: rawether will
72 # immediately pass these up to this Tcl script, which will buffer
73 # the packets until they are requested by eCos; in addition an
74 # interrupt will be raised.
76 namespace eval ethernet {
77 # The protocol between eCos and this script.
78 variable SYNTH_ETH_TX 0x01
79 variable SYNTH_ETH_RX 0x02
80 variable SYNTH_ETH_START 0x03
81 variable SYNTH_ETH_STOP 0x04
82 variable SYNTH_ETH_GETPARAMS 0x05
83 variable SYNTH_ETH_MULTIALL 0x06
85 # This array holds all the interesting data for all the
86 # interfaces, indexed by the instance id. It is also useful
87 # to keep track of the instance id's associated with ethernet
92 # One-off initialization, for example loading images. If this fails
93 # then all attempts at instantiation will fail as well.
95 variable install_dir $synth::device_install_dir
96 variable rawether_executable [file join $ethernet::install_dir "rawether"]
98 if { ![file exists $rawether_executable] } {
99 synth::report_error "Ethernet device, rawether executable has not been installed in $ethernet::install_dir.\n"
101 } elseif { ![file executable $rawether_executable] } {
102 synth::report_error "Ethernet device, installed program $rawether_executable is not executable.\n"
106 if { $synth::flag_gui } {
107 foreach _image [list "netrecord.xbm"] {
108 variable image_[file rootname $_image]
109 if { ! [synth::load_image "ethernet::image_[file rootname $_image]" [file join $ethernet::install_dir $_image]] } {
116 # Maximum number of packets that should be buffered per interface.
117 # This can be changed in the target definition
118 variable max_buffered_packets 16
120 if { [synth::tdf_has_option "ethernet" "max_buffer"] } {
121 set ethernet::max_buffered_packets [synth::tdf_get_option "ethernet" "max_buffer"]
122 if { ![string is integer -strict $ethernet::max_buffered_packets] } {
123 synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \
124 Entry max_buffer should be a simple integer, not $ethernet::max_buffered_packets\n"
129 # Define hooks for tx and rx packets
130 synth::hook_define "ethernet_tx"
131 synth::hook_define "ethernet_rx"
133 # Get a list of known ethernet devices
134 proc devices_get_list { } {
137 lappend result $::ethernet::data($id,name)
142 # ----------------------------------------------------------------------------
143 proc instantiate { id name data } {
144 if { ! $ethernet::init_ok } {
145 synth::report_warning "Cannot instantiate ethernet device $name, initialization failed.\n"
149 # id is a small number that uniquely identifies this device. It will
150 # be used as an array index.
151 # name is something like eth0 or eth1
152 # There should be no device-specific data
154 # The hard work is done by an auxiliary process which needs to be
155 # spawned off. It requires some additional information to map the
156 # eCos device name on to a suitable Linux network device such
157 # as tap0. That information has to come from the config file.
158 if { ![synth::tdf_has_option "ethernet" $name] } {
159 synth::report_error "Cannot instantiate ethernet device $name\n \
160 No entry in target definition file $synth::target_definition\n"
163 set use [synth::tdf_get_option "ethernet" $name]
165 # Do some validation here, before the rawether process is started.
166 # Typical entries would look like
168 # eth1 ethertap [[tap-device] [MAC] [persistent]]
172 if { [regexp -- {^\s*real\s*[a-zA-z0-9_]+$} $use] } {
174 } elseif { [regexp -- {^\s*ethertap\s*(.*)$} $use junk optional ] } {
175 if { "" != $optional } {
176 if { ! [regexp -- {^tap[0-9]+\s*(.*)$} $optional junk mac ] } {
177 synth::report_error "Cannot instantiate ethernet device $name\n \
178 Invalid entry \"$use\" in target definition file $synth::target_definition\n \
179 Should be \"ethertap \[<tap-device> \[<MAC address>\]\] [persistent]\"\n"
183 if { ! [regexp -- {^\s*([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\s*} $mac ] } {
184 synth::report_error "Cannot instantiate ethernet device $name\n \
185 Invalid entry \"$use\" in target definition file $synth::target_definition\n \
186 MAC address should be of the form xx:xx:xx:xx:xx:xx, all hexadecimal digits.\n"
192 synth::report_error "Cannot instantiate ethernet device $name\n \
193 Invalid entry \"$use\" in target definition file $synth::target_definition\n \
194 Should be \"real <Linux ethernet device>\" or \"ethertap \[<tap-device> \[<MAC address>\]\]\"\n"
198 # Now spawn the rawether process. Its stdin and stdout are
199 # pipes connected to ecosynth. Its stderr is redirected to
200 # the current tty to avoid confusion between incoming ethernet
201 # packets and diagnostics.
202 if { [catch { set rawether [open "|$ethernet::rawether_executable $use 2>/dev/tty" w+] } message ] } {
203 synth::report_error "Failed to spawn rawether process for device $name\n $message"
207 # No translation on this pipe please.
208 fconfigure $rawether -translation binary -encoding binary -buffering none
210 # Now wait for the rawether device to initialize. It should send back a single
211 # byte, '0' for failure or '1' for success. Failure is followed by a text
212 # message which should be reported. Success is followed by a six-byte MAC
214 set reply [read $rawether 1]
215 if { "" == $reply } {
216 synth::report_error "rawether process for device $name exited unexpectedly.\n"
217 catch { close $rawether }
221 if { "1" != $reply } {
222 set message [read $rawether 1024]
223 synth::report_error "rawether process was unable to initialize eCos device $name ($use)\n $message"
224 catch { close $rawether }
228 set reply [read $rawether 7]
229 if { [string length $reply] != 7 } {
230 synth::report_error "rawether process for eCos device $name ($use) failed to provide the initialization response.\n"
231 catch { close $rawether }
234 set mac [string range $reply 0 5]
235 set multi [string index $reply 6]
237 # Finally allocate an interrupt vector
238 set vector [synth::interrupt_allocate $name]
239 if { -1 == $vector } {
240 # No more interrupts left. An error will have been reported already.
241 catch { close $rawether }
245 # The device is up and running. Fill in the array entries
246 lappend ethernet::ids $id
247 set ethernet::data($id,alive) 1
248 set ethernet::data($id,name) $name
249 set ethernet::data($id,rawether) $rawether
250 set ethernet::data($id,packets) [list]
251 set ethernet::data($id,packet_count) 0
252 set ethernet::data($id,up) 0
253 set ethernet::data($id,interrupt_vector) $vector
254 set ethernet::data($id,MAC) $mac
255 set ethernet::data($id,multi) $multi
257 # Set up the event handler to handle incoming packets. There should
258 # not be any until the interface is brought up
259 fileevent $rawether readable [list ethernet::handle_packet $name $id $rawether]
261 # Finally return the request handler. The eCos device driver will
262 # automatically get back an ack.
263 return ethernet::handle_request
266 # ----------------------------------------------------------------------------
267 # eCos has sent a request to a device instance. Most of these requests should
268 # just be forwarded to rawether. Some care has to be taken to preserve
269 # packet boundaries and avoid confusion. It is also necessary to worry
270 # about the rawether process exiting unexpectedly, which may cause
271 # puts operations to raise an error (subject to buffering).
273 # Note: it might actually be more efficient to always send a header plus
274 # 1514 bytes of data, reducing the number of system calls at the cost of
275 # some extra data copying, but with at least two process switches per
276 # ethernet transfer efficiency is not going to be particularly good
279 proc send_rawether { id packet } {
280 if { $ethernet::data($id,alive) } {
281 set chan $ethernet::data($id,rawether)
282 if { [catch { puts -nonewline $chan $packet } ] } {
283 set ethernet::data($id,alive) 0
284 # No further action is needed here, instead the read handler
285 # will detect EOF and report abnormal termination.
290 proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } {
292 if { $reqcode == $ethernet::SYNTH_ETH_TX } {
293 # Transmit a single packet. To preserve packet boundaries
294 # this involves a four-byte header containing opcode and
295 # size, followed by the data itself.
296 set header [binary format "ccs" $reqcode 0 [string length $reqdata]]
297 ethernet::send_rawether $id $header
298 ethernet::send_rawether $id $reqdata
299 if { $ethernet::logging_enabled } {
300 ethernet::log_packet $ethernet::data($id,name) "tx" $reqdata
302 synth::hook_call "ethernet_tx" $ethernet::data($id,name) $reqdata
304 } elseif { $reqcode == $ethernet::SYNTH_ETH_RX } {
305 # Return a single packet to eCos, plus a count of the number
306 # of remaining packets. All packets are buffered here, not
308 if { $ethernet::data($id,packet_count) == 0 } {
309 synth::send_reply 0 0 ""
311 incr ethernet::data($id,packet_count) -1
312 set packet [lindex $ethernet::data($id,packets) 0]
313 set ethernet::data($id,packets) [lrange $ethernet::data($id,packets) 1 end]
314 synth::send_reply $ethernet::data($id,packet_count) [string length $packet] $packet
315 if { $ethernet::logging_enabled } {
316 ethernet::log_packet $ethernet::data($id,name) "rx" $packet
318 synth::hook_call "ethernet_rx" $ethernet::data($id,name) $packet
320 } elseif { $reqcode == $ethernet::SYNTH_ETH_START } {
321 # Start the interface in either normal or promiscuous
322 # mode, depending on arg1. No reply is expected. Also
323 # mark the interface as up so that any packets transmitted
324 # by rawether will not be discarded
325 set ethernet::data($id,up) 1
326 set header [binary format "ccs" $reqcode $arg1 0]
327 ethernet::send_rawether $id $header
328 } elseif { $reqcode == $ethernet::SYNTH_ETH_STOP } {
329 # Stop the interface. All pending packets should be
330 # discarded and no new packets should be accepted.
331 # No reply is expected so just pass this on to rawether
332 set ethernet::data($id,up) 0
333 set ethernet::data($id,packets) [list]
334 set ethernet::data($id,packet_count) 0
335 set header [binary format "ccs" $reqcode 0 0]
336 ethernet::send_rawether $id $header
337 } elseif { $reqcode == $ethernet::SYNTH_ETH_GETPARAMS } {
338 # Retrieve the interrupt number, the MAC address,
339 # and the multicast flag for this interface. eCos should be
340 # expecting back 6 bytes of data for the MAC, plus an
341 # extra byte for the multi flag, and the interrupt
342 # number as the return code. This is all known locally.
343 set reply "$ethernet::data($id,MAC)$ethernet::data($id,multi)"
344 synth::send_reply $ethernet::data($id,interrupt_vector) 7 $reply
345 } elseif { $reqcode == $ethernet::SYNTH_ETH_MULTIALL } {
346 set header [binary format "ccs" $reqcode $arg1 0]
347 ethernet::send_rawether $id $header
349 synth::report_error "Received unexpected request $reqcode for ethernet device"
353 # ----------------------------------------------------------------------------
356 # The rawether process continually reads packets from the low-level device
357 # and tries to forward them on to this script, where they will be received
358 # by an event handler. The packet consists of a four-byte header containing
359 # the size, followed by the ethernet data itself. This ensures that
360 # packet boundaries are preserved. Incoming packets are buffered inside
361 # the auxiliary until eCos sends an RX request, and an interrupt is
364 # If eCos stops accepting data or if it cannot process the ethernet packets
365 # quickly enough then the auxiliary could end up buffering an unbounded
366 # amount of data. That is a bad idea, so there is an upper bound on the
367 # number of buffered packets. Any excess packets get dropped.
369 # Error conditions or EOF indicate that rawether has terminated. This
370 # should not happen during normal operation. rawether should only exit
371 # because of an ecos_exit hook when the channel gets closed, and the
372 # event handler gets removed first.
374 # Incoming packets are logged when they are received by eCos, not when
375 # they are received from the rawether device. That gives a somewhat more
376 # accurate view of what is happening inside eCos - a packet stuck in
377 # a fifo has little impact.
378 proc _handle_packet_error { msg id } {
379 append msg " No further I/O will happen on this interface.\n"
380 synth::report_warning $msg
381 set ethernet::data($id,alive) 0
382 fileevent $ethernet::data($id,rawether) readable ""
383 catch { close $ethernet::data($id,rawether) }
386 proc handle_packet { name id chan } {
387 set header [read $chan 4]
388 if { 4 != [string length $header] } {
389 ethernet::_handle_packet_error "rawether process for $name has terminated unexpectedly.\n" $id
393 binary scan $header "ccs" code arg1 len
394 if { $ethernet::SYNTH_ETH_RX != $code } {
395 set msg "protocol mismatch from rawether process for $name\n"
396 append msg " Function code $code not recognised.\n"
397 ethernet::_handle_packet_error $msg $id
400 if { ($len < 14) || ($len > 1514) } {
401 set msg "protocol mismatch from rawether process for $name\n"
402 append msg " Invalid transfer length $len\n"
403 ethernet::_handle_packet_error $msg $id
407 set data [read $chan $len]
408 if { $len != [string length $data] } {
409 set msg "protocol mismatch from rawether process for $name\n"
410 append msg " Expected $len byte ethernet packet, received [string length $data] bytes\n"
411 ethernet::_handle_packet_error $msg $id
415 # The data has been received correctly. Should it be buffered?
416 if { !$ethernet::data($id,up) } {
419 if { $ethernet::data($id,packet_count) >= $ethernet::max_buffered_packets } {
423 # Store the packet, and inform eCos there is work to be done
424 lappend ethernet::data($id,packets) $data
425 incr ethernet::data($id,packet_count)
426 synth::interrupt_raise $ethernet::data($id,interrupt_vector)
430 # ----------------------------------------------------------------------------
431 # When eCos has exited, the rawether processes can and should be
432 # shut down immediately.
433 proc ecos_exited { arg_list } {
434 foreach id $ethernet::ids {
435 if { $ethernet::data($id,alive) } {
436 set ethernet::data($id,alive) 0
437 fileevent $ethernet::data($id,rawether) readable ""
438 catch { close $ethernet::data($id,rawether) }
442 synth::hook_add "ecos_exit" ethernet::ecos_exited
444 # ----------------------------------------------------------------------------
445 # Read in various data files for use by the filters
447 # Other possible sources of information include arp, ypcat, and
448 # dns. Those are avoided for now because they involve running
449 # additional processes that might hang for a while. Also arp
450 # would only give useful information for very recently accessed
451 # machines, NIS might not be running, and dns could involve an
452 # expensive lookup while the system is running .
454 array set services [list]
455 array set hosts [list]
456 array set protocols [list]
458 proc read_services { } {
460 set fd [open "/etc/services" "r"]
461 while { -1 != [gets $fd line] } {
466 if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)/((?:tcp)|(?:udp)).*$} $line junk name number protocol] } {
467 set ethernet::services($number,$protocol) $name
474 proc read_protocols { } {
476 set fd [open "/etc/protocols" "r"]
477 while { -1 != [gets $fd line] } {
481 if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)\s.*} $line junk name number] } {
482 set ethernet::protocols($number) $name
489 proc read_hosts { } {
491 set fd [open "/etc/hosts" "r"]
492 while { -1 != [gets $fd line] } {
497 # Deliberately ignore parts of the name after the first .
498 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] } {
499 # The number should be naturalized if it is going to match reliably
500 scan $line "%d.%d.%d.%d" a b c d
501 set index [expr (($a & 0x0FF) << 24) | (($b & 0x0FF) << 16) | (($c & 0x0FF) << 8) | ($d & 0x0FF)]
502 set ethernet::hosts($index) $name
509 # ----------------------------------------------------------------------------
510 # Filtering support. This is only really used when running in GUI mode.
511 # However all the relevant options are still extracted and validated,
512 # to avoid warnings about unrecognised options.
514 variable logging_enabled 0
517 # Construct a string for the data, either all of it or up to max_show bytes.
518 # This is just hex in chunks of four bytes.
519 proc format_hex_data { data } {
522 set len [string length $data]
523 if { $len > $ethernet::max_show } {
524 set len $ethernet::max_show
526 binary scan $data "H[expr 2 * $len]" hex
527 for { set i 0 } { $i < $len } { incr i 4 } {
528 append result "[string range $hex [expr $i * 2] [expr ($i * 2) + 7]] "
530 set result [string trimright $result]
534 # Given an IPv4 network address, turn it into a.b.c.d and the
535 # host name as well (if known). The argument should be a 32-bit
537 proc inet_ipv4_ntoa { number } {
538 set result [format "%d.%d.%d.%d" [expr ($number >> 24) & 0x0FF] [expr ($number >> 16) & 0x0FF] \
539 [expr ($number >> 8) & 0x0FF] [expr $number & 0x0FF]]
540 if { [info exists ethernet::hosts($number) ] } {
541 append result "($ethernet::hosts($number))"
546 # Given an ipv4 address encapsulated in an IPv6 address, do the necessary
547 # conversion. We have something like 123:4567, we want a.b.c.d plus
549 proc inet_ipv4_in_ipv6_ntoa { top bottom } {
553 if { "" == $bottom } {
557 set bottom "0x$bottom"
559 set ipv4 [expr ($top << 16) | $bottom]
560 return inet_ipv4_ntoa $ipv4
563 # Ditto for IPv6. The argument should be a 32-digit hexadecimal string.
564 # For now there is no simple way of mapping these onto host names,
565 # unless the address is an IPv4-mapped or compatible one, or one of
566 # special cases such as loopback.
567 proc inet_ipv6_ntoa { number } {
568 # We have something like 12345678abcdef. Start by inserting the appropriate
570 set result [format "%s:%s:%s:%s:%s:%s:%s:%s" [string range $number 0 3] [string range $number 4 7] \
571 [string range $number 8 11] [string range $number 12 15] [string range $number 16 19] \
572 [string range $number 20 23] [string range $number 24 27] [string range $number 28 31]]
573 # Now eliminate unwanted 0's at the start of each range.
574 regsub {^0+} $result {} result
575 regsub -all {:0+} $result {:} result
577 # If we have ended up with sequences of colons, abbreviate
579 regsub -all {::+} $result {::} result
581 # There are a couple of special addresses
582 if { "::1" == $result } {
583 return "::1(loopback)"
584 } elseif { "::" == $result } {
585 return "::(IN6ADDR_ANY)"
588 # Look for IPv4-mapped addresses.
592 if { [regexp -nocase -- {::ffff:([0-9a-f]{0,3}):([0-9a-f]{0,3})$} $result junk ipv4_1 ipv4_2] } {
593 set result [inet_ipv4_in_ipv6_nto $ipv4_1 $ipv4_2]
594 return "::FFFF:$result"
595 } elseif { [regexp -nocase -- {::([0-9a-f]{0,3}):([0-9a-f]{0,3})$} $result junk ipv4_1 ipv4_2] } {
596 set result [inet_ipv4_in_ipv6_nto $ipv4_1 $ipv4_2]
599 # Could still be aggregatable global unicast, link-local, site-local or multicast.
600 # But not decoded further for now.
605 proc log_packet { device direction packet } {
606 if { [string length $packet] < 14 } {
609 binary scan $packet {H2H2H2H2H2H2 H2H2H2H2H2H2 S} dest5 dest4 dest3 dest2 dest1 dest0 src5 src4 src3 src2 src1 src0 eth_protocol
610 set packet [string range $packet 14 end]
612 set ether_msg "$device $direction: [string length $packet] bytes, "
613 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]
614 set eth_protocol [expr $eth_protocol & 0x0FFFF]
615 if { $eth_protocol <= 1536 } {
616 append ether_msg " 802.3 "
617 if { [string length $packet] < 8 } {
620 binary scan $packet {a6 S} junk eth_protocol
621 set packet [string range $packet 8 end]
623 append ether_msg [format " %04x" $eth_protocol]
624 if { $eth_protocol == 0x0800 } {
625 append ether_msg "(ip)"
626 } elseif { $eth_protocol == 0x00806 } {
627 append ether_msg "(arp)"
628 } elseif { $eth_protocol == 0x08035 } {
629 append ether_msg "(rarp)"
631 append ether_msg " [ethernet::format_hex_data $packet]\n"
632 synth::output $ether_msg "eth_ether"
634 if { 0x0806 == $eth_protocol } {
635 # An ARP request. This should always be 28 bytes.
636 if { [string length $packet] < 28 } {
639 binary scan $packet {SSccS H2H2H2H2H2H2 I H2H2H2H2H2H2 I} hard_type prot_type hard_size prot_size op \
640 sender5 sender4 sender3 sender2 sender1 sender0 sender_ip \
641 target5 target4 target3 target2 target1 target0 target_ip
642 set hard_type [expr $hard_type & 0x0FFFF]
643 set prot_type [expr $prot_type & 0x0FFFF]
644 set hard_size [expr $hard_size & 0x0FF]
645 set prot_size [expr $prot_size & 0x0FF]
646 set op [expr $op & 0x0FFFF]
647 set sender_ip [expr $sender_ip & 0x0FFFFFFFF]
648 set target_ip [expr $target_ip & 0x0FFFFFFFF]
650 set arp_msg "$device $direction: ARP "
652 append arp_msg "request "
653 } elseif { $op == 2 } {
654 append arp_msg "reply "
656 append_arp_msg "<unknown opcode> "
658 if { $hard_type != 1 } {
659 append arp_msg "(unexpected hard_type field $hard_type, should be 1) "
661 if { $prot_type != 0x0800 } {
662 append arp_msg "(unexpected prot_type field $prot_type, should be 0x0800) "
664 if { $hard_size != 6 } {
665 append arp_msg "(unexpected hard_size field $hard_size, should be 6) "
667 if { $prot_size != 4 } {
668 append arp_msg "(unexpected prot_size field $prot_size, should be 4) "
670 append arp_msg [format ", sender %s:%s:%s:%s:%s:%s " $sender5 $sender4 $sender3 $sender2 $sender1 $sender0]
671 append arp_msg [ethernet::inet_ipv4_ntoa $sender_ip]
672 append arp_msg [format ", target %s:%s:%s:%s:%s:%s " $target5 $target4 $target3 $target2 $target1 $target0]
673 append arp_msg [ethernet::inet_ipv4_ntoa $target_ip]
676 synth::output $arp_msg "eth_arp"
680 if { 0x0800 != $eth_protocol } {
684 # We have an IP packet. Is this IPv4 or IPv6? The first byte contains
685 # the version and the overall length of the IP header in 32-bit words
686 if { [string length $packet] < 20 } {
689 binary scan $packet {c} tmp
690 set ip_version [expr ($tmp >> 4) & 0x0F]
691 set ip_hdrsize [expr $tmp & 0x0F]
692 if { 4 == $ip_version } {
693 binary scan $packet {ccSSSccSII} tmp tos len id frag ttl ip_protocol checksum source_ip dest_ip
694 set ipv4_msg "$device $direction: IPv4"
696 append ipv4_msg [format " tos %02x," [expr $tos & 0x0FF]]
698 append ipv4_msg [format " len %d, id %d," [expr $len & 0x0FFFF] [expr $id & 0x0FFFF]]
700 append ipv4_msg [format " frag %u" [expr 8 * ($frag & 0x01FFF)]]
701 if { 0 != ($frag & 0x04000) } {
702 append ipv4_msg " DF"
704 if { 0 != ($frag & 0x02000) } {
705 append ipv4_msg " MF"
709 append ipv4_msg [format " ttl %d," $ttl]
710 set ip_protocol [expr $ip_protocol & 0x0FF]
711 if { [info exists ethernet::protocols($ip_protocol)] } {
712 append ipv4_msg " $ethernet::protocols($ip_protocol),"
714 append ipv4_msg [format " protocol %d" $ip_protocol]
717 set source_name [ethernet::inet_ipv4_ntoa $source_ip]
718 set dest_name [ethernet::inet_ipv4_ntoa $dest_ip]
719 append ipv4_msg " >${dest_name}, <${source_name}\n"
721 synth::output $ipv4_msg "eth_ipv4"
723 # If this packet is a fragment other than the first, do not try to decode
724 # subsequent packets. The header information will not be present.
725 if { 0 != ($frag & 0x01FFF)} {
728 set packet [string range $packet [expr 4 * $ip_hdrsize] end]
730 } elseif { 6 == $ip_version } {
731 if { [string length $packet] < 40 } {
734 binary scan $packet {ISccH16H16} flow payload_length next_header hop_limit source_ip dest_ip
735 set ipv6_msg "$device $direction: IPv6"
736 set prio [expr ($flow & 0x0F000000) >> 24]
737 set flow [expr $flow & 0x00FFFFFF]
739 append ipv6_msg [format " flow %04x prio %x," $flow $prio]
741 append ipv6_msg " payload [expr $payload bytes & 0x0FFFF],"
742 append ipv6_msg " hop limit [expr $hop_limit & 0x0FF],"
743 set next_header [expr $next_header & 0x0FF]
744 if { [info exists ethernet::protocols($next_header)] } {
745 append ipv6_msg " $ethernet::protocols($next_header),"
747 append ipv6_msg [format " protocol %d," $next_header]
750 set source_name [ethernet::inet_ipv6_ntoa $source_ip]
751 set dest_name [ethernet::inet_ipv6_ntoa $dest_ip]
752 append ipv6_msg " >${dest_name}, <${source_name}\n"
754 synth::output $ipv6_msg "eth_ipv6"
756 set packet [string range $packet 40 end]
759 synth::output "$device $direction: unknown IP version $ip_version\n" "eth_ipv4"
764 # Now for some known protocols, icmp, tcp, udp and icmpv6
765 # Possible ipv6-frag should be handled here as well. The
766 # fragment header should be followed by another header such
768 if { 1 == $ip_protocol } {
770 if { [string length $packet] < 4 } {
773 binary scan $packet {ccS} code type checksum
775 set icmpv4_msg "$device $direction: ICMPv4 "
780 append icmpv4_msg "ping reply"
781 if { [string length $packet] >= 8 } {
782 # The id and seq are in the sender's format, not network format.
783 # We have to assume either little or bigendian, so go for the former
784 binary scan $packet {iss} junk id seq
785 append icmpv4_msg [format " id %u, seq %u" [expr $id & 0x0FFFF] [expr $seq & 0x0FFFF]]
787 set packet [string range $packet 8 end]
791 append icmpv4_msg "unreachable/"
793 0 { append icmpv4_msg "network" }
794 1 { append icmpv4_msg "host" }
795 2 { append icmpv4_msg "protocol" }
796 3 { append icmpv4_msg "port" }
797 4 { append icmpv4_msg "frag needed but don't frag set" }
798 5 { append icmpv4_msg "source route failed" }
799 6 { append icmpv4_msg "destination network unknown" }
800 7 { append icmpv4_msg "destination host unknown" }
801 8 { append icmpv4_msg "source host isolated" }
802 9 { append icmpv4_msg "destination network prohibited" }
803 10 { append icmpv4_msg "destination host prohibited" }
804 11 { append icmpv4_msg "network for TOS" }
805 12 { append icmpv4_msg "host for TOS" }
806 13 { append icmpv4_msg "communication prohibited" }
807 14 { append icmpv4_msg "host precedence violation" }
808 15 { append icmpv4_msg "precedence cutoff" }
809 default { append icmpv4_msg "unknown" }
814 append icmpv4_msg "source quench"
818 append icmpv4_msg "redirect/"
820 0 { append icmpv4_msg "network" }
821 1 { append icmpv4_msg "host" }
822 2 { append icmpv4_msg "tos & network" }
823 3 { append icmpv4_msg "tos & host" }
824 default { append icmpv4_msg "unknown" }
829 append icmpv4_msg "ping request"
830 if { [string length $packet] >= 8 } {
831 binary scan $packet {iss} junk id seq
832 append icmpv4_msg [format " id %u, seq %u" [expr $id & 0x0FFFF] [expr $seq & 0x0FFFF]]
834 set packet [string range $packet 8 end]
838 append icmpv4_msg "router advertisement"
841 append icmpv4_msg "router solicitation"
844 append icmpv4_msg "time exceeded/"
846 0 { append icmpv4_msg "transit" }
847 1 { append icmpv4_msg "reassembly" }
848 default { append icmpv4_msg "unknown" }
853 append icmpv4_msg "parameter problem/"
855 0 { append icmpv4_msg "IP header bad" }
856 1 { append icmpv4_msg "required option missing" }
857 default { append icmpv4_msg "unknown" }
862 append icmpv4_msg "timestamp request"
865 append icmpv4_msg "timestamp reply"
868 append icmpv4_msg "information request"
871 append icmpv4_msg "information reply"
874 append icmpv4_msg "address mask request"
877 append icmpv4_msg "address mask reply"
880 append icmpv4_msg "unknown"
883 if { $error && ([string length $packet] >= 36) } {
884 # The ICMP message contains an IP header and hopefully the TCP or UDP ports as well
885 # Only deal with the simple cases.
886 binary scan $packet {iiccSiccSIISS} icmp_junk1 icmp_junk2 ip_lenver ip_junk1 ip_junk2 ip_junk3 ip_junk4 ip_protocol ip_junk5 \
887 ip_source ip_dest ip_source_port ip_dest_port
888 if { (5 == ($ip_lenver & 0x0F)) && ((6 == $ip_protocol) || (17 == $ip_protocol)) } {
889 if { 6 == $ip_protocol } {
890 append icmpv4_msg ", tcp"
892 append icmpv4_msg ", udp"
894 append icmpv4_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv4_ntoa $ip_source]:$ip_source_port"
898 append icmpv4_msg "\n"
899 synth::output $icmpv4_msg "eth_icmpv4"
901 # Only some of the requests contain additional data that should be displayed
906 } elseif { 58 == $ip_protocol } {
908 if { [string length $packet] < 4 } {
911 binary scan $packet {ccS} code type checksum
913 set icmpv6_msg "$device $direction: ICMPv6 "
918 append icmpv6_msg "unreachable/"
920 0 { append icmpv6_msg "no route" }
921 1 { append icmpv6_msg "prohibited" }
922 2 { append icmpv6_msg "not a neighbour" }
923 3 { append icmpv6_msg "any other reason" }
924 4 { append icmpv6_msg "UDP port unreachable" }
925 default { append icmpv6_msg "unknown" }
930 append icmpv6_msg "packet too big"
934 append icmpv6_msg "time exceeded/"
936 0 { append icmpv6_msg "hop limit" }
937 1 { append icmpv6_msg "fragment reassembly" }
938 default { append icmpv6_msg "unknown" }
943 append icmpv6_msg "parameter problem"
945 0 { append icmpv6_msg "erroneous header" }
946 1 { append icmpv6_msg "unrecognized next header" }
947 2 { append icmpv6_msg "unrecognized option" }
948 default { append icmpv6_msg "unknown" }
953 append icmpv6_msg "ping request"
954 # FIXME: is this the same format as for icmpv4?
957 append icmpv6_msg "ping reply"
958 # FIXME: is this the same format as for icmpv4?
961 append icmpv6_msg "group membership query"
964 append icmpv6_msg "group membership report"
967 append icmpv6_msg "group membership reduction"
970 append icmpv6_msg "router solicitation"
973 append icmpv6_msg "router advertisement"
976 append icmpv6_msg "neighbour solicitation"
979 append icmpv6_msg "neighbour advertisement"
982 append icmpv6_msg "redirect"
986 if { $error && ([string length $packet] >= 44) } {
987 # The ICMP message contains an IPv6 header and hopefully the TCP or UDP ports as well
988 binary scan $packet {isccH16H16SS} icmp_junk1 icmp_junk2 ip_protocol icmp_junk3 ip_source ip_dest ip_source_port ip_dest_port
989 if { 6 == $ip_protocol } {
990 append icmpv6_msg ", tcp"
991 } elseif { 17 == $ip_protocol } {
992 append icmpv6_msg ", udp"
994 append icmpv6_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv6_ntoa $ip_source]:$ip_source_port"
996 append icmpv6_msg "\n"
997 synth::output $icmpv6_msg "eth_icmpv6"
1003 } elseif { 6 == $ip_protocol } {
1005 if { [string length $packet] < 20 } {
1008 binary scan $packet {SSIIccSSS} source_port dest_port seq ack hdrsize flags winsize checksum urg
1009 set source_port [expr $source_port & 0x0FFFF]
1010 set dest_port [expr $dest_port & 0x0FFFF]
1011 set hdrsize [expr ($hdrsize >> 4) & 0x0F]
1012 set winsize [expr $winsize & 0x0FFFF]
1013 set urg [expr $urg & 0x0FFFF]
1015 set tcp_msg "$device $direction tcp: "
1016 append tcp_msg " >${dest_name}:${dest_port}"
1017 if { [info exists ethernet::services($dest_port,udp)] } {
1018 append tcp_msg "($ethernet::services($dest_port,udp))"
1020 append tcp_msg "<${source_name}:$source_port"
1021 if { [info exists ethernet::services($source_port,udp)] } {
1022 append tcp_msg "($ethernet::services($source_port,udp))"
1026 if { $flags & 0x08 } {
1027 append tcp_msg "PSH "
1029 if { $flags & 0x04 } {
1030 append tcp_msg "RST "
1032 if { $flags & 0x02 } {
1033 append tcp_msg "SYN "
1035 if { $flags & 0x01 } {
1036 append tcp_msg "FIN "
1038 append tcp_msg [format "seq %u" $seq]
1040 if { 0 != ($flags & 0x010) } {
1041 append tcp_msg [format ", ACK %u" $ack]
1043 append tcp_msg ", win $winsize"
1044 if { 0 != ($flags & 0x020) } {
1045 append tcp_msg ", URG $urg"
1048 synth::output $tcp_msg "eth_tcp"
1050 set packet [string range $packet [expr 4 * $hdrsize] end]
1051 } elseif { 17 == $ip_protocol } {
1053 if { [string length $packet] < 8 } {
1056 set udp_msg "$device $direction: udp "
1057 binary scan $packet {SSSS} source_port dest_port len checksum
1058 set source_port [expr $source_port & 0x0FFFF]
1059 set dest_port [expr $dest_port & 0x0FFFF]
1060 append udp_msg [format "%d bytes, " [expr $len & 0x0FFFF]]
1061 append udp_msg " >${dest_name}:$dest_port"
1062 if { [info exists ethernet::services($dest_port,udp)] } {
1063 append udp_msg "($ethernet::services($dest_port,udp))"
1065 append udp_msg "<${source_name}:$source_port"
1066 if { [info exists ethernet::services($source_port,udp)] } {
1067 append udp_msg "($ethernet::services($source_port,udp))"
1070 synth::output $udp_msg "eth_udp"
1071 set packet [string range $packet 8 end]
1073 # Unknown protocol, so no way of knowing where the data starts.
1077 # At this point we may have a payload. This should be
1078 # dumped in both hex and ascii. The code tries to preserve
1080 if { [string length $packet] == 0 } {
1083 set hexdata_msg "$device $direction: data [format_hex_data $packet]\n"
1084 set asciidata_msg "$device $direction: data "
1085 set len [string length $packet]
1086 if { $len > $ethernet::max_show } {
1087 set len $ethernet::max_show
1089 for { set i 0 } { $i < $len } { incr i } {
1090 set char [string index $packet $i]
1091 if { "\r" == $char } {
1092 append asciidata_msg "\\r"
1093 } elseif { "\n" == $char } {
1094 append asciidata_msg "\\n"
1095 } elseif { "\t" == $char } {
1096 append asciidata_msg "\\t"
1097 } elseif { [string is print -strict $char] } {
1098 append asciidata_msg " $char"
1100 append asciidata_msg "??"
1102 if { 3 == ($i % 4) } {
1103 append asciidata_msg " "
1106 append asciidata_msg "\n"
1107 synth::output $hexdata_msg "eth_hexdata"
1108 synth::output $asciidata_msg "eth_asciidata"
1113 # A utility for handling the ethernet record button on the toolbar
1114 proc logging_button_toggle { } {
1115 if { $ethernet::logging_enabled } {
1116 set ethernet::logging_enabled 0
1117 .toolbar.ethernet_logging configure -relief flat
1119 set ethernet::logging_enabled 1
1120 .toolbar.ethernet_logging configure -relief sunken
1124 # A dummy procedure for initialization. All of this could execute at
1125 # the toplevel, but there are lots of locals.
1126 proc filters_initialize { } {
1127 ethernet::read_services
1128 ethernet::read_protocols
1129 ethernet::read_hosts
1131 # Add a button on the toolbar for enabling/disabling logging.
1132 # Also add an entry to the help menu
1133 if { $synth::flag_gui } {
1134 button .toolbar.ethernet_logging -image $ethernet::image_netrecord -borderwidth 2 -relief flat -command ethernet::logging_button_toggle
1135 pack .toolbar.ethernet_logging -side left -padx 2
1136 synth::register_balloon_help .toolbar.ethernet_logging "Record ethernet traffic"
1138 if { [synth::tdf_has_option "ethernet" "logging"] } {
1139 set ethernet::logging_enabled [synth::tdf_get_option "ethernet" "logging"]
1141 # Default to logging ethernet traffic. This may not be the right thing to do
1142 # because users may see too much output by default, but it is easy enough
1144 set ethernet::logging_enabled 1
1146 if { $ethernet::logging_enabled } {
1147 .toolbar.ethernet_logging configure -relief sunken
1150 set ethernet_help [file join $synth::device_src_dir "doc" "devs-eth-synth-ecosynth.html"]
1151 if { ![file readable $ethernet_help] } {
1152 synth::report_warning "Failed to locate synthetic ethernet documentation $ethernet_help\n \
1153 Help->Ethernet target menu option disabled.\n"
1154 set ethernet_help ""
1156 if { "" == $ethernet_help } {
1157 .menubar.help add command -label "Ethernet" -state disabled
1159 .menubar.help add command -label "Ethernet" -command [list synth::handle_help "file://$ethernet_help"]
1163 if { [synth::tdf_has_option "ethernet" "max_show"] } {
1164 set ethernet::max_show [synth::tdf_get_option "ethernet" "max_show"]
1165 if { ! [string is integer -strict $ethernet::max_show] } {
1166 synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \
1167 Entry max_show should be a simple integer, not $ethernet::max_show\n"
1168 set ethernet::init_ok 0
1172 # Filters. First, perform some validation.
1173 set known_filters [list "ether" "arp" "ipv4" "ipv6" "icmpv4" "icmpv6" "udp" "tcp" "hexdata" "asciidata"]
1174 set tdf_filters [synth::tdf_get_options "ethernet" "filter"]
1175 array set filter_options [list]
1177 foreach filter $tdf_filters {
1178 if { 0 == [llength $filter] } {
1179 synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \
1180 Option \"filter\" requires the name of a known filters.\n"
1181 set ethernet::init_ok 0
1184 set name [lindex $filter 0]
1185 if { [info exists filter_options($name)] } {
1186 synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \
1187 \"filter $name\" should be defined only once.\n"
1188 set ethernet::init_ok 0
1191 if { -1 == [lsearch -exact $known_filters $name] } {
1192 synth::report_error "Ethernet device, invalid value in target definition file $synth::target_definition\n \
1193 Unknown filter \"$name\".\n \
1194 Known filters are $known_filters\n"
1195 set ethernet::init_ok 0
1198 set filter_options($name) [lrange $filter 1 end]
1201 # We now know about all the filter entries in the target definition file.
1202 # Time to create the filters themselves, provided we are running in GUI mode.
1203 if { $synth::flag_gui } {
1204 foreach filter $known_filters {
1205 if { ! [info exists filter_options($filter)] } {
1206 synth::filter_add "eth_$filter" -text "ethernet $filter"
1208 array set parsed_options [list]
1210 if { ![synth::filter_parse_options $filter_options($filter) parsed_options message] } {
1211 synth::report_error \
1212 "Invalid entry in target definition file $synth::target_definition\n \
1213 Ethernet filter $filter\n $message"
1214 set ethernet::init_ok 0
1216 set parsed_options("-text") "ethernet $filter"
1217 synth::filter_add_parsed "eth_$filter" parsed_options
1223 ethernet::filters_initialize
1226 return ethernet::instantiate