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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
#![allow(clippy::needless_doctest_main)]
//! A macro for defining subscriptions in Retina.
//!
//! # Specifying Subscriptions in a .rs File
//!
//! [`filter`](macro@self::filter) is an attribute macro that parses a user-defined filter
//! expression to generate sub-filters that are statically inlined at each processing layer. This
//! verifies filter expressions at compile time and avoids overheads associated with interpreting
//! filters at runtime. The `filter` attribute macro must be tied to the callback requested.
//!
//! [`retina_main`](macro@self::retina_main) is an attribute macro that must be used alongside
//! [`filter`](macro@self::filter). It indicates to the framework how many subscriptions to expect.
//!
//! ## Usage
//! ```rust,no_run
//! use retina_core::config::default_config;
//! use retina_core::Runtime;
//! use retina_filtergen::{filter, retina_main};
//! use retina_datatypes::*;
//!
//! #[filter("ipv4 and tcp.port >= 100")]
//! fn log_tcp(conn: &ConnRecord) { // Callback; datatype(s) by reference
//!    println!("{:#?}", conn);
//! }
//!
//! #[filter("tls.sni ~ 'netflix'")]
//! fn log_tls(conn: &ConnRecord, tls: &TlsHandshake) {
//!    println!("{:#?}, {:#?}", conn, tls);
//! }
//!
//! #[retina_main(2)] // 2 subscriptions expected
//! fn main() {
//!    let cfg = default_config();
//!    // \note SubscribedWrapper is the structure built at compile-time to
//!    // 'wrap' all requested data. It must be specified as the generic parameter to Runtime.
//!    let mut runtime: Runtime<SubscribedWrapper> = Runtime::new(cfg, filter).unwrap();
//!    runtime.run();
//! }
//! ```
//!
//! # Specifying Subscriptions in TOML File
//!
//! [`subscription`](macro@self::subscription) is an attribute macro that allows users to specify
//! a list of subscriptions from an input file. This may be useful, for example, for programs that require
//! a large number of filters to be applied to the same callback.
//!
//! Input TOML files should be a list of `subscriptions`, each of which has `filter`, `datatypes`,
//! and `callback` specified. The datatypes in the TOML file must match the order and names of the
//! datatypes in the callbacks in code.
//!
//! ## Usage
//! ```toml
//![[subscriptions]]
//! filter = "(http.user_agent = '/mm.jpg') and (tcp.dst_port = 80 or tcp.dst_port = 8080)"
//! datatypes = [
//!     "HttpTransaction",
//!     "FilterStr",
//! ]
//! callback = "http_cb"
//!
//![[subscriptions]]
//! filter = "http.user_agent = 'HttpBrowser/1.0'"
//! datatypes = [
//!     "HttpTransaction",
//!     "FilterStr",
//! ]
//! callback = "http_cb"
//! ```
//!
//! ```rust,ignore
//! use retina_core::config::default_config;
//! use retina_core::Runtime;
//! use retina_filtergen::subscription;
//! use retina_datatypes::*;
//!
//! fn http_cb(http: &HttpTransaction, matched_filter: &FilterStr) {
//!    println!("Matched {} with {:#?}", matched_filter, http);
//! }
//!
//! #[subscription("examples/XX/spec.toml")]
//! fn main() {
//!    let cfg = default_config();
//!    let mut runtime: Runtime<SubscribedWrapper> = Runtime::new(cfg, filter).unwrap();
//!    runtime.run();
//! }
//! ```
//!
//! # Datatype syntax
//! All subscribed datatypes -- parameters to callbacks -- must be requested by reference.
//! Supported datatypes are defined in the [retina_datatypes](../datatypes) crate.
//!
//! # Filter syntax
//! The Retina filter syntax is similar to that of [Wireshark display
//! filters](https://wiki.wireshark.org/DisplayFilters). However, Retina is capable of filtering on
//! live traffic for online analysis, whereas display filters can only be applied for offline
//! analysis.
//!
//! A filter expression is a logical expression of constraints on attributes of the input traffic.
//! Each constraint is either a binary predicate that compares the value of an entity's attribute
//! with a constant, or a unary predicate that matches against the entity itself.
//!
//! ## Protocols
//! All protocol identifiers are valid as long as Retina contains an appropriate [protocol
//! module](../retina_core/protocols) of the same name. The [`filter`](macro@self::filter) macro
//! automatically generates filtering code using structs defined in the protocol's corresponding
//! parser module. The exception to this is `ethernet`, which Retina filters for by default.
//!
//! For example, [`ipv4`](../retina_core/protocols/packet/ipv4) and
//! [`tls`](../retina_core/protocols/stream/tls) are filterable protocols because they are both
//! protocol modules included in Retina.
//!
//! Retina will also automatically expand filter expressions to their fully-qualified form. For
//! example, the filter `tcp` is equivalent to `(ipv4 and tcp) or (ipv6 and tcp)`.
//!
//! ## Fields
//! All field identifiers are valid as long as Retina exposes a public accessor method for the
//! corresponding protocol struct of the same name, and the method returns a supported RHS field
//! type.
//!
//! For example,
//! [`ipv4.src_addr`](../retina_core/protocols/packet/ipv4/struct.Ipv4.html#method.src_addr) and
//! [`tls.sni`](../retina_core/protocols/stream/tls/struct.Tls.html#method.sni) are both filterable
//! fields because `src_addr()` is a public method associated with the `Ipv4` struct that returns an
//! `Ipv4Addr`, and `sni()` is a public method associated with the `Tls` struct that returns a
//! `String`.
//!
//! Retina also supports two combined fields: `addr` and `port`. Logically, these are equivalent to
//! `src_addr or dst_addr` and `src_port or dst_port`, respectively, except in predicates that use
//! the `!=` comparison operator (details below).
//!
//! ## Field types (RHS values)
//! | Type          | Example            |
//! |---------------|--------------------|
//! | IPv4 address  | `1.2.3.4`          |
//! | IPv4 prefix   | `1.2.3.4/24`       |
//! | IPv6 address  | `2001:db8::1`      |
//! | IPv6 prefix   | `2001:db8::1/64`   |
//! | Integer       | `443`              |
//! | String        | `'Safari'`         |
//! | Integer range | `1024..5000`       |
//!
//! ## Binary comparison operators
//! | Operator |   Alias   |         Description        | Example                         |
//! |----------|-----------|----------------------------|---------------------------------|
//! | `=`      |           | Equals                     | `ipv4.addr = 127.0.0.1`         |
//! | `!=`     | `ne`      | Not equals                 | `udp.dst_port != 53`            |
//! | `>=`     | `ge`      | Greater than or equals     | `tcp.port >= 1024`              |
//! | `<=`     | `le`      | Less than or equals        | `tls.version <= 771`            |
//! | `>`      | `gt`      | Greater than               | `ipv4.time_to_live > 64`        |
//! | `<`      | `lt`      | Less than                  | `ipv6.payload_length < 500`     |
//! | `in`     |           | In a range, or in a subnet | `ipv4.src_addr in 1.2.3.4/16`   |
//! | `~`      | `matches` | Regular expression match   | `tls.sni ~ 'netflix\\.com$'`    |
//!
//! **Possible pitfalls involving `!=`**
//!
//! Retina differs from Wireshark behavior regarding the `!=` operator. When applied to combined
//! fields (i.e., `addr` or `port`), Retina follows connection-level semantics instead of
//! packet-level semantics. For example, `tcp.port != 80` is equivalent to `tcp.src_port != 80 and
//! tcp.dst_port != 80`.
//!
//! **Regular expressions**
//!
//! Retina compiles regular expressions exactly once, using the
//! [`regex`](https://crates.io/crates/regex) and
//! [`lazy_static`](https://crates.io/crates/lazy_static) crates. As of this writing, `proc-macro`
//! crates like this one cannot export any items other than procedural macros, thus requiring
//! applications that wish to use Retina's regular expression filtering to specify `regex` and
//! `lazy_static` as dependencies. Also note that regular expressions are written as normal strings
//! in Rust, and not as [raw string
//! literals](https://doc.rust-lang.org/stable/reference/tokens.html#raw-string-literals). They are
//! allowed to match anywhere in the text, unless start (`^`) and end (`$`) anchors are used.
//!
//! ## Logical operators
//! | Operator | Alias | Description | Example                                      |
//! |----------|-------|-------------|----------------------------------------------|
//! | `and`    | `AND` | Logical AND | `tcp.port = 443 and tls`                     |
//! | `or`     | `OR`  | Logical OR  | `http.method = 'GET' or http.method = 'POST'`|
//!
//! `AND` takes precedence over `OR` in the absence of parentheses.
//!
//! Retina does not yet support the logical `NOT` operator. For some expressions, it can be
//! approximated using the `!=` binary comparison operator, taking the above mentioned pitfall into
//! consideration.

use proc_macro::TokenStream;
use quote::quote;
use retina_core::filter::ptree::*;
use retina_core::filter::*;
use std::str::FromStr;
use syn::parse_macro_input;
use utils::DELIVER;

#[macro_use]
extern crate lazy_static;

mod cache;
mod data;
mod deliver_filter;
mod packet_filter;
mod parse;
mod proto_filter;
mod session_filter;
mod utils;

use crate::cache::*;
use crate::data::*;
use crate::deliver_filter::gen_deliver_filter;
use crate::packet_filter::gen_packet_filter;
use crate::parse::*;
use crate::proto_filter::gen_proto_filter;
use crate::session_filter::gen_session_filter;

// Build a string that can be used to generate a hardware (NIC) filter at runtime.
fn get_hw_filter(packet_continue: &PTree) -> String {
    let ret = packet_continue.to_filter_string();
    let _flat_ptree =
        Filter::new(&ret).unwrap_or_else(|err| panic!("Invalid HW filter {}: {:?}", &ret, err));
    ret
}

// Returns a PTree from the given config
fn filter_subtree(input: &SubscriptionConfig, filter_layer: FilterLayer) -> PTree {
    let mut ptree = PTree::new_empty(filter_layer);

    for i in 0..input.subscriptions.len() {
        let spec = &input.subscriptions[i];
        let filter = Filter::new(&spec.filter)
            .unwrap_or_else(|err| panic!("Failed to parse filter {}: {:?}", spec.filter, err));

        let patterns = filter.get_patterns_flat();
        let deliver = Deliver {
            id: i,
            as_str: spec.as_str(),
            must_deliver: spec.datatypes.iter().any(|d| d.as_str == "FilterStr"),
        };
        ptree.add_filter(&patterns, spec, &deliver);
        DELIVER.lock().unwrap().insert(i, spec.clone());
    }

    ptree.collapse();
    println!("{}", ptree);
    ptree
}

// Generate code from the given config (all subscriptions)
// Also includes the original input (typically a callback or main function)
fn generate(input: syn::ItemFn, config: SubscriptionConfig) -> TokenStream {
    let mut statics: Vec<proc_macro2::TokenStream> = vec![];

    let packet_cont_ptree = filter_subtree(&config, FilterLayer::PacketContinue);
    let packet_continue = gen_packet_filter(
        &packet_cont_ptree,
        &mut statics,
        FilterLayer::PacketContinue,
    );

    let packet_ptree = filter_subtree(&config, FilterLayer::Packet);
    let packet_filter = gen_packet_filter(&packet_ptree, &mut statics, FilterLayer::Packet);

    let conn_ptree = filter_subtree(&config, FilterLayer::Protocol);
    let proto_filter = gen_proto_filter(&conn_ptree, &mut statics);

    let session_ptree = filter_subtree(&config, FilterLayer::Session);
    let session_filter = gen_session_filter(&session_ptree, &mut statics);

    let conn_deliver_ptree = filter_subtree(&config, FilterLayer::ConnectionDeliver);
    let conn_deliver_filter = gen_deliver_filter(
        &conn_deliver_ptree,
        &mut statics,
        FilterLayer::ConnectionDeliver,
    );
    let packet_deliver_ptree = filter_subtree(&config, FilterLayer::PacketDeliver);
    let packet_deliver_filter = gen_deliver_filter(
        &packet_deliver_ptree,
        &mut statics,
        FilterLayer::PacketDeliver,
    );

    let mut tracked_data = TrackedDataBuilder::new(&config);
    let subscribable = tracked_data.subscribable_wrapper();
    let tracked = tracked_data.tracked();

    let filter_str = get_hw_filter(&packet_cont_ptree); // Packet-level keep/drop filter

    let lazy_statics = if statics.is_empty() {
        quote! {}
    } else {
        quote! {
        lazy_static::lazy_static! {
            #( #statics )*
            }
        }
    };

    quote! {
        use retina_core::filter::actions::*;
        // Import potentially-needed traits
        use retina_core::subscription::{Trackable, Subscribable};
        use retina_datatypes::{FromSession, Tracked, FromMbuf, StaticData};

        #subscribable

        #tracked

        #lazy_statics

        pub fn filter() -> retina_core::filter::FilterFactory<TrackedWrapper> {

            fn packet_continue(mbuf: &retina_core::Mbuf,
                               core_id: &retina_core::CoreId) -> Actions {
                #packet_continue
            }

            fn packet_filter(mbuf: &retina_core::Mbuf, tracked: &TrackedWrapper) -> Actions {
                #packet_filter
            }

            fn protocol_filter(conn: &retina_core::protocols::ConnData,
                               tracked: &TrackedWrapper) -> Actions {
                #proto_filter
            }

            fn session_filter(session: &retina_core::protocols::Session,
                              conn: &retina_core::protocols::ConnData,
                              tracked: &TrackedWrapper) -> Actions
            {
                #session_filter
            }

            fn packet_deliver(mbuf: &retina_core::Mbuf,
                              conn: &retina_core::protocols::ConnData,
                              tracked: &TrackedWrapper)
            {
                #packet_deliver_filter
            }

            fn connection_deliver(conn: &retina_core::protocols::ConnData,
                                  tracked: &TrackedWrapper)
            {
                #conn_deliver_filter
            }

            retina_core::filter::FilterFactory::new(
                #filter_str,
                packet_continue,
                packet_filter,
                protocol_filter,
                session_filter,
                packet_deliver,
                connection_deliver,
            )
        }

        #input

    }
    .into()
}

/// Generate a Retina program from a specification file.
/// This expects an input TOML file with the subscription specifications.
#[proc_macro_attribute]
pub fn subscription(args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::ItemFn);
    let inp_file = parse_macro_input!(args as syn::LitStr).value();
    let config = SubscriptionConfig::from_file(&inp_file);
    generate(input, config)
}

/// Generate a Retina program without a specification file.
/// This expects a #[filter("...")] macro followed by the expected callback.
/// It must be used with #[retina_main(X)], where X = number of subscriptions.
#[proc_macro_attribute]
pub fn filter(args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::ItemFn);
    let filter_str = parse_macro_input!(args as syn::LitStr).value();
    let (datatypes, callback) = parse_input(&input);
    println!(
        "Filter: {}, Datatypes: {:?}, Callback: {:?}",
        filter_str, datatypes, callback
    );

    // If more subscriptions to parse, just output the callback
    add_subscription(callback, datatypes, filter_str);
    if !is_done() {
        return quote! {
            #input
        }
        .into();
    }

    // Otherwise, ready to assemble
    let config = SubscriptionConfig::from_raw(&CACHED_SUBSCRIPTIONS.lock().unwrap());

    generate(input, config)
}

// For generating a Retina program without a specification file
// This expects to receive the number of subscriptions
#[proc_macro_attribute]
pub fn retina_main(args: TokenStream, input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::ItemFn);
    let count = usize::from_str(parse_macro_input!(args as syn::LitInt).base10_digits()).unwrap();
    println!("Expecting {} subsctription(s)", count);
    set_count(count);

    // More subscriptions expected
    if !is_done() {
        return quote! {
            #input
        }
        .into();
    }

    // Otherwise, ready to assemble
    let config = SubscriptionConfig::from_raw(&CACHED_SUBSCRIPTIONS.lock().unwrap());

    generate(input, config)
}