diff --git a/src/common/log.c b/src/common/log.c index 608ffea950..ebe6c228ce 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -52,6 +52,13 @@ #define raw_assert(x) assert(x) // assert OK +/** Defining compile-time constants for Tor log levels (used by the Rust + * log wrapper at src/rust/tor_log) */ +const int _LOG_WARN = LOG_WARN; +const int _LOG_NOTICE = LOG_NOTICE; +const log_domain_mask_t _LD_GENERAL = LD_GENERAL; +const log_domain_mask_t _LD_NET = LD_NET; + /** Information for a single logfile; only used in log.c */ typedef struct logfile_t { struct logfile_t *next; /**< Next logfile_t in the linked list. */ diff --git a/src/common/torlog.h b/src/common/torlog.h index be8a39a1b9..0ed990b6ef 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -31,6 +31,16 @@ * "maximum severity" read "most severe" and "numerically *lowest* severity". */ +/** This defines log levels that are linked in the Rust log module, rather + * than re-defining these in both Rust and C. + * + * C_RUST_COUPLED src/rust/tor_log LogSeverity, LogDomain + */ +extern const int _LOG_WARN; +extern const int _LOG_NOTICE; +extern const log_domain_mask_t _LD_NET; +extern const log_domain_mask_t _LD_GENERAL; + /** Debug-level severity: for hyper-verbose messages of no interest to * anybody but developers. */ #define LOG_DEBUG 7 diff --git a/src/or/main.c b/src/or/main.c index e449b95b91..c19d4f6909 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -132,7 +132,7 @@ void evdns_shutdown(int); #ifdef HAVE_RUST // helper function defined in Rust to output a log message indicating if tor is // running with Rust enabled. See src/rust/tor_util -char *rust_welcome_string(void); +void rust_welcome_string(void); #endif /********* PROTOTYPES **********/ @@ -3283,11 +3283,7 @@ tor_init(int argc, char *argv[]) } #ifdef HAVE_RUST - char *rust_str = rust_welcome_string(); - if (rust_str != NULL && strlen(rust_str) > 0) { - log_notice(LD_GENERAL, "%s", rust_str); - } - tor_free(rust_str); + rust_welcome_string(); #endif /* defined(HAVE_RUST) */ if (network_init()<0) { diff --git a/src/rust/Cargo.lock b/src/rust/Cargo.lock index 224d2135bf..116ef17f1c 100644 --- a/src/rust/Cargo.lock +++ b/src/rust/Cargo.lock @@ -4,6 +4,7 @@ version = "0.0.1" dependencies = [ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", "tor_allocate 0.0.1", + "tor_log 0.1.0", ] [[package]] @@ -26,6 +27,7 @@ dependencies = [ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", "smartlist 0.0.1", "tor_allocate 0.0.1", + "tor_log 0.1.0", "tor_util 0.0.1", ] @@ -43,6 +45,14 @@ dependencies = [ "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tor_log" +version = "0.1.0" +dependencies = [ + "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tor_allocate 0.0.1", +] + [[package]] name = "tor_rust" version = "0.1.0" diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml index 953c9b96b7..4ae8033eb3 100644 --- a/src/rust/Cargo.toml +++ b/src/rust/Cargo.toml @@ -1,5 +1,6 @@ [workspace] -members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", "tor_rust"] +members = ["tor_util", "protover", "smartlist", "external", "tor_allocate", +"tor_rust", "tor_log"] [profile.release] debug = true diff --git a/src/rust/protover/Cargo.toml b/src/rust/protover/Cargo.toml index 04d2f2ed7d..279916bd25 100644 --- a/src/rust/protover/Cargo.toml +++ b/src/rust/protover/Cargo.toml @@ -18,6 +18,9 @@ path = "../tor_util" [dependencies.tor_allocate] path = "../tor_allocate" +[dependencies.tor_log] +path = "../tor_log" + [lib] name = "protover" path = "lib.rs" diff --git a/src/rust/protover/lib.rs b/src/rust/protover/lib.rs index 5a5dea4408..a9e8e1ee64 100644 --- a/src/rust/protover/lib.rs +++ b/src/rust/protover/lib.rs @@ -26,6 +26,7 @@ extern crate libc; extern crate smartlist; extern crate external; extern crate tor_allocate; +extern crate tor_log; mod protover; pub mod ffi; diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs index 7d5947ca2f..1c159afef7 100644 --- a/src/rust/protover/protover.rs +++ b/src/rust/protover/protover.rs @@ -1,13 +1,14 @@ // Copyright (c) 2016-2017, The Tor Project, Inc. */ // See LICENSE for licensing information */ -use external::c_tor_version_as_new_as; - use std::str::FromStr; use std::fmt; use std::collections::{HashMap, HashSet}; use std::string::String; +use tor_log::*; +use external::c_tor_version_as_new_as; + /// The first version of Tor that included "proto" entries in its descriptors. /// Authorities should use this to decide whether to guess proto lines. /// @@ -186,7 +187,6 @@ fn get_versions(version_string: &str) -> Result, &'static str> { Ok(versions) } - /// Parse the subprotocol type and its version numbers. /// /// # Inputs @@ -240,6 +240,20 @@ fn get_proto_and_vers<'a>( fn contains_only_supported_protocols(proto_entry: &str) -> bool { let (name, mut vers) = match get_proto_and_vers(proto_entry) { Ok(n) => n, + Err("Too many versions to expand") => { + tor_log_msg!( + LogSeverity::Warn, + LogDomain::LdNet, + "get_versions", + "When expanding a protocol list from an authority, I + got too many protocols. This is possibly an attack or a bug, + unless the Tor network truly has expanded to support over {} + different subprotocol versions. The offending string was: {}", + MAX_PROTOCOLS_TO_EXPAND, + proto_entry + ); + return false; + } Err(_) => return false, }; diff --git a/src/rust/tor_allocate/tor_allocate.rs b/src/rust/tor_allocate/tor_allocate.rs index 359df1cd7a..3c0037f139 100644 --- a/src/rust/tor_allocate/tor_allocate.rs +++ b/src/rust/tor_allocate/tor_allocate.rs @@ -1,12 +1,17 @@ // Copyright (c) 2016-2017, The Tor Project, Inc. */ // See LICENSE for licensing information */ +// No-op defined purely for testing at the module level +use libc::c_char; -use libc::{c_char, c_void}; +#[cfg(not(feature = "testing"))] use std::{ptr, slice, mem}; +use libc::c_void; -#[cfg(not(test))] -extern "C" { - fn tor_malloc_(size: usize) -> *mut c_void; +// Define a no-op implementation for testing Rust modules without linking to C +#[cfg(feature = "testing")] +pub fn allocate_and_copy_string(s: &String) -> *mut c_char { + use std::ffi::CString; + CString::new(s.as_str()).unwrap().into_raw() } // Defined only for tests, used for testing purposes, so that we don't need @@ -17,6 +22,11 @@ unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void { malloc(size) } +#[cfg(all(not(test), not(feature = "testing")))] +extern "C" { + fn tor_malloc_(size: usize) -> *mut c_void; +} + /// Allocate memory using tor_malloc_ and copy an existing string into the /// allocated buffer, returning a pointer that can later be called in C. /// @@ -28,6 +38,7 @@ unsafe extern "C" fn tor_malloc_(size: usize) -> *mut c_void { /// /// A `*mut c_char` that should be freed by tor_free in C /// +#[cfg(not(feature = "testing"))] pub fn allocate_and_copy_string(src: &String) -> *mut c_char { let bytes: &[u8] = src.as_bytes(); diff --git a/src/rust/tor_log/Cargo.toml b/src/rust/tor_log/Cargo.toml new file mode 100644 index 0000000000..f31d27b045 --- /dev/null +++ b/src/rust/tor_log/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "tor_log" +version = "0.1.0" +authors = ["The Tor Project"] + +[lib] +name = "tor_log" +path = "lib.rs" +crate_type = ["rlib", "staticlib"] + +[features] +testing = [] + +[dependencies] +libc = "0.2.22" + +[dependencies.tor_allocate] +path = "../tor_allocate" diff --git a/src/rust/tor_log/lib.rs b/src/rust/tor_log/lib.rs new file mode 100644 index 0000000000..915910d003 --- /dev/null +++ b/src/rust/tor_log/lib.rs @@ -0,0 +1,17 @@ +//! Copyright (c) 2016-2017, The Tor Project, Inc. */ +//! See LICENSE for licensing information */ + +//! Logging wrapper for Rust to utilize Tor's logger, found at +//! src/common/log.c and src/common/torlog.h +//! +//! Exposes different interfaces depending on whether we are running in test +//! or non-test mode. When testing, we use a no-op implementation, +//! otherwise we link directly to C. + +extern crate libc; +extern crate tor_allocate; + +mod tor_log; + +pub use tor_log::*; +pub use tor_log::log::*; diff --git a/src/rust/tor_log/tor_log.rs b/src/rust/tor_log/tor_log.rs new file mode 100644 index 0000000000..394e232442 --- /dev/null +++ b/src/rust/tor_log/tor_log.rs @@ -0,0 +1,236 @@ +// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// See LICENSE for licensing information */ + + +/// The related domain which the logging message is relevant. For example, +/// log messages relevant to networking would use LogDomain::LdNet, whereas +/// general messages can use LdGeneral. +#[derive(Eq, PartialEq)] +pub enum LogDomain { + LdNet, + LdGeneral, +} + +/// The severity level at which to log messages. +#[derive(Eq, PartialEq)] +pub enum LogSeverity { + Notice, + Warn, +} + +/// Main entry point for Rust modules to log messages. +/// +/// # Inputs +/// +/// * A `severity` of type LogSeverity, which defines the level of severity the +/// message will be logged. +/// * A `domain` of type LogDomain, which defines the domain the log message +/// will be associated with. +/// * A `function` of type &str, which defines the name of the function where +/// the message is being logged. There is a current RFC for a macro that +/// defines function names. When it is, we should use it. See +/// https://github.com/rust-lang/rfcs/pull/1719 +/// * A `message` of type &str, which is the log message itself. +#[macro_export] +macro_rules! tor_log_msg { + ($severity: path, + $domain: path, + $function: expr, + $($message:tt)*) => + { + { + use std::ffi::CString; + + /// Default function name to log in case of errors when converting + /// a function name to a CString + const ERR_LOG_FUNCTION: &'static str = "tor_log_msg"; + + /// Default message to log in case of errors when converting a log + /// message to a CString + const ERR_LOG_MSG: &'static str = "Unable to log message from Rust + module due to error when converting to CString"; + + let func = match CString::new($function) { + Ok(n) => n, + Err(_) => CString::new(ERR_LOG_FUNCTION).unwrap(), + }; + + let msg = match CString::new(format!($($message)*)) { + Ok(n) => n, + Err(_) => CString::new(ERR_LOG_MSG).unwrap(), + }; + + let func_ptr = func.as_ptr(); + let msg_ptr = msg.as_ptr(); + + unsafe { + tor_log_string(translate_severity($severity), + translate_domain($domain), + func_ptr, msg_ptr + ) + } + } + }; +} + +/// This module exposes no-op functionality purely for the purpose of testing +/// Rust at the module level. +#[cfg(any(test, feature = "testing"))] +pub mod log { + use libc::{c_char, c_int}; + use super::LogDomain; + use super::LogSeverity; + + /// Expose a no-op logging interface purely for testing Rust modules at the + /// module level. + pub fn tor_log_string<'a>( + severity: c_int, + domain: u32, + function: *const c_char, + message: *const c_char, + ) -> (c_int, u32, String, String) { + use std::ffi::CStr; + + let func = unsafe { CStr::from_ptr(function) }.to_str().unwrap(); + let func_allocated = String::from(func); + + let msg = unsafe { CStr::from_ptr(message) }.to_str().unwrap(); + let msg_allocated = String::from(msg); + (severity, domain, func_allocated, msg_allocated) + } + + pub unsafe fn translate_domain(_domain: LogDomain) -> u32 { + 1 + } + + pub unsafe fn translate_severity(_severity: LogSeverity) -> c_int { + 1 + } +} + +/// This implementation is used when compiling for actual use, as opposed to +/// testing. +#[cfg(all(not(test), not(feature = "testing")))] +pub mod log { + use libc::{c_char, c_int}; + use super::LogDomain; + use super::LogSeverity; + + /// Severity log types. These mirror definitions in /src/common/torlog.h + /// C_RUST_COUPLED: src/common/log.c, log domain types + extern "C" { + #[no_mangle] + static _LOG_WARN: c_int; + static _LOG_NOTICE: c_int; + } + + /// Domain log types. These mirror definitions in /src/common/torlog.h + /// C_RUST_COUPLED: src/common/log.c, log severity types + extern "C" { + #[no_mangle] + static _LD_NET: u32; + static _LD_GENERAL: u32; + } + + /// Translate Rust defintions of log domain levels to C. This exposes a 1:1 + /// mapping between types. + /// + /// Allow for default cases in case Rust and C log types get out of sync + #[allow(unreachable_patterns)] + pub unsafe fn translate_domain(domain: LogDomain) -> u32 { + match domain { + LogDomain::LdNet => _LD_NET, + LogDomain::LdGeneral => _LD_GENERAL, + _ => _LD_GENERAL, + } + } + + /// Translate Rust defintions of log severity levels to C. This exposes a + /// 1:1 mapping between types. + /// + /// Allow for default cases in case Rust and C log types get out of sync + #[allow(unreachable_patterns)] + pub unsafe fn translate_severity(severity: LogSeverity) -> c_int { + match severity { + LogSeverity::Warn => _LOG_WARN, + LogSeverity::Notice => _LOG_NOTICE, + _ => _LOG_NOTICE, + } + } + + /// The main entry point into Tor's logger. When in non-test mode, this + /// will link directly with `tor_log_string` in /src/or/log.c + extern "C" { + pub fn tor_log_string( + severity: c_int, + domain: u32, + function: *const c_char, + string: *const c_char, + ); + } +} + +#[cfg(test)] +mod test { + use tor_log::*; + use tor_log::log::*; + + use libc::c_int; + + #[test] + fn test_get_log_message() { + + fn test_macro<'a>() -> (c_int, u32, String, String) { + let (x, y, z, a) = + tor_log_msg!( + LogSeverity::Warn, + LogDomain::LdNet, + "test_macro", + "test log message {}", + "a", + ); + (x, y, z, a) + } + + let (severity, domain, function_name, log_msg) = test_macro(); + + let expected_severity = + unsafe { translate_severity(LogSeverity::Warn) }; + assert_eq!(severity, expected_severity); + + let expected_domain = unsafe { translate_domain(LogDomain::LdNet) }; + assert_eq!(domain, expected_domain); + + assert_eq!("test_macro", function_name); + assert_eq!("test log message a", log_msg); + } + + #[test] + fn test_get_log_message_multiple_values() { + fn test_macro<'a>() -> (c_int, u32, String, String) { + let (x, y, z, a) = tor_log_msg!( + LogSeverity::Warn, + LogDomain::LdNet, + "test_macro 2", + "test log message {} {} {} {}", + 10, + 9, + 8, + 7 + ); + (x, y, z, a) + } + + let (severity, domain, function_name, log_msg) = test_macro(); + + let expected_severity = + unsafe { translate_severity(LogSeverity::Warn) }; + assert_eq!(severity, expected_severity); + + let expected_domain = unsafe { translate_domain(LogDomain::LdNet) }; + assert_eq!(domain, expected_domain); + + assert_eq!("test_macro 2", function_name); + assert_eq!("test log message 10 9 8 7", log_msg); + } +} diff --git a/src/rust/tor_util/Cargo.toml b/src/rust/tor_util/Cargo.toml index d7379a5988..adc3390b53 100644 --- a/src/rust/tor_util/Cargo.toml +++ b/src/rust/tor_util/Cargo.toml @@ -11,6 +11,9 @@ crate_type = ["rlib", "staticlib"] [dependencies.tor_allocate] path = "../tor_allocate" +[dependencies.tor_log] +path = "../tor_log" + [dependencies] libc = "0.2.22" diff --git a/src/rust/tor_util/ffi.rs b/src/rust/tor_util/ffi.rs index 5c3cdba4be..866d119740 100644 --- a/src/rust/tor_util/ffi.rs +++ b/src/rust/tor_util/ffi.rs @@ -5,8 +5,7 @@ //! called from C. //! -use libc::c_char; -use tor_allocate::allocate_and_copy_string; +use tor_log::*; /// Returns a short string to announce Rust support during startup. /// @@ -17,10 +16,12 @@ use tor_allocate::allocate_and_copy_string; /// tor_free(rust_str); /// ``` #[no_mangle] -pub extern "C" fn rust_welcome_string() -> *mut c_char { - let rust_welcome = String::from( +pub extern "C" fn rust_welcome_string() { + tor_log_msg!( + LogSeverity::Notice, + LogDomain::LdGeneral, + "rust_welcome_string", "Tor is running with Rust integration. Please report \ - any bugs you encounter.", + any bugs you encounter." ); - allocate_and_copy_string(&rust_welcome) } diff --git a/src/rust/tor_util/lib.rs b/src/rust/tor_util/lib.rs index 42fa9d5ad0..86785c344a 100644 --- a/src/rust/tor_util/lib.rs +++ b/src/rust/tor_util/lib.rs @@ -7,5 +7,6 @@ extern crate libc; extern crate tor_allocate; +extern crate tor_log; pub mod ffi; diff --git a/src/test/test_rust.sh b/src/test/test_rust.sh index 133f2bb940..0268668b3d 100755 --- a/src/test/test_rust.sh +++ b/src/test/test_rust.sh @@ -11,7 +11,7 @@ for crate in $crates; do cd "${abs_top_builddir:-../../..}/src/rust" CARGO_TARGET_DIR="${abs_top_builddir:-../../..}/src/rust/target" \ CARGO_HOME="${abs_top_builddir:-../../..}/src/rust" \ - "${CARGO:-cargo}" test ${CARGO_ONLINE-"--frozen"} \ + "${CARGO:-cargo}" test --all-features ${CARGO_ONLINE-"--frozen"} \ --manifest-path "${abs_top_srcdir:-.}/src/rust/${crate}/Cargo.toml" \ || exitcode=1 cd -