# Check if request has response in Zeek script

**URL:** https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825
**Category:** Development
**Tags:** development
**Created:** [December 1, 2022, 1:43pm UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825 "2022-12-01T13:43:44Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![Leviath](https://avatars.discourse-cdn.com/v4/letter/l/ba8739/32.png) [@Leviath](https://community.zeek.org/u/Leviath)
#### Post date: [December 1, 2022, 1:43pm UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825/1 "2022-12-01T13:43:44Z")

</div>

Good Morning,

I have a Zeek machine generating logs on a Modbus traffic.

My logs look like this :

```nohighlight
1342774501.072580 32 10.2.2.2 51411 10.2.2.3 502 255 READ_HOLDING_REGISTERS request 
1342774501.087014 32 10.2.2.2 51411 10.2.2.3 502 255 READ_HOLDING_REGISTERS response

```

‘tid’ is the transaction id to identify a request/response couple. I want to know if a robot hasn’t responded to the Controller by logging only requests that did not have a response within 1 second.

My code is :

```auto
module Modbus_Extended;

export {
    redef enum Log::ID += { LOG_DETAILED,
                            LOG_MASK_WRITE_REGISTER,
                            LOG_READ_WRITE_MULTIPLE_REGISTERS};

    type Modbus_Detailed: record {
        ts : time &log; # Timestamp of event
        tid : count &log; # Zeek unique ID for connection
        id : conn_id &log; # Zeek connection struct (addresses and ports)
        unit_id : count &log; # Modbus unit-id
        func : string &log &optional; # Modbus Function
        network_direction : string &log &optional; # Message direction (request or response)
        address : count &log &optional; # Starting address for value(s) field
        quantity : count &log &optional; # Number of addresses/values read or written to
        values : string &log &optional; # Coils, discrete_inputs, or registers read/written to
    };
    global log_modbus_detailed: event(rec: Modbus_Detailed);

global transaction_ids: set[string, string] = {};

event modbus_message(c: connection,
                     headers: ModbusHeaders,
                     is_orig: bool) &priority=-5 {

    local modbus_detailed_rec: Modbus_Detailed;

    if(headers$tid !in transaction_ids[count]){
        add transaction_ids[headers$tid, c$modbus$ts]
    }else{
        delete transaction_ids[headers$tid, c$modbus$ts]
    }
    for(i in transaction_ids[timestamp]){
        if(c$modbus$ts > transactions_ids[headers$tid, i] +1)
        {
            Log::write(LOG_DETAILED, modbus_detailed_rec);
        }
    }
}
}

```

My guess is that I have to store transaction ids and check if I get only one occurence within this timelapse and then log it into a file, but I can figure out how to do it. Currently I can only generate logs with all the modbus traffic.

Thank you for your help

---

<div class="post-metadata">

### Author: ![awelzel](https://yyz1.discourse-cdn.com/flex011/user_avatar/community.zeek.org/awelzel/32/609_2.png) [@awelzel](https://community.zeek.org/u/awelzel)
#### Post date: [December 1, 2022, 3:55pm UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825/2 "2022-12-01T15:55:44Z")

</div>

> [@Leviath](#):
>
> My guess is that I have to store transaction ids and check if I get only one occurence within this timelapse and then log it into a file, but I can figure out how to do it. Currently I can only generate logs with all the modbus traffic.

One way is to add a `&create_expire` attribute to your `transaction_ids` table and register an expiration function (`&expire_func`) See the [docs](https://docs.zeek.org/en/master/script-reference/attributes.html?highlight=expire_func#expire-func) for an explanation. The other “expire” attributes you can use follow in the docs.

Within the expiration function you can then log the ~~just~~ to be expired record.

You may want to key transactions by the connection identifier `c$id` as well as different connections may use the same `tid` values.

In the Zeek base code, examples of this approach are in the [Tunnels module](https://github.com/zeek/zeek/blob/dbbb6cd6f0b482e146eddf1eac09230f69a8dbbd/scripts/base/frameworks/tunnels/main.zeek#L84-L87) using a global table and the [MQTT module](https://github.com/zeek/zeek/blob/dbbb6cd6f0b482e146eddf1eac09230f69a8dbbd/scripts/base/protocols/mqtt/main.zeek#L122-L128), tracking publish/subscribes on the connection objects directly, for example.

Hope this helps,  
Arne

---

<div class="post-metadata">

### Author: ![Leviath](https://avatars.discourse-cdn.com/v4/letter/l/ba8739/32.png) [@Leviath](https://community.zeek.org/u/Leviath)
#### Post date: [December 2, 2022, 10:40am UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825/3 "2022-12-02T10:40:31Z")

</div>

Thank you for your answer,

I tried what you suggested and now my code looks like this :

```auto
function expire (transac: set[time,count,count,string,string], ts: time, tid: count, unit_id: count, func: string, direction: string): interval
{
        local modbus_detailed_rec: Modbus_Detailed;

        modbus_detailed_rec$ts = ts;
        modbus_detailed_rec$tid = tid;

        modbus_detailed_rec$unit_id = unit_id;
        modbus_detailed_rec$func = func;
        modbus_detailed_rec$network_direction = direction;
        Log::write(LOG_DETAILED, modbus_detailed_rec);
}

global transaction_ids: set[time,count,count,string,string] &create_expire=1sec &expire_func=expire;

event modbus_message(c: connection,
                     headers: ModbusHeaders,
                     is_orig: bool) &priority=-5 {

    local modbus_detailed_rec: Modbus_Detailed;

    if (( headers$function_code < 0x81 || headers$function_code > 0x98 )) {
        if (is_orig)
            modbus_detailed_rec$network_direction = request_str_g;
        else
            modbus_detailed_rec$network_direction = response_str_g;

        }

        modbus_detailed_rec$ts = network_time();
        modbus_detailed_rec$tid = headers$tid;
        modbus_detailed_rec$id = c$id;

        modbus_detailed_rec$unit_id = headers$uid;
        modbus_detailed_rec$func = Modbus::function_codes[headers$function_code];

        #if (headers$tid !in transaction_ids || c$modbus$id !in transaction_ids){
                add transaction_ids[modbus_detailed_rec$ts,modbus_detailed_rec$tid, modbus_detailed_rec$unit_id,modbus_detailed_rec$func,modbus_detailed_rec$network_direction];
        #}
        #else{
                delete transaction_ids[modbus_detailed_rec$ts,modbus_detailed_rec$tid,modbus_detailed_rec$unit_id,modbus_detailed_rec$func,modbus_detailed_rec$network_direction];
        #}
}

```

I would need to log a lot of data from the event (timestamp, tid, cid, source\_ip, dest\_ip, unit\_id, func) so I added fields to my set to store it.

The problem is that I can’t set a condition to find if the tid has already been seen in order to “refresh” the timer. In the end all my entries will be logged after 1 sec. I can’t look for a specific value in a multiple fields set. How should I do it ?

---

<div class="post-metadata">

### Author: ![awelzel](https://yyz1.discourse-cdn.com/flex011/user_avatar/community.zeek.org/awelzel/32/609_2.png) [@awelzel](https://community.zeek.org/u/awelzel)
#### Post date: [December 2, 2022, 11:51am UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825/4 "2022-12-02T11:51:05Z")

</div>

Hello Lev,

instead of keeping the individual values in as set, use a table of `[conn_id, count]` representing connection and transaction id, pointing to the record you want to log.

Something like:

```auto
global transactions: table[conn_id, count] of Modbus_Detailed &create_expire=1sec &expire_func=expire;

```

You should then be able to use

```auto
if ( [c$id, headers$tid] !in transactions ) {
   local rec = Create a new Modbus_Detailed record...
   transactions[c$id, headers$tid] = rec;
} else {
   Log::write(transactions[c$id, headers$tid]);
   delete transactions[c$id, headers$tid];
}

```

…and have a corresponding `Log::write()` in the `expire()` function.

Hope that helps.

---

<div class="post-metadata">

### Author: ![Leviath](https://avatars.discourse-cdn.com/v4/letter/l/ba8739/32.png) [@Leviath](https://community.zeek.org/u/Leviath)
#### Post date: [December 2, 2022, 2:05pm UTC](https://community.zeek.org/t/check-if-request-has-response-in-zeek-script/6825/6 "2022-12-02T14:05:44Z")

</div>

Thank you, it worked perfectly for me 🙂
