1 |
27 |
unneback |
# {{{ Banner
|
2 |
|
|
|
3 |
|
|
# ============================================================================
|
4 |
|
|
#
|
5 |
|
|
# ethernet.tcl
|
6 |
|
|
#
|
7 |
|
|
# Ethernet support for the eCos synthetic target I/O auxiliary
|
8 |
|
|
#
|
9 |
|
|
# ============================================================================
|
10 |
|
|
# ####COPYRIGHTBEGIN####
|
11 |
|
|
#
|
12 |
|
|
# ----------------------------------------------------------------------------
|
13 |
|
|
# Copyright (C) 2002 Bart Veer
|
14 |
|
|
#
|
15 |
|
|
# This file is part of the eCos host tools.
|
16 |
|
|
#
|
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)
|
20 |
|
|
# any later version.
|
21 |
|
|
#
|
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
|
25 |
|
|
# more details.
|
26 |
|
|
#
|
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 |
|
|
# ----------------------------------------------------------------------------
|
31 |
|
|
#
|
32 |
|
|
# ####COPYRIGHTEND####
|
33 |
|
|
# ============================================================================
|
34 |
|
|
# #####DESCRIPTIONBEGIN####
|
35 |
|
|
#
|
36 |
|
|
# Author(s): bartv
|
37 |
|
|
# Contact(s): bartv
|
38 |
|
|
# Date: 2002/08/07
|
39 |
|
|
# Version: 0.01
|
40 |
|
|
# Description:
|
41 |
|
|
# Implementation of the ethernet device. This script should only ever
|
42 |
|
|
# be run from inside the ecosynth auxiliary.
|
43 |
|
|
#
|
44 |
|
|
# ####DESCRIPTIONEND####
|
45 |
|
|
# ============================================================================
|
46 |
|
|
|
47 |
|
|
# }}}
|
48 |
|
|
|
49 |
|
|
# Overview.
|
50 |
|
|
#
|
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,
|
58 |
|
|
#
|
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.
|
66 |
|
|
#
|
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.
|
75 |
|
|
|
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
|
84 |
|
|
|
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
|
88 |
|
|
# devices.
|
89 |
|
|
array set data [list]
|
90 |
|
|
set ids [list]
|
91 |
|
|
|
92 |
|
|
# One-off initialization, for example loading images. If this fails
|
93 |
|
|
# then all attempts at instantiation will fail as well.
|
94 |
|
|
variable init_ok 1
|
95 |
|
|
variable install_dir $synth::device_install_dir
|
96 |
|
|
variable rawether_executable [file join $ethernet::install_dir "rawether"]
|
97 |
|
|
|
98 |
|
|
if { ![file exists $rawether_executable] } {
|
99 |
|
|
synth::report_error "Ethernet device, rawether executable has not been installed in $ethernet::install_dir.\n"
|
100 |
|
|
set init_ok 0
|
101 |
|
|
} elseif { ![file executable $rawether_executable] } {
|
102 |
|
|
synth::report_error "Ethernet device, installed program $rawether_executable is not executable.\n"
|
103 |
|
|
set init_ok 0
|
104 |
|
|
}
|
105 |
|
|
|
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]] } {
|
110 |
|
|
set init_ok 0
|
111 |
|
|
}
|
112 |
|
|
}
|
113 |
|
|
unset _image
|
114 |
|
|
}
|
115 |
|
|
|
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
|
119 |
|
|
|
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"
|
125 |
|
|
set init_ok 0
|
126 |
|
|
}
|
127 |
|
|
}
|
128 |
|
|
|
129 |
|
|
# Define hooks for tx and rx packets
|
130 |
|
|
synth::hook_define "ethernet_tx"
|
131 |
|
|
synth::hook_define "ethernet_rx"
|
132 |
|
|
|
133 |
|
|
# Get a list of known ethernet devices
|
134 |
|
|
proc devices_get_list { } {
|
135 |
|
|
set result [list]
|
136 |
|
|
foreach id $ids {
|
137 |
|
|
lappend result $::ethernet::data($id,name)
|
138 |
|
|
}
|
139 |
|
|
return $result
|
140 |
|
|
}
|
141 |
|
|
|
142 |
|
|
# ----------------------------------------------------------------------------
|
143 |
|
|
proc instantiate { id name data } {
|
144 |
|
|
if { ! $ethernet::init_ok } {
|
145 |
|
|
synth::report_warning "Cannot instantiate ethernet device $name, initialization failed.\n"
|
146 |
|
|
return ""
|
147 |
|
|
}
|
148 |
|
|
|
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
|
153 |
|
|
|
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"
|
161 |
|
|
return ""
|
162 |
|
|
}
|
163 |
|
|
set use [synth::tdf_get_option "ethernet" $name]
|
164 |
|
|
|
165 |
|
|
# Do some validation here, before the rawether process is started.
|
166 |
|
|
# Typical entries would look like
|
167 |
|
|
# eth0 real eth1
|
168 |
|
|
# eth1 ethertap [[tap-device] [MAC]]
|
169 |
|
|
set junk ""
|
170 |
|
|
set optional ""
|
171 |
|
|
set mac ""
|
172 |
|
|
if { [regexp -- {^\s*real\s*[a-zA-z0-9_]+$} $use] } {
|
173 |
|
|
# Real ethernet.
|
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>\]\]\"\n"
|
180 |
|
|
return ""
|
181 |
|
|
}
|
182 |
|
|
if { "" != $mac } {
|
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"
|
187 |
|
|
return ""
|
188 |
|
|
}
|
189 |
|
|
}
|
190 |
|
|
}
|
191 |
|
|
} else {
|
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"
|
195 |
|
|
return ""
|
196 |
|
|
}
|
197 |
|
|
|
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"
|
204 |
|
|
return ""
|
205 |
|
|
}
|
206 |
|
|
|
207 |
|
|
# No translation on this pipe please.
|
208 |
|
|
fconfigure $rawether -translation binary -encoding binary -buffering none
|
209 |
|
|
|
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
|
213 |
|
|
# address.
|
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 }
|
218 |
|
|
return ""
|
219 |
|
|
}
|
220 |
|
|
|
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 }
|
225 |
|
|
return ""
|
226 |
|
|
}
|
227 |
|
|
|
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 }
|
232 |
|
|
return ""
|
233 |
|
|
}
|
234 |
|
|
set mac [string range $reply 0 5]
|
235 |
|
|
set multi [string index $reply 6]
|
236 |
|
|
|
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 }
|
242 |
|
|
return ""
|
243 |
|
|
}
|
244 |
|
|
|
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
|
256 |
|
|
|
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]
|
260 |
|
|
|
261 |
|
|
# Finally return the request handler. The eCos device driver will
|
262 |
|
|
# automatically get back an ack.
|
263 |
|
|
return ethernet::handle_request
|
264 |
|
|
}
|
265 |
|
|
|
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).
|
272 |
|
|
#
|
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
|
277 |
|
|
# anyway.
|
278 |
|
|
|
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.
|
286 |
|
|
}
|
287 |
|
|
}
|
288 |
|
|
}
|
289 |
|
|
|
290 |
|
|
proc handle_request { id reqcode arg1 arg2 reqdata reqlen reply_len } {
|
291 |
|
|
|
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
|
301 |
|
|
}
|
302 |
|
|
synth::hook_call "ethernet_tx" $ethernet::data($id,name) $reqdata
|
303 |
|
|
|
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
|
307 |
|
|
# in rawether.
|
308 |
|
|
if { $ethernet::data($id,packet_count) == 0 } {
|
309 |
|
|
synth::send_reply 0 0 ""
|
310 |
|
|
} else {
|
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
|
317 |
|
|
}
|
318 |
|
|
synth::hook_call "ethernet_rx" $ethernet::data($id,name) $packet
|
319 |
|
|
}
|
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
|
348 |
|
|
} else {
|
349 |
|
|
synth::report_error "Received unexpected request $reqcode for ethernet device"
|
350 |
|
|
}
|
351 |
|
|
}
|
352 |
|
|
|
353 |
|
|
# ----------------------------------------------------------------------------
|
354 |
|
|
# Incoming data.
|
355 |
|
|
#
|
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
|
362 |
|
|
# generated.
|
363 |
|
|
#
|
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.
|
368 |
|
|
#
|
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.
|
373 |
|
|
#
|
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) }
|
384 |
|
|
}
|
385 |
|
|
|
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
|
390 |
|
|
return
|
391 |
|
|
}
|
392 |
|
|
|
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
|
398 |
|
|
return
|
399 |
|
|
}
|
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
|
404 |
|
|
return
|
405 |
|
|
}
|
406 |
|
|
|
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
|
412 |
|
|
return
|
413 |
|
|
}
|
414 |
|
|
|
415 |
|
|
# The data has been received correctly. Should it be buffered?
|
416 |
|
|
if { !$ethernet::data($id,up) } {
|
417 |
|
|
return
|
418 |
|
|
}
|
419 |
|
|
if { $ethernet::data($id,packet_count) >= $ethernet::max_buffered_packets } {
|
420 |
|
|
return
|
421 |
|
|
}
|
422 |
|
|
|
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)
|
427 |
|
|
|
428 |
|
|
}
|
429 |
|
|
|
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) }
|
439 |
|
|
}
|
440 |
|
|
}
|
441 |
|
|
}
|
442 |
|
|
synth::hook_add "ecos_exit" ethernet::ecos_exited
|
443 |
|
|
|
444 |
|
|
# ----------------------------------------------------------------------------
|
445 |
|
|
# Read in various data files for use by the filters
|
446 |
|
|
#
|
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 .
|
453 |
|
|
|
454 |
|
|
array set services [list]
|
455 |
|
|
array set hosts [list]
|
456 |
|
|
array set protocols [list]
|
457 |
|
|
|
458 |
|
|
proc read_services { } {
|
459 |
|
|
catch {
|
460 |
|
|
set fd [open "/etc/services" "r"]
|
461 |
|
|
while { -1 != [gets $fd line] } {
|
462 |
|
|
set junk ""
|
463 |
|
|
set name ""
|
464 |
|
|
set number ""
|
465 |
|
|
set protocol ""
|
466 |
|
|
if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)/((?:tcp)|(?:udp)).*$} $line junk name number protocol] } {
|
467 |
|
|
set ethernet::services($number,$protocol) $name
|
468 |
|
|
}
|
469 |
|
|
}
|
470 |
|
|
close $fd
|
471 |
|
|
}
|
472 |
|
|
}
|
473 |
|
|
|
474 |
|
|
proc read_protocols { } {
|
475 |
|
|
catch {
|
476 |
|
|
set fd [open "/etc/protocols" "r"]
|
477 |
|
|
while { -1 != [gets $fd line] } {
|
478 |
|
|
set junk ""
|
479 |
|
|
set name ""
|
480 |
|
|
set number ""
|
481 |
|
|
if { [regexp -- {^([-a-zA-Z0-9_]+)\s*([0-9]+)\s.*} $line junk name number] } {
|
482 |
|
|
set ethernet::protocols($number) $name
|
483 |
|
|
}
|
484 |
|
|
}
|
485 |
|
|
close $fd
|
486 |
|
|
}
|
487 |
|
|
}
|
488 |
|
|
|
489 |
|
|
proc read_hosts { } {
|
490 |
|
|
catch {
|
491 |
|
|
set fd [open "/etc/hosts" "r"]
|
492 |
|
|
while { -1 != [gets $fd line] } {
|
493 |
|
|
set junk ""
|
494 |
|
|
set name ""
|
495 |
|
|
set number ""
|
496 |
|
|
|
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
|
503 |
|
|
}
|
504 |
|
|
}
|
505 |
|
|
close $fd
|
506 |
|
|
}
|
507 |
|
|
}
|
508 |
|
|
|
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.
|
513 |
|
|
|
514 |
|
|
variable logging_enabled 0
|
515 |
|
|
variable max_show 64
|
516 |
|
|
|
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 } {
|
520 |
|
|
set result ""
|
521 |
|
|
|
522 |
|
|
set len [string length $data]
|
523 |
|
|
if { $len > $ethernet::max_show } {
|
524 |
|
|
set len $ethernet::max_show
|
525 |
|
|
}
|
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]] "
|
529 |
|
|
}
|
530 |
|
|
set result [string trimright $result]
|
531 |
|
|
return $result
|
532 |
|
|
}
|
533 |
|
|
|
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
|
536 |
|
|
# integer.
|
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))"
|
542 |
|
|
}
|
543 |
|
|
return $result
|
544 |
|
|
}
|
545 |
|
|
|
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
|
548 |
|
|
# a host address.
|
549 |
|
|
proc inet_ipv4_in_ipv6_ntoa { top bottom } {
|
550 |
|
|
if { "" == $top } {
|
551 |
|
|
set top 0
|
552 |
|
|
}
|
553 |
|
|
if { "" == $bottom } {
|
554 |
|
|
set bottom 0
|
555 |
|
|
}
|
556 |
|
|
set top "0x$top"
|
557 |
|
|
set bottom "0x$bottom"
|
558 |
|
|
|
559 |
|
|
set ipv4 [expr ($top << 16) | $bottom]
|
560 |
|
|
return inet_ipv4_ntoa $ipv4
|
561 |
|
|
}
|
562 |
|
|
|
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
|
569 |
|
|
# colons.
|
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
|
576 |
|
|
|
577 |
|
|
# If we have ended up with sequences of colons, abbreviate
|
578 |
|
|
# them into pairs.
|
579 |
|
|
regsub -all {::+} $result {::} result
|
580 |
|
|
|
581 |
|
|
# There are a couple of special addresses
|
582 |
|
|
if { "::1" == $result } {
|
583 |
|
|
return "::1(loopback)"
|
584 |
|
|
} elseif { "::" == $result } {
|
585 |
|
|
return "::(IN6ADDR_ANY)"
|
586 |
|
|
}
|
587 |
|
|
|
588 |
|
|
# Look for IPv4-mapped addresses.
|
589 |
|
|
set junk ""
|
590 |
|
|
set ipv4_1 ""
|
591 |
|
|
set ipv4_2 ""
|
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]
|
597 |
|
|
return "::$result"
|
598 |
|
|
} else {
|
599 |
|
|
# Could still be aggregatable global unicast, link-local, site-local or multicast.
|
600 |
|
|
# But not decoded further for now.
|
601 |
|
|
return $result
|
602 |
|
|
}
|
603 |
|
|
}
|
604 |
|
|
|
605 |
|
|
proc log_packet { device direction packet } {
|
606 |
|
|
if { [string length $packet] < 14 } {
|
607 |
|
|
return
|
608 |
|
|
}
|
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]
|
611 |
|
|
|
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 } {
|
618 |
|
|
return
|
619 |
|
|
}
|
620 |
|
|
binary scan $packet {a6 S} junk eth_protocol
|
621 |
|
|
set packet [string range $packet 8 end]
|
622 |
|
|
}
|
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)"
|
630 |
|
|
}
|
631 |
|
|
append ether_msg " [ethernet::format_hex_data $packet]\n"
|
632 |
|
|
synth::output $ether_msg "eth_ether"
|
633 |
|
|
|
634 |
|
|
if { 0x0806 == $eth_protocol } {
|
635 |
|
|
# An ARP request. This should always be 28 bytes.
|
636 |
|
|
if { [string length $packet] < 28 } {
|
637 |
|
|
return
|
638 |
|
|
}
|
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]
|
649 |
|
|
|
650 |
|
|
set arp_msg "$device $direction: ARP "
|
651 |
|
|
if { $op == 1 } {
|
652 |
|
|
append arp_msg "request "
|
653 |
|
|
} elseif { $op == 2 } {
|
654 |
|
|
append arp_msg "reply "
|
655 |
|
|
} else {
|
656 |
|
|
append_arp_msg "<unknown opcode> "
|
657 |
|
|
}
|
658 |
|
|
if { $hard_type != 1 } {
|
659 |
|
|
append arp_msg "(unexpected hard_type field $hard_type, should be 1) "
|
660 |
|
|
}
|
661 |
|
|
if { $prot_type != 0x0800 } {
|
662 |
|
|
append arp_msg "(unexpected prot_type field $prot_type, should be 0x0800) "
|
663 |
|
|
}
|
664 |
|
|
if { $hard_size != 6 } {
|
665 |
|
|
append arp_msg "(unexpected hard_size field $hard_size, should be 6) "
|
666 |
|
|
}
|
667 |
|
|
if { $prot_size != 4 } {
|
668 |
|
|
append arp_msg "(unexpected prot_size field $prot_size, should be 4) "
|
669 |
|
|
}
|
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]
|
674 |
|
|
append arp_msg "\n"
|
675 |
|
|
|
676 |
|
|
synth::output $arp_msg "eth_arp"
|
677 |
|
|
return
|
678 |
|
|
}
|
679 |
|
|
|
680 |
|
|
if { 0x0800 != $eth_protocol } {
|
681 |
|
|
return
|
682 |
|
|
}
|
683 |
|
|
|
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 } {
|
687 |
|
|
return
|
688 |
|
|
}
|
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"
|
695 |
|
|
if { 0 != $tos } {
|
696 |
|
|
append ipv4_msg [format " tos %02x," [expr $tos & 0x0FF]]
|
697 |
|
|
}
|
698 |
|
|
append ipv4_msg [format " len %d, id %d," [expr $len & 0x0FFFF] [expr $id & 0x0FFFF]]
|
699 |
|
|
if { 0 != $frag } {
|
700 |
|
|
append ipv4_msg [format " frag %u" [expr 8 * ($frag & 0x01FFF)]]
|
701 |
|
|
if { 0 != ($frag & 0x04000) } {
|
702 |
|
|
append ipv4_msg " DF"
|
703 |
|
|
}
|
704 |
|
|
if { 0 != ($frag & 0x02000) } {
|
705 |
|
|
append ipv4_msg " MF"
|
706 |
|
|
}
|
707 |
|
|
append ipv4_msg ","
|
708 |
|
|
}
|
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),"
|
713 |
|
|
} else {
|
714 |
|
|
append ipv4_msg [format " protocol %d" $ip_protocol]
|
715 |
|
|
}
|
716 |
|
|
|
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"
|
720 |
|
|
|
721 |
|
|
synth::output $ipv4_msg "eth_ipv4"
|
722 |
|
|
|
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)} {
|
726 |
|
|
return
|
727 |
|
|
}
|
728 |
|
|
set packet [string range $packet [expr 4 * $ip_hdrsize] end]
|
729 |
|
|
|
730 |
|
|
} elseif { 6 == $ip_version } {
|
731 |
|
|
if { [string length $packet] < 40 } {
|
732 |
|
|
return
|
733 |
|
|
}
|
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]
|
738 |
|
|
if { 0 != $flow } {
|
739 |
|
|
append ipv6_msg [format " flow %04x prio %x," $flow $prio]
|
740 |
|
|
}
|
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),"
|
746 |
|
|
} else {
|
747 |
|
|
append ipv6_msg [format " protocol %d," $next_header]
|
748 |
|
|
}
|
749 |
|
|
|
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"
|
753 |
|
|
|
754 |
|
|
synth::output $ipv6_msg "eth_ipv6"
|
755 |
|
|
|
756 |
|
|
set packet [string range $packet 40 end]
|
757 |
|
|
|
758 |
|
|
} else {
|
759 |
|
|
synth::output "$device $direction: unknown IP version $ip_version\n" "eth_ipv4"
|
760 |
|
|
return
|
761 |
|
|
}
|
762 |
|
|
|
763 |
|
|
|
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
|
767 |
|
|
# as tcp or udp.
|
768 |
|
|
if { 1 == $ip_protocol } {
|
769 |
|
|
# ipv4 ICMP
|
770 |
|
|
if { [string length $packet] < 4 } {
|
771 |
|
|
return
|
772 |
|
|
}
|
773 |
|
|
binary scan $packet {ccS} code type checksum
|
774 |
|
|
|
775 |
|
|
set icmpv4_msg "$device $direction: ICMPv4 "
|
776 |
|
|
set error 0
|
777 |
|
|
set data 0
|
778 |
|
|
switch -- $code {
|
779 |
|
|
|
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]]
|
786 |
|
|
set data 1
|
787 |
|
|
set packet [string range $packet 8 end]
|
788 |
|
|
}
|
789 |
|
|
}
|
790 |
|
|
3 {
|
791 |
|
|
append icmpv4_msg "unreachable/"
|
792 |
|
|
switch -- $type {
|
793 |
|
|
|
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" }
|
810 |
|
|
}
|
811 |
|
|
set error 1
|
812 |
|
|
}
|
813 |
|
|
4 {
|
814 |
|
|
append icmpv4_msg "source quench"
|
815 |
|
|
set error 1
|
816 |
|
|
}
|
817 |
|
|
5 {
|
818 |
|
|
append icmpv4_msg "redirect/"
|
819 |
|
|
switch -- $type {
|
820 |
|
|
|
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" }
|
825 |
|
|
}
|
826 |
|
|
set error 1
|
827 |
|
|
}
|
828 |
|
|
8 {
|
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]]
|
833 |
|
|
set data 1
|
834 |
|
|
set packet [string range $packet 8 end]
|
835 |
|
|
}
|
836 |
|
|
}
|
837 |
|
|
9 {
|
838 |
|
|
append icmpv4_msg "router advertisement"
|
839 |
|
|
}
|
840 |
|
|
10 {
|
841 |
|
|
append icmpv4_msg "router solicitation"
|
842 |
|
|
}
|
843 |
|
|
11 {
|
844 |
|
|
append icmpv4_msg "time exceeded/"
|
845 |
|
|
switch -- $type {
|
846 |
|
|
|
847 |
|
|
1 { append icmpv4_msg "reassembly" }
|
848 |
|
|
default { append icmpv4_msg "unknown" }
|
849 |
|
|
}
|
850 |
|
|
set error 1
|
851 |
|
|
}
|
852 |
|
|
12 {
|
853 |
|
|
append icmpv4_msg "parameter problem/"
|
854 |
|
|
switch -- $type {
|
855 |
|
|
|
856 |
|
|
1 { append icmpv4_msg "required option missing" }
|
857 |
|
|
default { append icmpv4_msg "unknown" }
|
858 |
|
|
}
|
859 |
|
|
set error 1
|
860 |
|
|
}
|
861 |
|
|
13 {
|
862 |
|
|
append icmpv4_msg "timestamp request"
|
863 |
|
|
}
|
864 |
|
|
14 {
|
865 |
|
|
append icmpv4_msg "timestamp reply"
|
866 |
|
|
}
|
867 |
|
|
15 {
|
868 |
|
|
append icmpv4_msg "information request"
|
869 |
|
|
}
|
870 |
|
|
16 {
|
871 |
|
|
append icmpv4_msg "information reply"
|
872 |
|
|
}
|
873 |
|
|
17 {
|
874 |
|
|
append icmpv4_msg "address mask request"
|
875 |
|
|
}
|
876 |
|
|
18 {
|
877 |
|
|
append icmpv4_msg "address mask reply"
|
878 |
|
|
}
|
879 |
|
|
default {
|
880 |
|
|
append icmpv4_msg "unknown"
|
881 |
|
|
}
|
882 |
|
|
}
|
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"
|
891 |
|
|
} else {
|
892 |
|
|
append icmpv4_msg ", udp"
|
893 |
|
|
}
|
894 |
|
|
append icmpv4_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv4_ntoa $ip_source]:$ip_source_port"
|
895 |
|
|
}
|
896 |
|
|
}
|
897 |
|
|
|
898 |
|
|
append icmpv4_msg "\n"
|
899 |
|
|
synth::output $icmpv4_msg "eth_icmpv4"
|
900 |
|
|
|
901 |
|
|
# Only some of the requests contain additional data that should be displayed
|
902 |
|
|
if { !$data } {
|
903 |
|
|
return
|
904 |
|
|
}
|
905 |
|
|
|
906 |
|
|
} elseif { 58 == $ip_protocol } {
|
907 |
|
|
# ipv6 ICMP
|
908 |
|
|
if { [string length $packet] < 4 } {
|
909 |
|
|
return
|
910 |
|
|
}
|
911 |
|
|
binary scan $packet {ccS} code type checksum
|
912 |
|
|
|
913 |
|
|
set icmpv6_msg "$device $direction: ICMPv6 "
|
914 |
|
|
set error 0
|
915 |
|
|
set data 0
|
916 |
|
|
switch -- $code {
|
917 |
|
|
1 {
|
918 |
|
|
append icmpv6_msg "unreachable/"
|
919 |
|
|
switch -- $type {
|
920 |
|
|
|
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" }
|
926 |
|
|
}
|
927 |
|
|
set error 1
|
928 |
|
|
}
|
929 |
|
|
2 {
|
930 |
|
|
append icmpv6_msg "packet too big"
|
931 |
|
|
set error 1
|
932 |
|
|
}
|
933 |
|
|
3 {
|
934 |
|
|
append icmpv6_msg "time exceeded/"
|
935 |
|
|
switch -- $type {
|
936 |
|
|
|
937 |
|
|
1 { append icmpv6_msg "fragment reassembly" }
|
938 |
|
|
default { append icmpv6_msg "unknown" }
|
939 |
|
|
}
|
940 |
|
|
set error 1
|
941 |
|
|
}
|
942 |
|
|
4 {
|
943 |
|
|
append icmpv6_msg "parameter problem"
|
944 |
|
|
switch -- $type {
|
945 |
|
|
|
946 |
|
|
1 { append icmpv6_msg "unrecognized next header" }
|
947 |
|
|
2 { append icmpv6_msg "unrecognized option" }
|
948 |
|
|
default { append icmpv6_msg "unknown" }
|
949 |
|
|
}
|
950 |
|
|
set error 1
|
951 |
|
|
}
|
952 |
|
|
128 {
|
953 |
|
|
append icmpv6_msg "ping request"
|
954 |
|
|
# FIXME: is this the same format as for icmpv4?
|
955 |
|
|
}
|
956 |
|
|
129 {
|
957 |
|
|
append icmpv6_msg "ping reply"
|
958 |
|
|
# FIXME: is this the same format as for icmpv4?
|
959 |
|
|
}
|
960 |
|
|
130 {
|
961 |
|
|
append icmpv6_msg "group membership query"
|
962 |
|
|
}
|
963 |
|
|
131 {
|
964 |
|
|
append icmpv6_msg "group membership report"
|
965 |
|
|
}
|
966 |
|
|
132 {
|
967 |
|
|
append icmpv6_msg "group membership reduction"
|
968 |
|
|
}
|
969 |
|
|
133 {
|
970 |
|
|
append icmpv6_msg "router solicitation"
|
971 |
|
|
}
|
972 |
|
|
134 {
|
973 |
|
|
append icmpv6_msg "router advertisement"
|
974 |
|
|
}
|
975 |
|
|
135 {
|
976 |
|
|
append icmpv6_msg "neighbour solicitation"
|
977 |
|
|
}
|
978 |
|
|
136 {
|
979 |
|
|
append icmpv6_msg "neighbour advertisement"
|
980 |
|
|
}
|
981 |
|
|
137 {
|
982 |
|
|
append icmpv6_msg "redirect"
|
983 |
|
|
}
|
984 |
|
|
}
|
985 |
|
|
|
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"
|
993 |
|
|
}
|
994 |
|
|
append icmpv6_msg " >[ethernet::inet_ipv4_ntoa $ip_dest]:$ip_dest_port <[ethernet::inet_ipv6_ntoa $ip_source]:$ip_source_port"
|
995 |
|
|
}
|
996 |
|
|
append icmpv6_msg "\n"
|
997 |
|
|
synth::output $icmpv6_msg "eth_icmpv6"
|
998 |
|
|
|
999 |
|
|
if { !$data } {
|
1000 |
|
|
return
|
1001 |
|
|
}
|
1002 |
|
|
|
1003 |
|
|
} elseif { 6 == $ip_protocol } {
|
1004 |
|
|
# TCP
|
1005 |
|
|
if { [string length $packet] < 20 } {
|
1006 |
|
|
return
|
1007 |
|
|
}
|
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]
|
1014 |
|
|
|
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))"
|
1019 |
|
|
}
|
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))"
|
1023 |
|
|
}
|
1024 |
|
|
|
1025 |
|
|
append tcp_msg ", "
|
1026 |
|
|
if { $flags & 0x08 } {
|
1027 |
|
|
append tcp_msg "PSH "
|
1028 |
|
|
}
|
1029 |
|
|
if { $flags & 0x04 } {
|
1030 |
|
|
append tcp_msg "RST "
|
1031 |
|
|
}
|
1032 |
|
|
if { $flags & 0x02 } {
|
1033 |
|
|
append tcp_msg "SYN "
|
1034 |
|
|
}
|
1035 |
|
|
if { $flags & 0x01 } {
|
1036 |
|
|
append tcp_msg "FIN "
|
1037 |
|
|
}
|
1038 |
|
|
append tcp_msg [format "seq %u" $seq]
|
1039 |
|
|
|
1040 |
|
|
if { 0 != ($flags & 0x010) } {
|
1041 |
|
|
append tcp_msg [format ", ACK %u" $ack]
|
1042 |
|
|
}
|
1043 |
|
|
append tcp_msg ", win $winsize"
|
1044 |
|
|
if { 0 != ($flags & 0x020) } {
|
1045 |
|
|
append tcp_msg ", URG $urg"
|
1046 |
|
|
}
|
1047 |
|
|
append tcp_msg "\n"
|
1048 |
|
|
synth::output $tcp_msg "eth_tcp"
|
1049 |
|
|
|
1050 |
|
|
set packet [string range $packet [expr 4 * $hdrsize] end]
|
1051 |
|
|
} elseif { 17 == $ip_protocol } {
|
1052 |
|
|
# UDP
|
1053 |
|
|
if { [string length $packet] < 8 } {
|
1054 |
|
|
return
|
1055 |
|
|
}
|
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))"
|
1064 |
|
|
}
|
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))"
|
1068 |
|
|
}
|
1069 |
|
|
append udp_msg "\n"
|
1070 |
|
|
synth::output $udp_msg "eth_udp"
|
1071 |
|
|
set packet [string range $packet 8 end]
|
1072 |
|
|
} else {
|
1073 |
|
|
# Unknown protocol, so no way of knowing where the data starts.
|
1074 |
|
|
return
|
1075 |
|
|
}
|
1076 |
|
|
|
1077 |
|
|
# At this point we may have a payload. This should be
|
1078 |
|
|
# dumped in both hex and ascii. The code tries to preserve
|
1079 |
|
|
# alignment.
|
1080 |
|
|
if { [string length $packet] == 0 } {
|
1081 |
|
|
return
|
1082 |
|
|
}
|
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
|
1088 |
|
|
}
|
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"
|
1099 |
|
|
} else {
|
1100 |
|
|
append asciidata_msg "??"
|
1101 |
|
|
}
|
1102 |
|
|
if { 3 == ($i % 4) } {
|
1103 |
|
|
append asciidata_msg " "
|
1104 |
|
|
}
|
1105 |
|
|
}
|
1106 |
|
|
append asciidata_msg "\n"
|
1107 |
|
|
synth::output $hexdata_msg "eth_hexdata"
|
1108 |
|
|
synth::output $asciidata_msg "eth_asciidata"
|
1109 |
|
|
|
1110 |
|
|
return
|
1111 |
|
|
}
|
1112 |
|
|
|
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
|
1118 |
|
|
} else {
|
1119 |
|
|
set ethernet::logging_enabled 1
|
1120 |
|
|
.toolbar.ethernet_logging configure -relief sunken
|
1121 |
|
|
}
|
1122 |
|
|
}
|
1123 |
|
|
|
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
|
1130 |
|
|
|
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"
|
1137 |
|
|
|
1138 |
|
|
if { [synth::tdf_has_option "ethernet" "logging"] } {
|
1139 |
|
|
set ethernet::logging_enabled [synth::tdf_get_option "ethernet" "logging"]
|
1140 |
|
|
} else {
|
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
|
1143 |
|
|
# to disable.
|
1144 |
|
|
set ethernet::logging_enabled 1
|
1145 |
|
|
}
|
1146 |
|
|
if { $ethernet::logging_enabled } {
|
1147 |
|
|
.toolbar.ethernet_logging configure -relief sunken
|
1148 |
|
|
}
|
1149 |
|
|
|
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 ""
|
1155 |
|
|
}
|
1156 |
|
|
if { "" == $ethernet_help } {
|
1157 |
|
|
.menubar.help add command -label "Ethernet" -state disabled
|
1158 |
|
|
} else {
|
1159 |
|
|
.menubar.help add command -label "Ethernet" -command [list synth::handle_help "file://$ethernet_help"]
|
1160 |
|
|
}
|
1161 |
|
|
}
|
1162 |
|
|
|
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
|
1169 |
|
|
}
|
1170 |
|
|
}
|
1171 |
|
|
|
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]
|
1176 |
|
|
|
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
|
1182 |
|
|
continue
|
1183 |
|
|
}
|
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
|
1189 |
|
|
continue
|
1190 |
|
|
}
|
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
|
1196 |
|
|
continue
|
1197 |
|
|
}
|
1198 |
|
|
set filter_options($name) [lrange $filter 1 end]
|
1199 |
|
|
}
|
1200 |
|
|
|
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"
|
1207 |
|
|
} else {
|
1208 |
|
|
array set parsed_options [list]
|
1209 |
|
|
set message ""
|
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
|
1215 |
|
|
} else {
|
1216 |
|
|
set parsed_options("-text") "ethernet $filter"
|
1217 |
|
|
synth::filter_add_parsed "eth_$filter" parsed_options
|
1218 |
|
|
}
|
1219 |
|
|
}
|
1220 |
|
|
}
|
1221 |
|
|
}
|
1222 |
|
|
}
|
1223 |
|
|
ethernet::filters_initialize
|
1224 |
|
|
}
|
1225 |
|
|
|
1226 |
|
|
return ethernet::instantiate
|