Nov 10

lua Lesson 2 – Filling In The Blanks

In the first section of this series, I presented a simple Lua script to extract expert data from Tshark. Continuing down this path, a few scripting changes have been made to enhance functionality and introduce new concepts. Specifically:
  • White spacing was modified to improve readability and consistency
  • Additional extractions have been added to provide contextual information
  • A calculated value has been added
  • Script timing logic has been added
  • Output format has been modified to accommodate for the additional data being presented
  • A separate reusable function has been added
After these modifications, our output should resemble the following:
StreamXpert 1_1 output

 Spacing and Indentation

Unlike Python, indentation is not a requirement and generally speaking, LUA only requires spaces between names and keywords.  Thus, the following code snippets are functionally equivalent:
if tcp_analysis_retransmission() ~= nil then l_stream[“retr”] = l_stream[“retr”] + 1 end
if tcp_analysis_retransmission() ~= nil then
l_stream[“retr”] = l_stream[“retr”] + 1
For the most part, spacing and indentation are a matter of readability and programmer preference.

Additional Field Extractions

More field extractors have been added to enhance functionality.

  • frame.len is now being extracted. This allows us to track the total frame Bytes per connection. Alternately, if we wanted to look at “just” tcp payload Bytes, we could have chosen tcp.len.
  • Each stream entry now tracks the socket information (ip.src, ip.dst, tcp.srcport, tcp.dstport). This provides greater visual context, than (just) the stream identifier.

 Calculated Value

The total byte calculation and additional fields are logical extensions upon what we did in the 1.0 version of this script. For Bytes, we are adding the current frame length to the current running total stored in bytes and storing this value back into bytes. As you hopefully remember, l_stream is a pointer to the current stream record.
l_stream[“bytes”] = l_stream[“bytes”] + tostring(frame_len())

Script Timing

Within tap.packet() we introduce a new variable called start_time. This variable is initialized to the value returned from the os.time() function the first time tap.packet() is called. As we call tap.packet() (for each frame which contains TCP) we check to see if this variable exists. If it exists (not nil), we don’t overwrite it.

if start_time == nil then
start_time = os.time()
As mentioned previously, the aforementioned code could have been written on a single line. I chose to extend this over multiple lines to make its functionality more apparent. At the end of tap.draw() we subtract start_time from the current os.time() to arrive at an elapsed time, which we print using io.write().
 io.write(“\n”, “Elapsed Time = “, os.time() – start_time, ” Seconds”, “\n”)

Output Formatting

  • All print() functions have been replaced with io.write(). Io.write() is very similar to print(). However, it doesn’t place a newline at the end, nor insert tabs between arguments. While the functionality of print() can be a convenience (and necessary in certain instances), we are expliciting formatting output and don’t want unintended tabs. This link describes some of the differences between io.write() and print().
  • Field lengths were relatively short and predictable in our first script. However, as we introduce fields such as Bytes there is a significant amount of variation in field size which can occur. Thus, we explicitly format the output to ensure that our headers and data align. We use the string.format() function to specify field length and justification. String.format(), in conjuntion with io.write(), behaves similarly to the C printf() function.

Round() Function

In the first exercise we created two functions: tap.packet() and tap.draw(). In this exercise we create a new function called round(). Round() is used to calculate the average frame length per stream. Before discussing round(), I want to take a moment to talk about functions. Here are a few simple lines of code that illustrate a simple function and how functions return values for use in other functions.
function test_function(your_value)
 new_value = “The value handed to this function was ” .. your_value
 return (new_value)
io.write(“Please enter a value and press return: “)
io.write(test_function(io.read()), “\n”)
  • We initialize our function named test_function. Functions (like other code blocks) are delimited by end statements.
  • We print a line telling the user what to do. “Please enter a value and press return: “
  • The last line is a bit more complex. We need to work from the innermost function outwards.
    • io.read() waits for user input (delimited by a return key). Once the return sequence is detected, the value is passed as an argument to test_function().
    • test_function() accepts this value and stores it in “your_value”, appends it to a string and return(s) it. The “..” (two dots) is the Lua concatenation operator.
    • Lastly, io.write displays this value onscreen. Function arguments are separated by commas. The “\n” is the newline.
Let’s talk about round(). LUA has no built-in mathematical rounding function. To overcome this limitation, we create our own rounding function using the modulus operator and the math.ceiling() and math.floor() functions. Modulus “%” is the remainder from a division operation. Thus, if we divide a number by 1 we will end up with the decimal remainder. If this remainder is greater than or equal to.5, we round to the nearest integer. Conversely, less than .5, we round down.
function round(n)
if n % 1 >= 0.5 then return math.ceil(n) else return math.floor(n) end
While we could place the rounding code within tap.draw(), we may use this function again as this script evolves. I am not sure yet 🙂

One last note

Within tap.draw(), there is section of code within one of the io.write() calls which may appear a bit unusual. The full line is a bit long, so I am just going to concentrate on the snippet of interest. Specifically, I am focusing in on the section of code involving the round() function. To get the frame size average we would expect to divide the total bytes by total frames. This would, in turn, be passed to our round() function which would round it off to the nearest integer. Thus, we might anticipate the following:
round(l_stream[“bytes”] / l_stream[“frames”])
However, the code is written a bit differently:
round(l_stream[“bytes”] * (1 / l_stream[“frames”]))
There is a reason for this, has to do with the fact that Lua is much faster at multiplying than dividing. Thus, it’s a performance optimization trick.


That’s it for now. We will continue to extend our script in future installments. I hope you found this interesting and useful. As always, feedback is appreciated.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>