Project

General

Profile

Rust Notes

This page exists to keep notes (comments, observations, etc) with respect to using Rust in Suricata for certain functions such as protocol parsing.

2017-01-18 Populating a C struct from Rust (and integrating Rust into the build)

I wanted to try passing a pointer to a struct from C to Rust and having Rust populate it to avoid the use of opaque (from C) objects. The branch can be found here:

https://github.com/jasonish/suricata/tree/ish-rust-1

it turned out to be relatively easy to do, and profiling showed that the overhead was not very high.

This branch also shows one way that a Rust library can be integrated into the build so extra git checkouts are not required, additionally the Rust code is compiled into a static library for use by Suricata.

2017-01-18 DNS Performance Test

This test used a simple DNS header parser for UDP DNS requests and responses. Profiling was turned on and a 140MB file of DNS traffic was used.


*** Rust

App Layer              IP ver   Proto   cnt            min            max            avg         
--------------------   ------   -----   ----------     ------------   ------------   ----------- 
dns                     IPv4      17        926708              399          79881          1063        985.1m  100.00
Proto detect            IPv4      17        926708              471          37353          1122          1.0b

*** Master

App Layer              IP ver   Proto   cnt            min            max            avg         
--------------------   ------   -----   ----------     ------------   ------------   ----------- 
dns                     IPv4      17        926709              384         212034           995        922.7m  100.00
Proto detect            IPv4      17        926709              384          34758           765        709.2m

Debugging

We'll need to have a configure flag for suricata to force the non-release version of crates.

Gdb allows you to step through C and rust code nicely.

panic

In Rust you can use panic!("some explanation of why"); similar to the BUG_ON() macro in Suricata/C

Backtrace (partial):

#0  0x00000000005fcf9d in rust_panic ()
#1  0x00000000005e4973 in sys_common::unwind::begin_unwind_inner::hd4253a1812044857bct ()
#2  0x0000000000679a18 in nfs3_parser::sys_common::unwind::begin_unwind<&str> (msg=..., file_line=0x93d808 <nfs3::NfsTcpParser::parse_tcp_data_ts::_FILE_LINE::h5eeb7498016bdd39pMc>)
    at src/libstd/sys/common/unwind/mod.rs:227
#3  0x0000000000684ea9 in nfs3_parser::nfs3::NfsTcpParser::parse_tcp_data_ts (self=0x7fffec5a9f40, i=...) at <std macros>:3
#4  0x0000000000686ff9 in nfs3_parser::nfs3::NfsTcpParser.RParser::parse (self=0x7fffec5a9f40, i=..., direction=0 '\000') at src/nfs3.rs:786
#5  0x00000000006871b1 in nfs3_parser::nfs3::r_nfstcp_parse (direction=0 '\000', 
    input=0x219e0c0 "<data omitted>"..., input_len=1448, 
    ptr=0x7fffec5a9f40) at src/rparser.rs:143
#6  0x000000000068712f in r_nfstcp_parse () at src/rparser.rs:131
#7  0x00000000004506dc in AppLayerParserParse (tv=<optimized out>, alp_tctx=<optimized out>, f=0x1bc0870, alproto=18, flags=<optimized out>, input=<optimized out>, input_len=<optimized out>)
    at app-layer-parser.c:993

Getting info about a struct:

(gdb) f 3
#3  0x0000000000684ea9 in nfs3_parser::nfs3::NfsTcpParser::parse_tcp_data_ts (self=0x7fffec5a9f40, i=...) at <std macros>:3
3    in <std macros>
(gdb) print rpc_phdr
$1 = (struct RpcRequestPacketPartial *) 0x7ffff2a05448
(gdb) print *rpc_phdr
$2 = {hdr = {frag_is_last = true, frag_len = 32932, xid = 1041988801, msgtype = 0}, rpcver = 2, program = 100003, progver = 3, procedure = 7}

Profiling

Profiling with tools like perf should be done in --release mode, however this mode by default strips symbols.

To change, add:

[profile.release]
debug = true

To the Cargo.toml.

Allow "cargo test" To Work

Once we start calling back into Suricata C code, "cargo test" may fail to compile as Suricata is not available as a library to link against. To keep your Rust tests workings, C functions can be declared as function pointers then registered at some point during Suricata startup, an example of this can be found in the DNS app-layer which uses a "context" object to pass the function pointers into the Rust app-layer. Some more general and global should be done though.

The context object can be seen here:

https://github.com/jasonish/suricata/blob/7835e07d6f57595375c4fd8915b2f3549df6cf65/src/app-layer-dns-udp.c#L160

And its registered into Rust a few lines down at:

https://github.com/jasonish/suricata/blob/7835e07d6f57595375c4fd8915b2f3549df6cf65/src/app-layer-dns-udp.c#L174

Safely calling a callback from C is done here:

https://github.com/jasonish/suricata/blob/7835e07d6f57595375c4fd8915b2f3549df6cf65/rust/src/dns/dns.rs#L366

Vendoring Cargo/Rust Dependencies

Dependencies specified in Cargo.toml are downloaded as needed. While this is likely fine for development, it is not ideal for the release tarballs, ideally those would contain all the dependent cargo source. "cargo-vendor" is a Cargo plugin that should allow us to pull down these dependencies during a make dist.

https://users.rust-lang.org/t/cargo-vendoring-now-on-nightly/6776
https://crates.io/crates/cargo-vendor

Useful links

https://locka99.gitbooks.io/a-guide-to-porting-c-to-rust/content/
https://jbendig.github.io/fix-rs/2017/01/24/how-to-optimize-rust-programs-on-linux/

Licensing

Using Crates creates risk of implicitly importing incompatible licenses. Example: nom (MIT) has an optional dependency regex (MIT/APLv2) which depends on thread-id (APLv2). Mixing our GPL with MIT is fine, but APLv2 is not compatible.

We probably need to lock specific versions of all crates we use and review the licensing of all dependences before upgrading.