diff options
author | Stuart Stock <stuart@int08h.com> | 2018-10-07 15:27:28 -0500 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2018-10-07 15:27:28 -0500 |
commit | b43bcb27ad303afd56cfe1d767e95c10cf3d1cb2 (patch) | |
tree | d27910dd465085a42a01b212aaeea64d1197b014 /src/bin/roughenough-server.rs | |
parent | 7875dda06327f771cd67eb135b381e6d87c228de (diff) | |
download | roughenough-b43bcb27ad303afd56cfe1d767e95c10cf3d1cb2.zip |
Give client and server binaries more distinctive names
As pointed out in #10, the binary names are quite generic. Rename
`client` to `roughenough-client` and `server` to `roughenough-server`.
Fixes #10
Diffstat (limited to 'src/bin/roughenough-server.rs')
-rw-r--r-- | src/bin/roughenough-server.rs | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/bin/roughenough-server.rs b/src/bin/roughenough-server.rs new file mode 100644 index 0000000..13a7026 --- /dev/null +++ b/src/bin/roughenough-server.rs @@ -0,0 +1,278 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! Roughtime server +//! +//! # Configuration +//! The `roughenough` server is configured via a YAML config file. See the documentation +//! for [FileConfig](struct.FileConfig.html) for details. +//! +//! To run the server: +//! +//! ```bash +//! $ cargo run --release --bin server /path/to/config.file +//! ``` +//! + +extern crate byteorder; +extern crate ctrlc; +extern crate hex; +#[macro_use] +extern crate log; +extern crate mio; +extern crate mio_extras; +extern crate ring; +extern crate roughenough; +extern crate simple_logger; +extern crate time; +extern crate untrusted; +extern crate yaml_rust; + +use std::env; +use std::io::ErrorKind; +use std::process; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use mio::net::UdpSocket; +use mio::{Events, Poll, PollOpt, Ready, Token}; +use mio_extras::timer::Timer; + +use byteorder::{LittleEndian, WriteBytesExt}; + +use roughenough::config; +use roughenough::config::ServerConfig; +use roughenough::keys::{LongTermKey, OnlineKey}; +use roughenough::merkle::MerkleTree; +use roughenough::{Error, RtMessage, Tag}; +use roughenough::{MIN_REQUEST_LENGTH, VERSION}; + +const MESSAGE: Token = Token(0); +const STATUS: Token = Token(1); + +fn make_response(srep: &RtMessage, cert_bytes: &[u8], path: &[u8], idx: u32) -> RtMessage { + let mut index = [0; 4]; + (&mut index as &mut [u8]) + .write_u32::<LittleEndian>(idx) + .unwrap(); + + let sig_bytes = srep.get_field(Tag::SIG).unwrap(); + let srep_bytes = srep.get_field(Tag::SREP).unwrap(); + + let mut response = RtMessage::new(5); + response.add_field(Tag::SIG, sig_bytes).unwrap(); + response.add_field(Tag::PATH, path).unwrap(); + response.add_field(Tag::SREP, srep_bytes).unwrap(); + response.add_field(Tag::CERT, cert_bytes).unwrap(); + response.add_field(Tag::INDX, &index).unwrap(); + + response +} + +// extract the client's nonce from its request +fn nonce_from_request(buf: &[u8], num_bytes: usize) -> Result<&[u8], Error> { + if num_bytes < MIN_REQUEST_LENGTH as usize { + return Err(Error::RequestTooShort); + } + + let tag_count = &buf[..4]; + let expected_nonc = &buf[8..12]; + let expected_pad = &buf[12..16]; + + let tag_count_is_2 = tag_count == [0x02, 0x00, 0x00, 0x00]; + let tag1_is_nonc = expected_nonc == Tag::NONC.wire_value(); + let tag2_is_pad = expected_pad == Tag::PAD.wire_value(); + + if tag_count_is_2 && tag1_is_nonc && tag2_is_pad { + Ok(&buf[0x10..0x50]) + } else { + Err(Error::InvalidRequest) + } +} + +fn polling_loop(config: &Box<ServerConfig>, online_key: &mut OnlineKey, cert_bytes: &[u8]) { + let response_counter = AtomicUsize::new(0); + let keep_running = Arc::new(AtomicBool::new(true)); + let kr = keep_running.clone(); + + ctrlc::set_handler(move || kr.store(false, Ordering::Release)) + .expect("failed setting Ctrl-C handler"); + + let sock_addr = config.socket_addr().expect(""); + let socket = UdpSocket::bind(&sock_addr).expect("failed to bind to socket"); + let poll_duration = Some(Duration::from_millis(100)); + + let mut timer: Timer<()> = Timer::default(); + timer.set_timeout(config.status_interval(), ()); + + let mut buf = [0u8; 65_536]; + let mut events = Events::with_capacity(32); + let mut num_bad_requests = 0u64; + + let poll = Poll::new().unwrap(); + poll.register(&socket, MESSAGE, Ready::readable(), PollOpt::edge()) + .unwrap(); + poll.register(&timer, STATUS, Ready::readable(), PollOpt::edge()) + .unwrap(); + + let mut merkle = MerkleTree::new(); + let mut requests = Vec::with_capacity(config.batch_size() as usize); + + macro_rules! check_ctrlc { + () => { + if !keep_running.load(Ordering::Acquire) { + warn!("Ctrl-C caught, exiting..."); + return; + } + }; + } + + loop { + check_ctrlc!(); + + poll.poll(&mut events, poll_duration).expect("poll failed"); + + for event in events.iter() { + match event.token() { + MESSAGE => { + let mut done = false; + + 'process_batch: loop { + check_ctrlc!(); + + merkle.reset(); + requests.clear(); + + let resp_start = response_counter.load(Ordering::SeqCst); + + for i in 0..config.batch_size() { + match socket.recv_from(&mut buf) { + Ok((num_bytes, src_addr)) => { + if let Ok(nonce) = nonce_from_request(&buf, num_bytes) { + requests.push((Vec::from(nonce), src_addr)); + merkle.push_leaf(nonce); + } else { + num_bad_requests += 1; + info!( + "Invalid request ({} bytes) from {} (#{} in batch, resp #{})", + num_bytes, src_addr, i, resp_start + i as usize + ); + } + } + Err(e) => match e.kind() { + ErrorKind::WouldBlock => { + done = true; + break; + } + _ => { + error!( + "Error receiving from socket: {:?}: {:?}", + e.kind(), + e + ); + break; + } + }, + }; + } + + if requests.is_empty() { + break 'process_batch; + } + + let merkle_root = merkle.compute_root(); + let srep = online_key.make_srep(time::get_time(), &merkle_root); + + for (i, &(ref nonce, ref src_addr)) in requests.iter().enumerate() { + let paths = merkle.get_paths(i); + + let resp = make_response(&srep, cert_bytes, &paths, i as u32); + let resp_bytes = resp.encode().unwrap(); + + let bytes_sent = socket + .send_to(&resp_bytes, &src_addr) + .expect("send_to failed"); + let num_responses = response_counter.fetch_add(1, Ordering::SeqCst); + + info!( + "Responded {} bytes to {} for '{}..' (#{} in batch, resp #{})", + bytes_sent, + src_addr, + hex::encode(&nonce[0..4]), + i, + num_responses + ); + } + if done { + break 'process_batch; + } + } + } + + STATUS => { + info!( + "responses {}, invalid requests {}", + response_counter.load(Ordering::SeqCst), + num_bad_requests + ); + + timer.set_timeout(config.status_interval(), ()); + } + + _ => unreachable!(), + } + } + } +} + +pub fn main() { + use log::Level; + + simple_logger::init_with_level(Level::Info).unwrap(); + + info!("Roughenough server v{} starting", VERSION); + + let mut args = env::args(); + if args.len() != 2 { + error!("Usage: server <ENV|/path/to/config.yaml>"); + process::exit(1); + } + + let arg1 = args.nth(1).unwrap(); + let config = match config::make_config(&arg1) { + Err(e) => { + error!("{:?}", e); + process::exit(1) + }, + Ok(ref cfg) if !config::is_valid_config(&cfg) => process::exit(1), + Ok(cfg) => cfg, + }; + + let mut online_key = OnlineKey::new(); + let mut long_term_key = LongTermKey::new(config.seed()); + let cert_bytes = long_term_key.make_cert(&online_key).encode().unwrap(); + + info!("Long-term public key : {}", long_term_key); + info!("Online public key : {}", online_key); + info!("Max response batch size : {}", config.batch_size()); + info!("Status updates every : {} seconds", config.status_interval().as_secs()); + info!("Server listening on : {}:{}", config.interface(), config.port()); + + polling_loop(&config, &mut online_key, &cert_bytes); + + info!("Done."); + process::exit(0); +} |