All Classes Namespaces Files Functions Pages
converttobpf.tcl
Go to the documentation of this file.
1 ##
2 # @brief Basic namespace for all Excentis Tcl extensions
3 #
4 namespace eval excentis {
5 
6 ##
7 # @brief Namespace for Excentis Tcl extensions for basic functions.
8 #
9 # This includes functionality such as:
10 # - Network frame generation
11 # - Address parsing and conversion
12 # - ...
13 #
14 namespace eval basic {
15 
16 ## \file
17 #
18 # @brief Utility module for <b>parsing DisplayFilter</b> strings and <b>converting</b> them <b>to BPF</b> filters, which is the only filter format used by the ByteBlower client.
19 #
20 # @copyright Excentis nv - www.excentis.com
21 #
22 
23 proc ParseFilter { filter } {
24  # Parses the filter in subfilters.
25  # Each subfilter is a string containing a field, operation and value.
26  # Field, operation and value must be separated by a space.
27  # A filter can contain at maximum two subfilters, where a subfilter is
28  # within parentheses.
29  # Example filters:
30  # udp.srcport == 1024
31  # (ip.src == 10.11.0.2) and (ip.dst == 10.11.0.3)
32  # The following filters are invalid:
33  # udp.srcport==1024
34  # ((ip.src == 10.11.0.2) and (ip.dst == 10.11.0.3))
35 
36  # First convert the filter to lower case and remove leading and/or trailing spaces
37  set filter [string trim [string tolower $filter]]
38 
39  # Remove extra spaces within filter string
40  set newFilter ""
41  set rest ""
42  set newPart ""
43  while { [scan $filter "%s %\[^\n\]" newPart rest] > 1} {
44  set newFilter [append newFilter $newPart " "]
45  set filter $rest
46  }
47  set filter [append newFilter $newPart] ; # append the last part
48 
49  # Recursive processing on standardized filter
50  return [ConvertSubFilter $filter]
51 }
52 
53 proc ConvertSubFilter { inFilter } {
54  set result ""
55 
56  set endPosition [GetSubFilter $inFilter]
57  if { $endPosition != {}} {
58  set startPosition [lindex [lindex [regexp -indices -inline {[^\s\t]} $inFilter] 0] 0]
59  if {[string index $inFilter $startPosition] == "("} {
60  incr startPosition
61  }
62  set firstFilter [string range $inFilter $startPosition [expr $endPosition-1]]
63  append result "(" [ConvertSubFilter $firstFilter] ")"
64  # now an operator can follow
65  set endPosition1 [lindex [lindex [regexp -indices -inline {[^\s\t]} [string range $inFilter [expr $endPosition + 1] end] ] 0] 0]
66  if {$endPosition1 == {}} {
67  return $result
68  }
69  # There is an "operator"
70  set endPosition [expr $endPosition + $endPosition1 +1]
71  set startPosition $endPosition
72  set endPosition1 [lindex [lindex [regexp -indices -inline {[\s\t]} [string range $inFilter $endPosition end] ] 0] 0]
73  set endPosition [expr $endPosition + $endPosition1]
74  # Get the operator
75  append result " " [string range $inFilter $startPosition $endPosition]
76  set endPosition1 [lindex [lindex [regexp -indices -inline {[^\s\t]} [string range $inFilter [expr $endPosition + 1] end] ] 0] 0]
77  set endPosition [expr $endPosition + $endPosition1 ]
78  set secondFilter [string range $inFilter $endPosition end]
79  append result [ConvertSubFilter $secondFilter]
80  return $result
81  } else {
82  # Atomic case: convert a single BPF filter component
83  return [ConvertToBpf $inFilter]
84  }
85 
86 }
87 
88 proc GetSubFilter { inFilter } {
89  # Find where the subfilter which starts with inFilter stops.
90  # @return position of closing parenthesis + 1
91 
92  # Move search point to first non-space character in inFilter
93  set position [lindex [lindex [regexp -indices -inline {[^\s\t]} $inFilter] 0] 0]
94  if {$position == {}} {
95  # Empty filter
96  return $position
97  }
98 
99  if {[string index $inFilter $position] == ")"} {
100  # A subfilter should not start with a closing brace
101  error "ParseFilter error: Unbalanced brace found. Please make sure each opening brace finds a closing one."
102  } elseif {[string index $inFilter $position] == "("} {
103  # The subfilter consists of everything up to the matching closing brace
104  set numberOfBracesToFind 1
105  while {1} {
106  # Find next brace in the (remaining) substring, after the current search point
107  set position1 [lindex [lindex [regexp -indices -inline {[()]} [string range $inFilter [expr $position + 1] end] ] 0] 0]
108  if {$position1 == {}} {
109  error "ParseFilter error: Unbalanced brace found. Please make sure each opening brace finds a closing one."
110  }
111  # Move search point to the next brace
112  set position [expr $position + $position1 + 1]
113  # Brace counting
114  if {[string index $inFilter $position] == "("} {
115  incr numberOfBracesToFind
116  } else {
117  incr numberOfBracesToFind -1
118  }
119  if {$numberOfBracesToFind == 0} {
120  # Current search point is end of subfilter
121  return $position
122  }
123  }
124  } else {
125  # Not a brace, so we have to find a valid field, operator and value.
126  set position1 [lindex [lindex [regexp -indices -inline {[\s\t]} [string range $inFilter [expr $position + 1] end]] 0] 0]
127  if {$position1 == {}} {
128  # Unexpected end of subfilter
129  error "ParseFilter error: Error during parsing of filter string. Expected a logical operation."
130  }
131  set position [expr $position + $position1 + 1]
132  set position1 [lindex [lindex [regexp -indices -inline {[\s\t]} [string range $inFilter [expr $position + 1] end]] 0] 0]
133  if {$position1 == {}} {
134  # Unexpected end of subfilter
135  error "ParseFilter error: Error during parsing of filter string. Expected a value."
136  }
137  set position [expr $position + $position1 + 1]
138 
139  if {[string index $inFilter [expr $position + 1]] == "("} {
140  set position1 [lindex [lindex [regexp -indices -inline {[)]} [string range $inFilter [expr $position + 1] end]] 0] 0]
141  set position [expr $position + $position1 + 1]
142  }
143  set position1 [lindex [lindex [regexp -indices -inline {[\s\t]} [string range $inFilter [expr $position + 1] end]] 0] 0]
144  if {$position1 == {}} {
145  return $position1
146  }
147  set position [expr $position + $position1 + 1]
148  return $position
149  }
150 }
151 
152 proc ConvertToBpf { filter } {
153  # @param filter : A filter string containing a field, operation and value.
154  # @return A bpf filter which filters the packet based on the user filter.
155  #
156  # Examples:
157  # ConvertToBpf "ip.src == 10.0.0.1" returns "ip src 10.0.0.1"
158  # ConvertToBpf "ip" returns "ip"
159 
160  set filterParts [split $filter]
161  if {[llength $filterParts] > 3} {
162  error "ConvertToBpf error: Invalid filter format: \"$filter\""
163  }
164  # FIELD
165  set field [lindex $filterParts 0]
166  # OPERATION
167  set operation [ lindex $filterParts 1 ]
168  switch -- $operation {
169  "eq" - "==" { set operation "=" }
170  "gt" - ">" { set operation ">" }
171  "lt" - "<" { set operation "<" }
172  "ge" - ">=" { set operation ">=" }
173  "le" - "<=" { set operation "<=" }
174  "" { }
175  "!=" - "ne" { set operation "!=" }
176  default { error "ConvertToBpf error: Invalid filter operation: $operation" }
177  }
178  # VALUE
179  set value [ lindex $filterParts 2 ]
180 
181  set result ""
182  switch -regexp $field {
183  {^eth\.} { set result [ConvertToBpfEth $field $operation $value] }
184  {^vlan\.} { set result [ConvertToBpfVlan $field $operation $value] }
185  {^ip\.} { set result [ConvertToBpfIp $field $operation $value] }
186  {^udp\.} { set result [ConvertToBpfUdp $field $operation $value] }
187  {^ipv6\.} { set result [ConvertToBpfIpv6 $field $operation $value] }
188  {^tcp\.} { set result [ConvertToBpfTcp $field $operation $value] }
189  default { error "ConvertToBpf error: Unknown or unsupported filter protocol: $field\n Contact byteblower@exentis.com if you require support for this." }
190  }
191 
192  return $result
193 }
194 
195 proc ConvertToBpfEth { field operation value } {
196  # Convert Ethernet filter to bpf
197  #
198  # @param field : field name
199  # @param operation : comparison operator
200  # @param value : value to compare against
201 
202  set result ""
203  set invert 0
204  if { [string equal $operation "="]} {
205  set invert 0
206  } elseif { [string equal $operation "!="]} {
207  set invert 1
208  set operation "="
209  } else {
210  if { ! [string equal $field "eth.len"]} {
211  error "ConvertToBpfEth error: Operation '$operation' not allowed on Ethernet protocol fields (except eth.len)\n Contact byteblower@exentis.com if you require support for this."
212  }
213  }
214 
215  switch -exact $field {
216  eth.dst {
217  # Convert the value from 000102030405 to 00:01:02:03:04:05
218  set result "ether dst $value"
219  }
220  eth.src {
221  set result "ether src $value"
222  }
223  eth.type {
224  set result "ether proto $value"
225  }
226  eth.len {
227  set result "len $operation $value"
228  }
229  default {
230  error "ConvertToBpfEth error: Unknown or unsupported Ethernet field: $field\n Contact byteblower@exentis.com if you require support for this."
231  }
232  }
233  if {$invert} {
234  set result "!($result)"
235  }
236 
237  return $result
238 }
239 
240 proc ConvertToBpfVlan { field operation value } {
241  # Convert VLAN filter to bpf
242  #
243  # @param field : field name
244  # @param operation : comparison operator
245  # @param value : value to compare against
246 
247  set result ""
248  set invert 0
249  if { [string equal $operation "="]} {
250  set invert 0
251  } elseif { [string equal $operation "!="]} {
252  set invert 1
253  set operation "="
254  } else {
255  error "ConvertToBpfVlan error: Operation '$operation' not allowed on VLAN protocol fields\n Contact byteblower@exentis.com if you require support for this."
256  }
257 
258  switch -exact $field {
259  vlan.id {
260  set result "vlan $value"
261  }
262  default {
263  error "ConvertToBpfVlan error: Unknown or unsupported VLAN field: $field\n Contact byteblower@exentis.com if you require support for this."
264  }
265  }
266  if {$invert} {
267  set result "!($result)"
268  }
269 
270  return $result
271 }
272 
273 proc ConvertToBpfIp { field operation value } {
274  # Convert IP filter to bpf
275  #
276  # @param field : field name
277  # @param operation : comparison operator
278  # @param value : value to compare against
279 
280  set result ""
281  set invert 0
282  if { [string equal $operation "="]} {
283  set invert 0
284  } elseif { [string equal $operation "!="]} {
285  set invert 1
286  set operation "="
287  } else {
288  error "ConvertToBpfIp error: Operation '$operation' not allowed on IP protocol fields\n Contact byteblower@exentis.com if you require support for this."
289  }
290 
291  switch -exact $field {
292  ip.dst {
293  set result "ip dst $value"
294  }
295  ip.src {
296  set result "ip src $value"
297  }
298  ip.version {
299  set result "ip\[0\] & 0xF0 = $value"
300  }
301  ip.hdr_len {
302  # Ip header length ( in words )
303  set result "ip\[0\] & 0x0F = $value"
304  }
305  ip.tos {
306  # Ip Type of Service
307  set result "ip\[1\] = $value"
308  }
309  ip.len {
310  # Ip Total Length
311  set result "ip\[2:2\] $operation $value"
312  }
313  ip.id {
314  # Ip identification
315  set result "ip\[4:2\] = $value"
316  }
317  ip.flags.df {
318  # Ip don't fragment flag
319  set result "ip\[6\] & 0x40 = [expr $value * 0x40]"
320  }
321  ip.flags.mf {
322  # Ip more fragments flag
323  set result "ip\[7\] & 0x20 = [expr $value * 0x20]"
324  }
325  ip.frag_offset {
326  # Ip fragment offset
327  set result "ip\[7:2\] & 0x1FFF = $value"
328  }
329  ip.ttl {
330  # Ip time to live
331  set result "ip\[8\] = $value"
332  }
333  ip.proto {
334  # Higher layer protocol
335  set result "ip\[9\] = $value"
336  }
337  ip.checksum {
338  # Ip header checksum
339  set result "ip\[10:2\] = $value"
340  }
341  default {
342  error "ConvertToBpfIp error: Unknown or unsupported IP field: $field\n Contact byteblower@exentis.com if you require support for this."
343  }
344  }
345  if {$invert} {
346  set result "!($result)"
347  }
348 
349  return $result
350 }
351 
352 proc ConvertToBpfUdp {field operation value} {
353  # Convert UDP filter to bpf
354  #
355  # @param field : field name
356  # @param operation : comparison operator
357  # @param value : value to compare against
358 
359  set result ""
360  set invert 0
361  if { [string equal $operation "="]} {
362  set invert 0
363  } elseif { [string equal $operation "!="]} {
364  set invert 1
365  set operation "="
366  } else {
367  error "ConvertToBpfUdp error: Operation '$operation' not allowed on UDP protocol fields\n Contact byteblower@exentis.com if you require support for this."
368  }
369  switch -exact $field {
370  udp.srcport {
371  set result "udp src port $value"
372  }
373  udp.dstport {
374  set result "udp dst port $value"
375  }
376  udp.length {
377  set result "udp\[4:2\] = $value"
378  }
379  udp.checksum {
380  set result "udp\[6:2\] = $value"
381  }
382  default {
383  error "ConvertToBpfUdp error: Unknown or unsupported UDP field: $field\n Contact byteblower@exentis.com if you require support for this."
384  }
385  }
386  if {$invert} {
387  set result "!($result)"
388  }
389  return $result
390 
391 }
392 
393 proc ConvertToBpfTcp {field operation value} {
394  # Convert TCP filter to bpf
395  #
396  # @param field : field name
397  # @param operation : comparison operator
398  # @param value : value to compare against
399 
400  set result ""
401  set invert 0
402  if { [string equal $operation "="]} {
403  set invert 0
404  } elseif { [string equal $operation "!="]} {
405  set invert 1
406  set operation "="
407  } else {
408  error "ConvertToBpfTcp error: Operation '$operation' not allowed on TCP protocol fields\n Contact byteblower@exentis.com if you require support for this."
409  }
410 
411  switch -exact $field {
412  tcp.srcport {
413  set result "tcp src port $value"
414  }
415  tcp.dstport {
416  set result "tcp dst port $value"
417  }
418  default {
419  error "ConvertToBpfTcp error: Unknown or unsupported TCP field: $field\n Contact byteblower@exentis.com if you require support for this."
420  }
421  }
422  if {$invert} {
423  set result "!($result)"
424  }
425  return $result
426 
427 }
428 
429 proc ConvertToBpfIpv6 { field operation value } {
430  # Convert IPv6 filter to bpf
431  #
432  # @param field : field name
433  # @param operation : comparison operator
434  # @param value : value to compare against
435 
436  set result ""
437  set invert 0
438  if { [string equal $operation "="]} {
439  set invert 0
440  } elseif { [string equal $operation "!="]} {
441  set invert 1
442  set operation "="
443  } else {
444  error "ConvertToBpfIpv6 error: Operation '$operation' not allowed on IPv6 protocol fields\n Contact byteblower@exentis.com if you require support for this."
445  }
446 
447  switch -exact $field {
448  ipv6.dst {
449  set result "ip6 dst $value"
450  }
451  ipv6.src {
452  set result "ip6 src $value"
453  }
454  ipv6.version {
455  set result "ip6\[0\] & 0xF0 = $value"
456  }
457  ipv6.class {
458  # Ipv6 traffic class ( in words )
459  set result "ip6\[0:2\] & 0x0FF0 = $value"
460  }
461  ipv6.flow {
462  # Ip Type of Service
463  set result "ip6\[1:3\] & 0x0FFFFF = $value"
464  }
465  ipv6.plen {
466  # Ipv6 payload Length
467  set result "ip6\[4:2\] $operation $value"
468  }
469  ipv6.nxt {
470  # Ipv6 next header
471  set result "ip6\[6\] = $value"
472  }
473  ipv6.hlim {
474  # Ipv6 hop limit
475  set result "ip6\[7\] = $value"
476  }
477  default {
478  error "ConvertToBpfIpv6 error: Unknown or unsupported IPv6 field: $field\n Contact byteblower@exentis.com if you require support for this."
479  }
480  }
481  if {$invert} {
482  set result "!($result)"
483  }
484 
485  return $result
486 }
487 
488 
489 }
490 
491 }