├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── examples └── read_pcapng.rs ├── src ├── block.rs ├── blocks │ ├── constants.rs │ ├── enhanced_packet.rs │ ├── interface_description.rs │ ├── interface_stats.rs │ ├── mod.rs │ └── section_header.rs ├── lib.rs ├── options.rs └── util.rs └── testcases └── test001.ntar /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.rs.bk 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcapng" 3 | version = "0.0.5" 4 | authors = ["Richo Healey "] 5 | 6 | repository = "https://github.com/richo/pcapng-rs" 7 | description = "a pcapng parser, implemented in rust" 8 | license = "MIT" 9 | documentation = "http://richo.psych0tik.net/pcapng-rs/pcapng/" 10 | 11 | [dependencies] 12 | nom = "^4.0" 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Richo Healey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | lib: 2 | cargo build 3 | 4 | clean: 5 | cargo clean 6 | 7 | doc: clean 8 | cargo doc 9 | 10 | release: upload_docs 11 | cargo publish 12 | 13 | upload_docs: doc 14 | git init target/doc 15 | cd target/doc && git add -A . 16 | cd target/doc && git remote add origin git@github.com:richo/pcapng-rs.git 17 | cd target/doc && git commit -m 'doc build on $(pwd)' 18 | cd target/doc && git push --force origin HEAD:gh-pages 19 | 20 | .PHONY: lib doc upload_docs clean 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pcapng-rs 2 | ========= 3 | 4 | pcapng offers you a a pcapng parser in native rust code. A couple of variations 5 | on how to read pcapng files in from a file are in `examples/`. 6 | 7 | Under the hood, it usees [nom][nom] to implement it's parsing, which lets this 8 | library stay small and compact. It's currently in a very unstable state, and 9 | I'll probably shuffle a lot of interfaces around (Especially surrounding where 10 | the actual Block classes live), but if you build something atop it, please let 11 | me know and I'll attempt to accomodate. 12 | 13 | At the highest level, the easiest way to get packets is to read the whole 14 | pcapng file into memory, and then run the parser over it: 15 | 16 | ```rust 17 | let mut fh = fs::File::open("filename.pcapng").unwrap(); 18 | let mut buf: Vec = Vec::new(); 19 | let read = fh.read_to_end(&mut buf); 20 | 21 | match pcapng::block::parse_blocks(&buf[..]) { 22 | IResult::Done(_, blocks) => { 23 | for i in blocks { 24 | println!("{:?}", i.parse()); 25 | } 26 | } 27 | IResult::Error(e) => panic!("Error: {:?}", e), 28 | IResult::Incomplete(i) => panic!("Incomplete: {:?}", i), 29 | 30 | } 31 | ``` 32 | 33 | Other approaches using the actual Consumer infra are preferable if you want to 34 | stream, but involve writing much more code. 35 | 36 | ## Contact 37 | 38 | If you're using this, I would love to know. I'm reachable as `richo` on 39 | freenode or mozilla's irc. 40 | 41 | ## License 42 | 43 | Released under the terms of the MIT license. 44 | 45 | [nom]: https://github.com/Geal/nom 46 | -------------------------------------------------------------------------------- /examples/read_pcapng.rs: -------------------------------------------------------------------------------- 1 | extern crate nom; 2 | extern crate pcapng; 3 | 4 | use std::env; 5 | use std::fs; 6 | use std::io::Read; 7 | use pcapng::block::parse_blocks; 8 | 9 | fn main() { 10 | let args: Vec<_> = env::args().collect(); 11 | if args.len() != 2 { 12 | println!("Usage: {} ", args[0]); 13 | return; 14 | } 15 | 16 | let mut fh = fs::File::open(&args[1]).unwrap(); 17 | let mut buf: Vec = Vec::new(); 18 | let _ = fh.read_to_end(&mut buf); 19 | 20 | match parse_blocks(buf.as_slice()) { 21 | Ok((_, blocks)) => { 22 | for i in blocks { 23 | if let Ok((_, blk)) = i.parse() { 24 | println!("{:?}", blk); 25 | } 26 | } 27 | } 28 | Err(nom::Err::Error(e)) => panic!("Error: {:?}", e), 29 | Err(nom::Err::Incomplete(i)) => panic!("Incomplete: {:?}", i), 30 | Err(nom::Err::Failure(f)) => panic!("Failure: {:?}", f), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/block.rs: -------------------------------------------------------------------------------- 1 | use nom::{le_u32, IResult}; 2 | 3 | use blocks; 4 | use util; 5 | 6 | #[derive(Debug)] 7 | pub enum Block<'a> { 8 | SectionHeader(blocks::SectionHeader<'a>), 9 | EnhancedPacket(blocks::EnhancedPacket<'a>), 10 | InterfaceDescription(blocks::InterfaceDescription<'a>), 11 | InterfaceStatistics(blocks::InterfaceStatistics<'a>), 12 | UnknownBlock(RawBlock<'a>), 13 | } 14 | 15 | /// Public representation of a parsed block 16 | #[derive(Debug)] 17 | pub struct RawBlock<'a> { 18 | // 0 1 2 3 19 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | // | Block Type | 22 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | // | Block Total Length | 24 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // / Block Body / 26 | // / /* variable length, aligned to 32 bits */ / 27 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28 | // | Block Total Length | 29 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | pub ty: u32, 31 | pub block_length: u32, 32 | pub body: &'a [u8], 33 | pub check_length: u32, 34 | } 35 | 36 | impl<'a> RawBlock<'a> { 37 | pub fn parse(self) -> IResult<&'a [u8], Block<'a> > { 38 | match self.ty { 39 | blocks::section_header::TY => { 40 | match blocks::section_header::parse(self) { 41 | Ok((left, blk)) => Ok((left, Block::SectionHeader(blk))), 42 | Err(e) => Err(e), 43 | } 44 | } 45 | 46 | blocks::enhanced_packet::TY => { 47 | match blocks::enhanced_packet::parse(self) { 48 | Ok((left, blk)) => Ok((left, Block::EnhancedPacket(blk))), 49 | Err(e) => Err(e), 50 | } 51 | } 52 | 53 | blocks::interface_stats::TY => { 54 | match blocks::interface_stats::parse(self) { 55 | Ok((left, blk)) => { 56 | Ok((left, Block::InterfaceStatistics(blk))) 57 | }, 58 | Err(e) => Err(e), 59 | } 60 | } 61 | 62 | blocks::interface_description::TY => { 63 | match blocks::interface_description::parse(self) { 64 | Ok((left, blk)) => { 65 | Ok((left, Block::InterfaceDescription(blk))) 66 | }, 67 | Err(e) => Err(e), 68 | } 69 | } 70 | _ => Ok((&[], Block::UnknownBlock(self))), 71 | } 72 | } 73 | } 74 | 75 | 76 | named!(pub parse_block, 77 | do_parse!( 78 | ty: le_u32 79 | >> block_length: le_u32 80 | >> body: take!((block_length - 12) as usize) 81 | >> take!(util::pad_to_32bits((block_length - 12) as usize)) 82 | >> check_length: le_u32 83 | 84 | >> ( RawBlock { 85 | ty: ty, 86 | block_length: block_length, 87 | body: body, 88 | check_length: check_length 89 | } ) 90 | ) 91 | ); 92 | 93 | named!(pub parse_blocks >, 94 | many1!(complete!(parse_block)) 95 | ); 96 | 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | 102 | #[test] 103 | fn test_parse_block() { 104 | let input = b"\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1c\x00\x00\x00"; 105 | match parse_block(input) { 106 | Ok((left, RawBlock { ty, block_length, body, check_length })) => { 107 | // Ignored because we do not currently parse the whole block 108 | assert_eq!(left, b""); 109 | assert_eq!(ty, 0x0A0D0D0A); 110 | assert_eq!(block_length, 28); 111 | assert_eq!(body, b"M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"); 112 | assert_eq!(check_length, 28); 113 | } 114 | _ => { 115 | assert_eq!(1, 2); 116 | } 117 | } 118 | } 119 | 120 | #[test] 121 | fn test_parse_blocks() { 122 | let input = b"\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1c\x00\x00\x00\ 123 | \n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1c\x00\x00\x00"; 124 | match parse_blocks(input) { 125 | Ok((left, blocks)) => { 126 | assert_eq!(blocks.len(), 2); 127 | for i in blocks { 128 | let RawBlock { ty, block_length, body, check_length } = i; 129 | // Ignored because we do not currently parse the whole block 130 | assert_eq!(left, b""); 131 | assert_eq!(ty, 0x0A0D0D0A); 132 | assert_eq!(block_length, 28); 133 | assert_eq!(body, b"M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"); 134 | assert_eq!(check_length, 28); 135 | } 136 | } 137 | err => { 138 | println!("error: {:?}", err); 139 | assert_eq!(1, 2); 140 | } 141 | } 142 | } 143 | 144 | #[test] 145 | fn test_parse_weird_length_block() { 146 | let input = b"\n\r\r\n\x1b\x00\x00\x00<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x00\x1b\x00\x00\x00"; 147 | match parse_block(input) { 148 | Ok((left, RawBlock { ty, block_length, body, check_length })) => { 149 | // Ignored because we do not currently parse the whole block 150 | assert_eq!(left, b""); 151 | assert_eq!(ty, 0x0A0D0D0A); 152 | assert_eq!(27, block_length); 153 | assert_eq!(body.len() + 12, block_length as usize); 154 | assert_eq!(body, b"<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff"); 155 | assert_eq!(body.len() + 12, check_length as usize); 156 | } 157 | _ => { 158 | unreachable!("Couldn't parse the block"); 159 | } 160 | } 161 | } 162 | 163 | #[test] 164 | fn test_multiple_options() { 165 | let input = b"\x0a\x0d\x0d\x0a\x40\x00\x00\x00\x4d\x3c\x2b\x1a\x01\x00\x00\x00\ 166 | \xff\xff\xff\xff\xff\xff\xff\xff\x03\x00\x0b\x00\x57\x69\x6e\x64\ 167 | \x6f\x77\x73\x20\x58\x50\x00\x00\x04\x00\x0c\x00\x54\x65\x73\x74\ 168 | \x30\x30\x34\x2e\x65\x78\x65\x00\x00\x00\x00\x00\x40\x00\x00\x00"; 169 | match parse_block(input) { 170 | Ok((left, block)) => { 171 | assert_eq!(left, b""); 172 | match block.parse() { 173 | Ok((_, Block::SectionHeader(blk))) => { 174 | if let Some(opts) = blk.options { 175 | assert_eq!(opts.options.len(), 3); 176 | 177 | let o = &opts.options[0]; 178 | assert_eq!(o.code, 0x03); 179 | assert_eq!(o.length, 0x0b); 180 | assert_eq!(&o.value[..], b"Windows XP\x00"); 181 | 182 | let o = &opts.options[1]; 183 | assert_eq!(o.code, 0x04); 184 | assert_eq!(o.length, 0x0c); 185 | assert_eq!(&o.value[..], b"Test004.exe\x00"); 186 | 187 | let o = &opts.options[2]; 188 | assert_eq!(o.code, 0x00); 189 | assert_eq!(o.length, 0x00); 190 | assert_eq!(&o.value[..], b""); 191 | } else { 192 | unreachable!(); 193 | } 194 | } , 195 | err =>{ 196 | panic!("error: {:?}", err); 197 | } 198 | } 199 | } 200 | _ => { 201 | panic!("Hit a codepath we shouldn't have"); 202 | } 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/blocks/constants.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types)] 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Eq, PartialEq)] 5 | pub enum BlockType { 6 | InterfaceDescription = 1, 7 | Packet = 2, 8 | SimplePacket = 3, 9 | NameResolution = 4, 10 | InterfaceStatistics = 5, 11 | EnhancedPacket = 6, 12 | IrigTimestamp = 7, 13 | Arinc429_AFDX = 8, 14 | SectionHeader = 0x0A0D0D0A, 15 | } 16 | 17 | #[repr(C)] 18 | /// Link Type 19 | // Taken from https://www.winpcap.org/ntar/draft/PCAP-DumpFileFormat.html#appendixLinkTypes 20 | pub enum LinkType { 21 | NULL = 0, 22 | ETHERNET = 1, 23 | EXP_ETHERNET = 2, 24 | AX25 = 3, 25 | PRONET = 4, 26 | CHAOS = 5, 27 | TOKEN_RING = 6, 28 | ARCNET = 7, 29 | SLIP = 8, 30 | PPP = 9, 31 | FDDI = 10, 32 | PPP_HDLC = 50, 33 | PPP_ETHER = 51, 34 | SYMANTEC_FIREWALL = 52, 35 | ATM_RFC1483 = 100, 36 | RAW = 101, 37 | SLIP_BSDOS = 102, 38 | PPP_BSDOS = 103, 39 | C_HDLC = 104, 40 | IEEE802_11 = 105, 41 | ATM_CLIP = 106, 42 | FRELAY = 107, 43 | LOOP = 108, 44 | ENC = 109, 45 | LANE8023 = 110, 46 | HIPPI = 111, 47 | HDLC = 112, 48 | LINUX_SLL = 113, 49 | LTALK = 114, 50 | ECONET = 115, 51 | IPFILTER = 116, 52 | PFLOG = 117, 53 | CISCO_IOS = 118, 54 | PRISM_HEADER = 119, 55 | AIRONET_HEADER = 120, 56 | HHDLC = 121, 57 | IP_OVER_FC = 122, 58 | SUNATM = 123, 59 | RIO = 124, 60 | PCI_EXP = 125, 61 | AURORA = 126, 62 | IEEE802_11_RADIO = 127, 63 | TZSP = 128, 64 | ARCNET_LINUX = 129, 65 | JUNIPER_MLPPP = 130, 66 | JUNIPER_MLFR = 131, 67 | JUNIPER_ES = 132, 68 | JUNIPER_GGSN = 133, 69 | JUNIPER_MFR = 134, 70 | JUNIPER_ATM2 = 135, 71 | JUNIPER_SERVICES = 136, 72 | JUNIPER_ATM1 = 137, 73 | APPLE_IP_OVER_IEEE1394 = 138, 74 | MTP2_WITH_PHDR = 139, 75 | MTP2 = 140, 76 | MTP3 = 141, 77 | SCCP = 142, 78 | DOCSIS = 143, 79 | LINUX_IRDA = 144, 80 | IBM_SP = 145, 81 | IBM_SN = 146, 82 | } 83 | 84 | #[repr(C)] 85 | pub enum LinkTypeOptions { 86 | EndOfOpt = 0, 87 | Comment = 1, 88 | Name = 2, 89 | Description = 3, 90 | Ipv4Addr = 4, 91 | Ipv6Addr = 5, 92 | MacAddr = 6, 93 | EuiAddr = 7, 94 | Speed = 8, 95 | TsResol = 9, 96 | Tzone = 10, 97 | Filter = 11, 98 | OS = 12, 99 | Fcslen = 13, 100 | TsOffset = 14, 101 | } 102 | 103 | #[repr(C)] 104 | pub enum EnhancedPacketOptions { 105 | EndOfOpt = 0, 106 | Comment = 1, 107 | Flags = 2, 108 | Hash = 3, 109 | DropCount = 4, 110 | } 111 | 112 | #[repr(C)] 113 | pub enum InterfaceStatisticsOptions { 114 | EndOfOpt = 0, 115 | Comment = 1, 116 | StartTime = 2, 117 | EndTime = 3, 118 | IfRecv = 4, 119 | IfDrop = 5, 120 | FilterAccept = 6, 121 | OSDrop = 7, 122 | UsrDeliv = 8, 123 | } 124 | -------------------------------------------------------------------------------- /src/blocks/enhanced_packet.rs: -------------------------------------------------------------------------------- 1 | use nom::{IResult, le_u32}; 2 | use block::RawBlock; 3 | use options::{parse_options, Options}; 4 | use util; 5 | 6 | pub const TY: u32 = 0x00000006; 7 | 8 | // 0 1 2 3 9 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | // +---------------------------------------------------------------+ 11 | // 0 | Block Type = 0x00000006 | 12 | // +---------------------------------------------------------------+ 13 | // 4 | Block Total Length | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // 8 | Interface ID | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // 12 | Timestamp (High) | 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // 16 | Timestamp (Low) | 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | // 20 | Captured Len | 22 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | // 24 | Packet Len | 24 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // 28 / / 26 | // / Packet Data / 27 | // / /* variable length, aligned to 32 bits */ / 28 | // / / 29 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30 | // / / 31 | // / Options (variable) / 32 | // / / 33 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 34 | // | Block Total Length | 35 | // +---------------------------------------------------------------+ 36 | 37 | named!(enhanced_packet_body, 38 | do_parse!( 39 | interface_id: le_u32 40 | >> timestamp_hi: le_u32 41 | >> timestamp_lo: le_u32 42 | >> captured_len: le_u32 43 | >> packet_len: le_u32 44 | 45 | // Captured Len: number of bytes captured from the packet (i.e. the length of the Packet 46 | // Data field). It will be the minimum value among the actual Packet Length and the 47 | // snapshot length (defined in Figure 9). The value of this field does not include the 48 | // padding bytes added at the end of the Packet Data field to align the Packet Data 49 | 50 | // Field to a 32-bit boundary 51 | >> data: take!(captured_len as usize) 52 | >> take!(util::pad_to_32bits(captured_len as usize)) 53 | >> options: opt!(parse_options) 54 | 55 | 56 | >> ( 57 | EnhancedPacket { 58 | ty: TY, 59 | block_length: 0, 60 | interface_id: interface_id, 61 | timestamp_hi: timestamp_hi, 62 | timestamp_lo: timestamp_lo, 63 | captured_len: captured_len, 64 | packet_len: packet_len, 65 | data: data, 66 | options: options, 67 | check_length: 0, 68 | } 69 | ) 70 | ) 71 | ); 72 | 73 | pub fn parse(blk: RawBlock) -> IResult<&[u8], EnhancedPacket> { 74 | match enhanced_packet_body(blk.body) { 75 | // FIXME(richo) actually do something with the leftover bytes 76 | Ok((left, mut block)) => { 77 | block.block_length = blk.block_length; 78 | block.check_length = blk.check_length; 79 | Ok((left, block)) 80 | }, 81 | other => other 82 | } 83 | } 84 | 85 | #[derive(Debug)] 86 | /// An Enhanced Packet Block is the standard container for storing the packets coming from the 87 | /// network. 88 | pub struct EnhancedPacket<'a> { 89 | pub ty: u32, 90 | pub block_length: u32, 91 | pub interface_id: u32, 92 | pub timestamp_hi: u32, 93 | pub timestamp_lo: u32, 94 | pub captured_len: u32, 95 | pub packet_len: u32, 96 | pub data: &'a [u8], 97 | pub options: Option>, 98 | pub check_length: u32, 99 | } 100 | -------------------------------------------------------------------------------- /src/blocks/interface_description.rs: -------------------------------------------------------------------------------- 1 | use nom::{IResult, le_u32, le_u16}; 2 | use block::RawBlock; 3 | use options::{parse_options, Options}; 4 | 5 | pub const TY: u32 = 0x00000001; 6 | 7 | // 0 1 2 3 8 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 9 | // +---------------------------------------------------------------+ 10 | // 0 | Block Type = 0x00000001 | 11 | // +---------------------------------------------------------------+ 12 | // 4 | Block Total Length | 13 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 14 | // 8 | LinkType | Reserved | 15 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 16 | // 12 | SnapLen | 17 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 18 | // 16 / / 19 | // / Options (variable) / 20 | // / / 21 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | // | Block Total Length | 23 | // +---------------------------------------------------------------+ 24 | 25 | named!(interface_description_body, 26 | do_parse!( 27 | link_type: le_u16 28 | >> reserved: le_u16 29 | >> snap_len: le_u32 30 | >> options: opt!(parse_options) 31 | >> ( 32 | InterfaceDescription { 33 | ty: TY, 34 | block_length: 0, 35 | link_type: link_type, 36 | reserved: reserved, 37 | snap_len: snap_len, 38 | options: options, 39 | check_length: 0, 40 | } 41 | 42 | ) 43 | ) 44 | ); 45 | 46 | #[derive(Debug)] 47 | pub struct InterfaceDescription<'a> { 48 | pub ty: u32, 49 | pub block_length: u32, 50 | pub link_type: u16, 51 | pub reserved: u16, 52 | pub snap_len: u32, 53 | // sduquette: Make options a Vec instead? 54 | pub options: Option>, 55 | pub check_length: u32, 56 | } 57 | 58 | pub fn parse(blk: RawBlock) -> IResult<&[u8], InterfaceDescription> { 59 | match interface_description_body(blk.body) { 60 | // FIXME(richo) Actually do something with the leftover bytes 61 | Ok((left, mut block)) => { 62 | block.block_length = blk.block_length; 63 | block.check_length = blk.check_length; 64 | Ok((left, block)) 65 | }, 66 | other => other 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod tests { 72 | 73 | use super::*; 74 | use block::parse_block; 75 | use blocks::constants::{BlockType, LinkType, LinkTypeOptions}; 76 | 77 | #[test] 78 | fn test_parse_interface_description_header() { 79 | let input = b"\x01\x00\x00\x00\x88\x00\x00\x00\x01\x00\x00\x00\x00\x00\x04\x00\x02\x00\x32\ 80 | \x00\x5C\x44\x65\x76\x69\x63\x65\x5C\x4E\x50\x46\x5F\x7B\x45\x34\x43\x31\x34\x31\x32\x38\ 81 | \x2D\x34\x31\x46\x35\x2D\x34\x32\x43\x35\x2D\x39\x41\x35\x35\x2D\x44\x36\x32\x32\x33\x42\ 82 | \x30\x32\x43\x32\x42\x31\x7D\x00\x00\x09\x00\x01\x00\x06\x00\x00\x00\x0C\x00\x2B\x00\x33\ 83 | \x32\x2D\x62\x69\x74\x20\x57\x69\x6E\x64\x6F\x77\x73\x20\x37\x20\x53\x65\x72\x76\x69\x63\ 84 | \x65\x20\x50\x61\x63\x6B\x20\x31\x2C\x20\x62\x75\x69\x6C\x64\x20\x37\x36\x30\x31\x00\x00\ 85 | \x00\x00\x00\x88\x00\x00\x00"; 86 | 87 | match parse_block(input) { 88 | Ok((_, block)) => { 89 | match parse(block) { 90 | Ok((left, interface_description_header)) => { 91 | assert_eq!(left, []); 92 | assert_eq!(interface_description_header.ty, BlockType::InterfaceDescription as u32); 93 | assert_eq!(interface_description_header.block_length, 136); 94 | assert_eq!(interface_description_header.link_type, LinkType::ETHERNET as u16); 95 | assert_eq!(interface_description_header.snap_len, 0x40000); 96 | assert_eq!(interface_description_header.check_length, 136); 97 | 98 | if let Some(opts) = interface_description_header.options { 99 | assert_eq!(opts.options.len(), 4); 100 | 101 | let o = &opts.options[0]; 102 | assert_eq!(o.code, LinkTypeOptions::Name as u16); 103 | assert_eq!(o.length, 0x32); 104 | assert_eq!(o.value[..], b"\\Device\\NPF_{E4C14128-41F5-42C5-9A55-D6223B02C2B1}"[..]); 105 | 106 | let o = &opts.options[1]; 107 | assert_eq!(o.code, LinkTypeOptions::TsResol as u16); 108 | assert_eq!(o.length, 1); 109 | assert_eq!(o.value[..], b"\x06"[..]); 110 | 111 | let o = &opts.options[2]; 112 | assert_eq!(o.code, LinkTypeOptions::OS as u16); 113 | assert_eq!(o.value[..], b"32-bit Windows 7 Service Pack 1, build 7601"[..]); 114 | 115 | } else { 116 | panic!("expected options."); 117 | } 118 | }, 119 | err => { 120 | panic!("failed to parse interface_description block: {:?}", err); 121 | } 122 | } 123 | }, 124 | Err(nom::Err::Incomplete(e)) => { 125 | println!("Incomplete: {:?}", e); 126 | assert!(false, "failed to parse interface_description header"); 127 | } 128 | Err(nom::Err::Error(e)) => { 129 | println!("Error: {:?}", e); 130 | assert!(false, "failed to parse interface_description header"); 131 | } 132 | Err(nom::Err::Failure(f)) => { 133 | println!("Failure: {:?}", f); 134 | assert!(false, "failed to parse interface_description header"); 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/blocks/interface_stats.rs: -------------------------------------------------------------------------------- 1 | use nom::{IResult, le_u32}; 2 | use block::RawBlock; 3 | use blocks::constants::*; 4 | use options::{parse_options, Options}; 5 | 6 | pub const TY: u32 = BlockType::InterfaceStatistics as u32; 7 | 8 | // 0 1 2 3 9 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | // +---------------------------------------------------------------+ 11 | // 0 | Block Type = 0x00000005 | 12 | // +---------------------------------------------------------------+ 13 | // 4 | Block Total Length | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // 8 | Interface ID | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // 12 | Timestamp (High) | 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // 16 | Timestamp (Low) | 20 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 21 | // 20 / / 22 | // / Options (variable) / 23 | // / / 24 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // | Block Total Length | 26 | // +---------------------------------------------------------------+ 27 | 28 | named!(interface_stats_body<&[u8], InterfaceStatistics>, 29 | do_parse!( 30 | interface_id: le_u32 >> 31 | timestamp_high: le_u32 >> 32 | timestamp_low: le_u32 >> 33 | options: opt!(parse_options) >> 34 | ( 35 | InterfaceStatistics { 36 | ty: TY, 37 | block_length: 0, 38 | interface_id: interface_id, 39 | timestamp_high: timestamp_high, 40 | timestamp_low: timestamp_low, 41 | options: options, 42 | check_length: 0, 43 | } 44 | 45 | ) 46 | ) 47 | ); 48 | 49 | #[derive(Debug)] 50 | pub struct InterfaceStatistics<'a> { 51 | pub ty: u32, 52 | pub block_length: u32, 53 | pub interface_id: u32, 54 | pub timestamp_high: u32, 55 | pub timestamp_low: u32, 56 | pub options: Option>, 57 | pub check_length: u32, 58 | } 59 | 60 | pub fn parse(blk: RawBlock) -> IResult<&[u8], InterfaceStatistics> { 61 | match interface_stats_body(blk.body) { 62 | // FIXME(richo) Actually do something with the leftover bytes 63 | Ok((left, mut block)) => { 64 | block.block_length = blk.block_length; 65 | block.check_length = blk.check_length; 66 | Ok((left, block)) 67 | }, 68 | other => other 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | 75 | use super::*; 76 | use block::parse_block; 77 | 78 | #[test] 79 | fn test_parse_interface_stats_header() { 80 | let input = b"\x05\x00\x00\x00\x6C\x00\x00\x00\x00\x00\x00\x00\x06\x3B\x05\x00\x20\xBD\x9C\ 81 | \x64\x01\x00\x1C\x00\x43\x6F\x75\x6E\x74\x65\x72\x73\x20\x70\x72\x6F\x76\x69\x64\x65\x64\x20\ 82 | \x62\x79\x20\x64\x75\x6D\x70\x63\x61\x70\x02\x00\x08\x00\x06\x3B\x05\x00\x6E\xD9\x8A\x63\x03\ 83 | \x00\x08\x00\x06\x3B\x05\x00\xC8\xBC\x9C\x64\x04\x00\x08\x00\x35\x00\x00\x00\x00\x00\x00\x00\ 84 | \x05\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x6C\x00\x00\x00"; 85 | 86 | match parse_block(input) { 87 | Ok((_, block)) => { 88 | if let Ok((left, interface_stats_header)) = parse(block) { 89 | 90 | assert_eq!(left, b""); 91 | assert_eq!(interface_stats_header.ty, TY); 92 | } else { 93 | assert!(false, "failed to parse interface_stats_header"); 94 | } 95 | } 96 | Err(nom::Err::Incomplete(e)) => { 97 | println!("Incomplete: {:?}", e); 98 | assert!(false, "failed to parse interface_stats header"); 99 | } 100 | Err(nom::Err::Error(e)) => { 101 | println!("Error: {:?}", e); 102 | assert!(false, "failed to parse interface_stats header"); 103 | } 104 | Err(nom::Err::Failure(f)) => { 105 | println!("Failure: {:?}", f); 106 | assert!(false, "failed to parse interface_stats header"); 107 | } 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/blocks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constants; 2 | pub mod section_header; 3 | pub mod enhanced_packet; 4 | pub mod interface_description; 5 | pub mod interface_stats; 6 | 7 | pub use self::section_header::SectionHeader; 8 | pub use self::enhanced_packet::EnhancedPacket; 9 | pub use self::interface_description::InterfaceDescription; 10 | pub use self::interface_stats::InterfaceStatistics; 11 | -------------------------------------------------------------------------------- /src/blocks/section_header.rs: -------------------------------------------------------------------------------- 1 | use nom::IResult; 2 | use nom::{le_u64, le_u32, le_u16}; 3 | use block::RawBlock; 4 | use options::{parse_options, Options}; 5 | 6 | pub const TY: u32 = 0x0A0D0D0A; 7 | 8 | // 0 1 2 3 9 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 10 | // +---------------------------------------------------------------+ 11 | // 0 | Block Type = 0x0A0D0D0A | 12 | // +---------------------------------------------------------------+ 13 | // 4 | Block Total Length | 14 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 15 | // 8 | Byte-Order Magic | 16 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 17 | // 12 | Major Version | Minor Version | 18 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 19 | // 16 | | 20 | // | Section Length | 21 | // | | 22 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 23 | // 24 / / 24 | // / Options (variable) / 25 | // / / 26 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 27 | // | Block Total Length | 28 | // +---------------------------------------------------------------+ 29 | 30 | named!(section_header_body, 31 | do_parse!( 32 | magic: le_u32 33 | >> major_version: le_u16 34 | >> minor_version: le_u16 35 | >> _section_length: le_u64 36 | >> options: opt!(parse_options) 37 | 38 | // Can we get the blocks by virtue of knowing how much data we have left here? 39 | >> ( { 40 | let section_length = if _section_length == 0xFFFFFFFFFFFFFFFF { 41 | SectionLength::Unspecified 42 | } else { 43 | SectionLength::Bytes(_section_length) 44 | }; 45 | 46 | assert_eq!(magic, 0x1A2B3C4D); 47 | SectionHeader { 48 | ty: TY, 49 | block_length: 0, 50 | magic: magic, 51 | major_version: major_version, 52 | minor_version: minor_version, 53 | section_length: section_length, 54 | options: options, 55 | check_length: 0, 56 | } } ) 57 | ) 58 | ); 59 | 60 | #[derive(PartialEq,Debug)] 61 | pub enum SectionLength { 62 | Bytes(u64), 63 | Unspecified, 64 | } 65 | 66 | #[derive(Debug)] 67 | pub struct SectionHeader<'a> { 68 | pub ty: u32, 69 | pub block_length: u32, 70 | pub magic: u32, 71 | pub major_version: u16, 72 | pub minor_version: u16, 73 | pub section_length: SectionLength, 74 | pub options: Option>, 75 | pub check_length: u32, 76 | } 77 | 78 | pub fn parse(blk: RawBlock) -> IResult<&[u8], SectionHeader> { 79 | // TODO(richo) Actually parse out the options afterward 80 | // I think that we can do this by invoking an options parser, and using the fact that we're 81 | // dealing with slices by this point to our advantage 82 | match section_header_body(blk.body) { 83 | // FIXME(richo) actually do something with the leftover bytes 84 | Ok((left, mut block)) => { 85 | block.block_length = blk.block_length; 86 | block.check_length = blk.check_length; 87 | Ok((left, block)) 88 | }, 89 | other => other 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | use block::parse_block; 97 | use blocks::constants::BlockType; 98 | 99 | #[test] 100 | fn test_parse_section_header() { 101 | let input = b"\n\r\r\n\x1c\x00\x00\x00M<+\x1a\x01\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\x1c\x00\x00\x00"; 102 | match parse_block(input) { 103 | Ok((_, block)) => { 104 | if let Ok((left, section_header)) = parse(block) { 105 | 106 | // Ignored because we do not currently parse the whole block 107 | assert_eq!(left, b""); 108 | assert_eq!(section_header.ty, BlockType::SectionHeader as u32); 109 | assert_eq!(section_header.block_length, 28); 110 | assert_eq!(section_header.magic, 0x1A2B3C4D); 111 | assert_eq!(section_header.section_length, SectionLength::Unspecified); 112 | assert!(section_header.options.is_none()); 113 | assert_eq!(section_header.check_length, 28); 114 | } 115 | } 116 | _ => { 117 | assert!(false); 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate nom; 3 | 4 | pub mod block; 5 | pub mod blocks; 6 | pub mod options; 7 | mod util; 8 | -------------------------------------------------------------------------------- /src/options.rs: -------------------------------------------------------------------------------- 1 | use nom::*; 2 | use util; 3 | 4 | // FIXME(richo) Flesh this out properly with it's own discrete parser. 5 | #[derive(Debug)] 6 | pub struct Options<'a> { 7 | pub options: Vec>, 8 | } 9 | 10 | #[derive(Debug)] 11 | pub struct Opt<'a> { 12 | pub code: u16, 13 | pub length: u16, 14 | pub value: &'a [u8], 15 | } 16 | 17 | // 0 1 2 3 18 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 19 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 20 | // | Option Code | Option Length | 21 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 22 | // / Option Value / 23 | // / /* variable length, aligned to 32 bits */ / 24 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 25 | // / / 26 | // / . . . other options . . . / 27 | // / / 28 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 29 | // | Option Code == opt_endofopt | Option Length == 0 | 30 | // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 31 | 32 | named!(option, 33 | do_parse!( 34 | code: le_u16 35 | >> length: le_u16 36 | >> value: take!(length as usize ) 37 | >> take!(util::pad_to_32bits(length as usize)) 38 | >> ( 39 | Opt { 40 | code: code, 41 | length: length, 42 | value: value, 43 | } 44 | ) 45 | ) 46 | ); 47 | 48 | // It's not abundantly clear to me that this is actually safe. 49 | // My belief is that because we're operating on a &[u8] that was carved out of the high level 50 | // buffer, and that *it* is a fat pointer with a length, the runtime will stop us from running off 51 | // the end, but it needs to be actually proven. 52 | named!(pub parse_options, 53 | do_parse!( 54 | opts: many1!(complete!(option)) 55 | >> ( { 56 | // It's also not super clear to me that we actually want to include the final option 57 | // in the vector. 58 | if let Some(last) = opts.last() { 59 | assert_eq!(last.code, 0x0); 60 | assert_eq!(last.length, 0x0); 61 | } 62 | Options { 63 | options: opts 64 | } 65 | }) 66 | ) 67 | ); 68 | 69 | #[cfg(test)] 70 | 71 | 72 | #[test] 73 | fn test_parse_options() { 74 | let input = b"\x12\x42\x08\x00asdfasdf\x00\x00\x00\x00"; 75 | match parse_options(input) { 76 | Ok((left, opts)) => { 77 | assert_eq!(left, b""); 78 | assert_eq!(opts.options.len(), 2); 79 | let o = &opts.options[0]; 80 | assert_eq!(o.code, 0x4212); 81 | assert_eq!(o.length, 0x08); 82 | assert_eq!(o.value, b"asdfasdf"); 83 | 84 | } 85 | err => { 86 | panic!("Hit a codepath we shouldn't have: {:?}", err); 87 | } 88 | } 89 | } 90 | 91 | #[test] 92 | fn test_multiple_options() { 93 | let input = b"\x03\x00\x0b\x00\x57\x69\x6e\x64\ 94 | \x6f\x77\x73\x20\x58\x50\x00\x00\x04\x00\x0c\x00\x54\x65\x73\x74\ 95 | \x30\x30\x34\x2e\x65\x78\x65\x00\x00\x00\x00\x00"; 96 | match parse_options(input) { 97 | Ok((left, opts)) => { 98 | assert_eq!(left, []); 99 | assert_eq!(opts.options.len(), 3); 100 | 101 | let o = &opts.options[0]; 102 | assert_eq!(o.code, 0x03); 103 | assert_eq!(o.length, 0x0b); 104 | assert_eq!(&o.value[..], b"Windows XP\x00"); 105 | 106 | let o = &opts.options[1]; 107 | assert_eq!(o.code, 0x04); 108 | assert_eq!(o.length, 0x0c); 109 | assert_eq!(&o.value[..], b"Test004.exe\x00"); 110 | 111 | let o = &opts.options[2]; 112 | assert_eq!(o.code, 0x00); 113 | assert_eq!(o.length, 0x00); 114 | assert_eq!(&o.value[..], b""); 115 | } 116 | err => { 117 | panic!("Hit a codepath we shouldn't have: {:?}", err); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | /// The padding you'll need to read to catch the reader up to alignment. 2 | /// Returns a usize, because you'll probably never pass it to anything other than take! 3 | pub fn pad_to_32bits(length: usize) -> usize { 4 | let padding = length % 4; 5 | if padding == 0 { 6 | 0 7 | } else { 8 | let delta = 4 - padding; 9 | delta as usize 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /testcases/test001.ntar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richo/pcapng-rs/a1b7835a84825b109035b80510e64f45bcafc68c/testcases/test001.ntar --------------------------------------------------------------------------------