aboutsummaryrefslogtreecommitdiff
path: root/broccoli/script/modbus.bro
blob: 1505c4ee0227a1170635d5eb332918f2940bbda7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
@load frameworks/communication/listen

module Pasad;

redef Communication::listen_port = 47760/tcp;

redef Communication::listen_ssl = F;

## DATA STRUCTURES

export {
    redef enum Log::ID += { LOG };

    type Transaction: record {
        start_address:  count;
        quantity:   count;
    };

    type TransactionTable: table[count] of Transaction;

    type Info: record {
        transactions:   TransactionTable    &default=TransactionTable();
    };

    type RegisterData: record {
        ip:         addr    &log;
        uid:        count   &log;
        regtype:    string  &log;
        address:    count   &log;
        register:   count   &log;
    };

    const enable_filtering : bool = F;
    const filter_ip_addr : addr = 192.168.211.146;
    const filter_mem_addr : count = 64;
}

redef record connection += {
    pasad: Info &default=Info();
};

redef Communication::nodes += {
    ["pasad"] = [$host = 127.0.0.1, $events = /pasad/, $connect=F, $ssl=F]
};

## CUSTOM EVENTS

event pasad_register_received(data: RegisterData) {
    Log::write(Pasad::LOG, data);
    print fmt("Received address=%d, register=%d", data$address, data$register);
}

event pasad_unmatched_response(tid: count) {
    print fmt("Unmatched response: tid=%d", tid);
}

## CUSTOM FUNCTIONS

function pasad_check_filter(ip: addr, start_address: count, quantity: count) : bool {
    if (!enable_filtering)
        return T;
    if (ip != filter_ip_addr)
        return F;

    if (start_address == 0 && quantity == 0)
        return T;
    if (start_address > filter_mem_addr)
        return F;
    return filter_mem_addr < start_address + quantity;
}

function pasad_generate_event(transaction: Transaction, c: connection,
        headers: ModbusHeaders, registers: ModbusRegisters, regtype: string,
        i: count) {
    local data = RegisterData(
            $ip=c$id$resp_h,
            $uid=headers$uid,
            $regtype=regtype,
            $address=transaction$start_address + i,
            $register=registers[i]
    );
    event pasad_register_received(data);
}

function pasad_generate_events(transaction: Transaction, c: connection,
        headers: ModbusHeaders, registers: ModbusRegisters, regtype: string) {
    # TODO: check registers size
    if (enable_filtering) {
        print fmt("%d   %d    %d", filter_mem_addr, transaction$start_address, transaction$quantity);
        pasad_generate_event(transaction, c, headers, registers, regtype,
                filter_mem_addr - transaction$start_address);
    } else {
        local i = 0;
        while (i < transaction$quantity) {
            pasad_generate_event(transaction, c, headers, registers, regtype, i);
            ++i;
        }
    }
}

## EVENT HANDLERS

event bro_init() &priority=5 {
    Log::create_stream(Pasad::LOG, [$columns=RegisterData, $path="pasad-parsed"]);
}

event modbus_read_holding_registers_request(c: connection,
        headers: ModbusHeaders, start_address: count, quantity: count) {
    if (!pasad_check_filter(c$id$resp_h, start_address, quantity)) {
        print fmt("Filtered %s/%d/%d", c$id$resp_h, start_address, quantity);
        return;
    }

    local tid = headers$tid;
    local transaction = Transaction(
            $start_address=start_address,
            $quantity=quantity
    );
    c$pasad$transactions[tid] = transaction;
}

event modbus_read_holding_registers_response(c: connection,
        headers: ModbusHeaders, registers: ModbusRegisters) {
    if (!pasad_check_filter(c$id$resp_h, 0, 0)) {
        print fmt("Filtered %s", c$id$resp_h);
        return;
    }

    local tid = headers$tid;
    if (tid !in c$pasad$transactions) {
        event pasad_unmatched_response(tid);
        return;
    }
    local transaction = c$pasad$transactions[tid];
    delete c$pasad$transactions[tid];
    pasad_generate_events(transaction, c, headers, registers, "h");
}