diff options
Diffstat (limited to 'rust/src/filetracker.rs')
-rw-r--r-- | rust/src/filetracker.rs | 350 |
1 files changed, 350 insertions, 0 deletions
diff --git a/rust/src/filetracker.rs b/rust/src/filetracker.rs new file mode 100644 index 0000000..3ae65ee --- /dev/null +++ b/rust/src/filetracker.rs @@ -0,0 +1,350 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Gap handling and Chunk-based file transfer tracker module. +//! +//! GAP handling. If a data gap is encountered, the file is truncated +//! and new data is no longer pushed down to the lower level APIs. +//! The tracker does continue to follow the file +// +//! Tracks chunk based file transfers. Chunks may be transferred out +//! of order, but cannot be transferred in parallel. So only one +//! chunk at a time. +//! +//! Author: Victor Julien <victor@inliniac.net> + +use crate::core::*; +use std::collections::HashMap; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use crate::filecontainer::*; + +#[derive(Debug)] +struct FileChunk { + contains_gap: bool, + chunk: Vec<u8>, +} + +impl FileChunk { + pub fn new(size: u32) -> FileChunk { + FileChunk { + contains_gap: false, + chunk: Vec::with_capacity(size as usize), + } + } +} + +#[derive(Debug)] +#[derive(Default)] +pub struct FileTransferTracker { + pub tracked: u64, + cur_ooo: u64, // how many bytes do we have queued from ooo chunks + track_id: u32, + chunk_left: u32, + + pub file: FileContainer, + pub file_flags: u16, + + pub tx_id: u64, + + fill_bytes: u8, + pub file_open: bool, + file_closed: bool, + chunk_is_last: bool, + chunk_is_ooo: bool, + file_is_truncated: bool, + + chunks: HashMap<u64, FileChunk>, + cur_ooo_chunk_offset: u64, + + in_flight: u64, +} + +impl FileTransferTracker { + pub fn new() -> FileTransferTracker { + FileTransferTracker { + chunks:HashMap::new(), + ..Default::default() + } + } + + pub fn is_done(&self) -> bool { + !self.file_open + } + + pub fn is_initialized(&self) -> bool { + return self.file_open || self.file_is_truncated || self.file_closed; + } + + fn open(&mut self, config: &'static SuricataFileContext, name: &[u8]) -> i32 + { + let r = self.file.file_open(config, self.track_id, name, self.file_flags); + if r == 0 { + self.file_open = true; + } + r + } + + pub fn close(&mut self, config: &'static SuricataFileContext) + { + if !self.file_is_truncated { + SCLogDebug!("closing file with id {}", self.track_id); + self.file.file_close(config, &self.track_id, self.file_flags); + } + self.file_open = false; + self.file_closed = true; + self.tracked = 0; + } + + pub fn trunc (&mut self, config: &'static SuricataFileContext) + { + if self.file_is_truncated || !self.file_open { + return; + } + let myflags = self.file_flags | 1; // TODO util-file.c::FILE_TRUNCATED + self.file.file_close(config, &self.track_id, myflags); + SCLogDebug!("truncated file"); + self.file_is_truncated = true; + self.chunks.clear(); + self.in_flight = 0; + self.cur_ooo = 0; + } + + pub fn new_chunk(&mut self, config: &'static SuricataFileContext, + name: &[u8], data: &[u8], chunk_offset: u64, chunk_size: u32, + fill_bytes: u8, is_last: bool, xid: &u32) -> u32 + { + if self.chunk_left != 0 || self.fill_bytes != 0 { + SCLogDebug!("current chunk incomplete: truncating"); + self.trunc(config); + } + + SCLogDebug!("NEW CHUNK: chunk_size {} fill_bytes {}", chunk_size, fill_bytes); + + // for now assume that is_last means its really the last chunk + // so no out of order chunks coming after. This means that if + // the last chunk is out or order, we've missed chunks before. + if chunk_offset != self.tracked { + SCLogDebug!("NEW CHUNK IS OOO: expected {}, got {}", self.tracked, chunk_offset); + if is_last { + SCLogDebug!("last chunk is out of order, this means we missed data before"); + self.trunc(config); + } + self.chunk_is_ooo = true; + self.cur_ooo_chunk_offset = chunk_offset; + } + + self.chunk_left = chunk_size; + self.fill_bytes = fill_bytes; + self.chunk_is_last = is_last; + + if self.file_is_truncated || self.file_closed { + return 0; + } + if !self.file_open { + SCLogDebug!("NEW CHUNK: FILE OPEN"); + self.track_id = *xid; + self.open(config, name); + } + + if self.file_open { + let res = self.update(config, data, 0); + SCLogDebug!("NEW CHUNK: update res {:?}", res); + return res; + } + + 0 + } + + /// update the file tracker + /// If gap_size > 0 'data' should not be used. + /// return how much we consumed of data + pub fn update(&mut self, config: &'static SuricataFileContext, data: &[u8], gap_size: u32) -> u32 + { + if self.file_is_truncated { + let consumed = std::cmp::min(data.len() as u32, self.chunk_left); + self.chunk_left = self.chunk_left.saturating_sub(data.len() as u32); + return consumed; + } + let mut consumed = 0_usize; + let is_gap = gap_size > 0; + if is_gap || gap_size > 0 { + SCLogDebug!("is_gap {} size {} ooo? {}", is_gap, gap_size, self.chunk_is_ooo); + } + + if self.chunk_left == 0 && self.fill_bytes == 0 { + //SCLogDebug!("UPDATE: nothing to do"); + if self.chunk_is_last { + SCLogDebug!("last empty chunk, closing"); + self.close(config); + self.chunk_is_last = false; + } + return 0 + } else if self.chunk_left == 0 { + SCLogDebug!("FILL BYTES {} from prev run", self.fill_bytes); + if data.len() >= self.fill_bytes as usize { + consumed += self.fill_bytes as usize; + self.fill_bytes = 0; + SCLogDebug!("CHUNK(pre) fill bytes now 0"); + } else { + consumed += data.len(); + self.fill_bytes -= data.len() as u8; + SCLogDebug!("CHUNK(pre) fill bytes now still {}", self.fill_bytes); + } + SCLogDebug!("FILL BYTES: returning {}", consumed); + return consumed as u32 + } + SCLogDebug!("UPDATE: data {} chunk_left {}", data.len(), self.chunk_left); + + if self.chunk_left > 0 { + if self.chunk_left <= data.len() as u32 { + let d = &data[0..self.chunk_left as usize]; + + if !self.chunk_is_ooo { + let res = self.file.file_append(config, &self.track_id, d, is_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + + self.tracked += self.chunk_left as u64; + } else { + SCLogDebug!("UPDATE: appending data {} to ooo chunk at offset {}/{}", + d.len(), self.cur_ooo_chunk_offset, self.tracked); + let c = match self.chunks.entry(self.cur_ooo_chunk_offset) { + Vacant(entry) => { + entry.insert(FileChunk::new(self.chunk_left)) + }, + Occupied(entry) => entry.into_mut(), + }; + self.cur_ooo += d.len() as u64; + c.contains_gap |= is_gap; + c.chunk.extend(d); + + self.in_flight += d.len() as u64; + SCLogDebug!("{:p} in_flight {}", self, self.in_flight); + } + + consumed += self.chunk_left as usize; + if self.fill_bytes > 0 { + let extra = data.len() - self.chunk_left as usize; + if extra >= self.fill_bytes as usize { + consumed += self.fill_bytes as usize; + self.fill_bytes = 0; + SCLogDebug!("CHUNK(post) fill bytes now 0"); + } else { + consumed += extra; + self.fill_bytes -= extra as u8; + SCLogDebug!("CHUNK(post) fill bytes now still {}", self.fill_bytes); + } + self.chunk_left = 0; + } else { + self.chunk_left = 0; + + if !self.chunk_is_ooo { + loop { + let _offset = self.tracked; + match self.chunks.remove(&self.tracked) { + Some(c) => { + self.in_flight -= c.chunk.len() as u64; + + let res = self.file.file_append(config, &self.track_id, &c.chunk, c.contains_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + + self.tracked += c.chunk.len() as u64; + self.cur_ooo -= c.chunk.len() as u64; + + SCLogDebug!("STORED OOO CHUNK at offset {}, tracked now {}, stored len {}", _offset, self.tracked, c.chunk.len()); + }, + _ => { + SCLogDebug!("NO STORED CHUNK found at _offset {}", self.tracked); + break; + }, + }; + } + } else { + SCLogDebug!("UPDATE: complete ooo chunk. Offset {}", self.cur_ooo_chunk_offset); + + self.chunk_is_ooo = false; + self.cur_ooo_chunk_offset = 0; + } + } + if self.chunk_is_last { + SCLogDebug!("last chunk, closing"); + self.close(config); + self.chunk_is_last = false; + } else { + SCLogDebug!("NOT last chunk, keep going"); + } + + } else { + if !self.chunk_is_ooo { + let res = self.file.file_append(config, &self.track_id, data, is_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + self.tracked += data.len() as u64; + } else { + let c = match self.chunks.entry(self.cur_ooo_chunk_offset) { + Vacant(entry) => entry.insert(FileChunk::new(32768)), + Occupied(entry) => entry.into_mut(), + }; + c.chunk.extend(data); + c.contains_gap |= is_gap; + self.cur_ooo += data.len() as u64; + self.in_flight += data.len() as u64; + } + + self.chunk_left -= data.len() as u32; + consumed += data.len(); + } + } + consumed as u32 + } + + pub fn get_queued_size(&self) -> u64 { + self.cur_ooo + } + + pub fn get_inflight_size(&self) -> u64 { + self.in_flight + } + pub fn get_inflight_cnt(&self) -> usize { + self.chunks.len() + } +} |